diff --git a/CMakeLists.txt b/CMakeLists.txt index 78388e20f..d7115583f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ include (OpenMWMacros) # Version set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 19) +set (OPENMW_VERSION_MINOR 20) set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") @@ -25,12 +25,14 @@ set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VE configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) -option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE) +option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE) +option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) # Apps and tools option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) +option(BUILD_OPENCS "build OpenMW Construction Set" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF) @@ -167,9 +169,9 @@ if (WIN32) set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) else (WIN32) -set(PLATFORM_INCLUDE_DIR "") -find_path (UUID_INCLUDE_DIR uuid/uuid.h) -include_directories(${UUID_INCLUDE_DIR}) + set(PLATFORM_INCLUDE_DIR "") + find_path (UUID_INCLUDE_DIR uuid/uuid.h) + include_directories(${UUID_INCLUDE_DIR}) endif (WIN32) if (MSVC10) set(PLATFORM_INCLUDE_DIR "") @@ -183,7 +185,7 @@ endif (APPLE) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) -find_package (Threads) + find_package (Threads) endif() # find boost without components so we can use Boost_VERSION @@ -196,6 +198,10 @@ if (Boost_VERSION LESS 104900) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} wave) endif() +IF(BOOST_STATIC) + set(Boost_USE_STATIC_LIBS ON) +endif() + find_package(OGRE REQUIRED) find_package(MyGUI REQUIRED) find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) @@ -244,7 +250,7 @@ if (APPLE) else () set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG}) endif () - + #set(OGRE_PLUGIN_DIR "${OGRE_PLUGIN_DIR}/") configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist @@ -356,7 +362,7 @@ if(DPKG_PROGRAM) SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") - SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-filesystem1.46.1 (>= 1.46.1), libboost-program-options1.46.1 (>= 1.46.1), libboost-system1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") + SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") @@ -462,6 +468,10 @@ if (BUILD_MWINIIMPORTER) add_subdirectory( apps/mwiniimporter ) endif() +if (BUILD_OPENCS) + add_subdirectory (apps/opencs) +endif() + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 4f6d9dbfc..0cd6e3905 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -59,7 +59,7 @@ struct Arguments std::string outname; std::vector types; - + ESMData data; ESM::ESMReader reader; ESM::ESMWriter writer; @@ -74,7 +74,7 @@ bool parseOptions (int argc, char** argv, Arguments &info) ("version,v", "print version information and quit.") ("raw,r", "Show an unformatted list of all records and subrecords.") // The intention is that this option would interact better - // with other modes including clone, dump, and raw. + // with other modes including clone, dump, and raw. ("type,t", bpo::value< std::vector >(), "Show only records of this type (four character record code). May " "be specified multiple times. Only affects dump mode.") @@ -262,7 +262,7 @@ void printRaw(ESM::ESMReader &esm) int load(Arguments& info) { ESM::ESMReader& esm = info.reader; - esm.setEncoding(info.encoding); + esm.setEncoding(ToUTF8::calculateEncoding(info.encoding)); std::string filename = info.filename; std::cout << "Loading file: " << filename << std::endl; @@ -321,7 +321,7 @@ int load(Arguments& info) if (info.types.size() > 0) { std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), + match = std::find(info.types.begin(), info.types.end(), n.toString()); if (match == info.types.end()) interested = false; } @@ -425,7 +425,7 @@ int clone(Arguments& info) if (++i % 3 == 0) std::cout << std::endl; } - + if (i % 3 != 0) std::cout << std::endl; @@ -450,7 +450,7 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - + name.val = record->getType().val; esm.startRecord(name.toString(), record->getFlags()); @@ -485,7 +485,7 @@ int clone(Arguments& info) std::cerr << "\r" << perc << "%"; } } - + std::cout << "\rDone!" << std::endl; esm.close(); @@ -513,7 +513,7 @@ int comp(Arguments& info) fileOne.encoding = info.encoding; fileTwo.encoding = info.encoding; - + fileOne.filename = info.filename; fileTwo.filename = info.outname; @@ -534,9 +534,9 @@ int comp(Arguments& info) std::cout << "Not equal, different amount of records." << std::endl; return 1; } - - - + + + return 0; } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index b5f97e979..a732f1938 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -738,7 +738,6 @@ void Record::print() default: std::cout << "unknown type"; } - std::cout << "\n Dirty: " << mData.mDirty << std::endl; } template<> diff --git a/apps/launcher/model/datafilesmodel.cpp b/apps/launcher/model/datafilesmodel.cpp index d85a15e73..716c9e902 100644 --- a/apps/launcher/model/datafilesmodel.cpp +++ b/apps/launcher/model/datafilesmodel.cpp @@ -272,7 +272,7 @@ void DataFilesModel::addMasters(const QString &path) foreach (const QString &path, dir.entryList()) { try { ESM::ESMReader fileReader; - fileReader.setEncoding(mEncoding.toStdString()); + fileReader.setEncoding(ToUTF8::calculateEncoding(mEncoding.toStdString())); fileReader.open(dir.absoluteFilePath(path).toStdString()); ESM::ESMReader::MasterList mlist = fileReader.getMasters(); @@ -335,7 +335,7 @@ void DataFilesModel::addPlugins(const QString &path) try { ESM::ESMReader fileReader; - fileReader.setEncoding(mEncoding.toStdString()); + fileReader.setEncoding(ToUTF8::calculateEncoding(mEncoding.toStdString())); fileReader.open(dir.absoluteFilePath(path).toStdString()); ESM::ESMReader::MasterList mlist = fileReader.getMasters(); diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 19b69794f..def70615b 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -18,8 +18,609 @@ MwIniImporter::MwIniImporter() { 0, 0 } }; const char *fallback[] = { + + // light + "LightAttenuation:UseConstant", + "LightAttenuation:ConstantValue", + "LightAttenuation:UseLinear", + "LightAttenuation:LinearMethod", + "LightAttenuation:LinearValue", + "LightAttenuation:LinearRadiusMult", + "LightAttenuation:UseQuadratic", + "LightAttenuation:QuadraticMethod", + "LightAttenuation:QuadraticValue", + "LightAttenuation:QuadraticRadiusMult", + "LightAttenuation:OutQuadInLin", + + // inventory + "Inventory:DirectionalDiffuseR", + "Inventory:DirectionalDiffuseG", + "Inventory:DirectionalDiffuseB", + "Inventory:DirectionalAmbientR", + "Inventory:DirectionalAmbientG", + "Inventory:DirectionalAmbientB", + "Inventory:DirectionalRotationX", + "Inventory:DirectionalRotationY", + "Inventory:UniformScaling", + + // map + "Map:Travel Siltstrider Red", + "Map:Travel Siltstrider Green", + "Map:Travel Siltstrider Blue", + "Map:Travel Boat Red", + "Map:Travel Boat Green", + "Map:Travel Boat Blue", + "Map:Travel Magic Red", + "Map:Travel Magic Green", + "Map:Travel Magic Blue", + "Map:Show Travel Lines", + + // water + "Water:Map Alpha", + "Water:World Alpha", + "Water:SurfaceTextureSize", + "Water:SurfaceTileCount", + "Water:SurfaceFPS", + "Water:SurfaceTexture", + "Water:SurfaceFrameCount", + "Water:TileTextureDivisor", + "Water:RippleTexture", + "Water:RippleFrameCount", + "Water:RippleLifetime", + "Water:MaxNumberRipples", + "Water:RippleScale", + "Water:RippleRotSpeed", + "Water:RippleAlphas", + "Water:PSWaterReflectTerrain", + "Water:PSWaterReflectUpdate", + "Water:NearWaterRadius", + "Water:NearWaterPoints", + "Water:NearWaterUnderwaterFreq", + "Water:NearWaterUnderwaterVolume", + "Water:NearWaterIndoorTolerance", + "Water:NearWaterOutdoorTolerance", + "Water:NearWaterIndoorID", + "Water:NearWaterOutdoorID", + "Water:UnderwaterSunriseFog", + "Water:UnderwaterDayFog", + "Water:UnderwaterSunsetFog", + "Water:UnderwaterNightFog", + "Water:UnderwaterIndoorFog", + "Water:UnderwaterColor", + "Water:UnderwaterColorWeight", + + // pixelwater + "PixelWater:SurfaceFPS", + "PixelWater:TileCount", + "PixelWater:Resolution", + + // fonts + "Fonts:Font 0", + "Fonts:Font 1", + "Fonts:Font 2", + + // UI colors + "FontColor:color_normal", + "FontColor:color_normal_over", + "FontColor:color_normal_pressed", + "FontColor:color_active", + "FontColor:color_active_over", + "FontColor:color_active_pressed", + "FontColor:color_disabled", + "FontColor:color_disabled_over", + "FontColor:color_disabled_pressed", + "FontColor:color_link", + "FontColor:color_link_over", + "FontColor:color_link_pressed", + "FontColor:color_journal_link", + "FontColor:color_journal_link_over", + "FontColor:color_journal_link_pressed", + "FontColor:color_journal_topic", + "FontColor:color_journal_topic_over", + "FontColor:color_journal_topic_pressed", + "FontColor:color_answer", + "FontColor:color_answer_over", + "FontColor:color_answer_pressed", + "FontColor:color_header", + "FontColor:color_notify", + "FontColor:color_big_normal", + "FontColor:color_big_normal_over", + "FontColor:color_big_normal_pressed", + "FontColor:color_big_link", + "FontColor:color_big_link_over", + "FontColor:color_big_link_pressed", + "FontColor:color_big_answer", + "FontColor:color_big_answer_over", + "FontColor:color_big_answer_pressed", + "FontColor:color_big_header", + "FontColor:color_big_notify", + "FontColor:color_background", + "FontColor:color_focus", + "FontColor:color_health", + "FontColor:color_magic", + "FontColor:color_fatigue", + "FontColor:color_misc", + "FontColor:color_weapon_fill", + "FontColor:color_magic_fill", + "FontColor:color_positive", + "FontColor:color_negative", + "FontColor:color_count", + + // level up messages + "Level Up:Level2", + "Level Up:Level3", + "Level Up:Level4", + "Level Up:Level5", + "Level Up:Level6", + "Level Up:Level7", + "Level Up:Level8", + "Level Up:Level9", + "Level Up:Level10", + "Level Up:Level11", + "Level Up:Level12", + "Level Up:Level13", + "Level Up:Level14", + "Level Up:Level15", + "Level Up:Level16", + "Level Up:Level17", + "Level Up:Level18", + "Level Up:Level19", + "Level Up:Level20", + "Level Up:Default", + + // character creation multiple choice test + "Question 1:Question", + "Question 1:AnswerOne", + "Question 1:AnswerTwo", + "Question 1:AnswerThree", + "Question 1:Sound", + "Question 2:Question", + "Question 2:AnswerOne", + "Question 2:AnswerTwo", + "Question 2:AnswerThree", + "Question 2:Sound", + "Question 3:Question", + "Question 3:AnswerOne", + "Question 3:AnswerTwo", + "Question 3:AnswerThree", + "Question 3:Sound", + "Question 4:Question", + "Question 4:AnswerOne", + "Question 4:AnswerTwo", + "Question 4:AnswerThree", + "Question 4:Sound", + "Question 5:Question", + "Question 5:AnswerOne", + "Question 5:AnswerTwo", + "Question 5:AnswerThree", + "Question 5:Sound", + "Question 6:Question", + "Question 6:AnswerOne", + "Question 6:AnswerTwo", + "Question 6:AnswerThree", + "Question 6:Sound", + "Question 7:Question", + "Question 7:AnswerOne", + "Question 7:AnswerTwo", + "Question 7:AnswerThree", + "Question 7:Sound", + "Question 8:Question", + "Question 8:AnswerOne", + "Question 8:AnswerTwo", + "Question 8:AnswerThree", + "Question 8:Sound", + "Question 9:Question", + "Question 9:AnswerOne", + "Question 9:AnswerTwo", + "Question 9:AnswerThree", + "Question 9:Sound", + "Question 10:Question", + "Question 10:AnswerOne", + "Question 10:AnswerTwo", + "Question 10:AnswerThree", + "Question 10:Sound", + + // blood textures and models + "Blood:Model 0", + "Blood:Model 1", + "Blood:Model 2", + "Blood:Texture 0", + "Blood:Texture 1", + "Blood:Texture 2", + "Blood:Texture Name 0", + "Blood:Texture Name 1", + "Blood:Texture Name 2", + + // movies + "Movies:Company Logo", + "Movies:Morrowind Logo", + "Movies:New Game", + "Movies:Loading", + "Movies:Options Menu", + + // weather related values + + "Weather Thunderstorm:Thunder Sound ID 0", + "Weather Thunderstorm:Thunder Sound ID 1", + "Weather Thunderstorm:Thunder Sound ID 2", + "Weather Thunderstorm:Thunder Sound ID 3", "Weather:Sunrise Time", "Weather:Sunset Time", + "Weather:Sunrise Duration", + "Weather:Sunset Duration", + "Weather:Hours Between Weather Changes", // AKA weather update time + "Weather Thunderstorm:Thunder Frequency", + "Weather Thunderstorm:Thunder Threshold", + + "Weather:EnvReduceColor", + "Weather:LerpCloseColor", + "Weather:BumpFadeColor", + "Weather:AlphaReduce", + "Weather:Minimum Time Between Environmental Sounds", + "Weather:Maximum Time Between Environmental Sounds", + "Weather:Sun Glare Fader Max", + "Weather:Sun Glare Fader Angle Max", + "Weather:Sun Glare Fader Color", + "Weather:Timescale Clouds", + "Weather:Precip Gravity", + "Weather:Rain Ripples", + "Weather:Rain Ripple Radius", + "Weather:Rain Ripples Per Drop", + "Weather:Rain Ripple Scale", + "Weather:Rain Ripple Speed", + "Weather:Fog Depth Change Speed", + "Weather:Sky Pre-Sunrise Time", + "Weather:Sky Post-Sunrise Time", + "Weather:Sky Pre-Sunset Time", + "Weather:Sky Post-Sunset Time", + "Weather:Ambient Pre-Sunrise Time", + "Weather:Ambient Post-Sunrise Time", + "Weather:Ambient Pre-Sunset Time", + "Weather:Ambient Post-Sunset Time", + "Weather:Fog Pre-Sunrise Time", + "Weather:Fog Post-Sunrise Time", + "Weather:Fog Pre-Sunset Time", + "Weather:Fog Post-Sunset Time", + "Weather:Sun Pre-Sunrise Time", + "Weather:Sun Post-Sunrise Time", + "Weather:Sun Pre-Sunset Time", + "Weather:Sun Post-Sunset Time", + "Weather:Stars Post-Sunset Start", + "Weather:Stars Pre-Sunrise Finish", + "Weather:Stars Fading Duration", + "Weather:Snow Ripples", + "Weather:Snow Ripple Radius", + "Weather:Snow Ripples Per Flake", + "Weather:Snow Ripple Scale", + "Weather:Snow Ripple Speed", + "Weather:Snow Gravity Scale", + "Weather:Snow High Kill", + "Weather:Snow Low Kill", + + "Weather Clear:Cloud Texture", + "Weather Clear:Clouds Maximum Percent", + "Weather Clear:Transition Delta", + "Weather Clear:Sky Sunrise Color", + "Weather Clear:Sky Day Color", + "Weather Clear:Sky Sunset Color", + "Weather Clear:Sky Night Color", + "Weather Clear:Fog Sunrise Color", + "Weather Clear:Fog Day Color", + "Weather Clear:Fog Sunset Color", + "Weather Clear:Fog Night Color", + "Weather Clear:Ambient Sunrise Color", + "Weather Clear:Ambient Day Color", + "Weather Clear:Ambient Sunset Color", + "Weather Clear:Ambient Night Color", + "Weather Clear:Sun Sunrise Color", + "Weather Clear:Sun Day Color", + "Weather Clear:Sun Sunset Color", + "Weather Clear:Sun Night Color", + "Weather Clear:Sun Disc Sunset Color", + "Weather Clear:Land Fog Day Depth", + "Weather Clear:Land Fog Night Depth", + "Weather Clear:Wind Speed", + "Weather Clear:Cloud Speed", + "Weather Clear:Glare View", + "Weather Clear:Ambient Loop Sound ID", + + "Weather Cloudy:Cloud Texture", + "Weather Cloudy:Clouds Maximum Percent", + "Weather Cloudy:Transition Delta", + "Weather Cloudy:Sky Sunrise Color", + "Weather Cloudy:Sky Day Color", + "Weather Cloudy:Sky Sunset Color", + "Weather Cloudy:Sky Night Color", + "Weather Cloudy:Fog Sunrise Color", + "Weather Cloudy:Fog Day Color", + "Weather Cloudy:Fog Sunset Color", + "Weather Cloudy:Fog Night Color", + "Weather Cloudy:Ambient Sunrise Color", + "Weather Cloudy:Ambient Day Color", + "Weather Cloudy:Ambient Sunset Color", + "Weather Cloudy:Ambient Night Color", + "Weather Cloudy:Sun Sunrise Color", + "Weather Cloudy:Sun Day Color", + "Weather Cloudy:Sun Sunset Color", + "Weather Cloudy:Sun Night Color", + "Weather Cloudy:Sun Disc Sunset Color", + "Weather Cloudy:Land Fog Day Depth", + "Weather Cloudy:Land Fog Night Depth", + "Weather Cloudy:Wind Speed", + "Weather Cloudy:Cloud Speed", + "Weather Cloudy:Glare View", + "Weather Cloudy:Ambient Loop Sound ID", + + "Weather Foggy:Cloud Texture", + "Weather Foggy:Clouds Maximum Percent", + "Weather Foggy:Transition Delta", + "Weather Foggy:Sky Sunrise Color", + "Weather Foggy:Sky Day Color", + "Weather Foggy:Sky Sunset Color", + "Weather Foggy:Sky Night Color", + "Weather Foggy:Fog Sunrise Color", + "Weather Foggy:Fog Day Color", + "Weather Foggy:Fog Sunset Color", + "Weather Foggy:Fog Night Color", + "Weather Foggy:Ambient Sunrise Color", + "Weather Foggy:Ambient Day Color", + "Weather Foggy:Ambient Sunset Color", + "Weather Foggy:Ambient Night Color", + "Weather Foggy:Sun Sunrise Color", + "Weather Foggy:Sun Day Color", + "Weather Foggy:Sun Sunset Color", + "Weather Foggy:Sun Night Color", + "Weather Foggy:Sun Disc Sunset Color", + "Weather Foggy:Land Fog Day Depth", + "Weather Foggy:Land Fog Night Depth", + "Weather Foggy:Wind Speed", + "Weather Foggy:Cloud Speed", + "Weather Foggy:Glare View", + "Weather Foggy:Ambient Loop Sound ID", + + "Weather Thunderstorm:Cloud Texture", + "Weather Thunderstorm:Clouds Maximum Percent", + "Weather Thunderstorm:Transition Delta", + "Weather Thunderstorm:Sky Sunrise Color", + "Weather Thunderstorm:Sky Day Color", + "Weather Thunderstorm:Sky Sunset Color", + "Weather Thunderstorm:Sky Night Color", + "Weather Thunderstorm:Fog Sunrise Color", + "Weather Thunderstorm:Fog Day Color", + "Weather Thunderstorm:Fog Sunset Color", + "Weather Thunderstorm:Fog Night Color", + "Weather Thunderstorm:Ambient Sunrise Color", + "Weather Thunderstorm:Ambient Day Color", + "Weather Thunderstorm:Ambient Sunset Color", + "Weather Thunderstorm:Ambient Night Color", + "Weather Thunderstorm:Sun Sunrise Color", + "Weather Thunderstorm:Sun Day Color", + "Weather Thunderstorm:Sun Sunset Color", + "Weather Thunderstorm:Sun Night Color", + "Weather Thunderstorm:Sun Disc Sunset Color", + "Weather Thunderstorm:Land Fog Day Depth", + "Weather Thunderstorm:Land Fog Night Depth", + "Weather Thunderstorm:Wind Speed", + "Weather Thunderstorm:Cloud Speed", + "Weather Thunderstorm:Glare View", + "Weather Thunderstorm:Rain Loop Sound ID", + "Weather Thunderstorm:Using Precip", + "Weather Thunderstorm:Rain Diameter", + "Weather Thunderstorm:Rain Height Min", + "Weather Thunderstorm:Rain Height Max", + "Weather Thunderstorm:Rain Threshold", + "Weather Thunderstorm:Max Raindrops", + "Weather Thunderstorm:Rain Entrance Speed", + "Weather Thunderstorm:Ambient Loop Sound ID", + "Weather Thunderstorm:Flash Decrement", + + "Weather Rain:Cloud Texture", + "Weather Rain:Clouds Maximum Percent", + "Weather Rain:Transition Delta", + "Weather Rain:Sky Sunrise Color", + "Weather Rain:Sky Day Color", + "Weather Rain:Sky Sunset Color", + "Weather Rain:Sky Night Color", + "Weather Rain:Fog Sunrise Color", + "Weather Rain:Fog Day Color", + "Weather Rain:Fog Sunset Color", + "Weather Rain:Fog Night Color", + "Weather Rain:Ambient Sunrise Color", + "Weather Rain:Ambient Day Color", + "Weather Rain:Ambient Sunset Color", + "Weather Rain:Ambient Night Color", + "Weather Rain:Sun Sunrise Color", + "Weather Rain:Sun Day Color", + "Weather Rain:Sun Sunset Color", + "Weather Rain:Sun Night Color", + "Weather Rain:Sun Disc Sunset Color", + "Weather Rain:Land Fog Day Depth", + "Weather Rain:Land Fog Night Depth", + "Weather Rain:Wind Speed", + "Weather Rain:Cloud Speed", + "Weather Rain:Glare View", + "Weather Rain:Rain Loop Sound ID", + "Weather Rain:Using Precip", + "Weather Rain:Rain Diameter", + "Weather Rain:Rain Height Min", + "Weather Rain:Rain Height Max", + "Weather Rain:Rain Threshold", + "Weather Rain:Rain Entrance Speed", + "Weather Rain:Ambient Loop Sound ID", + "Weather Rain:Max Raindrops", + + "Weather Overcast:Cloud Texture", + "Weather Overcast:Clouds Maximum Percent", + "Weather Overcast:Transition Delta", + "Weather Overcast:Sky Sunrise Color", + "Weather Overcast:Sky Day Color", + "Weather Overcast:Sky Sunset Color", + "Weather Overcast:Sky Night Color", + "Weather Overcast:Fog Sunrise Color", + "Weather Overcast:Fog Day Color", + "Weather Overcast:Fog Sunset Color", + "Weather Overcast:Fog Night Color", + "Weather Overcast:Ambient Sunrise Color", + "Weather Overcast:Ambient Day Color", + "Weather Overcast:Ambient Sunset Color", + "Weather Overcast:Ambient Night Color", + "Weather Overcast:Sun Sunrise Color", + "Weather Overcast:Sun Day Color", + "Weather Overcast:Sun Sunset Color", + "Weather Overcast:Sun Night Color", + "Weather Overcast:Sun Disc Sunset Color", + "Weather Overcast:Land Fog Day Depth", + "Weather Overcast:Land Fog Night Depth", + "Weather Overcast:Wind Speed", + "Weather Overcast:Cloud Speed", + "Weather Overcast:Glare View", + "Weather Overcast:Ambient Loop Sound ID", + + "Weather Ashstorm:Cloud Texture", + "Weather Ashstorm:Clouds Maximum Percent", + "Weather Ashstorm:Transition Delta", + "Weather Ashstorm:Sky Sunrise Color", + "Weather Ashstorm:Sky Day Color", + "Weather Ashstorm:Sky Sunset Color", + "Weather Ashstorm:Sky Night Color", + "Weather Ashstorm:Fog Sunrise Color", + "Weather Ashstorm:Fog Day Color", + "Weather Ashstorm:Fog Sunset Color", + "Weather Ashstorm:Fog Night Color", + "Weather Ashstorm:Ambient Sunrise Color", + "Weather Ashstorm:Ambient Day Color", + "Weather Ashstorm:Ambient Sunset Color", + "Weather Ashstorm:Ambient Night Color", + "Weather Ashstorm:Sun Sunrise Color", + "Weather Ashstorm:Sun Day Color", + "Weather Ashstorm:Sun Sunset Color", + "Weather Ashstorm:Sun Night Color", + "Weather Ashstorm:Sun Disc Sunset Color", + "Weather Ashstorm:Land Fog Day Depth", + "Weather Ashstorm:Land Fog Night Depth", + "Weather Ashstorm:Wind Speed", + "Weather Ashstorm:Cloud Speed", + "Weather Ashstorm:Glare View", + "Weather Ashstorm:Ambient Loop Sound ID", + "Weather Ashstorm:Storm Threshold", + + "Weather Blight:Cloud Texture", + "Weather Blight:Clouds Maximum Percent", + "Weather Blight:Transition Delta", + "Weather Blight:Sky Sunrise Color", + "Weather Blight:Sky Day Color", + "Weather Blight:Sky Sunset Color", + "Weather Blight:Sky Night Color", + "Weather Blight:Fog Sunrise Color", + "Weather Blight:Fog Day Color", + "Weather Blight:Fog Sunset Color", + "Weather Blight:Fog Night Color", + "Weather Blight:Ambient Sunrise Color", + "Weather Blight:Ambient Day Color", + "Weather Blight:Ambient Sunset Color", + "Weather Blight:Ambient Night Color", + "Weather Blight:Sun Sunrise Color", + "Weather Blight:Sun Day Color", + "Weather Blight:Sun Sunset Color", + "Weather Blight:Sun Night Color", + "Weather Blight:Sun Disc Sunset Color", + "Weather Blight:Land Fog Day Depth", + "Weather Blight:Land Fog Night Depth", + "Weather Blight:Wind Speed", + "Weather Blight:Cloud Speed", + "Weather Blight:Glare View", + "Weather Blight:Ambient Loop Sound ID", + "Weather Blight:Storm Threshold", + "Weather Blight:Disease Chance", + + // for Bloodmoon + "Weather Snow:Cloud Texture", + "Weather Snow:Clouds Maximum Percent", + "Weather Snow:Transition Delta", + "Weather Snow:Sky Sunrise Color", + "Weather Snow:Sky Day Color", + "Weather Snow:Sky Sunset Color", + "Weather Snow:Sky Night Color", + "Weather Snow:Fog Sunrise Color", + "Weather Snow:Fog Day Color", + "Weather Snow:Fog Sunset Color", + "Weather Snow:Fog Night Color", + "Weather Snow:Ambient Sunrise Color", + "Weather Snow:Ambient Day Color", + "Weather Snow:Ambient Sunset Color", + "Weather Snow:Ambient Night Color", + "Weather Snow:Sun Sunrise Color", + "Weather Snow:Sun Day Color", + "Weather Snow:Sun Sunset Color", + "Weather Snow:Sun Night Color", + "Weather Snow:Sun Disc Sunset Color", + "Weather Snow:Land Fog Day Depth", + "Weather Snow:Land Fog Night Depth", + "Weather Snow:Wind Speed", + "Weather Snow:Cloud Speed", + "Weather Snow:Glare View", + "Weather Snow:Snow Diameter", + "Weather Snow:Snow Height Min", + "Weather Snow:Snow Height Max", + "Weather Snow:Snow Entrance Speed", + "Weather Snow:Max Snowflakes", + "Weather Snow:Ambient Loop Sound ID", + "Weather Snow:Snow Threshold", + + // for Bloodmoon + "Weather Blizzard:Cloud Texture", + "Weather Blizzard:Clouds Maximum Percent", + "Weather Blizzard:Transition Delta", + "Weather Blizzard:Sky Sunrise Color", + "Weather Blizzard:Sky Day Color", + "Weather Blizzard:Sky Sunset Color", + "Weather Blizzard:Sky Night Color", + "Weather Blizzard:Fog Sunrise Color", + "Weather Blizzard:Fog Day Color", + "Weather Blizzard:Fog Sunset Color", + "Weather Blizzard:Fog Night Color", + "Weather Blizzard:Ambient Sunrise Color", + "Weather Blizzard:Ambient Day Color", + "Weather Blizzard:Ambient Sunset Color", + "Weather Blizzard:Ambient Night Color", + "Weather Blizzard:Sun Sunrise Color", + "Weather Blizzard:Sun Day Color", + "Weather Blizzard:Sun Sunset Color", + "Weather Blizzard:Sun Night Color", + "Weather Blizzard:Sun Disc Sunset Color", + "Weather Blizzard:Land Fog Day Depth", + "Weather Blizzard:Land Fog Night Depth", + "Weather Blizzard:Wind Speed", + "Weather Blizzard:Cloud Speed", + "Weather Blizzard:Glare View", + "Weather Blizzard:Ambient Loop Sound ID", + "Weather Blizzard:Storm Threshold", + + // moons + "Moons:Secunda Size", + "Moons:Secunda Axis Offset", + "Moons:Secunda Speed", + "Moons:Secunda Daily Increment", + "Moons:Secunda Moon Shadow Early Fade Angle", + "Moons:Secunda Fade Start Angle", + "Moons:Secunda Fade End Angle", + "Moons:Secunda Fade In Start", + "Moons:Secunda Fade In Finish", + "Moons:Secunda Fade Out Start", + "Moons:Secunda Fade Out Finish", + "Moons:Masser Size", + "Moons:Masser Axis Offset", + "Moons:Masser Speed", + "Moons:Masser Daily Increment", + "Moons:Masser Moon Shadow Early Fade Angle", + "Moons:Masser Fade Start Angle", + "Moons:Masser Fade End Angle", + "Moons:Masser Fade In Start", + "Moons:Masser Fade In Finish", + "Moons:Masser Fade Out Start", + "Moons:Masser Fade Out Finish", + "Moons:Script Color", + 0 }; @@ -52,10 +653,21 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::string line; while (std::getline(file, line)) { + line = toUTF8(line); + + // unify Unix-style and Windows file ending + if (!(line.empty()) && (line[line.length()-1]) == '\r') { + line = line.substr(0, line.length()-1); + } + if(line[0] == '[') { - if(line.length() > 2) { - section = line.substr(1, line.length()-3); + int pos = line.find(']'); + if(pos < 2) { + std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl; + continue; } + + section = line.substr(1, line.find(']')-1); continue; } @@ -147,7 +759,7 @@ void MwIniImporter::mergeFallback(multistrmap &cfg, multistrmap &ini) { std::string value(*it); std::replace( value.begin(), value.end(), ' ', '_' ); std::replace( value.begin(), value.end(), ':', '_' ); - value.append(",").append(vc->substr(0,vc->length()-1)); + value.append(",").append(vc->substr(0,vc->length())); insertMultistrmap(cfg, "fallback", value); } } @@ -216,3 +828,16 @@ void MwIniImporter::writeToFile(boost::iostreams::stream #include +#include "../../components/to_utf8/to_utf8.hpp" + class MwIniImporter { public: typedef std::map strmap; typedef std::map > multistrmap; MwIniImporter(); + void setInputEncoding(const ToUTF8::FromType& encoding); void setVerbose(bool verbose); multistrmap loadIniFile(std::string filename); multistrmap loadCfgFile(std::string filename); @@ -25,9 +28,11 @@ class MwIniImporter { private: void insertMultistrmap(multistrmap &cfg, std::string key, std::string value); std::string numberToString(int n); + std::string toUTF8(const std::string &str); bool mVerbose; strmap mMergeMap; std::vector mMergeFallback; + ToUTF8::FromType mEncoding; }; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 234d7d57d..e90f26dd2 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -18,6 +18,11 @@ int main(int argc, char *argv[]) { ("cfg,c", bpo::value(), "openmw.cfg file") ("output,o", bpo::value()->default_value(""), "openmw.cfg file") ("game-files,g", "import esm and esp files") + ("encoding,e", bpo::value()-> default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") ; p_desc.add("ini", 1).add("cfg", 1); @@ -57,6 +62,10 @@ int main(int argc, char *argv[]) { MwIniImporter importer; importer.setVerbose(vm.count("verbose")); + // Font encoding settings + std::string encoding(vm["encoding"].as()); + importer.setInputEncoding(ToUTF8::calculateEncoding(encoding)); + MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt new file mode 100644 index 000000000..abbc953ca --- /dev/null +++ b/apps/opencs/CMakeLists.txt @@ -0,0 +1,76 @@ + +set (OPENCS_SRC + main.cpp editor.cpp + + model/doc/documentmanager.cpp model/doc/document.cpp + + model/world/universalid.cpp model/world/idcollection.cpp model/world/data.cpp model/world/idtable.cpp + model/world/commands.cpp model/world/idtableproxymodel.cpp model/world/record.cpp + model/world/columnbase.cpp + + model/tools/tools.cpp model/tools/operation.cpp model/tools/stage.cpp model/tools/verifier.cpp + model/tools/mandatoryid.cpp model/tools/reportmodel.cpp + + view/doc/viewmanager.cpp view/doc/view.cpp view/doc/operations.cpp view/doc/operation.cpp view/doc/subviewfactory.cpp + view/doc/subview.cpp + + view/world/table.cpp view/world/tablesubview.cpp view/world/subviews.cpp view/world/util.cpp + view/world/dialoguesubview.cpp + + view/tools/reportsubview.cpp view/tools/subviews.cpp + ) + +set (OPENCS_HDR + editor.hpp + + model/doc/documentmanager.hpp model/doc/document.hpp model/doc/state.hpp + + model/world/universalid.hpp model/world/record.hpp model/world/idcollection.hpp model/world/data.hpp + model/world/idtable.hpp model/world/columns.hpp model/world/idtableproxymodel.hpp + model/world/commands.hpp model/world/columnbase.hpp + + model/tools/tools.hpp model/tools/operation.hpp model/tools/stage.hpp model/tools/verifier.hpp + model/tools/mandatoryid.hpp model/tools/reportmodel.hpp + + view/doc/viewmanager.hpp view/doc/view.hpp view/doc/operations.hpp view/doc/operation.hpp view/doc/subviewfactory.hpp + view/doc/subview.hpp view/doc/subviewfactoryimp.hpp + + view/world/table.hpp view/world/tablesubview.hpp view/world/subviews.hpp view/world/util.hpp + view/world/dialoguesubview.hpp + + view/tools/reportsubview.hpp view/tools/subviews.hpp + ) + +set (OPENCS_US + ) + +set (OPENCS_RES + ) + +source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR}) + +if(WIN32) + set(QT_USE_QTMAIN TRUE) +endif(WIN32) + +find_package(Qt4 COMPONENTS QtCore QtGui QtXml QtXmlPatterns REQUIRED) +include(${QT_USE_FILE}) + +qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) +qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR}) +qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(opencs + ${OPENCS_SRC} + ${OPENCS_UI_HDR} + ${OPENCS_MOC_SRC} + ${OPENCS_RES_SRC} +) + +target_link_libraries(opencs + ${Boost_LIBRARIES} + ${QT_LIBRARIES} + components +) \ No newline at end of file diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp new file mode 100644 index 000000000..1632ed220 --- /dev/null +++ b/apps/opencs/editor.cpp @@ -0,0 +1,49 @@ + +#include "editor.hpp" + +#include + +#include + +#include "model/doc/document.hpp" +#include "model/world/data.hpp" + +CS::Editor::Editor() : mViewManager (mDocumentManager), mNewDocumentIndex (0) +{ + connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ())); +} + +void CS::Editor::createDocument() +{ + std::ostringstream stream; + + stream << "NewDocument" << (++mNewDocumentIndex); + + CSMDoc::Document *document = mDocumentManager.addDocument (stream.str()); + + static const char *sGlobals[] = + { + "Day", "DaysPassed", "GameHour", "Month", "PCRace", "PCVampire", "PCWerewolf", "PCYear", 0 + }; + + for (int i=0; sGlobals[i]; ++i) + { + ESM::Global record; + record.mId = sGlobals[i]; + record.mValue = i==0 ? 1 : 0; + record.mType = ESM::VT_Float; + document->getData().getGlobals().add (record); + } + + document->getData().merge(); /// \todo remove once proper ESX loading is implemented + + mViewManager.addView (document); +} + +int CS::Editor::run() +{ + /// \todo Instead of creating an empty document, open a small welcome dialogue window with buttons for new/load/recent projects + createDocument(); + + return QApplication::exec(); +} \ No newline at end of file diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp new file mode 100644 index 000000000..60f7beaf1 --- /dev/null +++ b/apps/opencs/editor.hpp @@ -0,0 +1,37 @@ +#ifndef CS_EDITOR_H +#define CS_EDITOR_H + +#include + +#include "model/doc/documentmanager.hpp" +#include "view/doc/viewmanager.hpp" + +namespace CS +{ + class Editor : public QObject + { + Q_OBJECT + + int mNewDocumentIndex; ///< \todo remove when the proper new document dialogue is implemented. + + CSMDoc::DocumentManager mDocumentManager; + CSVDoc::ViewManager mViewManager; + + // not implemented + Editor (const Editor&); + Editor& operator= (const Editor&); + + public: + + Editor(); + + int run(); + ///< \return error status + + public slots: + + void createDocument(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp new file mode 100644 index 000000000..4b1a688c2 --- /dev/null +++ b/apps/opencs/main.cpp @@ -0,0 +1,39 @@ + +#include "editor.hpp" + +#include +#include + +#include + +class Application : public QApplication +{ + private: + + bool notify (QObject *receiver, QEvent *event) + { + try + { + return QApplication::notify (receiver, event); + } + catch (const std::exception& exception) + { + std::cerr << "An exception has been caught: " << exception.what() << std::endl; + } + + return false; + } + + public: + + Application (int& argc, char *argv[]) : QApplication (argc, argv) {} +}; + +int main(int argc, char *argv[]) +{ + Application mApplication (argc, argv); + + CS::Editor editor; + + return editor.run(); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp new file mode 100644 index 000000000..9d2694483 --- /dev/null +++ b/apps/opencs/model/doc/document.cpp @@ -0,0 +1,114 @@ + +#include "document.hpp" + +CSMDoc::Document::Document (const std::string& name) +: mTools (mData) +{ + mName = name; ///< \todo replace with ESX list + + connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); + + connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); + connect (&mTools, SIGNAL (done (int)), this, SLOT (operationDone (int))); + + // dummy implementation -> remove when proper save is implemented. + mSaveCount = 0; + connect (&mSaveTimer, SIGNAL(timeout()), this, SLOT (saving())); +} + +QUndoStack& CSMDoc::Document::getUndoStack() +{ + return mUndoStack; +} + +int CSMDoc::Document::getState() const +{ + int state = 0; + + if (!mUndoStack.isClean()) + state |= State_Modified; + + if (mSaveCount) + state |= State_Locked | State_Saving | State_Operation; + + if (int operations = mTools.getRunningOperations()) + state |= State_Locked | State_Operation | operations; + + return state; +} + +const std::string& CSMDoc::Document::getName() const +{ + return mName; +} + +void CSMDoc::Document::save() +{ + mSaveCount = 1; + mSaveTimer.start (500); + emit stateChanged (getState(), this); + emit progress (1, 16, State_Saving, 1, this); +} + +CSMWorld::UniversalId CSMDoc::Document::verify() +{ + CSMWorld::UniversalId id = mTools.runVerifier(); + emit stateChanged (getState(), this); + return id; +} + +void CSMDoc::Document::abortOperation (int type) +{ + mTools.abortOperation (type); + + if (type==State_Saving) + { + mSaveTimer.stop(); + emit stateChanged (getState(), this); + } +} + +void CSMDoc::Document::modificationStateChanged (bool clean) +{ + emit stateChanged (getState(), this); +} + +void CSMDoc::Document::operationDone (int type) +{ + emit stateChanged (getState(), this); +} + +void CSMDoc::Document::saving() +{ + ++mSaveCount; + + emit progress (mSaveCount, 16, State_Saving, 1, this); + + if (mSaveCount>15) + { + mSaveCount = 0; + mSaveTimer.stop(); + mUndoStack.setClean(); + emit stateChanged (getState(), this); + } +} + +const CSMWorld::Data& CSMDoc::Document::getData() const +{ + return mData; +} + +CSMWorld::Data& CSMDoc::Document::getData() +{ + return mData; +} + +CSMTools::ReportModel *CSMDoc::Document::getReport (const CSMWorld::UniversalId& id) +{ + return mTools.getReport (id); +} + +void CSMDoc::Document::progress (int current, int max, int type) +{ + emit progress (current, max, type, 1, this); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp new file mode 100644 index 000000000..43e8bba37 --- /dev/null +++ b/apps/opencs/model/doc/document.hpp @@ -0,0 +1,87 @@ +#ifndef CSM_DOC_DOCUMENT_H +#define CSM_DOC_DOCUMENT_H + +#include + +#include +#include +#include + +#include "../world/data.hpp" + +#include "../tools/tools.hpp" + +#include "state.hpp" + +class QAbstractItemModel; + +namespace CSMDoc +{ + class Document : public QObject + { + Q_OBJECT + + private: + + std::string mName; ///< \todo replace name with ESX list + CSMWorld::Data mData; + CSMTools::Tools mTools; + + // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is + // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. + QUndoStack mUndoStack; + + int mSaveCount; ///< dummy implementation -> remove when proper save is implemented. + QTimer mSaveTimer; ///< dummy implementation -> remove when proper save is implemented. + + // not implemented + Document (const Document&); + Document& operator= (const Document&); + + public: + + Document (const std::string& name); + ///< \todo replace name with ESX list + + QUndoStack& getUndoStack(); + + int getState() const; + + const std::string& getName() const; + ///< \todo replace with ESX list + + void save(); + + CSMWorld::UniversalId verify(); + + void abortOperation (int type); + + const CSMWorld::Data& getData() const; + + CSMWorld::Data& getData(); + + CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id); + ///< The ownership of the returned report is not transferred. + + signals: + + void stateChanged (int state, CSMDoc::Document *document); + + void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + + private slots: + + void modificationStateChanged (bool clean); + + void operationDone (int type); + + void saving(); + ///< dummy implementation -> remove when proper save is implemented. + + public slots: + + void progress (int current, int max, int type); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp new file mode 100644 index 000000000..8ae2764f2 --- /dev/null +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -0,0 +1,37 @@ + +#include "documentmanager.hpp" + +#include +#include + +#include "document.hpp" + +CSMDoc::DocumentManager::DocumentManager() {} + +CSMDoc::DocumentManager::~DocumentManager() +{ + for (std::vector::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter) + delete *iter; +} + +CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::string& name) +{ + Document *document = new Document (name); + + mDocuments.push_back (document); + + return document; +} + +bool CSMDoc::DocumentManager::removeDocument (Document *document) +{ + std::vector::iterator iter = std::find (mDocuments.begin(), mDocuments.end(), document); + + if (iter==mDocuments.end()) + throw std::runtime_error ("removing invalid document"); + + mDocuments.erase (iter); + delete document; + + return mDocuments.empty(); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp new file mode 100644 index 000000000..730c7fae1 --- /dev/null +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -0,0 +1,32 @@ +#ifndef CSM_DOC_DOCUMENTMGR_H +#define CSM_DOC_DOCUMENTMGR_H + +#include +#include + +namespace CSMDoc +{ + class Document; + + class DocumentManager + { + std::vector mDocuments; + + DocumentManager (const DocumentManager&); + DocumentManager& operator= (const DocumentManager&); + + public: + + DocumentManager(); + + ~DocumentManager(); + + Document *addDocument (const std::string& name); + ///< The ownership of the returned document is not transferred to the caller. + + bool removeDocument (Document *document); + ///< \return last document removed? + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp new file mode 100644 index 000000000..04e6fae89 --- /dev/null +++ b/apps/opencs/model/doc/state.hpp @@ -0,0 +1,19 @@ +#ifndef CSM_DOC_STATE_H +#define CSM_DOC_STATE_H + +namespace CSMDoc +{ + enum State + { + State_Modified = 1, + State_Locked = 2, + State_Operation = 4, + + State_Saving = 8, + State_Verifying = 16, + State_Compiling = 32, // not implemented yet + State_Searching = 64 // not implemented yet + }; +} + +#endif diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp new file mode 100644 index 000000000..f9f2ca378 --- /dev/null +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -0,0 +1,21 @@ + +#include "mandatoryid.hpp" + +#include "../world/idcollection.hpp" + +CSMTools::MandatoryIdStage::MandatoryIdStage (const CSMWorld::IdCollectionBase& idCollection, + const CSMWorld::UniversalId& collectionId, const std::vector& ids) +: mIdCollection (idCollection), mCollectionId (collectionId), mIds (ids) +{} + +int CSMTools::MandatoryIdStage::setup() +{ + return mIds.size(); +} + +void CSMTools::MandatoryIdStage::perform (int stage, std::vector& messages) +{ + if (mIdCollection.searchId (mIds.at (stage))==-1 || + mIdCollection.getRecord (mIds.at (stage)).isDeleted()) + messages.push_back (mCollectionId.toString() + "|Missing mandatory record: " + mIds.at (stage)); +} \ No newline at end of file diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp new file mode 100644 index 000000000..14fcec204 --- /dev/null +++ b/apps/opencs/model/tools/mandatoryid.hpp @@ -0,0 +1,38 @@ +#ifndef CSM_TOOLS_MANDATORYID_H +#define CSM_TOOLS_MANDATORYID_H + +#include +#include + +#include "../world/universalid.hpp" + +#include "stage.hpp" + +namespace CSMWorld +{ + class IdCollectionBase; +} + +namespace CSMTools +{ + /// \brief Verify stage: make sure that records with specific IDs exist. + class MandatoryIdStage : public Stage + { + const CSMWorld::IdCollectionBase& mIdCollection; + CSMWorld::UniversalId mCollectionId; + std::vector mIds; + + public: + + MandatoryIdStage (const CSMWorld::IdCollectionBase& idCollection, const CSMWorld::UniversalId& collectionId, + const std::vector& ids); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/operation.cpp b/apps/opencs/model/tools/operation.cpp new file mode 100644 index 000000000..71761cdae --- /dev/null +++ b/apps/opencs/model/tools/operation.cpp @@ -0,0 +1,84 @@ + +#include "operation.hpp" + +#include +#include + +#include + +#include "../doc/state.hpp" + +#include "stage.hpp" + +void CSMTools::Operation::prepareStages() +{ + mCurrentStage = mStages.begin(); + mCurrentStep = 0; + mCurrentStepTotal = 0; + mTotalSteps = 0; + + for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) + { + iter->second = iter->first->setup(); + mTotalSteps += iter->second; + } +} + +CSMTools::Operation::Operation (int type) : mType (type) {} + +CSMTools::Operation::~Operation() +{ + for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) + delete iter->first; +} + +void CSMTools::Operation::run() +{ + prepareStages(); + + QTimer timer; + + timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify())); + + timer.start (0); + + exec(); +} + +void CSMTools::Operation::appendStage (Stage *stage) +{ + mStages.push_back (std::make_pair (stage, 0)); +} + +void CSMTools::Operation::abort() +{ + exit(); +} + +void CSMTools::Operation::verify() +{ + std::vector messages; + + while (mCurrentStage!=mStages.end()) + { + if (mCurrentStep>=mCurrentStage->second) + { + mCurrentStep = 0; + ++mCurrentStage; + } + else + { + mCurrentStage->first->perform (mCurrentStep++, messages); + ++mCurrentStepTotal; + break; + } + } + + emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); + + for (std::vector::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter) + emit reportMessage (iter->c_str(), mType); + + if (mCurrentStage==mStages.end()) + exit(); +} \ No newline at end of file diff --git a/apps/opencs/model/tools/operation.hpp b/apps/opencs/model/tools/operation.hpp new file mode 100644 index 000000000..4731c58fa --- /dev/null +++ b/apps/opencs/model/tools/operation.hpp @@ -0,0 +1,54 @@ +#ifndef CSM_TOOLS_OPERATION_H +#define CSM_TOOLS_OPERATION_H + +#include + +#include + +namespace CSMTools +{ + class Stage; + + class Operation : public QThread + { + Q_OBJECT + + int mType; + std::vector > mStages; // stage, number of steps + std::vector >::iterator mCurrentStage; + int mCurrentStep; + int mCurrentStepTotal; + int mTotalSteps; + + void prepareStages(); + + public: + + Operation (int type); + + virtual ~Operation(); + + virtual void run(); + + void appendStage (Stage *stage); + ///< The ownership of \a stage is transferred to *this. + /// + /// \attention Do no call this function while this Operation is running. + + signals: + + void progress (int current, int max, int type); + + void reportMessage (const QString& message, int type); + + public slots: + + void abort(); + + private slots: + + void verify(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp new file mode 100644 index 000000000..b12531875 --- /dev/null +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -0,0 +1,71 @@ + +#include "reportmodel.hpp" + +#include + +int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return mRows.size(); +} + +int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return 2; +} + +QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const +{ + if (role!=Qt::DisplayRole) + return QVariant(); + + if (index.column()==0) + return static_cast (mRows.at (index.row()).first.getType()); + else + return mRows.at (index.row()).second.c_str(); +} + +QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (role!=Qt::DisplayRole) + return QVariant(); + + if (orientation==Qt::Vertical) + return QVariant(); + + return tr (section==0 ? "Type" : "Description"); +} + +bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent) +{ + if (parent.isValid()) + return false; + + mRows.erase (mRows.begin()+row, mRows.begin()+row+count); + + return true; +} + +void CSMTools::ReportModel::add (const std::string& row) +{ + std::string::size_type index = row.find ('|'); + + if (index==std::string::npos) + throw std::logic_error ("invalid report message"); + + beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); + + mRows.push_back (std::make_pair (row.substr (0, index), row.substr (index+1))); + + endInsertRows(); +} + +const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const +{ + return mRows.at (row).first; +} \ No newline at end of file diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp new file mode 100644 index 000000000..55c25d907 --- /dev/null +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -0,0 +1,37 @@ +#ifndef CSM_TOOLS_REPORTMODEL_H +#define CSM_TOOLS_REPORTMODEL_H + +#include +#include + +#include + +#include "../world/universalid.hpp" + +namespace CSMTools +{ + class ReportModel : public QAbstractTableModel + { + Q_OBJECT + + std::vector > mRows; + + public: + + virtual int rowCount (const QModelIndex & parent = QModelIndex()) const; + + virtual int columnCount (const QModelIndex & parent = QModelIndex()) const; + + virtual QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + + virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); + + void add (const std::string& row); + + const CSMWorld::UniversalId& getUniversalId (int row) const; + }; +} + +#endif diff --git a/apps/opencs/model/tools/stage.cpp b/apps/opencs/model/tools/stage.cpp new file mode 100644 index 000000000..6f4567e57 --- /dev/null +++ b/apps/opencs/model/tools/stage.cpp @@ -0,0 +1,4 @@ + +#include "stage.hpp" + +CSMTools::Stage::~Stage() {} \ No newline at end of file diff --git a/apps/opencs/model/tools/stage.hpp b/apps/opencs/model/tools/stage.hpp new file mode 100644 index 000000000..3020936f3 --- /dev/null +++ b/apps/opencs/model/tools/stage.hpp @@ -0,0 +1,24 @@ +#ifndef CSM_TOOLS_STAGE_H +#define CSM_TOOLS_STAGE_H + +#include +#include + +namespace CSMTools +{ + class Stage + { + public: + + virtual ~Stage(); + + virtual int setup() = 0; + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages) = 0; + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif + diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp new file mode 100644 index 000000000..8dd1c0fe6 --- /dev/null +++ b/apps/opencs/model/tools/tools.cpp @@ -0,0 +1,123 @@ + +#include "tools.hpp" + +#include + +#include "verifier.hpp" + +#include "../doc/state.hpp" + +#include "../world/data.hpp" +#include "../world/universalid.hpp" + +#include "reportmodel.hpp" +#include "mandatoryid.hpp" + +CSMTools::Operation *CSMTools::Tools::get (int type) +{ + switch (type) + { + case CSMDoc::State_Verifying: return mVerifier; + } + + return 0; +} + +const CSMTools::Operation *CSMTools::Tools::get (int type) const +{ + return const_cast (this)->get (type); +} + +CSMTools::Verifier *CSMTools::Tools::getVerifier() +{ + if (!mVerifier) + { + mVerifier = new Verifier; + + connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (mVerifier, SIGNAL (finished()), this, SLOT (verifierDone())); + connect (mVerifier, SIGNAL (reportMessage (const QString&, int)), + this, SLOT (verifierMessage (const QString&, int))); + + std::vector mandatoryIds; // I want C++11, damn it! + mandatoryIds.push_back ("Day"); + mandatoryIds.push_back ("DaysPassed"); + mandatoryIds.push_back ("GameHour"); + mandatoryIds.push_back ("Month"); + mandatoryIds.push_back ("PCRace"); + mandatoryIds.push_back ("PCVampire"); + mandatoryIds.push_back ("PCWerewolf"); + mandatoryIds.push_back ("PCYear"); + + mVerifier->appendStage (new MandatoryIdStage (mData.getGlobals(), + CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); + } + + return mVerifier; +} + +CSMTools::Tools::Tools (CSMWorld::Data& data) : mData (data), mVerifier (0), mNextReportNumber (0) +{ + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) + delete iter->second; +} + +CSMTools::Tools::~Tools() +{ + delete mVerifier; +} + +CSMWorld::UniversalId CSMTools::Tools::runVerifier() +{ + mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); + mActiveReports[CSMDoc::State_Verifying] = mNextReportNumber-1; + + getVerifier()->start(); + + return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, mNextReportNumber-1); +} + +void CSMTools::Tools::abortOperation (int type) +{ + if (Operation *operation = get (type)) + operation->abort(); +} + +int CSMTools::Tools::getRunningOperations() const +{ + static const int sOperations[] = + { + CSMDoc::State_Verifying, + -1 + }; + + int result = 0; + + for (int i=0; sOperations[i]!=-1; ++i) + if (const Operation *operation = get (sOperations[i])) + if (operation->isRunning()) + result |= sOperations[i]; + + return result; +} + +CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& id) +{ + if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults) + throw std::logic_error ("invalid request for report model: " + id.toString()); + + return mReports.at (id.getIndex()); +} + +void CSMTools::Tools::verifierDone() +{ + emit done (CSMDoc::State_Verifying); +} + +void CSMTools::Tools::verifierMessage (const QString& message, int type) +{ + std::map::iterator iter = mActiveReports.find (type); + + if (iter!=mActiveReports.end()) + mReports[iter->second]->add (message.toStdString()); +} \ No newline at end of file diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp new file mode 100644 index 000000000..652345c6d --- /dev/null +++ b/apps/opencs/model/tools/tools.hpp @@ -0,0 +1,73 @@ +#ifndef CSM_TOOLS_TOOLS_H +#define CSM_TOOLS_TOOLS_H + +#include + +#include + +namespace CSMWorld +{ + class Data; + class UniversalId; +} + +namespace CSMTools +{ + class Verifier; + class Operation; + class ReportModel; + + class Tools : public QObject + { + Q_OBJECT + + CSMWorld::Data& mData; + Verifier *mVerifier; + std::map mReports; + int mNextReportNumber; + std::map mActiveReports; // type, report number + + // not implemented + Tools (const Tools&); + Tools& operator= (const Tools&); + + Verifier *getVerifier(); + + Operation *get (int type); + ///< Returns a 0-pointer, if operation hasn't been used yet. + + const Operation *get (int type) const; + ///< Returns a 0-pointer, if operation hasn't been used yet. + + public: + + Tools (CSMWorld::Data& data); + + virtual ~Tools(); + + CSMWorld::UniversalId runVerifier(); + ///< \return ID of the report for this verification run + + void abortOperation (int type); + ///< \attention The operation is not aborted immediately. + + int getRunningOperations() const; + + ReportModel *getReport (const CSMWorld::UniversalId& id); + ///< The ownership of the returned report is not transferred. + + private slots: + + void verifierDone(); + + void verifierMessage (const QString& message, int type); + + signals: + + void progress (int current, int max, int type); + + void done (int type); + }; +} + +#endif diff --git a/apps/opencs/model/tools/verifier.cpp b/apps/opencs/model/tools/verifier.cpp new file mode 100644 index 000000000..9c00d4ea7 --- /dev/null +++ b/apps/opencs/model/tools/verifier.cpp @@ -0,0 +1,7 @@ + +#include "verifier.hpp" + +#include "../doc/state.hpp" + +CSMTools::Verifier::Verifier() : Operation (CSMDoc::State_Verifying) +{} diff --git a/apps/opencs/model/tools/verifier.hpp b/apps/opencs/model/tools/verifier.hpp new file mode 100644 index 000000000..054f87169 --- /dev/null +++ b/apps/opencs/model/tools/verifier.hpp @@ -0,0 +1,17 @@ +#ifndef CSM_TOOLS_VERIFIER_H +#define CSM_TOOLS_VERIFIER_H + +#include "operation.hpp" + +namespace CSMTools +{ + class Verifier : public Operation + { + public: + + Verifier(); + + }; +} + +#endif diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp new file mode 100644 index 000000000..7adc7e6c3 --- /dev/null +++ b/apps/opencs/model/world/columnbase.cpp @@ -0,0 +1,13 @@ + +#include "columnbase.hpp" + +CSMWorld::ColumnBase::ColumnBase (const std::string& title, int flags) +: mTitle (title), mFlags (flags) +{} + +CSMWorld::ColumnBase::~ColumnBase() {} + +bool CSMWorld::ColumnBase::isUserEditable() const +{ + return isEditable(); +} \ No newline at end of file diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp new file mode 100644 index 000000000..dc077eff6 --- /dev/null +++ b/apps/opencs/model/world/columnbase.hpp @@ -0,0 +1,57 @@ +#ifndef CSM_WOLRD_COLUMNBASE_H +#define CSM_WOLRD_COLUMNBASE_H + +#include + +#include +#include + +#include "record.hpp" + +namespace CSMWorld +{ + struct ColumnBase + { + enum Roles + { + Role_Flags = Qt::UserRole + }; + + enum Flags + { + Flag_Table = 1, // column should be displayed in table view + Flag_Dialogue = 2 // column should be displayed in dialogue view + }; + + std::string mTitle; + int mFlags; + + ColumnBase (const std::string& title, int flag); + + virtual ~ColumnBase(); + + virtual bool isEditable() const = 0; + + virtual bool isUserEditable() const; + ///< Can this column be edited directly by the user? + }; + + template + struct Column : public ColumnBase + { + std::string mTitle; + int mFlags; + + Column (const std::string& title, int flags = Flag_Table | Flag_Dialogue) + : ColumnBase (title, flags) {} + + virtual QVariant get (const Record& record) const = 0; + + virtual void set (Record& record, const QVariant& data) + { + throw std::logic_error ("Column " + mTitle + " is not editable"); + } + }; +} + +#endif diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp new file mode 100644 index 000000000..5abf4ea8b --- /dev/null +++ b/apps/opencs/model/world/columns.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_WOLRD_COLUMNS_H +#define CSM_WOLRD_COLUMNS_H + +#include "columnbase.hpp" + +namespace CSMWorld +{ + template + struct FloatValueColumn : public Column + { + FloatValueColumn() : Column ("Value") {} + + virtual QVariant get (const Record& record) const + { + return record.get().mValue; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT base = record.getBase(); + base.mValue = data.toFloat(); + record.setModified (base); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct StringIdColumn : public Column + { + StringIdColumn() : Column ("ID") {} + + virtual QVariant get (const Record& record) const + { + return record.get().mId.c_str(); + } + + virtual bool isEditable() const + { + return false; + } + }; + + template + struct RecordStateColumn : public Column + { + RecordStateColumn() : Column ("*") {} + + virtual QVariant get (const Record& record) const + { + if (record.mState==Record::State_Erased) + return static_cast (Record::State_Deleted); + + return static_cast (record.mState); + } + + virtual void set (Record& record, const QVariant& data) + { + record.mState = static_cast (data.toInt()); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; + + template + struct FixedRecordTypeColumn : public Column + { + int mType; + + FixedRecordTypeColumn (int type) : Column ("Type", 0), mType (type) {} + + virtual QVariant get (const Record& record) const + { + return mType; + } + + virtual bool isEditable() const + { + return false; + } + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp new file mode 100644 index 000000000..e22ecf992 --- /dev/null +++ b/apps/opencs/model/world/commands.cpp @@ -0,0 +1,108 @@ + +#include "commands.hpp" + +#include + +#include "idtableproxymodel.hpp" +#include "idtable.hpp" + +CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, + const QVariant& new_, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_) +{ + mOld = mModel.data (mIndex, Qt::EditRole); + + setText ("Modify " + mModel.headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); +} + +void CSMWorld::ModifyCommand::redo() +{ + mModel.setData (mIndex, mNew); +} + +void CSMWorld::ModifyCommand::undo() +{ + mModel.setData (mIndex, mOld); +} + +CSMWorld::CreateCommand::CreateCommand (IdTableProxyModel& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id) +{ + setText (("Create record " + id).c_str()); +} + +void CSMWorld::CreateCommand::redo() +{ + mModel.addRecord (mId); +} + +void CSMWorld::CreateCommand::undo() +{ + mModel.removeRow (mModel.getModelIndex (mId, 0).row()); +} + +CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id), mOld (0) +{ + setText (("Revert record " + id).c_str()); + + mOld = model.getRecord (id).clone(); +} + +CSMWorld::RevertCommand::~RevertCommand() +{ + delete mOld; +} + +void CSMWorld::RevertCommand::redo() +{ + QModelIndex index = mModel.getModelIndex (mId, 1); + RecordBase::State state = static_cast (mModel.data (index).toInt()); + + if (state==RecordBase::State_ModifiedOnly) + { + mModel.removeRows (index.row(), 1); + } + else + { + mModel.setData (index, static_cast (RecordBase::State_BaseOnly)); + } +} + +void CSMWorld::RevertCommand::undo() +{ + mModel.setRecord (*mOld); +} + +CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id), mOld (0) +{ + setText (("Delete record " + id).c_str()); + + mOld = model.getRecord (id).clone(); +} + +CSMWorld::DeleteCommand::~DeleteCommand() +{ + delete mOld; +} + +void CSMWorld::DeleteCommand::redo() +{ + QModelIndex index = mModel.getModelIndex (mId, 1); + RecordBase::State state = static_cast (mModel.data (index).toInt()); + + if (state==RecordBase::State_ModifiedOnly) + { + mModel.removeRows (index.row(), 1); + } + else + { + mModel.setData (index, static_cast (RecordBase::State_Deleted)); + } +} + +void CSMWorld::DeleteCommand::undo() +{ + mModel.setRecord (*mOld); +} \ No newline at end of file diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp new file mode 100644 index 000000000..af419215d --- /dev/null +++ b/apps/opencs/model/world/commands.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_WOLRD_COMMANDS_H +#define CSM_WOLRD_COMMANDS_H + +#include "record.hpp" + +#include + +#include +#include +#include + +class QModelIndex; +class QAbstractItemModel; + +namespace CSMWorld +{ + class IdTableProxyModel; + class IdTable; + class RecordBase; + + class ModifyCommand : public QUndoCommand + { + QAbstractItemModel& mModel; + QModelIndex mIndex; + QVariant mNew; + QVariant mOld; + + public: + + ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, + QUndoCommand *parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + + class CreateCommand : public QUndoCommand + { + IdTableProxyModel& mModel; + std::string mId; + + public: + + CreateCommand (IdTableProxyModel& model, const std::string& id, QUndoCommand *parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + + class RevertCommand : public QUndoCommand + { + IdTable& mModel; + std::string mId; + RecordBase *mOld; + + // not implemented + RevertCommand (const RevertCommand&); + RevertCommand& operator= (const RevertCommand&); + + public: + + RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual ~RevertCommand(); + + virtual void redo(); + + virtual void undo(); + }; + + class DeleteCommand : public QUndoCommand + { + IdTable& mModel; + std::string mId; + RecordBase *mOld; + + // not implemented + DeleteCommand (const DeleteCommand&); + DeleteCommand& operator= (const DeleteCommand&); + + public: + + DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual ~DeleteCommand(); + + virtual void redo(); + + virtual void undo(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp new file mode 100644 index 000000000..a3522503e --- /dev/null +++ b/apps/opencs/model/world/data.cpp @@ -0,0 +1,62 @@ + +#include "data.hpp" + +#include + +#include + +#include + +#include "idtable.hpp" +#include "columns.hpp" + +void CSMWorld::Data::addModel (QAbstractTableModel *model, UniversalId::Type type1, + UniversalId::Type type2) +{ + mModels.push_back (model); + mModelIndex.insert (std::make_pair (type1, model)); + + if (type2!=UniversalId::Type_None) + mModelIndex.insert (std::make_pair (type2, model)); +} + +CSMWorld::Data::Data() +{ + mGlobals.addColumn (new StringIdColumn); + mGlobals.addColumn (new RecordStateColumn); + mGlobals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Global)); + mGlobals.addColumn (new FloatValueColumn); + + addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); +} + +CSMWorld::Data::~Data() +{ + for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) + delete *iter; +} + +const CSMWorld::IdCollection& CSMWorld::Data::getGlobals() const +{ + return mGlobals; +} + +CSMWorld::IdCollection& CSMWorld::Data::getGlobals() +{ + return mGlobals; +} + +QAbstractTableModel *CSMWorld::Data::getTableModel (const UniversalId& id) +{ + std::map::iterator iter = mModelIndex.find (id.getType()); + + if (iter==mModelIndex.end()) + throw std::logic_error ("No table model available for " + id.toString()); + + return iter->second; +} + +void CSMWorld::Data::merge() +{ + mGlobals.merge(); +} \ No newline at end of file diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp new file mode 100644 index 000000000..f7748cb5d --- /dev/null +++ b/apps/opencs/model/world/data.hpp @@ -0,0 +1,50 @@ +#ifndef CSM_WOLRD_DATA_H +#define CSM_WOLRD_DATA_H + +#include +#include + +#include + +#include "idcollection.hpp" +#include "universalid.hpp" + +class QAbstractTableModel; + +namespace CSMWorld +{ + class Data + { + IdCollection mGlobals; + std::vector mModels; + std::map mModelIndex; + + // not implemented + Data (const Data&); + Data& operator= (const Data&); + + void addModel (QAbstractTableModel *model, UniversalId::Type type1, + UniversalId::Type type2 = UniversalId::Type_None); + + public: + + Data(); + + ~Data(); + + const IdCollection& getGlobals() const; + + IdCollection& getGlobals(); + + QAbstractTableModel *getTableModel (const UniversalId& id); + ///< If no table model is available for \a id, an exception is thrown. + /// + /// \note The returned table may either be the model for the ID itself or the model that + /// contains the record specified by the ID. + + void merge(); + ///< Merge modified into base. + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp new file mode 100644 index 000000000..fc4bb1ef6 --- /dev/null +++ b/apps/opencs/model/world/idcollection.cpp @@ -0,0 +1,6 @@ + +#include "idcollection.hpp" + +CSMWorld::IdCollectionBase::IdCollectionBase() {} + +CSMWorld::IdCollectionBase::~IdCollectionBase() {} \ No newline at end of file diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp new file mode 100644 index 000000000..1b2d1e349 --- /dev/null +++ b/apps/opencs/model/world/idcollection.hpp @@ -0,0 +1,325 @@ +#ifndef CSM_WOLRD_IDCOLLECTION_H +#define CSM_WOLRD_IDCOLLECTION_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "columnbase.hpp" + +namespace CSMWorld +{ + class IdCollectionBase + { + // not implemented + IdCollectionBase (const IdCollectionBase&); + IdCollectionBase& operator= (const IdCollectionBase&); + + public: + + IdCollectionBase(); + + virtual ~IdCollectionBase(); + + virtual int getSize() const = 0; + + virtual std::string getId (int index) const = 0; + + virtual int getIndex (const std::string& id) const = 0; + + virtual int getColumns() const = 0; + + virtual const ColumnBase& getColumn (int column) const = 0; + + virtual QVariant getData (int index, int column) const = 0; + + virtual void setData (int index, int column, const QVariant& data) = 0; + + virtual void merge() = 0; + ///< Merge modified into base. + + virtual void purge() = 0; + ///< Remove records that are flagged as erased. + + virtual void removeRows (int index, int count) = 0; + + virtual void appendBlankRecord (const std::string& id) = 0; + + virtual int searchId (const std::string& id) const = 0; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) + + virtual void replace (int index, const RecordBase& record) = 0; + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. + + virtual void appendRecord (const RecordBase& record) = 0; + ///< If the record type does not match, an exception is thrown. + + virtual std::string getId (const RecordBase& record) const = 0; + ///< Return ID for \a record. + /// + /// \attention Throw san exception, if the type of \a record does not match. + + virtual const RecordBase& getRecord (const std::string& id) const = 0; + }; + + ///< \brief Collection of ID-based records + template + class IdCollection : public IdCollectionBase + { + std::vector > mRecords; + std::map mIndex; + std::vector *> mColumns; + + // not implemented + IdCollection (const IdCollection&); + IdCollection& operator= (const IdCollection&); + + public: + + IdCollection(); + + virtual ~IdCollection(); + + void add (const ESXRecordT& record); + ///< Add a new record (modified) + + virtual int getSize() const; + + virtual std::string getId (int index) const; + + virtual int getIndex (const std::string& id) const; + + virtual int getColumns() const; + + virtual QVariant getData (int index, int column) const; + + virtual void setData (int index, int column, const QVariant& data); + + virtual const ColumnBase& getColumn (int column) const; + + virtual void merge(); + ///< Merge modified into base. + + virtual void purge(); + ///< Remove records that are flagged as erased. + + virtual void removeRows (int index, int count) ; + + virtual void appendBlankRecord (const std::string& id); + + virtual int searchId (const std::string& id) const; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) + + virtual void replace (int index, const RecordBase& record); + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. + + virtual void appendRecord (const RecordBase& record); + ///< If the record type does not match, an exception is thrown. + + virtual std::string getId (const RecordBase& record) const; + ///< Return ID for \a record. + /// + /// \attention Throw san exception, if the type of \a record does not match. + + virtual const RecordBase& getRecord (const std::string& id) const; + + void addColumn (Column *column); + }; + + template + IdCollection::IdCollection() + {} + + template + IdCollection::~IdCollection() + { + for (typename std::vector *>::iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter) + delete *iter; + } + + template + void IdCollection::add (const ESXRecordT& record) + { + std::string id; + + std::transform (record.mId.begin(), record.mId.end(), std::back_inserter (id), + (int(*)(int)) std::tolower); + + std::map::iterator iter = mIndex.find (id); + + if (iter==mIndex.end()) + { + Record record2; + record2.mState = Record::State_ModifiedOnly; + record2.mModified = record; + + mRecords.push_back (record2); + mIndex.insert (std::make_pair (id, mRecords.size()-1)); + } + else + { + mRecords[iter->second].setModified (record); + } + } + + template + int IdCollection::getSize() const + { + return mRecords.size(); + } + + template + std::string IdCollection::getId (int index) const + { + return mRecords.at (index).get().mId; + } + + template + int IdCollection::getIndex (const std::string& id) const + { + int index = searchId (id); + + if (index==-1) + throw std::runtime_error ("invalid ID: " + id); + + return index; + } + + template + int IdCollection::getColumns() const + { + return mColumns.size(); + } + + template + QVariant IdCollection::getData (int index, int column) const + { + return mColumns.at (column)->get (mRecords.at (index)); + } + + template + void IdCollection::setData (int index, int column, const QVariant& data) + { + return mColumns.at (column)->set (mRecords.at (index), data); + } + + template + const ColumnBase& IdCollection::getColumn (int column) const + { + return *mColumns.at (column); + } + + template + void IdCollection::addColumn (Column *column) + { + mColumns.push_back (column); + } + + template + void IdCollection::merge() + { + for (typename std::vector >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter) + iter->merge(); + + purge(); + } + + template + void IdCollection::purge() + { + mRecords.erase (std::remove_if (mRecords.begin(), mRecords.end(), + std::mem_fun_ref (&Record::isErased) // I want lambda :( + ), mRecords.end()); + } + + template + void IdCollection::removeRows (int index, int count) + { + mRecords.erase (mRecords.begin()+index, mRecords.begin()+index+count); + + typename std::map::iterator iter = mIndex.begin(); + + while (iter!=mIndex.end()) + { + if (iter->second>=index) + { + if (iter->second>=index+count) + { + iter->second -= count; + } + else + { + mIndex.erase (iter++); + } + } + + ++iter; + } + } + + template + void IdCollection::appendBlankRecord (const std::string& id) + { + ESXRecordT record; + record.mId = id; + record.blank(); + add (record); + } + + template + int IdCollection::searchId (const std::string& id) const + { + std::string id2; + + std::transform (id.begin(), id.end(), std::back_inserter (id2), + (int(*)(int)) std::tolower); + + std::map::const_iterator iter = mIndex.find (id2); + + if (iter==mIndex.end()) + return -1; + + return iter->second; + } + + template + void IdCollection::replace (int index, const RecordBase& record) + { + mRecords.at (index) = dynamic_cast&> (record); + } + + template + void IdCollection::appendRecord (const RecordBase& record) + { + mRecords.push_back (dynamic_cast&> (record)); + mIndex.insert (std::make_pair (getId (record), mRecords.size()-1)); + } + + template + std::string IdCollection::getId (const RecordBase& record) const + { + const Record& record2 = dynamic_cast&> (record); + return (record2.isModified() ? record2.mModified : record2.mBase).mId; + } + + template + const RecordBase& IdCollection::getRecord (const std::string& id) const + { + int index = getIndex (id); + return mRecords.at (index); + } +} + +#endif diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp new file mode 100644 index 000000000..2815e318c --- /dev/null +++ b/apps/opencs/model/world/idtable.cpp @@ -0,0 +1,137 @@ + +#include "idtable.hpp" + +#include "idcollection.hpp" + +CSMWorld::IdTable::IdTable (IdCollectionBase *idCollection) : mIdCollection (idCollection) +{ + +} + +CSMWorld::IdTable::~IdTable() +{ + +} + +int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return mIdCollection->getSize(); +} + +int CSMWorld::IdTable::columnCount (const QModelIndex & parent) const +{ + if (parent.isValid()) + return 0; + + return mIdCollection->getColumns(); +} + +QVariant CSMWorld::IdTable::data (const QModelIndex & index, int role) const +{ + if (role!=Qt::DisplayRole && role!=Qt::EditRole) + return QVariant(); + + if (role==Qt::EditRole && !mIdCollection->getColumn (index.column()).isEditable()) + return QVariant(); + + return mIdCollection->getData (index.row(), index.column()); +} + +QVariant CSMWorld::IdTable::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (orientation==Qt::Vertical) + return QVariant(); + + if (role==Qt::DisplayRole) + return tr (mIdCollection->getColumn (section).mTitle.c_str()); + + if (role==ColumnBase::Role_Flags) + return mIdCollection->getColumn (section).mFlags; + + return QVariant(); +} + +bool CSMWorld::IdTable::setData ( const QModelIndex &index, const QVariant &value, int role) +{ + if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) + { + mIdCollection->setData (index.row(), index.column(), value); + + emit dataChanged (CSMWorld::IdTable::index (index.row(), 0), + CSMWorld::IdTable::index (index.row(), mIdCollection->getColumns()-1)); + + return true; + } + + return false; +} + +Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const +{ + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + if (mIdCollection->getColumn (index.column()).isUserEditable()) + flags |= Qt::ItemIsEditable; + + return flags; +} + +bool CSMWorld::IdTable::removeRows (int row, int count, const QModelIndex& parent) +{ + if (parent.isValid()) + return false; + + beginRemoveRows (parent, row, row+count-1); + + mIdCollection->removeRows (row, count); + + endRemoveRows(); + + return true; +} + +void CSMWorld::IdTable::addRecord (const std::string& id) +{ + int index = mIdCollection->getSize(); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendBlankRecord (id); + + endInsertRows(); +} + +QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const +{ + return index (mIdCollection->getIndex (id), column); +} + +void CSMWorld::IdTable::setRecord (const RecordBase& record) +{ + int index = mIdCollection->searchId (mIdCollection->getId (record)); + + if (index==-1) + { + int index = mIdCollection->getSize(); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendRecord (record); + + endInsertRows(); + } + else + { + mIdCollection->replace (index, record); + emit dataChanged (CSMWorld::IdTable::index (index, 0), + CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1)); + } +} + +const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) const +{ + return mIdCollection->getRecord (id); +} \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp new file mode 100644 index 000000000..deaebaa38 --- /dev/null +++ b/apps/opencs/model/world/idtable.hpp @@ -0,0 +1,53 @@ +#ifndef CSM_WOLRD_IDTABLE_H +#define CSM_WOLRD_IDTABLE_H + +#include + +namespace CSMWorld +{ + class IdCollectionBase; + class RecordBase; + + class IdTable : public QAbstractTableModel + { + Q_OBJECT + + IdCollectionBase *mIdCollection; + + // not implemented + IdTable (const IdTable&); + IdTable& operator= (const IdTable&); + + public: + + IdTable (IdCollectionBase *idCollection); + ///< The ownership of \a idCollection is not transferred. + + virtual ~IdTable(); + + virtual int rowCount (const QModelIndex & parent = QModelIndex()) const; + + virtual int columnCount (const QModelIndex & parent = QModelIndex()) const; + + virtual QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + + virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + virtual bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + virtual Qt::ItemFlags flags (const QModelIndex & index) const; + + virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); + + void addRecord (const std::string& id); + + QModelIndex getModelIndex (const std::string& id, int column) const; + + void setRecord (const RecordBase& record); + ///< Add record or overwrite existing recrod. + + const RecordBase& getRecord (const std::string& id) const; + }; +} + +#endif diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp new file mode 100644 index 000000000..78995f60b --- /dev/null +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -0,0 +1,18 @@ + +#include "idtableproxymodel.hpp" + +#include "idtable.hpp" + +CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) +: QSortFilterProxyModel (parent) +{} + +void CSMWorld::IdTableProxyModel::addRecord (const std::string& id) +{ + dynamic_cast (*sourceModel()).addRecord (id); +} + +QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const +{ + return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); +} \ No newline at end of file diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp new file mode 100644 index 000000000..3f1537cce --- /dev/null +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -0,0 +1,24 @@ +#ifndef CSM_WOLRD_IDTABLEPROXYMODEL_H +#define CSM_WOLRD_IDTABLEPROXYMODEL_H + +#include + +#include + +namespace CSMWorld +{ + class IdTableProxyModel : public QSortFilterProxyModel + { + Q_OBJECT + + public: + + IdTableProxyModel (QObject *parent = 0); + + virtual void addRecord (const std::string& id); + + virtual QModelIndex getModelIndex (const std::string& id, int column) const; + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp new file mode 100644 index 000000000..229985a8a --- /dev/null +++ b/apps/opencs/model/world/record.cpp @@ -0,0 +1,21 @@ + +#include "record.hpp" + +CSMWorld::RecordBase::~RecordBase() {} + +bool CSMWorld::RecordBase::RecordBase::isDeleted() const +{ + return mState==State_Deleted || mState==State_Erased; +} + + +bool CSMWorld::RecordBase::RecordBase::isErased() const +{ + return mState==State_Erased; +} + + +bool CSMWorld::RecordBase::RecordBase::isModified() const +{ + return mState==State_Modified || mState==State_ModifiedOnly; +} \ No newline at end of file diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp new file mode 100644 index 000000000..53bb7ea2c --- /dev/null +++ b/apps/opencs/model/world/record.hpp @@ -0,0 +1,104 @@ +#ifndef CSM_WOLRD_RECORD_H +#define CSM_WOLRD_RECORD_H + +#include + +namespace CSMWorld +{ + struct RecordBase + { + enum State + { + State_BaseOnly = 0, // defined in base only + State_Modified = 1, // exists in base, but has been modified + State_ModifiedOnly = 2, // newly created in modified + State_Deleted = 3, // exists in base, but has been deleted + State_Erased = 4 // does not exist at all (we mostly treat that the same way as deleted) + }; + + State mState; + + virtual ~RecordBase(); + + virtual RecordBase *clone() const = 0; + + bool isDeleted() const; + + bool isErased() const; + + bool isModified() const; + }; + + template + struct Record : public RecordBase + { + ESXRecordT mBase; + ESXRecordT mModified; + + virtual RecordBase *clone() const; + + const ESXRecordT& get() const; + ///< Throws an exception, if the record is deleted. + + const ESXRecordT& getBase() const; + ///< Throws an exception, if the record is deleted. Returns modified, if there is no base. + + void setModified (const ESXRecordT& modified); + ///< Throws an exception, if the record is deleted. + + void merge(); + ///< Merge modified into base. + }; + + template + RecordBase *Record::clone() const + { + return new Record (*this); + } + + template + const ESXRecordT& Record::get() const + { + if (mState==State_Erased) + throw std::logic_error ("attempt to access a deleted record"); + + return mState==State_BaseOnly ? mBase : mModified; + } + + template + const ESXRecordT& Record::getBase() const + { + if (mState==State_Erased) + throw std::logic_error ("attempt to access a deleted record"); + + return mState==State_ModifiedOnly ? mModified : mBase; + } + + template + void Record::setModified (const ESXRecordT& modified) + { + if (mState==State_Erased) + throw std::logic_error ("attempt to modify a deleted record"); + + mModified = modified; + + if (mState!=State_ModifiedOnly) + mState = mBase==mModified ? State_BaseOnly : State_Modified; + } + + template + void Record::merge() + { + if (isModified()) + { + mBase = mModified; + mState = State_BaseOnly; + } + else if (mState==State_Deleted) + { + mState = State_Erased; + } + } +} + +#endif diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp new file mode 100644 index 000000000..d8775643a --- /dev/null +++ b/apps/opencs/model/world/universalid.cpp @@ -0,0 +1,237 @@ + +#include "universalid.hpp" + +#include +#include +#include + +namespace +{ + struct TypeData + { + CSMWorld::UniversalId::Class mClass; + CSMWorld::UniversalId::Type mType; + const char *mName; + }; + + static const TypeData sNoArg[] = + { + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables" }, + + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + }; + + static const TypeData sIdArg[] = + { + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable" }, + + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + }; + + static const TypeData sIndexArg[] = + { + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results" }, + + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + }; +} + +CSMWorld::UniversalId::UniversalId (const std::string& universalId) +{ + std::string::size_type index = universalId.find (':'); + + if (index==std::string::npos) + { + std::string type = universalId.substr (0, index); + + if (index==std::string::npos) + { + for (int i=0; sNoArg[i].mName; ++i) + if (type==sNoArg[i].mName) + { + mArgumentType = ArgumentType_None; + mType = sNoArg[i].mType; + mClass = sNoArg[i].mClass; + return; + } + } + else + { + for (int i=0; sIdArg[i].mName; ++i) + if (type==sIdArg[i].mName) + { + mArgumentType = ArgumentType_Id; + mType = sIdArg[i].mType; + mClass = sIdArg[i].mClass; + mId = universalId.substr (0, index); + return; + } + + for (int i=0; sIndexArg[i].mName; ++i) + if (type==sIndexArg[i].mName) + { + mArgumentType = ArgumentType_Index; + mType = sIndexArg[i].mType; + mClass = sIndexArg[i].mClass; + + std::istringstream stream (universalId.substr (0, index)); + + if (stream >> mIndex) + return; + + break; + } + } + } + + throw std::runtime_error ("invalid UniversalId: " + universalId); +} + +CSMWorld::UniversalId::UniversalId (Type type) : mArgumentType (ArgumentType_None), mType (type), mIndex (0) +{ + for (int i=0; sNoArg[i].mName; ++i) + if (type==sNoArg[i].mType) + { + mClass = sNoArg[i].mClass; + return; + } + + throw std::logic_error ("invalid argument-less UniversalId type"); +} + +CSMWorld::UniversalId::UniversalId (Type type, const std::string& id) +: mArgumentType (ArgumentType_Id), mType (type), mId (id), mIndex (0) +{ + for (int i=0; sIdArg[i].mName; ++i) + if (type==sIdArg[i].mType) + { + mClass = sIdArg[i].mClass; + return; + } + + throw std::logic_error ("invalid ID argument UniversalId type"); +} + +CSMWorld::UniversalId::UniversalId (Type type, int index) +: mArgumentType (ArgumentType_Index), mType (type), mIndex (index) +{ + for (int i=0; sIndexArg[i].mName; ++i) + if (type==sIndexArg[i].mType) + { + mClass = sIndexArg[i].mClass; + return; + } + + throw std::logic_error ("invalid index argument UniversalId type"); +} + +CSMWorld::UniversalId::Class CSMWorld::UniversalId::getClass() const +{ + return mClass; +} + +CSMWorld::UniversalId::ArgumentType CSMWorld::UniversalId::getArgumentType() const +{ + return mArgumentType; +} + +CSMWorld::UniversalId::Type CSMWorld::UniversalId::getType() const +{ + return mType; +} + +const std::string& CSMWorld::UniversalId::getId() const +{ + if (mArgumentType!=ArgumentType_Id) + throw std::logic_error ("invalid access to ID of non-ID UniversalId"); + + return mId; +} + +int CSMWorld::UniversalId::getIndex() const +{ + if (mArgumentType!=ArgumentType_Index) + throw std::logic_error ("invalid access to index of non-index UniversalId"); + + return mIndex; +} + +bool CSMWorld::UniversalId::isEqual (const UniversalId& universalId) const +{ + if (mClass!=universalId.mClass || mArgumentType!=universalId.mArgumentType || mType!=universalId.mType) + return false; + + switch (mArgumentType) + { + case ArgumentType_Id: return mId==universalId.mId; + case ArgumentType_Index: return mIndex==universalId.mIndex; + + default: return true; + } +} + +bool CSMWorld::UniversalId::isLess (const UniversalId& universalId) const +{ + if (mTypeuniversalId.mType) + return false; + + switch (mArgumentType) + { + case ArgumentType_Id: return mId +#include + +#include + +namespace CSMWorld +{ + class UniversalId + { + public: + + enum Class + { + Class_None = 0, + Class_Record, + Class_SubRecord, + Class_RecordList, + Class_Collection, // multiple types of records combined + Class_Transient, // not part of the world data or the project data + Class_NonRecord // record like data that is not part of the world + }; + + enum ArgumentType + { + ArgumentType_None, + ArgumentType_Id, + ArgumentType_Index + }; + + enum Type + { + Type_None, + + Type_Globals, + + Type_Global, + + Type_VerificationResults + }; + + private: + + Class mClass; + ArgumentType mArgumentType; + Type mType; + std::string mId; + int mIndex; + + public: + + UniversalId (const std::string& universalId); + + UniversalId (Type type = Type_None); + ///< Using a type for a non-argument-less UniversalId will throw an exception. + + UniversalId (Type type, const std::string& id); + ///< Using a type for a non-ID-argument UniversalId will throw an exception. + + UniversalId (Type type, int index); + ///< Using a type for a non-index-argument UniversalId will throw an exception. + + Class getClass() const; + + ArgumentType getArgumentType() const; + + Type getType() const; + + const std::string& getId() const; + ///< Calling this function for a non-ID type will throw an exception. + + int getIndex() const; + ///< Calling this function for a non-index type will throw an exception. + + bool isEqual (const UniversalId& universalId) const; + + bool isLess (const UniversalId& universalId) const; + + std::string getTypeName() const; + + std::string toString() const; + }; + + bool operator== (const UniversalId& left, const UniversalId& right); + bool operator!= (const UniversalId& left, const UniversalId& right); + + bool operator< (const UniversalId& left, const UniversalId& right); + + std::ostream& operator< (std::ostream& stream, const UniversalId& universalId); +} + +Q_DECLARE_METATYPE (CSMWorld::UniversalId) + +#endif diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp new file mode 100644 index 000000000..3f415da03 --- /dev/null +++ b/apps/opencs/view/doc/operation.cpp @@ -0,0 +1,54 @@ + +#include "operation.hpp" + +#include + +#include "../../model/doc/document.hpp" + +void CSVDoc::Operation::updateLabel (int threads) +{ + if (threads==-1 || ((threads==0)!=mStalling)) + { + std::string name ("unknown operation"); + + switch (mType) + { + case CSMDoc::State_Saving: name = "saving"; break; + case CSMDoc::State_Verifying: name = "verifying"; break; + } + + std::ostringstream stream; + + if ((mStalling = (threads<=0))) + { + stream << name << " (waiting for a free worker thread)"; + } + else + { + stream << name << " (%p%)"; + } + + setFormat (stream.str().c_str()); + } +} + +CSVDoc::Operation::Operation (int type) : mType (type), mStalling (false) +{ + /// \todo Add a cancel button or a pop up menu with a cancel item + + updateLabel(); + + /// \todo assign different progress bar colours to allow the user to distinguish easily between operation types +} + +void CSVDoc::Operation::setProgress (int current, int max, int threads) +{ + updateLabel (threads); + setRange (0, max); + setValue (current); +} + +int CSVDoc::Operation::getType() const +{ + return mType; +} \ No newline at end of file diff --git a/apps/opencs/view/doc/operation.hpp b/apps/opencs/view/doc/operation.hpp new file mode 100644 index 000000000..362725b6f --- /dev/null +++ b/apps/opencs/view/doc/operation.hpp @@ -0,0 +1,31 @@ +#ifndef CSV_DOC_OPERATION_H +#define CSV_DOC_OPERATION_H + +#include + +namespace CSVDoc +{ + class Operation : public QProgressBar + { + Q_OBJECT + + int mType; + bool mStalling; + + // not implemented + Operation (const Operation&); + Operation& operator= (const Operation&); + + void updateLabel (int threads = -1); + + public: + + Operation (int type); + + void setProgress (int current, int max, int threads); + + int getType() const; + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/operations.cpp b/apps/opencs/view/doc/operations.cpp new file mode 100644 index 000000000..ba444a119 --- /dev/null +++ b/apps/opencs/view/doc/operations.cpp @@ -0,0 +1,47 @@ + +#include "operations.hpp" + +#include + +#include "operation.hpp" + +CSVDoc::Operations::Operations() +{ + /// \todo make widget height fixed (exactly the height required to display all operations) + + setFeatures (QDockWidget::NoDockWidgetFeatures); + + QWidget *widget = new QWidget; + setWidget (widget); + + mLayout = new QVBoxLayout; + + widget->setLayout (mLayout); +} + +void CSVDoc::Operations::setProgress (int current, int max, int type, int threads) +{ + for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) + if ((*iter)->getType()==type) + { + (*iter)->setProgress (current, max, threads); + return; + } + + Operation *operation = new Operation (type); + + mLayout->addWidget (operation); + mOperations.push_back (operation); + operation->setProgress (current, max, threads); +} + +void CSVDoc::Operations::quitOperation (int type) +{ + for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) + if ((*iter)->getType()==type) + { + delete *iter; + mOperations.erase (iter); + break; + } +} \ No newline at end of file diff --git a/apps/opencs/view/doc/operations.hpp b/apps/opencs/view/doc/operations.hpp new file mode 100644 index 000000000..b96677450 --- /dev/null +++ b/apps/opencs/view/doc/operations.hpp @@ -0,0 +1,37 @@ +#ifndef CSV_DOC_OPERATIONS_H +#define CSV_DOC_OPERATIONS_H + +#include + +#include + +class QVBoxLayout; + +namespace CSVDoc +{ + class Operation; + + class Operations : public QDockWidget + { + Q_OBJECT + + QVBoxLayout *mLayout; + std::vector mOperations; + + // not implemented + Operations (const Operations&); + Operations& operator= (const Operations&); + + public: + + Operations(); + + void setProgress (int current, int max, int type, int threads); + ///< Implicitly starts the operation, if it is not running already. + + void quitOperation (int type); + ///< Calling this function for an operation that is not running is a no-op. + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp new file mode 100644 index 000000000..1c356fa73 --- /dev/null +++ b/apps/opencs/view/doc/subview.cpp @@ -0,0 +1,18 @@ + +#include "subview.hpp" + +CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) : mUniversalId (id) +{ + /// \todo add a button to the title bar that clones this sub view + + setWindowTitle (mUniversalId.toString().c_str()); + + /// \todo remove (for testing only) + setMinimumWidth (100); + setMinimumHeight (60); +} + +CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const +{ + return mUniversalId; +} diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp new file mode 100644 index 000000000..985c5eb3c --- /dev/null +++ b/apps/opencs/view/doc/subview.hpp @@ -0,0 +1,45 @@ +#ifndef CSV_DOC_SUBVIEW_H +#define CSV_DOC_SUBVIEW_H + +#include "../../model/doc/document.hpp" + +#include "../../model/world/universalid.hpp" + +#include "subviewfactory.hpp" + +#include + +class QUndoStack; + +namespace CSMWorld +{ + class Data; +} + +namespace CSVDoc +{ + class SubView : public QDockWidget + { + Q_OBJECT + + CSMWorld::UniversalId mUniversalId; + + // not implemented + SubView (const SubView&); + SubView& operator= (SubView&); + + public: + + SubView (const CSMWorld::UniversalId& id); + + CSMWorld::UniversalId getUniversalId() const; + + virtual void setEditLock (bool locked) = 0; + + signals: + + void focusId (const CSMWorld::UniversalId& universalId); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp new file mode 100644 index 000000000..8576f6b1d --- /dev/null +++ b/apps/opencs/view/doc/subviewfactory.cpp @@ -0,0 +1,38 @@ + +#include "subviewfactory.hpp" + +#include + +#include + +CSVDoc::SubViewFactoryBase::SubViewFactoryBase() {} + +CSVDoc::SubViewFactoryBase::~SubViewFactoryBase() {} + + +CSVDoc::SubViewFactoryManager::SubViewFactoryManager() {} + +CSVDoc::SubViewFactoryManager::~SubViewFactoryManager() +{ + for (std::map::iterator iter (mSubViewFactories.begin()); + iter!=mSubViewFactories.end(); ++iter) + delete iter->second; +} + +void CSVDoc::SubViewFactoryManager::add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory) +{ + assert (mSubViewFactories.find (id)==mSubViewFactories.end()); + + mSubViewFactories.insert (std::make_pair (id, factory)); +} + +CSVDoc::SubView *CSVDoc::SubViewFactoryManager::makeSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document) +{ + std::map::iterator iter = mSubViewFactories.find (id.getType()); + + if (iter==mSubViewFactories.end()) + throw std::runtime_error ("Failed to create a sub view for: " + id.toString()); + + return iter->second->makeSubView (id, document); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/subviewfactory.hpp b/apps/opencs/view/doc/subviewfactory.hpp new file mode 100644 index 000000000..1f7c15480 --- /dev/null +++ b/apps/opencs/view/doc/subviewfactory.hpp @@ -0,0 +1,55 @@ +#ifndef CSV_DOC_SUBVIEWFACTORY_H +#define CSV_DOC_SUBVIEWFACTORY_H + +#include + +#include "../../model/world/universalid.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSVDoc +{ + class SubView; + + class SubViewFactoryBase + { + // not implemented + SubViewFactoryBase (const SubViewFactoryBase&); + SubViewFactoryBase& operator= (const SubViewFactoryBase&); + + public: + + SubViewFactoryBase(); + + virtual ~SubViewFactoryBase(); + + virtual SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) = 0; + ///< The ownership of the returned sub view is not transferred. + }; + + class SubViewFactoryManager + { + std::map mSubViewFactories; + + // not implemented + SubViewFactoryManager (const SubViewFactoryManager&); + SubViewFactoryManager& operator= (const SubViewFactoryManager&); + + public: + + SubViewFactoryManager(); + + ~SubViewFactoryManager(); + + void add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory); + ///< The ownership of \a factory is transferred to this. + + SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + ///< The ownership of the returned sub view is not transferred. + }; +} + +#endif diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp new file mode 100644 index 000000000..d16e0b2b7 --- /dev/null +++ b/apps/opencs/view/doc/subviewfactoryimp.hpp @@ -0,0 +1,50 @@ +#ifndef CSV_DOC_SUBVIEWFACTORYIMP_H +#define CSV_DOC_SUBVIEWFACTORYIMP_H + +#include "../../model/doc/document.hpp" + +#include "subviewfactory.hpp" + +namespace CSVDoc +{ + template + class SubViewFactory : public SubViewFactoryBase + { + public: + + virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + }; + + template + CSVDoc::SubView *SubViewFactory::makeSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document) + { + return new SubViewT (id, document); + } + + template + class SubViewFactoryWithCreateFlag : public SubViewFactoryBase + { + bool mCreateAndDelete; + + public: + + SubViewFactoryWithCreateFlag (bool createAndDelete); + + virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + }; + + template + SubViewFactoryWithCreateFlag::SubViewFactoryWithCreateFlag (bool createAndDelete) + : mCreateAndDelete (createAndDelete) + {} + + template + CSVDoc::SubView *SubViewFactoryWithCreateFlag::makeSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document) + { + return new SubViewT (id, document, mCreateAndDelete); + } +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp new file mode 100644 index 000000000..13edb6e74 --- /dev/null +++ b/apps/opencs/view/doc/view.cpp @@ -0,0 +1,216 @@ + +#include "view.hpp" + +#include +#include + +#include +#include +#include + +#include "../../model/doc/document.hpp" + +#include "../world/subviews.hpp" + +#include "../tools/subviews.hpp" + +#include "viewmanager.hpp" +#include "operations.hpp" +#include "subview.hpp" + +void CSVDoc::View::closeEvent (QCloseEvent *event) +{ + if (!mViewManager.closeRequest (this)) + event->ignore(); +} + +void CSVDoc::View::setupFileMenu() +{ + QMenu *file = menuBar()->addMenu (tr ("&File")); + + QAction *new_ = new QAction (tr ("New"), this); + connect (new_, SIGNAL (triggered()), this, SIGNAL (newDocumentRequest())); + file->addAction (new_); + + mSave = new QAction (tr ("&Save"), this); + connect (mSave, SIGNAL (triggered()), this, SLOT (save())); + file->addAction (mSave); +} + +void CSVDoc::View::setupEditMenu() +{ + QMenu *edit = menuBar()->addMenu (tr ("&Edit")); + + mUndo = mDocument->getUndoStack().createUndoAction (this, tr("&Undo")); + mUndo->setShortcuts (QKeySequence::Undo); + edit->addAction (mUndo); + + mRedo= mDocument->getUndoStack().createRedoAction (this, tr("&Redo")); + mRedo->setShortcuts (QKeySequence::Redo); + edit->addAction (mRedo); +} + +void CSVDoc::View::setupViewMenu() +{ + QMenu *view = menuBar()->addMenu (tr ("&View")); + + QAction *newWindow = new QAction (tr ("&New View"), this); + connect (newWindow, SIGNAL (triggered()), this, SLOT (newView())); + view->addAction (newWindow); +} + +void CSVDoc::View::setupWorldMenu() +{ + QMenu *world = menuBar()->addMenu (tr ("&World")); + + QAction *globals = new QAction (tr ("Globals"), this); + connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); + world->addAction (globals); + + mVerify = new QAction (tr ("&Verify"), this); + connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); + world->addAction (mVerify); +} + +void CSVDoc::View::setupUi() +{ + setupFileMenu(); + setupEditMenu(); + setupViewMenu(); + setupWorldMenu(); +} + +void CSVDoc::View::updateTitle() +{ + std::ostringstream stream; + + stream << mDocument->getName(); + + if (mDocument->getState() & CSMDoc::State_Modified) + stream << " *"; + + if (mViewTotal>1) + stream << " [" << (mViewIndex+1) << "/" << mViewTotal << "]"; + + setWindowTitle (stream.str().c_str()); +} + +void CSVDoc::View::updateActions() +{ + bool editing = !(mDocument->getState() & CSMDoc::State_Locked); + + for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) + (*iter)->setEnabled (editing); + + mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo()); + mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo()); + + mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving)); + mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); +} + +CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) +: mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews) +{ + setDockOptions (QMainWindow::AllowNestedDocks); + + resize (300, 300); /// \todo get default size from settings and set reasonable minimal size + + mOperations = new Operations; + addDockWidget (Qt::BottomDockWidgetArea, mOperations); + + updateTitle(); + + setupUi(); + + CSVWorld::addSubViewFactories (mSubViewFactory); + CSVTools::addSubViewFactories (mSubViewFactory); +} + +CSVDoc::View::~View() +{ +} + +const CSMDoc::Document *CSVDoc::View::getDocument() const +{ + return mDocument; +} + +CSMDoc::Document *CSVDoc::View::getDocument() +{ + return mDocument; +} + +void CSVDoc::View::setIndex (int viewIndex, int totalViews) +{ + mViewIndex = viewIndex; + mViewTotal = totalViews; + updateTitle(); +} + +void CSVDoc::View::updateDocumentState() +{ + updateTitle(); + updateActions(); + + static const int operations[] = + { + CSMDoc::State_Saving, CSMDoc::State_Verifying, + -1 // end marker + }; + + int state = mDocument->getState() ; + + for (int i=0; operations[i]!=-1; ++i) + if (!(state & operations[i])) + mOperations->quitOperation (operations[i]); + + QList subViews = findChildren(); + + for (QList::iterator iter (subViews.begin()); iter!=subViews.end(); ++iter) + (*iter)->setEditLock (state & CSMDoc::State_Locked); +} + +void CSVDoc::View::updateProgress (int current, int max, int type, int threads) +{ + mOperations->setProgress (current, max, type, threads); +} + +void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id) +{ + /// \todo add an user setting for limiting the number of sub views per top level view. Automatically open a new top level view if this + /// number is exceeded + + /// \todo if the sub view limit setting is one, the sub view title bar should be hidden and the text in the main title bar adjusted + /// accordingly + + /// \todo add an user setting to reuse sub views (on a per document basis or on a per top level view basis) + + SubView *view = mSubViewFactory.makeSubView (id, *mDocument); + addDockWidget (Qt::TopDockWidgetArea, view); + + connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&)), this, + SLOT (addSubView (const CSMWorld::UniversalId&))); + + view->show(); +} + +void CSVDoc::View::newView() +{ + mViewManager.addView (mDocument); +} + +void CSVDoc::View::save() +{ + mDocument->save(); +} + +void CSVDoc::View::verify() +{ + addSubView (mDocument->verify()); +} + +void CSVDoc::View::addGlobalsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Globals); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp new file mode 100644 index 000000000..b1dedafe9 --- /dev/null +++ b/apps/opencs/view/doc/view.hpp @@ -0,0 +1,103 @@ +#ifndef CSV_DOC_VIEW_H +#define CSV_DOC_VIEW_H + +#include +#include + +#include + +#include "subviewfactory.hpp" + +class QAction; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class UniversalId; +} + +namespace CSVDoc +{ + class ViewManager; + class Operations; + + class View : public QMainWindow + { + Q_OBJECT + + ViewManager& mViewManager; + CSMDoc::Document *mDocument; + int mViewIndex; + int mViewTotal; + QAction *mUndo; + QAction *mRedo; + QAction *mSave; + QAction *mVerify; + std::vector mEditingActions; + Operations *mOperations; + SubViewFactoryManager mSubViewFactory; + + // not implemented + View (const View&); + View& operator= (const View&); + + private: + + void closeEvent (QCloseEvent *event); + + void setupFileMenu(); + + void setupEditMenu(); + + void setupViewMenu(); + + void setupWorldMenu(); + + void setupUi(); + + void updateTitle(); + + void updateActions(); + + public: + + View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews); + ///< The ownership of \a document is not transferred to *this. + + virtual ~View(); + + const CSMDoc::Document *getDocument() const; + + CSMDoc::Document *getDocument(); + + void setIndex (int viewIndex, int totalViews); + + void updateDocumentState(); + + void updateProgress (int current, int max, int type, int threads); + + signals: + + void newDocumentRequest(); + + public slots: + + void addSubView (const CSMWorld::UniversalId& id); + + private slots: + + void newView(); + + void save(); + + void verify(); + + void addGlobalsSubView(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp new file mode 100644 index 000000000..22847c78b --- /dev/null +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -0,0 +1,112 @@ + +#include "viewmanager.hpp" + +#include + +#include "../../model/doc/documentmanager.hpp" +#include "../../model/doc/document.hpp" + +#include "view.hpp" + +void CSVDoc::ViewManager::updateIndices() +{ + std::map > documents; + + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + { + std::map >::iterator document = documents.find ((*iter)->getDocument()); + + if (document==documents.end()) + document = + documents.insert ( + std::make_pair ((*iter)->getDocument(), std::make_pair (0, countViews ((*iter)->getDocument())))). + first; + + (*iter)->setIndex (document->second.first++, document->second.second); + } +} + +CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) +: mDocumentManager (documentManager) +{ + +} + +CSVDoc::ViewManager::~ViewManager() +{ + for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + delete *iter; +} + +CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) +{ + if (countViews (document)==0) + { + // new document + connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), + this, SLOT (documentStateChanged (int, CSMDoc::Document *))); + + connect (document, SIGNAL (progress (int, int, int, int, CSMDoc::Document *)), + this, SLOT (progress (int, int, int, int, CSMDoc::Document *))); + } + + View *view = new View (*this, document, countViews (document)+1); + + mViews.push_back (view); + + view->show(); + + connect (view, SIGNAL (newDocumentRequest ()), this, SIGNAL (newDocumentRequest())); + + updateIndices(); + + return view; +} + +int CSVDoc::ViewManager::countViews (const CSMDoc::Document *document) const +{ + int count = 0; + + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + if ((*iter)->getDocument()==document) + ++count; + + return count; +} + +bool CSVDoc::ViewManager::closeRequest (View *view) +{ + std::vector::iterator iter = std::find (mViews.begin(), mViews.end(), view); + + if (iter!=mViews.end()) + { + bool last = countViews (view->getDocument())<=1; + + /// \todo check if save is in progress -> warn user about possible data loss + /// \todo check if document has not been saved -> return false and start close dialogue + + mViews.erase (iter); + view->deleteLater(); + + if (last) + mDocumentManager.removeDocument (view->getDocument()); + else + updateIndices(); + } + + return true; +} + +void CSVDoc::ViewManager::documentStateChanged (int state, CSMDoc::Document *document) +{ + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + if ((*iter)->getDocument()==document) + (*iter)->updateDocumentState(); +} + +void CSVDoc::ViewManager::progress (int current, int max, int type, int threads, CSMDoc::Document *document) +{ + for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + if ((*iter)->getDocument()==document) + (*iter)->updateProgress (current, max, type, threads); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp new file mode 100644 index 000000000..5e4b1be07 --- /dev/null +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -0,0 +1,58 @@ +#ifndef CSV_DOC_VIEWMANAGER_H +#define CSV_DOC_VIEWMANAGER_H + +#include + +#include + +namespace CSMDoc +{ + class Document; + class DocumentManager; +} + +namespace CSVDoc +{ + class View; + + class ViewManager : public QObject + { + Q_OBJECT + + CSMDoc::DocumentManager& mDocumentManager; + std::vector mViews; + + // not implemented + ViewManager (const ViewManager&); + ViewManager& operator= (const ViewManager&); + + void updateIndices(); + + public: + + ViewManager (CSMDoc::DocumentManager& documentManager); + + virtual ~ViewManager(); + + View *addView (CSMDoc::Document *document); + ///< The ownership of the returned view is not transferred. + + int countViews (const CSMDoc::Document *document) const; + ///< Return number of views for \a document. + + bool closeRequest (View *view); + + signals: + + void newDocumentRequest(); + + private slots: + + void documentStateChanged (int state, CSMDoc::Document *document); + + void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + }; + +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp new file mode 100644 index 000000000..fe1be85d7 --- /dev/null +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -0,0 +1,32 @@ + +#include "reportsubview.hpp" + +#include +#include + +#include "../../model/tools/reportmodel.hpp" + +CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) +: CSVDoc::SubView (id), mModel (document.getReport (id)) +{ + setWidget (mTable = new QTableView (this)); + mTable->setModel (mModel); + + mTable->horizontalHeader()->setResizeMode (QHeaderView::Interactive); + mTable->verticalHeader()->hide(); + mTable->setSortingEnabled (true); + mTable->setSelectionBehavior (QAbstractItemView::SelectRows); + mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); + + connect (mTable, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&))); +} + +void CSVTools::ReportSubView::setEditLock (bool locked) +{ + // ignored. We don't change document state anyway. +} + +void CSVTools::ReportSubView::show (const QModelIndex& index) +{ + focusId (mModel->getUniversalId (index.row())); +} \ No newline at end of file diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp new file mode 100644 index 000000000..626ceb663 --- /dev/null +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -0,0 +1,42 @@ +#ifndef CSV_TOOLS_REPORTSUBVIEW_H +#define CSV_TOOLS_REPORTSUBVIEW_H + +#include "../doc/subview.hpp" + +class QTableView; +class QModelIndex; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMTools +{ + class ReportModel; +} + +namespace CSVTools +{ + class Table; + + class ReportSubView : public CSVDoc::SubView + { + Q_OBJECT + + CSMTools::ReportModel *mModel; + QTableView *mTable; + + public: + + ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + + virtual void setEditLock (bool locked); + + private slots: + + void show (const QModelIndex& index); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp new file mode 100644 index 000000000..781cf602e --- /dev/null +++ b/apps/opencs/view/tools/subviews.cpp @@ -0,0 +1,12 @@ + +#include "subviews.hpp" + +#include "../doc/subviewfactoryimp.hpp" + +#include "reportsubview.hpp" + +void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) +{ + manager.add (CSMWorld::UniversalId::Type_VerificationResults, + new CSVDoc::SubViewFactory); +} \ No newline at end of file diff --git a/apps/opencs/view/tools/subviews.hpp b/apps/opencs/view/tools/subviews.hpp new file mode 100644 index 000000000..1bac32228 --- /dev/null +++ b/apps/opencs/view/tools/subviews.hpp @@ -0,0 +1,14 @@ +#ifndef CSV_TOOLS_SUBVIEWS_H +#define CSV_TOOLS_SUBVIEWS_H + +namespace CSVDoc +{ + class SubViewFactoryManager; +} + +namespace CSVTools +{ + void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); +} + +#endif diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp new file mode 100644 index 000000000..354223757 --- /dev/null +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -0,0 +1,40 @@ + +#include "dialoguesubview.hpp" + +#include +#include +#include + +#include "../../model/world/columnbase.hpp" + +CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + bool createAndDelete) +: SubView (id) +{ + QWidget *widget = new QWidget (this); + + setWidget (widget); + + QGridLayout *layout = new QGridLayout; + + widget->setLayout (layout); + + QAbstractTableModel *model = document.getData().getTableModel (id); + + int columns = model->columnCount(); + + for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + + if (flags & CSMWorld::ColumnBase::Flag_Dialogue) + { + layout->addWidget (new QLabel (model->headerData (i, Qt::Horizontal).toString()), i, 0); + } + } +} + +void CSVWorld::DialogueSubView::setEditLock (bool locked) +{ + +} \ No newline at end of file diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp new file mode 100644 index 000000000..c57dab108 --- /dev/null +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -0,0 +1,24 @@ +#ifndef CSV_WORLD_DIALOGUESUBVIEW_H +#define CSV_WORLD_DIALOGUESUBVIEW_H + +#include "../doc/subview.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class DialogueSubView : public CSVDoc::SubView + { + + public: + + DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, bool createAndDelete); + + virtual void setEditLock (bool locked); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp new file mode 100644 index 000000000..080a175ea --- /dev/null +++ b/apps/opencs/view/world/subviews.cpp @@ -0,0 +1,16 @@ + +#include "subviews.hpp" + +#include "../doc/subviewfactoryimp.hpp" + +#include "tablesubview.hpp" +#include "dialoguesubview.hpp" + +void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) +{ + manager.add (CSMWorld::UniversalId::Type_Globals, + new CSVDoc::SubViewFactoryWithCreateFlag (true)); + + manager.add (CSMWorld::UniversalId::Type_Global, + new CSVDoc::SubViewFactoryWithCreateFlag (true)); +} \ No newline at end of file diff --git a/apps/opencs/view/world/subviews.hpp b/apps/opencs/view/world/subviews.hpp new file mode 100644 index 000000000..51e4cb083 --- /dev/null +++ b/apps/opencs/view/world/subviews.hpp @@ -0,0 +1,14 @@ +#ifndef CSV_WORLD_SUBVIEWS_H +#define CSV_WORLD_SUBVIEWS_H + +namespace CSVDoc +{ + class SubViewFactoryManager; +} + +namespace CSVWorld +{ + void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); +} + +#endif diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp new file mode 100644 index 000000000..0721ead2c --- /dev/null +++ b/apps/opencs/view/world/table.cpp @@ -0,0 +1,199 @@ + +#include "table.hpp" + +#include + +#include +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtableproxymodel.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/record.hpp" + +#include "util.hpp" + +void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + QMenu menu (this); + + /// \todo add menu items for select all and clear selection + + if (!mEditLock) + { + if (mCreateAction) + menu.addAction (mCreateAction); + + if (listRevertableSelectedIds().size()>0) + menu.addAction (mRevertAction); + + if (listDeletableSelectedIds().size()>0) + menu.addAction (mDeleteAction); + } + + menu.exec (event->globalPos()); +} + +std::vector CSVWorld::Table::listRevertableSelectedIds() const +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + std::vector revertableIds; + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) + { + std::string id = mProxyModel->data (*iter).toString().toStdString(); + + CSMWorld::RecordBase::State state = + static_cast (mModel->data (mModel->getModelIndex (id, 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_BaseOnly) + revertableIds.push_back (id); + } + + return revertableIds; +} + +std::vector CSVWorld::Table::listDeletableSelectedIds() const +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + std::vector deletableIds; + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) + { + std::string id = mProxyModel->data (*iter).toString().toStdString(); + + CSMWorld::RecordBase::State state = + static_cast (mModel->data (mModel->getModelIndex (id, 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_Deleted) + deletableIds.push_back (id); + } + + return deletableIds; +} + +CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, + bool createAndDelete) +: mUndoStack (undoStack), mCreateAction (0), mEditLock (false) +{ + mModel = &dynamic_cast (*data.getTableModel (id)); + + mProxyModel = new CSMWorld::IdTableProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); + horizontalHeader()->setResizeMode (QHeaderView::Interactive); + verticalHeader()->hide(); + setSortingEnabled (true); + setSelectionBehavior (QAbstractItemView::SelectRows); + setSelectionMode (QAbstractItemView::ExtendedSelection); + + int columns = mModel->columnCount(); + + for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + + if (flags & CSMWorld::ColumnBase::Flag_Table) + { + CommandDelegate *delegate = new CommandDelegate (undoStack, this); + mDelegates.push_back (delegate); + setItemDelegateForColumn (i, delegate); + } + else + hideColumn (i); + } + + /// \todo make initial layout fill the whole width of the table + + if (createAndDelete) + { + mCreateAction = new QAction (tr ("Add Record"), this); + connect (mCreateAction, SIGNAL (triggered()), this, SLOT (createRecord())); + addAction (mCreateAction); + } + + mRevertAction = new QAction (tr ("Revert Record"), this); + connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); + addAction (mRevertAction); + + mDeleteAction = new QAction (tr ("Delete Record"), this); + connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); + addAction (mDeleteAction); +} + +void CSVWorld::Table::setEditLock (bool locked) +{ + for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) + (*iter)->setEditLock (locked); + + mEditLock = locked; +} + +CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const +{ + return CSMWorld::UniversalId ( + static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), + mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString()); +} + +#include /// \todo remove +void CSVWorld::Table::createRecord() +{ + if (!mEditLock) + { + /// \todo ask the user for an ID instead. + static int index = 0; + + std::ostringstream stream; + stream << "id" << index++; + + mUndoStack.push (new CSMWorld::CreateCommand (*mProxyModel, stream.str())); + } +} + +void CSVWorld::Table::revertRecord() +{ + if (!mEditLock) + { + std::vector revertableIds = listRevertableSelectedIds(); + + if (revertableIds.size()>0) + { + if (revertableIds.size()>1) + mUndoStack.beginMacro (tr ("Revert multiple records")); + + for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); + + if (revertableIds.size()>1) + mUndoStack.endMacro(); + } + } +} + +void CSVWorld::Table::deleteRecord() +{ + if (!mEditLock) + { + std::vector deletableIds = listDeletableSelectedIds(); + + if (deletableIds.size()>0) + { + if (deletableIds.size()>1) + mUndoStack.beginMacro (tr ("Delete multiple records")); + + for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); + + if (deletableIds.size()>1) + mUndoStack.endMacro(); + } + } +} \ No newline at end of file diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp new file mode 100644 index 000000000..df0224583 --- /dev/null +++ b/apps/opencs/view/world/table.hpp @@ -0,0 +1,65 @@ +#ifndef CSV_WORLD_TABLE_H +#define CSV_WORLD_TABLE_H + +#include +#include + +#include + +class QUndoStack; +class QAction; + +namespace CSMWorld +{ + class Data; + class UniversalId; + class IdTableProxyModel; + class IdTable; +} + +namespace CSVWorld +{ + class CommandDelegate; + + ///< Table widget + class Table : public QTableView + { + Q_OBJECT + + std::vector mDelegates; + QUndoStack& mUndoStack; + QAction *mCreateAction; + QAction *mRevertAction; + QAction *mDeleteAction; + CSMWorld::IdTableProxyModel *mProxyModel; + CSMWorld::IdTable *mModel; + bool mEditLock; + + private: + + void contextMenuEvent (QContextMenuEvent *event); + + std::vector listRevertableSelectedIds() const; + + std::vector listDeletableSelectedIds() const; + + public: + + Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete); + ///< \param createAndDelete Allow creation and deletion of records. + + void setEditLock (bool locked); + + CSMWorld::UniversalId getUniversalId (int row) const; + + private slots: + + void createRecord(); + + void revertRecord(); + + void deleteRecord(); + }; +} + +#endif diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp new file mode 100644 index 000000000..bb4bb76c6 --- /dev/null +++ b/apps/opencs/view/world/tablesubview.cpp @@ -0,0 +1,25 @@ + +#include "tablesubview.hpp" + +#include "../../model/doc/document.hpp" + +#include "table.hpp" + +CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + bool createAndDelete) +: SubView (id) +{ + setWidget (mTable = new Table (id, document.getData(), document.getUndoStack(), createAndDelete)); + + connect (mTable, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (rowActivated (const QModelIndex&))); +} + +void CSVWorld::TableSubView::setEditLock (bool locked) +{ + mTable->setEditLock (locked); +} + +void CSVWorld::TableSubView::rowActivated (const QModelIndex& index) +{ + focusId (mTable->getUniversalId (index.row())); +} \ No newline at end of file diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp new file mode 100644 index 000000000..0e7b8aa30 --- /dev/null +++ b/apps/opencs/view/world/tablesubview.hpp @@ -0,0 +1,35 @@ +#ifndef CSV_WORLD_TABLESUBVIEW_H +#define CSV_WORLD_TABLESUBVIEW_H + +#include "../doc/subview.hpp" + +class QModelIndex; + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class Table; + + class TableSubView : public CSVDoc::SubView + { + Q_OBJECT + + Table *mTable; + + public: + + TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, bool createAndDelete); + + virtual void setEditLock (bool locked); + + private slots: + + void rowActivated (const QModelIndex& index); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp new file mode 100644 index 000000000..7181dd4d1 --- /dev/null +++ b/apps/opencs/view/world/util.cpp @@ -0,0 +1,57 @@ + +#include "util.hpp" + +#include + +#include "../../model/world/commands.hpp" + +CSVWorld::NastyTableModelHack::NastyTableModelHack (QAbstractItemModel& model) +: mModel (model) +{} + +int CSVWorld::NastyTableModelHack::rowCount (const QModelIndex & parent) const +{ + return mModel.rowCount (parent); +} + +int CSVWorld::NastyTableModelHack::columnCount (const QModelIndex & parent) const +{ + return mModel.columnCount (parent); +} + +QVariant CSVWorld::NastyTableModelHack::data (const QModelIndex & index, int role) const +{ + return mModel.data (index, role); +} + +bool CSVWorld::NastyTableModelHack::setData ( const QModelIndex &index, const QVariant &value, int role) +{ + mData = value; + return true; +} + +QVariant CSVWorld::NastyTableModelHack::getData() const +{ + return mData; +} + +CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent) +: QStyledItemDelegate (parent), mUndoStack (undoStack), mEditLock (false) +{} + +void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, + const QModelIndex& index) const +{ + if (!mEditLock) + { + NastyTableModelHack hack (*model); + QStyledItemDelegate::setModelData (editor, &hack, index); + mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, hack.getData())); + } + ///< \todo provide some kind of feedback to the user, indicating that editing is currently not possible. +} + +void CSVWorld::CommandDelegate::setEditLock (bool locked) +{ + mEditLock = locked; +} \ No newline at end of file diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp new file mode 100644 index 000000000..136583118 --- /dev/null +++ b/apps/opencs/view/world/util.hpp @@ -0,0 +1,50 @@ +#ifndef CSV_WORLD_UTIL_H +#define CSV_WORLD_UTIL_H + +#include +#include + +class QUndoStack; + +namespace CSVWorld +{ + ///< \brief Getting the data out of an editor widget + /// + /// Really, Qt? Really? + class NastyTableModelHack : public QAbstractTableModel + { + QAbstractItemModel& mModel; + QVariant mData; + + public: + + NastyTableModelHack (QAbstractItemModel& model); + + int rowCount (const QModelIndex & parent = QModelIndex()) const; + + int columnCount (const QModelIndex & parent = QModelIndex()) const; + + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + + bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant getData() const; + }; + + ///< \brief Use commands instead of manipulating the model directly + class CommandDelegate : public QStyledItemDelegate + { + QUndoStack& mUndoStack; + bool mEditLock; + + public: + + CommandDelegate (QUndoStack& undoStack, QObject *parent); + + void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const; + + void setEditLock (bool locked); + }; +} + +#endif diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2299053cd..e2d28a808 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -334,12 +335,15 @@ void OMW::Engine::go() mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster, mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoding, mFallbackMap)); + //Load translation data + mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster); + // Create window manager - this manages all the MW-specific GUI windows MWScript::registerExtensions (mExtensions); mEnvironment.setWindowManager (new MWGui::WindowManager( mExtensions, mFpsLevel, mNewGame, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), - mCfgMgr.getCachePath ().string(), mScriptConsoleMode)); + mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage)); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); @@ -356,7 +360,7 @@ void OMW::Engine::go() // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); - mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts)); + mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); // Sets up the input system mEnvironment.setInputManager (new MWInput::InputManager (*mOgre, @@ -487,9 +491,10 @@ void OMW::Engine::showFPS(int level) mFpsLevel = level; } -void OMW::Engine::setEncoding(const std::string& encoding) +void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) { mEncoding = encoding; + mTranslationDataStorage.setEncoding (encoding); } void OMW::Engine::setFallbackValues(std::map fallbackMap) diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 57402c91e..00889197e 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -5,6 +5,7 @@ #include #include +#include #include "mwbase/environment.hpp" @@ -59,7 +60,7 @@ namespace OMW class Engine : private Ogre::FrameListener { MWBase::Environment mEnvironment; - std::string mEncoding; + ToUTF8::FromType mEncoding; Files::PathContainer mDataDirs; boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; @@ -79,9 +80,9 @@ namespace OMW Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; - Files::Collections mFileCollections; bool mFSStrict; + Translation::Storage mTranslationDataStorage; // not implemented Engine (const Engine&); @@ -154,7 +155,7 @@ namespace OMW void setCompileAll (bool all); /// Font encoding - void setEncoding(const std::string& encoding); + void setEncoding(const ToUTF8::FromType& encoding); void setAnimationVerbose(bool animverbose); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 2da52311f..96dbf8987 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -131,9 +131,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ->default_value(false), "enable console-only script functionality") ("script-run", bpo::value()->default_value(""), - "select a file that is executed in the console on startup\n\n" - "Note: The file contains a list of script lines, but not a complete scripts. " - "That means no begin/end and no variable declarations.") + "select a file containing a list of console commands that is executed on startup") ("new-game", bpo::value()->implicit_value(true) ->default_value(false), "activate char gen/new game mechanics") @@ -183,21 +181,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // Font encoding settings std::string encoding(variables["encoding"].as()); - if (encoding == "win1250") - { - std::cout << "Using Central and Eastern European font encoding." << std::endl; - engine.setEncoding(encoding); - } - else if (encoding == "win1251") - { - std::cout << "Using Cyrillic font encoding." << std::endl; - engine.setEncoding(encoding); - } - else - { - std::cout << "Using default (English) font encoding." << std::endl; - engine.setEncoding("win1252"); - } + std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; + engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings engine.enableFSStrict(variables["fs-strict"].as()); diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c177912d4..30bfced06 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwmechanics/stat.hpp" #include "../mwgui/mode.hpp" @@ -233,6 +235,8 @@ namespace MWBase virtual void startSpellMaking(MWWorld::Ptr actor) = 0; virtual void startEnchanting(MWWorld::Ptr actor) = 0; virtual void startTraining(MWWorld::Ptr actor) = 0; + + virtual const Translation::Storage& getTranslationDataStorage() const = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 40ebde246..cc625b306 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -133,6 +133,10 @@ namespace MWBase virtual char getGlobalVariableType (const std::string& name) const = 0; ///< Return ' ', if there is no global variable with this name. + + virtual std::vector getGlobals () const = 0; + + virtual std::string getCurrentCellName() const = 0; virtual MWWorld::Ptr getPtr (const std::string& name, bool activeOnly) = 0; ///< Return a pointer to a liveCellRef with the given name. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 843d1af4c..09a15a2cb 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -204,33 +204,10 @@ namespace MWClass std::string text; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - if (ref->mRef.mTeleport) { - std::string dest; - if (ref->mRef.mDestCell != "") - { - // door leads to an interior, use interior name as tooltip - dest = ref->mRef.mDestCell; - } - else - { - // door leads to exterior, use cell name (if any), otherwise translated region name - int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (ref->mRef.mDoorDest.pos[0], ref->mRef.mDoorDest.pos[1], x, y); - const ESM::Cell* cell = store.get().find(x,y); - if (cell->mName != "") - dest = cell->mName; - else - { - const ESM::Region* region = - store.get().find(cell->mRegion); - dest = region->mName; - } - } text += "\n#{sTo}"; - text += "\n"+dest; + text += "\n" + getDestination(*ref); } if (ref->mRef.mLockLevel > 0) @@ -246,6 +223,37 @@ namespace MWClass return info; } + std::string Door::getDestination (const MWWorld::LiveCellRef& door) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string dest; + if (door.mRef.mDestCell != "") + { + // door leads to an interior, use interior name as tooltip + dest = door.mRef.mDestCell; + } + else + { + // door leads to exterior, use cell name (if any), otherwise translated region name + int x,y; + MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.mDoorDest.pos[0], door.mRef.mDoorDest.pos[1], x, y); + const ESM::Cell* cell = store.get().find(x,y); + if (cell->mName != "") + dest = cell->mName; + else + { + const ESM::Region* region = + store.get().find(cell->mRegion); + + //name as is, not a token + return region->mName; + } + } + + return "#{sCell=" + dest + "}"; + } + MWWorld::Ptr Door::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index b0f86f12d..05ba0248b 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -31,6 +31,9 @@ namespace MWClass virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + static std::string getDestination (const MWWorld::LiveCellRef& door); + ///< @return destination cell name or token + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const; ///< Lock object diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index a97cf98de..f9d8c3459 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -39,47 +40,14 @@ #include "filter.hpp" -namespace -{ - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } - - bool stringCompareNoCase (std::string first, std::string second) - { - unsigned int i=0; - while ( (itolower(second[i])) return false; - ++i; - } - if (first.length()::iterator it = dialogs.begin(); for (; it != dialogs.end(); ++it) { - mDialogueMap[toLower(it->mId)] = *it; + mDialogueMap[Misc::StringUtils::lowerCase(it->mId)] = *it; } } void DialogueManager::addTopic (const std::string& topic) { - mKnownTopics[toLower(topic)] = true; + mKnownTopics[Misc::StringUtils::lowerCase(topic)] = true; } void DialogueManager::parseText (const std::string& text) { - std::list::iterator it; - for(it = mActorKnownTopics.begin();it != mActorKnownTopics.end();++it) + std::vector hypertext = ParseHyperText(text); + + //calculation of standard form fir all hyperlinks + for (size_t i = 0; i < hypertext.size(); ++i) { - size_t pos = find_str_ci(text,*it,0); - if(pos !=std::string::npos) + if (hypertext[i].mLink) { - mKnownTopics[*it] = true; + size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText); + for(; asterisk_count > 0; --asterisk_count) + hypertext[i].mText.append("*"); + + hypertext[i].mText = mTranslationDataStorage.topicStandardForm(hypertext[i].mText); } } + + for (size_t i = 0; i < hypertext.size(); ++i) + { + std::list::iterator it; + for(it = mActorKnownTopics.begin(); it != mActorKnownTopics.end(); ++it) + { + if (hypertext[i].mLink) + { + if( hypertext[i].mText == *it ) + { + mKnownTopics[hypertext[i].mText] = true; + } + } + else if( !mTranslationDataStorage.hasTranslation() ) + { + size_t pos = Misc::StringUtils::lowerCase(hypertext[i].mText).find(*it, 0); + if(pos !=std::string::npos) + { + mKnownTopics[*it] = true; + } + } + } + } + updateTopics(); } @@ -155,7 +152,9 @@ namespace MWDialogue } parseText (info->mResponse); - win->addText (info->mResponse); + + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); mLastTopic = it->mId; mLastDialogue = *info; @@ -238,6 +237,45 @@ namespace MWDialogue } } + void DialogueManager::executeTopic (const std::string& topic) + { + Filter filter (mActor, mChoice, mTalkedTo); + + const MWWorld::Store &dialogues = + MWBase::Environment::get().getWorld()->getStore().get(); + + const ESM::Dialogue& dialogue = *dialogues.find (topic); + + if (const ESM::DialInfo *info = filter.search (dialogue)) + { + parseText (info->mResponse); + + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + + if (dialogue.mType==ESM::Dialogue::Persuasion) + { + std::string modifiedTopic = "s" + topic; + + modifiedTopic.erase (std::remove (modifiedTopic.begin(), modifiedTopic.end(), ' '), modifiedTopic.end()); + + const MWWorld::Store& gmsts = + MWBase::Environment::get().getWorld()->getStore().get(); + + win->addTitle (gmsts.find (modifiedTopic)->getString()); + } + else + win->addTitle (topic); + + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + win->addText (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + + executeScript (info->mResultScript); + + mLastTopic = topic; + mLastDialogue = *info; + } + } + void DialogueManager::updateTopics() { std::list keywordList; @@ -256,10 +294,11 @@ namespace MWDialogue { if (filter.search (*iter)) { - mActorKnownTopics.push_back (toLower (iter->mId)); + std::string lower = Misc::StringUtils::lowerCase(iter->mId); + mActorKnownTopics.push_back (lower); //does the player know the topic? - if (mKnownTopics.find (toLower (iter->mId)) != mKnownTopics.end()) + if (mKnownTopics.find (lower) != mKnownTopics.end()) { keywordList.push_back (iter->mId); } @@ -317,7 +356,7 @@ namespace MWDialogue win->setServices (windowServices); // sort again, because the previous sort was case-sensitive - keywordList.sort(stringCompareNoCase); + keywordList.sort(Misc::StringUtils::ciEqual); win->setKeywords(keywordList); mChoice = choice; @@ -332,24 +371,7 @@ namespace MWDialogue ESM::Dialogue ndialogue = mDialogueMap[keyword]; if (mDialogueMap[keyword].mType == ESM::Dialogue::Topic) { - Filter filter (mActor, mChoice, mTalkedTo); - - if (const ESM::DialInfo *info = filter.search (mDialogueMap[keyword])) - { - std::string text = info->mResponse; - std::string script = info->mResultScript; - - parseText (text); - - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->addTitle (keyword); - win->addText (info->mResponse); - - executeScript (script); - - mLastTopic = keyword; - mLastDialogue = *info; - } + executeTopic (keyword); } } } @@ -390,7 +412,9 @@ namespace MWDialogue mIsInChoice = false; std::string text = info->mResponse; parseText (text); - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (text); + + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addText (Interpreter::fixDefinesDialog(text, interpreterContext)); executeScript (info->mResultScript); mLastTopic = mLastTopic; mLastDialogue = *info; @@ -412,7 +436,7 @@ namespace MWDialogue { MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->askQuestion(question); - mChoiceMap[toLower(question)] = choice; + mChoiceMap[Misc::StringUtils::lowerCase(question)] = choice; mIsInChoice = true; } @@ -445,30 +469,22 @@ namespace MWDialogue else if (curDisp + mTemporaryDispositionChange > 100) mTemporaryDispositionChange = 100 - curDisp; - // practice skill MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); - if (success) - MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, 0); - - // add status message to dialogue window std::string text; if (type == MWBase::MechanicsManager::PT_Admire) - text = "sAdmire"; + text = "Admire"; else if (type == MWBase::MechanicsManager::PT_Taunt) - text = "sTaunt"; + text = "Taunt"; else if (type == MWBase::MechanicsManager::PT_Intimidate) - text = "sIntimidate"; - else - text = "sBribe"; + text = "Intimidate"; + else{ + text = "Bribe"; + } - text += (success ? "Success" : "Fail"); - - MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->addTitle(MyGUI::LanguageManager::getInstance().replaceTags("#{"+text+"}")); - - /// \todo text from INFO record, how to get the ID? + executeTopic (text + (success ? " Success" : " Fail")); } int DialogueManager::getTemporaryDispositionChange() const @@ -480,4 +496,57 @@ namespace MWDialogue { mTemporaryDispositionChange += delta; } + + std::vector ParseHyperText(const std::string& text) + { + std::vector result; + + MyGUI::UString utext(text); + + size_t pos_begin, pos_end, iteration_pos = 0; + for(;;) + { + pos_begin = utext.find('@', iteration_pos); + if (pos_begin != std::string::npos) + pos_end = utext.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + result.push_back( HyperTextToken(utext.substr(iteration_pos, pos_begin - iteration_pos), false) ); + + std::string link = utext.substr(pos_begin + 1, pos_end - pos_begin - 1); + result.push_back( HyperTextToken(link, true) ); + + iteration_pos = pos_end + 1; + } + else + { + result.push_back( HyperTextToken(utext.substr(iteration_pos), false) ); + break; + } + } + + return result; + } + + size_t RemovePseudoAsterisks(std::string& phrase) + { + size_t pseudoAsterisksCount = 0; + const char specialPseudoAsteriskCharacter = 127; + + if( !phrase.empty() ) + { + std::string::reverse_iterator rit = phrase.rbegin(); + + while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) + { + pseudoAsterisksCount++; + ++rit; + } + } + + phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); + + return pseudoAsterisksCount; + } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 9e1971b0f..1ca2ae5eb 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -7,6 +7,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" @@ -20,10 +21,11 @@ namespace MWDialogue std::map mKnownTopics;// Those are the topics the player knows. std::list mActorKnownTopics; + Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; std::ostream mErrorStream; Compiler::StreamErrorHandler mErrorHandler; - + MWWorld::Ptr mActor; bool mTalkedTo; @@ -46,9 +48,11 @@ namespace MWDialogue void printError (const std::string& error); + void executeTopic (const std::string& topic); + public: - DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose); + DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose, Translation::Storage& translationDataStorage); virtual void startDialogue (const MWWorld::Ptr& actor); @@ -70,6 +74,21 @@ namespace MWDialogue virtual int getTemporaryDispositionChange () const; virtual void applyTemporaryDispositionChange (int delta); }; + + + struct HyperTextToken + { + HyperTextToken(const std::string& text, bool link) : mText(text), mLink(link) {} + + std::string mText; + bool mLink; + }; + + // In translations (at least Russian) the links are marked with @#, so + // it should be a function to parse it + std::vector ParseHyperText(const std::string& text); + + size_t RemovePseudoAsterisks(std::string& phrase); } #endif diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index fc06e866c..db1a81c2c 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -63,7 +63,7 @@ namespace MWGui void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { mAlchemy.clear(); - + mWindowManager.removeGuiMode(GM_Alchemy); mWindowManager.removeGuiMode(GM_Inventory); } @@ -119,7 +119,6 @@ namespace MWGui if (mIngredients[i]->isUserString("ToolTipType")) { MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); - ingred.getRefData().setCount(ingred.getRefData().getCount()-1); if (ingred.getRefData().getCount() == 0) removeIngredient(mIngredients[i]); } diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 20bc95445..479a82efa 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -195,13 +195,13 @@ void ContainerBase::sellAlreadyBoughtItem(MyGUI::Widget* _sender, int count) if (isInventory()) { MWBase::Environment::get().getWindowManager()->getTradeWindow()->addItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count, true); MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); } else { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count, true); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); } @@ -218,13 +218,13 @@ void ContainerBase::sellItem(MyGUI::Widget* _sender, int count) if (isInventory()) { MWBase::Environment::get().getWindowManager()->getTradeWindow()->addBarteredItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count, false); MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); } else { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addBarteredItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count, false); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 827da1085..5b04cc37e 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -16,6 +16,8 @@ #include "../mwmechanics/npcstats.hpp" +#include "../mwdialogue/dialoguemanagerimp.hpp" + #include "dialogue_history.hpp" #include "widgets.hpp" #include "list.hpp" @@ -52,7 +54,6 @@ bool sortByLength (const std::string& left, const std::string& right) { return left.size() > right.size(); } - } @@ -180,9 +181,30 @@ void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) if(color != "#B29154") { MyGUI::UString key = mHistory->getColorTextAt(cursorPosition); - if(color == "#686EBA") MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); - if(color == "#572D21") MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); + if(color == "#686EBA") + { + std::map::iterator i = mHyperLinks.upper_bound(cursorPosition); + if( !mHyperLinks.empty() ) + { + --i; + + if( i->first + i->second.mLength > cursorPosition) + { + MWBase::Environment::get().getDialogueManager()->keywordSelected(i->second.mTrueValue); + } + } + else + { + // the link was colored, but it is not in mHyperLinks. + // It means that those liunks are not marked with @# and found + // by topic name search + MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); + } + } + + if(color == "#572D21") + MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); } } @@ -258,6 +280,7 @@ void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) setTitle(npcName); mTopicsList->clear(); + mHyperLinks.clear(); mHistory->setCaption(""); updateOptions(); } @@ -316,6 +339,22 @@ void addColorInString(std::string& str, const std::string& keyword,std::string c size_t pos = 0; while((pos = find_str_ci(str,keyword, pos)) != std::string::npos) { + // do not add color if this portion of text is already colored. + { + MyGUI::TextIterator iterator (str); + MyGUI::UString colour; + while(iterator.moveNext()) + { + size_t iteratorPos = iterator.getPosition(); + iterator.getTagColour(colour); + if (iteratorPos == pos) + break; + } + + if (colour == color1) + return; + } + str.insert(pos,color1); pos += color1.length(); pos += keyword.length(); @@ -324,7 +363,7 @@ void addColorInString(std::string& str, const std::string& keyword,std::string c } } -std::string DialogueWindow::parseText(std::string text) +std::string DialogueWindow::parseText(const std::string& text) { bool separatorReached = false; // only parse topics that are below the separator (this prevents actions like "Barter" that are not topics from getting blue-colored) @@ -342,11 +381,56 @@ std::string DialogueWindow::parseText(std::string text) // sort by length to make sure longer topics are replaced first std::sort(topics.begin(), topics.end(), sortByLength); - for(std::vector::const_iterator it = topics.begin(); it != topics.end(); ++it) + std::vector hypertext = MWDialogue::ParseHyperText(text); + + size_t historySize = 0; + if(mHistory->getClient()->getSubWidgetText() != nullptr) { - addColorInString(text,*it,"#686EBA","#B29154"); + historySize = mHistory->getOnlyText().size(); } - return text; + + std::string result; + size_t hypertextPos = 0; + for (size_t i = 0; i < hypertext.size(); ++i) + { + if (hypertext[i].mLink) + { + size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText); + std::string standardForm = hypertext[i].mText; + for(; asterisk_count > 0; --asterisk_count) + standardForm.append("*"); + + standardForm = + MWBase::Environment::get().getWindowManager()-> + getTranslationDataStorage().topicStandardForm(standardForm); + + if( std::find(topics.begin(), topics.end(), std::string(standardForm) ) != topics.end() ) + { + result.append("#686EBA").append(hypertext[i].mText).append("#B29154"); + + mHyperLinks[historySize+hypertextPos].mLength = MyGUI::UString(hypertext[i].mText).length(); + mHyperLinks[historySize+hypertextPos].mTrueValue = lower_string(standardForm); + } + else + result += hypertext[i].mText; + } + else + { + if( !mWindowManager.getTranslationDataStorage().hasTranslation() ) + { + for(std::vector::const_iterator it = topics.begin(); it != topics.end(); ++it) + { + addColorInString(hypertext[i].mText, *it, "#686EBA", "#B29154"); + } + } + + result += hypertext[i].mText; + } + + hypertextPos += MyGUI::UString(hypertext[i].mText).length(); + } + + return result; } void DialogueWindow::addText(std::string text) @@ -354,6 +438,11 @@ void DialogueWindow::addText(std::string text) mHistory->addDialogText("#B29154"+parseText(text)+"#B29154"); } +void DialogueWindow::addMessageBox(const std::string& text) +{ + mHistory->addDialogText("\n#FFFFFF"+text+"#B29154"); +} + void DialogueWindow::addTitle(std::string text) { // This is called from the dialogue manager, so text is @@ -378,6 +467,7 @@ void DialogueWindow::updateOptions() { //Clear the list of topics mTopicsList->clear(); + mHyperLinks.clear(); mHistory->eraseText(0, mHistory->getTextLength()); if (mPtr.getTypeName() == typeid(ESM::NPC).name()) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 082d92524..5c11c311a 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -65,6 +65,7 @@ namespace MWGui void setKeywords(std::list keyWord); void removeKeyword(std::string keyWord); void addText(std::string text); + void addMessageBox(const std::string& text); void addTitle(std::string text); void askQuestion(std::string question); void goodbye(); @@ -92,12 +93,18 @@ namespace MWGui virtual void onReferenceUnavailable(); + struct HyperLink + { + size_t mLength; + std::string mTrueValue; + }; + private: void updateOptions(); /** *Helper function that add topic keyword in blue in a text. */ - std::string parseText(std::string text); + std::string parseText(const std::string& text); int mServices; @@ -109,6 +116,8 @@ namespace MWGui MyGUI::EditPtr mDispositionText; PersuasionDialog mPersuasionDialog; + + std::map mHyperLinks; }; } #endif diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 53c23c25d..4090b592d 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -1,5 +1,10 @@ #include "formatting.hpp" +#include + +#include "../mwscript/interpretercontext.hpp" +#include "../mwworld/ptr.hpp" + #include #include @@ -68,6 +73,9 @@ std::vector BookTextParser::split(std::string text, const int width { std::vector result; + MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + text = Interpreter::fixDefinesBook(text, interpreterContext); + boost::algorithm::replace_all(text, "
", "\n"); boost::algorithm::replace_all(text, "

", "\n\n"); @@ -167,6 +175,10 @@ std::vector BookTextParser::split(std::string text, const int width MyGUI::IntSize BookTextParser::parse(std::string text, MyGUI::Widget* parent, const int width) { + MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + text = Interpreter::fixDefinesBook(text, interpreterContext); + + mParent = parent; mWidth = width; mHeight = 0; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 9b4075f57..689baf488 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -319,7 +319,7 @@ void HUD::setCellName(const std::string& cellName) mCellNameTimer = 5.0f; mCellName = cellName; - mCellNameBox->setCaption(mCellName); + mCellNameBox->setCaptionWithReplacing("#{sCell=" + mCellName + "}"); mCellNameBox->setVisible(mMapVisible); } } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index bb3dc67e6..ffb6c5282 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -24,19 +24,6 @@ #include "scrollwindow.hpp" #include "spellwindow.hpp" -namespace -{ - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } -} - namespace MWGui { @@ -284,7 +271,7 @@ namespace MWGui for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { - if (toLower(it->getCellRef().mRefID) == "gold_001") + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) return it->getRefData().getCount(); } return 0; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 9d5d236be..bbd3d4d28 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -84,9 +84,9 @@ book formatText(std::string text,book mBook,int maxLine, int lineSize) MWGui::JournalWindow::JournalWindow (MWBase::WindowManager& parWindowManager) : WindowBase("openmw_journal.layout", parWindowManager) , mLastPos(0) - , mVisible(false) , mPageNumber(0) { + mMainWidget->setVisible(false); //setCoord(0,0,498, 342); center(); @@ -115,20 +115,15 @@ MWGui::JournalWindow::JournalWindow (MWBase::WindowManager& parWindowManager) //displayLeftText(list.front()); } -void MWGui::JournalWindow::setVisible(bool visible) +void MWGui::JournalWindow::close() { - if (mVisible && !visible) - MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); - mVisible = visible; - - mMainWidget->setVisible(visible); + MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); } void MWGui::JournalWindow::open() { mPageNumber = 0; - std::string journalOpenSound = "book open"; - MWBase::Environment::get().getSoundManager()->playSound (journalOpenSound, 1.0, 1.0); + MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); if(MWBase::Environment::get().getJournal()->begin()!=MWBase::Environment::get().getJournal()->end()) { book journal; diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index a62e48803..3317880cd 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -15,8 +15,7 @@ namespace MWGui public: JournalWindow(MWBase::WindowManager& parWindowManager); virtual void open(); - - virtual void setVisible(bool visible); // only used to play close sound + virtual void close(); private: void displayLeftText(std::string text); @@ -41,7 +40,6 @@ namespace MWGui std::vector mLeftPages; std::vector mRightPages; int mPageNumber; //store the number of the current left page - bool mVisible; }; } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 8bcaa6ce0..d721e209a 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -226,9 +226,14 @@ namespace MWGui if (start == "splash") splash.push_back (*it); } - std::string randomSplash = splash[rand() % splash.size()]; + if (splash.size()) + { + std::string randomSplash = splash[rand() % splash.size()]; - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, "General"); - mBackgroundImage->setImageTexture (randomSplash); + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, "General"); + mBackgroundImage->setImageTexture (randomSplash); + } + else + std::cerr << "No loading screens found!" << std::endl; } } diff --git a/apps/openmw/mwgui/map_window.cpp b/apps/openmw/mwgui/map_window.cpp index 0a26ebb8f..4e2ee517e 100644 --- a/apps/openmw/mwgui/map_window.cpp +++ b/apps/openmw/mwgui/map_window.cpp @@ -299,7 +299,7 @@ MapWindow::~MapWindow() void MapWindow::setCellName(const std::string& cellName) { - setTitle(cellName); + setTitle("#{sCell=" + cellName + "}"); } void MapWindow::addVisitedLocation(const std::string& name, int x, int y) diff --git a/apps/openmw/mwgui/stats_window.cpp b/apps/openmw/mwgui/stats_window.cpp index 38a06940f..70ceed857 100644 --- a/apps/openmw/mwgui/stats_window.cpp +++ b/apps/openmw/mwgui/stats_window.cpp @@ -252,6 +252,7 @@ void StatsWindow::onFrame () } setFactions(PCstats.getFactionRanks()); + setExpelled(PCstats.getExpelled ()); const std::string &signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); @@ -273,6 +274,15 @@ void StatsWindow::setFactions (const FactionList& factions) } } +void StatsWindow::setExpelled (const std::set& expelled) +{ + if (mExpelled != expelled) + { + mExpelled = expelled; + mChanged = true; + } +} + void StatsWindow::setBirthSign (const std::string& signId) { if (signId != mBirthSignId) @@ -462,6 +472,10 @@ void StatsWindow::updateSkillArea() if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::NpcStats PCstats = MWWorld::Class::get(player).getNpcStats(player); + std::set& expelled = PCstats.getExpelled (); + addGroup(mWindowManager.getGameSettingString("sFaction", "Faction"), coord1, coord2); FactionList::const_iterator end = mFactions.end(); for (FactionList::const_iterator it = mFactions.begin(); it != end; ++it) @@ -473,36 +487,42 @@ void StatsWindow::updateSkillArea() std::string text; text += std::string("#DDC79E") + faction->mName; - text += std::string("\n#BF9959") + faction->mRanks[it->second]; - if (it->second < 9) + if (expelled.find(it->first) != expelled.end()) + text += "\n#{sExpelled}"; + else { - // player doesn't have max rank yet - text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[it->second+1]; + text += std::string("\n#BF9959") + faction->mRanks[it->second]; - ESM::RankData rankData = faction->mData.mRankData[it->second+1]; - const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute1); - const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute2); - assert(attr1 && attr2); - - text += "\n#BF9959#{" + attr1->mName + "}: " + boost::lexical_cast(rankData.mAttribute1) - + ", #{" + attr2->mName + "}: " + boost::lexical_cast(rankData.mAttribute2); - - text += "\n\n#DDC79E#{sFavoriteSkills}"; - text += "\n#BF9959"; - for (int i=0; i<6; ++i) + if (it->second < 9) { - text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkillID[i]]+"}"; - if (i<5) - text += ", "; + // player doesn't have max rank yet + text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[it->second+1]; + + ESM::RankData rankData = faction->mData.mRankData[it->second+1]; + const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute1); + const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute2); + assert(attr1 && attr2); + + text += "\n#BF9959#{" + attr1->mName + "}: " + boost::lexical_cast(rankData.mAttribute1) + + ", #{" + attr2->mName + "}: " + boost::lexical_cast(rankData.mAttribute2); + + text += "\n\n#DDC79E#{sFavoriteSkills}"; + text += "\n#BF9959"; + for (int i=0; i<6; ++i) + { + text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkillID[i]]+"}"; + if (i<5) + text += ", "; + } + + text += "\n"; + + if (rankData.mSkill1 > 0) + text += "\n#{sNeedOneSkill} " + boost::lexical_cast(rankData.mSkill1); + if (rankData.mSkill2 > 0) + text += "\n#{sNeedTwoSkills} " + boost::lexical_cast(rankData.mSkill2); } - - text += "\n"; - - if (rankData.mSkill1 > 0) - text += "\n#{sNeedOneSkill} " + boost::lexical_cast(rankData.mSkill1); - if (rankData.mSkill2 > 0) - text += "\n#{sNeedTwoSkills} " + boost::lexical_cast(rankData.mSkill2); } w->setUserString("ToolTipType", "Layout"); diff --git a/apps/openmw/mwgui/stats_window.hpp b/apps/openmw/mwgui/stats_window.hpp index f43682c96..6619680fa 100644 --- a/apps/openmw/mwgui/stats_window.hpp +++ b/apps/openmw/mwgui/stats_window.hpp @@ -50,6 +50,7 @@ namespace MWGui MyGUI::Widget* addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void setFactions (const FactionList& factions); + void setExpelled (const std::set& expelled); void setBirthSign (const std::string &signId); void onWindowResize(MyGUI::Window* window); @@ -71,6 +72,7 @@ namespace MWGui std::string mBirthSignId; int mReputation, mBounty; std::vector mSkillWidgets; //< Skills and other information + std::set mExpelled; bool mChanged; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 510bcb58f..97df211ac 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -31,6 +31,7 @@ ToolTips::ToolTips(MWBase::WindowManager* windowManager) : , mRemainingDelay(0.0) , mLastMouseX(0) , mLastMouseY(0) + , mHorizontalScrollIndex(0) { getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); @@ -52,6 +53,7 @@ void ToolTips::setEnabled(bool enabled) void ToolTips::onFrame(float frameDuration) { + while (mDynamicToolTipBox->getChildCount()) { MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0)); @@ -103,7 +105,7 @@ void ToolTips::onFrame(float frameDuration) else { - const MyGUI::IntPoint& lastPressed = InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); + const MyGUI::IntPoint& lastPressed = InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); if (mousePos == lastPressed) // mouseclick makes tooltip disappear return; @@ -114,11 +116,13 @@ void ToolTips::onFrame(float frameDuration) } else { + mHorizontalScrollIndex = 0; mRemainingDelay = mDelay; } mLastMouseX = mousePos.left; mLastMouseY = mousePos.top; + if (mRemainingDelay > 0) return; @@ -148,7 +152,8 @@ void ToolTips::onFrame(float frameDuration) { return; } - + + // special handling for markers on the local map: the tooltip should only be visible // if the marker is not hidden due to the fog of war. if (focus->getUserString ("IsMarker") == "true") @@ -354,7 +359,7 @@ void ToolTips::findImageExtension(std::string& image) } IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) -{ +{ mDynamicToolTipBox->setVisible(true); std::string caption = info.caption; @@ -388,6 +393,8 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) setCoord(0, 0, 300, 300); const IntPoint padding(8, 8); + + const int maximumWidth = 500; const int imageCaptionHPadding = (caption != "" ? 8 : 0); const int imageCaptionVPadding = (caption != "" ? 4 : 0); @@ -411,7 +418,7 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) IntSize textSize = textWidget->getTextSize(); captionSize += IntSize(imageSize, 0); // adjust for image - IntSize totalSize = IntSize( std::max(textSize.width, captionSize.width + ((image != "") ? imageCaptionHPadding : 0)), + IntSize totalSize = IntSize( std::min(std::max(textSize.width,captionSize.width + ((image != "") ? imageCaptionHPadding : 0)),maximumWidth), ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight ); if (!info.effects.empty()) @@ -499,8 +506,24 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) (captionHeight-captionSize.height)/2, captionSize.width-imageSize, captionSize.height); + + //if its too long we do hscroll with the caption + if (captionSize.width > maximumWidth){ + mHorizontalScrollIndex = mHorizontalScrollIndex + 2; + if (mHorizontalScrollIndex > captionSize.width){ + mHorizontalScrollIndex = -totalSize.width; + } + int horizontal_scroll = mHorizontalScrollIndex; + if (horizontal_scroll < 40){ + horizontal_scroll = 40; + }else{ + horizontal_scroll = 80 - mHorizontalScrollIndex; + } + captionWidget->setPosition (IntPoint(horizontal_scroll, captionWidget->getPosition().top + padding.top)); + } else { + captionWidget->setPosition (captionWidget->getPosition() + padding); + } - captionWidget->setPosition (captionWidget->getPosition() + padding); textWidget->setPosition (textWidget->getPosition() + IntPoint(0, padding.top)); // only apply vertical padding, the horizontal works automatically due to Align::HCenter if (image != "") diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 270df4ae2..4048d0d5a 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -90,6 +90,9 @@ namespace MWGui float mFocusToolTipX; float mFocusToolTipY; + + int mHorizontalScrollIndex; + float mDelay; float mRemainingDelay; // remaining time until tooltip will show diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c6a21c461..51eb3d87e 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -211,7 +211,7 @@ namespace MWGui + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100)); MWMechanics::NpcStats sellerSkill = MWWorld::Class::get(mPtr).getNpcStats(mPtr); - MWMechanics::CreatureStats sellerStats = MWWorld::Class::get(mPtr).getCreatureStats(mPtr); + MWMechanics::CreatureStats sellerStats = MWWorld::Class::get(mPtr).getCreatureStats(mPtr); MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::NpcStats playerSkill = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); MWMechanics::CreatureStats playerStats = MWWorld::Class::get(playerPtr).getCreatureStats(playerPtr); @@ -239,7 +239,10 @@ namespace MWGui MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterFailDisposition); return; } - } + + //skill use! + MWWorld::Class::get(playerPtr).skillUsageSucceeded(playerPtr, ESM::Skill::Mercantile, 0); + } int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt(); MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterSuccessDisposition); @@ -398,19 +401,23 @@ namespace MWGui return items; } - void TradeWindow::sellToNpc(MWWorld::Ptr item, int count) + void TradeWindow::sellToNpc(MWWorld::Ptr item, int count, bool boughtItem) { + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, boughtItem); + + mCurrentBalance += diff; + mCurrentMerchantOffer += diff; - mCurrentBalance -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,true); - mCurrentMerchantOffer -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,true); updateLabels(); } - void TradeWindow::buyFromNpc(MWWorld::Ptr item, int count) + void TradeWindow::buyFromNpc(MWWorld::Ptr item, int count, bool soldItem) { + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, !soldItem); + + mCurrentBalance -= diff; + mCurrentMerchantOffer -= diff; - mCurrentBalance += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,false); - mCurrentMerchantOffer += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count,false); updateLabels(); } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index db386d8b6..219e44036 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -27,8 +27,8 @@ namespace MWGui void startTrade(MWWorld::Ptr actor); - void sellToNpc(MWWorld::Ptr item, int count); ///< only used for adjusting the gold balance - void buyFromNpc(MWWorld::Ptr item, int count); ///< only used for adjusting the gold balance + void sellToNpc(MWWorld::Ptr item, int count, bool boughtItem); ///< only used for adjusting the gold balance + void buyFromNpc(MWWorld::Ptr item, int count, bool soldItem); ///< only used for adjusting the gold balance bool npcAcceptsItem(MWWorld::Ptr item); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index abbc6172f..9615e95f7 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -82,7 +82,7 @@ namespace MWGui oss << price; toAdd->setUserString("price",oss.str()); - toAdd->setCaptionWithReplacing(travelId+" - "+boost::lexical_cast(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing("#{sCell=" + travelId + "} - " + boost::lexical_cast(price)+"#{sgp}"); toAdd->setSize(toAdd->getTextSize().width,sLineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); toAdd->setUserString("Destination", travelId); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 31b5cce35..4f2c98c08 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -204,6 +204,7 @@ namespace MWGui MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.2); mProgressBar.setVisible (false); mWindowManager.removeGuiMode (GM_Rest); + mWindowManager.removeGuiMode (GM_RestBed); mWaiting = false; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 376eca88d..7f5d34939 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -55,7 +56,8 @@ using namespace MWGui; WindowManager::WindowManager( const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, - const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts) + const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, + Translation::Storage& translationDataStorage) : mGuiManager(NULL) , mHud(NULL) , mMap(NULL) @@ -104,6 +106,7 @@ WindowManager::WindowManager( , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI")) , mHudEnabled(true) + , mTranslationDataStorage (translationDataStorage) { // Set up the GUI system @@ -534,10 +537,14 @@ void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) void WindowManager::messageBox (const std::string& message, const std::vector& buttons) { - if (buttons.empty()) - { - mMessageBoxManager->createMessageBox(message); + if(buttons.empty()){ + /* If there are no buttons, and there is a dialogue window open, messagebox goes to the dialogue window */ + if(!mGuiModes.empty() && mGuiModes.back() == GM_Dialogue) + mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); + else + mMessageBoxManager->createMessageBox(message); } + else { mMessageBoxManager->createInteractiveMessageBox(message, buttons); @@ -612,7 +619,7 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) if (cell->mCell->mName != "") { name = cell->mCell->mName; - mMap->addVisitedLocation (name, cell->mCell->getGridX (), cell->mCell->getGridY ()); + mMap->addVisitedLocation ("#{sCell=" + name + "}", cell->mCell->getGridX (), cell->mCell->getGridY ()); } else { @@ -722,13 +729,25 @@ void WindowManager::setDragDrop(bool dragDrop) void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) { - const ESM::GameSetting *setting = - MWBase::Environment::get().getWorld()->getStore().get().find(_tag); + std::string tag(_tag); - if (setting && setting->mType == ESM::VT_String) - _result = setting->getString(); + std::string tokenToFind = "sCell="; + size_t tokenLength = tokenToFind.length(); + + if (tag.substr(0, tokenLength) == tokenToFind) + { + _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); + } else - _result = _tag; + { + const ESM::GameSetting *setting = + MWBase::Environment::get().getWorld()->getStore().get().find(tag); + + if (setting && setting->mType == ESM::VT_String) + _result = setting->getString(); + else + _result = tag; + } } void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed) @@ -1035,3 +1054,8 @@ void WindowManager::startTraining(MWWorld::Ptr actor) { mTrainingWindow->startTraining(actor); } + +const Translation::Storage& WindowManager::getTranslationDataStorage() const +{ + return mTranslationDataStorage; +} diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 2e684b5da..8bcb88e22 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -29,6 +29,11 @@ namespace Compiler class Extensions; } +namespace Translation +{ + class Storage; +} + namespace OEngine { namespace GUI @@ -76,7 +81,8 @@ namespace MWGui WindowManager(const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, - const std::string& cacheDir, bool consoleOnlyScripts); + const std::string& cacheDir, bool consoleOnlyScripts, + Translation::Storage& translationDataStorage); virtual ~WindowManager(); /** @@ -219,6 +225,8 @@ namespace MWGui virtual void startEnchanting(MWWorld::Ptr actor); virtual void startTraining(MWWorld::Ptr actor); + virtual const Translation::Storage& getTranslationDataStorage() const; + private: OEngine::GUI::MyGUIManager *mGuiManager; HUD *mHud; @@ -250,6 +258,7 @@ namespace MWGui SpellCreationDialog* mSpellCreationDialog; EnchantingDialog* mEnchantingDialog; TrainingWindow* mTrainingWindow; + Translation::Storage& mTranslationDataStorage; CharacterCreation* mCharGen; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index af30c9b04..6a1c3aa9b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -624,7 +624,9 @@ namespace MWInput void InputManager::toggleAutoMove() { if (mWindows.isGuiMode()) return; - mPlayer.setAutoMove (!mPlayer.getAutoMove()); + + if (mControlSwitch["playercontrols"]) + mPlayer.setAutoMove (!mPlayer.getAutoMove()); } void InputManager::toggleWalking() diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 989bdedd7..f53ccdce3 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -258,4 +260,18 @@ namespace MWMechanics return scaledDuration-usedUp; } + + bool ActiveSpells::isSpellActive(std::string id) const + { + boost::algorithm::to_lower(id); + for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) + { + std::string left = iter->first; + boost::algorithm::to_lower(left); + + if (iter->first == id) + return true; + } + return false; + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index e7a239854..6b832f4cd 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -58,6 +58,9 @@ namespace MWMechanics void removeSpell (const std::string& id); + bool isSpellActive (std::string id) const; + ///< case insensitive + const MagicEffects& getMagicEffects() const; TIterator begin() const; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index c07c60209..8ab81bfdf 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -203,7 +203,7 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const bool mismatch = false; - for (int i=0; i (iter->mEffects.mList.size()); ++iter) + for (int i=0; i (iter->mEffects.mList.size()); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 1b6e4b9f3..079f8520b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -38,7 +38,7 @@ namespace MWMechanics creatureStats.getAttribute(5).setBase (player->mNpdt52.mEndurance); creatureStats.getAttribute(6).setBase (player->mNpdt52.mPersonality); creatureStats.getAttribute(7).setBase (player->mNpdt52.mLuck); - + const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -46,7 +46,7 @@ namespace MWMechanics if (mRaceSelected) { const ESM::Race *race = - esmStore.get().find(player->mRace); + esmStore.get().find(player->mRace); bool male = (player->mFlags & ESM::NPC::Female) == 0; @@ -72,14 +72,14 @@ namespace MWMechanics for (int i=0; i<27; ++i) { int bonus = 0; - + for (int i2=0; i2<7; ++i2) if (race->mData.mBonus[i2].mSkill==i) { bonus = race->mData.mBonus[i2].mBonus; break; } - + npcStats.getSkill (i).setBase (5 + bonus); } @@ -270,7 +270,7 @@ namespace MWMechanics // basic player profile; should not change anymore after the creation phase is finished. MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); - + MWBase::World *world = MWBase::Environment::get().getWorld(); const ESM::NPC *player = world->getPlayer().getPlayer().get()->mBase; @@ -442,7 +442,7 @@ namespace MWMechanics if (playerStats.hasCommonDisease() || playerStats.hasBlightDisease()) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispDiseaseMod")->getFloat(); - if (playerNpcStats.getDrawState() == MWMechanics::DrawState_::DrawState_Weapon) + if (playerNpcStats.getDrawState() == MWMechanics::DrawState_Weapon) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispWeaponDrawn")->getFloat(); int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used @@ -455,7 +455,7 @@ namespace MWMechanics return basePrice; MWMechanics::NpcStats sellerSkill = MWWorld::Class::get(ptr).getNpcStats(ptr); - MWMechanics::CreatureStats sellerStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + MWMechanics::CreatureStats sellerStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::NpcStats playerSkill = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); @@ -471,13 +471,13 @@ namespace MWMechanics float d = std::min(sellerSkill.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - + float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); float npcTerm = (d + e + f) * sellerStats.getFatigueTerm(); float buyTerm = 0.01 * (100 - 0.5 * (pcTerm - npcTerm)); float sellTerm = 0.01 * (50 - 0.5 * (npcTerm - pcTerm)); - float x; + float x; if(buying) x = buyTerm; else x = std::min(buyTerm, sellTerm); int offerPrice; diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 20a0360e7..26c4c8e9a 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -117,12 +117,15 @@ float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& cla if (usageType>=4) throw std::runtime_error ("skill usage type out of range"); - if (usageType>0) + if (usageType>=0) { skillFactor = skill->mData.mUseValue[usageType]; - if (skillFactor<=0) + if (skillFactor<0) throw std::runtime_error ("invalid skill gain factor"); + + if (skillFactor==0) + return 0; } const MWWorld::Store &gmst = @@ -217,7 +220,7 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas std::stringstream message; message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", "")) % std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}") - % base; + % static_cast (base); MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), std::vector()); if (mLevelProgress >= 10) @@ -350,4 +353,4 @@ void MWMechanics::NpcStats::setWerewolf (bool set) int MWMechanics::NpcStats::getWerewolfKills() const { return mWerewolfKills; -} \ No newline at end of file +} diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5f1f1ad43..8cb8e9fa8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -332,6 +332,8 @@ void RenderingManager::update (float duration, bool paused) } mOcclusionQuery->update(duration); + mRendering.update(duration); + if(paused) { Ogre::ControllerManager::getSingleton().setTimeFactor(0.f); @@ -350,8 +352,6 @@ void RenderingManager::update (float duration, bool paused) mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); - mRendering.update(duration); - MWWorld::RefData &data = MWBase::Environment::get() .getWorld() diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index a4108c17e..35243533c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -25,26 +25,26 @@ namespace MWScript { namespace Ai { - template - class OpAiActivate : public Interpreter::Opcode1 - { - public: + template + class OpAiActivate : public Interpreter::Opcode1 + { + public: - virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) - { - MWWorld::Ptr ptr = R()(runtime); + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - // discard additional arguments (reset), because we have no idea what they mean. - for (unsigned int i=0; i class OpAiTravel : public Interpreter::Opcode1 @@ -68,7 +68,7 @@ namespace MWScript for (unsigned int i=0; i idleList; for (unsigned int i=0; i - class OpSetHello : public Interpreter::Opcode0 + class OpGetAiSetting : public Interpreter::Opcode0 { + int mIndex; public: + OpGetAiSetting(int index) : mIndex(index) {} virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); - - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (0, value); + runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex)); } }; - template - class OpSetFight : public Interpreter::Opcode0 + class OpModAiSetting : public Interpreter::Opcode0 { + int mIndex; public: + OpModAiSetting(int index) : mIndex(index) {} virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (1, value); + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex, + MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex) + value); } }; - template - class OpSetFlee : public Interpreter::Opcode0 + class OpSetAiSetting : public Interpreter::Opcode0 { + int mIndex; public: + OpSetAiSetting(int index) : mIndex(index) {} virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (2, value); - } - }; - - template - class OpSetAlarm : public Interpreter::Opcode0 - { - public: - - virtual void execute (Interpreter::Runtime& runtime) - { - MWWorld::Ptr ptr = R()(runtime); - - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); - - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (3, value); + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex, + value); } }; @@ -289,9 +274,9 @@ namespace MWScript for (unsigned int i=0; i); - interpreter.installSegment3 (opcodeAIActivateExplicit, new OpAiActivate); + interpreter.installSegment3 (opcodeAIActivate, new OpAiActivate); + interpreter.installSegment3 (opcodeAIActivateExplicit, new OpAiActivate); interpreter.installSegment3 (opcodeAiTravel, new OpAiTravel); interpreter.installSegment3 (opcodeAiTravelExplicit, new OpAiTravel); interpreter.installSegment3 (opcodeAiEscort, new OpAiEscort); @@ -443,19 +452,39 @@ namespace MWScript interpreter.installSegment3 (opcodeAiFollowCell, new OpAiFollowCell); interpreter.installSegment3 (opcodeAiFollowCellExplicit, new OpAiFollowCell); interpreter.installSegment5 (opcodeGetAiPackageDone, new OpGetAiPackageDone); - interpreter.installSegment5 (opcodeGetAiPackageDoneExplicit, new OpGetAiPackageDone); + + interpreter.installSegment5 (opcodeGetAiPackageDoneExplicit, + new OpGetAiPackageDone); interpreter.installSegment5 (opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage); interpreter.installSegment5 (opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); interpreter.installSegment3 (opcodeGetDetected, new OpGetDetected); interpreter.installSegment3 (opcodeGetDetectedExplicit, new OpGetDetected); - interpreter.installSegment5 (opcodeSetHello, new OpSetHello); - interpreter.installSegment5 (opcodeSetHelloExplicit, new OpSetHello); - interpreter.installSegment5 (opcodeSetFight, new OpSetFight); - interpreter.installSegment5 (opcodeSetFightExplicit, new OpSetFight); - interpreter.installSegment5 (opcodeSetFlee, new OpSetFlee); - interpreter.installSegment5 (opcodeSetFleeExplicit, new OpSetFlee); - interpreter.installSegment5 (opcodeSetAlarm, new OpSetAlarm); - interpreter.installSegment5 (opcodeSetAlarmExplicit, new OpSetAlarm); + interpreter.installSegment5 (opcodeSetHello, new OpSetAiSetting(0)); + interpreter.installSegment5 (opcodeSetHelloExplicit, new OpSetAiSetting(0)); + interpreter.installSegment5 (opcodeSetFight, new OpSetAiSetting(1)); + interpreter.installSegment5 (opcodeSetFightExplicit, new OpSetAiSetting(1)); + interpreter.installSegment5 (opcodeSetFlee, new OpSetAiSetting(2)); + interpreter.installSegment5 (opcodeSetFleeExplicit, new OpSetAiSetting(2)); + interpreter.installSegment5 (opcodeSetAlarm, new OpSetAiSetting(3)); + interpreter.installSegment5 (opcodeSetAlarmExplicit, new OpSetAiSetting(3)); + + interpreter.installSegment5 (opcodeModHello, new OpModAiSetting(0)); + interpreter.installSegment5 (opcodeModHelloExplicit, new OpModAiSetting(0)); + interpreter.installSegment5 (opcodeModFight, new OpModAiSetting(1)); + interpreter.installSegment5 (opcodeModFightExplicit, new OpModAiSetting(1)); + interpreter.installSegment5 (opcodeModFlee, new OpModAiSetting(2)); + interpreter.installSegment5 (opcodeModFleeExplicit, new OpModAiSetting(2)); + interpreter.installSegment5 (opcodeModAlarm, new OpModAiSetting(3)); + interpreter.installSegment5 (opcodeModAlarmExplicit, new OpModAiSetting(3)); + + interpreter.installSegment5 (opcodeGetHello, new OpGetAiSetting(0)); + interpreter.installSegment5 (opcodeGetHelloExplicit, new OpGetAiSetting(0)); + interpreter.installSegment5 (opcodeGetFight, new OpGetAiSetting(1)); + interpreter.installSegment5 (opcodeGetFightExplicit, new OpGetAiSetting(1)); + interpreter.installSegment5 (opcodeGetFlee, new OpGetAiSetting(2)); + interpreter.installSegment5 (opcodeGetFleeExplicit, new OpGetAiSetting(2)); + interpreter.installSegment5 (opcodeGetAlarm, new OpGetAiSetting(3)); + interpreter.installSegment5 (opcodeGetAlarmExplicit, new OpGetAiSetting(3)); } } } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 5e9746b2f..1fa69d1fd 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -3,6 +3,10 @@ #include +#include + +#include + #include #include @@ -10,27 +14,17 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/actionequip.hpp" +#include "../mwworld/inventorystore.hpp" #include "interpretercontext.hpp" #include "ref.hpp" -namespace -{ - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } -} - namespace MWScript { namespace Container @@ -78,7 +72,7 @@ namespace MWScript Interpreter::Type_Integer sum = 0; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (toLower(iter->getCellRef().mRefID) == toLower(item)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) sum += iter->getRefData().getCount(); runtime.push (sum); @@ -99,17 +93,22 @@ namespace MWScript Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); - + if (count<0) throw std::runtime_error ("second argument for RemoveItem must be non-negative"); MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + + std::string itemName = ""; + Interpreter::Type_Integer originalCount = count; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end() && count; ++iter) { - if (toLower(iter->getCellRef().mRefID) == toLower(item)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) { + itemName = MWWorld::Class::get(*iter).getName(*iter); + if (iter->getRefData().getCount()<=count) { count -= iter->getRefData().getCount(); @@ -122,18 +121,222 @@ namespace MWScript } } } + + /* The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory */ + std::string msgBox; + if(originalCount - count > 1) + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); + std::stringstream temp; + temp << boost::format(msgBox) % (originalCount - count) % itemName; + msgBox = temp.str(); + } + else + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); + std::stringstream temp; + temp << boost::format(msgBox) % itemName; + msgBox = temp.str(); + } + + if(originalCount - count > 0) + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, std::vector()); // To be fully compatible with original Morrowind, we would need to check if // count is >= 0 here and throw an exception. But let's be tollerant instead. } }; + template + class OpEquip : public Interpreter::Opcode0 + { + public: + + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string item = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + MWWorld::ContainerStoreIterator it = invStore.begin(); + for (; it != invStore.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) + break; + } + if (it == invStore.end()) + throw std::runtime_error("Item to equip not found"); + + MWWorld::ActionEquip action (*it); + action.execute(ptr); + } + }; + + template + class OpGetArmorType : public Interpreter::Opcode0 + { + public: + + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer location = runtime[0].mInteger; + runtime.pop(); + + int slot; + switch (location) + { + case 0: + slot = MWWorld::InventoryStore::Slot_Helmet; + break; + case 1: + slot = MWWorld::InventoryStore::Slot_Cuirass; + break; + case 2: + slot = MWWorld::InventoryStore::Slot_LeftPauldron; + break; + case 3: + slot = MWWorld::InventoryStore::Slot_RightPauldron; + break; + case 4: + slot = MWWorld::InventoryStore::Slot_Greaves; + break; + case 5: + slot = MWWorld::InventoryStore::Slot_Boots; + break; + case 6: + slot = MWWorld::InventoryStore::Slot_LeftGauntlet; + break; + case 7: + slot = MWWorld::InventoryStore::Slot_RightGauntlet; + break; + case 8: + slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield + break; + case 9: + slot = MWWorld::InventoryStore::Slot_LeftGauntlet; + break; + case 10: + slot = MWWorld::InventoryStore::Slot_RightGauntlet; + break; + default: + throw std::runtime_error ("armor index out of range"); + } + + MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + + MWWorld::ContainerStoreIterator it = invStore.getSlot (slot); + if (it == invStore.end() || it->getTypeName () != typeid(ESM::Armor).name()) + { + runtime.push(-1); + return; + } + + int skill = MWWorld::Class::get(*it).getEquipmentSkill (*it) ; + if (skill == ESM::Skill::HeavyArmor) + runtime.push(2); + else if (skill == ESM::Skill::MediumArmor) + runtime.push(1); + else if (skill == ESM::Skill::LightArmor) + runtime.push(0); + else + runtime.push(-1); + } + }; + + template + class OpHasItemEquipped : public Interpreter::Opcode0 + { + public: + + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string item = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = invStore.getSlot (slot); + if (it != invStore.end() && Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) + { + runtime.push(1); + return; + } + } + runtime.push(0); + } + }; + + template + class OpHasSoulGem : public Interpreter::Opcode0 + { + public: + + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + const std::string &name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + for (MWWorld::ContainerStoreIterator it = invStore.begin(MWWorld::ContainerStore::Type_Miscellaneous); + it != invStore.end(); ++it) + { + + if (Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name)) + { + runtime.push(1); + return; + } + } + runtime.push(0); + } + }; + + template + class OpGetWeaponType : public Interpreter::Opcode0 + { + public: + + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + MWWorld::ContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight); + if (it == invStore.end() || it->getTypeName () != typeid(ESM::Weapon).name()) + { + runtime.push(-1); + return; + } + + runtime.push(it->get()->mBase->mData.mType); + } + }; + const int opcodeAddItem = 0x2000076; const int opcodeAddItemExplicit = 0x2000077; const int opcodeGetItemCount = 0x2000078; const int opcodeGetItemCountExplicit = 0x2000079; const int opcodeRemoveItem = 0x200007a; const int opcodeRemoveItemExplicit = 0x200007b; + const int opcodeEquip = 0x20001b3; + const int opcodeEquipExplicit = 0x20001b4; + const int opcodeGetArmorType = 0x20001d1; + const int opcodeGetArmorTypeExplicit = 0x20001d2; + const int opcodeHasItemEquipped = 0x20001d5; + const int opcodeHasItemEquippedExplicit = 0x20001d6; + const int opcodeHasSoulGem = 0x20001de; + const int opcodeHasSoulGemExplicit = 0x20001df; + const int opcodeGetWeaponType = 0x20001e0; + const int opcodeGetWeaponTypeExplicit = 0x20001e1; void registerExtensions (Compiler::Extensions& extensions) { @@ -142,6 +345,11 @@ namespace MWScript opcodeGetItemCountExplicit); extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem, opcodeRemoveItemExplicit); + extensions.registerInstruction ("equip", "c", opcodeEquip, opcodeEquipExplicit); + extensions.registerFunction ("getarmortype", 'l', "l", opcodeGetArmorType, opcodeGetArmorTypeExplicit); + extensions.registerFunction ("hasitemequipped", 'l', "c", opcodeHasItemEquipped, opcodeHasItemEquippedExplicit); + extensions.registerFunction ("hassoulgem", 'l', "c", opcodeHasSoulGem, opcodeHasSoulGemExplicit); + extensions.registerFunction ("getweapontype", 'l', "", opcodeGetWeaponType, opcodeGetWeaponTypeExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -152,6 +360,16 @@ namespace MWScript interpreter.installSegment5 (opcodeGetItemCountExplicit, new OpGetItemCount); interpreter.installSegment5 (opcodeRemoveItem, new OpRemoveItem); interpreter.installSegment5 (opcodeRemoveItemExplicit, new OpRemoveItem); + interpreter.installSegment5 (opcodeEquip, new OpEquip); + interpreter.installSegment5 (opcodeEquipExplicit, new OpEquip); + interpreter.installSegment5 (opcodeGetArmorType, new OpGetArmorType); + interpreter.installSegment5 (opcodeGetArmorTypeExplicit, new OpGetArmorType); + interpreter.installSegment5 (opcodeHasItemEquipped, new OpHasItemEquipped); + interpreter.installSegment5 (opcodeHasItemEquippedExplicit, new OpHasItemEquipped); + interpreter.installSegment5 (opcodeHasSoulGem, new OpHasSoulGem); + interpreter.installSegment5 (opcodeHasSoulGemExplicit, new OpHasSoulGem); + interpreter.installSegment5 (opcodeGetWeaponType, new OpGetWeaponType); + interpreter.installSegment5 (opcodeGetWeaponTypeExplicit, new OpGetWeaponType); } } } diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 0096c69ab..8d65dfdd5 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -106,6 +106,58 @@ namespace MWScript } }; + template + class OpGetForceRun : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr); + + runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + } + }; + + template + class OpGetForceSneak : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr); + + runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + } + }; + + class OpGetPcRunning : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); + runtime.push (MWWorld::Class::get(ptr).getStance (ptr, MWWorld::Class::Run)); + } + }; + + class OpGetPcSneaking : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); + runtime.push (MWWorld::Class::get(ptr).getStance (ptr, MWWorld::Class::Sneak)); + } + }; + const int numberOfControls = 7; const int opcodeEnable = 0x200007e; @@ -120,6 +172,12 @@ namespace MWScript const int opcodeForceSneak = 0x200015a; const int opcodeForceSneakExplicit = 0x200015b; const int opcodeGetDisabled = 0x2000175; + const int opcodeGetPcRunning = 0x20001c9; + const int opcodeGetPcSneaking = 0x20001ca; + const int opcodeGetForceRun = 0x20001cb; + const int opcodeGetForceSneak = 0x20001cc; + const int opcodeGetForceRunExplicit = 0x20001cd; + const int opcodeGetForceSneakExplicit = 0x20001ce; const char *controls[numberOfControls] = { @@ -151,6 +209,10 @@ namespace MWScript opcodeClearForceSneakExplicit); extensions.registerInstruction ("forcesneak", "", opcodeForceSneak, opcodeForceSneakExplicit); + extensions.registerFunction ("getpcrunning", 'l', "", opcodeGetPcRunning); + extensions.registerFunction ("getpcsneaking", 'l', "", opcodeGetPcSneaking); + extensions.registerFunction ("getforcerun", 'l', "", opcodeGetForceRun, opcodeGetForceRunExplicit); + extensions.registerFunction ("getforcesneak", 'l', "", opcodeGetForceSneak, opcodeGetForceSneakExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -181,6 +243,12 @@ namespace MWScript new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); interpreter.installSegment5 (opcodeForceSneakExplicit, new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + interpreter.installSegment5 (opcodeGetPcRunning, new OpGetPcRunning); + interpreter.installSegment5 (opcodeGetPcSneaking, new OpGetPcSneaking); + interpreter.installSegment5 (opcodeGetForceRun, new OpGetForceRun); + interpreter.installSegment5 (opcodeGetForceRunExplicit, new OpGetForceRun); + interpreter.installSegment5 (opcodeGetForceSneak, new OpGetForceSneak); + interpreter.installSegment5 (opcodeGetForceSneakExplicit, new OpGetForceSneak); } } } diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 72ae25724..f63623275 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -12,6 +12,7 @@ #include "../mwbase/journal.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" #include "interpretercontext.hpp" @@ -159,6 +160,33 @@ namespace MWScript } }; + template + class OpGetReputation : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getReputation ()); + } + }; + + template + class OpSameFaction : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer(); + + runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).isSameFaction (MWWorld::Class::get(player).getNpcStats (player))); + } + }; const int opcodeJournal = 0x2000133; const int opcodeSetJournalIndex = 0x2000134; @@ -172,6 +200,10 @@ namespace MWScript const int opcodeModReputation = 0x20001ae; const int opcodeSetReputationExplicit = 0x20001af; const int opcodeModReputationExplicit = 0x20001b0; + const int opcodeGetReputation = 0x20001b1; + const int opcodeGetReputationExplicit = 0x20001b2; + const int opcodeSameFaction = 0x20001b5; + const int opcodeSameFactionExplicit = 0x20001b6; void registerExtensions (Compiler::Extensions& extensions) { @@ -188,6 +220,10 @@ namespace MWScript opcodeSetReputationExplicit); extensions.registerInstruction("modreputation", "l", opcodeModReputation, opcodeModReputationExplicit); + extensions.registerFunction("getreputation", 'l', "", opcodeGetReputation, + opcodeGetReputationExplicit); + extensions.registerFunction("samefaction", 'l', "", opcodeSameFaction, + opcodeSameFactionExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -200,10 +236,14 @@ namespace MWScript interpreter.installSegment5 (opcodeForceGreeting, new OpForceGreeting); interpreter.installSegment5 (opcodeForceGreetingExplicit, new OpForceGreeting); interpreter.installSegment5 (opcodeGoodbye, new OpGoodbye); + interpreter.installSegment5 (opcodeGetReputation, new OpGetReputation); interpreter.installSegment5 (opcodeSetReputation, new OpSetReputation); interpreter.installSegment5 (opcodeModReputation, new OpModReputation); interpreter.installSegment5 (opcodeSetReputationExplicit, new OpSetReputation); interpreter.installSegment5 (opcodeModReputationExplicit, new OpModReputation); + interpreter.installSegment5 (opcodeGetReputationExplicit, new OpGetReputation); + interpreter.installSegment5 (opcodeSameFaction, new OpSameFaction); + interpreter.installSegment5 (opcodeSameFactionExplicit, new OpSameFaction); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 19517ac8d..8a0913517 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -4,7 +4,7 @@ Segment 0: (not implemented yet) opcodes 0x20-0x3f unused -Segment 1: +Segment 1:< (not implemented yet) opcodes 0x20-0x3f unused @@ -37,15 +37,21 @@ op 0x20014: SetPCFacRep op 0x20015: SetPCFacRep, explicit reference op 0x20016: ModPCFacRep op 0x20017: ModPCFacRep, explicit reference -op 0x20018: AIActivate -op 0x20019: AIActivate, explicit reference -op 0x2001a: AiEscortCell -op 0x2001b: AiEscortCell, explicit reference -op 0x2001c: AiFollow -op 0x2001d: AiFollow, explicit reference -op 0x2001e: AiFollowCell -op 0x2001f: AiFollowCell, explicit reference -op s 0x20020-0x3ffff unused +op 0x20018: PcExpelled +op 0x20019: PcExpelled, explicit +op 0x2001a: PcExpell +op 0x2001b: PcExpell, explicit +op 0x2001c: PcClearExpelled +op 0x2001d: PcClearExpelled, explicit +op 0x2001e: AIActivate +op 0x2001f: AIActivate, explicit reference +op 0x20020: AiEscortCell +op 0x20021: AiEscortCell, explicit reference +op 0x20022: AiFollow +op 0x20023: AiFollow, explicit reference +op 0x20024: AiFollowCell +op 0x20025: AiFollowCell, explicit reference +op s 0x20026-0x3ffff unused Segment 4: (not implemented yet) @@ -234,9 +240,71 @@ op 0x20001ad: SetReputation op 0x20001ae: ModReputation op 0x20001af: SetReputation, explicit op 0x20001b0: ModReputation, explicit -op 0x20001b1: GetCurrentAIPackage -op 0x20001b2: GetCurrentAIPackage, explicit reference -op 0x20001b3: GetDetected -op 0x20001b4: GetDetected, explicit reference -opcodes 0x20001b5-0x3ffffff unused +op 0x20001b1: GetReputation +op 0x20001b2: GetReputation, explicit +op 0x20001b3: Equip +op 0x20001b4: Equip, explicit +op 0x20001b5: SameFaction +op 0x20001b6: SameFaction, explicit +op 0x20001b7: ModHello +op 0x20001b8: ModHello, explicit reference +op 0x20001b9: ModFight +op 0x20001ba: ModFight, explicit reference +op 0x20001bb: ModFlee +op 0x20001bc: ModFlee, explicit reference +op 0x20001bd: ModAlarm +op 0x20001be: ModAlarm, explicit reference +op 0x20001bf: GetHello +op 0x20001c0: GetHello, explicit reference +op 0x20001c1: GetFight +op 0x20001c2: GetFight, explicit reference +op 0x20001c3: GetFlee +op 0x20001c4: GetFlee, explicit reference +op 0x20001c5: GetAlarm +op 0x20001c6: GetAlarm, explicit reference +op 0x20001c7: GetLocked +op 0x20001c8: GetLocked, explicit reference +op 0x20001c9: GetPcRunning +op 0x20001ca: GetPcSneaking +op 0x20001cb: GetForceRun +op 0x20001cc: GetForceSneak +op 0x20001cd: GetForceRun, explicit +op 0x20001ce: GetForceSneak, explicit +op 0x20001cf: GetEffect +op 0x20001d0: GetEffect, explicit +op 0x20001d1: GetArmorType +op 0x20001d2: GetArmorType, explicit +op 0x20001d3: GetAttacked +op 0x20001d4: GetAttacked, explicit +op 0x20001d5: HasItemEquipped +op 0x20001d6: HasItemEquipped, explicit +op 0x20001d7: GetWeaponDrawn +op 0x20001d8: GetWeaponDrawn, explicit +op 0x20001d9: GetRace +op 0x20001da: GetRace, explicit +op 0x20001db: GetSpellEffects +op 0x20001dc: GetSpellEffects, explicit +op 0x20001dd: GetCurrentTime +op 0x20001de: HasSoulGem +op 0x20001df: HasSoulGem, explicit +op 0x20001e0: GetWeaponType +op 0x20001e1: GetWeaponType, explicit +op 0x20001e2: GetWerewolfKills +op 0x20001e3: ModScale +op 0x20001e4: ModScale, explicit +op 0x20001e5: SetDelete +op 0x20001e6: SetDelete, explicit +op 0x20001e7: GetSquareRoot +op 0x20001e8: RaiseRank +op 0x20001e9: RaiseRank, explicit +op 0x20001ea: LowerRank +op 0x20001eb: LowerRank, explicit +op 0x20001ec: GetPCCrimeLevel +op 0x20001ed: SetPCCrimeLevel +op 0x20001ee: SetPCCrimeLevel +op 0x20001ef: GetCurrentAIPackage +op 0x20001f0: GetCurrentAIPackage, explicit reference +op 0x20001f1: GetDetected +op 0x20001f2: GetDetected, explicit reference +opcodes 0x20001f3-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 72c2db164..7d437f3c0 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -181,7 +181,7 @@ opcodeEnableStatsReviewMenu); extensions.registerInstruction ("enablemapmenu", "", opcodeEnableMapMenu); extensions.registerInstruction ("enablestatsmenu", "", opcodeEnableStatsMenu); - extensions.registerInstruction ("enablerestmenu", "", opcodeEnableRest); + extensions.registerInstruction ("enablerest", "", opcodeEnableRest); extensions.registerInstruction ("enablelevelupmenu", "", opcodeEnableRest); extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu); diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 577ad008f..cb0e4a7f9 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -11,10 +11,13 @@ #include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/inputmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/npcstats.hpp" + #include "locals.hpp" #include "globalscripts.hpp" @@ -174,6 +177,146 @@ namespace MWScript MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat = value; } + std::vector InterpreterContext::getGlobals () const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + return world->getGlobals(); + + } + + char InterpreterContext::getGlobalType (const std::string& name) const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + return world->getGlobalVariableType(name); + } + + std::string InterpreterContext::getActionBinding(const std::string& action) const + { + std::vector actions = MWBase::Environment::get().getInputManager()->getActionSorting (); + for (std::vector::const_iterator it = actions.begin(); it != actions.end(); ++it) + { + std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (*it); + if(desc == "") + continue; + + if(desc == action) + return MWBase::Environment::get().getInputManager()->getActionBindingName (*it); + } + + return "None"; + } + + std::string InterpreterContext::getNPCName() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mName; + } + + std::string InterpreterContext::getNPCRace() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mRace; + } + + std::string InterpreterContext::getNPCClass() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mClass; + } + + std::string InterpreterContext::getNPCFaction() const + { + ESM::NPC npc = *mReference.get()->mBase; + return npc.mFaction; + } + + std::string InterpreterContext::getNPCRank() const + { + std::map ranks = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks(); + std::map::const_iterator it = ranks.begin(); + + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Faction *faction = store.get().find(it->first); + + return faction->mRanks[it->second]; + } + + std::string InterpreterContext::getPCName() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + ESM::NPC player = *world->getPlayer().getPlayer().get()->mBase; + return player.mName; + } + + std::string InterpreterContext::getPCRace() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + ESM::NPC player = *world->getPlayer().getPlayer().get()->mBase; + return player.mRace; + } + + std::string InterpreterContext::getPCClass() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + ESM::NPC player = *world->getPlayer().getPlayer().get()->mBase; + return player.mClass; + } + + std::string InterpreterContext::getPCRank() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; + + std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); + std::map::const_iterator it = ranks.begin(); + + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Faction *faction = store.get().find(factionId); + + if(it->second < 0 || it->second > 9) // there are only 10 ranks + return ""; + + return faction->mRanks[it->second]; + } + + std::string InterpreterContext::getPCNextRank() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; + + std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); + std::map::const_iterator it = ranks.begin(); + + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Faction *faction = store.get().find(factionId); + + if(it->second < 0 || it->second > 9) + return ""; + + if(it->second <= 8) // If player is at max rank, there is no next rank + return faction->mRanks[it->second + 1]; + else + return faction->mRanks[it->second]; + } + + int InterpreterContext::getPCBounty() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + return MWWorld::Class::get (player).getNpcStats (player).getBounty(); + } + + std::string InterpreterContext::getCurrentCellName() const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + return world->getCurrentCellName(); + } + bool InterpreterContext::isScriptRunning (const std::string& name) const { return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name); diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 6d97f7949..f0b2758d9 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -75,6 +75,36 @@ namespace MWScript virtual void setGlobalLong (const std::string& name, int value); virtual void setGlobalFloat (const std::string& name, float value); + + virtual std::vector getGlobals () const; + + virtual char getGlobalType (const std::string& name) const; + + virtual std::string getActionBinding(const std::string& action) const; + + virtual std::string getNPCName() const; + + virtual std::string getNPCRace() const; + + virtual std::string getNPCClass() const; + + virtual std::string getNPCFaction() const; + + virtual std::string getNPCRank() const; + + virtual std::string getPCName() const; + + virtual std::string getPCRace() const; + + virtual std::string getPCClass() const; + + virtual std::string getPCRank() const; + + virtual std::string getPCNextRank() const; + + virtual int getPCBounty() const; + + virtual std::string getCurrentCellName() const; virtual bool isScriptRunning (const std::string& name) const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index c09f0c860..b687471aa 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -13,6 +13,10 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" + +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -272,6 +276,121 @@ namespace MWScript }; bool OpToggleVanityMode::sActivate = true; + template + class OpGetLocked : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + runtime.push (ptr.getCellRef ().mLockLevel > 0); + } + }; + + template + class OpGetEffect : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + int key = runtime[0].mInteger; + runtime.pop(); + + runtime.push (MWWorld::Class::get(ptr).getCreatureStats (ptr).getMagicEffects ().get ( + MWMechanics::EffectKey(key)).mMagnitude > 0); + } + }; + + template + class OpGetAttacked : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + runtime.push(MWWorld::Class::get(ptr).getCreatureStats (ptr).getAttacked ()); + } + }; + + template + class OpGetWeaponDrawn : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + runtime.push(MWWorld::Class::get(ptr).getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + } + }; + + template + class OpGetSpellEffects : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + std::string id = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + runtime.push(MWWorld::Class::get(ptr).getCreatureStats(ptr).getActiveSpells().isSpellActive(id)); + } + }; + + class OpGetCurrentTime : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + runtime.push(MWBase::Environment::get().getWorld()->getTimeStamp().getHour()); + } + }; + + template + class OpSetDelete : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + int parameter = runtime[0].mInteger; + runtime.pop(); + + if (parameter == 1) + { + if (ptr.isInCell()) + MWBase::Environment::get().getWorld()->deleteObject (ptr); + else + ptr.getRefData().setCount(0); + } + } + }; + + class OpGetSquareRoot : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + float param = runtime[0].mFloat; + runtime.pop(); + + runtime.push(std::sqrt (param)); + } + }; + const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeActivate = 0x2000075; @@ -291,6 +410,20 @@ namespace MWScript const int opcodeToggleVanityMode = 0x2000174; const int opcodeGetPcSleep = 0x200019f; const int opcodeWakeUpPc = 0x20001a2; + const int opcodeGetLocked = 0x20001c7; + const int opcodeGetLockedExplicit = 0x20001c8; + const int opcodeGetEffect = 0x20001cf; + const int opcodeGetEffectExplicit = 0x20001d0; + const int opcodeGetAttacked = 0x20001d3; + const int opcodeGetAttackedExplicit = 0x20001d4; + const int opcodeGetWeaponDrawn = 0x20001d7; + const int opcodeGetWeaponDrawnExplicit = 0x20001d8; + const int opcodeGetSpellEffects = 0x20001db; + const int opcodeGetSpellEffectsExplicit = 0x20001dc; + const int opcodeGetCurrentTime = 0x20001dd; + const int opcodeSetDelete = 0x20001e5; + const int opcodeSetDeleteExplicit = 0x20001e6; + const int opcodeGetSquareRoot = 0x20001e7; void registerExtensions (Compiler::Extensions& extensions) { @@ -317,6 +450,14 @@ namespace MWScript extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc); + extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit); + extensions.registerFunction ("geteffect", 'l', "l", opcodeGetEffect, opcodeGetEffectExplicit); + extensions.registerFunction ("getattacked", 'l', "", opcodeGetAttacked, opcodeGetAttackedExplicit); + extensions.registerFunction ("getweapondrawn", 'l', "", opcodeGetWeaponDrawn, opcodeGetWeaponDrawnExplicit); + extensions.registerFunction ("getspelleffects", 'l', "c", opcodeGetSpellEffects, opcodeGetSpellEffectsExplicit); + extensions.registerFunction ("getcurrenttime", 'f', "", opcodeGetCurrentTime); + extensions.registerInstruction ("setdelete", "l", opcodeSetDelete, opcodeSetDeleteExplicit); + extensions.registerFunction ("getsquareroot", 'f', "f", opcodeGetSquareRoot); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -340,6 +481,20 @@ namespace MWScript interpreter.installSegment5 (opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (opcodeWakeUpPc, new OpWakeUpPc); + interpreter.installSegment5 (opcodeGetLocked, new OpGetLocked); + interpreter.installSegment5 (opcodeGetLockedExplicit, new OpGetLocked); + interpreter.installSegment5 (opcodeGetEffect, new OpGetEffect); + interpreter.installSegment5 (opcodeGetEffectExplicit, new OpGetEffect); + interpreter.installSegment5 (opcodeGetAttacked, new OpGetAttacked); + interpreter.installSegment5 (opcodeGetAttackedExplicit, new OpGetAttacked); + interpreter.installSegment5 (opcodeGetWeaponDrawn, new OpGetWeaponDrawn); + interpreter.installSegment5 (opcodeGetWeaponDrawnExplicit, new OpGetWeaponDrawn); + interpreter.installSegment5 (opcodeGetSpellEffects, new OpGetSpellEffects); + interpreter.installSegment5 (opcodeGetSpellEffectsExplicit, new OpGetSpellEffects); + interpreter.installSegment5 (opcodeGetCurrentTime, new OpGetCurrentTime); + interpreter.installSegment5 (opcodeSetDelete, new OpSetDelete); + interpreter.installSegment5 (opcodeSetDeleteExplicit, new OpSetDelete); + interpreter.installSegment5 (opcodeGetSquareRoot, new OpGetSquareRoot); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index f6a31dd7f..9e630aedf 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -392,6 +392,46 @@ namespace MWScript } }; + class OpGetPCCrimeLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + runtime.push (static_cast (MWWorld::Class::get (player).getNpcStats (player).getBounty())); + } + }; + + class OpSetPCCrimeLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat); + runtime.pop(); + } + }; + + class OpModPCCrimeLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat + MWWorld::Class::get (player).getNpcStats (player).getBounty()); + runtime.pop(); + } + }; + template class OpAddSpell : public Interpreter::Opcode0 { @@ -780,6 +820,210 @@ namespace MWScript } }; + template + class OpGetRace : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string race = runtime.getStringLiteral(runtime[0].mInteger); + boost::algorithm::to_lower(race); + runtime.pop(); + + std::string npcRace = ptr.get()->mBase->mRace; + boost::algorithm::to_lower(npcRace); + + runtime.push (npcRace == race); + } + }; + + class OpGetWerewolfKills : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); + + runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getWerewolfKills ()); + } + }; + + template + class OpPcExpelled : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string factionID = ""; + if(arg0 >0 ) + { + factionID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + } + else + { + if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + { + factionID = ""; + } + else + { + factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + } + } + boost::algorithm::to_lower(factionID); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + if(factionID!="") + { + std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); + if (expelled.find (factionID) != expelled.end()) + { + runtime.push(1); + } + else + { + runtime.push(0); + } + } + else + { + runtime.push(0); + } + } + }; + + template + class OpPcExpell : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string factionID = ""; + if(arg0 >0 ) + { + factionID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + } + else + { + if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + { + factionID = ""; + } + else + { + factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + } + } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + if(factionID!="") + { + std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); + boost::algorithm::to_lower(factionID); + expelled.insert(factionID); + } + } + }; + + template + class OpPcClearExpelled : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string factionID = ""; + if(arg0 >0 ) + { + factionID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + } + else + { + if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + { + factionID = ""; + } + else + { + factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + } + } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + if(factionID!="") + { + std::set& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled (); + boost::algorithm::to_lower(factionID); + expelled.erase (factionID); + } + } + }; + + template + class OpRaiseRank : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string factionID = ""; + if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + return; + else + { + factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + // no-op when executed on the player + if (ptr == player) + return; + + std::map& ranks = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks (); + ranks[factionID] = ranks[factionID]+1; + } + }; + + template + class OpLowerRank : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string factionID = ""; + if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + return; + else + { + factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + // no-op when executed on the player + if (ptr == player) + return; + + std::map& ranks = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks (); + ranks[factionID] = ranks[factionID]-1; + } + }; const int numberOfAttributes = 8; @@ -812,6 +1056,10 @@ namespace MWScript const int opcodeModSkill = 0x20000fa; const int opcodeModSkillExplicit = 0x2000115; + const int opcodeGetPCCrimeLevel = 0x20001ec; + const int opcodeSetPCCrimeLevel = 0x20001ed; + const int opcodeModPCCrimeLevel = 0x20001ee; + const int opcodeAddSpell = 0x2000147; const int opcodeAddSpellExplicit = 0x2000148; const int opcodeRemoveSpell = 0x2000149; @@ -850,6 +1098,22 @@ namespace MWScript const int opcodeGetBlightDisease = 0x20001aa; const int opcodeGetBlightDiseaseExplicit = 0x20001ab; + const int opcodeGetRace = 0x20001d9; + const int opcodeGetRaceExplicit = 0x20001da; + + const int opcodeGetWerewolfKills = 0x20001e2; + + const int opcodePcExpelled = 0x20018; + const int opcodePcExpelledExplicit = 0x20019; + const int opcodePcExpell = 0x2001a; + const int opcodePcExpellExplicit = 0x2001b; + const int opcodePcClearExpelled = 0x2001c; + const int opcodePcClearExpelledExplicit = 0x2001d; + const int opcodeRaiseRank = 0x20001e8; + const int opcodeRaiseRankExplicit = 0x20001e9; + const int opcodeLowerRank = 0x20001ea; + const int opcodeLowerRankExplicit = 0x20001eb; + void registerExtensions (Compiler::Extensions& extensions) { static const char *attributes[numberOfAttributes] = @@ -870,7 +1134,7 @@ namespace MWScript "alteration", "illusion", "conjuration", "mysticism", "restoration", "alchemy", "unarmored", "security", "sneak", "acrobatics", "lightarmor", "shortblade", "marksman", - "merchantile", "speechcraft", "handtohand" + "mercantile", "speechcraft", "handtohand" }; std::string get ("get"); @@ -921,6 +1185,10 @@ namespace MWScript opcodeModSkill+i, opcodeModSkillExplicit+i); } + extensions.registerFunction ("getpccrimelevel", 'f', "", opcodeGetPCCrimeLevel); + extensions.registerInstruction ("setpccrimelevel", "f", opcodeSetPCCrimeLevel); + extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel); + extensions.registerInstruction ("addspell", "c", opcodeAddSpell, opcodeAddSpellExplicit); extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, opcodeRemoveSpellExplicit); @@ -950,6 +1218,15 @@ namespace MWScript opcodeGetCommonDiseaseExplicit); extensions.registerFunction ("getblightdisease", 'l', "", opcodeGetBlightDisease, opcodeGetBlightDiseaseExplicit); + + extensions.registerFunction ("getrace", 'l', "c", opcodeGetRace, + opcodeGetRaceExplicit); + extensions.registerFunction ("getwerewolfkills", 'f', "", opcodeGetWerewolfKills); + extensions.registerFunction ("pcexpelled", 'l', "/S", opcodePcExpelled, opcodePcExpelledExplicit); + extensions.registerInstruction ("pcexpell", "/S", opcodePcExpell, opcodePcExpellExplicit); + extensions.registerInstruction ("pcclearexpelled", "/S", opcodePcClearExpelled, opcodePcClearExpelledExplicit); + extensions.registerInstruction ("raiserank", "", opcodeRaiseRank, opcodeRaiseRankExplicit); + extensions.registerInstruction ("lowerrank", "", opcodeLowerRank, opcodeLowerRankExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -1006,6 +1283,10 @@ namespace MWScript interpreter.installSegment5 (opcodeModSkillExplicit+i, new OpModSkill (i)); } + interpreter.installSegment5 (opcodeGetPCCrimeLevel, new OpGetPCCrimeLevel); + interpreter.installSegment5 (opcodeSetPCCrimeLevel, new OpSetPCCrimeLevel); + interpreter.installSegment5 (opcodeModPCCrimeLevel, new OpModPCCrimeLevel); + interpreter.installSegment5 (opcodeAddSpell, new OpAddSpell); interpreter.installSegment5 (opcodeAddSpellExplicit, new OpAddSpell); interpreter.installSegment5 (opcodeRemoveSpell, new OpRemoveSpell); @@ -1045,6 +1326,21 @@ namespace MWScript interpreter.installSegment5 (opcodeGetCommonDiseaseExplicit, new OpGetCommonDisease); interpreter.installSegment5 (opcodeGetBlightDisease, new OpGetBlightDisease); interpreter.installSegment5 (opcodeGetBlightDiseaseExplicit, new OpGetBlightDisease); + + interpreter.installSegment5 (opcodeGetRace, new OpGetRace); + interpreter.installSegment5 (opcodeGetRaceExplicit, new OpGetRace); + interpreter.installSegment5 (opcodeGetWerewolfKills, new OpGetWerewolfKills); + + interpreter.installSegment3 (opcodePcExpelled, new OpPcExpelled); + interpreter.installSegment3 (opcodePcExpelledExplicit, new OpPcExpelled); + interpreter.installSegment3 (opcodePcExpell, new OpPcExpell); + interpreter.installSegment3 (opcodePcExpellExplicit, new OpPcExpell); + interpreter.installSegment3 (opcodePcClearExpelled, new OpPcClearExpelled); + interpreter.installSegment3 (opcodePcClearExpelledExplicit, new OpPcClearExpelled); + interpreter.installSegment5 (opcodeRaiseRank, new OpRaiseRank); + interpreter.installSegment5 (opcodeRaiseRankExplicit, new OpRaiseRank); + interpreter.installSegment5 (opcodeLowerRank, new OpLowerRank); + interpreter.installSegment5 (opcodeLowerRankExplicit, new OpLowerRank); } } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index a64651a97..2dd8f3e16 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -53,6 +53,23 @@ namespace MWScript } }; + template + class OpModScale : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Float scale = runtime[0].mFloat; + runtime.pop(); + + // add the parameter to the object's scale. + MWBase::Environment::get().getWorld()->scaleObject(ptr,ptr.getCellRef().mScale + scale); + } + }; + template class OpSetAngle : public Interpreter::Opcode0 { @@ -532,6 +549,8 @@ namespace MWScript const int opcodePlaceAtPc = 0x200019c; const int opcodePlaceAtMe = 0x200019d; const int opcodePlaceAtMeExplicit = 0x200019e; + const int opcodeModScale = 0x20001e3; + const int opcodeModScaleExplicit = 0x20001e4; void registerExtensions (Compiler::Extensions& extensions) { @@ -548,6 +567,7 @@ namespace MWScript extensions.registerInstruction("placeitem","cffff",opcodePlaceItem); extensions.registerInstruction("placeatpc","clfl",opcodePlaceAtPc); extensions.registerInstruction("placeatme","clfl",opcodePlaceAtMe,opcodePlaceAtMeExplicit); + extensions.registerInstruction("modscale","f",opcodeModScale,opcodeModScaleExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -574,7 +594,9 @@ namespace MWScript interpreter.installSegment5(opcodePlaceItem,new OpPlaceItem); interpreter.installSegment5(opcodePlaceAtPc,new OpPlaceAtPc); interpreter.installSegment5(opcodePlaceAtMe,new OpPlaceAtMe); - interpreter.installSegment5(opcodePlaceAtMeExplicit,new OpPlaceAtMe); + interpreter.installSegment5(opcodePlaceAtMeExplicit,new OpPlaceAtMe); + interpreter.installSegment5(opcodeModScale,new OpModScale); + interpreter.installSegment5(opcodeModScaleExplicit,new OpModScale); } } } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index a2cccfd13..5f61ab8f0 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -313,7 +313,12 @@ void FFmpeg_Decoder::close() mStreams.erase(mStreams.begin()); } if(mFormatCtx) + { + AVIOContext* context = mFormatCtx->pb; + av_free(context); + mFormatCtx->pb = NULL; av_close_input_file(mFormatCtx); + } mFormatCtx = NULL; mDataStream.setNull(); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 9917254ee..e14219aae 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -6,7 +6,7 @@ #include #include "store.hpp" -namespace MWWorld +namespace MWWorld { class ESMStore { @@ -158,7 +158,7 @@ namespace MWWorld std::ostringstream id; id << "$dynamic" << mDynamicCount++; record.mId = id.str(); - + T *ptr = store.insert(record); for (iterator it = mStores.begin(); it != mStores.end(); ++it) { if (it->second == &store) { @@ -179,7 +179,7 @@ namespace MWWorld template <> inline const ESM::NPC *ESMStore::insert(const ESM::NPC &npc) { - if (StringUtils::ciEqual(npc.mId, "player")) { + if (Misc::StringUtils::ciEqual(npc.mId, "player")) { return mNpcs.insert(npc); } else if (mNpcs.search(npc.mId) != 0) { std::ostringstream msg; @@ -191,7 +191,7 @@ namespace MWWorld std::ostringstream id; id << "$dynamic" << mDynamicCount++; record.mId = id.str(); - + ESM::NPC *ptr = mNpcs.insert(record); mIds[ptr->mId] = ESM::REC_NPC_; return ptr; diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 76dede5a3..f010661b9 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -7,6 +7,17 @@ namespace MWWorld { + std::vector Globals::getGlobals () const + { + std::vector retval; + Collection::const_iterator it; + for(it = mVariables.begin(); it != mVariables.end(); ++it){ + retval.push_back(it->first); + } + + return retval; + } + Globals::Collection::const_iterator Globals::find (const std::string& name) const { Collection::const_iterator iter = mVariables.find (name); diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index c7aee5f93..681bd560e 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWWORLD_GLOBALS_H #define GAME_MWWORLD_GLOBALS_H +#include #include #include @@ -53,6 +54,8 @@ namespace MWWorld char getType (const std::string& name) const; ///< If there is no global variable with this name, ' ' is returned. + + std::vector getGlobals () const; }; } diff --git a/apps/openmw/mwworld/recordcmp.hpp b/apps/openmw/mwworld/recordcmp.hpp index 0b1655100..7de4f5565 100644 --- a/apps/openmw/mwworld/recordcmp.hpp +++ b/apps/openmw/mwworld/recordcmp.hpp @@ -1,62 +1,12 @@ #ifndef OPENMW_MWWORLD_RECORDCMP_H #define OPENMW_MWWORLD_RECORDCMP_H -#include #include -#include #include namespace MWWorld { - /// \todo move this to another location - class StringUtils - { - struct ci - { - bool operator()(int x, int y) const { - return std::tolower(x) < std::tolower(y); - } - }; - - public: - static bool ciLess(const std::string &x, const std::string &y) { - return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); - } - - static bool ciEqual(const std::string &x, const std::string &y) { - if (x.size() != y.size()) { - return false; - } - std::string::const_iterator xit = x.begin(); - std::string::const_iterator yit = y.begin(); - for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit) != std::tolower(*yit)) { - return false; - } - } - return true; - } - - /// Transforms input string to lower case w/o copy - static std::string &toLower(std::string &inout) { - std::transform( - inout.begin(), - inout.end(), - inout.begin(), - (int (*)(int)) std::tolower - ); - return inout; - } - - /// Returns lower case copy of input string - static std::string lowerCase(const std::string &in) - { - std::string out = in; - return toLower(out); - } - }; - struct RecordCmp { template @@ -67,17 +17,17 @@ namespace MWWorld template <> inline bool RecordCmp::operator()(const ESM::Dialogue &x, const ESM::Dialogue &y) const { - return StringUtils::ciLess(x.mId, y.mId); + return Misc::StringUtils::ciLess(x.mId, y.mId); } template <> inline bool RecordCmp::operator()(const ESM::Cell &x, const ESM::Cell &y) const { - return StringUtils::ciLess(x.mName, y.mName); + return Misc::StringUtils::ciLess(x.mName, y.mName); } template <> inline bool RecordCmp::operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const { - return StringUtils::ciLess(x.mCell, y.mCell); + return Misc::StringUtils::ciLess(x.mCell, y.mCell); } } // end namespace diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index fd93f39f1..431cd3cf9 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -105,12 +105,12 @@ namespace MWWorld const T *search(const std::string &id) const { T item; - item.mId = StringUtils::lowerCase(id); + item.mId = Misc::StringUtils::lowerCase(id); typename std::vector::const_iterator it = std::lower_bound(mStatic.begin(), mStatic.end(), item, RecordCmp()); - if (it != mStatic.end() && StringUtils::ciEqual(it->mId, id)) { + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->mId, id)) { return &(*it); } @@ -134,7 +134,7 @@ namespace MWWorld void load(ESM::ESMReader &esm, const std::string &id) { mStatic.push_back(T()); - mStatic.back().mId = StringUtils::lowerCase(id); + mStatic.back().mId = Misc::StringUtils::lowerCase(id); mStatic.back().load(esm); } @@ -169,7 +169,7 @@ namespace MWWorld } T *insert(const T &item) { - std::string id = StringUtils::lowerCase(item.mId); + std::string id = Misc::StringUtils::lowerCase(item.mId); std::pair result = mDynamic.insert(std::pair(id, item)); T *ptr = &result.first->second; @@ -182,7 +182,7 @@ namespace MWWorld } bool erase(const std::string &id) { - std::string key = StringUtils::lowerCase(id); + std::string key = Misc::StringUtils::lowerCase(id); typename Dynamic::iterator it = mDynamic.find(key); if (it == mDynamic.end()) { return false; @@ -213,7 +213,7 @@ namespace MWWorld inline void Store::load(ESM::ESMReader &esm, const std::string &id) { mStatic.push_back(ESM::Script()); mStatic.back().load(esm); - StringUtils::toLower(mStatic.back().mId); + Misc::StringUtils::toLower(mStatic.back().mId); } template <> @@ -287,6 +287,16 @@ namespace MWWorld public: typedef SharedIterator iterator; + virtual ~Store() + { + for (std::vector::const_iterator it = + mStatic.begin(); it != mStatic.end(); ++it) + { + delete *it; + } + + } + int getSize() const { return mStatic.size(); } @@ -375,12 +385,12 @@ namespace MWWorld const ESM::Cell *search(const std::string &id) const { ESM::Cell cell; - cell.mName = StringUtils::lowerCase(id); + cell.mName = Misc::StringUtils::lowerCase(id); std::vector::const_iterator it = std::lower_bound(mInt.begin(), mInt.end(), cell, RecordCmp()); - if (it != mInt.end() && StringUtils::ciEqual(it->mName, id)) { + if (it != mInt.end() && Misc::StringUtils::ciEqual(it->mName, id)) { return &(*it); } @@ -480,7 +490,7 @@ namespace MWWorld const ESM::Cell *searchExtByName(const std::string &id) const { std::vector::const_iterator it = mSharedExt.begin(); for (; it != mSharedExt.end(); ++it) { - if (StringUtils::ciEqual((*it)->mName, id)) { + if (Misc::StringUtils::ciEqual((*it)->mName, id)) { return *it; } } @@ -491,7 +501,7 @@ namespace MWWorld const ESM::Cell *searchExtByRegion(const std::string &id) const { std::vector::const_iterator it = mSharedExt.begin(); for (; it != mSharedExt.end(); ++it) { - if (StringUtils::ciEqual((*it)->mRegion, id)) { + if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { return *it; } } @@ -531,7 +541,7 @@ namespace MWWorld ptr = &result.first->second; mSharedExt.push_back(ptr); } else { - std::string key = StringUtils::lowerCase(cell.mName); + std::string key = Misc::StringUtils::lowerCase(cell.mName); // duplicate insertions are avoided by search(ESM::Cell &) std::pair result = @@ -551,7 +561,7 @@ namespace MWWorld } bool erase(const std::string &id) { - std::string key = StringUtils::lowerCase(id); + std::string key = Misc::StringUtils::lowerCase(id); DynamicInt::iterator it = mDynamicInt.find(key); if (it == mDynamicInt.end()) { @@ -681,7 +691,7 @@ namespace MWWorld pg.mCell = name; iterator it = std::lower_bound(mIntBegin, mIntEnd, pg, RecordCmp()); - if (it != mIntEnd && StringUtils::ciEqual(it->mCell, name)) { + if (it != mIntEnd && Misc::StringUtils::ciEqual(it->mCell, name)) { return &(*it); } return 0; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 616a0be39..3995123b0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -11,6 +11,8 @@ #include "../mwrender/sky.hpp" #include "../mwrender/player.hpp" +#include "../mwclass/door.hpp" + #include "player.hpp" #include "manualref.hpp" #include "cellfunctors.hpp" @@ -168,7 +170,7 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, - const std::string& encoding, std::map fallbackMap) + const ToUTF8::FromType& encoding, std::map fallbackMap) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mCells (mStore, mEsm), mNumFacing(0) @@ -193,7 +195,7 @@ namespace MWWorld mRendering->attachCameraTo(mPlayer->getPlayer()); mPhysics->addActor(mPlayer->getPlayer()); - + // global variables mGlobalVariables = new Globals (mStore); @@ -203,6 +205,8 @@ namespace MWWorld mGlobalVariables->setInt ("chargenstate", 1); } + mGlobalVariables->setInt ("pcrace", 3); + mWorldScene = new Scene(*mRendering, mPhysics); setFallbackValues(fallbackMap); @@ -235,7 +239,7 @@ namespace MWWorld MWWorld::Store::iterator it = regions.begin(); for (; it != regions.end(); ++it) { - if (MWWorld::StringUtils::ciEqual(cellName, it->mName)) + if (Misc::StringUtils::ciEqual(cellName, it->mName)) { return mStore.get().searchExtByRegion(it->mId); } @@ -294,6 +298,49 @@ namespace MWWorld return mGlobalVariables->getType (name); } + std::vector World::getGlobals () const + { + return mGlobalVariables->getGlobals(); + } + + std::string World::getCurrentCellName () const + { + std::string name; + + Ptr::CellStore *cell = mWorldScene->getCurrentCell(); + if (cell->mCell->isExterior()) + { + if (cell->mCell->mName != "") + { + name = cell->mCell->mName; + } + else + { + const ESM::Region* region = + MWBase::Environment::get().getWorld()->getStore().get().search(cell->mCell->mRegion); + if (region) + name = region->mName; + else + { + const ESM::GameSetting *setting = + MWBase::Environment::get().getWorld()->getStore().get().search("sDefaultCellname"); + + if (setting && setting->mType == ESM::VT_String) + name = setting->getString(); + else + name = "Wilderness"; + } + + } + } + else + { + name = cell->mCell->mName; + } + + return name; + } + Ptr World::getPtr (const std::string& name, bool activeOnly) { // the player is always in an active cell. @@ -560,16 +607,6 @@ namespace MWWorld } } - std::string toLower (const std::string& name) - { - std::string lowerCase; - - std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), - (int(*)(int)) std::tolower); - - return lowerCase; - } - void World::moveObject(const Ptr &ptr, CellStore &newCell, float x, float y, float z) { ESM::Position &pos = ptr.getRefData().getPosition(); @@ -584,7 +621,7 @@ namespace MWWorld { if (isPlayer) if (!newCell.isExterior()) - changeToInteriorCell(toLower(newCell.mCell->mName), pos); + changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.mCell->mName), pos); else { int cellX = newCell.mCell->getGridX(); @@ -660,7 +697,7 @@ namespace MWWorld { MWWorld::Class::get(ptr).adjustScale(ptr,scale); ptr.getCellRef().mScale = scale; - + if(ptr.getRefData().getBaseNode() == 0) return; mRendering->scaleObject(ptr, Vector3(scale,scale,scale)); @@ -673,7 +710,7 @@ namespace MWWorld rot.x = Ogre::Degree(x).valueRadians(); rot.y = Ogre::Degree(y).valueRadians(); rot.z = Ogre::Degree(z).valueRadians(); - + float *objRot = ptr.getRefData().getPosition().rot; if(ptr.getRefData().getBaseNode() == 0 || !mRendering->rotateObject(ptr, rot, adjust)) { @@ -781,7 +818,7 @@ namespace MWWorld const ESM::Potion *World::createRecord (const ESM::Potion& record) { - return mStore.insert(record); + return mStore.insert(record); } const ESM::Class *World::createRecord (const ESM::Class& record) @@ -802,14 +839,30 @@ namespace MWWorld const ESM::NPC *World::createRecord(const ESM::NPC &record) { bool update = false; - if (StringUtils::ciEqual(record.mId, "player")) { + + if (Misc::StringUtils::ciEqual(record.mId, "player")) + { + static const char *sRaces[] = + { + "Argonian", "Breton", "Dark Elf", "High Elf", "Imperial", "Khajiit", "Nord", "Orc", "Redguard", + "Woodelf", 0 + }; + + int i=0; + + for (; sRaces[i]; ++i) + if (Misc::StringUtils::ciEqual (sRaces[i], record.mRace)) + break; + + mGlobalVariables->setInt ("pcrace", sRaces[i] ? i+1 : 0); + const ESM::NPC *player = mPlayer->getPlayer().get()->mBase; update = record.isMale() != player->isMale() || - !StringUtils::ciEqual(record.mRace, player->mRace) || - !StringUtils::ciEqual(record.mHead, player->mHead) || - !StringUtils::ciEqual(record.mHair, player->mHair); + !Misc::StringUtils::ciEqual(record.mRace, player->mRace) || + !Misc::StringUtils::ciEqual(record.mHead, player->mHead) || + !Misc::StringUtils::ciEqual(record.mHair, player->mHair); } const ESM::NPC *ret = mStore.insert(record); if (update) { @@ -834,7 +887,7 @@ namespace MWWorld /// \todo split this function up into subfunctions mWorldScene->update (duration, paused); - + float pitch, yaw; Ogre::Vector3 eyepos; mRendering->getPlayerData(eyepos, pitch, yaw); @@ -1062,28 +1115,7 @@ namespace MWWorld if (ref.mRef.mTeleport) { World::DoorMarker newMarker; - - std::string dest; - if (ref.mRef.mDestCell != "") - { - // door leads to an interior, use interior name - dest = ref.mRef.mDestCell; - } - else - { - // door leads to exterior, use cell name (if any), otherwise translated region name - int x,y; - positionToIndex (ref.mRef.mDoorDest.pos[0], ref.mRef.mDoorDest.pos[1], x, y); - const ESM::Cell* cell = mStore.get().find(x,y); - if (cell->mName != "") - dest = cell->mName; - else - { - dest = mStore.get().find(cell->mRegion)->mName; - } - } - - newMarker.name = dest; + newMarker.name = MWClass::Door::getDestination(ref); ESM::Position pos = ref.mData.getPosition (); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 0962c292c..71ae9597f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -95,7 +95,7 @@ namespace MWWorld World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, - const std::string& encoding, std::map fallbackMap); + const ToUTF8::FromType& encoding, std::map fallbackMap); virtual ~World(); @@ -153,6 +153,10 @@ namespace MWWorld virtual char getGlobalVariableType (const std::string& name) const; ///< Return ' ', if there is no global variable with this name. + + virtual std::vector getGlobals () const; + + virtual std::string getCurrentCellName () const; virtual Ptr getPtr (const std::string& name, bool activeOnly); ///< Return a pointer to a liveCellRef with the given name. @@ -228,7 +232,7 @@ namespace MWWorld virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos); - ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. + ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const; @@ -323,7 +327,7 @@ namespace MWWorld } virtual void renderPlayer(); - + virtual void setupExternalRendering (MWRender::ExternalRendering& rendering); virtual int canRest(); diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 8d5ea2f1e..97feddffe 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -27,6 +27,8 @@ macro(_FIND_BULLET_LIBRARY _var) ${ARGN} PATHS ${BULLET_ROOT} + ${BULLET_ROOT}/lib/Debug + ${BULLET_ROOT}/lib/Release ${BULLET_ROOT}/out/release8/libs ${BULLET_ROOT}/out/debug8/libs PATH_SUFFIXES lib diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 29d6f46cd..f3d5f4aee 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -59,7 +59,11 @@ add_component_dir (compiler add_component_dir (interpreter context controlopcodes genericopcodes installopcodes interpreter localopcodes mathopcodes - miscopcodes opcodes runtime scriptopcodes spatialopcodes types + miscopcodes opcodes runtime scriptopcodes spatialopcodes types defines + ) + +add_component_dir (translation + translation ) include_directories(${BULLET_INCLUDE_DIRS}) diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2915a1ce7..580e006df 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -347,21 +347,9 @@ void ESMReader::fail(const std::string &msg) throw std::runtime_error(ss.str()); } -void ESMReader::setEncoding(const std::string& encoding) +void ESMReader::setEncoding(const ToUTF8::FromType& encoding) { - if (encoding == "win1250") - { - mEncoding = ToUTF8::WINDOWS_1250; - } - else if (encoding == "win1251") - { - mEncoding = ToUTF8::WINDOWS_1251; - } - else - { - // Default Latin encoding - mEncoding = ToUTF8::WINDOWS_1252; - } + mEncoding = encoding; } } diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 6a74c53e8..1d0f6f580 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -234,7 +234,7 @@ public: void fail(const std::string &msg); /// Sets font encoding for ESM strings - void setEncoding(const std::string& encoding); + void setEncoding(const ToUTF8::FromType& encoding); private: Ogre::DataStreamPtr mEsm; diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index 39c07fb31..ceaa86948 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -44,4 +44,14 @@ void Global::save(ESMWriter &esm) esm.writeHNT("FLTV", mValue); } + void Global::blank() + { + mValue = 0; + mType = VT_Float; + } + + bool operator== (const Global& left, const Global& right) + { + return left.mId==right.mId && left.mValue==right.mValue && left.mType==right.mType; + } } diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 302729d2e..6111648a6 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -18,11 +18,17 @@ class ESMWriter; struct Global { std::string mId; - unsigned mValue; + float mValue; VarType mType; void load(ESMReader &esm); void save(ESMWriter &esm); + + void blank(); + ///< Set record to default state (does not touch the ID). }; + +bool operator== (const Global& left, const Global& right); + } #endif diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 14e29f4ae..a73095a66 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -2,146 +2,16 @@ #include -#include - #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { -/// \todo Review GMST "fixing". Probably remove completely or at least make it optional. Its definitely not -/// working properly in its current state and I doubt it can be fixed without breaking other stuff. - -// Some handy macros -#define cI(s,x) { label = (s); boost::algorithm::to_lower(label); if (mId == label) return (mI == (x)); } -#define cF(s,x) { label = (s); boost::algorithm::to_lower(label); if (mId == label) return (mF == (x)); } -#define cS(s,x) { label = (s); boost::algorithm::to_lower(label); if (mId == label) return (mStr == (x)); } - -bool GameSetting::isDirtyTribunal() -{ - /* - Here, mId contains the game setting name, and we check the - setting for certain values. If it matches, this is a "mDirty" - entry. The correct entry (as defined in Tribunal and Bloodmoon - esms) are given in the comments. Many of the values are correct, - and are marked as 'same'. We still ignore them though, as they - are still in the wrong file and might override custom values - from other mods. - */ - - std::string label; - // Strings - cS("sProfitValue", "Profit Value"); // 'Profit:' - cS("sEditNote", "Edit Note"); // same - cS("sDeleteNote", "Delete Note?"); // same - cS("sMaxSale", "Max Sale"); // 'Seller Max' - cS("sMagicFabricantID", "Fabricant"); // 'Fabricant_summon' - cS("sTeleportDisabled", - "Teleportation magic does not work here.");// same - cS("sLevitateDisabled", - "Levitation magic does not work here."); // same - cS("sCompanionShare", "Companion Share"); // 'Share' - cS("sCompanionWarningButtonOne", - "Let the mercenary quit."); // same - cS("sCompanionWarningButtonTwo", - "Return to Companion Share display."); // same - cS("sCompanionWarningMessage", - "Your mercenary is poorer now than when he contracted with you. Your mercenary will quit if you do not give him gold or goods to bring his Profit Value to a positive value."); - // 'Your mercenary is poorer now than when he contracted with - // you. Your mercenary will quit if you do not give him gold - // or goods to bring his Profit to a positive value.' - // [The difference here is "Profit Value" -> "Profit"] - - // Strings that matches the mId - cS("sEffectSummonFabricant", "sEffectSummonFabricant");// 'Summon Fabricant' - return false; -} - -// Bloodmoon variant -bool GameSetting::isDirtyBloodmoon() -{ - std::string label; - // Strings - cS("sWerewolfPopup", "Werewolf"); // same - cS("sWerewolfRestMessage", - "You cannot rest in werewolf form."); // same - cS("sWerewolfRefusal", - "You cannot do this as a werewolf."); // same - cS("sWerewolfAlarmMessage", - "You have been detected changing from a werewolf state."); - // 'You have been detected as a known werewolf.' - - // Strings that matches the mId - cS("sMagicCreature01ID", "sMagicCreature01ID"); // 'BM_wolf_grey_summon' - cS("sMagicCreature02ID", "sMagicCreature02ID"); // 'BM_bear_black_summon' - cS("sMagicCreature03ID", "sMagicCreature03ID"); // 'BM_wolf_bone_summon' - cS("sMagicCreature04ID", "sMagicCreature04ID"); // same - cS("sMagicCreature05ID", "sMagicCreature05ID"); // same - cS("sEffectSummonCreature01", "sEffectSummonCreature01"); // 'Calf Wolf' - cS("sEffectSummonCreature02", "sEffectSummonCreature02"); // 'Calf Bear' - cS("sEffectSummonCreature03", "sEffectSummonCreature03"); // 'Summon Bonewolf' - cS("sEffectSummonCreature04", "sEffectSummonCreature04"); // same - cS("sEffectSummonCreature05", "sEffectSummonCreature05"); // same - - // Integers - cI("iWereWolfBounty", 10000); // 1000 - cI("iWereWolfFightMod", 100); // same - cI("iWereWolfFleeMod", 100); // same - cI("iWereWolfLevelToAttack", 20); // same - - // Floats - cF("fFleeDistance", 3000); // same - cF("fCombatDistanceWerewolfMod", 0.3); // same - cF("fWereWolfFatigue", 400); // same - cF("fWereWolfEnchant", 1); // 0 - cF("fWereWolfArmorer", 1); // 0 - cF("fWereWolfBlock", 1); // 0 - cF("fWereWolfSneak", 1); // 95 - cF("fWereWolfDestruction", 1); // 0 - cF("fWereWolfEndurance", 150); // same - cF("fWereWolfConjuration", 1); // 0 - cF("fWereWolfRestoration", 1); // 0 - cF("fWereWolfAthletics", 150); // 50 - cF("fWereWolfLuck", 1); // 25 - cF("fWereWolfSilverWeaponDamageMult", 1.5); // 2 - cF("fWereWolfMediumArmor", 1); // 0 - cF("fWereWolfShortBlade", 1); // 0 - cF("fWereWolfAcrobatics", 150); // 80 - cF("fWereWolfSpeechcraft", 1); // 0 - cF("fWereWolfAlteration", 1); // 0 - cF("fWereWolfIllusion", 1); // 0 - cF("fWereWolfLongBlade", 1); // 0 - cF("fWereWolfMarksman", 1); // 0 - cF("fWereWolfHandtoHand", 100); // same - cF("fWereWolfIntellegence", 1); // 0 - cF("fWereWolfAlchemy", 1); // 0 - cF("fWereWolfUnarmored", 100); // same - cF("fWereWolfAxe", 1); // 0 - cF("fWereWolfRunMult", 1.5); // 1.3 - cF("fWereWolfMagicka", 100); // same - cF("fWereWolfAgility", 150); // same - cF("fWereWolfBluntWeapon", 1); // 0 - cF("fWereWolfSecurity", 1); // 0 - cF("fWereWolfPersonality", 1); // 0 - cF("fWereWolfMerchantile", 1); // 0 - cF("fWereWolfHeavyArmor", 1); // 0 - cF("fWereWolfSpear", 1); // 0 - cF("fWereWolfStrength", 150); // same - cF("fWereWolfHealth", 2); // same - cF("fWereWolfMysticism", 1); // 0 - cF("fWereWolfLightArmor", 1); // 0 - cF("fWereWolfWillPower", 1); // 0 - cF("fWereWolfSpeed", 150); // 90 - return false; -} - void GameSetting::load(ESMReader &esm) { assert(mId != ""); - mDirty = false; - // We are apparently allowed to be empty if (!esm.hasMoreSubs()) { @@ -169,17 +39,8 @@ void GameSetting::load(ESMReader &esm) } else esm.fail("Unwanted subrecord type"); - - int spf = esm.getSpecial(); - - // Check if this is one of the mDirty values mentioned above. If it - // is, we set the mDirty flag. This will ONLY work if you've set - // the 'id' string correctly before calling load(). - - if ((spf != SF_Tribunal && isDirtyTribunal()) || (spf != SF_Bloodmoon - && isDirtyBloodmoon())) - mDirty = true; } + void GameSetting::save(ESMWriter &esm) { switch(mType) diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index a3471598c..ab9a9551e 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -25,66 +25,6 @@ struct GameSetting float mF; VarType mType; - // Set to true if this is a 'dirty' entry which should be ignored - bool mDirty; - - /* - These functions check if this game setting is one of the "dirty" - GMST records found in many mods. These are due to a serious bug in - the official TES3 editor. It only occurs in the newer editor - versions that came with Tribunal and Bloodmoon, and only if a - modder tries to make a mod without loading the corresponding - expansion master file. For example, if you have Tribunal installed - and try to make a mod without loading Tribunal.esm, the editor - will insert these GMST records as a replacement for the entries it - cannot find in the ESMs. - - The values of these "dirty" records differ in general from their - values as defined in Tribunal.esm and Bloodmoon.esm, and are - always set to the same "default" values. Most of these values are - nonsensical, ie. changing the "Seller Max" string to "Max Sale", - or change the stats of werewolves to useless values like 1. Some - of them break certain spell effects. - - It is most likely that these values are just leftover values from - an early stage of development that are inserted as default values - by the editor code. They are supposed to be overridden when the - correct esm file is loaded. When it isn't loaded however, you get - stuck with the initial value, and this gets written to every mod - by the editor, for some reason. - - Bethesda themselves have fallen for this bug. If you install both - Tribunal and Bloodmoon, the updated Tribunal.esm will contain the - dirty GMST settings from Bloodmoon, and Bloodmoon.esm will contain - some of the dirty settings from Tribunal. In other words, this bug - affects the game EVEN IF YOU DO NOT USE ANY MODS! - - The guys at Bethesda are well aware of this bug (and many others), - as the mod community and fan base complained about them for a long - time. But unfortunately it was never fixed. - - There are several tools available to help modders remove these - records from their files, but not all modders use them, and they - really shouldn't have to. In this file we choose instead to reject - all the corrupt values at load time. - - These functions checks if the current game setting is one of the - "dirty" ones as described above. TODO: I have not checked this - against other sources yet, do that later. Currently recognizes 22 - values for tribunal and 50 for bloodmoon. Legitimate GMSTs in mods - (setting values other than the default "dirty" ones) are not - affected and will work correctly. - */ - - /* - Checks for dirty tribunal values. These will be ignored if found - in any file except when they are found in "Tribunal.esm". - */ - bool isDirtyTribunal(); - - // Bloodmoon variant - bool isDirtyBloodmoon(); - void load(ESMReader &esm); int getInt() const; diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index b44c42986..347de96a6 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -95,6 +95,11 @@ namespace Files return iter->second; } + bool MultiDirCollection::doesExist (const std::string& file) const + { + return mFiles.find (file)!=mFiles.end(); + } + MultiDirCollection::TIter MultiDirCollection::begin() const { return mFiles.begin(); diff --git a/components/files/multidircollection.hpp b/components/files/multidircollection.hpp index e8abeb45d..3b420d677 100644 --- a/components/files/multidircollection.hpp +++ b/components/files/multidircollection.hpp @@ -73,6 +73,9 @@ namespace Files /// If the file does not exist, an exception is thrown. \a file must include /// the extension. + bool doesExist (const std::string& file) const; + ///< \return Does a file with the given name exist? + TIter begin() const; ///< Return iterator pointing to the first file. diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp index 4221da36e..bdba7b6af 100644 --- a/components/interpreter/context.hpp +++ b/components/interpreter/context.hpp @@ -49,6 +49,36 @@ namespace Interpreter virtual void setGlobalFloat (const std::string& name, float value) = 0; + virtual std::vector getGlobals () const = 0; + + virtual char getGlobalType (const std::string& name) const = 0; + + virtual std::string getActionBinding(const std::string& action) const = 0; + + virtual std::string getNPCName() const = 0; + + virtual std::string getNPCRace() const = 0; + + virtual std::string getNPCClass() const = 0; + + virtual std::string getNPCFaction() const = 0; + + virtual std::string getNPCRank() const = 0; + + virtual std::string getPCName() const = 0; + + virtual std::string getPCRace() const = 0; + + virtual std::string getPCClass() const = 0; + + virtual std::string getPCRank() const = 0; + + virtual std::string getPCNextRank() const = 0; + + virtual int getPCBounty() const = 0; + + virtual std::string getCurrentCellName() const = 0; + virtual bool isScriptRunning (const std::string& name) const = 0; virtual void startScript (const std::string& name) = 0; diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp new file mode 100644 index 000000000..18e5f81c1 --- /dev/null +++ b/components/interpreter/defines.cpp @@ -0,0 +1,209 @@ +#include "defines.hpp" + +#include +#include +#include +#include + +namespace Interpreter{ + + bool Check(const std::string str, const std::string escword, unsigned int* i, unsigned int* start){ + bool retval = str.find(escword) == 0; + if(retval){ + (*i) += escword.length(); + (*start) = (*i) + 1; + } + return retval; + } + + std::vector globals; + + bool longerStr(const std::string a, const std::string b){ + return a.length() > b.length(); + } + + std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context){ + + unsigned int start = 0; + std::ostringstream retval; + for(unsigned int i = 0; i < text.length(); i++){ + if(text[i] == eschar){ + retval << text.substr(start, i - start); + std::string temp = text.substr(i+1, 100); + transform(temp.begin(), temp.end(), temp.begin(), ::tolower); + + bool found; + + if( (found = Check(temp, "actionslideright", &i, &start))){ + retval << context.getActionBinding("#{sRight}"); + } + else if((found = Check(temp, "actionreadymagic", &i, &start))){ + retval << context.getActionBinding("#{sReady_Magic}"); + } + else if((found = Check(temp, "actionprevweapon", &i, &start))){ + retval << "PLACEHOLDER_ACTION_PREV_WEAPON"; + } + else if((found = Check(temp, "actionnextweapon", &i, &start))){ + retval << "PLACEHOLDER_ACTION_PREV_WEAPON"; + } + else if((found = Check(temp, "actiontogglerun", &i, &start))){ + retval << context.getActionBinding("#{sAuto_Run}"); + } + else if((found = Check(temp, "actionslideleft", &i, &start))){ + retval << context.getActionBinding("#{sLeft}"); + } + else if((found = Check(temp, "actionreadyitem", &i, &start))){ + retval << context.getActionBinding("#{sReady_Weapon}"); + } + else if((found = Check(temp, "actionprevspell", &i, &start))){ + retval << "PLACEHOLDER_ACTION_PREV_SPELL"; + } + else if((found = Check(temp, "actionnextspell", &i, &start))){ + retval << "PLACEHOLDER_ACTION_NEXT_SPELL"; + } + else if((found = Check(temp, "actionrestmenu", &i, &start))){ + retval << context.getActionBinding("#{sRestKey}"); + } + else if((found = Check(temp, "actionmenumode", &i, &start))){ + retval << context.getActionBinding("#{sJournal}"); + } + else if((found = Check(temp, "actionactivate", &i, &start))){ + retval << context.getActionBinding("#{sActivate}"); + } + else if((found = Check(temp, "actionjournal", &i, &start))){ + retval << context.getActionBinding("#{sJournal}"); + } + else if((found = Check(temp, "actionforward", &i, &start))){ + retval << context.getActionBinding("#{sForward}"); + } + else if((found = Check(temp, "pccrimelevel", &i, &start))){ + retval << context.getPCBounty(); + } + else if((found = Check(temp, "actioncrouch", &i, &start))){ + retval << context.getActionBinding("#{sCrouch_Sneak}"); + } + else if((found = Check(temp, "actionjump", &i, &start))){ + retval << context.getActionBinding("#{sJump}"); + } + else if((found = Check(temp, "actionback", &i, &start))){ + retval << context.getActionBinding("#{sBack}"); + } + else if((found = Check(temp, "actionuse", &i, &start))){ + retval << "PLACEHOLDER_ACTION_USE"; + } + else if((found = Check(temp, "actionrun", &i, &start))){ + retval << "PLACEHOLDER_ACTION_RUN"; + } + else if((found = Check(temp, "pcclass", &i, &start))){ + retval << context.getPCClass(); + } + else if((found = Check(temp, "pcrace", &i, &start))){ + retval << context.getPCRace(); + } + else if((found = Check(temp, "pcname", &i, &start))){ + retval << context.getPCName(); + } + else if((found = Check(temp, "cell", &i, &start))){ + retval << context.getCurrentCellName(); + } + + else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox + if( (found = Check(temp, "faction", &i, &start))){ + retval << context.getNPCFaction(); + } + else if((found = Check(temp, "nextpcrank", &i, &start))){ + retval << context.getPCNextRank(); + } + else if((found = Check(temp, "pcnextrank", &i, &start))){ + retval << context.getPCNextRank(); + } + else if((found = Check(temp, "pcrank", &i, &start))){ + retval << context.getPCRank(); + } + else if((found = Check(temp, "rank", &i, &start))){ + retval << context.getNPCRank(); + } + + else if((found = Check(temp, "class", &i, &start))){ + retval << context.getNPCClass(); + } + else if((found = Check(temp, "race", &i, &start))){ + retval << context.getNPCRace(); + } + else if((found = Check(temp, "name", &i, &start))){ + retval << context.getNPCName(); + } + } + else { // In messagebox or book, not dialogue + + /* empty outside dialogue */ + if( (found = Check(temp, "faction", &i, &start))); + else if((found = Check(temp, "nextpcrank", &i, &start))); + else if((found = Check(temp, "pcnextrank", &i, &start))); + else if((found = Check(temp, "pcrank", &i, &start))); + else if((found = Check(temp, "rank", &i, &start))); + + /* uses pc in messageboxes */ + else if((found = Check(temp, "class", &i, &start))){ + retval << context.getPCClass(); + } + else if((found = Check(temp, "race", &i, &start))){ + retval << context.getPCRace(); + } + else if((found = Check(temp, "name", &i, &start))){ + retval << context.getPCName(); + } + } + + /* Not a builtin, try global variables */ + if(!found){ + /* if list of globals is empty, grab it and sort it by descending string length */ + if(globals.empty()){ + globals = context.getGlobals(); + sort(globals.begin(), globals.end(), longerStr); + } + + for(unsigned int j = 0; j < globals.size(); j++){ + if(globals[j].length() > temp.length()){ // Just in case there's a global with a huuuge name + std::string temp = text.substr(i+1, globals[j].length()); + transform(temp.begin(), temp.end(), temp.begin(), ::tolower); + } + + if((found = Check(temp, globals[j], &i, &start))){ + char type = context.getGlobalType(globals[j]); + + switch(type){ + case 's': retval << context.getGlobalShort(globals[j]); break; + case 'l': retval << context.getGlobalLong(globals[j]); break; + case 'f': retval << context.getGlobalFloat(globals[j]); break; + } + break; + } + } + } + + /* Not found */ + if(!found){ + /* leave unmodified */ + i += 1; + start = i; + retval << eschar; + } + } + } + retval << text.substr(start, text.length() - start); + return retval.str (); + } + + std::string fixDefinesDialog(std::string text, Context& context){ + return fixDefinesReal(text, '%', false, context); + } + + std::string fixDefinesMsgBox(std::string text, Context& context){ + return fixDefinesReal(text, '^', false, context); + } + + std::string fixDefinesBook(std::string text, Context& context){ + return fixDefinesReal(text, '%', true, context); + } +} diff --git a/components/interpreter/defines.hpp b/components/interpreter/defines.hpp new file mode 100644 index 000000000..00c4386b8 --- /dev/null +++ b/components/interpreter/defines.hpp @@ -0,0 +1,13 @@ +#ifndef INTERPRETER_DEFINES_H_INCLUDED +#define INTERPRETER_DEFINES_H_INCLUDED + +#include +#include "context.hpp" + +namespace Interpreter{ + std::string fixDefinesDialog(std::string text, Context& context); + std::string fixDefinesMsgBox(std::string text, Context& context); + std::string fixDefinesBook(std::string text, Context& context); +} + +#endif diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 37c38fc30..1b4c823a0 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -10,6 +10,7 @@ #include "opcodes.hpp" #include "runtime.hpp" +#include "defines.hpp" namespace Interpreter { @@ -69,7 +70,8 @@ namespace Interpreter } } } - + + formattedMessage = fixDefinesMsgBox(formattedMessage, runtime.getContext()); return formattedMessage; } diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp index 53eed1fdc..0bc8e290a 100644 --- a/components/misc/stringops.cpp +++ b/components/misc/stringops.cpp @@ -1,8 +1,14 @@ #include "stringops.hpp" +#include +#include +#include + #include #include + + namespace Misc { diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index a32104bf3..029b617e1 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -1,8 +1,59 @@ #ifndef MISC_STRINGOPS_H #define MISC_STRINGOPS_H +#include +#include +#include + namespace Misc { +class StringUtils +{ + struct ci + { + bool operator()(int x, int y) const { + return std::tolower(x) < std::tolower(y); + } + }; + +public: + static bool ciLess(const std::string &x, const std::string &y) { + return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); + } + + static bool ciEqual(const std::string &x, const std::string &y) { + if (x.size() != y.size()) { + return false; + } + std::string::const_iterator xit = x.begin(); + std::string::const_iterator yit = y.begin(); + for (; xit != x.end(); ++xit, ++yit) { + if (std::tolower(*xit) != std::tolower(*yit)) { + return false; + } + } + return true; + } + + /// Transforms input string to lower case w/o copy + static std::string &toLower(std::string &inout) { + std::transform( + inout.begin(), + inout.end(), + inout.begin(), + (int (*)(int)) std::tolower + ); + return inout; + } + + /// Returns lower case copy of input string + static std::string lowerCase(const std::string &in) + { + std::string out = in; + return toLower(out); + } +}; + /// Returns true if str1 begins with substring str2 bool begins(const char* str1, const char* str2); diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 6f0ed8bfd..7db611247 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -357,3 +357,23 @@ std::string ToUTF8::getLegacyEnc(ToUTF8::FromType to) // Return a string return std::string(&output[0], outlen); } + +ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) +{ + if (encodingName == "win1250") + return ToUTF8::WINDOWS_1250; + else if (encodingName == "win1251") + return ToUTF8::WINDOWS_1251; + else + return ToUTF8::WINDOWS_1252; +} + +std::string ToUTF8::encodingUsingMessage(const std::string& encodingName) +{ + if (encodingName == "win1250") + return "Using Central and Eastern European font encoding."; + else if (encodingName == "win1251") + return "Using Cyrillic font encoding."; + else + return "Using default (English) font encoding."; +} diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 69e9fc92c..f52ae73bd 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -22,6 +22,9 @@ namespace ToUTF8 // page. std::string getUtf8(FromType from); std::string getLegacyEnc(FromType to); + + FromType calculateEncoding(const std::string& encodingName); + std::string encodingUsingMessage(const std::string& encodingName); } #endif diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp new file mode 100644 index 000000000..fb5b03861 --- /dev/null +++ b/components/translation/translation.cpp @@ -0,0 +1,118 @@ +#include "translation.hpp" +#include + +#include + +namespace Translation +{ + void Storage::loadTranslationData(const Files::Collections& dataFileCollections, + const std::string& esmFileName) + { + std::string esmNameNoExtension(Misc::StringUtils::lowerCase(esmFileName)); + //changing the extension + size_t dotPos = esmNameNoExtension.rfind('.'); + if (dotPos != std::string::npos) + esmNameNoExtension.resize(dotPos); + + loadData(mCellNamesTranslations, esmNameNoExtension, ".cel", dataFileCollections); + loadData(mPhraseForms, esmNameNoExtension, ".top", dataFileCollections); + loadData(mTopicIDs, esmNameNoExtension, ".mrk", dataFileCollections); + } + + void Storage::loadData(ContainerType& container, + const std::string& fileNameNoExtension, + const std::string& extension, + const Files::Collections& dataFileCollections) + { + std::string fileName = fileNameNoExtension + extension; + + if (dataFileCollections.getCollection (extension).doesExist (fileName)) + { + std::string path = dataFileCollections.getCollection (extension).getPath (fileName).string(); + + std::ifstream stream (path); + + if (!stream.is_open()) + throw std::runtime_error ("failed to open translation file: " + fileName); + + loadDataFromStream(container, stream); + } + } + + void Storage::loadDataFromStream(ContainerType& container, std::istream& stream) + { + std::string line; + while (!stream.eof()) + { + std::getline( stream, line ); + if (!line.empty() && *line.rbegin() == '\r') + line.resize(line.size() - 1); + + if (!line.empty()) + { + char* buffer = ToUTF8::getBuffer(line.size() + 1); + //buffer has at least line.size() + 1 bytes, so it must be safe + strcpy(buffer, line.c_str()); + line = ToUTF8::getUtf8(mEncoding); + + size_t tab_pos = line.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) + { + std::string key = line.substr(0, tab_pos); + std::string value = line.substr(tab_pos + 1); + + if (!key.empty() && !value.empty()) + container.insert(std::make_pair(key, value)); + } + } + } + } + + std::string Storage::translateCellName(const std::string& cellName) const + { + std::map::const_iterator entry = + mCellNamesTranslations.find(cellName); + + if (entry == mCellNamesTranslations.end()) + return cellName; + + return entry->second; + } + + std::string Storage::topicID(const std::string& phrase) const + { + std::string result = topicStandardForm(phrase); + + //seeking for the topic ID + std::map::const_iterator topicIDIterator = + mTopicIDs.find(result); + + if (topicIDIterator != mTopicIDs.end()) + result = topicIDIterator->second; + + return result; + } + + std::string Storage::topicStandardForm(const std::string& phrase) const + { + std::map::const_iterator phraseFormsIterator = + mPhraseForms.find(phrase); + + if (phraseFormsIterator != mPhraseForms.end()) + return phraseFormsIterator->second; + else + return phrase; + } + + void Storage::setEncoding (const ToUTF8::FromType& encoding) + { + mEncoding = encoding; + } + + bool Storage::hasTranslation() const + { + return !mCellNamesTranslations.empty() || + !mTopicIDs.empty() || + !mPhraseForms.empty(); + } +} diff --git a/components/translation/translation.hpp b/components/translation/translation.hpp new file mode 100644 index 000000000..80d44d871 --- /dev/null +++ b/components/translation/translation.hpp @@ -0,0 +1,42 @@ +#ifndef COMPONENTS_TRANSLATION_DATA_H +#define COMPONENTS_TRANSLATION_DATA_H + +#include +#include + +namespace Translation +{ + class Storage + { + public: + + void loadTranslationData(const Files::Collections& dataFileCollections, + const std::string& esmFileName); + + std::string translateCellName(const std::string& cellName) const; + std::string topicID(const std::string& phrase) const; + + // Standard form usually means nominative case + std::string topicStandardForm(const std::string& phrase) const; + + void setEncoding (const ToUTF8::FromType& encoding); + + bool hasTranslation() const; + + private: + typedef std::map ContainerType; + + void loadData(ContainerType& container, + const std::string& fileNameNoExtension, + const std::string& extension, + const Files::Collections& dataFileCollections); + + void loadDataFromStream(ContainerType& container, std::istream& stream); + + + ToUTF8::FromType mEncoding; + ContainerType mCellNamesTranslations, mTopicIDs, mPhraseForms; + }; +} + +#endif diff --git a/credits.txt b/credits.txt index 063e9940a..713fbe9ac 100644 --- a/credits.txt +++ b/credits.txt @@ -26,17 +26,19 @@ Jacob Essex (Yacoby) Jannik Heller (scrawl) Jason Hooks (jhooks) Karl-Felix Glatzer (k1ll) +lazydev Leon Saunders (emoose) Lukasz Gromanowski (lgro) Marcin Hulist (Gohan) Michael Mc Donnell Michael Papageorgiou (werdanith) +Nathan Jeffords (blunted2night) Nikolay Kasyanov (corristo) Pieter van der Kloet (pvdk) Roman Melnik (Kromgart) Sebastian Wick (swick) Sylvain T. (Garvek) - +Tom Mason (wheybags) Packagers: Alexander Olofsson (Ace) - Windows diff --git a/extern/shiny/CMakeLists.txt b/extern/shiny/CMakeLists.txt index 603336413..c27850ed6 100644 --- a/extern/shiny/CMakeLists.txt +++ b/extern/shiny/CMakeLists.txt @@ -70,6 +70,3 @@ endif() link_directories(${CMAKE_CURRENT_BINARY_DIR}) - -set(SHINY_LIBRARY ${SHINY_LIBRARY} PARENT_SCOPE) -set(SHINY_OGREPLATFORM_LIBRARY ${SHINY_OGREPLATFORM_LIBRARY} PARENT_SCOPE) diff --git a/extern/shiny/Main/MaterialInstance.cpp b/extern/shiny/Main/MaterialInstance.cpp index 0f8bcdda7..3abc781f6 100644 --- a/extern/shiny/Main/MaterialInstance.cpp +++ b/extern/shiny/Main/MaterialInstance.cpp @@ -72,8 +72,6 @@ namespace sh allowFixedFunction = retrieveValue(getProperty("allow_fixed_function"), NULL).get(); } - bool useShaders = mShadersEnabled || !allowFixedFunction; - // get passes of the top-most parent PassVector passes = getPasses(); if (passes.size() == 0) @@ -93,7 +91,7 @@ namespace sh // create or retrieve shaders bool hasVertex = it->hasProperty("vertex_program"); bool hasFragment = it->hasProperty("fragment_program"); - if (useShaders) + if (mShadersEnabled || !allowFixedFunction) { it->setContext(context); it->mShaderProperties.setContext(context); @@ -146,7 +144,7 @@ namespace sh bool foundVertex = std::find(usedTextureSamplersVertex.begin(), usedTextureSamplersVertex.end(), texIt->getName()) != usedTextureSamplersVertex.end(); bool foundFragment = std::find(usedTextureSamplersFragment.begin(), usedTextureSamplersFragment.end(), texIt->getName()) != usedTextureSamplersFragment.end(); if ( (foundVertex || foundFragment) - || (((!useShaders || (!hasVertex || !hasFragment)) && allowFixedFunction) && texIt->hasProperty("create_in_ffp") && retrieveValue(texIt->getProperty("create_in_ffp"), this).get())) + || (((!mShadersEnabled || (!hasVertex || !hasFragment)) && allowFixedFunction) && texIt->hasProperty("create_in_ffp") && retrieveValue(texIt->getProperty("create_in_ffp"), this).get())) { boost::shared_ptr texUnit = pass->createTextureUnitState (); texIt->copyAll (texUnit.get(), context); @@ -154,7 +152,7 @@ namespace sh mTexUnits.push_back(texUnit); // set texture unit indices (required by GLSL) - if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && mFactory->getCurrentLanguage () == Language_GLSL) + if (mShadersEnabled && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && mFactory->getCurrentLanguage () == Language_GLSL) { pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i); diff --git a/files/materials/core.h b/files/materials/core.h index e9577bbf3..1c9ea1d1d 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -1,5 +1,6 @@ -#define gammaCorrectRead(v) pow(v, float3(gammaCorrection,gammaCorrection,gammaCorrection)) -#define gammaCorrectOutput(v) pow(v, float3(1.f/gammaCorrection,1.f/gammaCorrection,1.f/gammaCorrection)) +#define gammaCorrectRead(v) pow(max(v, 0.00001f), float3(gammaCorrection,gammaCorrection,gammaCorrection)) +#define gammaCorrectOutput(v) pow(max(v, 0.00001f), float3(1.f/gammaCorrection,1.f/gammaCorrection,1.f/gammaCorrection)) + #if SH_HLSL == 1 || SH_CG == 1 diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 5ea076342..25624351c 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -227,7 +227,7 @@ float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); - if (worldPos.y >= waterLevel || waterEnabled != 1) + if (worldPos.y >= waterLevel || waterEnabled != 1.f) caustics = float3(1,1,1); #endif @@ -269,7 +269,7 @@ #if UNDERWATER // regular fog only if fragment is above water - if (worldPos.y > waterLevel) + if (worldPos.y > waterLevel || waterEnabled != 1.f) #endif shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, gammaCorrectRead(fogColour), fogValue); #endif diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index f320d009d..76bdb491d 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -122,8 +122,13 @@ namespace Physic */ void runPmove(); - - +//HACK: in Visual Studio 2010 and presumably above, this structures alignment +// must be 16, but the built in operator new & delete don't properly +// perform this alignment. +#if _MSC_VER >= 1600 + void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } + void operator delete (void * Data) { _aligned_free (Data); } +#endif private: diff --git a/readme.txt b/readme.txt index 9a9010994..21ae85530 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.19.0 +Version: 0.20.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -69,12 +69,9 @@ Allowed options: --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scri pts) at startup --script-console [=arg(=1)] (=0) enable console-only script functionality - --script-run arg select a file that is executed in the consol - e on startup + --script-run arg select a file containing a list of console + commands that is executed on startup - Note: The file contains a list of script - lines, but not a complete scripts. That mean - s no begin/end and no variable declarations. --new-game [=arg(=1)] (=0) activate char gen/new game mechanics --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding ) @@ -97,6 +94,36 @@ Allowed options: CHANGELOG +0.20.0 + +Bug #366: Changing the player's race during character creation does not change the look of the player character +Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell +Bug #437: Stop animations when paused +Bug #438: Time displays as "0 a.m." when it should be "12 a.m." +Bug #439: Text in "name" field of potion/spell creation window is persistent +Bug #440: Starting date at a new game is off by one day +Bug #442: Console window doesn't close properly sometimes +Bug #448: Do not break container window formatting when item names are very long +Bug #458: Topics sometimes not automatically added to known topic list +Bug #476: Auto-Moving allows player movement after using DisablePlayerControls +Bug #478: After sleeping in a bed the rest dialogue window opens automtically again +Bug #492: On creating potions the ingredients are removed twice +Feature #63: Mercantile skill +Feature #82: Persuasion Dialogue +Feature #219: Missing dialogue filters/functions +Feature #369: Add a FailedAction +Feature #377: Select head/hair on character creation +Feature #391: Dummy AI package classes +Feature #435: Global Map, 2nd Layer +Feature #450: Persuasion +Feature #457: Add more script instructions +Feature #474: update the global variable pcrace when the player's race is changed +Task #158: Move dynamically generated classes from Player class to World Class +Task #159: ESMStore rework and cleanup +Task #163: More Component Namespace Cleanup +Task #402: Move player data from MWWorld::Player to the player's NPC record +Task #446: Fix no namespace in BulletShapeLoader + 0.19.0 Bug #374: Character shakes in 3rd person mode near the origin