1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1999-2000 Revolution Software Ltd.
9  * This code is based on source code created by Revolution Software,
10  * used with permission.
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25  *
26  */
27 
28 #include "engines/icb/icon_menu.h"
29 #include "engines/icb/global_objects.h"
30 #include "engines/icb/sound.h"
31 #include "engines/icb/res_man.h"
32 #include "engines/icb/remora.h"
33 #include "engines/icb/mission.h"
34 
35 namespace ICB {
36 
37 // Use globals as it reduces rdata storage on PSX
38 const char *global_nothing_selected = "NOTHING_SELECTED";
39 
_icon_menu()40 _icon_menu::_icon_menu() {
41 	m_eIconMenuGameState = INACTIVE;
42 	m_bValidSelection = FALSE8;
43 	m_nKeyLock = FALSE8;
44 	m_nHighlightCounter = 0;
45 	m_bHighlightVisible = FALSE8;
46 	m_bAllowEscape = TRUE8;
47 	m_bWiderThanScreen = FALSE8;
48 	m_nAddedMedipacks = 0;
49 	m_nAddedClips = 0;
50 	m_nAddedSymbol = 0;
51 	m_nAddedFlashCount = 0;
52 
53 	strcpy(m_pcGlobalClusterFile, GLOBAL_CLUSTER_PATH);
54 	strcpy(m_pcIconCluster, ICON_CLUSTER_PATH);
55 
56 	m_nGlobalClusterHash = NULL_HASH;
57 	m_nIconClusterHash = NULL_HASH;
58 	m_nSelectedIconHash = NULL_HASH;
59 	m_pcSelectedIconName = global_nothing_selected;
60 }
61 
CycleIconMenu(const _input & sKeyboardState)62 bool8 _icon_menu::CycleIconMenu(const _input &sKeyboardState) {
63 	bool8 nRetVal = TRUE8;
64 
65 	static int32 lastInventoryPress = 0;
66 	int32 inventoryPress;
67 
68 	// Cycle the transparency for the highlight, to pulse the icon on screen
69 	++m_nHighlightCounter;
70 
71 	if (m_nHighlightCounter == ICON_MENU_HIGHLIGHT_SPEED) {
72 		m_nHighlightCounter = 0;
73 		m_bHighlightVisible = (bool8)!m_bHighlightVisible;
74 	}
75 
76 	inventoryPress = sKeyboardState.IsButtonSet(__INVENTORY);
77 
78 	// FIND GOBACK if there is one
79 	// found is -1 means none found
80 	int32 found = -1;
81 	int32 i;
82 
83 	// loop through all the icons or until we find a goback
84 	i = 0;
85 	while ((i < m_pIconList->GetIconCount()) && (found == -1)) {
86 		// get the icon
87 		const uint32 hash = m_pIconList->GetIconHash(i);
88 
89 		// look for goback or return
90 		if ((hash == HashString("return")) || (hash == HashString("goback")))
91 			found = i;
92 
93 		i++;
94 	}
95 
96 	// if there is a goback option and we are gholding down inventory key and we are not ucrrently on goback then scroll round until we
97 	// get there...
98 	// also only if we are not scrolling
99 	if ((found != -1) && (inventoryPress) && (m_nSelectedIcon != (uint)found) && (m_nScrollDirection == ICON_MENU_SCROLL_NONE)) {
100 		m_nSelectedIcon = found;
101 		m_pcSelectedIconName = const_cast<char *>(m_pIconList->GetIcon(m_nSelectedIcon));
102 		m_nSelectedIconHash = m_pIconList->GetIconHash(m_nSelectedIcon);
103 	}
104 
105 	// See what keys are being pressed.
106 
107 	// INVENTORY QUIT: we must not be in the remora, m_bAllowEscape must be true
108 	// key not locked, we are pressing inventory and we wern't last time...
109 	if ((!g_oRemora->IsActive()) && (m_bAllowEscape) && (!m_nKeyLock) && (inventoryPress) && (!lastInventoryPress)) {
110 		CloseDownIconMenu();
111 
112 		// Return the player's state to what it was before the menu was activated.
113 		MS->player.Pop_control_mode();
114 		MS->player.Set_player_status(STOOD);
115 
116 		// Lock the keypress so it can't be repeated.
117 		m_nKeyLock = TRUE8;
118 
119 		// Tell the calling function this function doesn't need calling any more.
120 		nRetVal = FALSE8;
121 
122 	}
123 	// REMORA QUIT: remora is active we just let go of inventory button, key not locked we have a return...
124 	else if ((g_oRemora->IsActive()) && (!m_nKeyLock) && (!inventoryPress) && (lastInventoryPress) && (found != -1)) {
125 		m_nLastSelection = found;
126 		m_bValidSelection = TRUE8;
127 
128 		// Close down the menu.
129 		CloseDownIconMenu();
130 
131 		// Lock the keypress so it can't be repeated.
132 		m_nKeyLock = TRUE8;
133 
134 		// Tell the calling function this function doesn't need calling any more.
135 		nRetVal = FALSE8;
136 
137 		lastInventoryPress = 0;
138 	}
139 	// CONVERSATION QUIT: remora is not active m_bAllowEscape is probably true
140 	// no key lock, inventory was pressed and has now been released...
141 	// and we have a quit!
142 	else if ((!g_oRemora->IsActive()) && (!m_bAllowEscape) && (!m_nKeyLock) && (!inventoryPress) && (lastInventoryPress) && (found != -1)) {
143 		m_nLastSelection = found;
144 		m_bValidSelection = TRUE8;
145 
146 		// Close down the menu.
147 		CloseDownIconMenu();
148 
149 		// Lock the keypress so it can't be repeated.
150 		m_nKeyLock = TRUE8;
151 
152 		// Tell the calling function this function doesn't need calling any more.
153 		nRetVal = FALSE8;
154 
155 		lastInventoryPress = 0;
156 	} else if (!m_nKeyLock && sKeyboardState.IsButtonSet(__INTERACT)) {
157 		// Player is selecting the current icon.  Don't select it if it is the 'empty' icon
158 		if (m_pIconList->GetIconHash(m_nSelectedIcon) != HashString(ICON_LIST_EMPTY_ICON)) {
159 			m_nLastSelection = m_nSelectedIcon;
160 			m_bValidSelection = TRUE8;
161 		}
162 
163 		if (!g_oRemora->IsActive()) {
164 			// Return the player's state to what it was before the menu was activated.
165 			MS->player.Pop_control_mode();
166 			MS->player.Set_player_status(STOOD);
167 		}
168 		// Close down the menu.
169 		CloseDownIconMenu();
170 
171 		// Lock the keypress so it can't be repeated.
172 		m_nKeyLock = TRUE8;
173 
174 		// Tell the calling function this function doesn't need calling any more.
175 		nRetVal = FALSE8;
176 	} else if (!m_nKeyLock && !sKeyboardState.IsButtonSet(__SIDESTEP) && (sKeyboardState.turn == __LEFT)) {
177 		// Move current selection left : if we are not scrolling & more than item in the list
178 		if ((m_nScrollDirection == ICON_MENU_SCROLL_NONE) && (m_pIconList->GetIconCount() > 1)) {
179 
180 			if (m_nSelectedIcon == 0)
181 				m_nSelectedIcon = m_pIconList->GetIconCount() - 1;
182 			else
183 				--m_nSelectedIcon;
184 
185 			// Set the name & hash value of the currently selected icon.
186 			// Set_string( m_pIconList->GetIcon( m_nSelectedIcon ), m_pcSelectedIconName, MAXLEN_ICON_NAME );
187 			m_pcSelectedIconName = const_cast<char *>(m_pIconList->GetIcon(m_nSelectedIcon));
188 			m_nSelectedIconHash = m_pIconList->GetIconHash(m_nSelectedIcon);
189 
190 			// Lock the keypress so it can't be repeated.
191 			m_nKeyLock = TRUE8;
192 
193 			// Tell the calling function this function does need calling again next cycle.
194 			nRetVal = TRUE8;
195 
196 			// Hey hey we are scrolling to the right even though pressed LEFT
197 			m_nScrollDirection = ICON_MENU_SCROLL_RIGHT;
198 		}
199 	} else if (!m_nKeyLock && !sKeyboardState.IsButtonSet(__SIDESTEP) && sKeyboardState.turn == __RIGHT) {
200 		// Move current selection right : if we are not scrolling & more than item in the list
201 		if ((m_nScrollDirection == ICON_MENU_SCROLL_NONE) && (m_pIconList->GetIconCount() > 1)) {
202 			if (m_nSelectedIcon == (uint32)(m_pIconList->GetIconCount() - 1))
203 				m_nSelectedIcon = 0;
204 			else
205 				++m_nSelectedIcon;
206 
207 			// Set the name & hash value of the currently selected icon.
208 			// Set_string( m_pIconList->GetIcon( m_nSelectedIcon ), m_pcSelectedIconName, MAXLEN_ICON_NAME );
209 			m_pcSelectedIconName = const_cast<char *>(m_pIconList->GetIcon(m_nSelectedIcon));
210 			m_nSelectedIconHash = m_pIconList->GetIconHash(m_nSelectedIcon);
211 
212 			// Lock the keypress so it can't be repeated.
213 			m_nKeyLock = TRUE8;
214 
215 			// Tell the calling function this function does need calling again next cycle.
216 			nRetVal = TRUE8;
217 
218 			// Hey hey we are scrolling to the left even though pressed RIGHT
219 			m_nScrollDirection = ICON_MENU_SCROLL_LEFT;
220 		}
221 	}
222 
223 	// Release the keylock if it is on and none of the keys that caused it to be set are still being pressed.
224 	if (m_nKeyLock && !sKeyboardState.IsButtonSet(__INVENTORY) && !sKeyboardState.IsButtonSet(__INTERACT)) {
225 		m_nKeyLock = FALSE8;
226 	}
227 
228 	// update last press
229 	// only update if we are coming back, so as not to do the
230 	// muck up first time you do remora after inventory
231 	if (nRetVal) {
232 		lastInventoryPress = inventoryPress;
233 	}
234 
235 	// Return a value to indicate if this function should be called again next cycle.
236 	return (nRetVal);
237 }
238 
CycleHoldingLogic()239 void _icon_menu::CycleHoldingLogic() {
240 	// Check if there is a current interact object.
241 	if (!MS->player.Fetch_player_interact_status()) {
242 		// No interact object, so drop whatever we're holding.
243 		ClearSelection();
244 	}
245 }
246 
CycleAddingLogic()247 void _icon_menu::CycleAddingLogic() {
248 	// Increment the flash counter.  If not a state toggle point, simply return.
249 	if (m_nAddedFlashCount++ < ICON_MENU_ADDED_FLASHRATE)
250 		return;
251 
252 	// Right, we are toggling the state of the flashing icons.  First reset the counter.
253 	m_nAddedFlashCount = 0;
254 
255 	// Behaviour now depends on whether the icon is currently being displayed ot not.
256 	if (m_nAddedSymbol == 0) {
257 		// Symbol is currently off so we are turning it on.  We need to know whether to
258 		// flash a medipack symbol or an ammo clip symbol.
259 		if (m_nAddedMedipacks > 0) {
260 			// Turning on a medipack symbol.
261 			m_nAddedSymbol = 1;
262 
263 			// Play a sound to go with it.
264 			RegisterSoundSpecial(defaultAddingMediSfx, addingMediDesc, 127, 0);
265 		} else if (m_nAddedClips > 0) {
266 			// Turning on a clips symbol.
267 			m_nAddedSymbol = 2;
268 
269 			// Play a sound to go with it.
270 			RegisterSoundSpecial(defaultAddingClipSfx, addingClipDesc, 127, 0);
271 		} else if (m_bEmailArrived) {
272 			// Turning on an email-arrived symbol.
273 			m_nAddedSymbol = 3;
274 
275 			// Play a sound to go with it.
276 			RegisterSoundSpecial(defaultEmailSfx, emailDesc, 127, 0);
277 		}
278 	} else {
279 		// See which symbol is active.
280 		switch (m_nAddedSymbol) {
281 		case 1:
282 			// Medipack symbol is currently being displayed.  Turn it off.
283 			--m_nAddedMedipacks;
284 			m_nAddedSymbol = 0;
285 			break;
286 
287 		case 2:
288 			// Clips symbol is currently being displayed.  Turn it off.
289 			--m_nAddedClips;
290 			m_nAddedSymbol = 0;
291 			break;
292 
293 		default:
294 			// This is a funny one.  First time in here will be because the symbol is 3, which
295 			// means an email-waiting symbol is being displayed.  To turn it off, we don't set
296 			// it to zero, however; instead we increment it, and then we keep incrementing it
297 			// until we hit the count that controls how long the symbol should be off before it
298 			// is flashed again.
299 			if (++m_nAddedSymbol == ICON_MENU_EMAIL_FLASHRATE)
300 				m_nAddedSymbol = 0;
301 		}
302 	}
303 }
304 
PreloadIcon(const char * pcIconPath,const char * pcIconName)305 void _icon_menu::PreloadIcon(const char *pcIconPath, const char *pcIconName) {
306 	uint32 nFullIconNameHash;
307 
308 	// Make the full URL for the icon.
309 	char pcFullIconName[MAXLEN_URL];
310 	sprintf(pcFullIconName, "%s%s.%s", pcIconPath, pcIconName, PX_BITMAP_EXT);
311 
312 	// Open the icon resource.
313 	nFullIconNameHash = NULL_HASH;
314 	rs_icons->Res_open(pcFullIconName, nFullIconNameHash, m_pcIconCluster, m_nIconClusterHash);
315 }
316 
GetLastSelection()317 const char *_icon_menu::GetLastSelection() {
318 	// Only return a selection if one has been made.
319 	if (m_bValidSelection) {
320 		if (m_pIconList->GetIconCount() > 0)
321 			return (m_pIconList->GetIcon(m_nLastSelection));
322 		else
323 			return (NULL);
324 	} else {
325 		return (NULL);
326 	}
327 }
328 
GetLastSelectionHash() const329 uint32 _icon_menu::GetLastSelectionHash() const {
330 	// Only return a selection if one has been made.
331 	if (m_bValidSelection) {
332 		if (m_pIconList->GetIconCount() > 0)
333 			return (m_pIconList->GetIconHash(m_nLastSelection));
334 		else
335 			return (NULL_HASH);
336 	} else {
337 		return (NULL_HASH);
338 	}
339 }
340 
CloseDownIconMenu()341 void _icon_menu::CloseDownIconMenu() {
342 	// The Remora has to call this function when it quits, to make sure the icon menu disappears with it, but
343 	// it is up to the script writer to make sure that the Remora has an icon menu displayed; therefore, there is
344 	// the possibility that the Remora will quit and try to call this function when no icon menu is being
345 	// displayed.  We have to check for this here.
346 	if (m_eIconMenuGameState == INACTIVE)
347 		return;
348 
349 	// Menu is active, so close it down.
350 	CloseDownIconMenuDisplay();
351 	m_eIconMenuGameState = INACTIVE;
352 }
353 
IsAdding() const354 bool8 _icon_menu::IsAdding() const {
355 	if ((m_nAddedMedipacks > 0) || (m_nAddedClips > 0) || m_bEmailArrived)
356 		return (TRUE8);
357 	else
358 		return (FALSE8);
359 }
360 
361 #define ICON_MENU_SCROLLCYCLES_INCREMENT (ICON_X_SIZE / 4)
362 #define ICON_MENU_SCROLLCYCLES_MAX ICON_X_SIZE
363 
364 // Scroll the icons smoothly left or right
365 // This returns the x-position to start drawing the icons from (nX)
366 // it also sets the first icon to start drawing (nIconIndex)
GetScrollingPosition(const int32 nInputX,uint32 & nIconIndex)367 int32 _icon_menu::GetScrollingPosition(const int32 nInputX, uint32 &nIconIndex) {
368 	int32 nX = nInputX;
369 
370 	// OK are we scrolling
371 	if (m_nScrollDirection != ICON_MENU_SCROLL_NONE) {
372 		if (m_nScrollCycles >= ICON_MENU_SCROLLCYCLES_MAX) {
373 			m_nScrollCycles = 0;
374 			m_nScrollDirection = ICON_MENU_SCROLL_NONE;
375 			m_nLastIconIndex = (uint8)nIconIndex;
376 		} else {
377 			if (m_nScrollDirection == ICON_MENU_SCROLL_RIGHT) {
378 				// scroll right
379 				nX += m_nScrollCycles;
380 				nX -= ICON_X_SIZE;
381 			} else {
382 				// scroll left : keep old icon index
383 				nX -= m_nScrollCycles;
384 				nIconIndex = m_nLastIconIndex;
385 			}
386 
387 			m_nScrollCycles += ICON_MENU_SCROLLCYCLES_INCREMENT;
388 		}
389 	}
390 
391 	return nX;
392 }
393 
394 } // End of namespace ICB
395