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 "mutationofjb/tasks/conversationtask.h"
24
25 #include "mutationofjb/assets.h"
26 #include "mutationofjb/game.h"
27 #include "mutationofjb/gamedata.h"
28 #include "mutationofjb/gamescreen.h"
29 #include "mutationofjb/script.h"
30 #include "mutationofjb/tasks/saytask.h"
31 #include "mutationofjb/tasks/sequentialtask.h"
32 #include "mutationofjb/tasks/taskmanager.h"
33 #include "mutationofjb/util.h"
34 #include "mutationofjb/widgets/conversationwidget.h"
35
36 namespace MutationOfJB {
37
start()38 void ConversationTask::start() {
39 setState(RUNNING);
40
41 Game &game = getTaskManager()->getGame();
42 game.getGameScreen().showConversationWidget(true);
43 ConversationWidget &widget = game.getGameScreen().getConversationWidget();
44 widget.setCallback(this);
45
46 _currentGroupIndex = 0;
47
48 showChoicesOrPick();
49 }
50
update()51 void ConversationTask::update() {
52 if (_sayTask) {
53 if (_sayTask->getState() == Task::FINISHED) {
54 _sayTask.reset();
55
56 switch (_substate) {
57 case SAYING_NO_QUESTIONS:
58 finish();
59 break;
60 case SAYING_QUESTION: {
61 const ConversationLineList &responseList = getTaskManager()->getGame().getAssets().getResponseList();
62 const ConversationLineList::Line *const line = responseList.getLine(_currentItem->_response);
63
64 _substate = SAYING_RESPONSE;
65 createSayTasks(line);
66 getTaskManager()->startTask(_sayTask);
67 break;
68 }
69 case SAYING_RESPONSE: {
70 startExtra();
71
72 if (_substate != RUNNING_EXTRA) {
73 gotoNextGroup();
74 }
75 break;
76 }
77 default:
78 break;
79 }
80 }
81 }
82
83 if (_innerExecCtx) {
84 Command::ExecuteResult res = _innerExecCtx->runActiveCommand();
85 if (res == Command::Finished) {
86 delete _innerExecCtx;
87 _innerExecCtx = nullptr;
88
89 gotoNextGroup();
90 }
91 }
92 }
93
onChoiceClicked(ConversationWidget * convWidget,int,uint32 data)94 void ConversationTask::onChoiceClicked(ConversationWidget *convWidget, int, uint32 data) {
95 const ConversationInfo::Item &item = getCurrentGroup()[data];
96 convWidget->clearChoices();
97
98 const ConversationLineList &toSayList = getTaskManager()->getGame().getAssets().getToSayList();
99 const ConversationLineList::Line *line = toSayList.getLine(item._question);
100
101 _substate = SAYING_QUESTION;
102 createSayTasks(line);
103 getTaskManager()->startTask(_sayTask);
104 _currentItem = &item;
105
106 if (!line->_speeches[0].isRepeating()) {
107 getTaskManager()->getGame().getGameData().getCurrentScene()->addExhaustedConvItem(_convInfo._context, data + 1, _currentGroupIndex + 1);
108 }
109 }
110
showChoicesOrPick()111 void ConversationTask::showChoicesOrPick() {
112 Game &game = getTaskManager()->getGame();
113 GameData &gameData = game.getGameData();
114 Scene *const scene = gameData.getScene(_sceneId);
115 if (!scene) {
116 return;
117 }
118
119 Common::Array<uint32> itemsWithValidQuestions;
120 Common::Array<uint32> itemsWithValidResponses;
121 Common::Array<uint32> itemsWithValidNext;
122
123 /*
124 Collect valid questions (not exhausted and not empty).
125 Collect valid responses (not exhausted and not empty).
126 If there are at least two visible questions, we show them.
127 If there is just one visible question, pick it automatically ONLY if this is not the first question in this conversation.
128 Otherwise we don't start the conversation.
129 If there are no visible questions, automatically pick the first valid response.
130 If nothing above applies, don't start the conversation.
131 */
132
133 const ConversationInfo::ItemGroup ¤tGroup = getCurrentGroup();
134 for (ConversationInfo::ItemGroup::size_type i = 0; i < currentGroup.size(); ++i) {
135 const ConversationInfo::Item &item = currentGroup[i];
136
137 if (scene->isConvItemExhausted(_convInfo._context, static_cast<uint8>(i + 1), static_cast<uint8>(_currentGroupIndex + 1))) {
138 continue;
139 }
140 const uint8 toSay = item._question;
141 const uint8 response = item._response;
142 const uint8 next = item._nextGroupIndex;
143
144 if (toSay != 0) {
145 itemsWithValidQuestions.push_back(i);
146 }
147
148 if (response != 0) {
149 itemsWithValidResponses.push_back(i);
150 }
151
152 if (next != 0) {
153 itemsWithValidNext.push_back(i);
154 }
155 }
156
157 if (itemsWithValidQuestions.size() > 1) {
158 ConversationWidget &widget = game.getGameScreen().getConversationWidget();
159 const ConversationLineList &toSayList = game.getAssets().getToSayList();
160
161 for (Common::Array<uint32>::size_type i = 0; i < itemsWithValidQuestions.size() && i < ConversationWidget::CONVERSATION_MAX_CHOICES; ++i) {
162 const ConversationInfo::Item &item = currentGroup[itemsWithValidQuestions[i]];
163 const ConversationLineList::Line *const line = toSayList.getLine(item._question);
164 const Common::String widgetText = toUpperCP895(line->_speeches[0]._text);
165 widget.setChoice(static_cast<int>(i), widgetText, itemsWithValidQuestions[i]);
166 }
167 _substate = IDLE;
168 _currentItem = nullptr;
169
170 _haveChoices = true;
171 } else if (itemsWithValidQuestions.size() == 1 && _haveChoices) {
172 const ConversationLineList &toSayList = game.getAssets().getToSayList();
173 const ConversationInfo::Item &item = currentGroup[itemsWithValidQuestions.front()];
174 const ConversationLineList::Line *const line = toSayList.getLine(item._question);
175
176 _substate = SAYING_QUESTION;
177 createSayTasks(line);
178 getTaskManager()->startTask(_sayTask);
179 _currentItem = &item;
180
181 if (!line->_speeches[0].isRepeating()) {
182 game.getGameData().getCurrentScene()->addExhaustedConvItem(_convInfo._context, itemsWithValidQuestions.front() + 1, _currentGroupIndex + 1);
183 }
184
185 _haveChoices = true;
186 } else if (!itemsWithValidResponses.empty() && _haveChoices) {
187 const ConversationLineList &responseList = game.getAssets().getResponseList();
188 const ConversationInfo::Item &item = currentGroup[itemsWithValidResponses.front()];
189 const ConversationLineList::Line *const line = responseList.getLine(item._response);
190
191 _substate = SAYING_RESPONSE;
192 createSayTasks(line);
193 getTaskManager()->startTask(_sayTask);
194 _currentItem = &item;
195
196 _haveChoices = true;
197 } else if (!itemsWithValidNext.empty() && _haveChoices) {
198 _currentGroupIndex = currentGroup[itemsWithValidNext.front()]._nextGroupIndex - 1;
199 showChoicesOrPick();
200 } else {
201 if (_haveChoices) {
202 finish();
203 } else {
204 _sayTask = TaskPtr(new SayTask("Nothing to talk about.", _convInfo._color)); // TODO: This is hardcoded in executable. Load it.
205 getTaskManager()->startTask(_sayTask);
206 _substate = SAYING_NO_QUESTIONS;
207 _currentItem = nullptr;
208 }
209 }
210 }
211
getCurrentGroup() const212 const ConversationInfo::ItemGroup &ConversationTask::getCurrentGroup() const {
213 assert(_currentGroupIndex < _convInfo._itemGroups.size());
214 return _convInfo._itemGroups[_currentGroupIndex];
215 }
216
finish()217 void ConversationTask::finish() {
218 setState(FINISHED);
219
220 Game &game = getTaskManager()->getGame();
221 game.getGameScreen().showConversationWidget(false);
222 ConversationWidget &widget = game.getGameScreen().getConversationWidget();
223 widget.setCallback(nullptr);
224 }
225
startExtra()226 void ConversationTask::startExtra() {
227 const ConversationLineList &responseList = getTaskManager()->getGame().getAssets().getResponseList();
228 const ConversationLineList::Line *const line = responseList.getLine(_currentItem->_response);
229 if (!line->_extra.empty()) {
230 _innerExecCtx = new ScriptExecutionContext(getTaskManager()->getGame());
231 Command *const extraCmd = _innerExecCtx->getExtra(line->_extra);
232 if (extraCmd) {
233 Command::ExecuteResult res = _innerExecCtx->startCommand(extraCmd);
234 if (res == Command::InProgress) {
235 _substate = RUNNING_EXTRA;
236 } else {
237 delete _innerExecCtx;
238 _innerExecCtx = nullptr;
239 }
240 } else {
241 warning("Extra '%s' not found", line->_extra.c_str());
242 delete _innerExecCtx;
243 _innerExecCtx = nullptr;
244 }
245 }
246 }
247
gotoNextGroup()248 void ConversationTask::gotoNextGroup() {
249 if (_currentItem->_nextGroupIndex == 0) {
250 finish();
251 } else {
252 _currentGroupIndex = _currentItem->_nextGroupIndex - 1;
253 showChoicesOrPick();
254 }
255 }
256
createSayTasks(const ConversationLineList::Line * line)257 void ConversationTask::createSayTasks(const ConversationLineList::Line *line) {
258 if (line->_speeches.size() == 1) {
259 const ConversationLineList::Speech &speech = line->_speeches[0];
260 _sayTask = TaskPtr(new SayTask(speech._text, getSpeechColor(speech)));
261 } else {
262 TaskPtrs tasks;
263 for (ConversationLineList::Speeches::const_iterator it = line->_speeches.begin(); it != line->_speeches.end(); ++it) {
264 tasks.push_back(TaskPtr(new SayTask(it->_text, getSpeechColor(*it))));
265 }
266 _sayTask = TaskPtr(new SequentialTask(tasks));
267 }
268 }
269
getSpeechColor(const ConversationLineList::Speech & speech)270 uint8 ConversationTask::getSpeechColor(const ConversationLineList::Speech &speech) {
271 uint8 color = WHITE;
272 if (_substate == SAYING_RESPONSE) {
273 color = _convInfo._color;
274 if (_mode == TalkCommand::RAY_AND_BUTTLEG_MODE) {
275 if (speech.isFirstSpeaker()) {
276 color = GREEN;
277 } else if (speech.isSecondSpeaker()) {
278 color = LIGHTBLUE;
279 }
280 }
281 }
282 return color;
283 }
284
285 }
286