static void DrawEditorTiles(DrawBuffer *b, Vec2i offset) { Vec2i pos; Tile *tile = &b->tiles[0][0]; pos.y = b->dy + offset.y; for (int y = 0; y < Y_TILES; y++, pos.y += TILE_HEIGHT) { pos.x = b->dx + offset.x; for (int x = 0; x < b->Size.x; x++, tile++, pos.x += TILE_WIDTH) { if (gMission.missionData->Type == MAPTYPE_STATIC) { Vec2i start = gMission.missionData->u.Static.Start; if (!Vec2iEqual(start, Vec2iZero()) && Vec2iEqual(start, Vec2iNew(x + b->xStart, y + b->yStart))) { // mission start BlitMasked( &gGraphicsDevice, PicManagerGetPic(&gPicManager, "editor/start"), pos, colorWhite, 1); } } } tile += X_TILES - b->Size.x; } }
int AIGoto(TActor *actor, Vec2i p, bool ignoreObjects) { Vec2i a = Vec2iFull2Real(actor->Pos); Vec2i currentTile = Vec2iToTile(a); Vec2i goalTile = Vec2iToTile(p); AIGotoContext *c = &actor->aiContext->Goto; // If we are already there, bail // This can happen if AI is trying to track the player, // but the player has died, for example. if (Vec2iEqual(currentTile, goalTile)) { return 0; } // If we are currently following an A* path, // and it is still valid, keep following it until // we have reached a new tile if (c && c->IsFollowing && AStarCloseToPath(c, currentTile, goalTile)) { return AStarFollow(c, currentTile, &actor->tileItem, a); } else if (AIHasClearPath(a, p, ignoreObjects)) { // Simple case: if there's a clear line between AI and target, // walk straight towards it return AIGotoDirect(a, p); } else { // We need to recalculate A* AStarContext ac; ac.Map = &gMap; ac.IsTileOk = ignoreObjects ? IsTileWalkable : IsTileWalkableAroundObjects; // First, if the goal tile is blocked itself, // find a nearby tile that can be walked to c->Goal = MapSearchTileAround(ac.Map, goalTile, ac.IsTileOk); c->PathIndex = 1; // start navigating to the next path node ASPathDestroy(c->Path); c->Path = ASPathCreate( &cPathNodeSource, &ac, ¤tTile, &c->Goal); // In case we can't calculate A* for some reason, // try simple navigation again if (ASPathGetCount(c->Path) <= 1) { debug( D_MAX, "Error: can't calculate path from {%d, %d} to {%d, %d}", currentTile.x, currentTile.y, goalTile.x, goalTile.y); return AIGotoDirect(a, p); } return AStarFollow(c, currentTile, &actor->tileItem, a); } }
static void MakeBackground(GraphicsDevice *g, int buildTables) { if (buildTables) { // Automatically pan camera to middle of screen Mission *m = gMission.missionData; Vec2i focusTile = Vec2iScaleDiv(m->Size, 2); // Better yet, if the map has a known start position, focus on that if (m->Type == MAPTYPE_STATIC && !Vec2iEqual(m->u.Static.Start, Vec2iZero())) { focusTile = m->u.Static.Start; } camera = Vec2iCenterOfTile(focusTile); } // Clear background first for (int i = 0; i < GraphicsGetScreenSize(&g->cachedConfig); i++) { g->buf[i] = PixelFromColor(g, colorBlack); } GrafxDrawExtra extra; extra.guideImage = brush.GuideImageSurface; extra.guideImageAlpha = brush.GuideImageAlpha; DrawBufferTerminate(&sDrawBuffer); DrawBufferInit(&sDrawBuffer, Vec2iNew(X_TILES, Y_TILES), &gGraphicsDevice); GrafxMakeBackground( g, &sDrawBuffer, &gCampaign, &gMission, &gMap, tintNone, 1, buildTables, camera, &extra); }
void UIButtonSetPic(UIObject *o, Pic *pic) { assert(o->Type == UITYPE_BUTTON && "invalid UI type"); o->u.Button.Pic = pic; if (Vec2iEqual(o->Size, Vec2iZero())) { o->Size = o->u.Button.Pic->size; } }
// Follow the current A* path static int AStarFollow( AIGotoContext *c, Vec2i currentTile, TTileItem *i, Vec2i a) { Vec2i *pathTile = ASPathGetNode(c->Path, c->PathIndex); c->IsFollowing = 1; // Check if we need to follow the next step in the path // Note: need to make sure the actor is fully within the current tile // otherwise it may get stuck at corners if (Vec2iEqual(currentTile, *pathTile) && IsTileItemInsideTile(i, currentTile)) { c->PathIndex++; pathTile = ASPathGetNode(c->Path, c->PathIndex); c->IsFollowing = 0; } // Go directly to the center of the next tile return AIGotoDirect(a, Vec2iCenterOfTile(*pathTile)); }
void GraphicsConfigSet( GraphicsConfig *c, const Vec2i res, const bool fullscreen, const int scaleFactor) { if (!Vec2iEqual(res, c->Res)) { c->Res = res; c->needRestart = true; } #define SET(_lhs, _rhs) \ if ((_lhs) != (_rhs)) \ { \ (_lhs) = (_rhs); \ c->needRestart = true; \ } SET(c->Fullscreen, fullscreen); SET(c->ScaleFactor, scaleFactor); }
void GraphicsConfigSet( GraphicsConfig *c, const Vec2i res, const bool fullscreen, const int scaleFactor, const ScaleMode scaleMode, const int brightness) { if (!Vec2iEqual(res, c->Res)) { c->Res = res; c->RestartFlags |= RESTART_RESOLUTION; } #define SET(_lhs, _rhs, _flag) \ if ((_lhs) != (_rhs)) \ { \ (_lhs) = (_rhs); \ c->RestartFlags |= (_flag); \ } SET(c->Fullscreen, fullscreen, RESTART_RESOLUTION); SET(c->ScaleFactor, scaleFactor, RESTART_RESOLUTION); SET(c->ScaleMode, scaleMode, RESTART_SCALE_MODE); SET(c->Brightness, brightness, RESTART_BRIGHTNESS); }
static bool TryRemoveMapObjectAt(const Vec2i pos, CArray *objs) { for (int i = 0; i < (int)objs->size; i++) { MapObjectPositions *mop = CArrayGet(objs, i); for (int j = 0; j < (int)mop->Positions.size; j++) { const Vec2i *mopPos = CArrayGet(&mop->Positions, j); if (Vec2iEqual(*mopPos, pos)) { CArrayDelete(&mop->Positions, j); if (mop->Positions.size == 0) { CArrayTerminate(&mop->Positions); CArrayDelete(objs, i); } return true; } } } return false; }
static Vec2i SeekTowards( const Vec2i pos, const Vec2i vel, const double speedMin, const Vec2i targetPos, const int seekFactor) { // Compensate for bullet's velocity const Vec2i targetVel = Vec2iMinus(Vec2iMinus(targetPos, pos), vel); // Don't seek if the coordinates are too big if (abs(targetVel.x) > 10000 || abs(targetVel.y) > 10000 || Vec2iIsZero(targetVel)) { return vel; } const double targetMag = sqrt( targetVel.x*targetVel.x + targetVel.y*targetVel.y); const double magnitude = MAX(speedMin, Vec2iEqual(vel, Vec2iZero()) ? speedMin : sqrt(vel.x*vel.x + vel.y*vel.y)); const double combinedX = vel.x / magnitude * seekFactor + targetVel.x / targetMag; const double combinedY = vel.y / magnitude * seekFactor + targetVel.y / targetMag; return Vec2iNew( (int)round(combinedX * magnitude / (seekFactor + 1)), (int)round(combinedY * magnitude / (seekFactor + 1))); }
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; }
int MouseHasMoved(Mouse *mouse) { return !Vec2iEqual(mouse->previousPos, mouse->currentPos); }