Merge pull request #5458 from OpenRCT2/refactor/nosdl/phase-1b

Remove SDL2 dependencies: Phase 1
This commit is contained in:
Richard Jenkins 2017-05-31 20:51:08 +01:00 committed by GitHub
commit ab8e8952e0
158 changed files with 9644 additions and 7097 deletions

View File

@ -1,7 +1,7 @@
language: c
before_install:
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then bash scripts/linux/install.sh; export OPENRCT_MAKE_OPTS="-j2 -k" ; fi
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then bash scripts/linux/install.sh; export OPENRCT_MAKE_OPTS="-j2 -k all openrct2-cli" ; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then sudo gem install xcpretty-travis-formatter; fi
sudo: required
@ -16,13 +16,13 @@ matrix:
services:
- docker
env:
- OPENRCT2_CMAKE_OPTS="-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=OpenRCT2" TARGET=ubuntu_amd64
- OPENRCT2_CMAKE_OPTS="-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=OpenRCT2 -DPORTABLE=ON" TARGET=ubuntu_amd64
- secure: "S3u2VCE2Vy8KNXoeh+DhnzjCmgTX0r95uEZrXDU+IKANOOCKn7Dg4OFDZE3LY/i1y2/EUDpnR5yLC38Ks795EUP/sv/OoMl4tjQ20yERjqWh+gcIRrgx7SdVabuAh3t4aBdaLD4Pfnj5avxeCt6rL7yGnj0wdbrbJSBZPsgSnuQ="
after_success:
- sudo chown -R $USER build
- cd build
# make install is done inside docker
- mv OpenRCT2/bin/openrct2 OpenRCT2/ && mv OpenRCT2/share/openrct2 OpenRCT2/data && mv OpenRCT2/share/doc/openrct2 OpenRCT2/doc
- mv OpenRCT2/bin/openrct2 OpenRCT2/ && mv OpenRCT2/bin/libopenrct2.so OpenRCT2/ && mv OpenRCT2/share/openrct2 OpenRCT2/data && mv OpenRCT2/share/doc/openrct2 OpenRCT2/doc
- rm -rf OpenRCT2/bin OpenRCT2/share # remove empty dirs
- tar cvzf openrct2-linux.tar.gz OpenRCT2/
- if [[ "z${TRAVIS_TAG}" != "z" ]] ; then

12
.vscode/launch.json vendored
View File

@ -7,10 +7,15 @@
"request": "launch",
"program": "${workspaceRoot}/bin/openrct2",
"args": [],
"stopAtEntry": true,
"stopAtEntry": false,
"cwd": "${workspaceRoot}/bin",
"environment": [],
"externalConsole": true,
"setupCommands": [
{
"text": "-enable-pretty-printing"
}
],
"linux": {
"MIMode": "gdb"
},
@ -27,6 +32,11 @@
"request": "attach",
"program": "${workspaceRoot}/bin/openrct2",
"processId": "${command.pickProcess}",
"setupCommands": [
{
"text": "-enable-pretty-printing"
}
],
"linux": {
"MIMode": "gdb"
},

View File

@ -1,484 +1,103 @@
# CMAKE project for openrct2
cmake_minimum_required(VERSION 2.6)
#
# Execute these commands in this directory:
#
# 1. mkdir build/; cd build/
#
# 2. Choose compiler:
# Build with native toolchain:
# cmake -DCMAKE_BUILD_TYPE=Debug ..
#
# Build with mingw:
# cmake -DCMAKE_TOOLCHAIN_FILE=../CMakeLists_mingw.txt -DCMAKE_BUILD_TYPE=Debug ..
#
# 3. make
#
# project title
set (PROJECT openrct2)
# OpenRCT2 resource directory
set (ORCT2_RESOURCE_DIR ${CMAKE_INSTALL_PREFIX}/share/${PROJECT}/)
project(${PROJECT})
if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)
message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt")
endif()
add_definitions(-DORCT2_RESOURCE_DIR="${ORCT2_RESOURCE_DIR}")
add_definitions(-DHAVE_CONFIG_H)
add_definitions(-DCURL_STATICLIB)
set(ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}")
set(CMAKE_MACOSX_RPATH 1)
# Define current git branch.
set(TITLE_SEQUENCE_URL "https://github.com/OpenRCT2/title-sequences/releases/download/v0.0.5/title-sequence-v0.0.5.zip")
set(TITLE_SEQUENCE_SHA1 "79ffb2585d12abcbfce205d7696e3472a504b005")
option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.")
option(WITH_TESTS "Build tests")
option(PORTABLE "Create a portable build (-rpath=$ORIGIN)" OFF)
# Define current git branch
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY ${ROOT_DIR}
OUTPUT_VARIABLE OPENRCT2_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
add_definitions(-DOPENRCT2_BRANCH="${OPENRCT2_BRANCH}")
# Define commit hash.
# Define commit hash
execute_process(
COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY ${ROOT_DIR}
OUTPUT_VARIABLE OPENRCT2_COMMIT_SHA1
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
add_definitions(-DOPENRCT2_COMMIT_SHA1="${OPENRCT2_COMMIT_SHA1}")
# Define short commit hash.
# Define short commit hash
execute_process(
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY ${ROOT_DIR}
OUTPUT_VARIABLE OPENRCT2_COMMIT_SHA1_SHORT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
# Tagged builds are not meant to display commit info
if (NOT OPENRCT2_COMMIT_SHA1_SHORT STREQUAL "HEAD")
add_definitions(-DOPENRCT2_COMMIT_SHA1_SHORT="${OPENRCT2_COMMIT_SHA1_SHORT}")
endif()
# Convenience functions to set compiler flags only if available
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
# Include sub-projects
include("${ROOT_DIR}/src/openrct2/CMakeLists.txt" NO_POLICY_SCOPE)
include("${ROOT_DIR}/src/openrct2-cli/CMakeLists.txt" NO_POLICY_SCOPE)
include("${ROOT_DIR}/src/openrct2-ui/CMakeLists.txt" NO_POLICY_SCOPE)
function(ADD_CHECK_C_COMPILER_FLAG
_CFLAGS
_CACHE_VAR
_FLAG
)
# g2
add_custom_command(
OUTPUT g2.dat
COMMAND ./openrct2 sprite build ${CMAKE_BINARY_DIR}/g2.dat ${ROOT_DIR}/resources/g2/sprites.json
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(g2 DEPENDS ${PROJECT} g2.dat)
CHECK_C_COMPILER_FLAG("${_FLAG}" "${_CACHE_VAR}")
if(${_CACHE_VAR})
# message(STATUS "Using CFLAG: ${_FLAG}")
set(${_CFLAGS} "${${_CFLAGS}} ${_FLAG}" PARENT_SCOPE)
else()
message(STATUS "Unsupported CFLAG: ${_FLAG}")
endif()
endfunction()
function(ADD_CHECK_CXX_COMPILER_FLAG
_CXXFLAGS
_CACHE_VAR
_FLAG
)
CHECK_CXX_COMPILER_FLAG("${_FLAG}" "${_CACHE_VAR}")
if(${_CACHE_VAR})
# message(STATUS "Using CXXFLAG: ${_FLAG}")
set(${_CXXFLAGS} "${${_CXXFLAGS}} ${_FLAG}" PARENT_SCOPE)
else()
message(STATUS "Unsupported CXXFLAG: ${_FLAG}")
endif()
endfunction()
# pkg-config
INCLUDE(FindPkgConfig)
# Needed for linking with non-broken OpenSSL on Apple platforms
if (APPLE)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/openssl/lib/pkgconfig")
endif (APPLE)
# Options
option(DISABLE_HTTP_TWITCH "Disable HTTP and Twitch support.")
option(DISABLE_NETWORK "Disable multiplayer functionality. Mainly for testing.")
option(STATIC "Create a static build.")
option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags")
option(DISABLE_OPENGL "Disable OpenGL support.")
option(DISABLE_RCT2 "Build a standalone version, without using code and data segments from vanilla. On by default." ON)
option(USE_MMAP "Use mmap to try loading rct2's data segment into memory.")
option(WITH_TESTS "Build tests")
option(DISABLE_TTF "Disable support for TTF provided by SDL2_ttf.")
option(ENABLE_LIGHTFX "Enable lighting effects." ON)
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -fstrict-aliasing -Werror -Wundef -Wmissing-declarations -Winit-self -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-missing-braces ")
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -Wno-comment -Wshadow -Wmissing-declarations -Wnonnull")
# macOS builds fail on the use of tmpnam otherwise (#4959)
if(APPLE)
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -Wno-error=deprecated-declarations -Wno-error=objc-method-access")
endif()
# On mingw all code is already PIC, this will avoid compiler error on redefining this option
if(NOT MINGW)
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -fPIC")
endif()
if (NOT DISABLE_RCT2)
set (FORCE32 ON)
message("DISABLE_RCT2 implies FORCE32")
endif()
if (DISABLE_HTTP_TWITCH)
add_definitions(-DDISABLE_HTTP -DDISABLE_TWITCH)
endif (DISABLE_HTTP_TWITCH)
if (DISABLE_TTF)
add_definitions(-DNO_TTF)
endif (DISABLE_TTF)
if (ENABLE_LIGHTFX)
add_definitions(-D__ENABLE_LIGHTFX__)
endif (ENABLE_LIGHTFX)
# Launchpad turns on -Wdate-time for compilers that support it, this shouldn't break our build
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_WRITE_STRINGS -Wno-error=date-time)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_WRITE_STRINGS -Wno-error=date-time)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_NULL_DEREFERENCE -Wnull-dereference)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_FINAL_TYPES -Wsuggest-final-types)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_FINAL_METHODS -Wsuggest-final-methods)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_OVERRIDE -Wsuggest-override)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_DUPLICATED_COND -Wduplicated-cond)
if (MINGW)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_OVERRIDE -Wno-error=redundant-decls)
endif ()
# Items below are not supported by ICC
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_REDUNDANT_DECLS -Wredundant-decls)
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_IGNORED_QUALIFIERS -Wignored-qualifiers)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_REDUNDANT_DECLS -Wredundant-decls)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_IGNORED_QUALIFIERS -Wignored-qualifiers)
# -Wstrict-overflow is only active when -fstrict-overflow is enabled, but -fstrict-overflow
# is enabled on -O2, -O3, -Os. This should help catch bugs locally before they reach Travis
# As of 2a435bf -Wstrict-overflow=1 passes, but higher values do not.
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -fstrict-overflow")
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_STRICT_OVERFLOW -Wstrict-overflow=1)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_STRICT_OVERFLOW -Wstrict-overflow=1)
if (FORCE32)
set(TARGET_M "-m32")
endif()
if (FORCE32)
set(OBJ_FORMAT "elf32-i386")
set(LINKER_SCRIPT "ld_script_i386.xc")
endif ()
if (DISABLE_OPENGL)
add_definitions(-DDISABLE_OPENGL)
else (DISABLE_OPENGL)
# Makes OpenGL function get queried in run-time rather than linked-in
add_definitions(-DOPENGL_NO_LINK)
endif (DISABLE_OPENGL)
if (USE_MMAP)
add_definitions(-DUSE_MMAP)
endif (USE_MMAP)
if (DISABLE_NETWORK)
add_definitions(-DDISABLE_NETWORK)
else (DISABLE_NETWORK)
if (WIN32)
SET(NETWORKLIBS ${NETWORKLIBS} ws2_32)
endif (WIN32)
# If you are on macOS, CMake might try using system-provided OpenSSL.
# This is too old and will not work.
PKG_CHECK_MODULES(SSL REQUIRED openssl>=1.0.0)
endif (DISABLE_NETWORK)
if (DISABLE_RCT2)
add_definitions(-DNO_RCT2)
endif (DISABLE_RCT2)
# Start of library checks
PKG_CHECK_MODULES(PNG libpng>=1.6)
if (NOT PNG_FOUND)
PKG_CHECK_MODULES(PNG libpng16)
endif (NOT PNG_FOUND)
if (NOT PNG_FOUND)
PKG_CHECK_MODULES(PNG libpng>=1.2)
endif (NOT PNG_FOUND)
if (NOT PNG_FOUND)
PKG_CHECK_MODULES(PNG REQUIRED libpng12)
endif (NOT PNG_FOUND)
PKG_CHECK_MODULES(ZLIB REQUIRED zlib)
PKG_CHECK_MODULES(JANSSON REQUIRED jansson>=2.5)
# Handle creating the rct2 text and data files on macOS and Linux
# See details in src/openrct2/rct2/interop.c:rct2_interop_setup_segment for how the values
# were derived.
if ((NOT DISABLE_RCT2) AND UNIX)
add_custom_command(
OUTPUT openrct2_text
COMMAND dd if="${CMAKE_CURRENT_SOURCE_DIR}/openrct2.exe" of="${CMAKE_BINARY_DIR}/openrct2_text" bs=4096 skip=1 count=1187
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/openrct2.exe
)
add_custom_command(
OUTPUT openrct2_data
COMMAND dd if="${CMAKE_CURRENT_SOURCE_DIR}/openrct2.exe" of="${CMAKE_BINARY_DIR}/openrct2_data" bs=4096 skip=1188 count=318
COMMAND dd if=/dev/zero of="${CMAKE_BINARY_DIR}/openrct2_data" bs=4096 seek=318 count=2630 conv=notrunc
COMMAND dd if="${CMAKE_CURRENT_SOURCE_DIR}/openrct2.exe" of="${CMAKE_BINARY_DIR}/openrct2_data" bs=4096 skip=1506 seek=2948 count=1 conv=notrunc
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/openrct2.exe
)
add_custom_target(segfiles DEPENDS openrct2_text openrct2_data)
if (NOT USE_MMAP)
if (APPLE)
set(RCT2_SEGMENT_LINKER_FLAGS "-sectcreate rct2_text __text ${CMAKE_BINARY_DIR}/openrct2_text -sectcreate rct2_data __data ${CMAKE_BINARY_DIR}/openrct2_data -segaddr rct2_data 0x8a4000 -segprot rct2_data rwx rwx -segaddr rct2_text 0x401000 -segprot rct2_text rwx rwx -segaddr __TEXT 0x2000000 -read_only_relocs suppress")
else (APPLE)
# For Linux we have to use objcopy to wrap regular binaries into a linkable
# format. We use specific section names which are then referenced in a
# bespoke linker script so they can be placed at predefined VMAs.
add_custom_command(
OUTPUT openrct2_text_section.o
COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_text openrct2_text_section.o --rename-section .data=.rct2_text,contents,alloc,load,readonly,code
DEPENDS segfiles
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_command(
OUTPUT openrct2_data_section.o
COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_data openrct2_data_section.o --rename-section .data=.rct2_data,contents,alloc,load,readonly,data
DEPENDS segfiles
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(linkable_sections DEPENDS openrct2_text_section.o openrct2_data_section.o)
SET_SOURCE_FILES_PROPERTIES(
openrct2_text_section.o openrct2_data_section.o
PROPERTIES
EXTERNAL_OBJECT true
GENERATED true
)
# can't use GLOB here, as the files don't exist yet at cmake-time
set(RCT2_SECTIONS "${CMAKE_BINARY_DIR}/openrct2_data_section.o" "${CMAKE_BINARY_DIR}/openrct2_text_section.o")
set(RCT2_SEGMENT_LINKER_FLAGS "-Wl,-T,\"${CMAKE_CURRENT_SOURCE_DIR}/distribution/linux/${LINKER_SCRIPT}\"")
endif (APPLE)
endif (NOT USE_MMAP)
elseif (USE_MMAP)
# No dd here, can't extract data segment
message(WARNING "Sorry, your platform is not supported, you have to extract data segment manually")
endif ((NOT DISABLE_RCT2) AND UNIX)
set(DEBUG_LEVEL 0 CACHE STRING "Select debug level for compilation. Use value in range 03.")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEBUG=${DEBUG_LEVEL}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG=${DEBUG_LEVEL}")
# include lib
include_directories("lib/")
# add source files
file(GLOB_RECURSE ORCT2_SOURCES "src/openrct2/*.c" "src/openrct2/*.cpp" "src/openrct2/*.h" "src/openrct2/*.hpp")
if (APPLE)
file(GLOB_RECURSE ORCT2_MM_SOURCES "src/openrct2/*.m")
set_source_files_properties(${ORCT2_MM_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c -fmodules")
endif (APPLE)
if (APPLE AND NOT USE_MMAP)
set(PIE_FLAG "-fno-pie")
else ()
set(PIE_FLAG "-fpie")
endif ()
# set necessary flags to compile code as is
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TARGET_M} -std=gnu11 ${COMMON_COMPILE_OPTIONS} -Wimplicit")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TARGET_M} -std=gnu++14 ${COMMON_COMPILE_OPTIONS} -Wnon-virtual-dtor")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TARGET_M}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS} ${PIE_FLAG}")
if (MINGW)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++")
endif ()
option(WITH_BREAKPAD "Enable breakpad")
if (WITH_BREAKPAD)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_BREAKPAD")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BREAKPAD")
set(BREAKPAD_DIR "/home/janisozaur/workspace/breakpad/src")
set(BREAKPAD_INCLUDE_DIR "${BREAKPAD_DIR}/src")
set(BREAKPAD_LIBRARY_DIR "${BREAKPAD_DIR}/src/client/linux")
set(BREAKPAD_LIBS breakpad_client pthread)
endif (WITH_BREAKPAD)
PKG_CHECK_MODULES(LIBZIP REQUIRED libzip>=1.0)
# find and include SDL2
PKG_CHECK_MODULES(SDL2 REQUIRED sdl2)
if (NOT DISABLE_TTF)
PKG_CHECK_MODULES(SDL2_TTF REQUIRED SDL2_ttf)
endif (NOT DISABLE_TTF)
if (STATIC)
if (NOT DISABLE_TTF)
# FreeType is required by SDL2_ttf, but not wired up properly in package
PKG_CHECK_MODULES(FREETYPE REQUIRED freetype2)
endif (NOT DISABLE_TTF)
SET(SDL2LIBS ${SDL2_STATIC_LIBRARIES} ${SDL2_TTF_STATIC_LIBRARIES} ${FREETYPE_STATIC_LIBRARIES})
else (STATIC)
SET(SDL2LIBS ${SDL2_LIBRARIES} ${SDL2_TTF_LIBRARIES})
endif (STATIC)
if (STATIC)
set(STATIC_START "-static")
SET(REQUIREDLIBS ${PNG_STATIC_LIBRARIES} ${JANSSON_STATIC_LIBRARIES} ${ZLIB_STATIC_LIBRARIES} ${SSL_STATIC_LIBRARIES} ${LIBZIP_STATIC_LIBRARIES})
else (STATIC)
SET(REQUIREDLIBS ${PNG_LIBRARIES} ${JANSSON_LIBRARIES} ${ZLIB_LIBRARIES} ${SSL_LIBRARIES} ${LIBZIP_LIBRARIES})
endif (STATIC)
if (NOT DISABLE_OPENGL)
if (WIN32)
# Curl depends on openssl and ws2 in mingw builds, but is not wired up in pkg-config
set(GLLIBS opengl32)
# mingw complains about "%zu" not being a valid format specifier for printf, unless we
# tell it that it is
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__USE_MINGW_ANSI_STDIO=1")
elseif (APPLE)
# GL doesn't work nicely with macOS, while find_package doesn't work with multiarch on Ubuntu.
find_package(OpenGL REQUIRED)
set(GLLIBS ${OPENGL_LIBRARY})
else (WIN32)
PKG_CHECK_MODULES(GL REQUIRED gl)
set(GLLIBS ${GL_LIBRARIES})
endif (WIN32)
endif (NOT DISABLE_OPENGL)
if (NOT DISABLE_HTTP_TWITCH)
PKG_CHECK_MODULES(LIBCURL REQUIRED libcurl)
if (WIN32)
# Curl depends on openssl and ws2 in mingw builds, but is not wired up in pkg-config
set(WSLIBS ws2_32)
endif (WIN32)
if (STATIC)
SET(HTTPLIBS ${LIBCURL_STATIC_LIBRARIES} ${WSLIBS})
else (STATIC)
SET(HTTPLIBS ${LIBCURL_LIBRARIES} ${WSLIBS})
endif (STATIC)
endif (NOT DISABLE_HTTP_TWITCH)
PKG_CHECK_MODULES(SPEEX REQUIRED speexdsp)
if (UNIX AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "BSD")
# Include libdl for dlopen
set(DLLIB dl)
endif ()
# libzip library has issues with some of the enabled warning, include it as system library
INCLUDE_DIRECTORIES(SYSTEM ${LIBZIP_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${SDL2_INCLUDE_DIRS} ${LIBCURL_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} ${SPEEX_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${BREAKPAD_INCLUDE_DIR} ${SSL_INCLUDE_DIRS})
LINK_DIRECTORIES(${SDL2_LIBRARY_DIRS} ${JANSSON_LIBRARY_DIRS} ${LIBCURL_LIBRARY_DIRS} ${PNG_LIBRARY_DIRS} ${ZLIB_LIBRARY_DIRS} ${BREAKPAD_LIBRARY_DIR} ${SSL_LIBRARY_DIRS} ${LIBZIP_LIBRARY_DIRS})
if (NOT DISABLE_RCT2)
# Disable optimizations for addresses.c for all compilers, to allow optimized
# builds without need for -fno-omit-frame-pointer
set_source_files_properties(src/openrct2/addresses.c PROPERTIES COMPILE_FLAGS -O0)
endif (NOT DISABLE_RCT2)
if (WIN32)
# build as library for now, replace with add_executable
if (USE_MMAP OR DISABLE_RCT2)
add_executable(${PROJECT} ${ORCT2_SOURCES} ${SPEEX_SOURCES})
else ()
add_library(${PROJECT} SHARED ${ORCT2_SOURCES} ${SPEEX_SOURCES})
# Include tests
if (WITH_TESTS)
enable_testing()
if (UNIX AND (NOT USE_MMAP) AND (NOT DISABLE_RCT2) AND (FORCE32))
include("${ROOT_DIR}/test/testpaint/CMakeLists.txt" NO_POLICY_SCOPE)
endif ()
else (WIN32)
add_executable(${PROJECT} ${ORCT2_SOURCES} ${ORCT2_MM_SOURCES} ${RCT2_SECTIONS})
if (NOT DISABLE_RCT2)
add_dependencies(${PROJECT} segfiles)
if (NOT APPLE AND NOT USE_MMAP)
add_dependencies(${PROJECT} linkable_sections)
endif ()
set_target_properties(${PROJECT} PROPERTIES LINK_FLAGS ${RCT2_SEGMENT_LINKER_FLAGS})
endif (NOT DISABLE_RCT2)
add_custom_command(
OUTPUT g2.dat
COMMAND ./openrct2 sprite build ${CMAKE_BINARY_DIR}/g2.dat ${CMAKE_CURRENT_SOURCE_DIR}/resources/g2/sprites.json
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(g2 DEPENDS ${PROJECT} g2.dat)
endif (WIN32)
if (UNIX AND NOT APPLE AND NOT DISABLE_TTF)
# FontConfig for TrueType fonts.
PKG_CHECK_MODULES(FONTCONFIG REQUIRED fontconfig)
INCLUDE_DIRECTORIES(${FONTCONFIG_INCLUDE_DIRS})
TARGET_LINK_LIBRARIES(${PROJECT} ${FONTCONFIG_LIBRARIES})
endif (UNIX AND NOT APPLE AND NOT DISABLE_TTF)
# install into ${CMAKE_INSTALL_PREFIX}/bin/
#install (TARGETS ${PROJECT} DESTINATION bin)
# libopenrct2.dll -> openrct2.dll
set_target_properties(${PROJECT} PROPERTIES PREFIX "")
set_target_properties(${PROJECT} PROPERTIES COMPILE_FLAGS "-Wundef")
# Link shared libs first
TARGET_LINK_LIBRARIES(${PROJECT} pthread ${GLLIBS})
# if creating a static binary, precede libraries with -static, then name all the libs
TARGET_LINK_LIBRARIES(${PROJECT} ${STATIC_START} ${SDL2LIBS} ${HTTPLIBS} ${NETWORKLIBS} ${SPEEX_LIBRARIES} ${DLLIB} ${REQUIREDLIBS} ${BREAKPAD_LIBS})
if (APPLE OR STATIC OR ${CMAKE_SYSTEM_NAME} MATCHES "BSD")
FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c)
TARGET_LINK_LIBRARIES(${PROJECT} ${ICONV_LIBRARIES})
include("${ROOT_DIR}/test/tests/CMakeLists.txt" NO_POLICY_SCOPE)
endif ()
# Install
# Don't recurse, grab all *.txt and *.md files
file(GLOB DOC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/distribution/*.txt")
list(APPEND DOC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/contributors.md" "${CMAKE_CURRENT_SOURCE_DIR}/licence.txt")
file(GLOB DOC_FILES "${ROOT_DIR}/distribution/*.txt")
list(APPEND DOC_FILES "${ROOT_DIR}/contributors.md"
"${ROOT_DIR}/licence.txt")
# CMake does not allow specifying a dependency chain which includes built-in
# targets, like `install`, so we have to trick it and execute dependency ourselves.
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" --build \"${CMAKE_CURRENT_BINARY_DIR}\" --target g2)")
install(CODE "file(DOWNLOAD https://github.com/OpenRCT2/title-sequences/releases/download/v0.0.5/title-sequence-v0.0.5.zip \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/${PROJECT}/title/title-sequences.zip EXPECTED_HASH SHA1=79ffb2585d12abcbfce205d7696e3472a504b005 SHOW_PROGRESS)")
install(CODE "file(DOWNLOAD ${TITLE_SEQUENCE_URL} \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/${PROJECT}/title/title-sequences.zip EXPECTED_HASH SHA1=${TITLE_SEQUENCE_SHA1} SHOW_PROGRESS)")
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E chdir \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/${PROJECT}/title/ \"${CMAKE_COMMAND}\" -E tar xvf title-sequences.zip)")
install(CODE "file(REMOVE \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/${PROJECT}/title/title-sequences.zip)")
install(TARGETS ${PROJECT} RUNTIME DESTINATION bin)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/g2.dat" DESTINATION share/${PROJECT})
install(DIRECTORY data/ DESTINATION share/${PROJECT})
install(FILES ${DOC_FILES} DESTINATION share/doc/${PROJECT})
install(FILES resources/logo/icon_x16.png DESTINATION share/icons/hicolor/16x16/apps RENAME openrct2.png)
install(FILES resources/logo/icon_x32.png DESTINATION share/icons/hicolor/32x32/apps RENAME openrct2.png)
install(FILES resources/logo/icon_x64.png DESTINATION share/icons/hicolor/64x64/apps RENAME openrct2.png)
install(FILES resources/logo/icon_x128.png DESTINATION share/icons/hicolor/128x128/apps RENAME openrct2.png)
install(FILES resources/logo/icon_x256.png DESTINATION share/icons/hicolor/256x256/apps RENAME openrct2.png)
install(FILES resources/logo/icon_flag.svg DESTINATION share/icons/hicolor/scalable/apps RENAME openrct2.svg)
install(FILES distribution/linux/openrct2.desktop DESTINATION share/applications)
if (WITH_TESTS)
enable_testing()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/tests/)
if (WIN32)
install(TARGETS "libopenrct2" RUNTIME DESTINATION "bin")
else ()
if (PORTABLE)
install(TARGETS "libopenrct2" LIBRARY DESTINATION "bin")
else ()
install(TARGETS "libopenrct2" LIBRARY DESTINATION "lib")
endif ()
endif ()
if (UNIX AND (NOT USE_MMAP) AND (NOT DISABLE_RCT2) AND (FORCE32))
set(OPENRCT2_SRCPATH "src/openrct2")
file(GLOB_RECURSE ORCT2_RIDE_SOURCES "${OPENRCT2_SRCPATH}/ride/*/*.c")
file(GLOB_RECURSE ORCT2_RIDE_DEP_SOURCES "${OPENRCT2_SRCPATH}/ride/ride_data.c" "${OPENRCT2_SRCPATH}/ride/track_data.c" "${OPENRCT2_SRCPATH}/ride/track_data_old.c" "${OPENRCT2_SRCPATH}/ride/track_paint.c" "${OPENRCT2_SRCPATH}/rct2/addresses.c" "${OPENRCT2_SRCPATH}/diagnostic.c" "${OPENRCT2_SRCPATH}/rct2/hook.c" "${OPENRCT2_SRCPATH}/paint/map_element/map_element.c" "${OPENRCT2_SRCPATH}/paint/paint_helpers.c")
file(GLOB_RECURSE ORCT2_TESTPAINT_SOURCES "test/testpaint/*.c" "test/testpaint/*.cpp" "test/testpaint/*.h")
add_executable(testpaint EXCLUDE_FROM_ALL ${ORCT2_RIDE_SOURCES} ${ORCT2_RIDE_DEP_SOURCES} ${ORCT2_TESTPAINT_SOURCES} ${RCT2_SECTIONS})
target_include_directories(testpaint PRIVATE "src/")
set_target_properties(testpaint PROPERTIES COMPILE_FLAGS "-DNO_VEHICLES -D__TESTPAINT__ -Wno-unused")
set_target_properties(testpaint PROPERTIES LINK_FLAGS ${RCT2_SEGMENT_LINKER_FLAGS})
add_dependencies(testpaint segfiles)
endif ()
set(CPACK_PACKAGE_VERSION_MAJOR 0)
set(CPACK_PACKAGE_VERSION_MINOR 0)
set(CPACK_PACKAGE_VERSION_PATCH 8)
INCLUDE(CPack)
install(TARGETS "openrct2" RUNTIME DESTINATION "bin")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/g2.dat" DESTINATION "share/openrct2")
install(DIRECTORY "data/" DESTINATION "share/openrct2")
install(FILES ${DOC_FILES} DESTINATION "share/doc/openrct2")
install(FILES "resources/logo/icon_x16.png" DESTINATION "share/icons/hicolor/16x16/apps RENAME openrct2.png")
install(FILES "resources/logo/icon_x32.png" DESTINATION "share/icons/hicolor/32x32/apps RENAME openrct2.png")
install(FILES "resources/logo/icon_x64.png" DESTINATION "share/icons/hicolor/64x64/apps RENAME openrct2.png")
install(FILES "resources/logo/icon_x128.png" DESTINATION "share/icons/hicolor/128x128/apps RENAME openrct2.png")
install(FILES "resources/logo/icon_x256.png" DESTINATION "share/icons/hicolor/256x256/apps RENAME openrct2.png")
install(FILES "resources/logo/icon_flag.svg" DESTINATION "share/icons/hicolor/scalable/apps RENAME openrct2.svg")
install(FILES "distribution/linux/openrct2.desktop" DESTINATION "share/applications")

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,11 @@
0.0.8 (in development)
------------------------------------------------------------------------
- Feature: OpenRCT2 now starts up on the display it was last shown on.
- Improved: Mouse can now be dragged to select scenery when saving track designs
- Fix: [#3178, #5456] Paths with non-ASCII characters not handled properly on macOS.
- Fix: [#3681] Steel Twister rollercoaster always shows all track designs
- Fix: Track components added by OpenRCT2 are now usable in older scenarios.
- Technical: [#5458] Begin offering headless build with reduced compile- and run-time dependencies
0.0.7 (2017-05-03)
------------------------------------------------------------------------

View File

@ -1,16 +1,18 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio 15
VisualStudioVersion = 15.0.26228.9
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openrct2-dll", "src\openrct2-dll\openrct2-dll.vcxproj", "{7B8DB129-79EF-417E-B372-8A18E009D261}"
ProjectSection(ProjectDependencies) = postProject
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951} = {8DD8AB7D-2EA6-44E3-8265-BAF08E832951}
{D24D94F6-2A74-480C-B512-629C306CE92F} = {D24D94F6-2A74-480C-B512-629C306CE92F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openrct2-win", "src\openrct2-win\openrct2-win.vcxproj", "{7A9A57D5-7006-4208-A290-5491BA3C8808}"
ProjectSection(ProjectDependencies) = postProject
{7B8DB129-79EF-417E-B372-8A18E009D261} = {7B8DB129-79EF-417E-B372-8A18E009D261}
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951} = {8DD8AB7D-2EA6-44E3-8265-BAF08E832951}
{D24D94F6-2A74-480C-B512-629C306CE92F} = {D24D94F6-2A74-480C-B512-629C306CE92F}
EndProjectSection
EndProject
@ -27,6 +29,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2202A816-377
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{480B577D-4E4A-4757-9A42-28A9AD33E6B0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libopenrct2ui", "src\openrct2-ui\libopenrct2ui.vcxproj", "{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}"
ProjectSection(ProjectDependencies) = postProject
{D24D94F6-2A74-480C-B512-629C306CE92F} = {D24D94F6-2A74-480C-B512-629C306CE92F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openrct2-cli", "src\openrct2-cli\openrct2-cli.vcxproj", "{B6808F71-30B4-4499-8FF6-0B1C86391842}"
ProjectSection(ProjectDependencies) = postProject
{D24D94F6-2A74-480C-B512-629C306CE92F} = {D24D94F6-2A74-480C-B512-629C306CE92F}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@ -35,35 +47,6 @@ Global
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|Win32.ActiveCfg = Debug|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|Win32.Build.0 = Debug|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|x64.ActiveCfg = Debug|x64
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|x64.Build.0 = Debug|x64
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|Win32.ActiveCfg = Release|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|Win32.Build.0 = Release|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|x64.ActiveCfg = Release|x64
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|x64.Build.0 = Release|x64
{57E60BA1-FB76-4316-909E-C1449C142327}.Debug|Win32.ActiveCfg = Debug|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Debug|Win32.Build.0 = Debug|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Debug|x64.ActiveCfg = Debug|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Release|Win32.ActiveCfg = Release|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Release|x64.ActiveCfg = Release|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|Win32.ActiveCfg = Debug|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|Win32.Build.0 = Debug|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|x64.ActiveCfg = Debug|x64
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|x64.Build.0 = Debug|x64
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|Win32.ActiveCfg = Release|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|Win32.Build.0 = Release|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|x64.ActiveCfg = Release|x64
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|x64.Build.0 = Release|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|Win32.ActiveCfg = Debug|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|Win32.Build.0 = Debug|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|x64.ActiveCfg = Debug|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|x64.Build.0 = Debug|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|Win32.ActiveCfg = Release|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|Win32.Build.0 = Release|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|x64.ActiveCfg = Release|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|x64.Build.0 = Release|x64
{7B8DB129-79EF-417E-B372-8A18E009D261}.Debug|Win32.ActiveCfg = Debug|Win32
{7B8DB129-79EF-417E-B372-8A18E009D261}.Debug|Win32.Build.0 = Debug|Win32
{7B8DB129-79EF-417E-B372-8A18E009D261}.Debug|x64.ActiveCfg = Debug|x64
@ -72,15 +55,62 @@ Global
{7B8DB129-79EF-417E-B372-8A18E009D261}.Release|Win32.Build.0 = Release|Win32
{7B8DB129-79EF-417E-B372-8A18E009D261}.Release|x64.ActiveCfg = Release|x64
{7B8DB129-79EF-417E-B372-8A18E009D261}.Release|x64.Build.0 = Release|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|Win32.ActiveCfg = Debug|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|Win32.Build.0 = Debug|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|x64.ActiveCfg = Debug|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Debug|x64.Build.0 = Debug|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|Win32.ActiveCfg = Release|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|Win32.Build.0 = Release|Win32
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|x64.ActiveCfg = Release|x64
{7A9A57D5-7006-4208-A290-5491BA3C8808}.Release|x64.Build.0 = Release|x64
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|Win32.ActiveCfg = Debug|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|Win32.Build.0 = Debug|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|x64.ActiveCfg = Debug|x64
{D24D94F6-2A74-480C-B512-629C306CE92F}.Debug|x64.Build.0 = Debug|x64
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|Win32.ActiveCfg = Release|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|Win32.Build.0 = Release|Win32
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|x64.ActiveCfg = Release|x64
{D24D94F6-2A74-480C-B512-629C306CE92F}.Release|x64.Build.0 = Release|x64
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|Win32.ActiveCfg = Debug|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|Win32.Build.0 = Debug|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|x64.ActiveCfg = Debug|x64
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Debug|x64.Build.0 = Debug|x64
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|Win32.ActiveCfg = Release|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|Win32.Build.0 = Release|Win32
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|x64.ActiveCfg = Release|x64
{62B020FA-E4FB-4C6E-B32A-DC999470F155}.Release|x64.Build.0 = Release|x64
{57E60BA1-FB76-4316-909E-C1449C142327}.Debug|Win32.ActiveCfg = Debug|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Debug|Win32.Build.0 = Debug|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Debug|x64.ActiveCfg = Debug|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Release|Win32.ActiveCfg = Release|Win32
{57E60BA1-FB76-4316-909E-C1449C142327}.Release|x64.ActiveCfg = Release|Win32
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Debug|Win32.ActiveCfg = Debug|Win32
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Debug|Win32.Build.0 = Debug|Win32
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Debug|x64.ActiveCfg = Debug|x64
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Debug|x64.Build.0 = Debug|x64
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Release|Win32.ActiveCfg = Release|Win32
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Release|Win32.Build.0 = Release|Win32
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Release|x64.ActiveCfg = Release|x64
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}.Release|x64.Build.0 = Release|x64
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Debug|Win32.ActiveCfg = Debug|Win32
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Debug|Win32.Build.0 = Debug|Win32
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Debug|x64.ActiveCfg = Debug|x64
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Debug|x64.Build.0 = Debug|x64
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Release|Win32.ActiveCfg = Release|Win32
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Release|Win32.Build.0 = Release|Win32
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Release|x64.ActiveCfg = Release|x64
{B6808F71-30B4-4499-8FF6-0B1C86391842}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D24D94F6-2A74-480C-B512-629C306CE92F} = {2202A816-377D-4FA0-A7AF-7D4105F8A4FB}
{57E60BA1-FB76-4316-909E-C1449C142327} = {480B577D-4E4A-4757-9A42-28A9AD33E6B0}
{62B020FA-E4FB-4C6E-B32A-DC999470F155} = {480B577D-4E4A-4757-9A42-28A9AD33E6B0}
{7A9A57D5-7006-4208-A290-5491BA3C8808} = {2202A816-377D-4FA0-A7AF-7D4105F8A4FB}
{7B8DB129-79EF-417E-B372-8A18E009D261} = {2202A816-377D-4FA0-A7AF-7D4105F8A4FB}
{7A9A57D5-7006-4208-A290-5491BA3C8808} = {2202A816-377D-4FA0-A7AF-7D4105F8A4FB}
{D24D94F6-2A74-480C-B512-629C306CE92F} = {2202A816-377D-4FA0-A7AF-7D4105F8A4FB}
{62B020FA-E4FB-4C6E-B32A-DC999470F155} = {480B577D-4E4A-4757-9A42-28A9AD33E6B0}
{57E60BA1-FB76-4316-909E-C1449C142327} = {480B577D-4E4A-4757-9A42-28A9AD33E6B0}
{8DD8AB7D-2EA6-44E3-8265-BAF08E832951} = {2202A816-377D-4FA0-A7AF-7D4105F8A4FB}
{B6808F71-30B4-4499-8FF6-0B1C86391842} = {2202A816-377D-4FA0-A7AF-7D4105F8A4FB}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,41 @@
# CMAKE project for openrct2-cli (CLI-only build of OpenRCT2)
cmake_minimum_required(VERSION 2.6)
if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)
message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt")
endif ()
if (PORTABLE)
set(CMAKE_INSTALL_RPATH "$ORIGIN")
endif ()
# CMake dependencies
include(FindPkgConfig)
# Third party libraries (which we want to eventually remove from openrct2-cli)
PKG_CHECK_MODULES(SDL2 REQUIRED sdl2)
# Sources
file(GLOB_RECURSE OPENRCT2_CLI_SOURCES
"${CMAKE_CURRENT_LIST_DIR}/*.c"
"${CMAKE_CURRENT_LIST_DIR}/*.cpp"
"${CMAKE_CURRENT_LIST_DIR}/*.h"
"${CMAKE_CURRENT_LIST_DIR}/*.hpp")
# Outputs
set (PROJECT openrct2-cli)
project(${PROJECT})
add_executable(${PROJECT} EXCLUDE_FROM_ALL ${OPENRCT2_CLI_SOURCES})
target_link_libraries(${PROJECT} "libopenrct2")
# Includes
target_include_directories(${PROJECT} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/.."
${SDL2_INCLUDE_DIRS})
# Compiler flags
if (FORCE32)
set(TARGET_M "-m32")
endif ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu11 ${TARGET_M}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++14 ${TARGET_M}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${TARGET_M}")

30
src/openrct2-cli/Cli.cpp Normal file
View File

@ -0,0 +1,30 @@
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/Context.h>
using namespace OpenRCT2;
/**
* Main entry point for non-Windows sytems. Windows instead uses its own DLL proxy.
*/
int main(int argc, char * * argv)
{
IContext * context = CreateContext();
int exitCode = context->RunOpenRCT2(argc, argv);
delete context;
return exitCode;
}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)'==''">..\..\</SolutionDir>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{B6808F71-30B4-4499-8FF6-0B1C86391842}</ProjectGuid>
<RootNamespace>openrct2-cli</RootNamespace>
<ProjectName>openrct2-cli</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>
<Import Project="..\..\openrct2.common.props" />
<PropertyGroup>
<TargetName>openrct2-cli</TargetName>
<LibraryPath>$(SolutionDir)bin;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<ObjectFileName>$(IntDir)\%(RelativeDir)</ObjectFileName>
<AdditionalOptions>$(OPENRCT2_CL_ADDITIONALOPTIONS) %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>libopenrct2.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ProgramDatabaseFile>$(OutDir)openrct2-cli.pdb</ProgramDatabaseFile>
<SubSystem>Console</SubSystem>
</Link>
<Lib>
<TargetMachine Condition="'$(Platform)'=='Win32'">MachineX86</TargetMachine>
<TargetMachine Condition="'$(Platform)'=='x64'">MachineX64</TargetMachine>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ResourceCompile Include="..\..\resources\OpenRCT2.rc" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Cli.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@ -20,7 +20,16 @@
#include <stdlib.h>
#include <windows.h>
#include <shellapi.h>
#include <openrct2/audio/AudioContext.h>
#include <openrct2/Context.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/ui/UiContext.h>
#include <openrct2-ui/audio/AudioContext.h>
#include <openrct2-ui/UiContext.h>
using namespace OpenRCT2;
using namespace OpenRCT2::Audio;
using namespace OpenRCT2::Ui;
#define DLLEXPORT extern "C" __declspec(dllexport)
@ -28,6 +37,25 @@ static char * * GetCommandLineArgs(int argc, wchar_t * * argvW);
static void FreeCommandLineArgs(int argc, char * * argv);
static char * ConvertUTF16toUTF8(const wchar_t * src);
static int NormalisedMain(int argc, char * * argv)
{
core_init();
int runGame = cmdline_run((const char * *)argv, argc);
if (runGame == 1)
{
IAudioContext * audioContext = CreateAudioContext();
IUiContext * uiContext = CreateUiContext();
IContext * context = CreateContext(audioContext, uiContext);
context->RunOpenRCT2(argc, argv);
delete context;
delete uiContext;
delete audioContext;
}
return gExitCode;
}
DLLEXPORT int LaunchOpenRCT2(int argc, wchar_t * * argvW)
{
char * * argv = GetCommandLineArgs(argc, argvW);
@ -37,7 +65,8 @@ DLLEXPORT int LaunchOpenRCT2(int argc, wchar_t * * argvW)
return -1;
}
int exitCode = RunOpenRCT2(argc, argv);
int exitCode = NormalisedMain(argc, argv);
FreeCommandLineArgs(argc, argv);
return exitCode;
}

View File

@ -45,7 +45,7 @@
<AdditionalOptions>$(OPENRCT2_CL_ADDITIONALOPTIONS) %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>libopenrct2.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>libopenrct2.lib;libopenrct2ui.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ProgramDatabaseFile>$(OutDir)openrct2-dll.pdb</ProgramDatabaseFile>
</Link>
<Lib>

View File

@ -0,0 +1,93 @@
# CMAKE project for openrct2-cli (UI build of OpenRCT2)
cmake_minimum_required(VERSION 2.6)
if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)
message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt")
endif ()
# Options
option(DISABLE_OPENGL "Disable OpenGL support.")
if (PORTABLE)
set(CMAKE_INSTALL_RPATH "$ORIGIN")
endif ()
# CMake dependencies
include(FindPkgConfig)
# Third party libraries
PKG_CHECK_MODULES(SDL2 REQUIRED sdl2)
PKG_CHECK_MODULES(SPEEX REQUIRED speexdsp)
if (NOT DISABLE_OPENGL)
# GL doesn't work nicely with macOS, while find_package doesn't work with multiarch on Ubuntu.
if (APPLE)
find_package(OpenGL REQUIRED)
elseif (NOT WIN32)
PKG_CHECK_MODULES(GL REQUIRED gl)
endif ()
endif ()
# Sources
file(GLOB_RECURSE OPENRCT2_UI_SOURCES
"${CMAKE_CURRENT_LIST_DIR}/*.c"
"${CMAKE_CURRENT_LIST_DIR}/*.cpp"
"${CMAKE_CURRENT_LIST_DIR}/*.h"
"${CMAKE_CURRENT_LIST_DIR}/*.hpp")
if (APPLE)
file(GLOB_RECURSE OPENRCT2_UI_M_SOURCES "${CMAKE_CURRENT_LIST_DIR}/*.m")
set_source_files_properties(${OPENRCT2_UI_M_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c -fmodules")
file(GLOB_RECURSE OPENRCT2_UI_MM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/*.mm")
set_source_files_properties(${OPENRCT2_UI_MM_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c++ -fmodules")
endif ()
# Outputs
set (PROJECT openrct2)
project(${PROJECT})
add_executable(${PROJECT} ${OPENRCT2_UI_SOURCES} ${OPENRCT2_UI_M_SOURCES} ${OPENRCT2_UI_MM_SOURCES})
target_link_libraries(${PROJECT} "libopenrct2"
${SDL2_LIBRARIES}
${SDL2_TTF_LIBRARIES}
${SPEEX_LIBRARIES})
if (APPLE)
target_link_libraries(${PROJECT} "-framework Cocoa")
endif ()
if (NOT DISABLE_OPENGL)
if (WIN32)
target_link_libraries(${PROJECT} opengl32)
elseif (APPLE)
target_link_libraries(${PROJECT} ${OPENGL_LIBRARY})
else ()
target_link_libraries(${PROJECT} ${GL_LIBRARIES})
endif ()
endif ()
# Includes
target_include_directories(${PROJECT} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/.."
${SDL2_INCLUDE_DIRS}
${SPEEX_INCLUDE_DIRS})
# Compiler flags
if (FORCE32)
set(TARGET_M "-m32")
endif ()
if (WIN32)
# mingw complains about "%zu" not being a valid format specifier for printf, unless we
# tell it that it is
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__USE_MINGW_ANSI_STDIO=1")
endif ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu11 ${TARGET_M}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++14 ${TARGET_M}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${TARGET_M}")
# Defines
if (DISABLE_OPENGL)
add_definitions(-DDISABLE_OPENGL)
else ()
# Makes OpenGL function get queried in run-time rather than linked-in
add_definitions(-DOPENGL_NO_LINK)
endif ()

View File

@ -14,9 +14,10 @@
*****************************************************************************/
#pragma endregion
#include "Cursors.h"
#include <openrct2/interface/Cursors.h>
#include "CursorRepository.h"
namespace Cursors
namespace OpenRCT2 { namespace Ui
{
static const CursorData BlankCursorData =
{
@ -654,7 +655,7 @@ namespace Cursors
&HandClosedDownCursorData, // CURSOR_HAND_CLOSED
};
const CursorData * GetCursorData(CURSOR_ID cursorId)
const CursorData * CursorRepository::GetCursorData(CURSOR_ID cursorId)
{
const CursorData * result = nullptr;
if (cursorId >= 0 && cursorId < CURSOR_COUNT)
@ -663,4 +664,4 @@ namespace Cursors
}
return result;
}
}
} }

View File

@ -0,0 +1,80 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <SDL.h>
#include <openrct2/core/Guard.hpp>
#include <openrct2/interface/Cursors.h>
#include "CursorRepository.h"
using namespace OpenRCT2::Ui;
CursorRepository::~CursorRepository()
{
for (size_t i = 0; i < CURSOR_COUNT; i++)
{
SDL_FreeCursor(_loadedCursors[i]);
_loadedCursors[i] = nullptr;
}
_currentCursor = CURSOR_UNDEFINED;
}
void CursorRepository::LoadCursors()
{
// Using system cursors
_loadedCursors[CURSOR_ARROW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
_loadedCursors[CURSOR_HAND_POINT] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
// Using custom cursors
for (size_t i = 0; i < CURSOR_COUNT; i++)
{
const CursorData * cursorData = GetCursorData((CURSOR_ID)i);
if (cursorData != nullptr)
{
_loadedCursors[i] = Create(cursorData);
}
}
_currentCursor = CURSOR_UNDEFINED;
SetCurrentCursor(CURSOR_ARROW);
}
CURSOR_ID CursorRepository::GetCurrentCursor()
{
return _currentCursor;
}
void CursorRepository::SetCurrentCursor(CURSOR_ID cursorId)
{
if (_currentCursor != cursorId)
{
SDL_Cursor * cursor = _loadedCursors[cursorId];
SDL_SetCursor(cursor);
_currentCursor = cursorId;
}
}
SDL_Cursor * CursorRepository::Create(const CursorData * cursorInfo)
{
SDL_Cursor * cursor = SDL_CreateCursor(
cursorInfo->Data,
cursorInfo->Mask,
CURSOR_WIDTH,
CURSOR_HEIGHT,
cursorInfo->HotSpot.X,
cursorInfo->HotSpot.Y);
return cursor;
}

View File

@ -0,0 +1,45 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/interface/Cursors.h>
struct SDL_Cursor;
namespace OpenRCT2
{
namespace Ui
{
class CursorRepository
{
private:
constexpr static sint32 CURSOR_WIDTH = 32;
constexpr static sint32 CURSOR_HEIGHT = 32;
SDL_Cursor * _loadedCursors[CURSOR_COUNT] = { nullptr };
CURSOR_ID _currentCursor = CURSOR_UNDEFINED;
public:
~CursorRepository();
void LoadCursors();
CURSOR_ID GetCurrentCursor();
void SetCurrentCursor(CURSOR_ID cursorId);
private:
SDL_Cursor * Create(const CursorData * cursorInfo);
static const CursorData * GetCursorData(CURSOR_ID cursorId);
};
}
}

View File

@ -0,0 +1,48 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include <SDL.h>
#include <stdexcept>
/**
* An exception which wraps an SDL error.
*/
class SDLException : public std::runtime_error
{
public:
explicit SDLException(const std::string& message)
: runtime_error(message.c_str())
{
SDL_GetError();
}
explicit SDLException(const char * message)
: runtime_error(message)
{
}
/**
* Throws an SDL exception with a message containing the given call information
* and the message given by SDL_GetError.
*/
static void Throw(const char * call)
{
std::string message = std::string(call) + ": " + std::string(SDL_GetError());
throw SDLException(message);
}
};

View File

@ -0,0 +1,301 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <SDL.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/core/String.hpp>
#include "TextComposition.h"
extern "C"
{
#include <openrct2/interface/console.h>
#include <openrct2/interface/window.h>
#include <openrct2/localisation/localisation.h>
}
#ifdef __MACOSX__
// macOS uses COMMAND rather than CTRL for many keyboard shortcuts
#define KEYBOARD_PRIMARY_MODIFIER KMOD_GUI
#else
#define KEYBOARD_PRIMARY_MODIFIER KMOD_CTRL
#endif
using namespace OpenRCT2;
using namespace OpenRCT2::Ui;
bool TextComposition::IsActive()
{
return SDL_IsTextInputActive() && _session.Buffer != nullptr;
}
TextInputSession * TextComposition::Start(utf8 * buffer, size_t bufferSize)
{
Guard::ArgumentNotNull(buffer);
// TODO This doesn't work, and position could be improved to where text entry is
SDL_Rect rect = { 10, 10, 100, 100 };
SDL_SetTextInputRect(&rect);
SDL_StartTextInput();
_session.Buffer = buffer;
_session.BufferSize = bufferSize - 1;
_session.Size = strlen(buffer);
_session.SelectionStart = _session.Size;
_session.SelectionSize = 0;
_session.ImeBuffer = _imeBuffer;
RecalculateLength();
return &_session;
}
void TextComposition::Stop()
{
SDL_StopTextInput();
_session.Buffer = nullptr;
_session.ImeBuffer = nullptr;
_imeActive = false;
}
void TextComposition::HandleMessage(const SDL_Event * e)
{
switch (e->type) {
case SDL_TEXTEDITING:
// When inputting Korean characters, `edit.length` is always zero
String::Set(_imeBuffer, sizeof(_imeBuffer), e->edit.text);
_imeStart = e->edit.start;
_imeLength = e->edit.length;
_imeActive = ((e->edit.length != 0 || String::SizeOf(e->edit.text) != 0) && _imeBuffer[0] != '\0');
break;
case SDL_TEXTINPUT:
// will receive an `SDL_TEXTINPUT` event when a composition is committed
_imeActive = false;
if (_session.Buffer != nullptr)
{
// HACK ` will close console, so don't input any text
if (e->text.text[0] == '`' && gConsoleOpen) {
break;
}
utf8 * newText = String::Duplicate(e->text.text);
utf8_remove_formatting(newText, false);
Insert(newText);
Memory::Free(newText);
console_refresh_caret();
window_update_textbox();
}
break;
case SDL_KEYDOWN:
{
if (_imeActive)
{
break;
}
uint16 modifier = e->key.keysym.mod;
SDL_Keycode key = e->key.keysym.sym;
if (key == SDLK_KP_ENTER)
{
// Map Keypad enter to regular enter.
key = SDLK_RETURN;
}
GetContext()->GetUiContext()->SetKeysPressed(key, e->key.keysym.scancode);
// Text input
if (_session.Buffer == nullptr)
{
break;
}
// Clear the input on <CTRL>Backspace (Windows/Linux) or <MOD>Backspace (macOS)
if (key == SDLK_BACKSPACE && (modifier & KEYBOARD_PRIMARY_MODIFIER))
{
Clear();
console_refresh_caret();
window_update_textbox();
}
switch (key) {
case SDLK_BACKSPACE:
// If backspace and we have input text with a cursor position none zero
if (_session.SelectionStart > 0)
{
size_t endOffset = _session.SelectionStart;
CursorLeft();
_session.SelectionSize = endOffset - _session.SelectionStart;
Delete();
console_refresh_caret();
window_update_textbox();
}
break;
case SDLK_HOME:
CursorHome();
console_refresh_caret();
break;
case SDLK_END:
CursorEnd();
console_refresh_caret();
break;
case SDLK_DELETE:
{
size_t startOffset = _session.SelectionStart;
CursorRight();
_session.SelectionSize = _session.SelectionStart - startOffset;
_session.SelectionStart = startOffset;
Delete();
console_refresh_caret();
window_update_textbox();
break;
}
case SDLK_RETURN:
window_cancel_textbox();
break;
case SDLK_LEFT:
CursorLeft();
console_refresh_caret();
break;
case SDLK_RIGHT:
CursorRight();
console_refresh_caret();
break;
case SDLK_v:
if ((modifier & KEYBOARD_PRIMARY_MODIFIER) && SDL_HasClipboardText())
{
utf8 * text = SDL_GetClipboardText();
utf8_remove_formatting(text, false);
Insert(text);
SDL_free(text);
window_update_textbox();
}
break;
}
}
}
}
void TextComposition::CursorHome()
{
_session.SelectionStart = 0;
}
void TextComposition::CursorEnd()
{
_session.SelectionStart = _session.SelectionSize;
}
void TextComposition::CursorLeft()
{
size_t selectionOffset = _session.SelectionStart;
if (selectionOffset > 0)
{
const utf8 * ch = _session.Buffer + selectionOffset;
do
{
ch--;
selectionOffset--;
}
while (!utf8_is_codepoint_start(ch) && selectionOffset > 0);
_session.SelectionStart = selectionOffset;
}
}
void TextComposition::CursorRight()
{
size_t selectionOffset = _session.SelectionStart;
size_t selectionMaxOffset = _session.Size;
if (selectionOffset < selectionMaxOffset)
{
const utf8 * ch = _session.Buffer + _session.SelectionStart;
do
{
ch++;
selectionOffset++;
}
while (!utf8_is_codepoint_start(ch) && selectionOffset < selectionMaxOffset);
_session.SelectionSize = Math::Max<size_t>(0, _session.SelectionSize - (selectionOffset - _session.SelectionStart));
_session.SelectionStart = selectionOffset;
}
}
void TextComposition::Insert(const utf8 * text)
{
const utf8 * ch = text;
uint32 codepoint;
while ((codepoint = utf8_get_next(ch, &ch)) != 0)
{
InsertCodepoint(codepoint);
}
}
void TextComposition::InsertCodepoint(codepoint_t codepoint)
{
size_t codepointLength = utf8_get_codepoint_length(codepoint);
size_t remainingSize = _session.BufferSize - _session.Size;
if (codepointLength <= remainingSize)
{
utf8 * buffer = _session.Buffer;
utf8 * insertPtr = buffer + _session.SelectionStart;
if (_session.SelectionStart < _session.Size)
{
// Shift bytes (including null terminator) right to make room for new codepoint
utf8 * targetShiftPtr = insertPtr + codepointLength;
size_t shiftSize = _session.Size - _session.SelectionStart + 1;
memmove(targetShiftPtr, insertPtr, shiftSize);
}
else
{
// Character is appended onto the end, so set byte after it to null terminator
buffer[_session.Size + codepointLength] = 0;
}
utf8_write_codepoint(insertPtr, codepoint);
_session.SelectionStart += codepointLength;
_session.Size += codepointLength;
_session.Length++;
}
}
void TextComposition::Clear()
{
utf8 * buffer = _session.Buffer;
buffer[0] = 0;
_session.Size = 0;
_session.Length = 0;
_session.SelectionStart = 0;
_session.SelectionSize = 0;
}
void TextComposition::Delete()
{
utf8 * buffer = _session.Buffer;
utf8 * targetShiftPtr = buffer + _session.SelectionStart;
utf8 * sourceShiftPtr = targetShiftPtr + _session.SelectionSize;
size_t shiftSize = _session.Size - (_session.SelectionStart - _session.SelectionSize + 1);
memmove(targetShiftPtr, sourceShiftPtr, shiftSize);
_session.SelectionSize = 0;
RecalculateLength();
}
void TextComposition::RecalculateLength()
{
_session.Size = String::SizeOf(_session.Buffer);
_session.Length = String::LengthOf(_session.Buffer);
}

View File

@ -0,0 +1,58 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <openrct2/Context.h>
#include <openrct2/ui/UiContext.h>
union SDL_Event;
namespace OpenRCT2
{
namespace Ui
{
/**
* Represents a
*/
class TextComposition
{
private:
TextInputSession _session = { 0 };
bool _imeActive = false;
sint32 _imeStart = 0;
sint32 _imeLength = 0;
utf8 _imeBuffer[32] = { 0 };
public:
bool IsActive();
TextInputSession * Start(utf8 * buffer, size_t bufferSize);
void Stop();
void HandleMessage(const SDL_Event * e);
private:
void CursorHome();
void CursorEnd();
void CursorLeft();
void CursorRight();
void Insert(const utf8 * text);
void InsertCodepoint(codepoint_t codepoint);
void Clear();
void Delete();
void RecalculateLength();
};
}
}

53
src/openrct2-ui/Ui.cpp Normal file
View File

@ -0,0 +1,53 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#ifndef _MSC_VER
#include <openrct2/audio/AudioContext.h>
#include <openrct2/Context.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/ui/UiContext.h>
#include "audio/AudioContext.h"
#include "UiContext.h"
using namespace OpenRCT2;
using namespace OpenRCT2::Audio;
using namespace OpenRCT2::Ui;
/**
* Main entry point for non-Windows sytems. Windows instead uses its own DLL proxy.
*/
int main(int argc, char * * argv)
{
core_init();
int runGame = cmdline_run((const char * *)argv, argc);
if (runGame == 1)
{
// Run OpenRCT2 with a UI context
IAudioContext * audioContext = CreateAudioContext();
IUiContext * uiContext = CreateUiContext();
IContext * context = CreateContext(audioContext, uiContext);
context->RunOpenRCT2(argc, argv);
delete context;
delete uiContext;
delete audioContext;
}
return gExitCode;
}
#endif

View File

@ -0,0 +1,348 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#ifdef __linux__
#include <dlfcn.h>
#include <sstream>
#include <openrct2/common.h>
#include <openrct2/core/String.hpp>
#include <openrct2/ui/UiContext.h>
#include "UiContext.h"
#include <SDL.h>
namespace OpenRCT2 { namespace Ui
{
enum class DIALOG_TYPE
{
NONE,
KDIALOG,
ZENITY,
};
class LinuxContext final : public IPlatformUiContext
{
private:
public:
LinuxContext()
{
}
void SetWindowIcon(SDL_Window * window) override
{
}
bool IsSteamOverlayAttached() override
{
// See http://syprog.blogspot.ru/2011/12/listing-loaded-shared-objects-in-linux.html
struct lmap
{
void * base_address;
char * path;
void * unused;
lmap * next;
lmap * prev;
};
struct dummy
{
void * pointers[3];
dummy * ptr;
};
bool result = false;
void * processHandle = dlopen(nullptr, RTLD_NOW);
if (processHandle != nullptr)
{
dummy * p = ((dummy *)processHandle)->ptr;
lmap * pl = (lmap *)p->ptr;
while (pl != nullptr)
{
if (strstr(pl->path, "gameoverlayrenderer.so") != nullptr)
{
result = true;
break;
}
pl = pl->next;
}
dlclose(processHandle);
}
return result;
}
void ShowMessageBox(SDL_Window * window, const std::string &message) override
{
log_verbose(message.c_str());
std::string executablePath;
DIALOG_TYPE dtype = GetDialogApp(&executablePath);
switch (dtype) {
case DIALOG_TYPE::KDIALOG:
{
std::string cmd = String::Format("%s --title \"OpenRCT2\" --msgbox \"%s\"", executablePath.c_str(), message.c_str());
Execute(cmd);
break;
}
case DIALOG_TYPE::ZENITY:
{
std::string cmd = String::Format("%s --title=\"OpenRCT2\" --info --text=\"%s\"", executablePath.c_str(), message.c_str());
Execute(cmd);
break;
}
default:
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "OpenRCT2", message.c_str(), window);
break;
}
}
std::string ShowFileDialog(SDL_Window * window, const FileDialogDesc &desc) override
{
std::string result;
std::string executablePath;
DIALOG_TYPE dtype = GetDialogApp(&executablePath);
switch (dtype) {
case DIALOG_TYPE::KDIALOG:
{
std::string action =
(desc.Type == FILE_DIALOG_TYPE::OPEN) ? "--getopenfilename" :
"--getsavefilename";
std::string filter = GetKDialogFilterString(desc.Filters);
std::string cmd = String::StdFormat("%s --title '%s' %s '%s' '%s'",
executablePath.c_str(),
desc.Title.c_str(),
action.c_str(),
desc.InitialDirectory.c_str(),
filter.c_str());
std::string output;
if (Execute(cmd, &output) == 0)
{
result = output;
}
break;
}
case DIALOG_TYPE::ZENITY:
{
std::string action = "--file-selection";
std::string flags;
if (desc.Type == FILE_DIALOG_TYPE::SAVE)
{
flags = "--confirm-overwrite --save";
}
std::string filters = GetZenityFilterString(desc.Filters);
std::string cmd = String::StdFormat("%s %s --filename='%s/' %s --title='%s' / %s",
executablePath.c_str(),
action.c_str(),
desc.InitialDirectory.c_str(),
flags.c_str(),
desc.Title.c_str(),
filters.c_str());
std::string output;
if (Execute(cmd, &output) == 0)
{
result = output;
}
break;
}
default:
log_error("KDialog or Zenity not installed.");
break;
}
if (!result.empty())
{
if (desc.Type == FILE_DIALOG_TYPE::OPEN && access(result.c_str(), F_OK) == -1)
{
std::string msg = String::StdFormat("\"%s\" not found: %s, please choose another file\n", result.c_str(), strerror(errno));
ShowMessageBox(window, msg);
return ShowFileDialog(window, desc);
}
else if (desc.Type == FILE_DIALOG_TYPE::SAVE && access(result.c_str(), F_OK) != -1 && dtype == DIALOG_TYPE::KDIALOG)
{
std::string cmd = String::StdFormat("%s --yesno \"Overwrite %s?\"", executablePath.c_str(), result.c_str());
if (Execute(cmd) != 0)
{
result = std::string();
}
}
}
return result;
}
std::string ShowDirectoryDialog(SDL_Window * window, const std::string &title) override
{
std::string result;
std::string executablePath;
DIALOG_TYPE dtype = GetDialogApp(&executablePath);
switch (dtype) {
case DIALOG_TYPE::KDIALOG:
{
std::string output;
std::string cmd = String::Format("%s --title '%s' --getexistingdirectory /", executablePath.c_str(), title.c_str());
if (Execute(cmd, &output) == 0)
{
result = output;
}
break;
}
case DIALOG_TYPE::ZENITY:
{
std::string output;
std::string cmd = String::Format("%s --title='%s' --file-selection --directory /", executablePath.c_str(), title.c_str());
if (Execute(cmd, &output) == 0)
{
result = output;
}
break;
}
default:
log_error("KDialog or Zenity not installed.");
break;
}
return result;
}
private:
static DIALOG_TYPE GetDialogApp(std::string * executablePath)
{
// Prefer zenity as it offers more required features, e.g., overwrite
// confirmation and selecting only existing files
if (Execute("which zenity", executablePath) == 0)
{
return DIALOG_TYPE::ZENITY;
}
if (Execute("which kdialog", executablePath) == 0)
{
return DIALOG_TYPE::KDIALOG;
}
return DIALOG_TYPE::NONE;
}
static sint32 Execute(const std::string &command, std::string * output = nullptr)
{
log_verbose("executing \"%s\"...\n", command.c_str());
FILE * fpipe = popen(command.c_str(), "r");
if (fpipe == nullptr)
{
return -1;
}
if (output != nullptr)
{
// Read output into buffer
std::vector<char> outputBuffer;
char buffer[1024];
size_t readBytes;
while ((readBytes = fread(buffer, 1, sizeof(buffer), fpipe)) > 0)
{
outputBuffer.insert(outputBuffer.begin(), buffer, buffer + readBytes);
}
// Trim line breaks
size_t outputLength = outputBuffer.size();
for (size_t i = outputLength - 1; i != SIZE_MAX; i--)
{
if (outputBuffer[i] == '\n')
{
outputLength = i;
}
else
{
break;
}
}
// Convert to string
*output = std::string(outputBuffer.data(), outputLength);
}
else
{
fflush(fpipe);
}
// Return exit code
return pclose(fpipe);
}
static std::string GetKDialogFilterString(const std::vector<FileDialogDesc::Filter> filters)
{
std::stringstream filtersb;
bool first = true;
for (const auto &filter : filters)
{
// KDialog wants filters space-delimited and we don't expect ';' anywhere else
std::string pattern = filter.Pattern;
for (size_t i = 0; i < pattern.size(); i++)
{
if (pattern[i] == ';')
{
pattern[i] = ' ';
}
}
if (first)
{
filtersb << String::StdFormat("%s | %s", pattern.c_str(), filter.Name.c_str());
first = false;
}
else
{
filtersb << String::StdFormat("\\n%s | %s", pattern.c_str(), filter.Name.c_str());
}
}
return filtersb.str();
}
static std::string GetZenityFilterString(const std::vector<FileDialogDesc::Filter> filters)
{
// Zenity seems to be case sensitive, while KDialog isn't
std::stringstream filtersb;
for (const auto &filter : filters)
{
filtersb << " --file-filter='" << filter.Name << " | ";
for (char c : filter.Pattern)
{
if (c == ';')
{
filtersb << ' ';
}
else if (isalpha(c))
{
filtersb << '['
<< (char)tolower(c)
<< (char)toupper(c)
<< ']';
}
else
{
filtersb << c;
}
}
filtersb << "'";
}
return filtersb.str();
}
};
IPlatformUiContext * CreatePlatformUiContext()
{
return new LinuxContext();
}
} }
#endif // __linux__

View File

@ -0,0 +1,224 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#ifdef _WIN32
#ifdef __MINGW32__
// 0x0600 == vista
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#endif // __MINGW32__
#include <sstream>
#include <openrct2/common.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Path.hpp>
#include <openrct2/core/String.hpp>
#include <openrct2/ui/UiContext.h>
#include "UiContext.h"
#undef interface
#include <windows.h>
#include <shlobj.h>
#include <SDL.h>
#include <SDL_syswm.h>
// Native resource IDs
#include "../../resources/resource.h"
static std::wstring SHGetPathFromIDListLongPath(LPCITEMIDLIST pidl)
{
std::wstring pszPath(MAX_PATH, 0);
while (!SHGetPathFromIDListEx(pidl, &pszPath[0], (DWORD)pszPath.size(), 0))
{
if (pszPath.size() >= SHRT_MAX)
{
// Clearly not succeeding at all, bail
return std::wstring();
}
pszPath.resize(pszPath.size() * 2);
}
return pszPath;
}
namespace OpenRCT2 { namespace Ui
{
class Win32Context : public IPlatformUiContext
{
private:
HMODULE _win32module;
public:
Win32Context()
{
_win32module = GetModuleHandleA(nullptr);
}
void SetWindowIcon(SDL_Window * window) override
{
if (_win32module != nullptr)
{
HICON icon = LoadIconA(_win32module, MAKEINTRESOURCEA(IDI_ICON));
if (icon != nullptr)
{
HWND hwnd = GetHWND(window);
if (hwnd != nullptr)
{
SendMessageA(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)icon);
}
}
}
}
bool IsSteamOverlayAttached() override
{
return (GetModuleHandleA("GameOverlayRenderer.dll") != nullptr);
}
void ShowMessageBox(SDL_Window * window, const std::string &message) override
{
HWND hwnd = GetHWND(window);
std::wstring messageW = String::ToUtf16(message);
MessageBoxW(hwnd, messageW.c_str(), L"OpenRCT2", MB_OK);
}
std::string ShowFileDialog(SDL_Window * window, const FileDialogDesc &desc) override
{
std::wstring wcFilename = String::ToUtf16(desc.DefaultFilename);
wcFilename.resize(Math::Max<size_t>(wcFilename.size(), MAX_PATH));
std::wstring wcTitle = String::ToUtf16(desc.Title);
std::wstring wcInitialDirectory = String::ToUtf16(desc.InitialDirectory);
std::wstring wcFilters = GetFilterString(desc.Filters);
// Set open file name options
OPENFILENAMEW openFileName = { 0 };
openFileName.lStructSize = sizeof(OPENFILENAMEW);
openFileName.hwndOwner = GetHWND(window);
openFileName.lpstrTitle = wcTitle.c_str();
openFileName.lpstrInitialDir = wcInitialDirectory.c_str();
openFileName.lpstrFilter = wcFilters.c_str();
openFileName.lpstrFile = &wcFilename[0];
openFileName.nMaxFile = (DWORD)wcFilename.size();
// Open dialog
BOOL dialogResult = FALSE;
DWORD commonFlags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
if (desc.Type == FILE_DIALOG_TYPE::OPEN)
{
openFileName.Flags = commonFlags | OFN_NONETWORKBUTTON | OFN_FILEMUSTEXIST;
dialogResult = GetOpenFileNameW(&openFileName);
}
else if (desc.Type == FILE_DIALOG_TYPE::SAVE)
{
openFileName.Flags = commonFlags | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT;
dialogResult = GetSaveFileNameW(&openFileName);
}
std::string resultFilename;
if (dialogResult)
{
resultFilename = String::ToUtf8(openFileName.lpstrFile);
// If there is no extension, append the pattern
std::string resultExtension = Path::GetExtension(resultFilename);
if (resultExtension.empty())
{
sint32 filterIndex = openFileName.nFilterIndex - 1;
assert(filterIndex >= 0);
assert(filterIndex < (sint32)desc.Filters.size());
std::string pattern = desc.Filters[filterIndex].Pattern;
std::string patternExtension = Path::GetExtension(pattern);
if (!patternExtension.empty())
{
resultFilename += patternExtension;
}
}
}
return resultFilename;
}
std::string ShowDirectoryDialog(SDL_Window * window, const std::string &title) override
{
std::string result;
// Initialize COM and get a pointer to the shell memory allocator
LPMALLOC lpMalloc;
if (SUCCEEDED(CoInitializeEx(0, COINIT_APARTMENTTHREADED)) &&
SUCCEEDED(SHGetMalloc(&lpMalloc)))
{
std::wstring titleW = String::ToUtf16(title);
BROWSEINFOW bi = { 0 };
bi.hwndOwner = GetHWND(window);
bi.lpszTitle = titleW.c_str();
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE | BIF_NONEWFOLDERBUTTON;
LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
if (pidl != nullptr)
{
result = String::ToUtf8(SHGetPathFromIDListLongPath(pidl));
}
}
else
{
log_error("Error opening directory browse window");
}
CoUninitialize();
return result;
}
private:
HWND GetHWND(SDL_Window * window)
{
HWND result = nullptr;
if (window != nullptr)
{
SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
if (SDL_GetWindowWMInfo(window, &wmInfo) != SDL_TRUE)
{
log_error("SDL_GetWindowWMInfo failed %s", SDL_GetError());
exit(-1);
}
result = wmInfo.info.win.window;
}
return result;
}
static std::wstring GetFilterString(const std::vector<FileDialogDesc::Filter> filters)
{
std::wstringstream filtersb;
for (auto filter : filters)
{
filtersb << String::ToUtf16(filter.Name)
<< '\0'
<< String::ToUtf16(filter.Pattern)
<< '\0';
}
return filtersb.str();
}
};
IPlatformUiContext * CreatePlatformUiContext()
{
return new Win32Context();
}
} }
#endif // _WIN32

View File

@ -0,0 +1,688 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <cmath>
#include <memory>
#include <vector>
#include <SDL.h>
#include <openrct2/audio/AudioMixer.h>
#include <openrct2/config/Config.h>
#include <openrct2/Context.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/String.hpp>
#include <openrct2/drawing/IDrawingEngine.h>
#include <openrct2/platform/Platform2.h>
#include <openrct2/ui/UiContext.h>
#include <openrct2/Version.h>
#include "CursorRepository.h"
#include "drawing/engines/DrawingEngines.h"
#include "SDLException.h"
#include "TextComposition.h"
#include "UiContext.h"
extern "C"
{
#include <openrct2/input.h>
#include <openrct2/interface/console.h>
#include <openrct2/interface/window.h>
}
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
using namespace OpenRCT2::Ui;
#ifdef __MACOSX__
// macOS uses COMMAND rather than CTRL for many keyboard shortcuts
#define KEYBOARD_PRIMARY_MODIFIER KMOD_GUI
#else
#define KEYBOARD_PRIMARY_MODIFIER KMOD_CTRL
#endif
class UiContext final : public IUiContext
{
private:
constexpr static uint32 TOUCH_DOUBLE_TIMEOUT = 300;
IPlatformUiContext * const _platformUiContext;
CursorRepository _cursorRepository;
SDL_Window * _window = nullptr;
sint32 _width = 0;
sint32 _height = 0;
uint32 _windowFlags = 0;
uint32 _windowFlagsLastCheckTick = 0;
bool _resolutionsAllowAnyAspectRatio = false;
std::vector<Resolution> _fsResolutions;
bool _steamOverlayActive = false;
// Input
TextComposition _textComposition;
CursorState _cursorState = { 0 };
uint32 _lastKeyPressed = 0;
const uint8 * _keysState = nullptr;
uint8 _keysPressed[256] = { 0 };
uint32 _lastGestureTimestamp = 0;
float _gestureRadius = 0;
public:
UiContext()
: _platformUiContext(CreatePlatformUiContext())
{
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
SDLException::Throw("SDL_Init(SDL_INIT_VIDEO)");
}
_cursorRepository.LoadCursors();
}
~UiContext() override
{
CloseWindow();
SDL_QuitSubSystem(SDL_INIT_VIDEO);
delete _platformUiContext;
}
// Window
SDL_Window * GetWindow() override
{
return _window;
}
sint32 GetWidth() override
{
return _width;
}
sint32 GetHeight() override
{
return _height;
}
void SetFullscreenMode(FULLSCREEN_MODE mode) override
{
static const sint32 SDLFSFlags[] = { 0, SDL_WINDOW_FULLSCREEN, SDL_WINDOW_FULLSCREEN_DESKTOP };
uint32 windowFlags = SDLFSFlags[(sint32)mode];
// HACK Changing window size when in fullscreen usually has no effect
if (mode == FULLSCREEN_MODE::FULLSCREEN)
{
SDL_SetWindowFullscreen(_window, 0);
}
// Set window size
if (mode == FULLSCREEN_MODE::FULLSCREEN)
{
UpdateFullscreenResolutions();
Resolution resolution = GetClosestResolution(gConfigGeneral.fullscreen_width, gConfigGeneral.fullscreen_height);
SDL_SetWindowSize(_window, resolution.Width, resolution.Height);
}
else if (mode == FULLSCREEN_MODE::WINDOWED)
{
SDL_SetWindowSize(_window, gConfigGeneral.window_width, gConfigGeneral.window_height);
}
if (SDL_SetWindowFullscreen(_window, windowFlags))
{
log_fatal("SDL_SetWindowFullscreen %s", SDL_GetError());
exit(1);
// TODO try another display mode rather than just exiting the game
}
}
std::vector<Resolution> GetFullscreenResolutions() override
{
UpdateFullscreenResolutions();
return _fsResolutions;
}
bool HasFocus() override
{
uint32 windowFlags = GetWindowFlags();
return (windowFlags & SDL_WINDOW_INPUT_FOCUS) != 0;
}
bool IsMinimised() override
{
uint32 windowFlags = GetWindowFlags();
return (windowFlags & SDL_WINDOW_MINIMIZED) ||
(windowFlags & SDL_WINDOW_HIDDEN);
}
bool IsSteamOverlayActive() override
{
return _steamOverlayActive;
}
// Input
const CursorState * GetCursorState() override
{
return &_cursorState;
}
const uint8 * GetKeysState() override
{
return _keysState;
}
const uint8 * GetKeysPressed() override
{
return _keysPressed;
}
CURSOR_ID GetCursor() override
{
return _cursorRepository.GetCurrentCursor();
}
void SetCursor(CURSOR_ID cursor) override
{
_cursorRepository.SetCurrentCursor(cursor);
}
void SetCursorVisible(bool value) override
{
SDL_ShowCursor(value ? SDL_ENABLE : SDL_DISABLE);
}
void GetCursorPosition(sint32 * x, sint32 * y) override
{
SDL_GetMouseState(x, y);
}
void SetCursorPosition(sint32 x, sint32 y) override
{
SDL_WarpMouseInWindow(nullptr, x, y);
}
void SetCursorTrap(bool value) override
{
SDL_SetWindowGrab(_window, value ? SDL_TRUE : SDL_FALSE);
}
void SetKeysPressed(uint32 keysym, uint8 scancode) override
{
_lastKeyPressed = keysym;
_keysPressed[scancode] = 1;
}
// Drawing
IDrawingEngine * CreateDrawingEngine(DRAWING_ENGINE_TYPE type) override
{
switch ((sint32)type) {
case DRAWING_ENGINE_SOFTWARE:
return CreateSoftwareDrawingEngine(this);
case DRAWING_ENGINE_SOFTWARE_WITH_HARDWARE_DISPLAY:
return CreateHardwareDisplayDrawingEngine(this);
#ifndef DISABLE_OPENGL
case DRAWING_ENGINE_OPENGL:
return CreateOpenGLDrawingEngine(this);
#endif
default:
return nullptr;
}
}
// Text input
bool IsTextInputActive() override
{
return _textComposition.IsActive();
}
TextInputSession * StartTextInput(utf8 * buffer, size_t bufferSize) override
{
return _textComposition.Start(buffer, bufferSize);
}
void StopTextInput() override
{
_textComposition.Stop();
}
void ProcessMessages() override
{
_lastKeyPressed = 0;
_cursorState.left &= ~CURSOR_CHANGED;
_cursorState.middle &= ~CURSOR_CHANGED;
_cursorState.right &= ~CURSOR_CHANGED;
_cursorState.old = 0;
_cursorState.touch = false;
SDL_Event e;
while (SDL_PollEvent(&e))
{
switch (e.type) {
case SDL_QUIT:
rct2_quit();
break;
case SDL_WINDOWEVENT:
// HACK: Fix #2158, OpenRCT2 does not draw if it does not think that the window is
// visible - due a bug in SDL 2.0.3 this hack is required if the
// window is maximised, minimised and then restored again.
if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
{
if (SDL_GetWindowFlags(_window) & SDL_WINDOW_MAXIMIZED)
{
SDL_RestoreWindow(_window);
SDL_MaximizeWindow(_window);
}
if ((SDL_GetWindowFlags(_window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
{
SDL_RestoreWindow(_window);
SDL_SetWindowFullscreen(_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
}
if (e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
{
OnResize(e.window.data1, e.window.data2);
}
switch (e.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_MOVED:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
{
// Update default display index
sint32 displayIndex = SDL_GetWindowDisplayIndex(_window);
if (displayIndex != gConfigGeneral.default_display)
{
gConfigGeneral.default_display = displayIndex;
config_save_default();
}
break;
}
}
if (gConfigSound.audio_focus && gConfigSound.sound_enabled)
{
if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
{
Mixer_SetVolume(1);
}
if (e.window.event == SDL_WINDOWEVENT_FOCUS_LOST)
{
Mixer_SetVolume(0);
}
}
break;
case SDL_MOUSEMOTION:
_cursorState.x = (sint32)(e.motion.x / gConfigGeneral.window_scale);
_cursorState.y = (sint32)(e.motion.y / gConfigGeneral.window_scale);
break;
case SDL_MOUSEWHEEL:
if (gConsoleOpen)
{
console_scroll(e.wheel.y);
break;
}
_cursorState.wheel += e.wheel.y * 128;
break;
case SDL_MOUSEBUTTONDOWN:
{
sint32 x = (sint32)(e.button.x / gConfigGeneral.window_scale);
sint32 y = (sint32)(e.button.y / gConfigGeneral.window_scale);
switch (e.button.button) {
case SDL_BUTTON_LEFT:
store_mouse_input(MOUSE_STATE_LEFT_PRESS, x, y);
_cursorState.left = CURSOR_PRESSED;
_cursorState.old = 1;
break;
case SDL_BUTTON_MIDDLE:
_cursorState.middle = CURSOR_PRESSED;
break;
case SDL_BUTTON_RIGHT:
store_mouse_input(MOUSE_STATE_RIGHT_PRESS, x, y);
_cursorState.right = CURSOR_PRESSED;
_cursorState.old = 2;
break;
}
break;
}
case SDL_MOUSEBUTTONUP:
{
sint32 x = (sint32)(e.button.x / gConfigGeneral.window_scale);
sint32 y = (sint32)(e.button.y / gConfigGeneral.window_scale);
switch (e.button.button) {
case SDL_BUTTON_LEFT:
store_mouse_input(MOUSE_STATE_LEFT_RELEASE, x, y);
_cursorState.left = CURSOR_RELEASED;
_cursorState.old = 3;
break;
case SDL_BUTTON_MIDDLE:
_cursorState.middle = CURSOR_RELEASED;
break;
case SDL_BUTTON_RIGHT:
store_mouse_input(MOUSE_STATE_RIGHT_RELEASE, x, y);
_cursorState.right = CURSOR_RELEASED;
_cursorState.old = 4;
break;
}
break;
}
// Apple sends touchscreen events for trackpads, so ignore these events on macOS
#ifndef __MACOSX__
case SDL_FINGERMOTION:
_cursorState.x = (sint32)(e.tfinger.x * _width);
_cursorState.y = (sint32)(e.tfinger.y * _height);
break;
case SDL_FINGERDOWN:
{
sint32 x = (sint32)(e.tfinger.x * _width);
sint32 y = (sint32)(e.tfinger.y * _height);
_cursorState.touchIsDouble = (!_cursorState.touchIsDouble &&
e.tfinger.timestamp - _cursorState.touchDownTimestamp < TOUCH_DOUBLE_TIMEOUT);
if (_cursorState.touchIsDouble)
{
store_mouse_input(MOUSE_STATE_RIGHT_PRESS, x, y);
_cursorState.right = CURSOR_PRESSED;
_cursorState.old = 2;
}
else
{
store_mouse_input(MOUSE_STATE_LEFT_PRESS, x, y);
_cursorState.left = CURSOR_PRESSED;
_cursorState.old = 1;
}
_cursorState.touch = true;
_cursorState.touchDownTimestamp = e.tfinger.timestamp;
break;
}
case SDL_FINGERUP:
{
sint32 x = (sint32)(e.tfinger.x * _width);
sint32 y = (sint32)(e.tfinger.y * _height);
if (_cursorState.touchIsDouble)
{
store_mouse_input(MOUSE_STATE_RIGHT_RELEASE, x, y);
_cursorState.left = CURSOR_RELEASED;
_cursorState.old = 4;
}
else {
store_mouse_input(MOUSE_STATE_LEFT_RELEASE, x, y);
_cursorState.left = CURSOR_RELEASED;
_cursorState.old = 3;
}
_cursorState.touch = true;
break;
}
#endif
case SDL_KEYDOWN:
_textComposition.HandleMessage(&e);
break;
case SDL_MULTIGESTURE:
if (e.mgesture.numFingers == 2)
{
if (e.mgesture.timestamp > _lastGestureTimestamp + 1000)
{
_gestureRadius = 0;
}
_lastGestureTimestamp = e.mgesture.timestamp;
_gestureRadius += e.mgesture.dDist;
// Zoom gesture
constexpr sint32 tolerance = 128;
sint32 gesturePixels = (sint32)(_gestureRadius * _width);
if (abs(gesturePixels) > tolerance)
{
_gestureRadius = 0;
main_window_zoom(gesturePixels > 0, true);
}
}
break;
case SDL_TEXTEDITING:
_textComposition.HandleMessage(&e);
break;
case SDL_TEXTINPUT:
_textComposition.HandleMessage(&e);
break;
default:
break;
}
}
_cursorState.any = _cursorState.left | _cursorState.middle | _cursorState.right;
// Updates the state of the keys
sint32 numKeys = 256;
_keysState = SDL_GetKeyboardState(&numKeys);
}
/**
* Helper function to set various render target features.
* Does not get triggered on resize, but rather manually on config changes.
*/
void TriggerResize() override
{
char scaleQualityBuffer[4];
uint8 scaleQuality = gConfigGeneral.scale_quality;
if (gConfigGeneral.use_nn_at_integer_scales &&
gConfigGeneral.window_scale == std::floor(gConfigGeneral.window_scale))
{
scaleQuality = 0;
}
snprintf(scaleQualityBuffer, sizeof(scaleQualityBuffer), "%u", scaleQuality);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, scaleQualityBuffer);
sint32 width, height;
SDL_GetWindowSize(_window, &width, &height);
OnResize(width, height);
}
void CreateWindow() override
{
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, gConfigGeneral.minimize_fullscreen_focus_loss ? "1" : "0");
// TODO This should probably be called somewhere else. It has nothing to do with window creation and can be done as soon as
// g1.dat is loaded.
// sub_68371D();
// Set window position to default display
sint32 defaultDisplay = Math::Clamp(0, gConfigGeneral.default_display, 0xFFFF);
sint32 x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(defaultDisplay);
sint32 y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(defaultDisplay);
// Get saved window size
sint32 width = gConfigGeneral.window_width;
sint32 height = gConfigGeneral.window_height;
if (width <= 0) width = 640;
if (height <= 0) height = 480;
// Create window in window first rather than fullscreen so we have the display the window is on first
uint32 flags = SDL_WINDOW_RESIZABLE;
if (gConfigGeneral.drawing_engine == DRAWING_ENGINE_OPENGL)
{
flags |= SDL_WINDOW_OPENGL;
}
_window = SDL_CreateWindow(OPENRCT2_NAME, x, y, width, height, flags);
if (_window == nullptr)
{
SDLException::Throw("SDL_CreateWindow(...)");
}
SDL_SetWindowMinimumSize(_window, 720, 480);
SetCursorTrap(gConfigGeneral.trap_cursor);
_platformUiContext->SetWindowIcon(_window);
// Initialise the surface, palette and draw buffer
OnResize(width, height);
UpdateFullscreenResolutions();
SetFullscreenMode((FULLSCREEN_MODE)gConfigGeneral.fullscreen_mode);
// Check if steam overlay renderer is loaded into the process
_steamOverlayActive = _platformUiContext->IsSteamOverlayAttached();
TriggerResize();
}
void CloseWindow() override
{
drawing_engine_dispose();
SDL_DestroyWindow(_window);
}
void ShowMessageBox(const std::string &message) override
{
_platformUiContext->ShowMessageBox(_window, message);
}
std::string ShowFileDialog(const FileDialogDesc &desc) override
{
return _platformUiContext->ShowFileDialog(_window, desc);
}
std::string ShowDirectoryDialog(const std::string &title) override
{
return _platformUiContext->ShowDirectoryDialog(_window, title);
}
private:
void OnResize(sint32 width, sint32 height)
{
// Scale the native window size to the game's canvas size
_width = (sint32)(width / gConfigGeneral.window_scale);
_height = (sint32)(height / gConfigGeneral.window_scale);
drawing_engine_resize();
uint32 flags = SDL_GetWindowFlags(_window);
if ((flags & SDL_WINDOW_MINIMIZED) == 0)
{
window_resize_gui(_width, _height);
window_relocate_windows(_width, _height);
}
gfx_invalidate_screen();
// Check if the window has been resized in windowed mode and update the config file accordingly
// This is called in rct2_update and is only called after resizing a window has finished
sint32 nonWindowFlags = SDL_WINDOW_MAXIMIZED |
SDL_WINDOW_MINIMIZED |
SDL_WINDOW_FULLSCREEN |
SDL_WINDOW_FULLSCREEN_DESKTOP;
if (!(flags & nonWindowFlags))
{
if (width != gConfigGeneral.window_width || height != gConfigGeneral.window_height)
{
gConfigGeneral.window_width = width;
gConfigGeneral.window_height = height;
config_save_default();
}
}
}
void UpdateFullscreenResolutions()
{
// Query number of display modes
sint32 displayIndex = SDL_GetWindowDisplayIndex(_window);
sint32 numDisplayModes = SDL_GetNumDisplayModes(displayIndex);
// Get desktop aspect ratio
SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(displayIndex, &mode);
// Get resolutions
auto resolutions = std::vector<Resolution>(numDisplayModes);
float desktopAspectRatio = (float)mode.w / mode.h;
for (sint32 i = 0; i < numDisplayModes; i++)
{
SDL_GetDisplayMode(displayIndex, i, &mode);
float aspectRatio = (float)mode.w / mode.h;
if (_resolutionsAllowAnyAspectRatio || std::fabs(desktopAspectRatio - aspectRatio) < 0.0001f)
{
resolutions.push_back({ mode.w, mode.h });
}
}
// Sort by area
std::sort(resolutions.begin(), resolutions.end(),
[](const Resolution &a, const Resolution &b) -> bool
{
sint32 areaA = a.Width * a.Height;
sint32 areaB = b.Width * b.Height;
return areaA < areaB;
});
// Remove duplicates
auto last = std::unique(resolutions.begin(), resolutions.end(),
[](const Resolution &a, const Resolution &b) -> bool
{
return (a.Width == b.Width && a.Height == b.Height);
});
resolutions.erase(last, resolutions.end());
// Update config fullscreen resolution if not set
if (gConfigGeneral.fullscreen_width == -1 || gConfigGeneral.fullscreen_height == -1)
{
gConfigGeneral.fullscreen_width = resolutions.back().Width;
gConfigGeneral.fullscreen_height = resolutions.back().Height;
}
}
Resolution GetClosestResolution(sint32 inWidth, sint32 inHeight)
{
Resolution result = { 640, 480 };
sint32 closestAreaDiff = -1;
sint32 destinationArea = inWidth * inHeight;
for (const Resolution &resolution : _fsResolutions)
{
// Check if exact match
if (resolution.Width == inWidth && resolution.Height == inHeight)
{
result = resolution;
break;
}
// Check if area is closer to best match
sint32 areaDiff = std::abs((resolution.Width * resolution.Height) - destinationArea);
if (closestAreaDiff == -1 || areaDiff < closestAreaDiff)
{
closestAreaDiff = areaDiff;
result = resolution;
}
}
return result;
}
uint32 GetWindowFlags()
{
// Don't check if window is minimised too frequently (every second is fine)
uint32 tick = Platform::GetTicks();
if (tick > _windowFlagsLastCheckTick + 1000)
{
_windowFlags = SDL_GetWindowFlags(_window);
_windowFlagsLastCheckTick = tick;
}
return _windowFlags;
}
};
IUiContext * OpenRCT2::Ui::CreateUiContext()
{
return new UiContext();
}

View File

@ -0,0 +1,47 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include <string>
#include <openrct2/common.h>
struct SDL_Window;
namespace OpenRCT2
{
interface IContext;
namespace Ui
{
struct FileDialogDesc;
interface IUiContext;
interface IPlatformUiContext
{
virtual ~IPlatformUiContext() = default;
virtual void SetWindowIcon(SDL_Window * window) abstract;
virtual bool IsSteamOverlayAttached() abstract;
virtual void ShowMessageBox(SDL_Window * window, const std::string &message) abstract;
virtual std::string ShowFileDialog(SDL_Window * window, const FileDialogDesc &desc) abstract;
virtual std::string ShowDirectoryDialog(SDL_Window * window, const std::string &title) abstract;
};
IUiContext * CreateUiContext();
IPlatformUiContext * CreatePlatformUiContext();
}
}

View File

@ -0,0 +1,186 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#if defined(__APPLE__) && defined(__MACH__)
#import <Cocoa/Cocoa.h>
#include <mach-o/dyld.h>
#include <dlfcn.h>
#include <openrct2/common.h>
#include <openrct2/core/String.hpp>
#include <openrct2/ui/UiContext.h>
#include "UiContext.h"
#include <SDL.h>
namespace OpenRCT2 { namespace Ui
{
class macOSContext final : public IPlatformUiContext
{
private:
public:
macOSContext()
{
@autoreleasepool {
if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)]) {
[NSWindow setAllowsAutomaticWindowTabbing:NO];
}
}
}
void SetWindowIcon(SDL_Window * window) override
{
}
bool IsSteamOverlayAttached() override
{
STUB();
return false;
}
void ShowMessageBox(SDL_Window * window, const std::string &message) override
{
@autoreleasepool
{
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle:@"OK"];
alert.messageText = [NSString stringWithUTF8String:message.c_str()];
[alert runModal];
}
}
std::string ShowFileDialog(SDL_Window * window, const FileDialogDesc &desc) override
{
@autoreleasepool
{
NSMutableArray *extensions = [NSMutableArray new];
for (const OpenRCT2::Ui::FileDialogDesc::Filter &filter: desc.Filters) {
if (filter.Pattern != "") {
NSString *fp = [NSString stringWithUTF8String:filter.Pattern.c_str()];
fp = [fp stringByReplacingOccurrencesOfString:@"*." withString:@""];
[extensions addObjectsFromArray:[fp componentsSeparatedByString:@";"]];
}
}
NSString *directory;
NSSavePanel *panel;
if (desc.Type == FILE_DIALOG_TYPE::SAVE)
{
NSString *filePath = [NSString stringWithUTF8String:desc.DefaultFilename.c_str()];
directory = filePath.stringByDeletingLastPathComponent;
NSString *basename = filePath.lastPathComponent;
panel = [NSSavePanel savePanel];
panel.nameFieldStringValue = [NSString stringWithFormat:@"%@.%@", basename, extensions.firstObject];
}
else if (desc.Type == FILE_DIALOG_TYPE::OPEN)
{
directory = [NSString stringWithUTF8String:desc.InitialDirectory.c_str()];
NSOpenPanel *open = [NSOpenPanel openPanel];
open.canChooseDirectories = false;
open.canChooseFiles = true;
open.allowsMultipleSelection = false;
panel = open;
} else {
return std::string();
}
panel.title = [NSString stringWithUTF8String:desc.Title.c_str()];
panel.allowedFileTypes = extensions;
panel.directoryURL = [NSURL fileURLWithPath:directory];
if ([panel runModal] == NSFileHandlingPanelCancelButton){
return std::string();
} else {
return panel.URL.path.UTF8String;
}
}
}
std::string ShowDirectoryDialog(SDL_Window * window, const std::string &title) override
{
@autoreleasepool
{
NSOpenPanel *panel = [NSOpenPanel openPanel];
panel.canChooseFiles = false;
panel.canChooseDirectories = true;
panel.allowsMultipleSelection = false;
utf8 *url = NULL;
if ([panel runModal] == NSFileHandlingPanelOKButton)
{
NSString *selectedPath = panel.URL.path;
const char *path = selectedPath.UTF8String;
url = _strdup(path);
}
return url;
}
}
private:
static sint32 Execute(const std::string &command, std::string * output = nullptr)
{
log_verbose("executing \"%s\"...\n", command.c_str());
FILE * fpipe = popen(command.c_str(), "r");
if (fpipe == nullptr)
{
return -1;
}
if (output != nullptr)
{
// Read output into buffer
std::vector<char> outputBuffer;
char buffer[1024];
size_t readBytes;
while ((readBytes = fread(buffer, 1, sizeof(buffer), fpipe)) > 0)
{
outputBuffer.insert(outputBuffer.begin(), buffer, buffer + readBytes);
}
// Trim line breaks
size_t outputLength = outputBuffer.size();
for (size_t i = outputLength - 1; i != SIZE_MAX; i--)
{
if (outputBuffer[i] == '\n')
{
outputLength = i;
}
else
{
break;
}
}
// Convert to string
*output = std::string(outputBuffer.data(), outputLength);
}
else
{
fflush(fpipe);
}
// Return exit code
return pclose(fpipe);
}
};
IPlatformUiContext * CreatePlatformUiContext()
{
return new macOSContext();
}
} }
#endif // __APPLE__ && __MACH__

View File

@ -0,0 +1,297 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <cmath>
#include <SDL.h>
#include <speex/speex_resampler.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/audio/AudioChannel.h>
#include <openrct2/audio/AudioSource.h>
#include "AudioContext.h"
#include "AudioFormat.h"
namespace OpenRCT2 { namespace Audio
{
class AudioChannelImpl : public ISDLAudioChannel
{
private:
ISDLAudioSource * _source = nullptr;
SpeexResamplerState * _resampler = nullptr;
sint32 _group = MIXER_GROUP_SOUND;
double _rate = 0;
uint64 _offset = 0;
sint32 _loop = 0;
sint32 _volume = 1;
float _volume_l = 0.f;
float _volume_r = 0.f;
float _oldvolume_l = 0.f;
float _oldvolume_r = 0.f;
sint32 _oldvolume = 0;
float _pan = 0;
bool _stopping = false;
bool _done = true;
bool _deleteondone = false;
bool _deletesourceondone = false;
public:
AudioChannelImpl()
{
SetRate(1);
SetVolume(SDL_MIX_MAXVOLUME);
SetPan(0.5f);
}
~AudioChannelImpl() override
{
if (_resampler != nullptr)
{
speex_resampler_destroy(_resampler);
_resampler = nullptr;
}
if (_deletesourceondone)
{
delete _source;
}
}
IAudioSource * GetSource() const override
{
return _source;
}
SpeexResamplerState * GetResampler() const override
{
return _resampler;
}
void SetResampler(SpeexResamplerState * value) override
{
_resampler = value;
}
sint32 GetGroup() const override
{
return _group;
}
void SetGroup(sint32 group) override
{
_group = group;
}
double GetRate() const override
{
return _rate;
}
void SetRate(double rate) override
{
_rate = Math::Max(0.001, rate);
}
uint64 GetOffset() const override
{
return _offset;
}
bool SetOffset(uint64 offset) override
{
if (_source != nullptr && offset < _source->GetLength())
{
AudioFormat format = _source->GetFormat();
sint32 samplesize = format.channels * format.BytesPerSample();
_offset = (offset / samplesize) * samplesize;
return true;
}
return false;
}
virtual sint32 GetLoop() const override
{
return _loop;
}
virtual void SetLoop(sint32 value) override
{
_loop = value;
}
sint32 GetVolume() const override
{
return _volume;
}
float GetVolumeL() const override
{
return _volume_l;
}
float GetVolumeR() const override
{
return _volume_r;
}
float GetOldVolumeL() const override
{
return _oldvolume_l;
}
float GetOldVolumeR() const override
{
return _oldvolume_r;
}
sint32 GetOldVolume() const override
{
return _oldvolume;
}
void SetVolume(sint32 volume) override
{
_volume = Math::Clamp(0, volume, SDL_MIX_MAXVOLUME);
}
float GetPan() const override
{
return _pan;
}
void SetPan(float pan) override
{
_pan = Math::Clamp(0.0f, pan, 1.0f);
double decibels = (std::abs(_pan - 0.5) * 2.0) * 100.0;
double attenuation = pow(10, decibels / 20.0);
if (_pan <= 0.5)
{
_volume_l = 1.0;
_volume_r = (float)(1.0 / attenuation);
}
else
{
_volume_r = 1.0;
_volume_l = (float)(1.0 / attenuation);
}
}
bool IsStopping() const override
{
return _stopping;
}
void SetStopping(bool value) override
{
_stopping = value;
}
bool IsDone() const override
{
return _done;
}
void SetDone(bool value) override
{
_done = value;
}
bool DeleteOnDone() const override
{
return _deleteondone;
}
void SetDeleteOnDone(bool value) override
{
_deleteondone = value;
}
void SetDeleteSourceOnDone(bool value) override
{
_deletesourceondone = value;
}
bool IsPlaying() const override
{
return !_done;
}
void Play(IAudioSource * source, sint32 loop) override
{
_source = static_cast<ISDLAudioSource *>(source);
_loop = loop;
_offset = 0;
_done = false;
}
void UpdateOldVolume() override
{
_oldvolume = _volume;
_oldvolume_l = _volume_l;
_oldvolume_r = _volume_r;
}
AudioFormat GetFormat() const override
{
AudioFormat result = { 0 };
if (_source != nullptr)
{
result = _source->GetFormat();
}
return result;
}
size_t Read(void * dst, size_t len) override
{
size_t bytesRead = 0;
size_t bytesToRead = len;
while (bytesToRead > 0 && !_done)
{
size_t readLen = _source->Read(dst, _offset, bytesToRead);
if (readLen > 0)
{
dst = (void *)((uintptr_t)dst + readLen);
bytesToRead -= readLen;
bytesRead += readLen;
_offset += readLen;
}
if (_offset >= _source->GetLength())
{
if (_loop == 0)
{
_done = true;
}
else if (_loop == MIXER_LOOP_INFINITE)
{
_offset = 0;
}
else
{
_loop--;
_offset = 0;
}
}
}
return bytesRead;
}
};
ISDLAudioChannel * AudioChannel::Create()
{
return new (std::nothrow) AudioChannelImpl();
}
} }

View File

@ -0,0 +1,100 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <SDL.h>
#include <openrct2/audio/AudioContext.h>
#include <openrct2/core/String.hpp>
#include "../SDLException.h"
#include "AudioContext.h"
namespace OpenRCT2 { namespace Audio
{
class AudioContext : public IAudioContext
{
private:
IAudioMixer * _audioMixer;
public:
AudioContext()
{
if (SDL_Init(SDL_INIT_AUDIO) < 0)
{
SDLException::Throw("SDL_Init(SDL_INIT_AUDIO)");
}
_audioMixer = AudioMixer::Create();
}
~AudioContext() override
{
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
IAudioMixer * GetMixer() override
{
return _audioMixer;
}
std::vector<std::string> GetOutputDevices() override
{
std::vector<std::string> devices;
sint32 numDevices = SDL_GetNumAudioDevices(SDL_FALSE);
for (sint32 i = 0; i < numDevices; i++)
{
std::string deviceName = String::ToStd(SDL_GetAudioDeviceName(i, SDL_FALSE));
devices.push_back(deviceName);
}
return devices;
}
void SetOutputDevice(const std::string &deviceName) override
{
const char * szDeviceName = nullptr;
if (!deviceName.empty())
{
szDeviceName = deviceName.c_str();
}
_audioMixer->Init(szDeviceName);
}
IAudioSource * CreateStreamFromWAV(const std::string &path) override
{
return AudioSource::CreateStreamFromWAV(path);
}
void StartTitleMusic() override { }
IAudioChannel * PlaySound(sint32 soundId, sint32 volume, sint32 pan) override { return nullptr; }
IAudioChannel * PlaySoundAtLocation(sint32 soundId, sint16 x, sint16 y, sint16 z) override { return nullptr; }
IAudioChannel * PlaySoundPanned(sint32 soundId, sint32 pan, sint16 x, sint16 y, sint16 z) override { return nullptr; }
void ToggleAllSounds() override { }
void PauseSounds() override { }
void UnpauseSounds() override { }
void StopAll() override { }
void StopCrowdSound() override { }
void StopRainSound() override { }
void StopRideMusic() override { }
void StopTitleMusic() override { }
void StopVehicleSounds() override { }
};
IAudioContext * CreateAudioContext()
{
return new AudioContext();
}
} }

View File

@ -0,0 +1,74 @@
#pragma once
#include <string>
#include <openrct2/common.h>
#include <openrct2/audio/AudioChannel.h>
#include <openrct2/audio/AudioSource.h>
struct SDL_RWops;
struct SpeexResamplerState_;
typedef struct SpeexResamplerState_ SpeexResamplerState;
namespace OpenRCT2 { namespace Audio
{
struct AudioFormat;
interface IAudioContext;
#pragma pack(push, 1)
struct WaveFormat
{
uint16 encoding;
uint16 channels;
uint32 frequency;
uint32 byterate;
uint16 blockalign;
uint16 bitspersample;
};
assert_struct_size(WaveFormat, 16);
struct WaveFormatEx
{
uint16 encoding;
uint16 channels;
uint32 frequency;
uint32 byterate;
uint16 blockalign;
uint16 bitspersample;
uint16 extrasize;
};
assert_struct_size(WaveFormatEx, 18);
#pragma pack(pop)
interface ISDLAudioSource : public IAudioSource
{
virtual AudioFormat GetFormat() const abstract;
};
interface ISDLAudioChannel : public IAudioChannel
{
virtual AudioFormat GetFormat() const abstract;
virtual SpeexResamplerState * GetResampler() const abstract;
virtual void SetResampler(SpeexResamplerState * value) abstract;
};
namespace AudioSource
{
IAudioSource * CreateMemoryFromCSS1(const std::string &path, size_t index, const AudioFormat * targetFormat = nullptr);
IAudioSource * CreateMemoryFromWAV(const std::string &path, const AudioFormat * targetFormat = nullptr);
IAudioSource * CreateStreamFromWAV(const std::string &path);
IAudioSource * CreateStreamFromWAV(SDL_RWops * rw);
}
namespace AudioChannel
{
ISDLAudioChannel * Create();
}
namespace AudioMixer
{
IAudioMixer * Create();
}
IAudioContext * CreateAudioContext();
} }

View File

@ -16,38 +16,41 @@
#pragma once
#include "../common.h"
#include <openrct2/common.h>
#include <SDL.h>
/**
* Represents the size, frequency and number of channels for
* an audio stream or buffer.
*/
struct AudioFormat
namespace OpenRCT2 { namespace Audio
{
sint32 freq;
SDL_AudioFormat format;
sint32 channels;
sint32 BytesPerSample() const
/**
* Represents the size, frequency and number of channels for
* an audio stream or buffer.
*/
struct AudioFormat
{
return (SDL_AUDIO_BITSIZE(format)) / 8;
sint32 freq;
SDL_AudioFormat format;
sint32 channels;
sint32 BytesPerSample() const
{
return (SDL_AUDIO_BITSIZE(format)) / 8;
}
sint32 GetByteRate() const
{
return BytesPerSample() * channels;
}
};
inline bool operator ==(const AudioFormat& lhs, const AudioFormat& rhs)
{
return lhs.freq == rhs.freq &&
lhs.format == rhs.format &&
lhs.channels == rhs.channels;
}
sint32 GetByteRate() const
inline bool operator !=(const AudioFormat& lhs, const AudioFormat& rhs)
{
return BytesPerSample() * channels;
return !(lhs == rhs);
}
};
inline bool operator ==(const AudioFormat& lhs, const AudioFormat& rhs)
{
return lhs.freq == rhs.freq &&
lhs.format == rhs.format &&
lhs.channels == rhs.channels;
}
inline bool operator !=(const AudioFormat& lhs, const AudioFormat& rhs)
{
return !(lhs == rhs);
}
} }

View File

@ -0,0 +1,542 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <SDL.h>
#include <speex/speex_resampler.h>
#include <list>
#include <openrct2/core/Guard.hpp>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/core/Util.hpp>
#include <openrct2/audio/audio.h>
#include <openrct2/audio/AudioChannel.h>
#include <openrct2/audio/AudioMixer.h>
#include <openrct2/audio/AudioSource.h>
#include "AudioContext.h"
#include "AudioFormat.h"
extern "C"
{
#include <openrct2/config/Config.h>
#include <openrct2/localisation/localisation.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/platform/platform.h>
#include <openrct2/rct2.h>
}
namespace OpenRCT2 { namespace Audio
{
struct Buffer
{
private:
void * _data = nullptr;
size_t _capacity = 0;
public:
void * GetData() const { return _data; }
void * GetData() { return _data; }
size_t GetCapacity() const { return _capacity; }
~Buffer()
{
Free();
}
void Free()
{
Memory::Free(_data);
_data = nullptr;
_capacity = 0;
}
void EnsureCapacity(size_t capacity)
{
if (_capacity < capacity)
{
_capacity = capacity;
_data = Memory::Reallocate(_data, capacity);
}
}
};
class AudioMixerImpl final : public IAudioMixer
{
private:
IAudioSource * _nullSource = nullptr;
SDL_AudioDeviceID _deviceId = 0;
AudioFormat _format = { 0 };
std::list<ISDLAudioChannel *> _channels;
float _volume = 1.0f;
float _adjustSoundVolume = 0.0f;
float _adjustMusicVolume = 0.0f;
uint8 _settingSoundVolume = 0xFF;
uint8 _settingMusicVolume = 0xFF;
IAudioSource * _css1Sources[SOUND_MAXID] = { nullptr };
IAudioSource * _musicSources[PATH_ID_END] = { nullptr };
Buffer _channelBuffer;
Buffer _convertBuffer;
Buffer _effectBuffer;
public:
AudioMixerImpl()
{
_nullSource = AudioSource::CreateNull();
}
~AudioMixerImpl()
{
Close();
delete _nullSource;
}
void Init(const char * device) override
{
Close();
SDL_AudioSpec want = { 0 };
want.freq = 44100;
want.format = AUDIO_S16SYS;
want.channels = 2;
want.samples = 1024;
want.callback = [](void * arg, uint8 * dst, sint32 length) -> void
{
auto mixer = static_cast<AudioMixerImpl *>(arg);
mixer->GetNextAudioChunk(dst, (size_t)length);
};
want.userdata = this;
SDL_AudioSpec have;
_deviceId = SDL_OpenAudioDevice(device, 0, &want, &have, 0);
_format.format = have.format;
_format.channels = have.channels;
_format.freq = have.freq;
LoadAllSounds();
SDL_PauseAudioDevice(_deviceId, 0);
}
void Close() override
{
// Free channels
Lock();
for (IAudioChannel * channel : _channels)
{
delete channel;
}
_channels.clear();
Unlock();
SDL_CloseAudioDevice(_deviceId);
// Free sources
for (size_t i = 0; i < Util::CountOf(_css1Sources); i++)
{
if (_css1Sources[i] != _nullSource)
{
SafeDelete(_css1Sources[i]);
}
}
for (size_t i = 0; i < Util::CountOf(_musicSources); i++)
{
if (_musicSources[i] != _nullSource)
{
SafeDelete(_musicSources[i]);
}
}
// Free buffers
_channelBuffer.Free();
_convertBuffer.Free();
_effectBuffer.Free();
}
void Lock() override
{
SDL_LockAudioDevice(_deviceId);
}
void Unlock() override
{
SDL_UnlockAudioDevice(_deviceId);
}
IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) override
{
Lock();
ISDLAudioChannel * channel = AudioChannel::Create();
if (channel != nullptr)
{
channel->Play(source, loop);
channel->SetDeleteOnDone(deleteondone);
channel->SetDeleteSourceOnDone(deletesourceondone);
_channels.push_back(channel);
}
Unlock();
return channel;
}
void Stop(IAudioChannel * channel) override
{
Lock();
channel->SetStopping(true);
Unlock();
}
bool LoadMusic(size_t pathId) override
{
bool result = false;
if (pathId < Util::CountOf(_musicSources))
{
IAudioSource * source = _musicSources[pathId];
if (source == nullptr)
{
const utf8 * path = get_file_path((sint32)pathId);
source = AudioSource::CreateMemoryFromWAV(path, &_format);
if (source == nullptr)
{
source = _nullSource;
}
_musicSources[pathId] = source;
}
result = source != _nullSource;
}
return result;
}
void SetVolume(float volume) override
{
_volume = volume;
}
IAudioSource * GetSoundSource(sint32 id) override
{
return _css1Sources[id];
}
IAudioSource * GetMusicSource(sint32 id) override
{
return _musicSources[id];
}
private:
void LoadAllSounds()
{
const utf8 * css1Path = get_file_path(PATH_ID_CSS1);
for (size_t i = 0; i < Util::CountOf(_css1Sources); i++)
{
auto source = AudioSource::CreateMemoryFromCSS1(css1Path, i, &_format);
if (source == nullptr)
{
source = _nullSource;
}
_css1Sources[i] = source;
}
}
void GetNextAudioChunk(uint8 * dst, size_t length)
{
UpdateAdjustedSound();
// Zero the output buffer
Memory::Set(dst, 0, length);
// Mix channels onto output buffer
auto it = _channels.begin();
while (it != _channels.end())
{
auto channel = *it;
sint32 group = channel->GetGroup();
if (group != MIXER_GROUP_SOUND || gConfigSound.sound_enabled)
{
MixChannel(channel, dst, length);
}
if ((channel->IsDone() && channel->DeleteOnDone()) || channel->IsStopping())
{
delete channel;
it = _channels.erase(it);
}
else
{
it++;
}
}
}
void UpdateAdjustedSound()
{
// Did the volume level get changed? Recalculate level in this case.
if (_settingSoundVolume != gConfigSound.sound_volume)
{
_settingSoundVolume = gConfigSound.sound_volume;
_adjustSoundVolume = powf(_settingSoundVolume / 100.f, 10.f / 6.f);
}
if (_settingMusicVolume != gConfigSound.ride_music_volume)
{
_settingMusicVolume = gConfigSound.ride_music_volume;
_adjustMusicVolume = powf(_settingMusicVolume / 100.f, 10.f / 6.f);
}
}
void MixChannel(ISDLAudioChannel * channel, uint8 * data, size_t length)
{
sint32 byteRate = _format.GetByteRate();
sint32 numSamples = (sint32)(length / byteRate);
double rate = 1;
if (_format.format == AUDIO_S16SYS)
{
rate = channel->GetRate();
}
bool mustConvert = false;
SDL_AudioCVT cvt;
cvt.len_ratio = 1;
AudioFormat streamformat = channel->GetFormat();
if (streamformat != _format)
{
if (SDL_BuildAudioCVT(&cvt, streamformat.format, streamformat.channels, streamformat.freq, _format.format, _format.channels, _format.freq) == -1)
{
// Unable to convert channel data
return;
}
mustConvert = true;
}
// Read raw PCM from channel
sint32 readSamples = (sint32)(numSamples * rate);
size_t readLength = (size_t)(readSamples / cvt.len_ratio) * byteRate;
_channelBuffer.EnsureCapacity(readLength);
size_t bytesRead = channel->Read(_channelBuffer.GetData(), readLength);
// Convert data to required format if necessary
void * buffer = nullptr;
size_t bufferLen = 0;
if (mustConvert)
{
if (Convert(&cvt, _channelBuffer.GetData(), bytesRead))
{
buffer = cvt.buf;
bufferLen = cvt.len_cvt;
}
else
{
return;
}
}
else
{
buffer = _channelBuffer.GetData();
bufferLen = bytesRead;
}
// Apply effects
if (rate != 1)
{
sint32 srcSamples = (sint32)(bufferLen / byteRate);
sint32 dstSamples = numSamples;
bufferLen = ApplyResample(channel, buffer, srcSamples, dstSamples);
buffer = _effectBuffer.GetData();
}
// Apply panning and volume
ApplyPan(channel, buffer, bufferLen, byteRate);
sint32 mixVolume = ApplyVolume(channel, buffer, bufferLen);
// Finally mix on to destination buffer
size_t dstLength = Math::Min(length, bufferLen);
SDL_MixAudioFormat(data, (const uint8 *)buffer, _format.format, (uint32)dstLength, mixVolume);
channel->UpdateOldVolume();
}
/**
* Resample the given buffer into _effectBuffer.
* Assumes that srcBuffer is the same format as _format.
*/
size_t ApplyResample(ISDLAudioChannel * channel, const void * srcBuffer, sint32 srcSamples, sint32 dstSamples)
{
sint32 byteRate = _format.GetByteRate();
// Create resampler
SpeexResamplerState * resampler = channel->GetResampler();
if (resampler == nullptr)
{
resampler = speex_resampler_init(_format.channels, _format.freq, _format.freq, 0, 0);
channel->SetResampler(resampler);
}
speex_resampler_set_rate(resampler, srcSamples, dstSamples);
// Ensure destination buffer is large enough
size_t effectBufferReqLen = dstSamples * byteRate;
_effectBuffer.EnsureCapacity(effectBufferReqLen);
uint32 inLen = srcSamples;
uint32 outLen = dstSamples;
speex_resampler_process_interleaved_int(
resampler,
(const spx_int16_t *)srcBuffer,
&inLen,
(spx_int16_t *)_effectBuffer.GetData(),
&outLen);
return outLen * byteRate;
}
void ApplyPan(const IAudioChannel * channel, void * buffer, size_t len, size_t sampleSize)
{
if (channel->GetPan() != 0.5f && _format.channels == 2)
{
switch (_format.format) {
case AUDIO_S16SYS:
EffectPanS16(channel, (sint16 *)buffer, (sint32)(len / sampleSize));
break;
case AUDIO_U8:
EffectPanU8(channel, (uint8 *)buffer, (sint32)(len / sampleSize));
break;
}
}
}
sint32 ApplyVolume(const IAudioChannel * channel, void * buffer, size_t len)
{
float volumeAdjust = _volume;
volumeAdjust *= (gConfigSound.master_volume / 100.0f);
switch (channel->GetGroup()) {
case MIXER_GROUP_SOUND:
volumeAdjust *= _adjustSoundVolume;
// Cap sound volume on title screen so music is more audible
if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)
{
volumeAdjust = Math::Min(volumeAdjust, 0.75f);
}
break;
case MIXER_GROUP_RIDE_MUSIC:
volumeAdjust *= _adjustMusicVolume;
break;
}
sint32 startVolume = (sint32)(channel->GetOldVolume() * volumeAdjust);
sint32 endVolume = (sint32)(channel->GetVolume() * volumeAdjust);
if (channel->IsStopping())
{
endVolume = 0;
}
sint32 mixVolume = (sint32)(channel->GetVolume() * volumeAdjust);
if (startVolume != endVolume)
{
// Set to max since we are adjusting the volume ourselves
mixVolume = SDL_MIX_MAXVOLUME;
// Fade between volume levels to smooth out sound and minimize clicks from sudden volume changes
sint32 fadeLength = (sint32)len / _format.BytesPerSample();
switch (_format.format) {
case AUDIO_S16SYS:
EffectFadeS16((sint16 *)buffer, fadeLength, startVolume, endVolume);
break;
case AUDIO_U8:
EffectFadeU8((uint8 *)buffer, fadeLength, startVolume, endVolume);
break;
}
}
return mixVolume;
}
static void EffectPanS16(const IAudioChannel * channel, sint16 * data, sint32 length)
{
const float dt = 1.0f / (length * 2);
float volumeL = channel->GetOldVolumeL();
float volumeR = channel->GetOldVolumeR();
const float d_left = dt * (channel->GetVolumeL() - channel->GetOldVolumeL());
const float d_right = dt * (channel->GetVolumeR() - channel->GetOldVolumeR());
for (sint32 i = 0; i < length * 2; i += 2)
{
data[i] = (sint16)(data[i] * volumeL);
data[i + 1] = (sint16)(data[i + 1] * volumeR);
volumeL += d_left;
volumeR += d_right;
}
}
static void EffectPanU8(const IAudioChannel * channel, uint8 * data, sint32 length)
{
float volumeL = channel->GetVolumeL();
float volumeR = channel->GetVolumeR();
float oldVolumeL = channel->GetOldVolumeL();
float oldVolumeR = channel->GetOldVolumeR();
for (sint32 i = 0; i < length * 2; i += 2)
{
float t = (float)i / (length * 2);
data[i] = (uint8)(data[i] * ((1.0 - t) * oldVolumeL + t * volumeL));
data[i + 1] = (uint8)(data[i + 1] * ((1.0 - t) * oldVolumeR + t * volumeR));
}
}
static void EffectFadeS16(sint16 * data, sint32 length, sint32 startvolume, sint32 endvolume)
{
float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME;
float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME;
for (sint32 i = 0; i < length; i++)
{
float t = (float)i / length;
data[i] = (sint16)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f));
}
}
static void EffectFadeU8(uint8* data, sint32 length, sint32 startvolume, sint32 endvolume)
{
float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME;
float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME;
for (sint32 i = 0; i < length; i++)
{
float t = (float)i / length;
data[i] = (uint8)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f));
}
}
bool Convert(SDL_AudioCVT * cvt, const void * src, size_t len)
{
// tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, can cause pops and static depending on sample rate and channels
bool result = false;
if (len != 0 && cvt->len_mult != 0)
{
size_t reqConvertBufferCapacity = len * cvt->len_mult;
_convertBuffer.EnsureCapacity(reqConvertBufferCapacity);
Memory::Copy(_convertBuffer.GetData(), src, len);
cvt->len = (sint32)len;
cvt->buf = (uint8 *)_convertBuffer.GetData();
if (SDL_ConvertAudio(cvt) >= 0)
{
result = true;
}
}
return result;
}
};
IAudioMixer * AudioMixer::Create()
{
return new AudioMixerImpl();
}
} }

View File

@ -0,0 +1,210 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <SDL.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/audio/AudioSource.h>
#include "AudioContext.h"
#include "AudioFormat.h"
namespace OpenRCT2 { namespace Audio
{
/**
* An audio source where raw PCM data is streamed directly from
* a file.
*/
class FileAudioSource final : public ISDLAudioSource
{
private:
AudioFormat _format = { 0 };
SDL_RWops * _rw = nullptr;
uint64 _dataBegin = 0;
uint64 _dataLength = 0;
public:
~FileAudioSource()
{
Unload();
}
uint64 GetLength() const override
{
return _dataLength;
}
AudioFormat GetFormat() const override
{
return _format;
}
size_t Read(void * dst, uint64 offset, size_t len) override
{
size_t bytesRead = 0;
sint64 currentPosition = SDL_RWtell(_rw);
if (currentPosition != -1)
{
size_t bytesToRead = (size_t)Math::Min<uint64>(len, _dataLength - offset);
sint64 dataOffset = _dataBegin + offset;
if (currentPosition != dataOffset)
{
sint64 newPosition = SDL_RWseek(_rw, dataOffset, SEEK_SET);
if (newPosition == -1)
{
return 0;
}
}
bytesRead = SDL_RWread(_rw, dst, 1, bytesToRead);
}
return bytesRead;
}
bool LoadWAV(SDL_RWops * rw)
{
const uint32 DATA = 0x61746164;
const uint32 FMT = 0x20746D66;
const uint32 RIFF = 0x46464952;
const uint32 WAVE = 0x45564157;
const uint16 pcmformat = 0x0001;
Unload();
if (rw == nullptr)
{
return false;
}
_rw = rw;
uint32 chunkId = SDL_ReadLE32(rw);
if (chunkId != RIFF)
{
log_verbose("Not a WAV file");
return false;
}
// Read and discard chunk size
SDL_ReadLE32(rw);
uint32 chunkFormat = SDL_ReadLE32(rw);
if (chunkFormat != WAVE)
{
log_verbose("Not in WAVE format");
return false;
}
uint32 fmtChunkSize = FindChunk(rw, FMT);
if (!fmtChunkSize)
{
log_verbose("Could not find FMT chunk");
return false;
}
uint64 chunkStart = SDL_RWtell(rw);
WaveFormat waveFormat;
SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1);
SDL_RWseek(rw, chunkStart + fmtChunkSize, RW_SEEK_SET);
if (waveFormat.encoding != pcmformat) {
log_verbose("Not in proper format");
return false;
}
_format.freq = waveFormat.frequency;
switch (waveFormat.bitspersample) {
case 8:
_format.format = AUDIO_U8;
break;
case 16:
_format.format = AUDIO_S16LSB;
break;
default:
log_verbose("Invalid bits per sample");
return false;
break;
}
_format.channels = waveFormat.channels;
uint32 dataChunkSize = FindChunk(rw, DATA);
if (dataChunkSize == 0)
{
log_verbose("Could not find DATA chunk");
return false;
}
_dataLength = dataChunkSize;
_dataBegin = SDL_RWtell(rw);
return true;
}
private:
uint32 FindChunk(SDL_RWops * rw, uint32 wantedId)
{
uint32 subchunkId = SDL_ReadLE32(rw);
uint32 subchunkSize = SDL_ReadLE32(rw);
if (subchunkId == wantedId)
{
return subchunkSize;
}
const uint32 FACT = 0x74636166;
const uint32 LIST = 0x5453494c;
const uint32 BEXT = 0x74786562;
const uint32 JUNK = 0x4B4E554A;
while (subchunkId == FACT || subchunkId == LIST || subchunkId == BEXT || subchunkId == JUNK)
{
SDL_RWseek(rw, subchunkSize, RW_SEEK_CUR);
subchunkId = SDL_ReadLE32(rw);
subchunkSize = SDL_ReadLE32(rw);
if (subchunkId == wantedId)
{
return subchunkSize;
}
}
return 0;
}
void Unload()
{
if (_rw != nullptr)
{
SDL_RWclose(_rw);
_rw = nullptr;
}
_dataBegin = 0;
_dataLength = 0;
}
};
IAudioSource * AudioSource::CreateStreamFromWAV(const std::string &path)
{
IAudioSource * source = nullptr;
SDL_RWops* rw = SDL_RWFromFile(path.c_str(), "rb");
if (rw != nullptr)
{
return AudioSource::CreateStreamFromWAV(rw);
}
return source;
}
IAudioSource * AudioSource::CreateStreamFromWAV(SDL_RWops * rw)
{
auto source = new FileAudioSource();
if (!source->LoadWAV(rw))
{
delete source;
source = nullptr;
}
return source;
}
} }

View File

@ -0,0 +1,241 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <openrct2/common.h>
#include <SDL.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/audio/AudioMixer.h>
#include <openrct2/audio/AudioSource.h>
#include "AudioContext.h"
#include "AudioFormat.h"
namespace OpenRCT2 { namespace Audio
{
/**
* An audio source where raw PCM data is initially loaded into RAM from
* a file and then streamed.
*/
class MemoryAudioSource final : public ISDLAudioSource
{
private:
AudioFormat _format = { 0 };
uint8 * _data = nullptr;
size_t _length = 0;
bool _isSDLWav = false;
public:
~MemoryAudioSource()
{
Unload();
}
uint64 GetLength() const override
{
return _length;
}
AudioFormat GetFormat() const override
{
return _format;
}
size_t Read(void * dst, uint64 offset, size_t len) override
{
size_t bytesToRead = 0;
if (offset < _length)
{
bytesToRead = (size_t)Math::Min<uint64>(len, _length - offset);
Memory::Copy<void>(dst, _data + offset, bytesToRead);
}
return bytesToRead;
}
bool LoadWAV(const utf8 * path)
{
log_verbose("MemoryAudioSource::LoadWAV(%s)", path);
Unload();
bool result = false;
SDL_RWops * rw = SDL_RWFromFile(path, "rb");
if (rw != nullptr)
{
SDL_AudioSpec audiospec = { 0 };
uint32 audioLen;
SDL_AudioSpec * spec = SDL_LoadWAV_RW(rw, false, &audiospec, &_data, &audioLen);
if (spec != nullptr)
{
_format.freq = spec->freq;
_format.format = spec->format;
_format.channels = spec->channels;
_length = audioLen;
_isSDLWav = true;
result = true;
}
else
{
log_verbose("Error loading %s, unsupported WAV format", path);
}
SDL_RWclose(rw);
}
else
{
log_verbose("Error loading %s", path);
}
return result;
}
bool LoadCSS1(const utf8 * path, size_t index)
{
log_verbose("MemoryAudioSource::LoadCSS1(%s, %d)", path, index);
Unload();
bool result = false;
SDL_RWops * rw = SDL_RWFromFile(path, "rb");
if (rw != nullptr)
{
uint32 numSounds;
SDL_RWread(rw, &numSounds, sizeof(numSounds), 1);
if (index < numSounds)
{
SDL_RWseek(rw, index * 4, RW_SEEK_CUR);
uint32 pcmOffset;
SDL_RWread(rw, &pcmOffset, sizeof(pcmOffset), 1);
SDL_RWseek(rw, pcmOffset, RW_SEEK_SET);
uint32 pcmSize;
SDL_RWread(rw, &pcmSize, sizeof(pcmSize), 1);
_length = pcmSize;
WaveFormatEx waveFormat;
SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1);
_format.freq = waveFormat.frequency;
_format.format = AUDIO_S16LSB;
_format.channels = waveFormat.channels;
_data = new (std::nothrow) uint8[_length];
if (_data != nullptr)
{
SDL_RWread(rw, _data, _length, 1);
result = true;
}
else
{
log_verbose("Unable to allocate data");
}
}
SDL_RWclose(rw);
}
else
{
log_verbose("Unable to load %s", path);
}
return result;
}
bool Convert(const AudioFormat * format)
{
if (*format != _format)
{
SDL_AudioCVT cvt;
if (SDL_BuildAudioCVT(&cvt, _format.format, _format.channels, _format.freq, format->format, format->channels, format->freq) >= 0)
{
cvt.len = (sint32)_length;
cvt.buf = new uint8[cvt.len * cvt.len_mult];
Memory::Copy(cvt.buf, _data, _length);
if (SDL_ConvertAudio(&cvt) >= 0)
{
Unload();
_data = cvt.buf;
_length = cvt.len_cvt;
_format = *format;
return true;
}
else
{
delete[] cvt.buf;
}
}
}
return false;
}
private:
void Unload()
{
if (_data != nullptr)
{
if (_isSDLWav)
{
SDL_FreeWAV(_data);
}
else
{
delete[] _data;
}
_data = nullptr;
}
_isSDLWav = false;
_length = 0;
}
};
IAudioSource * AudioSource::CreateMemoryFromCSS1(const std::string &path, size_t index, const AudioFormat * targetFormat)
{
auto source = new MemoryAudioSource();
if (source->LoadCSS1(path.c_str(), index))
{
if (targetFormat != nullptr)
{
if (!source->Convert(targetFormat))
{
delete source;
source = nullptr;
}
}
}
else
{
delete source;
source = nullptr;
}
return source;
}
IAudioSource * AudioSource::CreateMemoryFromWAV(const std::string &path, const AudioFormat * targetFormat)
{
auto source = new MemoryAudioSource();
if (source->LoadWAV(path.c_str()))
{
if (targetFormat != nullptr)
{
if (!source->Convert(targetFormat))
{
SafeDelete(source);
}
}
}
else
{
delete source;
source = nullptr;
}
return source;
}
} }

View File

@ -0,0 +1,36 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include <openrct2/common.h>
namespace OpenRCT2
{
namespace Drawing
{
interface IDrawingEngine;
}
namespace Ui
{
Drawing::IDrawingEngine * CreateSoftwareDrawingEngine(IUiContext * uiContext);
Drawing::IDrawingEngine * CreateHardwareDisplayDrawingEngine(IUiContext * uiContext);
#ifndef DISABLE_OPENGL
Drawing::IDrawingEngine * CreateOpenGLDrawingEngine(IUiContext * uiContext);
#endif
}
}

View File

@ -14,27 +14,35 @@
*****************************************************************************/
#pragma endregion
#include "../../core/Guard.hpp"
#include "../../core/Math.hpp"
#include "../../core/Memory.hpp"
#include "../IDrawingContext.h"
#include "../IDrawingEngine.h"
#include "../Rain.h"
#include <openrct2/config/Config.h>
#include <openrct2/Context.h>
#include <openrct2/ui/UiContext.h>
#include <openrct2/core/Guard.hpp>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/drawing/IDrawingContext.h>
#include <openrct2/drawing/IDrawingEngine.h>
#include <openrct2/drawing/Rain.h>
#include "DrawingEngines.h"
extern "C"
{
#include "../../config/Config.h"
#include "../../game.h"
#include "../../interface/screenshot.h"
#include "../../interface/viewport.h"
#include "../../interface/window.h"
#include "../../intro.h"
#include "../../platform/platform.h"
#include "../../rct2.h"
#include "../drawing.h"
#include "../lightfx.h"
#include <openrct2/config/Config.h>
#include <openrct2/game.h>
#include <openrct2/interface/screenshot.h>
#include <openrct2/interface/viewport.h>
#include <openrct2/interface/window.h>
#include <openrct2/intro.h>
#include <openrct2/platform/platform.h>
#include <openrct2/rct2.h>
#include <openrct2/drawing/drawing.h>
#include <openrct2/drawing/lightfx.h>
}
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
using namespace OpenRCT2::Ui;
class SoftwareDrawingEngine;
struct DirtyGrid
@ -184,8 +192,10 @@ public:
class SoftwareDrawingEngine final : public IDrawingEngine
{
private:
bool _hardwareDisplay;
IUiContext * const _uiContext;
bool const _hardwareDisplay;
SDL_Window * _window = nullptr;
SDL_Surface * _surface = nullptr;
SDL_Surface * _RGBASurface = nullptr;
SDL_Palette * _palette = nullptr;
@ -220,9 +230,11 @@ private:
SoftwareDrawingContext * _drawingContext;
public:
explicit SoftwareDrawingEngine(bool hardwareDisplay)
explicit SoftwareDrawingEngine(IUiContext * uiContext, bool hardwareDisplay)
: _uiContext(uiContext),
_hardwareDisplay(hardwareDisplay)
{
_hardwareDisplay = hardwareDisplay;
UNUSED(_uiContext); // Will be used in due course to retrieve window information
_drawingContext = new SoftwareDrawingContext(this);
#ifdef __ENABLE_LIGHTFX__
_lastLightFXenabled = (gConfigGeneral.enable_light_fx != 0);
@ -244,6 +256,7 @@ public:
void Initialise(SDL_Window * window) override
{
_window = window;
if (_hardwareDisplay)
{
// Try to create the accelerated renderer.
@ -308,7 +321,7 @@ public:
}
}
void SetPalette(SDL_Color * palette) override
void SetPalette(const rct_palette_entry * palette) override
{
if (_sdlRenderer != nullptr)
{
@ -316,7 +329,7 @@ public:
{
for (sint32 i = 0; i < 256; i++)
{
_paletteHWMapped[i] = SDL_MapRGB(_screenTextureFormat, palette[i].r, palette[i].g, palette[i].b);
_paletteHWMapped[i] = SDL_MapRGB(_screenTextureFormat, palette[i].red, palette[i].green, palette[i].blue);
}
#ifdef __ENABLE_LIGHTFX__
@ -333,17 +346,17 @@ public:
}
else
{
SDL_Surface * windowSurface = SDL_GetWindowSurface(gWindow);
if (windowSurface == nullptr)
SDL_Surface * windowSurface = SDL_GetWindowSurface(_window);
if (windowSurface != nullptr && _palette != nullptr)
{
log_fatal("SDL_GetWindowSurface failed %s", SDL_GetError());
exit(1);
}
if (_palette != nullptr && SDL_SetPaletteColors(_palette, palette, 0, 256))
{
log_fatal("SDL_SetPaletteColors failed %s", SDL_GetError());
exit(1);
SDL_Colour colours[256];
for (sint32 i = 0; i < 256; i++) {
colours[i].r = palette[i].red;
colours[i].g = palette[i].green;
colours[i].b = palette[i].blue;
colours[i].a = palette[i].alpha;
}
SDL_SetPaletteColors(_palette, colours, 0, 256);
}
}
}
@ -646,8 +659,8 @@ private:
// Determine region in pixels
uint32 left = Math::Max<uint32>(0, x * _dirtyGrid.BlockWidth);
uint32 top = Math::Max<uint32>(0, y * _dirtyGrid.BlockHeight);
uint32 right = Math::Min((uint32)gScreenWidth, left + (columns * _dirtyGrid.BlockWidth));
uint32 bottom = Math::Min((uint32)gScreenHeight, top + (rows * _dirtyGrid.BlockHeight));
uint32 right = Math::Min(_width, left + (columns * _dirtyGrid.BlockWidth));
uint32 bottom = Math::Min(_height, top + (rows * _dirtyGrid.BlockHeight));
if (right <= left || bottom <= top)
{
return;
@ -681,7 +694,7 @@ private:
// Copy the surface to the window
if (gConfigGeneral.window_scale == 1 || gConfigGeneral.window_scale <= 0)
{
SDL_Surface * windowSurface = SDL_GetWindowSurface(gWindow);
SDL_Surface * windowSurface = SDL_GetWindowSurface(_window);
if (SDL_BlitSurface(_surface, nullptr, windowSurface, nullptr))
{
log_fatal("SDL_BlitSurface %s", SDL_GetError());
@ -699,13 +712,13 @@ private:
// then scale to window size. Without changing to RGBA first, SDL complains
// about blit configurations being incompatible.
if (SDL_BlitScaled(_RGBASurface, nullptr, SDL_GetWindowSurface(gWindow), nullptr))
if (SDL_BlitScaled(_RGBASurface, nullptr, SDL_GetWindowSurface(_window), nullptr))
{
log_fatal("SDL_BlitScaled %s", SDL_GetError());
exit(1);
}
}
if (SDL_UpdateWindowSurface(gWindow))
if (SDL_UpdateWindowSurface(_window))
{
log_fatal("SDL_UpdateWindowSurface %s", SDL_GetError());
exit(1);
@ -726,14 +739,15 @@ private:
}
SDL_RenderCopy(_sdlRenderer, _screenTexture, nullptr, nullptr);
if (gSteamOverlayActive && gConfigGeneral.steam_overlay_pause)
bool isSteamOverlayActive = GetContext()->GetUiContext()->IsSteamOverlayActive();
if (isSteamOverlayActive && gConfigGeneral.steam_overlay_pause)
{
OverlayPreRenderCheck();
}
SDL_RenderPresent(_sdlRenderer);
if (gSteamOverlayActive && gConfigGeneral.steam_overlay_pause)
if (isSteamOverlayActive && gConfigGeneral.steam_overlay_pause)
{
OverlayPostRenderCheck();
}
@ -826,14 +840,14 @@ private:
}
};
IDrawingEngine * DrawingEngineFactory::CreateSoftware()
IDrawingEngine * OpenRCT2::Ui::CreateSoftwareDrawingEngine(IUiContext * uiContext)
{
return new SoftwareDrawingEngine(false);
return new SoftwareDrawingEngine(uiContext, false);
}
IDrawingEngine * DrawingEngineFactory::CreateSoftwareWithHardwareDisplay()
IDrawingEngine * OpenRCT2::Ui::CreateHardwareDisplayDrawingEngine(IUiContext * uiContext)
{
return new SoftwareDrawingEngine(true);
return new SoftwareDrawingEngine(uiContext, true);
}
SoftwareDrawingContext::SoftwareDrawingContext(SoftwareDrawingEngine * engine)

View File

@ -16,7 +16,7 @@
#pragma once
#include "../../../common.h"
#include <openrct2/common.h>
#include "OpenGLAPI.h"
#include "GLSLTypes.h"
#include "TextureCache.h"

View File

@ -16,7 +16,7 @@
#pragma once
#include "../../../common.h"
#include <openrct2/common.h>
#pragma pack(push, 1)

View File

@ -23,7 +23,7 @@
#include <SDL_video.h>
#include "../../../core/Console.hpp"
#include <openrct2/core/Console.hpp>
template <typename T>
static inline bool SetProc(T * func, const char * name)

View File

@ -16,7 +16,7 @@
#pragma once
#include "../../../common.h"
#include <openrct2/common.h>
#if OPENGL_NO_LINK

View File

@ -14,21 +14,32 @@
*****************************************************************************/
#pragma endregion
#ifdef DISABLE_OPENGL
#include "../../IDrawingEngine.h"
IDrawingEngine * DrawingEngineFactory::CreateOpenGL()
{
return nullptr;
}
#else
#ifndef DISABLE_OPENGL
#include <unordered_map>
#include <vector>
#include <SDL_platform.h>
#include <SDL.h>
#include <openrct2/config/Config.h>
#include <openrct2/core/Console.hpp>
#include <openrct2/core/Exception.hpp>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/drawing/IDrawingContext.h>
#include <openrct2/drawing/IDrawingEngine.h>
#include <openrct2/drawing/Rain.h>
#include <openrct2/config/Config.h>
#include <openrct2/ui/UiContext.h>
extern "C"
{
#include <openrct2/interface/screenshot.h>
#include <openrct2/interface/window.h>
#include <openrct2/intro.h>
#include <openrct2/drawing/drawing.h>
}
#include "../DrawingEngines.h"
#include "GLSLTypes.h"
#include "OpenGLAPI.h"
#include "OpenGLFramebuffer.h"
@ -40,23 +51,9 @@ IDrawingEngine * DrawingEngineFactory::CreateOpenGL()
#include "TextureCache.h"
#include "DrawCommands.h"
#include "../../../core/Console.hpp"
#include "../../../core/Exception.hpp"
#include "../../../core/Math.hpp"
#include "../../../core/Memory.hpp"
#include "../../IDrawingContext.h"
#include "../../IDrawingEngine.h"
#include "../../Rain.h"
#include "../../../config/Config.h"
extern "C"
{
#include "../../../config/Config.h"
#include "../../../interface/screenshot.h"
#include "../../../interface/window.h"
#include "../../../intro.h"
#include "../../drawing.h"
}
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
using namespace OpenRCT2::Ui;
struct OpenGLVersion
{
@ -229,8 +226,9 @@ public:
class OpenGLDrawingEngine : public IDrawingEngine
{
private:
SDL_Window * _window = nullptr;
SDL_GLContext _context = nullptr;
IUiContext * const _uiContext = nullptr;
SDL_Window * _window = nullptr;
SDL_GLContext _context = nullptr;
uint32 _width = 0;
uint32 _height = 0;
@ -250,8 +248,10 @@ public:
SDL_Color Palette[256];
vec4f GLPalette[256];
OpenGLDrawingEngine()
OpenGLDrawingEngine(IUiContext * uiContext)
: _uiContext(uiContext)
{
UNUSED(_uiContext); // Will be used in due course to retrieve window information
_drawingContext = new OpenGLDrawingContext(this);
}
@ -305,11 +305,14 @@ public:
_drawingContext->Resize(width, height);
}
void SetPalette(SDL_Color * palette) override
void SetPalette(const rct_palette_entry * palette) override
{
for (sint32 i = 0; i < 256; i++)
{
SDL_Color colour = palette[i];
SDL_Color colour;
colour.r = palette[i].red;
colour.g = palette[i].green;
colour.b = palette[i].blue;
colour.a = i == 0 ? 0 : 255;
Palette[i] = colour;
@ -498,9 +501,9 @@ private:
}
};
IDrawingEngine * DrawingEngineFactory::CreateOpenGL()
IDrawingEngine * OpenRCT2::Ui::CreateOpenGLDrawingEngine(IUiContext * uiContext)
{
return new OpenGLDrawingEngine();
return new OpenGLDrawingEngine(uiContext);
}
OpenGLDrawingContext::OpenGLDrawingContext(OpenGLDrawingEngine * engine)

View File

@ -16,9 +16,9 @@
#ifndef DISABLE_OPENGL
#include "../../../common.h"
#include <openrct2/common.h>
#include <SDL_video.h>
#include "../../../core/Memory.hpp"
#include <openrct2/core/Memory.hpp>
#include "OpenGLFramebuffer.h"
constexpr GLuint BACKBUFFER_ID = 0;

View File

@ -16,7 +16,7 @@
#pragma once
#include "../../../common.h"
#include <openrct2/common.h>
#include "OpenGLAPI.h"
struct SDL_Window;

View File

@ -16,17 +16,17 @@
#ifndef DISABLE_OPENGL
#include "../../../core/Console.hpp"
#include "../../../core/Exception.hpp"
#include "../../../core/FileStream.hpp"
#include "../../../core/Memory.hpp"
#include "../../../core/Path.hpp"
#include "../../../core/String.hpp"
#include <openrct2/core/Console.hpp>
#include <openrct2/core/Exception.hpp>
#include <openrct2/core/FileStream.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/core/Path.hpp>
#include <openrct2/core/String.hpp>
#include "OpenGLShaderProgram.h"
extern "C"
{
#include "../../../platform/platform.h"
#include <openrct2/platform/platform.h>
}
OpenGLShader::OpenGLShader(const char * name, GLenum type)

View File

@ -16,7 +16,7 @@
#pragma once
#include "../../../common.h"
#include <openrct2/common.h>
#include "OpenGLAPI.h"
class OpenGLShader final

View File

@ -16,7 +16,7 @@
#pragma once
#include "../../../common.h"
#include <openrct2/common.h>
#include "OpenGLAPI.h"
class CopyFramebufferShader;

View File

@ -18,12 +18,12 @@
#include <vector>
#include <stdexcept>
#include "../../../core/Memory.hpp"
#include <openrct2/core/Memory.hpp>
#include "TextureCache.h"
extern "C"
{
#include "../../drawing.h"
#include <openrct2/drawing/drawing.h>
}
TextureCache::~TextureCache()

View File

@ -20,7 +20,7 @@
#include <unordered_map>
#include <vector>
#include <SDL_pixels.h>
#include "../../../common.h"
#include <openrct2/common.h>
#include "OpenGLAPI.h"
#include "GLSLTypes.h"

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)'==''">..\..\</SolutionDir>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="audio\AudioChannel.cpp" />
<ClCompile Include="audio\AudioContext.cpp" />
<ClCompile Include="audio\AudioMixer.cpp" />
<ClCompile Include="audio\FileAudioSource.cpp" />
<ClCompile Include="audio\MemoryAudioSource.cpp" />
<ClCompile Include="CursorData.cpp" />
<ClCompile Include="CursorRepository.cpp" />
<ClCompile Include="drawing\engines\opengl\CopyFramebufferShader.cpp" />
<ClCompile Include="drawing\engines\opengl\DrawImageShader.cpp" />
<ClCompile Include="drawing\engines\opengl\DrawLineShader.cpp" />
<ClCompile Include="drawing\engines\opengl\FillRectShader.cpp" />
<ClCompile Include="drawing\engines\opengl\OpenGLAPI.cpp" />
<ClCompile Include="drawing\engines\opengl\OpenGLDrawingEngine.cpp" />
<ClCompile Include="drawing\engines\opengl\OpenGLFramebuffer.cpp" />
<ClCompile Include="drawing\engines\opengl\OpenGLShaderProgram.cpp" />
<ClCompile Include="drawing\engines\opengl\SwapFramebuffer.cpp" />
<ClCompile Include="drawing\engines\opengl\TextureCache.cpp" />
<ClCompile Include="drawing\engines\SoftwareDrawingEngine.cpp" />
<ClCompile Include="TextComposition.cpp" />
<ClCompile Include="Ui.cpp" />
<ClCompile Include="UiContext.cpp" />
<ClCompile Include="UiContext.Linux.cpp" />
<ClCompile Include="UiContext.Win32.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="audio\AudioContext.h" />
<ClInclude Include="audio\AudioFormat.h" />
<ClInclude Include="CursorRepository.h" />
<ClInclude Include="drawing\engines\DrawingEngines.h" />
<ClInclude Include="drawing\engines\opengl\CopyFramebufferShader.h" />
<ClInclude Include="drawing\engines\opengl\DrawCommands.h" />
<ClInclude Include="drawing\engines\opengl\DrawImageShader.h" />
<ClInclude Include="drawing\engines\opengl\DrawLineShader.h" />
<ClInclude Include="drawing\engines\opengl\FillRectShader.h" />
<ClInclude Include="drawing\engines\opengl\GLSLTypes.h" />
<ClInclude Include="drawing\engines\opengl\OpenGLAPI.h" />
<ClInclude Include="drawing\engines\opengl\OpenGLFramebuffer.h" />
<ClInclude Include="drawing\engines\opengl\OpenGLShaderProgram.h" />
<ClInclude Include="drawing\engines\opengl\SwapFramebuffer.h" />
<ClInclude Include="drawing\engines\opengl\TextureCache.h" />
<ClInclude Include="SDLException.h" />
<ClInclude Include="TextComposition.h" />
<ClInclude Include="UiContext.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{8DD8AB7D-2EA6-44E3-8265-BAF08E832951}</ProjectGuid>
<RootNamespace>openrct2-ui</RootNamespace>
<ProjectName>libopenrct2ui</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<!-- Debug|Win32 is reserved for RCT2 interop builds, this means it has to be a DLL -->
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="..\..\openrct2.common.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>openrct2</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>__ENABLE_LIGHTFX__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Breakpad)'=='true'">USE_BREAKPAD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<ObjectFileName>$(IntDir)\%(RelativeDir)</ObjectFileName>
<AdditionalOptions>$(OPENRCT2_CL_ADDITIONALOPTIONS) %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Lib>
<TargetMachine Condition="'$(Platform)'=='Win32'">MachineX86</TargetMachine>
<TargetMachine Condition="'$(Platform)'=='x64'">MachineX64</TargetMachine>
</Lib>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

327
src/openrct2/CMakeLists.txt Normal file
View File

@ -0,0 +1,327 @@
# CMAKE project for libopenrct2 (core OpenRCT2 component)
cmake_minimum_required(VERSION 2.6)
if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)
message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt")
endif ()
# CMake dependencies
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
include(FindPkgConfig)
# Options
option(STATIC "Create a static build.")
option(USE_MMAP "Use mmap to try loading rct2's data segment into memory.")
option(DISABLE_RCT2 "Build a standalone version, without using code and data segments from vanilla. On by default." ON)
option(DISABLE_HTTP_TWITCH "Disable HTTP and Twitch support.")
option(DISABLE_NETWORK "Disable multiplayer functionality. Mainly for testing.")
option(DISABLE_TTF "Disable support for TTF provided by SDL2_ttf.")
option(ENABLE_LIGHTFX "Enable lighting effects." ON)
if (NOT DISABLE_RCT2)
if (WIN32)
message(FATAL_ERROR "DISABLE_RCT2 not supported for Windows")
endif ()
if (NOT FORCE32)
message(FATAL_ERROR "DISABLE_RCT2 requires FORCE32")
endif ()
endif ()
# Needed for linking with non-broken OpenSSL on Apple platforms
if (APPLE)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/openssl/lib/pkgconfig")
endif ()
# Third party libraries
PKG_CHECK_MODULES(JANSSON REQUIRED jansson>=2.5)
PKG_CHECK_MODULES(LIBZIP REQUIRED libzip>=1.0)
PKG_CHECK_MODULES(ZLIB REQUIRED zlib)
PKG_CHECK_MODULES(PNG libpng>=1.6)
if (NOT PNG_FOUND)
PKG_CHECK_MODULES(PNG libpng16)
if (NOT PNG_FOUND)
PKG_CHECK_MODULES(PNG libpng>=1.2)
if (NOT PNG_FOUND)
PKG_CHECK_MODULES(PNG REQUIRED libpng12)
endif ()
endif ()
endif ()
# Third party libraries (optional)
if (NOT DISABLE_HTTP_TWITCH OR NOT DISABLE_NETWORK)
PKG_CHECK_MODULES(LIBCURL REQUIRED libcurl)
endif ()
if (NOT DISABLE_NETWORK)
find_package(OpenSSL 1.0.0 REQUIRED)
endif ()
# Third party libraries (which we want to eventually remove from libopenrct2)
PKG_CHECK_MODULES(SDL2 REQUIRED sdl2)
PKG_CHECK_MODULES(SPEEX REQUIRED speexdsp)
if (NOT DISABLE_TTF)
if (STATIC)
# FreeType is required by SDL2_ttf, but not wired up properly in package
PKG_CHECK_MODULES(FREETYPE REQUIRED freetype2)
endif ()
if (UNIX AND NOT APPLE)
PKG_CHECK_MODULES(FONTCONFIG REQUIRED fontconfig)
endif ()
PKG_CHECK_MODULES(SDL2_TTF REQUIRED SDL2_ttf)
endif ()
# Sources
file(GLOB_RECURSE OPENRCT2_CORE_SOURCES "${CMAKE_CURRENT_LIST_DIR}/*.c"
"${CMAKE_CURRENT_LIST_DIR}/*.cpp"
"${CMAKE_CURRENT_LIST_DIR}/*.h"
"${CMAKE_CURRENT_LIST_DIR}/*.hpp")
if (APPLE)
file(GLOB_RECURSE OPENRCT2_CORE_MM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/*.m")
set_source_files_properties(${OPENRCT2_CORE_MM_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c -fmodules")
endif ()
# Handle creating the rct2 text and data files on macOS and Linux
# See details in src/openrct2/rct2/interop.c:rct2_interop_setup_segment for how the values
# were derived.
if (NOT DISABLE_RCT2 AND UNIX)
set(OPENRCT2_EXE "${ROOT_DIR}/openrct2.exe")
add_custom_command(
OUTPUT openrct2_text
COMMAND dd if="${OPENRCT2_EXE}" of="${CMAKE_BINARY_DIR}/openrct2_text" bs=4096 skip=1 count=1187
DEPENDS ${OPENRCT2_EXE}
)
add_custom_command(
OUTPUT openrct2_data
COMMAND dd if="${OPENRCT2_EXE}" of="${CMAKE_BINARY_DIR}/openrct2_data" bs=4096 skip=1188 count=318
COMMAND dd if=/dev/zero of="${CMAKE_BINARY_DIR}/openrct2_data" bs=4096 seek=318 count=2630 conv=notrunc
COMMAND dd if="${OPENRCT2_EXE}" of="${CMAKE_BINARY_DIR}/openrct2_data" bs=4096 skip=1506 seek=2948 count=1 conv=notrunc
DEPENDS ${OPENRCT2_EXE}
)
add_custom_target(segfiles DEPENDS openrct2_text openrct2_data)
if (NOT USE_MMAP)
set(OBJ_FORMAT "elf32-i386")
set(LINKER_SCRIPT "ld_script_i386.xc")
if (APPLE)
set(RCT2_SEGMENT_LINKER_FLAGS "-sectcreate rct2_text __text ${CMAKE_BINARY_DIR}/openrct2_text -sectcreate rct2_data __data ${CMAKE_BINARY_DIR}/openrct2_data -segaddr rct2_data 0x8a4000 -segprot rct2_data rwx rwx -segaddr rct2_text 0x401000 -segprot rct2_text rwx rwx -segaddr __TEXT 0x2000000 -read_only_relocs suppress")
else ()
# For Linux we have to use objcopy to wrap regular binaries into a linkable
# format. We use specific section names which are then referenced in a
# bespoke linker script so they can be placed at predefined VMAs.
add_custom_command(
OUTPUT openrct2_text_section.o
COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_text openrct2_text_section.o --rename-section .data=.rct2_text,contents,alloc,load,readonly,code
DEPENDS segfiles
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_command(
OUTPUT openrct2_data_section.o
COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_data openrct2_data_section.o --rename-section .data=.rct2_data,contents,alloc,load,readonly,data
DEPENDS segfiles
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(linkable_sections DEPENDS openrct2_text_section.o openrct2_data_section.o)
set_source_files_properties(
openrct2_text_section.o openrct2_data_section.o
PROPERTIES
EXTERNAL_OBJECT true
GENERATED true
)
# can't use GLOB here, as the files don't exist yet at cmake-time
set(RCT2_SECTIONS "${CMAKE_BINARY_DIR}/openrct2_data_section.o" "${CMAKE_BINARY_DIR}/openrct2_text_section.o")
set(RCT2_SEGMENT_LINKER_FLAGS "-Wl,-T,\"${ROOT_DIR}/distribution/linux/${LINKER_SCRIPT}\"")
endif ()
endif ()
elseif (USE_MMAP)
# No dd here, can't extract data segment
message(WARNING "Sorry, your platform is not supported, you have to extract data segment manually")
endif ()
# Outputs
set(PROJECT libopenrct2)
project(${PROJECT})
add_library(${PROJECT} SHARED ${OPENRCT2_CORE_SOURCES} ${OPENRCT2_CORE_MM_SOURCES} ${RCT2_SECTIONS})
set_target_properties(${PROJECT} PROPERTIES PREFIX "")
set_target_properties(${PROJECT} PROPERTIES COMPILE_FLAGS "-Wundef")
if (NOT DISABLE_RCT2)
add_dependencies(${PROJECT} segfiles)
if (NOT USE_MMAP)
if (NOT APPLE)
add_dependencies(${PROJECT} linkable_sections)
endif ()
set_target_properties(${PROJECT} PROPERTIES LINK_FLAGS ${RCT2_SEGMENT_LINKER_FLAGS})
endif ()
endif ()
# Libraries
if (STATIC)
target_link_libraries(${PROJECT} ${SDL2_STATIC_LIBRARIES}
${JANSSON_STATIC_LIBRARIES}
${SPEEX_STATIC_LIBRARIES}
${PNG_STATIC_LIBRARIES}
${ZLIB_STATIC_LIBRARIES}
${LIBZIP_STATIC_LIBRARIES})
else ()
target_link_libraries(${PROJECT} ${SDL2_LIBRARIES}
${JANSSON_LIBRARIES}
${SPEEX_LIBRARIES}
${PNG_LIBRARIES}
${ZLIB_LIBRARIES}
${LIBZIP_LIBRARIES})
endif ()
if (UNIX AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "BSD")
# Include libdl for dlopen
target_link_libraries(${PROJECT} dl)
endif ()
if (NOT DISABLE_NETWORK)
if (WIN32)
target_link_libraries(${PROJECT} ws2_32)
endif ()
if (STATIC)
target_link_libraries(${PROJECT} ${LIBCURL_STATIC_LIBRARIES}
${SSL_STATIC_LIBRARIES})
else ()
target_link_libraries(${PROJECT} ${LIBCURL_LIBRARIES}
${OPENSSL_LIBRARIES})
endif ()
endif ()
if (NOT DISABLE_TTF)
if (STATIC)
target_link_libraries(${PROJECT} ${FREETYPE_STATIC_LIBRARIES}
${SDL2_TTF_STATIC_LIBRARIES})
if (UNIX AND NOT APPLE)
target_link_libraries(${PROJECT} ${FONTCONFIG_STATIC_LIBRARIES})
endif ()
else ()
target_link_libraries(${PROJECT} ${SDL2_TTF_LIBRARIES})
if (UNIX AND NOT APPLE)
target_link_libraries(${PROJECT} ${FONTCONFIG_LIBRARIES})
endif ()
endif ()
endif ()
if (APPLE OR STATIC OR ${CMAKE_SYSTEM_NAME} MATCHES "BSD")
find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c)
target_link_libraries(${PROJECT} ${ICONV_LIBRARIES})
endif()
# Includes
target_include_directories(${PROJECT} SYSTEM PRIVATE ${LIBZIP_INCLUDE_DIRS})
target_include_directories(${PROJECT} PRIVATE ${SDL2_INCLUDE_DIRS}
${JANSSON_INCLUDE_DIRS}
${SPEEX_INCLUDE_DIRS}
${PNG_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS})
if (NOT DISABLE_HTTP_TWITCH OR NOT DISABLE_NETWORK)
target_include_directories(${PROJECT} PRIVATE ${LIBCURL_INCLUDE_DIRS})
endif ()
if (NOT DISABLE_NETWORK)
target_include_directories(${PROJECT} PUBLIC ${OPENSSL_INCLUDE_DIR})
endif ()
if (NOT DISABLE_TTF AND UNIX AND NOT APPLE)
target_include_directories(${PROJECT} PRIVATE ${FONTCONFIG_INCLUDE_DIRS})
endif ()
# macOS builds fail on the use of tmpnam otherwise (#4959)
if(APPLE)
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -Wno-error=deprecated-declarations -Wno-error=objc-method-access")
endif()
# Compiler flags
set(DEBUG_LEVEL 0 CACHE STRING "Select debug level for compilation. Use value in range 03.")
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -fstrict-aliasing -Werror -Wundef -Wmissing-declarations -Winit-self -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-missing-braces ")
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -Wno-comment -Wshadow -Wmissing-declarations -Wnonnull")
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -DDEBUG=${DEBUG_LEVEL}")
# On mingw all code is already PIC, this will avoid compiler error on redefining this option
if (NOT MINGW)
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -fPIC")
endif ()
if (APPLE AND NOT USE_MMAP)
set(PIE_FLAG "-fno-pie")
else ()
set(PIE_FLAG "-fpie")
endif ()
if (FORCE32)
set(TARGET_M "-m32")
endif ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu11 ${COMMON_COMPILE_OPTIONS} ${TARGET_M} -Wimplicit")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++14 ${COMMON_COMPILE_OPTIONS} ${TARGET_M}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TARGET_M}")
function (ADD_CHECK_C_COMPILER_FLAG _CFLAGS _CACHE_VAR _FLAG)
CHECK_C_COMPILER_FLAG("${_FLAG}" "${_CACHE_VAR}")
if (${_CACHE_VAR})
set(${_CFLAGS} "${${_CFLAGS}} ${_FLAG}" PARENT_SCOPE)
else ()
message(STATUS "Unsupported CFLAG: ${_FLAG}")
endif ()
endfunction ()
function (ADD_CHECK_CXX_COMPILER_FLAG _CXXFLAGS _CACHE_VAR _FLAG)
CHECK_CXX_COMPILER_FLAG("${_FLAG}" "${_CACHE_VAR}")
if (${_CACHE_VAR})
set(${_CXXFLAGS} "${${_CXXFLAGS}} ${_FLAG}" PARENT_SCOPE)
else ()
message(STATUS "Unsupported CXXFLAG: ${_FLAG}")
endif ()
endfunction ()
# Launchpad turns on -Wdate-time for compilers that support it, this shouldn't break our build
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_WRITE_STRINGS -Wno-error=date-time)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_WRITE_STRINGS -Wno-error=date-time)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_NULL_DEREFERENCE -Wnull-dereference)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_FINAL_TYPES -Wsuggest-final-types)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_FINAL_METHODS -Wsuggest-final-methods)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_OVERRIDE -Wsuggest-override)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_DUPLICATED_COND -Wduplicated-cond)
if (MINGW)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_SUGGEST_OVERRIDE -Wno-error=redundant-decls)
endif ()
# Items below are not supported by ICC
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_REDUNDANT_DECLS -Wredundant-decls)
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_IGNORED_QUALIFIERS -Wignored-qualifiers)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_REDUNDANT_DECLS -Wredundant-decls)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_IGNORED_QUALIFIERS -Wignored-qualifiers)
# -Wstrict-overflow is only active when -fstrict-overflow is enabled, but -fstrict-overflow
# is enabled on -O2, -O3, -Os. This should help catch bugs locally before they reach Travis
# As of 2a435bf -Wstrict-overflow=1 passes, but higher values do not.
set(COMMON_COMPILE_OPTIONS "${COMMON_COMPILE_OPTIONS} -fstrict-overflow")
ADD_CHECK_C_COMPILER_FLAG(CMAKE_C_FLAGS C_WARN_STRICT_OVERFLOW -Wstrict-overflow=1)
ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS CXX_WARN_STRICT_OVERFLOW -Wstrict-overflow=1)
# Defines
if (USE_MMAP)
add_definitions(-DUSE_MMAP)
endif ()
if (DISABLE_NETWORK)
add_definitions(-DDISABLE_NETWORK)
endif ()
if (DISABLE_HTTP_TWITCH)
add_definitions(-DDISABLE_HTTP)
add_definitions(-DDISABLE_TWITCH)
endif ()
if (DISABLE_TTF)
add_definitions(-DNO_TTF)
endif ()
if (ENABLE_LIGHTFX)
add_definitions(-D__ENABLE_LIGHTFX__)
endif ()
if (DISABLE_RCT2)
add_definitions(-DNO_RCT2)
endif ()
if (NOT DISABLE_RCT2)
# Disable optimizations for addresses.c for all compilers, to allow optimized
# builds without need for -fno-omit-frame-pointer
set_source_files_properties(${CMAKE_CURRENT_LIST_DIR}/rct2/addresses.c PROPERTIES COMPILE_FLAGS -O0)
endif ()

702
src/openrct2/Context.cpp Normal file
View File

@ -0,0 +1,702 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <exception>
#include <memory>
#include <string>
#include "Context.h"
#include "ui/UiContext.h"
#include "core/Console.hpp"
#include "core/File.h"
#include "core/FileStream.hpp"
#include "core/Guard.hpp"
#include "core/String.hpp"
#include "FileClassifier.h"
#include "network/network.h"
#include "object/ObjectRepository.h"
#include "OpenRCT2.h"
#include "ParkImporter.h"
#include "platform/crash.h"
#include "PlatformEnvironment.h"
#include "ride/TrackDesignRepository.h"
#include "scenario/ScenarioRepository.h"
#include "title/TitleScreen.h"
#include "title/TitleSequenceManager.h"
#include "Version.h"
extern "C"
{
#include "audio/audio.h"
#include "config/Config.h"
#include "editor.h"
#include "game.h"
#include "interface/chat.h"
#include "interface/themes.h"
#include "intro.h"
#include "localisation/localisation.h"
#include "network/http.h"
#include "object_list.h"
#include "rct1.h"
#include "rct2.h"
#include "rct2/interop.h"
}
using namespace OpenRCT2;
using namespace OpenRCT2::Audio;
using namespace OpenRCT2::Ui;
namespace OpenRCT2
{
class Context : public IContext
{
private:
// The game update inverval in milliseconds, (1000 / 40fps) = 25ms
constexpr static uint32 UPDATE_TIME_MS = 25;
// Dependencies
IAudioContext * const _audioContext = nullptr;
IUiContext * const _uiContext = nullptr;
// Services
IPlatformEnvironment * _env = nullptr;
IObjectRepository * _objectRepository = nullptr;
ITrackDesignRepository * _trackDesignRepository = nullptr;
IScenarioRepository * _scenarioRepository = nullptr;
bool _isWindowMinimised = false;
uint32 _lastTick = 0;
uint32 _uncapTick = 0;
/** If set, will end the OpenRCT2 game loop. Intentially private to this module so that the flag can not be set back to false. */
bool _finished = false;
public:
// Singleton of Context.
// Remove this when GetContext() is no longer called so that
// multiple instances can be created in parallel
static Context * Instance;
public:
Context(IAudioContext * audioContext, IUiContext * uiContext)
: _audioContext(audioContext),
_uiContext(uiContext)
{
Instance = this;
}
~Context() override
{
network_close();
http_dispose();
language_close_all();
rct2_dispose();
config_release();
#ifndef DISABLE_NETWORK
EVP_MD_CTX_destroy(gHashCTX);
#endif // DISABLE_NETWORK
rct2_interop_dispose();
platform_free();
Instance = nullptr;
}
IAudioContext * GetAudioContext() override
{
return _audioContext;
}
IUiContext * GetUiContext() override
{
return _uiContext;
}
sint32 RunOpenRCT2(int argc, char * * argv) override
{
Initialise();
Launch();
return gExitCode;
}
/**
* Causes the OpenRCT2 game loop to finish.
*/
void Finish() override
{
_finished = true;
}
private:
bool Initialise()
{
#ifndef DISABLE_NETWORK
gHashCTX = EVP_MD_CTX_create();
Guard::Assert(gHashCTX != nullptr, "EVP_MD_CTX_create failed");
#endif // DISABLE_NETWORK
crash_init();
// Sets up the environment OpenRCT2 is running in, e.g. directory paths
_env = SetupEnvironment();
if (_env == nullptr)
{
return false;
}
if (!rct2_interop_setup_segment())
{
log_fatal("Unable to load RCT2 data sector");
return false;
}
if (gConfigGeneral.last_run_version != nullptr && String::Equals(gConfigGeneral.last_run_version, OPENRCT2_VERSION))
{
gOpenRCT2ShowChangelog = false;
}
else
{
gOpenRCT2ShowChangelog = true;
gConfigGeneral.last_run_version = String::Duplicate(OPENRCT2_VERSION);
config_save_default();
}
if (!gOpenRCT2Headless)
{
GetContext()->GetUiContext()->CreateWindow();
}
// TODO add configuration option to allow multiple instances
// if (!gOpenRCT2Headless && !platform_lock_single_instance()) {
// log_fatal("OpenRCT2 is already running.");
// return false;
// }
_objectRepository = CreateObjectRepository(_env);
_trackDesignRepository = CreateTrackDesignRepository(_env);
_scenarioRepository = CreateScenarioRepository(_env);
if (!language_open(gConfigGeneral.language))
{
log_error("Failed to open configured language...");
if (!language_open(LANGUAGE_ENGLISH_UK))
{
log_fatal("Failed to open fallback language...");
return false;
}
}
// TODO Ideally we want to delay this until we show the title so that we can
// still open the game window and draw a progress screen for the creation
// of the object cache.
_objectRepository->LoadOrConstruct();
// TODO Like objects, this can take a while if there are a lot of track designs
// its also really something really we might want to do in the background
// as its not required until the player wants to place a new ride.
_trackDesignRepository->Scan();
_scenarioRepository->Scan();
TitleSequenceManager::Scan();
if (!gOpenRCT2Headless)
{
audio_init();
audio_populate_devices();
}
http_init();
theme_manager_initialise();
rct2_interop_setup_hooks();
if (!rct2_init())
{
return false;
}
chat_init();
rct2_copy_original_user_files_over();
return true;
}
IPlatformEnvironment * SetupEnvironment()
{
utf8 userPath[MAX_PATH];
platform_resolve_openrct_data_path();
platform_resolve_user_data_path();
platform_get_user_directory(userPath, NULL, sizeof(userPath));
if (!platform_ensure_directory_exists(userPath))
{
Console::Error::WriteLine("Could not create user directory (do you have write access to your documents folder?)");
return nullptr;
}
platform_get_exe_path(gExePath, sizeof(gExePath));
log_verbose("Setting exe path to %s", gExePath);
config_set_defaults();
if (!config_open_default())
{
if (!config_find_or_browse_install_directory())
{
gConfigGeneral.last_run_version = String::Duplicate(OPENRCT2_VERSION);
config_save_default();
utf8 path[MAX_PATH];
config_get_default_path(path, sizeof(path));
Console::Error::WriteLine("An RCT2 install directory must be specified! Please edit \"game_path\" in %s.", path);
return nullptr;
}
config_save_default();
}
if (!rct2_init_directories())
{
return nullptr;
}
if (!rct2_startup_checks())
{
return nullptr;
}
utf8 path[260];
std::string basePaths[4];
basePaths[(size_t)DIRBASE::RCT1] = String::ToStd(gConfigGeneral.rct1_path);
basePaths[(size_t)DIRBASE::RCT2] = String::ToStd(gConfigGeneral.rct2_path);
platform_get_openrct_data_path(path, sizeof(path));
basePaths[(size_t)DIRBASE::OPENRCT2] = std::string(path);
platform_get_user_directory(path, nullptr, sizeof(path));
basePaths[(size_t)DIRBASE::USER] = std::string(path);
IPlatformEnvironment * env = CreatePlatformEnvironment(basePaths);
return env;
}
/**
* Launches the game, after command line arguments have been parsed and processed.
*/
void Launch()
{
gIntroState = INTRO_STATE_NONE;
if ((gOpenRCT2StartupAction == STARTUP_ACTION_TITLE) && gConfigGeneral.play_intro)
{
gOpenRCT2StartupAction = STARTUP_ACTION_INTRO;
}
switch (gOpenRCT2StartupAction) {
case STARTUP_ACTION_INTRO:
gIntroState = INTRO_STATE_PUBLISHER_BEGIN;
title_load();
break;
case STARTUP_ACTION_TITLE:
title_load();
break;
case STARTUP_ACTION_OPEN:
{
bool parkLoaded = false;
// A path that includes "://" is illegal with all common filesystems, so it is almost certainly a URL
// This way all cURL supported protocols, like http, ftp, scp and smb are automatically handled
if (strstr(gOpenRCT2StartupActionPath, "://") != nullptr)
{
#ifndef DISABLE_HTTP
// Download park and open it using its temporary filename
char tmpPath[MAX_PATH];
if (!http_download_park(gOpenRCT2StartupActionPath, tmpPath))
{
title_load();
break;
}
parkLoaded = OpenParkAutoDetectFormat(tmpPath);
#endif
}
else
{
parkLoaded = rct2_open_file(gOpenRCT2StartupActionPath);
}
if (!parkLoaded)
{
Console::Error::WriteLine("Failed to load '%s'", gOpenRCT2StartupActionPath);
title_load();
break;
}
gScreenFlags = SCREEN_FLAGS_PLAYING;
#ifndef DISABLE_NETWORK
if (gNetworkStart == NETWORK_MODE_SERVER)
{
if (gNetworkStartPort == 0)
{
gNetworkStartPort = gConfigNetwork.default_port;
}
if (String::IsNullOrEmpty(gCustomPassword))
{
network_set_password(gConfigNetwork.default_password);
}
else
{
network_set_password(gCustomPassword);
}
network_begin_server(gNetworkStartPort);
}
#endif // DISABLE_NETWORK
break;
}
case STARTUP_ACTION_EDIT:
if (String::SizeOf(gOpenRCT2StartupActionPath) == 0)
{
editor_load();
}
else if (!editor_load_landscape(gOpenRCT2StartupActionPath))
{
title_load();
}
break;
}
#ifndef DISABLE_NETWORK
if (gNetworkStart == NETWORK_MODE_CLIENT)
{
if (gNetworkStartPort == 0)
{
gNetworkStartPort = gConfigNetwork.default_port;
}
network_begin_client(gNetworkStartHost, gNetworkStartPort);
}
#endif // DISABLE_NETWORK
RunGameLoop();
}
/**
* Run the main game loop until the finished flag is set.
*/
void RunGameLoop()
{
log_verbose("begin openrct2 loop");
_finished = false;
do
{
if (ShouldRunVariableFrame())
{
RunVariableFrame();
}
else
{
RunFixedFrame();
}
} while (!_finished);
log_verbose("finish openrct2 loop");
}
bool ShouldRunVariableFrame()
{
if (!gConfigGeneral.uncap_fps) return false;
if (gGameSpeed > 4) return false;
if (gOpenRCT2Headless) return false;
if (_uiContext->IsMinimised()) return false;
return true;
}
void RunFixedFrame()
{
_uncapTick = 0;
uint32 currentTick = platform_get_ticks();
uint32 ticksElapsed = currentTick - _lastTick;
if (ticksElapsed < UPDATE_TIME_MS)
{
platform_sleep(UPDATE_TIME_MS - ticksElapsed);
_lastTick += UPDATE_TIME_MS;
}
else
{
_lastTick = currentTick;
}
GetContext()->GetUiContext()->ProcessMessages();
rct2_update();
if (!_isWindowMinimised)
{
platform_draw();
}
}
void RunVariableFrame()
{
uint32 currentTick = platform_get_ticks();
if (_uncapTick == 0)
{
_uncapTick = currentTick;
sprite_position_tween_reset();
}
// Limit number of updates per loop (any long pauses or debugging can make this update for a very long time)
if (currentTick - _uncapTick > UPDATE_TIME_MS * 60)
{
_uncapTick = currentTick - UPDATE_TIME_MS - 1;
}
GetContext()->GetUiContext()->ProcessMessages();
while (_uncapTick <= currentTick && currentTick - _uncapTick > UPDATE_TIME_MS)
{
// Get the original position of each sprite
sprite_position_tween_store_a();
// Update the game so the sprite positions update
rct2_update();
// Get the next position of each sprite
sprite_position_tween_store_b();
_uncapTick += UPDATE_TIME_MS;
}
// Tween the position of each sprite from the last position to the new position based on the time between the last
// tick and the next tick.
float nudge = 1 - ((float)(currentTick - _uncapTick) / UPDATE_TIME_MS);
sprite_position_tween_all(nudge);
platform_draw();
sprite_position_tween_restore();
}
bool OpenParkAutoDetectFormat(const utf8 * path)
{
ClassifiedFile info;
if (TryClassifyFile(path, &info))
{
if (info.Type == FILE_TYPE::SAVED_GAME ||
info.Type == FILE_TYPE::SCENARIO)
{
std::unique_ptr<IParkImporter> parkImporter;
if (info.Version <= 2)
{
parkImporter.reset(ParkImporter::CreateS4());
}
else
{
parkImporter.reset(ParkImporter::CreateS6());
}
if (info.Type == FILE_TYPE::SAVED_GAME)
{
try
{
parkImporter->LoadSavedGame(path);
parkImporter->Import();
game_fix_save_vars();
sprite_position_tween_reset();
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
game_load_init();
return true;
}
catch (const Exception &)
{
Console::Error::WriteLine("Error loading saved game.");
}
}
else
{
try
{
parkImporter->LoadScenario(path);
parkImporter->Import();
game_fix_save_vars();
sprite_position_tween_reset();
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
scenario_begin();
return true;
}
catch (const Exception &)
{
Console::Error::WriteLine("Error loading scenario.");
}
}
}
else
{
Console::Error::WriteLine("Invalid file type.");
}
}
else
{
Console::Error::WriteLine("Unable to detect file type.");
}
return false;
}
};
Context * Context::Instance = nullptr;
IContext * CreateContext()
{
return new Context(nullptr, nullptr);
}
IContext * CreateContext(Audio::IAudioContext * audioContext, IUiContext * uiContext)
{
return new Context(audioContext, uiContext);
}
IContext * GetContext()
{
return Context::Instance;
}
}
extern "C"
{
void openrct2_write_full_version_info(utf8 * buffer, size_t bufferSize)
{
String::Set(buffer, bufferSize, gVersionInfoFull);
}
void openrct2_finish()
{
GetContext()->Finish();
}
void context_setcurrentcursor(sint32 cursor)
{
GetContext()->GetUiContext()->SetCursor((CURSOR_ID)cursor);
}
void context_hide_cursor()
{
GetContext()->GetUiContext()->SetCursorVisible(false);
}
void context_show_cursor()
{
GetContext()->GetUiContext()->SetCursorVisible(true);
}
void context_get_cursor_position(sint32 * x, sint32 * y)
{
GetContext()->GetUiContext()->GetCursorPosition(x, y);
}
void context_get_cursor_position_scaled(sint32 * x, sint32 * y)
{
context_get_cursor_position(x, y);
// Compensate for window scaling.
*x = (sint32)ceilf(*x / gConfigGeneral.window_scale);
*y = (sint32)ceilf(*y / gConfigGeneral.window_scale);
}
void context_set_cursor_position(sint32 x, sint32 y)
{
GetContext()->GetUiContext()->SetCursorPosition(x, y);
}
const CursorState * context_get_cursor_state()
{
return GetContext()->GetUiContext()->GetCursorState();
}
const uint8 * context_get_keys_state()
{
return GetContext()->GetUiContext()->GetKeysState();
}
const uint8 * context_get_keys_pressed()
{
return GetContext()->GetUiContext()->GetKeysPressed();
}
TextInputSession * context_start_text_input(utf8 * buffer, size_t maxLength)
{
return GetContext()->GetUiContext()->StartTextInput(buffer, maxLength);
}
void context_stop_text_input()
{
GetContext()->GetUiContext()->StopTextInput();
}
bool context_is_input_active()
{
return GetContext()->GetUiContext()->IsTextInputActive();
}
void context_trigger_resize()
{
return GetContext()->GetUiContext()->TriggerResize();
}
void context_set_fullscreen_mode(sint32 mode)
{
return GetContext()->GetUiContext()->SetFullscreenMode((FULLSCREEN_MODE)mode);
}
sint32 context_get_resolutions(Resolution * * outResolutions)
{
auto resolutions = GetContext()->GetUiContext()->GetFullscreenResolutions();
sint32 count = (sint32)resolutions.size();
*outResolutions = Memory::AllocateArray<Resolution>(count);
Memory::CopyArray(*outResolutions, resolutions.data(), count);
return count;
}
sint32 context_get_width()
{
return GetContext()->GetUiContext()->GetWidth();
}
sint32 context_get_height()
{
return GetContext()->GetUiContext()->GetHeight();
}
bool context_has_focus()
{
return GetContext()->GetUiContext()->HasFocus();
}
void context_set_cursor_trap(bool value)
{
GetContext()->GetUiContext()->SetCursorTrap(value);
}
bool platform_open_common_file_dialog(utf8 * outFilename, file_dialog_desc * desc, size_t outSize)
{
FileDialogDesc desc2;
desc2.Type = (FILE_DIALOG_TYPE)desc->type;
desc2.Title = String::ToStd(desc->title);
desc2.InitialDirectory = String::ToStd(desc->initial_directory);
desc2.DefaultFilename = String::ToStd(desc->default_filename);
for (const auto &filter : desc->filters)
{
if (filter.name != nullptr)
{
desc2.Filters.push_back({ String::ToStd(filter.name), String::ToStd(filter.pattern) });
}
}
std::string result = GetContext()->GetUiContext()->ShowFileDialog(desc2);
String::Set(outFilename, outSize, result.c_str());
return !result.empty();
}
utf8 * platform_open_directory_browser(const utf8 * title)
{
std::string result = GetContext()->GetUiContext()->ShowDirectoryDialog(title);
return String::Duplicate(result.c_str());
}
}

119
src/openrct2/Context.h Normal file
View File

@ -0,0 +1,119 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include "common.h"
typedef struct CursorState
{
sint32 x, y;
uint8 left, middle, right, any;
sint32 wheel;
sint32 old;
bool touch, touchIsDouble;
uint32 touchDownTimestamp;
} CursorState;
typedef struct TextInputSession
{
utf8 * Buffer; // UTF-8 stream
size_t BufferSize; // Maximum number of bytes (excluding null terminator)
size_t Size; // Number of bytes (excluding null terminator)
size_t Length; // Number of codepoints
size_t SelectionStart; // Selection start, in bytes
size_t SelectionSize; // Selection length in bytes
const utf8 * ImeBuffer; // IME UTF-8 stream
} TextInputSession;
struct Resolution
{
sint32 Width;
sint32 Height;
};
enum
{
CURSOR_UP = 0,
CURSOR_DOWN = 1,
CURSOR_CHANGED = 2,
CURSOR_RELEASED = CURSOR_UP | CURSOR_CHANGED,
CURSOR_PRESSED = CURSOR_DOWN | CURSOR_CHANGED,
};
#ifdef __cplusplus
namespace OpenRCT2
{
namespace Audio
{
interface IAudioContext;
}
namespace Ui
{
interface IUiContext;
}
/**
* Represents an instance of OpenRCT2 and can be used to get various services.
*/
interface IContext
{
virtual ~IContext() = default;
virtual Audio::IAudioContext * GetAudioContext() abstract;
virtual Ui::IUiContext * GetUiContext() abstract;
virtual sint32 RunOpenRCT2(int argc, char * * argv) abstract;
virtual void Finish() abstract;
};
IContext * CreateContext();
IContext * CreateContext(Audio::IAudioContext * audioContext, Ui::IUiContext * uiContext);
IContext * GetContext();
}
#endif // __cplusplus
#ifdef __cplusplus
extern "C"
{
#endif
void context_setcurrentcursor(sint32 cursor);
void context_hide_cursor();
void context_show_cursor();
void context_get_cursor_position(sint32 * x, sint32 * y);
void context_get_cursor_position_scaled(sint32 * x, sint32 * y);
void context_set_cursor_position(sint32 x, sint32 y);
const CursorState * context_get_cursor_state();
const uint8 * context_get_keys_state();
const uint8 * context_get_keys_pressed();
TextInputSession * context_start_text_input(utf8 * buffer, size_t maxLength);
void context_stop_text_input();
bool context_is_input_active();
void context_trigger_resize();
void context_set_fullscreen_mode(sint32 mode);
sint32 context_get_resolutions(struct Resolution * * outResolutions);
sint32 context_get_width();
sint32 context_get_height();
bool context_has_focus();
void context_set_cursor_trap(bool value);
#ifdef __cplusplus
}
#endif

View File

@ -14,47 +14,18 @@
*****************************************************************************/
#pragma endregion
#include <memory>
#include <string>
#include "core/Console.hpp"
#include "core/File.h"
#include "core/FileStream.hpp"
#include "core/Guard.hpp"
#include "core/String.hpp"
#include "FileClassifier.h"
#include "network/network.h"
#include "object/ObjectRepository.h"
#include "OpenRCT2.h"
#include "ParkImporter.h"
#include "platform/crash.h"
#include "PlatformEnvironment.h"
#include "ride/TrackDesignRepository.h"
#include "scenario/ScenarioRepository.h"
#include "title/TitleScreen.h"
#include "title/TitleSequenceManager.h"
#include "Version.h"
extern "C"
{
#include "audio/audio.h"
#include "config/Config.h"
#include "editor.h"
#include "game.h"
#include "interface/chat.h"
#include "interface/themes.h"
#include "intro.h"
#include "localisation/localisation.h"
#include "network/http.h"
#include "object_list.h"
#include "platform/platform.h"
#include "rct1.h"
#include "rct2.h"
#include "rct2/interop.h"
}
// The game update interval in milliseconds, (1000 / 40fps) = 25ms
constexpr uint32 UPDATE_TIME_MS = 25;
extern "C"
{
sint32 gExitCode;
@ -76,252 +47,6 @@ extern "C"
// OpenSSL's message digest context used for calculating sprite checksums
EVP_MD_CTX * gHashCTX = nullptr;
#endif // DISABLE_NETWORK
}
namespace OpenRCT2
{
static IPlatformEnvironment * _env = nullptr;
static bool _isWindowMinimised;
static uint32 _isWindowMinimisedLastCheckTick;
static uint32 _lastTick;
static uint32 _uncapTick;
/** If set, will end the OpenRCT2 game loop. Intentionally private to this module so that the flag can not be set back to false. */
static bool _finished;
static bool ShouldRunVariableFrame();
static void RunGameLoop();
static void RunFixedFrame();
static void RunVariableFrame();
static bool OpenParkAutoDetectFormat(const utf8 * path);
}
extern "C"
{
void openrct2_write_full_version_info(utf8 * buffer, size_t bufferSize)
{
String::Set(buffer, bufferSize, gVersionInfoFull);
}
static void openrct2_set_exe_path()
{
platform_get_exe_path(gExePath, sizeof(gExePath));
log_verbose("Setting exe path to %s", gExePath);
}
bool openrct2_initialise()
{
#ifndef DISABLE_NETWORK
gHashCTX = EVP_MD_CTX_create();
Guard::Assert(gHashCTX != nullptr, "EVP_MD_CTX_create failed");
#endif // DISABLE_NETWORK
crash_init();
// Sets up the environment OpenRCT2 is running in, e.g. directory paths
OpenRCT2::_env = OpenRCT2::SetupEnvironment();
if (OpenRCT2::_env == nullptr)
{
return false;
}
if (!rct2_interop_setup_segment())
{
log_fatal("Unable to load RCT2 data sector");
return false;
}
if (gConfigGeneral.last_run_version != nullptr && String::Equals(gConfigGeneral.last_run_version, OPENRCT2_VERSION))
{
gOpenRCT2ShowChangelog = false;
}
else
{
gOpenRCT2ShowChangelog = true;
gConfigGeneral.last_run_version = String::Duplicate(OPENRCT2_VERSION);
config_save_default();
}
// TODO add configuration option to allow multiple instances
// if (!gOpenRCT2Headless && !platform_lock_single_instance()) {
// log_fatal("OpenRCT2 is already running.");
// return false;
// }
IObjectRepository * objRepo = CreateObjectRepository(OpenRCT2::_env);
ITrackDesignRepository * tdRepo = CreateTrackDesignRepository(OpenRCT2::_env);
IScenarioRepository * scenarioRepo = CreateScenarioRepository(OpenRCT2::_env);
if (!language_open(gConfigGeneral.language))
{
log_error("Failed to open configured language...");
if (!language_open(LANGUAGE_ENGLISH_UK))
{
log_fatal("Failed to open fallback language...");
return false;
}
}
// TODO Ideally we want to delay this until we show the title so that we can
// still open the game window and draw a progress screen for the creation
// of the object cache.
objRepo->LoadOrConstruct();
// TODO Like objects, this can take a while if there are a lot of track designs
// it's also really something we might want to do in the background
// as it's not required until the player wants to place a new ride.
tdRepo->Scan();
scenarioRepo->Scan();
TitleSequenceManager::Scan();
if (!gOpenRCT2Headless)
{
audio_init();
audio_populate_devices();
}
http_init();
theme_manager_initialise();
rct2_interop_setup_hooks();
if (!rct2_init())
{
return false;
}
chat_init();
rct2_copy_original_user_files_over();
return true;
}
/**
* Launches the game, after command line arguments have been parsed and processed.
*/
void openrct2_launch()
{
if (openrct2_initialise())
{
gIntroState = INTRO_STATE_NONE;
if ((gOpenRCT2StartupAction == STARTUP_ACTION_TITLE) && gConfigGeneral.play_intro)
{
gOpenRCT2StartupAction = STARTUP_ACTION_INTRO;
}
switch (gOpenRCT2StartupAction) {
case STARTUP_ACTION_INTRO:
gIntroState = INTRO_STATE_PUBLISHER_BEGIN;
title_load();
break;
case STARTUP_ACTION_TITLE:
title_load();
break;
case STARTUP_ACTION_OPEN:
{
bool parkLoaded = false;
// A path that includes "://" is illegal with all common filesystems, so it is almost certainly a URL
// This way all cURL supported protocols, like http, ftp, scp and smb are automatically handled
if (strstr(gOpenRCT2StartupActionPath, "://") != nullptr)
{
#ifndef DISABLE_HTTP
// Download park and open it using its temporary filename
char tmpPath[MAX_PATH];
if (!http_download_park(gOpenRCT2StartupActionPath, tmpPath))
{
title_load();
break;
}
parkLoaded = OpenRCT2::OpenParkAutoDetectFormat(tmpPath);
#endif
}
else
{
parkLoaded = rct2_open_file(gOpenRCT2StartupActionPath);
}
if (!parkLoaded)
{
Console::Error::WriteLine("Failed to load '%s'", gOpenRCT2StartupActionPath);
title_load();
break;
}
gScreenFlags = SCREEN_FLAGS_PLAYING;
#ifndef DISABLE_NETWORK
if (gNetworkStart == NETWORK_MODE_SERVER)
{
if (gNetworkStartPort == 0)
{
gNetworkStartPort = gConfigNetwork.default_port;
}
if (String::IsNullOrEmpty(gCustomPassword))
{
network_set_password(gConfigNetwork.default_password);
}
else
{
network_set_password(gCustomPassword);
}
network_begin_server(gNetworkStartPort);
}
#endif // DISABLE_NETWORK
break;
}
case STARTUP_ACTION_EDIT:
if (String::SizeOf(gOpenRCT2StartupActionPath) == 0)
{
editor_load();
}
else if (!editor_load_landscape(gOpenRCT2StartupActionPath))
{
title_load();
}
break;
}
#ifndef DISABLE_NETWORK
if (gNetworkStart == NETWORK_MODE_CLIENT)
{
if (gNetworkStartPort == 0)
{
gNetworkStartPort = gConfigNetwork.default_port;
}
network_begin_client(gNetworkStartHost, gNetworkStartPort);
}
#endif // DISABLE_NETWORK
OpenRCT2::RunGameLoop();
}
openrct2_dispose();
}
void openrct2_dispose()
{
network_close();
http_dispose();
language_close_all();
rct2_dispose();
config_release();
#ifndef DISABLE_NETWORK
EVP_MD_CTX_destroy(gHashCTX);
#endif // DISABLE_NETWORK
rct2_interop_dispose();
platform_free();
}
/**
* Causes the OpenRCT2 game loop to finish.
*/
void openrct2_finish()
{
OpenRCT2::_finished = true;
}
bool check_file_path(sint32 pathId)
{
@ -353,229 +78,3 @@ extern "C"
return true;
}
}
namespace OpenRCT2
{
IPlatformEnvironment * SetupEnvironment()
{
utf8 userPath[MAX_PATH];
platform_resolve_openrct_data_path();
platform_resolve_user_data_path();
platform_get_user_directory(userPath, NULL, sizeof(userPath));
if (!platform_ensure_directory_exists(userPath))
{
Console::Error::WriteLine("Could not create user directory (do you have write access to your documents folder?)");
return nullptr;
}
openrct2_set_exe_path();
config_set_defaults();
if (!config_open_default())
{
if (!config_find_or_browse_install_directory())
{
gConfigGeneral.last_run_version = String::Duplicate(OPENRCT2_VERSION);
config_save_default();
utf8 path[MAX_PATH];
config_get_default_path(path, sizeof(path));
Console::Error::WriteLine("An RCT2 install directory must be specified! Please edit \"game_path\" in %s.", path);
return nullptr;
}
config_save_default();
}
if (!rct2_init_directories())
{
return nullptr;
}
if (!rct2_startup_checks())
{
return nullptr;
}
utf8 path[260];
std::string basePaths[4];
basePaths[(size_t)DIRBASE::RCT1] = String::ToStd(gConfigGeneral.rct1_path);
basePaths[(size_t)DIRBASE::RCT2] = String::ToStd(gConfigGeneral.rct2_path);
platform_get_openrct_data_path(path, sizeof(path));
basePaths[(size_t)DIRBASE::OPENRCT2] = std::string(path);
platform_get_user_directory(path, nullptr, sizeof(path));
basePaths[(size_t)DIRBASE::USER] = std::string(path);
IPlatformEnvironment * env = CreatePlatformEnvironment(basePaths);
return env;
}
/**
* Run the main game loop until the finished flag is set.
*/
static void RunGameLoop()
{
log_verbose("begin openrct2 loop");
_finished = false;
do
{
if (ShouldRunVariableFrame())
{
RunVariableFrame();
}
else
{
RunFixedFrame();
}
}
while (!_finished);
log_verbose("finish openrct2 loop");
}
static bool IsMinimised()
{
// Don't check if window is minimised too frequently (every second is fine)
if (_lastTick > _isWindowMinimisedLastCheckTick + 1000)
{
uint32 windowFlags = SDL_GetWindowFlags(gWindow);
_isWindowMinimised = (windowFlags & SDL_WINDOW_MINIMIZED) ||
(windowFlags & SDL_WINDOW_HIDDEN);
}
return _isWindowMinimised;
}
static bool ShouldRunVariableFrame()
{
if (!gConfigGeneral.uncap_fps) return false;
if (gGameSpeed > 4) return false;
if (gOpenRCT2Headless) return false;
if (IsMinimised()) return false;
return true;
}
static void RunFixedFrame()
{
_uncapTick = 0;
uint32 currentTick = SDL_GetTicks();
uint32 ticksElapsed = currentTick - _lastTick;
if (ticksElapsed < UPDATE_TIME_MS)
{
SDL_Delay(UPDATE_TIME_MS - ticksElapsed);
_lastTick += UPDATE_TIME_MS;
}
else
{
_lastTick = currentTick;
}
platform_process_messages();
rct2_update();
if (!_isWindowMinimised)
{
platform_draw();
}
}
static void RunVariableFrame()
{
uint32 currentTick = SDL_GetTicks();
if (_uncapTick == 0)
{
_uncapTick = currentTick;
sprite_position_tween_reset();
}
// Limit number of updates per loop (any long pauses or debugging can make this update for a very long time)
if (currentTick - _uncapTick > UPDATE_TIME_MS * 60)
{
_uncapTick = currentTick - UPDATE_TIME_MS - 1;
}
platform_process_messages();
while (_uncapTick <= currentTick && currentTick - _uncapTick > UPDATE_TIME_MS)
{
// Get the original position of each sprite
sprite_position_tween_store_a();
// Update the game so the sprite positions update
rct2_update();
// Get the next position of each sprite
sprite_position_tween_store_b();
_uncapTick += UPDATE_TIME_MS;
}
// Tween the position of each sprite from the last position to the new position based on the time between the last
// tick and the next tick.
float nudge = 1 - ((float)(currentTick - _uncapTick) / UPDATE_TIME_MS);
sprite_position_tween_all(nudge);
platform_draw();
sprite_position_tween_restore();
}
static bool OpenParkAutoDetectFormat(const utf8 * path)
{
ClassifiedFile info;
if (TryClassifyFile(path, &info))
{
if (info.Type == FILE_TYPE::SAVED_GAME ||
info.Type == FILE_TYPE::SCENARIO)
{
std::unique_ptr<IParkImporter> parkImporter;
if (info.Version <= 2)
{
parkImporter.reset(ParkImporter::CreateS4());
}
else
{
parkImporter.reset(ParkImporter::CreateS6());
}
if (info.Type == FILE_TYPE::SAVED_GAME)
{
try
{
parkImporter->LoadSavedGame(path);
parkImporter->Import();
game_fix_save_vars();
sprite_position_tween_reset();
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
game_load_init();
return true;
}
catch (const Exception &)
{
Console::Error::WriteLine("Error loading saved game.");
}
}
else
{
try
{
parkImporter->LoadScenario(path);
parkImporter->Import();
game_fix_save_vars();
sprite_position_tween_reset();
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
scenario_begin();
return true;
}
catch (const Exception &)
{
Console::Error::WriteLine("Error loading scenario.");
}
}
}
else
{
Console::Error::WriteLine("Invalid file type.");
}
}
else
{
Console::Error::WriteLine("Unable to detect file type.");
}
return false;
}
}

View File

@ -24,6 +24,7 @@ extern "C"
{
#endif
#include "platform/platform.h"
#undef CreateWindow
#ifdef __cplusplus
}
#endif
@ -40,17 +41,6 @@ enum STARTUP_ACTION
STARTUP_ACTION_EDIT
};
#ifdef __cplusplus
interface IPlatformEnvironment;
namespace OpenRCT2
{
IPlatformEnvironment * SetupEnvironment();
}
#endif
#ifdef __cplusplus
extern "C"
{
@ -80,17 +70,10 @@ extern "C"
#endif
void openrct2_write_full_version_info(utf8 * buffer, size_t bufferSize);
bool openrct2_initialise();
void openrct2_launch();
void openrct2_dispose();
void openrct2_finish();
sint32 cmdline_run(const char * * argv, sint32 argc);
#ifdef __WINDOWS__
int RunOpenRCT2(int argc, char * * argv);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -1,290 +0,0 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <cmath>
#include <speex/speex_resampler.h>
#include "../core/Math.hpp"
#include "AudioChannel.h"
#include "AudioSource.h"
class AudioChannelImpl : public IAudioChannel
{
private:
IAudioSource * _source = nullptr;
SpeexResamplerState * _resampler = nullptr;
sint32 _group = MIXER_GROUP_SOUND;
double _rate = 0;
uint64 _offset = 0;
sint32 _loop = 0;
sint32 _volume = 1;
float _volume_l = 0.f;
float _volume_r = 0.f;
float _oldvolume_l = 0.f;
float _oldvolume_r = 0.f;
sint32 _oldvolume = 0;
float _pan = 0;
bool _stopping = false;
bool _done = true;
bool _deleteondone = false;
bool _deletesourceondone = false;
public:
AudioChannelImpl()
{
SetRate(1);
SetVolume(SDL_MIX_MAXVOLUME);
SetPan(0.5f);
}
~AudioChannelImpl() override
{
if (_resampler != nullptr)
{
speex_resampler_destroy(_resampler);
_resampler = nullptr;
}
if (_deletesourceondone)
{
delete _source;
}
}
IAudioSource * GetSource() const override
{
return _source;
}
SpeexResamplerState * GetResampler() const override
{
return _resampler;
}
void SetResampler(SpeexResamplerState * value) override
{
_resampler = value;
}
sint32 GetGroup() const override
{
return _group;
}
void SetGroup(sint32 group) override
{
_group = group;
}
double GetRate() const override
{
return _rate;
}
void SetRate(double rate) override
{
_rate = Math::Max(0.001, rate);
}
uint64 GetOffset() const override
{
return _offset;
}
bool SetOffset(uint64 offset) override
{
if (_source != nullptr && offset < _source->GetLength())
{
AudioFormat format = _source->GetFormat();
sint32 samplesize = format.channels * format.BytesPerSample();
_offset = (offset / samplesize) * samplesize;
return true;
}
return false;
}
virtual sint32 GetLoop() const override
{
return _loop;
}
virtual void SetLoop(sint32 value) override
{
_loop = value;
}
sint32 GetVolume() const override
{
return _volume;
}
float GetVolumeL() const override
{
return _volume_l;
}
float GetVolumeR() const override
{
return _volume_r;
}
float GetOldVolumeL() const override
{
return _oldvolume_l;
}
float GetOldVolumeR() const override
{
return _oldvolume_r;
}
sint32 GetOldVolume() const override
{
return _oldvolume;
}
void SetVolume(sint32 volume) override
{
_volume = Math::Clamp(0, volume, SDL_MIX_MAXVOLUME);
}
float GetPan() const override
{
return _pan;
}
void SetPan(float pan) override
{
_pan = Math::Clamp(0.0f, pan, 1.0f);
double decibels = (std::abs(_pan - 0.5) * 2.0) * 100.0;
double attenuation = pow(10, decibels / 20.0);
if (_pan <= 0.5)
{
_volume_l = 1.0;
_volume_r = (float)(1.0 / attenuation);
}
else
{
_volume_r = 1.0;
_volume_l = (float)(1.0 / attenuation);
}
}
bool IsStopping() const override
{
return _stopping;
}
void SetStopping(bool value) override
{
_stopping = value;
}
bool IsDone() const override
{
return _done;
}
void SetDone(bool value) override
{
_done = value;
}
bool DeleteOnDone() const override
{
return _deleteondone;
}
void SetDeleteOnDone(bool value) override
{
_deleteondone = value;
}
void SetDeleteSourceOnDone(bool value) override
{
_deletesourceondone = value;
}
bool IsPlaying() const override
{
return !_done;
}
void Play(IAudioSource * source, sint32 loop) override
{
_source = source;
_loop = loop;
_offset = 0;
_done = false;
}
void UpdateOldVolume() override
{
_oldvolume = _volume;
_oldvolume_l = _volume_l;
_oldvolume_r = _volume_r;
}
AudioFormat GetFormat() const override
{
AudioFormat result = { 0 };
if (_source != nullptr)
{
result = _source->GetFormat();
}
return result;
}
size_t Read(void * dst, size_t len) override
{
size_t bytesRead = 0;
size_t bytesToRead = len;
while (bytesToRead > 0 && !_done)
{
size_t readLen = _source->Read(dst, _offset, bytesToRead);
if (readLen > 0)
{
dst = (void *)((uintptr_t)dst + readLen);
bytesToRead -= readLen;
bytesRead += readLen;
_offset += readLen;
}
if (_offset >= _source->GetLength())
{
if (_loop == 0)
{
_done = true;
}
else if (_loop == MIXER_LOOP_INFINITE)
{
_offset = 0;
}
else
{
_loop--;
_offset = 0;
}
}
}
return bytesRead;
}
};
IAudioChannel * AudioChannel::Create()
{
return new (std::nothrow) AudioChannelImpl();
}

View File

@ -16,70 +16,61 @@
#pragma once
#include <speex/speex_resampler.h>
#include "../common.h"
#include "AudioFormat.h"
#include "AudioMixer.h"
interface IAudioSource;
/**
* Represents an audio channel that represents an audio source
* and a number of properties such as volume, pan and loop information.
*/
interface IAudioChannel
namespace OpenRCT2 { namespace Audio
{
virtual ~IAudioChannel() = default;
interface IAudioSource;
virtual IAudioSource * GetSource() const abstract;
/**
* Represents an audio channel that represents an audio source
* and a number of properties such as volume, pan and loop information.
*/
interface IAudioChannel
{
virtual ~IAudioChannel() = default;
virtual SpeexResamplerState * GetResampler() const abstract;
virtual void SetResampler(SpeexResamplerState * value) abstract;
virtual IAudioSource * GetSource() const abstract;
virtual sint32 GetGroup() const abstract;
virtual void SetGroup(sint32 group) abstract;
virtual sint32 GetGroup() const abstract;
virtual void SetGroup(sint32 group) abstract;
virtual double GetRate() const abstract;
virtual void SetRate(double rate) abstract;
virtual double GetRate() const abstract;
virtual void SetRate(double rate) abstract;
virtual uint64 GetOffset() const abstract;
virtual bool SetOffset(uint64 offset) abstract;
virtual uint64 GetOffset() const abstract;
virtual bool SetOffset(uint64 offset) abstract;
virtual sint32 GetLoop() const abstract;
virtual void SetLoop(sint32 value) abstract;
virtual sint32 GetLoop() const abstract;
virtual void SetLoop(sint32 value) abstract;
virtual sint32 GetVolume() const abstract;
virtual float GetVolumeL() const abstract;
virtual float GetVolumeR() const abstract;
virtual float GetOldVolumeL() const abstract;
virtual float GetOldVolumeR() const abstract;
virtual sint32 GetOldVolume() const abstract;
virtual void SetVolume(sint32 volume) abstract;
virtual sint32 GetVolume() const abstract;
virtual float GetVolumeL() const abstract;
virtual float GetVolumeR() const abstract;
virtual float GetOldVolumeL() const abstract;
virtual float GetOldVolumeR() const abstract;
virtual sint32 GetOldVolume() const abstract;
virtual void SetVolume(sint32 volume) abstract;
virtual float GetPan() const abstract;
virtual void SetPan(float pan) abstract;
virtual float GetPan() const abstract;
virtual void SetPan(float pan) abstract;
virtual bool IsStopping() const abstract;
virtual void SetStopping(bool value) abstract;
virtual bool IsStopping() const abstract;
virtual void SetStopping(bool value) abstract;
virtual bool IsDone() const abstract;
virtual void SetDone(bool value) abstract;
virtual bool IsDone() const abstract;
virtual void SetDone(bool value) abstract;
virtual bool DeleteOnDone() const abstract;
virtual void SetDeleteOnDone(bool value) abstract;
virtual bool DeleteOnDone() const abstract;
virtual void SetDeleteOnDone(bool value) abstract;
virtual void SetDeleteSourceOnDone(bool value) abstract;
virtual void SetDeleteSourceOnDone(bool value) abstract;
virtual bool IsPlaying() const abstract;
virtual bool IsPlaying() const abstract;
virtual void Play(IAudioSource * source, sint32 loop = MIXER_LOOP_NONE) abstract;
virtual void UpdateOldVolume() abstract;
virtual void Play(IAudioSource * source, sint32 loop = 0) abstract;
virtual void UpdateOldVolume() abstract;
virtual AudioFormat GetFormat() const abstract;
virtual size_t Read(void * dst, size_t len) abstract;
};
namespace AudioChannel
{
IAudioChannel * Create();
}
virtual size_t Read(void * dst, size_t len) abstract;
};
} }

View File

@ -0,0 +1,64 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include <string>
#include <vector>
#include "../common.h"
namespace OpenRCT2
{
namespace Audio
{
interface IAudioChannel;
interface IAudioMixer;
interface IAudioSource;
/**
* Audio services for playing music and sound effects.
*/
interface IAudioContext
{
virtual ~IAudioContext() = default;
virtual IAudioMixer * GetMixer() abstract;
virtual std::vector<std::string> GetOutputDevices() abstract;
virtual void SetOutputDevice(const std::string &deviceName) abstract;
virtual IAudioSource * CreateStreamFromWAV(const std::string &path) abstract;
virtual void StartTitleMusic() abstract;
virtual IAudioChannel * PlaySound(sint32 soundId, sint32 volume, sint32 pan) abstract;
virtual IAudioChannel * PlaySoundAtLocation(sint32 soundId, sint16 x, sint16 y, sint16 z) abstract;
virtual IAudioChannel * PlaySoundPanned(sint32 soundId, sint32 pan, sint16 x, sint16 y, sint16 z) abstract;
virtual void ToggleAllSounds() abstract;
virtual void PauseSounds() abstract;
virtual void UnpauseSounds() abstract;
virtual void StopAll() abstract;
virtual void StopCrowdSound() abstract;
virtual void StopRainSound() abstract;
virtual void StopRideMusic() abstract;
virtual void StopTitleMusic() abstract;
virtual void StopVehicleSounds() abstract;
};
}
}

View File

@ -1,549 +1,56 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <list>
#include "../core/Guard.hpp"
#include "../core/Math.hpp"
#include "../core/Memory.hpp"
#include "../core/Util.hpp"
#include <cmath>
#include "../config/Config.h"
#include "../Context.h"
#include "audio.h"
#include "AudioChannel.h"
#include "AudioContext.h"
#include "AudioMixer.h"
#include "AudioSource.h"
extern "C"
{
#include "../config/Config.h"
#include "../localisation/localisation.h"
#include "../OpenRCT2.h"
#include "../platform/platform.h"
#include "../rct2.h"
#include "audio.h"
}
IAudioMixer * gMixer;
using namespace OpenRCT2;
using namespace OpenRCT2::Audio;
struct Buffer
static IAudioMixer * GetMixer()
{
private:
void * _data = nullptr;
size_t _capacity = 0;
public:
void * GetData() const { return _data; }
void * GetData() { return _data; }
size_t GetCapacity() const { return _capacity; }
~Buffer()
{
Free();
}
void Free()
{
Memory::Free(_data);
_data = nullptr;
_capacity = 0;
}
void EnsureCapacity(size_t capacity)
{
if (_capacity < capacity)
{
_capacity = capacity;
_data = Memory::Reallocate(_data, capacity);
}
}
};
class AudioMixer final : public IAudioMixer
{
private:
IAudioSource * _nullSource = nullptr;
SDL_AudioDeviceID _deviceId = 0;
AudioFormat _format = { 0 };
std::list<IAudioChannel *> _channels;
float _volume = 1.0f;
float _adjustSoundVolume = 0.0f;
float _adjustMusicVolume = 0.0f;
uint8 _settingSoundVolume = 0xFF;
uint8 _settingMusicVolume = 0xFF;
IAudioSource * _css1Sources[SOUND_MAXID] = { nullptr };
IAudioSource * _musicSources[PATH_ID_END] = { nullptr };
Buffer _channelBuffer;
Buffer _convertBuffer;
Buffer _effectBuffer;
public:
AudioMixer()
{
_nullSource = AudioSource::CreateNull();
}
~AudioMixer()
{
Close();
delete _nullSource;
}
void Init(const char* device) override
{
Close();
SDL_AudioSpec want = { 0 };
want.freq = 44100;
want.format = AUDIO_S16SYS;
want.channels = 2;
want.samples = 1024;
want.callback = [](void * arg, uint8 * dst, sint32 length) -> void
{
auto mixer = static_cast<AudioMixer *>(arg);
mixer->GetNextAudioChunk(dst, (size_t)length);
};
want.userdata = this;
SDL_AudioSpec have;
_deviceId = SDL_OpenAudioDevice(device, 0, &want, &have, 0);
_format.format = have.format;
_format.channels = have.channels;
_format.freq = have.freq;
LoadAllSounds();
SDL_PauseAudioDevice(_deviceId, 0);
}
void Close() override
{
// Free channels
Lock();
for (IAudioChannel * channel : _channels)
{
delete channel;
}
_channels.clear();
Unlock();
SDL_CloseAudioDevice(_deviceId);
// Free sources
for (size_t i = 0; i < Util::CountOf(_css1Sources); i++)
{
if (_css1Sources[i] != _nullSource)
{
SafeDelete(_css1Sources[i]);
}
}
for (size_t i = 0; i < Util::CountOf(_musicSources); i++)
{
if (_musicSources[i] != _nullSource)
{
SafeDelete(_musicSources[i]);
}
}
// Free buffers
_channelBuffer.Free();
_convertBuffer.Free();
_effectBuffer.Free();
}
void Lock() override
{
SDL_LockAudioDevice(_deviceId);
}
void Unlock() override
{
SDL_UnlockAudioDevice(_deviceId);
}
IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) override
{
Lock();
IAudioChannel * channel = AudioChannel::Create();
if (channel != nullptr)
{
channel->Play(source, loop);
channel->SetDeleteOnDone(deleteondone);
channel->SetDeleteSourceOnDone(deletesourceondone);
_channels.push_back(channel);
}
Unlock();
return channel;
}
void Stop(IAudioChannel * channel) override
{
Lock();
channel->SetStopping(true);
Unlock();
}
bool LoadMusic(size_t pathId) override
{
bool result = false;
if (pathId < Util::CountOf(_musicSources))
{
IAudioSource * source = _musicSources[pathId];
if (source == nullptr)
{
const utf8 * path = get_file_path((sint32)pathId);
source = AudioSource::CreateMemoryFromWAV(path, &_format);
if (source == nullptr)
{
source = _nullSource;
}
_musicSources[pathId] = source;
}
result = source != _nullSource;
}
return result;
}
void SetVolume(float volume) override
{
_volume = volume;
}
IAudioSource * GetSoundSource(sint32 id) override
{
return _css1Sources[id];
}
IAudioSource * GetMusicSource(sint32 id) override
{
return _musicSources[id];
}
private:
void LoadAllSounds()
{
const utf8 * css1Path = get_file_path(PATH_ID_CSS1);
for (size_t i = 0; i < Util::CountOf(_css1Sources); i++)
{
auto source = AudioSource::CreateMemoryFromCSS1(css1Path, i, &_format);
if (source == nullptr)
{
source = _nullSource;
}
_css1Sources[i] = source;
}
}
void GetNextAudioChunk(uint8 * dst, size_t length)
{
UpdateAdjustedSound();
// Zero the output buffer
Memory::Set(dst, 0, length);
// Mix channels onto output buffer
std::list<IAudioChannel *>::iterator it = _channels.begin();
while (it != _channels.end())
{
IAudioChannel * channel = *it;
sint32 group = channel->GetGroup();
if (group != MIXER_GROUP_SOUND || gConfigSound.sound_enabled)
{
MixChannel(channel, dst, length);
}
if ((channel->IsDone() && channel->DeleteOnDone()) || channel->IsStopping())
{
delete channel;
it = _channels.erase(it);
}
else
{
it++;
}
}
}
void UpdateAdjustedSound()
{
// Did the volume level get changed? Recalculate level in this case.
if (_settingSoundVolume != gConfigSound.sound_volume)
{
_settingSoundVolume = gConfigSound.sound_volume;
_adjustSoundVolume = powf(_settingSoundVolume / 100.f, 10.f / 6.f);
}
if (_settingMusicVolume != gConfigSound.ride_music_volume)
{
_settingMusicVolume = gConfigSound.ride_music_volume;
_adjustMusicVolume = powf(_settingMusicVolume / 100.f, 10.f / 6.f);
}
}
void MixChannel(IAudioChannel * channel, uint8 * data, size_t length)
{
sint32 byteRate = _format.GetByteRate();
sint32 numSamples = (sint32)(length / byteRate);
double rate = 1;
if (_format.format == AUDIO_S16SYS)
{
rate = channel->GetRate();
}
bool mustConvert = false;
SDL_AudioCVT cvt;
cvt.len_ratio = 1;
AudioFormat streamformat = channel->GetFormat();
if (streamformat != _format)
{
if (SDL_BuildAudioCVT(&cvt, streamformat.format, streamformat.channels, streamformat.freq, _format.format, _format.channels, _format.freq) == -1)
{
// Unable to convert channel data
return;
}
mustConvert = true;
}
// Read raw PCM from channel
sint32 readSamples = (sint32)(numSamples * rate);
size_t readLength = (size_t)(readSamples / cvt.len_ratio) * byteRate;
_channelBuffer.EnsureCapacity(readLength);
size_t bytesRead = channel->Read(_channelBuffer.GetData(), readLength);
// Convert data to required format if necessary
void * buffer = nullptr;
size_t bufferLen = 0;
if (mustConvert)
{
if (Convert(&cvt, _channelBuffer.GetData(), bytesRead))
{
buffer = cvt.buf;
bufferLen = cvt.len_cvt;
}
else
{
return;
}
}
else
{
buffer = _channelBuffer.GetData();
bufferLen = bytesRead;
}
// Apply effects
if (rate != 1)
{
sint32 srcSamples = (sint32)(bufferLen / byteRate);
sint32 dstSamples = numSamples;
bufferLen = ApplyResample(channel, buffer, srcSamples, dstSamples);
buffer = _effectBuffer.GetData();
}
// Apply panning and volume
ApplyPan(channel, buffer, bufferLen, byteRate);
sint32 mixVolume = ApplyVolume(channel, buffer, bufferLen);
// Finally mix on to destination buffer
size_t dstLength = Math::Min(length, bufferLen);
SDL_MixAudioFormat(data, (const uint8 *)buffer, _format.format, (uint32)dstLength, mixVolume);
channel->UpdateOldVolume();
}
/**
* Resample the given buffer into _effectBuffer.
* Assumes that srcBuffer is the same format as _format.
*/
size_t ApplyResample(IAudioChannel * channel, const void * srcBuffer, sint32 srcSamples, sint32 dstSamples)
{
sint32 byteRate = _format.GetByteRate();
// Create resampler
SpeexResamplerState * resampler = channel->GetResampler();
if (resampler == nullptr)
{
resampler = speex_resampler_init(_format.channels, _format.freq, _format.freq, 0, 0);
channel->SetResampler(resampler);
}
speex_resampler_set_rate(resampler, srcSamples, dstSamples);
// Ensure destination buffer is large enough
size_t effectBufferReqLen = dstSamples * byteRate;
_effectBuffer.EnsureCapacity(effectBufferReqLen);
uint32 inLen = srcSamples;
uint32 outLen = dstSamples;
speex_resampler_process_interleaved_int(
resampler,
(const spx_int16_t *)srcBuffer,
&inLen,
(spx_int16_t *)_effectBuffer.GetData(),
&outLen);
return outLen * byteRate;
}
void ApplyPan(const IAudioChannel * channel, void * buffer, size_t len, size_t sampleSize)
{
if (channel->GetPan() != 0.5f && _format.channels == 2)
{
switch (_format.format) {
case AUDIO_S16SYS:
EffectPanS16(channel, (sint16 *)buffer, (sint32)(len / sampleSize));
break;
case AUDIO_U8:
EffectPanU8(channel, (uint8 *)buffer, (sint32)(len / sampleSize));
break;
}
}
}
sint32 ApplyVolume(const IAudioChannel * channel, void * buffer, size_t len)
{
float volumeAdjust = _volume;
volumeAdjust *= (gConfigSound.master_volume / 100.0f);
switch (channel->GetGroup()) {
case MIXER_GROUP_SOUND:
volumeAdjust *= _adjustSoundVolume;
// Cap sound volume on title screen so music is more audible
if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)
{
volumeAdjust = Math::Min(volumeAdjust, 0.75f);
}
break;
case MIXER_GROUP_RIDE_MUSIC:
volumeAdjust *= _adjustMusicVolume;
break;
}
sint32 startVolume = (sint32)(channel->GetOldVolume() * volumeAdjust);
sint32 endVolume = (sint32)(channel->GetVolume() * volumeAdjust);
if (channel->IsStopping())
{
endVolume = 0;
}
sint32 mixVolume = (sint32)(channel->GetVolume() * volumeAdjust);
if (startVolume != endVolume)
{
// Set to max since we are adjusting the volume ourselves
mixVolume = SDL_MIX_MAXVOLUME;
// Fade between volume levels to smooth out sound and minimize clicks from sudden volume changes
sint32 fadeLength = (sint32)len / _format.BytesPerSample();
switch (_format.format) {
case AUDIO_S16SYS:
EffectFadeS16((sint16 *)buffer, fadeLength, startVolume, endVolume);
break;
case AUDIO_U8:
EffectFadeU8((uint8 *)buffer, fadeLength, startVolume, endVolume);
break;
}
}
return mixVolume;
}
static void EffectPanS16(const IAudioChannel * channel, sint16 * data, sint32 length)
{
const float dt = 1.0f / (length * 2);
float volumeL = channel->GetOldVolumeL();
float volumeR = channel->GetOldVolumeR();
const float d_left = dt * (channel->GetVolumeL() - channel->GetOldVolumeL());
const float d_right = dt * (channel->GetVolumeR() - channel->GetOldVolumeR());
for (sint32 i = 0; i < length * 2; i += 2)
{
data[i] = (sint16)(data[i] * volumeL);
data[i + 1] = (sint16)(data[i + 1] * volumeR);
volumeL += d_left;
volumeR += d_right;
}
}
static void EffectPanU8(const IAudioChannel * channel, uint8 * data, sint32 length)
{
float volumeL = channel->GetVolumeL();
float volumeR = channel->GetVolumeR();
float oldVolumeL = channel->GetOldVolumeL();
float oldVolumeR = channel->GetOldVolumeR();
for (sint32 i = 0; i < length * 2; i += 2)
{
float t = (float)i / (length * 2);
data[i] = (uint8)(data[i] * ((1.0 - t) * oldVolumeL + t * volumeL));
data[i + 1] = (uint8)(data[i + 1] * ((1.0 - t) * oldVolumeR + t * volumeR));
}
}
static void EffectFadeS16(sint16 * data, sint32 length, sint32 startvolume, sint32 endvolume)
{
float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME;
float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME;
for (sint32 i = 0; i < length; i++)
{
float t = (float)i / length;
data[i] = (sint16)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f));
}
}
static void EffectFadeU8(uint8* data, sint32 length, sint32 startvolume, sint32 endvolume)
{
float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME;
float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME;
for (sint32 i = 0; i < length; i++)
{
float t = (float)i / length;
data[i] = (uint8)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f));
}
}
bool Convert(SDL_AudioCVT * cvt, const void * src, size_t len)
{
// tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, can cause pops and static depending on sample rate and channels
bool result = false;
if (len != 0 && cvt->len_mult != 0)
{
size_t reqConvertBufferCapacity = len * cvt->len_mult;
_convertBuffer.EnsureCapacity(reqConvertBufferCapacity);
Memory::Copy(_convertBuffer.GetData(), src, len);
cvt->len = (sint32)len;
cvt->buf = (uint8 *)_convertBuffer.GetData();
if (SDL_ConvertAudio(cvt) >= 0)
{
result = true;
}
}
return result;
}
};
IAudioContext * audioContext = GetContext()->GetAudioContext();
return audioContext->GetMixer();
}
void Mixer_Init(const char * device)
{
if (!gOpenRCT2Headless)
IAudioContext * audioContext = GetContext()->GetAudioContext();
if (device == nullptr)
{
gMixer = new AudioMixer();
gMixer->Init(device);
device = "";
}
audioContext->SetOutputDevice(std::string(device));
}
void * Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, double rate, sint32 deleteondone)
{
IAudioChannel * channel = nullptr;
if (!gOpenRCT2Headless && gConfigSound.sound_enabled)
if (gConfigSound.sound_enabled)
{
if (id >= SOUND_MAXID)
{
@ -551,7 +58,7 @@ void * Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, doubl
}
else
{
IAudioMixer * mixer = gMixer;
IAudioMixer * mixer = GetMixer();
mixer->Lock();
IAudioSource * source = mixer->GetSoundSource((sint32)id);
channel = mixer->Play(source, loop, deleteondone != 0, false);
@ -569,124 +76,103 @@ void * Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, doubl
void Mixer_Stop_Channel(void * channel)
{
if (!gOpenRCT2Headless)
{
gMixer->Stop(static_cast<IAudioChannel*>(channel));
}
GetMixer()->Stop(static_cast<IAudioChannel*>(channel));
}
void Mixer_Channel_Volume(void * channel, sint32 volume)
{
if (!gOpenRCT2Headless)
{
gMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetVolume(volume);
gMixer->Unlock();
}
IAudioMixer * audioMixer = GetMixer();
audioMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetVolume(volume);
audioMixer->Unlock();
}
void Mixer_Channel_Pan(void * channel, float pan)
{
if (!gOpenRCT2Headless)
{
gMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetPan(pan);
gMixer->Unlock();
}
IAudioMixer * audioMixer = GetMixer();
audioMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetPan(pan);
audioMixer->Unlock();
}
void Mixer_Channel_Rate(void* channel, double rate)
{
if (!gOpenRCT2Headless)
{
gMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetRate(rate);
gMixer->Unlock();
}
IAudioMixer * audioMixer = GetMixer();
audioMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetRate(rate);
audioMixer->Unlock();
}
sint32 Mixer_Channel_IsPlaying(void * channel)
{
bool isPlaying = false;
if (!gOpenRCT2Headless)
{
isPlaying = static_cast<IAudioChannel*>(channel)->IsPlaying();
}
return isPlaying;
return static_cast<IAudioChannel*>(channel)->IsPlaying();
}
uint64 Mixer_Channel_GetOffset(void * channel)
{
uint64 offset = 0;
if (!gOpenRCT2Headless)
{
offset = static_cast<IAudioChannel*>(channel)->GetOffset();
}
return offset;
return static_cast<IAudioChannel*>(channel)->GetOffset();
}
sint32 Mixer_Channel_SetOffset(void * channel, uint64 offset)
{
sint32 result = 0;
if (!gOpenRCT2Headless)
{
result = static_cast<IAudioChannel*>(channel)->SetOffset(offset);
}
return result;
return static_cast<IAudioChannel*>(channel)->SetOffset(offset);
}
void Mixer_Channel_SetGroup(void * channel, sint32 group)
{
if (!gOpenRCT2Headless)
{
static_cast<IAudioChannel *>(channel)->SetGroup(group);
}
static_cast<IAudioChannel *>(channel)->SetGroup(group);
}
void * Mixer_Play_Music(sint32 pathId, sint32 loop, sint32 streaming)
{
IAudioChannel * channel = nullptr;
if (!gOpenRCT2Headless)
IAudioMixer * mixer = GetMixer();
if (streaming)
{
IAudioMixer * mixer = gMixer;
if (streaming)
{
const utf8 * filename = get_file_path(pathId);
const utf8 * path = get_file_path(pathId);
SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
if (rw != nullptr)
IAudioContext * audioContext = GetContext()->GetAudioContext();
IAudioSource * source = audioContext->CreateStreamFromWAV(path);
if (source != nullptr)
{
channel = mixer->Play(source, loop, false, true);
if (channel == nullptr)
{
auto source = AudioSource::CreateStreamFromWAV(rw);
if (source != nullptr)
{
channel = mixer->Play(source, loop, false, true);
if (channel == nullptr)
{
delete source;
}
}
delete source;
}
}
else
}
else
{
if (mixer->LoadMusic(pathId))
{
if (mixer->LoadMusic(pathId))
{
IAudioSource * source = mixer->GetMusicSource(pathId);
channel = mixer->Play(source, MIXER_LOOP_INFINITE, false, false);
}
}
if (channel != nullptr)
{
channel->SetGroup(MIXER_GROUP_RIDE_MUSIC);
IAudioSource * source = mixer->GetMusicSource(pathId);
channel = mixer->Play(source, MIXER_LOOP_INFINITE, false, false);
}
}
if (channel != nullptr)
{
channel->SetGroup(MIXER_GROUP_RIDE_MUSIC);
}
return channel;
}
void Mixer_SetVolume(float volume)
{
if (!gOpenRCT2Headless)
{
gMixer->SetVolume(volume);
}
GetMixer()->SetVolume(volume);
}
sint32 DStoMixerVolume(sint32 volume)
{
return (sint32)(SDL_MIX_MAXVOLUME * (std::pow(10.0f, (float)volume / 2000)));
}
float DStoMixerPan(sint32 pan)
{
return (((float)pan + -DSBPAN_LEFT) / DSBPAN_RIGHT) / 2;
}
double DStoMixerRate(sint32 frequency)
{
return (double)frequency / 22050;
}

View File

@ -14,21 +14,11 @@
*****************************************************************************/
#pragma endregion
#ifndef _MIXER_H_
#define _MIXER_H_
#pragma once
#include "../common.h"
#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus
#include "../platform/platform.h"
#ifdef __cplusplus
}
#endif // __cplusplus
#define SDL_MIX_MAXVOLUME 128
#define MIXER_LOOP_NONE 0
#define MIXER_LOOP_INFINITE -1
@ -42,59 +32,61 @@ enum MIXER_GROUP
#ifdef __cplusplus
interface IAudioSource;
interface IAudioChannel;
/**
* Provides an audio stream by mixing multiple audio channels together.
*/
interface IAudioMixer
namespace OpenRCT2 { namespace Audio
{
virtual ~IAudioMixer() = default;
interface IAudioSource;
interface IAudioChannel;
virtual void Init(const char * device) abstract;
virtual void Close() abstract;
virtual void Lock() abstract;
virtual void Unlock() abstract;
virtual IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) abstract;
virtual void Stop(IAudioChannel * channel) abstract;
virtual bool LoadMusic(size_t pathid) abstract;
virtual void SetVolume(float volume) abstract;
/**
* Provides an audio stream by mixing multiple audio channels together.
*/
interface IAudioMixer
{
virtual ~IAudioMixer() = default;
virtual IAudioSource * GetSoundSource(sint32 id) abstract;
virtual IAudioSource * GetMusicSource(sint32 id) abstract;
};
virtual void Init(const char * device) abstract;
virtual void Close() abstract;
virtual void Lock() abstract;
virtual void Unlock() abstract;
virtual IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) abstract;
virtual void Stop(IAudioChannel * channel) abstract;
virtual bool LoadMusic(size_t pathid) abstract;
virtual void SetVolume(float volume) abstract;
virtual IAudioSource * GetSoundSource(sint32 id) abstract;
virtual IAudioSource * GetMusicSource(sint32 id) abstract;
};
} }
#endif
#ifdef __cplusplus
extern "C"
{
#endif
#ifndef DSBPAN_LEFT
#define DSBPAN_LEFT -10000
#endif
#ifndef DSBPAN_RIGHT
#define DSBPAN_RIGHT 10000
#endif
#ifndef DSBPAN_LEFT
#define DSBPAN_LEFT -10000
#endif
#ifndef DSBPAN_RIGHT
#define DSBPAN_RIGHT 10000
#endif
void Mixer_Init(const char* device);
void* Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, double rate, sint32 deleteondone);
void Mixer_Stop_Channel(void* channel);
void Mixer_Channel_Volume(void* channel, sint32 volume);
void Mixer_Channel_Pan(void* channel, float pan);
void Mixer_Channel_Rate(void* channel, double rate);
sint32 Mixer_Channel_IsPlaying(void* channel);
uint64 Mixer_Channel_GetOffset(void* channel);
sint32 Mixer_Channel_SetOffset(void* channel, uint64 offset);
void Mixer_Channel_SetGroup(void* channel, sint32 group);
void* Mixer_Play_Music(sint32 pathId, sint32 loop, sint32 streaming);
void Mixer_SetVolume(float volume);
static inline sint32 DStoMixerVolume(sint32 volume) { return (sint32)(SDL_MIX_MAXVOLUME * (SDL_pow(10, (float)volume / 2000))); }
static inline float DStoMixerPan(sint32 pan) { return (((float)pan + -DSBPAN_LEFT) / DSBPAN_RIGHT) / 2; }
static inline double DStoMixerRate(sint32 frequency) { return (double)frequency / 22050; }
void Mixer_Init(const char * device);
void* Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, double rate, sint32 deleteondone);
void Mixer_Stop_Channel(void* channel);
void Mixer_Channel_Volume(void* channel, sint32 volume);
void Mixer_Channel_Pan(void* channel, float pan);
void Mixer_Channel_Rate(void* channel, double rate);
sint32 Mixer_Channel_IsPlaying(void* channel);
uint64 Mixer_Channel_GetOffset(void* channel);
sint32 Mixer_Channel_SetOffset(void* channel, uint64 offset);
void Mixer_Channel_SetGroup(void* channel, sint32 group);
void* Mixer_Play_Music(sint32 pathId, sint32 loop, sint32 streaming);
void Mixer_SetVolume(float volume);
sint32 DStoMixerVolume(sint32 volume);
float DStoMixerPan(sint32 pan);
double DStoMixerRate(sint32 frequency);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -17,25 +17,24 @@
#pragma once
#include "../common.h"
#include "AudioFormat.h"
#include "AudioMixer.h"
/**
* Represents a readable source of audio PCM data.
*/
interface IAudioSource
namespace OpenRCT2 { namespace Audio
{
virtual ~IAudioSource() = default;
/**
* Represents a readable source of audio PCM data.
*/
interface IAudioSource
{
virtual ~IAudioSource() = default;
virtual uint64 GetLength() abstract;
virtual AudioFormat GetFormat() abstract;
virtual size_t Read(void * dst, uint64 offset, size_t len) abstract;
};
virtual uint64 GetLength() const abstract;
// virtual AudioFormat GetFormat() abstract;
virtual size_t Read(void * dst, uint64 offset, size_t len) abstract;
};
namespace AudioSource
{
IAudioSource * CreateNull();
IAudioSource * CreateMemoryFromCSS1(const utf8 * path, size_t index, const AudioFormat * targetFormat = nullptr);
IAudioSource * CreateMemoryFromWAV(const utf8 * path, const AudioFormat * targetFormat = nullptr);
IAudioSource * CreateStreamFromWAV(SDL_RWops * rw);
}
namespace AudioSource
{
IAudioSource * CreateNull();
}
} }

View File

@ -1,204 +0,0 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../core/Math.hpp"
#include "AudioSource.h"
#pragma pack(push, 1)
struct WaveFormat
{
uint16 encoding;
uint16 channels;
uint32 frequency;
uint32 byterate;
uint16 blockalign;
uint16 bitspersample;
};
assert_struct_size(WaveFormat, 16);
#pragma pack(pop)
/**
* An audio source where raw PCM data is streamed directly from
* a file.
*/
class FileAudioSource final : public IAudioSource
{
private:
AudioFormat _format = { 0 };
SDL_RWops * _rw = nullptr;
uint64 _dataBegin = 0;
uint64 _dataLength = 0;
public:
~FileAudioSource()
{
Unload();
}
uint64 GetLength() override
{
return _dataLength;
}
AudioFormat GetFormat() override
{
return _format;
}
size_t Read(void * dst, uint64 offset, size_t len) override
{
size_t bytesRead = 0;
sint64 currentPosition = SDL_RWtell(_rw);
if (currentPosition != -1)
{
size_t bytesToRead = (size_t)Math::Min<uint64>(len, _dataLength - offset);
sint64 dataOffset = _dataBegin + offset;
if (currentPosition != dataOffset)
{
sint64 newPosition = SDL_RWseek(_rw, dataOffset, SEEK_SET);
if (newPosition == -1)
{
return 0;
}
}
bytesRead = SDL_RWread(_rw, dst, 1, bytesToRead);
}
return bytesRead;
}
bool LoadWAV(SDL_RWops * rw)
{
const uint32 DATA = 0x61746164;
const uint32 FMT = 0x20746D66;
const uint32 RIFF = 0x46464952;
const uint32 WAVE = 0x45564157;
const uint16 pcmformat = 0x0001;
Unload();
if (rw == nullptr)
{
return false;
}
_rw = rw;
uint32 chunkId = SDL_ReadLE32(rw);
if (chunkId != RIFF)
{
log_verbose("Not a WAV file");
return false;
}
// Read and discard chunk size
SDL_ReadLE32(rw);
uint32 chunkFormat = SDL_ReadLE32(rw);
if (chunkFormat != WAVE)
{
log_verbose("Not in WAVE format");
return false;
}
uint32 fmtChunkSize = FindChunk(rw, FMT);
if (!fmtChunkSize)
{
log_verbose("Could not find FMT chunk");
return false;
}
uint64 chunkStart = SDL_RWtell(rw);
WaveFormat waveFormat;
SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1);
SDL_RWseek(rw, chunkStart + fmtChunkSize, RW_SEEK_SET);
if (waveFormat.encoding != pcmformat) {
log_verbose("Not in proper format");
return false;
}
_format.freq = waveFormat.frequency;
switch (waveFormat.bitspersample) {
case 8:
_format.format = AUDIO_U8;
break;
case 16:
_format.format = AUDIO_S16LSB;
break;
default:
log_verbose("Invalid bits per sample");
return false;
break;
}
_format.channels = waveFormat.channels;
uint32 dataChunkSize = FindChunk(rw, DATA);
if (dataChunkSize == 0)
{
log_verbose("Could not find DATA chunk");
return false;
}
_dataLength = dataChunkSize;
_dataBegin = SDL_RWtell(rw);
return true;
}
private:
uint32 FindChunk(SDL_RWops * rw, uint32 wantedId)
{
uint32 subchunkId = SDL_ReadLE32(rw);
uint32 subchunkSize = SDL_ReadLE32(rw);
if (subchunkId == wantedId)
{
return subchunkSize;
}
const uint32 FACT = 0x74636166;
const uint32 LIST = 0x5453494c;
const uint32 BEXT = 0x74786562;
const uint32 JUNK = 0x4B4E554A;
while (subchunkId == FACT || subchunkId == LIST || subchunkId == BEXT || subchunkId == JUNK)
{
SDL_RWseek(rw, subchunkSize, RW_SEEK_CUR);
subchunkId = SDL_ReadLE32(rw);
subchunkSize = SDL_ReadLE32(rw);
if (subchunkId == wantedId)
{
return subchunkSize;
}
}
return 0;
}
void Unload()
{
if (_rw != nullptr)
{
SDL_RWclose(_rw);
_rw = nullptr;
}
_dataBegin = 0;
_dataLength = 0;
}
};
IAudioSource * AudioSource::CreateStreamFromWAV(SDL_RWops * rw)
{
auto source = new FileAudioSource();
if (!source->LoadWAV(rw))
{
SafeDelete(source);
}
return source;
}

View File

@ -1,245 +0,0 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../core/Math.hpp"
#include "../core/Memory.hpp"
#include "AudioMixer.h"
#include "AudioSource.h"
#pragma pack(push, 1)
struct WaveFormatEx
{
uint16 encoding;
uint16 channels;
uint32 frequency;
uint32 byterate;
uint16 blockalign;
uint16 bitspersample;
uint16 extrasize;
};
assert_struct_size(WaveFormatEx, 18);
#pragma pack(pop)
/**
* An audio source where raw PCM data is initially loaded into RAM from
* a file and then streamed.
*/
class MemoryAudioSource final : public IAudioSource
{
private:
AudioFormat _format = { 0 };
uint8 * _data = nullptr;
size_t _length = 0;
bool _isSDLWav = false;
public:
~MemoryAudioSource()
{
Unload();
}
uint64 GetLength() override
{
return _length;
}
AudioFormat GetFormat() override
{
return _format;
}
size_t Read(void * dst, uint64 offset, size_t len) override
{
size_t bytesToRead = 0;
if (offset < _length)
{
bytesToRead = (size_t)Math::Min<uint64>(len, _length - offset);
Memory::Copy<void>(dst, _data + offset, bytesToRead);
}
return bytesToRead;
}
bool LoadWAV(const utf8 * path)
{
log_verbose("MemoryAudioSource::LoadWAV(%s)", path);
Unload();
bool result = false;
SDL_RWops * rw = SDL_RWFromFile(path, "rb");
if (rw != nullptr)
{
SDL_AudioSpec audiospec = { 0 };
uint32 audioLen;
SDL_AudioSpec * spec = SDL_LoadWAV_RW(rw, false, &audiospec, &_data, &audioLen);
if (spec != nullptr)
{
_format.freq = spec->freq;
_format.format = spec->format;
_format.channels = spec->channels;
_length = audioLen;
_isSDLWav = true;
result = true;
}
else
{
log_verbose("Error loading %s, unsupported WAV format", path);
}
SDL_RWclose(rw);
}
else
{
log_verbose("Error loading %s", path);
}
return result;
}
bool LoadCSS1(const utf8 * path, size_t index)
{
log_verbose("MemoryAudioSource::LoadCSS1(%s, %d)", path, index);
Unload();
bool result = false;
SDL_RWops * rw = SDL_RWFromFile(path, "rb");
if (rw != nullptr)
{
uint32 numSounds;
SDL_RWread(rw, &numSounds, sizeof(numSounds), 1);
if (index < numSounds)
{
SDL_RWseek(rw, index * 4, RW_SEEK_CUR);
uint32 pcmOffset;
SDL_RWread(rw, &pcmOffset, sizeof(pcmOffset), 1);
SDL_RWseek(rw, pcmOffset, RW_SEEK_SET);
uint32 pcmSize;
SDL_RWread(rw, &pcmSize, sizeof(pcmSize), 1);
_length = pcmSize;
WaveFormatEx waveFormat;
SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1);
_format.freq = waveFormat.frequency;
_format.format = AUDIO_S16LSB;
_format.channels = waveFormat.channels;
_data = new (std::nothrow) uint8[_length];
if (_data != nullptr)
{
SDL_RWread(rw, _data, _length, 1);
result = true;
}
else
{
log_verbose("Unable to allocate data");
}
}
SDL_RWclose(rw);
}
else
{
log_verbose("Unable to load %s", path);
}
return result;
}
bool Convert(const AudioFormat * format)
{
if (*format != _format)
{
SDL_AudioCVT cvt;
if (SDL_BuildAudioCVT(&cvt, _format.format, _format.channels, _format.freq, format->format, format->channels, format->freq) >= 0)
{
cvt.len = (sint32)_length;
cvt.buf = new uint8[cvt.len * cvt.len_mult];
Memory::Copy(cvt.buf, _data, _length);
if (SDL_ConvertAudio(&cvt) >= 0)
{
Unload();
_data = cvt.buf;
_length = cvt.len_cvt;
_format = *format;
return true;
}
else
{
delete[] cvt.buf;
}
}
}
return false;
}
private:
void Unload()
{
if (_data != nullptr)
{
if (_isSDLWav)
{
SDL_FreeWAV(_data);
}
else
{
delete[] _data;
}
_data = nullptr;
}
_isSDLWav = false;
_length = 0;
}
};
IAudioSource * AudioSource::CreateMemoryFromCSS1(const utf8 * path, size_t index, const AudioFormat * targetFormat)
{
auto source = new MemoryAudioSource();
if (source->LoadCSS1(path, index))
{
if (targetFormat != nullptr)
{
if (!source->Convert(targetFormat))
{
SafeDelete(source);
}
}
}
else
{
SafeDelete(source);
}
return source;
}
IAudioSource * AudioSource::CreateMemoryFromWAV(const utf8 * path, const AudioFormat * targetFormat)
{
auto source = new MemoryAudioSource();
if (source->LoadWAV(path))
{
if (targetFormat != nullptr)
{
if (!source->Convert(targetFormat))
{
SafeDelete(source);
}
}
}
else
{
SafeDelete(source);
}
return source;
}

View File

@ -16,29 +16,27 @@
#include "AudioSource.h"
/**
* An audio source representing silence.
*/
class NullAudioSource : public IAudioSource
namespace OpenRCT2 { namespace Audio
{
public:
uint64 GetLength() override
/**
* An audio source representing silence.
*/
class NullAudioSource : public IAudioSource
{
return 0;
}
public:
uint64 GetLength() const override
{
return 0;
}
AudioFormat GetFormat() override
size_t Read(void * dst, uint64 offset, size_t len) override
{
return 0;
}
};
IAudioSource * AudioSource::CreateNull()
{
return { 0 };
return new NullAudioSource();
}
size_t Read(void * dst, uint64 offset, size_t len) override
{
return 0;
}
};
IAudioSource * AudioSource::CreateNull()
{
return new NullAudioSource();
}
} }

View File

@ -14,193 +14,199 @@
*****************************************************************************/
#pragma endregion
#include "../config/Config.h"
#include "../Context.h"
#include "../core/Collections.hpp"
#include "../core/File.h"
#include "../core/FileStream.hpp"
#include "../core/Memory.hpp"
#include "../core/String.hpp"
#include "../core/Util.hpp"
#include "../localisation/string_ids.h"
#include "../OpenRCT2.h"
#include "../ui/UiContext.h"
#include "audio.h"
#include "AudioContext.h"
#include "AudioMixer.h"
extern "C"
{
#include "../config/Config.h"
#include "../interface/viewport.h"
#include "../intro.h"
#include "../localisation/language.h"
#include "../util/util.h"
#include "audio.h"
#include "../interface/viewport.h"
#include "../intro.h"
#include "../localisation/language.h"
#include "../ride/ride.h"
#include "../util/util.h"
}
typedef struct rct_audio_params {
bool in_range;
sint32 volume;
sint32 pan;
} rct_audio_params;
using namespace OpenRCT2::Audio;
audio_device *gAudioDevices = NULL;
sint32 gAudioDeviceCount;
sint32 gAudioCurrentDevice = -1;
void *gCrowdSoundChannel = 0;
bool gGameSoundsOff = false;
void *gRainSoundChannel = 0;
rct_ride_music gRideMusicList[AUDIO_MAX_RIDE_MUSIC];
rct_ride_music_params gRideMusicParamsList[6];
rct_ride_music_params *gRideMusicParamsListEnd;
void *gTitleMusicChannel = 0;
rct_vehicle_sound gVehicleSoundList[AUDIO_MAX_VEHICLE_SOUNDS];
rct_vehicle_sound_params gVehicleSoundParamsList[AUDIO_MAX_VEHICLE_SOUNDS];
rct_vehicle_sound_params *gVehicleSoundParamsListEnd;
sint32 gVolumeAdjustZoom = 0;
sint32 _volumeAdjust[SOUND_MAXID] = {
0, // SOUND_LIFT_1
0, // SOUND_TRACK_FRICTION_1
0, // SOUND_LIFT_2
0, // SOUND_SCREAM_1
0, // SOUND_CLICK_1
0, // SOUND_CLICK_2
0, // SOUND_PLACE_ITEM
0, // SOUND_SCREAM_2
0, // SOUND_SCREAM_3
0, // SOUND_SCREAM_4
0, // SOUND_SCREAM_5
0, // SOUND_SCREAM_6
0, // SOUND_LIFT_3
-400, // SOUND_PURCHASE
0, // SOUND_CRASH
0, // SOUND_LAYING_OUT_WATER
0, // SOUND_WATER_1
0, // SOUND_WATER_2
0, // SOUND_TRAIN_WHISTLE
0, // SOUND_TRAIN_CHUGGING
-1000, // SOUND_WATER_SPLASH
0, // SOUND_HAMMERING
-800, // SOUND_RIDE_LAUNCH_1
-1700, // SOUND_RIDE_LAUNCH_2
-700, // SOUND_COUGH_1
-700, // SOUND_COUGH_2
-700, // SOUND_COUGH_3
-700, // SOUND_COUGH_4
0, // SOUND_RAIN_1
0, // SOUND_THUNDER_1
0, // SOUND_THUNDER_2
0, // SOUND_RAIN_2
0, // SOUND_RAIN_3
0, // SOUND_BALLOON_POP
-700, // SOUND_MECHANIC_FIX
0, // SOUND_SCREAM_7
-2500, // SOUND_TOILET_FLUSH original value: -1000
0, // SOUND_CLICK_3
0, // SOUND_QUACK
0, // SOUND_NEWS_ITEM
0, // SOUND_WINDOW_OPEN
-900, // SOUND_LAUGH_1
-900, // SOUND_LAUGH_2
-900, // SOUND_LAUGH_3
0, // SOUND_APPLAUSE
-600, // SOUND_HAUNTED_HOUSE_SCARE
-700, // SOUND_HAUNTED_HOUSE_SCREAM_1
-700, // SOUND_HAUNTED_HOUSE_SCREAM_2
-2550, // SOUND_48
-2900, // SOUND_49
0, // SOUND_ERROR
-3400, // SOUND_51
0, // SOUND_LIFT_4
0, // SOUND_LIFT_5
0, // SOUND_TRACK_FRICTION_2
0, // SOUND_LIFT_6
0, // SOUND_LIFT_7
0, // SOUND_TRACK_FRICTION_3
0, // SOUND_SCREAM_8
0, // SOUND_TRAM
-2000, // SOUND_DOOR_OPEN
-2700, // SOUND_DOOR_CLOSE
-700 // SOUND_62
struct AudioParams
{
bool in_range;
sint32 volume;
sint32 pan;
};
rct_audio_params audio_get_params_from_location(sint32 soundId, const rct_xyz16 *location);
void audio_stop_channel(void **channel);
audio_device * gAudioDevices = nullptr;
sint32 gAudioDeviceCount;
sint32 gAudioCurrentDevice = -1;
bool gGameSoundsOff = false;
sint32 gVolumeAdjustZoom = 0;
void * gTitleMusicChannel = nullptr;
void * gRainSoundChannel = nullptr;
rct_ride_music gRideMusicList[AUDIO_MAX_RIDE_MUSIC];
rct_ride_music_params gRideMusicParamsList[6];
rct_ride_music_params * gRideMusicParamsListEnd;
rct_vehicle_sound gVehicleSoundList[AUDIO_MAX_VEHICLE_SOUNDS];
rct_vehicle_sound_params gVehicleSoundParamsList[AUDIO_MAX_VEHICLE_SOUNDS];
rct_vehicle_sound_params * gVehicleSoundParamsListEnd;
static sint32 SoundVolumeAdjust[SOUND_MAXID] =
{
0, // SOUND_LIFT_1
0, // SOUND_TRACK_FRICTION_1
0, // SOUND_LIFT_2
0, // SOUND_SCREAM_1
0, // SOUND_CLICK_1
0, // SOUND_CLICK_2
0, // SOUND_PLACE_ITEM
0, // SOUND_SCREAM_2
0, // SOUND_SCREAM_3
0, // SOUND_SCREAM_4
0, // SOUND_SCREAM_5
0, // SOUND_SCREAM_6
0, // SOUND_LIFT_3
-400, // SOUND_PURCHASE
0, // SOUND_CRASH
0, // SOUND_LAYING_OUT_WATER
0, // SOUND_WATER_1
0, // SOUND_WATER_2
0, // SOUND_TRAIN_WHISTLE
0, // SOUND_TRAIN_CHUGGING
-1000, // SOUND_WATER_SPLASH
0, // SOUND_HAMMERING
-800, // SOUND_RIDE_LAUNCH_1
-1700, // SOUND_RIDE_LAUNCH_2
-700, // SOUND_COUGH_1
-700, // SOUND_COUGH_2
-700, // SOUND_COUGH_3
-700, // SOUND_COUGH_4
0, // SOUND_RAIN_1
0, // SOUND_THUNDER_1
0, // SOUND_THUNDER_2
0, // SOUND_RAIN_2
0, // SOUND_RAIN_3
0, // SOUND_BALLOON_POP
-700, // SOUND_MECHANIC_FIX
0, // SOUND_SCREAM_7
-2500, // SOUND_TOILET_FLUSH original value: -1000
0, // SOUND_CLICK_3
0, // SOUND_QUACK
0, // SOUND_NEWS_ITEM
0, // SOUND_WINDOW_OPEN
-900, // SOUND_LAUGH_1
-900, // SOUND_LAUGH_2
-900, // SOUND_LAUGH_3
0, // SOUND_APPLAUSE
-600, // SOUND_HAUNTED_HOUSE_SCARE
-700, // SOUND_HAUNTED_HOUSE_SCREAM_1
-700, // SOUND_HAUNTED_HOUSE_SCREAM_2
-2550, // SOUND_48
-2900, // SOUND_49
0, // SOUND_ERROR
-3400, // SOUND_51
0, // SOUND_LIFT_4
0, // SOUND_LIFT_5
0, // SOUND_TRACK_FRICTION_2
0, // SOUND_LIFT_6
0, // SOUND_LIFT_7
0, // SOUND_TRACK_FRICTION_3
0, // SOUND_SCREAM_8
0, // SOUND_TRAM
-2000, // SOUND_DOOR_OPEN
-2700, // SOUND_DOOR_CLOSE
-700 // SOUND_62
};
AudioParams audio_get_params_from_location(sint32 soundId, const rct_xyz16 *location);
void audio_init()
{
sint32 result = SDL_Init(SDL_INIT_AUDIO);
if (result < 0) {
log_error("SDL_Init %s", SDL_GetError());
return;
}
if (str_is_null_or_empty(gConfigSound.device))
{
Mixer_Init(NULL);
gAudioCurrentDevice = 0;
}
else
{
Mixer_Init(gConfigSound.device);
if (str_is_null_or_empty(gConfigSound.device)) {
Mixer_Init(NULL);
gAudioCurrentDevice = 0;
} else {
Mixer_Init(gConfigSound.device);
for (sint32 i = 0; i < gAudioDeviceCount; i++) {
if (strcmp(gAudioDevices[i].name, gConfigSound.device) == 0) {
gAudioCurrentDevice = i;
}
}
}
}
void audio_quit()
{
SDL_QuitSubSystem(SDL_INIT_AUDIO);
audio_populate_devices();
for (sint32 i = 0; i < gAudioDeviceCount; i++)
{
if (String::Equals(gAudioDevices[i].name, gConfigSound.device))
{
gAudioCurrentDevice = i;
}
}
}
}
void audio_populate_devices()
{
if (gAudioDevices != NULL)
free(gAudioDevices);
if (gAudioDevices != nullptr)
{
Memory::Free(gAudioDevices);
gAudioDevices = nullptr;
}
gAudioDeviceCount = SDL_GetNumAudioDevices(SDL_FALSE);
if (gAudioDeviceCount <= 0)
return;
IAudioContext * audioContext = OpenRCT2::GetContext()->GetAudioContext();
std::vector<std::string> devices = audioContext->GetOutputDevices();
audio_device * systemAudioDevices = Memory::AllocateArray<audio_device>(gAudioDeviceCount);
for (sint32 i = 0; i < gAudioDeviceCount; i++) {
const char *utf8Name = SDL_GetAudioDeviceName(i, SDL_FALSE);
if (utf8Name == NULL)
utf8Name = language_get_string(STR_OPTIONS_SOUND_VALUE_UNKNOWN);
// Replace blanks with localised unknown string
for (size_t i = 0; i < devices.size(); i++)
{
if (devices[i].empty())
{
devices[i] = language_get_string(STR_OPTIONS_SOUND_VALUE_DEFAULT);
}
}
safe_strcpy(systemAudioDevices[i].name, utf8Name, AUDIO_DEVICE_NAME_SIZE);
}
#ifndef __LINUX__
gAudioDeviceCount++;
gAudioDevices = Memory::AllocateArray<audio_device>(gAudioDeviceCount);
safe_strcpy(gAudioDevices[0].name, language_get_string(STR_OPTIONS_SOUND_VALUE_DEFAULT), AUDIO_DEVICE_NAME_SIZE);
Memory::CopyArray(&gAudioDevices[1], systemAudioDevices, gAudioDeviceCount - 1);
#else
gAudioDevices = Memory::AllocateArray<audio_device>(gAudioDeviceCount);
Memory::CopyArray(gAudioDevices, systemAudioDevices, gAudioDeviceCount);
#endif // __LINUX__
// The first device is always system default on Windows and macOS
std::string defaultDevice = language_get_string(STR_OPTIONS_SOUND_VALUE_DEFAULT);
devices.insert(devices.begin(), defaultDevice);
#endif
free(systemAudioDevices);
}
sint32 audio_play_sound_panned(sint32 soundId, sint32 pan, sint16 x, sint16 y, sint16 z)
{
if (pan == AUDIO_PLAY_AT_LOCATION)
return audio_play_sound_at_location(soundId, x, y, z);
return audio_play_sound(soundId, 0, pan);
gAudioDeviceCount = (sint32)devices.size();
gAudioDevices = Memory::AllocateArray<audio_device>(gAudioDeviceCount);
for (sint32 i = 0; i < gAudioDeviceCount; i++)
{
auto device = &gAudioDevices[i];
String::Set(device->name, sizeof(device->name), devices[i].c_str());
}
}
sint32 audio_play_sound_at_location(sint32 soundId, sint16 x, sint16 y, sint16 z)
{
if (gGameSoundsOff)
return 0;
if (gGameSoundsOff)
return 0;
rct_xyz16 location;
location.x = x;
location.y = y;
location.z = z;
rct_xyz16 location;
location.x = x;
location.y = y;
location.z = z;
rct_audio_params params = audio_get_params_from_location(soundId, &location);
if (!params.in_range)
return soundId;
return audio_play_sound(soundId, params.volume, params.pan);
AudioParams params = audio_get_params_from_location(soundId, &location);
if (params.in_range)
{
soundId = audio_play_sound(soundId, params.volume, params.pan);
}
return soundId;
}
/**
@ -209,234 +215,249 @@ sint32 audio_play_sound_at_location(sint32 soundId, sint16 x, sint16 y, sint16 z
* @param location The location at which the sound effect is to be played.
* @return The audio parameters to be used when playing this sound effect.
*/
rct_audio_params audio_get_params_from_location(sint32 soundId, const rct_xyz16 *location)
AudioParams audio_get_params_from_location(sint32 soundId, const rct_xyz16 *location)
{
sint32 volumeDown = 0;
rct_audio_params params;
params.in_range = true;
params.volume = 0;
params.pan = 0;
sint32 volumeDown = 0;
AudioParams params;
params.in_range = true;
params.volume = 0;
params.pan = 0;
rct_map_element *element = map_get_surface_element_at(location->x / 32, location->y / 32);
if (element && (element->base_height * 8) - 5 > location->z)
volumeDown = 10;
rct_map_element * element = map_get_surface_element_at(location->x >> 5, location->y >> 5);
if (element && (element->base_height * 8) - 5 > location->z)
{
volumeDown = 10;
}
uint8 rotation = get_current_rotation();
rct_xy16 pos2 = coordinate_3d_to_2d(location, rotation);
rct_window *window = gWindowNextSlot;
while (true) {
window--;
if (window < g_window_list)
break;
uint8 rotation = get_current_rotation();
rct_xy16 pos2 = coordinate_3d_to_2d(location, rotation);
rct_window * window = gWindowNextSlot;
while (true)
{
window--;
if (window < g_window_list)
{
break;
}
rct_viewport *viewport = window->viewport;
if (!viewport || !(viewport->flags & VIEWPORT_FLAG_SOUND_ON))
continue;
rct_viewport * viewport = window->viewport;
if (viewport != nullptr && (viewport->flags & VIEWPORT_FLAG_SOUND_ON))
{
sint16 vy = pos2.y - viewport->view_y;
sint16 vx = pos2.x - viewport->view_x;
params.pan = viewport->x + (vx >> viewport->zoom);
params.volume = SoundVolumeAdjust[soundId] + ((-1024 * viewport->zoom - 1) << volumeDown) + 1;
sint16 vy = pos2.y - viewport->view_y;
sint16 vx = pos2.x - viewport->view_x;
params.pan = viewport->x + (vx >> viewport->zoom);
params.volume = _volumeAdjust[soundId] + ((-1024 * viewport->zoom - 1) << volumeDown) + 1;
if (vy < 0 || vy >= viewport->view_height || vx < 0 || vx >= viewport->view_width || params.volume < -10000)
{
params.in_range = false;
return params;
}
}
}
if (vy < 0 || vy >= viewport->view_height || vx < 0 || vx >= viewport->view_width || params.volume < -10000) {
params.in_range = false;
return params;
}
}
return params;
return params;
}
sint32 audio_play_sound(sint32 soundId, sint32 volume, sint32 pan)
{
if (gGameSoundsOff)
return 0;
if (gGameSoundsOff)
return 0;
sint32 mixerPan = 0;
if (pan != AUDIO_PLAY_AT_CENTRE) {
sint32 x2 = pan << 16;
uint16 screenWidth = Math::Max(64, gScreenWidth);
mixerPan = ((x2 / screenWidth) - 0x8000) >> 4;
}
sint32 mixerPan = 0;
if (pan != AUDIO_PLAY_AT_CENTRE)
{
sint32 x2 = pan << 16;
uint16 screenWidth = Math::Max<sint32>(64, OpenRCT2::GetContext()->GetUiContext()->GetWidth());
mixerPan = ((x2 / screenWidth) - 0x8000) >> 4;
}
Mixer_Play_Effect(soundId, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(mixerPan), 1, 1);
return 0;
Mixer_Play_Effect(soundId, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(mixerPan), 1, 1);
return 0;
}
void audio_start_title_music()
{
if (gGameSoundsOff || !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) || gIntroState != INTRO_STATE_NONE) {
audio_stop_title_music();
return;
}
if (gGameSoundsOff || !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) || gIntroState != INTRO_STATE_NONE)
{
audio_stop_title_music();
return;
}
if (gTitleMusicChannel)
return;
if (gTitleMusicChannel != nullptr)
{
return;
}
sint32 pathId;
switch (gConfigSound.title_music) {
case 1:
pathId = PATH_ID_CSS50;
break;
case 2:
pathId = PATH_ID_CSS17;
break;
case 3:
if (util_rand() & 1)
pathId = PATH_ID_CSS50;
else
pathId = PATH_ID_CSS17;
break;
default:
return;
}
sint32 pathId;
switch (gConfigSound.title_music) {
case 1:
pathId = PATH_ID_CSS50;
break;
case 2:
pathId = PATH_ID_CSS17;
break;
case 3:
pathId = (util_rand() & 1) ? PATH_ID_CSS50 :
PATH_ID_CSS17;
break;
default:
return;
}
gTitleMusicChannel = Mixer_Play_Music(pathId, MIXER_LOOP_INFINITE, true);
if (gTitleMusicChannel != NULL) {
Mixer_Channel_SetGroup(gTitleMusicChannel, MIXER_GROUP_TITLE_MUSIC);
}
gTitleMusicChannel = Mixer_Play_Music(pathId, MIXER_LOOP_INFINITE, true);
if (gTitleMusicChannel != nullptr)
{
Mixer_Channel_SetGroup(gTitleMusicChannel, MIXER_GROUP_TITLE_MUSIC);
}
}
void audio_stop_ride_music()
{
for (sint32 i = 0; i < AUDIO_MAX_RIDE_MUSIC; i++) {
rct_ride_music *rideMusic = &gRideMusicList[i];
if (rideMusic->ride_id == (uint8)-1)
continue;
if (rideMusic->sound_channel)
Mixer_Stop_Channel(rideMusic->sound_channel);
rideMusic->ride_id = -1;
}
for (sint32 i = 0; i < AUDIO_MAX_RIDE_MUSIC; i++)
{
rct_ride_music * rideMusic = &gRideMusicList[i];
if (rideMusic->ride_id != RIDE_ID_NULL)
{
rideMusic->ride_id = RIDE_ID_NULL;
if (rideMusic->sound_channel != nullptr)
{
Mixer_Stop_Channel(rideMusic->sound_channel);
}
}
}
}
void audio_stop_all_music_and_sounds()
{
audio_stop_title_music();
audio_stop_vehicle_sounds();
audio_stop_ride_music();
audio_stop_crowd_sound();
audio_stop_rain_sound();
}
void audio_stop_crowd_sound()
{
audio_stop_channel(&gCrowdSoundChannel);
audio_stop_title_music();
audio_stop_vehicle_sounds();
audio_stop_ride_music();
peep_stop_crowd_noise();
audio_stop_rain_sound();
}
void audio_stop_title_music()
{
audio_stop_channel(&gTitleMusicChannel);
if (gTitleMusicChannel != nullptr)
{
Mixer_Stop_Channel(gTitleMusicChannel);
gTitleMusicChannel = nullptr;
}
}
void audio_stop_rain_sound()
{
audio_stop_channel(&gRainSoundChannel);
}
/**
* Stops the specified audio channel from playing.
* @param channel The channel to stop.
*/
void audio_stop_channel(void **channel)
{
if (!*channel)
return;
Mixer_Stop_Channel(*channel);
*channel = 0;
if (gRainSoundChannel != nullptr)
{
Mixer_Stop_Channel(gRainSoundChannel);
gRainSoundChannel = nullptr;
}
}
void audio_init_ride_sounds_and_info()
{
sint32 deviceNum = 0;
audio_init_ride_sounds(deviceNum);
sint32 deviceNum = 0;
audio_init_ride_sounds(deviceNum);
for (size_t m = 0; m < Util::CountOf(gRideMusicInfoList); m++) {
rct_ride_music_info *rideMusicInfo = gRideMusicInfoList[m];
const utf8 *path = get_file_path(rideMusicInfo->path_id);
if (File::Exists(path))
{
try
{
auto fs = FileStream(path, FILE_MODE_OPEN);
uint32 head = fs.ReadValue<uint32>();
if (head == 0x78787878) {
rideMusicInfo->length = 0;
}
}
catch (const Exception &)
{
}
}
}
for (size_t m = 0; m < Util::CountOf(gRideMusicInfoList); m++)
{
rct_ride_music_info *rideMusicInfo = gRideMusicInfoList[m];
const utf8 *path = get_file_path(rideMusicInfo->path_id);
if (File::Exists(path))
{
try
{
auto fs = FileStream(path, FILE_MODE_OPEN);
uint32 head = fs.ReadValue<uint32>();
if (head == 0x78787878)
{
rideMusicInfo->length = 0;
}
}
catch (const Exception &)
{
}
}
}
}
void audio_init_ride_sounds(sint32 device)
{
audio_close();
for (sint32 i = 0; i < AUDIO_MAX_VEHICLE_SOUNDS; i++) {
rct_vehicle_sound *vehicleSound = &gVehicleSoundList[i];
vehicleSound->id = -1;
}
audio_close();
for (sint32 i = 0; i < AUDIO_MAX_VEHICLE_SOUNDS; i++)
{
rct_vehicle_sound * vehicleSound = &gVehicleSoundList[i];
vehicleSound->id = 0xFFFF;
}
gAudioCurrentDevice = device;
config_save_default();
for (sint32 i = 0; i < AUDIO_MAX_RIDE_MUSIC; i++) {
rct_ride_music *rideMusic = &gRideMusicList[i];
rideMusic->ride_id = -1;
}
gAudioCurrentDevice = device;
config_save_default();
for (sint32 i = 0; i < AUDIO_MAX_RIDE_MUSIC; i++)
{
rct_ride_music * rideMusic = &gRideMusicList[i];
rideMusic->ride_id = RIDE_ID_NULL;
}
}
void audio_close()
{
audio_stop_crowd_sound();
audio_stop_title_music();
audio_stop_ride_music();
audio_stop_rain_sound();
gAudioCurrentDevice = -1;
peep_stop_crowd_noise();
audio_stop_title_music();
audio_stop_ride_music();
audio_stop_rain_sound();
gAudioCurrentDevice = -1;
}
void audio_toggle_all_sounds()
{
if (gGameSoundsOff) {
audio_unpause_sounds();
} else {
audio_pause_sounds();
}
gConfigSound.sound_enabled = !gConfigSound.sound_enabled;
if (gConfigSound.sound_enabled)
{
audio_unpause_sounds();
}
else
{
audio_stop_title_music();
audio_pause_sounds();
}
}
void audio_pause_sounds()
{
gGameSoundsOff = true;
audio_stop_vehicle_sounds();
audio_stop_ride_music();
audio_stop_crowd_sound();
audio_stop_rain_sound();
gGameSoundsOff = true;
audio_stop_vehicle_sounds();
audio_stop_ride_music();
peep_stop_crowd_noise();
audio_stop_rain_sound();
}
void audio_unpause_sounds()
{
gGameSoundsOff = false;
gGameSoundsOff = false;
}
void audio_stop_vehicle_sounds()
{
if (gOpenRCT2Headless || gAudioCurrentDevice == -1)
return;
if (gAudioCurrentDevice == -1)
{
return;
}
for (size_t i = 0; i < Util::CountOf(gVehicleSoundList); i++) {
rct_vehicle_sound *vehicleSound = &gVehicleSoundList[i];
if (vehicleSound->id == 0xFFFF)
continue;
if (vehicleSound->sound1_id != 0xFFFF)
Mixer_Stop_Channel(vehicleSound->sound1_channel);
if (vehicleSound->sound2_id != 0xFFFF)
Mixer_Stop_Channel(vehicleSound->sound2_channel);
vehicleSound->id = 0xFFFF;
}
for (size_t i = 0; i < Util::CountOf(gVehicleSoundList); i++)
{
rct_vehicle_sound * vehicleSound = &gVehicleSoundList[i];
if (vehicleSound->id != 0xFFFF)
{
vehicleSound->id = 0xFFFF;
if (vehicleSound->sound1_id != 0xFFFF)
{
Mixer_Stop_Channel(vehicleSound->sound1_channel);
}
if (vehicleSound->sound2_id != 0xFFFF)
{
Mixer_Stop_Channel(vehicleSound->sound2_channel);
}
}
}
}

View File

@ -14,167 +14,167 @@
*****************************************************************************/
#pragma endregion
#ifndef _AUDIO_H_
#define _AUDIO_H_
#pragma once
#include "../common.h"
#include "../world/sprite.h"
#define AUDIO_DEVICE_NAME_SIZE 256
#define AUDIO_MAX_RIDE_MUSIC 2
#define AUDIO_MAX_VEHICLE_SOUNDS 14
#define NUM_DEFAULT_MUSIC_TRACKS 46
#define AUDIO_PLAY_AT_CENTRE 0x8000
#define AUDIO_PLAY_AT_LOCATION 0x8001
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct audio_device {
char name[AUDIO_DEVICE_NAME_SIZE];
#define AUDIO_DEVICE_NAME_SIZE 256
#define AUDIO_MAX_RIDE_MUSIC 2
#define AUDIO_MAX_VEHICLE_SOUNDS 14
#define NUM_DEFAULT_MUSIC_TRACKS 46
#define AUDIO_PLAY_AT_CENTRE 0x8000
#define AUDIO_PLAY_AT_LOCATION 0x8001
typedef struct audio_device
{
char name[AUDIO_DEVICE_NAME_SIZE];
} audio_device;
#pragma pack(push, 1)
typedef struct rct_ride_music {
uint8 ride_id;
uint8 tune_id;
sint16 volume;
sint16 pan;
uint16 frequency;
void* sound_channel;
typedef struct rct_ride_music
{
uint8 ride_id;
uint8 tune_id;
sint16 volume;
sint16 pan;
uint16 frequency;
void* sound_channel;
} rct_ride_music;
#ifdef PLATFORM_32BIT
assert_struct_size(rct_ride_music, 12);
#endif
typedef struct rct_ride_music_info {
uint32 length;
uint32 offset;
uint8 path_id;
uint8 var_9;
typedef struct rct_ride_music_info
{
uint32 length;
uint32 offset;
uint8 path_id;
uint8 var_9;
} rct_ride_music_info;
assert_struct_size(rct_ride_music_info, 10);
typedef struct rct_ride_music_params {
uint8 ride_id;
uint8 tune_id;
sint32 offset;
sint16 volume;
sint16 pan;
uint16 frequency;
typedef struct rct_ride_music_params
{
uint8 ride_id;
uint8 tune_id;
sint32 offset;
sint16 volume;
sint16 pan;
uint16 frequency;
} rct_ride_music_params;
assert_struct_size(rct_ride_music_params, 12);
typedef struct rct_vehicle_sound {
uint16 id;
sint16 volume;
uint16 sound1_id;
sint16 sound1_volume;
sint16 sound1_pan;
uint16 sound1_freq;
uint16 sound2_id;
sint16 sound2_volume;
sint16 sound2_pan;
uint16 sound2_freq;
void* sound1_channel;
void* sound2_channel;
typedef struct rct_vehicle_sound
{
uint16 id;
sint16 volume;
uint16 sound1_id;
sint16 sound1_volume;
sint16 sound1_pan;
uint16 sound1_freq;
uint16 sound2_id;
sint16 sound2_volume;
sint16 sound2_pan;
uint16 sound2_freq;
void* sound1_channel;
void* sound2_channel;
} rct_vehicle_sound;
#ifdef PLATFORM_32BIT
assert_struct_size(rct_vehicle_sound, 28);
#endif
typedef struct rct_vehicle_sound_params {
uint16 id;
sint16 pan_x;
sint16 pan_y;
uint16 frequency;
sint16 volume;
uint16 var_A;
typedef struct rct_vehicle_sound_params
{
uint16 id;
sint16 pan_x;
sint16 pan_y;
uint16 frequency;
sint16 volume;
uint16 var_A;
} rct_vehicle_sound_params;
assert_struct_size(rct_vehicle_sound_params, 12);
#pragma pack(pop)
typedef enum RCT2_SOUND {
SOUND_LIFT_1 = 0,
SOUND_TRACK_FRICTION_1 = 1,
SOUND_LIFT_2 = 2,
SOUND_SCREAM_1 = 3,
SOUND_CLICK_1 = 4,
SOUND_CLICK_2 = 5,
SOUND_PLACE_ITEM = 6,
SOUND_SCREAM_2 = 7,
SOUND_SCREAM_3 = 8,
SOUND_SCREAM_4 = 9,
SOUND_SCREAM_5 = 10,
SOUND_SCREAM_6 = 11,
SOUND_LIFT_3 = 12,
SOUND_PURCHASE = 13,
SOUND_CRASH = 14,
SOUND_LAYING_OUT_WATER = 15,
SOUND_WATER_1 = 16,
SOUND_WATER_2 = 17,
SOUND_TRAIN_WHISTLE = 18,
SOUND_TRAIN_CHUGGING = 19,
SOUND_WATER_SPLASH = 20,
SOUND_HAMMERING = 21,
SOUND_RIDE_LAUNCH_1 = 22,
SOUND_RIDE_LAUNCH_2 = 23,
SOUND_COUGH_1 = 24,
SOUND_COUGH_2 = 25,
SOUND_COUGH_3 = 26,
SOUND_COUGH_4 = 27,
SOUND_RAIN_1 = 28,
SOUND_THUNDER_1 = 29,
SOUND_THUNDER_2 = 30,
SOUND_RAIN_2 = 31,
SOUND_RAIN_3 = 32,
SOUND_BALLOON_POP = 33,
SOUND_MECHANIC_FIX = 34,
SOUND_SCREAM_7 = 35,
SOUND_TOILET_FLUSH = 36,
SOUND_CLICK_3 = 37,
SOUND_QUACK = 38,
SOUND_NEWS_ITEM = 39,
SOUND_WINDOW_OPEN = 40,
SOUND_LAUGH_1 = 41,
SOUND_LAUGH_2 = 42,
SOUND_LAUGH_3 = 43,
SOUND_APPLAUSE = 44,
SOUND_HAUNTED_HOUSE_SCARE = 45,
SOUND_HAUNTED_HOUSE_SCREAM_1 = 46,
SOUND_HAUNTED_HOUSE_SCREAM_2 = 47,
SOUND_48 = 48,
SOUND_49 = 49,
SOUND_ERROR = 50,
SOUND_51 = 51,
SOUND_LIFT_4 = 52,
SOUND_LIFT_5 = 53,
SOUND_TRACK_FRICTION_2 = 54,
SOUND_LIFT_6 = 55,
SOUND_LIFT_7 = 56,
SOUND_TRACK_FRICTION_3 = 57,
SOUND_SCREAM_8 = 58,
SOUND_TRAM = 59,
SOUND_DOOR_OPEN = 60,
SOUND_DOOR_CLOSE = 61,
SOUND_62 = 62,
SOUND_MAXID
typedef enum RCT2_SOUND
{
SOUND_LIFT_1,
SOUND_TRACK_FRICTION_1,
SOUND_LIFT_2,
SOUND_SCREAM_1,
SOUND_CLICK_1,
SOUND_CLICK_2,
SOUND_PLACE_ITEM,
SOUND_SCREAM_2,
SOUND_SCREAM_3,
SOUND_SCREAM_4,
SOUND_SCREAM_5,
SOUND_SCREAM_6,
SOUND_LIFT_3,
SOUND_PURCHASE,
SOUND_CRASH,
SOUND_LAYING_OUT_WATER,
SOUND_WATER_1,
SOUND_WATER_2,
SOUND_TRAIN_WHISTLE,
SOUND_TRAIN_CHUGGING,
SOUND_WATER_SPLASH,
SOUND_HAMMERING,
SOUND_RIDE_LAUNCH_1,
SOUND_RIDE_LAUNCH_2,
SOUND_COUGH_1,
SOUND_COUGH_2,
SOUND_COUGH_3,
SOUND_COUGH_4,
SOUND_RAIN_1,
SOUND_THUNDER_1,
SOUND_THUNDER_2,
SOUND_RAIN_2,
SOUND_RAIN_3,
SOUND_BALLOON_POP,
SOUND_MECHANIC_FIX,
SOUND_SCREAM_7,
SOUND_TOILET_FLUSH,
SOUND_CLICK_3,
SOUND_QUACK,
SOUND_NEWS_ITEM,
SOUND_WINDOW_OPEN,
SOUND_LAUGH_1,
SOUND_LAUGH_2,
SOUND_LAUGH_3,
SOUND_APPLAUSE,
SOUND_HAUNTED_HOUSE_SCARE,
SOUND_HAUNTED_HOUSE_SCREAM_1,
SOUND_HAUNTED_HOUSE_SCREAM_2,
SOUND_48,
SOUND_49,
SOUND_ERROR,
SOUND_51,
SOUND_LIFT_4,
SOUND_LIFT_5,
SOUND_TRACK_FRICTION_2,
SOUND_LIFT_6,
SOUND_LIFT_7,
SOUND_TRACK_FRICTION_3,
SOUND_SCREAM_8,
SOUND_TRAM,
SOUND_DOOR_OPEN,
SOUND_DOOR_CLOSE,
SOUND_62,
SOUND_MAXID
} RCT2_SOUND;
extern audio_device *gAudioDevices;
extern sint32 gAudioDeviceCount;
extern sint32 gAudioCurrentDevice;
extern void *gCrowdSoundChannel;
extern bool gGameSoundsOff;
extern void *gRainSoundChannel;
extern rct_ride_music gRideMusicList[AUDIO_MAX_RIDE_MUSIC];
extern rct_ride_music_info *gRideMusicInfoList[NUM_DEFAULT_MUSIC_TRACKS];
extern rct_ride_music_params gRideMusicParamsList[6];
extern rct_ride_music_params *gRideMusicParamsListEnd;
extern void *gTitleMusicChannel;
extern rct_vehicle_sound gVehicleSoundList[AUDIO_MAX_VEHICLE_SOUNDS];
extern rct_vehicle_sound_params gVehicleSoundParamsList[AUDIO_MAX_VEHICLE_SOUNDS];
extern rct_vehicle_sound_params *gVehicleSoundParamsListEnd;
extern sint32 gVolumeAdjustZoom;
extern audio_device * gAudioDevices;
extern sint32 gAudioDeviceCount;
extern sint32 gAudioCurrentDevice;
extern bool gGameSoundsOff;
extern sint32 gVolumeAdjustZoom;
extern void * gTitleMusicChannel;
extern void * gRainSoundChannel;
extern rct_ride_music gRideMusicList[AUDIO_MAX_RIDE_MUSIC];
extern rct_ride_music_info * gRideMusicInfoList[NUM_DEFAULT_MUSIC_TRACKS];
extern rct_ride_music_params gRideMusicParamsList[6];
extern rct_ride_music_params * gRideMusicParamsListEnd;
extern rct_vehicle_sound gVehicleSoundList[AUDIO_MAX_VEHICLE_SOUNDS];
extern rct_vehicle_sound_params gVehicleSoundParamsList[AUDIO_MAX_VEHICLE_SOUNDS];
extern rct_vehicle_sound_params * gVehicleSoundParamsListEnd;
/**
* Deregisters the audio device.
@ -219,39 +219,15 @@ sint32 audio_play_sound(sint32 soundId, sint32 volume, sint32 pan);
*/
sint32 audio_play_sound_at_location(sint32 soundId, sint16 x, sint16 y, sint16 z);
/**
* rct2: 0x006BB76E
* @deprecated Use audio_play_sound_at_location or audio_play_sound instead.
* Plays the specified sound effect at a location specified by the pan parameter.
* @param soundId (eax) The sound effect to play.
* @param pan (ebx) If set to AUDIO_PLAY_AT_LOCATION, play the sound at the specified location; if set to AUDIO_PLAY_AT_CENTRE,
* play the sound at the centre of the viewport; if set to anything else, use the value of pan as a relative position to the
* centre of the viewport.
* @param x (cx) The x coordinate of the location.
* @param y (dx) The y coordinate of the location.
* @param z (bp) The z coordinate of the location.
* @return 0 if the sound was not out of range; otherwise, soundId.
*/
sint32 audio_play_sound_panned(sint32 soundId, sint32 pan, sint16 x, sint16 y, sint16 z);
/**
* Populates the gAudioDevices array with the available audio devices.
*/
void audio_populate_devices();
/**
* Terminates the audio subsystem.
* This appears to be unused.
*/
void audio_quit();
/**
* Starts playing the title music.
* rct2: 0x006BD0F8
*/
void audio_start_title_music();
/**
* Stops the crowd sound effect from playing.
* rct2: 0x006BD07F
*/
void audio_stop_crowd_sound();
/**
* Stops the rain sound effect from playing.
*/
void audio_stop_rain_sound();
@ -283,4 +259,6 @@ void audio_unpause_sounds();
void audio_stop_all_music_and_sounds();
#ifdef __cplusplus
}
#endif

View File

@ -103,11 +103,11 @@ exitcode_t CommandLine::HandleCommandConvert(CommandLineArgEnumerator * enumerat
WriteConvertFromAndToMessage(sourceFileType, destinationFileType);
gOpenRCT2Headless = true;
if (!openrct2_initialise())
{
Console::Error::WriteLine("Error while initialising OpenRCT2.");
return EXITCODE_FAIL;
}
// if (!openrct2_initialise())
// {
// Console::Error::WriteLine("Error while initialising OpenRCT2.");
// return EXITCODE_FAIL;
// }
try
{

View File

@ -389,9 +389,9 @@ static exitcode_t HandleCommandScanObjects(CommandLineArgEnumerator * enumerator
return result;
}
IPlatformEnvironment * env = OpenRCT2::SetupEnvironment();
IObjectRepository * objectRepository = CreateObjectRepository(env);
objectRepository->Construct();
// IPlatformEnvironment * env = OpenRCT2::SetupEnvironment();
// IObjectRepository * objectRepository = CreateObjectRepository(env);
// objectRepository->Construct();
return EXITCODE_OK;
}

View File

@ -20,8 +20,8 @@
#include "cmdline_sprite.h"
#include "drawing/drawing.h"
#include "Imaging.h"
#include "localisation/localisation.h"
#include "OpenRCT2.h"
#include "platform/platform.h"
#include "util/util.h"
#define MODE_DEFAULT 0
@ -61,6 +61,22 @@ rct_sprite_file_header spriteFileHeader;
rct_g1_element *spriteFileEntries;
uint8 *spriteFileData;
#ifdef _WIN32
static FILE * fopen_utf8(const char * path, const char * mode)
{
wchar_t * pathW = utf8_to_widechar(path);
wchar_t * modeW = utf8_to_widechar(mode);
FILE * file = _wfopen(pathW, modeW);
free(pathW);
free(modeW);
return file;
}
#define fopen fopen_utf8
#endif
static void sprite_file_load_palette(sint32 spriteIndex)
{
rct_g1_element *g1 = &spriteFileEntries[spriteIndex];
@ -90,14 +106,12 @@ static void sprite_entries_make_relative()
static bool sprite_file_open(const utf8 *path)
{
SDL_RWops *file;
file = SDL_RWFromFile(path, "rb");
FILE * file = fopen(path, "rb");
if (file == NULL)
return false;
if (SDL_RWread(file, &spriteFileHeader, sizeof(rct_sprite_file_header), 1) != 1) {
SDL_RWclose(file);
if (fread(&spriteFileHeader, sizeof(rct_sprite_file_header), 1, file) != 1) {
fclose(file);
return false;
}
@ -105,21 +119,21 @@ static bool sprite_file_open(const utf8 *path)
sint32 openEntryTableSize = spriteFileHeader.num_entries * sizeof(rct_g1_element_32bit);
rct_g1_element_32bit * openElements = (rct_g1_element_32bit *)malloc(openEntryTableSize);
if (openElements == NULL) {
SDL_RWclose(file);
fclose(file);
return false;
}
if (SDL_RWread(file, openElements, openEntryTableSize, 1) != 1) {
if (fread(openElements, openEntryTableSize, 1, file) != 1) {
free(openElements);
SDL_RWclose(file);
fclose(file);
return false;
}
spriteFileData = malloc(spriteFileHeader.total_size);
if (SDL_RWread(file, spriteFileData, spriteFileHeader.total_size, 1) != 1) {
if (fread(spriteFileData, spriteFileHeader.total_size, 1, file) != 1) {
free(spriteFileData);
free(openElements);
SDL_RWclose(file);
fclose(file);
return false;
}
@ -141,18 +155,18 @@ static bool sprite_file_open(const utf8 *path)
free(openElements);
}
SDL_RWclose(file);
fclose(file);
return true;
}
static bool sprite_file_save(const char *path)
{
SDL_RWops *file = SDL_RWFromFile(path, "wb");
FILE * file = fopen(path, "wb");
if (file == NULL)
return false;
if (SDL_RWwrite(file, &spriteFileHeader, sizeof(rct_sprite_file_header), 1) != 1) {
SDL_RWclose(file);
if (fwrite(&spriteFileHeader, sizeof(rct_sprite_file_header), 1, file) != 1) {
fclose(file);
return false;
}
@ -160,7 +174,7 @@ static bool sprite_file_save(const char *path)
sint32 saveEntryTableSize = spriteFileHeader.num_entries * sizeof(rct_g1_element_32bit);
rct_g1_element_32bit * saveElements = (rct_g1_element_32bit *)malloc(saveEntryTableSize);
if (saveElements == NULL) {
SDL_RWclose(file);
fclose(file);
return false;
}
@ -177,20 +191,20 @@ static bool sprite_file_save(const char *path)
outElement->zoomed_offset = inElement->zoomed_offset;
}
if (SDL_RWwrite(file, saveElements, saveEntryTableSize, 1) != 1) {
if (fwrite(saveElements, saveEntryTableSize, 1, file) != 1) {
free(saveElements);
SDL_RWclose(file);
fclose(file);
return false;
}
free(saveElements);
if (SDL_RWwrite(file, spriteFileData, spriteFileHeader.total_size, 1) != 1) {
SDL_RWclose(file);
if (fwrite(spriteFileData, spriteFileHeader.total_size, 1, file) != 1) {
fclose(file);
return false;
}
}
SDL_RWclose(file);
fclose(file);
return true;
}

View File

@ -31,6 +31,7 @@
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

View File

@ -15,6 +15,7 @@
#pragma endregion
#include <memory>
#include "../Context.h"
#include "../core/Console.hpp"
#include "../core/Exception.hpp"
#include "../core/FileStream.hpp"
@ -25,6 +26,7 @@
#include "../interface/window.h"
#include "../network/network.h"
#include "../OpenRCT2.h"
#include "../ui/UiContext.h"
#include "Config.h"
#include "IniReader.hpp"
#include "IniWriter.hpp"
@ -38,6 +40,9 @@ extern "C"
#include "../scenario/scenario.h"
}
using namespace OpenRCT2;
using namespace OpenRCT2::Ui;
namespace Config
{
#pragma region Enums
@ -155,6 +160,7 @@ namespace Config
model->window_height = reader->GetSint32("window_height", -1);
model->window_snap_proximity = reader->GetSint32("window_snap_proximity", 5);
model->window_width = reader->GetSint32("window_width", -1);
model->default_display = reader->GetSint32("default_display", 0);
model->drawing_engine = reader->GetEnum<sint32>("drawing_engine", DRAWING_ENGINE_SOFTWARE, Enum_DrawingEngine);
model->uncap_fps = reader->GetBoolean("uncap_fps", false);
@ -228,6 +234,7 @@ namespace Config
writer->WriteSint32("window_height", model->window_height);
writer->WriteSint32("window_snap_proximity", model->window_snap_proximity);
writer->WriteSint32("window_width", model->window_width);
writer->WriteSint32("default_display", model->default_display);
writer->WriteEnum<sint32>("drawing_engine", model->drawing_engine, Enum_DrawingEngine);
writer->WriteBoolean("uncap_fps", model->uncap_fps);
writer->WriteBoolean("test_unfinished_tracks", model->test_unfinished_tracks);
@ -688,7 +695,8 @@ extern "C"
}
while (1)
{
platform_show_messagebox("OpenRCT2 needs files from the original RollerCoaster Tycoon 2 in order to work. Please select the directory where you installed RollerCoaster Tycoon 2.");
IUiContext * uiContext = GetContext()->GetUiContext();
uiContext->ShowMessageBox("OpenRCT2 needs files from the original RollerCoaster Tycoon 2 in order to work. Please select the directory where you installed RollerCoaster Tycoon 2.");
utf8 * installPath = platform_open_directory_browser("Please select your RCT2 directory");
if (installPath == nullptr)
{
@ -703,9 +711,8 @@ extern "C"
return true;
}
utf8 message[MAX_PATH];
snprintf(message, MAX_PATH, "Could not find %s" PATH_SEPARATOR "Data" PATH_SEPARATOR "g1.dat at this path", installPath);
platform_show_messagebox(message);
std::string message = String::StdFormat("Could not find %s" PATH_SEPARATOR "Data" PATH_SEPARATOR "g1.dat at this path", installPath);
uiContext->ShowMessageBox(message);
}
}
return true;

View File

@ -25,6 +25,7 @@ typedef struct GeneralConfiguration
utf8 * rct2_path;
// Display
sint32 default_display;
sint32 window_width;
sint32 window_height;
sint32 fullscreen_mode;

View File

@ -0,0 +1,67 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include "../common.h"
namespace OpenRCT2
{
/**
* Represents a registration of some service which when deleted will be
* unregistered.
*/
interface IRegistration
{
virtual ~IRegistration() = default;
};
class Registration
{
private:
/**
* Class which can wrap a function in an IRegistration.
*/
template<typename T>
struct CallbackRegistration : public IRegistration
{
private:
T _callback;
public:
CallbackRegistration(T callback) :
_callback(callback)
{
}
virtual ~CallbackRegistration() override
{
_callback();
}
};
public:
/**
* Creates a new IRegistration which when deleted, calls the given
* function.
*/
template<typename T>
static IRegistration * Create(T unregisterCallback)
{
return new CallbackRegistration<T>(unregisterCallback);
}
};
}

View File

@ -47,6 +47,30 @@ namespace String
return returnValue;
}
std::string ToUtf8(const std::wstring &s)
{
std::string result;
utf8 * cstr = widechar_to_utf8(s.c_str());
if (cstr != nullptr)
{
result = std::string(cstr);
}
free(cstr);
return result;
}
std::wstring ToUtf16(const std::string &s)
{
std::wstring result;
wchar_t * wcstr = utf8_to_widechar(s.c_str());
if (wcstr != nullptr)
{
result = std::wstring(wcstr);
}
free(wcstr);
return result;
}
bool IsNullOrEmpty(const utf8 * str)
{
return str == nullptr || str[0] == '\0';

View File

@ -24,8 +24,10 @@ namespace String
{
constexpr const utf8 * Empty = "";
std::string ToStd(const utf8 * str);
std::string StdFormat(const utf8 * format, ...);
std::string ToStd(const utf8 * str);
std::string StdFormat(const utf8 * format, ...);
std::string ToUtf8(const std::wstring &s);
std::wstring ToUtf16(const std::string &s);
bool IsNullOrEmpty(const utf8 * str);
sint32 Compare(const std::string &a, const std::string &b, bool ignoreCase = false);

View File

@ -18,24 +18,28 @@
#include "../common.h"
extern "C" {
extern "C"
{
#include "drawing.h"
}
interface IDrawingEngine;
interface IDrawingContext
namespace OpenRCT2 { namespace Drawing
{
virtual ~IDrawingContext() { }
interface IDrawingEngine;
virtual IDrawingEngine * GetEngine() abstract;
interface IDrawingContext
{
virtual ~IDrawingContext() { }
virtual void Clear(uint8 paletteIndex) abstract;
virtual void FillRect(uint32 colour, sint32 left, sint32 top, sint32 right, sint32 bottom) abstract;
virtual void FilterRect(FILTER_PALETTE_ID palette, sint32 left, sint32 top, sint32 right, sint32 bottom) abstract;
virtual void DrawLine(uint32 colour, sint32 x1, sint32 y1, sint32 x2, sint32 y2) abstract;
virtual void DrawSprite(uint32 image, sint32 x, sint32 y, uint32 tertiaryColour) abstract;
virtual void DrawSpriteRawMasked(sint32 x, sint32 y, uint32 maskImage, uint32 colourImage) abstract;
virtual void DrawSpriteSolid(uint32 image, sint32 x, sint32 y, uint8 colour) abstract;
virtual void DrawGlyph(uint32 image, sint32 x, sint32 y, uint8 * palette) abstract;
};
virtual OpenRCT2::Drawing::IDrawingEngine * GetEngine() abstract;
virtual void Clear(uint8 paletteIndex) abstract;
virtual void FillRect(uint32 colour, sint32 left, sint32 top, sint32 right, sint32 bottom) abstract;
virtual void FilterRect(FILTER_PALETTE_ID palette, sint32 left, sint32 top, sint32 right, sint32 bottom) abstract;
virtual void DrawLine(uint32 colour, sint32 x1, sint32 y1, sint32 x2, sint32 y2) abstract;
virtual void DrawSprite(uint32 image, sint32 x, sint32 y, uint32 tertiaryColour) abstract;
virtual void DrawSpriteRawMasked(sint32 x, sint32 y, uint32 maskImage, uint32 colourImage) abstract;
virtual void DrawSpriteSolid(uint32 image, sint32 x, sint32 y, uint8 colour) abstract;
virtual void DrawGlyph(uint32 image, sint32 x, sint32 y, uint8 * palette) abstract;
};
} }

View File

@ -18,14 +18,13 @@
#include "../common.h"
#include <SDL_video.h>
enum DRAWING_ENGINE
{
DRAWING_ENGINE_NONE = -1,
DRAWING_ENGINE_SOFTWARE,
DRAWING_ENGINE_SOFTWARE_WITH_HARDWARE_DISPLAY,
DRAWING_ENGINE_OPENGL,
DRAWING_ENGINE_COUNT,
};
enum DRAWING_ENGINE_FLAGS
@ -41,47 +40,46 @@ enum DRAWING_ENGINE_FLAGS
#ifdef __cplusplus
struct rct_drawpixelinfo;
interface IDrawingContext;
struct rct_palette_entry;
struct SDL_Window;
interface IDrawingEngine
namespace OpenRCT2 { namespace Drawing
{
virtual ~IDrawingEngine() { }
interface IDrawingContext;
virtual void Initialise(SDL_Window * window) abstract;
virtual void Resize(uint32 width, uint32 height) abstract;
virtual void SetPalette(SDL_Color * colours) abstract;
interface IDrawingEngine
{
virtual ~IDrawingEngine() { }
virtual void SetUncappedFrameRate(bool uncapped) abstract;
virtual void Initialise(SDL_Window * window) abstract;
virtual void Resize(uint32 width, uint32 height) abstract;
virtual void SetPalette(const rct_palette_entry * colours) abstract;
virtual void Invalidate(sint32 left, sint32 top, sint32 right, sint32 bottom) abstract;
virtual void Draw() abstract;
virtual void CopyRect(sint32 x, sint32 y, sint32 width, sint32 height, sint32 dx, sint32 dy) abstract;
virtual sint32 Screenshot() abstract;
virtual void SetUncappedFrameRate(bool uncapped) abstract;
virtual IDrawingContext * GetDrawingContext(rct_drawpixelinfo * dpi) abstract;
virtual rct_drawpixelinfo * GetDrawingPixelInfo() abstract;
virtual void Invalidate(sint32 left, sint32 top, sint32 right, sint32 bottom) abstract;
virtual void Draw() abstract;
virtual void CopyRect(sint32 x, sint32 y, sint32 width, sint32 height, sint32 dx, sint32 dy) abstract;
virtual sint32 Screenshot() abstract;
virtual DRAWING_ENGINE_FLAGS GetFlags() abstract;
virtual IDrawingContext * GetDrawingContext(rct_drawpixelinfo * dpi) abstract;
virtual rct_drawpixelinfo * GetDrawingPixelInfo() abstract;
virtual void InvalidateImage(uint32 image) abstract;
};
virtual DRAWING_ENGINE_FLAGS GetFlags() abstract;
namespace DrawingEngineFactory
{
IDrawingEngine * CreateSoftware();
IDrawingEngine * CreateSoftwareWithHardwareDisplay();
IDrawingEngine * CreateOpenGL();
}
virtual void InvalidateImage(uint32 image) abstract;
};
interface IRainDrawer
{
virtual ~IRainDrawer() { }
virtual void Draw(sint32 x,
sint32 y,
sint32 width,
sint32 height,
sint32 xStart,
sint32 yStart) abstract;
};
interface IRainDrawer
{
virtual ~IRainDrawer() { }
virtual void Draw(sint32 x,
sint32 y,
sint32 width,
sint32 height,
sint32 xStart,
sint32 yStart) abstract;
};
} }
#endif

View File

@ -14,7 +14,11 @@
*****************************************************************************/
#pragma endregion
#include <stdexcept>
#include "../Context.h"
#include "../ui/UiContext.h"
#include "../core/Exception.hpp"
#include "../core/Registration.hpp"
#include "IDrawingContext.h"
#include "IDrawingEngine.h"
#include "NewDrawing.h"
@ -29,8 +33,12 @@ extern "C"
#include "../rct2.h"
}
static sint32 _drawingEngineType = DRAWING_ENGINE_SOFTWARE;
static IDrawingEngine * _drawingEngine = nullptr;
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
using namespace OpenRCT2::Ui;
static sint32 _drawingEngineType = DRAWING_ENGINE_SOFTWARE;
static IDrawingEngine * _drawingEngine = nullptr;
extern "C"
{
@ -69,20 +77,11 @@ extern "C"
{
assert(_drawingEngine == nullptr);
IDrawingEngine * drawingEngine = nullptr;
_drawingEngineType = gConfigGeneral.drawing_engine;
switch (_drawingEngineType) {
case DRAWING_ENGINE_SOFTWARE:
drawingEngine = DrawingEngineFactory::CreateSoftware();
break;
case DRAWING_ENGINE_SOFTWARE_WITH_HARDWARE_DISPLAY:
drawingEngine = DrawingEngineFactory::CreateSoftwareWithHardwareDisplay();
break;
case DRAWING_ENGINE_OPENGL:
drawingEngine = DrawingEngineFactory::CreateOpenGL();
break;
}
IContext * context = GetContext();
IUiContext * uiContext = context->GetUiContext();
IDrawingEngine * drawingEngine = uiContext->CreateDrawingEngine((DRAWING_ENGINE_TYPE)_drawingEngineType);
if (drawingEngine == nullptr)
{
if (_drawingEngineType == DRAWING_ENGINE_SOFTWARE)
@ -105,7 +104,7 @@ extern "C"
{
try
{
drawingEngine->Initialise(gWindow);
drawingEngine->Initialise(uiContext->GetWindow());
drawingEngine->SetUncappedFrameRate(gConfigGeneral.uncap_fps == 1);
_drawingEngine = drawingEngine;
}
@ -140,10 +139,12 @@ extern "C"
{
drawing_engine_init();
}
_drawingEngine->Resize(gScreenWidth, gScreenHeight);
IUiContext * uiContext = GetContext()->GetUiContext();
_drawingEngine->Resize(uiContext->GetWidth(), uiContext->GetHeight());
}
void drawing_engine_set_palette(SDL_Color * colours)
void drawing_engine_set_palette(const rct_palette_entry * colours)
{
if (_drawingEngine != nullptr)
{

View File

@ -16,8 +16,6 @@
#pragma once
#include <SDL_video.h>
#ifdef __cplusplus
extern "C"
{
@ -32,7 +30,7 @@ sint32 drawing_engine_get_type();
bool drawing_engine_requires_restart(sint32 srcEngine, sint32 dstEngine);
void drawing_engine_init();
void drawing_engine_resize();
void drawing_engine_set_palette(SDL_Color * colours);
void drawing_engine_set_palette(const rct_palette_entry * colours);
void drawing_engine_draw();
void drawing_engine_copy_rect(sint32 x, sint32 y, sint32 width, sint32 height, sint32 dx, sint32 dy);
void drawing_engine_dispose();

View File

@ -26,6 +26,8 @@ extern "C"
#include "Rain.h"
#include "../core/Math.hpp"
using namespace OpenRCT2::Drawing;
typedef void (* DrawRainFunc)(IRainDrawer * rainDrawer, sint32 left, sint32 top, sint32 width, sint32 height);
static void DrawLightRain(IRainDrawer * rainDrawer, sint32 left, sint32 top, sint32 width, sint32 height);

View File

@ -18,6 +18,9 @@
#include "../common.h"
interface IRainDrawer;
namespace OpenRCT2 { namespace Drawing
{
interface IRainDrawer;
} }
void DrawRain(rct_drawpixelinfo * dpi, IRainDrawer * rainDrawer);
void DrawRain(rct_drawpixelinfo * dpi, OpenRCT2::Drawing::IRainDrawer * rainDrawer);

View File

@ -15,6 +15,7 @@
#pragma endregion
#include "../common.h"
#include "../Context.h"
#include "../core/Guard.hpp"
#include "../interface/window.h"
#include "../localisation/localisation.h"
@ -546,7 +547,7 @@ void load_palette(){
*/
void gfx_invalidate_screen()
{
gfx_set_dirty_blocks(0, 0, gScreenWidth, gScreenHeight);
gfx_set_dirty_blocks(0, 0, context_get_width(), context_get_height());
}
/**

View File

@ -253,6 +253,7 @@ typedef struct rct_palette {
extern sint16 gCurrentFontSpriteBase;
extern uint16 gCurrentFontFlags;
extern rct_palette_entry gPalette[256];
extern uint8 gGamePalette[256 * 4];
extern uint32 gPaletteEffectFrame;
extern const FILTER_PALETTE_ID GlassPaletteIds[COLOUR_COUNT];
@ -337,7 +338,7 @@ void FASTCALL gfx_draw_sprite_raw_masked_software(rct_drawpixelinfo *dpi, sint32
// string
sint32 clip_text(char *buffer, sint32 width);
sint32 gfx_wrap_string(char* buffer, sint32 width, sint32* num_lines, sint32* font_height);
sint32 gfx_get_string_width(char *buffer);
sint32 gfx_get_string_width(const utf8 * buffer);
sint32 gfx_get_string_width_new_lined(char* buffer);
void gfx_draw_string(rct_drawpixelinfo *dpi, char *buffer, sint32 colour, sint32 x, sint32 y);
void gfx_draw_string_left(rct_drawpixelinfo *dpi, rct_string_id format, void *args, sint32 colour, sint32 x, sint32 y);

View File

@ -16,21 +16,26 @@
#include <memory>
#include "../common.h"
#include "../config/Config.h"
#include "../Context.h"
#include "../core/File.h"
#include "../core/FileStream.hpp"
#include "../core/Memory.hpp"
#include "../core/Util.hpp"
#include "../OpenRCT2.h"
#include "../sprites.h"
#include "../ui/UiContext.h"
extern "C"
{
#include "../config/Config.h"
#include "../rct2/addresses.h"
#include "../util/util.h"
#include "drawing.h"
}
using namespace OpenRCT2;
using namespace OpenRCT2::Ui;
extern "C"
{
static void * _g1Buffer = nullptr;
@ -116,7 +121,8 @@ extern "C"
log_fatal("Unable to load g1 graphics");
if (!gOpenRCT2Headless)
{
platform_show_messagebox("Unable to load g1.dat. Your RollerCoaster Tycoon 2 path may be incorrectly set.");
IUiContext * uiContext = GetContext()->GetUiContext();
uiContext->ShowMessageBox("Unable to load g1.dat. Your RollerCoaster Tycoon 2 path may be incorrectly set.");
}
return false;
}
@ -174,7 +180,8 @@ extern "C"
log_fatal("Unable to load g2 graphics");
if (!gOpenRCT2Headless)
{
platform_show_messagebox("Unable to load g2.dat");
IUiContext * uiContext = GetContext()->GetUiContext();
uiContext->ShowMessageBox("Unable to load g2.dat");
}
}
return false;

View File

@ -101,7 +101,7 @@ sint32 gfx_get_string_width_new_lined(utf8 *text)
* rct2: 0x006C2321
* buffer (esi)
*/
sint32 gfx_get_string_width(char* buffer)
sint32 gfx_get_string_width(const utf8 * buffer)
{
return ttf_get_string_width(buffer);
}

View File

@ -17,6 +17,7 @@
#include "audio/audio.h"
#include "cheats.h"
#include "config/Config.h"
#include "Context.h"
#include "editor.h"
#include "game.h"
#include "input.h"
@ -138,7 +139,7 @@ void game_create_windows()
window_main_open();
window_top_toolbar_open();
window_game_bottom_toolbar_open();
window_resize_gui(gScreenWidth, gScreenHeight);
window_resize_gui(context_get_width(), context_get_height());
}
enum {

View File

@ -17,6 +17,7 @@
#include <SDL_keycode.h>
#include "audio/audio.h"
#include "config/Config.h"
#include "Context.h"
#include "game.h"
#include "input.h"
#include "interface/chat.h"
@ -137,8 +138,10 @@ void game_handle_input()
if (_inputFlags & INPUT_FLAG_5) {
game_handle_input_mouse(x, y, state);
} else if (x != 0x80000000) {
x = clamp(0, x, gScreenWidth - 1);
y = clamp(0, y, gScreenHeight - 1);
sint32 screenWidth = context_get_width();
sint32 screenHeight = context_get_height();
x = clamp(0, x, screenWidth - 1);
y = clamp(0, y, screenHeight - 1);
game_handle_input_mouse(x, y, state);
process_mouse_over(x, y);
@ -158,8 +161,9 @@ static sint32 game_get_next_input(sint32 *x, sint32 *y)
{
rct_mouse_data *input = get_mouse_input();
if (input == NULL) {
*x = gCursorState.x;
*y = gCursorState.y;
const CursorState * cursorState = context_get_cursor_state();
*x = cursorState->x;
*y = cursorState->y;
return 0;
} else {
*x = input->x;
@ -199,7 +203,7 @@ static void input_scroll_drag_begin(sint32 x, sint32 y, rct_window* w, rct_widge
_ticksSinceDragStart = 0;
_dragScrollIndex = window_get_scroll_data_index(w, widgetIndex);
platform_hide_cursor();
context_hide_cursor();
}
/**
@ -240,7 +244,7 @@ static void input_scroll_drag_continue(sint32 x, sint32 y, rct_window* w)
sint32 fixedCursorPositionX = (sint32) ceilf(gInputDragLastX * gConfigGeneral.window_scale);
sint32 fixedCursorPositionY = (sint32) ceilf(gInputDragLastY * gConfigGeneral.window_scale);
platform_set_cursor_position(fixedCursorPositionX, fixedCursorPositionY);
context_set_cursor_position(fixedCursorPositionX, fixedCursorPositionY);
}
/**
@ -251,7 +255,7 @@ static void input_scroll_right(sint32 x, sint32 y, sint32 state)
{
rct_window* w = window_find_by_number(_dragWidget.window_classification, _dragWidget.window_number);
if (w == NULL) {
platform_show_cursor();
context_show_cursor();
_inputState = INPUT_STATE_RESET;
return;
}
@ -266,7 +270,7 @@ static void input_scroll_right(sint32 x, sint32 y, sint32 state)
break;
case MOUSE_STATE_RIGHT_RELEASE:
_inputState = INPUT_STATE_RESET;
platform_show_cursor();
context_show_cursor();
break;
}
}
@ -468,7 +472,7 @@ static void input_window_resize_begin(rct_window *w, rct_widgetindex widgetIndex
static void input_window_resize_continue(rct_window *w, sint32 x, sint32 y)
{
if (y < (sint32)gScreenHeight - 2) {
if (y < (sint32)context_get_height() - 2) {
sint32 dx, dy, targetWidth, targetHeight;
dx = x - gInputDragLastX;
dy = y - gInputDragLastY;
@ -501,8 +505,8 @@ static void input_viewport_drag_begin(rct_window *w, sint32 x, sint32 y)
_dragWidget.window_classification = w->classification;
_dragWidget.window_number = w->number;
_ticksSinceDragStart = 0;
platform_get_cursor_position(&gInputDragLastX, &gInputDragLastY);
platform_hide_cursor();
context_get_cursor_position(&gInputDragLastX, &gInputDragLastY);
context_hide_cursor();
// gInputFlags |= INPUT_FLAG_5;
}
@ -513,7 +517,7 @@ static void input_viewport_drag_continue()
rct_window *w;
rct_viewport *viewport;
platform_get_cursor_position(&newDragX, &newDragY);
context_get_cursor_position(&newDragX, &newDragY);
dx = newDragX - gInputDragLastX;
dy = newDragY - gInputDragLastY;
@ -529,7 +533,7 @@ static void input_viewport_drag_continue()
viewport = w->viewport;
_ticksSinceDragStart += gTicksSinceLastUpdate;
if (viewport == NULL) {
platform_show_cursor();
context_show_cursor();
_inputState = INPUT_STATE_RESET;
} else if (dx != 0 || dy != 0) {
if (!(w->flags & WF_NO_SCROLLING)) {
@ -551,13 +555,13 @@ static void input_viewport_drag_continue()
}
}
platform_set_cursor_position(gInputDragLastX, gInputDragLastY);
context_set_cursor_position(gInputDragLastX, gInputDragLastY);
}
static void input_viewport_drag_end()
{
_inputState = INPUT_STATE_RESET;
platform_show_cursor();
context_show_cursor();
}
#pragma endregion
@ -1013,7 +1017,7 @@ static void input_widget_left(sint32 x, sint32 y, rct_window *w, rct_widgetindex
break;
default:
if (widget_is_enabled(w, widgetIndex) && !widget_is_disabled(w, widgetIndex)) {
audio_play_sound_panned(SOUND_CLICK_1, w->x + (widget->left + widget->right) / 2, 0, 0, 0);
audio_play_sound(SOUND_CLICK_1, 0, w->x + ((widget->left + widget->right) / 2));
// Set new cursor down widget
gPressedWidget.window_classification = windowClass;
@ -1267,7 +1271,7 @@ void input_state_widget_pressed(sint32 x, sint32 y, sint32 state, rct_widgetinde
break;
sint32 mid_point_x = (widget->left + widget->right) / 2 + w->x;
audio_play_sound_panned(5, mid_point_x, 0, 0, 0);
audio_play_sound(SOUND_CLICK_2, 0, mid_point_x);
if (cursor_w_class != w->classification || cursor_w_number != w->number || widgetIndex != cursor_widgetIndex)
break;
@ -1410,14 +1414,15 @@ void title_handle_keyboard_input()
if (!gConsoleOpen) {
// Handle modifier keys and key scrolling
gInputPlaceObjectModifier = PLACE_OBJECT_MODIFIER_NONE;
if (gKeysState[SDL_SCANCODE_LSHIFT] || gKeysState[SDL_SCANCODE_RSHIFT])
const uint8 * keysState = context_get_keys_state();
if (keysState[SDL_SCANCODE_LSHIFT] || keysState[SDL_SCANCODE_RSHIFT])
gInputPlaceObjectModifier |= PLACE_OBJECT_MODIFIER_SHIFT_Z;
if (gKeysState[SDL_SCANCODE_LCTRL] || gKeysState[SDL_SCANCODE_RCTRL])
if (keysState[SDL_SCANCODE_LCTRL] || keysState[SDL_SCANCODE_RCTRL])
gInputPlaceObjectModifier |= PLACE_OBJECT_MODIFIER_COPY_Z;
if (gKeysState[SDL_SCANCODE_LALT] || gKeysState[SDL_SCANCODE_RALT])
if (keysState[SDL_SCANCODE_LALT] || keysState[SDL_SCANCODE_RALT])
gInputPlaceObjectModifier |= 4;
#ifdef __MACOSX__
if (gKeysState[SDL_SCANCODE_LGUI] || gKeysState[SDL_SCANCODE_RGUI]) {
if (keysState[SDL_SCANCODE_LGUI] || keysState[SDL_SCANCODE_RGUI]) {
gInputPlaceObjectModifier |= 8;
}
#endif
@ -1429,7 +1434,7 @@ void title_handle_keyboard_input()
// Reserve backtick for console
if (key == SDL_SCANCODE_GRAVE) {
if ((gConfigGeneral.debugging_tools && !platform_is_input_active()) || gConsoleOpen) {
if ((gConfigGeneral.debugging_tools && !context_is_input_active()) || gConsoleOpen) {
window_cancel_textbox();
console_toggle();
}
@ -1475,17 +1480,18 @@ void game_handle_keyboard_input()
// Handle modifier keys and key scrolling
gInputPlaceObjectModifier = PLACE_OBJECT_MODIFIER_NONE;
if (gKeysState[SDL_SCANCODE_LSHIFT] || gKeysState[SDL_SCANCODE_RSHIFT]) {
const uint8 * keysState = context_get_keys_state();
if (keysState[SDL_SCANCODE_LSHIFT] || keysState[SDL_SCANCODE_RSHIFT]) {
gInputPlaceObjectModifier |= PLACE_OBJECT_MODIFIER_SHIFT_Z;
}
if (gKeysState[SDL_SCANCODE_LCTRL] || gKeysState[SDL_SCANCODE_RCTRL]) {
if (keysState[SDL_SCANCODE_LCTRL] || keysState[SDL_SCANCODE_RCTRL]) {
gInputPlaceObjectModifier |= PLACE_OBJECT_MODIFIER_COPY_Z;
}
if (gKeysState[SDL_SCANCODE_LALT] || gKeysState[SDL_SCANCODE_RALT]) {
if (keysState[SDL_SCANCODE_LALT] || keysState[SDL_SCANCODE_RALT]) {
gInputPlaceObjectModifier |= 4;
}
#ifdef __MACOSX__
if (gKeysState[SDL_SCANCODE_LGUI] || gKeysState[SDL_SCANCODE_RGUI]) {
if (keysState[SDL_SCANCODE_LGUI] || keysState[SDL_SCANCODE_RGUI]) {
gInputPlaceObjectModifier |= 8;
}
#endif
@ -1500,7 +1506,7 @@ void game_handle_keyboard_input()
// Reserve backtick for console
if (key == SDL_SCANCODE_GRAVE) {
if ((gConfigGeneral.debugging_tools && !platform_is_input_active()) || gConsoleOpen) {
if ((gConfigGeneral.debugging_tools && !context_is_input_active()) || gConsoleOpen) {
window_cancel_textbox();
console_toggle();
}
@ -1535,10 +1541,10 @@ void game_handle_keyboard_input()
*/
sint32 get_next_key()
{
sint32 i;
for (i = 0; i < 221; i++) {
if (gKeysPressed[i]) {
gKeysPressed[i] = 0;
uint8 * keysPressed = (uint8 *)context_get_keys_pressed();
for (sint32 i = 0; i < 221; i++) {
if (keysPressed[i]) {
keysPressed[i] = 0;
return i;
}
}
@ -1557,7 +1563,7 @@ void sub_6ED990(uint8 cursor_id)
if (_inputState == INPUT_STATE_RESIZING) {
cursor_id = CURSOR_DIAGONAL_ARROWS;
}
cursors_setcurrentcursor(cursor_id);
context_setcurrentcursor(cursor_id);
}
@ -1607,24 +1613,23 @@ void game_handle_edge_scroll()
return;
if (mainWindow->viewport == NULL)
return;
uint32 window_flags = SDL_GetWindowFlags(gWindow);
if ((window_flags & SDL_WINDOW_INPUT_FOCUS) == 0)
if (!context_has_focus())
return;
scrollX = 0;
scrollY = 0;
// Scroll left / right
if (gCursorState.x == 0)
const CursorState * cursorState = context_get_cursor_state();
if (cursorState->x == 0)
scrollX = -1;
else if (gCursorState.x >= gScreenWidth - 1)
else if (cursorState->x >= context_get_width() - 1)
scrollX = 1;
// Scroll up / down
if (gCursorState.y == 0)
if (cursorState->y == 0)
scrollY = -1;
else if (gCursorState.y >= gScreenHeight - 1)
else if (cursorState->y >= context_get_height() - 1)
scrollY = 1;
// Scroll viewport
@ -1660,25 +1665,26 @@ void game_handle_key_scroll()
scrollX = 0;
scrollY = 0;
const uint8 * keysState = context_get_keys_state();
for (sint32 shortcutId = SHORTCUT_SCROLL_MAP_UP; shortcutId <= SHORTCUT_SCROLL_MAP_RIGHT; shortcutId++) {
uint16 shortcutKey = gShortcutKeys[shortcutId];
uint8 scancode = shortcutKey & 0xFF;
if (shortcutKey == 0xFFFF) continue;
if (!gKeysState[scancode]) continue;
if (!keysState[scancode]) continue;
if (shortcutKey & SHIFT) {
if (!gKeysState[SDL_SCANCODE_LSHIFT] && !gKeysState[SDL_SCANCODE_RSHIFT]) continue;
if (!keysState[SDL_SCANCODE_LSHIFT] && !keysState[SDL_SCANCODE_RSHIFT]) continue;
}
if (shortcutKey & CTRL) {
if (!gKeysState[SDL_SCANCODE_LCTRL] && !gKeysState[SDL_SCANCODE_RCTRL]) continue;
if (!keysState[SDL_SCANCODE_LCTRL] && !keysState[SDL_SCANCODE_RCTRL]) continue;
}
if (shortcutKey & ALT) {
if (!gKeysState[SDL_SCANCODE_LALT] && !gKeysState[SDL_SCANCODE_RALT]) continue;
if (!keysState[SDL_SCANCODE_LALT] && !keysState[SDL_SCANCODE_RALT]) continue;
}
#ifdef __MACOSX__
if (shortcutKey & CMD) {
if (!gKeysState[SDL_SCANCODE_LGUI] && !gKeysState[SDL_SCANCODE_RGUI]) continue;
if (!keysState[SDL_SCANCODE_LGUI] && !keysState[SDL_SCANCODE_RGUI]) continue;
}
#endif

View File

@ -1,116 +0,0 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <SDL_mouse.h>
#include "../core/Guard.hpp"
#include "Cursors.h"
namespace Cursors
{
constexpr sint32 CURSOR_WIDTH = 32;
constexpr sint32 CURSOR_HEIGHT = 32;
static SDL_Cursor * _loadedCursors[CURSOR_COUNT];
static bool _initialised = false;
static CURSOR_ID _currentCursor = CURSOR_UNDEFINED;
static SDL_Cursor * Create(const CursorData * cursorInfo)
{
SDL_Cursor * cursor = SDL_CreateCursor(
cursorInfo->Data,
cursorInfo->Mask,
CURSOR_WIDTH,
CURSOR_HEIGHT,
cursorInfo->HotSpot.X,
cursorInfo->HotSpot.Y);
return cursor;
}
void Initialise()
{
Guard::Assert(!_initialised, "Cursors have already been initialised.");
_initialised = true;
// Using system cursors
_loadedCursors[CURSOR_ARROW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
_loadedCursors[CURSOR_HAND_POINT] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
// Using custom cursors
for (size_t i = 0; i < CURSOR_COUNT; i++)
{
const CursorData * cursorData = GetCursorData((CURSOR_ID)i);
if (cursorData != nullptr)
{
_loadedCursors[i] = Create(cursorData);
}
}
_currentCursor = CURSOR_UNDEFINED;
SetCurrentCursor(CURSOR_ARROW);
}
void Dispose()
{
if (_initialised)
{
for (size_t i = 0; i < CURSOR_COUNT; i++)
{
SDL_FreeCursor(_loadedCursors[i]);
_loadedCursors[i] = nullptr;
}
_currentCursor = CURSOR_UNDEFINED;
_initialised = false;
}
}
CURSOR_ID GetCurrentCursor()
{
return _currentCursor;
}
void SetCurrentCursor(CURSOR_ID cursorId)
{
if (_currentCursor != cursorId)
{
SDL_Cursor * cursor = _loadedCursors[cursorId];
SDL_SetCursor(cursor);
_currentCursor = cursorId;
}
}
}
extern "C"
{
void cursors_initialise()
{
Cursors::Initialise();
}
void cursors_dispose()
{
Cursors::Dispose();
}
sint32 cursors_getcurrentcursor()
{
return Cursors::GetCurrentCursor();
}
void cursors_setcurrentcursor(sint32 cursorId)
{
Cursors::SetCurrentCursor((CURSOR_ID)cursorId);
}
}

View File

@ -53,7 +53,7 @@ enum CURSOR_ID
#ifdef __cplusplus
namespace Cursors
namespace OpenRCT2 { namespace Ui
{
struct CursorData
{
@ -65,27 +65,6 @@ namespace Cursors
uint8 Data[32 * 4];
uint8 Mask[32 * 4];
};
const CursorData * GetCursorData(CURSOR_ID cursorId);
void Initialise();
void Dispose();
CURSOR_ID GetCurrentCursor();
void SetCurrentCursor(CURSOR_ID cursorId);
}
} }
#endif
#ifdef __cplusplus
extern "C"
{
#endif
void cursors_initialise();
void cursors_dispose();
sint32 cursors_getcurrentcursor();
void cursors_setcurrentcursor(sint32 cursorId);
#ifdef __cplusplus
}
#endif

View File

@ -16,6 +16,7 @@
#include "../audio/audio.h"
#include "../audio/AudioMixer.h"
#include "../Context.h"
#include "../interface/themes.h"
#include "../localisation/localisation.h"
#include "../network/network.h"
@ -25,17 +26,18 @@
#include "chat.h"
bool gChatOpen = false;
char _chatCurrentLine[CHAT_MAX_MESSAGE_LENGTH];
char _chatHistory[CHAT_HISTORY_SIZE][CHAT_INPUT_SIZE];
uint32 _chatHistoryTime[CHAT_HISTORY_SIZE];
uint32 _chatHistoryIndex = 0;
uint32 _chatCaretTicks = 0;
sint32 _chatLeft;
sint32 _chatTop;
sint32 _chatRight;
sint32 _chatBottom;
sint32 _chatWidth;
sint32 _chatHeight;
static char _chatCurrentLine[CHAT_MAX_MESSAGE_LENGTH];
static char _chatHistory[CHAT_HISTORY_SIZE][CHAT_INPUT_SIZE];
static uint32 _chatHistoryTime[CHAT_HISTORY_SIZE];
static uint32 _chatHistoryIndex = 0;
static uint32 _chatCaretTicks = 0;
static sint32 _chatLeft;
static sint32 _chatTop;
static sint32 _chatRight;
static sint32 _chatBottom;
static sint32 _chatWidth;
static sint32 _chatHeight;
static TextInputSession * _chatTextInputSession;
static const char* chat_history_get(uint32 index);
static uint32 chat_history_get_time(uint32 index);
@ -44,13 +46,13 @@ static void chat_clear_input();
void chat_open()
{
gChatOpen = true;
platform_start_text_input(_chatCurrentLine, sizeof(_chatCurrentLine));
_chatTextInputSession = context_start_text_input(_chatCurrentLine, sizeof(_chatCurrentLine));
}
void chat_close()
{
gChatOpen = false;
platform_stop_text_input();
context_stop_text_input();
}
void chat_toggle()
@ -82,9 +84,9 @@ void chat_draw(rct_drawpixelinfo * dpi)
}
_chatLeft = 10;
_chatRight = min((gScreenWidth - 10), CHAT_MAX_WINDOW_WIDTH);
_chatRight = min((context_get_width() - 10), CHAT_MAX_WINDOW_WIDTH);
_chatWidth = _chatRight - _chatLeft;
_chatBottom = gScreenHeight - 45;
_chatBottom = context_get_height() - 45;
_chatTop = _chatBottom - 10;
char lineBuffer[CHAT_INPUT_SIZE + 10];
@ -160,8 +162,8 @@ void chat_draw(rct_drawpixelinfo * dpi)
// TODO: Show caret if the input text has multiple lines
if (_chatCaretTicks < 15 && gfx_get_string_width(lineBuffer) < (_chatWidth - 10)) {
memcpy(lineBuffer, _chatCurrentLine, gTextInput.selection_offset);
lineBuffer[gTextInput.selection_offset] = 0;
memcpy(lineBuffer, _chatCurrentLine, _chatTextInputSession->SelectionStart);
lineBuffer[_chatTextInputSession->SelectionStart] = 0;
sint32 caretX = x + gfx_get_string_width(lineBuffer);
sint32 caretY = y + 14;

View File

@ -18,6 +18,7 @@
#include <SDL_scancode.h>
#include "../config/Config.h"
#include "../Context.h"
#include "../drawing/drawing.h"
#include "../game.h"
#include "../input.h"
@ -63,6 +64,7 @@ static sint32 _consoleCaretTicks;
static utf8 _consolePrintfBuffer[CONSOLE_BUFFER_2_SIZE];
static utf8 _consoleErrorBuffer[CONSOLE_BUFFER_2_SIZE];
static sint32 _consoleScrollPos = 0;
static TextInputSession * _consoleTextInputSession;
static utf8 _consoleHistory[CONSOLE_HISTORY_SIZE][CONSOLE_INPUT_SIZE];
static sint32 _consoleHistoryIndex = 0;
@ -91,14 +93,14 @@ void console_open()
_consoleScrollPos = 0;
console_refresh_caret();
console_update_scroll();
platform_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine));
_consoleTextInputSession = context_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine));
}
void console_close()
{
gConsoleOpen = false;
console_invalidate();
platform_stop_text_input();
context_stop_text_input();
}
void console_toggle()
@ -125,7 +127,7 @@ void console_update()
_consoleLeft = 0;
_consoleTop = 0;
_consoleRight = gScreenWidth;
_consoleRight = context_get_width();
_consoleBottom = 322;
if (gConsoleOpen) {
@ -235,8 +237,8 @@ void console_draw(rct_drawpixelinfo *dpi)
// Draw caret
if (_consoleCaretTicks < 15) {
memcpy(lineBuffer, _consoleCurrentLine, gTextInput.selection_offset);
lineBuffer[gTextInput.selection_offset] = 0;
memcpy(lineBuffer, _consoleCurrentLine, _consoleTextInputSession->SelectionStart);
lineBuffer[_consoleTextInputSession->SelectionStart] = 0;
sint32 caretX = x + gfx_get_string_width(lineBuffer);
sint32 caretY = y + lineHeight;
@ -270,15 +272,17 @@ void console_input(sint32 c)
_consoleHistoryIndex--;
memcpy(_consoleCurrentLine, _consoleHistory[_consoleHistoryIndex], 256);
}
textinputbuffer_recalculate_length(&gTextInput);
gTextInput.selection_offset = strlen(_consoleCurrentLine);
_consoleTextInputSession->Size = strlen(_consoleTextInputSession->Buffer);
_consoleTextInputSession->Length = utf8_length(_consoleTextInputSession->Buffer);
_consoleTextInputSession->SelectionStart = strlen(_consoleCurrentLine);
break;
case SDL_SCANCODE_DOWN:
if (_consoleHistoryIndex < _consoleHistoryCount - 1) {
_consoleHistoryIndex++;
memcpy(_consoleCurrentLine, _consoleHistory[_consoleHistoryIndex], 256);
textinputbuffer_recalculate_length(&gTextInput);
gTextInput.selection_offset = strlen(_consoleCurrentLine);
_consoleTextInputSession->Size = strlen(_consoleTextInputSession->Buffer);
_consoleTextInputSession->Length = utf8_length(_consoleTextInputSession->Buffer);
_consoleTextInputSession->SelectionStart = strlen(_consoleCurrentLine);
} else {
_consoleHistoryIndex = _consoleHistoryCount;
console_clear_input();
@ -412,7 +416,7 @@ static void console_clear_input()
{
_consoleCurrentLine[0] = 0;
if (gConsoleOpen) {
platform_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine));
context_start_text_input(_consoleCurrentLine, sizeof(_consoleCurrentLine));
}
}
@ -1005,7 +1009,7 @@ static sint32 cc_set(const utf8 **argv, sint32 argc)
gConfigGeneral.window_scale = clamp(newScale, 0.5f, 5.0f);
config_save_default();
gfx_invalidate_screen();
platform_trigger_resize();
context_trigger_resize();
console_execute_silent("get window_scale");
}
else if (strcmp(argv[0], "window_limit") == 0 && invalidArguments(&invalidArgs, int_valid[0])) {

View File

@ -16,6 +16,7 @@
#include "../audio/audio.h"
#include "../config/Config.h"
#include "../Context.h"
#include "../drawing/drawing.h"
#include "../game.h"
#include "../Imaging.h"
@ -46,7 +47,7 @@ void screenshot_check()
screenshotIndex = screenshot_dump();
if (screenshotIndex != -1) {
audio_play_sound(SOUND_WINDOW_OPEN, 100, gScreenWidth / 2);
audio_play_sound(SOUND_WINDOW_OPEN, 100, context_get_width() / 2);
} else {
window_error_open(STR_SCREENSHOT_FAILED, STR_NONE);
}
@ -58,13 +59,7 @@ void screenshot_check()
static void screenshot_get_rendered_palette(rct_palette* palette) {
for (sint32 i = 0; i < 256; i++) {
const SDL_Color *renderedEntry = &gPalette[i];
rct_palette_entry *entry = &palette->entries[i];
entry->red = renderedEntry->r;
entry->green = renderedEntry->g;
entry->blue = renderedEntry->b;
entry->alpha = renderedEntry->a;
palette->entries[i] = gPalette[i];
}
}
@ -288,7 +283,7 @@ sint32 cmdline_for_screenshot(const char **argv, sint32 argc)
}
gOpenRCT2Headless = true;
if (openrct2_initialise()) {
// if (openrct2_initialise()) {
drawing_engine_init();
rct2_open_file(inputPath);
@ -373,7 +368,7 @@ sint32 cmdline_for_screenshot(const char **argv, sint32 argc)
free(dpi.bits);
drawing_engine_dispose();
}
openrct2_dispose();
// }
// openrct2_dispose();
return 1;
}

View File

@ -15,6 +15,7 @@
#pragma endregion
#include "../config/Config.h"
#include "../Context.h"
#include "../drawing/drawing.h"
#include "../game.h"
#include "../input.h"
@ -424,8 +425,8 @@ static void viewport_move(sint16 x, sint16 y, rct_window* w, rct_viewport* viewp
if (w->flags & WF_7){
sint32 left = max(viewport->x, 0);
sint32 top = max(viewport->y, 0);
sint32 right = min(viewport->x + viewport->width, gScreenWidth);
sint32 bottom = min(viewport->y + viewport->height, gScreenHeight);
sint32 right = min(viewport->x + viewport->width, context_get_width());
sint32 bottom = min(viewport->y + viewport->height, context_get_height());
if (left >= right) return;
if (top >= bottom) return;
@ -447,7 +448,7 @@ static void viewport_move(sint16 x, sint16 y, rct_window* w, rct_viewport* viewp
viewport->x = 0;
}
sint32 eax = viewport->x + viewport->width - gScreenWidth;
sint32 eax = viewport->x + viewport->width - context_get_width();
if (eax > 0){
viewport->width -= eax;
viewport->view_width -= eax * zoom;
@ -465,7 +466,7 @@ static void viewport_move(sint16 x, sint16 y, rct_window* w, rct_viewport* viewp
viewport->y = 0;
}
eax = viewport->y + viewport->height - gScreenHeight;
eax = viewport->y + viewport->height - context_get_height();
if (eax > 0){
viewport->height -= eax;
viewport->view_height -= eax * zoom;

View File

@ -22,6 +22,7 @@
#include "../platform/platform.h"
#include "../localisation/localisation.h"
#include "../util/util.h"
#include "../Context.h"
#include <math.h>
@ -1104,15 +1105,15 @@ static void widget_text_box_draw(rct_drawpixelinfo *dpi, rct_window *w, rct_widg
// Make a copy of the string for measuring the width.
char temp_string[TEXT_INPUT_SIZE] = { 0 };
memcpy(temp_string, wrapped_string, min(string_length, gTextInput.selection_offset));
memcpy(temp_string, wrapped_string, min(string_length, gTextInput->SelectionStart));
sint32 cur_x = l + gfx_get_string_width(temp_string) + 3;
sint32 width = 6;
if ((uint32)gTextInput.selection_offset < strlen(gTextBoxInput)){
if ((uint32)gTextInput->SelectionStart < strlen(gTextBoxInput)){
// Make a new 1 character wide string for measuring the width
// of the character that the cursor is under.
temp_string[1] = '\0';
temp_string[0] = gTextBoxInput[gTextInput.selection_offset];
temp_string[0] = gTextBoxInput[gTextInput->SelectionStart];
width = max(gfx_get_string_width(temp_string) - 2, 4);
}

View File

@ -15,6 +15,7 @@
#pragma endregion
#include "../audio/audio.h"
#include "../Context.h"
#include "../core/Guard.hpp"
#include "../drawing/drawing.h"
#include "../editor.h"
@ -47,6 +48,7 @@ char gTextBoxInput[TEXT_INPUT_SIZE] = { 0 };
sint32 gMaxTextBoxInputLength = 0;
sint32 gTextBoxFrameNo = 0;
bool gUsingWidgetTextBox = 0;
TextInputSession * gTextInput;
uint16 gWindowUpdateTicks;
uint8 gToolbarDirtyFlags;
@ -295,7 +297,7 @@ static bool window_other_wheel_input(rct_window *w, rct_widgetindex widgetIndex,
static void window_all_wheel_input()
{
// Get wheel value
sint32 raw = gCursorState.wheel;
sint32 raw = context_get_cursor_state()->wheel;
sint32 wheel = 0;
while (1) {
raw -= 120;
@ -311,14 +313,17 @@ static void window_all_wheel_input()
wheel += 17;
}
raw -= 120;
gCursorState.wheel = raw;
// TODO do something about this hack
CursorState * cursorState = (CursorState *)context_get_cursor_state();
cursorState->wheel = raw;
if (wheel == 0)
return;
// Check window cursor is over
if (!(input_test_flag(INPUT_FLAG_5))) {
rct_window *w = window_find_from_point(gCursorState.x, gCursorState.y);
rct_window *w = window_find_from_point(cursorState->x, cursorState->y);
if (w != NULL) {
// Check if main window
if (w->classification == WC_MAIN_WINDOW || w->classification == WC_VIEWPORT) {
@ -327,7 +332,7 @@ static void window_all_wheel_input()
}
// Check scroll view, cursor is over
rct_widgetindex widgetIndex = window_find_widget_from_point(w, gCursorState.x, gCursorState.y);
rct_widgetindex widgetIndex = window_find_widget_from_point(w, cursorState->x, cursorState->y);
if (widgetIndex != -1) {
rct_widget *widget = &w->widgets[widgetIndex];
if (widget->type == WWT_SCROLL) {
@ -451,7 +456,7 @@ rct_window *window_create(sint32 x, sint32 y, sint32 width, sint32 height, rct_w
// Play sounds and flash the window
if (!(flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))){
w->flags |= WF_WHITE_BORDER_MASK;
audio_play_sound_panned(SOUND_WINDOW_OPEN, x + (width / 2), 0, 0, 0);
audio_play_sound(SOUND_WINDOW_OPEN, 0, x + (width / 2));
}
w->number = 0;
@ -495,8 +500,8 @@ rct_window *window_create(sint32 x, sint32 y, sint32 width, sint32 height, rct_w
*/
static bool sub_6EA8EC(sint32 x, sint32 y, sint32 width, sint32 height)
{
uint16 screenWidth = gScreenWidth;
uint16 screenHeight = gScreenHeight;
uint16 screenWidth = context_get_width();
uint16 screenHeight = context_get_height();
sint32 unk;
unk = -(width / 4);
@ -522,8 +527,8 @@ static bool sub_6EA934(sint32 x, sint32 y, sint32 width, sint32 height)
{
if (x < 0) return false;
if (y < 28) return false;
if (x + width > gScreenWidth) return false;
if (y + height > gScreenHeight) return false;
if (x + width > context_get_width()) return false;
if (y + height > context_get_height()) return false;
return sub_6EA95D(x, y, width, height);
}
@ -564,8 +569,8 @@ static bool sub_6EA95D(sint32 x, sint32 y, sint32 width, sint32 height)
*/
rct_window *window_create_auto_pos(sint32 width, sint32 height, rct_window_event_list *event_handlers, rct_windowclass cls, uint16 flags)
{
uint16 screenWidth = gScreenWidth;
uint16 screenHeight = gScreenHeight;
uint16 screenWidth = context_get_width();
uint16 screenHeight = context_get_height();
// TODO dead code, looks like it is cascading the new window offset from an existing window
// we will have to re-implement this in our own way.
@ -574,11 +579,11 @@ rct_window *window_create_auto_pos(sint32 width, sint32 height, rct_window_event
// cls &= ~0x80;
// rct_window *w = window_find_by_number(0, 0);
// if (w != NULL) {
// if (w->x > -60 && w->x < gScreenWidth - 20) {
// if (w->y < gScreenHeight - 20) {
// if (w->x > -60 && w->x < screenWidth - 20) {
// if (w->y < screenHeight - 20) {
// sint32 x = w->x;
// if (w->x + width > gScreenWidth)
// x = gScreenWidth - 20 - width;
// if (w->x + width > screenWidth)
// x = screenWidth - 20 - width;
// sint32 y = w->y;
// return window_create(x + 10, y + 10, width, height, event_handlers, cls, flags);
// }
@ -684,12 +689,13 @@ foundSpace:
return window_create(x, y, width, height, event_handlers, cls, flags);
}
rct_window *window_create_centred(sint32 width, sint32 height, rct_window_event_list *event_handlers, rct_windowclass cls, uint16 flags)
rct_window * window_create_centred(sint32 width, sint32 height, rct_window_event_list *event_handlers, rct_windowclass cls, uint16 flags)
{
sint32 x, y;
sint32 screenWidth = context_get_width();
sint32 screenHeight = context_get_height();
x = (gScreenWidth - width) / 2;
y = max(28, (gScreenHeight - height) / 2);
sint32 x = (screenWidth - width) / 2;
sint32 y = max(28, (screenHeight - height) / 2);
return window_create(x, y, width, height, event_handlers, cls, flags);
}
@ -1228,7 +1234,7 @@ void window_push_others_right(rct_window* window)
continue;
window_invalidate(w);
if (window->x + window->width + 13 >= gScreenWidth)
if (window->x + window->width + 13 >= context_get_width())
continue;
uint16 push_amount = window->x + window->width - w->x + 3;
w->x += push_amount;
@ -1263,7 +1269,7 @@ void window_push_others_below(rct_window *w1)
continue;
// Check if there is room to push it down
if (w1->y + w1->height + 80 >= gScreenHeight)
if (w1->y + w1->height + 80 >= context_get_height())
continue;
// Invalidate the window's current area
@ -1471,7 +1477,7 @@ void window_viewport_get_map_coords_by_cursor(rct_window *w, sint16 *map_x, sint
{
// Get mouse position to offset against.
sint32 mouse_x, mouse_y;
platform_get_cursor_position_scaled(&mouse_x, &mouse_y);
context_get_cursor_position_scaled(&mouse_x, &mouse_y);
// Compute map coordinate by mouse position.
get_map_coordinates_from_pos(mouse_x, mouse_y, VIEWPORT_INTERACTION_MASK_NONE, map_x, map_y, NULL, NULL, NULL);
@ -1499,7 +1505,7 @@ void window_viewport_centre_tile_around_cursor(rct_window *w, sint16 map_x, sint
// Get mouse position to offset against.
sint32 mouse_x, mouse_y;
platform_get_cursor_position_scaled(&mouse_x, &mouse_y);
context_get_cursor_position_scaled(&mouse_x, &mouse_y);
// Rebase mouse position onto centre of window, and compensate for zoom level.
sint32 rebased_x = ((w->width >> 1) - mouse_x) * (1 << w->viewport->zoom),
@ -2397,7 +2403,7 @@ static void window_snap_right(rct_window *w, sint32 proximity)
leftMost = min(leftMost, w2->x);
}
screenWidth = gScreenWidth;
screenWidth = context_get_width();
if (screenWidth >= wLeftProximity && screenWidth <= wRightProximity)
leftMost = min(leftMost, screenWidth);
@ -2430,7 +2436,7 @@ static void window_snap_bottom(rct_window *w, sint32 proximity)
topMost = min(topMost, w2->y);
}
screenHeight = gScreenHeight;
screenHeight = context_get_height();
if (screenHeight >= wTopProximity && screenHeight <= wBottomProximity)
topMost = min(topMost, screenHeight);
@ -2443,7 +2449,7 @@ void window_move_and_snap(rct_window *w, sint32 newWindowX, sint32 newWindowY, s
sint32 originalX = w->x;
sint32 originalY = w->y;
newWindowY = clamp(29, newWindowY, gScreenHeight - 34);
newWindowY = clamp(29, newWindowY, context_get_height() - 34);
if (snapProximity > 0) {
w->x = newWindowX;
@ -2507,7 +2513,7 @@ void window_start_textbox(rct_window *call_w, rct_widgetindex call_widget, rct_s
// from crashing the game.
gTextBoxInput[maxLength - 1] = '\0';
platform_start_text_input(gTextBoxInput, maxLength);
context_start_text_input(gTextBoxInput, maxLength);
}
void window_cancel_textbox()
@ -2520,7 +2526,7 @@ void window_cancel_textbox()
window_event_textinput_call(w, gCurrentTextBox.widget_index, NULL);
gCurrentTextBox.window.classification = WC_NULL;
gCurrentTextBox.window.number = 0;
platform_stop_text_input();
context_stop_text_input();
gUsingWidgetTextBox = false;
widget_invalidate(w, gCurrentTextBox.widget_index);
gCurrentTextBox.widget_index = WWT_LAST;

Some files were not shown because too many files have changed in this diff Show More