1#include "map_view.h"
2
3#include "editor.h"
4
5#include <engine/graphics.h>
6#include <engine/keys.h>
7#include <engine/shared/config.h>
8
9#include <game/client/ui.h>
10#include <game/editor/editor_actions.h>
11#include <game/editor/explanations.h>
12
13void CMapView::CState::Reset(CEditor *pEditor)
14{
15 m_Zoom = CSmoothValue(200.0f, 10.0f, 2000.0f);
16 m_Zoom.OnInit(pEditor);
17 m_WorldZoom = 1.0f;
18 m_WorldOffset = vec2(0.0f, 0.0f);
19 m_EditorOffset = vec2(0.0f, 0.0f);
20 m_MouseWorldScale = 1.0f;
21 m_MouseWorldPos = vec2(0.0f, 0.0f);
22 m_MouseWorldNoParaPos = vec2(0.0f, 0.0f);
23 m_MouseDeltaWorld = vec2(0.0f, 0.0f);
24 m_ActiveOp = EActiveOp::NONE;
25}
26
27void CMapView::OnInit(CEditor *pEditor)
28{
29 CEditorComponent::OnInit(pEditor);
30 RegisterSubComponent(Component&: m_MapGrid);
31 RegisterSubComponent(Component&: m_ProofMode);
32 InitSubComponents();
33}
34
35void CMapView::OnMapLoad()
36{
37 m_ProofMode.OnMapLoad();
38}
39
40bool CMapView::IsFocused()
41{
42 return GetWorldOffset() == (m_ProofMode.IsModeMenu() ? m_ProofMode.CurrentMenuBackgroundPosition() : vec2(0.0f, 0.0f));
43}
44
45void CMapView::Focus()
46{
47 SetWorldOffset(m_ProofMode.IsModeMenu() ? m_ProofMode.CurrentMenuBackgroundPosition() : vec2(0.0f, 0.0f));
48}
49
50void CMapView::RenderGroupBorder()
51{
52 std::shared_ptr<CLayerGroup> pGroup = Map()->SelectedGroup();
53 if(pGroup)
54 {
55 pGroup->MapScreen();
56
57 for(size_t i = 0; i < Map()->m_vSelectedLayers.size(); i++)
58 {
59 std::shared_ptr<CLayer> pLayer = Map()->SelectedLayerType(Index: i, Type: LAYERTYPE_TILES);
60 if(pLayer)
61 {
62 CUIRect BorderRect;
63 BorderRect.x = 0.0f;
64 BorderRect.y = 0.0f;
65 pLayer->GetSize(pWidth: &BorderRect.w, pHeight: &BorderRect.h);
66 BorderRect.DrawOutline(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
67 }
68 }
69 }
70}
71
72void CMapView::RenderEditorMap()
73{
74 if(Editor()->m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->ShiftIsPressed() && !Input()->ModifierIsPressed() && Input()->KeyPress(Key: KEY_G))
75 {
76 const bool AnyHidden =
77 !Map()->m_pGameLayer->m_Visible ||
78 (Map()->m_pFrontLayer && !Map()->m_pFrontLayer->m_Visible) ||
79 (Map()->m_pTeleLayer && !Map()->m_pTeleLayer->m_Visible) ||
80 (Map()->m_pSpeedupLayer && !Map()->m_pSpeedupLayer->m_Visible) ||
81 (Map()->m_pTuneLayer && !Map()->m_pTuneLayer->m_Visible) ||
82 (Map()->m_pSwitchLayer && !Map()->m_pSwitchLayer->m_Visible);
83 Map()->m_pGameLayer->m_Visible = AnyHidden;
84 if(Map()->m_pFrontLayer)
85 Map()->m_pFrontLayer->m_Visible = AnyHidden;
86 if(Map()->m_pTeleLayer)
87 Map()->m_pTeleLayer->m_Visible = AnyHidden;
88 if(Map()->m_pSpeedupLayer)
89 Map()->m_pSpeedupLayer->m_Visible = AnyHidden;
90 if(Map()->m_pTuneLayer)
91 Map()->m_pTuneLayer->m_Visible = AnyHidden;
92 if(Map()->m_pSwitchLayer)
93 Map()->m_pSwitchLayer->m_Visible = AnyHidden;
94 }
95
96 for(auto &pGroup : Map()->m_vpGroups)
97 {
98 if(pGroup->m_Visible)
99 pGroup->Render(pRenderMap: Map());
100 }
101
102 // render the game, tele, speedup, front, tune and switch above everything else
103 if(Map()->m_pGameGroup->m_Visible)
104 {
105 Map()->m_pGameGroup->MapScreen();
106 for(auto &pLayer : Map()->m_pGameGroup->m_vpLayers)
107 {
108 if(pLayer->m_Visible && pLayer->IsEntitiesLayer())
109 pLayer->Render(pRenderMap: Map());
110 }
111 }
112
113 std::shared_ptr<CLayerTiles> pSelectedTilesLayer = std::static_pointer_cast<CLayerTiles>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
114 if(Editor()->m_ShowTileInfo != CEditor::SHOW_TILE_OFF && pSelectedTilesLayer && pSelectedTilesLayer->m_Visible && Zoom()->GetValue() <= 300.0f)
115 {
116 Map()->SelectedGroup()->MapScreen();
117 pSelectedTilesLayer->ShowInfo();
118 }
119}
120
121void CMapView::Render(CUIRect View)
122{
123 // render all good stuff
124 if(!Editor()->m_ShowPicker)
125 {
126 RenderEditorMap();
127 }
128 else
129 {
130 // fix aspect ratio of the image in the picker
131 float Max = std::min(a: View.w, b: View.h);
132 View.w = View.h = Max;
133 }
134
135 const bool Inside = Ui()->MouseInside(pRect: &View);
136
137 // fetch mouse position
138 float wx = MouseWorldPos().x;
139 float wy = MouseWorldPos().y;
140 float mx = Ui()->MouseX();
141 float my = Ui()->MouseY();
142
143 static float s_StartWx = 0;
144 static float s_StartWy = 0;
145
146 // remap the screen so it can display the whole tileset
147 if(Editor()->m_ShowPicker)
148 {
149 CUIRect Screen = *Ui()->Screen();
150 float Size = 32.0f * 16.0f;
151 float w = Size * (Screen.w / View.w);
152 float h = Size * (Screen.h / View.h);
153 float x = -(View.x / Screen.w) * w;
154 float y = -(View.y / Screen.h) * h;
155 wx = x + w * mx / Screen.w;
156 wy = y + h * my / Screen.h;
157 std::shared_ptr<CLayerTiles> pTileLayer = std::static_pointer_cast<CLayerTiles>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
158 if(pTileLayer)
159 {
160 CScreenRect ScreenRect(x, y, w, h);
161 Graphics()->MapScreen(ScreenRect);
162 Editor()->m_pTilesetPicker->m_Image = pTileLayer->m_Image;
163 if(Editor()->m_BrushColorEnabled)
164 {
165 Editor()->m_pTilesetPicker->m_Color = pTileLayer->m_Color;
166 Editor()->m_pTilesetPicker->m_Color.a = 255;
167 }
168 else
169 {
170 Editor()->m_pTilesetPicker->m_Color = {255, 255, 255, 255};
171 }
172
173 Editor()->m_pTilesetPicker->m_HasGame = pTileLayer->m_HasGame;
174 Editor()->m_pTilesetPicker->m_HasTele = pTileLayer->m_HasTele;
175 Editor()->m_pTilesetPicker->m_HasSpeedup = pTileLayer->m_HasSpeedup;
176 Editor()->m_pTilesetPicker->m_HasFront = pTileLayer->m_HasFront;
177 Editor()->m_pTilesetPicker->m_HasSwitch = pTileLayer->m_HasSwitch;
178 Editor()->m_pTilesetPicker->m_HasTune = pTileLayer->m_HasTune;
179
180 Editor()->m_pTilesetPicker->Render(pRenderMap: Map());
181
182 if(Editor()->m_ShowTileInfo != CEditor::SHOW_TILE_OFF)
183 Editor()->m_pTilesetPicker->ShowInfo();
184
185 str_copy(dst&: Editor()->m_aTooltip, src: "Click or drag left mouse button to create a brush. Hover individual tiles for explanation.");
186 }
187 else
188 {
189 std::shared_ptr<CLayerQuads> pQuadLayer = std::static_pointer_cast<CLayerQuads>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
190 if(pQuadLayer)
191 {
192 Editor()->m_pQuadsetPicker->m_Image = pQuadLayer->m_Image;
193 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[0].x = f2fx(v: View.x);
194 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[0].y = f2fx(v: View.y);
195 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[1].x = f2fx(v: (View.x + View.w));
196 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[1].y = f2fx(v: View.y);
197 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[2].x = f2fx(v: View.x);
198 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[2].y = f2fx(v: (View.y + View.h));
199 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[3].x = f2fx(v: (View.x + View.w));
200 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[3].y = f2fx(v: (View.y + View.h));
201 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[4].x = f2fx(v: (View.x + View.w / 2));
202 Editor()->m_pQuadsetPicker->m_vQuads[0].m_aPoints[4].y = f2fx(v: (View.y + View.h / 2));
203 Editor()->m_pQuadsetPicker->Render(pRenderMap: Map());
204 }
205 }
206 }
207
208 // draw layer borders
209 std::pair<int, std::shared_ptr<CLayer>> apEditLayers[128];
210 size_t NumEditLayers = 0;
211
212 if(Editor()->m_ShowPicker && Map()->SelectedLayer(Index: 0) && Map()->SelectedLayer(Index: 0)->m_Type == LAYERTYPE_TILES)
213 {
214 apEditLayers[0] = {0, Editor()->m_pTilesetPicker};
215 NumEditLayers++;
216 }
217 else if(Editor()->m_ShowPicker)
218 {
219 apEditLayers[0] = {0, Editor()->m_pQuadsetPicker};
220 NumEditLayers++;
221 }
222 else
223 {
224 // pick a type of layers to edit, preferring Tiles layers.
225 int EditingType = -1;
226 for(size_t i = 0; i < Map()->m_vSelectedLayers.size(); i++)
227 {
228 std::shared_ptr<CLayer> pLayer = Map()->SelectedLayer(Index: i);
229 if(pLayer && (EditingType == -1 || pLayer->m_Type == LAYERTYPE_TILES))
230 {
231 EditingType = pLayer->m_Type;
232 if(EditingType == LAYERTYPE_TILES)
233 break;
234 }
235 }
236 for(size_t i = 0; i < Map()->m_vSelectedLayers.size() && NumEditLayers < 128; i++)
237 {
238 apEditLayers[NumEditLayers] = {Map()->m_vSelectedLayers[i], Map()->SelectedLayerType(Index: i, Type: EditingType)};
239 if(apEditLayers[NumEditLayers].second)
240 {
241 NumEditLayers++;
242 }
243 }
244
245 RenderGroupBorder();
246 MapGrid()->Render();
247 }
248
249 const bool ShouldPan = Ui()->HotItem() == Editor()->MapView() && ((Input()->ModifierIsPressed() && Ui()->MouseButton(Index: 0)) || Ui()->MouseButton(Index: 2));
250 if(Editor()->m_pContainerPanned == Editor()->MapView())
251 {
252 // do panning
253 if(ShouldPan)
254 {
255 if(Input()->ShiftIsPressed())
256 Map()->m_MapViewState.m_ActiveOp = EActiveOp::PAN_EDITOR;
257 else
258 Map()->m_MapViewState.m_ActiveOp = EActiveOp::PAN_WORLD;
259 Ui()->SetActiveItem(Editor()->MapView());
260 }
261 else
262 Map()->m_MapViewState.m_ActiveOp = EActiveOp::NONE;
263
264 if(Map()->m_MapViewState.m_ActiveOp == EActiveOp::PAN_WORLD)
265 OffsetWorld(Offset: -Ui()->MouseDelta() * MouseWorldScale());
266 else if(Map()->m_MapViewState.m_ActiveOp == EActiveOp::PAN_EDITOR)
267 OffsetEditor(Offset: -Ui()->MouseDelta() * MouseWorldScale());
268
269 if(Map()->m_MapViewState.m_ActiveOp == EActiveOp::NONE)
270 Editor()->m_pContainerPanned = nullptr;
271 }
272
273 if(Inside)
274 {
275 Ui()->SetHotItem(Editor()->MapView());
276
277 // do global operations like pan and zoom
278 if(Ui()->CheckActiveItem(pId: nullptr) && (Ui()->MouseButton(Index: 0) || Ui()->MouseButton(Index: 2)))
279 {
280 s_StartWx = wx;
281 s_StartWy = wy;
282
283 if(ShouldPan && Editor()->m_pContainerPanned == nullptr)
284 Editor()->m_pContainerPanned = Editor()->MapView();
285 }
286
287 // brush editing
288 if(Ui()->HotItem() == Editor()->MapView())
289 {
290 if(Editor()->m_ShowPicker)
291 {
292 std::shared_ptr<CLayer> pLayer = Map()->SelectedLayer(Index: 0);
293 int Layer;
294 if(pLayer == Map()->m_pGameLayer)
295 Layer = LAYER_GAME;
296 else if(pLayer == Map()->m_pFrontLayer)
297 Layer = LAYER_FRONT;
298 else if(pLayer == Map()->m_pSwitchLayer)
299 Layer = LAYER_SWITCH;
300 else if(pLayer == Map()->m_pTeleLayer)
301 Layer = LAYER_TELE;
302 else if(pLayer == Map()->m_pSpeedupLayer)
303 Layer = LAYER_SPEEDUP;
304 else if(pLayer == Map()->m_pTuneLayer)
305 Layer = LAYER_TUNE;
306 else
307 Layer = NUM_LAYERS;
308
309 CExplanations::EGametype ExplanationGametype;
310 if(Editor()->m_SelectEntitiesImage == "DDNet")
311 ExplanationGametype = CExplanations::EGametype::DDNET;
312 else if(Editor()->m_SelectEntitiesImage == "FNG")
313 ExplanationGametype = CExplanations::EGametype::FNG;
314 else if(Editor()->m_SelectEntitiesImage == "Race")
315 ExplanationGametype = CExplanations::EGametype::RACE;
316 else if(Editor()->m_SelectEntitiesImage == "Vanilla")
317 ExplanationGametype = CExplanations::EGametype::VANILLA;
318 else if(Editor()->m_SelectEntitiesImage == "blockworlds")
319 ExplanationGametype = CExplanations::EGametype::BLOCKWORLDS;
320 else
321 ExplanationGametype = CExplanations::EGametype::NONE;
322
323 if(Layer != NUM_LAYERS)
324 {
325 const char *pExplanation = CExplanations::Explain(Gametype: ExplanationGametype, Tile: (int)wx / 32 + (int)wy / 32 * 16, Layer);
326 if(pExplanation)
327 str_copy(dst&: Editor()->m_aTooltip, src: pExplanation);
328 }
329 }
330 else if(Editor()->m_pBrush->IsEmpty() && Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS) != nullptr)
331 str_copy(dst&: Editor()->m_aTooltip, src: "Use left mouse button to drag and create a brush. Hold shift to select multiple quads. Press R to rotate selected quads. Use ctrl+right click to select layer.");
332 else if(Editor()->m_pBrush->IsEmpty())
333 {
334 if(g_Config.m_EdLayerSelector)
335 str_copy(dst&: Editor()->m_aTooltip, src: "Use left mouse button to drag and create a brush. Use ctrl+right click to select layer of hovered tile.");
336 else
337 str_copy(dst&: Editor()->m_aTooltip, src: "Use left mouse button to drag and create a brush.");
338 }
339 else
340 {
341 // Alt behavior handled in CEditor::MouseAxisLock
342 str_copy(dst&: Editor()->m_aTooltip, src: "Use left mouse button to paint with the brush. Right click to clear the brush. Hold Alt to lock the mouse movement to a single axis.");
343 }
344
345 if(Ui()->CheckActiveItem(pId: Editor()->MapView()))
346 {
347 CUIRect r;
348 r.x = s_StartWx;
349 r.y = s_StartWy;
350 r.w = wx - s_StartWx;
351 r.h = wy - s_StartWy;
352 if(r.w < 0)
353 {
354 r.x += r.w;
355 r.w = -r.w;
356 }
357
358 if(r.h < 0)
359 {
360 r.y += r.h;
361 r.h = -r.h;
362 }
363
364 if(Map()->m_MapViewState.m_ActiveOp == EActiveOp::BRUSH_DRAW)
365 {
366 if(!Editor()->m_pBrush->IsEmpty())
367 {
368 // draw with brush
369 for(size_t k = 0; k < NumEditLayers; k++)
370 {
371 size_t BrushIndex = k % Editor()->m_pBrush->m_vpLayers.size();
372 if(apEditLayers[k].second->m_Type == Editor()->m_pBrush->m_vpLayers[BrushIndex]->m_Type)
373 {
374 if(apEditLayers[k].second->m_Type == LAYERTYPE_TILES)
375 {
376 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: apEditLayers[k].second);
377 std::shared_ptr<CLayerTiles> pBrushLayer = std::static_pointer_cast<CLayerTiles>(r: Editor()->m_pBrush->m_vpLayers[BrushIndex]);
378
379 if((!pLayer->m_HasTele || pBrushLayer->m_HasTele) && (!pLayer->m_HasSpeedup || pBrushLayer->m_HasSpeedup) && (!pLayer->m_HasFront || pBrushLayer->m_HasFront) && (!pLayer->m_HasGame || pBrushLayer->m_HasGame) && (!pLayer->m_HasSwitch || pBrushLayer->m_HasSwitch) && (!pLayer->m_HasTune || pBrushLayer->m_HasTune))
380 pLayer->BrushDraw(pBrush: pBrushLayer.get(), WorldPos: vec2(wx, wy));
381 }
382 else
383 {
384 apEditLayers[k].second->BrushDraw(pBrush: Editor()->m_pBrush->m_vpLayers[BrushIndex].get(), WorldPos: vec2(wx, wy));
385 }
386 }
387 }
388 }
389 }
390 else if(Map()->m_MapViewState.m_ActiveOp == EActiveOp::BRUSH_GRAB)
391 {
392 if(!Ui()->MouseButton(Index: 0))
393 {
394 std::shared_ptr<CLayerQuads> pQuadLayer = std::static_pointer_cast<CLayerQuads>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
395 if(Input()->ShiftIsPressed() && pQuadLayer)
396 {
397 Map()->DeselectQuads();
398 for(size_t i = 0; i < pQuadLayer->m_vQuads.size(); i++)
399 {
400 const CQuad &Quad = pQuadLayer->m_vQuads[i];
401 vec2 Position = vec2(fx2f(v: Quad.m_aPoints[4].x), fx2f(v: Quad.m_aPoints[4].y));
402 if(r.Inside(Point: Position) && !Map()->IsQuadSelected(Index: i))
403 Map()->ToggleSelectQuad(Index: i);
404 }
405 }
406 else
407 {
408 // TODO: do all layers
409 int Grabs = 0;
410 for(size_t k = 0; k < NumEditLayers; k++)
411 Grabs += apEditLayers[k].second->BrushGrab(pBrush: Editor()->m_pBrush.get(), Rect: r);
412 if(Grabs == 0)
413 Editor()->m_pBrush->Clear();
414
415 Editor()->m_ShowPickerToggle = false; // Close the tile picker after grabbing brush if it was toggled open
416 Map()->DeselectQuads();
417 Map()->DeselectQuadPoints();
418 }
419 }
420 else
421 {
422 if(NumEditLayers > 0)
423 {
424 apEditLayers[0].second->BrushSelecting(Rect: r);
425 }
426 Ui()->MapScreen();
427 }
428 }
429 else if(Map()->m_MapViewState.m_ActiveOp == EActiveOp::BRUSH_PAINT)
430 {
431 if(!Ui()->MouseButton(Index: 0))
432 {
433 for(size_t k = 0; k < NumEditLayers; k++)
434 {
435 size_t BrushIndex = k;
436 if(Editor()->m_pBrush->m_vpLayers.size() != NumEditLayers)
437 BrushIndex = 0;
438 std::shared_ptr<CLayer> pBrush = Editor()->m_pBrush->IsEmpty() ? nullptr : Editor()->m_pBrush->m_vpLayers[BrushIndex];
439 apEditLayers[k].second->FillSelection(Empty: Editor()->m_pBrush->IsEmpty(), pBrush: pBrush.get(), Rect: r);
440 }
441 std::shared_ptr<IEditorAction> Action = std::make_shared<CEditorBrushDrawAction>(args: Map(), args&: Map()->m_SelectedGroup);
442 Map()->m_EditorHistory.RecordAction(pAction: Action);
443 }
444 else
445 {
446 if(NumEditLayers > 0)
447 {
448 apEditLayers[0].second->BrushSelecting(Rect: r);
449 }
450 Ui()->MapScreen();
451 }
452 }
453 }
454 else
455 {
456 if(Ui()->MouseButton(Index: 1))
457 {
458 Editor()->m_pBrush->Clear();
459 }
460
461 if(!Input()->ModifierIsPressed() && Ui()->MouseButton(Index: 0) && Map()->m_MapViewState.m_ActiveOp == EActiveOp::NONE && !Editor()->QuadKnife()->IsActive())
462 {
463 Ui()->SetActiveItem(Editor()->MapView());
464
465 if(Editor()->m_pBrush->IsEmpty())
466 Map()->m_MapViewState.m_ActiveOp = EActiveOp::BRUSH_GRAB;
467 else
468 {
469 Map()->m_MapViewState.m_ActiveOp = EActiveOp::BRUSH_DRAW;
470 for(size_t k = 0; k < NumEditLayers; k++)
471 {
472 size_t BrushIndex = k;
473 if(Editor()->m_pBrush->m_vpLayers.size() != NumEditLayers)
474 BrushIndex = 0;
475
476 if(apEditLayers[k].second->m_Type == Editor()->m_pBrush->m_vpLayers[BrushIndex]->m_Type)
477 apEditLayers[k].second->BrushPlace(pBrush: Editor()->m_pBrush->m_vpLayers[BrushIndex].get(), WorldPos: vec2(wx, wy));
478 }
479 }
480
481 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
482 if(Input()->ShiftIsPressed() && pLayer)
483 Map()->m_MapViewState.m_ActiveOp = EActiveOp::BRUSH_PAINT;
484 }
485
486 if(!Editor()->m_pBrush->IsEmpty())
487 {
488 Editor()->m_pBrush->m_OffsetX = -(int)wx;
489 Editor()->m_pBrush->m_OffsetY = -(int)wy;
490 for(const auto &pLayer : Editor()->m_pBrush->m_vpLayers)
491 {
492 if(pLayer->m_Type == LAYERTYPE_TILES)
493 {
494 Editor()->m_pBrush->m_OffsetX = -(int)(wx / 32.0f) * 32;
495 Editor()->m_pBrush->m_OffsetY = -(int)(wy / 32.0f) * 32;
496 break;
497 }
498 }
499
500 std::shared_ptr<CLayerGroup> pGroup = Map()->SelectedGroup();
501 if(!Editor()->m_ShowPicker && pGroup)
502 {
503 Editor()->m_pBrush->m_OffsetX += pGroup->m_OffsetX;
504 Editor()->m_pBrush->m_OffsetY += pGroup->m_OffsetY;
505 Editor()->m_pBrush->m_ParallaxX = pGroup->m_ParallaxX;
506 Editor()->m_pBrush->m_ParallaxY = pGroup->m_ParallaxY;
507 Editor()->m_pBrush->Render(pRenderMap: Map());
508
509 CUIRect BorderRect;
510 BorderRect.x = 0.0f;
511 BorderRect.y = 0.0f;
512 Editor()->m_pBrush->GetSize(pWidth: &BorderRect.w, pHeight: &BorderRect.h);
513 BorderRect.DrawOutline(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
514 }
515 }
516 }
517 }
518
519 // quad & sound editing
520 {
521 if(!Editor()->m_ShowPicker && Editor()->m_pBrush->IsEmpty())
522 {
523 // fetch layers
524 std::shared_ptr<CLayerGroup> pGroup = Map()->SelectedGroup();
525 if(pGroup)
526 pGroup->MapScreen();
527
528 for(size_t k = 0; k < NumEditLayers; k++)
529 {
530 auto &[LayerIndex, pEditLayer] = apEditLayers[k];
531
532 if(pEditLayer->m_Type == LAYERTYPE_QUADS)
533 {
534 std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(r: pEditLayer);
535
536 if(Editor()->m_ActiveEnvelopePreview == CEditor::EEnvelopePreview::NONE)
537 Editor()->m_ActiveEnvelopePreview = CEditor::EEnvelopePreview::ALL;
538
539 if(Editor()->QuadKnife()->IsActive())
540 {
541 Editor()->QuadKnife()->DoSlice();
542 }
543 else
544 {
545 Editor()->UpdateHotQuadPoint(pLayer: pLayer.get());
546
547 Graphics()->TextureClear();
548 Graphics()->QuadsBegin();
549 for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
550 {
551 for(int v = 0; v < 4; v++)
552 Editor()->DoQuadPoint(LayerIndex, pLayer, pQuad: &pLayer->m_vQuads[i], QuadIndex: i, v);
553
554 Editor()->DoQuad(LayerIndex, pLayer, pQuad: &pLayer->m_vQuads[i], Index: i);
555 }
556 Graphics()->QuadsEnd();
557 }
558 }
559 else if(pEditLayer->m_Type == LAYERTYPE_SOUNDS)
560 {
561 std::shared_ptr<CLayerSounds> pLayer = std::static_pointer_cast<CLayerSounds>(r: pEditLayer);
562
563 Editor()->UpdateHotSoundSource(pLayer: pLayer.get());
564
565 Graphics()->TextureClear();
566 Graphics()->QuadsBegin();
567 for(size_t i = 0; i < pLayer->m_vSources.size(); i++)
568 {
569 Editor()->DoSoundSource(LayerIndex, pSource: &pLayer->m_vSources[i], Index: i);
570 }
571 Graphics()->QuadsEnd();
572 }
573 }
574
575 Ui()->MapScreen();
576 }
577 }
578
579 // menu proof selection
580 if(ProofMode()->IsModeMenu() && !Editor()->m_ShowPicker)
581 {
582 ProofMode()->InitMenuBackgroundPositions();
583 const std::vector<vec2> &MenuBackgroundPositions = ProofMode()->MenuBackgroundPositions();
584 for(int i = 0; i < (int)MenuBackgroundPositions.size(); i++)
585 {
586 vec2 Pos = MenuBackgroundPositions[i];
587 const void *pId = &MenuBackgroundPositions[i];
588 Pos += GetWorldOffset() - MenuBackgroundPositions[ProofMode()->CurrentMenuProofIndex()];
589 Pos.y -= 3.0f;
590
591 if(distance(a: Pos, b: MouseWorldNoParaPos()) <= 20.0f)
592 {
593 Ui()->SetHotItem(pId);
594
595 if(i != ProofMode()->CurrentMenuProofIndex() && Ui()->CheckActiveItem(pId))
596 {
597 if(!Ui()->MouseButton(Index: 0))
598 {
599 ProofMode()->SetCurrentMenuProofIndex(i);
600 SetWorldOffset(MenuBackgroundPositions[i]);
601 Ui()->SetActiveItem(nullptr);
602 }
603 }
604 else if(Ui()->HotItem() == pId)
605 {
606 char aTooltipPrefix[32] = "Switch proof position to";
607 if(i == ProofMode()->CurrentMenuProofIndex())
608 str_copy(dst&: aTooltipPrefix, src: "Current proof position at");
609
610 char aNumBuf[8];
611 if(i < (TILE_TIME_CHECKPOINT_LAST - TILE_TIME_CHECKPOINT_FIRST))
612 str_format(buffer: aNumBuf, buffer_size: sizeof(aNumBuf), format: "#%d", i + 1);
613 else
614 aNumBuf[0] = '\0';
615
616 char aTooltipPositions[128];
617 str_format(buffer: aTooltipPositions, buffer_size: sizeof(aTooltipPositions), format: "%s %s", ProofMode()->MenuBackgroundPositionName(MenuProofIndex: i), aNumBuf);
618
619 for(int k : ProofMode()->MenuBackgroundCollisions(MenuProofIndex: i))
620 {
621 if(k == ProofMode()->CurrentMenuProofIndex())
622 str_copy(dst&: aTooltipPrefix, src: "Current proof position at");
623
624 Pos = MenuBackgroundPositions[k];
625 Pos += GetWorldOffset() - MenuBackgroundPositions[ProofMode()->CurrentMenuProofIndex()];
626 Pos.y -= 3.0f;
627
628 if(distance(a: Pos, b: MouseWorldNoParaPos()) > 20.0f)
629 continue;
630
631 if(i < (TILE_TIME_CHECKPOINT_LAST - TILE_TIME_CHECKPOINT_FIRST))
632 str_format(buffer: aNumBuf, buffer_size: sizeof(aNumBuf), format: "#%d", k + 1);
633 else
634 aNumBuf[0] = '\0';
635
636 char aTooltipPositionsCopy[128];
637 str_copy(dst&: aTooltipPositionsCopy, src: aTooltipPositions);
638 str_format(buffer: aTooltipPositions, buffer_size: sizeof(aTooltipPositions), format: "%s, %s %s", aTooltipPositionsCopy, ProofMode()->MenuBackgroundPositionName(MenuProofIndex: k), aNumBuf);
639 }
640 str_format(buffer: Editor()->m_aTooltip, buffer_size: sizeof(Editor()->m_aTooltip), format: "%s %s.", aTooltipPrefix, aTooltipPositions);
641
642 if(Ui()->MouseButton(Index: 0))
643 Ui()->SetActiveItem(pId);
644 }
645 break;
646 }
647 }
648 }
649
650 if(!Input()->ModifierIsPressed() && Editor()->m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr)
651 {
652 float PanSpeed = Input()->ShiftIsPressed() ? 200.0f : 64.0f;
653 if(Input()->KeyPress(Key: KEY_A))
654 OffsetWorld(Offset: {-PanSpeed * MouseWorldScale(), 0});
655 else if(Input()->KeyPress(Key: KEY_D))
656 OffsetWorld(Offset: {PanSpeed * MouseWorldScale(), 0});
657 if(Input()->KeyPress(Key: KEY_W))
658 OffsetWorld(Offset: {0, -PanSpeed * MouseWorldScale()});
659 else if(Input()->KeyPress(Key: KEY_S))
660 OffsetWorld(Offset: {0, PanSpeed * MouseWorldScale()});
661 }
662 }
663
664 if(Ui()->CheckActiveItem(pId: Editor()->MapView()) && Editor()->m_pContainerPanned == nullptr)
665 {
666 // release mouse
667 if(!Ui()->MouseButton(Index: 0))
668 {
669 if(Map()->m_MapViewState.m_ActiveOp == EActiveOp::BRUSH_DRAW)
670 {
671 std::shared_ptr<IEditorAction> pAction = std::make_shared<CEditorBrushDrawAction>(args: Map(), args&: Map()->m_SelectedGroup);
672
673 if(!pAction->IsEmpty()) // Avoid recording tile draw action when placing quads only
674 Map()->m_EditorHistory.RecordAction(pAction);
675 }
676
677 Map()->m_MapViewState.m_ActiveOp = EActiveOp::NONE;
678 Ui()->SetActiveItem(nullptr);
679 }
680 }
681
682 if(!Editor()->m_ShowPicker && Map()->SelectedGroup() && Map()->SelectedGroup()->m_UseClipping)
683 {
684 std::shared_ptr<CLayerGroup> pGameGroup = Map()->m_pGameGroup;
685 pGameGroup->MapScreen();
686
687 CUIRect ClipRect;
688 ClipRect.x = Map()->SelectedGroup()->m_ClipX;
689 ClipRect.y = Map()->SelectedGroup()->m_ClipY;
690 ClipRect.w = Map()->SelectedGroup()->m_ClipW;
691 ClipRect.h = Map()->SelectedGroup()->m_ClipH;
692 ClipRect.DrawOutline(Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
693 }
694
695 if(!Editor()->m_ShowPicker)
696 ProofMode()->RenderScreenSizes();
697
698 if(!Editor()->m_ShowPicker && Editor()->m_ShowEnvelopePreview && Editor()->m_ActiveEnvelopePreview != CEditor::EEnvelopePreview::NONE)
699 {
700 const std::shared_ptr<CLayer> pSelectedLayer = Map()->SelectedLayer(Index: 0);
701 if(pSelectedLayer != nullptr && pSelectedLayer->m_Type == LAYERTYPE_QUADS)
702 {
703 Editor()->DoQuadEnvelopes(pLayerQuads: static_cast<const CLayerQuads *>(pSelectedLayer.get()));
704 }
705 Editor()->m_ActiveEnvelopePreview = CEditor::EEnvelopePreview::NONE;
706 }
707
708 Ui()->MapScreen();
709}
710
711void CMapView::UpdateMouseWorld()
712{
713 const vec2 UpdatedMousePos = Ui()->UpdatedMousePos();
714 const vec2 UpdatedMouseDelta = Ui()->UpdatedMouseDelta();
715
716 // fix correct world x and y
717 const std::shared_ptr<CLayerGroup> pGroup = Map()->SelectedGroup();
718 if(pGroup)
719 {
720 CScreenRect GroupRect = pGroup->Mapping();
721
722 float WorldWidth = GroupRect.Width();
723 float WorldHeight = GroupRect.Height();
724
725 Map()->m_MapViewState.m_MouseWorldScale = WorldWidth / Graphics()->WindowWidth();
726
727 Map()->m_MapViewState.m_MouseWorldPos.x = GroupRect.m_TopLeft.x + WorldWidth * (UpdatedMousePos.x / Graphics()->WindowWidth());
728 Map()->m_MapViewState.m_MouseWorldPos.y = GroupRect.m_TopLeft.y + WorldHeight * (UpdatedMousePos.y / Graphics()->WindowHeight());
729 Map()->m_MapViewState.m_MouseDeltaWorld.x = UpdatedMouseDelta.x * (WorldWidth / Graphics()->WindowWidth());
730 Map()->m_MapViewState.m_MouseDeltaWorld.y = UpdatedMouseDelta.y * (WorldHeight / Graphics()->WindowHeight());
731 }
732 else
733 {
734 Map()->m_MapViewState.m_MouseWorldPos = vec2(-1.0f, -1.0f);
735 Map()->m_MapViewState.m_MouseDeltaWorld = vec2(0.0f, 0.0f);
736 }
737
738 Map()->m_MapViewState.m_MouseWorldNoParaPos = vec2(-1.0f, -1.0f);
739 for(const std::shared_ptr<CLayerGroup> &pGameGroup : Map()->m_vpGroups)
740 {
741 if(!pGameGroup->m_GameGroup)
742 continue;
743
744 CScreenRect GroupRect = pGameGroup->Mapping();
745
746 float WorldWidth = GroupRect.Width();
747 float WorldHeight = GroupRect.Height();
748
749 Map()->m_MapViewState.m_MouseWorldNoParaPos.x = GroupRect.m_TopLeft.x + WorldWidth * (UpdatedMousePos.x / Graphics()->WindowWidth());
750 Map()->m_MapViewState.m_MouseWorldNoParaPos.y = GroupRect.m_TopLeft.y + WorldHeight * (UpdatedMousePos.y / Graphics()->WindowHeight());
751 }
752}
753
754void CMapView::ResetMouseDeltaWorld()
755{
756 Map()->m_MapViewState.m_MouseDeltaWorld = vec2(0.0f, 0.0f);
757}
758
759float CMapView::MouseWorldScale() const
760{
761 return Map()->m_MapViewState.m_MouseWorldScale;
762}
763
764vec2 CMapView::MouseDeltaWorld() const
765{
766 return Map()->m_MapViewState.m_MouseDeltaWorld;
767}
768
769vec2 CMapView::MouseWorldPos() const
770{
771 return Map()->m_MapViewState.m_MouseWorldPos;
772}
773
774vec2 CMapView::MouseWorldNoParaPos() const
775{
776 return Map()->m_MapViewState.m_MouseWorldNoParaPos;
777}
778
779void CMapView::ResetZoom()
780{
781 SetEditorOffset({0, 0});
782 Zoom()->SetValue(100.0f);
783}
784
785float CMapView::ScaleLength(float Value) const
786{
787 return GetWorldZoom() * Value;
788}
789
790void CMapView::ZoomMouseTarget(float ZoomFactor)
791{
792 // zoom to the current mouse position
793 // get absolute mouse position
794
795 CScreenRect ScreenRect = Graphics()->MapScreenToWorld(
796 CenterX: GetWorldOffset().x, CenterY: GetWorldOffset().y,
797 ParallaxX: 100.0f, ParallaxY: 100.0f, ParallaxZoom: 100.0f, OffsetX: 0.0f, OffsetY: 0.0f, Aspect: Graphics()->ScreenAspect(), Zoom: GetWorldZoom());
798
799 float WorldWidth = ScreenRect.Width();
800 float WorldHeight = ScreenRect.Height();
801
802 float MouseWorldX = ScreenRect.m_TopLeft.x + WorldWidth * (Ui()->MouseX() / Ui()->Screen()->w);
803 float MouseWorldY = ScreenRect.m_TopLeft.y + WorldHeight * (Ui()->MouseY() / Ui()->Screen()->h);
804
805 // adjust camera
806 OffsetWorld(Offset: (vec2(MouseWorldX, MouseWorldY) - GetWorldOffset()) * (1.0f - ZoomFactor));
807}
808
809void CMapView::UpdateZoom()
810{
811 float OldLevel = Zoom()->GetValue();
812 bool UpdatedZoom = Zoom()->UpdateValue();
813 Zoom()->SetValueRange(MinValue: 10.0f, MaxValue: g_Config.m_EdLimitMaxZoomLevel ? 2000.0f : std::numeric_limits<float>::max());
814 float NewLevel = Zoom()->GetValue();
815 if(UpdatedZoom && g_Config.m_EdZoomTarget)
816 ZoomMouseTarget(ZoomFactor: NewLevel / OldLevel);
817 Map()->m_MapViewState.m_WorldZoom = NewLevel / 100.0f;
818}
819
820CSmoothValue *CMapView::Zoom()
821{
822 return &Map()->m_MapViewState.m_Zoom;
823}
824
825const CSmoothValue *CMapView::Zoom() const
826{
827 return &Map()->m_MapViewState.m_Zoom;
828}
829
830CProofMode *CMapView::ProofMode()
831{
832 return &m_ProofMode;
833}
834
835const CProofMode *CMapView::ProofMode() const
836{
837 return &m_ProofMode;
838}
839
840CMapGrid *CMapView::MapGrid()
841{
842 return &m_MapGrid;
843}
844
845const CMapGrid *CMapView::MapGrid() const
846{
847 return &m_MapGrid;
848}
849
850void CMapView::OffsetWorld(vec2 Offset)
851{
852 Map()->m_MapViewState.m_WorldOffset += Offset;
853}
854
855void CMapView::OffsetEditor(vec2 Offset)
856{
857 Map()->m_MapViewState.m_EditorOffset += Offset;
858}
859
860void CMapView::SetWorldOffset(vec2 WorldOffset)
861{
862 Map()->m_MapViewState.m_WorldOffset = WorldOffset;
863}
864
865void CMapView::SetEditorOffset(vec2 EditorOffset)
866{
867 Map()->m_MapViewState.m_EditorOffset = EditorOffset;
868}
869
870vec2 CMapView::GetWorldOffset() const
871{
872 return Map()->m_MapViewState.m_WorldOffset;
873}
874
875vec2 CMapView::GetEditorOffset() const
876{
877 return Map()->m_MapViewState.m_EditorOffset;
878}
879
880float CMapView::GetWorldZoom() const
881{
882 return Map()->m_MapViewState.m_WorldZoom;
883}
884