static HandleInputResult HandleInput( int c, int m, int *xc, int *yc, int *xcOld, int *ycOld, Mission *scrap) { HandleInputResult result = { false, false, false, false }; Mission *mission = CampaignGetCurrentMission(&gCampaign); UIObject *o = NULL; brush.Pos = GetMouseTile(&gEventHandlers); // Find whether the mouse has hovered over a tooltip bool hadTooltip = sTooltipObj != NULL; if (!UITryGetObject(sObjs, gEventHandlers.mouse.currentPos, &sTooltipObj) || !sTooltipObj->Tooltip) { sTooltipObj = NULL; } // Need to redraw if we either had a tooltip (draw to remove) or there's a // tooltip to draw if (hadTooltip || sTooltipObj) { result.Redraw = true; } // Make sure a redraw is done immediately if the resolution changes // Otherwise the resolution change is ignored and we try to redraw // later, when the draw buffer has not yet been recreated if (gEventHandlers.HasResolutionChanged) { result.Redraw = true; } // Also need to redraw if the brush is active to update the highlight if (brush.IsActive) { result.Redraw = true; } if (m && (m == SDL_BUTTON_LEFT || m == SDL_BUTTON_RIGHT || m == SDL_BUTTON_WHEELUP || m == SDL_BUTTON_WHEELDOWN)) { result.Redraw = true; if (UITryGetObject(sObjs, gEventHandlers.mouse.currentPos, &o)) { if (!o->DoNotHighlight) { if (sLastHighlightedObj) { UIObjectUnhighlight(sLastHighlightedObj); } sLastHighlightedObj = o; UIObjectHighlight(o); } CArrayTerminate(&sDrawObjs); *xcOld = *xc; *ycOld = *yc; // Only change selection on left/right click if (m == SDL_BUTTON_LEFT || m == SDL_BUTTON_RIGHT) { if (!(o->Flags & UI_LEAVE_YC)) { *yc = o->Id; AdjustYC(yc); } if (!(o->Flags & UI_LEAVE_XC)) { *xc = o->Id2; AdjustXC(*yc, xc); } } if (!(o->Flags & UI_SELECT_ONLY) && (!(o->Flags & UI_SELECT_ONLY_FIRST) || (*xc == *xcOld && *yc == *ycOld))) { if (m == SDL_BUTTON_LEFT || m == SDL_BUTTON_WHEELUP) { c = SDLK_PAGEUP; } else if (m == SDL_BUTTON_RIGHT || m == SDL_BUTTON_WHEELDOWN) { c = SDLK_PAGEDOWN; } } } else { if (!(brush.IsActive && mission)) { UIObjectUnhighlight(sObjs); CArrayTerminate(&sDrawObjs); sLastHighlightedObj = NULL; } } } if (!o && (MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_LEFT) || MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_RIGHT))) { result.Redraw = true; if (brush.IsActive && mission->Type == MAPTYPE_STATIC) { // Draw a tile if (IsBrushPosValid(brush.Pos, mission)) { int isMain = MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_LEFT); EditorResult r = EditorBrushStartPainting(&brush, mission, isMain); if (r == EDITOR_RESULT_CHANGED || r == EDITOR_RESULT_CHANGED_AND_RELOAD) { fileChanged = 1; Autosave(); result.RemakeBg = true; sHasUnbakedChanges = true; } if (r == EDITOR_RESULT_CHANGED_AND_RELOAD) { Setup(0); } } } } else { if (mission) { // Clamp brush position brush.Pos = Vec2iClamp( brush.Pos, Vec2iZero(), Vec2iMinus(mission->Size, Vec2iUnit())); EditorResult r = EditorBrushStopPainting(&brush, mission); if (r == EDITOR_RESULT_CHANGED || r == EDITOR_RESULT_CHANGED_AND_RELOAD) { fileChanged = 1; Autosave(); result.Redraw = true; result.RemakeBg = true; sHasUnbakedChanges = true; } if (r == EDITOR_RESULT_CHANGED_AND_RELOAD) { Setup(0); } } } // Pan the camera based on keyboard cursor keys if (mission) { if (KeyIsDown(&gEventHandlers.keyboard, SDLK_LEFT)) { camera.x -= CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } else if (KeyIsDown(&gEventHandlers.keyboard, SDLK_RIGHT)) { camera.x += CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } if (KeyIsDown(&gEventHandlers.keyboard, SDLK_UP)) { camera.y -= CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } else if (KeyIsDown(&gEventHandlers.keyboard, SDLK_DOWN)) { camera.y += CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } // Also pan the camera based on middle mouse drag if (MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_MIDDLE)) { camera = Vec2iAdd(camera, Vec2iMinus( gEventHandlers.mouse.previousPos, gEventHandlers.mouse.currentPos)); result.Redraw = result.RemakeBg = true; } camera.x = CLAMP(camera.x, 0, Vec2iCenterOfTile(mission->Size).x); camera.y = CLAMP(camera.y, 0, Vec2iCenterOfTile(mission->Size).y); } bool hasQuit = false; if (gEventHandlers.keyboard.modState & (KMOD_ALT | KMOD_CTRL)) { result.Redraw = true; switch (c) { case 'z': // Undo // Do this by swapping the current mission with the last mission // This requires a bit of copy-acrobatics; because missions // are saved in Setup(), but by this stage the mission has already // changed, _two_ mission caches are used, copied in sequence. // That is, if the current mission is at state B, the first cache // is still at state B (copied after the mission has changed // already), and the second cache is at state A. // If we were to perform an undo and still maintain functionality, // we need to copy such that the states change from B,B,A to // A,A,B. // However! The above is true only if we have "baked" changes // The editor has been optimised to perform some changes // without reloading map files; that is, the files are actually // in states C,B,A. // In this case, another set of "acrobatics" is required if (sHasUnbakedChanges) { MissionCopy(&lastMission, mission); // B,A,Z -> B,A,B } else { MissionCopy(mission, &lastMission); // B,B,A -> A,B,A MissionCopy(&lastMission, ¤tMission); // A,B,A -> A,B,B } fileChanged = 1; Setup(0); // A,B,B -> A,A,B break; case 'x': MissionTerminate(scrap); MissionCopy(scrap, mission); Delete(*xc, *yc); break; case 'c': MissionTerminate(scrap); MissionCopy(scrap, mission); break; case 'v': // Use map size as a proxy to whether there's a valid scrap mission if (!Vec2iEqual(scrap->Size, Vec2iZero())) { InsertMission(&gCampaign, scrap, gCampaign.MissionIndex); fileChanged = 1; Setup(0); } break; case 'q': hasQuit = true; break; case 'n': InsertMission(&gCampaign, NULL, gCampaign.Setting.Missions.size); gCampaign.MissionIndex = gCampaign.Setting.Missions.size - 1; fileChanged = 1; Setup(0); break; case 'o': if (!fileChanged || ConfirmScreen( "File has been modified, but not saved", "Open anyway? (Y/N)")) { Open(); } break; case 's': Save(); break; case 'm': result.WillDisplayAutomap = true; break; case 'e': EditCharacters(&gCampaign.Setting); Setup(0); UIObjectUnhighlight(sObjs); CArrayTerminate(&sDrawObjs); break; } } else { if (c != 0) { result.Redraw = true; } switch (c) { case SDLK_F1: HelpScreen(); break; case SDLK_HOME: if (gCampaign.MissionIndex > 0) { gCampaign.MissionIndex--; } Setup(0); break; case SDLK_END: if (gCampaign.MissionIndex < (int)gCampaign.Setting.Missions.size) { gCampaign.MissionIndex++; } Setup(0); break; case SDLK_INSERT: switch (*yc) { case YC_CHARACTERS: if (gCampaign.Setting.characters.OtherChars.size > 0) { int ch = 0; CArrayPushBack(&mission->Enemies, &ch); CharacterStoreAddBaddie(&gCampaign.Setting.characters, ch); *xc = mission->Enemies.size - 1; } break; case YC_SPECIALS: if (gCampaign.Setting.characters.OtherChars.size > 0) { int ch = 0; CArrayPushBack(&mission->SpecialChars, &ch); CharacterStoreAddSpecial(&gCampaign.Setting.characters, ch); *xc = mission->SpecialChars.size - 1; } break; case YC_ITEMS: { int item = 0; CArrayPushBack(&mission->Items, &item); CArrayPushBack(&mission->ItemDensities, &item); *xc = mission->Items.size - 1; } break; default: if (*yc >= YC_OBJECTIVES) { AddObjective(mission); } else { InsertMission(&gCampaign, NULL, gCampaign.MissionIndex); } break; } fileChanged = 1; Setup(0); break; case SDLK_DELETE: Delete(*xc, *yc); break; case SDLK_PAGEUP: if (Change(o, *yc, 1)) { fileChanged = 1; } Setup(0); break; case SDLK_PAGEDOWN: if (Change(o, *yc, -1)) { fileChanged = 1; } Setup(0); break; case SDLK_ESCAPE: hasQuit = true; break; case SDLK_BACKSPACE: fileChanged |= UIObjectDelChar(sObjs); break; default: c = KeyGetTyped(&gEventHandlers.keyboard); if (c) { fileChanged |= UIObjectAddChar(sObjs, (char)c); } break; } } if (gEventHandlers.HasQuit) { hasQuit = true; } if (hasQuit && (!fileChanged || ConfirmScreen( "File has been modified, but not saved", "Quit anyway? (Y/N)"))) { result.Done = true; } return result; }
EditorResult EditorBrushStopPainting(EditorBrush *b, Mission *m) { EditorResult result = EDITOR_RESULT_NONE; if (b->IsPainting) { switch (b->Type) { case BRUSHTYPE_LINE: EditorBrushPaintLine(b, m); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_BOX: EditorBrushPaintBox(b, m, b->PaintType, MAP_UNSET); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_BOX_FILLED: EditorBrushPaintBox(b, m, b->PaintType, b->PaintType); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_ROOM: EditorBrushPaintBox(b, m, MAP_WALL, MAP_ROOM); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_ROOM_PAINTER: // Reload map to update tiles result = EDITOR_RESULT_RELOAD; break; case BRUSHTYPE_SELECT: if (b->IsMoving) { // Move the tiles from the source to the target // Need to copy all the tiles to a temp buffer first in case // we are moving to an overlapped position CArray movedTiles; Vec2i v; int i; int delta; CArrayInit(&movedTiles, sizeof(unsigned short)); // Copy tiles to temp from selection, setting them to MAP_FLOOR // in the process for (v.y = 0; v.y < b->SelectionSize.y; v.y++) { for (v.x = 0; v.x < b->SelectionSize.x; v.x++) { Vec2i vOffset = Vec2iAdd(v, b->SelectionStart); int idx = vOffset.y * m->Size.x + vOffset.x; unsigned short *tile = CArrayGet( &m->u.Static.Tiles, idx); CArrayPushBack(&movedTiles, tile); *tile = MAP_FLOOR; } } // Move the selection to the new position b->SelectionStart.x += b->Pos.x - b->DragPos.x; b->SelectionStart.y += b->Pos.y - b->DragPos.y; // Copy tiles to the new area, for parts of the new area that // are valid i = 0; for (v.y = 0; v.y < b->SelectionSize.y; v.y++) { for (v.x = 0; v.x < b->SelectionSize.x; v.x++) { Vec2i vOffset = Vec2iAdd(v, b->SelectionStart); if (vOffset.x >= 0 && vOffset.x < m->Size.x && vOffset.y >= 0 && vOffset.y < m->Size.y) { int idx = vOffset.y * m->Size.x + vOffset.x; unsigned short *tileFrom = CArrayGet(&movedTiles, i); unsigned short *tileTo = CArrayGet( &m->u.Static.Tiles, idx); *tileTo = *tileFrom; result = EDITOR_RESULT_CHANGED_AND_RELOAD; } i++; } } // Update the selection to fit within map boundaries delta = -b->SelectionStart.x; if (delta > 0) { b->SelectionStart.x += delta; b->SelectionSize.x -= delta; } delta = -b->SelectionStart.y; if (delta > 0) { b->SelectionStart.y += delta; b->SelectionSize.y -= delta; } delta = b->SelectionStart.x + b->SelectionSize.x - m->Size.x; if (delta > 0) { b->SelectionSize.x -= delta; } delta = b->SelectionStart.y + b->SelectionSize.y - m->Size.y; if (delta > 0) { b->SelectionSize.y -= delta; } // Check if the selection is still valid; if not, invalidate it if (b->SelectionSize.x < 0 || b->SelectionSize.y < 0) { b->SelectionSize = Vec2iZero(); } b->IsMoving = 0; } else { // Record the selection size b->SelectionStart = Vec2iMin(b->LastPos, b->Pos); b->SelectionSize.x = abs(b->LastPos.x - b->Pos.x) + 1; b->SelectionSize.y = abs(b->LastPos.y - b->Pos.y) + 1; // Disallow 1x1 selection sizes if (b->SelectionSize.x <= 1 && b->SelectionSize.y <= 1) { b->SelectionSize = Vec2iZero(); } } break; case BRUSHTYPE_SET_EXIT: { Vec2i exitStart = Vec2iMin(b->LastPos, b->Pos); Vec2i exitEnd = Vec2iMax(b->LastPos, b->Pos); // Clamp within map boundaries exitStart = Vec2iClamp( exitStart, Vec2iZero(), Vec2iMinus(m->Size, Vec2iUnit())); exitEnd = Vec2iClamp( exitEnd, Vec2iZero(), Vec2iMinus(m->Size, Vec2iUnit())); // Check that size is big enough Vec2i size = Vec2iAdd(Vec2iMinus(exitEnd, exitStart), Vec2iUnit()); if (size.x >= 3 && size.y >= 3) { // Check that exit area has changed if (!Vec2iEqual(exitStart, m->u.Static.Exit.Start) || !Vec2iEqual(exitEnd, m->u.Static.Exit.End)) { m->u.Static.Exit.Start = exitStart; m->u.Static.Exit.End = exitEnd; result = EDITOR_RESULT_CHANGED_AND_RELOAD; } } } break; default: // do nothing break; } } b->IsPainting = 0; CArrayClear(&b->HighlightedTiles); return result; }