1 /**
2 	Clonk menu controls
3 	Author: Newton
4 
5 	This object provides handling of the clonk controls including item
6 	management, backpack controls and standard throwing behaviour. It
7 	should be included into any clonk/crew definition.
8 	The controls in System.ocg/PlayerControl.c only provide basic movement
9 	handling, namely the movement left, right, up and down. The rest is
10 	handled here:
11 	(object) menu control and it's callbacks and
12 	forwards to script.
13 
14 	Objects that inherit this object need to return _inherited(...) in the
15 	following callbacks (if defined):
16 		Construction, Collection2, Ejection, RejectCollect, Departure,
17 		Entrance, AttachTargetLost, CrewSelection, Death,
18 		Destruction, OnActionChanged
19 
20 	Used properties
21 	this.control.menu: the menu that is currently assigned to the Clonk. Use the methods SetMenu/GetMenu/etc to access it.
22 
23 */
24 
25 /* ++++++++++++++++++++++++ Callbacks ++++++++++++++++++++++++ */
26 
Construction()27 protected func Construction()
28 {
29 	if (this.control == nil)
30 		this.control = {};
31 
32 	this.control.menu = nil;
33 	return _inherited(...);
34 }
35 
36 
37 // ...aaand the same for when the clonk is deselected
CrewSelection(bool unselect)38 protected func CrewSelection(bool unselect)
39 {
40 	if (unselect)
41 	{
42 		// if there is still a menu, cancel it too...
43 		CancelMenu();
44 	}
45 	return _inherited(unselect, ...);
46 }
47 
Destruction()48 protected func Destruction()
49 {
50 	// close open menus, ...
51 	CancelMenu();
52 	return _inherited(...);
53 }
54 
Death()55 protected func Death()
56 {
57 	// close open menus, ...
58 	CancelMenu();
59 	return _inherited(...);
60 }
61 
62 /* +++++++++++++++++++++++ Menu control +++++++++++++++++++++++ */
63 
HasMenuControl()64 func HasMenuControl()
65 {
66 	return true;
67 }
68 
69 // helper function that can be attached to a proplist to set callbacks on-the-fly
GetTrue()70 func GetTrue()
71 {
72 	return true;
73 }
74 
75 /*
76 Sets the menu this Clonk currently has focus of. Old menus that have been opened via SetMenu will be closed, making sure that only one menu is open at a time.
77 Additionally, the Clonk's control is disabled while a menu is open.
78 The menu parameter can either be an object that closes its menu via a Close() callback or it can be a menu ID as returned by GuiOpen. When /menu/ is such an ID,
79 the menu will be closed via GuiClose when a new menu is opened. If you need to do cleaning up, you will have to use the OnClose callback of the menu.
80 When you call SetMenu with a menu ID, you should also call clonk->MenuClosed(), once your menu is closed.
81 */
SetMenu(new_menu,bool unclosable)82 func SetMenu(new_menu, bool unclosable)
83 {
84 	unclosable = unclosable ?? false;
85 	var current_menu = this.control.menu;
86 
87 	// no news?
88 	if (new_menu) // if new_menu==nil, it is important that we still do the cleaning-up below even if we didn't have a menu before (see MenuClosed())
89 		if (current_menu == new_menu)
90 			return;
91 
92 	// close old one!
93 	if (current_menu != nil)
94 	{
95 		if (GetType(current_menu) == C4V_C4Object)
96 			current_menu->Close();
97 		else if (GetType(current_menu) == C4V_PropList)
98 			GuiClose(current_menu.ID);
99 		else
100 			FatalError("Library_ClonkControl::SetMenu() was called with invalid parameter.");
101 	}
102 	else
103 	{
104 		// we have a new menu but didn't have another one before? Enable menu controls!
105 		if (new_menu)
106 		{
107 			this->~CancelUse();
108 			// stop clonk
109 			SetComDir(COMD_Stop);
110 
111 			if (this->~HasVirtualCursor())
112 			{
113 				this->~VirtualCursor()->StartAim(this, 0, new_menu);
114 			}
115 			else
116 			{
117 				if (GetType(new_menu) == C4V_C4Object && new_menu->~CursorUpdatesEnabled())
118 					SetPlayerControlEnabled(GetOwner(), CON_GUICursor, true);
119 
120 				SetPlayerControlEnabled(GetOwner(), CON_GUIClick1, true);
121 				SetPlayerControlEnabled(GetOwner(), CON_GUIClick2, true);
122 			}
123 		}
124 	}
125 
126 	if (new_menu)
127 	{
128 		if (GetType(new_menu) == C4V_C4Object)
129 		{
130 			this.control.menu = new_menu;
131 		}
132 		else if (GetType(new_menu) == C4V_Int)
133 		{
134 			// add a proplist, so that it is always safe to call functions on clonk->GetMenu()
135 			this.control.menu =
136 			{
137 				ID = new_menu
138 			};
139 		}
140 		else
141 			FatalError("Library_ClonkControl::SetMenu called with invalid parameter!");
142 
143 		// make sure the menu is unclosable even if it is just a GUI ID
144 		if (unclosable)
145 		{
146 			this.control.menu.Unclosable = Library_ClonkControl.GetTrue;
147 		}
148 	}
149 	else
150 	{
151 		// always disable cursors, even if no old menu existed, because it can happen that a menu removes itself and thus the Clonk never knows whether the cursors are active or not
152 		if (this->~HasVirtualCursor())
153 		{
154 			this->~RemoveVirtualCursor(); // for gamepads
155 		}
156 		SetPlayerControlEnabled(GetOwner(), CON_GUICursor, false);
157 		SetPlayerControlEnabled(GetOwner(), CON_GUIClick1, false);
158 		SetPlayerControlEnabled(GetOwner(), CON_GUIClick2, false);
159 
160 		this.control.menu = nil;
161 	}
162 	return this.control.menu;
163 }
164 
MenuClosed()165 func MenuClosed()
166 {
167 	// make sure not to clean up the menu again
168 	this.control.menu = nil;
169 	// and remove cursors etc.
170 	SetMenu(nil);
171 }
172 
173 /*
174 Returns the current menu or nil. If a menu is returned, it is always a proplist (but not necessarily an object).
175 Stuff like if (clonk->GetMenu()) clonk->GetMenu()->~IsClosable(); is always safe.
176 If you want to remove the menu, the suggested method is clonk->TryCancelMenu() to handle unclosable menus correctly.
177 */
GetMenu()178 func GetMenu()
179 {
180 	// No new-style menu set? Return the classic menu ID. This is deprecated and should be removed in some future.
181 	// This function must return a proplist, but clashes with the engine-defined "GetMenu".
182 	// This workaround here at least allows developers to reach the Clonk's menu ID.
183 	if (this.control.menu == nil)
184 	{
185 		var menu_id = inherited(...);
186 		if (menu_id) return {ID = menu_id};
187 	}
188 	return this.control.menu;
189 }
190 
191 // Returns true when an existing menu was closed
CancelMenu()192 func CancelMenu()
193 {
194 	if (this.control.menu)
195 	{
196 		SetMenu(nil);
197 		return true;
198 	}
199 
200 	return false;
201 }
202 
203 // Tries to cancel a non-unclosable menu. Returns true when there is no menu left after this call (even if there never was one).
TryCancelMenu()204 func TryCancelMenu()
205 {
206 	if (!this.control.menu)
207 		return true;
208 	if (this.control.menu->~Unclosable())
209 		return false;
210 	CancelMenu();
211 	return true;
212 }
213 
RejectShiftCursor()214 public func RejectShiftCursor()
215 {
216 	if (this.control.menu && this.control.menu->~Unclosable())
217 		return true;
218 	return _inherited(...);
219 }
220 
OnShiftCursor()221 public func OnShiftCursor()
222 {
223 	TryCancelMenu();
224 	return _inherited(...);
225 }
226 
227