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 &currentGroup = 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