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