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