/* 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 "common/file.h" #include "common/path.h" #include "common/textconsole.h" #include "tetraedge/tetraedge.h" #include "tetraedge/game/application.h" #include "tetraedge/game/billboard.h" #include "tetraedge/game/syberia_game.h" #include "tetraedge/game/in_game_scene.h" #include "tetraedge/game/in_game_scene_xml_parser.h" #include "tetraedge/game/character.h" #include "tetraedge/game/characters_shadow.h" #include "tetraedge/game/object3d.h" #include "tetraedge/game/particle_xml_parser.h" #include "tetraedge/game/scene_lights_xml_parser.h" #include "tetraedge/te/te_bezier_curve.h" #include "tetraedge/te/te_camera.h" #include "tetraedge/te/te_core.h" #include "tetraedge/te/te_free_move_zone.h" #include "tetraedge/te/te_light.h" #include "tetraedge/te/te_renderer.h" #include "tetraedge/te/te_lua_script.h" #include "tetraedge/te/te_lua_thread.h" //#define TETRAEDGE_DEBUG_PATHFINDING //#define TETRAEDGE_DEBUG_LIGHTS namespace Tetraedge { /*static*/ const int InGameScene::MAX_FIRE = 50; const int InGameScene::MAX_SNOW = 250; const int InGameScene::MAX_SMOKE = 350; const float InGameScene::DUREE_MAX_FIRE = 32000.0f; const float InGameScene::SCALE_FIRE = 0.1f; const int InGameScene::MAX_FLAKE = 10; const float InGameScene::DUREE_MIN_FLAKE = 3000.0f; const float InGameScene::DUREE_MAX_FLAKE = 5000.0f; const float InGameScene::SCALE_FLAKE = 0.1f; const float InGameScene::DEPTH_MAX_FLAKE = 0.1f; InGameScene::InGameScene() : _character(nullptr), _charactersShadow(nullptr), _shadowLightNo(-1), _waitTime(-1.0), _shadowColor(0, 0, 0, 0x80), _shadowFov(20.0f), _shadowFarPlane(1000), _shadowNearPlane(1), _maskAlpha(false), _verticalScrollTime(1000000.0f), _verticalScrollPlaying(false) { } void InGameScene::activateAnchorZone(const Common::String &name, bool val) { for (AnchorZone *zone : _anchorZones) { if (zone->_name == name) zone->_activated = val; } } void InGameScene::addAnchorZone(const Common::String &s1, const Common::String &name, float radius) { for (AnchorZone *zone : _anchorZones) { if (zone->_name == name) { zone->_radius = radius; return; } } assert(currentCamera()); currentCamera()->apply(); AnchorZone *zone = new AnchorZone(); zone->_name = name; zone->_radius = radius; zone->_activated = true; if (s1.contains("Int")) { TeButtonLayout *btn = hitObjectGui().buttonLayoutChecked(name); TeVector3f32 pos = btn->position(); pos.x() += g_engine->getDefaultScreenWidth() / 2.0f; pos.y() += g_engine->getDefaultScreenHeight() / 2.0f; zone->_loc = currentCamera()->worldTransformationMatrix() * currentCamera()->transformPoint2Dto3D(pos); } else { if (s1.contains("Dummy")) { Dummy d = dummy(name); zone->_loc = d._position; } } _anchorZones.push_back(zone); } bool InGameScene::addMarker(const Common::String &markerName, const Common::Path &imgPath, float x, float y, const Common::String &locType, const Common::String &markerVal, float anchorX, float anchorY) { const TeMarker *marker = findMarker(markerName); if (!marker) { SyberiaGame *game = dynamic_cast(g_engine->getGame()); assert(game); Application *app = g_engine->getApplication(); TeSpriteLayout *markerSprite = new TeSpriteLayout(); // Note: game checks paths here but seems to just use the original? markerSprite->setName(markerName); markerSprite->setAnchor(TeVector3f32(anchorX, anchorY, 0.0f)); markerSprite->load(imgPath); markerSprite->setSizeType(TeILayout::RELATIVE_TO_PARENT); markerSprite->setPositionType(TeILayout::RELATIVE_TO_PARENT); TeVector3f32 newPos; if (locType == "PERCENT") { TeVector3f32 parentSize; //if (g_engine->gameType() == TetraedgeEngine::kSyberia) parentSize = app->frontLayout().userSize(); //else // parentSize = app->getMainWindow().size(); newPos.x() = parentSize.x() * (x / 100.0f); newPos.y() = parentSize.y() * (y / 100.0f); } else { newPos.x() = x / g_engine->getDefaultScreenWidth(); newPos.y() = y / g_engine->getDefaultScreenHeight(); } markerSprite->setPosition(newPos); const TeVector3f32 winSize = app->getMainWindow().size(); float xscale = 1.0f; float yscale = 1.0f; // Originally this is only done in Syberia 2, but // should be fine to calculate in Syberia 1, as long // as the root layout is loaded. TeLayout *bglayout = _bgGui.layoutChecked("background"); TeSpriteLayout *rootlayout = Game::findSpriteLayoutByName(bglayout, "root"); if (rootlayout && rootlayout->_tiledSurfacePtr && rootlayout->_tiledSurfacePtr->tiledTexture()) { TeVector2s32 bgSize = rootlayout->_tiledSurfacePtr->tiledTexture()->totalSize(); xscale = 800.0f / bgSize._x; yscale = 600.0f / bgSize._y; } if (g_engine->getCore()->fileFlagSystemFlag("definition") == "SD") { markerSprite->setSize(TeVector3f32(xscale * 0.07f, yscale * (4.0f / ((winSize.y() / winSize.x()) * 4.0f)) * 0.07f, 0.0)); } else { markerSprite->setSize(TeVector3f32(xscale * 0.04f, yscale * (4.0f / ((winSize.y() / winSize.x()) * 4.0f)) * 0.04f, 0.0)); } markerSprite->setVisible(game->markersVisible()); markerSprite->_tiledSurfacePtr->_frameAnim.setLoopCount(-1); markerSprite->play(); TeMarker newMarker; newMarker._name = markerName; newMarker._val = markerVal; _markers.push_back(newMarker); TeLayout *bg = game->forGui().layout("background"); if (bg) bg->addChild(markerSprite); _sprites.push_back(markerSprite); } else { setImagePathMarker(markerName, imgPath); } return true; } /*static*/ float InGameScene::angularDistance(float a1, float a2) { float result = a2 - a1; if (result >= -M_PI && result > M_PI) { result = result - (M_PI * 2); } else { result = result + (M_PI * 2); } return result; } bool InGameScene::aroundAnchorZone(const AnchorZone *zone) { if (!zone->_activated) return false; const TeVector3f32 charpos = _character->_model->position(); float xoff = charpos.x() - zone->_loc.x(); float zoff = charpos.z() - zone->_loc.z(); return sqrt(xoff * xoff + zoff * zoff) <= zone->_radius; } TeLayout *InGameScene::background() { return _bgGui.layout("background"); } Billboard *InGameScene::billboard(const Common::String &name) { for (Billboard *billboard : _billboards) { if (billboard->model()->name() == name) return billboard; } return nullptr; } bool InGameScene::changeBackground(const Common::Path &name) { Common::Path path = g_engine->getCore()->findFile(name); if (Common::File::exists(path)) { _bgGui.spriteLayoutChecked("root")->load(path); if (g_engine->gameType() == TetraedgeEngine::kSyberia2) _bgGui.spriteLayoutChecked("root")->play(); return true; } return false; } Character *InGameScene::character(const Common::String &name) { if (_character && _character->_model->name() == name) return _character; for (Character *c : _characters) { if (c->_model->name() == name) return c; } // WORKAROUND: Didn't find char, try again with case insensitive // for "OScar" typo in scenes/ValTrain/19000. for (Character *c : _characters) { if (c->_model->name().compareToIgnoreCase(name) == 0) return c; } return nullptr; } void InGameScene::close() { reset(); _loadedPath = ""; TeScene::close(); freeGeometry(); if (_character && _character->_model && !findKate()) { models().push_back(_character->_model); if (_character->_shadowModel[0]) { models().push_back(_character->_shadowModel[0]); models().push_back(_character->_shadowModel[1]); } } _objects.clear(); for (TeFreeMoveZone *zone : _freeMoveZones) delete zone; _freeMoveZones.clear(); _hitObjects.clear(); for (TePickMesh2 *mesh : _clickMeshes) delete mesh; _clickMeshes.clear(); _bezierCurves.clear(); _dummies.clear(); freeSceneObjects(); } void InGameScene::convertPathToMesh(TeFreeMoveZone *zone) { TeIntrusivePtr model = new TeModel(); model->meshes().clear(); model->setMeshCount(1); model->setName("shadowReceiving"); model->setPosition(zone->position()); model->setRotation(zone->rotation()); model->setScale(zone->scale()); uint64 nverticies = zone->freeMoveZoneVerticies().size(); TeMesh *mesh0 = model->meshes()[0].get(); mesh0->setConf(nverticies, nverticies, TeMesh::MeshMode_Triangles, 0, 0); for (uint i = 0; i < nverticies; i++) { mesh0->setIndex(i, i); mesh0->setVertex(i, zone->freeMoveZoneVerticies()[i]); mesh0->setNormal(i, TeVector3f32(0, 0, 1)); } _zoneModels.push_back(model); } TeIntrusivePtr InGameScene::curve(const Common::String &curveName) { for (TeIntrusivePtr &c : _bezierCurves) { if (c->name() == curveName) return c; } return TeIntrusivePtr(); } void InGameScene::deleteAllCallback() { for (auto &pair : _callbacks) { for (auto *cb : pair._value) { delete cb; } pair._value.clear(); } _callbacks.clear(); } void InGameScene::deleteMarker(const Common::String &markerName) { if (!isMarker(markerName)) return; for (uint i = 0; i < _markers.size(); i++) { if (_markers[i]._name == markerName) { _markers.remove_at(i); break; } } Game *game = g_engine->getGame(); TeLayout *bg = game->forGui().layout("background"); if (!bg) return; for (Te3DObject2 *child : bg->childList()) { if (child->name() == markerName) { bg->removeChild(child); break; } } } void InGameScene::deserializeCam(Common::ReadStream &stream, TeIntrusivePtr &cam) { cam->setProjMatrixType(2); cam->viewport(0, 0, _viewportSize.getX(), _viewportSize.getY()); // load name/position/rotation/scale Te3DObject2::deserialize(stream, *cam); cam->setFov(stream.readFloatLE()); cam->setAspectRatio(stream.readFloatLE()); // Original loads the second val then ignores it and sets 3000. cam->setOrthoPlanes(stream.readFloatLE(), 3000.0); stream.readFloatLE(); } void InGameScene::deserializeModel(Common::ReadStream &stream, TeIntrusivePtr &model, TePickMesh2 *pickmesh) { TeVector3f32 vec; TeVector2f32 vec2; TeQuaternion rot; TeColor col; Common::SharedPtr mesh(TeMesh::makeInstance()); assert(pickmesh); TeVector3f32::deserialize(stream, vec); model->setPosition(vec); pickmesh->setPosition(vec); TeQuaternion::deserialize(stream, rot); model->setRotation(rot); pickmesh->setRotation(rot); TeVector3f32::deserialize(stream, vec); model->setScale(vec); pickmesh->setScale(vec); uint32 indexcount = stream.readUint32LE(); uint32 vertexcount = stream.readUint32LE(); if (indexcount > 100000 || vertexcount > 100000) error("InGameScene::deserializeModel: Unxpected counts %d %d", indexcount, vertexcount); mesh->setConf(vertexcount, indexcount, TeMesh::MeshMode_Triangles, 0, 0); for (uint i = 0; i < indexcount; i++) mesh->setIndex(i, stream.readUint32LE()); for (uint i = 0; i < vertexcount; i++) { TeVector3f32::deserialize(stream, vec); mesh->setVertex(i, vec); } for (uint i = 0; i < vertexcount; i++) { TeVector3f32::deserialize(stream, vec); mesh->setNormal(i, vec); } for (uint i = 0; i < vertexcount; i++) { TeVector2f32::deserialize(stream, vec2); mesh->setTextureUV(i, vec2); } for (uint i = 0; i < vertexcount; i++) { col.deserialize(stream); mesh->setColor(i, col); } pickmesh->setNbTriangles(indexcount / 3); for (uint i = 0; i < indexcount; i++) { vec = mesh->vertex(mesh->index(i)); pickmesh->verticies()[i] = vec; } model->addMesh(mesh); } void InGameScene::draw() { if (currentCameraIndex() >= (int)cameras().size()) return; currentCamera()->apply(); drawMask(); drawReflection(); #ifdef TETRAEDGE_DEBUG_PATHFINDING if (_character && _character->curve()) { _character->curve()->setVisible(true); _character->curve()->draw(); } for (TeFreeMoveZone *zone : _freeMoveZones) { zone->setVisible(true); zone->draw(); } for (TePickMesh2 *mesh : _clickMeshes) { mesh->setVisible(true); mesh->draw(); } #endif g_engine->getRenderer()->updateGlobalLight(); for (uint i = 0; i < _lights.size(); i++) _lights[i]->update(i); TeCamera::restore(); drawKate(); TeScene::draw(); } void InGameScene::drawKate() { if (_rippleMasks.size()) error("TODO: Implement InGameScene::drawKate"); } void InGameScene::drawMask() { if (_masks.empty()) return; TeIntrusivePtr cam = currentCamera(); if (!cam) return; cam->apply(); TeRenderer *rend = g_engine->getRenderer(); if (!_maskAlpha) rend->colorMask(false, false, false, false); for (auto &mask : _masks) mask->draw(); if (!_maskAlpha) rend->colorMask(true, true, true, true); } void InGameScene::drawReflection() { if (_rippleMasks.empty() || currentCameraIndex() >= (int)cameras().size()) return; currentCamera()->apply(); if (!_maskAlpha) g_engine->getRenderer()->colorMask(false, false, false, false); for (uint i = _rippleMasks.size() - 1; i > 0; i--) { _rippleMasks[i]->draw(); } if (!_maskAlpha) g_engine->getRenderer()->colorMask(true, true, true, true); } void InGameScene::drawPath() { if (currentCameraIndex() >= (int)cameras().size()) return; currentCamera()->apply(); g_engine->getRenderer()->disableZBuffer(); for (uint i = 0; i < _freeMoveZones.size(); i++) _freeMoveZones[i]->draw(); g_engine->getRenderer()->enableZBuffer(); } InGameScene::Dummy InGameScene::dummy(const Common::String &name) { for (const Dummy &dummy : _dummies) { if (dummy._name == name) return dummy; } return Dummy(); } bool InGameScene::findKate() { for (auto &m : models()) { if (m->name() == "Kate") return true; } return false; } const InGameScene::TeMarker *InGameScene::findMarker(const Common::String &name) { for (const TeMarker &marker : _markers) { if (marker._name == name) return ▮ } return nullptr; } const InGameScene::TeMarker *InGameScene::findMarkerByInt(const Common::String &val) { for (const TeMarker &marker : _markers) { if (marker._val == val) return ▮ } return nullptr; } InGameScene::SoundStep InGameScene::findSoundStep(const Common::String &name) { for (const auto &step : _soundSteps) { if (step._key == name) return step._value; } return SoundStep(); } void InGameScene::freeGeometry() { _loadedPath.set(""); _youkiManager.reset(); freeSceneObjects(); if (_character) _character->setFreeMoveZone(nullptr); for (Character *character : _characters) character->setFreeMoveZone(nullptr); for (TeFreeMoveZone *zone : _freeMoveZones) delete zone; _freeMoveZones.clear(); _bezierCurves.clear(); _dummies.clear(); cameras().clear(); models().clear(); _zoneModels.clear(); _masks.clear(); _shadowReceivingObjects.clear(); if (_charactersShadow) _charactersShadow->destroy(); _sceneLights.clear(); if (_charactersShadow) { delete _charactersShadow; _charactersShadow = nullptr; } } void InGameScene::freeSceneObjects() { if (_character) { _character->setCharLookingAt(nullptr); _character->deleteAllCallback(); } if (_characters.size() == 1) { _characters[0]->deleteAllCallback(); } SyberiaGame *game = dynamic_cast(g_engine->getGame()); assert(game); game->unloadCharacters(); _characters.clear(); for (Object3D *obj : _object3Ds) { obj->deleteLater(); } _object3Ds.clear(); for (Billboard *bb : _billboards) { bb->deleteLater(); } _billboards.clear(); for (TeSpriteLayout *sprite : _sprites) { sprite->deleteLater(); } _sprites.clear(); // TODO: Clean up snows, waterCones, smokes, snowCones _particles.clear(); TeParticle::deleteAll(); deleteAllCallback(); _markers.clear(); // TODO: Clean up randomAnims for (RippleMask *rmask : _rippleMasks) { delete rmask; } _rippleMasks.clear(); for (InGameScene::AnchorZone *zone : _anchorZones) { delete zone; } _anchorZones.clear(); } float InGameScene::getHeadHorizontalRotation(Character *cter, const TeVector3f32 &vec) { TeVector3f32 pos = vec - cter->_model->position(); TeVector3f32 zvec = TeVector3f32(0, 0, 1.0f); zvec.rotate(cter->_model->rotation()); float angle = atan2f(-pos.x(), pos.z()) - atan2f(-zvec.x(), zvec.z()); if (angle < -M_PI) angle += (float)(M_PI * 2); else if (angle > M_PI) angle -= (float)(M_PI * 2); return angle; } float InGameScene::getHeadVerticalRotation(Character *cter, const TeVector3f32 &vec) { TeVector3f32 modelPos = cter->_model->position(); TeVector3f32 headWorldTrans = cter->_model->worldTransformationMatrix() * cter->lastHeadBoneTrans(); modelPos.y() = headWorldTrans.y(); TeVector3f32 offsetPos = vec - modelPos; currentCamera()->apply(); float angle = atan2f(offsetPos.y(), TeVector2f32(offsetPos.x(), offsetPos.z()).length()); return angle; } Common::Path InGameScene::imagePathMarker(const Common::String &name) { if (!isMarker(name)) return Common::Path(); Game *game = g_engine->getGame(); TeLayout *bg = game->forGui().layoutChecked("background"); for (Te3DObject2 *child : bg->childList()) { TeSpriteLayout *spritelayout = dynamic_cast(child); if (spritelayout && spritelayout->name() == name) { return spritelayout->_tiledSurfacePtr->loadedPath(); } } return Common::Path(); } void InGameScene::initScroll() { _scrollOffset = TeVector2f32(0.5f, 0.0f); } bool InGameScene::isMarker(const Common::String &name) { for (const TeMarker &marker : _markers) { if (marker._name == name) return true; } return false; } bool InGameScene::isObjectBlocking(const Common::String &name) { for (const Common::String &b: _blockingObjects) { if (name == b) return true; } return false; } TeVector2f32 InGameScene::layerSize() { TeLayout *bglayout = _bgGui.layout("background"); TeVector3f32 sz; if (bglayout) { TeLayout *rootlayout = Game::findSpriteLayoutByName(bglayout, "root"); if (!rootlayout) error("InGameScene::layerSize: No root layout inside the background"); sz = rootlayout->size(); _scrollScale = TeVector2f32(sz.x(), sz.y()); } else { sz = g_engine->getApplication()->getMainWindow().size(); } return TeVector2f32(sz.x(), sz.y()); } bool InGameScene::load(const Common::Path &scenePath) { // Syberia 1 has loadActZones function contents inline. loadActZones(); if (!_lights.empty()) { g_engine->getRenderer()->disableAllLights(); for (uint i = 0; i < _lights.size(); i++) { _lights[i]->disable(i); } _lights.clear(); } _shadowLightNo = -1; TeCore *core = g_engine->getCore(); const Common::Path lightsPath = core->findFile(getLightsFileName()); if (Common::File::exists(lightsPath)) loadLights(lightsPath); if (!Common::File::exists(scenePath)) return false; close(); _loadedPath = scenePath.getParent(); Common::File scenefile; if (!scenefile.open(scenePath)) return false; uint32 ncameras = scenefile.readUint32LE(); if (ncameras > 1024) error("Improbable number of cameras %d", ncameras); for (uint i = 0; i < ncameras; i++) { TeIntrusivePtr cam = new TeCamera(); deserializeCam(scenefile, cam); cameras().push_back(cam); } uint32 nobjects = scenefile.readUint32LE(); if (nobjects > 1024) error("Improbable number of objects %d", nobjects); for (uint i = 0; i < nobjects; i++) { TeIntrusivePtr model = new TeModel(); const Common::String modelname = Te3DObject2::deserializeString(scenefile); model->setName(modelname); const Common::String objname = Te3DObject2::deserializeString(scenefile); TePickMesh2 *pickmesh = new TePickMesh2(); deserializeModel(scenefile, model, pickmesh); if (modelname.contains("Clic")) { //debug("Loaded clickMesh %s", modelname.c_str()); _hitObjects.push_back(model); model->setVisible(false); model->setColor(TeColor(0, 0xff, 0, 0xff)); models().push_back(model); pickmesh->setName(modelname); _clickMeshes.push_back(pickmesh); } else { delete pickmesh; if (modelname.substr(0, 2) != "ZB") { if (objname.empty()) { debug("[InGameScene::load] Unknown type of object named : %s", modelname.c_str()); } else { InGameScene::Object obj; obj._name = objname; obj._model = model; _objects.push_back(obj); model->setVisible(false); models().push_back(model); } } } } uint32 nfreemovezones = scenefile.readUint32LE(); if (nfreemovezones > 1024) error("Improbable number of free move zones %d", nfreemovezones); for (uint i = 0; i < nfreemovezones; i++) { TeFreeMoveZone *zone = new TeFreeMoveZone(); TeFreeMoveZone::deserialize(scenefile, *zone, &_blockers, &_rectBlockers, &_actZones); _freeMoveZones.push_back(zone); zone->setVisible(false); } uint32 ncurves = scenefile.readUint32LE(); if (ncurves > 1024) error("Improbable number of curves %d", ncurves); for (uint i = 0; i < ncurves; i++) { TeIntrusivePtr curve = new TeBezierCurve(); TeBezierCurve::deserialize(scenefile, *curve); curve->setVisible(true); _bezierCurves.push_back(curve); } uint32 ndummies = scenefile.readUint32LE(); if (ndummies > 1024) error("Improbable number of dummies %d", ndummies); for (uint i = 0; i < ndummies; i++) { InGameScene::Dummy dummy; TeVector3f32 vec; TeQuaternion rot; dummy._name = Te3DObject2::deserializeString(scenefile); TeVector3f32::deserialize(scenefile, vec); dummy._position = vec; TeQuaternion::deserialize(scenefile, rot); dummy._rotation = rot; TeVector3f32::deserialize(scenefile, vec); dummy._scale = vec; _dummies.push_back(dummy); } for (TeFreeMoveZone *zone : _freeMoveZones) { convertPathToMesh(zone); } _charactersShadow = CharactersShadow::makeInstance(); _charactersShadow->create(this); onMainWindowSizeChanged(); return true; } static Common::Path _sceneFileNameBase(const Common::String &zone, const Common::String &scene) { Common::Path retval("scenes"); retval.joinInPlace(zone).joinInPlace(scene); return retval; } static Common::Path _sceneFileNameBase() { const Game *game = g_engine->getGame(); return _sceneFileNameBase(game->currentZone(), game->currentScene()); } bool InGameScene::loadXml(const Common::String &zone, const Common::String &scene) { _maskAlpha = false; _zoneName = zone; _sceneName = scene; _blockers.clear(); _rectBlockers.clear(); TeFreeMoveZone::setCollisionSlide(false); loadBlockers(); Common::Path xmlpath = _sceneFileNameBase(zone, scene).joinInPlace("Scene") .appendInPlace(scene).appendInPlace(".xml"); Common::Path path = g_engine->getCore()->findFile(xmlpath); InGameSceneXmlParser parser(this); parser.setAllowText(); Common::String fixedbuf; if (g_engine->gameType() == TetraedgeEngine::kSyberia2 && scene == "GangcarVideo") { // // WORKAROUND: scenes/A1_RomHaut/GangcarVideo/SceneGangcarVideo.xml // in Syberia 2 has an embedded comment, which is invalid XML. // Patch the contents of the file before loading. // Common::File xmlFile; if (!xmlFile.open(path)) error("InGameScene::loadXml: Can't open %s", path.toString(Common::Path::kNativeSeparator).c_str()); const int64 bufsize = xmlFile.size(); char *buf = new char[bufsize+1]; buf[bufsize] = '\0'; xmlFile.read(buf, bufsize); fixedbuf = Common::String(buf); delete [] buf; size_t offset = fixedbuf.find(""); if (offset != Common::String::npos) fixedbuf.replace(offset + 29, 3, " "); // replace the > at the end offset = fixedbuf.find("