/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ */ #include "common/config-manager.h" #include "common/debug.h" #include "common/algorithm.h" #include "common/ptr.h" #include "common/textconsole.h" #include "graphics/palette.h" #include "teenagent/scene.h" #include "teenagent/resources.h" #include "teenagent/surface.h" #include "teenagent/objects.h" #include "teenagent/teenagent.h" #include "teenagent/dialog.h" #include "teenagent/music.h" namespace TeenAgent { Scene::Scene(TeenAgentEngine *engine, OSystem *system) : intro(false), _id(0), ons(0), orientation(kActorRight), actor_talking(false), message_timer(0), message_first_frame(0), message_last_frame(0), message_animation(NULL), current_event(SceneEvent::kNone), hide_actor(false), callback(0), callback_timer(0), _idle_timer(0) { _engine = engine; _system = system; _fade_timer = 0; on_enabled = true; memset(palette, 0, sizeof(palette)); background.pixels = 0; FilePack varia; varia.open("varia.res"); Common::ScopedPtr s(varia.getStream(1)); if (!s) error("invalid resource data"); teenagent.load(s, Animation::kTypeVaria); if (teenagent.empty()) error("invalid mark animation"); s.reset(varia.getStream(2)); if (!s) error("invalid resource data"); teenagent_idle.load(s, Animation::kTypeVaria); if (teenagent_idle.empty()) error("invalid mark animation"); varia.close(); loadObjectData(); } Scene::~Scene() { if (background.pixels) background.free(); delete[] ons; ons = 0; } void Scene::warp(const Common::Point &_point, byte o) { Common::Point point(_point); position = point; path.clear(); if (o) orientation = o; } bool Scene::findPath(Scene::Path &p, const Common::Point &src, const Common::Point &dst) const { const Common::Array &scene_walkboxes = walkboxes[_id - 1]; if (dst.x < 0 || dst.x > 319 || dst.y < 0 || dst.y > 199) return false; debug(1, "findPath %d,%d -> %d,%d", src.x, src.y, dst.x, dst.y); p.clear(); p.push_back(src); p.push_back(dst); Common::List boxes; for(uint i = 0; i < scene_walkboxes.size(); ++i) { const Walkbox & w = scene_walkboxes[i]; if (!w.rect.in(src) && !w.rect.in(dst)) boxes.push_back(i); } for(Path::iterator i = p.begin(); i != p.end() && !boxes.empty(); ) { Path::iterator next = i; ++next; if (next == p.end()) break; const Common::Point &p1 = *i, &p2 = *next; debug(1, "%d,%d -> %d,%d", p1.x, p1.y, p2.x, p2.y); Common::List::iterator wi; for(wi = boxes.begin(); wi != boxes.end(); ++wi) { const Walkbox & w = scene_walkboxes[*wi]; int mask = w.rect.intersects_line(p1, p2); if (mask == 0) { continue; } w.dump(1); debug(1, "%u: intersection mask 0x%04x, searching hints", *wi, mask); int dx = p2.x - p1.x, dy = p2.y - p1.y; if (dx >= 0) { if ((mask & 8) != 0 && w.side_hint[3] != 0) { debug(1, "hint left: %u", w.side_hint[3]); Common::Point w1, w2; w.rect.side(w1, w2, w.side_hint[3], p1); debug(1, "hint: %d,%d-%d,%d", w1.x, w1.y, w2.x, w2.y); p.insert(next, w1); if (mask & 2) p.insert(next, w2); boxes.erase(wi); break; } } else { if ((mask & 2) != 0 && w.side_hint[1] != 0) { debug(1, "hint right: %u", w.side_hint[1]); Common::Point w1, w2; w.rect.side(w1, w2, w.side_hint[1], p1); debug(1, "hint: %d,%d-%d,%d", w1.x, w1.y, w2.x, w2.y); p.insert(next, w1); if (mask & 8) p.insert(next, w2); boxes.erase(wi); break; } } if (dy >= 0) { if ((mask & 1) != 0 && w.side_hint[0] != 0) { debug(1, "hint top: %u", w.side_hint[0]); Common::Point w1, w2; w.rect.side(w1, w2, w.side_hint[0], p1); debug(1, "hint: %d,%d-%d,%d", w1.x, w1.y, w2.x, w2.y); p.insert(next, w1); if (mask & 4) p.insert(next, w2); boxes.erase(wi); break; } } else { if ((mask & 4) != 0 && w.side_hint[2] != 0) { debug(1, "hint bottom: %u", w.side_hint[2]); Common::Point w1, w2; w.rect.side(w1, w2, w.side_hint[2], p1); debug(1, "hint: %d,%d-%d,%d", w1.x, w1.y, w2.x, w2.y); p.insert(next, w1); if (mask & 1) p.insert(next, w2); boxes.erase(wi); break; } } } if (wi == boxes.end()) ++i; } p.pop_front(); return true; } void Scene::moveTo(const Common::Point &_point, byte orient, bool validate) { Common::Point point(_point); debug(0, "moveTo(%d, %d, %u)", point.x, point.y, orient); const Common::Array &scene_walkboxes = walkboxes[_id - 1]; for (byte i = 0; i < scene_walkboxes.size(); ++i) { const Walkbox &w = scene_walkboxes[i]; if (w.rect.in(point)) { debug(0, "bumped into walkbox %u", i); w.dump(); byte o = w.orientation; switch (o) { case 1: point.y = w.rect.top - 1; break; case 2: point.x = w.rect.right + 1; break; case 3: point.y = w.rect.bottom + 1; break; case 4: point.x = w.rect.left - 1; break; default: if (validate) return; } } } if (point == position) { if (orient != 0) orientation = orient; nextEvent(); return; } path.clear(); if (scene_walkboxes.empty()) { path.push_back(point); return; } if (!findPath(path, position, point)) { _engine->cancel(); return; } orientation = orient; } void Scene::loadObjectData() { Resources *res = Resources::instance(); //loading objects & walkboxes objects.resize(42); walkboxes.resize(42); fades.resize(42); for (byte i = 0; i < 42; ++i) { Common::Array &scene_objects = objects[i]; scene_objects.clear(); uint16 scene_table = res->dseg.get_word(0x7254 + i * 2); uint16 object_addr; while ((object_addr = res->dseg.get_word(scene_table)) != 0) { Object obj; obj.load(res->dseg.ptr(object_addr)); //obj.dump(); scene_objects.push_back(obj); scene_table += 2; } debug(0, "scene[%u] has %u object(s)", i + 1, scene_objects.size()); byte *walkboxes_base = res->dseg.ptr(READ_LE_UINT16(res->dseg.ptr(0x6746 + i * 2))); byte walkboxes_n = *walkboxes_base++; debug(0, "scene[%u] has %u walkboxes", i + 1, walkboxes_n); Common::Array &scene_walkboxes = walkboxes[i]; for (byte j = 0; j < walkboxes_n; ++j) { Walkbox w; w.load(walkboxes_base + 14 * j); if ((w.side_hint[0] | w.side_hint[1] | w.side_hint[2] | w.side_hint[3]) == 0) { w.side_hint[0] = 2; w.side_hint[1] = 3; w.side_hint[2] = 4; w.side_hint[3] = 1; } //walkbox[i]->dump(); scene_walkboxes.push_back(w); } byte *fade_table = res->dseg.ptr(res->dseg.get_word(0x663e + i * 2)); Common::Array &scene_fades = fades[i]; while(READ_LE_UINT16(fade_table) != 0xffff) { FadeType fade; fade.load(fade_table); fade_table += 9; scene_fades.push_back(fade); } debug(0, "scene[%u] has %u fadeboxes", i + 1, scene_fades.size()); } } Object *Scene::findObject(const Common::Point &point) { if (_id == 0) return NULL; Common::Array &scene_objects = objects[_id - 1]; for (uint i = 0; i < scene_objects.size(); ++i) { Object &obj = scene_objects[i]; if (obj.enabled != 0 && obj.rect.in(point)) return &obj; } return NULL; } byte *Scene::getOns(int id) { Resources *res = Resources::instance(); return res->dseg.ptr(res->dseg.get_word(0xb4f5 + (id - 1) * 2)); } byte *Scene::getLans(int id) { Resources *res = Resources::instance(); return res->dseg.ptr(0xd89e + (id - 1) * 4); } void Scene::loadOns() { debug(0, "loading ons animation"); Resources *res = Resources::instance(); uint16 addr = res->dseg.get_word(0xb4f5 + (_id - 1) * 2); //debug(0, "ons index: %04x", addr); ons_count = 0; byte b; byte on_id[16]; while ((b = res->dseg.get_byte(addr)) != 0xff) { debug(0, "on: %04x = %02x", addr, b); ++addr; if (b == 0) continue; on_id[ons_count++] = b; } delete[] ons; ons = NULL; if (ons_count > 0) { ons = new Surface[ons_count]; for (uint32 i = 0; i < ons_count; ++i) { Common::ScopedPtr s(res->ons.getStream(on_id[i])); if (s) { ons[i].load(s, Surface::kTypeOns); } } } } void Scene::loadLans() { debug(0, "loading lans animation"); Resources *res = Resources::instance(); //load lan000 for (byte i = 0; i < 4; ++i) { animation[i].free(); uint16 bx = 0xd89e + (_id - 1) * 4 + i; byte bxv = res->dseg.get_byte(bx); uint16 res_id = 4 * (_id - 1) + i + 1; debug(0, "lan[%u]@%04x = %02x, resource id: %u", i, bx, bxv, res_id); if (bxv == 0) continue; Common::ScopedPtr s(res->loadLan000(res_id)); if (s) { animation[i].load(s, Animation::kTypeLan); if (bxv != 0 && bxv != 0xff) animation[i].id = bxv; } } } void Scene::init(int id, const Common::Point &pos) { debug(0, "init(%d)", id); _id = id; on_enabled = true; //reset on-rendering flag on loading. sounds.clear(); for (byte i = 0; i < 4; ++i) custom_animation[i].free(); if (background.pixels == NULL) background.create(320, 200, 1); warp(pos); Resources *res = Resources::instance(); res->loadOff(background, palette, id); if (id == 24) { //dark scene if (res->dseg.get_byte(0xDBA4) != 1) { //dim down palette uint i; for (i = 0; i < 624; ++i) { palette[i] = palette[i] > 0x20 ? palette[i] - 0x20 : 0; } for (i = 726; i < 768; ++i) { palette[i] = palette[i] > 0x20 ? palette[i] - 0x20 : 0; } } } Common::ScopedPtr stream(res->on.getStream(id)); int sub_hack = 0; if (id == 7) { //something patched in the captains room switch(res->dseg.get_byte(0xdbe6)) { case 2: break; case 1: sub_hack = 1; break; default: sub_hack = 2; } } on.load(stream, SurfaceList::kTypeOn, sub_hack); loadOns(); loadLans(); //check music int now_playing = _engine->music->getId(); if (now_playing != res->dseg.get_byte(0xDB90)) _engine->music->load(res->dseg.get_byte(0xDB90)); _system->copyRectToScreen((const byte *)background.pixels, background.pitch, 0, 0, background.w, background.h); setPalette(0); } void Scene::playAnimation(byte idx, uint id, bool loop, bool paused, bool ignore) { debug(0, "playAnimation(%u, %u, loop:%s, paused:%s, ignore:%s)", idx, id, loop?"true":"false", paused?"true":"false", ignore?"true":"false"); assert(idx < 4); Common::ScopedPtr s(Resources::instance()->loadLan(id + 1)); if (!s) error("playing animation %u failed", id); custom_animation[idx].load(s); custom_animation[idx].loop = loop; custom_animation[idx].paused = paused; custom_animation[idx].ignore = ignore; } void Scene::playActorAnimation(uint id, bool loop, bool ignore) { debug(0, "playActorAnimation(%u, loop:%s, ignore:%s)", id, loop?"true":"false", ignore?"true":"false"); Common::ScopedPtr s(Resources::instance()->loadLan(id + 1)); if (!s) error("playing animation %u failed", id); actor_animation.load(s); actor_animation.loop = loop; actor_animation.ignore = ignore; actor_animation.id = id; } Animation * Scene::getAnimation(byte slot) { assert(slot < 4); return custom_animation + slot; } byte Scene::peekFlagEvent(uint16 addr) const { for(EventList::const_iterator i = events.reverse_begin(); i != events.end(); --i) { const SceneEvent &e = *i; if (e.type == SceneEvent::kSetFlag && e.callback == addr) return e.color; } return Resources::instance()->dseg.get_byte(addr); } void Scene::push(const SceneEvent &event) { //debug(0, "push"); //event.dump(); if (event.type == SceneEvent::kWalk && !events.empty()) { SceneEvent &prev = events.back(); if (prev.type == SceneEvent::kWalk && prev.color == event.color) { debug(0, "fixing double-move [skipping event!]"); if ((event.color & 2) != 0) { //relative move prev.dst.x += event.dst.x; prev.dst.y += event.dst.y; } else { prev.dst = event.dst; } return; } } events.push_back(event); } bool Scene::processEvent(const Common::Event &event) { switch (event.type) { case Common::EVENT_LBUTTONDOWN: case Common::EVENT_RBUTTONDOWN: if (!message.empty() && message_first_frame == 0) { clearMessage(); nextEvent(); return true; } return false; case Common::EVENT_KEYDOWN: switch(event.kbd.keycode) { case Common::KEYCODE_ESCAPE: case Common::KEYCODE_SPACE: { if (intro && event.kbd.keycode == Common::KEYCODE_ESCAPE) { intro = false; clearMessage(); events.clear(); sounds.clear(); current_event.clear(); message_color = 0xd1; for (int i = 0; i < 4; ++i) custom_animation[i].free(); _engine->playMusic(4); _engine->loadScene(10, Common::Point(136, 153)); return true; } if (!message.empty() && message_first_frame == 0) { clearMessage(); nextEvent(); return true; } break; } #if 0 case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (event.kbd.flags & Common::KBD_CTRL) { uint feature = event.kbd.keycode - '1'; if (feature < DebugFeatures::kMax) { debug_features.feature[feature] = !debug_features.feature[feature]; debug(0, "switched feature %u %s", feature, debug_features.feature[feature] ? "on": "off"); } } break; #endif default: break; } default: return false; } } struct ZOrderCmp { inline bool operator()(const Surface *a, const Surface *b) const { return a->y + a->h < b->y + b->h; } }; int Scene::lookupZoom(uint y) const { Resources *res = Resources::instance(); for(byte *zoom_table = res->dseg.ptr(res->dseg.get_word(0x70f4 + (_id - 1) * 2)); zoom_table[0] != 0xff && zoom_table[1] != 0xff; zoom_table += 2 ) { //debug(0, "%d %d->%d", y, zoom_table[0], zoom_table[1]); if (y <= zoom_table[0]) { //debug(0, "%d %d->%d", y, zoom_table[0], zoom_table[1]); return 256u * (100 - zoom_table[1]) / 100; } } return 256; } void Scene::paletteEffect(byte step) { Resources *res = Resources::instance(); byte *src = res->dseg.ptr(0x6609); byte *dst = palette + 3 * 0xf2; for(byte i = 0; i < 0xd; ++i) { for(byte c = 0; c < 3; ++c, ++src) *dst++ = *src > step? *src - step: 0; } } byte Scene::findFade() const { if (_id <= 0) return 0; const Common::Array &scene_fades = fades[_id - 1]; for(uint i = 0; i < scene_fades.size(); ++i) { const FadeType &fade = scene_fades[i]; if (fade.rect.in(position)) { return fade.value; } } return 0; } bool Scene::render(bool tick_game, bool tick_mark, uint32 delta) { Resources *res = Resources::instance(); bool busy; bool restart; uint32 game_delta = tick_game? 1: 0; uint32 mark_delta = tick_mark? 1: 0; do { restart = false; busy = processEventQueue(); if (_fade_timer && game_delta != 0) { if (_fade_timer > 0) { _fade_timer -= game_delta; setPalette(_fade_timer); } else { _fade_timer += game_delta; setPalette(_fade_timer + 4); } } switch(current_event.type) { case SceneEvent::kCredits: { _system->fillScreen(0); ///\todo: optimize me Graphics::Surface *surface = _system->lockScreen(); res->font7.render(surface, current_event.dst.x, current_event.dst.y -= game_delta, current_event.message, current_event.color); _system->unlockScreen(); if (current_event.dst.y < -(int)current_event.timer) current_event.clear(); } return true; default: ; } if (!message.empty() && message_timer != 0) { if (message_timer <= delta) { clearMessage(); nextEvent(); continue; } else message_timer -= delta; } if (current_event.type == SceneEvent::kCreditsMessage) { _system->fillScreen(0); Graphics::Surface *surface = _system->lockScreen(); if (current_event.lan == 8) { res->font8.shadow_color = current_event.orientation; res->font8.render(surface, current_event.dst.x, current_event.dst.y, message, current_event.color); } else { res->font7.render(surface, current_event.dst.x, current_event.dst.y, message, 0xd1); } _system->unlockScreen(); return true; } if (background.pixels && debug_features.feature[DebugFeatures::kShowBack]) { _system->copyRectToScreen((const byte *)background.pixels, background.pitch, 0, 0, background.w, background.h); } else _system->fillScreen(0); Graphics::Surface *surface = _system->lockScreen(); bool got_any_animation = false; if (ons != NULL && debug_features.feature[DebugFeatures::kShowOns]) { for (uint32 i = 0; i < ons_count; ++i) { Surface *s = ons + i; if (s != NULL) s->render(surface); } } Common::List z_order; for (byte i = 0; i < 4; ++i) { Animation *a = custom_animation + i; Surface *s = a->currentFrame(game_delta); if (s != NULL) { if (!a->ignore) busy = true; if (!a->paused && !a->loop) got_any_animation = true; } else { a = animation + i; if (!custom_animation[i].empty()) { debug(0, "custom animation ended, restart animation in the same slot."); custom_animation[i].free(); a->restart(); } s = a->currentFrame(game_delta); } if (current_event.type == SceneEvent::kWaitLanAnimationFrame && current_event.slot == i) { if (s == NULL) { restart |= nextEvent(); continue; } int index = a->currentIndex(); if (index == current_event.animation) { debug(0, "kWaitLanAnimationFrame(%d, %d) complete", current_event.slot, current_event.animation); restart |= nextEvent(); } } if (s == NULL) continue; if (debug_features.feature[DebugFeatures::kShowLan]) z_order.push_back(s); if (a->id == 0) continue; Object *obj = getObject(a->id); if (obj != NULL) { obj->rect.left = s->x; obj->rect.top = s->y; obj->rect.right = s->w + s->x; obj->rect.bottom = s->h + s->y; obj->rect.save(); //obj->dump(); } } Common::sort(z_order.begin(), z_order.end(), ZOrderCmp()); Common::List::iterator z_order_it; Surface *mark = actor_animation.currentFrame(game_delta); int horizon = position.y; for(z_order_it = z_order.begin(); z_order_it != z_order.end(); ++z_order_it) { Surface *s = *z_order_it; if (s->y + s->h > horizon) break; s->render(surface); } if (mark != NULL) { actor_animation_position = mark->render(surface); if (!actor_animation.ignore) busy = true; else busy = false; got_any_animation = true; } else if (!hide_actor) { actor_animation.free(); uint zoom = lookupZoom(position.y); { byte fade = findFade(); static byte old_fade = 0; if (fade != old_fade) { old_fade = fade; paletteEffect(fade); if (_fade_timer == 0) setPalette(4); } } if (!path.empty()) { const Common::Point &destination = path.front(); Common::Point dp(destination.x - position.x, destination.y - position.y); int o; if (ABS(dp.x) > ABS(dp.y)) o = dp.x > 0 ? kActorRight : kActorLeft; else { o = dp.y > 0 ? kActorDown : kActorUp; } if (tick_mark) { int speed_x = zoom / 32; //8 * zoom / 256 int speed_y = (o == kActorDown || o == kActorUp? 2: 1) * zoom / 256; if (speed_x == 0) speed_x = 1; if (speed_y == 0) speed_y = 1; position.y += (ABS(dp.y) < speed_y? dp.y: SIGN(dp.y) * speed_y); position.x += (o == kActorDown || o == kActorUp)? (ABS(dp.x) < speed_y? dp.x: SIGN(dp.x) * speed_y): (ABS(dp.x) < speed_x? dp.x: SIGN(dp.x) * speed_x); } _idle_timer = 0; teenagent_idle.resetIndex(); actor_animation_position = teenagent.render(surface, position, o, mark_delta, false, zoom); if (tick_mark && position == destination) { path.pop_front(); if (path.empty()) { if (orientation == 0) orientation = o; //save last orientation nextEvent(); got_any_animation = true; restart = true; } busy = true; } else busy = true; } else { teenagent.resetIndex(); _idle_timer += mark_delta; if (_idle_timer < 50) actor_animation_position = teenagent.render(surface, position, orientation, 0, actor_talking, zoom); else actor_animation_position = teenagent_idle.renderIdle(surface, position, orientation, mark_delta, zoom); } } if (restart) { _system->unlockScreen(); continue; } //removed mark == null. In final scene of chapter 2 mark rendered above table. //if it'd cause any bugs, add hack here. (_id != 23 && mark == NULL) if (on_enabled && debug_features.feature[DebugFeatures::kShowOn]) { on.render(surface, actor_animation_position); } for(; z_order_it != z_order.end(); ++z_order_it) { Surface *s = *z_order_it; s->render(surface); } if (!message.empty()) { bool visible = true; if (message_first_frame != 0 && message_animation != NULL) { int index = message_animation->currentIndex() + 1; //debug(0, "message: %s first: %u index: %u", message.c_str(), message_first_frame, index); if (index < message_first_frame) visible = false; if (index > message_last_frame) { clearMessage(); visible = false; } } if (visible) { res->font7.render(surface, message_pos.x, message_pos.y, message, message_color); busy = true; } } if (!busy && !restart && tick_game && callback_timer) { if (--callback_timer == 0) { if (_engine->inventory->active()) _engine->inventory->activate(false); _engine->processCallback(callback); } //debug(0, "callback timer = %u", callback_timer); } //if (!current_event.empty()) // current_event.dump(); if (!debug_features.feature[DebugFeatures::kHidePath]) { const Common::Array & scene_walkboxes = walkboxes[_id - 1]; for (uint i = 0; i < scene_walkboxes.size(); ++i) { scene_walkboxes[i].rect.render(surface, 0xd0 + i); } Common::Point last_p = position; for(Path::const_iterator p = path.begin(); p != path.end(); ++p) { const Common::Point dp(p->x - last_p.x, p->y - last_p.y); if (dp.x != 0) { surface->hLine(last_p.x, last_p.y, p->x, 0xfe); } else if (dp.y != 0) { surface->vLine(last_p.x, last_p.y, p->y, 0xfe); } last_p = *p; } } _system->unlockScreen(); if (current_event.type == SceneEvent::kWait) { if (current_event.timer > delta) { busy = true; current_event.timer -= delta; } if (current_event.timer <= delta) restart |= nextEvent(); } if (!restart && current_event.type == SceneEvent::kWaitForAnimation && !got_any_animation) { debug(0, "no animations, nextevent"); nextEvent(); restart = true; } if (busy) { _idle_timer = 0; teenagent_idle.resetIndex(); } } while (restart); for (Sounds::iterator i = sounds.begin(); i != sounds.end();) { Sound &sound = *i; if (sound.delay == 0) { debug(1, "sound %u started", sound.id); _engine->playSoundNow(sound.id); i = sounds.erase(i); } else { sound.delay -= game_delta; ++i; } } return busy; } bool Scene::processEventQueue() { while (!events.empty() && current_event.empty()) { //debug(0, "processing next event"); current_event = events.front(); events.pop_front(); switch (current_event.type) { case SceneEvent::kSetOn: { byte on_id = current_event.ons; if (on_id != 0) { --on_id; byte *ptr = getOns(current_event.scene == 0 ? _id : current_event.scene); debug(0, "on[%u] = %02x", on_id, current_event.color); ptr[on_id] = current_event.color; } else { on_enabled = current_event.color != 0; debug(0, "%s on rendering", on_enabled? "enabling": "disabling"); } loadOns(); current_event.clear(); } break; case SceneEvent::kSetLan: { if (current_event.lan != 0) { debug(0, "lan[%u] = %02x", current_event.lan - 1, current_event.color); byte *ptr = getLans(current_event.scene == 0 ? _id : current_event.scene); ptr[current_event.lan - 1] = current_event.color; } loadLans(); current_event.clear(); } break; case SceneEvent::kLoadScene: { if (current_event.scene != 0) { init(current_event.scene, current_event.dst); if (current_event.orientation != 0) orientation = current_event.orientation; } else { //special case, empty scene background.free(); on.free(); delete[] ons; ons = NULL; for (byte i = 0; i < 4; ++i) { animation[i].free(); custom_animation[i].free(); } } current_event.clear(); } break; case SceneEvent::kWalk: { Common::Point dst = current_event.dst; if ((current_event.color & 2) != 0) { //relative move dst.x += position.x; dst.y += position.y; } if ((current_event.color & 1) != 0) { warp(dst, current_event.orientation); current_event.clear(); } else moveTo(dst, current_event.orientation); } break; case SceneEvent::kCreditsMessage: case SceneEvent::kMessage: { message = current_event.message; message_animation = NULL; if (current_event.first_frame) { message_timer = 0; message_first_frame = current_event.first_frame; message_last_frame = current_event.last_frame; if (current_event.slot > 0) { message_animation = custom_animation + (current_event.slot - 1); //else if (!animation[current_event.slot].empty()) // message_animation = animation + current_event.slot; } else message_animation = &actor_animation; debug(0, "async message %d-%d (slot %u)", message_first_frame, message_last_frame, current_event.slot); } else { message_timer = current_event.timer? current_event.timer * 110: messageDuration(message); message_first_frame = message_last_frame = 0; } Common::Point p; if (current_event.dst.x == 0 && current_event.dst.y == 0) { p = Common::Point((actor_animation_position.left + actor_animation_position.right) / 2, actor_animation_position.top); } else { p = current_event.dst; } byte message_slot = current_event.slot; if (message_slot != 0) { --message_slot; assert(message_slot < 4); const Surface *s = custom_animation[message_slot].currentFrame(0); if (s == NULL) s = animation[message_slot].currentFrame(0); if (s != NULL) { p.x = s->x + s->w / 2; p.y = s->y; } else warning("no animation in slot %u", message_slot); } message_pos = messagePosition(message, p); message_color = current_event.color; if (message_first_frame) current_event.clear(); //async message, clearing event } break; case SceneEvent::kPlayAnimation: { byte slot = current_event.slot & 7; //0 - mark's if (current_event.animation != 0) { debug(0, "playing animation %u in slot %u(%02x)", current_event.animation, slot, current_event.slot); if (slot != 0) { --slot; assert(slot < 4); playAnimation(slot, current_event.animation, (current_event.slot & 0x80) != 0, (current_event.slot & 0x40) != 0, (current_event.slot & 0x20) != 0); } else actor_talking = true; } else { if (slot != 0) { --slot; debug(0, "cancelling animation in slot %u", slot); assert(slot < 4); custom_animation[slot].free(); } else actor_talking = true; } current_event.clear(); } break; case SceneEvent::kPauseAnimation: { byte slot = current_event.slot & 7; //0 - mark's if (slot != 0) { --slot; debug(1, "pause animation in slot %u", slot); custom_animation[slot].paused = (current_event.slot & 0x80) != 0; } else { actor_talking = false; } current_event.clear(); } break; case SceneEvent::kClearAnimations: for (byte i = 0; i < 4; ++i) custom_animation[i].free(); actor_talking = false; current_event.clear(); break; case SceneEvent::kPlayActorAnimation: debug(0, "playing actor animation %u", current_event.animation); playActorAnimation(current_event.animation, (current_event.slot & 0x80) != 0, (current_event.slot & 0x20) != 0); current_event.clear(); break; case SceneEvent::kPlayMusic: debug(0, "setting music %u", current_event.music); _engine->setMusic(current_event.music); Resources::instance()->dseg.set_byte(0xDB90, current_event.music); current_event.clear(); break; case SceneEvent::kPlaySound: debug(0, "playing sound %u, delay: %u", current_event.sound, current_event.color); sounds.push_back(Sound(current_event.sound, current_event.color)); current_event.clear(); break; case SceneEvent::kEnableObject: { debug(0, "%s object #%u", current_event.color ? "enabling" : "disabling", current_event.object - 1); Object *obj = getObject(current_event.object - 1, current_event.scene == 0 ? _id : current_event.scene); obj->enabled = current_event.color; obj->save(); current_event.clear(); } break; case SceneEvent::kHideActor: hide_actor = current_event.color != 0; current_event.clear(); break; case SceneEvent::kWaitForAnimation: debug(0, "waiting for the animation"); break; case SceneEvent::kWaitLanAnimationFrame: debug(0, "waiting for the frame %d in slot %d", current_event.animation, current_event.slot); break; case SceneEvent::kTimer: callback = current_event.callback; callback_timer = current_event.timer; debug(0, "triggering callback %04x in %u frames", callback, callback_timer); current_event.clear(); break; case SceneEvent::kEffect: _system->delayMillis(80); //2 vsyncs _system->setShakePos(8); _system->updateScreen(); _system->delayMillis(80); //2 vsyncs _system->setShakePos(0); _system->updateScreen(); _system->delayMillis(80); //2 vsyncs _system->setShakePos(4); _system->updateScreen(); _system->delayMillis(80); //2 vsyncs _system->setShakePos(0); _system->updateScreen(); current_event.clear(); break; case SceneEvent::kFade: _fade_timer = current_event.orientation != 0? 5: -5; current_event.clear(); break; case SceneEvent::kWait: debug(0, "wait %u", current_event.timer); break; case SceneEvent::kCredits: debug(0, "showing credits"); break; case SceneEvent::kQuit: debug(0, "quit!"); _engine->quitGame(); break; case SceneEvent::kSetFlag: debug(0, "async set_flag(%04x, %d)", current_event.callback, current_event.color); Resources::instance()->dseg.set_byte(current_event.callback, current_event.color); current_event.clear(); break; default: error("empty/unhandler event[%d]", (int)current_event.type); } } if (events.empty()) { message_color = 0xd1; hide_actor = false; } return !current_event.empty(); } void Scene::setPalette(unsigned mul) { //debug(0, "setPalette(%u)", mul); byte p[3*256]; for (int i = 0; i < 3*256; ++i) { p[i] = (unsigned)palette[i] * mul; } _system->getPaletteManager()->setPalette(p, 0, 256); } Object *Scene::getObject(int id, int scene_id) { assert(id > 0); if (scene_id == 0) scene_id = _id; if (scene_id == 0) return NULL; Common::Array &scene_objects = objects[scene_id - 1]; --id; if (id >= (int)scene_objects.size()) return NULL; return &scene_objects[id]; } Common::Point Scene::messagePosition(const Common::String &str, Common::Point message_position) { Resources *res = Resources::instance(); int lines = 1; for(uint i = 0; i < str.size(); ++i) if (str[i] == '\n') ++lines; uint w = res->font7.render(NULL, 0, 0, str, 0); uint h = res->font7.height * lines + 3; message_position.x -= w / 2; message_position.y -= h; if (message_position.x + w > 320) message_position.x = 320 - w; if (message_position.x < 0) message_position.x = 0; if (message_position.y + h > 320) message_position.y = 200 - h; if (message_position.y < 0) message_position.y = 0; return message_position; } uint Scene::messageDuration(const Common::String &str) { //original game uses static delays: 100-slow, 50, 20 and 1 tick - crazy speed. //total delay = total message length * delay / 8 + 60. uint total_width = str.size(); int speed = Common::ConfigManager::instance().getInt("talkspeed"); if (speed < 0) speed = 60; uint delay_delta = 1 + (255 - speed) * 99 / 255; uint delay = 60 + (total_width * delay_delta) / 8; //debug(0, "delay = %u, delta: %u", delay, delay_delta); return delay * 10; } void Scene::displayMessage(const Common::String &str, byte color, const Common::Point &pos) { //assert(!str.empty()); //debug(0, "displayMessage: %s", str.c_str()); message = str; message_pos = (pos.x | pos.y)? pos: messagePosition(str, position); message_color = color; message_timer = messageDuration(message); } void Scene::clear() { clearMessage(); events.clear(); current_event.clear(); for(int i = 0; i < 4; ++i) { animation[i].free(); custom_animation[i].free(); } callback = 0; callback_timer = 0; } void Scene::clearMessage() { message.clear(); message_timer = 0; message_color = 0xd1; message_first_frame = 0; message_last_frame = 0; message_animation = NULL; } } // End of namespace TeenAgent