1 /**
2 ControllerGoal
3
4 Shows the scenario goal in the top right corner, next to the wealth icon.
5 Goal icon is clickable. On click displays a detailed goal description.
6
7 @authors Maikel, Clonkonaut
8 */
9
10 // HUD margin and size in tenths of em.
11 static const GUI_Controller_Goal_IconSize = 25;
12 static const GUI_Controller_Goal_IconMargin = 5;
13
14 // Does also use constants defined by ControllerWealth to ensure distance between both icons.
15
16 local goal_gui_menu;
17 local goal_gui_id;
18
19 local goal_info_id;
20 local goals;
21
22 /*-- Wealth Showing / Hiding --*/
23
ShiftGoal()24 public func ShiftGoal()
25 {
26 if (goal_gui_id == nil) return;
27
28 var offset = GUI_Controller_Wealth_IconSize + GUI_Controller_Wealth_IconMargin;
29 var x_end = GUI_Controller_Goal_IconMargin + offset;
30 var x_begin = GUI_Controller_Goal_IconMargin + GUI_Controller_Goal_IconSize + offset;
31
32 var update = {
33 Left = Format("100%%%s", ToEmString(-x_begin)),
34 Right = Format("100%%%s", ToEmString(-x_end)),
35 };
36
37 GuiUpdate(update, goal_gui_id);
38 }
39
UnshiftGoal()40 public func UnshiftGoal()
41 {
42 if (goal_gui_id == nil) return;
43
44 var x_end = GUI_Controller_Goal_IconMargin;
45 var x_begin = GUI_Controller_Goal_IconMargin + GUI_Controller_Goal_IconSize;
46
47 var update = {
48 Left = Format("100%%%s", ToEmString(-x_begin)),
49 Right = Format("100%%%s", ToEmString(-x_end)),
50 };
51
52 GuiUpdate(update, goal_gui_id);
53 }
54
55 /*-- Creation --*/
56
Construction()57 public func Construction()
58 {
59 var y_begin = GUI_Controller_Goal_IconMargin;
60 var y_end = y_begin + GUI_Controller_Goal_IconSize;
61 // Also take into account the margin and size of the wealth HUD if shown
62 var x_begin = y_end;
63 var x_end = y_begin;
64 if (this->~IsShowingWealth())
65 {
66 x_begin += GUI_Controller_Wealth_IconSize + GUI_Controller_Wealth_IconMargin;
67 x_end += GUI_Controller_Wealth_IconSize + GUI_Controller_Wealth_IconMargin;
68 }
69 // See also ShiftGoal() / UnshiftGoal()
70
71 goal_gui_menu =
72 {
73 Target = this,
74 Player = NO_OWNER, // The goal icon will become visible if OnGoalUpdate is called
75 Style = GUI_Multiple | GUI_IgnoreMouse,
76 Left = Format("100%%%s", ToEmString(-x_begin)),
77 Right = Format("100%%%s", ToEmString(-x_end)),
78 Top = ToEmString(y_begin),
79 Bottom = ToEmString(y_end),
80 Priority = 1,
81 text =
82 {
83 Style = GUI_TextHCenter | GUI_TextBottom,
84 Text = nil,
85 Priority = 3,
86 },
87 };
88 goal_gui_id = GuiOpen(goal_gui_menu);
89
90 return _inherited(...);
91 }
92
Destruction()93 private func Destruction()
94 {
95 GuiClose(goal_gui_id);
96 if (goal_info_id) GuiClose(goal_info_id);
97
98 _inherited(...);
99 }
100
101 /*-- Callbacks --*/
102
103 // Callback from the goal library: display this goal.
OnGoalUpdate(object goal)104 public func OnGoalUpdate(object goal)
105 {
106 // Also notify the open info menu.
107 OnGoalWindowUpdate(goal);
108 // If there is no goal hide the menu
109 if (!goal)
110 {
111 GuiUpdate({ Player = NO_OWNER }, goal_gui_id);
112
113 return _inherited(goal, ...);
114 }
115
116 // Get current goal display settings
117 var symbol = GetGoalSymbol(goal);
118 var graphics = GetGoalGraphicsName(goal);
119 var text = goal->~GetShortDescription(GetOwner());
120
121 // Determine changes
122 var update_symbol = goal_gui_menu.Symbol != symbol;
123 var update_graphics = goal_gui_menu.GraphicsName != graphics;
124 var update_text = goal_gui_menu.text.Text != text;
125
126 // Only update if something has changed.
127 if (update_symbol || update_graphics || update_text)
128 {
129 goal_gui_menu.text.Text = text;
130 goal_gui_menu.Symbol = symbol;
131 goal_gui_menu.GraphicsName = graphics;
132
133 goal_gui_menu.Player = GetOwner();
134 goal_gui_menu.Style = GUI_Multiple;
135 // Also add an hover and mouse click element.
136 goal_gui_menu.hover =
137 {
138 Symbol = { Std = nil, OnHover = GUI_Controller_Goal},
139 OnClick = GuiAction_Call(this, "OnGoalClick"),
140 OnMouseIn = GuiAction_SetTag("OnHover"),
141 OnMouseOut = GuiAction_SetTag("Std"),
142 Priority = 2,
143 };
144 GuiUpdate(goal_gui_menu, goal_gui_id);
145 }
146
147 return _inherited(goal, ...);
148 }
149
OnGoalClick()150 private func OnGoalClick()
151 {
152 if (goal_info_id)
153 CloseGoalWindow();
154 else
155 OpenGoalWindow();
156 }
157
158 /*-- Goal Info Menu --*/
159
OpenGoalWindow()160 private func OpenGoalWindow()
161 {
162 goals = FindObjects(Find_Category(C4D_Goal));
163 var nr_goals = GetLength(goals);
164 var menu_width = BoundBy(nr_goals * 2, 10, 20); // in em
165
166 // Safety: there has to be at least one goal.
167 if (nr_goals <= 0)
168 return;
169
170 // Main menu
171 var goal_info_menu =
172 {
173 Target = this,
174 Player = GetOwner(),
175 Decoration = GUI_MenuDeco,
176 Left = Format("50%%-%dem", menu_width),
177 Right = Format("50%%+%dem", menu_width),
178 Top = "50%-4em",
179 Bottom = "50%+8em",
180 OnClose = GuiAction_Call(this, "OnGoalWindowClosed"),
181 };
182
183 // Close button
184 GuiAddCloseButton(goal_info_menu, this, "OnCloseButtonClick");
185
186 // Text submenu
187 goal_info_menu.text =
188 {
189 Target = this,
190 ID = 1,
191 Left = "0%",
192 Right = "100%",
193 Top = "0%+4em",
194 Bottom = "100%",
195 };
196
197 // Goal icons: maximum number of 10 goals for now
198 for (var i = 0; i < Min(10, nr_goals); i++)
199 {
200 var menu = GoalSubMenu(goals[i], i);
201 GuiAddSubwindow(menu, goal_info_menu);
202 }
203
204 goal_info_id = GuiOpen(goal_info_menu);
205
206 // Select first goal and show its description.
207 OnGoalGUIHover(goals[0]);
208 }
209
CloseGoalWindow()210 private func CloseGoalWindow()
211 {
212 GuiClose(goal_info_id);
213 }
214
OnGoalWindowClosed()215 private func OnGoalWindowClosed()
216 {
217 goal_info_id = nil;
218 }
219
GoalSubMenu(object goal,int nr,int size)220 private func GoalSubMenu(object goal, int nr, int size)
221 {
222 if (size == nil)
223 size = 4;
224
225 var symbol = GetGoalSymbol(goal);
226 var graphics = GetGoalGraphicsName(goal);
227 // Create the goal submenu with id counting upwards from 2.
228 var prop_goal =
229 {
230 Target = this,
231 ID = nr + 2,
232 Left = Format("0%%+%dem", nr * size),
233 Right = Format("0%%+%dem", (nr + 1) * size),
234 Top = "0%",
235 Bottom = Format("0%%+%dem", size),
236 Symbol = symbol,
237 GraphicsName = graphics,
238 BackgroundColor = {Std = 0, Hover = 0x50ffffff},
239 OnMouseIn = [GuiAction_SetTag("Hover"), GuiAction_Call(this, "OnGoalGUIHover", goal)],
240 OnMouseOut = GuiAction_SetTag("Std"),
241 fulfilled = nil
242 };
243 // Indicate whether the goal is already fulfilled
244 if (goal->~IsFulfilled())
245 {
246 prop_goal.fulfilled =
247 {
248 Target = this,
249 Left = "100%-1em",
250 Right = "100%",
251 Top = "0%",
252 Bottom = "0%+1em",
253 Symbol = Icon_Ok,
254 };
255 }
256
257 return prop_goal;
258 }
259
OnCloseButtonClick()260 public func OnCloseButtonClick()
261 {
262 CloseGoalWindow();
263 }
264
OnGoalGUIHover(object goal)265 public func OnGoalGUIHover(object goal)
266 {
267 if (!goal)
268 return;
269 // Change text to the current goal.
270 var text = Format("<c ff0000>%s:</c> %s", goal->GetName(), goal->~GetDescription(GetOwner()));
271 GuiUpdateText(text, goal_info_id, 1, this);
272 }
273
OnGoalWindowUpdate(object goal)274 private func OnGoalWindowUpdate(object goal)
275 {
276 if (!goal || !goal_info_id)
277 return;
278
279 var index = GetIndexOf(goals, goal);
280 if (index == -1) return;
281
282 var menu = GoalSubMenu(goal, index);
283 // Update only very selectively. (To e.g. not reset the background/tag)
284 var update =
285 {
286 Symbol = menu.Symbol,
287 GraphicsName = menu.GraphicsName,
288 fulfilled = menu.fulfilled,
289 };
290 GuiUpdate(update, goal_info_id, menu.ID, menu.Target);
291 }
292
GetGoalSymbol(object goal)293 private func GetGoalSymbol(object goal)
294 {
295 return goal->~GetPictureDefinition(GetOwner()) ?? goal->GetID();
296 }
297
GetGoalGraphicsName(object goal)298 private func GetGoalGraphicsName(object goal)
299 {
300 return goal->~GetPictureName(GetOwner()) ?? goal->GetGraphics(GetOwner());
301 }
302