Better speech bubble positioning.
[scummvm-innocent.git] / engines / innocent / actor.cpp
blob3d75293dc9234196f8a3d8cadafede60ae8fed1e
1 /* ScummVM - Graphic Adventure Engine
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.
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.
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.
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.
21 * $URL$
22 * $Id$
26 #include "innocent/actor.h"
28 #include <sstream>
30 #include "common/rect.h"
32 #include "innocent/graphics.h"
33 #include "innocent/innocent.h"
34 #include "innocent/inter.h"
35 #include "innocent/logic.h"
36 #include "innocent/resources.h"
37 #include "innocent/room.h"
38 #include "innocent/util.h"
40 namespace Innocent {
43 Actor::Actor(const CodePointer &code) : Animation(code, Common::Point()) {
44 byte *header = code.code();
45 _base = header - code.offset();
46 readHeader(header);
47 snprintf(_debugInfo, 50, "actor at %s", +code);
48 _direction = kDirNone;
49 _frame = 0;
50 _debug = false;
51 _attentionNeeded = false;
52 _nextDirection = kDirNone;
54 Engine::instance().logic()->addAnimation(this);
56 init_opcodes<37>();
59 bool Actor::isFine() const {
60 return _room == Log.currentRoom() && _base && !_attentionNeeded &&
61 (!_framequeue.empty() || !_confused || _nextDirection);
64 void Actor::setAnimation(uint16 offset) {
65 _base = _base - _baseOffset + offset;
66 _baseOffset = offset;
67 _offset = 0;
68 _debugInvalid = false;
69 _confused = _attentionNeeded = false;
70 clearMainSprite();
71 _interval = 1;
72 _counter = _ticksLeft = 0;
73 _nextDirection = kDirNone;
76 void Actor::setAnimation(const CodePointer &anim) {
77 debugC(3, kDebugLevelScript, "setting animation code of %s to %s", _debugInfo, +anim);
78 _base = anim.code();
79 _baseOffset = anim.offset();
80 _offset = 0;
81 _debugInvalid = false;
82 _confused = _attentionNeeded = false;
83 clearMainSprite();
84 _interval = 1;
85 _counter = _ticksLeft = 0;
86 _nextDirection = kDirNone;
89 void Actor::hide() {
90 _base = 0;
91 _baseOffset = _offset = 0;
94 void Actor::callMe(const CodePointer &code) {
95 debugC(3, kDebugLevelScript, "actor will call %s when needed", +code);
96 _callBacks.push(code);
99 void Actor::tellMe(const CodePointer &code, uint16 timeout) {
100 _roomCallbacks.push_back(RoomCallback(timeout, code));
103 bool Actor::isSpeaking() const {
104 return _speech.active();
107 void Actor::callMeWhenSilent(const CodePointer &cp) {
108 _speech.callWhenDone(cp);
111 void Actor::say(const Common::String &text) {
112 _speech = Speech(this, text);
115 Actor::Speech::~Speech() { while (!_cb.empty()) Log.runLater(_cb.pop()); }
117 bool Actor::isMoving() const {
118 //TODO stub
119 return false;
122 void Actor::callMeWhenStill(const CodePointer &cp) {
123 assert(false);
126 void Actor::setFrame(uint16 frame) {
127 _frame = frame;
128 Frame f(Log.room()->getFrame(frame));
129 if (f.position().x == 999)
130 return;
131 _position = f.position();
132 debugC(5, kDebugLevelActor, "actor set to frame %d, position %d:%d", f.index(), _position.x, _position.y);
135 Common::List<Actor::Frame> Actor::findPath(Actor::Frame from, uint16 to) {
136 Common::List<Common::List<Frame> > reachable;
138 Common::List<Frame> zero;
139 zero.push_back(from);
140 reachable.push_back(zero);
142 bool found = false;
143 while (!found) {
144 Common::List<Common::List<Frame> >::iterator back = reachable.end();
145 back--;
146 Common::List<Frame>::iterator current = back->begin();
147 Common::List<Frame> next;
148 std::ostringstream s;
149 while (!found && current != back->end()) {
150 std::vector<byte> nexts = current->nexts();
151 for (int i = 0; i < 8; i++)
152 if (nexts[i]) {
153 s << ", " << int(nexts[i]);
154 next.push_back(Log.room()->getFrame(nexts[i]));
155 if (nexts[i] == to) {
156 found = true;
157 break;
160 current++;
162 reachable.push_back(next);
163 debugC(4, kDebugLevelActor, "reachable on this level:%s", s.str().c_str());
166 Common::List<Frame> path;
168 Common::List<Common::List<Frame> >::iterator level = reachable.end();
169 level--;
170 Common::List<Frame>::iterator current = level->end();
171 current--;
172 uint16 index = level->size() - 1;
174 forever {
175 path.push_front(*current);
176 if (*current == from)
177 break;
178 level--;
179 current = level->begin();
180 uint16 new_index = 0;
181 while (index >= current->nextCount()) {
182 index -= current->nextCount();
183 new_index++;
184 current++;
186 index = new_index;
189 return path;
192 void Actor::moveTo(uint16 frame) {
193 Frame cur = Log.room()->getFrame(_frame);
195 Common::List<Frame> path = findPath(cur, frame);
197 Common::List<Frame>::iterator it = path.end();
198 it--;
199 if (it->index() != frame) {
200 Common::List<Frame> p;
201 p.push_back(Log.room()->getFrame(frame));
202 path = p;
205 std::ostringstream s;
206 it = path.begin();
207 it++;
208 while (it != path.end()) {
209 _framequeue.push(*it);
210 s << " " << int(it->index());
211 it++;
214 debugC(3, kDebugLevelActor, "found path: %s", s.str().c_str());
215 if (!_base)
216 nextFrame();
219 void Actor::setRoom(uint16 r, uint16 frame, uint16 next_frame) {
220 _room = r;
221 unless (next_frame)
222 next_frame = frame;
223 _nextFrame = next_frame;
224 setFrame(frame);
226 setAnimation(CodePointer(_puppeteer.mainCodeOffset(), Log.mainInterpreter()));
229 bool Actor::nextFrame() {
230 if (_framequeue.empty())
231 return false;
233 Frame next = _framequeue.front();
234 Frame current = Log.room()->getFrame(_frame);
236 Direction direction = next - current;
238 debugC(4, kDebugLevelActor, "switching frames %d -> %d", current.index(), next.index());
239 if (turnTo(direction))
240 return true;
242 // setFrame(_framequeue.front().index());
243 setAnimation(_puppeteer.moveAnimator(direction));
244 _frame = next.index();
245 _framequeue.pop();
246 return true;
249 bool Actor::turnTo(Direction dir) {
250 if (dir == _direction)
251 return false;
253 Direction d = _direction>>dir;
254 debugC(4, kDebugLevelActor, "turning %d -> %d >> %d", _direction, d, dir);
255 setAnimation(_puppeteer.turnAnimator(d));
256 return true;
259 void Actor::animate() {
260 unless (_puppeteer.valid())
261 return;
263 unless (_attentionNeeded || _confused/* || _timedOut*/)
264 return;
266 debugC(4, kDebugLevelActor, "attention needed");
268 if (nextFrame()) {
269 return;
272 if (_nextAnimator) {
273 setAnimation(_nextAnimator);
274 _nextAnimator = 0;
275 return;
278 if (_nextDirection) {
279 if (turnTo(_nextDirection))
280 return;
281 _direction = _nextDirection;
282 _nextDirection = kDirNone;
283 /* ax = _nextPuppeteer;
284 _nextPuppeteer = 0;
285 if (ax)
286 goto set_anim;*/
287 setAnimation(_puppeteer.offset());
288 /* } else if (_nextPuppeteer) {
289 ax = _nextPuppeteer;
290 _nextPuppeteer = 0;
291 if (ax)
292 goto set_anim;*/
295 if (_confused) {
296 if (turnTo(kDirDown))
297 return;
298 _direction = kDirDown;
300 /* } else {
301 if (turnTo(kDirUp))
302 return;
303 _direction = kDirUp;
304 setAnimation(_puppeteer.offset());
308 Animation::Status Actor::tick() {
309 _speech.tick();
310 animate();
311 callBacks();
313 if (_room == Log.currentRoom()) {
314 Animation::Status s;
315 if (_debug) gDebugLevel += 3;
316 s = Animation::tick();
317 if (_debug) gDebugLevel -= 3;
318 return s;
319 } else
320 return kOk;
323 void Actor::toggleDebug() {
324 _debug = !_debug;
327 void Actor::readHeader(const byte *code) {
328 // uint16 segment = READ_LE_UINT16(code + kOffsetCode);
329 /* if (segment == 0)
330 _base = Log.mainInterpreter()->rawCode(0);
331 else if (segment == 1)
332 _base = Log.blockInterpreter()->rawCode(0);
333 else error("segment %x", segment);*/
334 _interval = code[kOffsetInterval];
335 _ticksLeft = READ_LE_UINT16(code + kOffsetTicksLeft);
336 _zIndex = 0;
337 _position = Common::Point(READ_LE_UINT16(code + kOffsetLeft), READ_LE_UINT16(code + kOffsetTop));
338 uint16 baseOff = READ_LE_UINT16(code + kOffsetCode);
339 _offset = READ_LE_UINT16(code + kOffsetOffset);
340 if (_offset || baseOff) {
341 _base += baseOff;
342 _baseOffset = baseOff;
343 } else {
344 _base = 0;
345 _baseOffset = _offset = 0;
347 uint16 sprite = READ_LE_UINT16(code + kOffsetMainSprite);
348 _room = READ_LE_UINT16(code + kOffsetRoom);
350 debugC(3, kDebugLevelFiles, "loading %s: interv %d ticks %d z%d pos%d:%d code %d offset %d sprite %d room %d", _debugInfo, _interval, _ticksLeft, _zIndex, _position.x, _position.y, baseOff, _offset, sprite, _room);
352 if (sprite != 0xffff)
353 setMainSprite(sprite);
356 void Actor::callBacks() {
357 unless (isFine()) {
358 Common::Queue<CodePointer> cb = _callBacks;
359 _callBacks.clear();
360 while (!cb.empty())
361 cb.pop().run();
364 foreach (RoomCallback, _roomCallbacks) {
365 if (_room == Log.currentRoom() || !it->timeout) {
366 it->callback.run();
367 Common::List<RoomCallback>::iterator done = it;
368 it++;
369 _roomCallbacks.erase(done);
370 } else
371 it->timeout--;
375 void Puppeteer::parse(const byte *data) {
376 _actorId = READ_LE_UINT16(data + kActorId);
377 _offset = READ_LE_UINT16(data + kMainCode);
379 assert (kTurnAnimators == kMoveAnimators + 16);
380 const byte *d = data + kMoveAnimators;
381 for (int i = 0; i < 16; i++) {
382 _animators[i] = READ_LE_UINT16(d);
383 d += 2;
387 static const Direction mixeddirs[] = {kDirUp, kDirRight, kDirDown, kDirLeft,
388 kDirUpRight, kDirDownRight, kDirDownLeft, kDirUpLeft};
390 CodePointer Puppeteer::moveAnimator(Direction d) {
391 uint16 off = mainCodeOffset();
392 for (int i = 0; i < 8; i++) {
393 if (mixeddirs[i] == d)
394 off = _animators[i];
397 return CodePointer(off, Log.mainInterpreter());
400 CodePointer Puppeteer::turnAnimator(Direction d) {
401 uint16 off = mainCodeOffset();
402 for (int i = 0; i < 8; i++) {
403 if (mixeddirs[i] == d)
404 off = _animators[i + 8];
407 return CodePointer(off, Log.mainInterpreter());
410 Actor::Speech::Speech(Actor *parent, const Common::String &text) : _text(text), _ticksLeft(20), _actor(parent) {
411 const Common::Point &position = parent->getSpeechPosition();
412 debugC(1, kDebugLevelActor, "adding speech \"%s\" for %s at %d:%d", text.c_str(), parent->_debugInfo, position.x, position.y);
413 _image = new Innocent::Sprite;
414 _rect = Graf.paintSpeechInBubble(position, 235, reinterpret_cast<const byte *>(text.c_str()), _image);
417 void Actor::Speech::tick() {
418 unless (_ticksLeft--) {
419 _text.clear();
420 while (!_cb.empty())
421 Log.runLater(_cb.pop());
425 Common::Point Actor::getSpeechPosition() const {
426 Common::Point speechPosition(_position);
427 if (_mainSprite.get()) {
428 speechPosition.y -= _mainSprite.get()->h - _mainSprite.get()->_hotPoint.y;
429 speechPosition.x -= _mainSprite.get()->_hotPoint.x;
431 return speechPosition;
434 void Actor::paint(Graphics *g) {
435 Animation::paint(g);
436 _speech.paint(g);
439 void Actor::Speech::paint(Graphics *g) {
440 if (_text.empty())
441 return;
443 g->paint(_image, Common::Point(_rect.left, _rect.top), Graphics::kPaintSemiTransparent | Graphics::kPaintPositionIsTop);
446 Direction Actor::Frame::operator-(const Actor::Frame &other) const {
447 for (int i = 0; i < 8; i++)
448 if (other._nexts[i] == _index)
449 return mixeddirs[i];
451 return kDirNone;
454 Direction operator>>(Direction _a, Direction _b) {
455 assert(sizeof(Direction) == sizeof(int32));
456 int32 a(_a), b(_b);
458 b -= a;
460 if (b < -3)
461 b += 8;
462 if (b > 4)
463 b -= 8;
465 if (b > 0)
466 a++;
467 if (b < 0)
468 a--;
470 if (a < 1)
471 a += 8;
472 if (a > 8)
473 a -= 8;
474 return *reinterpret_cast<Direction *>(&a);
477 template <int opcode>
478 Animation::Status Actor::opcodeHandler(){
479 return Animation::opcodeHandler<opcode>();
482 template<int N>
483 void Actor::init_opcodes() {
484 _handlers[N] = &Innocent::Actor::opcodeHandler<N>;
485 init_opcodes<N-1>();
488 template<>
489 void Actor::init_opcodes<-1>() {}
491 Animation::Status Actor::op(byte opcode) {
492 return (this->*_handlers[opcode])();
495 #define OPCODE(n) template<> Animation::Status Actor::opcodeHandler<n>()
497 OPCODE(0x01) {
498 debugC(1, kDebugLevelActor, "actor opcode 0x01: I don't know what to do");
500 _offset = 0;
501 _base = 0;
502 _confused = true;
503 return kFrameDone;
506 OPCODE(0x14) {
507 uint16 off = shift();
509 debugC(1, kDebugLevelAnimation, "actor opcode 0x14: jump to 0x%04x if I'm speaking STUB", off);
511 // also, some check for non-protagonists
513 return kOk;
516 OPCODE(0x15) {
517 debugC(1, kDebugLevelAnimation, "actor opcode 0x15: look at cursor direction if its mode is 'See' STUB");
519 return kOk;
522 OPCODE(0x16) {
523 byte val = embeddedByte();
524 uint16 off = shift();
526 debugC(1, kDebugLevelAnimation, "actor opcode 0x16: if look direction is %d then jump to 0x%04x STUB", val, off);
528 return kOk;
531 OPCODE(0x17) {
532 byte val = embeddedByte();
533 uint16 off = shift();
535 debugC(3, kDebugLevelAnimation, "actor opcode 0x17: if facing (currently %d) is %d then change code to 0x%04x", _direction, val, off);
537 if (val == _direction) {
538 setAnimation(off);
541 return kOk;
544 OPCODE(0x18) {
545 uint16 val = shift();
547 debugC(3, kDebugLevelAnimation, "actor opcode 0x18: set next animator to %x", val);
548 _nextAnimator = val;
550 return kOk;
553 OPCODE(0x23) {
554 byte dir = embeddedByte();
556 debugC(3, kDebugLevelAnimation, "actor opcode 0x23: face %d", dir);
558 switch (dir) {
559 case 1:
560 _direction = kDirUp;
561 break;
562 case 2:
563 _direction = kDirUpRight;
564 break;
565 case 3:
566 _direction = kDirRight;
567 break;
568 case 4:
569 _direction = kDirDownRight;
570 break;
571 case 5:
572 _direction = kDirDown;
573 break;
574 case 6:
575 _direction = kDirDownLeft;
576 break;
577 case 7:
578 _direction = kDirLeft;
579 break;
580 case 8:
581 _direction = kDirUpLeft;
582 break;
583 default:
584 assert(false);
587 return kOk;
590 OPCODE(0x24) {
591 byte dir = embeddedByte();
593 debugC(3, kDebugLevelAnimation, "actor opcode 0x24: set attention needed flag to %d", dir);
594 _attentionNeeded = true;
596 return kOk;
599 } // end of namespace