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