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