1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/winexe.h"
24 #include "common/translation.h"
25 #include "common/stream.h"
26 
27 #include "gui/widgets/tab.h"
28 #include "gui/widgets/edittext.h"
29 
30 #include "gui/ThemeEval.h"
31 
32 #include "engines/nancy/dialogs.h"
33 #include "engines/nancy/iff.h"
34 
35 #include "engines/nancy/state/scene.h"
36 
37 namespace Nancy {
38 
NancyOptionsWidget(GuiObject * boss,const Common::String & name,const Common::String & domain)39 NancyOptionsWidget::NancyOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) :
40 		OptionsContainerWidget(boss, name, "NancyOptionsDialog", false, domain) {
41 	_playerSpeechCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "NancyOptionsDialog.PlayerSpeech", _("Player Speech"), _("Enable player speech. Only works if speech is enabled in the Audio settings."));
42 	_characterSpeechCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "NancyOptionsDialog.CharacterSpeech", _("Character Speech"), _("Enable NPC speech. Only works if speech is enabled in the Audio settings."));
43 	_originalMenusCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "NancyOptionsDialog.OriginalMenus", _("Use original menus"), _("Use the original engine's main, save/load, and setup menus. ScummVM's Global Main Menu can still be accessed through its keymap."));
44 
45 	// I18N: Second Chance is the name of the original engine's autosave system
46 	_secondChanceCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "NancyOptionsDialog.SecondChance", _("Enable Second Chance"), _("Enable the Second Chance feature, which automatically saves at specific scenes. Enabling this disables timed autosaves."));
47 
48 	new GUI::StaticTextWidget(widgetsBoss(), "NancyOptionsDialog.SpeechSettingsLabel", _("Speech Options"));
49 	new GUI::StaticTextWidget(widgetsBoss(), "NancyOptionsDialog.EngineSettingsLabel", _("Engine Options"));
50 }
51 
load()52 void NancyOptionsWidget::load() {
53 	_playerSpeechCheckbox->setState(ConfMan.getBool("player_speech", _domain));
54 	_characterSpeechCheckbox->setState(ConfMan.getBool("character_speech", _domain));
55 	_originalMenusCheckbox->setState(ConfMan.getBool("original_menus", _domain));
56 	_secondChanceCheckbox->setState(ConfMan.getBool("second_chance", _domain));
57 }
58 
save()59 bool NancyOptionsWidget::save() {
60 	ConfMan.setBool("player_speech", _playerSpeechCheckbox->getState(), _domain);
61 	ConfMan.setBool("character_speech", _characterSpeechCheckbox->getState(), _domain);
62 	ConfMan.setBool("original_menus", _originalMenusCheckbox->getState(), _domain);
63 	ConfMan.setBool("second_chance", _secondChanceCheckbox->getState(), _domain);
64 
65 	return true;
66 }
67 
defineLayout(GUI::ThemeEval & layouts,const Common::String & layoutName,const Common::String & overlayedLayout) const68 void NancyOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
69 	layouts.addDialog(layoutName, overlayedLayout)
70 		.addLayout(GUI::ThemeLayout::kLayoutVertical)
71 			.addPadding(16, 16, 16, 16)
72 			.addWidget("SpeechSettingsLabel", "OptionsLabel")
73 			.addWidget("PlayerSpeech", "Checkbox")
74 			.addWidget("CharacterSpeech", "Checkbox")
75 			.addSpace(16)
76 			.addWidget("EngineSettingsLabel", "OptionsLabel")
77 			.addWidget("OriginalMenus", "Checkbox")
78 			.addWidget("SecondChance", "Checkbox")
79 		.closeLayout()
80 	.closeDialog();
81 }
82 
isInGame() const83 bool NancyOptionsWidget::isInGame() const {
84 	return _domain.equals(ConfMan.getActiveDomainName());
85 }
86 
87 // TODO rewrite this class so its layout is not hardcoded
CheatDialog()88 CheatDialog::CheatDialog() : GUI::Dialog(20, 20, 600, 440) {
89 	_backgroundType = GUI::ThemeEngine::kDialogBackgroundSpecial;
90 	Common::WinResources *res = Common::WinResources::createFromEXE("game.exe");
91 	Common::Array<Common::WinResourceID> dialogIDs = res->getIDList(Common::kWinDialog);
92 	State::SceneInfo scene = NancySceneState.getSceneInfo();
93 	Time playerTime = NancySceneState._timers.playerTime;
94 	Time timerTime = NancySceneState._timers.timerTime;
95 	bool timerIsActive = NancySceneState._timers.timerIsActive;
96 	if (!timerIsActive) {
97 		timerTime = 0;
98 	}
99 
100 	GUI::TabWidget *_tabs = new GUI::TabWidget(this, 0, 0, 600, 370);
101 	new GUI::ButtonWidget(this, 420, 410, 60, 20, _("Cancel"), Common::U32String(), GUI::kCloseCmd);
102 	new GUI::ButtonWidget(this, 520, 410, 60, 20, _("Ok"), Common::U32String(), GUI::kOKCmd);
103 
104 	_tabs->addTab(_("General"), "Cheat.General");
105 
106 	new GUI::StaticTextWidget(_tabs, 30, 20, 150, 20, _("Scene Data"), Graphics::kTextAlignLeft);
107 	_restartScene = new GUI::CheckboxWidget(_tabs, 35, 50, 150, 20, _("Restart the Scene"));
108 	_scene = new GUI::EditTextWidget(_tabs, 35, 75, 45, 20, Common::U32String::format("%u", scene.sceneID), Common::U32String(), kInputSceneNr, kInputSceneNr);
109 	new GUI::StaticTextWidget(_tabs, 85, 75, 150, 20, _("Scene Number"), Graphics::kTextAlignLeft);
110 	_frame = new GUI::EditTextWidget(_tabs, 35, 100, 45, 20, Common::U32String::format("%u", scene.frameID), Common::U32String(), kInputFrameNr, kInputFrameNr);
111 	new GUI::StaticTextWidget(_tabs, 85, 100, 150, 20, _("Frame Number"), Graphics::kTextAlignLeft);
112 	_offset = new GUI::EditTextWidget(_tabs, 35, 125, 45, 20, Common::U32String::format("%u", scene.verticalOffset), Common::U32String(), kInputScroll, kInputScroll);
113 
114 	// I18N: The Y position (a.k.a vertical scroll) of the background
115 	new GUI::StaticTextWidget(_tabs, 85, 125, 150, 20, _("Background Top (Y)"), Graphics::kTextAlignLeft);
116 
117 	new GUI::StaticTextWidget(_tabs, 30, 160, 150, 20, _("Hints Remaining"), Graphics::kTextAlignLeft);
118 	new GUI::StaticTextWidget(_tabs, 35, 185, 45, 20, _("Easy"), Graphics::kTextAlignLeft);
119 	_hintsRemainingEasy = new GUI::EditTextWidget(_tabs, 35, 205, 45, 20, Common::U32String::format("%u", NancySceneState._hintsRemaining[0]), Common::U32String(), kInputHintsEasy, kInputHintsEasy);
120 	new GUI::StaticTextWidget(_tabs, 85, 185, 45, 20, _("Medium"), Graphics::kTextAlignLeft);
121 	_hintsRemainingMedium = new GUI::EditTextWidget(_tabs, 85, 205, 45, 20, Common::U32String::format("%u", NancySceneState._hintsRemaining[1]), Common::U32String(), kInputHintsMedium, kInputHintsMedium);
122 	new GUI::StaticTextWidget(_tabs, 135, 185, 45, 20, _("Hard"), Graphics::kTextAlignLeft);
123 	_hintsRemainingHard = new GUI::EditTextWidget(_tabs, 135, 205, 45, 20, Common::U32String::format("%u", NancySceneState._hintsRemaining[2]), Common::U32String(), kInputHintsHard, kInputHintsHard);
124 
125 	new GUI::StaticTextWidget(_tabs, 250, 20, 150, 20, _("Player Data"), Graphics::kTextAlignLeft);
126 	new GUI::StaticTextWidget(_tabs, 255, 50, 150, 20, _("Player Time:"), Graphics::kTextAlignLeft);
127 	_playerTimeDays = new GUI::EditTextWidget(_tabs, 255, 75, 35, 20, Common::U32String::format("%u", playerTime.getDays()), Common::U32String(), kInputPlayerTime, kInputPlayerTime);
128 	new GUI::StaticTextWidget(_tabs, 295, 75, 40, 20, _("Days"), Graphics::kTextAlignLeft);
129 	_playerTimeHours = new GUI::EditTextWidget(_tabs, 335, 75, 35, 20, Common::U32String::format("%u", playerTime.getHours()), Common::U32String(), kInputPlayerTime, kInputPlayerTime);
130 	new GUI::StaticTextWidget(_tabs, 375, 75, 40, 20, _("Hours"), Graphics::kTextAlignLeft);
131 	_playerTimeMinutes = new GUI::EditTextWidget(_tabs, 415, 75, 35, 20, Common::U32String::format("%u", playerTime.getMinutes()), Common::U32String(), kInputPlayerTime, kInputPlayerTime);
132 	new GUI::StaticTextWidget(_tabs, 455, 75, 50, 20, _("Minutes"), Graphics::kTextAlignLeft);
133 	_difficulty = new GUI::EditTextWidget(_tabs, 255, 105, 35, 20, Common::U32String::format("%u", NancySceneState._difficulty), Common::U32String(), kInputDifficulty, kInputDifficulty);
134 	new GUI::StaticTextWidget(_tabs, 295, 105, 150, 20, _("Player Difficulty Level"), Graphics::kTextAlignLeft);
135 
136 	new GUI::StaticTextWidget(_tabs, 250, 140, 150, 20, _("Software Timer"), Graphics::kTextAlignLeft);
137 	_timerOn = new GUI::CheckboxWidget(_tabs, 255, 170, 150, 20, _("Timer On"));
138 	_timerOn->setState(timerIsActive);
139 	_timerHours = new GUI::EditTextWidget(_tabs, 255, 195, 35, 20, Common::U32String::format("%u", timerTime.getTotalHours()), Common::U32String(), kInputTimer, kInputTimer);
140 	new GUI::StaticTextWidget(_tabs, 295, 195, 40, 20, _("Hours"), Graphics::kTextAlignLeft);
141 	_timerMinutes = new GUI::EditTextWidget(_tabs, 335, 195, 35, 20, Common::U32String::format("%u", timerTime.getMinutes()), Common::U32String(), kInputTimer, kInputTimer);
142 	new GUI::StaticTextWidget(_tabs, 375, 195, 50, 20, _("Minutes"), Graphics::kTextAlignLeft);
143 	_timerSeconds = new GUI::EditTextWidget(_tabs, 425, 195, 35, 20, Common::U32String::format("%u", timerTime.getSeconds()), Common::U32String(), kInputTimer, kInputTimer);
144 	new GUI::StaticTextWidget(_tabs, 465, 195, 50, 20, _("Seconds"), Graphics::kTextAlignLeft);
145 
146 	_tabs->addTab(_("Inventory"), "Cheat.Inventory");
147 
148 	for (uint i = 0; i < dialogIDs.size(); ++i) {
149 		Common::SeekableReadStream *resStream = res->getResource(Common::kWinDialog, dialogIDs[i].getID());
150 
151 		Common::String idString;
152 		resStream->skip(0x16);
153 		while (true) {
154 			char add = resStream->readByte();
155 			if (add != 0) {
156 				idString += add;
157 				resStream->skip(1);
158 			} else {
159 				resStream->skip(1);
160 				break;
161 			}
162 		}
163 
164 		if (!idString.hasPrefix("Inventory")) {
165 			continue;
166 		}
167 
168 		idString.trim();
169 		uint numItems = 0;
170 
171 		while (resStream->pos() < resStream->size()) {
172 			if (resStream->readUint16LE() == 0xFFFF && resStream->readSint16LE() == 0x80) {
173 				// Found a resource, read its string id
174 				Common::String itemLabel;
175 
176 				while (true) {
177 					char add = resStream->readByte();
178 					if (add != 0) {
179 						itemLabel += add;
180 						resStream->skip(1);
181 					} else {
182 						resStream->skip(1);
183 						break;
184 					}
185 				}
186 				GUI::CheckboxWidget *box = new GUI::CheckboxWidget(_tabs, 250 * (numItems / 10) + 20, (350 / 10) * (numItems % 10) + 15, 250, 250/10, Common::U32String(itemLabel));
187 				box->setState(NancySceneState.hasItem(numItems) == kTrue);
188 				_inventory.push_back(box);
189 
190 				++numItems;
191 			}
192 		}
193 
194 		break;
195 	}
196 
197 	_tabs->setActiveTab(0);
198 }
199 
handleCommand(GUI::CommandSender * sender,uint32 cmd,uint32 data)200 void CheatDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
201 	switch (cmd) {
202 	case GUI::kOKCmd: {
203 		if (_restartScene->getState()) {
204 			uint sceneID = atoi(Common::String(_scene->getEditString()).c_str());
205 			IFF iff(Common::String::format("S%u", sceneID));
206 			if (iff.load()) {
207 				NancySceneState.changeScene(
208 					atoi(Common::String(_scene->getEditString()).c_str()),
209 					atoi(Common::String(_frame->getEditString()).c_str()),
210 					atoi(Common::String(_offset->getEditString()).c_str()),
211 					true);
212 			} else {
213 				new GUI::StaticTextWidget(this, 20, 410, 150, 20, _("Invalid Scene ID!"), Graphics::kTextAlignLeft);
214 				return;
215 			}
216 		}
217 
218 		if (_timerOn->getState()) {
219 			NancySceneState._timers.timerIsActive = true;
220 			Time &timer = NancySceneState._timers.timerTime;
221 			timer = 0;
222 			timer += 1000 * atoi(Common::String(_timerSeconds->getEditString()).c_str());
223 			timer += 60000 * atoi(Common::String(_timerMinutes->getEditString()).c_str());
224 			timer += 3600000 * atoi(Common::String(_timerHours->getEditString()).c_str());
225 		} else {
226 			NancySceneState.stopTimer();
227 		}
228 
229 		Time &playerTime = NancySceneState._timers.timerTime;
230 		playerTime = 0;
231 		playerTime += 60000 * atoi(Common::String(_playerTimeMinutes->getEditString()).c_str());
232 		playerTime += 3600000 * atoi(Common::String(_playerTimeHours->getEditString()).c_str());
233 		playerTime += 86400000 * atoi(Common::String(_playerTimeMinutes->getEditString()).c_str());
234 
235 		NancySceneState._difficulty = atoi(Common::String(_difficulty->getEditString()).c_str());
236 		NancySceneState._hintsRemaining[0] = atoi(Common::String(_hintsRemainingEasy->getEditString()).c_str());
237 		NancySceneState._hintsRemaining[1] = atoi(Common::String(_hintsRemainingMedium->getEditString()).c_str());
238 		NancySceneState._hintsRemaining[2] = atoi(Common::String(_hintsRemainingHard->getEditString()).c_str());
239 
240 		for (uint i = 0; i < _inventory.size(); ++i) {
241 			if (NancySceneState.hasItem(i) == kTrue && !_inventory[i]->getState()) {
242 				NancySceneState.removeItemFromInventory(i, false);
243 			} else if (NancySceneState.hasItem(i) == kFalse && _inventory[i]->getState()) {
244 				NancySceneState.addItemToInventory(i);
245 			}
246 		}
247 		cmd = GUI::kCloseCmd;
248 
249 		break;
250 	}
251 	case kInputSceneNr:
252 		sanitizeInput(_scene);
253 		break;
254 	case kInputFrameNr:
255 		sanitizeInput(_frame);
256 		break;
257 	case kInputScroll:
258 		sanitizeInput(_offset);
259 		break;
260 	case kInputDifficulty:
261 		sanitizeInput(_scene, 2);
262 		break;
263 	case kInputHintsEasy:
264 		sanitizeInput(_hintsRemainingEasy);
265 		break;
266 	case kInputHintsMedium:
267 		sanitizeInput(_hintsRemainingMedium);
268 		break;
269 	case kInputHintsHard:
270 		sanitizeInput(_hintsRemainingHard);
271 		break;
272 	case kInputPlayerTime:
273 		sanitizeInput(_playerTimeMinutes, 59);
274 		sanitizeInput(_playerTimeHours, 23);
275 		sanitizeInput(_playerTimeDays);
276 		break;
277 	case kInputTimer:
278 		sanitizeInput(_timerSeconds, 59);
279 		sanitizeInput(_timerMinutes, 59);
280 		sanitizeInput(_timerHours, 23);
281 		break;
282 	default:
283 		break;
284 	}
285 
286 	Dialog::handleCommand(sender, cmd, data);
287 }
288 
sanitizeInput(GUI::EditTextWidget * textWidget,int maxValue)289 void CheatDialog::sanitizeInput(GUI::EditTextWidget *textWidget, int maxValue) {
290 	const Common::U32String &str = textWidget->getEditString();
291 	for (uint i = 0; i < str.size(); ++i) {
292 		if (!Common::isDigit(str[i])) {
293 			textWidget->setEditString(str.substr(0, i));
294 			break;
295 		}
296 	}
297 
298 	if (maxValue > -1) {
299 		int number = atoi(Common::String(str).c_str());
300 		if (number > maxValue) {
301 			textWidget->setEditString(Common::U32String::format("%d", maxValue));
302 		}
303 	}
304 
305 	textWidget->setCaretPos(str.size());
306 }
307 
EventFlagDialog()308 EventFlagDialog::EventFlagDialog() : GUI::Dialog(20, 20, 600, 440) {
309 	_backgroundType = GUI::ThemeEngine::kDialogBackgroundSpecial;
310 	Common::WinResources *res = Common::WinResources::createFromEXE("game.exe");
311 	Common::Array<Common::WinResourceID> dialogIDs = res->getIDList(Common::kWinDialog);
312 
313 	GUI::TabWidget *_tabs = new GUI::TabWidget(this, 0, 0, 600, 370);
314 	new GUI::ButtonWidget(this, 520, 410, 60, 20, _("Close"), Common::U32String(), GUI::kCloseCmd);
315 
316 	for (uint i = 0; i < dialogIDs.size(); ++i) {
317 		Common::SeekableReadStream *resStream = res->getResource(Common::kWinDialog, dialogIDs[i].getID());
318 
319 		Common::String idString;
320 		resStream->skip(0x16);
321 		while (true) {
322 			char add = resStream->readByte();
323 			if (add != 0) {
324 				idString += add;
325 				resStream->skip(1);
326 			} else {
327 				resStream->skip(1);
328 				break;
329 			}
330 		}
331 
332 		if (!idString.hasPrefix("Event")) {
333 			continue;
334 		}
335 
336 		idString.trim();
337 		_tabs->addTab(idString, Common::String("tab " + idString));
338 		uint numFlags = 0;
339 
340 		while (resStream->pos() < resStream->size()) {
341 			if (resStream->readUint16LE() == 0xFFFF && resStream->readSint16LE() == 0x80) {
342 				// Found a resource, read its string id
343 				Common::String flagLabel;
344 
345 				while (true) {
346 					char add = resStream->readByte();
347 					if (add != 0) {
348 						flagLabel += add;
349 						resStream->skip(1);
350 					} else {
351 						resStream->skip(1);
352 						break;
353 					}
354 				}
355 
356 				// String begins with number so we assume it's an event flag radio button
357 				if (Common::isDigit(flagLabel.firstChar())) {
358 					Common::String num;
359 					uint j = 0;
360 					while (true) {
361 						if (Common::isDigit(flagLabel[j])) {
362 							num += flagLabel[j];
363 						} else {
364 							break;
365 						}
366 						++j;
367 					}
368 
369 					uint32 command = atoi(num.c_str()) << 16 | 'ev';
370 
371 					GUI::CheckboxWidget *box = new GUI::CheckboxWidget(_tabs, 300 * (numFlags / 12) + 20, (350 / 12) * (numFlags % 12) + 15, 300, 350/12, Common::U32String(flagLabel), Common::U32String(), command);
372 					box->setState(NancySceneState.getEventFlag(command >> 16));
373 
374 					++numFlags;
375 				}
376 			}
377 		}
378 	}
379 
380 	_tabs->setActiveTab(0);
381 }
382 
handleCommand(GUI::CommandSender * sender,uint32 cmd,uint32 data)383 void EventFlagDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
384 	Dialog::handleCommand(sender, cmd, data);
385 	if (cmd & 'ev') {
386 		cmd >>= 16;
387 		NancySceneState.setEventFlag(cmd, data == 0 ? kFalse : kTrue);
388 	}
389 }
390 
391 } // End of namespace Nancy
392