/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "agds/character.h" #include "agds/agds.h" #include "agds/animation.h" #include "agds/object.h" #include "agds/region.h" #include "agds/resourceManager.h" #include "common/array.h" #include "common/debug.h" #include "common/stream.h" #include "common/system.h" #include "common/textconsole.h" #include "common/util.h" #include "graphics/managed_surface.h" namespace AGDS { Character::Character(AGDSEngine *engine, const Common::String &name) : _engine(engine), _object(), _animation(nullptr), _jokes(false), _name(name), _enabled(true), _visible(false), _stopped(false), _shown(false), _phase(-1), _frames(0), _direction(-1), _movementDirections(0) { } Character::~Character() { } void Character::load(Common::SeekableReadStream &stream) { debug("loading character..."); stream.readUint32LE(); // unk uint16 magic = stream.readUint16LE(); switch (magic) { case 0xdead: _movementDirections = 16; break; case 0x8888: _movementDirections = 8; break; default: error("invalid magic %04x", magic); } _animations.clear(); while (stream.pos() < stream.size()) { uint size = stream.readUint32LE(); uint index = stream.readUint16LE(); debug("header size %u, index: %u", size, index); uint16 frames = stream.readUint16LE(); uint16 format = stream.readUint16LE(); Common::String filename = readString(stream); AnimationDescription animation; animation.filename = filename; debug("%s:%u: animation %s, frames: %d, format: %d", _name.c_str(), _animations.size(), animation.filename.c_str(), frames, format); while (frames--) { int x = stream.readSint16LE(); int y = stream.readSint16LE(); uint w = stream.readUint32LE(); uint h = stream.readUint32LE(); AnimationDescription::Frame frame = {x, y, w, h}; animation.frames.push_back(frame); debug("frame %d, %d, %dx%d", x, y, w, h); uint unk1 = stream.readUint32LE(); uint unk2 = stream.readUint32LE(); uint unk3 = stream.readUint32LE(); uint unk4 = stream.readUint32LE(); // GRP file offset? uint unk5 = stream.readUint32LE(); uint unk6 = stream.readByte(); uint unk7 = stream.readUint32LE(); uint unk8 = stream.readUint32LE(); stream.readUint32LE(); // CDCDCDCD uint unk9 = stream.readUint32LE(); uint unk10 = stream.readUint32LE(); stream.readUint32LE(); // CDCDCDCD uint unk11 = stream.readByte(); stream.readUint32LE(); // CDCDCDCD debug("unknown: %u %u %u 0x%08x - %u %u %u %u - %u %u %u", unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8, unk9, unk10, unk11); } _animations[index] = animation; } } void Character::associate(const Common::String &name) { _object = _engine->loadObject(name); _engine->runObject(_object); } void Character::visible(bool visible) { if (visible) { _shown = true; } _visible = visible; } void Character::loadState(Common::ReadStream &stream) { int x = stream.readUint16LE(); int y = stream.readUint16LE(); int dir = stream.readSint16LE(); debug("character at %d, %d, dir: %d", x, y, dir); position(Common::Point(x, y)); direction(dir); _visible = stream.readUint16LE(); _enabled = stream.readUint16LE(); } void Character::saveState(Common::WriteStream &stream) const { stream.writeUint16LE(_pos.x); stream.writeUint16LE(_pos.y); stream.writeUint16LE(_direction); stream.writeUint16LE(_visible); stream.writeUint16LE(_enabled); } bool Character::direction(int dir) { debug("setDirection %d", dir); _direction = dir; if (dir < 0) return false; _animationPos = Common::Point(); return animate(dir, 100, false); } void Character::notifyProcess(const Common::String &name) { debug("%s:notifyProcess %s", _name.c_str(), name.c_str()); if (!_processName.empty()) _engine->reactivate(name, "Character::notifyProcess", false); _processName = name; } bool Character::moveTo(const Common::String &processName, Common::Point dst, int dir) { if (!_visible) return false; debug("character move %d,%d %d", dst.x, dst.y, dir); notifyProcess(processName); _pos = dst; _shown = true; bool r = direction(dir); auto *screen = _engine->getCurrentScreen(); if (screen) { auto objects = screen->find(dst); for (auto &object : objects) { auto region = object->getTrapRegion(); if (region && region->pointIn(dst)) { debug("starting trap process"); _engine->runProcess(object, object->getTrapHandler()); } } } return r; } void Character::pointTo(const Common::String &processName, Common::Point dst) { debug("character point to stub %d,%d, process: %s", dst.x, dst.y, processName.c_str()); notifyProcess(processName); if (!_processName.empty() && !_engine->activeCurtain()) { _engine->reactivate(_processName, "Character::pointTo"); _processName.clear(); } _shown = true; } bool Character::animate(int direction, int speed, bool jokes) { if (direction == -1 || !_enabled) return false; if (_stopped) { debug("character stopped, skipping"); _stopped = false; return false; } auto character = jokes ? _engine->jokes() : this; auto description = character->animationDescription(direction); if (!description) { warning("no %s animation %d", jokes ? "jokes" : "character", direction); return false; } auto animation = _engine->loadAnimation(description->filename); if (!animation) { warning("no %s animation file %s", jokes ? "jokes" : "character", description->filename.c_str()); return false; } _description = description; _shown = true; _animation = animation; _animation->speed(speed); _animation->rewind(); _phase = 0; _frames = _animation->frames(); _jokes = jokes; if (jokes) _jokesDirection = direction; else _direction = direction; debug("character animation frames: %d, enabled: %d, visible: %d", _frames, _enabled, _visible); return true; } bool Character::animate(Common::Point pos, int direction, int speed) { debug("animate character: %d,%d %d %d", pos.x, pos.y, direction, speed); auto ok = animate(direction, speed, true); if (!ok) return false; _animationPos = pos; return true; } void Character::stop() { _stopped = true; } void Character::leave(const Common::String &processName) { debug("character %s: leave, process: %s", _object->getName().c_str(), processName.c_str()); notifyProcess(processName); } void Character::tick(bool reactivate) { if (!active()) return; if (_animation) { auto screen = _engine->getCurrentScreen(); auto scale = screen ? screen->getZScale(_pos.y) : 1; _animation->scale(scale); if (!_stopped && _phase >= 0 && _phase < _frames) { _animation->tick(); _phase = _animation->phase(); if (_phase >= _frames) { bool wasJokes = _jokes; _jokes = false; _phase = -1; _frames = 0; if (wasJokes) direction(_direction); } return; } } if (reactivate && !_processName.empty() && !_engine->activeCurtain()) { _engine->reactivate(_processName, "Character::tick"); _processName.clear(); } } bool Character::pointIn(Common::Point pos) const { if (!_animation) return false; Common::Rect rect(_animation->width(), _animation->height()); rect.moveTo(animationPosition()); return rect.contains(pos); } Common::Point Character::animationPosition() const { Common::Point pos = _pos + _animationPos; if (_animation) { pos.y -= _animation->visibleHeight(); pos.x -= _animation->visibleCenter(); if (_description) { auto &frames = _description->frames; if (_phase >= 0 && _phase < static_cast(frames.size())) { auto &frame = frames[_phase]; pos.x += frame.x * _animation->scale(); pos.y += frame.y * _animation->scale(); } } } return pos; } void Character::paint(Graphics::Surface &backbuffer, Common::Point pos) const { if (!_enabled || !visible() || !_animation) return; pos += animationPosition(); int fogAlpha = 0; if (_fog) { auto z = this->z(); if (z >= _fogMinZ && z < _fogMaxZ) { fogAlpha = 255 * (z - _fogMinZ) / (_fogMaxZ - _fogMinZ); } else if (z >= _fogMaxZ) { fogAlpha = 255; } } _animation->paint(backbuffer, pos, _fog.get(), fogAlpha); } int Character::z() const { int y = _pos.y + _animationPos.y; // fixme: add temp var : _movePos? // debug("char z = %d", y); return g_system->getHeight() - y; } void Character::reset() { _fog.reset(); _shown = false; _animation.reset(); _phase = -1; _frames = 0; } void Character::setFog(Graphics::ManagedSurface *surface, int minZ, int maxZ) { _fog.reset(surface); _fogMinZ = minZ; _fogMaxZ = maxZ; } int Character::getDirectionForMovement(Common::Point delta) { auto angle = atan2(delta.y, delta.x); if (angle < 0) angle += M_PI * 2; if (_movementDirections == 16) { if (angle < 6.1850053125 && angle > 0.0981746875) { if (angle > 0.5235983333333333) { if (angle > 0.9490219791666666) { if (angle > 1.374445625) { if (angle > 1.767144375) { if (angle > 2.192568020833333) { if (angle > 2.617991666666666) { if (angle > 3.0434153125) { if (angle > 3.2397646875) { if (angle > 3.665188333333333) { if (angle > 4.090611979166667) { if (angle > 4.516035625) { if (angle > 4.908734375) { if (angle > 5.334158020833333) { if (angle > 5.759581666666667) return 3; else return 2; } else { return 1; } } else { return 0; } } else { return 15; } } else { return 14; } } else { return 13; } } else { return 12; } } else { return 11; } } else { return 10; } } else { return 9; } } else { return 8; } } else { return 7; } } else { return 6; } } else { return 5; } } else { return 4; } } else if (angle < 5.89048125 && angle > 0.39269875) { if (angle > 1.17809625) { if (angle > 1.96349375) { if (angle > 2.74889125) { if (angle > 3.53428875) { if (angle > 4.31968625) { if (angle > 5.105083749999999) return 2; else return 0; } else { return 14; } } else { return 12; } } else { return 10; } } else { return 8; } } else { return 6; } } else { return 4; } } } // namespace AGDS