// Copyright (c) 2014 Alex Diener. All rights reserved. #include "shell/Shell.h" #include "shell/ShellKeyCodes.h" #include "utilities/IOUtilities.h" #include "watertowerclassic/LevelEditorScreen.h" #include "watertowerclassic/ShellStateGlobals.h" #include "watertowerclassic/SharedEvents.h" #include #include #include #include enum { MENU_ITEM_LEVEL_NUMBER, MENU_ITEM_ADD_LEVEL, MENU_ITEM_DELETE_LEVEL, MENU_ITEM_TEST_LEVEL, MENU_ITEM_SAVE, MENU_ITEM_REVERT, MENU_ITEM_DONE, MENU_ITEM_COUNT }; enum { CONFIRM_CHOICE_YES, CONFIRM_CHOICE_NO, CONFIRM_CHOICE_CANCEL }; #define SUPERCLASS Screen #define LEVEL_STRING_MAX 32 #define TOOL_ROW_COUNT 4 #define TOOL_COLUMN_COUNT 7 #define TOOL_GRID_LEFT 514 #define TOOL_GRID_TOP 2 #define TOOL_GRID_PADDING 2 #define TOOL_HIGHLIGHT_THICKNESS 2 #define WATER_CONTROLS_TOP 260 static const int toolLayout[TOOL_ROW_COUNT * TOOL_COLUMN_COUNT] = { OBJECT_BLOCK_STONE, OBJECT_BLOCK_LADDER, OBJECT_BLOCK_DRY_SOLID, OBJECT_BLOCK_WET_SOLID, OBJECT_BLOCK_LOCK, OBJECT_POWERUP_KEY, LEVEL_EDITOR_TOOL_PENCIL, OBJECT_BLOCK_DEATH, OBJECT_BLOCK_TELEPORT, OBJECT_BLOCK_LEVER, OBJECT_BLOCK_SWITCHABLE, OBJECT_BLOCK_CRUMBLE, OBJECT_POWERUP_HEART, LEVEL_EDITOR_TOOL_RECT, OBJECT_BLOCK_FALLING, OBJECT_BLOCK_PUSHABLE, OBJECT_BLOCK_FLOATING, OBJECT_BLOCK_ICE, OBJECT_BLOCK_FIRE, OBJECT_POWERUP_GEM, LEVEL_EDITOR_TOOL_ERASER, OBJECT_BLOCK_MOVING, OBJECT_BLOCK_PHASING, OBJECT_BLOCK_TRAMPOLINE, OBJECT_BLOCK_SAND, OBJECT_BLOCK_STICKY, OBJECT_PLAYER_POSITION, LEVEL_EDITOR_TOOL_INSPECT }; LevelEditorScreen * LevelEditorScreen_create(GameSession * gameSession) { stemobject_create_implementation(LevelEditorScreen, init, gameSession) } static struct LevelBlock initBlockToPlace(int type) { struct LevelBlock block; block.type = type; switch (type) { case BLOCK_TYPE_SWITCHABLE: memset(block.properties.switchable.switchIDs, 0x00, sizeof(block.properties.switchable.switchIDs)); block.properties.switchable.switchIDs[0] = 1; block.properties.switchable.on = true; break; case BLOCK_TYPE_MOVING: block.properties.moving.direction = DIRECTION_RIGHT; block.properties.moving.speed = 0x00010000; break; case BLOCK_TYPE_PHASING: block.properties.phasing.initialDelay = 0; block.properties.phasing.onPeriod = 1; block.properties.phasing.offPeriod = 1; block.properties.phasing.initiallyOn = true; break; case BLOCK_TYPE_LEVER: block.properties.lever.id = 1; break; case BLOCK_TYPE_TELEPORT: block.properties.teleport.id = 1; break; case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: block.properties.pushable.yVelocity = 0; break; case BLOCK_TYPE_FALLING: block.properties.falling.isUnsteady = false; block.properties.falling.isFalling = false; block.properties.falling.yVelocity = 0; break; } block.markedForRemoval = false; return block; } bool LevelEditorScreen_init(LevelEditorScreen * self, GameSession * gameSession) { int blockTypeIndex; call_super(init, self); self->activate = LevelEditorScreen_activate; self->deactivate = LevelEditorScreen_deactivate; self->dispose = LevelEditorScreen_dispose; self->gameSession = gameSession; self->inputController = InputController_create(gameSession->inputMap, "left", "right", "down", "up", "jump", "action", "pause", NULL); self->inspectorView = LevelEditorInspectorView_create(self, self->gameSession->resourceManager, 512, 80); self->menuItemIndex = 0; self->reorderingLevel = false; self->selectedTool = LEVEL_EDITOR_TOOL_PENCIL; self->selectedObject = OBJECT_BLOCK_STONE; for (blockTypeIndex = 0; blockTypeIndex < 20; blockTypeIndex++) { self->blockDefaults[blockTypeIndex] = initBlockToPlace(blockTypeIndex + 1); } self->blockToPlace = &self->blockDefaults[self->selectedObject - 1]; self->inspectorView->block = self->blockToPlace; self->draggingBlocks = false; self->draggingWaterLevel = false; self->draggingWaterSpeed = false; self->draggingInspector = false; self->confirmState = CONFIRM_NONE; self->showPowerupWarning = false; glGenBuffersARB(1, &self->vertexBufferID); glGenBuffersARB(1, &self->indexBufferID); self->bitmapFont = ResourceManager_referenceResource(self->gameSession->resourceManager, "bitmap_font", "pixelfont.json"); self->spriteTexture = self->gameSession->resourceManager->referenceResource(self->gameSession->resourceManager, "texture", "sprites.json"); self->textureAtlas = self->gameSession->resourceManager->referenceResource(self->gameSession->resourceManager, "texture_atlas", "sprite_atlas.json"); return true; } void LevelEditorScreen_dispose(LevelEditorScreen * self) { glDeleteBuffersARB(1, &self->vertexBufferID); glDeleteBuffersARB(1, &self->indexBufferID); ResourceManager_releaseResource(self->gameSession->resourceManager, "bitmap_font", "pixelfont.json"); ResourceManager_releaseResource(self->gameSession->resourceManager, "texture", "sprites.json"); ResourceManager_releaseResource(self->gameSession->resourceManager, "texture_atlas", "sprite_atlas.json"); InputController_dispose(self->inputController); LevelEditorInspectorView_dispose(self->inspectorView); call_super(dispose, self); } static bool isMenuItemEnabled(LevelEditorScreen * self, unsigned int menuItemIndex) { switch (menuItemIndex) { case MENU_ITEM_ADD_LEVEL: return self->levelSet->levelCount < LEVEL_COUNT_MAX; case MENU_ITEM_DELETE_LEVEL: return self->levelSet->levelCount > 1; case MENU_ITEM_SAVE: case MENU_ITEM_REVERT: return self->gameSession->hasUnsavedLevelChanges; default: return true; } } static bool hasCancelOption(enum LevelEditorConfirmState confirmState) { return confirmState == CONFIRM_SAVE || confirmState == CONFIRM_SAVE_BEFORE_QUIT; } static void confirmAction(LevelEditorScreen * self, unsigned int confirmChoice) { switch (self->confirmState) { case CONFIRM_SAVE: if (confirmChoice == CONFIRM_CHOICE_YES) { GameSession_replaceLevelSetAtIndex(self->gameSession, self->gameSession->selectedLevelSetIndex, LevelSetModel_copy(self->levelSet)); } if (confirmChoice != CONFIRM_CHOICE_CANCEL) { self->gameSession->hasUnsavedLevelChanges = false; InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "back"); } break; case CONFIRM_SAVE_BEFORE_QUIT: if (confirmChoice == CONFIRM_CHOICE_YES) { GameSession_replaceLevelSetAtIndex(self->gameSession, self->gameSession->selectedLevelSetIndex, LevelSetModel_copy(self->levelSet)); } if (confirmChoice != CONFIRM_CHOICE_CANCEL) { exit(EXIT_SUCCESS); } break; case CONFIRM_REVERT: if (confirmChoice == CONFIRM_CHOICE_YES) { LevelSetModel_dispose(self->levelSet); self->levelSet = LevelSetModel_copy(self->gameSession->levelSets[self->gameSession->selectedLevelSetIndex]); self->levelIndex = self->gameSession->startLevelIndex; if (self->inspectorView->block != self->blockToPlace) { self->inspectorView->block = NULL; } self->gameSession->hasUnsavedLevelChanges = false; while (!isMenuItemEnabled(self, self->menuItemIndex)) { self->menuItemIndex += 1; self->menuItemIndex %= MENU_ITEM_COUNT; } } break; case CONFIRM_DELETE_LEVEL: if (confirmChoice == CONFIRM_CHOICE_YES) { LevelSetModel_deleteLevelAtIndex(self->levelSet, self->levelIndex); self->gameSession->hasUnsavedLevelChanges = true; if (self->levelIndex >= self->levelSet->levelCount) { self->levelIndex--; } if (self->inspectorView->block != self->blockToPlace) { self->inspectorView->block = NULL; } while (!isMenuItemEnabled(self, self->menuItemIndex)) { self->menuItemIndex += 1; self->menuItemIndex %= MENU_ITEM_COUNT; } } break; default: break; } self->confirmState = CONFIRM_NONE; Shell_redisplay(); } static void menuAction(LevelEditorScreen * self, unsigned int menuItemIndex) { if (self->draggingBlocks || self->draggingWaterLevel || self->draggingWaterSpeed || self->draggingInspector || menuItemIndex >= MENU_ITEM_COUNT || !isMenuItemEnabled(self, menuItemIndex)) { return; } self->menuItemIndex = menuItemIndex; Shell_redisplay(); switch (menuItemIndex) { case MENU_ITEM_LEVEL_NUMBER: self->reorderingLevel = !self->reorderingLevel; break; case MENU_ITEM_ADD_LEVEL: LevelSetModel_addLevelAtIndex(self->levelSet, LevelModel_create(), self->levelIndex + 1); self->gameSession->hasUnsavedLevelChanges = true; self->levelIndex++; if (self->inspectorView->block != self->blockToPlace) { self->inspectorView->block = NULL; } while (!isMenuItemEnabled(self, self->menuItemIndex)) { self->menuItemIndex += 1; self->menuItemIndex %= MENU_ITEM_COUNT; } break; case MENU_ITEM_DELETE_LEVEL: self->confirmState = CONFIRM_DELETE_LEVEL; self->confirmChoice = CONFIRM_CHOICE_NO; break; case MENU_ITEM_TEST_LEVEL: self->gameSession->testingLevel = true; self->gameSession->levelSet = self->levelSet; self->gameSession->startLevelIndex = self->levelIndex; InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "test_level"); break; case MENU_ITEM_SAVE: GameSession_replaceLevelSetAtIndex(self->gameSession, self->gameSession->selectedLevelSetIndex, LevelSetModel_copy(self->levelSet)); self->gameSession->hasUnsavedLevelChanges = false; while (!isMenuItemEnabled(self, self->menuItemIndex)) { self->menuItemIndex += 1; self->menuItemIndex %= MENU_ITEM_COUNT; } break; case MENU_ITEM_REVERT: self->confirmState = CONFIRM_REVERT; self->confirmChoice = CONFIRM_CHOICE_NO; break; case MENU_ITEM_DONE: if (self->reorderingLevel) { self->reorderingLevel = false; } else if (self->gameSession->hasUnsavedLevelChanges) { self->confirmState = CONFIRM_SAVE; self->confirmChoice = CONFIRM_CHOICE_YES; } else { InputController_reset(self->inputController); ScreenManager_transition(self->screenManager, "back"); } break; } } static void menuBack(LevelEditorScreen * self) { if (self->confirmState != CONFIRM_NONE) { confirmAction(self, CONFIRM_CHOICE_CANCEL); } else { menuAction(self, MENU_ITEM_DONE); } } static void menuLeft(LevelEditorScreen * self) { if (self->confirmState != CONFIRM_NONE) { unsigned int choiceCount; if (hasCancelOption(self->confirmState)) { choiceCount = 3; } else { choiceCount = 2; } self->confirmChoice += choiceCount - 1; self->confirmChoice %= choiceCount; Shell_redisplay(); } else if (self->menuItemIndex == MENU_ITEM_LEVEL_NUMBER && self->levelIndex > 0) { self->levelIndex--; if (self->reorderingLevel) { LevelSetModel_swapLevels(self->levelSet, self->levelIndex + 1, self->levelIndex); self->gameSession->hasUnsavedLevelChanges = true; } if (self->inspectorView->block != self->blockToPlace) { self->inspectorView->block = NULL; } Shell_redisplay(); } } static void menuRight(LevelEditorScreen * self) { if (self->confirmState != CONFIRM_NONE) { unsigned int choiceCount; if (hasCancelOption(self->confirmState)) { choiceCount = 3; } else { choiceCount = 2; } self->confirmChoice += 1; self->confirmChoice %= choiceCount; Shell_redisplay(); } else if (self->menuItemIndex == MENU_ITEM_LEVEL_NUMBER && self->levelIndex < self->levelSet->levelCount - 1) { self->levelIndex++; if (self->reorderingLevel) { LevelSetModel_swapLevels(self->levelSet, self->levelIndex - 1, self->levelIndex); self->gameSession->hasUnsavedLevelChanges = true; } if (self->inspectorView->block != self->blockToPlace) { self->inspectorView->block = NULL; } Shell_redisplay(); } } static void menuDown(LevelEditorScreen * self) { if (!self->reorderingLevel && !self->draggingBlocks && !self->draggingWaterLevel && !self->draggingWaterSpeed && !self->draggingInspector && self->confirmState == CONFIRM_NONE) { do { self->menuItemIndex += 1; self->menuItemIndex %= MENU_ITEM_COUNT; } while (!isMenuItemEnabled(self, self->menuItemIndex)); Shell_redisplay(); } } static void menuUp(LevelEditorScreen * self) { if (!self->reorderingLevel && !self->draggingBlocks && !self->draggingWaterLevel && !self->draggingWaterSpeed && !self->draggingInspector && self->confirmState == CONFIRM_NONE) { do { self->menuItemIndex += MENU_ITEM_COUNT - 1; self->menuItemIndex %= MENU_ITEM_COUNT; } while (!isMenuItemEnabled(self, self->menuItemIndex)); Shell_redisplay(); } } static bool isPowerupSelected(LevelEditorScreen * self) { return self->selectedObject == OBJECT_POWERUP_KEY || self->selectedObject == OBJECT_POWERUP_HEART || self->selectedObject == OBJECT_POWERUP_GEM; } static bool isBlockSelected(LevelEditorScreen * self) { return self->selectedObject >= OBJECT_BLOCK_STONE && self->selectedObject <= OBJECT_BLOCK_FALLING; } static void copyLevelToEditBuffer(LevelEditorScreen * self) { self->editingPowerupCount = self->levelSet->levels[self->levelIndex]->powerupCount; memcpy(self->editingPowerups, self->levelSet->levels[self->levelIndex]->powerups, sizeof(self->editingPowerups)); memcpy(self->editingBlocks, self->levelSet->levels[self->levelIndex]->blocks, sizeof(self->editingBlocks)); } static void copyEditBufferToLevel(LevelEditorScreen * self) { self->levelSet->levels[self->levelIndex]->powerupCount = self->editingPowerupCount; memcpy(self->levelSet->levels[self->levelIndex]->powerups, self->editingPowerups, sizeof(self->editingPowerups)); memcpy(self->levelSet->levels[self->levelIndex]->blocks, self->editingBlocks, sizeof(self->editingBlocks)); } static void setBlockPosition(struct LevelBlock * block, unsigned int x, unsigned int y) { block->tilePosition.x = x; block->tilePosition.y = y; switch (block->type) { case BLOCK_TYPE_MOVING: block->properties.moving.position.x = x * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE / 2; block->properties.moving.position.y = y * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE / 2; break; case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: block->properties.pushable.position.x = x * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE / 2; block->properties.pushable.position.y = y * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE / 2; break; case BLOCK_TYPE_FALLING: block->properties.falling.position.x = x * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE / 2; block->properties.falling.position.y = y * LEVEL_BLOCK_SIZE + LEVEL_BLOCK_SIZE / 2; break; default: break; } } static int roundingIntDivide(int lhs, int rhs) { return lhs / rhs + lhs * 2 / rhs % 2; } static void placeBlocksInLine(LevelEditorScreen * self, unsigned int blockIndex1, unsigned int blockIndex2) { int x1, x2, y1, y2; unsigned int range, index; unsigned int blockIndexX, blockIndexY; x1 = blockIndex1 % LEVEL_BLOCK_COUNT_X; y1 = blockIndex1 / LEVEL_BLOCK_COUNT_X; x2 = blockIndex2 % LEVEL_BLOCK_COUNT_X; y2 = blockIndex2 / LEVEL_BLOCK_COUNT_X; if (abs(x1 - x2) > abs(y1 - y2)) { range = abs(x1 - x2); } else { range = abs(y1 - y2); } for (index = 0; index <= range; index++) { blockIndexX = x1 + roundingIntDivide((x2 - x1) * index, range + 1); blockIndexY = y1 + roundingIntDivide((y2 - y1) * index, range + 1); if (self->erasing) { self->editingBlocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX].type = BLOCK_TYPE_EMPTY; } else { setBlockPosition(self->blockToPlace, blockIndexX, blockIndexY); self->editingBlocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX] = *self->blockToPlace; } } } static void placeBlocksInRect(LevelEditorScreen * self, unsigned int blockIndex1, unsigned int blockIndex2) { unsigned int x1, x2, y1, y2; unsigned int left, right, top, bottom; unsigned int blockIndexX, blockIndexY; x1 = blockIndex1 % LEVEL_BLOCK_COUNT_X; y1 = blockIndex1 / LEVEL_BLOCK_COUNT_X; x2 = blockIndex2 % LEVEL_BLOCK_COUNT_X; y2 = blockIndex2 / LEVEL_BLOCK_COUNT_X; left = x1 < x2 ? x1 : x2; right = x1 > x2 ? x1 : x2; top = y1 < y2 ? y1 : y2; bottom = y1 > y2 ? y1 : y2; for (blockIndexY = top; blockIndexY <= bottom; blockIndexY++) { for (blockIndexX = left; blockIndexX <= right; blockIndexX++) { if (self->erasing) { self->editingBlocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX].type = BLOCK_TYPE_EMPTY; } else { setBlockPosition(self->blockToPlace, blockIndexX, blockIndexY); self->editingBlocks[blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX] = *self->blockToPlace; } } } } static enum LevelPowerupType getPowerupTypeFromObjectType(enum LevelEditorObjectType objectType) { switch (objectType) { case OBJECT_POWERUP_KEY: return POWERUP_TYPE_KEY; case OBJECT_POWERUP_HEART: return POWERUP_TYPE_HEART; case OBJECT_POWERUP_GEM: default: return POWERUP_TYPE_GEM; } } static struct LevelPowerup initPowerupAtPosition(enum LevelEditorObjectType objectType, int x, int y) { struct LevelPowerup powerup; powerup.type = getPowerupTypeFromObjectType(objectType); powerup.tilePosition.x = x; powerup.tilePosition.y = y; return powerup; } unsigned int getPowerupIndexByLocation(LevelEditorScreen * self, int tileX, int tileY) { unsigned int powerupIndex; for (powerupIndex = 0; powerupIndex < self->editingPowerupCount; powerupIndex++) { if (self->editingPowerups[powerupIndex].tilePosition.x == tileX && self->editingPowerups[powerupIndex].tilePosition.y == tileY) { return powerupIndex; } } return UINT_MAX; } static void placePowerup(LevelEditorScreen * self, int x, int y) { struct LevelPowerup powerup; unsigned int powerupIndex; powerupIndex = getPowerupIndexByLocation(self, x, y); if (self->erasing) { if (powerupIndex != UINT_MAX) { self->editingPowerupCount--; for (; powerupIndex < self->editingPowerupCount; powerupIndex++) { self->editingPowerups[powerupIndex] = self->editingPowerups[powerupIndex + 1]; } } } else { powerup = initPowerupAtPosition(self->selectedObject, x, y); if (powerupIndex != UINT_MAX) { self->editingPowerups[powerupIndex] = powerup; } else if (self->editingPowerupCount >= LEVEL_POWERUP_COUNT_MAX) { self->showPowerupWarning = true; } else { self->editingPowerups[self->editingPowerupCount++] = powerup; } } } static void placePowerupsInLine(LevelEditorScreen * self, unsigned int blockIndex1, unsigned int blockIndex2) { int x1, x2, y1, y2; unsigned int range, index; unsigned int blockIndexX, blockIndexY; x1 = blockIndex1 % LEVEL_BLOCK_COUNT_X; y1 = blockIndex1 / LEVEL_BLOCK_COUNT_X; x2 = blockIndex2 % LEVEL_BLOCK_COUNT_X; y2 = blockIndex2 / LEVEL_BLOCK_COUNT_X; if (abs(x1 - x2) > abs(y1 - y2)) { range = abs(x1 - x2); } else { range = abs(y1 - y2); } for (index = 0; index <= range; index++) { blockIndexX = x1 + roundingIntDivide((x2 - x1) * index, range + 1); blockIndexY = y1 + roundingIntDivide((y2 - y1) * index, range + 1); placePowerup(self, blockIndexX, blockIndexY); } } static void placePowerupsInRect(LevelEditorScreen * self, unsigned int blockIndex1, unsigned int blockIndex2) { unsigned int x1, x2, y1, y2; unsigned int left, right, top, bottom; unsigned int blockIndexX, blockIndexY; x1 = blockIndex1 % LEVEL_BLOCK_COUNT_X; y1 = blockIndex1 / LEVEL_BLOCK_COUNT_X; x2 = blockIndex2 % LEVEL_BLOCK_COUNT_X; y2 = blockIndex2 / LEVEL_BLOCK_COUNT_X; left = x1 < x2 ? x1 : x2; right = x1 > x2 ? x1 : x2; top = y1 < y2 ? y1 : y2; bottom = y1 > y2 ? y1 : y2; for (blockIndexY = top; blockIndexY <= bottom; blockIndexY++) { for (blockIndexX = left; blockIndexX <= right; blockIndexX++) { placePowerup(self, blockIndexX, blockIndexY); } } } static void applyDrag(LevelEditorScreen * self, unsigned int blockIndex) { if (isBlockSelected(self)) { if (self->selectedTool == LEVEL_EDITOR_TOOL_PENCIL || self->selectedTool == LEVEL_EDITOR_TOOL_ERASER) { placeBlocksInLine(self, self->dragStartIndex, blockIndex); } else if (self->selectedTool == LEVEL_EDITOR_TOOL_RECT) { copyLevelToEditBuffer(self); placeBlocksInRect(self, self->dragStartIndex, blockIndex); } } else if (isPowerupSelected(self)) { if (self->selectedTool == LEVEL_EDITOR_TOOL_PENCIL || self->selectedTool == LEVEL_EDITOR_TOOL_ERASER) { placePowerupsInLine(self, self->dragStartIndex, blockIndex); } else if (self->selectedTool == LEVEL_EDITOR_TOOL_RECT) { copyLevelToEditBuffer(self); placePowerupsInRect(self, self->dragStartIndex, blockIndex); } } else { self->levelSet->levels[self->levelIndex]->playerStartPosition = VECTOR2i(blockIndex % LEVEL_BLOCK_COUNT_X, blockIndex / LEVEL_BLOCK_COUNT_X); } if (self->selectedTool == LEVEL_EDITOR_TOOL_PENCIL || self->selectedTool == LEVEL_EDITOR_TOOL_ERASER) { self->dragStartIndex = blockIndex; } } static unsigned int getBlockIndexFromPosition(float x, float y) { int blockIndexX, blockIndexY; blockIndexX = x / LEVEL_BLOCK_SIZE; blockIndexY = y / LEVEL_BLOCK_SIZE; if (blockIndexX < 0) { blockIndexX = 0; } else if (blockIndexX >= LEVEL_BLOCK_COUNT_X) { blockIndexX = LEVEL_BLOCK_COUNT_X - 1; } if (blockIndexY < 0) { blockIndexY = 0; } else if (blockIndexY >= LEVEL_BLOCK_COUNT_Y) { blockIndexY = LEVEL_BLOCK_COUNT_Y - 1; } return blockIndexY * LEVEL_BLOCK_COUNT_X + blockIndexX; } static bool mouseDown(Atom eventID, void * eventData, void * context) { struct mouseEvent * event = eventData; LevelEditorScreen * self = context; unsigned int itemIndex; if (self->confirmState != CONFIRM_NONE) { if (event->position.y >= 240 && event->position.y <= 272) { float yesLeft, yesRight, noLeft, noRight, cancelLeft = 0.0f, cancelRight = 0.0f, positionX; positionX = 320.0f - GLBitmapFont_measureString(self->bitmapFont, hasCancelOption(self->confirmState) ? "YES | NO | CANCEL" : "YES | NO", GLBITMAPFONT_USE_STRLEN) * 32 / 2; yesLeft = positionX; positionX += GLBitmapFont_measureString(self->bitmapFont, "YES", GLBITMAPFONT_USE_STRLEN) * 32; yesRight = positionX; positionX += GLBitmapFont_measureString(self->bitmapFont, " | ", GLBITMAPFONT_USE_STRLEN) * 32; noLeft = positionX; positionX += GLBitmapFont_measureString(self->bitmapFont, "NO", GLBITMAPFONT_USE_STRLEN) * 32; noRight = positionX; if (hasCancelOption(self->confirmState)) { positionX += GLBitmapFont_measureString(self->bitmapFont, " | ", GLBITMAPFONT_USE_STRLEN) * 32; cancelLeft = positionX; positionX += GLBitmapFont_measureString(self->bitmapFont, "CANCEL", GLBITMAPFONT_USE_STRLEN) * 32; cancelRight = positionX; } if (event->position.x >= yesLeft && event->position.x <= yesRight) { confirmAction(self, 0); } else if (event->position.x >= noLeft && event->position.x <= noRight) { confirmAction(self, 1); } else if (hasCancelOption(self->confirmState) && event->position.x >= cancelLeft && event->position.x <= cancelRight) { confirmAction(self, 2); } } return true; } if (event->position.x >= 512 && event->position.x <= 640 && event->position.y >= 358) { itemIndex = (event->position.y - 358.0f) / 16.0f; if (itemIndex == MENU_ITEM_LEVEL_NUMBER) { char levelNumberString[LEVEL_STRING_MAX]; float stringWidth; snprintf_safe(levelNumberString, LEVEL_STRING_MAX, "Level %u/%u", self->levelIndex + 1, self->levelSet->levelCount); stringWidth = GLBitmapFont_measureString(self->bitmapFont, levelNumberString, GLBITMAPFONT_USE_STRLEN) * 16.0f; if (event->position.x < 512 + (640 - 512 - stringWidth) / 2) { self->menuItemIndex = MENU_ITEM_LEVEL_NUMBER; menuLeft(self); } else if (event->position.x > 640 - (640 - 512 - stringWidth) / 2) { self->menuItemIndex = MENU_ITEM_LEVEL_NUMBER; menuRight(self); } else { menuAction(self, itemIndex); } } else { menuAction(self, itemIndex); } return true; } if (event->position.x >= TOOL_GRID_LEFT - TOOL_GRID_PADDING && event->position.y <= TOOL_GRID_TOP + (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING) * TOOL_ROW_COUNT) { int rowIndex, columnIndex, value; columnIndex = (event->position.x - (TOOL_GRID_LEFT - TOOL_GRID_PADDING * 0.5f)) / (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING); rowIndex = (event->position.y - (TOOL_GRID_TOP - TOOL_GRID_PADDING * 0.5f)) / (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING); if (columnIndex < 0) { columnIndex = 0; } else if (columnIndex >= TOOL_COLUMN_COUNT) { columnIndex = TOOL_COLUMN_COUNT - 1; } if (rowIndex < 0) { rowIndex = 0; } else if (rowIndex >= TOOL_ROW_COUNT) { rowIndex = TOOL_ROW_COUNT - 1; } value = toolLayout[rowIndex * TOOL_COLUMN_COUNT + columnIndex]; switch (value) { case LEVEL_EDITOR_TOOL_PENCIL: case LEVEL_EDITOR_TOOL_RECT: if (isBlockSelected(self)) { self->blockToPlace = &self->blockDefaults[self->selectedObject - 1]; } case LEVEL_EDITOR_TOOL_ERASER: self->inspectorView->block = self->blockToPlace; case LEVEL_EDITOR_TOOL_INSPECT: self->selectedTool = value; break; default: self->selectedObject = value; if (isBlockSelected(self)) { self->blockToPlace = &self->blockDefaults[self->selectedObject - 1]; self->inspectorView->block = self->blockToPlace; } else { self->inspectorView->block = NULL; } break; } Shell_redisplay(); return true; } if (event->position.x >= 512 && event->position.x <= 640 && event->position.y >= self->inspectorView->offsetY && event->position.y <= self->inspectorView->offsetY + LEVEL_EDITOR_INSPECTOR_MAX_HEIGHT) { self->draggingInspector = true; return LevelEditorInspectorView_mouseDown(self->inspectorView, event); } if (event->position.x >= 512 && event->position.x <= 640 && event->position.y >= WATER_CONTROLS_TOP && event->position.y < WATER_CONTROLS_TOP + 18) { self->draggingWaterLevel = true; self->rawDragOrigin = VECTOR2f(roundf(event->rawPosition.x), roundf(event->rawPosition.y) + self->levelSet->levels[self->levelIndex]->waterLevel); Shell_redisplay(); return true; } if (event->position.x >= 512 && event->position.x <= 640 && event->position.y >= WATER_CONTROLS_TOP + 18 && event->position.y < WATER_CONTROLS_TOP + 36) { self->draggingWaterSpeed = true; self->rawDragOrigin = VECTOR2f(roundf(event->rawPosition.x), roundf(event->rawPosition.y) + self->levelSet->levels[self->levelIndex]->waterSpeed / 0.005f); Shell_redisplay(); return true; } if (event->position.x <= 512) { unsigned int blockIndex; self->showPowerupWarning = false; blockIndex = getBlockIndexFromPosition(event->position.x, event->position.y); if (self->selectedTool == LEVEL_EDITOR_TOOL_INSPECT) { struct LevelBlock * block; block = &self->levelSet->levels[self->levelIndex]->blocks[blockIndex]; if (block->type != BLOCK_TYPE_EMPTY) { self->selectedObject = (enum LevelEditorObjectType) block->type; self->blockDefaults[self->selectedObject - 1] = *block; self->blockToPlace = &self->blockDefaults[self->selectedObject - 1]; self->inspectorView->block = block; } else { self->inspectorView->block = NULL; } } else { self->dragStartIndex = blockIndex; self->draggingBlocks = true; copyLevelToEditBuffer(self); if (isBlockSelected(self)) { if (self->selectedTool == LEVEL_EDITOR_TOOL_ERASER || self->editingBlocks[blockIndex].type == (enum LevelBlockType) self->selectedObject) { self->erasing = true; } else { self->erasing = false; } } else if (isPowerupSelected(self)) { unsigned int powerupIndex; powerupIndex = getPowerupIndexByLocation(self, blockIndex % LEVEL_BLOCK_COUNT_X, blockIndex / LEVEL_BLOCK_COUNT_X); if (self->selectedTool == LEVEL_EDITOR_TOOL_ERASER || (powerupIndex != UINT_MAX && self->editingPowerups[powerupIndex].type == getPowerupTypeFromObjectType(self->selectedObject))) { self->erasing = true; } else { self->erasing = false; } } applyDrag(self, blockIndex); self->gameSession->hasUnsavedLevelChanges = true; } Shell_redisplay(); return true; } return false; } static bool mouseUp(Atom eventID, void * eventData, void * context) { struct mouseEvent * event = eventData; LevelEditorScreen * self = context; if (self->draggingBlocks) { copyEditBufferToLevel(self); self->draggingBlocks = false; return true; } if (self->draggingWaterLevel) { self->draggingWaterLevel = false; Shell_redisplay(); return true; } if (self->draggingWaterSpeed) { self->draggingWaterSpeed = false; Shell_redisplay(); return true; } if (self->draggingInspector) { if (LevelEditorInspectorView_mouseUp(self->inspectorView, event) && self->inspectorView->block != self->blockToPlace) { self->gameSession->hasUnsavedLevelChanges = true; *self->blockToPlace = *self->inspectorView->block; } self->draggingInspector = false; Shell_redisplay(); return true; } return false; } static bool mouseDragged(Atom eventID, void * eventData, void * context) { struct mouseEvent * event = eventData; LevelEditorScreen * self = context; if (self->draggingBlocks) { applyDrag(self, getBlockIndexFromPosition(event->position.x, event->position.y)); Shell_redisplay(); return true; } if (self->draggingWaterLevel) { self->levelSet->levels[self->levelIndex]->waterLevel = roundf(self->rawDragOrigin.y - event->rawPosition.y); self->gameSession->hasUnsavedLevelChanges = true; Shell_redisplay(); return true; } if (self->draggingWaterSpeed) { self->levelSet->levels[self->levelIndex]->waterSpeed = roundf(self->rawDragOrigin.y - event->rawPosition.y) * 0.005f; self->gameSession->hasUnsavedLevelChanges = true; Shell_redisplay(); return true; } if (self->draggingInspector) { return LevelEditorInspectorView_mouseDragged(self->inspectorView, event); } return false; } static bool keyDown(Atom eventID, void * eventData, void * context) { struct keyEvent * event = eventData; LevelEditorScreen * self = context; if (event->isRepeat) { return false; } switch (event->keyCode) { case KEYBOARD_ESCAPE: menuBack(self); return true; case KEYBOARD_RETURN_OR_ENTER: case KEYBOARD_SPACEBAR: InputController_triggerAction(self->inputController, ATOM("jump")); return true; case KEYBOARD_LEFT_ARROW: InputController_triggerAction(self->inputController, ATOM("left")); return true; case KEYBOARD_RIGHT_ARROW: InputController_triggerAction(self->inputController, ATOM("right")); return true; case KEYBOARD_DOWN_ARROW: InputController_triggerAction(self->inputController, ATOM("down")); return true; case KEYBOARD_UP_ARROW: InputController_triggerAction(self->inputController, ATOM("up")); return true; } return InputController_keyDown(self->inputController, event->keyCode); } static bool keyUp(Atom eventID, void * eventData, void * context) { struct keyEvent * event = eventData; LevelEditorScreen * self = context; switch (event->keyCode) { case KEYBOARD_RETURN_OR_ENTER: case KEYBOARD_SPACEBAR: InputController_releaseAction(self->inputController, ATOM("jump")); return true; case KEYBOARD_LEFT_ARROW: InputController_releaseAction(self->inputController, ATOM("left")); return true; case KEYBOARD_RIGHT_ARROW: InputController_releaseAction(self->inputController, ATOM("right")); return true; case KEYBOARD_DOWN_ARROW: InputController_releaseAction(self->inputController, ATOM("down")); return true; case KEYBOARD_UP_ARROW: InputController_releaseAction(self->inputController, ATOM("up")); return true; } return InputController_keyUp(self->inputController, event->keyCode); } static bool gamepadButtonDown(Atom eventID, void * eventData, void * context) { struct gamepadButtonEvent * event = eventData; LevelEditorScreen * self = context; return InputController_gamepadButtonDown(self->inputController, event->device->vendorID, event->device->productID, event->buttonID); } static bool gamepadButtonUp(Atom eventID, void * eventData, void * context) { struct gamepadButtonEvent * event = eventData; LevelEditorScreen * self = context; return InputController_gamepadButtonUp(self->inputController, event->device->vendorID, event->device->productID, event->buttonID); } static bool gamepadAxisMove(Atom eventID, void * eventData, void * context) { struct gamepadAxisEvent * event = eventData; LevelEditorScreen * self = context; return InputController_gamepadAxisMoved(self->inputController, event->device->vendorID, event->device->productID, event->axisID, event->value, event->lastValue); } static bool actionDown(Atom eventID, void * eventData, void * context) { LevelEditorScreen * self = context; Atom actionID = eventData; if (actionID == ATOM("left")) { menuLeft(self); return true; } if (actionID == ATOM("right")) { menuRight(self); return true; } if (actionID == ATOM("down")) { menuDown(self); return true; } if (actionID == ATOM("up")) { menuUp(self); return true; } if (actionID == ATOM("jump")) { if (self->confirmState != CONFIRM_NONE) { confirmAction(self, self->confirmChoice); } else { menuAction(self, self->menuItemIndex); } return true; } if (actionID == ATOM("action")) { menuBack(self); return true; } return false; } static void getMenuItemVertices(LevelEditorScreen * self, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { Color4f colors[MENU_ITEM_COUNT]; unsigned int colorIndex; char levelNumberString[LEVEL_STRING_MAX]; char waterLevelString[32], waterSpeedString[32]; snprintf_safe(levelNumberString, LEVEL_STRING_MAX, "< Level %u/%u >", self->levelIndex + 1, self->levelSet->levelCount); for (colorIndex = 0; colorIndex < MENU_ITEM_COUNT; colorIndex++) { if (!isMenuItemEnabled(self, colorIndex)) { colors[colorIndex] = MENU_COLOR_DISABLED; } else if (colorIndex == self->menuItemIndex) { if (colorIndex == MENU_ITEM_LEVEL_NUMBER && self->reorderingLevel) { colors[colorIndex] = MENU_COLOR_SELECTED; } else { colors[colorIndex] = MENU_COLOR_HIGHLIGHTED; } } else { colors[colorIndex] = MENU_COLOR_ENABLED; } } GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, levelNumberString, GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 366.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_LEVEL_NUMBER], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "ADD LEVEL", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 382.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_ADD_LEVEL], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "DELETE LEVEL", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 398.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_DELETE_LEVEL], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "TEST LEVEL", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 414.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_TEST_LEVEL], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "SAVE", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 430.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_SAVE], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "REVERT", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 446.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_REVERT], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "DONE", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 462.0f), VECTOR2f(0.5f, 0.5f), colors[MENU_ITEM_DONE], GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); if (self->showPowerupWarning) { GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "Too many powerups", GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(576.0f, 342.0f), VECTOR2f(0.5f, 0.5f), MENU_COLOR_ERROR, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } snprintf_safe(waterLevelString, 32, "WLvl: %.0f", self->levelSet->levels[self->levelIndex]->waterLevel); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, waterLevelString, GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(517.0f, WATER_CONTROLS_TOP), VECTOR2f(0.0f, 1.0f), self->draggingWaterLevel ? MENU_COLOR_SELECTED : MENU_COLOR_ENABLED, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); snprintf_safe(waterSpeedString, 32, "WSpd: %.3f", self->levelSet->levels[self->levelIndex]->waterSpeed); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, waterSpeedString, GLBITMAPFONT_USE_STRLEN, -16, VECTOR2f(517.0f, WATER_CONTROLS_TOP + 20), VECTOR2f(0.0f, 1.0f), self->draggingWaterSpeed ? MENU_COLOR_SELECTED : MENU_COLOR_ENABLED, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } static const char * getToolSpriteName(int tool) { switch (tool) { case OBJECT_BLOCK_STONE: return "stoneblock"; case OBJECT_BLOCK_DRY_SOLID: return "blueblock"; case OBJECT_BLOCK_WET_SOLID: return "yellowblock"; case OBJECT_BLOCK_CRUMBLE: return "crumblingblock"; case OBJECT_BLOCK_LEVER: return "switch"; case OBJECT_BLOCK_SWITCHABLE: return "switchableblock"; case OBJECT_BLOCK_PHASING: return "phasingblock"; case OBJECT_BLOCK_FALLING: return "fallingblock"; case OBJECT_BLOCK_LADDER: return "ladder"; case OBJECT_BLOCK_TRAMPOLINE: return "trampoline"; case OBJECT_BLOCK_MOVING: return "movingblock"; case OBJECT_BLOCK_TELEPORT: return "teleport"; case OBJECT_BLOCK_PUSHABLE: return "boulder"; case OBJECT_BLOCK_FLOATING: return "woodblock"; case OBJECT_BLOCK_ICE: return "iceblock"; case OBJECT_BLOCK_FIRE: return "fireblock"; case OBJECT_BLOCK_DEATH: return "deadlyblock"; case OBJECT_BLOCK_SAND: return "sandblock"; case OBJECT_BLOCK_STICKY: return "stickyblock"; case OBJECT_BLOCK_LOCK: return "lockedblock"; case OBJECT_POWERUP_KEY: return "key"; case OBJECT_POWERUP_HEART: return "heart"; case OBJECT_POWERUP_GEM: return "gem"; case OBJECT_PLAYER_POSITION: return "player_front"; case LEVEL_EDITOR_TOOL_PENCIL: return "pencil"; case LEVEL_EDITOR_TOOL_ERASER: return "eraser"; case LEVEL_EDITOR_TOOL_RECT: return "cross"; case LEVEL_EDITOR_TOOL_INSPECT: return "arrow"; default: return NULL; } } static void getToolVertices(LevelEditorScreen * self, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { unsigned int rowIndex, columnIndex; const char * spriteName; for (rowIndex = 0; rowIndex < TOOL_ROW_COUNT; rowIndex++) { for (columnIndex = 0; columnIndex < TOOL_COLUMN_COUNT; columnIndex++) { spriteName = getToolSpriteName(toolLayout[rowIndex * TOOL_COLUMN_COUNT + columnIndex]); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, spriteName, VECTOR2f(TOOL_GRID_LEFT + (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING) * columnIndex, TOOL_GRID_TOP + (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING) * rowIndex), VECTOR2f(0.0f, 1.0f), VECTOR2f(LEVEL_BLOCK_SIZE, -LEVEL_BLOCK_SIZE), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } } } static void getConfirmationVertices(LevelEditorScreen * self, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { const char * promptString = NULL; float positionX; switch (self->confirmState) { case CONFIRM_NONE: return; case CONFIRM_SAVE: promptString = "Save changes?"; break; case CONFIRM_SAVE_BEFORE_QUIT: promptString = "Save changes before quitting?"; break; case CONFIRM_REVERT: promptString = "Revert to last save?"; break; case CONFIRM_DELETE_LEVEL: promptString = "Delete level from set?"; break; } GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, promptString, GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(320.0f, 240.0f), VECTOR2f(0.5f, 0.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); positionX = 320.0f - GLBitmapFont_measureString(self->bitmapFont, hasCancelOption(self->confirmState) ? "YES | NO | CANCEL" : "YES | NO", GLBITMAPFONT_USE_STRLEN) * 32 / 2; GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "YES", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(positionX, 240.0f), VECTOR2f(0.0f, 1.0f), self->confirmChoice == CONFIRM_CHOICE_YES ? MENU_COLOR_HIGHLIGHTED : MENU_COLOR_ENABLED, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); positionX += GLBitmapFont_measureString(self->bitmapFont, "YES", GLBITMAPFONT_USE_STRLEN) * 32; GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, " | ", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(positionX, 240.0f), VECTOR2f(0.0f, 1.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); positionX += GLBitmapFont_measureString(self->bitmapFont, " | ", GLBITMAPFONT_USE_STRLEN) * 32; GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "NO", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(positionX, 240.0f), VECTOR2f(0.0f, 1.0f), self->confirmChoice == CONFIRM_CHOICE_NO ? MENU_COLOR_HIGHLIGHTED : MENU_COLOR_ENABLED, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); positionX += GLBitmapFont_measureString(self->bitmapFont, "NO", GLBITMAPFONT_USE_STRLEN) * 32; if (hasCancelOption(self->confirmState)) { GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, " | ", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(positionX, 240.0f), VECTOR2f(0.0f, 1.0f), MENU_COLOR_NONINTERACTIVE, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); positionX += GLBitmapFont_measureString(self->bitmapFont, " | ", GLBITMAPFONT_USE_STRLEN) * 32; GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, "CANCEL", GLBITMAPFONT_USE_STRLEN, -32, VECTOR2f(positionX, 240.0f), VECTOR2f(0.0f, 1.0f), self->confirmChoice == CONFIRM_CHOICE_CANCEL ? MENU_COLOR_HIGHLIGHTED : MENU_COLOR_ENABLED, GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); positionX += GLBitmapFont_measureString(self->bitmapFont, "CANCEL", GLBITMAPFONT_USE_STRLEN) * 32; } } static unsigned int getLayoutIndex(int value) { unsigned int index; for (index = 0; index < TOOL_ROW_COUNT * TOOL_COLUMN_COUNT; index++) { if (toolLayout[index] == value) { return index; } } return 0; } static bool draw(Atom eventID, void * eventData, void * context) { LevelEditorScreen * self = context; struct vertex_p2f_t2f_c4f * vertices; GLushort * indexes; unsigned int vertexCount, indexCount, textIndexCount, textIndexCount2; unsigned int toolLayoutIndex, objectLayoutIndex; Vector2f objectHighlightPosition, toolHighlightPosition; glClearColor(1.0f, 1.0f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); glOrtho(0.0f, 640.0f, 480.0f, 0.0f, -1.0f, 1.0f); LevelEditorView_draw(self->view); LevelEditorInspectorView_draw(self->inspectorView); toolLayoutIndex = getLayoutIndex(self->selectedTool); objectLayoutIndex = getLayoutIndex(self->selectedObject); objectHighlightPosition = VECTOR2f(TOOL_GRID_LEFT + (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING) * (objectLayoutIndex % TOOL_COLUMN_COUNT), TOOL_GRID_TOP + (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING) * (objectLayoutIndex / TOOL_COLUMN_COUNT)); toolHighlightPosition = VECTOR2f(TOOL_GRID_LEFT + (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING) * (toolLayoutIndex % TOOL_COLUMN_COUNT), TOOL_GRID_TOP + (LEVEL_BLOCK_SIZE + TOOL_GRID_PADDING) * (toolLayoutIndex / TOOL_COLUMN_COUNT)); glBegin(GL_QUADS); if (self->selectedTool == LEVEL_EDITOR_TOOL_PENCIL || self->selectedTool == LEVEL_EDITOR_TOOL_RECT) { glColor4f(0.0f, 0.0f, 1.0f, 1.0f); } else { glColor4f(0.75f, 0.75f, 1.0f, 1.0f); } glVertex2f(objectHighlightPosition.x - TOOL_HIGHLIGHT_THICKNESS, objectHighlightPosition.y - TOOL_HIGHLIGHT_THICKNESS); glVertex2f(objectHighlightPosition.x + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS, objectHighlightPosition.y - TOOL_HIGHLIGHT_THICKNESS); glVertex2f(objectHighlightPosition.x + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS, objectHighlightPosition.y + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS); glVertex2f(objectHighlightPosition.x - TOOL_HIGHLIGHT_THICKNESS, objectHighlightPosition.y + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS); glColor4f(0.0f, 0.0f, 1.0f, 1.0f); glVertex2f(toolHighlightPosition.x - TOOL_HIGHLIGHT_THICKNESS, toolHighlightPosition.y - TOOL_HIGHLIGHT_THICKNESS); glVertex2f(toolHighlightPosition.x + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS, toolHighlightPosition.y - TOOL_HIGHLIGHT_THICKNESS); glVertex2f(toolHighlightPosition.x + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS, toolHighlightPosition.y + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS); glVertex2f(toolHighlightPosition.x - TOOL_HIGHLIGHT_THICKNESS, toolHighlightPosition.y + LEVEL_BLOCK_SIZE + TOOL_HIGHLIGHT_THICKNESS); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glVertex2f(objectHighlightPosition.x, objectHighlightPosition.y); glVertex2f(objectHighlightPosition.x + LEVEL_BLOCK_SIZE, objectHighlightPosition.y); glVertex2f(objectHighlightPosition.x + LEVEL_BLOCK_SIZE, objectHighlightPosition.y + LEVEL_BLOCK_SIZE); glVertex2f(objectHighlightPosition.x, objectHighlightPosition.y + LEVEL_BLOCK_SIZE); glVertex2f(toolHighlightPosition.x, toolHighlightPosition.y); glVertex2f(toolHighlightPosition.x + LEVEL_BLOCK_SIZE, toolHighlightPosition.y); glVertex2f(toolHighlightPosition.x + LEVEL_BLOCK_SIZE, toolHighlightPosition.y + LEVEL_BLOCK_SIZE); glVertex2f(toolHighlightPosition.x, toolHighlightPosition.y + LEVEL_BLOCK_SIZE); glEnd(); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glBindBufferARB(GL_ARRAY_BUFFER, self->vertexBufferID); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, self->indexBufferID); vertexCount = indexCount = 0; getToolVertices(self, NULL, NULL, &vertexCount, &indexCount); textIndexCount = indexCount; getMenuItemVertices(self, NULL, NULL, &vertexCount, &indexCount); textIndexCount2 = indexCount; getConfirmationVertices(self, NULL, NULL, &vertexCount, &indexCount); glBufferDataARB(GL_ARRAY_BUFFER, sizeof(struct vertex_p2f_t2f_c4f) * vertexCount, NULL, GL_STREAM_DRAW); glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * indexCount, NULL, GL_STREAM_DRAW); vertices = glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY); indexes = glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); vertexCount = indexCount = 0; getToolVertices(self, vertices, indexes, &vertexCount, &indexCount); getMenuItemVertices(self, vertices, indexes, &vertexCount, &indexCount); getConfirmationVertices(self, vertices, indexes, &vertexCount, &indexCount); glUnmapBufferARB(GL_ARRAY_BUFFER); glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER); glVertexPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, position)); glTexCoordPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, texCoords)); glColorPointer(4, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, color)); self->spriteTexture->activate(self->spriteTexture); glDrawElements(GL_TRIANGLES, textIndexCount, GL_UNSIGNED_SHORT, 0); self->spriteTexture->deactivate(self->spriteTexture); GLTexture_activate(self->bitmapFont->atlas->texture); glDrawElements(GL_TRIANGLES, textIndexCount2 - textIndexCount, GL_UNSIGNED_SHORT, (void *) (ptrdiff_t) (textIndexCount * sizeof(GLushort))); GLTexture_deactivate(self->bitmapFont->atlas->texture); if (self->confirmState != CONFIRM_NONE) { glEnable(GL_BLEND); glColor4f(1.0f, 1.0f, 1.0f, 0.75f); glBegin(GL_QUADS); glVertex2f(0.0f, 0.0f); glVertex2f(640.0f, 0.0f); glVertex2f(640.0f, 480.0f); glVertex2f(0.0f, 480.0f); glEnd(); GLTexture_activate(self->bitmapFont->atlas->texture); glDrawElements(GL_TRIANGLES, indexCount - textIndexCount2, GL_UNSIGNED_SHORT, (void *) (ptrdiff_t) (textIndexCount2 * sizeof(GLushort))); GLTexture_deactivate(self->bitmapFont->atlas->texture); } glBindBufferARB(GL_ARRAY_BUFFER, 0); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); return true; } static bool confirmQuit(Atom eventID, void * eventData, void * context) { LevelEditorScreen * self = context; if (self->gameSession->hasUnsavedLevelChanges) { if (self->confirmState == CONFIRM_SAVE_BEFORE_QUIT) { Shell_systemBeep(); } else { self->confirmState = CONFIRM_SAVE_BEFORE_QUIT; self->confirmChoice = CONFIRM_CHOICE_YES; Shell_redisplay(); } } return false; } void LevelEditorScreen_activate(LevelEditorScreen * self) { EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_UP), mouseUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DRAGGED), mouseDragged, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), keyUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_DOWN), gamepadButtonDown, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_UP), gamepadButtonUp, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_AXIS_MOVE), gamepadAxisMove, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); EventDispatcher_registerForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_CONFIRM_QUIT), confirmQuit, self); EventDispatcher_registerForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); if (self->gameSession->testingLevel) { self->gameSession->testingLevel = false; } else { self->levelSet = LevelSetModel_copy(self->gameSession->levelSets[self->gameSession->selectedLevelSetIndex]); self->levelIndex = 0; self->view = LevelEditorView_create(self, self->gameSession->resourceManager); if (self->inspectorView->block != self->blockToPlace) { self->inspectorView->block = NULL; } } Shell_redisplay(); Shell_setCursorVisible(true); } void LevelEditorScreen_deactivate(LevelEditorScreen * self) { EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), mouseDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_UP), mouseUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DRAGGED), mouseDragged, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), keyDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), keyUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_DOWN), gamepadButtonDown, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_BUTTON_UP), gamepadButtonUp, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_GAMEPAD_AXIS_MOVE), gamepadAxisMove, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_DRAW), draw, self); EventDispatcher_unregisterForEvent(self->screenManager->eventDispatcher, ATOM(EVENT_CONFIRM_QUIT), confirmQuit, self); EventDispatcher_unregisterForEvent(self->inputController->eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDown, self); if (!self->gameSession->testingLevel) { LevelSetModel_dispose(self->levelSet); self->view->dispose(self->view); } }