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