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/ini-file.h"
24 #include "common/stream.h"
25 #include "common/system.h"
26 #include "common/events.h"
27
28 #include "graphics/surface.h"
29
30 #include "petka/big_dialogue.h"
31 #include "petka/flc.h"
32 #include "petka/sound.h"
33 #include "petka/petka.h"
34 #include "petka/video.h"
35 #include "petka/q_system.h"
36 #include "petka/q_manager.h"
37 #include "petka/objects/object_star.h"
38 #include "petka/objects/object_cursor.h"
39 #include "petka/interfaces/main.h"
40 #include "petka/objects/heroes.h"
41 #include "petka/objects/object_case.h"
42
43 namespace Petka {
44
createReaction(QMessage * messages,QMessage * end)45 QReaction *createReaction(QMessage *messages, QMessage *end) {
46 QReaction *reaction = new QReaction();
47 while (messages != end) {
48 reaction->messages.push_back(*messages++);
49 }
50 return reaction;
51 }
52
QVisibleObject()53 QVisibleObject::QVisibleObject()
54 : _resourceId(-1), _z(240) {}
55
QMessageObject()56 QMessageObject::QMessageObject() {
57 _id = -1;
58 _status = 0;
59 _time = 0;
60 _dialogColor = -1;
61 _animate = true;
62 _isShown = true;
63 _isActive = true;
64 _updateZ = false;
65 _holdMessages = false;
66 _loopedSound = false;
67 _startSound = false;
68 _reaction = nullptr;
69 }
70
processMessage(const QMessage & msg)71 void QMessageObject::processMessage(const QMessage &msg) {
72 bool reacted = false;
73 int opcode = (msg.opcode == kObjectUse) ? (msg.sender->_id << 16) | kObjectUse : msg.opcode;
74 for (uint i = 0; i < _reactions.size(); ++i) {
75 QReaction *r = &_reactions[i];
76 if (r->opcode != msg.opcode ||
77 (r->status != -1 && r->status != _status) ||
78 (r->senderId != -1 && r->senderId != msg.sender->_id)) {
79 continue;
80 }
81 bool fallback;
82 if (g_vm->getBigDialogue()->findHandler(_id, opcode, &fallback) && !fallback) {
83 g_vm->getBigDialogue()->setHandler(_id, opcode);
84 g_vm->getQSystem()->_mainInterface->_dialog.setSender(this);
85 }
86 processReaction(r, &msg);
87 reacted = true;
88 }
89
90 if (reacted || !g_vm->getBigDialogue()->findHandler(_id, opcode, nullptr)) {
91 switch (msg.opcode) {
92 case kAddInv:
93 g_vm->getQSystem()->getCase()->addItem(msg.objId);
94 break;
95 case kDelInv:
96 g_vm->getQSystem()->getCase()->removeItem(msg.objId);
97 break;
98 case kSetInv:
99 g_vm->getQSystem()->getCase()->transformItem(msg.sender->_id, msg.objId);
100 break;
101 case kAvi: {
102 Common::String videoName = g_vm->resMgr()->findResourceName((uint16) msg.arg1);
103 g_vm->playVideo(g_vm->openFile(videoName, false));
104 break;
105 }
106 case kContinue:
107 g_vm->getQSystem()->_mainInterface->_dialog.endUserMsg();
108 break;
109 case kCursor:
110 g_vm->getQSystem()->getCursor()->setInvItem(this, msg.arg1);
111 g_vm->videoSystem()->makeAllDirty();
112 break;
113 case kDialog:
114 g_vm->getQSystem()->_mainInterface->_dialog.start(msg.arg1, this);
115 break;
116 case kSetPos:
117 setPos(Common::Point(msg.arg1, msg.arg2), false);
118 break;
119 case kSet:
120 case kPlay:
121 play(msg.arg1, msg.arg2);
122 break;
123 case kAnimate:
124 _animate = msg.arg1;
125 break;
126 case kEnd:
127 if (_reaction && _reactionId == msg.arg1) {
128 QReaction *reaction = _reaction;
129 _reaction = nullptr;
130 processReaction(reaction);
131 }
132 break;
133 case kStatus:
134 _status = (int8) msg.arg1;
135 break;
136 case kOn:
137 _isActive = true;
138 show(true);
139 break;
140 case kOff:
141 _isActive = false;
142 show(false);
143 break;
144 case kStop:
145 g_vm->getQSystem()->getCursor()->show(msg.arg1);
146 g_vm->getQSystem()->getStar()->_isActive = msg.arg1;
147 break;
148 case kShow:
149 show(msg.arg1);
150 break;
151 case kShake:
152 g_vm->videoSystem()->setShake(msg.arg1);
153 break;
154 case kSystem:
155 switch (msg.arg1){
156 case 0:
157 g_vm->getQSystem()->getStar()->_isActive = false;
158 break;
159 case 1:
160 g_vm->getQSystem()->getStar()->_isActive = true;
161 break;
162 case 242:
163 Engine::quitGame();
164 break;
165 default:
166 break;
167 }
168 break;
169 case kHide:
170 show(false);
171 break;
172 case kZBuffer:
173 _updateZ = msg.arg1;
174 _z = (msg.arg2 != -1) ? msg.arg2 : _z;
175 break;
176 case kActive:
177 _isActive = msg.arg1;
178 break;
179 case kPassive:
180 _isActive = false;
181 break;
182 case kJump: {
183 Common::Point p;
184 p.x = (msg.arg1 == 0xffff ? _walkX : msg.arg1);
185 p.y = (msg.arg2 == -1 ? _walkY : msg.arg2);
186 g_vm->getQSystem()->getPetka()->setPos(p, false);
187 break;
188 }
189 case kJumpVich: {
190 Common::Point p;
191 p.x = (msg.arg1 == 0xffff ? _walkX : msg.arg1);
192 p.y = (msg.arg2 == -1 ? _walkY : msg.arg2);
193 g_vm->getQSystem()->getChapay()->setPos(p, false);
194 break;
195 }
196 case kWalk:
197 if (!reacted) {
198 if (_walkX == -1) {
199 g_vm->getQSystem()->getPetka()->walk(msg.arg1, msg.arg2);
200
201 } else {
202 g_vm->getQSystem()->getPetka()->walk(_walkX, _walkY);
203 }
204 }
205 break;
206 case kWalkTo: {
207 int destX = msg.arg1;
208 int destY = msg.arg2;
209 if (destX == -1 || destY == -1) {
210 destX = _walkX;
211 destY = _walkY;
212 }
213 if (destX != -1) {
214 g_vm->getQSystem()->getPetka()->walk(destX, destY);
215 QReaction *r = g_vm->getQSystem()->getPetka()->_heroReaction;
216 if (r) {
217 for (uint i = 0; i < r->messages.size(); ++i) {
218 if (r->messages[i].opcode == kGoTo) {
219 g_vm->getQSystem()->getChapay()->walk(destX, destY);
220 break;
221 }
222 }
223 }
224 }
225 break;
226 }
227 case kWalkVich: {
228 int destX = msg.arg1;
229 int destY = msg.arg2;
230 if (destX == -1 || destY == -1) {
231 destX = _walkX;
232 destY = _walkY;
233 }
234 if (destX != -1)
235 g_vm->getQSystem()->getChapay()->walk(destX, destY);
236 break;
237 }
238 case kDescription: {
239 Common::ScopedPtr<Common::SeekableReadStream> invStream(g_vm->openFile("invntr.txt", true));
240 if (invStream) {
241 Common::String desc;
242 Common::INIFile invIni;
243
244 invIni.allowNonEnglishCharacters();
245 invIni.loadFromStream(*invStream);
246 invIni.getKey(_name, "ALL", desc);
247
248 g_vm->getQSystem()->_mainInterface->setTextDescription(Common::convertToU32String(desc.c_str(), Common::kWindows1251), msg.arg1);
249 }
250 break;
251 }
252 case kPart:
253 g_vm->loadPartAtNextFrame(msg.arg1);
254 break;
255 case kChapter:
256 g_vm->loadChapter(msg.arg1);
257 break;
258 case kToMap:
259 g_vm->getQSystem()->toggleMapInterface();
260 break;
261 default:
262 break;
263 }
264 } else {
265 for (uint i = 0; i < _reactions.size(); ++i) {
266 QReaction &r = _reactions[i];
267 if (r.opcode != msg.opcode ||
268 (r.status != -1 && r.status != _status) ||
269 (r.senderId != -1 && r.senderId != msg.sender->_id)) {
270 continue;
271 }
272 g_vm->getQSystem()->_mainInterface->_dialog.setReaction(createReaction(r.messages.data(), r.messages.end()));
273 }
274 g_vm->getBigDialogue()->setHandler(_id, opcode);
275 g_vm->getQSystem()->_mainInterface->_dialog.start(msg.arg1, this);
276 }
277
278 }
279
show(bool v)280 void QMessageObject::show(bool v) {
281 _isShown = v;
282 }
283
setReaction(int16 id,QReaction * reaction)284 void QMessageObject::setReaction(int16 id, QReaction *reaction) {
285 delete _reaction;
286 _reaction = reaction;
287 _reactionId = id;
288 }
289
processReaction(QReaction * r,const QMessage * msg)290 void QMessageObject::processReaction(QReaction *r, const QMessage *msg) {
291 bool deleteReaction = (msg == nullptr);
292 for (uint j = 0; j < r->messages.size(); ++j) {
293 QMessage &rMsg = r->messages[j];
294 if (rMsg.opcode == kCheck && g_vm->getQSystem()->findObject(rMsg.objId)->_status != rMsg.arg1) {
295 break;
296 }
297 if (msg && rMsg.opcode == kIf &&
298 ((rMsg.arg1 != 0xffff && rMsg.arg1 != msg->arg1) ||
299 (rMsg.arg2 != -1 && rMsg.arg2 != msg->arg2) ||
300 (rMsg.arg3 != -1 && rMsg.arg3 != msg->arg3))) {
301 break;
302 }
303 if (msg && rMsg.opcode == kRandom && rMsg.arg2 != -1) {
304 rMsg.arg1 = (int16) g_vm->getRnd().getRandomNumber((uint) (rMsg.arg2 - 1));
305 }
306 g_vm->getQSystem()->addMessage(rMsg.objId, rMsg.opcode, rMsg.arg1, rMsg.arg2, rMsg.arg3, rMsg.unk, this);
307 bool processed = true;
308 switch (rMsg.opcode) {
309 case kDialog: {
310 g_vm->getQSystem()->_mainInterface->_dialog.setReaction(createReaction(r->messages.data() + j + 1, r->messages.end()));
311 break;
312 }
313 case kPlay: {
314 QMessageObject *obj = g_vm->getQSystem()->findObject(rMsg.objId);
315 obj->setReaction(rMsg.arg1, createReaction(r->messages.data() + j + 1, r->messages.end()));
316 break;
317 }
318 case kWalk:
319 case kWalkTo:
320 g_vm->getQSystem()->getPetka()->setReactionAfterWalk(j, r, this, deleteReaction);
321 return;
322 case kWalkVich:
323 g_vm->getQSystem()->getChapay()->setReactionAfterWalk(j, r, this, deleteReaction);
324 return;
325 default:
326 processed = false;
327 break;
328 }
329 if (processed)
330 break;
331 }
332 if (deleteReaction)
333 delete r;
334 }
335
play(int id,int type)336 void QMessageObject::play(int id, int type) {
337 if (g_vm->getQSystem()->_totalInit) {
338 _resourceId = id;
339 _loopedSound = (type == 5);
340 return;
341 }
342
343 if (_loopedSound || g_vm->isDemo()) {
344 removeSound();
345 }
346
347 FlicDecoder *flc = g_vm->resMgr()->getFlic(_resourceId);
348 if (flc) {
349 g_vm->videoSystem()->addDirtyRect(Common::Point(_x, _y), *flc);
350 }
351
352 _resourceId = id;
353
354 loadSound();
355
356 flc = g_vm->resMgr()->getFlic(id);
357 flc->setFrame(1);
358 _time = 0;
359 _loopedSound = (type == 5);
360 }
361
loadSound()362 void QMessageObject::loadSound() {
363 Common::String name = g_vm->resMgr()->findSoundName(_resourceId);
364 _sound = g_vm->soundMgr()->addSound(name, Audio::Mixer::kSFXSoundType);
365 _startSound = false;
366 }
367
removeSound()368 void QMessageObject::removeSound() {
369 Common::String name = g_vm->resMgr()->findSoundName(_resourceId);
370 g_vm->soundMgr()->removeSound(name);
371 _sound = nullptr;
372 }
373
readString(Common::ReadStream & readStream)374 static Common::String readString(Common::ReadStream &readStream) {
375 uint32 stringSize = readStream.readUint32LE();
376 byte *data = (byte *)malloc(stringSize + 1);
377 readStream.read(data, stringSize);
378 data[stringSize] = '\0';
379 Common::String str((char *)data);
380 free(data);
381 return str;
382 }
383
readScriptData(Common::SeekableReadStream & stream)384 void QMessageObject::readScriptData(Common::SeekableReadStream &stream) {
385 _id = stream.readUint16LE();
386 _name = readString(stream);
387 _reactions.resize(stream.readUint32LE());
388
389 for (uint i = 0; i < _reactions.size(); ++i) {
390 QReaction *reaction = &_reactions[i];
391 reaction->opcode = stream.readUint16LE();
392 reaction->status = stream.readByte();
393 reaction->senderId = stream.readUint16LE();
394 reaction->messages.resize(stream.readUint32LE());
395 for (uint j = 0; j < reaction->messages.size(); ++j) {
396 QMessage *msg = &reaction->messages[j];
397 msg->objId = stream.readUint16LE();
398 msg->opcode = stream.readUint16LE();
399 msg->arg1 = stream.readUint16LE();
400 msg->arg2 = stream.readUint16LE();
401 msg->arg3 = stream.readUint16LE();
402 }
403 }
404 }
405
readInisData(Common::INIFile & names,Common::INIFile & cast,Common::INIFile * bgs)406 void QMessageObject::readInisData(Common::INIFile &names, Common::INIFile &cast, Common::INIFile *bgs) {
407 names.getKey(_name, "all", _nameOnScreen);
408 Common::String rgbString;
409 if (cast.getKey(_name, "all", rgbString)) {
410 int r, g, b;
411 sscanf(rgbString.c_str(), "%d %d %d", &r, &g, &b);
412 _dialogColor = g_vm->_system->getScreenFormat().RGBToColor((byte)r, (byte)g, (byte)b);
413 }
414 }
415
QObject()416 QObject::QObject() {
417 _animate = true;
418 _updateZ = true;
419 _frame = 1;
420 _sound = nullptr;
421 _x = 0;
422 _y = 0;
423 _walkX = -1;
424 _walkY = -1;
425 }
426
isInPoint(Common::Point p)427 bool QObject::isInPoint(Common::Point p) {
428 if (!_isActive)
429 return false;
430
431 FlicDecoder *flc = g_vm->resMgr()->getFlic(_resourceId);
432 if (!flc || !flc->getBounds().contains(p.x - _x, p.y - _y))
433 return false;
434
435 const Graphics::Surface *s = flc->getCurrentFrame();
436 auto format = g_system->getScreenFormat();
437
438 byte index = *(const byte *)s->getBasePtr(p.x - _x, p.y - _y);
439 const byte *pal = flc->getPalette();
440
441 return format.RGBToColor(pal[0], pal[1], pal[2]) != format.RGBToColor(pal[index * 3], pal[index * 3 + 1], pal[index * 3 + 2]);
442 }
443
draw()444 void QObject::draw() {
445 if (!_isShown || _resourceId == -1) {
446 return;
447 }
448 FlicDecoder *flc = g_vm->resMgr()->getFlic(_resourceId);
449 if (!flc) {
450 return;
451 }
452 if (_animate && _startSound) {
453 if (_sound) {
454 _sound->play(_loopedSound);
455 if (_loopedSound) {
456 _sound = nullptr;
457 }
458 }
459 _startSound = false;
460 }
461
462 int xOff = g_vm->getQSystem()->_xOffset;
463 VideoSystem *videoSys = g_vm->videoSystem();
464
465 Common::Rect screen(640 + xOff, 480);
466 Common::Rect flcBounds(flc->getBounds());
467 Common::Rect objBounds(flcBounds);
468
469 objBounds.translate(_x, _y);
470
471 Common::Rect intersect(screen.findIntersectingRect(objBounds));
472 if (intersect.isEmpty())
473 return;
474
475 Graphics::Surface *surface = flc->getCurrentFrame()->getSubArea(flcBounds).convertTo(g_system->getScreenFormat(), flc->getPalette());
476
477 for (Common::Rect dirty : videoSys->rects()) {
478 dirty.translate(xOff, 0);
479
480 Common::Rect destRect(intersect.findIntersectingRect(dirty));
481 if (destRect.isEmpty())
482 continue;
483
484 Common::Rect srcRect(destRect);
485
486 srcRect.translate(-_x, -_y);
487 srcRect.translate(-flcBounds.left, -flcBounds.top);
488
489 destRect.translate(-xOff, 0);
490 videoSys->transBlitFrom(*surface, srcRect, destRect, flc->getTransColor(surface->format));
491 }
492
493 surface->free();
494 delete surface;
495 }
496
updateZ()497 void QObject::updateZ() {
498 if (!_animate || !_isShown || !_updateZ)
499 return;
500 FlicDecoder *flc = g_vm->resMgr()->getFlic(_resourceId);
501 if (flc) {
502 _z = 1;
503 const Common::Array<Common::Rect> &rects = flc->getMskRects();
504 for (uint i = 0; i < rects.size(); ++i) {
505 if (_y + rects[i].bottom > _z)
506 _z = _y + rects[i].bottom;
507 }
508
509 }
510 }
511
show(bool v)512 void QObject::show(bool v) {
513 FlicDecoder *flc = g_vm->resMgr()->getFlic(_resourceId);
514 if (flc) {
515 g_vm->videoSystem()->addDirtyRect(Common::Point(_x, _y), *flc);
516 }
517 QMessageObject::show(v);
518 }
519
update(int time)520 void QObject::update(int time) {
521 if (!_animate || !_isShown)
522 return;
523 _time += time;
524 FlicDecoder *flc = g_vm->resMgr()->getFlic(_resourceId);
525 if (flc && flc->getFrameCount() != 1) {
526 if (_sound) {
527 Common::Rect bounds = flc->getBounds();
528 _sound->setBalance(bounds.left + bounds.width() / 2 - g_vm->getQSystem()->_xOffset, 640);
529 }
530
531 while (_time >= (int32)flc->getDelay()) {
532 if (_sound && flc->getCurFrame() == 0) {
533 _startSound = true;
534 }
535 g_vm->videoSystem()->addDirtyRect(Common::Point(_x, _y), *flc);
536 flc->setFrame(-1);
537 if (flc->getCurFrame() == (int32)flc->getFrameCount() - 1) {
538 g_vm->getQSystem()->addMessage(_id, kEnd, _resourceId, 0, 0, 0, 0);
539 }
540 if (flc->getCurFrame() + 1 == (int32)flc->getFrameCount() / 2) {
541 g_vm->getQSystem()->addMessage(_id, kHalf, _resourceId, 0, 0, 0, 0);
542 }
543 g_vm->videoSystem()->addDirtyRect(Common::Point(_x, _y), *flc);
544 _time -= flc->getDelay();
545 }
546 }
547 }
548
setPos(Common::Point p,bool)549 void QObject::setPos(Common::Point p, bool) {
550 FlicDecoder *flc = g_vm->resMgr()->getFlic(_resourceId);
551 if (flc) {
552 g_vm->videoSystem()->addDirtyMskRects(Common::Point(_x, _y), *flc);
553 g_vm->videoSystem()->addDirtyMskRects(p, *flc);
554 _x = p.x;
555 _y = p.y;
556 }
557 }
558
onClick(Common::Point p)559 void QObject::onClick(Common::Point p) {
560 QSystem *sys = g_vm->getQSystem();
561 QObjectCursor *cursor = g_vm->getQSystem()->getCursor();
562
563 sys->getPetka()->stopWalk();
564 sys->getChapay()->stopWalk();
565
566 switch (cursor->_actionType) {
567 case kActionLook:
568 g_vm->getQSystem()->addMessage(_id, kLook, 0, 0, 0, 0, this);
569 break;
570 case kActionWalk:
571 g_vm->getQSystem()->addMessage(_id, kWalk, p.x, p.y, 0, 0, this);
572 break;
573 case kActionUse:
574 g_vm->getQSystem()->addMessage(_id, kUse, 0, 0, 0, 0, this);
575 break;
576 case kActionTake:
577 g_vm->getQSystem()->addMessage(_id, kTake, 0, 0, 0, 0, this);
578 break;
579 case kActionTalk:
580 g_vm->getQSystem()->addMessage(_id, kTalk, 0, 0, 0, 0, this);
581 break;
582 case kActionObjUseChapayev:
583 g_vm->getQSystem()->addMessage(_id, kObjectUse, p.x, p.y, 0, 0, g_vm->getQSystem()->getChapay());
584 break;
585 case kActionObjUse:
586 g_vm->getQSystem()->addMessage(_id, kObjectUse, 0, 0, 0, 0, cursor->_invObj);
587 break;
588 default:
589 break;
590 }
591 }
592
onMouseMove(Common::Point p)593 void QObject::onMouseMove(Common::Point p) {
594 g_vm->getQSystem()->_mainInterface->_objUnderCursor = this;
595 }
596
597 }
598