#include #include #include #include "Editor.h" #include "Level.h" #include "constant.h" #include "InputText.h" #include "InputMenu.h" #include "InputMenuBar.h" using namespace std; extern PALETTE currentColorPalette; extern RGB black, blue, white, red, pink, cyan, orange; extern char oldPauseKey; extern char oldMenuKey; extern int installMouseReturnValue; extern int oldMousePos; extern int oldMouseB; extern RLE_SPRITE *enemy[4]; extern RLE_SPRITE *bonus[40]; extern FONT *joystix; extern bool blitEnemies; extern BITMAP *vramEnemy[4]; extern int layerCount; extern BITMAP *layer[maxLayers]; extern bool forgotScreen; extern void drawSpritesForBlitting(); extern void showFrame(); extern void drawPupils(BITMAP *canvas, int x, int y, direction headed); extern void drawFrame(); extern volatile bool heartBeat; extern void unload(); extern void go(int *x, int *y, int step, int xMax, int yMax, direction way); extern char* fnPrompt(BITMAP *canvas, int layerDonation, const char *title, const char *defaultQuery, bool blinking); extern void setHeartBeat(); int cursorX, cursorY; int selectedX, selectedY; bool cursorShown; bool hSymmetry, vSymmetry; char *levelFn; Level dummyLevel; tile clipboardGrid[boardW][boardH]; int clipboardW = 0; int clipboardH = 0; int undoBufferPosition, cleanUndoPosition; vector undoBuffer; bool typing, editing; bool clickedInBoard; bool clickedOnCorner; int clickX, clickY; int dragDistanceX, dragDistanceY; bool dirtyLevelGraphics; int editorBonusIndex; const int menuBarLength = 5; const char *menuBarCommand[menuBarLength] = { "File", "Edit", "Insert", "Level", "Tools" }; const int menuBarAcceleratorKey[menuBarLength] = { 0, 0, 0, 0, 0 }; const int fileMenuLength = 7; const char *fileMenuCommand[fileMenuLength] = { "New F2, Ctrl+N", "Open... Ctrl+O", "Save Ctrl+S", "Save As... ", "Revert... ", "View file name... ", "Exit Q, Esc" }; const int fileMenuAcceleratorKey[fileMenuLength] = { 0, 0, 0, 5, 0, 0, 1 }; const int fileMenuCommandCharCount = 19; void (* const fileMenuFunction[fileMenuLength])() = { &fileNew, &fileOpen, &fileSave, &fileSaveAs, &fileRevert, &fileViewFileName, &fileExit }; const int editMenuLength = 7; const char *editMenuCommand[editMenuLength] = { "Undo Alt+Bksp, Ctrl+Z", "Redo Ctrl+Bksp, F4, Ctrl+Y", "Cut Shift+Del, Ctrl+X", "Copy Ctrl+Ins, Ctrl+C", "Paste Shift+Ins, Ctrl+V", "Clear 2, D, ., Del", "Select All Ctrl+A" }; const int editMenuAcceleratorKey[editMenuLength] = { 0, 0, 2, 0, 0, 2, 7 }; const int editMenuCommandCharCount = 27; void (* const editMenuFunction[editMenuLength])() = { &editUndo, &editRedo, &editCut, &editCopy, &editPaste, &insertDot, &editSelectAll }; const int insertMenuLength = 4; const char *insertMenuCommand[insertMenuLength] = { "Wall 1, W, Spacebar", "Dot 2, D, ., Del", "Energy 3", "Start 4, S" }; const int insertMenuAcceleratorKey[insertMenuLength] = { 0, 0, 0, 0 }; const int insertMenuCommandCharCount = 21; void (* const insertMenuFunction[insertMenuLength])() = { &insertWall, &insertDot, &insertEnergy, &insertStart }; const int levelMenuLength = 5; const char *levelMenuCommand[levelMenuLength] = { "Set \"enemy route\" 5, M, C", "Edit \"enemy route\" 6, R", "Set bonus route 7, B", "Edit bonus route 8, Shift+R", "Edit energizer lifespan 9" }; const int levelMenuAcceleratorKey[levelMenuLength] = { 5, 7, 4, 6, 15 }; const int levelMenuCommandCharCount = 29; void (* const levelMenuFunction[levelMenuLength])() = { &levelSetEnemyRoute, &levelEditEnemyRoute, &levelSetBonusRoute, &levelEditBonusRoute, &levelSetEnergyLifespan }; const int toolsMenuLength = 4; const char *toolsMenuCommand[toolsMenuLength] = { "H Symmetry Shift+H, V slash", "V Symmetry Shift+V, -", "H Flip H", "V Flip V" }; const int toolsMenuAcceleratorKey[toolsMenuLength] = { 2, 3, 2, 3 }; const int toolsMenuCommandCharCount = 29; void (* const toolsMenuFunction[toolsMenuLength])() = { &toolsHSymmetry, &toolsVSymmetry, &toolsHFlip, &toolsVFlip, }; const int barMenuLength[menuBarLength] = { fileMenuLength, editMenuLength, insertMenuLength, levelMenuLength, toolsMenuLength }; const char **barMenuCommand[menuBarLength] = { fileMenuCommand, editMenuCommand, insertMenuCommand, levelMenuCommand, toolsMenuCommand }; const int *barMenuAcceleratorKey[menuBarLength] = { fileMenuAcceleratorKey, editMenuAcceleratorKey, insertMenuAcceleratorKey, levelMenuAcceleratorKey, toolsMenuAcceleratorKey }; const int barMenuCommandCharCount[menuBarLength] = { fileMenuCommandCharCount, editMenuCommandCharCount, insertMenuCommandCharCount, levelMenuCommandCharCount, toolsMenuCommandCharCount }; void (* const * const barMenuFunction[menuBarLength])() = { fileMenuFunction, editMenuFunction, insertMenuFunction, levelMenuFunction, toolsMenuFunction }; InputMenuBar menuBar(NULL, InputMenu(NULL, 0, barY, menuBarLength, menuBarCommand, menuBarAcceleratorKey, false, true), barMenuLength, barMenuCommand, barMenuAcceleratorKey, barMenuCommandCharCount, barMenuFunction, true, false); static const int yesNoMenuLength = 2; static const char *yesNoMenuCommand[yesNoMenuLength] = { "Yes", "No" }; static const int yesNoMenuAcceleratorKey[yesNoMenuLength] = { 0, 0 }; const int yesNoCancelMenuLength = 3; const char *yesNoCancelMenuCommand[yesNoCancelMenuLength] = { "Yes", "No", "Cancel" }; const int yesNoCancelMenuAcceleratorKey[yesNoCancelMenuLength] = { 0, 0, 0 }; void fileNew() { typing = false; if (askAboutSavingChanges()) { free(levelFn); levelFn = NULL; undoBuffer[0].newLevel(); undoBuffer.resize(1); undoBufferPosition = 0; cleanUndoPosition = 0; dirtyLevelGraphics = true; } } void fileOpen() { typing = false; if (askAboutSavingChanges()) discardChanges(true); } void fileSave() { typing = false; saveChanges(false); } void fileSaveAs() { typing = false; saveChanges(true); } void fileRevert() { typing = false; if (levelFn) { if (promptMenu("Discard all changes?", yesNoMenuLength, yesNoMenuCommand, yesNoMenuAcceleratorKey) == 0) discardChanges(false); } else note("This is a new level--no file."); } void fileViewFileName() { typing = false; if (levelFn) note(levelFn); else note("This is a new level--no file."); } void fileExit() { typing = false; if (askAboutSavingChanges()) editing = false; } void editUndo() { typing = false; if (undoBufferPosition) { undoBufferPosition--; dirtyLevelGraphics = true; } } void editRedo() { typing = false; if (undoBufferPosition < undoBuffer.size() - 1) { undoBufferPosition++; dirtyLevelGraphics = true; } } void editCut() { typing = false; if (selectedX || selectedY) { clipboardW = (selectedX < 0 ? -selectedX : selectedX) + 1; clipboardH = (selectedY < 0 ? -selectedY : selectedY) + 1; int leftEdge, rightEdge, topEdge, bottomEdge; findSelectionEdges(&leftEdge, &rightEdge, &topEdge, &bottomEdge); for (int x = 0; x < clipboardW; x++) for (int y = 0; y < clipboardH; y++) clipboardGrid[x][y] = undoBuffer[undoBufferPosition].getTile(leftEdge + x, topEdge + y); prepareToGetDirty(); safeSymmetryGridFlood(dotTile); } } void editCopy() { typing = false; if (selectedX || selectedY) { clipboardW = (selectedX < 0 ? -selectedX : selectedX) + 1; clipboardH = (selectedY < 0 ? -selectedY : selectedY) + 1; int leftEdge, rightEdge, topEdge, bottomEdge; findSelectionEdges(&leftEdge, &rightEdge, &topEdge, &bottomEdge); for (int x = 0; x < clipboardW; x++) for (int y = 0; y < clipboardH; y++) clipboardGrid[x][y] = undoBuffer[undoBufferPosition].getTile(leftEdge + x, topEdge + y); } } void editPaste() { typing = false; if (clipboardW) { bool really = true; int leftEdge, rightEdge, topEdge, bottomEdge; if (selectedX || selectedY) findSelectionEdges(&leftEdge, &rightEdge, &topEdge, &bottomEdge); else { leftEdge = cursorX; topEdge = cursorY; rightEdge = (cursorX + clipboardW - 1) % boardW; bottomEdge = (cursorY + clipboardH - 1) % boardH; } const int runAwayRight = rightEdge < leftEdge ? rightEdge + boardW : rightEdge; const int runAwayBottom = bottomEdge < topEdge ? bottomEdge + boardH : bottomEdge; const int rightCenter = boardW / 2; const int leftCenter = evenBoardW ? rightCenter - 1 : rightCenter; const int mangledLeftEdge = leftEdge > leftCenter ? leftEdge - boardW : leftEdge; const int mangledRightEdge = rightEdge < rightCenter ? rightEdge + boardW : rightEdge; if (hSymmetry && mangledLeftEdge + runAwayRight - leftEdge > leftCenter) for (int x = rightCenter - mangledLeftEdge > mangledRightEdge - leftCenter ? boardW - 1 - rightEdge : leftEdge; x < rightCenter; x++) for (int runAwayY = topEdge; runAwayY <= runAwayBottom; runAwayY++) really = really && (!symmetrySafe(x, runAwayY % boardH) || clipboardGrid[(x - mangledLeftEdge) % clipboardW][(runAwayY - topEdge) % clipboardH] == clipboardGrid[(boardW - 1 - x - mangledLeftEdge) % clipboardW][(runAwayY - topEdge) % clipboardH]); const int bottomCenter = boardH / 2; const int topCenter = evenBoardH ? bottomCenter - 1 : bottomCenter; const int mangledTopEdge = topEdge > topCenter ? topEdge - boardH : topEdge; const int mangledBottomEdge = bottomEdge < bottomCenter ? bottomEdge + boardH : bottomEdge; if (vSymmetry && mangledTopEdge + runAwayBottom - topEdge > topCenter) for (int runAwayX = leftEdge; runAwayX <= runAwayRight; runAwayX++) for (int y = bottomCenter - mangledTopEdge > mangledBottomEdge - topCenter ? boardH - 1 - bottomEdge : topEdge; y < bottomCenter; y++) really = really && (!symmetrySafe(runAwayX % boardW, y) || clipboardGrid[(runAwayX - leftEdge) % clipboardW][(y - mangledTopEdge) % clipboardH] == clipboardGrid[(runAwayX - leftEdge) % clipboardW][(boardH - 1 - y - mangledTopEdge) % clipboardH]); if (really) { if (!(selectedX || selectedY)) { selectedX = clipboardW - 1; selectedY = clipboardH - 1; } prepareToGetDirty(); const int selectionW = (selectedX < 0 ? -selectedX : selectedX) + 1; const int selectionH = (selectedY < 0 ? -selectedY : selectedY) + 1; for (int x = 0; x < selectionW; x++) for (int y = 0; y < selectionH; y++) safeSymmetrySetTile(leftEdge + x, topEdge + y, clipboardGrid[x % clipboardW][y % clipboardH]); } } } void editSelectAll() { typing = false; cursorX = boardW - 1; cursorY = boardH - 1; selectedX = -(boardW - 1); selectedY = -(boardH - 1); } void insertWall() { if (!typing) { prepareToGetDirty(); typing = true; } safeSymmetryGridFlood(wallTile); } void insertDot() { if (!typing) { prepareToGetDirty(); typing = true; } safeSymmetryGridFlood(dotTile); } void insertEnergy() { if (!typing) { prepareToGetDirty(); typing = true; } safeSymmetryGridFlood(energyTile); } void insertStart() { if (!typing) { prepareToGetDirty(); typing = true; } setTile( undoBuffer[undoBufferPosition].getPacManStartX(), undoBuffer[undoBufferPosition].getPacManStartY(), dotTile); undoBuffer[undoBufferPosition].setPacManStartX(cursorX); undoBuffer[undoBufferPosition].setPacManStartY(cursorY); symmetrySetTile(undoBuffer[undoBufferPosition].getPacManStartX(), undoBuffer[undoBufferPosition].getPacManStartY(), dotTile); setTile( undoBuffer[undoBufferPosition].getPacManStartX(), undoBuffer[undoBufferPosition].getPacManStartY(), emptyTile); dirtyLevelGraphics = true; } void levelSetEnemyRoute() { editRoute(cursorX, cursorY, vector(), true); } void levelEditEnemyRoute() { editRoute(undoBuffer[undoBufferPosition].getEnemyStartX(), undoBuffer[undoBufferPosition].getEnemyStartY(), undoBuffer[undoBufferPosition].getEnemyRoute(), true); } void levelSetBonusRoute() { editRoute(cursorX, cursorY, vector(), false); } void levelEditBonusRoute() { editRoute(undoBuffer[undoBufferPosition].getBonusStartX(), undoBuffer[undoBufferPosition].getBonusStartY(), undoBuffer[undoBufferPosition].getBonusRoute(), false); } void levelSetEnergyLifespan() { typing = false; char buf[bufSizeForItoa]; itoa(undoBuffer[undoBufferPosition].getEnergizerLifespan(), buf, 10); char *proposal = promptText(buf); if (proposal) { const int number = atoi(proposal); if (number < 0) note("No negative energy lifespans!"); else if (number == 0) note("That input was ignored... :)"); else if (number > 0) { prepareToGetDirty(); undoBuffer[undoBufferPosition].setEnergizerLifespan(number); } free(proposal); } } void toolsHSymmetry() { typing = false; hSymmetry = !hSymmetry; } void toolsVSymmetry() { typing = false; vSymmetry = !vSymmetry; } void toolsHFlip() { typing = false; prepareToGetDirty(); for (int x = 0; x < boardW; x++) for (int y = 0; y < boardH; y++) setTile(x, y, undoBuffer[undoBufferPosition - 1].getTile(boardW - 1 - x, y)); undoBuffer[undoBufferPosition].setPacManStartX(boardW - 1 - undoBuffer[undoBufferPosition].getPacManStartX()); undoBuffer[undoBufferPosition].setEnemyStartX(boardW - 1 - undoBuffer[undoBufferPosition].getEnemyStartX()); for (vector::iterator i = undoBuffer[undoBufferPosition].enemyRouteBegin(); i != undoBuffer[undoBufferPosition].enemyRouteEnd(); i++) *i = hOtherWay[*i]; undoBuffer[undoBufferPosition].setBonusStartX(boardW - 1 - undoBuffer[undoBufferPosition].getBonusStartX()); for (vector::iterator i = undoBuffer[undoBufferPosition].bonusRouteBegin(); i != undoBuffer[undoBufferPosition].bonusRouteEnd(); i++) *i = hOtherWay[*i]; cursorX = boardW - 1 - cursorX; selectedX = -selectedX; dirtyLevelGraphics = true; } void toolsVFlip() { typing = false; prepareToGetDirty(); for (int x = 0; x < boardW; x++) for (int y = 0; y < boardH; y++) setTile(x, y, undoBuffer[undoBufferPosition - 1].getTile(x, boardH - 1 - y)); undoBuffer[undoBufferPosition].setPacManStartY(boardH - 1 - undoBuffer[undoBufferPosition].getPacManStartY()); undoBuffer[undoBufferPosition].setEnemyStartY(boardH - 1 - undoBuffer[undoBufferPosition].getEnemyStartY()); for (vector::iterator i = undoBuffer[undoBufferPosition].enemyRouteBegin(); i != undoBuffer[undoBufferPosition].enemyRouteEnd(); i++) *i = vOtherWay[*i]; undoBuffer[undoBufferPosition].setBonusStartY(boardH - 1 - undoBuffer[undoBufferPosition].getBonusStartY()); for (vector::iterator i = undoBuffer[undoBufferPosition].bonusRouteBegin(); i != undoBuffer[undoBufferPosition].bonusRouteEnd(); i++) *i = vOtherWay[*i]; cursorY = boardH - 1 - cursorY; selectedY = -selectedY; dirtyLevelGraphics = true; } void initializeEditor() { dummyLevel.newLevel(); menuBar = InputMenuBar(layer[3], InputMenu(layer[0], 0, barY, menuBarLength, menuBarCommand, menuBarAcceleratorKey, false, true), barMenuLength, barMenuCommand, barMenuAcceleratorKey, barMenuCommandCharCount, barMenuFunction, true, true); } void findSelectionEdges(int *leftPointer, int *rightPointer, int *topPointer, int *bottomPointer) { if (selectedX < 0) { *leftPointer = (cursorX + selectedX + boardW) % boardW; *rightPointer = cursorX; } else { *leftPointer = cursorX; *rightPointer = (cursorX + selectedX) % boardW; } if (selectedY < 0) { *topPointer = (cursorY + selectedY + boardH) % boardH; *bottomPointer = cursorY; } else { *topPointer = cursorY; *bottomPointer = (cursorY + selectedY) % boardH; } } void prepareToGetDirty() { undoBuffer.resize(undoBufferPosition + 1); if (cleanUndoPosition > undoBufferPosition) cleanUndoPosition = -1; undoBuffer.push_back(undoBuffer[undoBufferPosition]); undoBufferPosition++; } void note(const char *msg) { menuBar.erase(); textout_ex(layer[0], joystix, msg, 0, barY, 15, -1); drawFrame(); showFrame(); clear_keybuf(); readkey(); rectfill(layer[0], 0, barY, SCREEN_W - 1, SCREEN_H - 1, 0); menuBar.draw(); } char* promptText(const char *prompt) { const int promptLength = strlen(prompt); menuBar.erase(); char *result = InputText(layer[0], 3, 0, barY, SCREEN_W / charWidth, 4, 10, true, prompt, 0, -promptLength).block(true); rectfill(layer[0], 0, barY, SCREEN_W - 1, SCREEN_H - 1, 0); menuBar.draw(); drawFrame(); showFrame(); return result; } int promptMenu(const char *prompt, int length, const char **command, const int *acceleratorKey) { menuBar.erase(); textout_ex(layer[0], joystix, prompt, 0, barY, 15, -1); const int result = InputMenu(layer[0], (strlen(prompt) + 1) * charWidth, barY, length, command, acceleratorKey, false, true).block(true); rectfill(layer[0], 0, barY, SCREEN_W - 1, SCREEN_H - 1, 0); menuBar.draw(); drawFrame(); showFrame(); return result; } bool saveChanges(bool promptForFileName) { bool newFn = false; char *fn; if (promptForFileName || !levelFn) { fn = promptText("Please enter the file name."); if (fn) if (file_exists(fn, FA_ALL, NULL) && promptMenu("File exists. Delete?", 2, yesNoMenuCommand, yesNoMenuAcceleratorKey) != 0) { free(fn); fn = NULL; } else newFn = true; } else fn = levelFn; if (fn) if (undoBuffer[undoBufferPosition].saveAs(fn)) { if (newFn) { free(levelFn); levelFn = fn; } cleanUndoPosition = undoBufferPosition; return true; } else { if (newFn) free(fn); note(undoBuffer[undoBufferPosition].getProblem()); return false; } else return false; } bool discardChanges(bool promptForFileName) { bool newFn = false; char *fn; if (promptForFileName) { set_clip_rect(layer[2], 0, 0, SCREEN_W - 1, SCREEN_H - 1); clear_bitmap(layer[2]); fn = fnPrompt(layer[2], 3, "Open", "*.*", true); clear_bitmap(layer[2]); set_clip_rect(layer[2], 0, 0, boardW * tileSize - 1, boardH * tileSize - 1); if (fn) newFn = true; } else fn = levelFn; if (fn) if (undoBuffer[0].open(fn)) { if (newFn) { free(levelFn); levelFn = fn; } undoBuffer.resize(1); undoBufferPosition = 0; cleanUndoPosition = 0; dirtyLevelGraphics = true; return true; } else { if (newFn) free(fn); note(undoBuffer[0].getProblem()); return false; } else return false; } bool askAboutSavingChanges() { bool result; if (undoBufferPosition == cleanUndoPosition) result = true; else { bool keepAsking; do switch (promptMenu("Save changes?", yesNoCancelMenuLength, yesNoCancelMenuCommand, yesNoCancelMenuAcceleratorKey)) { case 0: result = true; keepAsking = !saveChanges(false); break; case 1: result = true; keepAsking = false; break; case 2: case -1: result = false; keepAsking = false; } while (keepAsking); } return result; } bool safe(int x, int y) { return x != undoBuffer[undoBufferPosition].getPacManStartX() || y != undoBuffer[undoBufferPosition].getPacManStartY(); } bool symmetrySafe(int x, int y) { return (x != undoBuffer[undoBufferPosition].getPacManStartX() || y != undoBuffer[undoBufferPosition].getPacManStartY()) && (!hSymmetry || boardW - 1 - x != undoBuffer[undoBufferPosition].getPacManStartX() || y != undoBuffer[undoBufferPosition].getPacManStartY()) && (!vSymmetry || ((x != undoBuffer[undoBufferPosition].getPacManStartX() || boardH - 1 - y != undoBuffer[undoBufferPosition].getPacManStartY()) && (!hSymmetry || boardW - 1 - x != undoBuffer[undoBufferPosition].getPacManStartX() || boardH - 1 - y != undoBuffer[undoBufferPosition].getPacManStartY()))); } void safeSetTile(int x, int y, tile value) { if (safe(x, y)) setTile(x, y, value); } void setTile(int x, int y, tile value) { if (undoBuffer[undoBufferPosition].getTile(x, y) != value) { undoBuffer[undoBufferPosition].setTile(x, y, value); dirtyLevelGraphics = true; } } void safeSymmetrySetTile(int x, int y, tile value) { if (symmetrySafe(x, y)) symmetrySetTile(x, y, value); } void symmetrySetTile(int x, int y, tile value) { setTile(x, y, value); if (hSymmetry) setTile(boardW - 1 - x, y, value); if (vSymmetry) { setTile(x, boardH - 1 - y, value); if (hSymmetry) setTile(boardW - 1 - x, boardH - 1 - y, value); } } void symmetryHLine(BITMAP *canvas, int x1, int y, int x2, int color) { hline(canvas, x1, y, x2, color); if (hSymmetry) hline(canvas, 639 - x2, y, 639 - x1, color); if (vSymmetry) { hline(canvas, x1, 459 - y, x2, color); if (hSymmetry) hline(canvas, 639 - x2, 459 - y, 639 - x1, color); } } void symmetryVLine(BITMAP *canvas, int x, int y1, int y2, int color) { vline(canvas, x, y1, y2, color); if (hSymmetry) vline(canvas, 639 - x, y1, y2, color); if (vSymmetry) { vline(canvas, x, 459 - y2, 459 - y1, color); if (hSymmetry) vline(canvas, 639 - x, 459 - y2, 459 - y1, color); } } void symmetryRectFill(BITMAP *canvas, int x1, int y1, int x2, int y2, int color) { rectfill(canvas, x1, y1, x2, y2, color); if (hSymmetry) rectfill(canvas, 639 - x2, y1, 639 - x1, y2, color); if (vSymmetry) { rectfill(canvas, x1, 459 - y2, x2, 459 - y1, color); if (hSymmetry) rectfill(canvas, 639 - x2, 459 - y2, 639 - x1, 459 - y1, color); } } void safeSymmetryGridFlood(tile value) { int leftEdge, rightEdge, topEdge, bottomEdge; findSelectionEdges(&leftEdge, &rightEdge, &topEdge, &bottomEdge); int x = leftEdge; while (true) { int y = topEdge; while (true) { safeSymmetrySetTile(x, y, value); if (y == bottomEdge) break; y = (y + 1) % boardH; } if (x == rightEdge) break; x = (x + 1) % boardW; } } void drawSelection(BITMAP *canvas, int leftEdge, int rightEdge, int topEdge, int bottomEdge) { if (rightEdge < leftEdge) { symmetryHLine(canvas, leftEdge * tileSize, topEdge * tileSize, boardW * tileSize - 1, interactivityColor); symmetryHLine(canvas, 0, topEdge * tileSize, (rightEdge + 1) * tileSize - 1, interactivityColor); symmetryHLine(canvas, leftEdge * tileSize, (bottomEdge + 1) * tileSize - 1, boardW * tileSize - 1, interactivityColor); symmetryHLine(canvas, 0, (bottomEdge + 1) * tileSize - 1, (rightEdge + 1) * tileSize - 1, interactivityColor); } else { symmetryHLine(canvas, leftEdge * tileSize, topEdge * tileSize, (rightEdge + 1) * tileSize - 1, interactivityColor); symmetryHLine(canvas, leftEdge * tileSize, (bottomEdge + 1) * tileSize - 1, (rightEdge + 1) * tileSize - 1, interactivityColor); } if (bottomEdge < topEdge) { symmetryVLine(canvas, leftEdge * tileSize, topEdge * tileSize, boardH * tileSize - 1, interactivityColor); symmetryVLine(canvas, leftEdge * tileSize, 0, (bottomEdge + 1) * tileSize - 1, interactivityColor); symmetryVLine(canvas, (rightEdge + 1) * tileSize - 1, topEdge * tileSize, boardH * tileSize - 1, interactivityColor); symmetryVLine(canvas, (rightEdge + 1) * tileSize - 1, 0, (bottomEdge + 1) * tileSize - 1, interactivityColor); } else { symmetryVLine(canvas, leftEdge * tileSize, topEdge * tileSize, (bottomEdge + 1) * tileSize - 1, interactivityColor); symmetryVLine(canvas, (rightEdge + 1) * tileSize - 1, topEdge * tileSize, (bottomEdge + 1) * tileSize - 1, interactivityColor); } } void drawControls() { clear_bitmap(layer[1]); if (selectedX || selectedY) { int leftEdge, rightEdge, topEdge, bottomEdge; findSelectionEdges(&leftEdge, &rightEdge, &topEdge, &bottomEdge); drawSelection(layer[1], leftEdge, rightEdge, topEdge, bottomEdge); } if (cursorShown) rectfill(layer[1], cursorX * tileSize, cursorY * tileSize, (cursorX + 1) * tileSize - 1, (cursorY + 1) * tileSize - 1, interactivityColor); } bool respondToMouse() { const int mousePosition = mouse_pos; const int mouseButtons = mouse_b; int mickeyx, mickeyy; get_mouse_mickeys(&mickeyx, &mickeyy); int mouseX = mousePosition >> 16; int mouseY = mousePosition & 0xffff; if (mickeyx || mickeyy) { if (clickedInBoard) { dragDistanceX += mickeyx / mouseSpeed; if (clickedOnCorner) { if (dragDistanceX >= boardW * tileSize) dragDistanceX = boardW * tileSize - 1; if (dragDistanceX <= -(boardW * tileSize)) dragDistanceX = -(boardW * tileSize - 1); } dragDistanceY += mickeyy / mouseSpeed; if (clickedOnCorner) { if (dragDistanceY >= boardH * tileSize) dragDistanceY = boardH * tileSize - 1; if (dragDistanceY <= -(boardH * tileSize)) dragDistanceY = -(boardH * tileSize - 1); } } if ((oldMouseB & 0x7) && clickedInBoard) { mouseX = ((clickX + dragDistanceX) % (boardW * tileSize) + boardW * tileSize) % (boardW * tileSize); mouseY = ((clickY + dragDistanceY) % (boardH * tileSize) + boardH * tileSize) % (boardH * tileSize); position_mouse(mouseX, mouseY); } } const int cornerX = ((mouseX + halfTileSize) / tileSize) % boardW; const int cornerY = ((mouseY + halfTileSize) / tileSize) % boardH; const int cornerClickX = ((clickX + halfTileSize) / tileSize) % boardW; const int cornerClickY = ((clickY + halfTileSize) / tileSize) % boardH; const int tileX = (mouseX / tileSize) % boardW; const int tileY = (mouseY / tileSize) % boardH; bool inBoard = mouseX < boardW * tileSize && mouseY < boardH * tileSize; bool onCorner = (mouseX % tileSize < clickPointSnap || mouseX % tileSize > tileSize - clickPointSnap) && (mouseY % tileSize < clickPointSnap || mouseY % tileSize > tileSize - clickPointSnap); int left, right, top, bottom; if (dragDistanceX < 0) { left = cornerX; right = (cornerClickX + boardW - 1) % boardW; } else { left = cornerClickX; right = (cornerX + boardW - 1) % boardW; } if (dragDistanceY < 0) { top = cornerY; bottom = (cornerClickY + boardH - 1) % boardH; } else { top = cornerClickY; bottom = (cornerY + boardH - 1) % boardH; } clear_bitmap(layer[2]); menuBar.acknowledgeMouse(); if (mouseButtons & 0x7) { if (!(oldMouseB & 0x7)) { if (menuBar.within(mouseX, mouseY)) { clickedInBoard = false; clickedOnCorner = false; } else { clickedInBoard = inBoard; clickedOnCorner = onCorner; clickX = mouseX; clickY = mouseY; dragDistanceX = 0; dragDistanceY = 0; } if (clickedInBoard) prepareToGetDirty(); } if (clickedOnCorner) drawSelection(layer[2], left, right, top, bottom); else if (clickedInBoard) { if ((mouseButtons & 0x1) && (mouseButtons & 0x2) || (mouseButtons & 0x4)) safeSymmetrySetTile(tileX, tileY, energyTile); else if (mouseButtons & 0x1) safeSymmetrySetTile(tileX, tileY, wallTile); else if (mouseButtons & 0x2) safeSymmetrySetTile(tileX, tileY, dotTile); } } else if ((oldMouseB & 0x7) && clickedOnCorner) { cursorX = dragDistanceX < 0 ? cornerX : (cornerX + boardW - 1) % boardW; cursorY = dragDistanceY < 0 ? cornerY : (cornerY + boardH - 1) % boardH; selectedX = dragDistanceX < 0 ? (right >= left ? right : right + boardW) - left : (left <= right ? left : left - boardW) - right; selectedY = dragDistanceY < 0 ? (bottom >= top ? bottom : bottom + boardH) - top : (top <= bottom ? top : top - boardH) - bottom; } if (onCorner && !menuBar.within(mouseX, mouseY)) { circlefill(layer[2], cornerX * tileSize, cornerY * tileSize, clickPointRadius, interactivityColor); if (cornerX == 0) circlefill(layer[2], boardW * tileSize, cornerY * tileSize, clickPointRadius, interactivityColor); if (cornerY == 0) { circlefill(layer[2], cornerX * tileSize, boardH * tileSize, clickPointRadius, interactivityColor); if (cornerX == 0) circlefill(layer[2], boardW * tileSize, boardH * tileSize, clickPointRadius, interactivityColor); } } oldMousePos = mousePosition; oldMouseB = mouseButtons; return true; } bool respondToKeyStroke() { switch (readkey() >> 8) { case KEY_A: if (key_shifts & KB_CTRL_FLAG) { editSelectAll(); return true; } else return false; case KEY_B: case KEY_7: levelSetBonusRoute(); return true; case KEY_C: if (key_shifts & KB_CTRL_FLAG) { editCopy(); return true; } else { levelSetEnemyRoute(); return true; } case KEY_D: case KEY_2: case KEY_STOP: insertDot(); return true; case KEY_E: if (key_shifts & KB_ALT_FLAG) { typing = false; menuBar.bringUpMenu(1); return true; } else return false; case KEY_F: if (key_shifts & KB_ALT_FLAG) { typing = false; menuBar.bringUpMenu(0); return true; } else return false; case KEY_H: if (key_shifts & KB_SHIFT_FLAG) { toolsHSymmetry(); return true; } else { toolsHFlip(); return true; } case KEY_I: if (key_shifts & KB_ALT_FLAG) { typing = false; menuBar.bringUpMenu(2); return true; } else return false; case KEY_L: if (key_shifts & KB_ALT_FLAG) { typing = false; menuBar.bringUpMenu(3); return true; } else return false; case KEY_M: case KEY_5: levelSetEnemyRoute(); return true; case KEY_N: if (key_shifts & KB_CTRL_FLAG) { fileNew(); return true; } else return false; case KEY_O: if (key_shifts & KB_CTRL_FLAG) { fileOpen(); return true; } else return false; case KEY_Q: case KEY_ESC: fileExit(); return true; case KEY_R: case KEY_6: if (key_shifts & KB_SHIFT_FLAG) { levelEditBonusRoute(); return true; } else { levelEditEnemyRoute(); return true; } case KEY_S: if (key_shifts & KB_CTRL_FLAG) { fileSave(); return true; } else { insertStart(); return true; } case KEY_T: if (key_shifts & KB_ALT_FLAG) { typing = false; menuBar.bringUpMenu(4); return true; } else return false; case KEY_V: if (key_shifts & KB_SHIFT_FLAG) if (key_shifts & KB_CTRL_FLAG) return false; else { toolsVSymmetry(); return true; } else if (key_shifts & KB_CTRL_FLAG) { editPaste(); return true; } else { toolsVFlip(); return true; } case KEY_W: case KEY_1: case KEY_SPACE: insertWall(); return true; case KEY_X: if (key_shifts & KB_CTRL_FLAG) { editCut(); return true; } else return false; case KEY_Y: if (key_shifts & KB_CTRL_FLAG) { editRedo(); return true; } else return false; case KEY_Z: if (key_shifts & KB_CTRL_FLAG) { editUndo(); return true; } else return false; case KEY_3: insertEnergy(); return true; case KEY_4: insertStart(); return true; case KEY_8: levelEditBonusRoute(); return true; case KEY_9: levelSetEnergyLifespan(); return true; case KEY_0_PAD: case KEY_INSERT: if (key_shifts & KB_SHIFT_FLAG) if (key_shifts & KB_CTRL_FLAG) return false; else { editPaste(); return true; } else if (key_shifts & KB_CTRL_FLAG) { editCopy(); return true; } else return false; case KEY_2_PAD: case KEY_DOWN: if ((key_shifts & KB_SHIFT_FLAG) && selectedY == -(boardH - 1)) return false; else { cursorY = (cursorY + 1) % boardH; if (key_shifts & KB_SHIFT_FLAG) selectedY--; else { selectedX = 0; selectedY = 0; } return true; } case KEY_4_PAD: case KEY_LEFT: if ((key_shifts & KB_SHIFT_FLAG) && selectedX == boardW - 1) return false; else { cursorX = (cursorX + boardW - 1) % boardW; if (key_shifts & KB_SHIFT_FLAG) selectedX++; else { selectedX = 0; selectedY = 0; } return true; } case KEY_6_PAD: case KEY_RIGHT: if ((key_shifts & KB_SHIFT_FLAG) && selectedX == -(boardW - 1)) return false; else { cursorX = (cursorX + 1) % boardW; if (key_shifts & KB_SHIFT_FLAG) selectedX--; else { selectedX = 0; selectedY = 0; } return true; } case KEY_8_PAD: case KEY_UP: if ((key_shifts & KB_SHIFT_FLAG) && selectedY == boardH - 1) return false; else { cursorY = (cursorY + boardH - 1) % boardH; if (key_shifts & KB_SHIFT_FLAG) selectedY++; else { selectedX = 0; selectedY = 0; } return true; } case KEY_F2: fileNew(); return true; case KEY_F4: editRedo(); return true; case KEY_MINUS: case KEY_MINUS_PAD: toolsVSymmetry(); return true; case KEY_BACKSPACE: if (key_shifts & KB_CTRL_FLAG) if (key_shifts & KB_ALT_FLAG) return false; else { editRedo(); return true; } else if (key_shifts & KB_ALT_FLAG) { editUndo(); return true; } else return false; case KEY_BACKSLASH: if (key_shifts & KB_SHIFT_FLAG) { toolsHSymmetry(); return true; } else return false; case KEY_DEL: case KEY_DEL_PAD: if (key_shifts & KB_SHIFT_FLAG) { editCut(); return true; } else { insertDot(); return true; } default: return false; } } bool respondToKeyArray() { bool result = false; const char pauseKey = key[KEY_PAUSE]; const char menuKey = key[KEY_MENU]; if (menuKey && !oldMenuKey) { typing = false; menuBar.bringUpMenu(0); result = true; } oldPauseKey = pauseKey; oldMenuKey = menuKey; return result; } bool editRoute(int initX, int initY, vector route, bool enemyRoute) { if (!enemyRoute && initX != 0 && initX != boardW - 1 && initY != 0 && initY != boardH - 1) return false; remove_int(setHeartBeat); int x = initX; int y = initY; int position = 0; bool editingRoute = true; while (editingRoute) { clear_bitmap(layer[2]); if (enemyRoute) { if (blitEnemies) draw_sprite(layer[2], vramEnemy[blinky], x * tileSize, y * tileSize); else draw_rle_sprite(layer[2], enemy[blinky], x * tileSize, y * tileSize); if (position) drawPupils(layer[2], x * tileSize, y * tileSize, route[position - 1]); else { circlefill(layer[2], x * tileSize + leftEyeX, y * tileSize + leftEyeY, pupilRadius, 16); circlefill(layer[2], x * tileSize + rightEyeX, y * tileSize + rightEyeY, pupilRadius, 16); } } else draw_rle_sprite(layer[2], bonus[editorBonusIndex], x * tileSize, y * tileSize); drawFrame(); showFrame(); while (!keypressed()) { if (forgotScreen) { drawSpritesForBlitting(); clear_bitmap(layer[0]); clear_bitmap(layer[1]); clear_bitmap(layer[2]); clear_bitmap(layer[3]); undoBuffer[undoBufferPosition].drawLevel(layer[0]); menuBar.draw(); drawControls(); if (enemyRoute) { if (blitEnemies) draw_sprite(layer[2], vramEnemy[blinky], x * tileSize, y * tileSize); else draw_rle_sprite(layer[2], enemy[blinky], x * tileSize, y * tileSize); if (position) switch (route[position - 1]) { case up: circlefill(layer[2], x * tileSize + leftEyeX, y * tileSize + leftEyeY - pupilOffset, pupilRadius, 16); circlefill(layer[2], x * tileSize + rightEyeX, y * tileSize + rightEyeY - pupilOffset, pupilRadius, 16); break; case left: circlefill(layer[2], x * tileSize + leftEyeX - pupilOffset, y * tileSize + leftEyeY, pupilRadius, 16); circlefill(layer[2], x * tileSize + rightEyeX - pupilOffset, y * tileSize + rightEyeY, pupilRadius, 16); break; case right: circlefill(layer[2], x * tileSize + leftEyeX + pupilOffset, y * tileSize + leftEyeY, pupilRadius, 16); circlefill(layer[2], x * tileSize + rightEyeX + pupilOffset, y * tileSize + rightEyeY, pupilRadius, 16); break; case down: circlefill(layer[2], x * tileSize + leftEyeX, y * tileSize + leftEyeY + pupilOffset, pupilRadius, 16); circlefill(layer[2], x * tileSize + rightEyeX, y * tileSize + rightEyeY + pupilOffset, pupilRadius, 16); } else { circlefill(layer[2], x * tileSize + leftEyeX, y * tileSize + leftEyeY, pupilRadius, 16); circlefill(layer[2], x * tileSize + rightEyeX, y * tileSize + rightEyeY, pupilRadius, 16); } } else draw_rle_sprite(layer[2], bonus[editorBonusIndex], x * tileSize, y * tileSize); drawFrame(); showFrame(); forgotScreen = false; } rest(0); if (keyboard_needs_poll()) if (poll_keyboard()) { unload(); allegro_message("Couldn't poll the keyboard!"); exit(0); } } int nowWhat = 0; switch (readkey() >> 8) { case KEY_Y: if (key_shifts & KB_CTRL_FLAG) nowWhat = 1; break; case KEY_Z: if (key_shifts & KB_CTRL_FLAG) nowWhat = 2; break; case KEY_1_PAD: nowWhat = 3; break; case KEY_2_PAD: case KEY_DOWN: nowWhat = 4; break; case KEY_4_PAD: case KEY_LEFT: nowWhat = 5; break; case KEY_6_PAD: case KEY_RIGHT: nowWhat = 6; break; case KEY_7_PAD: nowWhat = 7; break; case KEY_8_PAD: case KEY_UP: nowWhat = 8; break; case KEY_F4: nowWhat = 1; break; case KEY_ESC: nowWhat = 10; break; case KEY_BACKSPACE: if (key_shifts & KB_CTRL_FLAG) { if (!(key_shifts & KB_ALT_FLAG)) nowWhat = 1; } else if (key_shifts & KB_ALT_FLAG) nowWhat = 2; break; case KEY_ENTER: case KEY_ENTER_PAD: nowWhat = 9; break; case KEY_HOME: nowWhat = 7; break; case KEY_END: nowWhat = 3; } switch (nowWhat) { case 1: if (position < route.size()) go(&x, &y, 1, boardW, boardH, route[position++]); break; case 2: if (position) go(&x, &y, 1, boardW, boardH, otherWay[route[--position]]); break; case 3: while (position < route.size()) go(&x, &y, 1, boardW, boardH, route[position++]); break; case 4: if (!position || route[position - 1] != up) { route.resize(position); route.push_back(down); position++; y = (y + 1) % boardH; } break; case 5: if (!position || route[position - 1] != right) { route.resize(position); route.push_back(left); position++; x = (x + boardW - 1) % boardW; } break; case 6: if (!position || route[position - 1] != left) { route.resize(position); route.push_back(right); position++; x = (x + 1) % boardW; } break; case 7: while (position) go(&x, &y, 1, boardW, boardH, otherWay[route[--position]]); break; case 8: if (!position || route[position - 1] != down) { route.resize(position); route.push_back(up); position++; y = (y + boardH - 1) % boardH; } break; case 9: if (position) { prepareToGetDirty(); route.resize(position); if (enemyRoute) { undoBuffer[undoBufferPosition].setEnemyStartX(initX); undoBuffer[undoBufferPosition].setEnemyStartY(initY); undoBuffer[undoBufferPosition].setEnemyRoute(route); } else { undoBuffer[undoBufferPosition].setBonusStartX(initX); undoBuffer[undoBufferPosition].setBonusStartY(initY); undoBuffer[undoBufferPosition].setBonusRoute(route); } editingRoute = false; } break; case 10: editingRoute = false; } } clear_bitmap(layer[2]); install_int(setHeartBeat, cursorBlinkDelay); return true; } void edit(Level *userLevel, const char *fn) { oldMouseB = 0x0; currentColorPalette[energyColor] = white; currentColorPalette[blinkyColor] = red; layerCount = 4; clear_bitmap(layer[0]); clear_bitmap(layer[1]); clear_bitmap(layer[2]); clear_bitmap(layer[3]); set_clip_rect(layer[2], 0, 0, boardW * tileSize - 1, boardH * tileSize - 1); userLevel->drawLevel(layer[0]); menuBar.draw(); drawFrame(); showFrame(); fade_in(currentColorPalette, fadeSpeed); cursorX = 0; cursorY = 0; selectedX = 0; selectedY = 0; cursorShown = true; if (fn) { levelFn = (char*)malloc((strlen(fn) + 1) * sizeof(char)); if (!levelFn) { unload(); allegro_message("Outrageous shortage of memory."); exit(0); } strcpy(levelFn, fn); } else levelFn = NULL; undoBuffer.clear(); undoBuffer.push_back(*userLevel); undoBufferPosition = 0; cleanUndoPosition = 0; typing = false; editing = true; heartBeat = false; dirtyLevelGraphics = false; install_int(setHeartBeat, cursorBlinkDelay); while (editing) { if (dirtyLevelGraphics) { undoBuffer[undoBufferPosition].drawLevel(layer[0]); dirtyLevelGraphics = false; } menuBar.draw(); drawControls(); drawFrame(); showFrame(); const int oldRetraceCount = retrace_count; do { if (forgotScreen) { drawSpritesForBlitting(); clear_bitmap(layer[0]); clear_bitmap(layer[1]); clear_bitmap(layer[2]); clear_bitmap(layer[3]); undoBuffer[undoBufferPosition].drawLevel(layer[0]); dirtyLevelGraphics = false; menuBar.draw(); drawControls(); drawFrame(); showFrame(); forgotScreen = false; } if (keyboard_needs_poll()) if (poll_keyboard()) { unload(); allegro_message("Couldn't poll the keyboard!"); exit(0); } if (mouse_needs_poll()) if (poll_mouse()) { unload(); allegro_message("Couldn't poll the mouse!"); exit(0); } if (keypressed() && respondToKeyStroke()) { cursorShown = true; heartBeat = false; remove_int(setHeartBeat); install_int(setHeartBeat, cursorBlinkDelay); } respondToKeyArray(); if (installMouseReturnValue != -1) respondToMouse(); } while (retrace_count == oldRetraceCount); if (menuBar.getBarMenuIsUp()) { menuBar.block(true); menuBar.uncommit(); menuBar.erase(); } if (heartBeat) { cursorShown = !cursorShown; typing = false; heartBeat = false; } } remove_int(setHeartBeat); *userLevel = undoBuffer[undoBufferPosition]; free(levelFn); fade_out(fadeSpeed); set_clip_rect(layer[2], 0, 0, SCREEN_W - 1, SCREEN_H - 1); undoBuffer.clear(); // Just to save memory. }