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 * 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 "engines/stark/tools/command.h"
24
25 #include "common/debug.h"
26 #include "common/tokenizer.h"
27
28 namespace Stark {
29 namespace Tools {
30
Command(Command * command)31 Command::Command(Command *command) {
32 _index = command->_index;
33 _subType = command->_subType;
34 _subTypeDesc = command->_subTypeDesc;
35 _arguments = command->_arguments;
36 }
37
Command(Resources::Command * resource)38 Command::Command(Resources::Command *resource) {
39 _index = resource->getIndex();
40 _subType = (Resources::Command::SubType) resource->getSubType();
41 _subTypeDesc = searchSubTypeDesc(_subType);
42 _arguments = resource->getArguments();
43 }
44
searchSubTypeDesc(Resources::Command::SubType subType)45 const Command::SubTypeDesc *Command::searchSubTypeDesc(Resources::Command::SubType subType) {
46 static const SubTypeDesc typeNames[] = {
47 { Resources::Command::kCommandBegin, "begin", kFlowNormal },
48 { Resources::Command::kCommandEnd, "end", kFlowEnd },
49 { Resources::Command::kScriptCall, "scriptCall", kFlowNormal },
50 { Resources::Command::kDialogCall, "dialogCall", kFlowNormal },
51 { Resources::Command::kSetInteractiveMode, "setInteractiveMode", kFlowNormal },
52 { Resources::Command::kLocationGoTo, "locationGoTo", kFlowEnd },
53 { Resources::Command::kWalkTo, "walkTo", kFlowBranch },
54 { Resources::Command::kGameLoop, "gameLoop", kFlowNormal },
55 { Resources::Command::kScriptPause, "scriptPause", kFlowNormal },
56 { Resources::Command::kScriptPauseRandom, "scriptPauseRandom", kFlowNormal },
57 { Resources::Command::kScriptPauseSkippable, "scriptPauseSkippable", kFlowNormal },
58 { Resources::Command::kScriptAbort, "scriptAbort", kFlowNormal },
59 { Resources::Command::kExit2DLocation, "exit2DLocation", kFlowEnd },
60 { Resources::Command::kGoto2DLocation, "goto2DLocation", kFlowEnd },
61 { Resources::Command::kRumbleScene, "rumbleScene", kFlowNormal },
62 { Resources::Command::kFadeScene, "fadeScene", kFlowNormal },
63 { Resources::Command::kSwayScene, "swayScene", kFlowNormal },
64 { Resources::Command::kLocationGoToNewCD, "locationGoToNewCD", kFlowEnd },
65 { Resources::Command::kGameEnd, "gameEnd", kFlowNormal },
66 { Resources::Command::kInventoryOpen, "inventoryOpen", kFlowNormal },
67 { Resources::Command::kFloatScene, "floatScene", kFlowNormal },
68 { Resources::Command::kBookOfSecretsOpen, "bookOfSecretsOpen", kFlowNormal },
69 { Resources::Command::kDoNothing, "doNothing", kFlowNormal },
70 { Resources::Command::kItem3DPlaceOn, "item3DPlaceOn", kFlowNormal },
71 { Resources::Command::kItem3DWalkTo, "item3DWalkTo", kFlowNormal },
72 { Resources::Command::kItem3DFollowPath, "item3DFollowPath", kFlowNormal },
73 { Resources::Command::kItemLookAt, "itemLookAt", kFlowNormal },
74 { Resources::Command::kItem2DFollowPath, "item2DFollowPath", kFlowNormal },
75 { Resources::Command::kItemEnable, "itemEnable", kFlowNormal },
76 { Resources::Command::kItemSetActivity, "itemSetActivity", kFlowNormal },
77 { Resources::Command::kItemSelectInInventory, "itemSelectInInventory", kFlowNormal },
78 { Resources::Command::kUseAnimHierarchy, "useAnimHierarchy", kFlowNormal },
79 { Resources::Command::kPlayAnimation, "playAnimation", kFlowNormal },
80 { Resources::Command::kScriptEnable, "scriptEnable", kFlowNormal },
81 { Resources::Command::kShowPlay, "showPlay", kFlowNormal },
82 { Resources::Command::kKnowledgeSetBoolean, "knowledgeSetBoolean", kFlowNormal },
83 { Resources::Command::kKnowledgeSetInteger, "knowledgeSetInteger", kFlowNormal },
84 { Resources::Command::kKnowledgeAddInteger, "knowledgeAddInteger", kFlowNormal },
85 { Resources::Command::kEnableFloorField, "enableFloorField", kFlowNormal },
86 { Resources::Command::kPlayAnimScriptItem, "playAnimScriptItem", kFlowNormal },
87 { Resources::Command::kItemAnimFollowPath, "itemAnimFollowPath", kFlowNormal },
88 { Resources::Command::kKnowledgeAssignBool, "knowledgeAssignBool", kFlowNormal },
89 { Resources::Command::kKnowledgeAssignInteger, "knowledgeAssignInteger", kFlowNormal },
90 { Resources::Command::kLocationScrollTo, "locationScrollTo", kFlowNormal },
91 { Resources::Command::kSoundPlay, "soundPlay", kFlowNormal },
92 { Resources::Command::kKnowledgeSetIntRandom, "knowledgeSetIntRandom", kFlowNormal },
93 { Resources::Command::kKnowledgeSubValue, "knowledgeSubValue", kFlowNormal },
94 { Resources::Command::kItemLookDirection, "itemLookDirection", kFlowNormal },
95 { Resources::Command::kStopPlayingSound, "stopPlayingSound", kFlowNormal },
96 { Resources::Command::kLayerGoTo, "layerGoTo", kFlowNormal },
97 { Resources::Command::kLayerEnable, "layerEnable", kFlowNormal },
98 { Resources::Command::kLocationScrollSet, "locationScrollSet", kFlowNormal },
99 { Resources::Command::kFullMotionVideoPlay, "fullMotionVideoPlay", kFlowNormal },
100 { Resources::Command::kAnimSetFrame, "animSetFrame", kFlowNormal },
101 { Resources::Command::kKnowledgeAssignNegatedBool, "knowledgeAssignNegatedBool", kFlowNormal },
102 { Resources::Command::kDiaryEnableEntry, "diaryEnableEntry", kFlowNormal },
103 { Resources::Command::kPATChangeTooltip, "pATChangeTooltip", kFlowNormal },
104 { Resources::Command::kSoundChange, "soundChange", kFlowNormal },
105 { Resources::Command::kLightSetColor, "lightSetColor", kFlowNormal },
106 { Resources::Command::kLightFollowPath, "lightFollowPath", kFlowNormal },
107 { Resources::Command::kItem3DRunTo, "item3DRunTo", kFlowNormal },
108 { Resources::Command::kItemPlaceDirection, "itemPlaceDirection", kFlowNormal },
109 { Resources::Command::kItemRotateDirection, "itemRotateDirection", kFlowNormal },
110 { Resources::Command::kActivateTexture, "activateTexture", kFlowNormal },
111 { Resources::Command::kActivateMesh, "activateMesh", kFlowNormal },
112 { Resources::Command::kItem3DSetWalkTarget, "item3DSetWalkTarget", kFlowNormal },
113 { Resources::Command::kSpeakWithoutTalking, "speakWithoutTalking", kFlowNormal },
114 { Resources::Command::kIsOnFloorField, "isOnFloorField", kFlowBranch },
115 { Resources::Command::kIsItemEnabled, "isItemEnabled", kFlowBranch },
116 { Resources::Command::kIsScriptEnabled, "isScriptEnabled", kFlowBranch },
117 { Resources::Command::kIsKnowledgeBooleanSet, "isKnowledgeBooleanSet", kFlowBranch },
118 { Resources::Command::kIsKnowledgeIntegerInRange, "isKnowledgeIntegerInRange", kFlowBranch },
119 { Resources::Command::kIsKnowledgeIntegerAbove, "isKnowledgeIntegerAbove", kFlowBranch },
120 { Resources::Command::kIsKnowledgeIntegerEqual, "isKnowledgeIntegerEqual", kFlowBranch },
121 { Resources::Command::kIsKnowledgeIntegerLower, "isKnowledgeIntegerLower", kFlowBranch },
122 { Resources::Command::kIsScriptActive, "isScriptActive", kFlowBranch },
123 { Resources::Command::kIsRandom, "isRandom", kFlowBranch },
124 { Resources::Command::kIsAnimScriptItemReached, "isAnimScriptItemReached", kFlowBranch },
125 { Resources::Command::kIsItemOnPlace, "isItemOnPlace", kFlowBranch },
126 { Resources::Command::kIsAnimPlaying, "isAnimPlaying", kFlowBranch },
127 { Resources::Command::kIsItemActivity, "isItemActivity", kFlowBranch },
128 { Resources::Command::kIsItemNearPlace, "isItemNearPlace", kFlowBranch },
129 { Resources::Command::kIsAnimAtTime, "isAnimAtTime", kFlowBranch },
130 { Resources::Command::kIsLocation2D, "isLocation2D", kFlowBranch },
131 { Resources::Command::kIsInventoryOpen, "isInventoryOpen", kFlowBranch }
132 };
133
134 for (uint i = 0; i < ARRAYSIZE(typeNames); i++) {
135 if (typeNames[i].subType == subType) {
136 return &typeNames[i];
137 }
138 }
139
140 return nullptr;
141 }
142
getEffectiveArguments() const143 Command::ArgumentArray Command::getEffectiveArguments() const {
144 uint effectiveArgumentsStart;
145 switch (_subTypeDesc->controlFlowType) {
146 case kFlowEnd:
147 effectiveArgumentsStart = 0;
148 break;
149 case kFlowNormal:
150 effectiveArgumentsStart = 1;
151 break;
152 case kFlowBranch:
153 effectiveArgumentsStart = 2;
154 break;
155 default:
156 error("Unhandled control flow type '%d'", _subTypeDesc->controlFlowType);
157 }
158
159 ArgumentArray effectiveArguments;
160 for (uint i = effectiveArgumentsStart; i < _arguments.size(); i++) {
161 effectiveArguments.push_back(_arguments[i]);
162 }
163
164 return effectiveArguments;
165 }
166
describeArguments(DefinitionRegistry * definitions) const167 Common::String Command::describeArguments(DefinitionRegistry *definitions) const {
168 Common::String desc;
169
170 for (uint i = 0; i < _arguments.size(); i++) {
171 switch (_arguments[i].type) {
172 case Resources::Command::Argument::kTypeInteger1:
173 case Resources::Command::Argument::kTypeInteger2:
174 desc += Common::String::format("%d", _arguments[i].intValue);
175 break;
176
177 case Resources::Command::Argument::kTypeResourceReference: {
178 if (definitions) {
179 desc += definitions->getFromReference(_arguments[i].referenceValue);
180 } else {
181 desc += _arguments[i].referenceValue.describe();
182 }
183 }
184 break;
185 case Resources::Command::Argument::kTypeString:
186 desc += _arguments[i].stringValue;
187 break;
188 default:
189 error("Unknown argument type %d", _arguments[i].type);
190 }
191
192 if (i != _arguments.size() - 1) {
193 desc += ", ";
194 }
195 }
196
197 return desc;
198 }
199
printCall() const200 void Command::printCall() const {
201 debug("%d: %s(%s)", _index, _subTypeDesc->name, describeArguments(nullptr).c_str());
202 }
203
getIndex() const204 uint16 Command::getIndex() const {
205 return _index;
206 }
207
hasSubtypeDescription() const208 bool Command::hasSubtypeDescription() const {
209 return _subTypeDesc != nullptr;
210 }
211
getSubType() const212 Resources::Command::SubType Command::getSubType() const {
213 return _subType;
214 }
215
CFGCommand(Resources::Command * resource)216 CFGCommand::CFGCommand(Resources::Command *resource) :
217 Command(resource),
218 _followerIndex(-1),
219 _trueBranchIndex(-1),
220 _falseBranchIndex(-1),
221 _follower(nullptr),
222 _trueBranch(nullptr),
223 _falseBranch(nullptr),
224 _block(nullptr) {
225 if (_subTypeDesc) {
226 initBranches();
227 }
228 }
229
initBranches()230 void CFGCommand::initBranches() {
231 switch (_subTypeDesc->controlFlowType) {
232 case kFlowNormal:
233 _followerIndex = _arguments[0].intValue;
234 break;
235 case kFlowBranch:
236 if (_arguments[0].intValue == _arguments[1].intValue) {
237 // Degenerate conditions are handled here so that blocks are not split after them
238 _followerIndex = _arguments[0].intValue;
239 } else {
240 _falseBranchIndex = _arguments[0].intValue;
241 _trueBranchIndex = _arguments[1].intValue;
242 }
243 break;
244 case kFlowEnd:
245 // No followers
246 break;
247 }
248 }
249
isEntryPoint() const250 bool CFGCommand::isEntryPoint() const {
251 return _subType == Resources::Command::kCommandBegin;
252 }
253
isBranch() const254 bool CFGCommand::isBranch() const {
255 return _trueBranchIndex >= 0 && _falseBranchIndex >= 0;
256 }
257
isBranchTarget() const258 bool CFGCommand::isBranchTarget() const {
259 return _predecessors.size() > 1;
260 }
261
getBlock() const262 Block *CFGCommand::getBlock() const {
263 return _block;
264 }
265
setBlock(Block * block)266 void CFGCommand::setBlock(Block *block) {
267 _block = block;
268 }
269
getFollower() const270 CFGCommand *CFGCommand::getFollower() const {
271 return _follower;
272 }
273
getTrueBranch() const274 CFGCommand *CFGCommand::getTrueBranch() const {
275 return _trueBranch;
276 }
277
getFalseBranch() const278 CFGCommand *CFGCommand::getFalseBranch() const {
279 return _falseBranch;
280 }
281
linkBranches(const Common::Array<CFGCommand * > & commands)282 void CFGCommand::linkBranches(const Common::Array<CFGCommand *> &commands) {
283 if (_followerIndex >= 0) {
284 _follower = findCommandWithIndex(commands, _followerIndex);
285 _follower->_predecessors.push_back(this);
286 }
287
288 if (_falseBranchIndex >= 0) {
289 _falseBranch = findCommandWithIndex(commands, _falseBranchIndex);
290 _falseBranch->_predecessors.push_back(this);
291 }
292
293 if (_trueBranchIndex >= 0) {
294 _trueBranch = findCommandWithIndex(commands, _trueBranchIndex);
295 _trueBranch->_predecessors.push_back(this);
296 }
297 }
298
findCommandWithIndex(const Common::Array<CFGCommand * > & commands,int32 index)299 CFGCommand *CFGCommand::findCommandWithIndex(const Common::Array<CFGCommand *> &commands, int32 index) {
300 for (uint i = 0; i < commands.size(); i++) {
301 CFGCommand *command = commands[i];
302
303 if (command->_index == index) {
304 return command;
305 }
306 }
307
308 error("Unable to find command with index %d", index);
309 }
310
registerReference(const ResourceReference & reference)311 void DefinitionRegistry::registerReference(const ResourceReference &reference) {
312 if (!reference.canResolve()) {
313 // The reference uses archives that are not currently loaded
314 return;
315 }
316
317 Resources::Object *object = reference.resolve<Resources::Object>();
318 if (!_definitions.contains(object)) {
319 // TODO: There is no guarantee the definition is unique
320 _definitions[object] = object->getType().getName() + stringToCamelCase(object->getName());
321 }
322 }
323
getFromReference(const ResourceReference & reference) const324 Common::String DefinitionRegistry::getFromReference(const ResourceReference &reference) const {
325 if (!reference.canResolve()) {
326 // The reference uses archives that are not currently loaded
327 return reference.describe();
328 }
329
330 Resources::Object *object = reference.resolve<Resources::Object>();
331
332 if (_definitions.contains(object)) {
333 return _definitions.getVal(object);
334 } else {
335 return reference.describe();
336 }
337 }
338
stringToCamelCase(const Common::String & input)339 Common::String DefinitionRegistry::stringToCamelCase(const Common::String &input) {
340 Common::String clean = input;
341
342 // First replace all non alphanumerical characters with spaces
343 for (uint i = 0; i < clean.size(); i++) {
344 if (!Common::isAlnum(clean[i])) {
345 clean.setChar(' ', i);
346 }
347 }
348
349 // Then turn the string into camel case
350 Common::String output;
351 Common::StringTokenizer tokens = Common::StringTokenizer(clean);
352 while (!tokens.empty()) {
353 Common::String token = tokens.nextToken();
354
355 char upperFirstLetter = toupper(token[0]);
356 token.setChar(upperFirstLetter, 0);
357
358 output += token;
359 }
360
361 return output;
362 }
363
printAll() const364 void DefinitionRegistry::printAll() const {
365 DefinitionMap::const_iterator it = _definitions.begin();
366 while (it != _definitions.end()) {
367 ResourceReference reference;
368 reference.buildFromResource(it->_key);
369
370 debug("let %s = %s", it->_value.c_str(), reference.describe().c_str());
371
372 it++;
373 }
374 }
375
376 } // End of namespace Tools
377 } // End of namespace Stark
378