From 59f7c0c27d6c6377fbaeb11d382efd63ff0f7255 Mon Sep 17 00:00:00 2001 From: demo Date: Sun, 2 Jul 2017 16:30:13 +0100 Subject: [PATCH] - update minimum SFML version to 2.4 (fix deprecated get/setColor) - embed sfeMovie - update cmake to add FFmpeg FindFFMPEG.cmake - update deprecated code for FFMPEG3 upgrade in sfeMovie (90%) - remove unused code from sfeMovie (SubtitleStream) - add isUsable to item - add touch support (untested) - add UIText interface - execute action given drawable id and action name - refactor buttons - refactor cel decoding (fix small font) - refactor input events code - refactor setText actions into one (text.setText) - refactor StringButton, Text2 - update android minimum version - update itemClass description to support skipping matches - update UIObject interface to add method to get action - update gamefiles - resize level panel if user panels are open/closed and if size = 640x480 - update item prefixes and descriptions - update level right click --- .gitignore | 2 + BUILD.txt | 40 +- CMakeLists.txt | 76 +- DGEngine.vcxproj | 75 +- License Notes.txt | 3 + android/AndroidManifest.xml | 2 +- android/jni/Android.mk | 5 + android/jni/Application.mk | 2 +- cmake_modules/FindFFmpeg.cmake | 165 ++++ gamefilesd/level/afterLevelLoad.json | 18 +- gamefilesd/level/item/amulets.json | 10 +- gamefilesd/level/item/armors.json | 8 +- gamefilesd/level/item/baseItems.json | 20 +- gamefilesd/level/item/books.json | 5 +- gamefilesd/level/item/bows.json | 2 +- gamefilesd/level/item/descriptions.json | 160 +++- gamefilesd/level/item/potions.json | 28 +- gamefilesd/level/item/prefixes.json | 76 +- gamefilesd/level/item/scrolls.json | 6 +- gamefilesd/level/item/staffs.json | 2 +- gamefilesd/level/item/suffixes.json | 75 +- gamefilesd/level/item/swords.json | 2 +- gamefilesd/level/load.json | 12 +- gamefilesd/level/loadBase.json | 7 +- gamefilesd/level/newGame.json | 7 +- gamefilesd/level/town/players2.json | 2 +- gamefilesd/level/town/sounds.json | 1 + gamefilesd/res/level/actions.json | 1 + gamefilesd/res/level/actions/basePanel.json | 5 + gamefilesd/res/level/actions/beltUse.json | 16 + .../res/level/actions/inventoryText.json | 8 +- .../res/level/actions/inventoryUse.json | 10 - .../res/level/actions/playerBeltUse.json | 16 +- .../res/level/actions/playerBodyHands2.json | 16 +- .../res/level/actions/playerStashUse.json | 80 +- .../res/level/actions/playerUpdate.json | 94 ++- gamefilesd/res/level/actions/resizeLevel.json | 49 ++ gamefilesd/res/level/actions/stashUse.json | 16 + gamefilesd/res/level/fonts.json | 1 + gamefilesd/towners/adria/buy/confirm2.json | 53 +- gamefilesd/towners/adria/buy/listItems.json | 77 +- gamefilesd/towners/adria/buy/panel.json | 46 +- .../towners/adria/recharge/confirm2.json | 4 +- .../towners/adria/recharge/listItems.json | 35 +- gamefilesd/towners/adria/sell/confirm2.json | 52 +- gamefilesd/towners/adria/sell/listItems.json | 112 +-- gamefilesd/towners/adria/sell/panel.json | 87 +-- .../towners/cain/identify/confirm2.json | 45 +- gamefilesd/towners/cain/identify/item.json | 53 +- .../towners/cain/identify/listItems.json | 35 +- gamefilesd/towners/cain/identify/panel.json | 141 +--- .../towners/common/loadMenuButtons.json | 40 + gamefilesd/towners/common/setItemInfo.json | 105 +++ .../towners/common/setMenuItemText.json | 117 +++ .../towners/common/setMenuPriceText.json | 44 ++ gamefilesd/towners/common/setPriceInfo.json | 28 + gamefilesd/towners/griswold/buy/confirm2.json | 53 +- .../towners/griswold/buy/listItems.json | 77 +- gamefilesd/towners/griswold/buy/panel.json | 46 +- .../towners/griswold/buyPremium/confirm2.json | 53 +- .../griswold/buyPremium/listItems.json | 77 +- .../towners/griswold/buyPremium/panel.json | 46 +- .../towners/griswold/repair/confirm2.json | 4 +- .../towners/griswold/repair/listItems.json | 35 +- .../towners/griswold/sell/confirm2.json | 52 +- .../towners/griswold/sell/listItems.json | 74 +- gamefilesd/towners/griswold/sell/panel.json | 45 +- gamefilesd/towners/pepin/buy/confirm2.json | 53 +- gamefilesd/towners/pepin/buy/listItems.json | 77 +- gamefilesd/towners/pepin/buy/panel.json | 46 +- gamefilesd/towners/wirt/buy/confirm2.json | 53 +- gamefilesd/towners/wirt/buy/listItems.json | 42 +- gamefilesd/towners/wirt/buy/panel.json | 46 +- gamefilesd/ui/level/char/closePanels.json | 2 + gamefilesd/ui/level/char/panel.json | 196 ++--- gamefilesd/ui/level/menu/game2.json | 4 +- gamefilesd/ui/level/panel/big.json | 4 +- gamefilesd/ui/level/panel/small.json | 18 +- gamefilesd/ui/level/showText.json | 1 + gamefilesd/ui/mainMenu.json | 6 +- gamefilesd/ui/settings.json | 34 +- gamefileshf/level/afterLevelLoad.json | 16 +- gamefileshf/ui/level/menu/game2.json | 34 +- gamefileshf/ui/mainMenu.json | 6 +- src/Actions/ActButton.h | 33 - src/Actions/ActDrawable.h | 49 +- src/Actions/ActGame.h | 2 +- src/Actions/ActMenu.h | 78 +- src/Actions/ActText.h | 165 ---- src/Actions/ActUIText.h | 125 +++ src/BitmapButton.cpp | 359 ++++++--- src/BitmapButton.h | 20 +- src/BitmapText.h | 1 + src/Button.h | 2 +- src/Cel.cpp | 15 +- src/Cel.h | 8 + src/CelUtils.cpp | 16 +- src/CelUtils.h | 11 +- src/Circle.h | 1 + src/FadeInOut.cpp | 4 +- src/Game.cpp | 110 +-- src/Game.h | 86 +- src/Game/CelLevelObject.h | 3 +- src/Game/ImageLevelObject.h | 1 - src/Game/Item.cpp | 16 +- src/Game/Item.h | 2 + src/Game/ItemClass.cpp | 8 +- src/Game/ItemClass.h | 4 +- src/Game/ItemLocation.h | 2 +- src/Game/Level.cpp | 150 +++- src/Game/Level.h | 22 +- src/Game/Namer.cpp | 13 +- src/Game/Namer.h | 2 +- src/Game/Player.h | 2 +- src/Image.h | 1 + src/InputText.cpp | 30 +- src/InputText.h | 1 + src/Menu.cpp | 29 +- src/Menu.h | 1 + src/Movie2.cpp | 11 + src/Movie2.h | 5 +- src/MovieStub.cpp | 11 + src/MovieStub.h | 1 + src/Parser/Game/ParseItemClass.cpp | 51 +- src/Parser/Game/ParseLevel.cpp | 4 + src/Parser/ParseAction.cpp | 189 +++-- src/Parser/ParseButton.cpp | 3 +- src/Parser/ParseCelFile.cpp | 5 + src/Parser/ParsePredicate.cpp | 7 + src/Parser/ParseTexture.cpp | 24 +- src/Predicates/PredItem.h | 37 + src/Predicates/PredPlayer.h | 1 + src/Rectangle.h | 1 + src/ScrollableText.cpp | 11 + src/ScrollableText.h | 1 + src/StringButton.cpp | 360 ++++++--- src/StringButton.h | 28 +- src/StringText.h | 9 +- src/Text2.cpp | 36 +- src/Text2.h | 15 +- src/TextUtils.cpp | 89 +++ src/TextUtils.h | 39 + src/UIObject.h | 1 + src/UIText.h | 13 + src/Utils.h | 2 +- src/sfeMovie/AudioStream.cpp | 481 ++++++++++++ src/sfeMovie/AudioStream.hpp | 137 ++++ src/sfeMovie/Demuxer.cpp | 737 ++++++++++++++++++ src/sfeMovie/Demuxer.hpp | 331 ++++++++ src/sfeMovie/Macros.cpp | 37 + src/sfeMovie/Macros.hpp | 68 ++ src/sfeMovie/Movie.cpp | 168 ++++ src/sfeMovie/Movie.hpp | 225 ++++++ src/sfeMovie/MovieImpl.cpp | 443 +++++++++++ src/sfeMovie/MovieImpl.hpp | 161 ++++ src/sfeMovie/Stream.cpp | 309 ++++++++ src/sfeMovie/Stream.hpp | 194 +++++ src/sfeMovie/StreamSelection.cpp | 36 + src/sfeMovie/StreamSelection.hpp | 60 ++ src/sfeMovie/Timer.cpp | 220 ++++++ src/sfeMovie/Timer.hpp | 174 +++++ src/sfeMovie/TimerPriorities.cpp | 30 + src/sfeMovie/TimerPriorities.hpp | 33 + src/sfeMovie/Utilities.cpp | 39 + src/sfeMovie/Utilities.hpp | 57 ++ src/sfeMovie/VideoStream.cpp | 329 ++++++++ src/sfeMovie/VideoStream.hpp | 155 ++++ 167 files changed, 7500 insertions(+), 2647 deletions(-) create mode 100755 cmake_modules/FindFFmpeg.cmake create mode 100755 gamefilesd/res/level/actions/beltUse.json delete mode 100755 gamefilesd/res/level/actions/inventoryUse.json create mode 100755 gamefilesd/res/level/actions/resizeLevel.json create mode 100755 gamefilesd/res/level/actions/stashUse.json create mode 100755 gamefilesd/towners/common/loadMenuButtons.json create mode 100755 gamefilesd/towners/common/setItemInfo.json create mode 100755 gamefilesd/towners/common/setMenuItemText.json create mode 100755 gamefilesd/towners/common/setMenuPriceText.json create mode 100755 gamefilesd/towners/common/setPriceInfo.json create mode 100755 src/Actions/ActUIText.h create mode 100755 src/Predicates/PredItem.h create mode 100755 src/TextUtils.cpp create mode 100755 src/TextUtils.h create mode 100755 src/UIText.h create mode 100755 src/sfeMovie/AudioStream.cpp create mode 100755 src/sfeMovie/AudioStream.hpp create mode 100755 src/sfeMovie/Demuxer.cpp create mode 100755 src/sfeMovie/Demuxer.hpp create mode 100755 src/sfeMovie/Macros.cpp create mode 100755 src/sfeMovie/Macros.hpp create mode 100755 src/sfeMovie/Movie.cpp create mode 100755 src/sfeMovie/Movie.hpp create mode 100755 src/sfeMovie/MovieImpl.cpp create mode 100755 src/sfeMovie/MovieImpl.hpp create mode 100755 src/sfeMovie/Stream.cpp create mode 100755 src/sfeMovie/Stream.hpp create mode 100755 src/sfeMovie/StreamSelection.cpp create mode 100755 src/sfeMovie/StreamSelection.hpp create mode 100755 src/sfeMovie/Timer.cpp create mode 100755 src/sfeMovie/Timer.hpp create mode 100755 src/sfeMovie/TimerPriorities.cpp create mode 100755 src/sfeMovie/TimerPriorities.hpp create mode 100755 src/sfeMovie/Utilities.cpp create mode 100755 src/sfeMovie/Utilities.hpp create mode 100755 src/sfeMovie/VideoStream.cpp create mode 100755 src/sfeMovie/VideoStream.hpp diff --git a/.gitignore b/.gitignore index 2e433858..3066e642 100755 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ /DGEngine /DIABDAT /DIABDAT.* +/FFMPEG* +/FFmpeg* /gamefiles.zip /gamefilesd.zip /gamefilesd/res/icon.png diff --git a/BUILD.txt b/BUILD.txt index a26c9b17..6ed71197 100755 --- a/BUILD.txt +++ b/BUILD.txt @@ -1,7 +1,7 @@ Windows -To compile in Windows, you need Visual Studio 2015, since this project -uses C++ features that are present only in VS2015. +To compile in Windows, you need Visual Studio 2017, since this project +uses C++ features that are present only in VS2017. The provided project expects the following folders in the root of the project to build properly. @@ -11,8 +11,11 @@ project to build properly. /SFML - http://www.sfml-dev.org/files/SFML-2.3.2-windows-vc14-32-bit.zip -/sfeMovie - https://github.com/Yalir/sfeMovie/tree/feature/OpenFromStream - Use this branch of sfeMovie that has OpenFromStream. +/FFmpeg - http://ffmpeg.zeranoe.com/builds/ + Get both 32 bit shared and dev packages (version 2.x or 3.x) + +DGEngine now bundles a modified version of sfeMovie (no subtitle support, +FFmpeg 3.x API suport), so you only need FFmpeg >= 2.8 now. If you want to skip movie playback support, use the preprocessor define USE_SFML_MOVIE_STUB which uses a stub class that does nothing instead. @@ -22,15 +25,13 @@ sfeMovie with OpenFromStream support installed. Use this to fix that. To prevent missing DLL errors when running from Visual Studio, place the following in the root of the project: -avcodec-56.dll (sfeMovie) -avdevice-56.dll (sfeMovie) -avfilter-5.dll (sfeMovie) -avformat-56.dll (sfeMovie) -avutil-54.dll (sfeMovie) +avcodec-56.dll (FFmpeg 2) +avdevice-56.dll (FFmpeg 2) +avfilter-5.dll (FFmpeg 2) +avformat-56.dll (FFmpeg 2) +avutil-54.dll (FFmpeg 2) openal32.dll (SFML) physfs.dll (PhysicsFS) -sfeMovie-d.dll (sfeMovie) -sfeMovie.dll (sfeMovie) sfml-audio-2.dll (SFML) sfml-audio-d-2.dll (SFML) sfml-graphics-2.dll (SFML) @@ -41,8 +42,8 @@ sfml-system-2.dll (SFML) sfml-system-d-2.dll (SFML) sfml-window-2.dll (SFML) sfml-window-d-2.dll (SFML) -swresample-1.dll (sfeMovie) -swscale-3.dll (sfeMovie) +swresample-1.dll (FFmpeg 2) +swscale-3.dll (FFmpeg 2) Linux @@ -52,15 +53,20 @@ and to have both PhysicsFS and SFML installed. sudo apt-get install libphysfs-dev sudo apt-get install libsfml-dev -You also need to use the preprocessor define USE_SFML_MOVIE_STUB to skip -movie support. You can try to get it to use sfeMovie for Linux on your own. -The provided CMake project does this by default. +Optional (for movie support) FFmpeg: + +sudo apt-get install libavdevice-dev libavformat-dev libavfilter-dev libavcodec-dev libswscale-dev libavutil-dev + +Movie support is enabled by default (CMake), unless FFmpeg isn't found. +In CMake, set DGENGINE_MOVIE_SUPPORT to FALSE to skip movie support. CMake -A CMake project file is provided. It doesn't add video playback support. +A CMake project file is provided. Movie support is enabled by default. It will generate a project to compile on the target platform. cmake CMakeLists.txt +cmake CMakeLists.txt -DDGENGINE_MOVIE_SUPPORT:BOOL=FALSE Both PhysicsFS and SFML must be installed. +FFmpeg is also required for movie support. diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aba6e5f..08e3f179 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,27 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) project(DGEngine) -include_directories(./src) +if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + message(FATAL_ERROR "GCC 5.0+ is required") + endif() -add_definitions(-DUSE_SFML_MOVIE_STUB) + if(NOT BEOS) + add_definitions(-Wall) + endif() +endif() + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake_modules") + +option(DGENGINE_MOVIE_SUPPORT "Enable Movie support" TRUE) + +if(DGENGINE_MOVIE_SUPPORT) + find_package(FFmpeg COMPONENTS avcodec avformat avutil swscale) +endif() +find_package(PhysFS REQUIRED) +find_package(SFML 2.3 REQUIRED system window graphics network audio) + +include_directories(./src) set(SOURCE_FILES src/Main.cpp @@ -84,11 +102,14 @@ set(SOURCE_FILES src/StringText.h src/Text2.cpp src/Text2.h + src/TextUtils.cpp + src/TextUtils.h src/TileSet.cpp src/TileSet.h src/UIObject.h src/Utils.cpp src/Utils.h + src/UIText.h src/Variable.cpp src/Variable.h src/Variant.h @@ -123,6 +144,7 @@ set(SOURCE_FILES src/Actions/ActSound.h src/Actions/ActText.h src/Actions/ActTexture.h + src/Actions/ActUIText.h src/Actions/ActVariable.h src/Actions/ActVisibility.h src/Game/CelLevelObject.cpp @@ -261,6 +283,7 @@ set(SOURCE_FILES src/Parser/Utils/ParseUtilsVal.h src/Predicates/Predicate.h src/Predicates/PredIO.h + src/Predicates/PredItem.h src/Predicates/PredPlayer.h src/rapidjson/allocators.h src/rapidjson/document.h @@ -304,28 +327,47 @@ set(SOURCE_FILES src/variant/variant_visitor.hpp ) -add_executable(${PROJECT_NAME} ${SOURCE_FILES}) +if(FFmpeg_FOUND) + SET(SOURCE_FILES ${SOURCE_FILES} + src/sfeMovie/AudioStream.cpp + src/sfeMovie/AudioStream.hpp + src/sfeMovie/Demuxer.cpp + src/sfeMovie/Demuxer.hpp + src/sfeMovie/Macros.cpp + src/sfeMovie/Macros.hpp + src/sfeMovie/Movie.cpp + src/sfeMovie/Movie.hpp + src/sfeMovie/MovieImpl.cpp + src/sfeMovie/MovieImpl.hpp + src/sfeMovie/Stream.cpp + src/sfeMovie/Stream.hpp + src/sfeMovie/StreamSelection.cpp + src/sfeMovie/StreamSelection.hpp + src/sfeMovie/Timer.cpp + src/sfeMovie/Timer.hpp + src/sfeMovie/TimerPriorities.cpp + src/sfeMovie/TimerPriorities.hpp + src/sfeMovie/Utilities.cpp + src/sfeMovie/Utilities.hpp + src/sfeMovie/VideoStream.cpp + src/sfeMovie/VideoStream.hpp + ) +else() + add_definitions(-DUSE_SFML_MOVIE_STUB) +endif() -if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) - message(FATAL_ERROR "GCC 5.0+ is required") - endif() +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) - if(NOT BEOS) - add_definitions(-Wall) - endif() +if(FFmpeg_FOUND) + include_directories(${FFmpeg_INCLUDES}) + target_link_libraries(${PROJECT_NAME} ${FFmpeg_LIBRARIES}) endif() -# Detect and add PhysFS -find_package(PhysFS REQUIRED) if(PHYSFS_FOUND) - include_directories(${PHYSFS_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} ${PHYSFS_LIBRARY}) + include_directories(${PHYSFS_INCLUDE_DIRS}) + target_link_libraries(${PROJECT_NAME} ${PHYSFS_LIBRARY}) endif() -# Detect and add SFML -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) -find_package(SFML 2.3 REQUIRED system window graphics network audio) if(SFML_FOUND) include_directories(${SFML_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} ${SFML_LIBRARIES}) diff --git a/DGEngine.vcxproj b/DGEngine.vcxproj index b0c6daf7..9b8c39f2 100755 --- a/DGEngine.vcxproj +++ b/DGEngine.vcxproj @@ -116,11 +116,23 @@ + + + + + + + + + + + + @@ -155,6 +167,7 @@ + @@ -246,10 +259,22 @@ + + + + + + + + + + + + @@ -277,8 +302,10 @@ + + @@ -358,30 +385,30 @@ - Level4 Disabled true - .\src;.\SFML\include;.\PhysicsFS\src;.\sfeMovie\debug\include;%(AdditionalIncludeDirectories) + .\src;.\SFML\include;.\PhysicsFS\src;.\FFmpeg\include;%(AdditionalIncludeDirectories) + Level3 true - freetype.lib;jpeg.lib;openal32.lib;sfml-audio-d.lib;sfml-graphics-d.lib;sfml-main-d.lib;sfml-network-d.lib;sfml-system-d.lib;sfml-window-d.lib;physfs.lib;sfeMovie-d.lib;%(AdditionalDependencies) - .\SFML\lib;.\PhysicsFS\MinSizeRel;.\sfeMovie\debug\lib;%(AdditionalLibraryDirectories) + freetype.lib;jpeg.lib;openal32.lib;sfml-audio-d.lib;sfml-graphics-d.lib;sfml-main-d.lib;sfml-network-d.lib;sfml-system-d.lib;sfml-window-d.lib;physfs.lib;avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) + .\SFML\lib;.\PhysicsFS\MinSizeRel;.\FFmpeg\lib;%(AdditionalLibraryDirectories) Windows - Level4 + Level3 Disabled true - .\src;.\SFML\include;.\PhysicsFS\src;.\sfeMovie\debug\include;%(AdditionalIncludeDirectories) + .\src;.\SFML\include;.\PhysicsFS\src;.\FFmpeg\include;%(AdditionalIncludeDirectories) USE_SFML_MOVIE_STUB;%(PreprocessorDefinitions) true - freetype.lib;jpeg.lib;openal32.lib;sfml-audio-d.lib;sfml-graphics-d.lib;sfml-main-d.lib;sfml-network-d.lib;sfml-system-d.lib;sfml-window-d.lib;physfs.lib;%(AdditionalDependencies) - .\SFML\lib;.\PhysicsFS\MinSizeRel;.\sfeMovie\debug\lib;%(AdditionalLibraryDirectories) + freetype.lib;jpeg.lib;openal32.lib;sfml-audio-d.lib;sfml-graphics-d.lib;sfml-main-d.lib;sfml-network-d.lib;sfml-system-d.lib;sfml-window-d.lib;physfs.lib;avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) + .\SFML\lib;.\PhysicsFS\MinSizeRel;.\FFmpeg\lib;%(AdditionalLibraryDirectories) Windows @@ -390,72 +417,72 @@ EnableAllWarnings Disabled true - .\src;.\SFML\include;.\PhysicsFS\src;.\sfeMovie\debug\include;%(AdditionalIncludeDirectories) + .\src;.\SFML\include;.\PhysicsFS\src;.\FFmpeg\include;%(AdditionalIncludeDirectories) USE_SFML_MOVIE_STUB;%(PreprocessorDefinitions) true - freetype.lib;jpeg.lib;openal32.lib;sfml-audio-d.lib;sfml-graphics-d.lib;sfml-main-d.lib;sfml-network-d.lib;sfml-system-d.lib;sfml-window-d.lib;physfs.lib;%(AdditionalDependencies) - .\SFML\lib;.\PhysicsFS\MinSizeRel;.\sfeMovie\debug\lib;%(AdditionalLibraryDirectories) + freetype.lib;jpeg.lib;openal32.lib;sfml-audio-d.lib;sfml-graphics-d.lib;sfml-main-d.lib;sfml-network-d.lib;sfml-system-d.lib;sfml-window-d.lib;physfs.lib;avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) + .\SFML\lib;.\PhysicsFS\MinSizeRel;.\FFmpeg\lib;%(AdditionalLibraryDirectories) Windows - Level4 + Level3 Full true true true - .\src;.\SFML\include;.\PhysicsFS\src;.\sfeMovie\release\include;%(AdditionalIncludeDirectories) + .\src;.\SFML\include;.\PhysicsFS\src;.\FFmpeg\include;%(AdditionalIncludeDirectories) AnySuitable true true - freetype.lib;jpeg.lib;openal32.lib;sfml-audio.lib;sfml-graphics.lib;sfml-main.lib;sfml-network.lib;sfml-system.lib;sfml-window.lib;physfs.lib;sfeMovie.lib;%(AdditionalDependencies) - .\SFML\lib;.\PhysicsFS\MinSizeRel;.\sfeMovie\release\lib;%(AdditionalLibraryDirectories) + freetype.lib;jpeg.lib;openal32.lib;sfml-audio.lib;sfml-graphics.lib;sfml-main.lib;sfml-network.lib;sfml-system.lib;sfml-window.lib;physfs.lib;avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) + .\SFML\lib;.\PhysicsFS\MinSizeRel;.\FFmpeg\lib;%(AdditionalLibraryDirectories) Windows No - Level4 + Level3 Full true true true - .\src;.\SFML\include;.\PhysicsFS\src;.\sfeMovie\release\include;%(AdditionalIncludeDirectories) + .\src;.\SFML\include;.\PhysicsFS\src;.\FFmpeg\include;%(AdditionalIncludeDirectories) AnySuitable USE_SFML_MOVIE_STUB;%(PreprocessorDefinitions) true true - freetype.lib;jpeg.lib;openal32.lib;sfml-audio.lib;sfml-graphics.lib;sfml-main.lib;sfml-network.lib;sfml-system.lib;sfml-window.lib;physfs.lib;%(AdditionalDependencies) - .\SFML\lib;.\PhysicsFS\MinSizeRel;.\sfeMovie\release\lib;%(AdditionalLibraryDirectories) + freetype.lib;jpeg.lib;openal32.lib;sfml-audio.lib;sfml-graphics.lib;sfml-main.lib;sfml-network.lib;sfml-system.lib;sfml-window.lib;physfs.lib;avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) + .\SFML\lib;.\PhysicsFS\MinSizeRel;.\FFmpeg\lib;%(AdditionalLibraryDirectories) Windows No - Level4 + Level3 Full true true true - .\src;.\SFML\include;.\PhysicsFS\src;.\sfeMovie\release\include;%(AdditionalIncludeDirectories) - SFML_STATIC;SFEMOVIE_STATIC;%(PreprocessorDefinitions) + .\src;.\SFML\include;.\PhysicsFS\src;.\FFmpeg\include;%(AdditionalIncludeDirectories) + SFML_STATIC;%(PreprocessorDefinitions) AnySuitable No true true - flac.lib;freetype.lib;gdi32.lib;jpeg.lib;ogg.lib;openal32.lib;opengl32.lib;winmm.lib;ws2_32.lib;sfml-audio-s.lib;sfml-graphics-s.lib;sfml-main.lib;sfml-network-s.lib;sfml-system-s.lib;sfml-window-s.lib;vorbis.lib;vorbisenc.lib;vorbisfile.lib;physfs-s.lib;sfeMovie-s.lib;avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) - .\SFML\lib;.\PhysicsFS\MinSizeRel;.\sfeMovie\release\lib;%(AdditionalLibraryDirectories) + flac.lib;freetype.lib;gdi32.lib;jpeg.lib;ogg.lib;openal32.lib;opengl32.lib;winmm.lib;ws2_32.lib;sfml-audio-s.lib;sfml-graphics-s.lib;sfml-main.lib;sfml-network-s.lib;sfml-system-s.lib;sfml-window-s.lib;vorbis.lib;vorbisenc.lib;vorbisfile.lib;physfs-s.lib;avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) + .\SFML\lib;.\PhysicsFS\MinSizeRel;.\FFmpeg\lib;%(AdditionalLibraryDirectories) Windows diff --git a/License Notes.txt b/License Notes.txt index 5585d56d..e2ee9d79 100755 --- a/License Notes.txt +++ b/License Notes.txt @@ -54,6 +54,9 @@ Mapbox Variant uses the BSD license. https://github.com/mapbox/variant/ +FindFFmpeg.cmake was taken from Dolphin that uses the GPL v2 license. +https://github.com/dolphin-emu/dolphin + Small utility functions (string manipulation, etc) were taken from stackoverflow which use the cc by-sa 3.0 license with attribution required. I will update those functions in the future and point out the original authors in the next commits. diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index fa0017b7..16cdf9da 100755 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -5,7 +5,7 @@ android:versionName="1.0" > - : where to find .h +# FFmpeg_LIBRARY_: where to find the library +# FFmpeg_INCLUDES: aggregate all the include paths +# FFmpeg_LIBRARIES: aggregate all the paths to the libraries +# FFmpeg_FOUND: True if all components have been found +# +# This module defines the following targets, which are prefered over variables: +# +# FFmpeg::: Target to use directly, with include path, +# library and dependencies set up. If you are using a static build, you are +# responsible for adding any external dependencies (such as zlib, bzlib...). +# +# can be one of: +# avcodec +# avdevice +# avfilter +# avformat +# postproc +# swresample +# swscale +# + +set(_FFmpeg_ALL_COMPONENTS + avcodec + avdevice + avfilter + avformat + avutil + postproc + swresample + swscale +) + +set(_FFmpeg_DEPS_avcodec avutil) +set(_FFmpeg_DEPS_avdevice avcodec avformat avutil) +set(_FFmpeg_DEPS_avfilter avutil) +set(_FFmpeg_DEPS_avformat avcodec avutil) +set(_FFmpeg_DEPS_postproc avutil) +set(_FFmpeg_DEPS_swresample avutil) +set(_FFmpeg_DEPS_swscale avutil) + +function(find_ffmpeg LIBNAME) + if(DEFINED ENV{FFMPEG_DIR}) + set(FFMPEG_DIR $ENV{FFMPEG_DIR}) + endif() + + if(FFMPEG_DIR) + list(APPEND INCLUDE_PATHS + ${FFMPEG_DIR} + ${FFMPEG_DIR}/ffmpeg + ${FFMPEG_DIR}/lib${LIBNAME} + ${FFMPEG_DIR}/include/lib${LIBNAME} + ${FFMPEG_DIR}/include/ffmpeg + ${FFMPEG_DIR}/include + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + ) + list(APPEND LIB_PATHS + ${FFMPEG_DIR} + ${FFMPEG_DIR}/lib + ${FFMPEG_DIR}/lib${LIBNAME} + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + ) + else() + list(APPEND INCLUDE_PATHS + /usr/local/include/ffmpeg + /usr/local/include/lib${LIBNAME} + /usr/include/ffmpeg + /usr/include/lib${LIBNAME} + /usr/include/ffmpeg/lib${LIBNAME} + ) + + list(APPEND LIB_PATHS + /usr/local/lib + /usr/lib + ) + endif() + + find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h + HINTS ${INCLUDE_PATHS} + ) + + find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME} + HINTS ${LIB_PATHS} + ) + + if(NOT FFMPEG_DIR AND (NOT FFmpeg_LIBRARY_${LIBNAME} OR NOT FFmpeg_INCLUDE_${LIBNAME})) + # Didn't find it in the usual paths, try pkg-config + find_package(PkgConfig QUIET) + pkg_check_modules(FFmpeg_PKGCONFIG_${LIBNAME} QUIET lib${LIBNAME}) + + find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h + ${FFmpeg_PKGCONFIG_${LIBNAME}_INCLUDE_DIRS} + ) + + find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME} + ${FFmpeg_PKGCONFIG_${LIBNAME}_LIBRARY_DIRS} + ) + endif() + + if(FFmpeg_INCLUDE_${LIBNAME} AND FFmpeg_LIBRARY_${LIBNAME}) + set(FFmpeg_INCLUDE_${LIBNAME} "${FFmpeg_INCLUDE_${LIBNAME}}" PARENT_SCOPE) + set(FFmpeg_LIBRARY_${LIBNAME} "${FFmpeg_LIBRARY_${LIBNAME}}" PARENT_SCOPE) + set(FFmpeg_${c}_FOUND TRUE PARENT_SCOPE) + if(NOT FFmpeg_FIND_QUIETLY) + message("-- Found ${LIBNAME}: ${FFmpeg_INCLUDE_${LIBNAME}} ${FFmpeg_LIBRARY_${LIBNAME}}") + endif() + endif() +endfunction() + +foreach(c ${_FFmpeg_ALL_COMPONENTS}) + find_ffmpeg(${c}) +endforeach() + +foreach(c ${_FFmpeg_ALL_COMPONENTS}) + if(FFmpeg_${c}_FOUND) + list(APPEND FFmpeg_INCLUDES ${FFmpeg_INCLUDE_${c}}) + list(APPEND FFmpeg_LIBRARIES ${FFmpeg_LIBRARY_${c}}) + + add_library(FFmpeg::${c} IMPORTED UNKNOWN) + set_target_properties(FFmpeg::${c} PROPERTIES + IMPORTED_LOCATION ${FFmpeg_LIBRARY_${c}} + INTERFACE_INCLUDE_DIRECTORIES ${FFmpeg_INCLUDE_${c}} + ) + if(_FFmpeg_DEPS_${c}) + set(deps) + foreach(dep ${_FFmpeg_DEPS_${c}}) + list(APPEND deps FFmpeg::${dep}) + endforeach() + + set_target_properties(FFmpeg::${c} PROPERTIES + INTERFACE_LINK_LIBRARIES "${deps}" + ) + unset(deps) + endif() + endif() +endforeach() + +if(FFmpeg_INCLUDES) + list(REMOVE_DUPLICATES FFmpeg_INCLUDES) +endif() + +foreach(c ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS FFmpeg_INCLUDE_${c} FFmpeg_LIBRARY_${c}) +endforeach() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FFmpeg + REQUIRED_VARS ${_FFmpeg_REQUIRED_VARS} + HANDLE_COMPONENTS +) + +foreach(c ${_FFmpeg_ALL_COMPONENTS}) + unset(_FFmpeg_DEPS_${c}) +endforeach() +unset(_FFmpeg_ALL_COMPONENTS) +unset(_FFmpeg_REQUIRED_VARS) diff --git a/gamefilesd/level/afterLevelLoad.json b/gamefilesd/level/afterLevelLoad.json index 27bb8bbf..e2d09152 100755 --- a/gamefilesd/level/afterLevelLoad.json +++ b/gamefilesd/level/afterLevelLoad.json @@ -2,11 +2,21 @@ "action": [ { "name": "if.notEqual", - "param1": "|currentLevel|name|", - "param2": "Town Center", + "param1": "{1}", + "param2": "town", "then": { "name": "player.setRestStatus", "status": 1 }, "else": { "name": "player.setRestStatus", "status": 0 } }, - "updatePlayerTexture" - ] + "updateLifeManaOrbs", + "updatePlayerTexture", + { + "name": "if.equal", + "param1": "{2}", + "param2": "positionPlayer", + "then": { "name": "load", "file": ["level/positionPlayer.json", "{3}"] } + } + ], + "load": "ui/level/char/updateVisiblePanels.json", + "load": "level/playOrStopMusic.json", + "load": ["level/setMapAction.json", "{1}"] } \ No newline at end of file diff --git a/gamefilesd/level/item/amulets.json b/gamefilesd/level/item/amulets.json index cc69a49e..2d13d850 100755 --- a/gamefilesd/level/item/amulets.json +++ b/gamefilesd/level/item/amulets.json @@ -19,10 +19,12 @@ "formulas": { "sell": "price * 0.25" }, - "description1": "indestructible", - "description2": "magic1", - "description3": "magic2", - "description4": "noAttributes", + "descriptions": [ + { "index": 0, "name": "indestructible" }, + { "index": 1, "name": "magic" }, + { "index": 2, "name": "magic", "skip": 1 }, + { "index": 3, "name": "noAttributes" } + ], "inventorySize": [1, 1], "actions": { "action": "pickItemInLevel", diff --git a/gamefilesd/level/item/armors.json b/gamefilesd/level/item/armors.json index 7694a229..6a7ffc81 100755 --- a/gamefilesd/level/item/armors.json +++ b/gamefilesd/level/item/armors.json @@ -290,9 +290,11 @@ "celIndexInventory": {3}, "name": "Arkaine's Valor", "shortName": "Arkaine's Valor", - "description2": "unique", - "description3": null, - "description5": "arkanesValor", + "descriptions": [ + { "index": 1, "name": "unique" }, + { "index": 2, "name": null }, + { "index": 4, "name": "arkanesValor" } + ], "defaults": { "price": 42000, "armor": 25, diff --git a/gamefilesd/level/item/baseItems.json b/gamefilesd/level/item/baseItems.json index 8c425bbc..3dc6594c 100755 --- a/gamefilesd/level/item/baseItems.json +++ b/gamefilesd/level/item/baseItems.json @@ -16,10 +16,12 @@ "formulas": { "sell": "price * 0.25" }, - "description1": "armor", - "description2": "magic1", - "description3": "magic2", - "description4": "required", + "descriptions": [ + { "index": 0, "name": "armor" }, + { "index": 1, "name": "magic" }, + { "index": 2, "name": "magic", "skip": 1 }, + { "index": 3, "name": "required" } + ], "inventorySize": [2, 3], "actions": { "action": "pickItemInLevel", @@ -46,10 +48,12 @@ "formulas": { "sell": "price * 0.25" }, - "description1": "weapon", - "description2": "magic1", - "description3": "magic2", - "description4": "required", + "descriptions": [ + { "index": 0, "name": "weapon" }, + { "index": 1, "name": "magic" }, + { "index": 2, "name": "magic", "skip": 1 }, + { "index": 3, "name": "required" } + ], "inventorySize": [2, 3], "actions": { "action": "pickItemInLevel", diff --git a/gamefilesd/level/item/books.json b/gamefilesd/level/item/books.json index 7a4ff72c..24723890 100755 --- a/gamefilesd/level/item/books.json +++ b/gamefilesd/level/item/books.json @@ -16,7 +16,10 @@ "formulas": { "sell": "price * 0.25" }, - "description1": "rightClickToRead", + "descriptions": [ + { "index": 0, "name": "rightClickToRead" }, + { "index": 1, "name": "required" } + ], "inventorySize": [2, 2], "actions": { "action": "pickItemInLevel", diff --git a/gamefilesd/level/item/bows.json b/gamefilesd/level/item/bows.json index 364f811e..8ef6a7e7 100755 --- a/gamefilesd/level/item/bows.json +++ b/gamefilesd/level/item/bows.json @@ -18,7 +18,7 @@ "durability": 30, "durabilityMax": 30 }, - "description4": "requiredStrDex", + "descriptions": { "index": 3, "name": "requiredStrDex" }, "inventorySize": [2, 3], "actions": { "levelDrop": { "name": "sound.play", "id": "flipbow" }, diff --git a/gamefilesd/level/item/descriptions.json b/gamefilesd/level/item/descriptions.json index 6773d8af..0d83be58 100755 --- a/gamefilesd/level/item/descriptions.json +++ b/gamefilesd/level/item/descriptions.json @@ -15,83 +15,157 @@ "names": "Indestructible" }, { - "id": "magic1", + "id": "magic", "names": [ { "property": "identified", "value": { "min": false, "max": false, "text": "Not Identified" } }, { - "property": "strength", - "value": { "text": "+%strength% to Strength" } + "property": "identified", + "value": { "min": false, "max": false, "text": "" } }, { - "property": "magic", - "value": { "text": "+%magic% to Magic" } + "property": "magical", + "value": { "min": false, "max": false, "text": "" } }, { - "property": "dexterity", - "value": { "text": "+%dexterity% to Dexterity" } + "property": "resistAll", + "value": [ + { "min": "min", "max": -1, "text": "Resist All : -%resistAll%%" }, + { "min": 1, "max": "max", "text": "Resist All : +%resistAll%%" } + ] }, { - "property": "vitality", - "value": { "text": "+%vitality% to Vitality" } - } - ] - }, - { - "id": "magic2", - "names": [ + "property": "resistMagic", + "value": [ + { "min": "min", "max": -1, "text": "Resist Magic : -%resistMagic%%" }, + { "min": 1, "max": "max", "text": "Resist Magic : +%resistMagic%%" } + ] + }, { - "property": "magical", - "value": { "min": false, "max": false, "text": "" } + "property": "resistFire", + "value": [ + { "min": "min", "max": -1, "text": "Resist Fire : -%resistFire%%" }, + { "min": 1, "max": "max", "text": "Resist Fire : +%resistFire%%" } + ] }, { - "property": "identified", - "value": { "min": false, "max": false, "text": "" } + "property": "resistLightning", + "value": [ + { "min": "min", "max": -1, "text": "Resist Lightning : -%resistLightning%%" }, + { "min": 1, "max": "max", "text": "Resist Lightning : +%resistLightning%%" } + ] }, { "property": "toDamage", - "value": { "text": "+%toDamage% to Damage" } - } - ] - }, - { - "id": "noAttributes", - "names": "No Required Attributes" - }, - { - "id": "potion", - "names": [ + "value": [ + { "min": "min", "max": -1, "text": "-%toDamage%% to Damage" }, + { "min": 1, "max": "max", "text": "+%toDamage%% to Damage" } + ] + }, + { + "property": "toHit", + "value": [ + { "min": "min", "max": -1, "text": "Chance to Hit : -%toHit%%" }, + { "min": 1, "max": "max", "text": "Chance to Hit : +%toHit%%" } + ] + }, + { + "property": "toArmor", + "value": [ + { "min": "min", "max": -1, "text": "-%toArmor%% Armor" }, + { "min": 1, "max": "max", "text": "+%toArmor%% Armor" } + ] + }, { "property": "life", "value": [ - { "min": "min", "max": -1000, "text": "Kill Player" }, - { "min": -1000, "max": -1, "text": "Steal Life" }, - { "min": 1, "max": 1000, "text": "Recover Life" }, - { "min": 1000, "max": "max", "text": "Fully Recover Life" } + { "min": "min", "max": -1, "text": "Hit Points : -%life%" }, + { "min": 1, "max": "max", "text": "Hit Points : +%life%" } ] }, { "property": "mana", "value": [ - { "min": "min", "max": -1000, "text": "Remove All Mana" }, - { "min": -1000, "max": -1, "text": "Steal Mana" }, - { "min": 1, "max": 1000, "text": "Recover Mana" }, - { "min": 1000, "max": "max", "text": "Fully Recover Mana" } + { "min": "min", "max": -1, "text": "Mana : -%mana%" }, + { "min": 1, "max": "max", "text": "Mana : +%mana%" } + ] + }, + { + "property": "damage", + "value": [ + { "min": "min", "max": -2, "text": "Removes %damage% Points from Damage" }, + { "min": -1, "max": -1, "text": "Removes %damage% Point from Damage" }, + { "min": 1, "max": 1, "text": "Adds %damage% Point to Damage" }, + { "min": 2, "max": "max", "text": "Adds %damage% Points to Damage" } + ] + }, + { + "property": "allAttributes", + "value": [ + { "min": "min", "max": -1, "text": "-%allAttributes%% to All Attributes" }, + { "min": 1, "max": "max", "text": "+%allAttributes%% to All Attributes" } ] }, { - "property": "lifeAndMana", + "property": "strength", + "value": [ + { "min": "min", "max": -1, "text": "-%strength% to Strength" }, + { "min": 1, "max": "max", "text": "+%strength% to Strength" } + ] + }, + { + "property": "magic", + "value": [ + { "min": "min", "max": -1, "text": "-%magic% to Magic" }, + { "min": 1, "max": "max", "text": "+%magic% to Magic" } + ] + }, + { + "property": "dexterity", "value": [ - { "min": "min", "max": -1000, "text": "Remove All Life And Mana" }, - { "min": -1000, "max": -1, "text": "Steal Life And Mana" }, - { "min": 1, "max": 1000, "text": "Recover Life And Mana" }, - { "min": 1000, "max": "max", "text": "Fully Recover Life And Mana" } + { "min": "min", "max": -1, "text": "-%dexterity% to Dexterity" }, + { "min": 1, "max": "max", "text": "+%dexterity% to Dexterity" } + ] + }, + { + "property": "vitality", + "value": [ + { "min": "min", "max": -1, "text": "-%vitality% to Vitality" }, + { "min": 1, "max": "max", "text": "+%vitality% to Vitality" } ] } ] }, + { + "id": "noAttributes", + "names": "No Required Attributes" + }, + { + "id": "potionOfHealing", + "names": "Recover Life" + }, + { + "id": "potionOfMana", + "names": "Recover Mana" + }, + { + "id": "potionOfFullHealing", + "names": "Fully Recover Life" + }, + { + "id": "potionOfFullMana", + "names": "Fully Recover Mana" + }, + { + "id": "potionOfRejuvenation", + "names": "Recover Life And Mana" + }, + { + "id": "potionOfFullRejuvenation", + "names": "Fully Recover Life And Mana" + }, { "id": "required", "names": [ diff --git a/gamefilesd/level/item/potions.json b/gamefilesd/level/item/potions.json index 9f4dfa7b..c275d95f 100755 --- a/gamefilesd/level/item/potions.json +++ b/gamefilesd/level/item/potions.json @@ -21,8 +21,10 @@ "value": "$bonusLife * $life * 0.125", "valueMax": "$bonusLife * 3 * $life * 0.125" }, - "description1": "potion", - "description2": "rightClickToUse", + "descriptions": [ + { "index": 0, "name": "potionOfHealing" }, + { "index": 1, "name": "rightClickToUse" } + ], "inventorySize": [1, 1], "actions": { "action": "pickItemInLevel", @@ -49,7 +51,8 @@ "formulas": { "value": "$bonusMana * $mana * 0.125", "valueMax": "$bonusMana * 3 * $mana * 0.125" - } + }, + "descriptions": { "index": 0, "name": "potionOfMana" } }, { "id": "potionOfFullHealing", @@ -68,7 +71,8 @@ "formulas": { "value": 0, "valueMax": null - } + }, + "descriptions": { "index": 0, "name": "potionOfFullHealing" } }, { "id": "potionOfFullMana", @@ -83,7 +87,8 @@ "price": 150, "useOn": "manaDamage", "useOp": "=" - } + }, + "descriptions": { "index": 0, "name": "potionOfFullMana" } }, { "id": "potionOfRejuvenation", @@ -103,7 +108,8 @@ "valueMax": "$bonusLife * 3 * $life * 0.125", "value2": "$bonusMana * $mana * 0.125", "value2Max": "$bonusMana * 3 * $mana * 0.125" - } + }, + "descriptions": { "index": 0, "name": "potionOfRejuvenation" } }, { "id": "potionOfFullRejuvenation", @@ -120,7 +126,8 @@ }, "formulas": { "value2": 0 - } + }, + "descriptions": { "index": 0, "name": "potionOfFullRejuvenation" } }, { "id": "spectralElixir", @@ -139,8 +146,11 @@ "formulas": { "value": 3 }, - "description1": "", - "description2": "" + "descriptions": [ + { "index": 0, "name": "" }, + { "index": 1, "name": "" }, + { "index": 3, "name": "" } + ] } ] } \ No newline at end of file diff --git a/gamefilesd/level/item/prefixes.json b/gamefilesd/level/item/prefixes.json index f737c10c..9d3e9dbc 100755 --- a/gamefilesd/level/item/prefixes.json +++ b/gamefilesd/level/item/prefixes.json @@ -38,7 +38,7 @@ ] }, { - "property": "armor", + "property": "toArmor", "value": [ { "min": "min", "max": -51, "text": "Vulnerable" }, { "min": -50, "max": -25, "text": "Rusted" }, @@ -53,6 +53,46 @@ { "min": 151, "max": 170, "text": "Holy" }, { "min": 171, "max": "max", "text": "Godly" } ] + }, + { + "property": "resistAll", + "value": [ + { "min": 10, "max": 15, "text": "Topaz" }, + { "min": 16, "max": 20, "text": "Amber" }, + { "min": 21, "max": 30, "text": "Jade" }, + { "min": 31, "max": 40, "text": "Obsidian" }, + { "min": 41, "max": "max", "text": "Emerald" } + ] + }, + { + "property": "resistMagic", + "value": [ + { "min": 10, "max": 20, "text": "White" }, + { "min": 21, "max": 30, "text": "Pearl" }, + { "min": 31, "max": 40, "text": "Ivory" }, + { "min": 41, "max": 50, "text": "Crystal" }, + { "min": 51, "max": "max", "text": "Diamond" } + ] + }, + { + "property": "resistFire", + "value": [ + { "min": 10, "max": 20, "text": "Red" }, + { "min": 21, "max": 30, "text": "Crimson" }, + { "min": 31, "max": 40, "text": "Burgundy" }, + { "min": 41, "max": 50, "text": "Garnet" }, + { "min": 51, "max": "max", "text": "Ruby" } + ] + }, + { + "property": "resistLightning", + "value": [ + { "min": 10, "max": 20, "text": "Blue" }, + { "min": 21, "max": 30, "text": "Azure" }, + { "min": 31, "max": 40, "text": "Lapis" }, + { "min": 41, "max": 50, "text": "Cobalt" }, + { "min": 51, "max": "max", "text": "Sapphire" } + ] } ] }, @@ -82,22 +122,6 @@ }, { "property": "toHit", - "value": [ - { "min": "min", "max": -6, "text": "Clumsy" }, - { "min": -5, "max": -1, "text": "Dull" }, - { "min": 1, "max": 5, "text": "Sharp" }, - { "min": 6, "max": 10, "text": "Fine" }, - { "min": 11, "max": 15, "text": "Warrior's" }, - { "min": 16, "max": 20, "text": "Soldier's" }, - { "min": 21, "max": 30, "text": "Lord's" }, - { "min": 31, "max": 40, "text": "Knight's" }, - { "min": 41, "max": 50, "text": "Master's" }, - { "min": 51, "max": 75, "text": "Champion's" }, - { "min": 76, "max": "max", "text": "King's" } - ] - }, - { - "property": "chanceToHit", "value": [ { "min": "min", "max": -6, "text": "Tin" }, { "min": -5, "max": -1, "text": "Brass" }, @@ -113,6 +137,22 @@ { "min": 101, "max": "max", "text": "Strange" } ] }, + { + "property": "toHit", + "value": [ + { "min": "min", "max": -6, "text": "Clumsy" }, + { "min": -5, "max": -1, "text": "Dull" }, + { "min": 1, "max": 5, "text": "Sharp" }, + { "min": 6, "max": 10, "text": "Fine" }, + { "min": 11, "max": 15, "text": "Warrior's" }, + { "min": 16, "max": 20, "text": "Soldier's" }, + { "min": 21, "max": 30, "text": "Lord's" }, + { "min": 31, "max": 40, "text": "Knight's" }, + { "min": 41, "max": 50, "text": "Master's" }, + { "min": 51, "max": 75, "text": "Champion's" }, + { "min": 76, "max": "max", "text": "King's" } + ] + }, { "property": "resistAll", "value": [ @@ -189,7 +229,7 @@ "id": "jewelleryPrefixes", "names": [ { - "property": "chanceToHit", + "property": "toHit", "value": [ { "min": "min", "max": -6, "text": "Tin" }, { "min": -5, "max": -1, "text": "Brass" }, diff --git a/gamefilesd/level/item/scrolls.json b/gamefilesd/level/item/scrolls.json index b92b6188..c6c15ace 100755 --- a/gamefilesd/level/item/scrolls.json +++ b/gamefilesd/level/item/scrolls.json @@ -16,8 +16,10 @@ "formulas": { "sell": "price * 0.25" }, - "description1": "rightClickToRead2", - "description2": "required", + "descriptions": [ + { "index": 0, "name": "rightClickToRead2" }, + { "index": 1, "name": "required" } + ], "inventorySize": [1, 1], "actions": { "action": "pickItemInLevel", diff --git a/gamefilesd/level/item/staffs.json b/gamefilesd/level/item/staffs.json index 16e92699..19c8f8e1 100755 --- a/gamefilesd/level/item/staffs.json +++ b/gamefilesd/level/item/staffs.json @@ -19,7 +19,7 @@ "durability": 25, "durabilityMax": 25 }, - "description4": "requiredStrMag", + "descriptions": { "index": 3, "name": "requiredStrMag" }, "inventorySize": [2, 3], "actions": { "levelDrop": { "name": "sound.play", "id": "flipstaf" }, diff --git a/gamefilesd/level/item/suffixes.json b/gamefilesd/level/item/suffixes.json index 9aacfb2e..07b3035a 100755 --- a/gamefilesd/level/item/suffixes.json +++ b/gamefilesd/level/item/suffixes.json @@ -4,7 +4,67 @@ "id": "armorSuffixes", "names": [ { - "property": "hitPoints", + "property": "allAttributes", + "value": [ + { "min": "min", "max": -6, "text": "Of Trouble" }, + { "min": -5, "max": -1, "text": "Of The Pit" }, + { "min": 1, "max": 3, "text": "Of The Sky" }, + { "min": 4, "max": 7, "text": "Of The Moon" }, + { "min": 8, "max": 11, "text": "Of The Stars" }, + { "min": 12, "max": 15, "text": "Of The Heavens" }, + { "min": 16, "max": "max", "text": "Of The Zodiac" } + ] + }, + { + "property": "strength", + "value": [ + { "min": "min", "max": -6, "text": "Of Frailty" }, + { "min": -5, "max": -1, "text": "Of Weakness" }, + { "min": 1, "max": 5, "text": "Of Strength" }, + { "min": 6, "max": 10, "text": "Of Might" }, + { "min": 11, "max": 15, "text": "Of Power" }, + { "min": 16, "max": 20, "text": "Of Giants" }, + { "min": 21, "max": "max", "text": "Of Titans" } + ] + }, + { + "property": "magic", + "value": [ + { "min": "min", "max": -6, "text": "Of The Fool" }, + { "min": -5, "max": -1, "text": "Of Dyslexia" }, + { "min": 1, "max": 5, "text": "Of Magic" }, + { "min": 6, "max": 10, "text": "Of The Mind" }, + { "min": 11, "max": 15, "text": "Of Brilliance" }, + { "min": 16, "max": 20, "text": "Of Sorcery" }, + { "min": 21, "max": "max", "text": "Of Wizardry" } + ] + }, + { + "property": "dexterity", + "value": [ + { "min": "min", "max": -6, "text": "Of Paralysis" }, + { "min": -5, "max": -1, "text": "Of Atrophy" }, + { "min": -1, "max": 5, "text": "Of Dexterity" }, + { "min": 6, "max": 10, "text": "Of Skill" }, + { "min": 11, "max": 15, "text": "Of Accuracy" }, + { "min": 16, "max": 20, "text": "Of Precision" }, + { "min": 21, "max": "max", "text": "Of Perfection" } + ] + }, + { + "property": "vitality", + "value": [ + { "min": "min", "max": -6, "text": "Of Illness" }, + { "min": -5, "max": -1, "text": "Of Disease" }, + { "min": -1, "max": 5, "text": "Of Vitality" }, + { "min": 6, "max": 10, "text": "Of Zest" }, + { "min": 11, "max": 15, "text": "Of Vim" }, + { "min": 16, "max": 20, "text": "Of Vigor" }, + { "min": 21, "max": "max", "text": "Of Life" } + ] + }, + { + "property": "life", "value": [ { "min": "min", "max": -11, "text": "Of The Vulture" }, { "min": -10, "max": -1, "text": "Of The Jackal" }, @@ -140,6 +200,17 @@ { "min": 5, "max": 8, "text": "Of Puncturing" }, { "min": 9, "max": "max", "text": "Of Bashing" } ] + }, + { + "property": "damage", + "value": [ + { "min": 1, "max": 2, "text": "Of Quality" }, + { "min": 3, "max": 5, "text": "Of Maiming" }, + { "min": 6, "max": 8, "text": "Of Slaying" }, + { "min": 9, "max": 12, "text": "Of Gore" }, + { "min": 13, "max": 16, "text": "Of Carnage" }, + { "min": 17, "max": "max", "text": "Of Slaughter" } + ] } ] }, @@ -207,7 +278,7 @@ ] }, { - "property": "hitPoints", + "property": "life", "value": [ { "min": "min", "max": -11, "text": "Of The Vulture" }, { "min": -10, "max": -1, "text": "Of The Jackal" }, diff --git a/gamefilesd/level/item/swords.json b/gamefilesd/level/item/swords.json index ad9078d7..fe92d76f 100755 --- a/gamefilesd/level/item/swords.json +++ b/gamefilesd/level/item/swords.json @@ -18,7 +18,7 @@ "durability": 16, "durabilityMax": 16 }, - "description4": "requiredStrDex", + "descriptions": { "index": 3, "name": "requiredStrDex" }, "inventorySize": [1, 2] }, { diff --git a/gamefilesd/level/load.json b/gamefilesd/level/load.json index bb2c14dd..a90ccf01 100755 --- a/gamefilesd/level/load.json +++ b/gamefilesd/level/load.json @@ -18,17 +18,9 @@ { "name": "load", "file": "level/{2}/sounds.json" }, { "name": "loadingScreen.setProgress", "progress": 80 }, { "name": "load", "file": "level/{2}/players.json" }, - { "name": "loadingScreen.setProgress", "progress": 100 }, { "name": "load", "file": "level/{2}/music.json" }, - { "name": "if.equal", - "param1": "{3}", - "param2": "positionPlayer", - "then": { "name": "load", "file": ["level/positionPlayer.json", "{4}"] } - }, - { "name": "load", "file": "level/afterLevelLoad.json" }, - { "name": "load", "file": "ui/level/char/updateVisiblePanels.json" }, - { "name": "load", "file": "level/playOrStopMusic.json" }, - { "name": "load", "file": ["level/setMapAction.json", "{2}"] } + { "name": "load", "file": ["level/afterLevelLoad.json", "{2}", "{3}", "{4}"] }, + { "name": "loadingScreen.setProgress", "progress": 100 } ] } ] diff --git a/gamefilesd/level/loadBase.json b/gamefilesd/level/loadBase.json index 760e4360..cd51b5bf 100755 --- a/gamefilesd/level/loadBase.json +++ b/gamefilesd/level/loadBase.json @@ -38,12 +38,9 @@ { "name": "load", "file": "level/{2}/sounds.json" }, { "name": "loadingScreen.setProgress", "progress": 95 }, { "name": "load", "file": "level/{2}/players.json" }, - { "name": "loadingScreen.setProgress", "progress": 100 }, - { "name": "load", "file": "level/afterLevelLoad.json" }, - { "name": "load", "file": "ui/level/char/updateVisiblePanels.json" }, { "name": "load", "file": "level/{2}/music.json" }, - { "name": "load", "file": "level/playOrStopMusic.json" }, - { "name": "load", "file": ["level/setMapAction.json", "{2}"] } + { "name": "load", "file": ["level/afterLevelLoad.json", "{2}"] }, + { "name": "loadingScreen.setProgress", "progress": 100 } ] } ] diff --git a/gamefilesd/level/newGame.json b/gamefilesd/level/newGame.json index 674e3caa..6d9c36c7 100755 --- a/gamefilesd/level/newGame.json +++ b/gamefilesd/level/newGame.json @@ -40,12 +40,9 @@ { "name": "load", "file": "level/town/sounds.json" }, { "name": "loadingScreen.setProgress", "progress": 95 }, { "name": "load", "file": "level/town/players.json" }, - { "name": "loadingScreen.setProgress", "progress": 100 }, - { "name": "load", "file": "level/afterLevelLoad.json" }, - { "name": "load", "file": "ui/level/char/updateVisiblePanels.json" }, { "name": "load", "file": "level/town/music.json" }, - { "name": "load", "file": "level/playOrStopMusic.json" }, - { "name": "load", "file": ["level/setMapAction.json", "town"] } + { "name": "load", "file": ["level/afterLevelLoad.json", "town"] }, + { "name": "loadingScreen.setProgress", "progress": 100 } ] } ] diff --git a/gamefilesd/level/town/players2.json b/gamefilesd/level/town/players2.json index c407a30f..814a17b4 100755 --- a/gamefilesd/level/town/players2.json +++ b/gamefilesd/level/town/players2.json @@ -253,7 +253,7 @@ "index": 4, "class": "cape", "properties": { - "hitPoints": 12, + "life": 12, "price": 850, "magical": true } diff --git a/gamefilesd/level/town/sounds.json b/gamefilesd/level/town/sounds.json index 10783cf2..a71b78be 100755 --- a/gamefilesd/level/town/sounds.json +++ b/gamefilesd/level/town/sounds.json @@ -7,6 +7,7 @@ { "id": "pegboyWelcome", "file": "sfx/Towners/Pegboy32.wav" }, { "id": "storytWelcome", "file": "sfx/Towners/storyt25.wav" }, { "id": "tavownWelcome", "file": "sfx/Towners/tavown36.wav" }, + { "id": "witchWelcome", "file": "sfx/Towners/Witch38.wav" }, { "id": "cow1", "file": "sfx/Towners/Cow1.wav" }, { "id": "cow2", "file": "sfx/Towners/Cow2.wav" } ], diff --git a/gamefilesd/res/level/actions.json b/gamefilesd/res/level/actions.json index 08ccf19d..2227eda8 100755 --- a/gamefilesd/res/level/actions.json +++ b/gamefilesd/res/level/actions.json @@ -1,5 +1,6 @@ { "load": "res/level/actions/sounds.json", + "load": "res/level/actions/resizeLevel.json", "load": "res/level/actions/itemPanel.json", "load": "res/level/actions/basePanel.json", "load": "res/level/actions/charPanel.json", diff --git a/gamefilesd/res/level/actions/basePanel.json b/gamefilesd/res/level/actions/basePanel.json index 2ffbc85a..dcc31894 100755 --- a/gamefilesd/res/level/actions/basePanel.json +++ b/gamefilesd/res/level/actions/basePanel.json @@ -23,6 +23,11 @@ "id": "setPanelTextColorWhite", "action": { "name": "text.setColor", "id": "txtPanel" } }, + { + "name": "action.set", + "id": "rightClickLevel", + "action": { "name": "drawable.executeAction", "id": "level", "action": "rightClick" } + }, { "name": "action.set", "id": "clearPanelText", diff --git a/gamefilesd/res/level/actions/beltUse.json b/gamefilesd/res/level/actions/beltUse.json new file mode 100755 index 00000000..c9f25fb7 --- /dev/null +++ b/gamefilesd/res/level/actions/beltUse.json @@ -0,0 +1,16 @@ +{ + "action": { + "name": "action.set", + "id": "use{1}Item", + "action": { + "name": "if.equal", + "param1": "|currentLevel|currentPlayer.item.belt.{2}.isUsable|", + "param2": true, + "then": [ + { "name": "item.use", "inventory": "belt", "item": {2} }, + "updateAllPlayerStats" + ], + "else": "rightClickLevel" + } + } +} \ No newline at end of file diff --git a/gamefilesd/res/level/actions/inventoryText.json b/gamefilesd/res/level/actions/inventoryText.json index e5ade7d1..12de4e45 100755 --- a/gamefilesd/res/level/actions/inventoryText.json +++ b/gamefilesd/res/level/actions/inventoryText.json @@ -16,14 +16,14 @@ "param": "|currentLevel|currentPlayer.item.{2}.{3}.itemType|", "list": ["Amulet", "Ring"], "then": { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "txtPanel", "query": "currentLevel.currentPlayer.item.{2}.{3}", "text": "%name%\n%d.1%\n%d.2%", "removeEmptyLines": true }, "else": { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "txtPanel", "query": "currentLevel.currentPlayer.item.{2}.{3}", "text": "%name%\n%d.0%\n%d.1%\n%d.2%\n%d.3%", @@ -53,13 +53,13 @@ "then": [ "showItemInfo", { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfoTitle", "query": "currentLevel.currentPlayer.item.{2}.{3}", "text": "%name%" }, { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfoDescr", "query": "currentLevel.currentPlayer.item.{2}.{3}", "text": "%d.4%" diff --git a/gamefilesd/res/level/actions/inventoryUse.json b/gamefilesd/res/level/actions/inventoryUse.json deleted file mode 100755 index f6c6881b..00000000 --- a/gamefilesd/res/level/actions/inventoryUse.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "action": { - "name": "action.set", - "id": "use{1}Item", - "action": [ - { "name": "item.use", "inventory": "{2}", "item": {3} }, - "updateAllPlayerStats" - ] - } -} \ No newline at end of file diff --git a/gamefilesd/res/level/actions/playerBeltUse.json b/gamefilesd/res/level/actions/playerBeltUse.json index 1d2ac1cb..e61f702e 100755 --- a/gamefilesd/res/level/actions/playerBeltUse.json +++ b/gamefilesd/res/level/actions/playerBeltUse.json @@ -1,10 +1,10 @@ { - "load": ["res/level/actions/inventoryUse.json", "Belt1", "belt", 0], - "load": ["res/level/actions/inventoryUse.json", "Belt2", "belt", 1], - "load": ["res/level/actions/inventoryUse.json", "Belt3", "belt", 2], - "load": ["res/level/actions/inventoryUse.json", "Belt4", "belt", 3], - "load": ["res/level/actions/inventoryUse.json", "Belt5", "belt", 4], - "load": ["res/level/actions/inventoryUse.json", "Belt6", "belt", 5], - "load": ["res/level/actions/inventoryUse.json", "Belt7", "belt", 6], - "load": ["res/level/actions/inventoryUse.json", "Belt8", "belt", 7] + "load": ["res/level/actions/beltUse.json", "Belt1", 0], + "load": ["res/level/actions/beltUse.json", "Belt2", 1], + "load": ["res/level/actions/beltUse.json", "Belt3", 2], + "load": ["res/level/actions/beltUse.json", "Belt4", 3], + "load": ["res/level/actions/beltUse.json", "Belt5", 4], + "load": ["res/level/actions/beltUse.json", "Belt6", 5], + "load": ["res/level/actions/beltUse.json", "Belt7", 6], + "load": ["res/level/actions/beltUse.json", "Belt8", 7] } \ No newline at end of file diff --git a/gamefilesd/res/level/actions/playerBodyHands2.json b/gamefilesd/res/level/actions/playerBodyHands2.json index 196108aa..38e65ad0 100755 --- a/gamefilesd/res/level/actions/playerBodyHands2.json +++ b/gamefilesd/res/level/actions/playerBodyHands2.json @@ -13,14 +13,14 @@ "param": "|currentLevel|currentPlayer.item.body.LeftHand.itemType|", "list": ["Amulet", "Ring"], "then": { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "txtPanel", "query": "currentLevel.currentPlayer.item.body.LeftHand", "text": "%name%\n%d.1%\n%d.2%", "removeEmptyLines": true }, "else": { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "txtPanel", "query": "currentLevel.currentPlayer.item.body.LeftHand", "text": "%name%\n%d.0%\n%d.1%\n%d.2%\n%d.3%", @@ -50,13 +50,13 @@ "then": [ "showItemInfo", { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfoTitle", "query": "currentLevel.currentPlayer.item.body.LeftHand", "text": "%name%" }, { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfoDescr", "query": "currentLevel.currentPlayer.item.body.LeftHand", "text": "%d.4%" @@ -84,14 +84,14 @@ "param": "|currentLevel|currentPlayer.item.body.RightHand.itemType|", "list": ["Amulet", "Ring"], "then": { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "txtPanel", "query": "currentLevel.currentPlayer.item.body.RightHand", "text": "%name%\n%d.1%\n%d.2%", "removeEmptyLines": true }, "else": { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "txtPanel", "query": "currentLevel.currentPlayer.item.body.RightHand", "text": "%name%\n%d.0%\n%d.1%\n%d.2%\n%d.3%", @@ -121,13 +121,13 @@ "then": [ "showItemInfo", { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfoTitle", "query": "currentLevel.currentPlayer.item.body.RightHand", "text": "%name%" }, { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfoDescr", "query": "currentLevel.currentPlayer.item.body.RightHand", "text": "%d.4%" diff --git a/gamefilesd/res/level/actions/playerStashUse.json b/gamefilesd/res/level/actions/playerStashUse.json index dc9fa278..c1200184 100755 --- a/gamefilesd/res/level/actions/playerStashUse.json +++ b/gamefilesd/res/level/actions/playerStashUse.json @@ -1,42 +1,42 @@ { - "load": ["res/level/actions/inventoryUse.json", "Stash11", "stash", "[0,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash12", "stash", "[0,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash13", "stash", "[0,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash14", "stash", "[0,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash21", "stash", "[1,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash22", "stash", "[1,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash23", "stash", "[1,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash24", "stash", "[1,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash31", "stash", "[2,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash32", "stash", "[2,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash33", "stash", "[2,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash34", "stash", "[2,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash41", "stash", "[3,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash42", "stash", "[3,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash43", "stash", "[3,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash44", "stash", "[3,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash51", "stash", "[4,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash52", "stash", "[4,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash53", "stash", "[4,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash54", "stash", "[4,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash61", "stash", "[5,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash62", "stash", "[5,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash63", "stash", "[5,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash64", "stash", "[5,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash71", "stash", "[6,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash72", "stash", "[6,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash73", "stash", "[6,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash74", "stash", "[6,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash81", "stash", "[7,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash82", "stash", "[7,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash83", "stash", "[7,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash84", "stash", "[7,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash91", "stash", "[8,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash92", "stash", "[8,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash93", "stash", "[8,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash94", "stash", "[8,3]"], - "load": ["res/level/actions/inventoryUse.json", "Stash101", "stash", "[9,0]"], - "load": ["res/level/actions/inventoryUse.json", "Stash102", "stash", "[9,1]"], - "load": ["res/level/actions/inventoryUse.json", "Stash103", "stash", "[9,2]"], - "load": ["res/level/actions/inventoryUse.json", "Stash104", "stash", "[9,3]"] + "load": ["res/level/actions/stashUse.json", "Stash11", "0,0"], + "load": ["res/level/actions/stashUse.json", "Stash12", "0,1"], + "load": ["res/level/actions/stashUse.json", "Stash13", "0,2"], + "load": ["res/level/actions/stashUse.json", "Stash14", "0,3"], + "load": ["res/level/actions/stashUse.json", "Stash21", "1,0"], + "load": ["res/level/actions/stashUse.json", "Stash22", "1,1"], + "load": ["res/level/actions/stashUse.json", "Stash23", "1,2"], + "load": ["res/level/actions/stashUse.json", "Stash24", "1,3"], + "load": ["res/level/actions/stashUse.json", "Stash31", "2,0"], + "load": ["res/level/actions/stashUse.json", "Stash32", "2,1"], + "load": ["res/level/actions/stashUse.json", "Stash33", "2,2"], + "load": ["res/level/actions/stashUse.json", "Stash34", "2,3"], + "load": ["res/level/actions/stashUse.json", "Stash41", "3,0"], + "load": ["res/level/actions/stashUse.json", "Stash42", "3,1"], + "load": ["res/level/actions/stashUse.json", "Stash43", "3,2"], + "load": ["res/level/actions/stashUse.json", "Stash44", "3,3"], + "load": ["res/level/actions/stashUse.json", "Stash51", "4,0"], + "load": ["res/level/actions/stashUse.json", "Stash52", "4,1"], + "load": ["res/level/actions/stashUse.json", "Stash53", "4,2"], + "load": ["res/level/actions/stashUse.json", "Stash54", "4,3"], + "load": ["res/level/actions/stashUse.json", "Stash61", "5,0"], + "load": ["res/level/actions/stashUse.json", "Stash62", "5,1"], + "load": ["res/level/actions/stashUse.json", "Stash63", "5,2"], + "load": ["res/level/actions/stashUse.json", "Stash64", "5,3"], + "load": ["res/level/actions/stashUse.json", "Stash71", "6,0"], + "load": ["res/level/actions/stashUse.json", "Stash72", "6,1"], + "load": ["res/level/actions/stashUse.json", "Stash73", "6,2"], + "load": ["res/level/actions/stashUse.json", "Stash74", "6,3"], + "load": ["res/level/actions/stashUse.json", "Stash81", "7,0"], + "load": ["res/level/actions/stashUse.json", "Stash82", "7,1"], + "load": ["res/level/actions/stashUse.json", "Stash83", "7,2"], + "load": ["res/level/actions/stashUse.json", "Stash84", "7,3"], + "load": ["res/level/actions/stashUse.json", "Stash91", "8,0"], + "load": ["res/level/actions/stashUse.json", "Stash92", "8,1"], + "load": ["res/level/actions/stashUse.json", "Stash93", "8,2"], + "load": ["res/level/actions/stashUse.json", "Stash94", "8,3"], + "load": ["res/level/actions/stashUse.json", "Stash101", "9,0"], + "load": ["res/level/actions/stashUse.json", "Stash102", "9,1"], + "load": ["res/level/actions/stashUse.json", "Stash103", "9,2"], + "load": ["res/level/actions/stashUse.json", "Stash104", "9,3"] } \ No newline at end of file diff --git a/gamefilesd/res/level/actions/playerUpdate.json b/gamefilesd/res/level/actions/playerUpdate.json index 5164d0b8..6b1bc7dc 100755 --- a/gamefilesd/res/level/actions/playerUpdate.json +++ b/gamefilesd/res/level/actions/playerUpdate.json @@ -5,32 +5,78 @@ "id": "updateLifeManaOrbs", "action": [ { - "name": "drawable.resizeY", - "id": "lifeOrbEmpty", - "size": "|currentLevel|currentPlayer.lifeDamage|", - "inputRangeMax": "|currentLevel|currentPlayer.life|", - "range": [0, 88] + "name": "<=", + "param1": "|currentLevel|currentPlayer.life|", + "param2": 0, + "then": [ + { + "name": "drawable.resizeY", + "id": "lifeOrbEmpty", + "size": 1, + "inputRangeMax": 1, + "range": [0, 88] + }, + { + "name": "image.inverseResizeY", + "id": "lifeOrbFull", + "size": 1, + "inputRangeMax": 1, + "range": [0, 88] + } + ], + "else": [ + { + "name": "drawable.resizeY", + "id": "lifeOrbEmpty", + "size": "|currentLevel|currentPlayer.lifeDamage|", + "inputRangeMax": "|currentLevel|currentPlayer.life|", + "range": [0, 88] + }, + { + "name": "image.inverseResizeY", + "id": "lifeOrbFull", + "size": "|currentLevel|currentPlayer.lifeDamage|", + "inputRangeMax": "|currentLevel|currentPlayer.life|", + "range": [0, 88] + } + ] }, { - "name": "image.inverseResizeY", - "id": "lifeOrbFull", - "size": "|currentLevel|currentPlayer.lifeDamage|", - "inputRangeMax": "|currentLevel|currentPlayer.life|", - "range": [0, 88] - }, - { - "name": "drawable.resizeY", - "id": "manaOrbEmpty", - "size": "|currentLevel|currentPlayer.manaDamage|", - "inputRangeMax": "|currentLevel|currentPlayer.mana|", - "range": [0, 88] - }, - { - "name": "image.inverseResizeY", - "id": "manaOrbFull", - "size": "|currentLevel|currentPlayer.manaDamage|", - "inputRangeMax": "|currentLevel|currentPlayer.mana|", - "range": [0, 88] + "name": "<=", + "param1": "|currentLevel|currentPlayer.mana|", + "param2": 0, + "then": [ + { + "name": "drawable.resizeY", + "id": "manaOrbEmpty", + "size": 1, + "inputRangeMax": 1, + "range": [0, 88] + }, + { + "name": "image.inverseResizeY", + "id": "manaOrbFull", + "size": 1, + "inputRangeMax": 1, + "range": [0, 88] + } + ], + "else": [ + { + "name": "drawable.resizeY", + "id": "manaOrbEmpty", + "size": "|currentLevel|currentPlayer.manaDamage|", + "inputRangeMax": "|currentLevel|currentPlayer.mana|", + "range": [0, 88] + }, + { + "name": "image.inverseResizeY", + "id": "manaOrbFull", + "size": "|currentLevel|currentPlayer.manaDamage|", + "inputRangeMax": "|currentLevel|currentPlayer.mana|", + "range": [0, 88] + } + ] } ] }, diff --git a/gamefilesd/res/level/actions/resizeLevel.json b/gamefilesd/res/level/actions/resizeLevel.json new file mode 100755 index 00000000..1212b788 --- /dev/null +++ b/gamefilesd/res/level/actions/resizeLevel.json @@ -0,0 +1,49 @@ +{ + "action": { + "name": "action.set", + "id": "resizeLevel", + "action": { + "name": "if.equal", + "param1": "|game|stretchToFit|", + "param2": true, + "then": { + "name": "if.equal", + "param1": "|game|minSize.x|", + "param2": 640, + "then": { + "name": "if.equal", + "param1": "|game|minSize.y|", + "param2": 480, + "then": { + "name": "if.inList", + "param": true, + "list": ["|charPanel|visible|", "|questPanel|visible|"], + "then": { + "name": "if.inList", + "param": true, + "list": ["|invPanel|visible|", "|spellPanel|visible|"], + "then": { "name": "drawable.setSizeX", "id": "level", "size": 0 }, + "else": [ + { "name": "drawable.setPositionX", "id": "level", "position": 320 }, + { "name": "drawable.setSizeX", "id": "level", "size": 320 } + ] + }, + "else": { + "name": "if.inList", + "param": true, + "list": ["|invPanel|visible|", "|spellPanel|visible|"], + "then": [ + { "name": "drawable.setPositionX", "id": "level", "position": 0 }, + { "name": "drawable.setSizeX", "id": "level", "size": 320 } + ], + "else": [ + { "name": "drawable.setPositionX", "id": "level", "position": 0 }, + { "name": "drawable.setSizeX", "id": "level", "size": 640 } + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/gamefilesd/res/level/actions/stashUse.json b/gamefilesd/res/level/actions/stashUse.json new file mode 100755 index 00000000..afb118d3 --- /dev/null +++ b/gamefilesd/res/level/actions/stashUse.json @@ -0,0 +1,16 @@ +{ + "action": { + "name": "action.set", + "id": "use{1}Item", + "action": { + "name": "if.equal", + "param1": "|currentLevel|currentPlayer.item.stash.{2}.isUsable|", + "param2": true, + "then": [ + { "name": "item.use", "inventory": "stash", "item": [{2}] }, + "updateAllPlayerStats" + ], + "else": "rightClickLevel" + } + } +} \ No newline at end of file diff --git a/gamefilesd/res/level/fonts.json b/gamefilesd/res/level/fonts.json index fb7f131c..c9e2f0ca 100755 --- a/gamefilesd/res/level/fonts.json +++ b/gamefilesd/res/level/fonts.json @@ -5,6 +5,7 @@ "textureId": "smaltext", "palette": "town", "file": "ctrlpan/smaltext.cel", + "celSize": [13, 11], "charMapFile": "res/level/smaltextCharMap.bin", "charSizeFile": "res/level/smaltextSize.bin" }, diff --git a/gamefilesd/towners/adria/buy/confirm2.json b/gamefilesd/towners/adria/buy/confirm2.json index d6a8ac8e..27dde062 100755 --- a/gamefilesd/towners/adria/buy/confirm2.json +++ b/gamefilesd/towners/adria/buy/confirm2.json @@ -110,56 +110,11 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "adria", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "adria", "{1}", "{2}", + "|currentLevel|player.adria.item.{1}.{2}.price|"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.player.adria.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "name": "text.setTextFromQuery", - "id": "itemPrice", - "query": "currentLevel.player.adria.item.{1}.{2}", - "text": "%price%" - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "adria", "inventory": {1}, "item": {2} } - }, - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.adria.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.adria.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/adria/buy/listItems.json b/gamefilesd/towners/adria/buy/listItems.json index 3e2a465f..62007880 100755 --- a/gamefilesd/towners/adria/buy/listItems.json +++ b/gamefilesd/towners/adria/buy/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,43 +14,8 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [571, 94], @@ -63,45 +29,8 @@ "load": "currentLevel|adria.inventory.0", "text": "%price%", "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "adria", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.adria.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.adria.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "adria", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/adria/buy/panel.json b/gamefilesd/towners/adria/buy/panel.json index 54302286..6ff492b7 100755 --- a/gamefilesd/towners/adria/buy/panel.json +++ b/gamefilesd/towners/adria/buy/panel.json @@ -56,7 +56,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 94], @@ -68,12 +67,12 @@ "focusSound": "titlemov", "visibleItems": 4, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "onScrollDown": "focus.moveDown", "onScrollUp": "focus.moveUp", "items": { "load": "currentLevel|adria.inventory.0", - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", "file": ["towners/adria/buy/confirm.json", 0, "%idx%"] @@ -84,45 +83,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "adria", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.adria.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.adria.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "adria", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/adria/recharge/confirm2.json b/gamefilesd/towners/adria/recharge/confirm2.json index 7bdec6c1..751d9002 100755 --- a/gamefilesd/towners/adria/recharge/confirm2.json +++ b/gamefilesd/towners/adria/recharge/confirm2.json @@ -114,14 +114,14 @@ "anchorLeftPentagram", "anchorRightPentagram", { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfo", "query": "currentLevel.currentPlayer.item.{1}.{2}", "text": "%name%\n %d.0%, %d.3%", "removeEmptyLines": true }, { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemPrice", "query": "currentLevel.currentPlayer.item.{1}.{2}", "text": "%price%" diff --git a/gamefilesd/towners/adria/recharge/listItems.json b/gamefilesd/towners/adria/recharge/listItems.json index 070b7a8e..f68b28b3 100755 --- a/gamefilesd/towners/adria/recharge/listItems.json +++ b/gamefilesd/towners/adria/recharge/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,40 +14,6 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", "replaceVars": true, diff --git a/gamefilesd/towners/adria/sell/confirm2.json b/gamefilesd/towners/adria/sell/confirm2.json index b7b85cd8..75486878 100755 --- a/gamefilesd/towners/adria/sell/confirm2.json +++ b/gamefilesd/towners/adria/sell/confirm2.json @@ -74,7 +74,7 @@ "text": "Yes", "onClick": [ { "name": "player.addGold", "gold": "|currentLevel|currentPlayer.item.{1}.{2}.prices.sell|" }, - { "name": "item.delete", "inventory": "{1}", "item": {2} }, + { "name": "item.delete", "inventory": {1}, "item": {2} }, "updateBeltItems", { "name": "resource.popAll", "id": "userPanelSell" }, { "name": "load", "file": "towners/adria/sell/panel.json" } @@ -102,53 +102,11 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "hero", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "hero", "{1}", "{2}", + "|currentLevel|currentPlayer.item.{1}.{2}.prices.sell|"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.currentPlayer.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "name": "text.setTextFromQuery", - "id": "itemPrice", - "query": "currentLevel.currentPlayer.item.{1}.{2}", - "text": "%prices.sell%" - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.{1}.{2}|", - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/adria/sell/listItems.json b/gamefilesd/towners/adria/sell/listItems.json index 2fd84a7d..399bd608 100755 --- a/gamefilesd/towners/adria/sell/listItems.json +++ b/gamefilesd/towners/adria/sell/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,43 +14,8 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [571, 94], @@ -68,42 +34,8 @@ }, "text": "%prices.sell%", "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.stash.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "hero", 2, "%idx%", "%menuIdx%"] } }, { @@ -114,42 +46,8 @@ }, "text": "%prices.sell%", "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.belt.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.belt.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.belt.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "hero", 1, "%idx%", "%menuIdx%"] } } ] diff --git a/gamefilesd/towners/adria/sell/panel.json b/gamefilesd/towners/adria/sell/panel.json index 567a5575..30032bc6 100755 --- a/gamefilesd/towners/adria/sell/panel.json +++ b/gamefilesd/towners/adria/sell/panel.json @@ -56,7 +56,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 94], @@ -68,7 +67,7 @@ "focusSound": "titlemov", "visibleItems": 4, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "onScrollDown": "focus.moveDown", "onScrollUp": "focus.moveUp", "items": [ @@ -78,10 +77,10 @@ "property": "itemType", "value": ["Book", "Potion", "Scroll", "Staff"] }, - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", - "file": ["towners/adria/sell/confirm.json", "stash", "%idx%"] + "file": ["towners/adria/sell/confirm.json", 2, "%idx%"] }, "onFocus": [ "anchorLeftPentagram", @@ -89,42 +88,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.stash.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "hero", 2, "%idx%", "%menuIdx%"] } }, { @@ -133,10 +98,10 @@ "property": "itemType", "value": ["Book", "Potion", "Scroll", "Staff"] }, - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", - "file": ["towners/adria/sell/confirm.json", "belt", "%idx%"] + "file": ["towners/adria/sell/confirm.json", 1, "%idx%"] }, "onFocus": [ "anchorLeftPentagram", @@ -144,42 +109,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.belt.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.belt.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.belt.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "hero", 1, "%idx%", "%menuIdx%"] } } ] diff --git a/gamefilesd/towners/cain/identify/confirm2.json b/gamefilesd/towners/cain/identify/confirm2.json index 1535670c..5347451c 100755 --- a/gamefilesd/towners/cain/identify/confirm2.json +++ b/gamefilesd/towners/cain/identify/confirm2.json @@ -80,7 +80,7 @@ }, { "name": "item.setProperty", - "inventory": "{1}", + "inventory": {1}, "item": {2}, "property": "identified", "value": true @@ -111,47 +111,10 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "hero", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "hero", "{1}", "{2}", "%identifyPrice%"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.currentPlayer.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.{1}.{2}|", - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/cain/identify/item.json b/gamefilesd/towners/cain/identify/item.json index d786771c..e628b295 100755 --- a/gamefilesd/towners/cain/identify/item.json +++ b/gamefilesd/towners/cain/identify/item.json @@ -65,62 +65,15 @@ "horizontalAlign": "center", "horizontalSpaceOffset": 1, "text": "Done", - "onClick": [ - { - "name": "item.setProperty", - "inventory": "{1}", - "item": {2}, - "property": "identified", - "value": true - }, - { "name": "resource.popAll", "id": "userPanelItem" } - ], + "onClick": { "name": "resource.popAll", "id": "userPanelItem" }, "onFocus": [ "anchorLeftPentagram", "anchorRightPentagram" ] }, + "load": ["towners/common/setItemInfo.json", "hero", "{1}", "{2}"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.currentPlayer.item.{1}.{2}", - "text": "%name%\n %d.1% %d.2%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.{1}.{2}|", - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/cain/identify/listItems.json b/gamefilesd/towners/cain/identify/listItems.json index bc364330..2ac7358c 100755 --- a/gamefilesd/towners/cain/identify/listItems.json +++ b/gamefilesd/towners/cain/identify/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,40 +14,6 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", "replaceVars": true, diff --git a/gamefilesd/towners/cain/identify/panel.json b/gamefilesd/towners/cain/identify/panel.json index 1cd6de54..1b62ba3d 100755 --- a/gamefilesd/towners/cain/identify/panel.json +++ b/gamefilesd/towners/cain/identify/panel.json @@ -56,7 +56,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 94], @@ -68,7 +67,7 @@ "focusSound": "titlemov", "visibleItems": 4, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "onScrollDown": "focus.moveDown", "onScrollUp": "focus.moveUp", "items": [ @@ -78,14 +77,10 @@ "property": "identified", "value": true }, - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", - "file": [ - "towners/cain/identify/confirm.json", - "body", - "%idx%" - ] + "file": ["towners/cain/identify/confirm.json", 0, "%idx%"] }, "onFocus": [ "anchorLeftPentagram", @@ -93,42 +88,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.body.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.body.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.body.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "hero", 0, "%idx%", "%menuIdx%"] } }, { @@ -137,14 +98,10 @@ "property": "identified", "value": true }, - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", - "file": [ - "towners/cain/identify/confirm.json", - "belt", - "%idx%" - ] + "file": ["towners/cain/identify/confirm.json", 1, "%idx%"] }, "onFocus": [ "anchorLeftPentagram", @@ -152,42 +109,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.belt.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.belt.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.belt.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "hero", 1, "%idx%", "%menuIdx%"] } }, { @@ -196,14 +119,10 @@ "property": "identified", "value": true }, - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", - "file": [ - "towners/cain/identify/confirm.json", - "stash", - "%idx%" - ] + "file": ["towners/cain/identify/confirm.json", 2, "%idx%"] }, "onFocus": [ "anchorLeftPentagram", @@ -211,42 +130,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.stash.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "hero", 2, "%idx%", "%menuIdx%"] } } ] diff --git a/gamefilesd/towners/common/loadMenuButtons.json b/gamefilesd/towners/common/loadMenuButtons.json new file mode 100755 index 00000000..62c58b60 --- /dev/null +++ b/gamefilesd/towners/common/loadMenuButtons.json @@ -0,0 +1,40 @@ +{ + "button": [ + { + "id": "btnItem1", + "texture": "empty", + "textureRect": [518, 44], + "position": [52, 94], + "anchor": "none", + "captureInputEvents": false, + "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } + }, + { + "id": "btnItem2", + "texture": "empty", + "textureRect": [518, 44], + "position": [52, 142], + "anchor": "none", + "captureInputEvents": false, + "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } + }, + { + "id": "btnItem3", + "texture": "empty", + "textureRect": [518, 44], + "position": [52, 190], + "anchor": "none", + "captureInputEvents": false, + "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } + }, + { + "id": "btnItem4", + "texture": "empty", + "textureRect": [518, 44], + "position": [52, 238], + "anchor": "none", + "captureInputEvents": false, + "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } + } + ] +} \ No newline at end of file diff --git a/gamefilesd/towners/common/setItemInfo.json b/gamefilesd/towners/common/setItemInfo.json new file mode 100755 index 00000000..43e421f0 --- /dev/null +++ b/gamefilesd/towners/common/setItemInfo.json @@ -0,0 +1,105 @@ +{ + "action": [ + { + "name": "text.setText", + "id": "itemInfo", + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": "%name%\n" + }, + { + "name": "if.inList", + "param": "|currentLevel|player.{1}.item.{2}.{3}.itemType|", + "list": ["Book", "Potion", "Scroll"], + "else": [ + { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.identified|", + "param2": true, + "then": { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.1|", + "param2": "", + "then": { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.2|", + "param2": "", + "then": { + "name": "text.appendText", + "id": "itemInfo", + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.1%, %d.2%\n" + }, + "else": { + "name": "text.appendText", + "id": "itemInfo", + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.1%\n" + } + }, + "else": { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.2|", + "param2": "", + "then": { + "name": "text.appendText", + "id": "itemInfo", + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.2%\n" + } + } + } + }, + { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.0|", + "param2": "", + "then": { + "name": "text.appendText", + "id": "itemInfo", + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.0%," + } + } + ] + }, + { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.3|", + "param2": "", + "then": { + "name": "text.appendText", + "id": "itemInfo", + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.3%" + }, + "else": { + "name": "text.appendText", + "id": "itemInfo", + "text": " No Required Attributes" + } + }, + { + "replaceVars": true, + "name": "if.equal", + "param1": { + "name": "player.canEquipItem", + "item": { "player": "{1}", "inventory": {2}, "item": {3} } + }, + "param2": false, + "then": { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.unique|", + "param2": 1, + "then": { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.magical|", + "param2": 1, + "then": { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, + "else": { "name": "text.setColor", "id": "itemInfo" } + } + } + } + ] +} \ No newline at end of file diff --git a/gamefilesd/towners/common/setMenuItemText.json b/gamefilesd/towners/common/setMenuItemText.json new file mode 100755 index 00000000..d7667b27 --- /dev/null +++ b/gamefilesd/towners/common/setMenuItemText.json @@ -0,0 +1,117 @@ +{ + "action": [ + { + "name": "if.inList", + "param": "|currentLevel|player.{1}.item.{2}.{3}.itemType|", + "list": ["Book", "Potion", "Scroll"], + "else": [ + { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.identified|", + "param2": true, + "then": { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.1|", + "param2": "", + "then": { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.2|", + "param2": "", + "then": { + "name": "menu.appendText", + "id": "mainMenu", + "index": {4}, + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.1%, %d.2%\n" + }, + "else": { + "name": "menu.appendText", + "id": "mainMenu", + "index": {4}, + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.1%\n" + } + }, + "else": { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.2|", + "param2": "", + "then": { + "name": "menu.appendText", + "id": "mainMenu", + "index": {4}, + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.2%\n" + } + } + } + }, + { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.0|", + "param2": "", + "then": { + "name": "menu.appendText", + "id": "mainMenu", + "index": {4}, + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.0%," + } + } + ] + }, + { + "name": "if.notEqual", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.d.3|", + "param2": "", + "then": { + "name": "menu.appendText", + "id": "mainMenu", + "index": {4}, + "query": "currentLevel.player.{1}.item.{2}.{3}", + "text": " %d.3%" + }, + "else": { + "name": "menu.appendText", + "id": "mainMenu", + "index": {4}, + "text": " No Required Attributes" + } + }, + { + "name": "if.lower", + "param1": "|mainMenu|item.{4}.lineCount|", + "param2": 3, + "then": { + "name": "menu.appendText", + "id": "mainMenu", + "index": {4}, + "text": "\n" + } + }, + { + "replaceVars": true, + "name": "if.equal", + "param1": { + "name": "player.canEquipItem", + "item": { "player": "{1}", "inventory": {2}, "item": {3} } + }, + "param2": false, + "then": { + "name": "menu.setColor", "id": "mainMenu", "index": {4}, "color": "%textRed%" }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.unique|", + "param2": 1, + "then": { "name": "menu.setColor", "id": "mainMenu", "index": {4}, "color": "%textGold%" }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.magical|", + "param2": 1, + "then": { "name": "menu.setColor", "id": "mainMenu", "index": {4}, "color": "%textBlue%" }, + "else": { "name": "menu.setColor", "id": "mainMenu", "index": {4} } + } + } + } + ] +} \ No newline at end of file diff --git a/gamefilesd/towners/common/setMenuPriceText.json b/gamefilesd/towners/common/setMenuPriceText.json new file mode 100755 index 00000000..a53586bf --- /dev/null +++ b/gamefilesd/towners/common/setMenuPriceText.json @@ -0,0 +1,44 @@ +{ + "action": { + "replaceVars": true, + "name": "if.equal", + "param1": { + "name": "player.canEquipItem", + "item": { "player": "{1}", "inventory": {2}, "item": {3} } + }, + "param2": false, + "then": { + "name": "menu.setColor", + "id": "menuPrices", + "index": {4}, + "color": "%textRed%" + }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.unique|", + "param2": 1, + "then": { + "name": "menu.setColor", + "id": "menuPrices", + "index": {4}, + "color": "%textGold%" + }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.magical|", + "param2": 1, + "then": { + "name": "menu.setColor", + "id": "menuPrices", + "index": {4}, + "color": "%textBlue%" + }, + "else": { + "name": "menu.setColor", + "id": "menuPrices", + "index": {4} + } + } + } + } +} \ No newline at end of file diff --git a/gamefilesd/towners/common/setPriceInfo.json b/gamefilesd/towners/common/setPriceInfo.json new file mode 100755 index 00000000..a41c9da8 --- /dev/null +++ b/gamefilesd/towners/common/setPriceInfo.json @@ -0,0 +1,28 @@ +{ + "action": [ + { "name": "text.setText", "id": "itemPrice", "text": "{4}" }, + { + "replaceVars": true, + "name": "if.equal", + "param1": { + "name": "player.canEquipItem", + "item": { "player": "{1}", "inventory": {2}, "item": {3} } + }, + "param2": false, + "then": { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.unique|", + "param2": 1, + "then": { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" }, + "else": { + "name": "if.equal", + "param1": "|currentLevel|player.{1}.item.{2}.{3}.magical|", + "param2": 1, + "then": { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" }, + "else": { "name": "text.setColor", "id": "itemPrice" } + } + } + } + ] +} \ No newline at end of file diff --git a/gamefilesd/towners/griswold/buy/confirm2.json b/gamefilesd/towners/griswold/buy/confirm2.json index 881e5a84..89703fc1 100755 --- a/gamefilesd/towners/griswold/buy/confirm2.json +++ b/gamefilesd/towners/griswold/buy/confirm2.json @@ -110,56 +110,11 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "griswold", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "griswold", "{1}", "{2}", + "|currentLevel|player.griswold.item.{1}.{2}.price|"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.player.griswold.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "name": "text.setTextFromQuery", - "id": "itemPrice", - "query": "currentLevel.player.griswold.item.{1}.{2}", - "text": "%price%" - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "griswold", "inventory": {1}, "item": {2} } - }, - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/griswold/buy/listItems.json b/gamefilesd/towners/griswold/buy/listItems.json index 04ffea45..09162bda 100755 --- a/gamefilesd/towners/griswold/buy/listItems.json +++ b/gamefilesd/towners/griswold/buy/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,43 +14,8 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [571, 94], @@ -63,45 +29,8 @@ "load": "currentLevel|griswold.inventory.0", "text": "%price%", "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "griswold", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "griswold", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/griswold/buy/panel.json b/gamefilesd/towners/griswold/buy/panel.json index 9208183c..d6e8be71 100755 --- a/gamefilesd/towners/griswold/buy/panel.json +++ b/gamefilesd/towners/griswold/buy/panel.json @@ -56,7 +56,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 94], @@ -68,12 +67,12 @@ "focusSound": "titlemov", "visibleItems": 4, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "onScrollDown": "focus.moveDown", "onScrollUp": "focus.moveUp", "items": { "load": "currentLevel|griswold.inventory.0", - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", "file": ["towners/griswold/buy/confirm.json", 0, "%idx%"] @@ -84,45 +83,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "griswold", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "griswold", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/griswold/buyPremium/confirm2.json b/gamefilesd/towners/griswold/buyPremium/confirm2.json index cde05f1e..7a4ae6ca 100755 --- a/gamefilesd/towners/griswold/buyPremium/confirm2.json +++ b/gamefilesd/towners/griswold/buyPremium/confirm2.json @@ -110,56 +110,11 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "griswold", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "griswold", "{1}", "{2}", + "|currentLevel|player.griswold.item.{1}.{2}.price|"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.player.griswold.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "name": "text.setTextFromQuery", - "id": "itemPrice", - "query": "currentLevel.player.griswold.item.{1}.{2}", - "text": "%price%" - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "griswold", "inventory": {1}, "item": {2} } - }, - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/griswold/buyPremium/listItems.json b/gamefilesd/towners/griswold/buyPremium/listItems.json index 1d06cd96..dec42987 100755 --- a/gamefilesd/towners/griswold/buyPremium/listItems.json +++ b/gamefilesd/towners/griswold/buyPremium/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,43 +14,8 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [571, 94], @@ -63,45 +29,8 @@ "load": "currentLevel|griswold.inventory.1", "text": "%price%", "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "griswold", "inventory": 1, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.1.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.1.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "griswold", 1, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/griswold/buyPremium/panel.json b/gamefilesd/towners/griswold/buyPremium/panel.json index 4eafedda..458936b6 100755 --- a/gamefilesd/towners/griswold/buyPremium/panel.json +++ b/gamefilesd/towners/griswold/buyPremium/panel.json @@ -56,7 +56,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 94], @@ -68,12 +67,12 @@ "focusSound": "titlemov", "visibleItems": 4, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "onScrollDown": "focus.moveDown", "onScrollUp": "focus.moveUp", "items": { "load": "currentLevel|griswold.inventory.1", - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", "file": ["towners/griswold/buyPremium/confirm.json", 1, "%idx%"] @@ -84,45 +83,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "griswold", "inventory": 1, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.1.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.griswold.item.1.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "griswold", 1, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/griswold/repair/confirm2.json b/gamefilesd/towners/griswold/repair/confirm2.json index d71e6eb5..343aacfa 100755 --- a/gamefilesd/towners/griswold/repair/confirm2.json +++ b/gamefilesd/towners/griswold/repair/confirm2.json @@ -114,14 +114,14 @@ "anchorLeftPentagram", "anchorRightPentagram", { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemInfo", "query": "currentLevel.currentPlayer.item.{1}.{2}", "text": "%name%\n %d.0%, %d.3%", "removeEmptyLines": true }, { - "name": "text.setTextFromQuery", + "name": "text.setText", "id": "itemPrice", "query": "currentLevel.currentPlayer.item.{1}.{2}", "text": "%price%" diff --git a/gamefilesd/towners/griswold/repair/listItems.json b/gamefilesd/towners/griswold/repair/listItems.json index 9d4acaf9..1ba764cd 100755 --- a/gamefilesd/towners/griswold/repair/listItems.json +++ b/gamefilesd/towners/griswold/repair/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,40 +14,6 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", "replaceVars": true, diff --git a/gamefilesd/towners/griswold/sell/confirm2.json b/gamefilesd/towners/griswold/sell/confirm2.json index 7b86f681..04207b93 100755 --- a/gamefilesd/towners/griswold/sell/confirm2.json +++ b/gamefilesd/towners/griswold/sell/confirm2.json @@ -74,7 +74,7 @@ "text": "Yes", "onClick": [ { "name": "player.addGold", "gold": "|currentLevel|currentPlayer.item.{1}.{2}.prices.sell|" }, - { "name": "item.delete", "inventory": "{1}", "item": {2} }, + { "name": "item.delete", "inventory": {1}, "item": {2} }, { "name": "resource.popAll", "id": "userPanelSell" }, { "name": "load", "file": "towners/griswold/sell/panel.json" } ], @@ -101,53 +101,11 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "hero", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "hero", "{1}", "{2}", + "|currentLevel|currentPlayer.item.{1}.{2}.prices.sell|"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.currentPlayer.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "name": "text.setTextFromQuery", - "id": "itemPrice", - "query": "currentLevel.currentPlayer.item.{1}.{2}", - "text": "%prices.sell%" - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.{1}.{2}|", - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/griswold/sell/listItems.json b/gamefilesd/towners/griswold/sell/listItems.json index 4bc18dc0..7d9fd3ec 100755 --- a/gamefilesd/towners/griswold/sell/listItems.json +++ b/gamefilesd/towners/griswold/sell/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,43 +14,8 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [571, 94], @@ -67,42 +33,8 @@ }, "text": "%prices.sell%", "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.stash.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "hero", 2, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/griswold/sell/panel.json b/gamefilesd/towners/griswold/sell/panel.json index 8d691cdd..d793391e 100755 --- a/gamefilesd/towners/griswold/sell/panel.json +++ b/gamefilesd/towners/griswold/sell/panel.json @@ -56,7 +56,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 94], @@ -68,7 +67,7 @@ "focusSound": "titlemov", "visibleItems": 4, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "onScrollDown": "focus.moveDown", "onScrollUp": "focus.moveUp", "items": { @@ -77,10 +76,10 @@ "property": "itemType", "value": ["Amulet", "Armor", "Axe", "Bow", "Club", "Helmet", "Ring", "Shield", "Sword"] }, - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", - "file": ["towners/griswold/sell/confirm.json", "stash", "%idx%"] + "file": ["towners/griswold/sell/confirm.json", 2, "%idx%"] }, "onFocus": [ "anchorLeftPentagram", @@ -88,42 +87,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.canEquipItem.stash.%idx%|", - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|currentPlayer.item.stash.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "hero", 2, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/pepin/buy/confirm2.json b/gamefilesd/towners/pepin/buy/confirm2.json index 63f81821..f7e9b639 100755 --- a/gamefilesd/towners/pepin/buy/confirm2.json +++ b/gamefilesd/towners/pepin/buy/confirm2.json @@ -110,56 +110,11 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "pepin", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "pepin", "{1}", "{2}", + "|currentLevel|player.pepin.item.{1}.{2}.price|"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.player.pepin.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "name": "text.setTextFromQuery", - "id": "itemPrice", - "query": "currentLevel.player.pepin.item.{1}.{2}", - "text": "%price%" - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "pepin", "inventory": {1}, "item": {2} } - }, - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.pepin.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.pepin.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/pepin/buy/listItems.json b/gamefilesd/towners/pepin/buy/listItems.json index 8e48e7ef..428708c9 100755 --- a/gamefilesd/towners/pepin/buy/listItems.json +++ b/gamefilesd/towners/pepin/buy/listItems.json @@ -2,6 +2,7 @@ "load": "ui/level/panel/big/upperSeparator.json", "load": "ui/level/panel/big/lowerSeparator.json", "load": "ui/level/panel/big/scrollbar.json", + "load": "towners/common/loadMenuButtons.json", "keyboard": [ { "key": "tab", @@ -13,43 +14,8 @@ "action": "focus.moveDown" } ], - "button": [ - { - "id": "btnItem1", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 94], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 0 } - }, - { - "id": "btnItem2", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 142], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 1 } - }, - { - "id": "btnItem3", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 190], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 2 } - }, - { - "id": "btnItem4", - "texture": "empty", - "textureRect": [518, 44], - "position": [52, 238], - "anchor": "none", - "onClick": { "name": "menu.clickVisible", "id": "mainMenu", "index": 3 } - } - ], "menu": { "id": "menuPrices", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [571, 94], @@ -63,45 +29,8 @@ "load": "currentLevel|pepin.inventory.0", "text": "%price%", "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "pepin", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.pepin.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.pepin.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "pepin", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/pepin/buy/panel.json b/gamefilesd/towners/pepin/buy/panel.json index a220342f..3e83dd2f 100755 --- a/gamefilesd/towners/pepin/buy/panel.json +++ b/gamefilesd/towners/pepin/buy/panel.json @@ -56,7 +56,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 94], @@ -68,12 +67,12 @@ "focusSound": "titlemov", "visibleItems": 4, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "onScrollDown": "focus.moveDown", "onScrollUp": "focus.moveUp", "items": { "load": "currentLevel|pepin.inventory.0", - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", "file": ["towners/pepin/buy/confirm.json", 0, "%idx%"] @@ -84,45 +83,8 @@ "mainMenu.moveScrollbar" ], "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "pepin", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.pepin.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.pepin.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "pepin", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/wirt/buy/confirm2.json b/gamefilesd/towners/wirt/buy/confirm2.json index 9a0ca57c..14667095 100755 --- a/gamefilesd/towners/wirt/buy/confirm2.json +++ b/gamefilesd/towners/wirt/buy/confirm2.json @@ -99,56 +99,11 @@ ] } ], + "load": ["towners/common/setItemInfo.json", "wirt", "{1}", "{2}"], + "load": ["towners/common/setPriceInfo.json", "wirt", "{1}", "{2}", + "|currentLevel|player.wirt.item.{1}.{2}.price|"], "action": [ "anchorLeftPentagram", - "anchorRightPentagram", - { - "name": "text.setTextFromQuery", - "id": "itemInfo", - "query": "currentLevel.player.wirt.item.{1}.{2}", - "text": "%name%\n %d.0%, %d.3%", - "removeEmptyLines": true - }, - { - "name": "text.setTextFromQuery", - "id": "itemPrice", - "query": "currentLevel.player.wirt.item.{1}.{2}", - "text": "%price%" - }, - { - "replaceVars": true, - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "wirt", "inventory": {1}, "item": {2} } - }, - "param2": false, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textRed%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textRed%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.wirt.item.{1}.{2}.unique|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textGold%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textGold%" } - ], - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.wirt.item.{1}.{2}.magical|", - "param2": 1, - "then": [ - { "name": "text.setColor", "id": "itemInfo", "color": "%textBlue%" }, - { "name": "text.setColor", "id": "itemPrice", "color": "%textBlue%" } - ], - "else": [ - { "name": "text.setColor", "id": "itemInfo" }, - { "name": "text.setColor", "id": "itemPrice" } - ] - } - } - } + "anchorRightPentagram" ] } \ No newline at end of file diff --git a/gamefilesd/towners/wirt/buy/listItems.json b/gamefilesd/towners/wirt/buy/listItems.json index a9a7565b..b074f8e1 100755 --- a/gamefilesd/towners/wirt/buy/listItems.json +++ b/gamefilesd/towners/wirt/buy/listItems.json @@ -28,7 +28,6 @@ }, "menu": { "id": "menuPrices", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [571, 154], @@ -42,45 +41,8 @@ "load": "currentLevel|wirt.inventory.0", "text": "%price%", "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "wirt", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.wirt.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.wirt.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "menuPrices", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuPriceText.json", "wirt", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/towners/wirt/buy/panel.json b/gamefilesd/towners/wirt/buy/panel.json index 4d59da00..c583ca83 100755 --- a/gamefilesd/towners/wirt/buy/panel.json +++ b/gamefilesd/towners/wirt/buy/panel.json @@ -20,7 +20,6 @@ ], "menu": { "id": "mainMenu", - "replaceVars": true, "bitmapFont": "smaltext", "sound": "titlslct", "position": [52, 154], @@ -29,54 +28,17 @@ "anchor": "none", "visibleItems": 1, "size": [518, 186], - "verticalPad": 26, + "verticalPad": 14, "items": { "load": "currentLevel|wirt.inventory.0", - "text": "%name%\n %d.0%, %d.3%", + "text": "%name%\n", "onClick": { "name": "load", "file": ["towners/wirt/buy/confirm.json", 0, "%idx%"] }, "executeAction": { - "name": "if.equal", - "param1": { - "name": "player.canEquipItem", - "item": { "player": "wirt", "inventory": 0, "item": "%idx%" } - }, - "param2": false, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textRed%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.wirt.item.0.%idx%.unique|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textGold%" - }, - "else": { - "name": "if.equal", - "param1": "|currentLevel|player.wirt.item.0.%idx%.magical|", - "param2": 1, - "then": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%", - "color": "%textBlue%" - }, - "else": { - "name": "menu.setColor", - "id": "mainMenu", - "index": "%menuIdx%" - } - } - } + "name": "load", + "file": ["towners/common/setMenuItemText.json", "wirt", 0, "%idx%", "%menuIdx%"] } } }, diff --git a/gamefilesd/ui/level/char/closePanels.json b/gamefilesd/ui/level/char/closePanels.json index 254fa675..f8933465 100755 --- a/gamefilesd/ui/level/char/closePanels.json +++ b/gamefilesd/ui/level/char/closePanels.json @@ -1,5 +1,6 @@ { "action": [ + { "name": "resource.popAll", "id": "userPanel" }, { "name": "resource.pop", "id": "charPanelLabels" }, { "name": "resource.pop", "id": "charPanelQuests" }, { "name": "resource.pop", "id": "charPanelInventory" }, @@ -8,6 +9,7 @@ { "name": "drawable.visible", "id": "questPanel", "visible": false }, { "name": "drawable.visible", "id": "invPanel", "visible": false }, { "name": "drawable.visible", "id": "spellPanel", "visible": false }, + "resizeLevel", "clearPanelText" ] } \ No newline at end of file diff --git a/gamefilesd/ui/level/char/panel.json b/gamefilesd/ui/level/char/panel.json index bd3da973..3904468a 100755 --- a/gamefilesd/ui/level/char/panel.json +++ b/gamefilesd/ui/level/char/panel.json @@ -40,49 +40,54 @@ "id": "charPanel", "anchor": "left", "texture": "char", - "captureScrollEvent": true, + "captureInputEvents": true, + "visible": false, "onHoverEnter": "clearLevelHoverActions", "onHoverLeave": "setLevelHoverActions", - "visible": false + "onRightClick": "rightClickLevel" }, { "id": "invPanel", "anchor": "right", "position": [320, 0], "texture": "inv", - "captureScrollEvent": true, + "captureInputEvents": true, + "visible": false, "onHoverEnter": "clearLevelHoverActions", "onHoverLeave": "setLevelHoverActions", - "visible": false + "onRightClick": "rightClickLevel" }, { "id": "questPanel", "anchor": "left", "texture": "quest", - "captureScrollEvent": true, + "captureInputEvents": true, + "visible": false, "onHoverEnter": "clearLevelHoverActions", "onHoverLeave": "setLevelHoverActions", - "visible": false + "onRightClick": "rightClickLevel" }, { "id": "spellPanel", "anchor": "right", "position": [320, 0], "texture": "spellbk", - "captureScrollEvent": true, + "captureInputEvents": true, + "visible": false, "onHoverEnter": "clearLevelHoverActions", "onHoverLeave": "setLevelHoverActions", - "visible": false - } - ], - "image": [ + "onRightClick": "rightClickLevel" + }, { "id": "panel8", "texture": "panel8", "position": [0, 351], "textureRect": [0, 15, 640, 129], - "anchor": "bottom" - }, + "anchor": "bottom", + "onRightClick": "rightClickLevel" + } + ], + "image": [ { "id": "lifeOrbFull", "texture": "panel8", @@ -138,13 +143,6 @@ "onHoverEnter": "hideItemInfo", "onHoverLeave": "hideItemInfo" }, - { - "id": "panelBackground", - "texture": "empty", - "position": [0, 352], - "anchor": "bottom", - "textureRect": [640, 128] - }, { "id": "char", "texture": "empty", @@ -152,25 +150,28 @@ "position": [9, 361], "textureRect": [71, 19], "clickUp": true, - "onClick": { - "name": "if.equal", - "param1": "|charPanel|visible|", - "param2": false, - "then": [ - { "name": "resource.pop", "id": "charPanelQuests" }, - { "name": "load", "file": "ui/level/char/panelLabels.json" }, - { "name": "drawable.visible", "id": "charPanel", "visible": true }, - { "name": "drawable.visible", "id": "questPanel", "visible": false }, - "hideItemInfo" - ], - "else": [ - { "name": "resource.pop", "id": "charPanelLabels" }, - { "name": "resource.pop", "id": "charPanelQuests" }, - { "name": "drawable.visible", "id": "charPanel", "visible": false }, - { "name": "drawable.visible", "id": "questPanel", "visible": false }, - "setLevelHoverActions" - ] - }, + "onClick": [ + { + "name": "if.equal", + "param1": "|charPanel|visible|", + "param2": false, + "then": [ + { "name": "resource.pop", "id": "charPanelQuests" }, + { "name": "load", "file": "ui/level/char/panelLabels.json" }, + { "name": "drawable.visible", "id": "charPanel", "visible": true }, + { "name": "drawable.visible", "id": "questPanel", "visible": false }, + "hideItemInfo" + ], + "else": [ + { "name": "resource.pop", "id": "charPanelLabels" }, + { "name": "resource.pop", "id": "charPanelQuests" }, + { "name": "drawable.visible", "id": "charPanel", "visible": false }, + { "name": "drawable.visible", "id": "questPanel", "visible": false }, + "setLevelHoverActions" + ] + }, + "resizeLevel" + ], "onClickIn": { "name": "button.setTexture", "id": "char", "texture": "panel8bu" }, "onClickOut": { "name": "button.setTexture", "id": "char", "texture": "empty" }, "onHoverEnter": { @@ -192,24 +193,27 @@ "position": [9, 387], "textureRect": [0, 19, 71, 19], "clickUp": true, - "onClick": { - "name": "if.equal", - "param1": "|questPanel|visible|", - "param2": false, - "then": [ - { "name": "resource.pop", "id": "charPanelLabels" }, - { "name": "load", "file": "ui/level/char/panelQuests.json" }, - { "name": "drawable.visible", "id": "charPanel", "visible": false }, - { "name": "drawable.visible", "id": "questPanel", "visible": true } - ], - "else": [ - { "name": "resource.pop", "id": "charPanelLabels" }, - { "name": "resource.pop", "id": "charPanelQuests" }, - { "name": "drawable.visible", "id": "charPanel", "visible": false }, - { "name": "drawable.visible", "id": "questPanel", "visible": false }, - "setLevelHoverActions" - ] - }, + "onClick": [ + { + "name": "if.equal", + "param1": "|questPanel|visible|", + "param2": false, + "then": [ + { "name": "resource.pop", "id": "charPanelLabels" }, + { "name": "load", "file": "ui/level/char/panelQuests.json" }, + { "name": "drawable.visible", "id": "charPanel", "visible": false }, + { "name": "drawable.visible", "id": "questPanel", "visible": true } + ], + "else": [ + { "name": "resource.pop", "id": "charPanelLabels" }, + { "name": "resource.pop", "id": "charPanelQuests" }, + { "name": "drawable.visible", "id": "charPanel", "visible": false }, + { "name": "drawable.visible", "id": "questPanel", "visible": false }, + "setLevelHoverActions" + ] + }, + "resizeLevel" + ], "onClickIn": { "name": "button.setTexture", "id": "quest", "texture": "panel8bu" }, "onClickOut": { "name": "button.setTexture", "id": "quest", "texture": "empty" }, "onHoverEnter": { @@ -231,24 +235,27 @@ "position": [560, 361], "textureRect": [0, 76, 71, 19], "clickUp": true, - "onClick": { - "name": "if.equal", - "param1": "|invPanel|visible|", - "param2": false, - "then": [ - { "name": "resource.pop", "id": "charPanelSpells" }, - { "name": "load", "file": "ui/level/char/panelInventory.json" }, - { "name": "drawable.visible", "id": "invPanel", "visible": true }, - { "name": "drawable.visible", "id": "spellPanel", "visible": false } - ], - "else": [ - { "name": "resource.pop", "id": "charPanelInventory" }, - { "name": "resource.pop", "id": "charPanelSpells" }, - { "name": "drawable.visible", "id": "invPanel", "visible": false }, - { "name": "drawable.visible", "id": "spellPanel", "visible": false }, - "setLevelHoverActions" - ] - }, + "onClick": [ + { + "name": "if.equal", + "param1": "|invPanel|visible|", + "param2": false, + "then": [ + { "name": "resource.pop", "id": "charPanelSpells" }, + { "name": "load", "file": "ui/level/char/panelInventory.json" }, + { "name": "drawable.visible", "id": "invPanel", "visible": true }, + { "name": "drawable.visible", "id": "spellPanel", "visible": false } + ], + "else": [ + { "name": "resource.pop", "id": "charPanelInventory" }, + { "name": "resource.pop", "id": "charPanelSpells" }, + { "name": "drawable.visible", "id": "invPanel", "visible": false }, + { "name": "drawable.visible", "id": "spellPanel", "visible": false }, + "setLevelHoverActions" + ] + }, + "resizeLevel" + ], "onClickIn": { "name": "button.setTexture", "id": "inv", "texture": "panel8bu" }, "onClickOut": { "name": "button.setTexture", "id": "inv", "texture": "empty" }, "onHoverEnter": { @@ -270,24 +277,27 @@ "position": [560, 387], "textureRect": [0, 95, 71, 19], "clickUp": true, - "onClick": { - "name": "if.equal", - "param1": "|spellPanel|visible|", - "param2": false, - "then": [ - { "name": "resource.pop", "id": "charPanelInventory" }, - { "name": "load", "file": "ui/level/char/panelSpells.json" }, - { "name": "drawable.visible", "id": "invPanel", "visible": false }, - { "name": "drawable.visible", "id": "spellPanel", "visible": true } - ], - "else": [ - { "name": "resource.pop", "id": "charPanelInventory" }, - { "name": "resource.pop", "id": "charPanelSpells" }, - { "name": "drawable.visible", "id": "invPanel", "visible": false }, - { "name": "drawable.visible", "id": "spellPanel", "visible": false }, - "setLevelHoverActions" - ] - }, + "onClick": [ + { + "name": "if.equal", + "param1": "|spellPanel|visible|", + "param2": false, + "then": [ + { "name": "resource.pop", "id": "charPanelInventory" }, + { "name": "load", "file": "ui/level/char/panelSpells.json" }, + { "name": "drawable.visible", "id": "invPanel", "visible": false }, + { "name": "drawable.visible", "id": "spellPanel", "visible": true } + ], + "else": [ + { "name": "resource.pop", "id": "charPanelInventory" }, + { "name": "resource.pop", "id": "charPanelSpells" }, + { "name": "drawable.visible", "id": "invPanel", "visible": false }, + { "name": "drawable.visible", "id": "spellPanel", "visible": false }, + "setLevelHoverActions" + ] + }, + "resizeLevel" + ], "onClickIn": { "name": "button.setTexture", "id": "spell", "texture": "panel8bu" }, "onClickOut": { "name": "button.setTexture", "id": "spell", "texture": "empty" }, "onHoverEnter": { diff --git a/gamefilesd/ui/level/menu/game2.json b/gamefilesd/ui/level/menu/game2.json index 33980abf..f1801b9b 100755 --- a/gamefilesd/ui/level/menu/game2.json +++ b/gamefilesd/ui/level/menu/game2.json @@ -53,8 +53,8 @@ "name": "if.equal", "param1": "%colorCycling%", "param2": true, - "then": { "name": "button.setText", "id": "colorCycling", "text": "Color Cycling On" }, - "else": { "name": "button.setText", "id": "colorCycling", "text": "Color Cycling Off" } + "then": { "name": "text.setText", "id": "colorCycling", "text": "Color Cycling On" }, + "else": { "name": "text.setText", "id": "colorCycling", "text": "Color Cycling Off" } } }, { diff --git a/gamefilesd/ui/level/panel/big.json b/gamefilesd/ui/level/panel/big.json index f723a3a0..380e50fb 100755 --- a/gamefilesd/ui/level/panel/big.json +++ b/gamefilesd/ui/level/panel/big.json @@ -4,8 +4,8 @@ "texture": "empty", "anchor": "all", "textureRect": [640, 480], - "resizable": true, - "captureScrollEvent": true + "captureInputEvents": true, + "resizable": true }, "image": [ { diff --git a/gamefilesd/ui/level/panel/small.json b/gamefilesd/ui/level/panel/small.json index fb785b51..8e7098f9 100755 --- a/gamefilesd/ui/level/panel/small.json +++ b/gamefilesd/ui/level/panel/small.json @@ -21,6 +21,14 @@ } } ], + "button": { + "id": "background", + "texture": "empty", + "anchor": "all", + "textureRect": [640, 480], + "captureInputEvents": true, + "resizable": true + }, "image": [ { "id": "textWallBackground", @@ -42,13 +50,5 @@ "textureRect": [2, 0, 266, 3], "texture": "textbox2" } - ], - "button": { - "id": "background", - "texture": "empty", - "anchor": "all", - "textureRect": [640, 480], - "resizable": true, - "captureScrollEvent": true - } + ] } \ No newline at end of file diff --git a/gamefilesd/ui/level/showText.json b/gamefilesd/ui/level/showText.json index 10c40d64..9c966d04 100755 --- a/gamefilesd/ui/level/showText.json +++ b/gamefilesd/ui/level/showText.json @@ -28,6 +28,7 @@ "anchor": "all", "textureRect": [640, 480], "texture": "empty", + "captureInputEvents": true, "resizable": true, "onClick": [ { "name": "resource.pop" }, diff --git a/gamefilesd/ui/mainMenu.json b/gamefilesd/ui/mainMenu.json index 65d97f89..6fbf71b2 100755 --- a/gamefilesd/ui/mainMenu.json +++ b/gamefilesd/ui/mainMenu.json @@ -169,9 +169,9 @@ "file": "ui/settings.json" } }, - "onHoverEnter": { "name": "button.setText", "id": "versionInfo", "text": "Change Settings" }, + "onHoverEnter": { "name": "text.setText", "id": "versionInfo", "text": "Change Settings" }, "onHoverLeave": { - "name": "button.setText", + "name": "text.setText", "id": "versionInfo", "binding": ["|game|title|", "|game|version|"], "format": "[1] v[2]" @@ -180,7 +180,7 @@ "action": [ { "name": "menu.setFont", "id": "menu", "index": 1, "idFont": "font42y" }, { - "name": "button.setText", + "name": "text.setText", "id": "versionInfo", "binding": ["|game|title|", "|game|version|"], "format": "[1] v[2]" diff --git a/gamefilesd/ui/settings.json b/gamefilesd/ui/settings.json index d75d4e2c..1afd47af 100755 --- a/gamefilesd/ui/settings.json +++ b/gamefilesd/ui/settings.json @@ -107,11 +107,11 @@ "param2": false, "then": [ { "name": "loadJson", "json": { "stretchToFit": true } }, - { "name": "button.setText", "id": "stretchToFit", "text": "Stretch to Fit: On" } + { "name": "text.setText", "id": "stretchToFit", "text": "Stretch to Fit: On" } ], "else": [ { "name": "loadJson", "json": { "stretchToFit": false } }, - { "name": "button.setText", "id": "stretchToFit", "text": "Stretch to Fit: Off" } + { "name": "text.setText", "id": "stretchToFit", "text": "Stretch to Fit: Off" } ] }, "onFocus": [ @@ -134,11 +134,11 @@ "param2": false, "then": [ { "name": "loadJson", "json": { "keepAR": true } }, - { "name": "button.setText", "id": "keepAR", "text": "Keep Ratio: On" } + { "name": "text.setText", "id": "keepAR", "text": "Keep Ratio: On" } ], "else": [ { "name": "loadJson", "json": { "keepAR": false } }, - { "name": "button.setText", "id": "keepAR", "text": "Keep Ratio: Off" } + { "name": "text.setText", "id": "keepAR", "text": "Keep Ratio: Off" } ] }, "onFocus": [ @@ -161,11 +161,11 @@ "param2": false, "then": [ { "name": "loadJson", "json": { "smoothScreen": true } }, - { "name": "button.setText", "id": "smoothScreen", "text": "Smooth Screen: On" } + { "name": "text.setText", "id": "smoothScreen", "text": "Smooth Screen: On" } ], "else": [ { "name": "loadJson", "json": { "smoothScreen": false } }, - { "name": "button.setText", "id": "smoothScreen", "text": "Smooth Screen: Off" } + { "name": "text.setText", "id": "smoothScreen", "text": "Smooth Screen: Off" } ] }, "onFocus": [ @@ -191,7 +191,7 @@ "action": [ { "name": "loadJson", "json": { "framerate": 50 } }, { - "name": "button.setText", + "name": "text.setText", "id": "framerate", "binding": "|game|framerate|", "format": "Framerate Limit: [1]" @@ -203,7 +203,7 @@ "action": [ { "name": "loadJson", "json": { "framerate": 60 } }, { - "name": "button.setText", + "name": "text.setText", "id": "framerate", "binding": "|game|framerate|", "format": "Framerate Limit: [1]" @@ -215,7 +215,7 @@ "action": [ { "name": "loadJson", "json": { "framerate": 0 } }, { - "name": "button.setText", + "name": "text.setText", "id": "framerate", "text": "Framerate Limit: Off" } @@ -258,29 +258,29 @@ "name": "if.equal", "param1": "|game|stretchToFit|", "param2": true, - "then": { "name": "button.setText", "id": "stretchToFit", "text": "Stretch to Fit: On" }, - "else": { "name": "button.setText", "id": "stretchToFit", "text": "Stretch to Fit: Off" } + "then": { "name": "text.setText", "id": "stretchToFit", "text": "Stretch to Fit: On" }, + "else": { "name": "text.setText", "id": "stretchToFit", "text": "Stretch to Fit: Off" } }, { "name": "if.equal", "param1": "|game|keepAR|", "param2": true, - "then": { "name": "button.setText", "id": "keepAR", "text": "Keep Ratio: On" }, - "else": { "name": "button.setText", "id": "keepAR", "text": "Keep Ratio: Off" } + "then": { "name": "text.setText", "id": "keepAR", "text": "Keep Ratio: On" }, + "else": { "name": "text.setText", "id": "keepAR", "text": "Keep Ratio: Off" } }, { "name": "if.equal", "param1": "|game|smoothScreen|", "param2": true, - "then": { "name": "button.setText", "id": "smoothScreen", "text": "Smooth Screen: On" }, - "else": { "name": "button.setText", "id": "smoothScreen", "text": "Smooth Screen: Off" } + "then": { "name": "text.setText", "id": "smoothScreen", "text": "Smooth Screen: On" }, + "else": { "name": "text.setText", "id": "smoothScreen", "text": "Smooth Screen: Off" } }, { "name": "if.equal", "param1": "|game|framerate|", "param2": 0, - "then": { "name": "button.setText", "id": "framerate", "text": "Framerate Limit: Off" }, - "else": { "name": "button.setText", "id": "framerate", "binding": "|game|framerate|", "format": "Framerate Limit: [1]" } + "then": { "name": "text.setText", "id": "framerate", "text": "Framerate Limit: Off" }, + "else": { "name": "text.setText", "id": "framerate", "binding": "|game|framerate|", "format": "Framerate Limit: [1]" } }, "anchorLeftPentagram", "anchorRightPentagram" diff --git a/gamefileshf/level/afterLevelLoad.json b/gamefileshf/level/afterLevelLoad.json index 9c93da6d..b23c88da 100755 --- a/gamefileshf/level/afterLevelLoad.json +++ b/gamefileshf/level/afterLevelLoad.json @@ -2,12 +2,22 @@ "action": [ { "name": "if.notEqual", - "param1": "|currentLevel|name|", - "param2": "Town Center", + "param1": "{1}", + "param2": "town", "then": { "name": "player.setRestStatus", "status": 1 }, "else": { "name": "player.setRestStatus", "status": 0 } }, - "updatePlayerTexture" + "updateLifeManaOrbs", + "updatePlayerTexture", + { + "name": "if.equal", + "param1": "{2}", + "param2": "positionPlayer", + "then": { "name": "load", "file": ["level/positionPlayer.json", "{3}"] } + } ], + "load": "ui/level/char/updateVisiblePanels.json", + "load": "level/playOrStopMusic.json", + "load": ["level/setMapAction.json", "{1}"], "load": "level/player/updateSpeed.json" } \ No newline at end of file diff --git a/gamefileshf/ui/level/menu/game2.json b/gamefileshf/ui/level/menu/game2.json index 6c2efa8d..37ed16c8 100755 --- a/gamefileshf/ui/level/menu/game2.json +++ b/gamefileshf/ui/level/menu/game2.json @@ -33,21 +33,24 @@ { "name": "action.set", "id": "updateJog", - "action": { - "name": "if.equal", - "param1": "%jog%", - "param2": true, - "then": [ - { "name": "button.setText", "id": "jog", "text": "Jog" }, - { "name": "drawable.setSizeX", "id": "progressBar4", "size": 270 }, - { "name": "drawable.anchor", "id": "option4", "idAnchor": "optbar4", "anchor": "right", "offset": [-29, 0] } - ], - "else": [ - { "name": "button.setText", "id": "jog", "text": "Walk" }, - { "name": "drawable.setSizeX", "id": "progressBar4", "size": 14 }, - { "name": "drawable.anchor", "id": "option4", "idAnchor": "optbar4", "anchor": "left", "offset": [29, 0] } - ] - } + "action": [ + { + "name": "if.equal", + "param1": "%jog%", + "param2": true, + "then": [ + { "name": "text.setText", "id": "jog", "text": "Jog" }, + { "name": "drawable.setSizeX", "id": "progressBar4", "size": 270 }, + { "name": "drawable.anchor", "id": "option4", "idAnchor": "optbar4", "anchor": "right", "offset": [-29, 0] } + ], + "else": [ + { "name": "text.setText", "id": "jog", "text": "Walk" }, + { "name": "drawable.setSizeX", "id": "progressBar4", "size": 14 }, + { "name": "drawable.anchor", "id": "option4", "idAnchor": "optbar4", "anchor": "left", "offset": [29, 0] } + ] + }, + { "name": "load", "file": "level/player/updateSpeed.json" } + ] }, { "name": "action.set", @@ -275,7 +278,6 @@ "then": { "name": "variable.set", "key": "jog", "val": true }, "else": { "name": "variable.set", "key": "jog", "val": false } }, - { "name": "load", "file": "level/player/updateSpeed.json" }, "updateJog" ] }, diff --git a/gamefileshf/ui/mainMenu.json b/gamefileshf/ui/mainMenu.json index fd70062e..342c191f 100755 --- a/gamefileshf/ui/mainMenu.json +++ b/gamefileshf/ui/mainMenu.json @@ -184,9 +184,9 @@ "file": "ui/settings.json" } }, - "onHoverEnter": { "name": "button.setText", "id": "versionInfo", "text": "Change Settings" }, + "onHoverEnter": { "name": "text.setText", "id": "versionInfo", "text": "Change Settings" }, "onHoverLeave": { - "name": "button.setText", + "name": "text.setText", "id": "versionInfo", "binding": ["|game|title|", "|game|version|"], "format": "[1] v[2]" @@ -195,7 +195,7 @@ "action": [ { "name": "menu.setFont", "id": "menu", "index": 1, "idFont": "font42y" }, { - "name": "button.setText", + "name": "text.setText", "id": "versionInfo", "binding": ["|game|title|", "|game|version|"], "format": "[1] v[2]" diff --git a/src/Actions/ActButton.h b/src/Actions/ActButton.h index 6fcd0813..c77e4293 100755 --- a/src/Actions/ActButton.h +++ b/src/Actions/ActButton.h @@ -111,36 +111,3 @@ class ActButtonSetFont : public Action return true; } }; - -class ActButtonSetText : public Action -{ -private: - std::string id; - std::string textFormat; - std::vector bindings; - -public: - ActButtonSetText(const std::string& id_, const std::string& text_) - : id(id_), textFormat(text_) {} - - ActButtonSetText(const std::string& id_, const std::string& format_, - const std::vector& bindings_) : id(id_), - textFormat(format_), bindings(bindings_) {} - - virtual bool execute(Game& game) - { - auto button = game.Resources().getResource(id); - if (button != nullptr) - { - if (bindings.empty() == true) - { - button->setText(game.getVarOrPropString(textFormat)); - } - else - { - button->setText(Text2::getFormatString(game, bindings, textFormat)); - } - } - return true; - } -}; diff --git a/src/Actions/ActDrawable.h b/src/Actions/ActDrawable.h index c796066a..ee91c153 100755 --- a/src/Actions/ActDrawable.h +++ b/src/Actions/ActDrawable.h @@ -7,14 +7,14 @@ #include #include "Utils.h" -class ActDrawableAddPositionOffset : public Action +class ActDrawableAddToPosition : public Action { private: std::string id; sf::Vector2f offset; public: - ActDrawableAddPositionOffset(const std::string& id_, const sf::Vector2f& offset_) + ActDrawableAddToPosition(const std::string& id_, const sf::Vector2f& offset_) : id(id_), offset(offset_) {} virtual bool execute(Game& game) @@ -28,14 +28,14 @@ class ActDrawableAddPositionOffset : public Action } }; -class ActDrawableAddSizeOffset : public Action +class ActDrawableAddToSize : public Action { private: std::string id; sf::Vector2f offset; public: - ActDrawableAddSizeOffset(const std::string& id_, const sf::Vector2f& offset_) + ActDrawableAddToSize(const std::string& id_, const sf::Vector2f& offset_) : id(id_), offset(offset_) {} virtual bool execute(Game& game) @@ -56,12 +56,11 @@ class ActDrawableAnchor : public Action std::string idAnchor; Anchor anchor; sf::Vector2f offset; - bool addSize; public: - ActDrawableAnchor(const std::string& id_, const std::string& idAnchor_, Anchor anchor_, - const sf::Vector2f& offset_, bool addSize_) - : id(id_), idAnchor(idAnchor_), anchor(anchor_), offset(offset_), addSize(addSize_) {} + ActDrawableAnchor(const std::string& id_, const std::string& idAnchor_, + Anchor anchor_, const sf::Vector2f& offset_) + : id(id_), idAnchor(idAnchor_), anchor(anchor_), offset(offset_) {} virtual bool execute(Game& game) { @@ -137,11 +136,10 @@ class ActDrawableAnchorToFocused : public Action std::string id; Anchor anchor; sf::Vector2f offset; - bool addSize; public: - ActDrawableAnchorToFocused(const std::string& id_, Anchor anchor_, const sf::Vector2f& offset_, - bool addSize_) : id(id_), anchor(anchor_), offset(offset_), addSize(addSize_) {} + ActDrawableAnchorToFocused(const std::string& id_, Anchor anchor_, + const sf::Vector2f& offset_) : id(id_), anchor(anchor_), offset(offset_) {} virtual bool execute(Game& game) { @@ -182,7 +180,7 @@ class ActDrawableCenterOnMouseX : public Action { auto itemPos = itemAnchor->Position(); auto itemSize = item->Size().x; - auto newRange = std::max(0u, std::min(range - (unsigned)itemSize, range)); + auto newRange = std::min(range - (unsigned)itemSize, range); auto offset = std::max(0.f, std::min(game.MousePositionf().x - itemPos.x, (float)range)); auto numSteps = game.getVarOrProp(steps, -1); float newPos = itemPos.x; @@ -236,7 +234,7 @@ class ActDrawableCenterOnMouseY : public Action { auto itemPos = itemAnchor->Position(); auto itemSize = item->Size().y; - auto newRange = std::max(0u, std::min(range - (unsigned)itemSize, range)); + auto newRange = std::min(range - (unsigned)itemSize, range); auto offset = std::max(0.f, std::min(game.MousePositionf().y - itemPos.y, (float)range)); auto numSteps = game.getVarOrProp(steps, -1); float newPos = itemPos.y; @@ -284,6 +282,31 @@ class ActDrawableDelete : public Action } }; +class ActDrawableExecuteAction : public Action +{ +private: + std::string id; + uint16_t actionHash16; + +public: + ActDrawableExecuteAction(const std::string& id_, uint16_t actionHash16_) + : id(id_), actionHash16(actionHash16_) {} + + virtual bool execute(Game& game) + { + auto item = game.Resources().getResource(id); + if (item != nullptr) + { + auto action = item->getAction(actionHash16).get(); + if (action != nullptr) + { + return action->execute(game); + } + } + return true; + } +}; + class ActDrawableHorizontalAnchorToFocused : public Action { private: diff --git a/src/Actions/ActGame.h b/src/Actions/ActGame.h index 15a6c8ff..1c4f2568 100755 --- a/src/Actions/ActGame.h +++ b/src/Actions/ActGame.h @@ -33,7 +33,7 @@ class ActGameEnableInput : public Action virtual bool execute(Game& game) { - game.enableInput(enable); + game.EnableInput(enable); return true; } }; diff --git a/src/Actions/ActMenu.h b/src/Actions/ActMenu.h index 8eaa805b..d153fe24 100755 --- a/src/Actions/ActMenu.h +++ b/src/Actions/ActMenu.h @@ -5,8 +5,56 @@ #include "Game.h" #include "Menu.h" #include +#include "TextUtils.h" #include "Variable.h" +class ActMenuAppendText : public Action +{ +private: + std::string id; + size_t idx; + std::string textFormat; + std::vector bindings; + TextUtils::TextOp textOp; + +public: + ActMenuAppendText(const std::string& id_, size_t idx_, + const std::string& text_, TextUtils::TextOp textOp_) + : id(id_), idx(idx_), textFormat(text_), textOp(textOp_) {} + + ActMenuAppendText(const std::string& id_, size_t idx_, + const std::string& text_, const std::string& query_) + : id(id_), idx(idx_), textFormat(text_), + textOp(TextUtils::TextOp::Query) + { + bindings.push_back(query_); + } + + ActMenuAppendText(const std::string& id_, + size_t idx_, const std::string& format_, + const std::vector& bindings_) + : id(id_), idx(idx_), textFormat(format_), + bindings(bindings_), textOp(TextUtils::TextOp::FormatString) {} + + void RemoveEmptyLines() { textOp |= TextUtils::TextOp::RemoveEmptyLines; } + void Trim() { textOp |= TextUtils::TextOp::Trim; } + + virtual bool execute(Game& game) + { + auto menu = game.Resources().getResource(id); + if (menu != nullptr) + { + auto button = menu->getItem(idx); + if (button != nullptr) + { + button->setText(button->getText() + + TextUtils::getText(game, textOp, textFormat, bindings)); + } + } + return true; + } +}; + class ActMenuClick : public Action { private: @@ -82,7 +130,7 @@ class ActMenuMoveScrollbar : public Action if (menu != nullptr && scrollBar != nullptr && anchorTo != nullptr) { auto pos = anchorTo->DrawPosition(); - auto newRange = std::max(0u, std::min(range - (unsigned)scrollBar->Size().y, range)); + auto newRange = std::min(range - (unsigned)scrollBar->Size().y, range); auto itemCount = menu->getItemCount(); if (focus == true) { @@ -211,11 +259,31 @@ class ActMenuSetText : public Action private: std::string id; size_t idx; - std::string text; + std::string textFormat; + std::vector bindings; + TextUtils::TextOp textOp; public: - ActMenuSetText(const std::string& id_, size_t idx_, const std::string& text_) - : id(id_), idx(idx_), text(text_) {} + ActMenuSetText(const std::string& id_, size_t idx_, + const std::string& text_, TextUtils::TextOp textOp_) + : id(id_), idx(idx_), textFormat(text_), textOp(textOp_) {} + + ActMenuSetText(const std::string& id_, size_t idx_, + const std::string& text_, const std::string& query_) + : id(id_), idx(idx_), textFormat(text_), + textOp(TextUtils::TextOp::Query) + { + bindings.push_back(query_); + } + + ActMenuSetText(const std::string& id_, + size_t idx_, const std::string& format_, + const std::vector& bindings_) + : id(id_), idx(idx_), textFormat(format_), + bindings(bindings_), textOp(TextUtils::TextOp::FormatString) {} + + void RemoveEmptyLines() { textOp |= TextUtils::TextOp::RemoveEmptyLines; } + void Trim() { textOp |= TextUtils::TextOp::Trim; } virtual bool execute(Game& game) { @@ -225,7 +293,7 @@ class ActMenuSetText : public Action auto button = menu->getItem(idx); if (button != nullptr) { - button->setText(game.getVarOrPropString(text)); + button->setText(TextUtils::getText(game, textOp, textFormat, bindings)); } } return true; diff --git a/src/Actions/ActText.h b/src/Actions/ActText.h index 82b454bf..be7dd879 100755 --- a/src/Actions/ActText.h +++ b/src/Actions/ActText.h @@ -2,7 +2,6 @@ #include "Action.h" #include "Game.h" -#include "GameUtils.h" #include "Text2.h" class ActTextSetColor : public Action @@ -24,167 +23,3 @@ class ActTextSetColor : public Action return true; } }; - -class ActTextSetSpacing : public Action -{ -private: - std::string id; - int horizSpaceOffset{ 0 }; - int vertSpaceOffset{ 0 }; - bool hasHorizSpaceOffset{ false }; - bool hasVertSpaceOffset{ false }; - -public: - ActTextSetSpacing(const std::string& id_) : id(id_) {} - - void setHorizontalSpaceOffset(int offset) - { - horizSpaceOffset = offset; - hasHorizSpaceOffset = true; - } - - void setVerticalSpaceOffset(int offset) - { - vertSpaceOffset = offset; - hasVertSpaceOffset = true; - } - - virtual bool execute(Game& game) - { - auto text = game.Resources().getResource(id); - if (text != nullptr) - { - if (hasHorizSpaceOffset == true) - { - text->setHorizontalSpaceOffset(horizSpaceOffset); - } - if (hasVertSpaceOffset == true) - { - text->setVerticalSpaceOffset(vertSpaceOffset); - } - } - return true; - } -}; - -class ActTextSetText : public Action -{ -private: - std::string id; - std::string textFormat; - std::vector bindings; - - int horizSpaceOffset{ 0 }; - int vertSpaceOffset{ 0 }; - bool hasHorizSpaceOffset{ false }; - bool hasVertSpaceOffset{ false }; - -public: - ActTextSetText(const std::string& id_, const std::string& text_) - : id(id_), textFormat(text_) {} - - ActTextSetText(const std::string& id_, const std::string& format_, - const std::vector& bindings_) : id(id_), - textFormat(format_), bindings(bindings_) {} - - void setHorizontalSpaceOffset(int offset) - { - horizSpaceOffset = offset; - hasHorizSpaceOffset = true; - } - - void setVerticalSpaceOffset(int offset) - { - vertSpaceOffset = offset; - hasVertSpaceOffset = true; - } - - virtual bool execute(Game& game) - { - auto text = game.Resources().getResource(id); - if (text != nullptr) - { - if (hasHorizSpaceOffset == true) - { - text->setHorizontalSpaceOffset(horizSpaceOffset); - } - if (hasVertSpaceOffset == true) - { - text->setVerticalSpaceOffset(vertSpaceOffset); - } - if (bindings.empty() == true) - { - text->setText(game.getVarOrPropString(textFormat)); - } - else - { - text->setText(Text2::getFormatString(game, bindings, textFormat)); - } - } - return true; - } -}; - -class ActTextSetTextFromQuery : public Action -{ -private: - std::string id; - std::string query; - std::string newText; - bool trimText; - bool removeEmptyLines; - - int horizSpaceOffset{ 0 }; - int vertSpaceOffset{ 0 }; - bool hasHorizSpaceOffset{ false }; - bool hasVertSpaceOffset{ false }; - -public: - ActTextSetTextFromQuery(const std::string& id_, const std::string& query_, - const std::string& text_, bool trimText_, bool removeEmptyLines_) : id(id_), - query(query_), newText(text_), trimText(trimText_), removeEmptyLines(removeEmptyLines_) {} - - void setHorizontalSpaceOffset(int offset) - { - horizSpaceOffset = offset; - hasHorizSpaceOffset = true; - } - - void setVerticalSpaceOffset(int offset) - { - vertSpaceOffset = offset; - hasVertSpaceOffset = true; - } - - virtual bool execute(Game& game) - { - auto text = game.Resources().getResource(id); - if (text != nullptr) - { - if (hasHorizSpaceOffset == true) - { - text->setHorizontalSpaceOffset(horizSpaceOffset); - } - if (hasVertSpaceOffset == true) - { - text->setVerticalSpaceOffset(vertSpaceOffset); - } - auto queryable = game.getQueryable(query); - std::string str = newText; - if (queryable != nullptr) - { - str = GameUtils::replaceStringWithQueryable(newText, *queryable); - } - if (trimText == true) - { - str = Utils::trim(str, " \t\r\n"); - } - if (removeEmptyLines == true) - { - str = Utils::removeEmptyLines(str); - } - text->setText(str); - } - return true; - } -}; diff --git a/src/Actions/ActUIText.h b/src/Actions/ActUIText.h new file mode 100755 index 00000000..09f78f09 --- /dev/null +++ b/src/Actions/ActUIText.h @@ -0,0 +1,125 @@ +#pragma once + +#include "Action.h" +#include "Game.h" +#include "TextUtils.h" +#include "UIText.h" + +class ActUITextAppendText : public Action +{ +private: + std::string id; + std::string textFormat; + std::vector bindings; + TextUtils::TextOp textOp; + +public: + ActUITextAppendText(const std::string& id_, const std::string& text_, + TextUtils::TextOp textOp_) : id(id_), textFormat(text_), textOp(textOp_) {} + + ActUITextAppendText(const std::string& id_, const std::string& text_, + const std::string& query_) : id(id_), textFormat(text_), + textOp(TextUtils::TextOp::Query) + { + bindings.push_back(query_); + } + + ActUITextAppendText(const std::string& id_, const std::string& format_, + const std::vector& bindings_) : id(id_), + textFormat(format_), bindings(bindings_), + textOp(TextUtils::TextOp::FormatString) {} + + void RemoveEmptyLines() { textOp |= TextUtils::TextOp::RemoveEmptyLines; } + void Trim() { textOp |= TextUtils::TextOp::Trim; } + + virtual bool execute(Game& game) + { + auto text = game.Resources().getResource(id); + if (text != nullptr) + { + text->setText(text->getText() + + TextUtils::getText(game, textOp, textFormat, bindings)); + } + return true; + } +}; + +class ActUITextSetSpacing : public Action +{ +private: + std::string id; + int horizSpaceOffset{ 0 }; + int vertSpaceOffset{ 0 }; + bool hasHorizSpaceOffset{ false }; + bool hasVertSpaceOffset{ false }; + +public: + ActUITextSetSpacing(const std::string& id_) : id(id_) {} + + void setHorizontalSpaceOffset(int offset) + { + horizSpaceOffset = offset; + hasHorizSpaceOffset = true; + } + + void setVerticalSpaceOffset(int offset) + { + vertSpaceOffset = offset; + hasVertSpaceOffset = true; + } + + virtual bool execute(Game& game) + { + auto text = game.Resources().getResource(id); + if (text != nullptr) + { + if (hasHorizSpaceOffset == true) + { + text->setHorizontalSpaceOffset(horizSpaceOffset); + } + if (hasVertSpaceOffset == true) + { + text->setVerticalSpaceOffset(vertSpaceOffset); + } + } + return true; + } +}; + +class ActUITextSetText : public Action +{ +private: + std::string id; + std::string textFormat; + std::vector bindings; + TextUtils::TextOp textOp; + +public: + ActUITextSetText(const std::string& id_, const std::string& text_, + TextUtils::TextOp textOp_) : id(id_), textFormat(text_), textOp(textOp_) {} + + ActUITextSetText(const std::string& id_, const std::string& text_, + const std::string& query_) : id(id_), textFormat(text_), + textOp(TextUtils::TextOp::Query) + { + bindings.push_back(query_); + } + + ActUITextSetText(const std::string& id_, const std::string& format_, + const std::vector& bindings_) : id(id_), + textFormat(format_), bindings(bindings_), + textOp(TextUtils::TextOp::FormatString) {} + + void RemoveEmptyLines() { textOp |= TextUtils::TextOp::RemoveEmptyLines; } + void Trim() { textOp |= TextUtils::TextOp::Trim; } + + virtual bool execute(Game& game) + { + auto text = game.Resources().getResource(id); + if (text != nullptr) + { + text->setText(TextUtils::getText(game, textOp, textFormat, bindings)); + } + return true; + } +}; diff --git a/src/BitmapButton.cpp b/src/BitmapButton.cpp index 8ead055f..161f47ff 100755 --- a/src/BitmapButton.cpp +++ b/src/BitmapButton.cpp @@ -3,6 +3,33 @@ #include "GameUtils.h" #include "Utils.h" +std::shared_ptr BitmapButton::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("click"): + return clickAction; + case str2int16("rightClick"): + return rightClickAction; + case str2int16("doubleClick"): + return doubleClickAction; + case str2int16("clickDrag"): + return clickDragAction; + case str2int16("clickIn"): + return clickInAction; + case str2int16("clickOut"): + return clickOutAction; + case str2int16("focus"): + return focusAction; + case str2int16("hoverEnter"): + return hoverEnterAction; + case str2int16("hoverLeave"): + return hoverLeaveAction; + default: + return nullptr; + } +} + void BitmapButton::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) @@ -37,28 +64,29 @@ void BitmapButton::setAction(uint16_t nameHash16, const std::shared_ptr& } } -void BitmapButton::click(Game& game, bool playSound) +bool BitmapButton::click(Game& game, bool playSound) { - if (enabled == false) + if (enabled == true) { - return; - } - if (clickAction != nullptr) - { - if (focusEnable == true) + if (clickAction != nullptr) { - game.Resources().setFocused(this); - if (focusOnClick == true) + if (focusEnable == true) + { + game.Resources().setFocused(this); + if (focusOnClick == true) + { + game.Events().addBack(focusAction); + } + } + if (playSound == true) { - game.Events().addBack(focusAction); + game.addPlayingSound(clickSound.get()); } + game.Events().addBack(clickAction); + return true; } - if (playSound == true) - { - game.addPlayingSound(clickSound.get()); - } - game.Events().addBack(clickAction); } + return false; } void BitmapButton::focus(Game& game) const @@ -91,21 +119,10 @@ void BitmapButton::updateSize(const Game& game) } } -void BitmapButton::update(Game& game) +void BitmapButton::onHover(Game& game, bool contains) { - if (visible == false) - { - return; - } - - auto rect = sprite.getGlobalBounds(); - if (rect.contains(game.MousePositionf())) + if (contains == true) { - if (captureScrollEvent == true && - game.wasMouseScrolled() == true) - { - game.clearMouseScrolled(); - } if (hovered == false) { hovered = true; @@ -114,98 +131,258 @@ void BitmapButton::update(Game& game) game.Events().addBack(hoverEnterAction); } } - if (game.getMouseButton() == sf::Mouse::Left) + } + else + { + if (hovered == true) { - if (game.wasMouseClicked() == true) + hovered = false; + if (hoverLeaveAction != nullptr) { - game.clearMouseClicked(); - beingDragged = true; - wasClicked = true; - if (clickInAction != nullptr) - { - game.Events().addFront(clickInAction); - } - if (clickUp == false) - { - click(game, true); - if (game.wasMouseDoubleClicked() == true) - { - if (doubleClickAction != nullptr) - { - game.Events().addBack(doubleClickAction); - } - } - } + game.Events().addFront(hoverLeaveAction); } - else if (game.wasMouseReleased() == true) + } + } +} + +void BitmapButton::onMouseButtonPressed(Game& game, bool contains) +{ + if (contains == false) + { + return; + } + switch (game.MousePress().button) + { + case sf::Mouse::Left: + { + beingDragged = true; + wasLeftClicked = true; + + if (clickInAction != nullptr) + { + game.Events().addBack(clickInAction); + } + if (clickUp == false) + { + if (doubleClickAction != nullptr && + mouseDblClickClock.restart().asMilliseconds() <= GameUtils::DoubleClickDelay) { - if (clickOutAction != nullptr) - { - game.Events().addFront(clickOutAction); - } - if (clickUp == true && beingDragged == true) + game.clearMousePressed(); + game.Events().addBack(doubleClickAction); + } + else + { + if (click(game, true) == true) { - click(game, true); + game.clearMousePressed(); } - beingDragged = false; } } - else if (game.getMouseButton() == sf::Mouse::Right) + } + break; + case sf::Mouse::Right: + { + wasRightClicked = true; + if (clickUp == false) { - if (game.wasMouseClicked() == true) + if (rightClickAction != nullptr) { - game.clearMouseClicked(); - if (clickUp == false) - { - if (rightClickAction != nullptr) - { - game.Events().addFront(rightClickAction); - } - } + game.clearMousePressed(); + game.Events().addBack(rightClickAction); } - else if (game.wasMouseReleased() == true) + } + } + break; + default: + break; + } +} + +void BitmapButton::onMouseButtonReleased(Game& game, bool contains) +{ + switch (game.MouseRelease().button) + { + case sf::Mouse::Left: + { + if (wasLeftClicked == false) + { + break; + } + wasLeftClicked = false; + if (clickUp == true && + contains == true) + { + if (click(game, true) == true) { - if (clickUp == true) - { - if (rightClickAction != nullptr) - { - game.Events().addFront(rightClickAction); - } - } + game.clearMouseReleased(); + } + } + beingDragged = false; + if (clickOutAction != nullptr) + { + game.Events().addBack(clickOutAction); + } + } + break; + case sf::Mouse::Right: + { + if (wasRightClicked == false) + { + break; + } + wasRightClicked = false; + if (clickUp == true && + contains == true) + { + if (rightClickAction != nullptr) + { + game.clearMouseReleased(); + game.Events().addBack(rightClickAction); } } } - else + break; + default: + break; + } +} + +void BitmapButton::onMouseMoved(Game& game) +{ + if (beingDragged == true) + { + if (clickDragAction != nullptr) + { + game.Events().addBack(clickDragAction); + } + } +} + +void BitmapButton::onTouchBegan(Game& game, bool contains) +{ + if (contains == false) + { + return; + } + switch (game.TouchBegan().finger) + { + case 0: + { + beingDragged = true; + wasLeftClicked = true; + + if (clickInAction != nullptr) + { + game.Events().addBack(clickInAction); + } + } + break; + case 1: + { + wasRightClicked = true; + } + break; + default: + break; + } +} + +void BitmapButton::onTouchEnded(Game& game, bool contains) +{ + switch (game.TouchEnded().finger) + { + case 0: { - if (game.wasMouseReleased() == true && - game.getMouseButton() == sf::Mouse::Left) + if (wasLeftClicked == false) + { + break; + } + wasLeftClicked = false; + if (contains == true) { - beingDragged = false; - if (wasClicked == true) + if (doubleClickAction != nullptr && + mouseDblClickClock.restart().asMilliseconds() <= GameUtils::DoubleClickDelay) { - wasClicked = false; - if (clickOutAction != nullptr) + game.clearTouchEnded(); + game.Events().addBack(doubleClickAction); + } + else + { + if (click(game, true) == true) { - game.Events().addFront(clickOutAction); + game.clearTouchEnded(); } } } - if (hovered == true) + beingDragged = false; + if (clickOutAction != nullptr) { - hovered = false; - if (hoverLeaveAction != nullptr) - { - game.Events().addFront(hoverLeaveAction); - } + game.Events().addBack(clickOutAction); } } - if (beingDragged == true && game.wasMouseMoved() == true) + break; + case 1: { - if (clickDragAction != nullptr) + if (wasRightClicked == false) + { + break; + } + wasRightClicked = false; + if (contains == true) { - game.Events().addFront(clickDragAction); + if (rightClickAction != nullptr) + { + game.clearTouchEnded(); + game.Events().addBack(rightClickAction); + } } } + break; + default: + break; + } +} + +void BitmapButton::update(Game& game) +{ + if (visible == false) + { + return; + } + + auto contains = sprite.getGlobalBounds().contains(game.MousePositionf()); + + onHover(game, contains); + + if (game.wasMousePressed() == true) + { + onMouseButtonPressed(game, contains); + } + if (game.wasMouseReleased() == true) + { + onMouseButtonReleased(game, contains); + } + if (game.wasMouseMoved() == true) + { + onMouseMoved(game); + } + if (game.hasTouchBegan() == true) + { + onTouchBegan(game, contains); + } + if (game.hasTouchMoved() == true) + { + onMouseMoved(game); + } + if (game.hasTouchEnded() == true) + { + onTouchEnded(game, contains); + } + if (contains == true && + captureInputEvents == true) + { + game.clearInputEvents(); + } } bool BitmapButton::getProperty(const std::string& prop, Variable& var) const diff --git a/src/BitmapButton.h b/src/BitmapButton.h index a1256c7a..b325427f 100755 --- a/src/BitmapButton.h +++ b/src/BitmapButton.h @@ -23,15 +23,24 @@ class BitmapButton : public Button std::shared_ptr hoverLeaveAction; std::shared_ptr clickSound; std::shared_ptr focusSound; + sf::Clock mouseDblClickClock; bool focusEnable{ false }; bool focusOnClick{ false }; bool hovered{ false }; bool clickUp{ false }; bool beingDragged{ false }; - bool wasClicked{ false }; + bool wasLeftClicked{ false }; + bool wasRightClicked{ false }; bool visible{ true }; bool resizable{ false }; - bool captureScrollEvent{ false }; + bool captureInputEvents{ false }; + + void onHover(Game& game, bool contains); + void onMouseButtonPressed(Game& game, bool contains); + void onMouseButtonReleased(Game& game, bool contains); + void onMouseMoved(Game& game); + void onTouchBegan(Game& game, bool contains); + void onTouchEnded(Game& game, bool contains); public: sf::FloatRect getLocalBounds() const { return sprite.getLocalBounds(); } @@ -42,12 +51,13 @@ class BitmapButton : public Button bool getResizable() const { return resizable; } void setResizable(bool resizable_) { resizable = resizable_; } - bool getCaptureScrollEvent() const { return resizable; } - void setCaptureScrollEvent(bool captureScroll) { captureScrollEvent = captureScroll; } + bool getCaptureInputEvents() const { return captureInputEvents; } + void setCaptureInputEvents(bool captureEvents) { captureInputEvents = captureEvents; } + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); - virtual void click(Game& game, bool playSound); + virtual bool click(Game& game, bool playSound); virtual void enable(bool enable) { enabled = enable; } virtual void focus(Game& game) const; virtual void focusEnabled(bool focusOnClick_) { focusEnable = true; focusOnClick = focusOnClick_; } diff --git a/src/BitmapText.h b/src/BitmapText.h index 927e5829..f2b3d95f 100755 --- a/src/BitmapText.h +++ b/src/BitmapText.h @@ -40,6 +40,7 @@ class BitmapText : public DrawableText virtual std::string getText() const { return text; } + virtual std::shared_ptr getAction(uint16_t nameHash16) { return nullptr; } virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action) {} virtual void setAnchor(const Anchor anchor_) diff --git a/src/Button.h b/src/Button.h index 70f24b5f..95888022 100755 --- a/src/Button.h +++ b/src/Button.h @@ -8,7 +8,7 @@ class Button : public UIObject { public: - virtual void click(Game& game, bool playSound) = 0; + virtual bool click(Game& game, bool playSound) = 0; virtual void enable(bool enable) = 0; virtual void focus(Game& game) const = 0; virtual void focusEnabled(bool focusOnClick) = 0; diff --git a/src/Cel.cpp b/src/Cel.cpp index 807e1f83..a297b15f 100755 --- a/src/Cel.cpp +++ b/src/Cel.cpp @@ -499,8 +499,17 @@ CelFile::CelFile(const char* filename, bool isCl2_, bool isTileCel_) CelFrame CelFile::get(size_t index, const Palette& palette) const { std::vector rawImage; - auto width = getFrame(mFrames[index], palette, index, rawImage); - auto height = rawImage.size() / width; + size_t width = getFrame(mFrames[index], palette, index, rawImage); + size_t height; + if (defaultWidth > 0) + { + width = defaultWidth; + height = defaultHeight; + } + else + { + height = rawImage.size() / width; + } return CelFrame(rawImage, width, height); } @@ -573,7 +582,7 @@ size_t CelFile::readNormalFrames(sf::InputStream& file) { mFrames.push_back(std::vector(frameOffsets[i + 1] - frameOffsets[i])); - file.read(&mFrames[mFrames.size() - 1][0], frameOffsets[i + 1] - frameOffsets[i]); + file.read(&mFrames.back()[0], frameOffsets[i + 1] - frameOffsets[i]); } return numFrames; diff --git a/src/Cel.h b/src/Cel.h index cf9fcf21..3318385a 100755 --- a/src/Cel.h +++ b/src/Cel.h @@ -41,6 +41,8 @@ class CelFile private: std::vector> mFrames; size_t animLength{ 0 }; + size_t defaultWidth{ 0 }; + size_t defaultHeight{ 0 }; bool isCl2; bool isTileCel; @@ -55,6 +57,12 @@ class CelFile CelFrame get(size_t index, const Palette& palette) const; + void setDefaultSize(size_t defaultWidth_, size_t defaultHeight_) + { + defaultWidth = defaultWidth_; + defaultHeight = defaultHeight_; + } + size_t Size() const { return mFrames.size(); } ///< if normal cel file, returns same as numFrames(), for an archive, the number of frames in each subcel diff --git a/src/CelUtils.cpp b/src/CelUtils.cpp index 089abe52..1813ef27 100755 --- a/src/CelUtils.cpp +++ b/src/CelUtils.cpp @@ -3,9 +3,8 @@ #include "FileUtils.h" #include "Utils.h" -sf::Image CelUtils::loadImage(const char* fileName, const Palette& pal, bool isCl2) +sf::Image CelUtils::loadImage(const CelFile& celFile, const Palette& pal) { - CelFile celFile(fileName, isCl2, false); CelFrameCache cel(celFile, pal); size_t imgWidth = 0; @@ -40,10 +39,9 @@ sf::Image CelUtils::loadImage(const char* fileName, const Palette& pal, bool isC return img; } -sf::Image CelUtils::loadImage(const char* fileName, const Palette& pal, bool isCl2, +sf::Image CelUtils::loadImage(const CelFile& celFile, const Palette& pal, size_t& frameCountX, size_t& frameCountY) { - CelFile celFile(fileName, isCl2, false); CelFrameCache cel(celFile, pal); auto numFramesY = cel.size(); @@ -89,23 +87,19 @@ sf::Image CelUtils::loadImage(const char* fileName, const Palette& pal, bool isC return img; } -sf::Image CelUtils::loadImageFrame(const char* fileName, const Palette& pal, bool isCl2, +sf::Image CelUtils::loadImageFrame(const CelFile& celFile, const Palette& pal, size_t frameIdx) { - CelFile celFile(fileName, isCl2, false); - if (celFile.Size() > 0 && frameIdx < celFile.Size()) { return celFile.get(frameIdx, pal); } - return sf::Image(); } -sf::Image CelUtils::loadBitmapFontImage(const char* fileName, const char* fileNameBin, - const Palette& pal, bool isCl2) +sf::Image CelUtils::loadBitmapFontImage(const CelFile& celFile, + const char* fileNameBin, const Palette& pal) { - CelFile celFile(fileName, isCl2, false); CelFrameCache cel(celFile, pal); auto celSize = cel.size(); diff --git a/src/CelUtils.h b/src/CelUtils.h index 63df9abe..8a7b746c 100755 --- a/src/CelUtils.h +++ b/src/CelUtils.h @@ -1,17 +1,18 @@ #pragma once #include +#include "Cel.h" #include "Palette.h" namespace CelUtils { - sf::Image loadImage(const char* fileName, const Palette& pal, bool isCl2); - sf::Image loadImage(const char* fileName, const Palette& pal, bool isCl2, + sf::Image loadImage(const CelFile& celFile, const Palette& pal); + sf::Image loadImage(const CelFile& celFile, const Palette& pal, size_t& frameCountX, size_t& frameCountY); - sf::Image loadImageFrame(const char* fileName, const Palette& pal, bool isCl2, + sf::Image loadImageFrame(const CelFile& celFile, const Palette& pal, size_t frameIdx); - sf::Image loadBitmapFontImage(const char* fileName, const char* fileNameBin, - const Palette& pal, bool isCl2); + sf::Image loadBitmapFontImage(const CelFile& celFile, const char* fileNameBin, + const Palette& pal); } diff --git a/src/Circle.h b/src/Circle.h index e80486c9..28774c07 100755 --- a/src/Circle.h +++ b/src/Circle.h @@ -12,6 +12,7 @@ class Circle : public sf::CircleShape, public UIObject public: Circle(float radius = 0, std::size_t pointCount = 30) : sf::CircleShape(radius, pointCount) {} + virtual std::shared_ptr getAction(uint16_t nameHash16) { return nullptr; } virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action) {} virtual void setAnchor(const Anchor anchor_) { anchor = anchor_; } diff --git a/src/FadeInOut.cpp b/src/FadeInOut.cpp index cc779570..e80d7da7 100755 --- a/src/FadeInOut.cpp +++ b/src/FadeInOut.cpp @@ -44,7 +44,7 @@ void FadeInOut::update(Game& game) if (updateEnableInput == true) { updateEnableInput = false; - game.enableInput(enableInput); + game.EnableInput(enableInput); } currentTime += game.getElapsedTime(); @@ -63,7 +63,7 @@ void FadeInOut::update(Game& game) } if (enableInput == false) { - game.enableInput(true); + game.EnableInput(true); } game.setFadeInOut(nullptr); } diff --git a/src/Game.cpp b/src/Game.cpp index 0fa867a4..6ac13347 100755 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -53,16 +53,7 @@ void Game::play() while (window.isOpen() == true) { - mouseClicked = false; - mouseDoubleClicked = false; - mouseMoved = false; - mouseReleased = false; - mouseScrolled = false; - keyboardChar = 0; - keyPressed.code = sf::Keyboard::Unknown; - processEvents(); - checkKeyPress(); window.clear(); windowTex.clear(); @@ -85,6 +76,13 @@ void Game::play() void Game::processEvents() { + mousePressed = false; + mouseReleased = false; + mouseMoved = false; + mouseScrolled = false; + keyPressed = false; + textEntered = false; + sf::Event evt; while (window.pollEvent(evt)) { @@ -120,7 +118,6 @@ void Game::processEvents() case sf::Event::MouseMoved: onMouseMoved(evt.mouseMove); break; -#ifdef __ANDROID__ case sf::Event::TouchBegan: onTouchBegan(evt.touch); break; @@ -130,7 +127,6 @@ void Game::processEvents() case sf::Event::TouchEnded: onTouchEnded(evt.touch); break; -#endif default: break; } @@ -233,117 +229,109 @@ void Game::onGainedFocus() void Game::onTextEntered(const sf::Event::TextEvent& evt) { - if (disableInput == true) + if (enableInput == false) { return; } - if (evt.unicode < 256) - { - keyboardChar = static_cast(evt.unicode); - } + textEnteredEvt = evt; + textEntered = true; } void Game::onKeyPressed(const sf::Event::KeyEvent& evt) { - if (disableInput == true) + if (enableInput == false) { return; } - keyPressed = evt; + keyPressEvt = evt; + keyPressed = true; #ifdef __ANDROID__ - keyPressed.system = false; + keyPressEvt.system = false; #endif + if (loadingScreen != nullptr) + { + return; + } + auto action = resourceManager.getKeyboardAction(keyPressEvt); + if (action != nullptr) + { + eventManager.addBack(action); + } } void Game::onMouseWheelScrolled(const sf::Event::MouseWheelScrollEvent& evt) { - if (disableInput == true) + if (enableInput == false) { return; } - updateMouse(); - mouseWheel = evt; - mouseWheel.x = mousePositioni.x; - mouseWheel.y = mousePositioni.y; + mouseScrollEvt = evt; mouseScrolled = true; + updateMouse(); } void Game::onMouseButtonPressed(const sf::Event::MouseButtonEvent& evt) { - if (disableInput == true) + if (enableInput == false) { return; } - mouseButton = evt.button; - mouseClicked = true; + mousePressEvt = evt; mousePressed = true; - - auto mouseClickElapsedTime = mouseClickClock.restart(); - if (mouseClickElapsedTime.asMilliseconds() <= GameUtils::DoubleClickDelay) - { - mouseDoubleClicked = true; - } } void Game::onMouseButtonReleased(const sf::Event::MouseButtonEvent& evt) { - if (disableInput == true) + if (enableInput == false) { return; } - mouseButton = evt.button; - mousePressed = false; + mouseReleaseEvt = evt; mouseReleased = true; } void Game::onMouseMoved(const sf::Event::MouseMoveEvent& evt) { updateMouse(sf::Vector2i(evt.x, evt.y)); - if (disableInput == true) + if (enableInput == false) { return; } + mouseMoveEvt = evt; mouseMoved = true; } -#ifdef __ANDROID__ + void Game::onTouchBegan(const sf::Event::TouchEvent& evt) { - if (disableInput == true) + if (enableInput == false) { return; } - updateMouse(sf::Vector2i(evt.x, evt.y)); - sf::Event::MouseButtonEvent mouseEvt; - mouseEvt.button = sf::Mouse::Left; - mouseEvt.x = evt.x; - mouseEvt.y = evt.y; - onMouseButtonPressed(mouseEvt); + touchBeganEvt = evt; + touchBegan = true; } void Game::onTouchMoved(const sf::Event::TouchEvent& evt) { updateMouse(sf::Vector2i(evt.x, evt.y)); - if (disableInput == true) + if (enableInput == false) { return; } - mouseMoved = true; + touchMovedEvt = evt; + touchMoved = true; } void Game::onTouchEnded(const sf::Event::TouchEvent& evt) { - if (disableInput == true) + if (enableInput == false) { return; } - updateMouse(sf::Vector2i(evt.x, evt.y)); - sf::Event::MouseButtonEvent mouseEvt; - mouseEvt.button = sf::Mouse::Left; - mouseEvt.x = evt.x; - mouseEvt.y = evt.y; - onMouseButtonReleased(mouseEvt); + touchEndedEvt = evt; + touchEnded = true; } -#endif + void Game::updateMouse() { updateMouse(sf::Mouse::getPosition(window)); @@ -368,18 +356,6 @@ void Game::updateCursorPosition() } } -void Game::checkKeyPress() -{ - if (loadingScreen == nullptr && keyPressed.code != sf::Keyboard::Unknown) - { - auto action = resourceManager.getKeyboardAction(keyPressed); - if (action != nullptr) - { - eventManager.addBack(action); - } - } -} - void Game::updateEvents() { if (paused == false) diff --git a/src/Game.h b/src/Game.h index f9c4d0bc..2aa6fe3d 100755 --- a/src/Game.h +++ b/src/Game.h @@ -31,23 +31,31 @@ class Game : public sf::NonCopyable, public Queryable bool smoothScreen{ false }; bool stretchToFit{ false }; bool keepAR{ true }; - bool disableInput{ false }; + bool enableInput{ true }; bool pauseOnFocusLoss{ false }; bool paused{ false }; sf::Vector2i mousePositioni; sf::Vector2f mousePositionf; - sf::Mouse::Button mouseButton; - sf::Event::MouseWheelScrollEvent mouseWheel; - sf::Clock mouseClickClock; - bool mouseClicked{ false }; - bool mouseDoubleClicked{ false }; - bool mouseMoved{ false }; + + sf::Event::MouseButtonEvent mousePressEvt; + sf::Event::MouseButtonEvent mouseReleaseEvt; + sf::Event::MouseMoveEvent mouseMoveEvt; + sf::Event::MouseWheelScrollEvent mouseScrollEvt; + sf::Event::KeyEvent keyPressEvt; + sf::Event::TextEvent textEnteredEvt; + sf::Event::TouchEvent touchBeganEvt; + sf::Event::TouchEvent touchMovedEvt; + sf::Event::TouchEvent touchEndedEvt; bool mousePressed{ false }; bool mouseReleased{ false }; + bool mouseMoved{ false }; bool mouseScrolled{ false }; - char keyboardChar{ false }; - sf::Event::KeyEvent keyPressed; + bool keyPressed{ false }; + bool textEntered{ false }; + bool touchBegan{ false }; + bool touchMoved{ false }; + bool touchEnded{ false }; unsigned musicVolume{ 100 }; unsigned soundVolume{ 100 }; @@ -71,20 +79,18 @@ class Game : public sf::NonCopyable, public Queryable void onResized(const sf::Event::SizeEvent& evt); void onLostFocus(); void onGainedFocus(); + void onTextEntered(const sf::Event::TextEvent& evt); void onKeyPressed(const sf::Event::KeyEvent& evt); void onMouseWheelScrolled(const sf::Event::MouseWheelScrollEvent& evt); void onMouseButtonPressed(const sf::Event::MouseButtonEvent& evt); void onMouseButtonReleased(const sf::Event::MouseButtonEvent& evt); void onMouseMoved(const sf::Event::MouseMoveEvent& evt); -#ifdef __ANDROID__ void onTouchBegan(const sf::Event::TouchEvent& evt); void onTouchMoved(const sf::Event::TouchEvent& evt); void onTouchEnded(const sf::Event::TouchEvent& evt); -#endif void updateMouse(const sf::Vector2i mousePos); - void checkKeyPress(); void updateEvents(); void drawCursor(); void drawAndUpdate(); @@ -153,18 +159,49 @@ class Game : public sf::NonCopyable, public Queryable const sf::Vector2i& MousePositioni() const { return mousePositioni; } const sf::Vector2f& MousePositionf() const { return mousePositionf; } - sf::Mouse::Button getMouseButton() { return mouseButton; } - const sf::Event::MouseWheelScrollEvent& getMouseWheelScroll() const { return mouseWheel; } - void clearMouseClicked() { mouseClicked = false; } - void clearMouseDoubleClicked() { mouseDoubleClicked = false; } + + const sf::Event::MouseButtonEvent& MousePress() { return mousePressEvt; } + const sf::Event::MouseButtonEvent& MouseRelease() { return mouseReleaseEvt; } + const sf::Event::MouseMoveEvent& MouseMove() { return mouseMoveEvt; } + const sf::Event::MouseWheelScrollEvent& MouseScroll() { return mouseScrollEvt; } + const sf::Event::KeyEvent& KeyPress() { return keyPressEvt; } + const sf::Event::TextEvent& TextEntered() { return textEnteredEvt; } + const sf::Event::TouchEvent& TouchBegan() { return touchBeganEvt; } + const sf::Event::TouchEvent& TouchMoved() { return touchMovedEvt; } + const sf::Event::TouchEvent& TouchEnded() { return touchEndedEvt; } + + bool wasMousePressed() { return mousePressed; } + bool wasMouseReleased() { return mouseReleased; } + bool wasMouseMoved() { return mouseMoved; } + bool wasMouseScrolled() { return mouseScrolled; } + bool wasKeyPressed() { return keyPressed; } + bool wasTextEntered() { return textEntered; } + bool hasTouchBegan() { return touchBegan; } + bool hasTouchMoved() { return touchMoved; } + bool hasTouchEnded() { return touchEnded; } + + void clearMousePressed() { mousePressed = false; } + void clearMouseReleased() { mouseReleased = false; } + void clearMouseMoved() { mouseMoved = false; } void clearMouseScrolled() { mouseScrolled = false; } - bool wasMouseClicked() const { return mouseClicked; } - bool wasMouseDoubleClicked() const { return mouseDoubleClicked; } - bool wasMouseMoved() const { return mouseMoved; } - bool isMousePressed() const { return mousePressed; } - bool wasMouseReleased() const { return mouseReleased; } - bool wasMouseScrolled() const { return mouseScrolled; } - char getKeyboardChar() const { return keyboardChar; } + void clearKeyPressed() { keyPressed = false; } + void clearTextEntered() { textEntered = false; } + void clearTouchBegan() { touchBegan = false; } + void clearTouchMoved() { touchMoved = false; } + void clearTouchEnded() { touchEnded = false; } + + void clearInputEvents() + { + mousePressed = false; + mouseReleased = false; + mouseMoved = false; + mouseScrolled = false; + keyPressed = false; + textEntered = false; + touchBegan = false; + touchMoved = false; + touchEnded = false; + } void MinWidth(unsigned width_) { size.x = width_; } void MinHeight(unsigned height_) { size.y = height_; } @@ -208,7 +245,8 @@ class Game : public sf::NonCopyable, public Queryable const std::string& getTitle() const { return title; } const std::string& getVersion() const { return version; } - void enableInput(bool enable) { disableInput = !enable; } + bool isInputEnabled() { return enableInput; } + void EnableInput(bool enable) { enableInput = enable; } sf::RenderWindow& Window() { return window; } const sf::RenderWindow& Window() const { return window; } diff --git a/src/Game/CelLevelObject.h b/src/Game/CelLevelObject.h index d664d7a4..d0f9a258 100755 --- a/src/Game/CelLevelObject.h +++ b/src/Game/CelLevelObject.h @@ -15,7 +15,6 @@ class CelLevelObject : public LevelObject MapCoord mapPosition; size_t celIdx{ 0 }; - size_t palette{ 0 }; CelTextureCacheVector* celTexture{ nullptr }; std::pair frameRange; @@ -45,7 +44,7 @@ class CelLevelObject : public LevelObject virtual void MapPosition(const MapCoord& pos) { mapPosition = pos; } virtual void executeAction(Game& game) const; - virtual bool getNumberProp(const std::string& prop, Number32& value) const { return false; } + virtual bool getNumberProp(const char* prop, Number32& value) const { return false; } virtual bool Passable() const { return true; } virtual void setAction(const std::shared_ptr& action_) { action = action_; } diff --git a/src/Game/ImageLevelObject.h b/src/Game/ImageLevelObject.h index bfc13e68..392ce8e7 100755 --- a/src/Game/ImageLevelObject.h +++ b/src/Game/ImageLevelObject.h @@ -15,7 +15,6 @@ class ImageLevelObject : public LevelObject MapCoord mapPosition; std::pair frameRange; - size_t currentFrame{ 0 }; sf::Time frameTime{ sf::milliseconds(50) }; sf::Time currentTime; diff --git a/src/Game/Item.cpp b/src/Game/Item.cpp index 4eee5301..666b4f0d 100755 --- a/src/Game/Item.cpp +++ b/src/Game/Item.cpp @@ -181,6 +181,9 @@ bool Item::getProperty(const std::string& prop, Variable& var) const case str2int16("itemSubType"): var = Variable(ItemSubType()); break; + case str2int16("isUsable"): + var = Variable(isUsable()); + break; case str2int16("needsRecharge"): var = Variable(needsRecharge()); break; @@ -228,11 +231,10 @@ void Item::setProperty(const std::string& prop, const Variable& val) setIntByHash(str2int16(prop.c_str()), val2); } -bool Item::hasInt(const char* prop) const +bool Item::hasIntByHash(uint16_t propHash) const { if (propertiesSize > 0) { - auto propHash = str2int16(prop); for (size_t i = 0; i < propertiesSize; i++) { if (properties[i].first == propHash) @@ -244,6 +246,11 @@ bool Item::hasInt(const char* prop) const return false; } +bool Item::hasInt(const char* prop) const +{ + return hasIntByHash(str2int16(prop)); +} + LevelObjValue Item::getIntByHash(uint16_t propHash) const { LevelObjValue value = 0; @@ -398,6 +405,11 @@ bool Item::needsRepair() const return false; } +bool Item::isUsable() const +{ + return hasIntByHash(ItemProp::UseOn); +} + bool Item::useHelper(uint16_t propHash, uint16_t useOpHash, LevelObjValue value, Player& player) const { diff --git a/src/Game/Item.h b/src/Game/Item.h index 35d5fc34..e016ba6e 100755 --- a/src/Game/Item.h +++ b/src/Game/Item.h @@ -119,6 +119,7 @@ class Item : public LevelObject const ItemClass* Class() const { return class_; } + bool hasIntByHash(uint16_t propHash) const; bool hasInt(const char* prop) const; bool hasInt(const std::string& prop) const { @@ -149,6 +150,7 @@ class Item : public LevelObject bool needsRecharge() const; bool needsRepair() const; + bool isUsable() const; bool use(Player& player) const; diff --git a/src/Game/ItemClass.cpp b/src/Game/ItemClass.cpp index 015d1ff4..5b414fd4 100755 --- a/src/Game/ItemClass.cpp +++ b/src/Game/ItemClass.cpp @@ -148,11 +148,11 @@ bool ItemClass::getFullName(const Queryable& item, std::string& fullName) const return true; } -void ItemClass::setDescription(size_t idx, const std::shared_ptr& namer) +void ItemClass::setDescription(size_t idx, const std::shared_ptr& namer, uint16_t skipFirst) { if (idx < descriptions.size()) { - descriptions[idx] = namer; + descriptions[idx] = std::make_pair(namer, skipFirst); } } @@ -162,12 +162,12 @@ bool ItemClass::getDescription(size_t idx, const Queryable& item, std::string& d { return false; } - auto namer = descriptions[idx].get(); + auto namer = descriptions[idx].first.get(); if (namer == nullptr) { return false; } - description = namer->getName(item); + description = namer->getName(item, descriptions[idx].second); if (description.empty() == false) { description = GameUtils::replaceStringWithQueryable(description, item); diff --git a/src/Game/ItemClass.h b/src/Game/ItemClass.h index 9da373e7..a3b92cf5 100755 --- a/src/Game/ItemClass.h +++ b/src/Game/ItemClass.h @@ -35,7 +35,7 @@ class ItemClass std::shared_ptr prefix; std::shared_ptr suffix; - std::array, 5> descriptions; + std::array, uint16_t>, 5> descriptions; std::array, 6> formulas; size_t formulasSize{ 0 }; @@ -128,7 +128,7 @@ class ItemClass bool getFullName(const Queryable& item, std::string& fullName) const; - void setDescription(size_t idx, const std::shared_ptr& namer); + void setDescription(size_t idx, const std::shared_ptr& namer, uint16_t skipFirst); bool getDescription(size_t idx, const Queryable& item, std::string& description) const; diff --git a/src/Game/ItemLocation.h b/src/Game/ItemLocation.h index 4ed58568..673f41fe 100755 --- a/src/Game/ItemLocation.h +++ b/src/Game/ItemLocation.h @@ -23,7 +23,7 @@ class ItemCoordInventory ItemCoordInventory(const std::string& playerId_, size_t inventoryIdx_, size_t itemIdx_) : playerId(playerId_), inventoryIdx(((int16_t)inventoryIdx_) & 0x7FFF), - itemIdx(itemIdx_) {} + itemIdx((int16_t)itemIdx_) {} ItemCoordInventory(const std::string& playerId_, size_t inventoryIdx_, const ItemXY& itemXY_) : playerId(playerId_), diff --git a/src/Game/Level.cpp b/src/Game/Level.cpp index a25ca359..74d5b0d8 100755 --- a/src/Game/Level.cpp +++ b/src/Game/Level.cpp @@ -13,6 +13,28 @@ void Level::Init(const LevelMap& map_, Min& min_, CelFrameCache& cel_) hoverObject = nullptr; } +std::shared_ptr Level::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("click"): + case str2int16("leftClick"): + return leftAction; + case str2int16("rightClick"): + return rightAction; + case str2int16("hoverEnter"): + return hoverEnterAction; + case str2int16("hoverLeave"): + return hoverLeaveAction; + case str2int16("scrollDown"): + return scrollDownAction; + case str2int16("scrollUp"): + return scrollUpAction; + default: + return nullptr; + } +} + void Level::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) @@ -218,56 +240,120 @@ void Level::updateMouse(const Game& game) mapCoordOverMouse = map.getTile(mousePositionf); } +void Level::onMouseButtonPressed(Game& game) +{ + game.clearMousePressed(); + switch (game.MousePress().button) + { + case sf::Mouse::Left: + { + clickedMapPosition = getMapCoordOverMouse(); + clickedObject = nullptr; + if (leftAction != nullptr) + { + game.Events().addBack(leftAction); + } + } + break; + case sf::Mouse::Right: + { + if (rightAction != nullptr) + { + game.Events().addBack(rightAction); + } + } + break; + default: + break; + } +} + +void Level::onMouseScrolled(Game& game) +{ + game.clearMouseScrolled(); + if (game.MouseScroll().delta < 0.f) + { + if (scrollDownAction != nullptr) + { + game.Events().addBack(scrollDownAction); + } + } + else + { + if (scrollUpAction != nullptr) + { + game.Events().addBack(scrollUpAction); + } + } +} + +void Level::onTouchBegan(Game& game) +{ + game.clearTouchBegan(); + switch (game.TouchBegan().finger) + { + case 0: + { + clickedMapPosition = getMapCoordOverMouse(); + clickedObject = nullptr; + if (leftAction != nullptr) + { + game.Events().addBack(leftAction); + } + } + break; + case 1: + { + if (rightAction != nullptr) + { + game.Events().addBack(rightAction); + } + } + break; + default: + break; + } +} + void Level::update(Game& game) { - if (pause == true || visible == false) + if (visible == false) + { + return; + } + if (updateView == true) + { + updateView = false; + view.updateSize(game); + } + if (pause == true) { return; } updateZoom(game); - updateMouse(game); sf::FloatRect rect(view.getPosition(), view.getSize()); if (rect.contains(game.MousePositionf()) == true) { hasMouseInside = true; - if (game.wasMouseClicked() == true && - game.getMouseButton() == sf::Mouse::Left) + + if (game.wasMousePressed() == true) { - clickedMapPosition = getMapCoordOverMouse(); - clickedObject = nullptr; - if (leftAction != nullptr) - { - game.Events().addBack(leftAction); - } + onMouseButtonPressed(game); } - else if (game.wasMouseReleased() == true && - game.getMouseButton() == sf::Mouse::Right && - rightAction != nullptr) + if (game.wasMouseScrolled() == true) { - game.Events().addBack(rightAction); + onMouseScrolled(game); } - - if (game.wasMouseScrolled() == true) + if (game.hasTouchBegan() == true) { - game.clearMouseScrolled(); - const auto& scroll = game.getMouseWheelScroll(); - if (scroll.delta < 0.f) - { - if (scrollDownAction != nullptr) - { - game.Events().addBack(scrollDownAction); - } - } - else - { - if (scrollUpAction != nullptr) - { - game.Events().addBack(scrollUpAction); - } - } + onTouchBegan(game); + } + if (captureInputEvents == true) + { + game.clearInputEvents(); } } else diff --git a/src/Game/Level.h b/src/Game/Level.h index 8c11669b..e1ea4f87 100755 --- a/src/Game/Level.h +++ b/src/Game/Level.h @@ -24,6 +24,7 @@ class Level : public UIObject { private: View2 view; + bool updateView{ false }; float currentZoomFactor{ 1.f }; float startZoomFactor{ 1.f }; float stopZoomFactor{ 1.f }; @@ -69,6 +70,7 @@ class Level : public UIObject bool pause{ false }; bool visible{ true }; + bool captureInputEvents{ true }; std::vector quests; @@ -81,6 +83,10 @@ class Level : public UIObject void updateMouse(const Game& game); + void onMouseButtonPressed(Game& game); + void onMouseScrolled(Game& game); + void onTouchBegan(Game& game); + public: void Init(const LevelMap& map, Min& min, CelFrameCache& cel); @@ -177,6 +183,7 @@ class Level : public UIObject LevelObject* getHoverObject() const { return hoverObject; } void setHoverObject(LevelObject* object) { hoverObject = object; } + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); virtual void setAnchor(const Anchor anchor) { view.setAnchor(anchor); } @@ -184,9 +191,17 @@ class Level : public UIObject virtual const sf::Vector2f& DrawPosition() const { return view.getPosition(); } virtual const sf::Vector2f& Position() const { return view.getPosition(); } - virtual void Position(const sf::Vector2f& position) { view.setPosition(position); } + virtual void Position(const sf::Vector2f& position) + { + view.setPosition(position); + updateView = true; + } virtual sf::Vector2f Size() const { return view.getSize(); } - virtual void Size(const sf::Vector2f& size) { view.setSize(size); } + virtual void Size(const sf::Vector2f& size) + { + view.setSize(size); + updateView = true; + } float Zoom() const { return stopZoomFactor; } void Zoom(float factor, bool smooth = false); @@ -232,6 +247,9 @@ class Level : public UIObject virtual bool Visible() const { return visible; } virtual void Visible(bool visible_) { visible = visible_; } + bool getCaptureInputEvents() const { return captureInputEvents; } + void setCaptureInputEvents(bool captureEvents) { captureInputEvents = captureEvents; } + virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; virtual void update(Game& game); virtual bool getProperty(const std::string& prop, Variable& var) const; diff --git a/src/Game/Namer.cpp b/src/Game/Namer.cpp index 9c534859..7a6da187 100755 --- a/src/Game/Namer.cpp +++ b/src/Game/Namer.cpp @@ -1,6 +1,6 @@ #include "Namer.h" -std::string Namer::getName(const Queryable& obj) const +std::string Namer::getName(const Queryable& obj, uint16_t skipFirst) const { std::string strName; bool hasName = false; @@ -35,8 +35,15 @@ std::string Namer::getName(const Queryable& obj) const { if (value >= itemElem.min && value <= itemElem.max) { - strName = itemElem.text; - hasName = true; + if (skipFirst > 0) + { + skipFirst--; + } + else + { + strName = itemElem.text; + hasName = true; + } break; } } diff --git a/src/Game/Namer.h b/src/Game/Namer.h index f51305a0..710c61e7 100755 --- a/src/Game/Namer.h +++ b/src/Game/Namer.h @@ -26,5 +26,5 @@ class Namer public: Namer(const std::vector& nameValues_) : nameValues(nameValues_) {} - std::string getName(const Queryable& obj) const; + std::string getName(const Queryable& obj, uint16_t skipFirst = 0) const; }; diff --git a/src/Game/Player.h b/src/Game/Player.h index f3884b09..f25a687b 100755 --- a/src/Game/Player.h +++ b/src/Game/Player.h @@ -24,7 +24,7 @@ class Player : public LevelObject PlayerDirection direction{ PlayerDirection::All }; PlayerStatus status{ PlayerStatus::Size }; - uint8_t restStatus{ 0 }; + uint16_t restStatus{ 0 }; size_t celIdx{ 0 }; size_t textureIdx{ 0 }; diff --git a/src/Image.h b/src/Image.h index d048425e..adc4d5f0 100755 --- a/src/Image.h +++ b/src/Image.h @@ -25,6 +25,7 @@ class Image : public UIObject void setTexture(const sf::Texture& texture, bool resetRect = false) { sprite.setTexture(texture, resetRect); } void setTextureRect(const sf::IntRect& rectangle) { sprite.setTextureRect(rectangle); } + virtual std::shared_ptr getAction(uint16_t nameHash16) { return nullptr; } virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action) {} virtual void setAnchor(const Anchor anchor_) { anchor = anchor_; } diff --git a/src/InputText.cpp b/src/InputText.cpp index 9915f101..84a03525 100755 --- a/src/InputText.cpp +++ b/src/InputText.cpp @@ -3,6 +3,22 @@ #include "GameUtils.h" #include "Utils.h" +std::shared_ptr InputText::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("change"): + return actionChange; + case str2int16("click"): + case str2int16("enter"): + return actionEnter; + case str2int16("minSize"): + return actionMinSize; + default: + return nullptr; + } +} + void InputText::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) @@ -35,11 +51,17 @@ void InputText::click(Game& game) void InputText::update(Game& game) { - while (true) + if (text->Visible() == false) { - auto ch = game.getKeyboardChar(); - if (ch != 0) + return; + } + if (game.wasTextEntered() == true && + game.TextEntered().unicode < 256 && + game.TextEntered().unicode != 0) + { + while (true) { + auto ch = static_cast(game.TextEntered().unicode); auto txt = text->getText(); if (ch == 8 && txt.size() > 0) // backspace @@ -65,8 +87,8 @@ void InputText::update(Game& game) { game.Events().addBack(actionChange); } + break; } - break; } text->update(game); } diff --git a/src/InputText.h b/src/InputText.h index 46b050ca..88e1035a 100755 --- a/src/InputText.h +++ b/src/InputText.h @@ -29,6 +29,7 @@ class InputText : public UIObject sf::FloatRect getLocalBounds() const { return text->getLocalBounds(); } sf::FloatRect getGlobalBounds() const { return text->getGlobalBounds(); } + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); virtual void setAnchor(const Anchor anchor_) { text->setAnchor(anchor_); } diff --git a/src/Menu.cpp b/src/Menu.cpp index 3845b3d8..632d6c11 100755 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -3,6 +3,19 @@ #include "GameUtils.h" #include "Utils.h" +std::shared_ptr Menu::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("scrollDown"): + return scrollDownAction; + case str2int16("scrollUp"): + return scrollUpAction; + default: + return nullptr; + } +} + void Menu::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) @@ -177,7 +190,7 @@ void Menu::update(Game& game) if (game.wasMouseScrolled() == true) { - const auto& scroll = game.getMouseWheelScroll(); + const auto& scroll = game.MouseScroll(); if (scrollRect.contains(scroll.x, scroll.y)) { game.clearMouseScrolled(); @@ -218,6 +231,20 @@ bool Menu::getProperty(const std::string& prop, Variable& var) const case str2int16("visibleItems"): var = Variable((int64_t)visibleItems); break; + case str2int16("item"): + { + auto props2 = Utils::splitStringIn2(props.second, '.'); + auto btnIdx = std::strtoul(props2.first.c_str(), NULL, 10); + if (btnIdx < items.size()) + { + auto button = items[btnIdx].get(); + if (button != nullptr) + { + return button->getProperty(props2.second, var); + } + } + return false; + } default: return GameUtils::getUIObjProp(*this, propHash, props.second, var); } diff --git a/src/Menu.h b/src/Menu.h index 94246c15..ca1b33f0 100755 --- a/src/Menu.h +++ b/src/Menu.h @@ -140,6 +140,7 @@ class Menu : public UIObject } } + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); virtual void setAnchor(const Anchor anchor_) diff --git a/src/Movie2.cpp b/src/Movie2.cpp index bb29c4e2..81657364 100755 --- a/src/Movie2.cpp +++ b/src/Movie2.cpp @@ -4,6 +4,17 @@ #include "GameUtils.h" #include "Utils.h" +std::shared_ptr Movie2::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("complete"): + return actionComplete; + default: + return nullptr; + } +} + void Movie2::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) diff --git a/src/Movie2.h b/src/Movie2.h index f972c7fd..ef514090 100755 --- a/src/Movie2.h +++ b/src/Movie2.h @@ -5,7 +5,7 @@ #include "Actions/Action.h" #include "PhysFSStream.h" #include -#include +#include "sfeMovie/Movie.hpp" #include "UIObject.h" class Movie2 : public UIObject @@ -39,7 +39,8 @@ class Movie2 : public UIObject void pause() { movie.pause(); } void setVolume(float volume) { movie.setVolume(volume); } - + + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); virtual void setAnchor(const Anchor anchor_) { anchor = anchor_; } diff --git a/src/MovieStub.cpp b/src/MovieStub.cpp index 93f85dbd..e8dbd257 100755 --- a/src/MovieStub.cpp +++ b/src/MovieStub.cpp @@ -4,6 +4,17 @@ #include "GameUtils.h" #include "Utils.h" +std::shared_ptr Movie2::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("complete"): + return actionComplete; + default: + return nullptr; + } +} + void Movie2::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) diff --git a/src/MovieStub.h b/src/MovieStub.h index 3f7e782b..a52ea147 100755 --- a/src/MovieStub.h +++ b/src/MovieStub.h @@ -23,6 +23,7 @@ class Movie2 : public UIObject void setVolume(float volume) {} + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); virtual void setAnchor(const Anchor anchor_) { anchor = anchor_; } diff --git a/src/Parser/Game/ParseItemClass.cpp b/src/Parser/Game/ParseItemClass.cpp index c2e21ed1..6cf57c86 100755 --- a/src/Parser/Game/ParseItemClass.cpp +++ b/src/Parser/Game/ParseItemClass.cpp @@ -8,6 +8,15 @@ namespace Parser { using namespace rapidjson; + void parseDescriptionValue(ItemClass& itemClass, + const Level& level, const Value& elem) + { + itemClass.setDescription( + getUIntKey(elem, "index"), + level.getNamer(getStringKey(elem, "name")), + (uint16_t)getUIntKey(elem, "skip")); + } + std::shared_ptr parseItemClassHelper(const Game& game, const Level& level, const Value& elem, std::string& id) { @@ -203,6 +212,22 @@ namespace Parser } } + if (elem.HasMember("descriptions") == true) + { + const auto& descriptions = elem["descriptions"]; + if (descriptions.IsObject() == true) + { + parseDescriptionValue(*itemClass, *level, descriptions); + } + else if (descriptions.IsArray() == true) + { + for (const auto& val : descriptions) + { + parseDescriptionValue(*itemClass, *level, val); + } + } + } + if (elem.HasMember("prefix") == true) { auto namer = level->getNamer(getStringVal(elem["prefix"])); @@ -214,32 +239,6 @@ namespace Parser itemClass->setSuffix(namer); } - if (elem.HasMember("description1") == true) - { - auto namer = level->getNamer(getStringVal(elem["description1"])); - itemClass->setDescription(0, namer); - } - if (elem.HasMember("description2") == true) - { - auto namer = level->getNamer(getStringVal(elem["description2"])); - itemClass->setDescription(1, namer); - } - if (elem.HasMember("description3") == true) - { - auto namer = level->getNamer(getStringVal(elem["description3"])); - itemClass->setDescription(2, namer); - } - if (elem.HasMember("description4") == true) - { - auto namer = level->getNamer(getStringVal(elem["description4"])); - itemClass->setDescription(3, namer); - } - if (elem.HasMember("description5") == true) - { - auto namer = level->getNamer(getStringVal(elem["description5"])); - itemClass->setDescription(4, namer); - } - level->addItemClass(id, itemClass); } } diff --git a/src/Parser/Game/ParseLevel.cpp b/src/Parser/Game/ParseLevel.cpp index d9328748..a7d8f11f 100755 --- a/src/Parser/Game/ParseLevel.cpp +++ b/src/Parser/Game/ParseLevel.cpp @@ -138,6 +138,10 @@ namespace Parser level->resetView(); level->updateViewport(game); + if (elem.HasMember("captureInputEvents")) + { + level->setCaptureInputEvents(getBoolVal(elem["captureInputEvents"])); + } if (elem.HasMember("onLeftClick")) { level->setAction(str2int16("leftClick"), parseAction(game, elem["onLeftClick"])); diff --git a/src/Parser/ParseAction.cpp b/src/Parser/ParseAction.cpp index a30406d1..2f24c805 100755 --- a/src/Parser/ParseAction.cpp +++ b/src/Parser/ParseAction.cpp @@ -26,6 +26,7 @@ #include "Actions/ActSound.h" #include "Actions/ActText.h" #include "Actions/ActTexture.h" +#include "Actions/ActUIText.h" #include "Actions/ActVariable.h" #include "Actions/ActVisibility.h" #include "GameUtils.h" @@ -38,6 +39,100 @@ namespace Parser { using namespace rapidjson; + template + std::shared_ptr parseSetTextHelper(Game& game, const Value& elem) + { + std::shared_ptr action; + if (elem.HasMember("binding") == true) + { + action = std::make_shared( + getStringKey(elem, "id"), + getStringKey(elem, "format"), + getStringVectorKey(elem, "binding")); + } + else if (elem.HasMember("query") == true) + { + action = std::make_shared( + getStringKey(elem, "id"), + getStringKey(elem, "text"), + getStringKey(elem, "query")); + } + else + { + auto textOp = TextUtils::TextOp::Replace; + if (getBoolKey(elem, "set") == true) + { + textOp = TextUtils::TextOp::Set; + } + else if (getBoolKey(elem, "replaceAll") == true) + { + textOp = TextUtils::TextOp::ReplaceAll; + } + action = std::make_shared( + getStringKey(elem, "id"), + getStringKey(elem, "text"), + textOp); + } + if (getBoolKey(elem, "removeEmptyLines") == true) + { + action->RemoveEmptyLines(); + } + if (getBoolKey(elem, "trim") == true) + { + action->Trim(); + } + return action; + } + + template + std::shared_ptr parseSetMenuTextHelper(Game& game, const Value& elem) + { + auto index = getUIntKey(elem, "index"); + std::shared_ptr action; + if (elem.HasMember("binding") == true) + { + action = std::make_shared( + getStringKey(elem, "id"), + index, + getStringKey(elem, "format"), + getStringVectorKey(elem, "binding")); + } + else if (elem.HasMember("query") == true) + { + action = std::make_shared( + getStringKey(elem, "id"), + index, + getStringKey(elem, "text"), + getStringKey(elem, "query")); + } + else + { + auto textOp = TextUtils::TextOp::Replace; + if (getBoolKey(elem, "set") == true) + { + textOp = TextUtils::TextOp::Set; + } + else if (getBoolKey(elem, "replaceAll") == true) + { + textOp = TextUtils::TextOp::ReplaceAll; + } + action = std::make_shared( + getStringKey(elem, "id"), + index, + getStringKey(elem, "text"), + textOp); + } + if (getBoolKey(elem, "removeEmptyLines") == true) + { + action->RemoveEmptyLines(); + } + if (getBoolKey(elem, "trim") == true) + { + action->Trim(); + } + return action; + } + std::shared_ptr parseActionElem(Game& game, const Value& elem) { if (elem.HasMember("name") == false || @@ -145,22 +240,6 @@ namespace Parser getStringKey(elem, "id"), getStringKey(elem, "idFont")); } - case str2int16("button.setText"): - { - if (elem.HasMember("binding") == false) - { - return std::make_shared( - getStringKey(elem, "id"), - getStringKey(elem, "text")); - } - else - { - return std::make_shared( - getStringKey(elem, "id"), - getStringKey(elem, "format"), - getStringVectorKey(elem, "binding")); - } - } case str2int16("button.setTexture"): { return std::make_shared>( @@ -201,15 +280,15 @@ namespace Parser { return std::make_shared(getStringKey(elem, "file")); } - case str2int16("drawable.addPositionOffset"): + case str2int16("drawable.addToPosition"): { - return std::make_shared( + return std::make_shared( getStringKey(elem, "id"), getVector2fKey(elem, "offset")); } - case str2int16("drawable.addSizeOffset"): + case str2int16("drawable.addToSize"): { - return std::make_shared( + return std::make_shared( getStringKey(elem, "id"), getVector2fKey(elem, "offset")); } @@ -219,8 +298,7 @@ namespace Parser getStringKey(elem, "id"), getStringKey(elem, "idAnchor"), getAnchorKey(elem, "anchor"), - getVector2fKey(elem, "offset"), - getBoolKey(elem, "addSize")); + getVector2fKey(elem, "offset")); } case str2int16("drawable.anchorSizeX"): { @@ -243,8 +321,7 @@ namespace Parser return std::make_shared( getStringKey(elem, "id"), getAnchorKey(elem, "anchor"), - getVector2fKey(elem, "offset"), - getBoolKey(elem, "addSize")); + getVector2fKey(elem, "offset")); } case str2int16("drawable.centerOnMouseX"): { @@ -266,6 +343,12 @@ namespace Parser { return std::make_shared(getStringKey(elem, "id")); } + case str2int16("drawable.executeAction"): + { + return std::make_shared( + getStringKey(elem, "id"), + str2int16(getStringCharKey(elem, "action"))); + } case str2int16("drawable.horizontalAnchorToFocused"): { return std::make_shared( @@ -789,6 +872,10 @@ namespace Parser } return std::make_shared(json); } + case str2int16("menu.appendText"): + { + return parseSetMenuTextHelper(game, elem); + } case str2int16("menu.click"): { return std::make_shared( @@ -834,10 +921,7 @@ namespace Parser } case str2int16("menu.setText"): { - return std::make_shared( - getStringKey(elem, "id"), - getUIntKey(elem, "index"), - getStringKey(elem, "text")); + return parseSetMenuTextHelper(game, elem); } case str2int16("movie.pause"): { @@ -1037,6 +1121,10 @@ namespace Parser { return getSwitchCondition(game, elem); } + case str2int16("text.appendText"): + { + return parseSetTextHelper(game, elem); + } case str2int16("text.setColor"): { return std::make_shared( @@ -1045,7 +1133,8 @@ namespace Parser } case str2int16("text.setSpacing"): { - auto action = std::make_shared(getStringKey(elem, "id")); + auto action = std::make_shared( + getStringKey(elem, "id")); if (elem.HasMember("horizontal") == true) { action->setHorizontalSpaceOffset(getIntVal(elem["horizontal"])); @@ -1058,47 +1147,7 @@ namespace Parser } case str2int16("text.setText"): { - std::shared_ptr action; - if (elem.HasMember("binding") == false) - { - action = std::make_shared( - getStringKey(elem, "id"), - getStringKey(elem, "text")); - } - else - { - action = std::make_shared( - getStringKey(elem, "id"), - getStringKey(elem, "format"), - getStringVectorKey(elem, "binding")); - } - if (elem.HasMember("horizontalSpaceOffset") == true) - { - action->setHorizontalSpaceOffset(getIntVal(elem["horizontalSpaceOffset"])); - } - if (elem.HasMember("verticalSpaceOffset") == true) - { - action->setVerticalSpaceOffset(getIntVal(elem["verticalSpaceOffset"])); - } - return action; - } - case str2int16("text.setTextFromQuery"): - { - auto action = std::make_shared( - getStringKey(elem, "id"), - getStringKey(elem, "query"), - getStringKey(elem, "text"), - getBoolKey(elem, "trim"), - getBoolKey(elem, "removeEmptyLines")); - if (elem.HasMember("horizontalSpaceOffset") == true) - { - action->setHorizontalSpaceOffset(getIntVal(elem["horizontalSpaceOffset"])); - } - if (elem.HasMember("verticalSpaceOffset") == true) - { - action->setVerticalSpaceOffset(getIntVal(elem["verticalSpaceOffset"])); - } - return action; + return parseSetTextHelper(game, elem); } case str2int16("variable.clear"): { diff --git a/src/Parser/ParseButton.cpp b/src/Parser/ParseButton.cpp index eb8acf74..300b6503 100755 --- a/src/Parser/ParseButton.cpp +++ b/src/Parser/ParseButton.cpp @@ -35,7 +35,7 @@ namespace Parser button->setTextureRect(getIntRectKey(elem, "textureRect", rect)); } button->setResizable(getBoolKey(elem, "resizable")); - button->setCaptureScrollEvent(getBoolKey(elem, "captureScrollEvent")); + button->setCaptureInputEvents(getBoolKey(elem, "captureInputEvents")); return button; } @@ -49,6 +49,7 @@ namespace Parser } auto button = std::make_shared(); button->setText(std::move(text)); + button->setCaptureInputEvents(getBoolKey(elem, "captureInputEvents")); return button; } diff --git a/src/Parser/ParseCelFile.cpp b/src/Parser/ParseCelFile.cpp index 1208846f..32fc6711 100755 --- a/src/Parser/ParseCelFile.cpp +++ b/src/Parser/ParseCelFile.cpp @@ -22,6 +22,11 @@ namespace Parser { return nullptr; } + if (elem.HasMember("celSize") == true) + { + auto size = getVector2uVal(elem["celSize"]); + celFile->setDefaultSize(size.x, size.y); + } return celFile; } diff --git a/src/Parser/ParsePredicate.cpp b/src/Parser/ParsePredicate.cpp index 35807b35..550b12f9 100755 --- a/src/Parser/ParsePredicate.cpp +++ b/src/Parser/ParsePredicate.cpp @@ -1,5 +1,6 @@ #include "ParsePredicate.h" #include "Predicates/PredIO.h" +#include "Predicates/PredItem.h" #include "Predicates/PredPlayer.h" #include "Utils.h" #include "Utils/ParseUtils.h" @@ -21,6 +22,12 @@ namespace Parser { return std::make_shared(getStringKey(elem, "file")); } + case str2int16("item.hasRequiredStats"): + { + return std::make_shared( + getStringKey(elem, "level"), + getItemLocationKey(elem, "item")); + } case str2int16("player.canEquipItem"): { return std::make_shared( diff --git a/src/Parser/ParseTexture.cpp b/src/Parser/ParseTexture.cpp index 33615544..d33c14f9 100755 --- a/src/Parser/ParseTexture.cpp +++ b/src/Parser/ParseTexture.cpp @@ -1,4 +1,5 @@ #include "ParseTexture.h" +#include "ParseCelFile.h" #include "CelUtils.h" #include "ImageUtils.h" #include "Utils.h" @@ -23,9 +24,6 @@ namespace Parser { return img; } - - std::string fileName = getStringVal(elem["file"]); - if (elem.HasMember("palette")) { auto pal = game.Resources().getPalette(getStringVal(elem["palette"])); @@ -33,31 +31,36 @@ namespace Parser { return img; } - bool isCl2 = Utils::endsWith(fileName, "cl2"); - + auto celFile = parseCelFileObj(game, elem); + if (celFile == nullptr) + { + return img; + } if (elem.HasMember("charMapFile") == true) { - img = CelUtils::loadBitmapFontImage(fileName.c_str(), elem["charMapFile"].GetString(), *pal, isCl2); + img = CelUtils::loadBitmapFontImage(*celFile, elem["charMapFile"].GetString(), *pal); } else if (elem.HasMember("frame") == true) { auto frameIdx = getUIntVal(elem["frame"]); - img = CelUtils::loadImageFrame(fileName.c_str(), *pal, isCl2, frameIdx); + img = CelUtils::loadImageFrame(*celFile, *pal, frameIdx); } else if (numFramesX != nullptr && numFramesY != nullptr) { // construct cel already vertically splitted, if pieces exists (*numFramesX) = getUIntKey(elem, "pieces", 1); - img = CelUtils::loadImage(fileName.c_str(), *pal, isCl2, *numFramesX, *numFramesY); + img = CelUtils::loadImage(*celFile, *pal, *numFramesX, *numFramesY); } else { - img = CelUtils::loadImage(fileName.c_str(), *pal, isCl2); + img = CelUtils::loadImage(*celFile, *pal); } } else { - img = ImageUtils::loadImage(fileName.c_str(), getColorKey(elem, "mask")); + img = ImageUtils::loadImage( + getStringCharVal(elem["file"]), + getColorKey(elem, "mask")); if (elem.HasMember("split")) { @@ -82,7 +85,6 @@ namespace Parser } } } - return img; } diff --git a/src/Predicates/PredItem.h b/src/Predicates/PredItem.h new file mode 100755 index 00000000..b830d30e --- /dev/null +++ b/src/Predicates/PredItem.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Predicate.h" +#include "Game.h" +#include "Game/ItemProperties.h" + +class PredItemHasRequiredStats : public Predicate +{ +private: + std::string idLevel; + ItemLocation itemLocation; + +public: + PredItemHasRequiredStats(const std::string& idLevel_, + const ItemLocation& itemLocation_) + : idLevel(idLevel_), itemLocation(itemLocation_) {} + + virtual Variable getResult(const Game& game) const + { + auto level = game.Resources().getLevel(idLevel); + if (level != nullptr) + { + auto item = level->getItem(itemLocation); + if (item != nullptr) + { + if (item->getIntByHash(ItemProp::RequiredStrength) > 0 || + item->getIntByHash(ItemProp::RequiredMagic) > 0 || + item->getIntByHash(ItemProp::RequiredDexterity) > 0 || + item->getIntByHash(ItemProp::RequiredVitality) > 0) + { + return { true }; + } + } + } + return { false }; + } +}; diff --git a/src/Predicates/PredPlayer.h b/src/Predicates/PredPlayer.h index d8171a78..ba8f5db8 100755 --- a/src/Predicates/PredPlayer.h +++ b/src/Predicates/PredPlayer.h @@ -1,6 +1,7 @@ #pragma once #include "Predicate.h" +#include "Game.h" class PredPlayerCanEquip : public Predicate { diff --git a/src/Rectangle.h b/src/Rectangle.h index 281786eb..6820090c 100755 --- a/src/Rectangle.h +++ b/src/Rectangle.h @@ -12,6 +12,7 @@ class Rectangle : public sf::RectangleShape, public UIObject public: Rectangle(const sf::Vector2f& size = sf::Vector2f(0, 0)) : sf::RectangleShape(size) {} + virtual std::shared_ptr getAction(uint16_t nameHash16) { return nullptr; } virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action) {} virtual void setAnchor(const Anchor anchor_) { anchor = anchor_; } diff --git a/src/ScrollableText.cpp b/src/ScrollableText.cpp index 3fc5d2c3..82bb5a55 100755 --- a/src/ScrollableText.cpp +++ b/src/ScrollableText.cpp @@ -11,6 +11,17 @@ ScrollableText::ScrollableText(std::unique_ptr text_, const sf::Ti text->Visible(true); } +std::shared_ptr ScrollableText::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("complete"): + return completeAction; + default: + return nullptr; + } +} + void ScrollableText::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) diff --git a/src/ScrollableText.h b/src/ScrollableText.h index e90e71ff..061ec6db 100755 --- a/src/ScrollableText.h +++ b/src/ScrollableText.h @@ -35,6 +35,7 @@ class ScrollableText : public UIObject void setLoop(bool loop_) { loop = loop_; } void setPause(bool pause_) { pause = pause_; } + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); virtual void setAnchor(const Anchor anchor) { view.setAnchor(anchor); } diff --git a/src/StringButton.cpp b/src/StringButton.cpp index 4ae3bbda..3992ad10 100755 --- a/src/StringButton.cpp +++ b/src/StringButton.cpp @@ -3,6 +3,35 @@ #include "GameUtils.h" #include "Utils.h" +std::shared_ptr StringButton::getAction(uint16_t nameHash16) +{ + switch (nameHash16) + { + case str2int16("change"): + return text->getAction(nameHash16); + case str2int16("click"): + return clickAction; + case str2int16("rightClick"): + return rightClickAction; + case str2int16("doubleClick"): + return doubleClickAction; + case str2int16("clickDrag"): + return clickDragAction; + case str2int16("clickIn"): + return clickInAction; + case str2int16("clickOut"): + return clickOutAction; + case str2int16("focus"): + return focusAction; + case str2int16("hoverEnter"): + return hoverEnterAction; + case str2int16("hoverLeave"): + return hoverLeaveAction; + default: + return nullptr; + } +} + void StringButton::setAction(uint16_t nameHash16, const std::shared_ptr& action) { switch (nameHash16) @@ -40,28 +69,29 @@ void StringButton::setAction(uint16_t nameHash16, const std::shared_ptr& } } -void StringButton::click(Game& game, bool playSound) +bool StringButton::click(Game& game, bool playSound) { - if (enabled == false) + if (enabled == true) { - return; - } - if (clickAction != nullptr) - { - if (focusEnable == true) + if (clickAction != nullptr) { - game.Resources().setFocused(this); - if (focusOnClick == true) + if (focusEnable == true) { - game.Events().addBack(focusAction); + game.Resources().setFocused(this); + if (focusOnClick == true) + { + game.Events().addBack(focusAction); + } } + if (playSound == true) + { + game.addPlayingSound(clickSound.get()); + } + game.Events().addBack(clickAction); + return true; } - if (playSound == true) - { - game.addPlayingSound(clickSound.get()); - } - game.Events().addBack(clickAction); } + return false; } void StringButton::focus(Game& game) const @@ -75,17 +105,9 @@ void StringButton::draw(sf::RenderTarget& target, sf::RenderStates states) const target.draw(*text, states); } -void StringButton::update(Game& game) +void StringButton::onHover(Game& game, bool contains) { - if (text->Visible() == false) - { - return; - } - - text->update(game); - - auto rect = text->getGlobalBounds(); - if (rect.contains(game.MousePositionf())) + if (contains == true) { if (hovered == false) { @@ -95,96 +117,258 @@ void StringButton::update(Game& game) game.Events().addBack(hoverEnterAction); } } - if (game.getMouseButton() == sf::Mouse::Left) + } + else + { + if (hovered == true) { - if (game.wasMouseClicked() == true) + hovered = false; + if (hoverLeaveAction != nullptr) { - game.clearMouseClicked(); - beingDragged = true; - wasClicked = true; - if (clickInAction != nullptr) - { - game.Events().addFront(clickInAction); - } - if (clickUp == false) - { - click(game, true); - if (game.wasMouseDoubleClicked() == true) - { - if (doubleClickAction != nullptr) - { - game.Events().addBack(doubleClickAction); - } - } - } + game.Events().addFront(hoverLeaveAction); } - else if (game.wasMouseReleased() == true) + } + } +} + +void StringButton::onMouseButtonPressed(Game& game, bool contains) +{ + if (contains == false) + { + return; + } + switch (game.MousePress().button) + { + case sf::Mouse::Left: + { + beingDragged = true; + wasLeftClicked = true; + + if (clickInAction != nullptr) + { + game.Events().addBack(clickInAction); + } + if (clickUp == false) + { + if (doubleClickAction != nullptr && + mouseDblClickClock.restart().asMilliseconds() <= GameUtils::DoubleClickDelay) { - if (clickOutAction != nullptr) - { - game.Events().addFront(clickOutAction); - } - if (clickUp == true && beingDragged == true) + game.clearMousePressed(); + game.Events().addBack(doubleClickAction); + } + else + { + if (click(game, true) == true) { - click(game, true); + game.clearMousePressed(); } - beingDragged = false; } } - else if (game.getMouseButton() == sf::Mouse::Right) + } + break; + case sf::Mouse::Right: + { + wasRightClicked = true; + if (clickUp == false) { - if (game.wasMouseClicked() == true) + if (rightClickAction != nullptr) { - game.clearMouseClicked(); - if (clickUp == false) - { - if (rightClickAction != nullptr) - { - game.Events().addFront(rightClickAction); - } - } + game.clearMousePressed(); + game.Events().addBack(rightClickAction); } - else if (game.wasMouseReleased() == true) + } + } + break; + default: + break; + } +} + +void StringButton::onMouseButtonReleased(Game& game, bool contains) +{ + switch (game.MouseRelease().button) + { + case sf::Mouse::Left: + { + if (wasLeftClicked == false) + { + break; + } + wasLeftClicked = false; + if (clickUp == true && + contains == true) + { + if (click(game, true) == true) { - if (clickUp == true) - { - if (rightClickAction != nullptr) - { - game.Events().addFront(rightClickAction); - } - } + game.clearMouseReleased(); } } + beingDragged = false; + if (clickOutAction != nullptr) + { + game.Events().addBack(clickOutAction); + } } - else + break; + case sf::Mouse::Right: { - if (game.wasMouseReleased() == true && - game.getMouseButton() == sf::Mouse::Left) + if (wasRightClicked == false) + { + break; + } + wasRightClicked = false; + if (clickUp == true && + contains == true) { - beingDragged = false; - if (wasClicked == true) + if (rightClickAction != nullptr) { - wasClicked = false; - if (clickOutAction != nullptr) - { - game.Events().addFront(clickOutAction); - } + game.clearMouseReleased(); + game.Events().addBack(rightClickAction); } } - if (hovered == true) + } + break; + default: + break; + } +} + +void StringButton::onMouseMoved(Game& game) +{ + if (beingDragged == true) + { + if (clickDragAction != nullptr) { - hovered = false; - if (hoverLeaveAction != nullptr) + game.Events().addBack(clickDragAction); + } + } +} + +void StringButton::onTouchBegan(Game& game, bool contains) +{ + if (contains == false) + { + return; + } + switch (game.TouchBegan().finger) + { + case 0: + { + beingDragged = true; + wasLeftClicked = true; + + if (clickInAction != nullptr) + { + game.Events().addBack(clickInAction); + } + } + break; + case 1: + { + wasRightClicked = true; + } + break; + default: + break; + } +} + +void StringButton::onTouchEnded(Game& game, bool contains) +{ + switch (game.TouchEnded().finger) + { + case 0: + { + if (wasLeftClicked == false) + { + break; + } + wasLeftClicked = false; + if (contains == true) + { + if (doubleClickAction != nullptr && + mouseDblClickClock.restart().asMilliseconds() <= GameUtils::DoubleClickDelay) { - game.Events().addFront(hoverLeaveAction); + game.clearTouchEnded(); + game.Events().addBack(doubleClickAction); + } + else + { + if (click(game, true) == true) + { + game.clearTouchEnded(); + } } } + beingDragged = false; + if (clickOutAction != nullptr) + { + game.Events().addBack(clickOutAction); + } } - if (beingDragged == true && game.wasMouseMoved() == true) + break; + case 1: { - if (clickDragAction != nullptr) + if (wasRightClicked == false) + { + break; + } + wasRightClicked = false; + if (contains == true) { - game.Events().addFront(clickDragAction); + if (rightClickAction != nullptr) + { + game.clearTouchEnded(); + game.Events().addBack(rightClickAction); + } } } + break; + default: + break; + } +} + +void StringButton::update(Game& game) +{ + if (text->Visible() == false) + { + return; + } + + text->update(game); + + auto contains = text->getGlobalBounds().contains(game.MousePositionf()); + + onHover(game, contains); + + if (game.wasMousePressed() == true) + { + onMouseButtonPressed(game, contains); + } + if (game.wasMouseReleased() == true) + { + onMouseButtonReleased(game, contains); + } + if (game.wasMouseMoved() == true) + { + onMouseMoved(game); + } + if (game.hasTouchBegan() == true) + { + onTouchBegan(game, contains); + } + if (game.hasTouchMoved() == true) + { + onMouseMoved(game); + } + if (game.hasTouchEnded() == true) + { + onTouchEnded(game, contains); + } + if (contains == true && + captureInputEvents == true) + { + game.clearInputEvents(); + } } diff --git a/src/StringButton.h b/src/StringButton.h index 1f49e142..1b48bf4f 100755 --- a/src/StringButton.h +++ b/src/StringButton.h @@ -6,8 +6,9 @@ #include #include #include "Text2.h" +#include "UIText.h" -class StringButton : public Button +class StringButton : public Button, public UIText { private: std::unique_ptr text; @@ -23,26 +24,43 @@ class StringButton : public Button std::shared_ptr hoverLeaveAction; std::shared_ptr clickSound; std::shared_ptr focusSound; + sf::Clock mouseDblClickClock; bool focusEnable{ false }; bool focusOnClick{ false }; bool hovered{ false }; bool clickUp{ false }; bool beingDragged{ false }; - bool wasClicked{ false }; + bool wasLeftClicked{ false }; + bool wasRightClicked{ false }; + bool captureInputEvents{ false }; + + void onHover(Game& game, bool contains); + void onMouseButtonPressed(Game& game, bool contains); + void onMouseButtonReleased(Game& game, bool contains); + void onMouseMoved(Game& game); + void onTouchBegan(Game& game, bool contains); + void onTouchEnded(Game& game, bool contains); public: - std::string getText() const { return text->getText(); } + virtual std::string getText() const { return text->getText(); } void setText(std::unique_ptr text_) { text = std::move(text_); } - void setText(const std::string& text_) { text->setText(text_); } + virtual void setText(const std::string& text_) { text->setText(text_); } + + virtual void setHorizontalSpaceOffset(int offset) { text->setHorizontalSpaceOffset(offset); } + virtual void setVerticalSpaceOffset(int offset) { text->setVerticalSpaceOffset(offset); } DrawableText* getDrawableText() { return text->getDrawableText(); } sf::FloatRect getLocalBounds() const { return text->getLocalBounds(); } sf::FloatRect getGlobalBounds() const { return text->getGlobalBounds(); } + bool getCaptureInputEvents() const { return captureInputEvents; } + void setCaptureInputEvents(bool captureEvents) { captureInputEvents = captureEvents; } + + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); - virtual void click(Game& game, bool playSound); + virtual bool click(Game& game, bool playSound); virtual void enable(bool enable) { enabled = enable; } virtual void focus(Game& game) const; virtual void focusEnabled(bool focusOnClick_) { focusEnable = true; focusOnClick = focusOnClick_; } diff --git a/src/StringText.h b/src/StringText.h index 27c46c48..841fb41d 100755 --- a/src/StringText.h +++ b/src/StringText.h @@ -26,6 +26,7 @@ class StringText : public DrawableText virtual ~StringText() {} + virtual std::shared_ptr getAction(uint16_t nameHash16) { return nullptr; } virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action) {} virtual void setAnchor(const Anchor anchor_) @@ -55,13 +56,17 @@ class StringText : public DrawableText void setFont(const sf::Font& font) { text.setFont(font); } void setCharacterSize(unsigned int size) { text.setCharacterSize(size); } void setStyle(sf::Uint32 style) { text.setStyle(style); } - virtual void setColor(const sf::Color& color) { text.setColor(color); } + virtual void setColor(const sf::Color& color) { text.setFillColor(color); } + void setOutlineColor(const sf::Color& color) { text.setOutlineColor(color); } + void setOutlineThickness(float thickness) { text.setOutlineThickness(thickness); } virtual std::string getText() const { return text.getString().toAnsiString(); } const sf::Font* getFont() const { return text.getFont(); } unsigned int getCharacterSize() const { return text.getCharacterSize(); } sf::Uint32 getStyle() const { return text.getStyle(); } - const sf::Color& getColor() const { return text.getColor(); } + const sf::Color& getColor() const { return text.getFillColor(); } + const sf::Color& getOutlineColor() const { return text.getOutlineColor(); } + float getOutlineThickness() const { return text.getOutlineThickness(); } sf::Vector2f findCharacterPos(std::size_t index) const { return text.findCharacterPos(index); } virtual sf::FloatRect getLocalBounds() const { return text.getLocalBounds(); } virtual sf::FloatRect getGlobalBounds() const { return text.getGlobalBounds(); } diff --git a/src/Text2.cpp b/src/Text2.cpp index 11cb83c4..90794440 100755 --- a/src/Text2.cpp +++ b/src/Text2.cpp @@ -1,42 +1,28 @@ #include "Text2.h" #include "Game.h" #include "GameUtils.h" +#include "TextUtils.h" #include "Utils.h" -void Text2::setAction(uint16_t nameHash16, const std::shared_ptr& action) +std::shared_ptr Text2::getAction(uint16_t nameHash16) { switch (nameHash16) { case str2int16("change"): - changeAction = action; - return; + return changeAction; + default: + return nullptr; } } -std::string Text2::getFormatString(const Game& game, - const std::vector& bindings, const std::string& format) +void Text2::setAction(uint16_t nameHash16, const std::shared_ptr& action) { - if (bindings.size() > 0) + switch (nameHash16) { - if (format == "[1]") - { - return game.getVarOrPropString(bindings[0]); - } - else - { - std::string displayText = format; - if (format.size() > 2) - { - for (size_t i = 0; i < bindings.size(); i++) - { - auto str = game.getVarOrPropString(bindings[i]); - Utils::replaceStringInPlace(displayText, "[" + std::to_string(i + 1) + "]", str); - } - } - return displayText; - } + case str2int16("change"): + changeAction = action; + return; } - return ""; } void Text2::setBinding(const std::string& binding) @@ -58,7 +44,7 @@ void Text2::update(Game& game) } if (bindings.size() > 0) { - triggerOnChange = text->setText(Text2::getFormatString(game, bindings, format)); + triggerOnChange = text->setText(TextUtils::getFormatString(game, format, bindings)); } if (triggerOnChange == true) { diff --git a/src/Text2.h b/src/Text2.h index dc26927e..a8d3b73e 100755 --- a/src/Text2.h +++ b/src/Text2.h @@ -3,8 +3,9 @@ #include "DrawableText.h" #include #include "UIObject.h" +#include "UIText.h" -class Text2 : public UIObject +class Text2 : public UIObject, public UIText { private: std::unique_ptr text; @@ -16,14 +17,11 @@ class Text2 : public UIObject public: Text2(std::unique_ptr text_) : text(std::move(text_)) {} - static std::string getFormatString(const Game& game, - const std::vector& bindings, const std::string& format); - DrawableText* getDrawableText() { return text.get(); } - std::string getText() const { return text->getText(); } + virtual std::string getText() const { return text->getText(); } void setText(std::unique_ptr text_) { text = std::move(text_); } - void setText(const std::string& text_) { triggerOnChange = text->setText(text_); } + virtual void setText(const std::string& text_) { triggerOnChange = text->setText(text_); } sf::FloatRect getLocalBounds() const { return text->getLocalBounds(); } sf::FloatRect getGlobalBounds() const { return text->getGlobalBounds(); } @@ -34,9 +32,10 @@ class Text2 : public UIObject void setFormat(const std::string& format_) { format = format_; } void setHorizontalAlign(const HorizontalAlign align) { text->setHorizontalAlign(align); } void setVerticalAlign(const VerticalAlign align) { text->setVerticalAlign(align); } - void setHorizontalSpaceOffset(int offset) { text->setHorizontalSpaceOffset(offset); } - void setVerticalSpaceOffset(int offset) { text->setVerticalSpaceOffset(offset); } + virtual void setHorizontalSpaceOffset(int offset) { text->setHorizontalSpaceOffset(offset); } + virtual void setVerticalSpaceOffset(int offset) { text->setVerticalSpaceOffset(offset); } + virtual std::shared_ptr getAction(uint16_t nameHash16); virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action); virtual void setAnchor(const Anchor anchor) { text->setAnchor(anchor); } diff --git a/src/TextUtils.cpp b/src/TextUtils.cpp new file mode 100755 index 00000000..7e5859b3 --- /dev/null +++ b/src/TextUtils.cpp @@ -0,0 +1,89 @@ +#include "TextUtils.h" +#include "Game.h" +#include "GameUtils.h" + +namespace TextUtils +{ + std::string getFormatString(const Game& game, const std::string& format, + const std::vector& bindings) + { + if (bindings.size() > 0) + { + if (format == "[1]") + { + return game.getVarOrPropString(bindings[0]); + } + else + { + std::string displayText = format; + if (format.size() > 2) + { + for (size_t i = 0; i < bindings.size(); i++) + { + auto str = game.getVarOrPropString(bindings[i]); + Utils::replaceStringInPlace( + displayText, + "[" + std::to_string(i + 1) + "]", + str); + } + } + return displayText; + } + } + return ""; + } + + std::string getTextQueryable(const Game& game, const std::string& format, + const std::string& query) + { + auto queryable = game.getQueryable(query); + if (queryable != nullptr) + { + return GameUtils::replaceStringWithQueryable(format, *queryable); + } + return format; + } + + std::string getText(const Game& game, TextOp textOp, const std::string& textOrformat, + const std::vector& bindings) + { + std::string str; + switch (TextOp(((uint32_t)textOp) & 0x7u)) + { + default: + case TextOp::Set: + str = textOrformat; + break; + case TextOp::Replace: + str = game.getVarOrPropString(textOrformat); + break; + case TextOp::ReplaceAll: + str = game.getVarOrPropString(textOrformat); + break; + case TextOp::Query: + { + if (bindings.empty() == false) + { + str = getTextQueryable(game, textOrformat, bindings.front()); + } + else + { + str = textOrformat; + } + } + break; + case TextOp::FormatString: + str = getFormatString(game, textOrformat, bindings); + break; + } + if ((uint32_t)textOp & (uint32_t)TextOp::Trim) + { + str = Utils::trim(str, " \t\r\n"); + } + if ((uint32_t)textOp & (uint32_t)TextOp::RemoveEmptyLines) + { + str = Utils::removeEmptyLines(str); + } + return str; + } +} diff --git a/src/TextUtils.h b/src/TextUtils.h new file mode 100755 index 00000000..b91ba9c8 --- /dev/null +++ b/src/TextUtils.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class Game; + +namespace TextUtils +{ + enum class TextOp : uint32_t + { + Set = 0, + Replace = 1, + ReplaceAll = 2, + Query = 3, + FormatString = 4, + Trim = 8, + RemoveEmptyLines = 16, + }; + + using T = std::underlying_type_t; + + inline TextOp operator~ (TextOp a) { return (TextOp)~static_cast(a); } + inline TextOp operator| (TextOp a, TextOp b) { return (TextOp)(static_cast(a) | static_cast(b)); } + inline TextOp operator& (TextOp a, TextOp b) { return (TextOp)(static_cast(a) & static_cast(b)); } + inline TextOp operator^ (TextOp a, TextOp b) { return (TextOp)(static_cast(a) ^ static_cast(b)); } + inline TextOp& operator|= (TextOp& a, TextOp b) { a = (TextOp)(static_cast(a) | static_cast(b)); return a; } + inline TextOp& operator&= (TextOp& a, TextOp b) { a = (TextOp)(static_cast(a) & static_cast(b)); return a; } + inline TextOp& operator^= (TextOp& a, TextOp b) { a = (TextOp)(static_cast(a) ^ static_cast(b)); return a; } + + std::string getFormatString(const Game& game, const std::string& format, + const std::vector& bindings); + + std::string getTextQueryable(const Game& game, const std::string& format, + const std::string& query); + + std::string getText(const Game& game, TextOp textOp, const std::string& textOrformat, + const std::vector& bindings); +} diff --git a/src/UIObject.h b/src/UIObject.h index 13632034..2889a7c2 100755 --- a/src/UIObject.h +++ b/src/UIObject.h @@ -12,6 +12,7 @@ class UIObject : public sf::Drawable, public Queryable { public: // Action + virtual std::shared_ptr getAction(uint16_t nameHash16) = 0; virtual void setAction(uint16_t nameHash16, const std::shared_ptr& action) = 0; // Anchor diff --git a/src/UIText.h b/src/UIText.h new file mode 100755 index 00000000..cde37c63 --- /dev/null +++ b/src/UIText.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class UIText +{ +public: + virtual std::string getText() const = 0; + virtual void setText(const std::string& text) = 0; + + virtual void setHorizontalSpaceOffset(int offset) = 0; + virtual void setVerticalSpaceOffset(int offset) = 0; +}; diff --git a/src/Utils.h b/src/Utils.h index 5c023358..761e9a7a 100755 --- a/src/Utils.h +++ b/src/Utils.h @@ -62,7 +62,7 @@ namespace Utils auto outputDiff = x > y ? x - y : y - x; val -= inputRange.x; - val = std::lround((double)val * (double)outputDiff / (double)inputDiff) + (double)outputRange.x; + val = std::lround((double)val * (double)outputDiff / (double)inputDiff) + (long)outputRange.x; return val; } diff --git a/src/sfeMovie/AudioStream.cpp b/src/sfeMovie/AudioStream.cpp new file mode 100755 index 00000000..a6de6531 --- /dev/null +++ b/src/sfeMovie/AudioStream.cpp @@ -0,0 +1,481 @@ + +/* + * AudioStream.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +extern "C" +{ +#include +#include +#include +#include +#include +#include +} + +#include +#include +#include "AudioStream.hpp" +#include +#include "Utilities.hpp" + +namespace sfe +{ + namespace + { + void waitForStatusUpdate(const sf::SoundStream& stream, sf::SoundStream::Status expectedStatus) + { + // Wait for status to update + sf::Clock timeout; + while (stream.getStatus() != expectedStatus && timeout.getElapsedTime() < sf::seconds(5)) + sf::sleep(sf::microseconds(10)); + CHECK(timeout.getElapsedTime() < sf::seconds(5), "Audio did not reach state " + s(expectedStatus) + " within 5 seconds"); + } + + const int BytesPerSample = sizeof(sf::Int16); // Signed 16 bits audio sample + } + + AudioStream::AudioStream(AVFormatContext*& formatCtx, AVStream*& stream, DataSource& dataSource, + std::shared_ptr timer) : + Stream(formatCtx, stream, dataSource, timer), + + // Public properties + m_sampleRatePerChannel(0), + + // Private data + m_samplesBuffer(nullptr), + m_audioFrame(nullptr), + + // Resampling + m_swrCtx(nullptr), + m_dstNbSamples(0), + m_maxDstNbSamples(0), + m_dstNbChannels(0), + m_dstLinesize(0), + m_dstData(nullptr) + { + m_audioFrame = av_frame_alloc(); + CHECK(m_audioFrame, "AudioStream::AudioStream() - out of memory"); + + // Get some audio informations +#if LIBAVFORMAT_VERSION_MAJOR > 56 + m_sampleRatePerChannel = m_stream->codecpar->sample_rate; +#else + m_sampleRatePerChannel = m_stream->codec->sample_rate; +#endif + + // Alloc a two seconds buffer + m_samplesBuffer = (sf::Int16*)av_malloc(sizeof(sf::Int16) * av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) + * m_sampleRatePerChannel * 2); // * 2 is for 2 seconds + CHECK(m_samplesBuffer, "AudioStream::AudioStream() - out of memory"); + + // Initialize the sf::SoundStream + // Whatever the channel count is, it'll we resampled to stereo + sf::SoundStream::initialize(av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO), m_sampleRatePerChannel); + + // Initialize resampler to be able to give signed 16 bits samples to SFML + initResampler(); + } + + /** Default destructor + */ + AudioStream::~AudioStream() + { + if (m_audioFrame) + { + av_frame_free(&m_audioFrame); + } + + if (m_samplesBuffer) + { + av_free(m_samplesBuffer); + } + + if (m_dstData) + { + av_freep(&m_dstData[0]); + } + av_freep(&m_dstData); + + swr_free(&m_swrCtx); + } + + void AudioStream::flushBuffers() + { + sf::SoundStream::Status sfStatus = sf::SoundStream::getStatus(); + CHECK (sfStatus != sf::SoundStream::Playing, "Trying to flush while audio is playing, this will introduce an audio glitch!"); + + // Flush audio driver/OpenAL/SFML buffer + if (sfStatus != sf::SoundStream::Stopped) + sf::SoundStream::stop(); + + m_extraAudioTime = sf::Time::Zero; + Stream::flushBuffers(); + } + + MediaType AudioStream::getStreamKind() const + { + return Audio; + } + + void AudioStream::update() + { + sf::SoundStream::Status sfStatus = sf::SoundStream::getStatus(); + + switch (sfStatus) + { + case sf::SoundStream::Playing: + setStatus(sfe::Playing); + break; + + case sf::SoundStream::Paused: + setStatus(sfe::Paused); + break; + + case sf::SoundStream::Stopped: + setStatus(sfe::Stopped); + break; + + default: + break; + } + } + + bool AudioStream::fastForward(sf::Time targetPosition) + { + sf::Time currentPosition; + sf::Time pktDuration; + + do + { + if (! computeEncodedPosition(currentPosition)) + { + return false; + } + + AVPacket* packet = popEncodedData(); + + if (! packet) + { + return false; + } + + pktDuration = packetDuration(packet); + + if (currentPosition > targetPosition) + { + // Computations with packet duration and stream position are not always very accurate so + // this can happen some times. In such cases, the different is very small (less than 1ms) + // so we just accept it + + m_extraAudioTime = sf::Time::Zero; + + // Reinsert, we don't want to decode now + prependEncodedData(packet); + } + else if (currentPosition + pktDuration > targetPosition) + { + // Reinsert, we don't want to decode now + prependEncodedData(packet); + m_extraAudioTime = targetPosition - currentPosition; + + CHECK(m_extraAudioTime > sf::Time::Zero, "inconcistency error"); + CHECK(m_extraAudioTime <= pktDuration, "Should have discarded a full packet"); + } + else + { +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(packet); +#else + av_free_packet(packet); +#endif + av_free(packet); + } + } + while (currentPosition + pktDuration <= targetPosition); + + return true; + } + + bool AudioStream::onGetData(sf::SoundStream::Chunk& data) + { + AVPacket* packet = nullptr; + data.samples = m_samplesBuffer; + + const int stereoChannelCount = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); + + while (data.sampleCount < stereoChannelCount * m_sampleRatePerChannel && + (nullptr != (packet = popEncodedData()))) + { + bool needsMoreDecoding = false; + bool gotFrame = false; + + do + { + needsMoreDecoding = decodePacket(packet, m_audioFrame, gotFrame); + + if (gotFrame) + { + uint8_t* samplesBuffer = nullptr; + int samplesCount = 0; + + resampleFrame(m_audioFrame, samplesBuffer, samplesCount); + CHECK(samplesBuffer, "AudioStream::onGetData() - resampleFrame() error"); + CHECK(samplesCount > 0, "AudioStream::onGetData() - resampleFrame() error"); + CHECK(samplesToTime(data.sampleCount + samplesCount) < sf::seconds(2), + "AudioStream::onGetData() - Going to overflow!!"); + + if (m_extraAudioTime > sf::Time::Zero) + { + int samplesToDiscard = timeToSamples(m_extraAudioTime); + if (samplesToDiscard > samplesCount) + { + samplesToDiscard = samplesCount; + } + + if (samplesToDiscard < stereoChannelCount && samplesCount > 0) + { + m_extraAudioTime = sf::Time::Zero; + } + else + { + CHECK(((samplesToDiscard / std::max(samplesCount, samplesToDiscard)) + - (m_extraAudioTime.asMicroseconds() + / samplesToTime(samplesCount).asMicroseconds())) + < 0.1, + "It looks like an invalid amount of audio samples was discarded, " + "please report this bug"); + + samplesBuffer += samplesToDiscard * BytesPerSample; + samplesCount -= samplesToDiscard; + + m_extraAudioTime -= samplesToTime(samplesToDiscard); + } + } + + std::memcpy((void *)(data.samples + data.sampleCount), + samplesBuffer, samplesCount * BytesPerSample); + data.sampleCount += samplesCount; + } + } + while (needsMoreDecoding); + +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(packet); +#else + av_free_packet(packet); +#endif + av_free(packet); + } + + return (packet != nullptr); + } + + void AudioStream::onSeek(sf::Time timeOffset) + { + // CHECK(0, "AudioStream::onSeek() - not implemented"); + } + + bool AudioStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame) + { + bool needsMoreDecoding = false; + int igotFrame = 0; + +#if LIBAVCODEC_VERSION_MAJOR > 56 + int decodedLength = avcodec_decode_audio4(m_codecCtx, outputFrame, &igotFrame, packet); +#else + int decodedLength = avcodec_decode_audio4(m_stream->codec, outputFrame, &igotFrame, packet); +#endif + gotFrame = (igotFrame != 0); + CHECK(decodedLength >= 0, "AudioStream::decodePacket() - error: decodedLength=" + s(decodedLength)); + + if (decodedLength < packet->size) + { + needsMoreDecoding = true; + packet->data += decodedLength; + packet->size -= decodedLength; + } + + return needsMoreDecoding; + } + + void AudioStream::initResampler() + { + CHECK0(m_swrCtx, "AudioStream::initResampler() - resampler already initialized"); + int err = 0; + + /* create resampler context */ + m_swrCtx = swr_alloc(); + CHECK(m_swrCtx, "AudioStream::initResampler() - out of memory"); + + // Some media files don't define the channel layout, in this case take a default one + // according to the channels' count +#if LIBAVFORMAT_VERSION_MAJOR > 56 + if (m_stream->codecpar->channel_layout == 0) + { + m_stream->codecpar->channel_layout = av_get_default_channel_layout(m_stream->codecpar->channels); + } + + /* set options */ + av_opt_set_int (m_swrCtx, "in_channel_layout", m_stream->codecpar->channel_layout, 0); + av_opt_set_int (m_swrCtx, "in_sample_rate", m_stream->codecpar->sample_rate, 0); + av_opt_set_sample_fmt (m_swrCtx, "in_sample_fmt", (AVSampleFormat)m_stream->codecpar->format, 0); + av_opt_set_int (m_swrCtx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int (m_swrCtx, "out_sample_rate", m_stream->codecpar->sample_rate, 0); + av_opt_set_sample_fmt (m_swrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); +#else + if (m_stream->codec->channel_layout == 0) + { + m_stream->codec->channel_layout = av_get_default_channel_layout(m_stream->codec->channels); + } + + /* set options */ + av_opt_set_int (m_swrCtx, "in_channel_layout", m_stream->codec->channel_layout, 0); + av_opt_set_int (m_swrCtx, "in_sample_rate", m_stream->codec->sample_rate, 0); + av_opt_set_sample_fmt (m_swrCtx, "in_sample_fmt", m_stream->codec->sample_fmt, 0); + av_opt_set_int (m_swrCtx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int (m_swrCtx, "out_sample_rate", m_stream->codec->sample_rate, 0); + av_opt_set_sample_fmt (m_swrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); +#endif + + /* initialize the resampling context */ + err = swr_init(m_swrCtx); + CHECK(err >= 0, "AudioStream::initResampler() - resampling context initialization error"); + + /* compute the number of converted samples: buffering is avoided + * ensuring that the output buffer will contain at least all the + * converted input samples */ + m_maxDstNbSamples = m_dstNbSamples = 1024; + + /* Create the resampling output buffer */ + m_dstNbChannels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); + err = av_samples_alloc_array_and_samples(&m_dstData, &m_dstLinesize, m_dstNbChannels, + m_dstNbSamples, AV_SAMPLE_FMT_S16, 0); + CHECK(err >= 0, "AudioStream::initResampler() - av_samples_alloc_array_and_samples error"); + } + + void AudioStream::resampleFrame(const AVFrame* frame, uint8_t*& outSamples, int& outNbSamples) + { + CHECK(m_swrCtx, "AudioStream::resampleFrame() - resampler is not initialized, call AudioStream::initResamplerFirst() !"); + CHECK(frame, "AudioStream::resampleFrame() - invalid argument"); + + int src_rate, dst_rate, err, dst_bufsize; + src_rate = dst_rate = frame->sample_rate; + + /* compute destination number of samples */ + m_dstNbSamples = (int)av_rescale_rnd(swr_get_delay(m_swrCtx, src_rate) + + frame->nb_samples, dst_rate, src_rate, AV_ROUND_UP); + if (m_dstNbSamples > m_maxDstNbSamples) + { + av_free(m_dstData[0]); + err = av_samples_alloc(m_dstData, &m_dstLinesize, m_dstNbChannels, + m_dstNbSamples, AV_SAMPLE_FMT_S16, 1); + CHECK(err >= 0, "AudioStream::resampleFrame() - out of memory"); + m_maxDstNbSamples = m_dstNbSamples; + } + + /* convert to destination format */ + err = swr_convert(m_swrCtx, m_dstData, m_dstNbSamples, (const uint8_t **)frame->extended_data, frame->nb_samples); + CHECK(err >= 0, "AudioStream::resampleFrame() - swr_convert() error"); + + dst_bufsize = av_samples_get_buffer_size(&m_dstLinesize, m_dstNbChannels, + err, AV_SAMPLE_FMT_S16, 1); + CHECK(dst_bufsize >= 0, "AudioStream::resampleFrame() - av_samples_get_buffer_size() error"); + + outNbSamples = dst_bufsize / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); + outSamples = m_dstData[0]; + } + + int AudioStream::timeToSamples(const sf::Time& time) const + { + const int channelCount = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); + int64_t samplesPerSecond = m_sampleRatePerChannel * channelCount; + int64_t samples = (samplesPerSecond * time.asMicroseconds()) / 1000000; + CHECK(samples >= 0, "computation overflow"); + + // We don't want SFML to be confused by interverting left and right speaker sound in case + // samples are interleaved + if (samples % channelCount != 0) + samples -= samples % channelCount; + + return (int)samples; + } + + sf::Time AudioStream::samplesToTime(int nbSamples) const + { + int64_t samplesPerChannel = nbSamples / av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); + int64_t microseconds = 1000000 * samplesPerChannel / m_sampleRatePerChannel; + CHECK(microseconds >= 0, "computation overflow"); + + return sf::microseconds(microseconds); + } + + void AudioStream::willPlay(const Timer &timer) + { + Stream::willPlay(timer); + + if (Stream::getStatus() == sfe::Stopped) + { + sf::Time initialTime = sf::SoundStream::getPlayingOffset(); + sf::Clock timeout; + sf::SoundStream::play(); + + // Some audio drivers take time before the sound is actually played + // To avoid desynchronization with the timer, we don't return + // until the audio stream is actually started + while (sf::SoundStream::getPlayingOffset() == initialTime && timeout.getElapsedTime() < sf::seconds(5)) + sf::sleep(sf::microseconds(10)); + + CHECK(sf::SoundStream::getPlayingOffset() != initialTime, "is your audio device broken? Audio did not start within 5 seconds"); + } + else + { + sf::SoundStream::play(); + waitForStatusUpdate(*this, sf::SoundStream::Playing); + } + } + + void AudioStream::didPlay(const Timer& timer, sfe::Status previousStatus) + { + CHECK(SoundStream::getStatus() == SoundStream::Playing, "AudioStream::didPlay() - willPlay() not executed!"); + Stream::didPlay(timer, previousStatus); + } + + void AudioStream::didPause(const Timer& timer, sfe::Status previousStatus) + { + if (sf::SoundStream::getStatus() == sf::SoundStream::Playing) + { + sf::SoundStream::pause(); + waitForStatusUpdate(*this, sf::SoundStream::Paused); + } + + Stream::didPause(timer, previousStatus); + } + + void AudioStream::didStop(const Timer& timer, sfe::Status previousStatus) + { + sf::SoundStream::stop(); + waitForStatusUpdate(*this, sf::SoundStream::Stopped); + + Stream::didStop(timer, previousStatus); + } +} diff --git a/src/sfeMovie/AudioStream.hpp b/src/sfeMovie/AudioStream.hpp new file mode 100755 index 00000000..7ee69797 --- /dev/null +++ b/src/sfeMovie/AudioStream.hpp @@ -0,0 +1,137 @@ + +/* + * AudioStream.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifndef SFEMOVIE_AUDIOSTREAM_HPP +#define SFEMOVIE_AUDIOSTREAM_HPP + +#include +#include "Stream.hpp" +#include + +namespace sfe +{ + class AudioStream : public Stream, private sf::SoundStream + { + public: + /** Create an audio stream from the given FFmpeg stream + * + * At the end of the constructor, the stream is guaranteed + * to have all of its fields set and the decoder loaded + */ + AudioStream(AVFormatContext*& formatCtx, AVStream*& stream, DataSource& dataSource, + std::shared_ptr timer); + + /** Default destructor + */ + virtual ~AudioStream(); + + /** Empty the encoded data queue, destroy all the packets and flush the decoding pipeline + */ + void flushBuffers() override; + + /** Get the stream kind (either audio or video stream) + * + * @return the kind of stream represented by this stream + */ + MediaType getStreamKind() const override; + + /** Update the stream's status + */ + void update() override; + + /** @see Stream::fastForward() + */ + bool fastForward(sf::Time targetPosition) override; + + using sf::SoundStream::setVolume; + using sf::SoundStream::getVolume; + using sf::SoundStream::getSampleRate; + using sf::SoundStream::getChannelCount; + private: + bool onGetData(sf::SoundStream::Chunk& data) override; + void onSeek(sf::Time timeOffset) override; + + /** Decode the encoded data @a packet into @a outputFrame + * + * gotFrame being set to false means that decoding should still continue: + * - with a new packet if false is returned + * - with the same packet if true is returned + * + * @param packet the encoded data + * @param outputFrame one decoded data + * @param gotFrame set to true if a frame has been extracted to outputFrame, false otherwise + * @return true if there's still data to decode in this packet, false otherwise + */ + bool decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame); + + /** Initialize the audio resampler for conversion from many formats to signed 16 bits audio + * + * This must be called before any packet is decoded and resampled + */ + void initResampler(); + + /** Resample the decoded audio frame @a frame into signed 16 bits audio samples + * + * @param frame the audio samples to convert + * @param outSamples [out] the convertedSamples + * @param outNbSamples [out] the count of signed 16 bits samples in @a outSamples + */ + void resampleFrame(const AVFrame* frame, uint8_t*& outSamples, int& outNbSamples); + + /** @return the amount of samples that would last the given time with the current audio stream + * properties + */ + int timeToSamples(const sf::Time& time) const; + + /** @return the time that would last the given amount of samples with the current audio stream + * properties + */ + sf::Time samplesToTime(int nbSamples) const; + + // Timer::Observer interface + void willPlay(const Timer &timer) override; + void didPlay(const Timer& timer, sfe::Status previousStatus) override; + void didPause(const Timer& timer, sfe::Status previousStatus) override; + void didStop(const Timer& timer, sfe::Status previousStatus) override; + + // Public properties + unsigned m_sampleRatePerChannel; + + // Private data + sf::Int16* m_samplesBuffer; + AVFrame* m_audioFrame; + sf::Time m_extraAudioTime; + + // Resampling + struct SwrContext* m_swrCtx; + int m_dstNbSamples; + int m_maxDstNbSamples; + int m_dstNbChannels; + int m_dstLinesize; + uint8_t** m_dstData; + }; +} + +#endif diff --git a/src/sfeMovie/Demuxer.cpp b/src/sfeMovie/Demuxer.cpp new file mode 100755 index 00000000..54c9e222 --- /dev/null +++ b/src/sfeMovie/Demuxer.cpp @@ -0,0 +1,737 @@ + +/* + * Demuxer.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +extern "C" +{ +#include +#include +#include +#include +} + +#include "Demuxer.hpp" +#include "VideoStream.hpp" +#include "AudioStream.hpp" +#include "Utilities.hpp" +#include "TimerPriorities.hpp" +#include +#include + +namespace sfe +{ + std::list Demuxer::g_availableDemuxers; + std::list Demuxer::g_availableDecoders; + + static void loadFFmpeg() + { + ONCE(av_register_all()); + ONCE(avcodec_register_all()); + } + + static MediaType AVMediaTypeToMediaType(AVMediaType type) + { + switch (type) + { + case AVMEDIA_TYPE_AUDIO: return Audio; + case AVMEDIA_TYPE_VIDEO: return Video; + default: return Unknown; + } + } + + const std::list& Demuxer::getAvailableDemuxers() + { + AVInputFormat* demuxer = nullptr; + loadFFmpeg(); + + if (g_availableDemuxers.empty()) + { + while (nullptr != (demuxer = av_iformat_next(demuxer))) + { + DemuxerInfo info = + { + std::string(demuxer->name), + std::string(demuxer->long_name) + }; + + g_availableDemuxers.push_back(info); + } + } + + return g_availableDemuxers; + } + + const std::list& Demuxer::getAvailableDecoders() + { + AVCodec* codec = nullptr; + loadFFmpeg(); + + if (g_availableDecoders.empty()) + { + while (nullptr != (codec = av_codec_next(codec))) + { + DecoderInfo info = + { + avcodec_get_name(codec->id), + codec->long_name, + AVMediaTypeToMediaType(codec->type) + }; + + g_availableDecoders.push_back(info); + } + } + + return g_availableDecoders; + } + + Demuxer::Demuxer(const std::string& sourceFile, std::shared_ptr timer, + VideoStream::Delegate& videoDelegate) : + m_streamContext(), + m_formatCtx(nullptr), + m_eofReached(false), + m_streams(), + m_synchronized(), + m_timer(timer), + m_connectedAudioStream(nullptr), + m_connectedVideoStream(nullptr), + m_duration(sf::Time::Zero) + { + CHECK(sourceFile.size(), "Demuxer::Demuxer() - invalid argument: sourceFile"); + CHECK(timer, "Inconsistency error: null timer"); + + // Load all the decoders + loadFFmpeg(); + + init(sourceFile.c_str(), videoDelegate); + } + + Demuxer::Demuxer(sf::InputStream& inputStream, std::shared_ptr timer, + VideoStream::Delegate& videoDelegate) : + m_streamContext(), + m_formatCtx(nullptr), + m_eofReached(false), + m_streams(), + m_synchronized(), + m_timer(timer), + m_connectedAudioStream(nullptr), + m_connectedVideoStream(nullptr), + m_duration(sf::Time::Zero) + { + CHECK(timer, "Inconsistency error: null timer"); + + // Load all the decoders + loadFFmpeg(); + + m_streamContext = InputStreamIOContext(&inputStream); + m_formatCtx = ::avformat_alloc_context(); + m_formatCtx->pb = m_streamContext.getAVIOContext(); + init("", videoDelegate); + } + + void Demuxer::init(const char* fileName, VideoStream::Delegate& videoDelegate) + { + int err = 0; + + // Open the movie file + err = avformat_open_input(&m_formatCtx, fileName, nullptr, nullptr); + CHECK0(err, "Demuxer::Demuxer() - error while opening media stream"); + CHECK(m_formatCtx, "Demuxer() - inconsistency: media context cannot be nullptr"); + + // Read the general movie informations + err = avformat_find_stream_info(m_formatCtx, nullptr); + CHECK0(err, "Demuxer::Demuxer() - error while retreiving media information"); + + // Get the media duration if possible (otherwise rely on the streams) + if (m_formatCtx->duration != AV_NOPTS_VALUE) + { + int64_t secs, us; + secs = m_formatCtx->duration / AV_TIME_BASE; + us = m_formatCtx->duration % AV_TIME_BASE; + m_duration = sf::seconds(secs + (float)us / AV_TIME_BASE); + } + + // Find all interesting streams + for (unsigned int i = 0; i < m_formatCtx->nb_streams; i++) + { + AVStream* & ffstream = m_formatCtx->streams[i]; + + try + { + std::shared_ptr stream; + +#if LIBAVFORMAT_VERSION_MAJOR > 56 + switch (ffstream->codecpar->codec_type) +#else + switch (ffstream->codec->codec_type) +#endif + { + case AVMEDIA_TYPE_VIDEO: + stream = std::make_shared(m_formatCtx, ffstream, *this, m_timer, videoDelegate); + + if (m_duration == sf::Time::Zero) + { + extractDurationFromStream(ffstream); + } + break; + + case AVMEDIA_TYPE_AUDIO: + stream = std::make_shared(m_formatCtx, ffstream, *this, m_timer); + + if (m_duration == sf::Time::Zero) + { + extractDurationFromStream(ffstream); + } + break; + default: + break; + } + + // Don't create an entry in the map unless everything went well and stream did not get ignored + if (stream) + m_streams[ffstream->index] = stream; + } + catch (std::runtime_error) + { + CHECK(m_streams.find(ffstream->index) == m_streams.end(), + "Internal inconcistency error: stream whose loading failed should not be stored"); + } + } + + m_timer->addObserver(*this, DemuxerTimerPriority); + } + + Demuxer::~Demuxer() + { + if (m_timer->getStatus() != Stopped) + m_timer->stop(); + + m_timer->removeObserver(*this); + + // NB: these manual cleaning are important for the AVFormatContext to be deleted last, otherwise + // the streams lose their connection to the codec and leak + m_streams.clear(); + m_connectedAudioStream.reset(); + m_connectedVideoStream.reset(); + + if (m_formatCtx) + { + // Be very careful with this call: it'll also destroy its codec contexts and streams + avformat_close_input(&m_formatCtx); + } + + flushBuffers(); + } + + const std::map >& Demuxer::getStreams() const + { + return m_streams; + } + + std::set< std::shared_ptr > Demuxer::getStreamsOfType(MediaType type) const + { + std::set< std::shared_ptr > streamSet; + + for (const std::pair >& pair : m_streams) + { + if (pair.second->getStreamKind() == type) + streamSet.insert(pair.second); + } + + return streamSet; + } + + Streams Demuxer::computeStreamDescriptors(MediaType type) const + { + Streams entries; + std::set< std::shared_ptr > streamSet; + + for (const std::pair >& pair : m_streams) + { + if (pair.second->getStreamKind() == type) + { + StreamDescriptor entry; + entry.type = type; + entry.identifier = pair.first; + entry.language = pair.second->getLanguage(); + entries.push_back(entry); + } + } + + return entries; + } + + void Demuxer::selectAudioStream(std::shared_ptr stream) + { + Status oldStatus = m_timer->getStatus(); + CHECK(oldStatus == Stopped, "Changing the selected stream after starting " + "the movie playback isn't supported yet"); + + if (oldStatus == Playing) + m_timer->pause(); + + if (stream != m_connectedAudioStream) + { + if (m_connectedAudioStream) + { + m_connectedAudioStream->disconnect(); + } + + if (stream) + stream->connect(); + + m_connectedAudioStream = stream; + } + + if (oldStatus == Playing) + m_timer->play(); + } + + void Demuxer::selectFirstAudioStream() + { + std::set< std::shared_ptr > audioStreams = getStreamsOfType(Audio); + if (audioStreams.size()) + selectAudioStream(std::dynamic_pointer_cast(*audioStreams.begin())); + } + + std::shared_ptr Demuxer::getSelectedAudioStream() const + { + return std::dynamic_pointer_cast(m_connectedAudioStream); + } + + void Demuxer::selectVideoStream(std::shared_ptr stream) + { + Status oldStatus = m_timer->getStatus(); + CHECK(oldStatus == Stopped, "Changing the selected stream after starting " + "the movie playback isn't supported yet"); + + if (oldStatus == Playing) + m_timer->pause(); + + if (stream != m_connectedVideoStream) + { + if (m_connectedVideoStream) + { + m_connectedVideoStream->disconnect(); + } + + if (stream) + stream->connect(); + + m_connectedVideoStream = stream; + } + + if (oldStatus == Playing) + m_timer->play(); + } + + void Demuxer::selectFirstVideoStream() + { + std::set< std::shared_ptr > videoStreams = getStreamsOfType(Video); + if (videoStreams.size()) + selectVideoStream(std::dynamic_pointer_cast(*videoStreams.begin())); + } + + std::shared_ptr Demuxer::getSelectedVideoStream() const + { + return std::dynamic_pointer_cast(m_connectedVideoStream); + } + + void Demuxer::feedStream(Stream& stream) + { + CHECK(! stream.isPassive(), "Internal inconcistency - Cannot feed a passive stream"); + + sf::Lock l(m_synchronized); + + while ((!didReachEndOfFile() || hasPendingDataForStream(stream)) && stream.needsMoreData()) + { + AVPacket* pkt = NULL; + + pkt = gatherQueuedPacketForStream(stream); + + if (!pkt) + pkt = readPacket(); + + if (!pkt) + { + m_eofReached = true; + } + else + { + if (!distributePacket(pkt, stream)) + { +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(pkt); +#else + av_free_packet(pkt); +#endif + av_free(pkt); + } + } + } + } + + std::set> Demuxer::getSelectedStreams() const + { + std::set> set; + + if (m_connectedVideoStream) + set.insert(m_connectedVideoStream); + + if (m_connectedAudioStream) + set.insert(m_connectedAudioStream); + + return set; + } + + void Demuxer::update() + { + std::map > streams = getStreams(); + + for(std::pair > pair : streams) + { + pair.second->update(); + } + } + + bool Demuxer::didReachEndOfFile() const + { + return m_eofReached; + } + + sf::Time Demuxer::getDuration() const + { + return m_duration; + } + + AVPacket* Demuxer::readPacket() + { + sf::Lock l(m_synchronized); + + AVPacket *pkt = nullptr; + int err = 0; + + pkt = (AVPacket *)av_malloc(sizeof(*pkt)); + CHECK(pkt, "Demuxer::readPacket() - out of memory"); + av_init_packet(pkt); + + err = av_read_frame(m_formatCtx, pkt); + + if (err < 0) + { +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(pkt); +#else + av_free_packet(pkt); +#endif + av_free(pkt); + pkt = nullptr; + } + + return pkt; + } + + void Demuxer::flushBuffers() + { + sf::Lock l(m_synchronized); + + for (std::pair >&& pair : m_pendingDataForActiveStreams) + { + for (AVPacket* packet : pair.second) + { +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(packet); +#else + av_free_packet(packet); +#endif + av_free(packet); + } + } + + m_pendingDataForActiveStreams.clear(); + } + + void Demuxer::queueEncodedData(AVPacket* packet) + { + sf::Lock l(m_synchronized); + + std::set> connectedStreams = getSelectedStreams(); + + for (std::shared_ptr stream : connectedStreams) + { + if (stream->canUsePacket(packet)) + { + std::list& packets = m_pendingDataForActiveStreams[stream.get()]; + packets.push_back(packet); + return; + } + } + +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(packet); +#else + av_free_packet(packet); +#endif + av_free(packet); + } + + bool Demuxer::hasPendingDataForStream(const Stream& stream) const + { + sf::Lock l(m_synchronized); + + const std::map >::const_iterator it = + m_pendingDataForActiveStreams.find(&stream); + + if (it != m_pendingDataForActiveStreams.end()) + return ! it->second.empty(); + + return false; + } + + AVPacket* Demuxer::gatherQueuedPacketForStream(Stream& stream) + { + sf::Lock l(m_synchronized); + + std::map >::iterator it + = m_pendingDataForActiveStreams.find(&stream); + + if (it != m_pendingDataForActiveStreams.end()) + { + std::list& pendingPackets = it->second; + + if (! pendingPackets.empty()) + { + AVPacket* packet = pendingPackets.front(); + pendingPackets.pop_front(); + return packet; + } + } + + return NULL; + } + + bool Demuxer::distributePacket(AVPacket* packet, Stream& stream) + { + sf::Lock l(m_synchronized); + CHECK(packet, "Demuxer::distributePacket() - invalid argument"); + + bool distributed = false; + std::map >::iterator it = m_streams.find(packet->stream_index); + + if (it != m_streams.end()) + { + std::shared_ptr targetStream = it->second; + + // We don't want to store the packets for inactive streams, + // let them be freed + if (targetStream == getSelectedVideoStream() || + targetStream == getSelectedAudioStream()) + { + if (targetStream.get() == &stream || targetStream->isPassive()) + targetStream->pushEncodedData(packet); + else + queueEncodedData(packet); + + distributed = true; + } + } + + return distributed; + } + + void Demuxer::extractDurationFromStream(const AVStream* stream) + { + if (m_duration != sf::Time::Zero) + return; + + if (stream->duration != AV_NOPTS_VALUE) + { + int64_t secs, us; + secs = stream->duration / AV_TIME_BASE; + us = stream->duration % AV_TIME_BASE; + m_duration = sf::seconds(secs + (float)us / AV_TIME_BASE); + } + } + + void Demuxer::requestMoreData(Stream& starvingStream) + { + CHECK(! starvingStream.isPassive(), "Internal inconcistency - passive streams cannot request data"); + + sf::Lock l(m_synchronized); + feedStream(starvingStream); + } + + void Demuxer::resetEndOfFileStatus() + { + m_eofReached = false; + } + + bool Demuxer::didSeek(const Timer &timer, sf::Time oldPosition) + { + resetEndOfFileStatus(); + sf::Time newPosition = timer.getOffset(); + std::set< std::shared_ptr > connectedStreams; + + if (m_connectedVideoStream) + connectedStreams.insert(m_connectedVideoStream); + if (m_connectedAudioStream) + connectedStreams.insert(m_connectedAudioStream); + + CHECK(!connectedStreams.empty(), "Inconcistency error: seeking with no active stream"); + + // Trivial seeking to beginning + if (newPosition == sf::Time::Zero) + { + int64_t timestamp = 0; + + if (m_formatCtx->iformat->flags & AVFMT_SEEK_TO_PTS && m_formatCtx->start_time != AV_NOPTS_VALUE) + timestamp += m_formatCtx->start_time; + + + // Flush all streams + for (std::shared_ptr stream : connectedStreams) + stream->flushBuffers(); + flushBuffers(); + + // Seek to beginning + int err = avformat_seek_file(m_formatCtx, -1, INT64_MIN, timestamp, INT64_MAX, AVSEEK_FLAG_BACKWARD); + if (err < 0) + { + return false; + } + } + else // Seeking to some other position + { + // Initial target seek point + int64_t timestamp = ((int)newPosition.asSeconds()) * AV_TIME_BASE; + + // < 0 = before seek point + // > 0 = after seek point + std::map< std::shared_ptr, sf::Time> seekingGaps; + + static const float brokenSeekingThreshold = 60.f; // seconds + bool didReseekBackward = false; + bool didReseekForward = false; + int tooEarlyCount = 0; + int tooLateCount = 0; + int brokenSeekingCount = 0; + int ffmpegSeekFlags = AVSEEK_FLAG_BACKWARD; + + do + { + // Flush all streams + for (std::shared_ptr stream : connectedStreams) + stream->flushBuffers(); + flushBuffers(); + + // Seek to new estimated target + if (m_formatCtx->iformat->flags & AVFMT_SEEK_TO_PTS && m_formatCtx->start_time != AV_NOPTS_VALUE) + timestamp += m_formatCtx->start_time; + + int err = avformat_seek_file(m_formatCtx, -1, timestamp - 10 * AV_TIME_BASE, + timestamp, timestamp, ffmpegSeekFlags); + CHECK0(err, "avformat_seek_file failure"); + + // Compute the new gap + for (std::shared_ptr stream : connectedStreams) + { + if (stream->isPassive()) + continue; + + sf::Time position; + if (stream->computeEncodedPosition(position)) + { + seekingGaps[stream] = position - newPosition; + } + } + + tooEarlyCount = 0; + tooLateCount = 0; + brokenSeekingCount = 0; + + // Check the current situation + for (std::pair< std::shared_ptr, sf::Time>&& gapByStream : seekingGaps) + { + // < 0 = before seek point + // > 0 = after seek point + const sf::Time& gap = gapByStream.second; + float absoluteDiff = fabs(gap.asSeconds()); + + // Before seek point + if (gap < sf::Time::Zero) + { + if (absoluteDiff > brokenSeekingThreshold) + { + brokenSeekingCount++; + tooEarlyCount++; + } + // else: a bit early but not too much, this is the final situation we want + } + // After seek point + else if (gap > sf::Time::Zero) + { + tooLateCount++; + + if (absoluteDiff > brokenSeekingThreshold) + brokenSeekingCount++; + } + } + + CHECK(false == (tooEarlyCount && tooLateCount), + "Both too late and too early for different streams, unhandled situation!"); + + // Define what to do next + if (tooEarlyCount) + { + // Go forward by 1 sec + timestamp += AV_TIME_BASE; + didReseekForward = true; + } + else if (tooLateCount) + { + // Go backward by 1 sec + timestamp -= AV_TIME_BASE; + didReseekBackward = true; + } + + if (brokenSeekingCount) + { + if (ffmpegSeekFlags & AVSEEK_FLAG_ANY) + { + return false; + } + else + { + // Try to seek to non-key frame before giving up + // Image may be wrong but it's better than nothing :) + ffmpegSeekFlags |= AVSEEK_FLAG_ANY; + } + } + + CHECK(!(didReseekBackward && didReseekForward), "infinitely seeking backward and forward"); + } + while (tooEarlyCount != 0 || tooLateCount != 0); + } + + return true; + } +} diff --git a/src/sfeMovie/Demuxer.hpp b/src/sfeMovie/Demuxer.hpp new file mode 100755 index 00000000..4a6017cb --- /dev/null +++ b/src/sfeMovie/Demuxer.hpp @@ -0,0 +1,331 @@ + +/* + * Demuxer.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_DEMUXER_HPP +#define SFEMOVIE_DEMUXER_HPP + +#include +#include "Stream.hpp" +#include "AudioStream.hpp" +#include "VideoStream.hpp" +#include "Timer.hpp" +#include +#include +#include +#include +#include +#include + +namespace sfe +{ + class Demuxer : public Stream::DataSource, public Timer::Observer + { + public: + /** Describes a demuxer + * + * Ie. an audio/video container format parser such as avi, mov, mkv, ogv... parsers + */ + struct DemuxerInfo + { + std::string name; + std::string description; + }; + + /** Describes a decoder + * + * Ie. an audio/video stream decoder for h.264, theora, vp9, mp3, pcm, srt... streams + */ + struct DecoderInfo + { + std::string name; + std::string description; + MediaType type; + }; + + /** Return a list containing the names of all the demuxers (ie. container parsers) included + * in this sfeMovie build + */ + static const std::list& getAvailableDemuxers(); + + /** Return a list containing the names of all the decoders included + * in this sfeMovie build + */ + static const std::list& getAvailableDecoders(); + + /** Default constructor + * + * Open a media file and find its streams + * + * @param sourceFile the path of the media to open and play + * @param timer the timer with which the media streams will be synchronized + * @param videoDelegate the delegate that will handle the images produced by the VideoStreams + */ + Demuxer(const std::string& sourceFile, std::shared_ptr timer, VideoStream::Delegate& videoDelegate); + + /** Default constructor + * + * Open a media file from an InputStream and find its streams + * + * @param inputStream the InputStream of the media to open and play + * @param timer the timer with which the media streams will be synchronized + * @param videoDelegate the delegate that will handle the images produced by the VideoStreams + */ + Demuxer(sf::InputStream& inputStream, std::shared_ptr timer, VideoStream::Delegate& videoDelegate); + + /** Default destructor + */ + virtual ~Demuxer(); + + /** Return a list of the streams found in the media + * The map key is the index of the stream + * + * @return the list of streams + */ + const std::map >& getStreams() const; + + /** Return a set containing all the streams found in the media that match the given type + * + * @param the media type against which the returned streams should be filtered + * @return the audio streams + */ + std::set< std::shared_ptr > getStreamsOfType(MediaType type) const; + + /** Gather the required stream metadata from each stream of the given type + * + * @param type the type of the streams that are to be described + * @return the stream entries computed from the gathered metadata + */ + Streams computeStreamDescriptors(MediaType type) const; + + /** Enable the given audio stream and connect it to the reference timer + * + * If another stream of the same kind is already enabled, it is first disabled and disconnected + * so that only one stream of the same kind can be enabled at the same time. + * + * @param stream the audio stream to enable and connect for playing, or nullptr to disable audio + */ + void selectAudioStream(std::shared_ptr stream); + + /** Enable the first found audio stream, if it exists + * + * @see selectAudioStream + */ + void selectFirstAudioStream(); + + /** Get the currently selected audio stream, if there's one + * + * @return the currently selected audio stream, or nullptr if there's none + */ + std::shared_ptr getSelectedAudioStream() const; + + /** Enable the given video stream and connect it to the reference timer + * + * If another stream of the same kind is already enabled, it is first disabled and disconnected + * so that only one stream of the same kind can be enabled at the same time. + * + * @param stream the video stream to enable and connect for playing, or nullptr to disable video + */ + void selectVideoStream(std::shared_ptr stream); + + /** Enable the first found video stream, if it exists + * + * @see selectAudioStream + */ + void selectFirstVideoStream(); + + /** Get the currently selected video stream, if there's one + * + * @return the currently selected video stream, or nullptr if there's none + */ + std::shared_ptr getSelectedVideoStream() const; + + /** Read encoded data from the media and makes sure that the given stream + * has enough data + * + * @param stream The stream to feed + */ + void feedStream(Stream& stream); + + /** @return a list of all the active streams + */ + std::set> getSelectedStreams() const; + + /** Update the media status and eventually decode frames + */ + void update(); + + /** Tell whether the demuxer has reached the end of the file and can no more feed the streams + * + * @return whether the end of the media file has been reached + */ + bool didReachEndOfFile() const; + + /** Give the media duration + * + * @return the media duration + */ + sf::Time getDuration() const; + + private: + + static const int kBufferSize = 1024 * 1024 * 500; + + class InputStreamIOContext + { + private: + InputStreamIOContext(InputStreamIOContext const &); + InputStreamIOContext& operator = (InputStreamIOContext const &); + + public: + // Move constructor + InputStreamIOContext(InputStreamIOContext&& other) { + m_inputStream = other.m_inputStream; + m_ctx = other.m_ctx; + other.m_ctx = nullptr; + } + + // Move assignment operator + InputStreamIOContext& operator=(InputStreamIOContext&& other) { + m_inputStream = other.m_inputStream; + m_ctx = other.m_ctx; + other.m_ctx = nullptr; + return *this; + } + + InputStreamIOContext() : m_inputStream(nullptr), m_ctx(nullptr) {} + + InputStreamIOContext(sf::InputStream* inputStream) : m_inputStream(inputStream) { + unsigned char* buffer = static_cast(::av_malloc(kBufferSize)); + m_ctx = ::avio_alloc_context(buffer, kBufferSize, 0, this, + &InputStreamIOContext::read, nullptr, &InputStreamIOContext::seek); + } + + ~InputStreamIOContext() { + if (m_ctx != nullptr) + { + auto buf = m_ctx->buffer; + ::av_freep(&m_ctx); + ::av_free(buf); + } + } + + static int read(void* opaque, unsigned char* buf, int buf_size) { + InputStreamIOContext* h = static_cast(opaque); + return (int)h->m_inputStream->read(buf, buf_size); + } + + static int64_t seek(void* opaque, int64_t offset, int whence) { + InputStreamIOContext* h = static_cast(opaque); + + if (0x10000 == whence) { + return h->m_inputStream->getSize(); + } + + return h->m_inputStream->seek(offset); + } + + ::AVIOContext* getAVIOContext() { return m_ctx; } + + private: + sf::InputStream* m_inputStream; + ::AVIOContext* m_ctx; + }; + + /** Common Constructor + */ + void init(const char* fileName, VideoStream::Delegate& videoDelegate); + + /** Read a encoded packet from the media file + * + * You're responsible for freeing the returned packet + * + * @return the read packet, or nullptr if the end of file has been reached + */ + AVPacket* readPacket(); + + /** Empty the temporarily encoded data queue + */ + void flushBuffers(); + + /** Queue a packet that has been read and is to be used by an active stream in near future + * + * @param packet the packet to temporarily store + */ + void queueEncodedData(AVPacket* packet); + + /** Check whether data that should be distributed to the given stream is currently pending + * in the demuxer's temporary queue + * + * @param stream the stream for which pending data availability is to be checked + * @param whether pending data exists for the given stream + */ + bool hasPendingDataForStream(const Stream& stream) const; + + /** Look for a queued packet for the given stream + * + * @param stream the stream for which to search a packet + * @return if a packet for the given stream has been found, it is dequeued and returned + * otherwise NULL is returned + */ + AVPacket* gatherQueuedPacketForStream(Stream& stream); + + /** Distribute the given packet to the correct stream + * + * If the packet doesn't match any known stream, nothing is done + * + * @param packet the packet to distribute + * @param stream the stream that requested data from the demuxer, if the packet is not for this stream + * it must be queued + * @return true if the packet could be distributed, false otherwise + */ + bool distributePacket(AVPacket* packet, Stream& stream); + + /** Try to extract the media duration from the given stream + */ + void extractDurationFromStream(const AVStream* stream); + + // Data source interface + void requestMoreData(Stream& starvingStream) override; + void resetEndOfFileStatus() override; + + // Timer interface + bool didSeek(const Timer& timer, sf::Time oldPosition) override; + + InputStreamIOContext m_streamContext; + AVFormatContext* m_formatCtx; + bool m_eofReached; + std::map > m_streams; + mutable sf::Mutex m_synchronized; + std::shared_ptr m_timer; + std::shared_ptr m_connectedAudioStream; + std::shared_ptr m_connectedVideoStream; + sf::Time m_duration; + std::map > m_pendingDataForActiveStreams; + + static std::list g_availableDemuxers; + static std::list g_availableDecoders; + }; +} + +#endif diff --git a/src/sfeMovie/Macros.cpp b/src/sfeMovie/Macros.cpp new file mode 100755 index 00000000..a742dbaf --- /dev/null +++ b/src/sfeMovie/Macros.cpp @@ -0,0 +1,37 @@ + +/* + * Macros.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "Macros.hpp" + +namespace sfe +{ + std::string ff_err2str(int code) + { + char buf[AV_ERROR_MAX_STRING_SIZE]; + memset(buf, 0, AV_ERROR_MAX_STRING_SIZE); + + av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, code); + return std::string(buf); + } +} diff --git a/src/sfeMovie/Macros.hpp b/src/sfeMovie/Macros.hpp new file mode 100755 index 00000000..1b56630c --- /dev/null +++ b/src/sfeMovie/Macros.hpp @@ -0,0 +1,68 @@ + +/* + * Macros.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include + +extern "C" +{ + #include +} + +#ifndef SFEMOVIE_MACROS_HPP +#define SFEMOVIE_MACROS_HPP + +#ifndef NDEBUG +#define DEBUG 1 +#endif + +#define CHECK(value, message) if (!(value)) throw std::runtime_error(message); +#define CHECK0(value, message) CHECK(value == 0, message) +#define ONCE(sequence)\ +{ static bool __done = false; if (!__done) { { sequence; } __done = true; } } + +#define BENCH_START \ +{ \ +sf::Clock __bench; + +#define BENCH_END(title) \ +sfeLogDebug(std::string(title) + " took " + s(__bench.getElapsedTime().asMilliseconds()) + "ms"); \ +} + +#if defined(SFML_SYSTEM_WINDOWS) + #ifdef av_err2str + #undef av_err2str + #endif + + namespace sfe + { + std::string ff_err2str(int code); + } + + #define av_err2str sfe::ff_err2str +#endif + +#endif diff --git a/src/sfeMovie/Movie.cpp b/src/sfeMovie/Movie.cpp new file mode 100755 index 00000000..dddb110d --- /dev/null +++ b/src/sfeMovie/Movie.cpp @@ -0,0 +1,168 @@ + +/* + * Movie.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "Movie.hpp" +#include "MovieImpl.hpp" + + +namespace sfe +{ + Movie::Movie() : + m_impl(new MovieImpl(*this)) + { + } + + Movie::~Movie() + { + } + + + bool Movie::openFromFile(const std::string& filename) + { + return m_impl->openFromFile(filename); + } + + bool Movie::openFromStream(sf::InputStream& stream) + { + return m_impl->openFromStream(stream); + } + + const Streams& Movie::getStreams(MediaType type) const + { + return m_impl->getStreams(type); + } + + bool Movie::selectStream(const StreamDescriptor& streamDescriptor) + { + return m_impl->selectStream(streamDescriptor); + } + + void Movie::play() + { + m_impl->play(); + } + + + void Movie::pause() + { + m_impl->pause(); + } + + + void Movie::stop() + { + m_impl->stop(); + } + + + void Movie::update() + { + m_impl->update(); + } + + + void Movie::setVolume(float volume) + { + m_impl->setVolume(volume); + } + + + float Movie::getVolume() const + { + return m_impl->getVolume(); + } + + + sf::Time Movie::getDuration() const + { + return m_impl->getDuration(); + } + + + sf::Vector2f Movie::getSize() const + { + return m_impl->getSize(); + } + + + void Movie::fit(float x, float y, float width, float height, bool preserveRatio) + { + m_impl->fit(x, y, width, height, preserveRatio); + } + + + void Movie::fit(sf::FloatRect frame, bool preserveRatio) + { + m_impl->fit(frame, preserveRatio); + } + + + float Movie::getFramerate() const + { + return m_impl->getFramerate(); + } + + + unsigned int Movie::getSampleRate() const + { + return m_impl->getSampleRate(); + } + + + unsigned int Movie::getChannelCount() const + { + return m_impl->getChannelCount(); + } + + + Status Movie::getStatus() const + { + return m_impl->getStatus(); + } + + + sf::Time Movie::getPlayingOffset() const + { + return m_impl->getPlayingOffset(); + } + + + bool Movie::setPlayingOffset(const sf::Time& targetSeekTime) + { + return m_impl->setPlayingOffset(targetSeekTime); + } + + + const sf::Texture& Movie::getCurrentImage() const + { + return m_impl->getCurrentImage(); + } + + void Movie::draw(sf::RenderTarget& target, sf::RenderStates states) const + { + states.transform *= getTransform(); + target.draw(*m_impl, states); + } + +} // namespace sfe diff --git a/src/sfeMovie/Movie.hpp b/src/sfeMovie/Movie.hpp new file mode 100755 index 00000000..e9aa01fb --- /dev/null +++ b/src/sfeMovie/Movie.hpp @@ -0,0 +1,225 @@ + +/* + * Movie.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifndef SFEMOVIE_MOVIE_HPP +#define SFEMOVIE_MOVIE_HPP + +#include +#include +#include "StreamSelection.hpp" +#include +#include +#include + +namespace sfe +{ + /** Constants giving the a playback status + */ + enum Status + { + Stopped, //!< The playback is stopped (ie. not playing and at the beginning) + Paused, //!< The playback is paused + Playing, //!< The playback is playing + End + }; + + class MovieImpl; + /** Main class of the sfeMovie API. It is used to open media files, provide playback and basic controls + */ + class Movie : public sf::Drawable, public sf::Transformable + { + public: + Movie(); + ~Movie(); + + /** @brief Attemps to open a media file (movie or audio) + * + * Opening can fails either because of a wrong filename, + * or because you tried to open a media file that has no supported + * video or audio stream. + * + * @param filename the path to the media file + * @return true on success, false otherwise + */ + bool openFromFile(const std::string& filename); + + /** @brief Attemps to open a media file (movie or audio) from an InputStream + * + * Opening can fails either because of a wrong stream, + * or because you tried to open a media file that has no supported + * video or audio stream. + * + * @param stream the InputStream to the media file + * @return true on success, false otherwise + */ + bool openFromStream(sf::InputStream& stream); + + /** @brief Return a description of all the streams of the given type contained in the opened media + * + * @param type the stream type (audio, video...) to return + */ + const Streams& getStreams(MediaType type) const; + + /** @brief Request activation of the given stream. + * + * In case another stream of the same kind is already active, + * it is deactivated. + * + * @note When opening a new media file, the default behaviour is to automatically activate the first + * found audio and video streams + * + * @warning This method can only be used when the movie is stopped + * + * @param streamDescriptor the descriptor of the stream to activate + * @return true if the stream could be selected (ie. valid stream and movie is stopped) + */ + bool selectStream(const StreamDescriptor& streamDescriptor); + + /** @brief Start or resume playing the media playback + * + * This function starts the stream if it was stopped, resumes it if it was paused, + * and restarts it from beginning if it was already playing. This function is non blocking + * and lets the audio playback happen in the background. The video playback must be updated + * with the update() method. + */ + void play(); + + /** @brief Pauses the media playback + * + * If the media playback is already paused, + * this does nothing, otherwise the playback is paused. + */ + void pause(); + + /** @brief Stops the media playback. The playing offset is reset to the beginning. + * + * This function stops the stream if it was playing or paused, and does nothing + * if it was already stopped. It also resets the playing position (unlike pause()). + */ + void stop(); + + /** @brief Update the media status and eventually decode frames + */ + void update(); + + /** @brief Sets the sound's volume (default is 100) + * + * @param volume the volume in range [0, 100] + */ + void setVolume(float volume); + + /** @brief Returns the current sound's volume + * + * @return the sound's volume, in range [0, 100] + */ + float getVolume() const; + + /** @brief Returns the duration of the movie + * + * @return the duration as sf::Time + */ + sf::Time getDuration() const; + + /** @brief Returns the size (width, height) of the currently active video stream + * + * @return the size of the currently active video stream, or (0, 0) is there is none + */ + sf::Vector2f getSize() const; + + /** @see fitFrame(sf::FloatRect, bool) + */ + void fit(float x, float y, float width, float height, bool preserveRatio = true); + + /** @brief Scales the movie to fit the requested frame. + * + * If the ratio is preserved, the movie may be centered + * in the given frame, thus the movie position may be different from + * the one you specified. + * + * @note This method will erase any previously set scale and position + * + * @param frame the target frame in which you want to display the movie + * @param preserveRatio true to keep the original movie ratio, false otherwise + */ + void fit(sf::FloatRect frame, bool preserveRatio = true); + + /** @brief Returns the average amount of video frames per second + * + * In case of media that use Variable Frame Rate, this value is approximative + * + * @return the average video frame rate + */ + float getFramerate() const; + + /** @brief Returns the amount of audio samples per second + * + * @return the audio sample rate + */ + unsigned int getSampleRate() const; + + /** @brief Returns the count of audio channels + * + * @return the channels' count + */ + unsigned int getChannelCount() const; + + /** @brief Returns the current status of the movie + * + * @return See enum Status + */ + Status getStatus() const; + + /** @brief Returns the current playing position in the movie + * + * @return the playing position + */ + sf::Time getPlayingOffset() const; + + /** Seek up to @a targetSeekTime + * + * @param targetSeekTime the new expected playing offset + * @return true is seeking was successfull on all the streams, false otherwise + * If seeking failed, it is not guaranteed to still be playable and synchronized + */ + bool setPlayingOffset(const sf::Time& targetSeekTime); + + /** @brief Returns the latest movie image + * + * The returned image is a texture in VRAM. + * If the movie has no video stream, this returns an empty texture. + * + * @note As in the classic update()/draw() workflow, update() needs to be called + * before using this method if you want the image to be up to date + * + * @return the current image of the movie for the activated video stream + */ + const sf::Texture& getCurrentImage() const; + private: + void draw(sf::RenderTarget& Target, sf::RenderStates states) const; + std::shared_ptr m_impl; + }; +} // namespace sfe + +#endif diff --git a/src/sfeMovie/MovieImpl.cpp b/src/sfeMovie/MovieImpl.cpp new file mode 100755 index 00000000..980f9b26 --- /dev/null +++ b/src/sfeMovie/MovieImpl.cpp @@ -0,0 +1,443 @@ + +/* + * MovieImpl.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "MovieImpl.hpp" +#include "Demuxer.hpp" +#include "Timer.hpp" +#include "Utilities.hpp" +#include +#include + +namespace sfe +{ + MovieImpl::MovieImpl(sf::Transformable& movieView) : + m_movieView(movieView), + m_demuxer(nullptr), + m_timer(nullptr), + m_videoSprite() + { + } + + MovieImpl::~MovieImpl() + { + if (m_timer && m_timer->getStatus() != Stopped) + stop(); + } + + bool MovieImpl::openFromFile(const std::string& filename) + { + try + { + m_timer = std::make_shared(); + m_demuxer = std::make_shared(filename, m_timer, *this); + + return open(); + } + catch (std::runtime_error) + { + return false; + } + } + + bool MovieImpl::openFromStream(sf::InputStream& stream) + { + try + { + m_timer = std::make_shared(); + m_demuxer = std::make_shared(stream, m_timer, *this); + + return open(); + } + catch (std::runtime_error) + { + return false; + } + } + + bool MovieImpl::open() + { + m_audioStreamsDesc = m_demuxer->computeStreamDescriptors(Audio); + m_videoStreamsDesc = m_demuxer->computeStreamDescriptors(Video); + + std::set< std::shared_ptr > audioStreams = m_demuxer->getStreamsOfType(Audio); + std::set< std::shared_ptr > videoStreams = m_demuxer->getStreamsOfType(Video); + + m_demuxer->selectFirstAudioStream(); + m_demuxer->selectFirstVideoStream(); + + if (audioStreams.empty() && videoStreams.empty()) + { + return false; + } + else + { + if (!videoStreams.empty()) + { + sf::Vector2f size = getSize(); + m_displayFrame = sf::FloatRect(0, 0, size.x, size.y); + } + + return true; + } + } + + const Streams& MovieImpl::getStreams(MediaType type) const + { + switch (type) + { + case Audio: return m_audioStreamsDesc; + case Video: return m_videoStreamsDesc; + default: CHECK(false, "Movie::getStreams() - Unknown stream type:" + mediaTypeToString(type)); + } + } + + bool MovieImpl::selectStream(const StreamDescriptor& streamDescriptor) + { + if (!m_demuxer || !m_timer) + { + return false; + } + + if (m_timer->getStatus() != Stopped) + { + return false; + } + + std::map > streams = m_demuxer->getStreams(); + std::map >::iterator it = streams.find(streamDescriptor.identifier); + std::shared_ptr streamToSelect = nullptr; + + if (it != streams.end()) + { + streamToSelect = it->second; + } + + switch (streamDescriptor.type) + { + case Audio: + m_demuxer->selectAudioStream(std::dynamic_pointer_cast(streamToSelect)); + return true; + case Video: + m_demuxer->selectVideoStream(std::dynamic_pointer_cast(streamToSelect)); + return true; + default: + return false; + } + } + + void MovieImpl::play() + { + if (m_demuxer && m_timer) + { + if (m_timer->getStatus() == Playing) + { + return; + } + + m_timer->play(); + update(); + } + } + + void MovieImpl::pause() + { + if (m_demuxer && m_timer) + { + if (m_timer->getStatus() == Paused) + { + return; + } + + m_timer->pause(); + update(); + } + } + + void MovieImpl::stop() + { + if (m_demuxer && m_timer) + { + if (m_timer->getStatus() == Stopped) + { + return; + } + + m_timer->stop(); + update(); + + std::shared_ptr videoStream(m_demuxer->getSelectedVideoStream()); + + if (videoStream) + videoStream->preload(); + } + } + + void MovieImpl::update() + { + if (m_demuxer && m_timer) + { + m_demuxer->update(); + + if (getStatus() == Stopped && m_timer->getStatus() != Stopped) + { + m_timer->stop(); + } + + // Enable smoothing when the video is scaled + std::shared_ptr vStream = m_demuxer->getSelectedVideoStream(); + if (vStream) + { + sf::Vector2f movieScale = m_movieView.getScale(); + sf::Vector2f subviewScale = m_videoSprite.getScale(); + + if (std::fabs(movieScale.x - 1.f) < 0.00001 && + std::fabs(movieScale.y - 1.f) < 0.00001 && + std::fabs(subviewScale.x - 1.f) < 0.00001 && + std::fabs(subviewScale.y - 1.f) < 0.00001) + { + vStream->getVideoTexture().setSmooth(false); + } + else + { + vStream->getVideoTexture().setSmooth(true); + } + } + } + } + + void MovieImpl::setVolume(float volume) + { + if (m_demuxer && m_timer) + { + std::set< std::shared_ptr > audioStreams = m_demuxer->getStreamsOfType(Audio); + + for (std::shared_ptr stream : audioStreams) + { + std::shared_ptr audioStream = std::dynamic_pointer_cast(stream); + audioStream->setVolume(volume); + } + } + } + + float MovieImpl::getVolume() const + { + if (m_demuxer && m_timer) + { + std::shared_ptr audioStream = m_demuxer->getSelectedAudioStream(); + + if (audioStream) + return audioStream->getVolume(); + } + + return 0; + } + + sf::Time MovieImpl::getDuration() const + { + if (m_demuxer && m_timer) + { + return m_demuxer->getDuration(); + } + + return sf::Time::Zero; + } + + sf::Vector2f MovieImpl::getSize() const + { + if (m_demuxer && m_timer) + { + std::shared_ptr videoStream = m_demuxer->getSelectedVideoStream(); + + if (videoStream) + { + return sf::Vector2f(videoStream->getFrameSize()); + } + } + return sf::Vector2f(0, 0); + } + + void MovieImpl::fit(float x, float y, float width, float height, bool preserveRatio) + { + fit(sf::FloatRect(x, y, width, height), preserveRatio); + } + + void MovieImpl::fit(sf::FloatRect frame, bool preserveRatio) + { + sf::Vector2f movie_size = getSize(); + + if (movie_size.x == 0 || movie_size.y == 0) + { + return; + } + + sf::Vector2f wanted_size = sf::Vector2f(frame.width, frame.height); + sf::Vector2f new_size; + + if (preserveRatio) + { + sf::Vector2f target_size = movie_size; + + float source_ratio = movie_size.x / movie_size.y; + float target_ratio = wanted_size.x / wanted_size.y; + + if (source_ratio > target_ratio) + { + target_size.x = movie_size.x * wanted_size.x / movie_size.x; + target_size.y = movie_size.y * wanted_size.x / movie_size.x; + } + else + { + target_size.x = movie_size.x * wanted_size.y / movie_size.y; + target_size.y = movie_size.y * wanted_size.y / movie_size.y; + } + + new_size = target_size; + } + else + { + new_size = wanted_size; + } + + m_videoSprite.setPosition((wanted_size.x - new_size.x) / 2.f, + (wanted_size.y - new_size.y) / 2.f); + m_movieView.setPosition(frame.left, frame.top); + m_videoSprite.setScale((float)new_size.x / movie_size.x, (float)new_size.y / movie_size.y); + m_displayFrame = frame; + } + + float MovieImpl::getFramerate() const + { + if (m_demuxer && m_timer) + { + std::shared_ptr videoStream = m_demuxer->getSelectedVideoStream(); + + if (videoStream) + return videoStream->getFrameRate(); + } + + return 0; + } + + unsigned int MovieImpl::getSampleRate() const + { + if (m_demuxer && m_timer) + { + std::shared_ptr audioStream = m_demuxer->getSelectedAudioStream(); + + if (audioStream) + return audioStream->getSampleRate(); + } + + return 0; + } + + unsigned int MovieImpl::getChannelCount() const + { + if (m_demuxer && m_timer) + { + std::shared_ptr audioStream = m_demuxer->getSelectedAudioStream(); + if (audioStream) + return audioStream->getChannelCount(); + } + + return 0; + } + + Status MovieImpl::getStatus() const + { + Status st = Stopped; + + if (m_demuxer) + { + std::shared_ptr videoStream = m_demuxer->getSelectedVideoStream(); + std::shared_ptr audioStream = m_demuxer->getSelectedAudioStream(); + Status vStatus = videoStream ? videoStream->getStatus() : Stopped; + Status aStatus = audioStream ? audioStream->Stream::getStatus() : Stopped; + + if (vStatus == Playing || aStatus == Playing) + { + st = Playing; + } + else if (vStatus == Paused || aStatus == Paused) + { + st = Paused; + } + } + + return st; + } + + sf::Time MovieImpl::getPlayingOffset() const + { + if (m_demuxer && m_timer) + { + return m_timer->getOffset(); + } + + return sf::Time::Zero; + } + + bool MovieImpl::setPlayingOffset(const sf::Time& targetSeekTime) + { + bool seekingResult = false; + + if (m_demuxer && m_timer) + { + if (!(targetSeekTime < sf::Time::Zero || targetSeekTime >= getDuration())) + { + seekingResult = m_timer->seek(targetSeekTime); + + if (m_timer->getStatus() == Status::Stopped) + pause(); + } + } + + return seekingResult; + } + + const sf::Texture& MovieImpl::getCurrentImage() const + { + static sf::Texture emptyTexture; + + if (m_videoSprite.getTexture()) + { + return * m_videoSprite.getTexture(); + } + else + { + return emptyTexture; + } + } + + void MovieImpl::draw(sf::RenderTarget& target, sf::RenderStates states) const + { + target.draw(m_videoSprite, states); + } + + void MovieImpl::didUpdateVideo(const VideoStream& sender, const sf::Texture& image) + { + if (m_videoSprite.getTexture() != &image) + m_videoSprite.setTexture(image); + } +} diff --git a/src/sfeMovie/MovieImpl.hpp b/src/sfeMovie/MovieImpl.hpp new file mode 100755 index 00000000..1ce8195b --- /dev/null +++ b/src/sfeMovie/MovieImpl.hpp @@ -0,0 +1,161 @@ + +/* + * MovieImpl.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_MOVIEIMPL_HPP +#define SFEMOVIE_MOVIEIMPL_HPP + +#include +#include +#include +#include +#include +#include "VideoStream.hpp" + +namespace sfe +{ + class Demuxer; + + class MovieImpl : public VideoStream::Delegate, public sf::Drawable + { + public: + MovieImpl(sf::Transformable& movieView); + virtual ~MovieImpl(); + + /** @see Movie::openFromFile() + */ + bool openFromFile(const std::string& filename); + + /** @see Movie::openFromStream() + */ + bool openFromStream(sf::InputStream& stream); + + /** @see Movie::getStreams() + */ + const Streams& getStreams(MediaType type) const; + + /** @see Movie::selectStream() + */ + bool selectStream(const StreamDescriptor& streamDescriptor); + + /** @see Movie::play() + */ + void play(); + + + /** @see Movie::pause() + */ + void pause(); + + + /** @see Movie::stop() + */ + void stop(); + + + /** Update the media status and eventually decode frames + */ + void update(); + + + /** @see Movie::setVolume() + */ + void setVolume(float volume); + + + /** @see Movie::getVolume() + */ + float getVolume() const; + + + /** @see Movie::getDuration() + */ + sf::Time getDuration() const; + + + /** @see Movie::getSize() + */ + sf::Vector2f getSize() const; + + + /** @see fit(sf::FloatRect, bool) + */ + void fit(float x, float y, float width, float height, bool preserveRatio = true); + + + /** @see Movie::fit() + */ + void fit(sf::FloatRect frame, bool preserveRatio = true); + + + /** @see Movie::getFramerate() + */ + float getFramerate() const; + + + /** @see Movie::getSampleRate() + */ + unsigned int getSampleRate() const; + + + /** @see Movie::getChannelCount() + */ + unsigned int getChannelCount() const; + + + /** @see Movie::getStatus() + */ + Status getStatus() const; + + + /** @see Movie::getPlayingOffset() + */ + sf::Time getPlayingOffset() const; + + /** @see Movie::setPlayingOffset() + */ + bool setPlayingOffset(const sf::Time& targetSeekTime); + + /** @see Movie::getCurrentImage() + */ + const sf::Texture& getCurrentImage() const; + + void draw(sf::RenderTarget& target, sf::RenderStates states) const override; + void didUpdateVideo(const VideoStream& sender, const sf::Texture& image) override; + + private: + + bool open(); + + sf::Transformable& m_movieView; + std::shared_ptr m_demuxer; + std::shared_ptr m_timer; + sf::Sprite m_videoSprite; + Streams m_audioStreamsDesc; + Streams m_videoStreamsDesc; + sf::FloatRect m_displayFrame; + }; + +} + +#endif diff --git a/src/sfeMovie/Stream.cpp b/src/sfeMovie/Stream.cpp new file mode 100755 index 00000000..a4217622 --- /dev/null +++ b/src/sfeMovie/Stream.cpp @@ -0,0 +1,309 @@ + +/* + * Stream.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +extern "C" +{ +#include +#include +} + +#include "Stream.hpp" +#include "Utilities.hpp" +#include "TimerPriorities.hpp" +#include +#include +#include + +namespace sfe +{ + Stream::Stream(AVFormatContext*& formatCtx, AVStream*& stream, DataSource& dataSource, std::shared_ptr timer) : + m_formatCtx(formatCtx), + m_stream(stream), + m_dataSource(dataSource), + m_timer(timer), + m_codec(nullptr), +#if LIBAVFORMAT_VERSION_MAJOR > 56 + m_codecCtx(nullptr), +#endif + m_streamID(-1), + m_packetList(), + m_status(Stopped), + m_readerMutex() + { + CHECK(stream, "Stream::Stream() - invalid stream argument"); + CHECK(timer, "Inconcistency error: null timer"); + int err = 0; + + m_stream = stream; + m_streamID = stream->index; + CHECK(m_stream, "Inconcistency error: null stream") + CHECK(m_streamID >= 0, "Inconcistency error: invalid stream id"); + + // Get the decoder +#if LIBAVFORMAT_VERSION_MAJOR > 56 + m_codec = avcodec_find_decoder(m_stream->codecpar->codec_id); + CHECK(m_codec, "Stream() - no decoder for " + std::string(avcodec_get_name(m_stream->codecpar->codec_id)) + " codec"); + + // Load the codec + m_codecCtx = avcodec_alloc_context3(m_codec); + avcodec_parameters_to_context(m_codecCtx, stream->codecpar); + err = avcodec_open2(m_codecCtx, m_codec, nullptr); + CHECK0(err, "Stream() - unable to load decoder for codec " + std::string(avcodec_get_name(m_stream->codecpar->codec_id))); +#else + m_codec = avcodec_find_decoder(m_stream->codec->codec_id); + CHECK(m_codec, "Stream() - no decoder for " + std::string(avcodec_get_name(m_stream->codec->codec_id)) + " codec"); + + // Load the codec + err = avcodec_open2(m_stream->codec, m_codec, nullptr); + CHECK0(err, "Stream() - unable to load decoder for codec " + std::string(avcodec_get_name(m_stream->codec->codec_id))); +#endif + + AVDictionaryEntry* entry = av_dict_get(m_stream->metadata, "language", nullptr, 0); + if (entry) + { + m_language = entry->value; + } + } + + Stream::~Stream() + { + disconnect(); + Stream::flushBuffers(); + +#if LIBAVFORMAT_VERSION_MAJOR > 56 + if (m_formatCtx && m_stream && m_codecCtx) + { + avcodec_free_context(&m_codecCtx); + } +#else + if (m_formatCtx && m_stream && m_stream->codec) + { + avcodec_close(m_stream->codec); + } +#endif + } + + void Stream::connect() + { + if (isPassive()) + m_timer->addObserver(*this, PassiveStreamTimerPriority); + else + m_timer->addObserver(*this, ActiveStreamTimerPriority); + } + + void Stream::disconnect() + { + m_timer->removeObserver(*this); + } + + void Stream::pushEncodedData(AVPacket* packet) + { + CHECK(packet, "invalid argument"); + sf::Lock l(m_readerMutex); + m_packetList.push_back(packet); + } + + void Stream::prependEncodedData(AVPacket* packet) + { + CHECK(packet, "invalid argument"); + sf::Lock l(m_readerMutex); + m_packetList.push_front(packet); + } + + AVPacket* Stream::popEncodedData() + { + AVPacket* result = nullptr; + sf::Lock l(m_readerMutex); + + if (m_packetList.empty() && !isPassive()) + { + m_dataSource.requestMoreData(*this); + } + + if (!m_packetList.empty()) + { + result = m_packetList.front(); + m_packetList.pop_front(); + } + else + { +#if LIBAVFORMAT_VERSION_MAJOR > 56 + if (m_codecCtx->codec->capabilities & CODEC_CAP_DELAY) +#else + if (m_stream->codec->codec->capabilities & CODEC_CAP_DELAY) +#endif + { + AVPacket* flushPacket = (AVPacket*)av_malloc(sizeof(*flushPacket)); + av_init_packet(flushPacket); + flushPacket->data = nullptr; + flushPacket->size = 0; + result = flushPacket; + } + } + + return result; + } + + void Stream::flushBuffers() + { + sf::Lock l(m_readerMutex); + +#if LIBAVCODEC_VERSION_MAJOR > 56 + if (m_formatCtx && m_codecCtx) + avcodec_flush_buffers(m_codecCtx); +#else + if (m_formatCtx && m_stream) + avcodec_flush_buffers(m_stream->codec); +#endif + + AVPacket* pkt = nullptr; + + while (!m_packetList.empty()) + { + pkt = m_packetList.front(); + m_packetList.pop_front(); + +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(pkt); +#else + av_free_packet(pkt); +#endif + av_free(pkt); + } + } + + bool Stream::needsMoreData() const + { + return m_packetList.size() < 10; + } + + MediaType Stream::getStreamKind() const + { + return Unknown; + } + + Status Stream::getStatus() const + { + return m_status; + } + + std::string Stream::getLanguage() const + { + return m_language; + } + + bool Stream::computeEncodedPosition(sf::Time& position) + { + if (m_packetList.empty() && !isPassive()) + { + m_dataSource.requestMoreData(*this); + } + + if (! m_packetList.empty()) + { + sf::Lock l(m_readerMutex); + AVPacket* packet = m_packetList.front(); + CHECK(packet, "internal inconcistency"); + + int64_t timestamp = -424242; + + if (packet->dts != AV_NOPTS_VALUE) + { + timestamp = packet->dts; + } + else if (packet->pts != AV_NOPTS_VALUE) + { + int64_t startTime = m_stream->start_time != AV_NOPTS_VALUE ? m_stream->start_time : 0; + timestamp = packet->pts - startTime; + } + + AVRational seconds = av_mul_q(av_make_q((int)timestamp, 1), m_stream->time_base); + position = sf::milliseconds((int)(1000 * av_q2d(seconds))); + return true; + } + + return false; + } + + sf::Time Stream::packetDuration(const AVPacket* packet) const + { + CHECK(packet, "inconcistency error: null packet"); + CHECK(packet->stream_index == m_streamID, "Asking for duration of a packet for a different stream!"); + + if (packet->duration != 0) + { + AVRational seconds = av_mul_q(av_make_q(packet->duration, 1), m_stream->time_base); + return sf::seconds((float)av_q2d(seconds)); + } + else + { + return sf::seconds((float)(1. / av_q2d(av_guess_frame_rate(m_formatCtx, m_stream, nullptr)))); + } + } + + bool Stream::canUsePacket(AVPacket* packet) const + { + CHECK(packet, "inconcistency error: null argument"); + + return packet->stream_index == m_stream->index; + } + + bool Stream::isPassive() const + { + return false; + } + + void Stream::setStatus(Status status) + { + m_status = status; + } + + void Stream::didPlay(const Timer& timer, Status previousStatus) + { + setStatus(Playing); + } + + void Stream::didPause(const Timer& timer, Status previousStatus) + { + setStatus(Paused); + } + + void Stream::didStop(const Timer& timer, Status previousStatus) + { + setStatus(Stopped); + } + + bool Stream::didSeek(const Timer& timer, sf::Time oldPosition) + { + if (timer.getOffset() != sf::Time::Zero) + return fastForward(timer.getOffset()); + + return true; + } + + bool Stream::hasPackets() + { + return !m_packetList.empty(); + } +} diff --git a/src/sfeMovie/Stream.hpp b/src/sfeMovie/Stream.hpp new file mode 100755 index 00000000..cdd97fff --- /dev/null +++ b/src/sfeMovie/Stream.hpp @@ -0,0 +1,194 @@ + +/* + * Stream.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_STREAM_HPP +#define SFEMOVIE_STREAM_HPP + +#include "Macros.hpp" +#include "Timer.hpp" +#include +#include +#include +#include "Movie.hpp" + +extern "C" +{ +#include +} + +namespace sfe +{ + class Stream : public Timer::Observer + { + public: + struct DataSource + { + virtual void requestMoreData(Stream& starvingStream) = 0; + virtual void resetEndOfFileStatus() = 0; + }; + + /** Create a stream from the given FFmpeg stream + * + * At the end of the constructor, the stream is guaranteed + * to have all of its fields set and the decoder loaded + * + * @param stream the FFmpeg stream + * @param dataSource the encoded data provider for this stream + */ + Stream(AVFormatContext*& formatCtx, AVStream*& stream, DataSource& dataSource, std::shared_ptr timer); + + /** Default destructor + */ + virtual ~Stream(); + + /** Connect this stream against the reference timer to receive playback events; this allows this + * stream to be played + */ + void connect(); + + /** Disconnect this stream from the reference timer ; this disables this stream + */ + void disconnect(); + + /** Called by the demuxer to provide the stream with encoded data + * + * @return packet the encoded data usable by this stream + */ + virtual void pushEncodedData(AVPacket* packet); + + /** Reinsert an AVPacket at the beginning of the queue + * + * This is used for packets that contain several frames, but whose next frames + * cannot be decoded yet. These packets are repushed to be decoded when possible. + * + * @param packet the packet to re-insert at the beginning of the queue + */ + virtual void prependEncodedData(AVPacket* packet); + + /** Return the oldest encoded data that was pushed to this stream + * + * If no packet is stored when this method is called, it will ask the + * data source to feed this stream first + * + * @return the oldest encoded data, or nullptr if no data could be read from the media + */ + virtual AVPacket* popEncodedData(); + + /** Empty the encoded data queue, destroy all the packets and flush the decoding pipeline + * @warning Subclasses overriding this method must also call the Stream implementation + */ + virtual void flushBuffers(); + + /** Used by the demuxer to know if this stream should be fed with more data + * + * The default implementation returns true if the packet list contains less than 10 packets + * + * @return true if the demuxer should give more data to this stream, false otherwise + */ + virtual bool needsMoreData() const; + + /** Get the stream kind (either audio or video stream) + * + * @return the kind of stream represented by this stream + */ + virtual MediaType getStreamKind() const; + + /** Give the stream's status + * + * @return The stream's status (Playing, Paused or Stopped) + */ + Status getStatus() const; + + /** Return the stream's language code + * + * @return the language code of the stream as ISO 639-2 format + */ + std::string getLanguage() const; + + /** Compute the stream position in the media, by possibly fetching a packet + * + * @param[out] position the current stream position, if available + * @return true if stream position could be computed and @ref position is set, false otherwise + */ + bool computeEncodedPosition(sf::Time& position); + + /** Compute how much time would be covered by the given packet, it's the diff between + * the current packet pts, and the next packet pts + */ + sf::Time packetDuration(const AVPacket* packet) const; + + /** Discard the data not needed to start playback at the given position + * + * Every single bit of unneeded data must be discarded as streams synchronization accuracy will + * depend on this + * + * @param targetPosition the position for which the stream is expected to be ready to play + * @return true is fast forwarding could be done successfully, false otherwise + */ + virtual bool fastForward(sf::Time targetPosition) = 0; + + /** Update the current stream's status and eventually decode frames + */ + virtual void update() = 0; + + /** @return true if the given packet is for the current stream + */ + bool canUsePacket(AVPacket* packet) const; + + /** @return true if this stream never requests packets and let + * itself be fed, false otherwise. Default implementation always + * returns false + */ + virtual bool isPassive() const; + protected: + // Timer::Observer interface + void didPlay(const Timer& timer, Status previousStatus) override; + void didPause(const Timer& timer, Status previousStatus) override; + void didStop(const Timer& timer, Status previousStatus) override; + bool didSeek(const Timer& timer, sf::Time oldPosition) override; + + /** @return true if any raw packet for the current stream is queued + */ + bool hasPackets(); + + void setStatus(Status status); + + AVFormatContext* & m_formatCtx; + AVStream*& m_stream; + + DataSource& m_dataSource; + std::shared_ptr m_timer; + AVCodec* m_codec; +#if LIBAVFORMAT_VERSION_MAJOR > 56 + AVCodecContext* m_codecCtx; +#endif + int m_streamID; + std::string m_language; + std::list m_packetList; + Status m_status; + sf::Mutex m_readerMutex; + }; +} + +#endif diff --git a/src/sfeMovie/StreamSelection.cpp b/src/sfeMovie/StreamSelection.cpp new file mode 100755 index 00000000..752b6012 --- /dev/null +++ b/src/sfeMovie/StreamSelection.cpp @@ -0,0 +1,36 @@ + +/* + * StreamSelection.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "StreamSelection.hpp" + +namespace sfe +{ + StreamDescriptor StreamDescriptor::NoSelection(sfe::MediaType type) + { + StreamDescriptor descriptor; + descriptor.type = type; + descriptor.identifier = -1; + return descriptor; + } +} diff --git a/src/sfeMovie/StreamSelection.hpp b/src/sfeMovie/StreamSelection.hpp new file mode 100755 index 00000000..47b70e2f --- /dev/null +++ b/src/sfeMovie/StreamSelection.hpp @@ -0,0 +1,60 @@ + +/* + * StreamSelection.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_STREAM_SELECTION_HPP +#define SFEMOVIE_STREAM_SELECTION_HPP + +#include +#include + +namespace sfe +{ + enum MediaType + { + Audio, + Video, + Unknown + }; + + /** Structure that allows both knowing metadata about each stream, and identifying streams + * for selection through Movie::selectStream() + */ + struct StreamDescriptor + { + /** Return a stream descriptor that identifies no stream. This allows disabling a specific stream kind + * + * @param type the stream kind (audio, video...) to disable + * @return a StreamDescriptor that can be used to disable the given stream kind + */ + static StreamDescriptor NoSelection(MediaType type); + + MediaType type; //!< Stream kind: video or audio + int identifier; //!< Internal stream identifier in the media, used for choosing which stream to enable + std::string language; //!< Language code defined by ISO 639-2, if set by the media + }; + + typedef std::vector Streams; +} + +#endif diff --git a/src/sfeMovie/Timer.cpp b/src/sfeMovie/Timer.cpp new file mode 100755 index 00000000..691c76a3 --- /dev/null +++ b/src/sfeMovie/Timer.cpp @@ -0,0 +1,220 @@ + +/* + * Timer.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "Timer.hpp" +#include "Macros.hpp" + +namespace sfe +{ + Timer::Observer::Observer() + { + } + + Timer::Observer::~Observer() + { + } + + void Timer::Observer::willPlay(const Timer& timer) + { + } + + void Timer::Observer::didPlay(const Timer& timer, Status previousStatus) + { + } + + void Timer::Observer::didPause(const Timer& timer, Status previousStatus) + { + } + + void Timer::Observer::didStop(const Timer& timer, Status previousStatus) + { + } + + bool Timer::Observer::didSeek(const Timer& timer, sf::Time oldPosition) + { + return true; + } + + Timer::Timer() : + m_pausedTime(sf::Time::Zero), + m_status(Stopped), + m_timer(), + m_observers() + { + } + + void Timer::addObserver(Observer& anObserver, int priority) + { + CHECK(m_observers.find(&anObserver) == m_observers.end(), "Timer::addObserver() - cannot add the same observer twice"); + + m_observers.insert(std::make_pair(&anObserver, priority)); + m_observersByPriority[priority].insert(&anObserver); + } + + void Timer::removeObserver(Observer& anObserver) + { + std::map::iterator it = m_observers.find(&anObserver); + + if (it != m_observers.end()) + { + m_observersByPriority[it->second].erase(&anObserver); + m_observers.erase(it); + } + } + + void Timer::play() + { + CHECK(getStatus() != Playing, "Timer::play() - timer playing twice"); + + notifyObservers(Playing); + + Status oldStatus = getStatus(); + m_status = Playing; + m_timer.restart(); + + notifyObservers(oldStatus, getStatus()); + } + + void Timer::pause() + { + CHECK(getStatus() != Paused, "Timer::pause() - timer paused twice"); + + Status oldStatus = getStatus(); + m_status = Paused; + + if (oldStatus != Stopped) + m_pausedTime += m_timer.getElapsedTime(); + + notifyObservers(oldStatus, getStatus()); + } + + void Timer::stop() + { + CHECK(getStatus() != Stopped, "Timer::stop() - timer stopped twice"); + + Status oldStatus = getStatus(); + m_status = Stopped; + m_pausedTime = sf::Time::Zero; + + notifyObservers(oldStatus, getStatus()); + + seek(sf::Time::Zero); + } + + bool Timer::seek(sf::Time position) + { + Status oldStatus = getStatus(); + sf::Time oldPosition = getOffset(); + bool couldSeek = false; + + if (oldStatus == Playing) + pause(); + + m_pausedTime = position; + couldSeek = notifyObservers(oldPosition); + + if (oldStatus == Playing) + play(); + + return couldSeek; + } + + Status Timer::getStatus() const + { + return m_status; + } + + sf::Time Timer::getOffset() const + { + if (Timer::getStatus() == Playing) + return m_pausedTime + m_timer.getElapsedTime(); + else + return m_pausedTime; + } + + void Timer::notifyObservers(Status futureStatus) + { + for (std::pair >&& pairByPriority : m_observersByPriority) + { + for (Observer* observer : pairByPriority.second) + { + switch(futureStatus) + { + case Playing: + observer->willPlay(*this); + break; + + default: + CHECK(false, "Timer::notifyObservers() - unhandled case in switch"); + } + } + } + } + + void Timer::notifyObservers(Status oldStatus, Status newStatus) + { + CHECK(oldStatus != newStatus, "Timer::notifyObservers() - inconsistency: no change happened"); + + for (std::pair >&& pairByPriority : m_observersByPriority) + { + for (Observer* observer : pairByPriority.second) + { + switch(newStatus) + { + case Playing: + observer->didPlay(*this, oldStatus); + break; + + case Paused: + observer->didPause(*this, oldStatus); + break; + + case Stopped: + observer->didStop(*this, oldStatus); + break; + default: + break; + } + } + } + } + + bool Timer::notifyObservers(sf::Time oldPosition) + { + CHECK(getStatus() != Playing, "inconsistency in timer"); + bool successfullSeeking = true; + + for (std::pair >&& pairByPriority : m_observersByPriority) + { + for (Observer* observer : pairByPriority.second) + { + if (! observer->didSeek(*this, oldPosition)) + successfullSeeking = false; + } + } + + return successfullSeeking; + } + +} diff --git a/src/sfeMovie/Timer.hpp b/src/sfeMovie/Timer.hpp new file mode 100755 index 00000000..27693665 --- /dev/null +++ b/src/sfeMovie/Timer.hpp @@ -0,0 +1,174 @@ + +/* + * Timer.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_TIMER_HPP +#define SFEMOVIE_TIMER_HPP + +#include +#include +#include "Movie.hpp" + +namespace sfe +{ + class Timer + { + public: + class Observer + { + public: + /** Default constructor + */ + Observer(); + + /** Default destructor + */ + virtual ~Observer(); + + /** Called by @a timer before playing if this Observer is registered for notifications + * + * Playing won't start until all Observers are done executing willPlay() + * + * @param timer the timer that generated the notification + */ + virtual void willPlay(const Timer& timer); + + /** Called by @a timer when playing if this Observer is registered for notifications + * + * @param timer the timer that generated the notification + * @param previousStatus the timer's status before playing + */ + virtual void didPlay(const Timer& timer, Status previousStatus); + + /** Called by @a timer when pausing if this Observer is registered for notifications + * + * @param timer the timer that generated the notification + * @param previousStatus the timer's status before playing + */ + virtual void didPause(const Timer& timer, Status previousStatus); + + /** Called by @a timer when stopping if this Observer is registered for notifications + * + * @param timer the timer that generated the notification + * @param previousStatus the timer's status before playing + */ + virtual void didStop(const Timer& timer, Status previousStatus); + + /** Called by @a timer right after seeking if this Observer is registered for notifications + * + * When this method is called, the timer is guaranteed to be paused or stopped + * + * @param timer the timer that generated the notification + * @param position the position before seeking + * @return true if the observer successfully responded to the seeking event + */ + virtual bool didSeek(const Timer& timer, sf::Time oldPosition); + }; + + /** Default constructor + */ + Timer(); + + /** Register an observer that should be notified when this timer is + * played, paused or stopped + * + * @param anObserver the observer that should receive notifications + * @param priority the priority that should be taken into account when distributing notifications, + * observers with the lowest priority value will be notified first + */ + void addObserver(Observer& anObserver, int priority = 0); + + /** Stop sending notifications to this observer + * + * @param anObserver the observer that must receive no more notification + */ + void removeObserver(Observer& anObserver); + + /** Start this timer and notify all observers + */ + void play(); + + /** Pause this timer (but do not reset it) and notify all observers + */ + void pause(); + + /** Stop this timer and reset it and notify all observers + */ + void stop(); + + /** Seek to the given position, the timer's offset is updated accordingly + * + * If the timer was playing, it is paused, seeking occurs, then it is resumed. + * The timer offset is always updated, even if seeking fails + * + * @param position the new wished timer position + * @return true if seeking succeeded, false otherwise + */ + bool seek(sf::Time position); + + /** Return this timer status + * + * @return Playing, Paused or Stopped + */ + Status getStatus() const; + + /** Return the timer's time + * + * @return the timer's time + */ + sf::Time getOffset() const; + + private: + /** Notify all observers that the timer's status is about to change to @a futureStatus + * + * The status change won't occur before all observers have received the noficiation + * + * @param futureStatus the status to which this timer is about to change + */ + void notifyObservers(Status newStatus); + + /** Notify all observers that the timer's status changed from @a oldStatus to @a newStatus + * + * @param oldStatus the timer's status before the state change + * @param newStatus the timer's status after the state change + */ + void notifyObservers(Status oldStatus, Status newStatus); + + /** Notify all observers that the timer is seeking to a new position + * + * When the observer receives the notification, the timer is guaranteed to be paused or stopped + * + * @param oldPosition the timer position before seeking + * @return true if all the observers successfully responsed to the seek event + */ + bool notifyObservers(sf::Time oldPosition); + + sf::Time m_pausedTime; + Status m_status; + sf::Clock m_timer; + std::map m_observers; + std::map > m_observersByPriority; + }; +} + +#endif diff --git a/src/sfeMovie/TimerPriorities.cpp b/src/sfeMovie/TimerPriorities.cpp new file mode 100755 index 00000000..2451cf37 --- /dev/null +++ b/src/sfeMovie/TimerPriorities.cpp @@ -0,0 +1,30 @@ + +/* + * TimerPriorities.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "TimerPriorities.hpp" + +const int DefaultTimerPriority = 0; +const int DemuxerTimerPriority = -5; // Demuxer always notified first! +const int ActiveStreamTimerPriority = 5; +const int PassiveStreamTimerPriority = 10; // Always last diff --git a/src/sfeMovie/TimerPriorities.hpp b/src/sfeMovie/TimerPriorities.hpp new file mode 100755 index 00000000..62e7f616 --- /dev/null +++ b/src/sfeMovie/TimerPriorities.hpp @@ -0,0 +1,33 @@ + +/* + * TimerPriorities.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_TIMER_PRIORITIES_HPP +#define SFEMOVIE_TIMER_PRIORITIES_HPP + +extern const int DefaultTimerPriority; +extern const int DemuxerTimerPriority; +extern const int ActiveStreamTimerPriority; +extern const int PassiveStreamTimerPriority; + +#endif diff --git a/src/sfeMovie/Utilities.cpp b/src/sfeMovie/Utilities.cpp new file mode 100755 index 00000000..dc631e7a --- /dev/null +++ b/src/sfeMovie/Utilities.cpp @@ -0,0 +1,39 @@ + +/* + * Utilities.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "Utilities.hpp" + +namespace sfe +{ + std::string mediaTypeToString(MediaType type) + { + switch (type) + { + case Audio: return "audio"; + case Video: return "video"; + default: + case Unknown: return "unknown"; + } + } +} diff --git a/src/sfeMovie/Utilities.hpp b/src/sfeMovie/Utilities.hpp new file mode 100755 index 00000000..13be1a2c --- /dev/null +++ b/src/sfeMovie/Utilities.hpp @@ -0,0 +1,57 @@ + +/* + * Utilities.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_UTILITIES_HPP +#define SFEMOVIE_UTILITIES_HPP + +#include "Stream.hpp" +#include +#include + +namespace sfe +{ + /** Gives the string representing the given @a type + * + * Conversion is done as follow: + * Audio -> audio + * Video -> video + * Unknown -> unknown + * + * @param type the media type to stringify + * @return the stringified media type + */ + std::string mediaTypeToString(MediaType type); + + /** Stringify any type of object supported by ostringstream + */ + template + std::string s(const T& obj) + { + std::ostringstream ss; + ss << obj; + return ss.str(); + } +} + +#endif diff --git a/src/sfeMovie/VideoStream.cpp b/src/sfeMovie/VideoStream.cpp new file mode 100755 index 00000000..ed3556c7 --- /dev/null +++ b/src/sfeMovie/VideoStream.cpp @@ -0,0 +1,329 @@ + +/* + * VideoStream.cpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +extern "C" +{ +#include +#include +#include +#include +} + +#include "VideoStream.hpp" +#include "Utilities.hpp" + +namespace sfe +{ + VideoStream::VideoStream(AVFormatContext*& formatCtx, AVStream*& stream, + DataSource& dataSource, std::shared_ptr timer, Delegate& delegate) : + Stream(formatCtx ,stream, dataSource, timer), + m_texture(), + m_rawVideoFrame(nullptr), + m_rgbaVideoBuffer(), + m_rgbaVideoLinesize(), + m_delegate(delegate), + m_swsCtx(nullptr) + { + int err; + + for (int i = 0; i < 4;i++) + { + m_rgbaVideoBuffer[i] = nullptr; + m_rgbaVideoLinesize[i] = 0; + } + + m_rawVideoFrame = av_frame_alloc(); + CHECK(m_rawVideoFrame, "VideoStream() - out of memory"); + + // RGBA video buffer +#if LIBAVFORMAT_VERSION_MAJOR > 56 + err = av_image_alloc(m_rgbaVideoBuffer, m_rgbaVideoLinesize, + m_stream->codecpar->width, m_stream->codecpar->height, + AV_PIX_FMT_RGBA, 1); +#else + err = av_image_alloc(m_rgbaVideoBuffer, m_rgbaVideoLinesize, + m_stream->codec->width, m_stream->codec->height, + AV_PIX_FMT_RGBA, 1); +#endif + CHECK(err >= 0, "VideoStream() - av_image_alloc() error"); + + // SFML video frame +#if LIBAVFORMAT_VERSION_MAJOR > 56 + err = m_texture.create(m_stream->codecpar->width, m_stream->codecpar->height); +#else + err = m_texture.create(m_stream->codec->width, m_stream->codec->height); +#endif + CHECK(err, "VideoStream() - sf::Texture::create() error"); + + initRescaler(); + } + + VideoStream::~VideoStream() + { + if (m_rawVideoFrame) + { + av_frame_free(&m_rawVideoFrame); + } + + if (m_rgbaVideoBuffer[0]) + { + av_freep(&m_rgbaVideoBuffer[0]); + } + + if (m_swsCtx) + { + sws_freeContext(m_swsCtx); + } + } + + MediaType VideoStream::getStreamKind() const + { + return Video; + } + + sf::Vector2i VideoStream::getFrameSize() const + { +#if LIBAVFORMAT_VERSION_MAJOR > 56 + return sf::Vector2i(m_stream->codecpar->width, m_stream->codecpar->height); +#else + return sf::Vector2i(m_stream->codec->width, m_stream->codec->height); +#endif + } + + float VideoStream::getFrameRate() const + { + return static_cast(av_q2d(av_guess_frame_rate(m_formatCtx, m_stream, nullptr))); + } + + sf::Texture& VideoStream::getVideoTexture() + { + return m_texture; + } + + void VideoStream::update() + { + sf::Time gap; + bool couldComputeGap = false; + while (getStatus() == Playing && (couldComputeGap = getSynchronizationGap(gap)) && + gap < sf::Time::Zero) + { + if (!onGetData(m_texture)) + { + setStatus(Stopped); + } + else + { + static const sf::Time skipFrameThreshold(sf::milliseconds(50)); + if (getSynchronizationGap(gap) && gap + skipFrameThreshold >= sf::Time::Zero) + m_delegate.didUpdateVideo(*this, m_texture); + } + } + + if (! couldComputeGap) + { + setStatus(Stopped); + } + } + + void VideoStream::flushBuffers() + { + m_codecBufferingDelays.clear(); + Stream::flushBuffers(); + } + + bool VideoStream::fastForward(sf::Time targetPosition) + { + sf::Time position; + bool couldGetPosition = false; + + while ((couldGetPosition = computeEncodedPosition(position)) && position < targetPosition) + { + // We HAVE to decode the frames to get a full image when we reach the target position + if (! onGetData(m_texture)) + { + return false; + } + } + + return true; + } + + void VideoStream::preload() + { + onGetData(m_texture); + } + + bool VideoStream::onGetData(sf::Texture& texture) + { + AVPacket* packet = popEncodedData(); + bool gotFrame = false; + bool goOn = false; + + if (packet) + { + goOn = true; + + while (!gotFrame && packet && goOn) + { + bool needsMoreDecoding = false; + + CHECK(packet != nullptr, "inconsistency error"); + goOn = decodePacket(packet, m_rawVideoFrame, gotFrame, needsMoreDecoding); + + if (gotFrame) + { + rescale(m_rawVideoFrame, m_rgbaVideoBuffer, m_rgbaVideoLinesize); + texture.update(m_rgbaVideoBuffer[0]); + } + + if (!gotFrame && goOn) + { + // Decoding went fine but did not produce an image. This means the decoder is working in + // a pipelined way and wants more packets to output a full image. When the first full image will + // be generated, the encoded data queue head pts will be late compared to the generated image pts + // To take that into account we accumulate this time difference for reuse in getSynchronizationGap() + m_codecBufferingDelays.push_back(packetDuration(packet)); + +#if LIBAVFORMAT_VERSION_MAJOR > 56 + if (m_codecBufferingDelays.size() > (size_t)m_stream->codecpar->video_delay) + m_codecBufferingDelays.pop_front(); +#else + if (m_codecBufferingDelays.size() > (size_t)m_stream->codec->delay) + m_codecBufferingDelays.pop_front(); +#endif + } + + if (needsMoreDecoding) + { + prependEncodedData(packet); + } + else + { +#if LIBAVCODEC_VERSION_MAJOR > 56 + av_packet_unref(packet); +#else + av_free_packet(packet); +#endif + av_free(packet); + } + + if (!gotFrame && goOn) + { + packet = popEncodedData(); + } + } + } + + return goOn; + } + + bool VideoStream::getSynchronizationGap(sf::Time& gap) + { + sf::Time position; + if (computeEncodedPosition(position)) + { + gap = (position - codecBufferingDelay()) - m_timer->getOffset(); + return true; + } + else + { + return false; + } + } + + bool VideoStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding) + { + int gotPicture = 0; + needsMoreDecoding = false; + +#if LIBAVCODEC_VERSION_MAJOR > 56 + int decodedLength = avcodec_decode_video2(m_codecCtx, outputFrame, &gotPicture, packet); +#else + int decodedLength = avcodec_decode_video2(m_stream->codec, outputFrame, &gotPicture, packet); +#endif + gotFrame = (gotPicture != 0); + + if (decodedLength > 0 || gotFrame) + { + if (decodedLength < packet->size) + { + needsMoreDecoding = true; + packet->data += decodedLength; + packet->size -= decodedLength; + } + + return true; + } + else + { + return false; + } + } + + void VideoStream::initRescaler() + { + /* create scaling context */ + int algorithm = SWS_FAST_BILINEAR; + + if (getFrameSize().x % 8 != 0 && getFrameSize().x * getFrameSize().y < 500000) + { + algorithm |= SWS_ACCURATE_RND; + } + +#if LIBAVFORMAT_VERSION_MAJOR > 56 + m_swsCtx = sws_getCachedContext(nullptr, m_stream->codecpar->width, m_stream->codecpar->height, (AVPixelFormat)m_stream->codecpar->format, + m_stream->codecpar->width, m_stream->codecpar->height, AV_PIX_FMT_RGBA, + algorithm, nullptr, nullptr, nullptr); +#else + m_swsCtx = sws_getCachedContext(nullptr, m_stream->codec->width, m_stream->codec->height, m_stream->codec->pix_fmt, + m_stream->codec->width, m_stream->codec->height, AV_PIX_FMT_RGBA, + algorithm, nullptr, nullptr, nullptr); +#endif + CHECK(m_swsCtx, "VideoStream::initRescaler() - sws_getContext() error"); + } + + void VideoStream::rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4]) + { + CHECK(frame, "VideoStream::rescale() - invalid argument"); + sws_scale(m_swsCtx, frame->data, frame->linesize, 0, frame->height, outVideoBuffer, outVideoLinesize); + } + + void VideoStream::willPlay(const Timer &timer) + { + Stream::willPlay(timer); + if (getStatus() == Stopped) + { + preload(); + } + } + + sf::Time VideoStream::codecBufferingDelay() const + { + sf::Time delay; + for (const sf::Time& packetDelay : m_codecBufferingDelays) + delay += packetDelay; + + return delay; + } +} diff --git a/src/sfeMovie/VideoStream.hpp b/src/sfeMovie/VideoStream.hpp new file mode 100755 index 00000000..066392ec --- /dev/null +++ b/src/sfeMovie/VideoStream.hpp @@ -0,0 +1,155 @@ + +/* + * VideoStream.hpp + * sfeMovie project + * + * Copyright (C) 2010-2015 Lucas Soltic + * lucas.soltic@orange.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef SFEMOVIE_VIDEOSTREAM_HPP +#define SFEMOVIE_VIDEOSTREAM_HPP + +#include "Macros.hpp" +#include "Stream.hpp" +#include +#include + +namespace sfe +{ + class VideoStream : public Stream + { + public: + struct Delegate + { + virtual void didUpdateVideo(const VideoStream& sender, const sf::Texture& image) = 0; + }; + + /** Create a video stream from the given FFmpeg stream + * + * At the end of the constructor, the stream is guaranteed + * to have all of its fields set and the decoder loaded + */ + VideoStream(AVFormatContext*& formatCtx, AVStream*& stream, + DataSource& dataSource, std::shared_ptr timer, Delegate& delegate); + + /** Default destructor + */ + virtual ~VideoStream(); + + /** Get the stream kind (either audio or video stream) + * + * @return the kind of stream represented by this stream + */ + MediaType getStreamKind() const override; + + /** Get the video frame size (width, height) + * + * @return the video frame size + */ + sf::Vector2i getFrameSize() const; + + /** Get the average amount of video frame per second for this stream + * + * @param formatCtx the FFmpeg format context to which this stream belongs + * @return the average framerate + */ + float getFrameRate() const; + + /** Get the SFML texture that contains the latest video frame + */ + sf::Texture& getVideoTexture(); + + /** Update the video frame and the stream's status + */ + void update() override; + + /** @see Stream::flushBuffers() + */ + void flushBuffers() override; + + /** @see Stream::fastForward() + */ + bool fastForward(sf::Time targetPosition) override; + + /** Load packets until one frame can be decoded + */ + void preload(); + private: + bool onGetData(sf::Texture& texture); + + /** Returns the difference between the video stream timer and the reference timer + * + * A positive value means the video stream is ahead of the reference timer + * whereas a nevatige value means the video stream is late + * + * @param[out] gap the gap, if it could be computed + * @return true if the gap could be computed, false otherwise (@ref gap is left unmodified) + */ + bool getSynchronizationGap(sf::Time& gap); + + /** Decode the encoded data @a packet into @a outputFrame + * + * gotFrame being set to false means that decoding should still continue: + * - with a new packet if false is returned + * - with the same packet if true is returned + * + * @param packet the encoded data + * @param outputFrame one decoded data + * @param gotFrame set to true if a frame has been extracted to outputFrame, false otherwise + * @param goOn set to true if decoding can continue, or false if no more data can be decoded (EOF) + * @return true if decoding succeeded, false otherwise (EOF) + */ + bool decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding); + + /** Initialize the audio resampler for conversion from many formats to signed 16 bits audio + * + * This must be called before any packet is decoded and resampled + */ + void initRescaler(); + + /** Convert the decoded video frame @a frame into RGBA image data + * + * @param frame the audio samples to convert + * @param outSamples [out] the convertedSamples + * @param outNbSamples [out] the count of samples in @a outSamples + * @param outSamplesLength [out] the length of @a outSamples in bytes + */ + void rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4]); + + // Timer::Observer interface + void willPlay(const Timer &timer) override; + + /** Returns the delay caused by the FFmpeg decoder buffering + */ + sf::Time codecBufferingDelay() const; + + // Private data + sf::Texture m_texture; + AVFrame* m_rawVideoFrame; + uint8_t *m_rgbaVideoBuffer[4]; + int m_rgbaVideoLinesize[4]; + std::list m_codecBufferingDelays; + Delegate& m_delegate; + + // Rescaler data + struct SwsContext *m_swsCtx; + }; +} + +#endif