1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Handles viewport editing in console mode */
19 
20 #include "C4Include.h"
21 #include "editor/C4EditCursor.h"
22 
23 #include "editor/C4Console.h"
24 #include "object/C4Def.h"
25 #include "object/C4Object.h"
26 #include "game/C4Application.h"
27 #include "lib/C4Random.h"
28 #include "gui/C4MouseControl.h"
29 #include "landscape/C4Landscape.h"
30 #include "landscape/C4Texture.h"
31 #include "graphics/C4GraphicsResource.h"
32 #include "object/C4GameObjects.h"
33 #include "control/C4GameControl.h"
34 #include "script/C4AulExec.h"
35 #ifdef WITH_QT_EDITOR
36 #include "editor/C4ConsoleQtShapes.h"
37 #endif
38 #include "lib/StdMesh.h"
39 
40 #ifdef _WIN32
41 #include "res/resource.h"
42 #endif
43 
44 
GetDataString() const45 StdStrBuf C4EditCursorSelection::GetDataString() const
46 {
47 	StdStrBuf Output;
48 	// Compose info text by selected object(s)
49 	int32_t obj_count = size();
50 	switch (obj_count)
51 	{
52 		// No selection
53 	case 0:
54 		Output = LoadResStr("IDS_CNS_NOOBJECT");
55 		break;
56 		// One selected object
57 	case 1:
58 	{
59 		C4Object *obj = GetObject();
60 		if (obj)
61 			Output.Take(obj->GetDataString());
62 		else
63 			Output.Take(front().GetDataString());
64 		break;
65 	}
66 		// Multiple selected objects
67 	default:
68 		Output.Format(LoadResStr("IDS_CNS_MULTIPLEOBJECTS"), obj_count);
69 		break;
70 	}
71 	return Output;
72 }
73 
GetObject(int32_t index) const74 C4Object *C4EditCursorSelection::GetObject(int32_t index) const
75 {
76 	// Get indexed C4Object * in list
77 	C4Object *obj;
78 	for (const C4Value &v : (*this))
79 		if ((obj = v.getObj()))
80 			if (!index--)
81 				return obj;
82 	return nullptr;
83 }
84 
GetLastObject() const85 C4Object *C4EditCursorSelection::GetLastObject() const
86 {
87 	C4Object *obj, *last = nullptr;
88 	for (const C4Value &v : (*this))
89 		if ((obj = v.getObj()))
90 			last = obj;
91 	return last;
92 }
93 
ConsolidateEmpty()94 void C4EditCursorSelection::ConsolidateEmpty()
95 {
96 	// remove nullptred entries that may happen because objects got deleted
97 	this->remove(C4VNull);
98 }
99 
ClearPointers(C4Object * obj)100 bool C4EditCursorSelection::ClearPointers(C4Object *obj)
101 {
102 	bool found = false;
103 	for (C4Value &v : (*this))
104 		if (obj == v.getObj())
105 		{
106 			found = true;
107 			v.Set0();
108 		}
109 	if (found) ConsolidateEmpty();
110 	return found;
111 }
112 
IsContained(C4PropList * obj) const113 bool C4EditCursorSelection::IsContained(C4PropList *obj) const
114 {
115 	for (const C4Value &v : (*this)) if (obj == v.getPropList()) return true;
116 	return false;
117 }
118 
ObjectCount() const119 int32_t C4EditCursorSelection::ObjectCount() const
120 {
121 	// count only C4Object *
122 	int32_t count = 0;
123 	for (const C4Value &v : *this) if (v.getObj()) ++count;
124 	return count;
125 }
126 
127 
C4EditCursor()128 C4EditCursor::C4EditCursor()
129 #ifdef WITH_QT_EDITOR
130 	: shapes(new C4ConsoleQtShapes())
131 #endif
132 {
133 	Default();
134 }
135 
~C4EditCursor()136 C4EditCursor::~C4EditCursor()
137 {
138 	Clear();
139 }
140 
Execute()141 void C4EditCursor::Execute()
142 {
143 	// drawing
144 	switch (Mode)
145 	{
146 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
147 	case C4CNS_ModeEdit:
148 		// Hold selection
149 		if (Hold)
150 			EMMoveObject(fShiftWasDown ? EMMO_MoveForced : EMMO_Move, Fix0, Fix0, nullptr, &selection, nullptr, false);
151 		break;
152 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
153 	case C4CNS_ModeDraw:
154 		switch (Console.ToolsDlg.Tool)
155 		{
156 		case C4TLS_Fill:
157 			if (Hold) if (!Game.HaltCount) if (Console.Editing) ApplyToolFill();
158 			break;
159 		}
160 		break;
161 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
162 	}
163 	if (!::Game.iTick35 || ::Console.EditCursor.IsSelectionInvalidated())
164 	{
165 		selection.ConsolidateEmpty();
166 		Console.PropertyDlgUpdate(selection, false);
167 	}
168 }
169 
Init()170 bool C4EditCursor::Init()
171 {
172 
173 #ifdef USE_WIN32_WINDOWS
174 	if (!(hMenu = LoadMenu(Application.GetInstance(),MAKEINTRESOURCE(IDR_CONTEXTMENUS))))
175 		return false;
176 #endif
177 	Console.UpdateModeCtrls(Mode);
178 
179 	return true;
180 }
181 
ClearPointers(C4Object * pObj)182 void C4EditCursor::ClearPointers(C4Object *pObj)
183 {
184 	if (Target==pObj) Target=nullptr;
185 	if (selection.ClearPointers(pObj))
186 		OnSelectionChanged();
187 }
188 
Move(float iX,float iY,float iZoom,DWORD dwKeyState)189 bool C4EditCursor::Move(float iX, float iY, float iZoom, DWORD dwKeyState)
190 {
191 	// alt check
192 	bool fAltIsDown = (dwKeyState & MK_ALT) != 0;
193 	if (fAltIsDown != fAltWasDown)
194 	{
195 		if ((fAltWasDown = fAltIsDown))
196 			AltDown();
197 		else
198 			AltUp();
199 	}
200 
201 	// shift check
202 	bool fShiftIsDown = (dwKeyState & MK_SHIFT) != 0;
203 	if(fShiftIsDown != fShiftWasDown)
204 		fShiftWasDown = fShiftIsDown;
205 
206 	// Offset movement
207 	float xoff = iX-X; float yoff = iY-Y;
208 	X = iX; Y = iY; Zoom = iZoom;
209 
210 	// Drag rotation/scale of object
211 	if (DragTransform)
212 	{
213 		C4Object *obj = selection.GetObject();
214 		if (obj)
215 		{
216 			int32_t new_rot = (DragRot0 + int32_t(float(X - X2)*Zoom)) % 360;
217 			if (new_rot < 0) new_rot += 360;
218 			if (fShiftIsDown) new_rot = (new_rot + 23) / 45 * 45;
219 			int32_t new_con = DragCon0 + int32_t(float(Y2 - Y)*Zoom*(FullCon / 200));
220 			int32_t con_step = FullCon / 5;
221 			if (fShiftIsDown) new_con = (new_con + con_step/2) / con_step * con_step;
222 			if (!obj->Def->Oversize) new_con = std::min<int32_t>(new_con, FullCon);
223 			new_con = std::max<int32_t>(new_con, fShiftIsDown ? 1 : con_step);
224 			bool any_change = false;
225 			if (obj->Def->Rotateable)
226 				if (new_rot != DragRotLast)
227 					any_change = true;
228 			if (obj->Def->GrowthType)
229 				if (new_con != DragConLast)
230 					any_change = true;
231 			if (any_change)
232 			{
233 				EMMoveObject(EMMO_Transform, itofix(new_rot, 1), itofix(new_con, FullCon/100), obj, nullptr);
234 				DragRotLast = new_rot;
235 				DragConLast = new_con;
236 			}
237 		}
238 
239 	}
240 
241 	switch (Mode)
242 	{
243 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
244 	case C4CNS_ModeEdit:
245 #ifdef WITH_QT_EDITOR
246 		shapes->MouseMove(X, Y, Hold, 3.0f/Zoom, !!(dwKeyState & MK_SHIFT), !!(dwKeyState & MK_CONTROL));
247 #endif
248 		// Hold
249 		if (!DragFrame && Hold && !DragShape && !DragTransform)
250 		{
251 			MoveSelection(ftofix(xoff),ftofix(yoff), false);
252 			UpdateDropTarget(dwKeyState);
253 		}
254 		// Update target
255 		// Shift always indicates a target outside the current selection
256 		else
257 		{
258 			Target = (dwKeyState & MK_SHIFT) ? selection.GetLastObject() : nullptr;
259 			do
260 			{
261 				Target = Game.FindObject(nullptr,X,Y,0,0,OCF_NotContained, Target);
262 			}
263 			while ((dwKeyState & MK_SHIFT) && Target && selection.IsContained(Target));
264 		}
265 		break;
266 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
267 	case C4CNS_ModeCreateObject:
268 		// Drop target for contained object creation
269 		UpdateDropTarget(dwKeyState);
270 		break;
271 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
272 	case C4CNS_ModeDraw:
273 		switch (Console.ToolsDlg.Tool)
274 		{
275 		case C4TLS_Brush:
276 			if (Hold) ApplyToolBrush();
277 			break;
278 		case C4TLS_Line: case C4TLS_Rect:
279 			break;
280 		}
281 		break;
282 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
283 	}
284 
285 	// Update
286 	UpdateStatusBar();
287 	return true;
288 }
289 
Move(DWORD new_key_state)290 bool C4EditCursor::Move(DWORD new_key_state)
291 {
292 	// Move at last position with new key state
293 	return Move(X, Y, Zoom, new_key_state);
294 }
295 
UpdateStatusBar()296 void C4EditCursor::UpdateStatusBar()
297 {
298 	int32_t X=this->X, Y=this->Y;
299 	StdStrBuf str;
300 	switch (Mode)
301 	{
302 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
303 	case C4CNS_ModePlay:
304 		if (::MouseControl.GetCaption()) str.CopyUntil(::MouseControl.GetCaption(),'|');
305 		break;
306 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
307 	case C4CNS_ModeEdit:
308 		str.Format("%i/%i (%s)",X,Y,Target ? (Target->GetName()) : LoadResStr("IDS_CNS_NOTHING") );
309 		break;
310 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
311 	case C4CNS_ModeCreateObject:
312 		str.Format(LoadResStr("IDS_CNS_CREATESTATUS"), creator_def ? (creator_def->GetName()) : LoadResStr("IDS_CNS_NOTHING"));
313 		break;
314 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
315 	case C4CNS_ModeDraw:
316 		str.Format("%i/%i (fg: %s, bg: %s)",X,Y,
317                            MatValid(::Landscape.GetMat(X,Y)) ? ::MaterialMap.Map[::Landscape.GetMat(X,Y)].Name : LoadResStr("IDS_CNS_NOTHING"),
318                            MatValid(::Landscape.GetBackMat(X,Y)) ? ::MaterialMap.Map[::Landscape.GetBackMat(X,Y)].Name : LoadResStr("IDS_CNS_NOTHING") );
319 		break;
320 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
321 	}
322 	Console.DisplayInfoText(C4ConsoleGUI::CONSOLE_Cursor, str);
323 }
324 
OnSelectionChanged(bool by_objectlist)325 void C4EditCursor::OnSelectionChanged(bool by_objectlist)
326 {
327 	::Console.PropertyDlgUpdate(selection, true);
328 	if (!by_objectlist) ::Console.ObjectListDlg.Update(selection);
329 }
330 
AddToSelection(C4PropList * add_proplist)331 void C4EditCursor::AddToSelection(C4PropList *add_proplist)
332 {
333 	if (!add_proplist) return;
334 	C4Object *add_obj = add_proplist->GetObject();
335 	if (add_obj && !add_obj->Status) return;
336 	if (selection.IsContained(add_proplist)) return;
337 	// add object to selection and do script callback
338 	selection.push_back(C4VPropList(add_proplist));
339 }
340 
RemoveFromSelection(C4PropList * remove_proplist)341 bool C4EditCursor::RemoveFromSelection(C4PropList *remove_proplist)
342 {
343 	if (!remove_proplist) return false;
344 	C4Object *remove_obj = remove_proplist->GetObject();
345 	if (remove_obj && !remove_obj->Status) return false;
346 	// remove object from selection and do script callback
347 	if (!selection.IsContained(remove_proplist)) return false;
348 	selection.remove(C4VPropList(remove_proplist));
349 	return true;
350 }
351 
ClearSelection(C4PropList * next_selection)352 void C4EditCursor::ClearSelection(C4PropList *next_selection)
353 {
354 	// remove everything from selection
355 	selection.clear();
356 }
357 
LeftButtonDown(DWORD dwKeyState)358 bool C4EditCursor::LeftButtonDown(DWORD dwKeyState)
359 {
360 
361 	// Hold
362 	Hold=true;
363 
364 	switch (Mode)
365 	{
366 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
367 	case C4CNS_ModeEdit:
368 		// Click on shape?
369 #ifdef WITH_QT_EDITOR
370 		if (shapes->MouseDown(X, Y, 3.0f / Zoom, !!(dwKeyState & MK_SHIFT), !!(dwKeyState & MK_CONTROL)))
371 		{
372 			DragShape = shapes->IsDragging();
373 			break;
374 		}
375 #endif
376 		if (dwKeyState & MK_CONTROL)
377 		{
378 			// Toggle target
379 			if (Target)
380 				if (!RemoveFromSelection(Target))
381 					AddToSelection(Target);
382 		}
383 		else
384 		{
385 			// Click rotate/scale marker?
386 			if (IsHoveringTransformMarker())
387 			{
388 				DragTransform = true;
389 				X2 = X; Y2 = Y;
390 				C4Object *dragged_obj = selection.GetObject();
391 				DragRot0 = DragRotLast = dragged_obj->GetR();
392 				DragCon0 = DragConLast = dragged_obj->GetCon();
393 				break;
394 			}
395 			// Click on unselected: select single
396 			if (Target)
397 			{
398 				bool found = false;
399 				for (C4Value &obj : selection)
400 				{
401 					if(obj.getObj() && obj.getObj()->At(X, Y))
402 					{
403 						found = true;
404 						break;
405 					}
406 				}
407 				if(!found) // means loop didn't break
408 				{
409 					ClearSelection(Target);
410 					AddToSelection(Target);
411 				}
412 			}
413 			// Click on nothing: drag frame
414 			if (!Target)
415 				{ ClearSelection(); DragFrame=true; X2=X; Y2=Y; }
416 		}
417 		OnSelectionChanged();
418 		break;
419 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
420 	case C4CNS_ModeCreateObject:
421 		ApplyCreateObject(!!(dwKeyState & MK_CONTROL));
422 		break;
423 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
424 	case C4CNS_ModeDraw:
425 		switch (Console.ToolsDlg.Tool)
426 		{
427 		case C4TLS_Brush: ApplyToolBrush(); break;
428 		case C4TLS_Line: DragLine=true; X2=X; Y2=Y; break;
429 		case C4TLS_Rect: DragFrame=true; X2=X; Y2=Y; break;
430 		case C4TLS_Fill:
431 			if (Game.HaltCount)
432 				{ Hold=false; Console.Message(LoadResStr("IDS_CNS_FILLNOHALT")); return false; }
433 			break;
434 		case C4TLS_Picker: ApplyToolPicker(); break;
435 		}
436 		break;
437 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
438 	}
439 
440 	DropTarget=nullptr;
441 
442 	return true;
443 }
444 
RightButtonDown(DWORD dwKeyState)445 bool C4EditCursor::RightButtonDown(DWORD dwKeyState)
446 {
447 
448 	switch (Mode)
449 	{
450 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
451 	case C4CNS_ModeEdit:
452 		if ( (dwKeyState & MK_CONTROL) == 0)
453 		{
454 			// Check whether cursor is on anything in the selection
455 			bool fCursorIsOnSelection = false;
456 			for (C4Value &obj : selection)
457 			{
458 				if (obj.getObj() && obj.getObj()->At(X,Y))
459 				{
460 					fCursorIsOnSelection = true;
461 					break;
462 				}
463 			}
464 			if (!fCursorIsOnSelection)
465 			{
466 				// Click on unselected
467 				if (Target && !selection.IsContained(Target))
468 				{
469 					ClearSelection(Target); AddToSelection(Target);
470 				}
471 				// Click on nothing
472 				if (!Target) ClearSelection();
473 			}
474 		}
475 		OnSelectionChanged();
476 		break;
477 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
478 	}
479 
480 	return true;
481 }
482 
LeftButtonUp(DWORD dwKeyState)483 bool C4EditCursor::LeftButtonUp(DWORD dwKeyState)
484 {
485 	// Finish edit/tool
486 	switch (Mode)
487 	{
488 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
489 	case C4CNS_ModeEdit:
490 		// Finish object drag
491 		if (!DragFrame && Hold && !DragShape && !DragTransform)
492 		{
493 			MoveSelection(Fix0, Fix0, true);
494 		}
495 		if (DragFrame) FrameSelection();
496 		if (DropTarget) PutContents();
497 		break;
498 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
499 	case C4CNS_ModeDraw:
500 		switch (Console.ToolsDlg.Tool)
501 		{
502 		case C4TLS_Line:
503 			if (DragLine) ApplyToolLine();
504 			break;
505 		case C4TLS_Rect:
506 			if (DragFrame) ApplyToolRect();
507 			break;
508 		}
509 		break;
510 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
511 	}
512 
513 	// Release
514 #ifdef WITH_QT_EDITOR
515 	shapes->MouseUp(X, Y, !!(dwKeyState & MK_SHIFT), !!(dwKeyState & MK_CONTROL));
516 #endif
517 	Hold=false;
518 	DragFrame=false;
519 	DragLine=false;
520 	DragShape = false;
521 	DragTransform = false;
522 	DropTarget=nullptr;
523 	// Update
524 	UpdateStatusBar();
525 	return true;
526 }
527 
KeyDown(C4KeyCode KeyCode,DWORD dwKeyState)528 bool C4EditCursor::KeyDown(C4KeyCode KeyCode, DWORD dwKeyState)
529 {
530 	// alt check
531 	bool fAltIsDown = (dwKeyState & MK_ALT) != 0;
532 	fAltIsDown = fAltIsDown || KeyCode == K_ALT_L || KeyCode == K_ALT_R;
533 	if (fAltIsDown != fAltWasDown)
534 	{
535 		if ((fAltWasDown = fAltIsDown))
536 			AltDown();
537 		else
538 			AltUp();
539 	}
540 
541 	// shift check
542 	bool fShiftIsDown = (dwKeyState & MK_SHIFT) != 0;
543 	fShiftIsDown = fShiftIsDown || KeyCode == K_SHIFT_L || KeyCode == K_SHIFT_R;
544 	if(fShiftIsDown != fShiftWasDown)
545 		fShiftWasDown = fShiftIsDown;
546 
547 	return true;
548 }
549 
KeyUp(C4KeyCode KeyCode,DWORD dwKeyState)550 bool C4EditCursor::KeyUp(C4KeyCode KeyCode, DWORD dwKeyState)
551 {
552 	// alt check
553 	bool fAltIsDown = (dwKeyState & MK_ALT) != 0;
554 	fAltIsDown = fAltIsDown && !( KeyCode == K_ALT_L || KeyCode == K_ALT_R);
555 	if (fAltIsDown != fAltWasDown)
556 	{
557 		if ((fAltWasDown = fAltIsDown))
558 			AltDown();
559 		else
560 			AltUp();
561 	}
562 
563 	// shift check
564 	bool fShiftIsDown = (dwKeyState & MK_SHIFT) != 0;
565 	fShiftIsDown = fShiftIsDown && !(KeyCode == K_SHIFT_L || KeyCode == K_SHIFT_R);
566 	if(fShiftIsDown != fShiftWasDown)
567 		fShiftWasDown = fShiftIsDown;
568 
569 	return true;
570 }
571 
572 #ifdef USE_WIN32_WINDOWS
SetMenuItemEnable(HMENU hMenu,WORD id,bool fEnable)573 bool SetMenuItemEnable(HMENU hMenu, WORD id, bool fEnable)
574 {
575 	return !!EnableMenuItem(hMenu,id,MF_BYCOMMAND | MF_ENABLED | ( fEnable ? 0 : MF_GRAYED));
576 }
577 
SetMenuItemText(HMENU hMenu,WORD id,const char * szText)578 bool SetMenuItemText(HMENU hMenu, WORD id, const char *szText)
579 {
580 	MENUITEMINFOW minfo;
581 	ZeroMem(&minfo,sizeof(minfo));
582 	minfo.cbSize = sizeof(minfo);
583 	minfo.fMask = MIIM_ID | MIIM_TYPE | MIIM_DATA;
584 	minfo.fType = MFT_STRING;
585 	minfo.wID = id;
586 	StdBuf td = GetWideCharBuf(szText);
587 	minfo.dwTypeData = getMBufPtr<wchar_t>(td);
588 	minfo.cch = wcslen(minfo.dwTypeData);
589 	return !!SetMenuItemInfoW(hMenu,id,false,&minfo);
590 }
591 #endif
592 
RightButtonUp(DWORD dwKeyState)593 bool C4EditCursor::RightButtonUp(DWORD dwKeyState)
594 {
595 	Target=nullptr;
596 #ifndef WITH_QT_EDITOR
597 	DoContextMenu(dwKeyState);
598 #endif
599 	// Update
600 	UpdateStatusBar();
601 
602 	return true;
603 }
604 
Delete()605 bool C4EditCursor::Delete()
606 {
607 	if (!EditingOK()) return false;
608 	EMMoveObject(EMMO_Remove, Fix0, Fix0, nullptr, &selection);
609 	if (::Control.isCtrlHost())
610 	{
611 		OnSelectionChanged();
612 	}
613 	return true;
614 }
615 
OpenPropTools()616 bool C4EditCursor::OpenPropTools()
617 {
618 	switch (Mode)
619 	{
620 	case C4CNS_ModeEdit: case C4CNS_ModePlay:
621 		Console.PropertyDlgOpen();
622 		Console.PropertyDlgUpdate(selection, false);
623 		break;
624 	case C4CNS_ModeDraw:
625 		Console.ToolsDlg.Open();
626 		break;
627 	}
628 	return true;
629 }
630 
Duplicate()631 bool C4EditCursor::Duplicate()
632 {
633 	EMMoveObject(EMMO_Duplicate, Fix0, Fix0, nullptr, &selection);
634 	return true;
635 }
636 
PerformDuplication(int32_t * object_numbers,int32_t object_count,bool local_call)637 void C4EditCursor::PerformDuplication(int32_t *object_numbers, int32_t object_count, bool local_call)
638 {
639 	if (!object_count) return;
640 	// Remember last OEI so duplicated objects can be determined
641 	int32_t prev_oei = C4PropListNumbered::GetEnumerationIndex();
642 	// Get serialized objects
643 	C4RefCntPointer<C4ValueArray> object_numbers_c4v = new C4ValueArray();
644 	object_numbers_c4v->SetSize(object_count);
645 	int32_t total_object_count = 0;
646 	for (int32_t i = 0; i < object_count; ++i)
647 	{
648 		C4Object *obj = ::Objects.SafeObjectPointer(object_numbers[i]);
649 		if (!obj) continue;
650 		total_object_count = obj->AddObjectAndContentsToArray(object_numbers_c4v, total_object_count);
651 	}
652 	object_numbers_c4v->SetSize(total_object_count);
653 	int32_t objects_file_handle = ::ScriptEngine.CreateUserFile();
654 	C4AulParSet pars(C4VInt(objects_file_handle), C4VArray(object_numbers_c4v.Get()));
655 	C4Value result_c4v(::ScriptEngine.GetPropList()->Call(PSF_SaveScenarioObjects, &pars));
656 	bool result = !!result_c4v;
657 	if (result_c4v.GetType() == C4V_Nil)
658 	{
659 		// Function returned nil: This usually means there was a script error during object writing.
660 		// It could also mean the scripter overloaded global func SaveScenarioObjects and returned nil.
661 		// In either case, no the objects file will contain garbage so no objects were duplicated.
662 		LogF("ERROR: No valid result from global func " PSF_SaveScenarioObjects ". Regular object duplication failed.");
663 	}
664 	else
665 	{
666 		// Function completed successfully (returning true or false)
667 		C4AulUserFile *file = ::ScriptEngine.GetUserFile(objects_file_handle);
668 		if (!result || !file || !file->GetFileLength())
669 		{
670 			// Nothing written? Then we don't have objects.
671 			// That's OK; not an error.
672 		}
673 		else
674 		{
675 			// Create copy of objects by executing duplication script
676 			StdStrBuf data = file->GrabFileContents();
677 			AulExec.DirectExec(&::ScriptEngine, data.getData(), "object duplication", false, nullptr, true);
678 		}
679 	}
680 	::ScriptEngine.CloseUserFile(objects_file_handle);
681 	// Did duplication work?
682 	bool any_duplicates = false;
683 	for (C4Object *obj : ::Objects)
684 	{
685 		if (obj->Number > prev_oei)
686 		{
687 			any_duplicates = true;
688 			break;
689 		}
690 	}
691 	// If duplication created no objects, the user probably tried to copy a non-saved object
692 	// Just copy the old way then
693 	if (!any_duplicates)
694 	{
695 		PerformDuplicationLegacy(object_numbers, object_count, local_call);
696 		return;
697 	}
698 	// Update local client status: Put new objects into selection
699 	if (local_call)
700 	{
701 		selection.clear();
702 		int64_t X_all = 0, Y_all = 0, n_selected = 0;
703 		float old_x = X, old_y = Y;
704 		for (C4Object *obj : ::Objects)
705 		{
706 			if (obj->Number > prev_oei)
707 			{
708 				selection.push_back(C4VObj(obj));
709 				X_all += obj->GetX();
710 				Y_all += obj->GetY();
711 				++n_selected;
712 			}
713 		}
714 		// Reset EditCursor pos to center of duplicated objects, so they will be dragged along with the cursor
715 		if (n_selected)
716 		{
717 			X = X_all / n_selected;
718 			Y = Y_all / n_selected;
719 		}
720 		SetHold(true);
721 		OnSelectionChanged();
722 		// Ensure duplicated objects moved to the cursor without extra mouse movement
723 		// Move with shift key pressed to allow initial shift of HorizontalFixed items
724 		DWORD last_key_state = MK_SHIFT;
725 		if (fAltWasDown) last_key_state |= MK_ALT;
726 		bool shift_was_down = fShiftWasDown;
727 		Move(old_x, old_y, Zoom, last_key_state);
728 		fShiftWasDown = shift_was_down;
729 	}
730 }
731 
PerformDuplicationLegacy(int32_t * pObjects,int32_t iObjectNum,bool fLocalCall)732 void C4EditCursor::PerformDuplicationLegacy(int32_t *pObjects, int32_t iObjectNum, bool fLocalCall)
733 {
734 	// Old-style object copying: Just create new objects at old object position with same prototype
735 	C4Object *pOldObj, *pObj;
736 	for (int i = 0; i<iObjectNum; ++i)
737 		if ((pOldObj = ::Objects.SafeObjectPointer(pObjects[i])))
738 		{
739 			pObj = Game.CreateObject(pOldObj->GetPrototype(), pOldObj, pOldObj->Owner, pOldObj->GetX(), pOldObj->GetY());
740 			if (pObj && pObj->Status)
741 			{
742 				// local call? adjust selection then
743 				// do callbacks for all clients for sync reasons
744 				if (fLocalCall) selection.push_back(C4VObj(pObj));
745 				C4AulParSet pars(C4VObj(pObj));
746 				if (pOldObj->Status) pOldObj->Call(PSF_EditCursorDeselection, &pars);
747 				if (pObj->Status) pObj->Call(PSF_EditCursorSelection);
748 			}
749 		}
750 	// update status
751 	if (fLocalCall)
752 	{
753 		for (int i = 0; i<iObjectNum; ++i)
754 			if ((pOldObj = ::Objects.SafeObjectPointer(pObjects[i])))
755 				selection.remove(C4VObj(pOldObj));
756 		SetHold(true);
757 		OnSelectionChanged();
758 	}
759 }
760 
DrawObject(C4TargetFacet & cgo,C4Object * cobj,uint32_t select_mark_color,bool highlight,bool draw_transform_marker)761 void C4EditCursor::DrawObject(C4TargetFacet &cgo, C4Object *cobj, uint32_t select_mark_color, bool highlight, bool draw_transform_marker)
762 {
763 	// target pos (parallax)
764 	float line_width = std::max<float>(1.0f, 1.0f / cgo.Zoom);
765 	float offX, offY, newzoom;
766 	cobj->GetDrawPosition(cgo, offX, offY, newzoom);
767 	ZoomDataStackItem zdsi(cgo.X, cgo.Y, newzoom);
768 	if (select_mark_color)
769 	{
770 		FLOAT_RECT frame =
771 		{
772 			offX + cobj->Shape.x,
773 			offX + cobj->Shape.x + cobj->Shape.Wdt,
774 			offY + cobj->Shape.y,
775 			offY + cobj->Shape.y + cobj->Shape.Hgt
776 		};
777 		DrawSelectMark(cgo, frame, line_width, select_mark_color);
778 	}
779 	if (highlight)
780 	{
781 		uint32_t dwOldMod = cobj->ColorMod;
782 		uint32_t dwOldBlitMode = cobj->BlitMode;
783 		cobj->ColorMod = 0xffffffff;
784 		cobj->BlitMode = C4GFXBLIT_CLRSFC_MOD2 | C4GFXBLIT_ADDITIVE;
785 
786 		if (cobj->pMeshInstance)
787 			cobj->pMeshInstance->SetFaceOrdering(StdSubMeshInstance::FO_NearestToFarthest);
788 
789 		cobj->Draw(cgo, -1);
790 		cobj->DrawTopFace(cgo, -1);
791 
792 		if (cobj->pMeshInstance)
793 			cobj->pMeshInstance->SetFaceOrderingForClrModulation(cobj->ColorMod);
794 
795 		cobj->ColorMod = dwOldMod;
796 		cobj->BlitMode = dwOldBlitMode;
797 	}
798 	// Transformer knob
799 	if (draw_transform_marker)
800 	{
801 		float transform_marker_x = 0.0f, transform_marker_y = 0.0f;
802 		if (HasTransformMarker(&transform_marker_x, &transform_marker_y, cgo.Zoom))
803 		{
804 			transform_marker_x += offX; transform_marker_y += offY;
805 			float sz = float(::GraphicsResource.fctTransformKnob.Hgt) / cgo.Zoom;
806 			C4Facet transform_target_sfc(cgo.Surface, transform_marker_x-sz/2, transform_marker_y-sz/2, sz, sz);
807 			::GraphicsResource.fctTransformKnob.Draw(transform_target_sfc);
808 			// Transform knob while dragging
809 			if (DragTransform)
810 			{
811 				pDraw->SetBlitMode(C4GFXBLIT_ADDITIVE);
812 				transform_target_sfc.X += X - X2;
813 				transform_target_sfc.Y += Y - Y2;
814 				::GraphicsResource.fctTransformKnob.Draw(transform_target_sfc);
815 				pDraw->ResetBlitMode();
816 			}
817 		}
818 	}
819 }
820 
HasTransformMarker(float * x,float * y,float zoom) const821 bool C4EditCursor::HasTransformMarker(float *x, float *y, float zoom) const
822 {
823 	// Single selection only (assume obj is in selection)
824 	if (selection.size() != 1) return false;
825 	C4Object *obj = selection.GetObject();
826 	if (!obj) return false;
827 	// Show knob only for objects that can be scaled or rotated
828 	if (!obj->Def->GrowthType && !obj->Def->Rotateable) return false;
829 	// Show knob only if the shape has a certain minimum size in either extent (so small objects can still be moved)
830 	float vis_wdt = float(obj->Shape.Wdt) * zoom;
831 	float vis_hgt = float(obj->Shape.Wdt) * zoom;
832 	if (vis_wdt < ::GraphicsResource.fctTransformKnob.Hgt && vis_hgt < ::GraphicsResource.fctTransformKnob.Hgt) return false;
833 	// It's visible: Put it to the bottom of the shape without the shape expansion through rotation
834 	*x = 0;
835 	*y = float(obj->Def->Shape.y + obj->Def->Shape.Hgt) * obj->GetCon() / FullCon - float(::GraphicsResource.fctTransformKnob.Hgt) / (zoom*2);
836 	return true;
837 }
838 
Draw(C4TargetFacet & cgo)839 void C4EditCursor::Draw(C4TargetFacet &cgo)
840 {
841 	ZoomDataStackItem zdsi(cgo.X, cgo.Y, cgo.Zoom);
842 	float line_width = std::max<float>(1.0f, 1.0f / cgo.Zoom);
843 #ifdef WITH_QT_EDITOR
844 	// Draw shapes of selection
845 	shapes->Draw(cgo);
846 #endif
847 	// Draw selection marks
848 	for (C4Value &obj : selection)
849 	{
850 		C4Object *cobj = obj.getObj();
851 		if (!cobj) continue;
852 		DrawObject(cgo, cobj, 0xffffffff, fShiftWasDown, true); // highlight selection if shift is pressed
853 	}
854 	// Draw drag frame
855 	if (DragFrame)
856 		pDraw->DrawFrameDw(cgo.Surface,
857 		                               std::min(X, X2) + cgo.X - cgo.TargetX, std::min(Y, Y2) + cgo.Y - cgo.TargetY,
858 		                               std::max(X, X2) + cgo.X - cgo.TargetX, std::max(Y, Y2) + cgo.Y - cgo.TargetY, 0xffffffff, line_width);
859 	// Draw drag line
860 	if (DragLine)
861 		pDraw->DrawLineDw(cgo.Surface,
862 		                              X + cgo.X - cgo.TargetX, Y + cgo.Y - cgo.TargetY,
863 		                              X2 + cgo.X - cgo.TargetX, Y2 + cgo.Y - cgo.TargetY, 0xffffffff, line_width);
864 	// Draw drop target
865 	if (DropTarget)
866 		::GraphicsResource.fctMouseCursor.DrawX(cgo.Surface,
867 				DropTarget->GetX() + cgo.X - cgo.TargetX - ::GraphicsResource.fctMouseCursor.Wdt / 2 / cgo.Zoom,
868 				DropTarget->GetY() + DropTarget->Shape.y + cgo.Y - cgo.TargetY - ::GraphicsResource.fctMouseCursor.Hgt / cgo.Zoom,
869 				float(::GraphicsResource.fctMouseCursor.Wdt) / cgo.Zoom,
870 				float(::GraphicsResource.fctMouseCursor.Hgt) / cgo.Zoom, C4MC_Cursor_DropInto);
871 	// Draw paint circle
872 	if (Mode == C4CNS_ModeDraw && has_mouse_hover && ::Console.ToolsDlg.Grade>0 && ::Console.ToolsDlg.IsGradedTool())
873 	{
874 		// shadow for recognition on white background/material
875 		pDraw->DrawCircleDw(cgo.Surface, X + cgo.X - cgo.TargetX + 1.0f/cgo.Zoom, Y + cgo.Y - cgo.TargetY + 1.0f / cgo.Zoom, ::Console.ToolsDlg.Grade, 0xff000000, line_width);
876 		// actual circle
877 		pDraw->DrawCircleDw(cgo.Surface, X + cgo.X - cgo.TargetX, Y + cgo.Y - cgo.TargetY, ::Console.ToolsDlg.Grade, 0xffffffff, line_width);
878 	}
879 	// Draw creator preview
880 	if (Mode == C4CNS_ModeCreateObject && has_mouse_hover && creator_def)
881 	{
882 		C4TargetFacet cgo_creator;
883 		cgo_creator.Set(cgo.Surface, X + cgo.X - cgo.TargetX, Y + cgo.Y - cgo.TargetY,
884 			creator_def->Shape.Wdt, creator_def->Shape.Hgt, 0, 0, cgo.Zoom, 0, 0);
885 		if (!creator_overlay)
886 		{
887 			creator_overlay = std::make_unique<C4GraphicsOverlay>();
888 			creator_overlay->SetAsBase(&creator_def->Graphics, C4GFXBLIT_ADDITIVE);
889 		}
890 		creator_overlay->Draw(cgo_creator, nullptr, NO_OWNER);
891 	}
892 	// Draw object highlight
893 	C4Object *highlight = highlighted_object.getObj();
894 	if (highlight) DrawObject(cgo, highlight, 0xffff8000, true, false); // highlight selection if shift is pressed
895 }
896 
897 
DrawSelectMark(C4Facet & cgo,FLOAT_RECT frame,float width,uint32_t color)898 void C4EditCursor::DrawSelectMark(C4Facet &cgo, FLOAT_RECT frame, float width, uint32_t color)
899 {
900 	if ((cgo.Wdt<1) || (cgo.Hgt<1)) return;
901 
902 	if (!cgo.Surface) return;
903 
904 	const float EDGE_WIDTH = 2.f;
905 
906 	unsigned char c[4] = {
907 		static_cast<unsigned char>((color >> 16) & 0xff),
908 		static_cast<unsigned char>((color >>  8) & 0xff),
909 		static_cast<unsigned char>((color >>  0) & 0xff),
910 		static_cast<unsigned char>((color >> 24) & 0xff)
911 	};
912 
913 	const C4BltVertex vertices[] = {
914 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left + EDGE_WIDTH, frame.top, 0.f },
915 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left, frame.top, 0.f },
916 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left, frame.top, 0.f },
917 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left, frame.top+EDGE_WIDTH, 0.f },
918 
919 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left+EDGE_WIDTH, frame.bottom-1, 0.f },
920 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left, frame.bottom-1, 0.f },
921 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left, frame.bottom-1, 0.f },
922 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.left, frame.bottom-1-EDGE_WIDTH, 0.f },
923 
924 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1-EDGE_WIDTH, frame.top, 0.f },
925 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1, frame.top, 0.f },
926 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1, frame.top, 0.f },
927 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1, frame.top+EDGE_WIDTH, 0.f },
928 
929 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1-EDGE_WIDTH, frame.bottom-1, 0.f },
930 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1, frame.bottom-1, 0.f },
931 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1, frame.bottom-1, 0.f },
932 		{ 0.f, 0.f, { c[0], c[1], c[2], c[3] }, frame.right-1, frame.bottom-1-EDGE_WIDTH, 0.f },
933 	};
934 
935 	const unsigned int n_vertices = sizeof(vertices) / sizeof(vertices[0]);
936 
937 	pDraw->PerformMultiLines(cgo.Surface, vertices, n_vertices, width, nullptr);
938 }
939 
940 
MoveSelection(C4Real XOff,C4Real YOff,bool drag_finished)941 void C4EditCursor::MoveSelection(C4Real XOff, C4Real YOff, bool drag_finished)
942 {
943 	EMMoveObject(fShiftWasDown ? EMMO_MoveForced : EMMO_Move, XOff, YOff, nullptr, &selection, nullptr, drag_finished);
944 }
945 
FrameSelection()946 void C4EditCursor::FrameSelection()
947 {
948 	ClearSelection();
949 	for (C4Object *cobj : Objects)
950 	{
951 		if (cobj->Status && cobj->OCF & OCF_NotContained)
952 		{
953 			if (Inside(cobj->GetX(),std::min(X,X2),std::max(X,X2)) && Inside(cobj->GetY(),std::min(Y,Y2),std::max(Y,Y2)))
954 				AddToSelection(cobj);
955 		}
956 	}
957 	OnSelectionChanged();
958 }
959 
In(const char * szText)960 bool C4EditCursor::In(const char *szText)
961 {
962 	::Console.RegisterRecentInput(szText, C4Console::MRU_Object);
963 	EMMoveObject(EMMO_Script, Fix0, Fix0, nullptr, &selection, szText);
964 	selection.ConsolidateEmpty();
965 	::Console.PropertyDlgUpdate(selection, true);
966 	return true;
967 }
968 
Default()969 void C4EditCursor::Default()
970 {
971 	fAltWasDown=false;
972 	fShiftWasDown=false;
973 	Mode = C4CNS_ModeEdit;
974 	X=Y=X2=Y2=0;
975 	Target=DropTarget=nullptr;
976 #ifdef USE_WIN32_WINDOWS
977 	hMenu=nullptr;
978 #endif
979 	Hold=DragFrame=DragLine=DragShape=DragTransform=false;
980 	selection.clear();
981 	creator_def = nullptr;
982 	creator_overlay = nullptr;
983 	has_mouse_hover = false;
984 	selection_invalid = false;
985 	DragRot0 = DragRotLast = 0; DragCon0 = DragConLast = FullCon;
986 }
987 
Clear()988 void C4EditCursor::Clear()
989 {
990 #ifdef USE_WIN32_WINDOWS
991 	if (hMenu) DestroyMenu(hMenu); hMenu=nullptr;
992 #endif
993 #ifdef WITH_DEBUG_MODE
994 	ObjselectDelItems();
995 #endif
996 	selection.clear();
997 	Console.PropertyDlgUpdate(selection, false);
998 	creator_overlay.reset(nullptr);
999 #ifdef WITH_QT_EDITOR
1000 	shapes->ClearShapes(); // Should really be empty already
1001 #endif
1002 }
1003 
SetMode(int32_t iMode)1004 bool C4EditCursor::SetMode(int32_t iMode)
1005 {
1006 	// Store focus
1007 #ifdef USE_WIN32_WINDOWS
1008 	HWND hFocus=GetFocus();
1009 #endif
1010 	// Update console buttons (always)
1011 	Console.UpdateModeCtrls(iMode);
1012 	// No change
1013 	if (iMode==Mode) return true;
1014 	// Set mode
1015 	Mode = iMode;
1016 	// Update prop tools by mode
1017 	switch (Mode)
1018 	{
1019 	case C4CNS_ModeEdit: case C4CNS_ModePlay: case C4CNS_ModeCreateObject:
1020 		Console.ToolsDlgClose();
1021 		break;
1022 	case C4CNS_ModeDraw:
1023 		Console.PropertyDlgClose();
1024 		break;
1025 	}
1026 	if (Mode == C4CNS_ModePlay)
1027 	{
1028 		::MouseControl.ShowCursor();
1029 	}
1030 	else
1031 	{
1032 		OpenPropTools();
1033 		::MouseControl.HideCursor();
1034 	}
1035 	// Restore focus
1036 #ifdef USE_WIN32_WINDOWS
1037 	SetFocus(hFocus);
1038 #endif
1039 	// Done
1040 	return true;
1041 }
1042 
ToggleMode()1043 bool C4EditCursor::ToggleMode()
1044 {
1045 
1046 	if (!EditingOK()) return false;
1047 
1048 	// Step through modes
1049 	int32_t iNewMode;
1050 	switch (Mode)
1051 	{
1052 	case C4CNS_ModePlay: iNewMode=C4CNS_ModeEdit; break;
1053 #ifdef WITH_QT_EDITOR
1054 	case C4CNS_ModeEdit: iNewMode=C4CNS_ModeCreateObject; break;
1055 #else
1056 	case C4CNS_ModeEdit: iNewMode = C4CNS_ModeDraw; break;
1057 #endif
1058 	case C4CNS_ModeCreateObject: iNewMode = C4CNS_ModeDraw; break;
1059 	case C4CNS_ModeDraw: iNewMode=C4CNS_ModePlay; break;
1060 	default:             iNewMode=C4CNS_ModePlay; break;
1061 	}
1062 
1063 	// Set new mode
1064 	SetMode(iNewMode);
1065 
1066 	return true;
1067 }
1068 
ApplyCreateObject(bool container)1069 void C4EditCursor::ApplyCreateObject(bool container)
1070 {
1071 	if (!EditingOK()) return;
1072 	if (!creator_def) return;
1073 	if (container && !DropTarget) return;
1074 	// execute/send control
1075 	EMControl(CID_EMMoveObj, C4ControlEMMoveObject::CreateObject(creator_def->id, ftofix(X), ftofix(Y), container ? DropTarget : nullptr));
1076 }
1077 
ApplyToolBrush()1078 void C4EditCursor::ApplyToolBrush()
1079 {
1080 	if (!EditingOK(true)) return;
1081 	C4ToolsDlg *pTools=&Console.ToolsDlg;
1082 	// execute/send control
1083 	EMControl(CID_EMDrawTool, new C4ControlEMDrawTool(EMDT_Brush, ::Landscape.GetMode(), X,Y,0,0, pTools->Grade, pTools->Material, pTools->Texture, pTools->BackMaterial, pTools->BackTexture));
1084 }
1085 
ApplyToolLine()1086 void C4EditCursor::ApplyToolLine()
1087 {
1088 	if (!EditingOK(true)) return;
1089 	C4ToolsDlg *pTools=&Console.ToolsDlg;
1090 	// execute/send control
1091 	EMControl(CID_EMDrawTool, new C4ControlEMDrawTool(EMDT_Line, ::Landscape.GetMode(), X,Y,X2,Y2, pTools->Grade, pTools->Material,pTools->Texture, pTools->BackMaterial, pTools->BackTexture));
1092 }
1093 
ApplyToolRect()1094 void C4EditCursor::ApplyToolRect()
1095 {
1096 	if (!EditingOK(true)) return;
1097 	C4ToolsDlg *pTools=&Console.ToolsDlg;
1098 	// execute/send control
1099 	EMControl(CID_EMDrawTool, new C4ControlEMDrawTool(EMDT_Rect, ::Landscape.GetMode(), X,Y,X2,Y2, pTools->Grade, pTools->Material, pTools->Texture, pTools->BackMaterial, pTools->BackTexture));
1100 }
1101 
ApplyToolFill()1102 void C4EditCursor::ApplyToolFill()
1103 {
1104 	if (!EditingOK(true)) return;
1105 	C4ToolsDlg *pTools=&Console.ToolsDlg;
1106 	// execute/send control
1107 	EMControl(CID_EMDrawTool, new C4ControlEMDrawTool(EMDT_Fill, ::Landscape.GetMode(), X,Y,0,Y2, pTools->Grade, pTools->Material, nullptr, nullptr, nullptr));
1108 }
1109 
AppendMenuItem(int num,const StdStrBuf & label)1110 void C4EditCursor::AppendMenuItem(int num, const StdStrBuf & label)
1111 {
1112 #ifdef USE_WIN32_WINDOWS
1113 	itemsObjselect[num].ItemId = IDM_VPORTDYN_FIRST + num;
1114 	if (num)
1115 		AppendMenu(GetSubMenu(hMenu,0), MF_STRING, IDM_VPORTDYN_FIRST + num, label.GetWideChar());
1116 	else
1117 		AppendMenu(GetSubMenu(hMenu,0), MF_SEPARATOR, IDM_VPORTDYN_FIRST, nullptr);
1118 #endif
1119 }
1120 
DoContextMenu(DWORD dwKeyState)1121 bool C4EditCursor::DoContextMenu(DWORD dwKeyState)
1122 {
1123 	bool fObjectSelected = !!selection.GetObject();
1124 #ifdef USE_WIN32_WINDOWS
1125 	POINT point; GetCursorPos(&point);
1126 	HMENU hContext = GetSubMenu(hMenu,0);
1127 	SetMenuItemEnable(hContext, IDM_VIEWPORT_DELETE, fObjectSelected && Console.Editing);
1128 	SetMenuItemEnable(hContext, IDM_VIEWPORT_DUPLICATE, fObjectSelected && Console.Editing);
1129 	SetMenuItemEnable(hContext, IDM_VIEWPORT_CONTENTS, fObjectSelected && selection.GetObject()->Contents.ObjectCount() && Console.Editing);
1130 	SetMenuItemText(hContext,IDM_VIEWPORT_DELETE,LoadResStr("IDS_MNU_DELETE"));
1131 	SetMenuItemText(hContext,IDM_VIEWPORT_DUPLICATE,LoadResStr("IDS_MNU_DUPLICATE"));
1132 	SetMenuItemText(hContext,IDM_VIEWPORT_CONTENTS,LoadResStr("IDS_MNU_CONTENTS"));
1133 #endif
1134 
1135 	// Add selection and custom command entries for any objects at the cursor
1136 	ObjselectDelItems(); // clear previous entries
1137 	C4FindObjectAtPoint pFO(X,Y);
1138 	C4ValueArray * atcursor; atcursor = pFO.FindMany(::Objects, ::Objects.Sectors); // needs freeing (single object ptr)
1139 	int itemcount = atcursor->GetSize();
1140 	if(itemcount > 0)
1141 	{
1142 		// Count required entries for all objects and their custom commands
1143 		int entrycount = itemcount;
1144 		for (int i_item = 0; i_item < itemcount; ++i_item)
1145 		{
1146 			C4Object *pObj = (*atcursor)[i_item].getObj(); assert(pObj);
1147 			C4ValueArray *custom_commands = pObj->GetPropertyArray(P_EditCursorCommands);
1148 			if (custom_commands) entrycount += custom_commands->GetSize();
1149 		}
1150 #ifdef USE_WIN32_WINDOWS
1151 		// If too many entries would be shown, add a "..." in the end
1152 		const int maxentries = 25; // Maximum displayed objects. if you raise it, also change note with IDM_VPORTDYN_FIRST in resource.h
1153 		bool has_too_many_entries = (entrycount > maxentries);
1154 		if (has_too_many_entries) entrycount = maxentries + 1;
1155 #else
1156 		const int maxentries = std::numeric_limits<int>::max();
1157 #endif
1158 		itemsObjselect.resize(entrycount + 1); // +1 for a separator
1159 		// Add a separator bar
1160 		itemsObjselect[0].Object = nullptr;
1161 		itemsObjselect[0].Command.Clear();
1162 		itemsObjselect[0].EditCursor = this;
1163 		AppendMenuItem(0, StdStrBuf());
1164 		// Add all objects
1165 		int i_entry = 0;
1166 		for (int i_item = 0; i_item < itemcount; ++i_item)
1167 		{
1168 			++i_entry; if (i_entry >= maxentries) break;
1169 			// Add selection entry
1170 			C4Object *obj = (*atcursor)[i_item].getObj();
1171 			assert(obj);
1172 			itemsObjselect[i_entry].Object = obj;
1173 			itemsObjselect[i_entry].Command.Clear();
1174 			itemsObjselect[i_entry].EditCursor = this;
1175 			AppendMenuItem(i_entry, FormatString("%s #%i (%i/%i)", obj->GetName(), obj->Number, obj->GetX(), obj->GetY()));
1176 
1177 			// Add custom command entries
1178 			C4ValueArray *custom_commands = obj->GetPropertyArray(P_EditCursorCommands);
1179 			if (custom_commands) for (int i_cmd = 0; i_cmd < custom_commands->GetSize(); ++i_cmd)
1180 			{
1181 				++i_entry; if (i_entry >= maxentries) break;
1182 				const C4Value &cmd = custom_commands->GetItem(i_cmd);
1183 				StdStrBuf custom_command_szstr; C4AulFunc *custom_command; C4String *custom_command_string;
1184 				// Custom command either by string or by function pointer
1185 				if ((custom_command = cmd.getFunction()))
1186 					custom_command_szstr.Format("%s()", custom_command->GetName());
1187 				else if ((custom_command_string = cmd.getStr()))
1188 					custom_command_szstr.Copy(custom_command_string->GetData()); // copy just in case script get reloaded inbetween
1189 				if (custom_command_szstr.getLength())
1190 				{
1191 					itemsObjselect[i_entry].Object = obj;
1192 					itemsObjselect[i_entry].Command.Take(custom_command_szstr);
1193 					itemsObjselect[i_entry].EditCursor = this;
1194 					AppendMenuItem(i_entry, FormatString("%s->%s", obj->GetName(), custom_command_szstr.getData()));
1195 				}
1196 			}
1197 		}
1198 #ifdef USE_WIN32_WINDOWS
1199 		if (has_too_many_entries)
1200 		{
1201 			AppendMenu(hContext, MF_GRAYED, IDM_VPORTDYN_FIRST + maxentries + 1, L"...");
1202 			itemsObjselect[maxentries + 1].ItemId = IDM_VPORTDYN_FIRST + maxentries + 1;
1203 			itemsObjselect[maxentries + 1].Object = nullptr;
1204 			itemsObjselect[maxentries + 1].Command.Clear();
1205 		}
1206 #endif
1207 	}
1208 	delete atcursor;
1209 
1210 #ifdef USE_WIN32_WINDOWS
1211 	int32_t iItem = TrackPopupMenu(
1212 	                  hContext,
1213 	                  TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_NONOTIFY,
1214 	                  point.x,point.y, 0,
1215 	                  Console.hWindow,
1216 	                  nullptr);
1217 	switch (iItem)
1218 	{
1219 	case IDM_VIEWPORT_DELETE: Delete(); break;
1220 	case IDM_VIEWPORT_DUPLICATE: Duplicate(); break;
1221 	case IDM_VIEWPORT_CONTENTS: GrabContents(); break;
1222 	case 0: break;
1223 	default:
1224 		for(std::vector<ObjselItemDt>::iterator it = itemsObjselect.begin() + 1; it != itemsObjselect.end(); ++it)
1225 			if(it->ItemId == iItem)
1226 			{
1227 				if (it->Command.getLength())
1228 					DoContextObjCommand(it->Object, it->Command.getData());
1229 				else
1230 					DoContextObjsel(it->Object, (dwKeyState & MK_SHIFT) == 0);
1231 				break;
1232 			}
1233 		break;
1234 	}
1235 	ObjselectDelItems();
1236 #endif
1237 	return true;
1238 }
1239 
GrabContents()1240 void C4EditCursor::GrabContents()
1241 {
1242 	// Set selection
1243 	C4Object *pFrom;
1244 	if (!( pFrom = selection.GetObject() )) return;
1245 	ClearSelection();
1246 	for (C4Object *cont : pFrom->Contents) AddToSelection(cont);
1247 	OnSelectionChanged();
1248 	Hold=true;
1249 
1250 	// Exit all objects
1251 	EMMoveObject(EMMO_Exit, Fix0, Fix0, nullptr, &selection);
1252 }
1253 
UpdateDropTarget(DWORD dwKeyState)1254 void C4EditCursor::UpdateDropTarget(DWORD dwKeyState)
1255 {
1256 	// A drop target is set if holding down control either while moving an object or in object creation mode
1257 	DropTarget=nullptr;
1258 
1259 	if (dwKeyState & MK_CONTROL)
1260 		if (selection.GetObject() || (Mode == C4CNS_ModeCreateObject && creator_def))
1261 			for (C4Object *cobj : Objects)
1262 			{
1263 				if (cobj->Status)
1264 					if (!cobj->Contained)
1265 						if (Inside<int32_t>(X-(cobj->GetX()+cobj->Shape.x),0,cobj->Shape.Wdt-1))
1266 							if (Inside<int32_t>(Y-(cobj->GetY()+cobj->Shape.y),0,cobj->Shape.Hgt-1))
1267 								if (!selection.IsContained(cobj))
1268 									{ DropTarget=cobj; break; }
1269 			}
1270 
1271 }
1272 
PutContents()1273 void C4EditCursor::PutContents()
1274 {
1275 	if (!DropTarget) return;
1276 	EMMoveObject(EMMO_Enter, Fix0, Fix0, DropTarget, &selection);
1277 }
1278 
GetTarget()1279 C4Object *C4EditCursor::GetTarget()
1280 {
1281 	return Target;
1282 }
1283 
EditingOK(bool for_landscape_drawing)1284 bool C4EditCursor::EditingOK(bool for_landscape_drawing)
1285 {
1286 	if (!Console.Editing)
1287 	{
1288 		Hold=false;
1289 		Console.Message(LoadResStr("IDS_CNS_NONETEDIT"));
1290 		return false;
1291 	}
1292 	return true;
1293 }
1294 
GetMode()1295 int32_t C4EditCursor::GetMode()
1296 {
1297 	return Mode;
1298 }
1299 
ToolFailure()1300 void C4EditCursor::ToolFailure()
1301 {
1302 	C4ToolsDlg *pTools=&Console.ToolsDlg;
1303 	Hold=false;
1304 	Console.Message(FormatString(LoadResStr("IDS_CNS_NOMATDEF"),pTools->Material,pTools->Texture).getData());
1305 }
1306 
ApplyToolPicker()1307 void C4EditCursor::ApplyToolPicker()
1308 {
1309 	int32_t iMaterial;
1310 	BYTE byIndex;
1311 	switch (::Landscape.GetMode())
1312 	{
1313 	case LandscapeMode::Static:
1314 		{
1315 			bool material_set = false;
1316 			int32_t x = X/::Landscape.GetMapZoom();
1317 			int32_t y = Y/::Landscape.GetMapZoom();
1318 			// Material-texture from map
1319 			if ((byIndex = ::Landscape.GetMapIndex(x, y)))
1320 			{
1321 				const C4TexMapEntry *pTex = ::TextureMap.GetEntry(byIndex);
1322 				if (pTex && pTex->GetMaterialName() && *pTex->GetMaterialName())
1323 				{
1324 					const BYTE byIndexBkg = Landscape.GetBackMapIndex(x, y);
1325 					Console.ToolsDlg.SelectMaterial(pTex->GetMaterialName());
1326 					Console.ToolsDlg.SelectTexture(pTex->GetTextureName());
1327 
1328 					// Set background index if GUI backend supports it
1329 					if (Console.ToolsDlg.ModeBack)
1330 					{
1331 						const C4TexMapEntry *pBgTex = ::TextureMap.GetEntry(byIndexBkg);
1332 						if (pBgTex && !pBgTex->isNull())
1333 						{
1334 							Console.ToolsDlg.SelectBackMaterial(pBgTex->GetMaterialName());
1335 							Console.ToolsDlg.SelectBackTexture(pBgTex->GetTextureName());
1336 						}
1337 						else
1338 						{
1339 							Console.ToolsDlg.SelectBackMaterial(C4TLS_MatSky);
1340 						}
1341 					}
1342 					else
1343 					{
1344 						Console.ToolsDlg.SetIFT(byIndexBkg != 0);
1345 					}
1346 
1347 					material_set = true;
1348 				}
1349 			}
1350 			// default to sky, because invalid materials are always rendered as sky
1351 			if (!material_set) Console.ToolsDlg.SelectMaterial(C4TLS_MatSky);
1352 			break;
1353 		}
1354 	case LandscapeMode::Exact:
1355 		// Material only from landscape
1356 		if (MatValid(iMaterial=GBackMat(X,Y)))
1357 		{
1358 			Console.ToolsDlg.SelectMaterial(::MaterialMap.Map[iMaterial].Name);
1359 			Console.ToolsDlg.SetIFT(Landscape.GetBackPix(X, Y) != 0);
1360 		}
1361 		else
1362 			Console.ToolsDlg.SelectMaterial(C4TLS_MatSky);
1363 		break;
1364 	}
1365 	Hold=false;
1366 }
1367 
EMMoveObject(C4ControlEMObjectAction eAction,C4Real tx,C4Real ty,C4Object * pTargetObj,const C4EditCursorSelection * pObjs,const char * szScript,bool drag_finished)1368 void C4EditCursor::EMMoveObject(C4ControlEMObjectAction eAction, C4Real tx, C4Real ty, C4Object *pTargetObj, const C4EditCursorSelection *pObjs, const char *szScript, bool drag_finished)
1369 {
1370 	// construct object list
1371 	int32_t iObjCnt = 0; int32_t *pObjIDs = nullptr;
1372 	if (pObjs && (iObjCnt = pObjs->ObjectCount()))
1373 	{
1374 		pObjIDs = new int32_t [iObjCnt];
1375 		// fill
1376 		int32_t i = 0;
1377 		for (const C4Value &vobj : *pObjs)
1378 		{
1379 			C4Object *obj = vobj.getObj();
1380 			if (!obj) continue;
1381 			if (obj && obj->Status)
1382 				pObjIDs[i++] = obj->Number;
1383 			else
1384 				pObjIDs[i++] = 0;
1385 		}
1386 	}
1387 
1388 	// execute control
1389 	EMControl(CID_EMMoveObj, new C4ControlEMMoveObject(eAction, tx, ty, pTargetObj, iObjCnt, pObjIDs, szScript, drag_finished));
1390 
1391 }
1392 
EMControl(C4PacketType eCtrlType,C4ControlPacket * pCtrl)1393 void C4EditCursor::EMControl(C4PacketType eCtrlType, C4ControlPacket *pCtrl)
1394 {
1395 	::Control.DoInput(eCtrlType, pCtrl, CDT_Decide);
1396 }
1397 
1398 
ObjselectDelItems()1399 void C4EditCursor::ObjselectDelItems() {
1400 	if(!itemsObjselect.size()) return;
1401 	std::vector<ObjselItemDt>::iterator it = itemsObjselect.begin();
1402 	while(it != itemsObjselect.end()) {
1403 		#if defined(USE_WIN32_WINDOWS)
1404 		if(!it->ItemId) { ++it; continue; }
1405 		HMENU hContext = GetSubMenu(hMenu,0);
1406 		DeleteMenu(hContext, it->ItemId, MF_BYCOMMAND);
1407 		#endif
1408 		++it;
1409 	}
1410 	itemsObjselect.resize(0);
1411 }
1412 
AltDown()1413 bool C4EditCursor::AltDown()
1414 {
1415 	// alt only has an effect in draw mode (picker)
1416 	if (Mode == C4CNS_ModeDraw)
1417 	{
1418 		Console.ToolsDlg.SetAlternateTool();
1419 	}
1420 	// key not processed - allow further usages of Alt
1421 	return false;
1422 }
1423 
AltUp()1424 bool C4EditCursor::AltUp()
1425 {
1426 	if (Mode == C4CNS_ModeDraw)
1427 	{
1428 		Console.ToolsDlg.ResetAlternateTool();
1429 	}
1430 	// key not processed - allow further usages of Alt
1431 	return false;
1432 }
1433 
DoContextObjsel(C4Object * obj,bool clear)1434 void C4EditCursor::DoContextObjsel(C4Object * obj, bool clear)
1435 {
1436 	if (clear) ClearSelection(obj);
1437 	AddToSelection(obj);
1438 	OnSelectionChanged();
1439 }
1440 
DoContextObjCommand(C4Object * obj,const char * cmd)1441 void C4EditCursor::DoContextObjCommand(C4Object * obj, const char *cmd)
1442 {
1443 	// Command going through queue for sync
1444 	if (!obj || !cmd) return;
1445 	In(FormatString("Object(%d)->%s", obj->Number, cmd).getData());
1446 }
1447 
GetCurrentSelectionPosition(int32_t * x,int32_t * y)1448 bool C4EditCursor::GetCurrentSelectionPosition(int32_t *x, int32_t *y)
1449 {
1450 	C4Object *obj = selection.GetObject();
1451 	if (!obj || !obj->Status) return false;
1452 	*x = obj->GetX();
1453 	*y = obj->GetY();
1454 	return true;
1455 }
1456 
SetHighlightedObject(C4Object * new_highlight)1457 void C4EditCursor::SetHighlightedObject(C4Object *new_highlight)
1458 {
1459 	highlighted_object = C4VObj(new_highlight);
1460 }
1461 
IsHoveringTransformMarker() const1462 bool C4EditCursor::IsHoveringTransformMarker() const
1463 {
1464 	float trf_marker_x, trf_marker_y;
1465 	if (HasTransformMarker(&trf_marker_x, &trf_marker_y, Zoom))
1466 	{
1467 		C4Object *obj = selection.GetObject();
1468 		float dx = (float(X - obj->GetX()) - trf_marker_x) * Zoom;
1469 		float dy = (float(Y - obj->GetY()) - trf_marker_y) * Zoom;
1470 		if (dx*dx + dy*dy <= ::GraphicsResource.fctTransformKnob.Hgt * ::GraphicsResource.fctTransformKnob.Hgt / 4)
1471 			return true;
1472 	}
1473 	return false;
1474 }
1475