Compare commits
179 Commits
v1.0.0
...
fix-androi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cca4a81e9 | ||
|
|
3924b6bba3 | ||
|
|
a1c1e03da0 | ||
|
|
ac0a044a32 | ||
|
|
4aae167bd8 | ||
|
|
2e5be31ed4 | ||
|
|
45278f66a5 | ||
|
|
2205077d36 | ||
|
|
67f966f7a9 | ||
|
|
463968d798 | ||
|
|
109dc6680c | ||
|
|
23c5f070fa | ||
|
|
fd9843f8dc | ||
|
|
443070226f | ||
|
|
db63d8a085 | ||
|
|
f203cfcc83 | ||
|
|
9ceb490f72 | ||
|
|
5b7a676b35 | ||
|
|
e50e26cf87 | ||
|
|
f59c072d68 | ||
|
|
79c396c1a0 | ||
|
|
9bb053b3ba | ||
|
|
3168c2ec09 | ||
|
|
cb9c72d1db | ||
|
|
7a455dcad5 | ||
|
|
cacbecb324 | ||
|
|
c521dcaf57 | ||
|
|
b89c06008b | ||
|
|
c20e40652d | ||
|
|
2fe74627bd | ||
|
|
9864a2551d | ||
|
|
dea8c98399 | ||
|
|
108a20ef27 | ||
|
|
0adc65054c | ||
|
|
d72a74f6c3 | ||
|
|
1d05bac7a1 | ||
|
|
980b40dcdd | ||
|
|
b1a8707fb2 | ||
|
|
382de999cc | ||
|
|
06618d9e21 | ||
|
|
1b6e29acc0 | ||
|
|
e5992779f3 | ||
|
|
d7bc8e4176 | ||
|
|
629978d7a6 | ||
|
|
039ad65557 | ||
|
|
26e5104a96 | ||
|
|
d39276fe3a | ||
|
|
2a17a07784 | ||
|
|
56d35bef0d | ||
|
|
676098dc5f | ||
|
|
857d3902bb | ||
|
|
7803378d82 | ||
|
|
893a116fb4 | ||
|
|
916bf40602 | ||
|
|
40d6348b09 | ||
|
|
1e5047cd48 | ||
|
|
7750006127 | ||
|
|
1b69c97ce4 | ||
|
|
b3431fadf7 | ||
|
|
2a0d0633f1 | ||
|
|
4ed86be78e | ||
|
|
db13317613 | ||
|
|
8deb855b12 | ||
|
|
24277424d2 | ||
|
|
c593eceaaf | ||
|
|
e3a811c83d | ||
|
|
89fd83012a | ||
|
|
e71447a3d3 | ||
|
|
b89d6dfd72 | ||
|
|
3878be0d09 | ||
|
|
21d550ad1e | ||
|
|
134ab451e3 | ||
|
|
25fc67fec1 | ||
|
|
a41780caa2 | ||
|
|
4eb5e39946 | ||
|
|
354b0812c9 | ||
|
|
bee34bfdf4 | ||
|
|
d31e367870 | ||
|
|
16f0be1a45 | ||
|
|
ebba548206 | ||
|
|
c9fa9a4765 | ||
|
|
cfca07f7f2 | ||
|
|
0f60556b73 | ||
|
|
a1521049b5 | ||
|
|
1128813bf0 | ||
|
|
1f6339b3b3 | ||
|
|
79f5e00f6e | ||
|
|
957c5af66b | ||
|
|
c5c5ecd499 | ||
|
|
df4382f2e0 | ||
|
|
8700d0c601 | ||
|
|
27c5beea01 | ||
|
|
52af5cfc1f | ||
|
|
5a47f74023 | ||
|
|
c35ea77c59 | ||
|
|
b10c580d6f | ||
|
|
7096116296 | ||
|
|
330edde003 | ||
|
|
cac96bfc13 | ||
|
|
31edb19379 | ||
|
|
d482f0e610 | ||
|
|
c3bffa6777 | ||
|
|
140234f40e | ||
|
|
13b76287f8 | ||
|
|
a55feb9301 | ||
|
|
d86a887cf9 | ||
|
|
5b2a1d13a1 | ||
|
|
1ca08cb97a | ||
|
|
ca78b94a7d | ||
|
|
dcb53393c1 | ||
|
|
4ece7d1188 | ||
|
|
87289a34c0 | ||
|
|
2859410d4b | ||
|
|
9d53496521 | ||
|
|
4a8d5b13a8 | ||
|
|
8ba4fa309d | ||
|
|
0cab26227a | ||
|
|
1694c68ebc | ||
|
|
6c74c461c2 | ||
|
|
0ec5306de5 | ||
|
|
ba2ae3c303 | ||
|
|
019dbbb56e | ||
|
|
a47918a83c | ||
|
|
25e07fb597 | ||
|
|
43ad927817 | ||
|
|
d7966cdab2 | ||
|
|
33bda1612c | ||
|
|
f3eef3fe6c | ||
|
|
d8ae5fbd32 | ||
|
|
02e83a1989 | ||
|
|
5c8f7c4b00 | ||
|
|
3592a7232c | ||
|
|
ef067cd954 | ||
|
|
17382d7c7e | ||
|
|
5170948588 | ||
|
|
3ccb087d20 | ||
|
|
3f9145b745 | ||
|
|
5b79d8ca79 | ||
|
|
25c1eeee5c | ||
|
|
f5060c301b | ||
|
|
8a2ca4b733 | ||
|
|
9610de0a66 | ||
|
|
64627ddde9 | ||
|
|
63bcb2d009 | ||
|
|
df5bceaf2a | ||
|
|
66d46bd8a5 | ||
|
|
28a433ea76 | ||
|
|
2c69834a12 | ||
|
|
00ee53efca | ||
|
|
4989cf6d5a | ||
|
|
0d5aa705f0 | ||
|
|
307b032118 | ||
|
|
ab8279078e | ||
|
|
b7ac80e684 | ||
|
|
6064a4bc79 | ||
|
|
e41a4b8e16 | ||
|
|
39057fd1fb | ||
|
|
cbdb0eeba4 | ||
|
|
73b6a16551 | ||
|
|
42376b7172 | ||
|
|
6a6d691b8d | ||
|
|
e457fc1383 | ||
|
|
cf844e6224 | ||
|
|
684d943062 | ||
|
|
23d37621d5 | ||
|
|
8bd75e9737 | ||
|
|
8ef9b66950 | ||
|
|
8a006d2818 | ||
|
|
a60382e044 | ||
|
|
a20e2a8b50 | ||
|
|
032969ce5b | ||
|
|
365805afe8 | ||
|
|
da75a76ed9 | ||
|
|
757330bff4 | ||
|
|
f3869e0acf | ||
|
|
cee3af7cc4 | ||
|
|
8ab7f8493a | ||
|
|
1e5cda5dde | ||
|
|
f1006d981c |
11
.gitattributes
vendored
@@ -2,14 +2,23 @@
|
||||
*.c text eol=lf
|
||||
*.cc text eol=lf
|
||||
*.cmake text eol=lf
|
||||
*.gradle text eol=lf
|
||||
*.h text eol=lf
|
||||
*.md text eol=lf
|
||||
*.java text eol=lf
|
||||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
*.plist text eol=lf
|
||||
*.pro text eol=lf
|
||||
*.properties text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.yml text eol=lf
|
||||
.clang-format text eol=lf
|
||||
.editorconfig text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.gitignore text eol=lf
|
||||
gradlew text eol=lf
|
||||
CMakeLists.txt text eol=lf
|
||||
LICENSE text eol=lf
|
||||
|
||||
# Force CRLF
|
||||
*.bat text eol=crlf
|
||||
|
||||
@@ -2,19 +2,12 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/Build.yml'
|
||||
- 'src/**.cc'
|
||||
- 'src/**.h'
|
||||
- '**/CMakeLists.txt'
|
||||
- '**/*.cmake'
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/Build.yml'
|
||||
- 'src/**.cc'
|
||||
- 'src/**.h'
|
||||
- '**/CMakeLists.txt'
|
||||
- '**/*.cmake'
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -38,6 +31,50 @@ jobs:
|
||||
- name: cppcheck
|
||||
run: cppcheck --std=c++17 src/
|
||||
|
||||
android:
|
||||
name: Android
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
cache: gradle
|
||||
|
||||
- name: Cache cmake build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: os/android/app/.cxx
|
||||
key: android-cmake-v1
|
||||
|
||||
- name: Setup signing config
|
||||
if: env.KEYSTORE_FILE_BASE64 != '' && env.KEYSTORE_PROPERTIES_FILE_BASE64 != ''
|
||||
run: |
|
||||
cd os/android
|
||||
echo "$KEYSTORE_FILE_BASE64" | base64 --decode > debug.keystore
|
||||
echo "$KEYSTORE_PROPERTIES_FILE_BASE64" | base64 --decode > debug-keystore.properties
|
||||
env:
|
||||
KEYSTORE_FILE_BASE64: ${{ secrets.ANDROID_DEBUG_KEYSTORE_FILE_BASE64 }}
|
||||
KEYSTORE_PROPERTIES_FILE_BASE64: ${{ secrets.ANDROID_DEBUG_KEYSTORE_PROPERTIES_FILE_BASE64 }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd os/android
|
||||
./gradlew assembleDebug
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: fallout2-ce-debug.apk
|
||||
path: os/android/app/build/outputs/apk/debug/app-debug.apk
|
||||
retention-days: 7
|
||||
|
||||
linux:
|
||||
name: Linux (${{ matrix.arch }})
|
||||
|
||||
@@ -93,7 +130,10 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --build build -j $(nproc)
|
||||
cmake \
|
||||
--build build \
|
||||
-j $(nproc) \
|
||||
# EOL
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -111,29 +151,32 @@ jobs:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Dependencies
|
||||
run: |
|
||||
brew install sdl2
|
||||
|
||||
- name: Cache cmake build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: build
|
||||
key: macos-cmake-v1
|
||||
key: macos-cmake-v3
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -B build -D CMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
cmake \
|
||||
-B build \
|
||||
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
# EOL
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --build build -j $(sysctl -n hw.physicalcpu) --target package
|
||||
cmake \
|
||||
--build build \
|
||||
-j $(sysctl -n hw.physicalcpu) \
|
||||
--target package \
|
||||
# EOL
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: fallout2-ce-macos.dmg
|
||||
path: build/_CPack_Packages/Darwin/DragNDrop/fallout2-ce/fallout2-ce.dmg
|
||||
path: build/fallout2-ce.dmg
|
||||
retention-days: 7
|
||||
|
||||
windows:
|
||||
57
.github/workflows/release.yml
vendored
@@ -10,6 +10,52 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
android:
|
||||
name: Android
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
cache: gradle
|
||||
|
||||
- name: Cache cmake build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: os/android/app/.cxx
|
||||
key: android-cmake-v1
|
||||
|
||||
- name: Setup signing config
|
||||
if: env.KEYSTORE_FILE_BASE64 != '' && env.KEYSTORE_PROPERTIES_FILE_BASE64 != ''
|
||||
run: |
|
||||
cd os/android
|
||||
echo "$KEYSTORE_FILE_BASE64" | base64 --decode > release.keystore
|
||||
echo "$KEYSTORE_PROPERTIES_FILE_BASE64" | base64 --decode > release-keystore.properties
|
||||
env:
|
||||
KEYSTORE_FILE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_FILE_BASE64 }}
|
||||
KEYSTORE_PROPERTIES_FILE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_PROPERTIES_FILE_BASE64 }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd os/android
|
||||
./gradlew assembleRelease
|
||||
|
||||
- name: Upload
|
||||
run: |
|
||||
cd os/android/app/build/outputs/apk/release
|
||||
cp app-release.apk fallout2-ce-android.apk
|
||||
gh release upload ${{ github.ref_name }} fallout2-ce-android.apk
|
||||
rm fallout2-ce-android.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
linux:
|
||||
name: Linux (${{ matrix.arch }})
|
||||
|
||||
@@ -65,7 +111,10 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --build build -j $(nproc)
|
||||
cmake \
|
||||
--build build \
|
||||
-j $(nproc) \
|
||||
# EOL
|
||||
|
||||
- name: Upload
|
||||
run: |
|
||||
@@ -85,10 +134,6 @@ jobs:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Dependencies
|
||||
run: |
|
||||
brew install sdl2
|
||||
|
||||
- name: Import code signing certificates
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
with:
|
||||
@@ -99,7 +144,7 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: build
|
||||
key: macos-cmake-v1
|
||||
key: macos-cmake-v3
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
|
||||
@@ -4,13 +4,24 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
|
||||
set(EXECUTABLE_NAME fallout2-ce)
|
||||
|
||||
if (APPLE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "")
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "")
|
||||
endif()
|
||||
|
||||
project(${EXECUTABLE_NAME})
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||
set(CMAKE_CXX_EXTENSIONS NO)
|
||||
|
||||
add_executable(${EXECUTABLE_NAME} WIN32 MACOSX_BUNDLE
|
||||
if (ANDROID)
|
||||
add_library(${EXECUTABLE_NAME} SHARED)
|
||||
else()
|
||||
add_executable(${EXECUTABLE_NAME} WIN32 MACOSX_BUNDLE)
|
||||
endif()
|
||||
|
||||
target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
"src/actions.cc"
|
||||
"src/actions.h"
|
||||
"src/animation.cc"
|
||||
@@ -159,6 +170,8 @@ add_executable(${EXECUTABLE_NAME} WIN32 MACOSX_BUNDLE
|
||||
"src/palette.h"
|
||||
"src/party_member.cc"
|
||||
"src/party_member.h"
|
||||
"src/pcx.cc"
|
||||
"src/pcx.h"
|
||||
"src/perk_defs.h"
|
||||
"src/perk.cc"
|
||||
"src/perk.h"
|
||||
@@ -226,8 +239,8 @@ add_executable(${EXECUTABLE_NAME} WIN32 MACOSX_BUNDLE
|
||||
"src/window.h"
|
||||
"src/word_wrap.cc"
|
||||
"src/word_wrap.h"
|
||||
"src/world_map.cc"
|
||||
"src/world_map.h"
|
||||
"src/worldmap.cc"
|
||||
"src/worldmap.h"
|
||||
"src/xfile.cc"
|
||||
"src/xfile.h"
|
||||
)
|
||||
@@ -263,19 +276,31 @@ if(WIN32)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
"os/windows/fallout2-ce.ico"
|
||||
"os/windows/fallout2-ce.rc"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_sources(${EXECUTABLE_NAME} PUBLIC "os/macos/fallout2-ce.icns")
|
||||
set_source_files_properties("os/macos/fallout2-ce.icns" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||
|
||||
set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/macos/Info.plist")
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.alexbatalov.fallout2-ce")
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "${EXECUTABLE_NAME}")
|
||||
set(MACOSX_BUNDLE_ICON_FILE "fallout2-ce.icns")
|
||||
set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout 2")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0")
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "1.1.0")
|
||||
set(MACOSX_BUNDLE_BUNDLE_VERSION "1.1.0")
|
||||
endif()
|
||||
|
||||
add_subdirectory("third_party/fpattern")
|
||||
target_link_libraries(${EXECUTABLE_NAME} ${FPATTERN_LIBRARY})
|
||||
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${FPATTERN_INCLUDE_DIR})
|
||||
|
||||
if(NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
add_subdirectory("third_party/zlib")
|
||||
add_subdirectory("third_party/sdl2")
|
||||
else()
|
||||
|
||||
51
LICENSE.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Sustainable Use License
|
||||
|
||||
Version 1.0
|
||||
|
||||
## Acceptance
|
||||
|
||||
By using the software, you agree to all of the terms and conditions below.
|
||||
|
||||
## Copyright License
|
||||
|
||||
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.
|
||||
|
||||
## Limitations
|
||||
|
||||
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law.
|
||||
|
||||
## Patents
|
||||
|
||||
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
||||
|
||||
## Notices
|
||||
|
||||
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software.
|
||||
|
||||
## No Other Rights
|
||||
|
||||
These terms do not imply any licenses other than those expressly granted in these terms.
|
||||
|
||||
## Termination
|
||||
|
||||
If you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently.
|
||||
|
||||
## No Liability
|
||||
|
||||
As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
|
||||
|
||||
## Definitions
|
||||
|
||||
The "licensor" is the entity offering these terms.
|
||||
|
||||
The "software" is the software the licensor makes available under these terms, including any portion of it.
|
||||
|
||||
"You" refers to the individual or entity agreeing to these terms.
|
||||
|
||||
"Your company" is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
||||
|
||||
"Your license" is the license granted to you for the software under these terms.
|
||||
|
||||
"Use" means anything you do with the software requiring your license.
|
||||
|
||||
"Trademark" means trademarks, service marks, and similar rights.
|
||||
39
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## Installation
|
||||
|
||||
You must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2) or [Steam](https://store.steampowered.com/app/38410). Download latest build or build from source.
|
||||
You must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2) or [Steam](https://store.steampowered.com/app/38410). Download latest release or build from source.
|
||||
|
||||
### Windows
|
||||
|
||||
@@ -14,12 +14,6 @@ Download and copy `fallout2-ce.exe` to your `Fallout2` folder. It serves as a dr
|
||||
|
||||
- Download and copy `fallout2-ce` to this folder.
|
||||
|
||||
- Fix permissions (GitHub artifacts issue):
|
||||
|
||||
```console
|
||||
$ chmod a+x /home/john/Desktop/Fallout2/fallout2-ce
|
||||
```
|
||||
|
||||
- Install [SDL2](https://libsdl.org/download-2.0.php):
|
||||
|
||||
```console
|
||||
@@ -30,37 +24,34 @@ $ sudo apt install libsdl2-2.0-0
|
||||
|
||||
### macOS
|
||||
|
||||
> **NOTE**: macOS 11 or higher is required. The app is not universal. It should run on Apple Silicon under Rosetta 2, but I haven't tried it. The app is neither signed, nor notarized.
|
||||
> **NOTE**: macOS 10.11 (El Capitan) or higher is required. Runs natively on Intel-based Macs and Apple Silicon.
|
||||
|
||||
- Use Windows installation as a base - it contains data assets needed to play. Copy `Fallout2` folder somewhere, for example `/Applications/Fallout2`.
|
||||
|
||||
- Alternatively you can use Fallout 2 from Macplay/The Omni Group as a base - you need to extract game assets from the original bundle. Mount CD/DMG, right click `Fallout 2` -> `Show Package Contents`, navigate to `Contents/Resources`. Copy `GameData` folder somewhere, for example `/Applications/Fallout2`.
|
||||
|
||||
- Download and copy `fallout2-ce.app` to this folder.
|
||||
|
||||
- Run `fallout2-ce.app`.
|
||||
|
||||
- When running for the first time, macOS will complain that the app is not signed and you'll be present two options - `Move to bin`, and `Cancel`. Click `Cancel`, open `System Preferences`, go to `Security & Privacy`. The pane at the bottom will say `fallout2-ce was blocked from use because it is not from an identified developer`. Click `Open Anyway`. Confirm once again. Alternatively you can remove quarantine attribute from terminal:
|
||||
### Android
|
||||
|
||||
```console
|
||||
$ xattr -d com.apple.quarantine /Applications/Fallout2/fallout2-ce.app
|
||||
```
|
||||
> **NOTE**: Fallout 2 was designed with mouse in mind. There are many controls that require precise cursor positioning, which is not possible with fingers. When playing on Android you'll use fingers to move mouse cursor, not a character, or a map. Double tap to "click" left mouse button in the current cursor position, triple tap to "click" right mouse button. It might feel awkward at first, but it's super handy - you can play with just a thumb. This is not set in stone and might change in the future.
|
||||
|
||||
- Use Windows installation as a base - it contains data assets needed to play. Copy `Fallout2` folder to your device, for example to `Downloads`. You need `master.dat`, `critter.dat`, `patch000.dat`, and `data` folder.
|
||||
|
||||
- Download `fallout2-ce.apk` and copy it to your device. Open it with file explorer, follow instructions (install from unknown source).
|
||||
|
||||
- When you run the game for the first time it will immediately present file picker. Select the folder from the first step. Wait until this data is copied. A loading dialog will appear, just wait for about 30 seconds. The game will start automatically.
|
||||
|
||||
## Contributing
|
||||
|
||||
For now there are three major areas.
|
||||
Integrating Sfall goodies is the top priority. Quality of life updates are OK too. Please no large scale refactorings at this time as we need to reconcile changes from Reference Edition, which will make this process slow and error-prone. In any case open up an issue with your suggestion or to notify other people that something is being worked on.
|
||||
|
||||
### Intergrating Sfall
|
||||
|
||||
There are literally hundreds if not thousands of fixes and features in sfall. I guess not all of them are needed in Community Edition, but for the sake of compatibility with big mods out there, let's integrate them all.
|
||||
|
||||
### SDL
|
||||
## License
|
||||
|
||||
Migrate DirectX stuff to SDL. This is the shortest path to native Linux version.
|
||||
|
||||
### Prepare to 64-bit
|
||||
|
||||
Modern macOS requires apps to be 64-bit, so even if we have SDL, the scripting part of the game will not work, because of builtin SSL interpreter. It stores pointers (both functions and variables) as 32-bit integers, so 64-bit pointers will not fit into stack. Since the stack is shared for both instructions and data, it needs some attention.
|
||||
|
||||
|
||||
## Legal & License
|
||||
|
||||
See [Fallout 2 Reference Edition](https://github.com/alexbatalov/fallout2-re). Same conditions apply until the source code in this repository is changed significantly.
|
||||
The source code is this repository is available under the [Sustainable Use License](LICENSE.md).
|
||||
|
||||
13
os/android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/.idea/assetWizardSettings.xml
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/workspace.xml
|
||||
/local.properties
|
||||
/debug-keystore.properties
|
||||
/debug.keystore
|
||||
/release-keystore.properties
|
||||
/release.keystore
|
||||
5
os/android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/.cxx
|
||||
/build
|
||||
|
||||
# TODO: Cleanup root .gitignore
|
||||
!/src/debug
|
||||
100
os/android/app/build.gradle
Normal file
@@ -0,0 +1,100 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.alexbatalov.fallout2ce'
|
||||
minSdk 21
|
||||
targetSdk 32
|
||||
versionCode 2
|
||||
versionName '1.1.0'
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_static'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
|
||||
// TODO: Remove once format issues are resolved.
|
||||
cppFlags '-Wno-format-security'
|
||||
|
||||
// Specify target library explicitly as there is a shared zlib,
|
||||
// that we don't need to be linked in.
|
||||
targets 'fallout2-ce'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
// Override default debug signing config to make sure every CI runner
|
||||
// uses the same key for painless updates.
|
||||
def debugKeystorePropertiesFile = rootProject.file('debug-keystore.properties')
|
||||
if (debugKeystorePropertiesFile.exists()) {
|
||||
def debugKeystoreProperties = new Properties()
|
||||
debugKeystoreProperties.load(new FileInputStream(debugKeystorePropertiesFile))
|
||||
|
||||
debug {
|
||||
storeFile rootProject.file(debugKeystoreProperties.getProperty('storeFile'))
|
||||
storePassword debugKeystoreProperties.getProperty('storePassword')
|
||||
keyAlias debugKeystoreProperties.getProperty('keyAlias')
|
||||
keyPassword debugKeystoreProperties.getProperty('keyPassword')
|
||||
}
|
||||
}
|
||||
|
||||
def releaseKeystoreProperties = new Properties()
|
||||
def releaseKeystorePropertiesFile = rootProject.file('release-keystore.properties')
|
||||
if (releaseKeystorePropertiesFile.exists()) {
|
||||
releaseKeystoreProperties.load(new FileInputStream(releaseKeystorePropertiesFile))
|
||||
|
||||
release {
|
||||
storeFile rootProject.file(releaseKeystoreProperties.getProperty('storeFile'))
|
||||
storePassword releaseKeystoreProperties.getProperty('storePassword')
|
||||
keyAlias releaseKeystoreProperties.getProperty('keyAlias')
|
||||
keyPassword releaseKeystoreProperties.getProperty('keyPassword')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
// Prevents signing keys clashes between debug and release versions
|
||||
// for painless development.
|
||||
applicationIdSuffix '.debug'
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
|
||||
// Release signing config is optional and might not be present in CI
|
||||
// builds, hence `findByName`.
|
||||
signingConfig signingConfigs.findByName('release')
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
tasks["merge${variant.name.capitalize()}Assets"]
|
||||
.dependsOn("externalNativeBuild${variant.name.capitalize()}")
|
||||
}
|
||||
|
||||
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
|
||||
sourceSets.main {
|
||||
jniLibs.srcDir 'libs'
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path '../../../CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
}
|
||||
21
os/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
3
os/android/app/src/debug/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Fallout 2 (Debug)</string>
|
||||
</resources>
|
||||
102
os/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.alexbatalov.fallout2ce"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0.0"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
<uses-feature android:glEsVersion="0x00020000" />
|
||||
|
||||
<!-- Touchscreen support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<!-- Game controller support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
|
||||
<!-- External mouse input events -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.type.pc"
|
||||
android:required="false" />
|
||||
|
||||
<!-- Audio recording support -->
|
||||
<!-- if you want to capture audio, uncomment this. -->
|
||||
<!-- <uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" /> -->
|
||||
|
||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" />
|
||||
|
||||
<!-- Allow access to Bluetooth devices -->
|
||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
|
||||
|
||||
<!-- Allow access to the vibrator -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<!-- if you want to capture audio, uncomment this. -->
|
||||
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
|
||||
|
||||
<!-- Create a Java class extending SDLActivity and place it in a
|
||||
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
|
||||
|
||||
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
|
||||
in the XML below.
|
||||
|
||||
An example Java class can be found in README-android.md
|
||||
-->
|
||||
<application android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:hardwareAccelerated="true" >
|
||||
|
||||
<!-- Example of setting SDL hints from AndroidManifest.xml:
|
||||
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
|
||||
-->
|
||||
|
||||
<activity android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||
android:preferMinimalPostProcessing="true"
|
||||
android:exported="true"
|
||||
android:screenOrientation="landscape"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- Let Android know that we can handle some USB devices and should receive this event -->
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
<!-- Drop file event -->
|
||||
<!--
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
-->
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ImportActivity"
|
||||
android:theme="@style/AppTheme">
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.alexbatalov.fallout2ce;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
static boolean copyRecursively(ContentResolver contentResolver, DocumentFile src, File dest) {
|
||||
final DocumentFile[] documentFiles = src.listFiles();
|
||||
for (final DocumentFile documentFile : documentFiles) {
|
||||
if (documentFile.isFile()) {
|
||||
if (!copyFile(contentResolver, documentFile, new File(dest, documentFile.getName()))) {
|
||||
return false;
|
||||
}
|
||||
} else if (documentFile.isDirectory()) {
|
||||
final File subdirectory = new File(dest, documentFile.getName());
|
||||
if (!subdirectory.exists()) {
|
||||
subdirectory.mkdir();
|
||||
}
|
||||
|
||||
if (!copyRecursively(contentResolver, documentFile, subdirectory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean copyFile(ContentResolver contentResolver, DocumentFile src, File dest) {
|
||||
try {
|
||||
final InputStream inputStream = contentResolver.openInputStream(src.getUri());
|
||||
final OutputStream outputStream = new FileOutputStream(dest);
|
||||
|
||||
final byte[] buffer = new byte[16384];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.alexbatalov.fallout2ce;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ImportActivity extends Activity {
|
||||
private static final int IMPORT_REQUEST_CODE = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
startActivityForResult(intent, IMPORT_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
||||
if (requestCode == IMPORT_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
final Uri treeUri = resultData.getData();
|
||||
if (treeUri != null) {
|
||||
final DocumentFile treeDocument = DocumentFile.fromTreeUri(this, treeUri);
|
||||
if (treeDocument != null) {
|
||||
copyFiles(treeDocument);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyFiles(DocumentFile treeDocument) {
|
||||
ProgressDialog dialog = createProgressDialog();
|
||||
dialog.show();
|
||||
|
||||
new Thread(() -> {
|
||||
ContentResolver contentResolver = getContentResolver();
|
||||
File externalFilesDir = getExternalFilesDir(null);
|
||||
FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
|
||||
|
||||
startMainActivity();
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void startMainActivity() {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private ProgressDialog createProgressDialog() {
|
||||
ProgressDialog progressDialog = new ProgressDialog(this,
|
||||
android.R.style.Theme_Material_Light_Dialog);
|
||||
progressDialog.setTitle(R.string.loading_dialog_title);
|
||||
progressDialog.setMessage(getString(R.string.loading_dialog_message));
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progressDialog.setCancelable(false);
|
||||
|
||||
return progressDialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.alexbatalov.fallout2ce;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class MainActivity extends SDLActivity {
|
||||
private boolean noExit = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final File externalFilesDir = getExternalFilesDir(null);
|
||||
|
||||
final File configFile = new File(externalFilesDir, "fallout2.cfg");
|
||||
if (!configFile.exists()) {
|
||||
final File masterDatFile = new File(externalFilesDir, "master.dat");
|
||||
final File critterDatFile = new File(externalFilesDir, "critter.dat");
|
||||
if (!masterDatFile.exists() || !critterDatFile.exists()) {
|
||||
final Intent intent = new Intent(this, ImportActivity.class);
|
||||
startActivity(intent);
|
||||
|
||||
noExit = true;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (!noExit) {
|
||||
// Needed to make sure libc calls exit handlers, which releases
|
||||
// in-game resources.
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[]{
|
||||
"fallout2-ce",
|
||||
};
|
||||
}
|
||||
}
|
||||
22
os/android/app/src/main/java/org/libsdl/app/HIDDevice.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
interface HIDDevice
|
||||
{
|
||||
public int getId();
|
||||
public int getVendorId();
|
||||
public int getProductId();
|
||||
public String getSerialNumber();
|
||||
public int getVersion();
|
||||
public String getManufacturerName();
|
||||
public String getProductName();
|
||||
public UsbDevice getDevice();
|
||||
public boolean open();
|
||||
public int sendFeatureReport(byte[] report);
|
||||
public int sendOutputReport(byte[] report);
|
||||
public boolean getFeatureReport(byte[] report);
|
||||
public void setFrozen(boolean frozen);
|
||||
public void close();
|
||||
public void shutdown();
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.os.*;
|
||||
|
||||
//import com.android.internal.util.HexDump;
|
||||
|
||||
import java.lang.Runnable;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
private HIDDeviceManager mManager;
|
||||
private BluetoothDevice mDevice;
|
||||
private int mDeviceId;
|
||||
private BluetoothGatt mGatt;
|
||||
private boolean mIsRegistered = false;
|
||||
private boolean mIsConnected = false;
|
||||
private boolean mIsChromebook = false;
|
||||
private boolean mIsReconnecting = false;
|
||||
private boolean mFrozen = false;
|
||||
private LinkedList<GattOperation> mOperations;
|
||||
GattOperation mCurrentOperation = null;
|
||||
private Handler mHandler;
|
||||
|
||||
private static final int TRANSPORT_AUTO = 0;
|
||||
private static final int TRANSPORT_BREDR = 1;
|
||||
private static final int TRANSPORT_LE = 2;
|
||||
|
||||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||
|
||||
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||
|
||||
static class GattOperation {
|
||||
private enum Operation {
|
||||
CHR_READ,
|
||||
CHR_WRITE,
|
||||
ENABLE_NOTIFICATION
|
||||
}
|
||||
|
||||
Operation mOp;
|
||||
UUID mUuid;
|
||||
byte[] mValue;
|
||||
BluetoothGatt mGatt;
|
||||
boolean mResult = true;
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
}
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// This is executed in main thread
|
||||
BluetoothGattCharacteristic chr;
|
||||
|
||||
switch (mOp) {
|
||||
case CHR_READ:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||
if (!mGatt.readCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case CHR_WRITE:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||
chr.setValue(mValue);
|
||||
if (!mGatt.writeCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case ENABLE_NOTIFICATION:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
||||
if (chr != null) {
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
int properties = chr.getProperties();
|
||||
byte[] value;
|
||||
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
||||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
||||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||
} else {
|
||||
Log.e(TAG, "Unable to start notifications on input characteristic");
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mGatt.setCharacteristicNotification(chr, true);
|
||||
cccd.setValue(value);
|
||||
if (!mGatt.writeDescriptor(cccd)) {
|
||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
mResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean finish() {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||
if (valveService == null)
|
||||
return null;
|
||||
return valveService.getCharacteristic(uuid);
|
||||
}
|
||||
|
||||
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
||||
}
|
||||
|
||||
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
||||
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
||||
}
|
||||
|
||||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||
mManager = manager;
|
||||
mDevice = device;
|
||||
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mIsRegistered = false;
|
||||
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
mOperations = new LinkedList<GattOperation>();
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
mGatt = connectGatt();
|
||||
// final HIDDeviceBLESteamController finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.checkConnectionForChromebookIssue();
|
||||
// }
|
||||
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("SteamController.%s", mDevice.getAddress());
|
||||
}
|
||||
|
||||
public BluetoothGatt getGatt() {
|
||||
return mGatt;
|
||||
}
|
||||
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
} else {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
}
|
||||
|
||||
private BluetoothGatt connectGatt() {
|
||||
return connectGatt(false);
|
||||
}
|
||||
|
||||
protected int getConnectionState() {
|
||||
|
||||
Context context = mManager.getContext();
|
||||
if (context == null) {
|
||||
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (btManager == null) {
|
||||
// This device doesn't support Bluetooth. We should never be here, because how did
|
||||
// we instantiate a device to start with?
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
|
||||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void checkConnectionForChromebookIssue() {
|
||||
if (!mIsChromebook) {
|
||||
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
||||
// over and over.
|
||||
return;
|
||||
}
|
||||
|
||||
int connectionState = getConnectionState();
|
||||
|
||||
switch (connectionState) {
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
if (!mIsConnected) {
|
||||
// We are in the Bad Chromebook Place. We can force a disconnect
|
||||
// to try to recover.
|
||||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
else if (!isRegistered()) {
|
||||
if (mGatt.getServices().size() > 0) {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
||||
probeService(this);
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
||||
break;
|
||||
}
|
||||
|
||||
final HIDDeviceBLESteamController finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.checkConnectionForChromebookIssue();
|
||||
}
|
||||
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
private boolean isRegistered() {
|
||||
return mIsRegistered;
|
||||
}
|
||||
|
||||
private void setRegistered() {
|
||||
mIsRegistered = true;
|
||||
}
|
||||
|
||||
private boolean probeService(HIDDeviceBLESteamController controller) {
|
||||
|
||||
if (isRegistered()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mIsConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.v(TAG, "probeService controller=" + controller);
|
||||
|
||||
for (BluetoothGattService service : mGatt.getServices()) {
|
||||
if (service.getUuid().equals(steamControllerService)) {
|
||||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||
|
||||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
Log.v(TAG, "Found input characteristic");
|
||||
// Start notifications
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
enableNotification(chr.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
||||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||
mIsConnected = false;
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void finishCurrentGattOperation() {
|
||||
GattOperation op = null;
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null) {
|
||||
op = mCurrentOperation;
|
||||
mCurrentOperation = null;
|
||||
}
|
||||
}
|
||||
if (op != null) {
|
||||
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
||||
|
||||
// Our operation failed, let's add it back to the beginning of our queue.
|
||||
if (!result) {
|
||||
mOperations.addFirst(op);
|
||||
}
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void executeNextGattOperation() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null)
|
||||
return;
|
||||
|
||||
if (mOperations.isEmpty())
|
||||
return;
|
||||
|
||||
mCurrentOperation = mOperations.removeFirst();
|
||||
}
|
||||
|
||||
// Run in main thread
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation == null) {
|
||||
Log.e(TAG, "Current operation null in executor?");
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentOperation.run();
|
||||
// now wait for the GATT callback and when it comes, finish this operation
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void queueGattOperation(GattOperation op) {
|
||||
synchronized (mOperations) {
|
||||
mOperations.add(op);
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void enableNotification(UUID chrUuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void writeCharacteristic(UUID uuid, byte[] value) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void readCharacteristic(UUID uuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////// BluetoothGattCallback overridden methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
||||
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
||||
mIsReconnecting = false;
|
||||
if (newState == 2) {
|
||||
mIsConnected = true;
|
||||
// Run directly, without GattOperation
|
||||
if (!isRegistered()) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mGatt.discoverServices();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (newState == 0) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
||||
}
|
||||
|
||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
||||
if (status == 0) {
|
||||
if (gatt.getServices().size() == 0) {
|
||||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||
mIsReconnecting = true;
|
||||
mIsConnected = false;
|
||||
gatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
else {
|
||||
probeService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
||||
// Only register controller with the native side once it has been fully configured
|
||||
if (!isRegistered()) {
|
||||
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
||||
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
||||
setRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
// Enable this for verbose logging of controller input reports
|
||||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||
|
||||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||
}
|
||||
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
boolean hasWrittenInputDescriptor = true;
|
||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||
if (reportChr != null) {
|
||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||
reportChr.setValue(enterValveMode);
|
||||
gatt.writeCharacteristic(reportChr);
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
||||
}
|
||||
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
||||
}
|
||||
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
//Log.v(TAG, "onMtuChanged status=" + status);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////// Public API
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
// Valve Corporation
|
||||
final int VALVE_USB_VID = 0x28DE;
|
||||
return VALVE_USB_VID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
||||
final int D0G_BLE2_PID = 0x1106;
|
||||
return D0G_BLE2_PID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
// This will be read later via feature report by Steam
|
||||
return "12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
return "Valve Corporation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
return "Steam Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We need to skip the first byte, as that doesn't go over the air
|
||||
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
||||
writeCharacteristic(reportCharacteristic, actual_report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
||||
writeCharacteristic(reportCharacteristic, report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "getFeatureReport");
|
||||
readCharacteristic(reportCharacteristic);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
|
||||
BluetoothGatt g = mGatt;
|
||||
if (g != null) {
|
||||
g.disconnect();
|
||||
g.close();
|
||||
mGatt = null;
|
||||
}
|
||||
mManager = null;
|
||||
mIsRegistered = false;
|
||||
mIsConnected = false;
|
||||
mOperations.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,679 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class HIDDeviceManager {
|
||||
private static final String TAG = "hidapi";
|
||||
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
||||
|
||||
private static HIDDeviceManager sManager;
|
||||
private static int sManagerRefCount = 0;
|
||||
|
||||
public static HIDDeviceManager acquire(Context context) {
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager = new HIDDeviceManager(context);
|
||||
}
|
||||
++sManagerRefCount;
|
||||
return sManager;
|
||||
}
|
||||
|
||||
public static void release(HIDDeviceManager manager) {
|
||||
if (manager == sManager) {
|
||||
--sManagerRefCount;
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager.close();
|
||||
sManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
||||
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
||||
private int mNextDeviceId = 0;
|
||||
private SharedPreferences mSharedPreferences = null;
|
||||
private boolean mIsChromebook = false;
|
||||
private UsbManager mUsbManager;
|
||||
private Handler mHandler;
|
||||
private BluetoothManager mBluetoothManager;
|
||||
private List<BluetoothDevice> mLastBluetoothDevices;
|
||||
|
||||
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceDetached(usbDevice);
|
||||
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device connected: " + device);
|
||||
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Bluetooth device was disconnected, remove from controller manager (if any)
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
||||
|
||||
disconnectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private HIDDeviceManager(final Context context) {
|
||||
mContext = context;
|
||||
|
||||
HIDDeviceRegisterCallback();
|
||||
|
||||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
|
||||
// if (shouldClear) {
|
||||
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
// spedit.clear();
|
||||
// spedit.commit();
|
||||
// }
|
||||
// else
|
||||
{
|
||||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||
}
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public int getDeviceIDForIdentifier(String identifier) {
|
||||
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
|
||||
int result = mSharedPreferences.getInt(identifier, 0);
|
||||
if (result == 0) {
|
||||
result = mNextDeviceId++;
|
||||
spedit.putInt("next_device_id", mNextDeviceId);
|
||||
}
|
||||
|
||||
spedit.putInt(identifier, result);
|
||||
spedit.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void initializeUSB() {
|
||||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||
if (mUsbManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// Logging
|
||||
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
||||
Log.i(TAG,"Path: " + device.getDeviceName());
|
||||
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
||||
Log.i(TAG,"Product: " + device.getProductName());
|
||||
Log.i(TAG,"ID: " + device.getDeviceId());
|
||||
Log.i(TAG,"Class: " + device.getDeviceClass());
|
||||
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
||||
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
||||
Log.i(TAG,"Product ID: " + device.getProductId());
|
||||
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
||||
Log.i(TAG,"---------------------------------------");
|
||||
|
||||
// Get interface details
|
||||
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
||||
UsbInterface mUsbInterface = device.getInterface(index);
|
||||
Log.i(TAG," ***** *****");
|
||||
Log.i(TAG," Interface index: " + index);
|
||||
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
||||
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
||||
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||
|
||||
// Get endpoint details
|
||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||
{
|
||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||
Log.i(TAG," ++++ ++++ ++++");
|
||||
Log.i(TAG," Endpoint index: " + epi);
|
||||
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
||||
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
||||
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
||||
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
||||
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
||||
Log.i(TAG," Type: " + mEndpoint.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.i(TAG," No more devices connected.");
|
||||
*/
|
||||
|
||||
// Register for USB broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||
|
||||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
}
|
||||
}
|
||||
|
||||
UsbManager getUSBManager() {
|
||||
return mUsbManager;
|
||||
}
|
||||
|
||||
private void shutdownUSB() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mUsbBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
||||
return true;
|
||||
}
|
||||
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB360_IFACE_SUBCLASS = 93;
|
||||
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
||||
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x0079, // GPD Win 2
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x046d, // Logitech
|
||||
0x056e, // Elecom
|
||||
0x06a3, // Saitek
|
||||
0x0738, // Mad Catz
|
||||
0x07ff, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x1038, // SteelSeries
|
||||
0x11c9, // Nacon
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
0x146b, // BigBen
|
||||
0x1532, // Razer Sabertooth
|
||||
0x15e4, // Numark
|
||||
0x162e, // Joytech
|
||||
0x1689, // Razer Onza
|
||||
0x1949, // Lab126, Inc.
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
};
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
||||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB1_IFACE_SUBCLASS = 71;
|
||||
final int XB1_IFACE_PROTOCOL = 208;
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, /* 8BitDo */
|
||||
0x2e24, // Hyperkin
|
||||
};
|
||||
|
||||
if (usbInterface.getId() == 0 &&
|
||||
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
||||
connectHIDDeviceUSB(usbDevice);
|
||||
}
|
||||
|
||||
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
||||
List<Integer> devices = new ArrayList<Integer>();
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
devices.add(device.getId());
|
||||
}
|
||||
}
|
||||
for (int id : devices) {
|
||||
HIDDevice device = mDevicesById.get(id);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
boolean opened = false;
|
||||
if (permission_granted) {
|
||||
opened = device.open();
|
||||
}
|
||||
HIDDeviceOpenResult(device.getId(), opened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
||||
synchronized (this) {
|
||||
int interface_mask = 0;
|
||||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||
// Check to see if we've already added this interface
|
||||
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||
int interface_id = usbInterface.getId();
|
||||
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||
continue;
|
||||
}
|
||||
interface_mask |= (1 << interface_id);
|
||||
|
||||
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
||||
int id = device.getId();
|
||||
mDevicesById.put(id, device);
|
||||
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBluetooth() {
|
||||
Log.d(TAG, "Initializing Bluetooth");
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find bonded bluetooth controllers and create SteamControllers for them
|
||||
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (mBluetoothManager == null) {
|
||||
// This device doesn't support Bluetooth.
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
||||
if (btAdapter == null) {
|
||||
// This device has Bluetooth support in the codebase, but has no available adapters.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get our bonded devices.
|
||||
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
||||
|
||||
Log.d(TAG, "Bluetooth device available: " + device);
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
||||
|
||||
if (mIsChromebook) {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
||||
|
||||
// final HIDDeviceManager finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.chromebookConnectionHandler();
|
||||
// }
|
||||
// }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownBluetooth() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mBluetoothBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
||||
// This function provides a sort of dummy version of that, watching for changes in the
|
||||
// connected devices and attempting to add controllers as things change.
|
||||
public void chromebookConnectionHandler() {
|
||||
if (!mIsChromebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||
|
||||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||
connected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
||||
if (!currentConnected.contains(bluetoothDevice)) {
|
||||
disconnected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
|
||||
mLastBluetoothDevices = currentConnected;
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : disconnected) {
|
||||
disconnectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : connected) {
|
||||
connectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
|
||||
final HIDDeviceManager finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.chromebookConnectionHandler();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
||||
synchronized (this) {
|
||||
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
||||
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
||||
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
device.reconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.put(bluetoothDevice, device);
|
||||
mDevicesById.put(id, device);
|
||||
|
||||
// The Steam Controller will mark itself connected once initialization is complete
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
synchronized (this) {
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
if (device == null)
|
||||
return;
|
||||
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.remove(bluetoothDevice);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
||||
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
||||
if (bluetoothDevice == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the device has no local name, we really don't want to try an equality check against it.
|
||||
if (bluetoothDevice.getName() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
shutdownUSB();
|
||||
shutdownBluetooth();
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.shutdown();
|
||||
}
|
||||
mDevicesById.clear();
|
||||
mBluetoothDevices.clear();
|
||||
HIDDeviceReleaseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFrozen(boolean frozen) {
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.setFrozen(frozen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private HIDDevice getDevice(int id) {
|
||||
synchronized (this) {
|
||||
HIDDevice result = mDevicesById.get(id);
|
||||
if (result == null) {
|
||||
Log.v(TAG, "No device for id: " + id);
|
||||
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////// JNI interface functions
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||
|
||||
if (usb) {
|
||||
initializeUSB();
|
||||
}
|
||||
if (bluetooth) {
|
||||
initializeBluetooth();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean openDevice(int deviceID) {
|
||||
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||
HIDDevice device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look to see if this is a USB device and we have permission to access it
|
||||
UsbDevice usbDevice = device.getDevice();
|
||||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||
HIDDeviceOpenPending(deviceID);
|
||||
try {
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||
HIDDeviceOpenResult(deviceID, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return device.open();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int sendOutputReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendOutputReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int sendFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean getFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
return device.getFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void closeDevice(int deviceID) {
|
||||
try {
|
||||
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return;
|
||||
}
|
||||
|
||||
device.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////// Native methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private native void HIDDeviceRegisterCallback();
|
||||
private native void HIDDeviceReleaseCallback();
|
||||
|
||||
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
||||
native void HIDDeviceOpenPending(int deviceID);
|
||||
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
||||
native void HIDDeviceDisconnected(int deviceID);
|
||||
|
||||
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
||||
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
||||
}
|
||||
309
os/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
Normal file
@@ -0,0 +1,309 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import java.util.Arrays;
|
||||
|
||||
class HIDDeviceUSB implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
|
||||
protected HIDDeviceManager mManager;
|
||||
protected UsbDevice mDevice;
|
||||
protected int mInterfaceIndex;
|
||||
protected int mInterface;
|
||||
protected int mDeviceId;
|
||||
protected UsbDeviceConnection mConnection;
|
||||
protected UsbEndpoint mInputEndpoint;
|
||||
protected UsbEndpoint mOutputEndpoint;
|
||||
protected InputThread mInputThread;
|
||||
protected boolean mRunning;
|
||||
protected boolean mFrozen;
|
||||
|
||||
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
||||
mManager = manager;
|
||||
mDevice = usbDevice;
|
||||
mInterfaceIndex = interface_index;
|
||||
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
||||
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
return mDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
return mDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
catch (SecurityException exception) {
|
||||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
result = mDevice.getManufacturerName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getVendorId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
result = mDevice.getProductName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getProductId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||||
if (mConnection == null) {
|
||||
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force claim our interface
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
if (!mConnection.claimInterface(iface, true)) {
|
||||
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the endpoints
|
||||
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||||
UsbEndpoint endpt = iface.getEndpoint(j);
|
||||
switch (endpt.getDirection()) {
|
||||
case UsbConstants.USB_DIR_IN:
|
||||
if (mInputEndpoint == null) {
|
||||
mInputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
case UsbConstants.USB_DIR_OUT:
|
||||
if (mOutputEndpoint == null) {
|
||||
mOutputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the required endpoints were present
|
||||
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||||
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening for input
|
||||
mRunning = true;
|
||||
mInputThread = new InputThread();
|
||||
mInputThread.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||||
0x09/*HID set_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||||
if (r != report.length) {
|
||||
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
/* Offset the return buffer by 1, so that the report ID
|
||||
will remain in byte 0. */
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||||
0x01/*HID get_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++res;
|
||||
++length;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
if (res == length) {
|
||||
data = report;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(report, 0, res);
|
||||
}
|
||||
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mRunning = false;
|
||||
if (mInputThread != null) {
|
||||
while (mInputThread.isAlive()) {
|
||||
mInputThread.interrupt();
|
||||
try {
|
||||
mInputThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Keep trying until we're done
|
||||
}
|
||||
}
|
||||
mInputThread = null;
|
||||
}
|
||||
if (mConnection != null) {
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
mConnection.releaseInterface(iface);
|
||||
mConnection.close();
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
mManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
protected class InputThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||||
byte[] packet = new byte[packetSize];
|
||||
while (mRunning) {
|
||||
int r;
|
||||
try
|
||||
{
|
||||
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||||
break;
|
||||
}
|
||||
if (r < 0) {
|
||||
// Could be a timeout or an I/O error
|
||||
}
|
||||
if (r > 0) {
|
||||
byte[] data;
|
||||
if (r == packetSize) {
|
||||
data = packet;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(packet, 0, r);
|
||||
}
|
||||
|
||||
if (!mFrozen) {
|
||||
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
os/android/app/src/main/java/org/libsdl/app/SDL.java
Normal file
@@ -0,0 +1,85 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
SDL library initialization
|
||||
*/
|
||||
public class SDL {
|
||||
|
||||
// This function should be called first and sets up the native code
|
||||
// so it can call into the Java classes
|
||||
public static void setupJNI() {
|
||||
SDLActivity.nativeSetupJNI();
|
||||
SDLAudioManager.nativeSetupJNI();
|
||||
SDLControllerManager.nativeSetupJNI();
|
||||
}
|
||||
|
||||
// This function should be called each time the activity is started
|
||||
public static void initialize() {
|
||||
setContext(null);
|
||||
|
||||
SDLActivity.initialize();
|
||||
SDLAudioManager.initialize();
|
||||
SDLControllerManager.initialize();
|
||||
}
|
||||
|
||||
// This function stores the current activity (SDL or not)
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
|
||||
if (libraryName == null) {
|
||||
throw new NullPointerException("No library name provided.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Let's see if we have ReLinker available in the project. This is necessary for
|
||||
// some projects that have huge numbers of local libraries bundled, and thus may
|
||||
// trip a bug in Android's native library loader which ReLinker works around. (If
|
||||
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
||||
// internally.)
|
||||
//
|
||||
// To use ReLinker, just add it as a dependency. For more information, see
|
||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||
//
|
||||
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
|
||||
|
||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||
// they've changed during updates.
|
||||
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
||||
Object relinkInstance = forceMethod.invoke(null);
|
||||
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
||||
|
||||
// Actually load the library!
|
||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
|
||||
}
|
||||
catch (final Throwable e) {
|
||||
// Fall back
|
||||
try {
|
||||
System.loadLibrary(libraryName);
|
||||
}
|
||||
catch (final UnsatisfiedLinkError ule) {
|
||||
throw ule;
|
||||
}
|
||||
catch (final SecurityException se) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Context mContext;
|
||||
}
|
||||
2477
os/android/app/src/main/java/org/libsdl/app/SDLActivity.java
Normal file
394
os/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java
Normal file
@@ -0,0 +1,394 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
public class SDLAudioManager
|
||||
{
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
}
|
||||
|
||||
// Audio
|
||||
|
||||
protected static String getAudioFormatString(int audioFormat) {
|
||||
switch (audioFormat) {
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
int channelConfig;
|
||||
int sampleSize;
|
||||
int frameSize;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||
|
||||
/* On older devices let's use known good settings */
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 : 21);
|
||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
}
|
||||
switch (audioFormat)
|
||||
{
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
sampleSize = 1;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
sampleSize = 2;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
sampleSize = 4;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
sampleSize = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isCapture) {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
break;
|
||||
case 7:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
} else {
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||
desiredChannels = 6;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Log.v(TAG, "Speaker configuration (and order of channels):");
|
||||
|
||||
if ((channelConfig & 0x00000004) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000008) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000010) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000020) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
||||
}
|
||||
if ((channelConfig & 0x00000040) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000080) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000100) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000200) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000400) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000800) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00001000) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
||||
}
|
||||
*/
|
||||
}
|
||||
frameSize = (sampleSize * desiredChannels);
|
||||
|
||||
// Let the user pick a larger buffer if they really want -- but ye
|
||||
// gods they probably shouldn't, the minimums are horrifyingly high
|
||||
// latency already
|
||||
int minBufferSize;
|
||||
if (isCapture) {
|
||||
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
} else {
|
||||
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
}
|
||||
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
||||
|
||||
int[] results = new int[4];
|
||||
|
||||
if (isCapture) {
|
||||
if (mAudioRecord == null) {
|
||||
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
||||
channelConfig, audioFormat, desiredFrames * frameSize);
|
||||
|
||||
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
||||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.e(TAG, "Failed during initialization of AudioRecord");
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
results[0] = mAudioRecord.getSampleRate();
|
||||
results[1] = mAudioRecord.getAudioFormat();
|
||||
results[2] = mAudioRecord.getChannelCount();
|
||||
|
||||
} else {
|
||||
if (mAudioTrack == null) {
|
||||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
||||
|
||||
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
||||
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
||||
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
||||
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||
/* Try again, with safer values */
|
||||
|
||||
Log.e(TAG, "Failed during initialization of Audio Track");
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
results[0] = mAudioTrack.getSampleRate();
|
||||
results[1] = mAudioTrack.getAudioFormat();
|
||||
results[2] = mAudioTrack.getChannelCount();
|
||||
}
|
||||
results[3] = desiredFrames;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteFloatBuffer(float[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(float)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteShortBuffer(short[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(short)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteByteBuffer(byte[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length; ) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(byte)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioClose() {
|
||||
if (mAudioTrack != null) {
|
||||
mAudioTrack.stop();
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void captureClose() {
|
||||
if (mAudioRecord != null) {
|
||||
mAudioRecord.stop();
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
||||
try {
|
||||
|
||||
/* Set thread name */
|
||||
if (iscapture) {
|
||||
Thread.currentThread().setName("SDLAudioC" + device_id);
|
||||
} else {
|
||||
Thread.currentThread().setName("SDLAudioP" + device_id);
|
||||
}
|
||||
|
||||
/* Set thread priority */
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "modify thread properties failed " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
}
|
||||
@@ -0,0 +1,788 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
public class SDLControllerManager
|
||||
{
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||
int vendor_id, int product_id,
|
||||
boolean is_accelerometer, int button_mask,
|
||||
int naxes, int nhats, int nballs);
|
||||
public static native int nativeRemoveJoystick(int device_id);
|
||||
public static native int nativeAddHaptic(int device_id, String name);
|
||||
public static native int nativeRemoveHaptic(int device_id);
|
||||
public static native int onNativePadDown(int device_id, int keycode);
|
||||
public static native int onNativePadUp(int device_id, int keycode);
|
||||
public static native void onNativeJoy(int device_id, int axis,
|
||||
float value);
|
||||
public static native void onNativeHat(int device_id, int hat_id,
|
||||
int x, int y);
|
||||
|
||||
protected static SDLJoystickHandler mJoystickHandler;
|
||||
protected static SDLHapticHandler mHapticHandler;
|
||||
|
||||
private static final String TAG = "SDLControllerManager";
|
||||
|
||||
public static void initialize() {
|
||||
if (mJoystickHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
}
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
mHapticHandler = new SDLHapticHandler_API26();
|
||||
} else {
|
||||
mHapticHandler = new SDLHapticHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
||||
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
||||
return mJoystickHandler.handleMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollInputDevices() {
|
||||
mJoystickHandler.pollInputDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollHapticDevices() {
|
||||
mHapticHandler.pollHapticDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticRun(int device_id, float intensity, int length) {
|
||||
mHapticHandler.run(device_id, intensity, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticStop(int device_id)
|
||||
{
|
||||
mHapticHandler.stop(device_id);
|
||||
}
|
||||
|
||||
// Check if a given device is considered a possible SDL joystick
|
||||
public static boolean isDeviceSDLJoystick(int deviceId) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
||||
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
||||
if ((device == null) || (deviceId < 0)) {
|
||||
return false;
|
||||
}
|
||||
int sources = device.getSources();
|
||||
|
||||
/* This is called for every button press, so let's not spam the logs */
|
||||
/*
|
||||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
||||
}
|
||||
*/
|
||||
|
||||
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
||||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
||||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLJoystickHandler {
|
||||
|
||||
/**
|
||||
* Handles given MotionEvent.
|
||||
* @param event the event to be handled.
|
||||
* @return if given event was processed.
|
||||
*/
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding and removing of input devices.
|
||||
*/
|
||||
public void pollInputDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Actual joystick functionality available for API >= 12 devices */
|
||||
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
|
||||
static class SDLJoystick {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public String desc;
|
||||
public ArrayList<InputDevice.MotionRange> axes;
|
||||
public ArrayList<InputDevice.MotionRange> hats;
|
||||
}
|
||||
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||
@Override
|
||||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
||||
int arg0Axis = arg0.getAxis();
|
||||
int arg1Axis = arg1.getAxis();
|
||||
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg0Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<SDLJoystick> mJoysticks;
|
||||
|
||||
public SDLJoystickHandler_API16() {
|
||||
|
||||
mJoysticks = new ArrayList<SDLJoystick>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pollInputDevices() {
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
|
||||
for (int device_id : deviceIds) {
|
||||
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||
SDLJoystick joystick = getJoystick(device_id);
|
||||
if (joystick == null) {
|
||||
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||
joystick = new SDLJoystick();
|
||||
joystick.device_id = device_id;
|
||||
joystick.name = joystickDevice.getName();
|
||||
joystick.desc = getJoystickDescriptor(joystickDevice);
|
||||
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
||||
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
||||
|
||||
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
||||
Collections.sort(ranges, new RangeComparator());
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||
joystick.hats.add(range);
|
||||
} else {
|
||||
joystick.axes.add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mJoysticks.add(joystick);
|
||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
int device_id = joystick.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||
if (mJoysticks.get(i).device_id == device_id) {
|
||||
mJoysticks.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLJoystick getJoystick(int device_id) {
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
if (joystick.device_id == device_id) {
|
||||
return joystick;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||
if (joystick != null) {
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||
/* Normalize the value to -1...1 */
|
||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||
}
|
||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
||||
String desc = joystickDevice.getDescriptor();
|
||||
|
||||
if (desc != null && !desc.isEmpty()) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
return joystickDevice.getName();
|
||||
}
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
|
||||
@Override
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
int button_mask = 0;
|
||||
int[] keys = new int[] {
|
||||
KeyEvent.KEYCODE_BUTTON_A,
|
||||
KeyEvent.KEYCODE_BUTTON_B,
|
||||
KeyEvent.KEYCODE_BUTTON_X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y,
|
||||
KeyEvent.KEYCODE_BACK,
|
||||
KeyEvent.KEYCODE_MENU,
|
||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||
KeyEvent.KEYCODE_BUTTON_START,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||
KeyEvent.KEYCODE_BUTTON_L1,
|
||||
KeyEvent.KEYCODE_BUTTON_R1,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||
|
||||
// These don't map into any SDL controller buttons directly
|
||||
KeyEvent.KEYCODE_BUTTON_L2,
|
||||
KeyEvent.KEYCODE_BUTTON_R2,
|
||||
KeyEvent.KEYCODE_BUTTON_C,
|
||||
KeyEvent.KEYCODE_BUTTON_Z,
|
||||
KeyEvent.KEYCODE_BUTTON_1,
|
||||
KeyEvent.KEYCODE_BUTTON_2,
|
||||
KeyEvent.KEYCODE_BUTTON_3,
|
||||
KeyEvent.KEYCODE_BUTTON_4,
|
||||
KeyEvent.KEYCODE_BUTTON_5,
|
||||
KeyEvent.KEYCODE_BUTTON_6,
|
||||
KeyEvent.KEYCODE_BUTTON_7,
|
||||
KeyEvent.KEYCODE_BUTTON_8,
|
||||
KeyEvent.KEYCODE_BUTTON_9,
|
||||
KeyEvent.KEYCODE_BUTTON_10,
|
||||
KeyEvent.KEYCODE_BUTTON_11,
|
||||
KeyEvent.KEYCODE_BUTTON_12,
|
||||
KeyEvent.KEYCODE_BUTTON_13,
|
||||
KeyEvent.KEYCODE_BUTTON_14,
|
||||
KeyEvent.KEYCODE_BUTTON_15,
|
||||
KeyEvent.KEYCODE_BUTTON_16,
|
||||
};
|
||||
int[] masks = new int[] {
|
||||
(1 << 0), // A -> A
|
||||
(1 << 1), // B -> B
|
||||
(1 << 2), // X -> X
|
||||
(1 << 3), // Y -> Y
|
||||
(1 << 4), // BACK -> BACK
|
||||
(1 << 6), // MENU -> START
|
||||
(1 << 5), // MODE -> GUIDE
|
||||
(1 << 6), // START -> START
|
||||
(1 << 7), // THUMBL -> LEFTSTICK
|
||||
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||
(1 << 9), // L1 -> LEFTSHOULDER
|
||||
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||
(1 << 4), // SELECT -> BACK
|
||||
(1 << 0), // DPAD_CENTER -> A
|
||||
(1 << 15), // L2 -> ??
|
||||
(1 << 16), // R2 -> ??
|
||||
(1 << 17), // C -> ??
|
||||
(1 << 18), // Z -> ??
|
||||
(1 << 20), // 1 -> ??
|
||||
(1 << 21), // 2 -> ??
|
||||
(1 << 22), // 3 -> ??
|
||||
(1 << 23), // 4 -> ??
|
||||
(1 << 24), // 5 -> ??
|
||||
(1 << 25), // 6 -> ??
|
||||
(1 << 26), // 7 -> ??
|
||||
(1 << 27), // 8 -> ??
|
||||
(1 << 28), // 9 -> ??
|
||||
(1 << 29), // 10 -> ??
|
||||
(1 << 30), // 11 -> ??
|
||||
(1 << 31), // 12 -> ??
|
||||
// We're out of room...
|
||||
0xFFFFFFFF, // 13 -> ??
|
||||
0xFFFFFFFF, // 14 -> ??
|
||||
0xFFFFFFFF, // 15 -> ??
|
||||
0xFFFFFFFF, // 16 -> ??
|
||||
};
|
||||
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
||||
for (int i = 0; i < keys.length; ++i) {
|
||||
if (has_keys[i]) {
|
||||
button_mask |= masks[i];
|
||||
}
|
||||
}
|
||||
return button_mask;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
||||
@Override
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
||||
if (intensity == 0.0f) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
|
||||
int vibeValue = Math.round(intensity * 255);
|
||||
|
||||
if (vibeValue > 255) {
|
||||
vibeValue = 255;
|
||||
}
|
||||
if (vibeValue < 1) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
||||
// something went horribly wrong with the Android 8.0 APIs.
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler {
|
||||
|
||||
static class SDLHaptic {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public Vibrator vib;
|
||||
}
|
||||
|
||||
private final ArrayList<SDLHaptic> mHaptics;
|
||||
|
||||
public SDLHapticHandler() {
|
||||
mHaptics = new ArrayList<SDLHaptic>();
|
||||
}
|
||||
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(int device_id) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void pollHapticDevices() {
|
||||
|
||||
final int deviceId_VIBRATOR_SERVICE = 999999;
|
||||
boolean hasVibratorService = false;
|
||||
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
// It helps processing the device ids in reverse order
|
||||
// For example, in the case of the XBox 360 wireless dongle,
|
||||
// so the first controller seen by SDL matches what the receiver
|
||||
// considers to be the first controller
|
||||
|
||||
for (int i = deviceIds.length - 1; i > -1; i--) {
|
||||
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
||||
if (haptic == null) {
|
||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||
Vibrator vib = device.getVibrator();
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check VIBRATOR_SERVICE */
|
||||
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vib != null) {
|
||||
hasVibratorService = vib.hasVibrator();
|
||||
|
||||
if (hasVibratorService) {
|
||||
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
||||
if (haptic == null) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
||||
haptic.name = "VIBRATOR_SERVICE";
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
int device_id = haptic.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
|
||||
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
} // else: don't remove the vibrator if it is still present
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||
for (int i = 0; i < mHaptics.size(); i++) {
|
||||
if (mHaptics.get(i).device_id == device_id) {
|
||||
mHaptics.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLHaptic getHaptic(int device_id) {
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
if (haptic.device_id == device_id) {
|
||||
return haptic;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsRelativeMouse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean inRelativeMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public float getEventX(MotionEvent event) {
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
public float getEventY(MotionEvent event) {
|
||||
return event.getY(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
|
||||
// Handle relative mouse mode
|
||||
if (mRelativeModeEnabled) {
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event was not managed, call SDLGenericMotionListener_API12 method
|
||||
return super.onGenericMotion(v, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
} else {
|
||||
return event.getX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
} else {
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
// DeX desktop mouse cursor is a separate non-standard input type.
|
||||
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
|
||||
if (enabled) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
} else {
|
||||
SDLActivity.getContentView().releasePointerCapture();
|
||||
}
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
os/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 26 KiB |
BIN
os/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
os/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
BIN
os/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
os/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 43 KiB |
BIN
os/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
os/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 90 KiB |
BIN
os/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
os/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 148 KiB |
BIN
os/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
6
os/android/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
</resources>
|
||||
5
os/android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">Fallout 2</string>
|
||||
<string name="loading_dialog_title">PLEASE STAND BY</string>
|
||||
<string name="loading_dialog_message">Copying files…</string>
|
||||
</resources>
|
||||
8
os/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
9
os/android/build.gradle
Normal file
@@ -0,0 +1,9 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.1' apply false
|
||||
id 'com.android.library' version '7.2.1' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
21
os/android/gradle.properties
Normal file
@@ -0,0 +1,21 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
BIN
os/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
os/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Mon Jul 25 15:30:57 PST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
185
os/android/gradlew
vendored
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
os/android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
16
os/android/settings.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "Fallout 2 Community Edition"
|
||||
include ':app'
|
||||
@@ -29,7 +29,7 @@
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11</string>
|
||||
<string>10.11</string>
|
||||
<key>SDL_FILESYSTEM_BASE_DIR_TYPE</key>
|
||||
<string>parent</string>
|
||||
</dict>
|
||||
|
||||
BIN
os/macos/fallout2-ce.icns
Normal file
BIN
os/windows/fallout2-ce.ico
Normal file
|
After Width: | Height: | Size: 97 KiB |
1
os/windows/fallout2-ce.rc
Normal file
@@ -0,0 +1 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "fallout2-ce.ico"
|
||||
668
src/actions.cc
@@ -4,6 +4,8 @@
|
||||
#include "combat_defs.h"
|
||||
#include "obj_types.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
int _action_attack(Attack* attack);
|
||||
int _action_use_an_item_on_object(Object* a1, Object* a2, Object* a3);
|
||||
int _action_use_an_object(Object* a1, Object* a2);
|
||||
@@ -16,9 +18,11 @@ bool _can_see(Object* a1, Object* a2);
|
||||
bool _action_explode_running();
|
||||
int actionExplode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6);
|
||||
int actionTalk(Object* a1, Object* a2);
|
||||
void _action_dmg(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor);
|
||||
void actionDamage(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor);
|
||||
bool actionCheckPush(Object* a1, Object* a2);
|
||||
int actionPush(Object* a1, Object* a2);
|
||||
int _action_can_talk_to(Object* a1, Object* a2);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* ACTIONS_H */
|
||||
|
||||
1448
src/animation.cc
@@ -3,36 +3,16 @@
|
||||
|
||||
#include "combat_defs.h"
|
||||
#include "obj_types.h"
|
||||
#include "sound.h"
|
||||
|
||||
typedef enum AnimKind {
|
||||
ANIM_KIND_OBJ_MOVE_TO_OBJ = 0,
|
||||
ANIM_KIND_OBJ_MOVE_TO_TILE = 1,
|
||||
ANIM_KIND_2 = 2,
|
||||
ANIM_KIND_KNOCKDOWN = 3,
|
||||
ANIM_KIND_ANIMATE = 4,
|
||||
ANIM_KIND_ANIMATE_REVERSE = 5,
|
||||
ANIM_KIND_6 = 6,
|
||||
ANIM_KIND_SET_ROTATION_TO_TILE = 7,
|
||||
ANIM_KIND_ROTATE_CLOCKWISE = 8,
|
||||
ANIM_KIND_ROTATE_COUNTER_CLOCKWISE = 9,
|
||||
ANIM_KIND_HIDE = 10,
|
||||
ANIM_KIND_EXEC = 11,
|
||||
ANIM_KIND_EXEC_2 = 12,
|
||||
ANIM_KIND_14 = 14,
|
||||
ANIM_KIND_15 = 15,
|
||||
ANIM_KIND_16 = 16,
|
||||
ANIM_KIND_17 = 17,
|
||||
ANIM_KIND_18 = 18,
|
||||
ANIM_KIND_19 = 19,
|
||||
ANIM_KIND_20 = 20,
|
||||
ANIM_KIND_23 = 23,
|
||||
ANIM_KIND_24 = 24,
|
||||
ANIM_KIND_ANIMATE_FOREVER = 25,
|
||||
ANIM_KIND_26 = 26,
|
||||
ANIM_KIND_27 = 27,
|
||||
ANIM_KIND_28 = 28,
|
||||
} AnimKind;
|
||||
namespace fallout {
|
||||
|
||||
typedef enum AnimationRequestOptions {
|
||||
ANIMATION_REQUEST_UNRESERVED = 0x01,
|
||||
ANIMATION_REQUEST_RESERVED = 0x02,
|
||||
ANIMATION_REQUEST_NO_STAND = 0x04,
|
||||
ANIMATION_REQUEST_0x100 = 0x100,
|
||||
ANIMATION_REQUEST_INSIGNIFICANT = 0x200,
|
||||
} AnimationRequestOptions;
|
||||
|
||||
// Basic animations: 0-19
|
||||
// Knockdown and death: 20-35
|
||||
@@ -112,9 +92,13 @@ typedef enum AnimationType {
|
||||
LAST_SF_DEATH_ANIM = ANIM_FALL_FRONT_BLOOD_SF,
|
||||
} AnimationType;
|
||||
|
||||
typedef int AnimationProc(Object*, Object*);
|
||||
typedef int AnimationSoundProc(Sound*);
|
||||
typedef int AnimationProc2(Object*, Object*, void*);
|
||||
#define FID_ANIM_TYPE(value) ((value) & 0xFF0000) >> 16
|
||||
|
||||
// Signature of animation callback accepting 2 parameters.
|
||||
typedef int(AnimationCallback)(void* a1, void* a2);
|
||||
|
||||
// Signature of animation callback accepting 3 parameters.
|
||||
typedef int(AnimationCallback3)(void* a1, void* a2, void* a3);
|
||||
|
||||
typedef struct STRUCT_530014_28 {
|
||||
int tile;
|
||||
@@ -133,28 +117,31 @@ int _register_priority(int a1);
|
||||
int reg_anim_clear(Object* a1);
|
||||
int reg_anim_end();
|
||||
int animationIsBusy(Object* a1);
|
||||
int reg_anim_obj_move_to_obj(Object* a1, Object* a2, int actionPoints, int delay);
|
||||
int reg_anim_obj_run_to_obj(Object* owner, Object* destination, int actionPoints, int delay);
|
||||
int reg_anim_obj_move_to_tile(Object* obj, int tile_num, int elev, int actionPoints, int delay);
|
||||
int reg_anim_obj_run_to_tile(Object* obj, int tile_num, int elev, int actionPoints, int delay);
|
||||
int reg_anim_2(Object* obj, int tile_num, int elev, int a4, int a5);
|
||||
int reg_anim_knockdown(Object* obj, int tile, int elev, int anim, int delay);
|
||||
int reg_anim_animate(Object* obj, int anim, int delay);
|
||||
int reg_anim_animate_reverse(Object* obj, int anim, int delay);
|
||||
int reg_anim_6(Object* obj, int anim, int delay);
|
||||
int reg_anim_set_rotation_to_tile(Object* owner, int tile);
|
||||
int reg_anim_rotate_clockwise(Object* obj);
|
||||
int reg_anim_rotate_counter_clockwise(Object* obj);
|
||||
int reg_anim_hide(Object* obj);
|
||||
int reg_anim_11_0(Object* a1, Object* a2, AnimationProc* proc, int delay);
|
||||
int reg_anim_12(Object* a1, Object* a2, void* a3, AnimationProc2* proc, int delay);
|
||||
int reg_anim_11_1(Object* a1, Object* a2, AnimationProc* proc, int delay);
|
||||
int reg_anim_15(Object* obj, int a2, int a3);
|
||||
int reg_anim_17(Object* obj, int fid, int a3);
|
||||
int reg_anim_18(Object* obj, int a2, int a3);
|
||||
int reg_anim_update_light(Object* obj, int fid, int a3);
|
||||
int reg_anim_play_sfx(Object* obj, const char* a2, int a3);
|
||||
int reg_anim_animate_forever(Object* obj, int a2, int a3);
|
||||
int animationRegisterMoveToObject(Object* owner, Object* destination, int actionPoints, int delay);
|
||||
int animationRegisterRunToObject(Object* owner, Object* destination, int actionPoints, int delay);
|
||||
int animationRegisterMoveToTile(Object* owner, int tile, int elevation, int actionPoints, int delay);
|
||||
int animationRegisterRunToTile(Object* owner, int tile, int elevation, int actionPoints, int delay);
|
||||
int animationRegisterMoveToTileStraight(Object* object, int tile, int elevation, int anim, int delay);
|
||||
int animationRegisterMoveToTileStraightAndWaitForComplete(Object* owner, int tile, int elev, int anim, int delay);
|
||||
int animationRegisterAnimate(Object* owner, int anim, int delay);
|
||||
int animationRegisterAnimateReversed(Object* owner, int anim, int delay);
|
||||
int animationRegisterAnimateAndHide(Object* owner, int anim, int delay);
|
||||
int animationRegisterRotateToTile(Object* owner, int tile);
|
||||
int animationRegisterRotateClockwise(Object* owner);
|
||||
int animationRegisterRotateCounterClockwise(Object* owner);
|
||||
int animationRegisterHideObject(Object* object);
|
||||
int animationRegisterHideObjectForced(Object* object);
|
||||
int animationRegisterCallback(void* a1, void* a2, AnimationCallback* proc, int delay);
|
||||
int animationRegisterCallback3(void* a1, void* a2, void* a3, AnimationCallback3* proc, int delay);
|
||||
int animationRegisterCallbackForced(void* a1, void* a2, AnimationCallback* proc, int delay);
|
||||
int animationRegisterSetFlag(Object* object, int flag, int delay);
|
||||
int animationRegisterUnsetFlag(Object* object, int flag, int delay);
|
||||
int animationRegisterSetFid(Object* owner, int fid, int delay);
|
||||
int animationRegisterTakeOutWeapon(Object* owner, int weaponAnimationCode, int delay);
|
||||
int animationRegisterSetLightDistance(Object* owner, int lightDistance, int delay);
|
||||
int animationRegisterToggleOutline(Object* object, bool outline, int delay);
|
||||
int animationRegisterPlaySoundEffect(Object* owner, const char* soundEffectName, int delay);
|
||||
int animationRegisterAnimateForever(Object* owner, int anim, int delay);
|
||||
int reg_anim_26(int a1, int a2);
|
||||
int _make_path(Object* object, int from, int to, unsigned char* a4, int a5);
|
||||
int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback);
|
||||
@@ -169,4 +156,8 @@ void _dude_stand(Object* obj, int rotation, int fid);
|
||||
void _dude_standup(Object* a1);
|
||||
void animationStop();
|
||||
|
||||
int animationRegisterSetLightIntensity(Object* owner, int lightDistance, int lightIntensity, int delay);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* ANIMATION_H */
|
||||
|
||||
554
src/art.cc
@@ -1,17 +1,20 @@
|
||||
#include "art.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "animation.h"
|
||||
#include "debug.h"
|
||||
#include "draw.h"
|
||||
#include "game.h"
|
||||
#include "game_config.h"
|
||||
#include "memory.h"
|
||||
#include "object.h"
|
||||
#include "proto.h"
|
||||
#include "sfall_config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
namespace fallout {
|
||||
|
||||
typedef struct ArtListDescription {
|
||||
int flags;
|
||||
@@ -126,10 +129,8 @@ static int* gArtCritterFidShoudRunData;
|
||||
int artInit()
|
||||
{
|
||||
char path[COMPAT_MAX_PATH];
|
||||
int i;
|
||||
File* stream;
|
||||
char str[200];
|
||||
char *ptr, *curr;
|
||||
char string[200];
|
||||
|
||||
int cacheSize;
|
||||
if (!configGetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, &cacheSize)) {
|
||||
@@ -147,20 +148,36 @@ int artInit()
|
||||
gArtLanguageInitialized = true;
|
||||
}
|
||||
|
||||
for (i = 0; i < 11; i++) {
|
||||
gArtListDescriptions[i].flags = 0;
|
||||
sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[i].name, gArtListDescriptions[i].name);
|
||||
bool critterDbSelected = false;
|
||||
for (int objectType = 0; objectType < OBJ_TYPE_COUNT; objectType++) {
|
||||
gArtListDescriptions[objectType].flags = 0;
|
||||
sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[objectType].name, gArtListDescriptions[objectType].name);
|
||||
|
||||
if (artReadList(path, &(gArtListDescriptions[i].fileNames), &(gArtListDescriptions[i].fileNamesLength)) != 0) {
|
||||
int oldDb;
|
||||
if (objectType == OBJ_TYPE_CRITTER) {
|
||||
oldDb = _db_current();
|
||||
critterDbSelected = true;
|
||||
_db_select(_critter_db_handle);
|
||||
}
|
||||
|
||||
if (artReadList(path, &(gArtListDescriptions[objectType].fileNames), &(gArtListDescriptions[objectType].fileNamesLength)) != 0) {
|
||||
debugPrint("art_read_lst failed in art_init\n");
|
||||
if (critterDbSelected) {
|
||||
_db_select(oldDb);
|
||||
}
|
||||
cacheFree(&gArtCache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (objectType == OBJ_TYPE_CRITTER) {
|
||||
critterDbSelected = false;
|
||||
_db_select(oldDb);
|
||||
}
|
||||
}
|
||||
|
||||
_anon_alias = (int*)internal_malloc(sizeof(*_anon_alias) * gArtListDescriptions[1].fileNamesLength);
|
||||
_anon_alias = (int*)internal_malloc(sizeof(*_anon_alias) * gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength);
|
||||
if (_anon_alias == NULL) {
|
||||
gArtListDescriptions[1].fileNamesLength = 0;
|
||||
gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength = 0;
|
||||
debugPrint("Out of memory for anon_alias in art_init\n");
|
||||
cacheFree(&gArtCache);
|
||||
return -1;
|
||||
@@ -168,17 +185,17 @@ int artInit()
|
||||
|
||||
gArtCritterFidShoudRunData = (int*)internal_malloc(sizeof(*gArtCritterFidShoudRunData) * gArtListDescriptions[1].fileNamesLength);
|
||||
if (gArtCritterFidShoudRunData == NULL) {
|
||||
gArtListDescriptions[1].fileNamesLength = 0;
|
||||
gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength = 0;
|
||||
debugPrint("Out of memory for artCritterFidShouldRunData in art_init\n");
|
||||
cacheFree(&gArtCache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < gArtListDescriptions[1].fileNamesLength; i++) {
|
||||
gArtCritterFidShoudRunData[i] = 0;
|
||||
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
|
||||
gArtCritterFidShoudRunData[critterIndex] = 0;
|
||||
}
|
||||
|
||||
sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[1].name, gArtListDescriptions[1].name);
|
||||
sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_CRITTER].name, gArtListDescriptions[OBJ_TYPE_CRITTER].name);
|
||||
|
||||
stream = fileOpen(path, "rt");
|
||||
if (stream == NULL) {
|
||||
@@ -206,75 +223,70 @@ int artInit()
|
||||
tribalMaleFileName = gDefaultTribalMaleFileName;
|
||||
}
|
||||
|
||||
char *tribalFemaleFileName = NULL;
|
||||
char* tribalFemaleFileName = NULL;
|
||||
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_FEMALE_KEY, &tribalFemaleFileName);
|
||||
if (tribalFemaleFileName == NULL || tribalFemaleFileName[0] == '\0') {
|
||||
tribalFemaleFileName = gDefaultTribalFemaleFileName;
|
||||
}
|
||||
|
||||
ptr = gArtListDescriptions[1].fileNames;
|
||||
for (i = 0; i < gArtListDescriptions[1].fileNamesLength; i++) {
|
||||
if (compat_stricmp(ptr, jumpsuitMaleFileName) == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE] = i;
|
||||
} else if (compat_stricmp(ptr, jumpsuitFemaleFileName) == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_FEMALE] = i;
|
||||
char* critterFileNames = gArtListDescriptions[OBJ_TYPE_CRITTER].fileNames;
|
||||
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
|
||||
if (compat_stricmp(critterFileNames, "hmjmps") == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE] = critterIndex;
|
||||
} else if (compat_stricmp(critterFileNames, "hfjmps") == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_FEMALE] = critterIndex;
|
||||
}
|
||||
|
||||
if (compat_stricmp(ptr, tribalMaleFileName) == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_MALE] = i;
|
||||
_art_vault_guy_num = i;
|
||||
} else if (compat_stricmp(ptr, tribalFemaleFileName) == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_FEMALE] = i;
|
||||
if (compat_stricmp(critterFileNames, "hmwarr") == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_MALE] = critterIndex;
|
||||
_art_vault_guy_num = critterIndex;
|
||||
} else if (compat_stricmp(critterFileNames, "hfprim") == 0) {
|
||||
_art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_FEMALE] = critterIndex;
|
||||
}
|
||||
|
||||
ptr += 13;
|
||||
critterFileNames += 13;
|
||||
}
|
||||
|
||||
for (i = 0; i < gArtListDescriptions[1].fileNamesLength; i++) {
|
||||
if (!fileReadString(str, sizeof(str), stream)) {
|
||||
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
|
||||
if (!fileReadString(string, sizeof(string), stream)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = str;
|
||||
curr = ptr;
|
||||
while (*curr != '\0' && *curr != ',') {
|
||||
curr++;
|
||||
}
|
||||
char* sep1 = strchr(string, ',');
|
||||
if (sep1 != NULL) {
|
||||
_anon_alias[critterIndex] = atoi(sep1 + 1);
|
||||
|
||||
if (*curr != '\0') {
|
||||
_anon_alias[i] = atoi(curr + 1);
|
||||
|
||||
ptr = curr + 1;
|
||||
curr = ptr;
|
||||
while (*curr != '\0' && *curr != ',') {
|
||||
curr++;
|
||||
char* sep2 = strchr(sep1 + 1, ',');
|
||||
if (sep2 != NULL) {
|
||||
gArtCritterFidShoudRunData[critterIndex] = atoi(sep2 + 1);
|
||||
} else {
|
||||
gArtCritterFidShoudRunData[critterIndex] = 0;
|
||||
}
|
||||
|
||||
gArtCritterFidShoudRunData[i] = *curr != '\0' ? atoi(ptr) : 0;
|
||||
} else {
|
||||
_anon_alias[i] = _art_vault_guy_num;
|
||||
gArtCritterFidShoudRunData[i] = 1;
|
||||
_anon_alias[critterIndex] = _art_vault_guy_num;
|
||||
gArtCritterFidShoudRunData[critterIndex] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
fileClose(stream);
|
||||
|
||||
ptr = gArtListDescriptions[4].fileNames;
|
||||
for (i = 0; i < gArtListDescriptions[4].fileNamesLength; i++) {
|
||||
if (compat_stricmp(ptr, "grid001.frm") == 0) {
|
||||
_art_mapper_blank_tile = i;
|
||||
char* tileFileNames = gArtListDescriptions[OBJ_TYPE_TILE].fileNames;
|
||||
for (int tileIndex = 0; tileIndex < gArtListDescriptions[OBJ_TYPE_TILE].fileNamesLength; tileIndex++) {
|
||||
if (compat_stricmp(tileFileNames, "grid001.frm") == 0) {
|
||||
_art_mapper_blank_tile = tileIndex;
|
||||
}
|
||||
tileFileNames += 13;
|
||||
}
|
||||
|
||||
gHeadDescriptions = (HeadDescription*)internal_malloc(sizeof(HeadDescription) * gArtListDescriptions[8].fileNamesLength);
|
||||
gHeadDescriptions = (HeadDescription*)internal_malloc(sizeof(*gHeadDescriptions) * gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength);
|
||||
if (gHeadDescriptions == NULL) {
|
||||
gArtListDescriptions[8].fileNamesLength = 0;
|
||||
gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength = 0;
|
||||
debugPrint("Out of memory for head_info in art_init\n");
|
||||
cacheFree(&gArtCache);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[8].name, gArtListDescriptions[8].name);
|
||||
sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_HEAD].name, gArtListDescriptions[OBJ_TYPE_HEAD].name);
|
||||
|
||||
stream = fileOpen(path, "rt");
|
||||
if (stream == NULL) {
|
||||
@@ -283,46 +295,42 @@ int artInit()
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < gArtListDescriptions[8].fileNamesLength; i++) {
|
||||
if (!fileReadString(str, sizeof(str), stream)) {
|
||||
for (int headIndex = 0; headIndex < gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength; headIndex++) {
|
||||
if (!fileReadString(string, sizeof(string), stream)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = str;
|
||||
curr = ptr;
|
||||
while (*curr != '\0' && *curr != ',') {
|
||||
curr++;
|
||||
char* sep1 = strchr(string, ',');
|
||||
if (sep1 != NULL) {
|
||||
*sep1 = '\0';
|
||||
} else {
|
||||
sep1 = string;
|
||||
}
|
||||
|
||||
if (*curr != '\0') {
|
||||
ptr = curr + 1;
|
||||
curr = ptr;
|
||||
while (*curr != '\0' && *curr != ',') {
|
||||
curr++;
|
||||
}
|
||||
|
||||
if (*curr != '\0') {
|
||||
gHeadDescriptions[i].goodFidgetCount = atoi(ptr);
|
||||
|
||||
ptr = curr + 1;
|
||||
curr = ptr;
|
||||
while (*curr != '\0' && *curr != ',') {
|
||||
curr++;
|
||||
}
|
||||
|
||||
if (*curr != '\0') {
|
||||
gHeadDescriptions[i].neutralFidgetCount = atoi(ptr);
|
||||
|
||||
ptr = curr + 1;
|
||||
curr = strpbrk(ptr, " ,;\t\n");
|
||||
if (curr != NULL) {
|
||||
*curr = '\0';
|
||||
}
|
||||
|
||||
gHeadDescriptions[i].badFidgetCount = atoi(ptr);
|
||||
}
|
||||
}
|
||||
char* sep2 = strchr(sep1, ',');
|
||||
if (sep2 != NULL) {
|
||||
*sep2 = '\0';
|
||||
} else {
|
||||
sep2 = sep1;
|
||||
}
|
||||
|
||||
gHeadDescriptions[headIndex].goodFidgetCount = atoi(sep1 + 1);
|
||||
|
||||
char* sep3 = strchr(sep2, ',');
|
||||
if (sep3 != NULL) {
|
||||
*sep3 = '\0';
|
||||
} else {
|
||||
sep3 = sep2;
|
||||
}
|
||||
|
||||
gHeadDescriptions[headIndex].neutralFidgetCount = atoi(sep2 + 1);
|
||||
|
||||
char* sep4 = strpbrk(sep3 + 1, " ,;\t\n");
|
||||
if (sep4 != NULL) {
|
||||
*sep4 = '\0';
|
||||
}
|
||||
|
||||
gHeadDescriptions[headIndex].badFidgetCount = atoi(sep3 + 1);
|
||||
}
|
||||
|
||||
fileClose(stream);
|
||||
@@ -357,19 +365,19 @@ void artExit()
|
||||
// 0x418F1C
|
||||
char* artGetObjectTypeName(int objectType)
|
||||
{
|
||||
return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].name : NULL;
|
||||
return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].name : NULL;
|
||||
}
|
||||
|
||||
// 0x418F34
|
||||
int artIsObjectTypeHidden(int objectType)
|
||||
{
|
||||
return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].flags & 1 : 0;
|
||||
return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].flags & 1 : 0;
|
||||
}
|
||||
|
||||
// 0x418F7C
|
||||
int artGetFidgetCount(int headFid)
|
||||
{
|
||||
if ((headFid & 0xF000000) >> 24 != OBJ_TYPE_HEAD) {
|
||||
if (FID_TYPE(headFid) != OBJ_TYPE_HEAD) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -522,15 +530,15 @@ int artCacheFlush()
|
||||
}
|
||||
|
||||
// 0x4192B0
|
||||
int artCopyFileName(int type, int id, char* dest)
|
||||
int artCopyFileName(int objectType, int id, char* dest)
|
||||
{
|
||||
ArtListDescription* ptr;
|
||||
|
||||
if (type < 0 && type >= 11) {
|
||||
if (objectType < OBJ_TYPE_ITEM && objectType >= OBJ_TYPE_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ptr = &(gArtListDescriptions[type]);
|
||||
ptr = &(gArtListDescriptions[objectType]);
|
||||
|
||||
if (id >= ptr->fileNamesLength) {
|
||||
return -1;
|
||||
@@ -622,7 +630,7 @@ char* artBuildFilePath(int fid)
|
||||
|
||||
v10 = (fid & 0x70000000) >> 28;
|
||||
|
||||
v1 = _art_alias_fid(fid);
|
||||
v1 = artAliasFid(fid);
|
||||
if (v1 != -1) {
|
||||
v2 = v1;
|
||||
}
|
||||
@@ -630,15 +638,15 @@ char* artBuildFilePath(int fid)
|
||||
*_art_name = '\0';
|
||||
|
||||
v3 = v2 & 0xFFF;
|
||||
v4 = (v2 & 0xFF0000) >> 16;
|
||||
v4 = FID_ANIM_TYPE(v2);
|
||||
v5 = (v2 & 0xF000) >> 12;
|
||||
type = (v2 & 0xF000000) >> 24;
|
||||
type = FID_TYPE(v2);
|
||||
|
||||
if (v3 >= gArtListDescriptions[type].fileNamesLength) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (type < 0 || type >= 11) {
|
||||
if (type < OBJ_TYPE_ITEM || type >= OBJ_TYPE_COUNT) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -669,55 +677,45 @@ char* artBuildFilePath(int fid)
|
||||
|
||||
// art_read_lst
|
||||
// 0x419664
|
||||
static int artReadList(const char* path, char** out_arr, int* out_count)
|
||||
static int artReadList(const char* path, char** artListPtr, int* artListSizePtr)
|
||||
{
|
||||
File* stream;
|
||||
char str[200];
|
||||
char* arr;
|
||||
int count;
|
||||
char* brk;
|
||||
|
||||
stream = fileOpen(path, "rt");
|
||||
File* stream = fileOpen(path, "rt");
|
||||
if (stream == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
while (fileReadString(str, sizeof(str), stream)) {
|
||||
int count = 0;
|
||||
char string[200];
|
||||
while (fileReadString(string, sizeof(string), stream)) {
|
||||
count++;
|
||||
}
|
||||
|
||||
fileSeek(stream, 0, SEEK_SET);
|
||||
|
||||
*out_count = count;
|
||||
*artListSizePtr = count;
|
||||
|
||||
arr = (char*)internal_malloc(13 * count);
|
||||
*out_arr = arr;
|
||||
if (arr == NULL) {
|
||||
goto err;
|
||||
char* artList = (char*)internal_malloc(13 * count);
|
||||
*artListPtr = artList;
|
||||
if (artList == NULL) {
|
||||
fileClose(stream);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (fileReadString(str, sizeof(str), stream)) {
|
||||
brk = strpbrk(str, " ,;\r\t\n");
|
||||
while (fileReadString(string, sizeof(string), stream)) {
|
||||
char* brk = strpbrk(string, " ,;\r\t\n");
|
||||
if (brk != NULL) {
|
||||
*brk = '\0';
|
||||
}
|
||||
|
||||
strncpy(arr, str, 12);
|
||||
arr[12] = '\0';
|
||||
strncpy(artList, string, 12);
|
||||
artList[12] = '\0';
|
||||
|
||||
arr += 13;
|
||||
artList += 13;
|
||||
}
|
||||
|
||||
fileClose(stream);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
|
||||
fileClose(stream);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 0x419760
|
||||
@@ -864,54 +862,55 @@ ArtFrame* artGetFrame(Art* art, int frame, int rotation)
|
||||
// 0x4198C8
|
||||
bool artExists(int fid)
|
||||
{
|
||||
int v3;
|
||||
bool result;
|
||||
bool result = false;
|
||||
int oldDb = -1;
|
||||
|
||||
v3 = -1;
|
||||
result = false;
|
||||
|
||||
if ((fid & 0xF000000) >> 24 == 1) {
|
||||
v3 = _db_current(1);
|
||||
// _db_current(_critter_db_handle);
|
||||
_db_current(0);
|
||||
if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {
|
||||
oldDb = _db_current();
|
||||
_db_select(_critter_db_handle);
|
||||
}
|
||||
|
||||
char* filePath = artBuildFilePath(fid);
|
||||
if (filePath == NULL) {
|
||||
goto out;
|
||||
if (filePath != NULL) {
|
||||
int fileSize;
|
||||
if (dbGetFileSize(filePath, &fileSize) != -1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
int fileSize;
|
||||
if (dbGetFileSize(filePath, &fileSize) == -1) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
result = true;
|
||||
|
||||
out:
|
||||
|
||||
if (v3 != -1) {
|
||||
_db_current(v3);
|
||||
if (oldDb != -1) {
|
||||
_db_select(oldDb);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOTE: Exactly the same implementation as `artExists`.
|
||||
//
|
||||
// 0x419930
|
||||
bool _art_fid_valid(int fid)
|
||||
{
|
||||
// NOTE: Original Code involves calling some unknown function. Check in debugger in mapper.
|
||||
bool result = false;
|
||||
int oldDb = -1;
|
||||
|
||||
if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {
|
||||
oldDb = _db_current();
|
||||
_db_select(_critter_db_handle);
|
||||
}
|
||||
|
||||
char* filePath = artBuildFilePath(fid);
|
||||
if (filePath == NULL) {
|
||||
return false;
|
||||
if (filePath != NULL) {
|
||||
int fileSize;
|
||||
if (dbGetFileSize(filePath, &fileSize) != -1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
int fileSize;
|
||||
if (dbGetFileSize(filePath, &fileSize) == -1) {
|
||||
return false;
|
||||
if (oldDb != -1) {
|
||||
_db_select(oldDb);
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 0x419998
|
||||
@@ -923,7 +922,7 @@ int _art_alias_num(int index)
|
||||
// 0x4199AC
|
||||
int artCritterFidShouldRun(int fid)
|
||||
{
|
||||
if ((fid & 0xF000000) >> 24 == 1) {
|
||||
if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {
|
||||
return gArtCritterFidShoudRunData[fid & 0xFFF];
|
||||
}
|
||||
|
||||
@@ -931,64 +930,61 @@ int artCritterFidShouldRun(int fid)
|
||||
}
|
||||
|
||||
// 0x4199D4
|
||||
int _art_alias_fid(int fid)
|
||||
int artAliasFid(int fid)
|
||||
{
|
||||
int v2;
|
||||
int v3;
|
||||
int result;
|
||||
int type = FID_TYPE(fid);
|
||||
int anim = FID_ANIM_TYPE(fid);
|
||||
if (type == OBJ_TYPE_CRITTER) {
|
||||
if (anim == ANIM_ELECTRIFY
|
||||
|| anim == ANIM_BURNED_TO_NOTHING
|
||||
|| anim == ANIM_ELECTRIFIED_TO_NOTHING
|
||||
|| anim == ANIM_ELECTRIFY_SF
|
||||
|| anim == ANIM_BURNED_TO_NOTHING_SF
|
||||
|| anim == ANIM_ELECTRIFIED_TO_NOTHING_SF
|
||||
|| anim == ANIM_FIRE_DANCE
|
||||
|| anim == ANIM_CALLED_SHOT_PIC) {
|
||||
// NOTE: Original code is slightly different. It uses many mutually
|
||||
// mirrored bitwise operators. Probably result of some macros for
|
||||
// getting/setting individual bits on fid.
|
||||
return (fid & 0x70000000) | ((anim << 16) & 0xFF0000) | 0x1000000 | (fid & 0xF000) | (_anon_alias[fid & 0xFFF] & 0xFFF);
|
||||
}
|
||||
}
|
||||
|
||||
v2 = (fid & 0xF000000) >> 24;
|
||||
v3 = (fid & 0xFF0000) >> 16;
|
||||
|
||||
if (v2 != 1 || v3 != 27 && v3 != 29 && v3 != 30 && v3 != 55 && v3 != 57 && v3 != 58 && v3 != 33 && v3 != 64)
|
||||
result = -1;
|
||||
else
|
||||
result = ((fid & 0x70000000) >> 28 << 28) & 0x70000000 | (v3 << 16) & 0xFF0000 | 0x1000000 | (((fid & 0xF000) >> 12) << 12) & 0xF000 | _anon_alias[fid & 0xFFF] & 0xFFF;
|
||||
|
||||
return result;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 0x419A78
|
||||
static int artCacheGetFileSizeImpl(int fid, int* sizePtr)
|
||||
{
|
||||
int v4;
|
||||
char* str;
|
||||
char* ptr;
|
||||
int result;
|
||||
char path[COMPAT_MAX_PATH];
|
||||
bool loaded;
|
||||
int fileSize;
|
||||
int oldDb = -1;
|
||||
int result = -1;
|
||||
|
||||
v4 = -1;
|
||||
result = -1;
|
||||
|
||||
if ((fid & 0xF000000) >> 24 == 1) {
|
||||
v4 = _db_current(1);
|
||||
// _db_current(_critter_db_handle);
|
||||
_db_current(0);
|
||||
if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {
|
||||
oldDb = _db_current();
|
||||
_db_select(_critter_db_handle);
|
||||
}
|
||||
|
||||
str = artBuildFilePath(fid);
|
||||
if (str != NULL) {
|
||||
loaded = false;
|
||||
char* artFilePath = artBuildFilePath(fid);
|
||||
if (artFilePath != NULL) {
|
||||
int fileSize;
|
||||
bool loaded = false;
|
||||
|
||||
if (gArtLanguageInitialized) {
|
||||
ptr = str;
|
||||
while (*ptr != '\0' && *ptr != '\\') {
|
||||
ptr++;
|
||||
char* pch = strchr(artFilePath, '\\');
|
||||
if (pch == NULL) {
|
||||
pch = artFilePath;
|
||||
}
|
||||
|
||||
if (*ptr == '\0') {
|
||||
ptr = str;
|
||||
}
|
||||
char localizedPath[COMPAT_MAX_PATH];
|
||||
sprintf(localizedPath, "art\\%s\\%s", gArtLanguage, pch);
|
||||
|
||||
sprintf(path, "art\\%s\\%s", gArtLanguage, ptr);
|
||||
if (dbGetFileSize(path, &fileSize) == 0) {
|
||||
if (dbGetFileSize(localizedPath, &fileSize) == 0) {
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
if (dbGetFileSize(str, &fileSize) == 0) {
|
||||
if (dbGetFileSize(artFilePath, &fileSize) == 0) {
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
@@ -999,8 +995,8 @@ static int artCacheGetFileSizeImpl(int fid, int* sizePtr)
|
||||
}
|
||||
}
|
||||
|
||||
if (v4 != -1) {
|
||||
_db_current(v4);
|
||||
if (oldDb != -1) {
|
||||
_db_select(oldDb);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1009,43 +1005,33 @@ static int artCacheGetFileSizeImpl(int fid, int* sizePtr)
|
||||
// 0x419B78
|
||||
static int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data)
|
||||
{
|
||||
int v4;
|
||||
char* str;
|
||||
char* ptr;
|
||||
int result;
|
||||
char path[COMPAT_MAX_PATH];
|
||||
bool loaded;
|
||||
int oldDb = -1;
|
||||
int result = -1;
|
||||
|
||||
v4 = -1;
|
||||
result = -1;
|
||||
|
||||
if ((fid & 0xF000000) >> 24 == 1) {
|
||||
v4 = _db_current(1);
|
||||
// _db_current(_critter_db_handle);
|
||||
_db_current(0);
|
||||
if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {
|
||||
oldDb = _db_current();
|
||||
_db_select(_critter_db_handle);
|
||||
}
|
||||
|
||||
str = artBuildFilePath(fid);
|
||||
if (str != NULL) {
|
||||
loaded = false;
|
||||
char* artFileName = artBuildFilePath(fid);
|
||||
if (artFileName != NULL) {
|
||||
bool loaded = false;
|
||||
if (gArtLanguageInitialized) {
|
||||
ptr = str;
|
||||
while (*ptr != '\0' && *ptr != '\\') {
|
||||
ptr++;
|
||||
char* pch = strchr(artFileName, '\\');
|
||||
if (pch == NULL) {
|
||||
pch = artFileName;
|
||||
}
|
||||
|
||||
if (*ptr == '\0') {
|
||||
ptr = str;
|
||||
}
|
||||
char localizedPath[COMPAT_MAX_PATH];
|
||||
sprintf(localizedPath, "art\\%s\\%s", gArtLanguage, pch);
|
||||
|
||||
sprintf(path, "art\\%s\\%s", gArtLanguage, ptr);
|
||||
if (artRead(str, data) == 0) {
|
||||
if (artRead(localizedPath, data) == 0) {
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
if (artRead(str, data) == 0) {
|
||||
if (artRead(artFileName, data) == 0) {
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
@@ -1057,8 +1043,8 @@ static int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data)
|
||||
}
|
||||
}
|
||||
|
||||
if (v4 != -1) {
|
||||
_db_current(v4);
|
||||
if (oldDb != -1) {
|
||||
_db_select(oldDb);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1071,7 +1057,7 @@ static void artCacheFreeImpl(void* ptr)
|
||||
}
|
||||
|
||||
// 0x419C88
|
||||
int buildFid(int objectType, int a2, int anim, int a3, int rotation)
|
||||
int buildFid(int objectType, int frmId, int animType, int a3, int rotation)
|
||||
{
|
||||
int v7, v8, v9, v10;
|
||||
|
||||
@@ -1081,13 +1067,13 @@ int buildFid(int objectType, int a2, int anim, int a3, int rotation)
|
||||
goto zero;
|
||||
}
|
||||
|
||||
if (anim == 33 || anim < 20 || anim > 35) {
|
||||
if (animType == ANIM_FIRE_DANCE || animType < ANIM_FALL_BACK || animType > ANIM_FALL_FRONT_BLOOD) {
|
||||
goto zero;
|
||||
}
|
||||
|
||||
v7 = ((a3 << 12) & 0xF000) | (anim << 16) & 0xFF0000 | 0x1000000;
|
||||
v8 = (rotation << 28) & 0x70000000 | v7;
|
||||
v9 = a2 & 0xFFF;
|
||||
v7 = ((a3 << 12) & 0xF000) | ((animType << 16) & 0xFF0000) | 0x1000000;
|
||||
v8 = ((rotation << 28) & 0x70000000) | v7;
|
||||
v9 = frmId & 0xFFF;
|
||||
|
||||
if (artExists(v9 | v8) != 0) {
|
||||
goto out;
|
||||
@@ -1108,7 +1094,7 @@ zero:
|
||||
|
||||
out:
|
||||
|
||||
return (v10 << 28) & 0x70000000 | (objectType << 24) | (anim << 16) & 0xFF0000 | (a3 << 12) & 0xF000 | a2 & 0xFFF;
|
||||
return ((v10 << 28) & 0x70000000) | (objectType << 24) | ((animType << 16) & 0xFF0000) | ((a3 << 12) & 0xF000) | (frmId & 0xFFF);
|
||||
}
|
||||
|
||||
// 0x419D60
|
||||
@@ -1172,3 +1158,115 @@ int artRead(const char* path, unsigned char* data)
|
||||
fileClose(stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x41A070
|
||||
int artWriteFrameData(unsigned char* data, File* stream, int count)
|
||||
{
|
||||
unsigned char* ptr = data;
|
||||
for (int index = 0; index < count; index++) {
|
||||
ArtFrame* frame = (ArtFrame*)ptr;
|
||||
|
||||
if (fileWriteInt16(stream, frame->width) == -1) return -1;
|
||||
if (fileWriteInt16(stream, frame->height) == -1) return -1;
|
||||
if (fileWriteInt32(stream, frame->size) == -1) return -1;
|
||||
if (fileWriteInt16(stream, frame->x) == -1) return -1;
|
||||
if (fileWriteInt16(stream, frame->y) == -1) return -1;
|
||||
if (fileWrite(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1;
|
||||
|
||||
ptr += sizeof(ArtFrame) + frame->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x41A138
|
||||
int artWriteHeader(Art* art, File* stream)
|
||||
{
|
||||
if (fileWriteInt32(stream, art->field_0) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->framesPerSecond) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->actionFrame) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->frameCount) == -1) return -1;
|
||||
if (fileWriteInt16List(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1;
|
||||
if (fileWriteInt16List(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1;
|
||||
if (fileWriteInt32List(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1;
|
||||
if (fileWriteInt32(stream, art->field_3A) == -1) return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x41A1E8
|
||||
int artWrite(const char* path, unsigned char* data)
|
||||
{
|
||||
if (data == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
File* stream = fileOpen(path, "wb");
|
||||
if (stream == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Art* art = (Art*)data;
|
||||
if (artWriteHeader(art, stream) == -1) {
|
||||
fileClose(stream);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int index = 0; index < ROTATION_COUNT; index++) {
|
||||
if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {
|
||||
if (artWriteFrameData(data + sizeof(Art) + art->dataOffsets[index], stream, art->frameCount) != 0) {
|
||||
fileClose(stream);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileClose(stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FrmImage::FrmImage()
|
||||
{
|
||||
_key = nullptr;
|
||||
_data = nullptr;
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
}
|
||||
|
||||
FrmImage::~FrmImage()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
|
||||
bool FrmImage::lock(unsigned int fid)
|
||||
{
|
||||
if (isLocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_data = artLockFrameDataReturningSize(fid, &_key, &_width, &_height);
|
||||
if (!_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FrmImage::unlock()
|
||||
{
|
||||
if (isLocked()) {
|
||||
artUnlock(_key);
|
||||
_key = nullptr;
|
||||
_data = nullptr;
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
31
src/art.h
@@ -8,6 +8,8 @@
|
||||
#include "platform_compat.h"
|
||||
#include "proto_types.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef enum Head {
|
||||
HEAD_INVALID,
|
||||
HEAD_MARCUS,
|
||||
@@ -127,7 +129,7 @@ unsigned char* artLockFrameData(int fid, int frame, int direction, CacheEntry**
|
||||
unsigned char* artLockFrameDataReturningSize(int fid, CacheEntry** out_cache_entry, int* widthPtr, int* heightPtr);
|
||||
int artUnlock(CacheEntry* cache_entry);
|
||||
int artCacheFlush();
|
||||
int artCopyFileName(int a1, int a2, char* a3);
|
||||
int artCopyFileName(int objectType, int a2, char* a3);
|
||||
int _art_get_code(int a1, int a2, char* a3, char* a4);
|
||||
char* artBuildFilePath(int a1);
|
||||
int artGetFramesPerSecond(Art* art);
|
||||
@@ -144,8 +146,31 @@ bool artExists(int fid);
|
||||
bool _art_fid_valid(int fid);
|
||||
int _art_alias_num(int a1);
|
||||
int artCritterFidShouldRun(int a1);
|
||||
int _art_alias_fid(int a1);
|
||||
int buildFid(int a1, int a2, int a3, int a4, int a5);
|
||||
int artAliasFid(int fid);
|
||||
int buildFid(int objectType, int frmId, int animType, int a4, int rotation);
|
||||
int artRead(const char* path, unsigned char* data);
|
||||
int artWrite(const char* path, unsigned char* data);
|
||||
|
||||
class FrmImage {
|
||||
public:
|
||||
FrmImage();
|
||||
~FrmImage();
|
||||
|
||||
bool isLocked() const { return _key != nullptr; }
|
||||
bool lock(unsigned int fid);
|
||||
void unlock();
|
||||
|
||||
int getWidth() const { return _width; }
|
||||
int getHeight() const { return _height; }
|
||||
unsigned char* getData() const { return _data; }
|
||||
|
||||
private:
|
||||
CacheEntry* _key;
|
||||
unsigned char* _data;
|
||||
int _width;
|
||||
int _height;
|
||||
};
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif
|
||||
|
||||
10
src/audio.cc
@@ -1,14 +1,16 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "db.h"
|
||||
#include "debug.h"
|
||||
#include "memory_manager.h"
|
||||
#include "pointer_registry.h"
|
||||
#include "sound.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
namespace fallout {
|
||||
|
||||
static bool _defaultCompressionFunc(char* filePath);
|
||||
static int audioSoundDecoderReadHandler(int fileHandle, void* buf, unsigned int size);
|
||||
@@ -252,3 +254,5 @@ void audioExit()
|
||||
gAudioListLength = 0;
|
||||
gAudioList = NULL;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "audio_file.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
int audioOpen(const char* fname, int mode, ...);
|
||||
int audioClose(int fileHandle);
|
||||
int audioRead(int fileHandle, void* buffer, unsigned int size);
|
||||
@@ -13,4 +15,6 @@ int audioWrite(int handle, const void* buf, unsigned int size);
|
||||
int audioInit(AudioFileIsCompressedProc* isCompressedProc);
|
||||
void audioExit();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* AUDIO_H */
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
namespace fallout {
|
||||
|
||||
#define AUDIO_ENGINE_SOUND_BUFFERS 8
|
||||
|
||||
struct AudioEngineSoundBuffer {
|
||||
@@ -430,3 +432,5 @@ bool audioEngineSoundBufferGetStatus(int soundBufferIndex, unsigned int* statusP
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef AUDIO_ENGINE_H
|
||||
#define AUDIO_ENGINE_H
|
||||
|
||||
namespace fallout {
|
||||
|
||||
#define AUDIO_ENGINE_SOUND_BUFFER_LOCK_FROM_WRITE_POS 0x00000001
|
||||
#define AUDIO_ENGINE_SOUND_BUFFER_LOCK_ENTIRE_BUFFER 0x00000002
|
||||
|
||||
@@ -26,4 +28,6 @@ bool audioEngineSoundBufferLock(int soundBufferIndex, unsigned int writePos, uns
|
||||
bool audioEngineSoundBufferUnlock(int soundBufferIndex, void* audioPtr1, unsigned int audioBytes1, void* audioPtr2, unsigned int audioBytes2);
|
||||
bool audioEngineSoundBufferGetStatus(int soundBufferIndex, unsigned int* status);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* AUDIO_ENGINE_H */
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#include "audio_file.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "memory_manager.h"
|
||||
#include "platform_compat.h"
|
||||
#include "pointer_registry.h"
|
||||
#include "sound.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
namespace fallout {
|
||||
|
||||
static bool _defaultCompressionFunc__(char* filePath);
|
||||
static int audioFileSoundDecoderReadHandler(int fileHandle, void* buffer, unsigned int size);
|
||||
@@ -103,7 +105,7 @@ int audioFileOpen(const char* fname, int flags, ...)
|
||||
audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize));
|
||||
audioFile->fileSize *= 2;
|
||||
} else {
|
||||
audioFile->fileSize = compat_filelength(fileno(stream));
|
||||
audioFile->fileSize = getFileSize(stream);
|
||||
}
|
||||
|
||||
audioFile->position = 0;
|
||||
@@ -249,3 +251,5 @@ void audioFileExit()
|
||||
gAudioFileListLength = 0;
|
||||
gAudioFileList = NULL;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "sound_decoder.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef enum AudioFileFlags {
|
||||
AUDIO_FILE_IN_USE = 0x01,
|
||||
AUDIO_FILE_COMPRESSED = 0x02,
|
||||
@@ -30,4 +32,6 @@ int audioFileWrite(int handle, const void* buf, unsigned int size);
|
||||
int audioFileInit(AudioFileIsCompressedProc* isCompressedProc);
|
||||
void audioFileExit();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* AUDIO_FILE_H */
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#include "automap.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "art.h"
|
||||
#include "color.h"
|
||||
#include "config.h"
|
||||
@@ -20,10 +25,7 @@
|
||||
#include "text_font.h"
|
||||
#include "window_manager.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
namespace fallout {
|
||||
|
||||
#define AUTOMAP_OFFSET_COUNT (AUTOMAP_MAP_COUNT * ELEVATION_COUNT)
|
||||
|
||||
@@ -66,7 +68,7 @@ static const int _defam[AUTOMAP_MAP_COUNT][ELEVATION_COUNT] = {
|
||||
};
|
||||
|
||||
// 0x41B560
|
||||
static const int _displayMapList[AUTOMAP_MAP_COUNT] = {
|
||||
static int _displayMapList[AUTOMAP_MAP_COUNT] = {
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
@@ -299,15 +301,10 @@ void automapShow(bool isInGame, bool isUsingScanner)
|
||||
int frmIds[AUTOMAP_FRM_COUNT];
|
||||
memcpy(frmIds, gAutomapFrmIds, sizeof(gAutomapFrmIds));
|
||||
|
||||
unsigned char* frmData[AUTOMAP_FRM_COUNT];
|
||||
CacheEntry* frmHandle[AUTOMAP_FRM_COUNT];
|
||||
FrmImage frmImages[AUTOMAP_FRM_COUNT];
|
||||
for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) {
|
||||
int fid = buildFid(6, frmIds[index], 0, 0, 0);
|
||||
frmData[index] = artLockFrameData(fid, 0, 0, &(frmHandle[index]));
|
||||
if (frmData[index] == NULL) {
|
||||
while (--index >= 0) {
|
||||
artUnlock(frmHandle[index]);
|
||||
}
|
||||
int fid = buildFid(OBJ_TYPE_INTERFACE, frmIds[index], 0, 0, 0);
|
||||
if (!frmImages[index].lock(fid)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -327,17 +324,53 @@ void automapShow(bool isInGame, bool isUsingScanner)
|
||||
int automapWindowY = (screenGetHeight() - AUTOMAP_WINDOW_HEIGHT) / 2;
|
||||
int window = windowCreate(automapWindowX, automapWindowY, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);
|
||||
|
||||
int scannerBtn = buttonCreate(window, 111, 454, 15, 16, -1, -1, -1, KEY_LOWERCASE_S, frmData[AUTOMAP_FRM_BUTTON_UP], frmData[AUTOMAP_FRM_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT);
|
||||
int scannerBtn = buttonCreate(window,
|
||||
111,
|
||||
454,
|
||||
15,
|
||||
16,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
KEY_LOWERCASE_S,
|
||||
frmImages[AUTOMAP_FRM_BUTTON_UP].getData(),
|
||||
frmImages[AUTOMAP_FRM_BUTTON_DOWN].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (scannerBtn != -1) {
|
||||
buttonSetCallbacks(scannerBtn, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
}
|
||||
|
||||
int cancelBtn = buttonCreate(window, 277, 454, 15, 16, -1, -1, -1, KEY_ESCAPE, frmData[AUTOMAP_FRM_BUTTON_UP], frmData[AUTOMAP_FRM_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT);
|
||||
int cancelBtn = buttonCreate(window,
|
||||
277,
|
||||
454,
|
||||
15,
|
||||
16,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
KEY_ESCAPE,
|
||||
frmImages[AUTOMAP_FRM_BUTTON_UP].getData(),
|
||||
frmImages[AUTOMAP_FRM_BUTTON_DOWN].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (cancelBtn != -1) {
|
||||
buttonSetCallbacks(cancelBtn, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
}
|
||||
|
||||
int switchBtn = buttonCreate(window, 457, 340, 42, 74, -1, -1, KEY_LOWERCASE_L, KEY_LOWERCASE_H, frmData[AUTOMAP_FRM_SWITCH_UP], frmData[AUTOMAP_FRM_SWITCH_DOWN], NULL, BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x01);
|
||||
int switchBtn = buttonCreate(window,
|
||||
457,
|
||||
340,
|
||||
42,
|
||||
74,
|
||||
-1,
|
||||
-1,
|
||||
KEY_LOWERCASE_L,
|
||||
KEY_LOWERCASE_H,
|
||||
frmImages[AUTOMAP_FRM_SWITCH_UP].getData(),
|
||||
frmImages[AUTOMAP_FRM_SWITCH_DOWN].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x01);
|
||||
if (switchBtn != -1) {
|
||||
buttonSetCallbacks(switchBtn, _gsound_toggle_butt_press_, _gsound_toggle_butt_press_);
|
||||
}
|
||||
@@ -358,7 +391,7 @@ void automapShow(bool isInGame, bool isUsingScanner)
|
||||
gAutomapFlags |= AUTOMAP_WITH_SCANNER;
|
||||
}
|
||||
|
||||
automapRenderInMapWindow(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], gAutomapFlags);
|
||||
automapRenderInMapWindow(window, elevation, frmImages[AUTOMAP_FRM_BACKGROUND].getData(), gAutomapFlags);
|
||||
|
||||
bool isoWasEnabled = isoDisable();
|
||||
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
|
||||
@@ -442,7 +475,7 @@ void automapShow(bool isInGame, bool isUsingScanner)
|
||||
}
|
||||
|
||||
if (needsRefresh) {
|
||||
automapRenderInMapWindow(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], gAutomapFlags);
|
||||
automapRenderInMapWindow(window, elevation, frmImages[AUTOMAP_FRM_BACKGROUND].getData(), gAutomapFlags);
|
||||
needsRefresh = false;
|
||||
}
|
||||
}
|
||||
@@ -453,10 +486,6 @@ void automapShow(bool isInGame, bool isUsingScanner)
|
||||
|
||||
windowDestroy(window);
|
||||
fontSetCurrent(oldFont);
|
||||
|
||||
for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandle[index]);
|
||||
}
|
||||
}
|
||||
|
||||
// Renders automap in Map window.
|
||||
@@ -482,7 +511,7 @@ static void automapRenderInMapWindow(int window, int elevation, unsigned char* b
|
||||
continue;
|
||||
}
|
||||
|
||||
int objectType = (object->fid & 0xF000000) >> 24;
|
||||
int objectType = FID_TYPE(object->fid);
|
||||
unsigned char objectColor;
|
||||
|
||||
if ((flags & AUTOMAP_IN_GAME) != 0) {
|
||||
@@ -1048,7 +1077,7 @@ static void _decode_map_data(int elevation)
|
||||
if (object->tile != -1 && (object->flags & OBJECT_SEEN) != 0) {
|
||||
int contentType;
|
||||
|
||||
int objectType = (object->fid & 0xF000000) >> 24;
|
||||
int objectType = FID_TYPE(object->fid);
|
||||
if (objectType == OBJ_TYPE_SCENERY && object->pid != PROTO_ID_0x2000158) {
|
||||
contentType = 2;
|
||||
} else if (objectType == OBJ_TYPE_WALL) {
|
||||
@@ -1153,3 +1182,12 @@ int automapGetHeader(AutomapHeader** automapHeaderPtr)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void automapSetDisplayMap(int map, bool available)
|
||||
{
|
||||
if (map >= 0 && map < AUTOMAP_MAP_COUNT) {
|
||||
_displayMapList[map] = available ? 0 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "db.h"
|
||||
#include "map_defs.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
#define AUTOMAP_DB ("AUTOMAP.DB")
|
||||
#define AUTOMAP_TMP ("AUTOMAP.TMP")
|
||||
|
||||
@@ -54,4 +56,8 @@ int automapRenderInPipboyWindow(int win, int map, int elevation);
|
||||
int automapSaveCurrent();
|
||||
int automapGetHeader(AutomapHeader** automapHeaderPtr);
|
||||
|
||||
void automapSetDisplayMap(int map, bool available);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* AUTOMAP_H */
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
static HANDLE gInterplayGenericAutorunMutex;
|
||||
#endif
|
||||
|
||||
namespace fallout {
|
||||
|
||||
// 0x4139C0
|
||||
bool autorunMutexCreate()
|
||||
{
|
||||
@@ -34,3 +36,5 @@ void autorunMutexClose()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#ifndef AUTORUN_H
|
||||
#define AUTORUN_H
|
||||
|
||||
namespace fallout {
|
||||
|
||||
bool autorunMutexCreate();
|
||||
void autorunMutexClose();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* AUTORUN_H */
|
||||
|
||||
12
src/cache.cc
@@ -1,13 +1,15 @@
|
||||
#include "cache.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "memory.h"
|
||||
#include "sound.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
namespace fallout {
|
||||
|
||||
// The initial number of cache entries in new cache.
|
||||
#define CACHE_ENTRIES_INITIAL_CAPACITY (100)
|
||||
@@ -613,3 +615,5 @@ static int cacheEntriesCompareByMostRecentHit(const void* a1, const void* a2)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "heap.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
#define INVALID_CACHE_ENTRY ((CacheEntry*)-1)
|
||||
|
||||
typedef enum CacheEntryFlags {
|
||||
@@ -66,4 +68,6 @@ bool cacheUnlock(Cache* cache, CacheEntry* cacheEntry);
|
||||
bool cacheFlush(Cache* cache);
|
||||
bool cachePrintStats(Cache* cache, char* dest);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CACHE_H */
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "db.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
extern int gCharacterEditorRemainingCharacterPoints;
|
||||
|
||||
int characterEditorShow(bool isCreationMode);
|
||||
@@ -13,4 +15,6 @@ int characterEditorSave(File* stream);
|
||||
int characterEditorLoad(File* stream);
|
||||
void characterEditorReset();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CHARACTER_EDITOR_H */
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#include "character_selector.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "art.h"
|
||||
#include "character_editor.h"
|
||||
#include "color.h"
|
||||
@@ -9,6 +15,7 @@
|
||||
#include "debug.h"
|
||||
#include "draw.h"
|
||||
#include "game.h"
|
||||
#include "game_config.h"
|
||||
#include "game_sound.h"
|
||||
#include "memory.h"
|
||||
#include "message.h"
|
||||
@@ -17,14 +24,14 @@
|
||||
#include "palette.h"
|
||||
#include "platform_compat.h"
|
||||
#include "proto.h"
|
||||
#include "sfall_config.h"
|
||||
#include "skill.h"
|
||||
#include "stat.h"
|
||||
#include "text_font.h"
|
||||
#include "trait.h"
|
||||
#include "window_manager.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
namespace fallout {
|
||||
|
||||
#define CS_WINDOW_WIDTH (640)
|
||||
#define CS_WINDOW_HEIGHT (480)
|
||||
@@ -57,12 +64,28 @@
|
||||
#define CS_WINDOW_SECONDARY_STAT_MID_X (379)
|
||||
#define CS_WINDOW_BIO_X (438)
|
||||
|
||||
typedef enum PremadeCharacter {
|
||||
PREMADE_CHARACTER_NARG,
|
||||
PREMADE_CHARACTER_CHITSA,
|
||||
PREMADE_CHARACTER_MINGUN,
|
||||
PREMADE_CHARACTER_COUNT,
|
||||
} PremadeCharacter;
|
||||
|
||||
typedef struct PremadeCharacterDescription {
|
||||
char fileName[20];
|
||||
int face;
|
||||
char field_18[20];
|
||||
} PremadeCharacterDescription;
|
||||
|
||||
static bool characterSelectorWindowInit();
|
||||
static void characterSelectorWindowFree();
|
||||
static bool characterSelectorWindowRefresh();
|
||||
static bool characterSelectorWindowRenderFace();
|
||||
static bool characterSelectorWindowRenderStats();
|
||||
static bool characterSelectorWindowRenderBio();
|
||||
static bool characterSelectorWindowFatalError(bool result);
|
||||
|
||||
static void premadeCharactersLocalizePath(char* path);
|
||||
|
||||
// 0x51C84C
|
||||
static int gCurrentPremadeCharacter = PREMADE_CHARACTER_NARG;
|
||||
@@ -75,7 +98,7 @@ static PremadeCharacterDescription gPremadeCharacterDescriptions[PREMADE_CHARACT
|
||||
};
|
||||
|
||||
// 0x51C8D4
|
||||
static const int gPremadeCharacterCount = PREMADE_CHARACTER_COUNT;
|
||||
static int gPremadeCharacterCount = PREMADE_CHARACTER_COUNT;
|
||||
|
||||
// 0x51C7F8
|
||||
static int gCharacterSelectorWindow = -1;
|
||||
@@ -89,92 +112,35 @@ static unsigned char* gCharacterSelectorBackground = NULL;
|
||||
// 0x51C804
|
||||
static int gCharacterSelectorWindowPreviousButton = -1;
|
||||
|
||||
// 0x51C808
|
||||
static CacheEntry* gCharacterSelectorWindowPreviousButtonUpFrmHandle = NULL;
|
||||
|
||||
// 0x51C80C
|
||||
static CacheEntry* gCharacterSelectorWindowPreviousButtonDownFrmHandle = NULL;
|
||||
|
||||
// 0x51C810
|
||||
static int gCharacterSelectorWindowNextButton = -1;
|
||||
|
||||
// 0x51C814
|
||||
static CacheEntry* gCharacterSelectorWindowNextButtonUpFrmHandle = NULL;
|
||||
|
||||
// 0x51C818
|
||||
static CacheEntry* gCharacterSelectorWindowNextButtonDownFrmHandle = NULL;
|
||||
|
||||
// 0x51C81C
|
||||
static int gCharacterSelectorWindowTakeButton = -1;
|
||||
|
||||
// 0x51C820
|
||||
static CacheEntry* gCharacterSelectorWindowTakeButtonUpFrmHandle = NULL;
|
||||
|
||||
// 0x51C824
|
||||
static CacheEntry* gCharacterSelectorWindowTakeButtonDownFrmHandle = NULL;
|
||||
|
||||
// 0x51C828
|
||||
static int gCharacterSelectorWindowModifyButton = -1;
|
||||
|
||||
// 0x51C82C
|
||||
static CacheEntry* gCharacterSelectorWindowModifyButtonUpFrmHandle = NULL;
|
||||
|
||||
// 0x51C830
|
||||
static CacheEntry* gCharacterSelectorWindowModifyButtonDownFrmHandle = NULL;
|
||||
|
||||
// 0x51C834
|
||||
static int gCharacterSelectorWindowCreateButton = -1;
|
||||
|
||||
// 0x51C838
|
||||
static CacheEntry* gCharacterSelectorWindowCreateButtonUpFrmHandle = NULL;
|
||||
|
||||
// 0x51C83C
|
||||
static CacheEntry* gCharacterSelectorWindowCreateButtonDownFrmHandle = NULL;
|
||||
|
||||
// 0x51C840
|
||||
static int gCharacterSelectorWindowBackButton = -1;
|
||||
|
||||
// 0x51C844
|
||||
static CacheEntry* gCharacterSelectorWindowBackButtonUpFrmHandle = NULL;
|
||||
static FrmImage _takeButtonNormalFrmImage;
|
||||
static FrmImage _takeButtonPressedFrmImage;
|
||||
static FrmImage _modifyButtonNormalFrmImage;
|
||||
static FrmImage _modifyButtonPressedFrmImage;
|
||||
static FrmImage _createButtonNormalFrmImage;
|
||||
static FrmImage _createButtonPressedFrmImage;
|
||||
static FrmImage _backButtonNormalFrmImage;
|
||||
static FrmImage _backButtonPressedFrmImage;
|
||||
static FrmImage _nextButtonNormalFrmImage;
|
||||
static FrmImage _nextButtonPressedFrmImage;
|
||||
static FrmImage _previousButtonNormalFrmImage;
|
||||
static FrmImage _previousButtonPressedFrmImage;
|
||||
|
||||
// 0x51C848
|
||||
static CacheEntry* gCharacterSelectorWindowBackButtonDownFrmHandle = NULL;
|
||||
|
||||
// 0x667764
|
||||
static unsigned char* gCharacterSelectorWindowTakeButtonUpFrmData;
|
||||
|
||||
// 0x667768
|
||||
static unsigned char* gCharacterSelectorWindowModifyButtonDownFrmData;
|
||||
|
||||
// 0x66776C
|
||||
static unsigned char* gCharacterSelectorWindowBackButtonUpFrmData;
|
||||
|
||||
// 0x667770
|
||||
static unsigned char* gCharacterSelectorWindowCreateButtonUpFrmData;
|
||||
|
||||
// 0x667774
|
||||
static unsigned char* gCharacterSelectorWindowModifyButtonUpFrmData;
|
||||
|
||||
// 0x667778
|
||||
static unsigned char* gCharacterSelectorWindowBackButtonDownFrmData;
|
||||
|
||||
// 0x66777C
|
||||
static unsigned char* gCharacterSelectorWindowCreateButtonDownFrmData;
|
||||
|
||||
// 0x667780
|
||||
static unsigned char* gCharacterSelectorWindowTakeButtonDownFrmData;
|
||||
|
||||
// 0x667784
|
||||
static unsigned char* gCharacterSelectorWindowNextButtonDownFrmData;
|
||||
|
||||
// 0x667788
|
||||
static unsigned char* gCharacterSelectorWindowNextButtonUpFrmData;
|
||||
|
||||
// 0x66778C
|
||||
static unsigned char* gCharacterSelectorWindowPreviousButtonUpFrmData;
|
||||
|
||||
// 0x667790
|
||||
static unsigned char* gCharacterSelectorWindowPreviousButtonDownFrmData;
|
||||
static std::vector<PremadeCharacterDescription> gCustomPremadeCharacterDescriptions;
|
||||
|
||||
// 0x4A71D0
|
||||
int characterSelectorOpen()
|
||||
@@ -221,6 +187,8 @@ int characterSelectorOpen()
|
||||
if (characterEditorShow(1) == 0) {
|
||||
rc = 2;
|
||||
done = true;
|
||||
} else {
|
||||
characterSelectorWindowRefresh();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -229,6 +197,8 @@ int characterSelectorOpen()
|
||||
if (!characterEditorShow(1)) {
|
||||
rc = 2;
|
||||
done = true;
|
||||
} else {
|
||||
characterSelectorWindowRefresh();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -279,9 +249,6 @@ int characterSelectorOpen()
|
||||
// 0x4A7468
|
||||
static bool characterSelectorWindowInit()
|
||||
{
|
||||
int backgroundFid;
|
||||
unsigned char* backgroundFrmData;
|
||||
|
||||
if (gCharacterSelectorWindow != -1) {
|
||||
return false;
|
||||
}
|
||||
@@ -290,22 +257,21 @@ static bool characterSelectorWindowInit()
|
||||
int characterSelectorWindowY = (screenGetHeight() - CS_WINDOW_HEIGHT) / 2;
|
||||
gCharacterSelectorWindow = windowCreate(characterSelectorWindowX, characterSelectorWindowY, CS_WINDOW_WIDTH, CS_WINDOW_HEIGHT, _colorTable[0], 0);
|
||||
if (gCharacterSelectorWindow == -1) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
gCharacterSelectorWindowBuffer = windowGetBuffer(gCharacterSelectorWindow);
|
||||
if (gCharacterSelectorWindowBuffer == NULL) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
CacheEntry* backgroundFrmHandle;
|
||||
backgroundFid = buildFid(6, 174, 0, 0, 0);
|
||||
backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle);
|
||||
if (backgroundFrmData == NULL) {
|
||||
goto err;
|
||||
FrmImage backgroundFrmImage;
|
||||
int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 174, 0, 0, 0);
|
||||
if (!backgroundFrmImage.lock(backgroundFid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
blitBufferToBuffer(backgroundFrmData,
|
||||
blitBufferToBuffer(backgroundFrmImage.getData(),
|
||||
CS_WINDOW_WIDTH,
|
||||
CS_WINDOW_HEIGHT,
|
||||
CS_WINDOW_WIDTH,
|
||||
@@ -314,30 +280,28 @@ static bool characterSelectorWindowInit()
|
||||
|
||||
gCharacterSelectorBackground = (unsigned char*)internal_malloc(CS_WINDOW_BACKGROUND_WIDTH * CS_WINDOW_BACKGROUND_HEIGHT);
|
||||
if (gCharacterSelectorBackground == NULL)
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
|
||||
blitBufferToBuffer(backgroundFrmData + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X,
|
||||
blitBufferToBuffer(backgroundFrmImage.getData() + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X,
|
||||
CS_WINDOW_BACKGROUND_WIDTH,
|
||||
CS_WINDOW_BACKGROUND_HEIGHT,
|
||||
CS_WINDOW_WIDTH,
|
||||
gCharacterSelectorBackground,
|
||||
CS_WINDOW_BACKGROUND_WIDTH);
|
||||
|
||||
artUnlock(backgroundFrmHandle);
|
||||
backgroundFrmImage.unlock();
|
||||
|
||||
int fid;
|
||||
|
||||
// Setup "Previous" button.
|
||||
fid = buildFid(6, 122, 0, 0, 0);
|
||||
gCharacterSelectorWindowPreviousButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowPreviousButtonUpFrmHandle);
|
||||
if (gCharacterSelectorWindowPreviousButtonUpFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 122, 0, 0, 0);
|
||||
if (!_previousButtonNormalFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
fid = buildFid(6, 123, 0, 0, 0);
|
||||
gCharacterSelectorWindowPreviousButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowPreviousButtonDownFrmHandle);
|
||||
if (gCharacterSelectorWindowPreviousButtonDownFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 123, 0, 0, 0);
|
||||
if (!_previousButtonPressedFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
gCharacterSelectorWindowPreviousButton = buttonCreate(gCharacterSelectorWindow,
|
||||
@@ -349,27 +313,25 @@ static bool characterSelectorWindowInit()
|
||||
-1,
|
||||
-1,
|
||||
500,
|
||||
gCharacterSelectorWindowPreviousButtonUpFrmData,
|
||||
gCharacterSelectorWindowPreviousButtonDownFrmData,
|
||||
_previousButtonNormalFrmImage.getData(),
|
||||
_previousButtonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
0);
|
||||
if (gCharacterSelectorWindowPreviousButton == -1) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
buttonSetCallbacks(gCharacterSelectorWindowPreviousButton, _gsound_med_butt_press, _gsound_med_butt_release);
|
||||
|
||||
// Setup "Next" button.
|
||||
fid = buildFid(6, 124, 0, 0, 0);
|
||||
gCharacterSelectorWindowNextButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowNextButtonUpFrmHandle);
|
||||
if (gCharacterSelectorWindowNextButtonUpFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 124, 0, 0, 0);
|
||||
if (!_nextButtonNormalFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
fid = buildFid(6, 125, 0, 0, 0);
|
||||
gCharacterSelectorWindowNextButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowNextButtonDownFrmHandle);
|
||||
if (gCharacterSelectorWindowNextButtonDownFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 125, 0, 0, 0);
|
||||
if (!_nextButtonPressedFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
gCharacterSelectorWindowNextButton = buttonCreate(gCharacterSelectorWindow,
|
||||
@@ -381,27 +343,25 @@ static bool characterSelectorWindowInit()
|
||||
-1,
|
||||
-1,
|
||||
501,
|
||||
gCharacterSelectorWindowNextButtonUpFrmData,
|
||||
gCharacterSelectorWindowNextButtonDownFrmData,
|
||||
_nextButtonNormalFrmImage.getData(),
|
||||
_nextButtonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
0);
|
||||
if (gCharacterSelectorWindowNextButton == -1) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
buttonSetCallbacks(gCharacterSelectorWindowNextButton, _gsound_med_butt_press, _gsound_med_butt_release);
|
||||
|
||||
// Setup "Take" button.
|
||||
fid = buildFid(6, 8, 0, 0, 0);
|
||||
gCharacterSelectorWindowTakeButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowTakeButtonUpFrmHandle);
|
||||
if (gCharacterSelectorWindowTakeButtonUpFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);
|
||||
if (!_takeButtonNormalFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
fid = buildFid(6, 9, 0, 0, 0);
|
||||
gCharacterSelectorWindowTakeButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowTakeButtonDownFrmHandle);
|
||||
if (gCharacterSelectorWindowTakeButtonDownFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);
|
||||
if (!_takeButtonPressedFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
gCharacterSelectorWindowTakeButton = buttonCreate(gCharacterSelectorWindow,
|
||||
@@ -413,26 +373,24 @@ static bool characterSelectorWindowInit()
|
||||
-1,
|
||||
-1,
|
||||
KEY_LOWERCASE_T,
|
||||
gCharacterSelectorWindowTakeButtonUpFrmData,
|
||||
gCharacterSelectorWindowTakeButtonDownFrmData,
|
||||
_takeButtonNormalFrmImage.getData(),
|
||||
_takeButtonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (gCharacterSelectorWindowTakeButton == -1) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
buttonSetCallbacks(gCharacterSelectorWindowTakeButton, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
|
||||
// Setup "Modify" button.
|
||||
fid = buildFid(6, 8, 0, 0, 0);
|
||||
gCharacterSelectorWindowModifyButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowModifyButtonUpFrmHandle);
|
||||
if (gCharacterSelectorWindowModifyButtonUpFrmData == NULL)
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);
|
||||
if (!_modifyButtonNormalFrmImage.lock(fid))
|
||||
return characterSelectorWindowFatalError(false);
|
||||
|
||||
fid = buildFid(6, 9, 0, 0, 0);
|
||||
gCharacterSelectorWindowModifyButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowModifyButtonDownFrmHandle);
|
||||
if (gCharacterSelectorWindowModifyButtonDownFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);
|
||||
if (!_modifyButtonPressedFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
gCharacterSelectorWindowModifyButton = buttonCreate(gCharacterSelectorWindow,
|
||||
@@ -444,27 +402,25 @@ static bool characterSelectorWindowInit()
|
||||
-1,
|
||||
-1,
|
||||
KEY_LOWERCASE_M,
|
||||
gCharacterSelectorWindowModifyButtonUpFrmData,
|
||||
gCharacterSelectorWindowModifyButtonDownFrmData,
|
||||
_modifyButtonNormalFrmImage.getData(),
|
||||
_modifyButtonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (gCharacterSelectorWindowModifyButton == -1) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
buttonSetCallbacks(gCharacterSelectorWindowModifyButton, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
|
||||
// Setup "Create" button.
|
||||
fid = buildFid(6, 8, 0, 0, 0);
|
||||
gCharacterSelectorWindowCreateButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowCreateButtonUpFrmHandle);
|
||||
if (gCharacterSelectorWindowCreateButtonUpFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);
|
||||
if (!_createButtonNormalFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
fid = buildFid(6, 9, 0, 0, 0);
|
||||
gCharacterSelectorWindowCreateButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowCreateButtonDownFrmHandle);
|
||||
if (gCharacterSelectorWindowCreateButtonDownFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);
|
||||
if (!_createButtonPressedFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
gCharacterSelectorWindowCreateButton = buttonCreate(gCharacterSelectorWindow,
|
||||
@@ -476,27 +432,25 @@ static bool characterSelectorWindowInit()
|
||||
-1,
|
||||
-1,
|
||||
KEY_LOWERCASE_C,
|
||||
gCharacterSelectorWindowCreateButtonUpFrmData,
|
||||
gCharacterSelectorWindowCreateButtonDownFrmData,
|
||||
_createButtonNormalFrmImage.getData(),
|
||||
_createButtonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (gCharacterSelectorWindowCreateButton == -1) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
buttonSetCallbacks(gCharacterSelectorWindowCreateButton, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
|
||||
// Setup "Back" button.
|
||||
fid = buildFid(6, 8, 0, 0, 0);
|
||||
gCharacterSelectorWindowBackButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowBackButtonUpFrmHandle);
|
||||
if (gCharacterSelectorWindowBackButtonUpFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);
|
||||
if (!_backButtonNormalFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
fid = buildFid(6, 9, 0, 0, 0);
|
||||
gCharacterSelectorWindowBackButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowBackButtonDownFrmHandle);
|
||||
if (gCharacterSelectorWindowBackButtonDownFrmData == NULL) {
|
||||
goto err;
|
||||
fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);
|
||||
if (!_backButtonPressedFrmImage.lock(fid)) {
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
gCharacterSelectorWindowBackButton = buttonCreate(gCharacterSelectorWindow,
|
||||
@@ -508,12 +462,12 @@ static bool characterSelectorWindowInit()
|
||||
-1,
|
||||
-1,
|
||||
KEY_ESCAPE,
|
||||
gCharacterSelectorWindowBackButtonUpFrmData,
|
||||
gCharacterSelectorWindowBackButtonDownFrmData,
|
||||
_backButtonNormalFrmImage.getData(),
|
||||
_backButtonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (gCharacterSelectorWindowBackButton == -1) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
buttonSetCallbacks(gCharacterSelectorWindowBackButton, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
@@ -523,16 +477,10 @@ static bool characterSelectorWindowInit()
|
||||
windowRefresh(gCharacterSelectorWindow);
|
||||
|
||||
if (!characterSelectorWindowRefresh()) {
|
||||
goto err;
|
||||
return characterSelectorWindowFatalError(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
err:
|
||||
|
||||
characterSelectorWindowFree();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 0x4A7AD4
|
||||
@@ -547,102 +495,48 @@ static void characterSelectorWindowFree()
|
||||
gCharacterSelectorWindowPreviousButton = -1;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowPreviousButtonDownFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowPreviousButtonDownFrmHandle);
|
||||
gCharacterSelectorWindowPreviousButtonDownFrmHandle = NULL;
|
||||
gCharacterSelectorWindowPreviousButtonDownFrmData = NULL;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowPreviousButtonUpFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowPreviousButtonUpFrmHandle);
|
||||
gCharacterSelectorWindowPreviousButtonUpFrmHandle = NULL;
|
||||
gCharacterSelectorWindowPreviousButtonUpFrmData = NULL;
|
||||
}
|
||||
_previousButtonNormalFrmImage.unlock();
|
||||
_previousButtonPressedFrmImage.unlock();
|
||||
|
||||
if (gCharacterSelectorWindowNextButton != -1) {
|
||||
buttonDestroy(gCharacterSelectorWindowNextButton);
|
||||
gCharacterSelectorWindowNextButton = -1;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowNextButtonDownFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowNextButtonDownFrmHandle);
|
||||
gCharacterSelectorWindowNextButtonDownFrmHandle = NULL;
|
||||
gCharacterSelectorWindowNextButtonDownFrmData = NULL;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowNextButtonUpFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowNextButtonUpFrmHandle);
|
||||
gCharacterSelectorWindowNextButtonUpFrmHandle = NULL;
|
||||
gCharacterSelectorWindowNextButtonUpFrmData = NULL;
|
||||
}
|
||||
_nextButtonNormalFrmImage.unlock();
|
||||
_nextButtonPressedFrmImage.unlock();
|
||||
|
||||
if (gCharacterSelectorWindowTakeButton != -1) {
|
||||
buttonDestroy(gCharacterSelectorWindowTakeButton);
|
||||
gCharacterSelectorWindowTakeButton = -1;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowTakeButtonDownFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowTakeButtonDownFrmHandle);
|
||||
gCharacterSelectorWindowTakeButtonDownFrmHandle = NULL;
|
||||
gCharacterSelectorWindowTakeButtonDownFrmData = NULL;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowTakeButtonUpFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowTakeButtonUpFrmHandle);
|
||||
gCharacterSelectorWindowTakeButtonUpFrmHandle = NULL;
|
||||
gCharacterSelectorWindowTakeButtonUpFrmData = NULL;
|
||||
}
|
||||
_takeButtonNormalFrmImage.unlock();
|
||||
_takeButtonPressedFrmImage.unlock();
|
||||
|
||||
if (gCharacterSelectorWindowModifyButton != -1) {
|
||||
buttonDestroy(gCharacterSelectorWindowModifyButton);
|
||||
gCharacterSelectorWindowModifyButton = -1;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowModifyButtonDownFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowModifyButtonDownFrmHandle);
|
||||
gCharacterSelectorWindowModifyButtonDownFrmHandle = NULL;
|
||||
gCharacterSelectorWindowModifyButtonDownFrmData = NULL;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowModifyButtonUpFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowModifyButtonUpFrmHandle);
|
||||
gCharacterSelectorWindowModifyButtonUpFrmHandle = NULL;
|
||||
gCharacterSelectorWindowModifyButtonUpFrmData = NULL;
|
||||
}
|
||||
_modifyButtonNormalFrmImage.unlock();
|
||||
_modifyButtonPressedFrmImage.unlock();
|
||||
|
||||
if (gCharacterSelectorWindowCreateButton != -1) {
|
||||
buttonDestroy(gCharacterSelectorWindowCreateButton);
|
||||
gCharacterSelectorWindowCreateButton = -1;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowCreateButtonDownFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowCreateButtonDownFrmHandle);
|
||||
gCharacterSelectorWindowCreateButtonDownFrmHandle = NULL;
|
||||
gCharacterSelectorWindowCreateButtonDownFrmData = NULL;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowCreateButtonUpFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowCreateButtonUpFrmHandle);
|
||||
gCharacterSelectorWindowCreateButtonUpFrmHandle = NULL;
|
||||
gCharacterSelectorWindowCreateButtonUpFrmData = NULL;
|
||||
}
|
||||
_createButtonNormalFrmImage.unlock();
|
||||
_createButtonPressedFrmImage.unlock();
|
||||
|
||||
if (gCharacterSelectorWindowBackButton != -1) {
|
||||
buttonDestroy(gCharacterSelectorWindowBackButton);
|
||||
gCharacterSelectorWindowBackButton = -1;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowBackButtonDownFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowBackButtonDownFrmHandle);
|
||||
gCharacterSelectorWindowBackButtonDownFrmHandle = NULL;
|
||||
gCharacterSelectorWindowBackButtonDownFrmData = NULL;
|
||||
}
|
||||
|
||||
if (gCharacterSelectorWindowBackButtonUpFrmData != NULL) {
|
||||
artUnlock(gCharacterSelectorWindowBackButtonUpFrmHandle);
|
||||
gCharacterSelectorWindowBackButtonUpFrmHandle = NULL;
|
||||
gCharacterSelectorWindowBackButtonUpFrmData = NULL;
|
||||
}
|
||||
_backButtonNormalFrmImage.unlock();
|
||||
_backButtonPressedFrmImage.unlock();
|
||||
|
||||
if (gCharacterSelectorBackground != NULL) {
|
||||
internal_free(gCharacterSelectorBackground);
|
||||
@@ -657,7 +551,9 @@ static void characterSelectorWindowFree()
|
||||
static bool characterSelectorWindowRefresh()
|
||||
{
|
||||
char path[COMPAT_MAX_PATH];
|
||||
sprintf(path, "%s.gcd", gPremadeCharacterDescriptions[gCurrentPremadeCharacter].fileName);
|
||||
sprintf(path, "%s.gcd", gCustomPremadeCharacterDescriptions[gCurrentPremadeCharacter].fileName);
|
||||
premadeCharactersLocalizePath(path);
|
||||
|
||||
if (_proto_dude_init(path) == -1) {
|
||||
debugPrint("\n ** Error in dude init! **\n");
|
||||
return false;
|
||||
@@ -687,18 +583,17 @@ static bool characterSelectorWindowRenderFace()
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
CacheEntry* faceFrmHandle;
|
||||
int faceFid = buildFid(6, gPremadeCharacterDescriptions[gCurrentPremadeCharacter].face, 0, 0, 0);
|
||||
Art* frm = artLock(faceFid, &faceFrmHandle);
|
||||
if (frm != NULL) {
|
||||
unsigned char* data = artGetFrameData(frm, 0, 0);
|
||||
FrmImage faceFrmImage;
|
||||
int faceFid = buildFid(OBJ_TYPE_INTERFACE, gCustomPremadeCharacterDescriptions[gCurrentPremadeCharacter].face, 0, 0, 0);
|
||||
if (faceFrmImage.lock(faceFid)) {
|
||||
unsigned char* data = faceFrmImage.getData();
|
||||
if (data != NULL) {
|
||||
int width = artGetWidth(frm, 0, 0);
|
||||
int height = artGetHeight(frm, 0, 0);
|
||||
int width = faceFrmImage.getWidth();
|
||||
int height = faceFrmImage.getHeight();
|
||||
blitBufferToBufferTrans(data, width, height, width, (gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * 23 + 27), CS_WINDOW_WIDTH);
|
||||
success = true;
|
||||
}
|
||||
artUnlock(faceFrmHandle);
|
||||
faceFrmImage.unlock();
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -963,7 +858,8 @@ static bool characterSelectorWindowRenderBio()
|
||||
fontSetCurrent(101);
|
||||
|
||||
char path[COMPAT_MAX_PATH];
|
||||
sprintf(path, "%s.bio", gPremadeCharacterDescriptions[gCurrentPremadeCharacter].fileName);
|
||||
sprintf(path, "%s.bio", gCustomPremadeCharacterDescriptions[gCurrentPremadeCharacter].fileName);
|
||||
premadeCharactersLocalizePath(path);
|
||||
|
||||
File* stream = fileOpen(path, "rt");
|
||||
if (stream != NULL) {
|
||||
@@ -983,3 +879,124 @@ static bool characterSelectorWindowRenderBio()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: Inlined.
|
||||
//
|
||||
// 0x4A8BD0
|
||||
static bool characterSelectorWindowFatalError(bool result)
|
||||
{
|
||||
characterSelectorWindowFree();
|
||||
return result;
|
||||
}
|
||||
|
||||
void premadeCharactersInit()
|
||||
{
|
||||
char* fileNamesString;
|
||||
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PREMADE_CHARACTERS_FILE_NAMES_KEY, &fileNamesString);
|
||||
if (fileNamesString != NULL && *fileNamesString == '\0') {
|
||||
fileNamesString = NULL;
|
||||
}
|
||||
|
||||
char* faceFidsString;
|
||||
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PREMADE_CHARACTERS_FACE_FIDS_KEY, &faceFidsString);
|
||||
if (faceFidsString != NULL && *faceFidsString == '\0') {
|
||||
faceFidsString = NULL;
|
||||
}
|
||||
|
||||
if (fileNamesString != NULL && faceFidsString != NULL) {
|
||||
int fileNamesLength = 0;
|
||||
for (char* pch = fileNamesString; pch != NULL; pch = strchr(pch + 1, ',')) {
|
||||
fileNamesLength++;
|
||||
}
|
||||
|
||||
int faceFidsLength = 0;
|
||||
for (char* pch = faceFidsString; pch != NULL; pch = strchr(pch + 1, ',')) {
|
||||
faceFidsLength++;
|
||||
}
|
||||
|
||||
int premadeCharactersCount = std::min(fileNamesLength, faceFidsLength);
|
||||
gCustomPremadeCharacterDescriptions.resize(premadeCharactersCount);
|
||||
|
||||
for (int index = 0; index < premadeCharactersCount; index++) {
|
||||
char* pch;
|
||||
|
||||
pch = strchr(fileNamesString, ',');
|
||||
if (pch != NULL) {
|
||||
*pch = '\0';
|
||||
}
|
||||
|
||||
if (strlen(fileNamesString) > 11) {
|
||||
// Sfall fails here.
|
||||
continue;
|
||||
}
|
||||
|
||||
sprintf(gCustomPremadeCharacterDescriptions[index].fileName, "premade\\%s", fileNamesString);
|
||||
|
||||
if (pch != NULL) {
|
||||
*pch = ',';
|
||||
}
|
||||
|
||||
fileNamesString = pch + 1;
|
||||
|
||||
pch = strchr(faceFidsString, ',');
|
||||
if (pch != NULL) {
|
||||
*pch = '\0';
|
||||
}
|
||||
|
||||
gCustomPremadeCharacterDescriptions[index].face = atoi(faceFidsString);
|
||||
|
||||
if (pch != NULL) {
|
||||
*pch = ',';
|
||||
}
|
||||
|
||||
faceFidsString = pch + 1;
|
||||
|
||||
gCustomPremadeCharacterDescriptions[index].field_18[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if (gCustomPremadeCharacterDescriptions.empty()) {
|
||||
gCustomPremadeCharacterDescriptions.resize(PREMADE_CHARACTER_COUNT);
|
||||
|
||||
for (int index = 0; index < PREMADE_CHARACTER_COUNT; index++) {
|
||||
strcpy(gCustomPremadeCharacterDescriptions[index].fileName, gPremadeCharacterDescriptions[index].fileName);
|
||||
gCustomPremadeCharacterDescriptions[index].face = gPremadeCharacterDescriptions[index].face;
|
||||
strcpy(gCustomPremadeCharacterDescriptions[index].field_18, gPremadeCharacterDescriptions[index].field_18);
|
||||
}
|
||||
}
|
||||
|
||||
gPremadeCharacterCount = gCustomPremadeCharacterDescriptions.size();
|
||||
}
|
||||
|
||||
void premadeCharactersExit()
|
||||
{
|
||||
gCustomPremadeCharacterDescriptions.clear();
|
||||
}
|
||||
|
||||
static void premadeCharactersLocalizePath(char* path)
|
||||
{
|
||||
if (compat_strnicmp(path, "premade\\", 8) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* language;
|
||||
if (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (compat_stricmp(language, ENGLISH) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char localizedPath[COMPAT_MAX_PATH];
|
||||
strncpy(localizedPath, path, 8);
|
||||
strcpy(localizedPath + 8, language);
|
||||
strcpy(localizedPath + 8 + strlen(language), path + 7);
|
||||
|
||||
int fileSize;
|
||||
if (dbGetFileSize(localizedPath, &fileSize) == 0) {
|
||||
strcpy(path, localizedPath);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
#ifndef CHARACTER_SELECTOR_H
|
||||
#define CHARACTER_SELECTOR_H
|
||||
|
||||
typedef enum PremadeCharacter {
|
||||
PREMADE_CHARACTER_NARG,
|
||||
PREMADE_CHARACTER_CHITSA,
|
||||
PREMADE_CHARACTER_MINGUN,
|
||||
PREMADE_CHARACTER_COUNT,
|
||||
} PremadeCharacter;
|
||||
|
||||
typedef struct PremadeCharacterDescription {
|
||||
char fileName[20];
|
||||
int face;
|
||||
char field_18[20];
|
||||
} PremadeCharacterDescription;
|
||||
namespace fallout {
|
||||
|
||||
int characterSelectorOpen();
|
||||
|
||||
void premadeCharactersInit();
|
||||
void premadeCharactersExit();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CHARACTER_SELECTOR_H */
|
||||
|
||||
90
src/color.cc
@@ -1,11 +1,21 @@
|
||||
#include "color.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
namespace fallout {
|
||||
|
||||
#include <algorithm>
|
||||
#define COLOR_PALETTE_STACK_CAPACITY 16
|
||||
|
||||
typedef struct ColorPaletteStackEntry {
|
||||
unsigned char mappedColors[256];
|
||||
unsigned char cmap[768];
|
||||
unsigned char colorTable[32768];
|
||||
} ColorPaletteStackEntry;
|
||||
|
||||
static int colorPaletteFileOpen(const char* filePath, int flags);
|
||||
static int colorPaletteFileRead(int fd, void* buffer, size_t size);
|
||||
@@ -25,6 +35,12 @@ static char _aColor_cNoError[] = "color.c: No errors\n";
|
||||
// 0x50F95C
|
||||
static char _aColor_cColorTa[] = "color.c: color table not found\n";
|
||||
|
||||
// 0x50F984
|
||||
static char _aColor_cColorpa[] = "color.c: colorpalettestack overflow";
|
||||
|
||||
// 0x50F9AC
|
||||
static char aColor_cColor_0[] = "color.c: colorpalettestack underflow";
|
||||
|
||||
// 0x51DF10
|
||||
static char* _errorStr = _aColor_cNoError;
|
||||
|
||||
@@ -54,6 +70,9 @@ unsigned char _cmap[768] = {
|
||||
0x3F, 0x3F, 0x3F
|
||||
};
|
||||
|
||||
// 0x673050
|
||||
static ColorPaletteStackEntry* gColorPaletteStack[COLOR_PALETTE_STACK_CAPACITY];
|
||||
|
||||
// 0x673090
|
||||
unsigned char _systemCmap[256 * 3];
|
||||
|
||||
@@ -78,6 +97,9 @@ unsigned char _colorMixMulTable[65536];
|
||||
// 0x6A38D0
|
||||
unsigned char _colorTable[32768];
|
||||
|
||||
// 0x6AB8D0
|
||||
static int gColorPaletteStackSize;
|
||||
|
||||
// 0x6AB928
|
||||
static ColorPaletteFileReadProc* gColorPaletteFileReadProc;
|
||||
|
||||
@@ -572,6 +594,60 @@ void colorSetBrightness(double value)
|
||||
_setSystemPalette(_systemCmap);
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C8828
|
||||
bool colorPushColorPalette()
|
||||
{
|
||||
if (gColorPaletteStackSize >= COLOR_PALETTE_STACK_CAPACITY) {
|
||||
_errorStr = _aColor_cColorpa;
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorPaletteStackEntry* entry = (ColorPaletteStackEntry*)malloc(sizeof(*entry));
|
||||
gColorPaletteStack[gColorPaletteStackSize] = entry;
|
||||
|
||||
memcpy(entry->mappedColors, _mappedColor, sizeof(_mappedColor));
|
||||
memcpy(entry->cmap, _cmap, sizeof(_cmap));
|
||||
memcpy(entry->colorTable, _colorTable, sizeof(_colorTable));
|
||||
|
||||
gColorPaletteStackSize++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C88E0
|
||||
bool colorPopColorPalette()
|
||||
{
|
||||
if (gColorPaletteStackSize == 0) {
|
||||
_errorStr = aColor_cColor_0;
|
||||
return false;
|
||||
}
|
||||
|
||||
gColorPaletteStackSize--;
|
||||
|
||||
ColorPaletteStackEntry* entry = gColorPaletteStack[gColorPaletteStackSize];
|
||||
|
||||
memcpy(_mappedColor, entry->mappedColors, sizeof(_mappedColor));
|
||||
memcpy(_cmap, entry->cmap, sizeof(_cmap));
|
||||
memcpy(_colorTable, entry->colorTable, sizeof(_colorTable));
|
||||
|
||||
free(entry);
|
||||
gColorPaletteStack[gColorPaletteStackSize] = NULL;
|
||||
|
||||
_setIntensityTables();
|
||||
|
||||
for (int index = 0; index < 256; index++) {
|
||||
_setMixTableColor(index);
|
||||
}
|
||||
|
||||
_rebuildColorBlendTables();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x4C89CC
|
||||
bool _initColors()
|
||||
{
|
||||
@@ -599,5 +675,11 @@ void _colorsClose()
|
||||
_freeColorBlendTable(index);
|
||||
}
|
||||
|
||||
// TODO: Incomplete.
|
||||
for (int index = 0; index < gColorPaletteStackSize; index++) {
|
||||
free(gColorPaletteStack[index]);
|
||||
}
|
||||
|
||||
gColorPaletteStackSize = 0;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "memory_defs.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef const char*(ColorFileNameManger)(const char*);
|
||||
typedef void(ColorTransitionCallback)();
|
||||
|
||||
@@ -35,7 +37,11 @@ unsigned char* _getColorBlendTable(int ch);
|
||||
void _freeColorBlendTable(int a1);
|
||||
void colorPaletteSetMemoryProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc);
|
||||
void colorSetBrightness(double value);
|
||||
bool colorPushColorPalette();
|
||||
bool colorPopColorPalette();
|
||||
bool _initColors();
|
||||
void _colorsClose();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* COLOR_H */
|
||||
|
||||
1738
src/combat.cc
44
src/combat.h
@@ -1,11 +1,13 @@
|
||||
#ifndef COMBAT_H
|
||||
#define COMBAT_H
|
||||
|
||||
#include "db.h"
|
||||
#include "combat_defs.h"
|
||||
#include "db.h"
|
||||
#include "obj_types.h"
|
||||
#include "proto_types.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
extern int _combatNumTurns;
|
||||
extern unsigned int gCombatState;
|
||||
|
||||
@@ -17,16 +19,16 @@ void combatExit();
|
||||
int _find_cid(int a1, int a2, Object** a3, int a4);
|
||||
int combatLoad(File* stream);
|
||||
int combatSave(File* stream);
|
||||
bool _combat_safety_invalidate_weapon(Object* a1, Object* a2, int hitMode, Object* a4, int* a5);
|
||||
bool _combatTestIncidentalHit(Object* a1, Object* a2, Object* a3, Object* a4);
|
||||
bool _combat_safety_invalidate_weapon(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr);
|
||||
bool _combatTestIncidentalHit(Object* attacker, Object* defender, Object* attackerFriend, Object* weapon);
|
||||
Object* _combat_whose_turn();
|
||||
void _combat_data_init(Object* obj);
|
||||
Object* _combatAIInfoGetFriendlyDead(Object* obj);
|
||||
int _combatAIInfoSetFriendlyDead(Object* a1, Object* a2);
|
||||
Object* _combatAIInfoGetLastTarget(Object* obj);
|
||||
int _combatAIInfoSetLastTarget(Object* a1, Object* a2);
|
||||
Object* _combatAIInfoGetLastItem(Object* obj);
|
||||
int _combatAIInfoSetLastItem(Object* obj, Object* a2);
|
||||
Object* aiInfoGetFriendlyDead(Object* obj);
|
||||
int aiInfoSetFriendlyDead(Object* a1, Object* a2);
|
||||
Object* aiInfoGetLastTarget(Object* obj);
|
||||
int aiInfoSetLastTarget(Object* a1, Object* a2);
|
||||
Object* aiInfoGetLastItem(Object* obj);
|
||||
int aiInfoSetLastItem(Object* obj, Object* a2);
|
||||
void _combat_update_critter_outline_for_los(Object* critter, bool a2);
|
||||
void _combat_over_from_load();
|
||||
void _combat_give_exps(int exp_points);
|
||||
@@ -35,7 +37,7 @@ void _combat(STRUCT_664980* attack);
|
||||
void attackInit(Attack* attack, Object* a2, Object* a3, int a4, int a5);
|
||||
int _combat_attack(Object* a1, Object* a2, int a3, int a4);
|
||||
int _combat_bullet_start(const Object* a1, const Object* a2);
|
||||
void _compute_explosion_on_extras(Attack* attack, int a2, int a3, int a4);
|
||||
void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4);
|
||||
int _determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode);
|
||||
int _determine_to_hit_no_range(Object* a1, Object* a2, int a3, int a4, unsigned char* a5);
|
||||
int _determine_to_hit_from_tile(Object* a1, int a2, Object* a3, int a4, int a5);
|
||||
@@ -56,9 +58,31 @@ int _combat_explode_scenery(Object* a1, Object* a2);
|
||||
void _combat_delete_critter(Object* obj);
|
||||
void _combatKillCritterOutsideCombat(Object* critter_obj, char* msg);
|
||||
|
||||
int criticalsGetValue(int killType, int hitLocation, int effect, int dataMember);
|
||||
void criticalsSetValue(int killType, int hitLocation, int effect, int dataMember, int value);
|
||||
void criticalsResetValue(int killType, int hitLocation, int effect, int dataMember);
|
||||
int unarmedGetDamage(int hitMode, int* minDamagePtr, int* maxDamagePtr);
|
||||
int unarmedGetBonusCriticalChance(int hitMode);
|
||||
int unarmedGetActionPointCost(int hitMode);
|
||||
bool unarmedIsPenetrating(int hitMode);
|
||||
int unarmedGetPunchHitMode(bool isSecondary);
|
||||
int unarmedGetKickHitMode(bool isSecondary);
|
||||
bool unarmedIsPenetrating(int hitMode);
|
||||
bool damageModGetBonusHthDamageFix();
|
||||
bool damageModGetDisplayBonusDamage();
|
||||
|
||||
static inline bool isInCombat()
|
||||
{
|
||||
return (gCombatState & COMBAT_STATE_0x01) != 0;
|
||||
}
|
||||
|
||||
static inline bool isUnarmedHitMode(int hitMode)
|
||||
{
|
||||
return hitMode == HIT_MODE_PUNCH
|
||||
|| hitMode == HIT_MODE_KICK
|
||||
|| (hitMode >= FIRST_ADVANCED_UNARMED_HIT_MODE && hitMode <= LAST_ADVANCED_UNARMED_HIT_MODE);
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* COMBAT_H */
|
||||
|
||||
522
src/combat_ai.cc
@@ -6,6 +6,8 @@
|
||||
#include "db.h"
|
||||
#include "obj_types.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef enum AiMessageType {
|
||||
AI_MESSAGE_TYPE_RUN,
|
||||
AI_MESSAGE_TYPE_MOVE,
|
||||
@@ -48,7 +50,7 @@ void _caiTeamCombatExit();
|
||||
Object* _ai_search_inven_weap(Object* critter, int a2, Object* a3);
|
||||
Object* _ai_search_inven_armor(Object* critter);
|
||||
int _cAIPrepWeaponItem(Object* critter, Object* item);
|
||||
void _cai_attempt_w_reload(Object* critter_obj, int a2);
|
||||
void aiAttemptWeaponReload(Object* critter, int animate);
|
||||
void _combat_ai_begin(int a1, void* a2);
|
||||
void _combat_ai_over();
|
||||
int _cai_perform_distance_prefs(Object* a1, Object* a2);
|
||||
@@ -66,4 +68,6 @@ void _combatai_notify_onlookers(Object* a1);
|
||||
void _combatai_notify_friends(Object* a1);
|
||||
void _combatai_delete_critter(Object* obj);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* COMBAT_AI_H */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef COMBAT_AI_DEFS_H
|
||||
#define COMBAT_AI_DEFS_H
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef enum AreaAttackMode {
|
||||
AREA_ATTACK_MODE_ALWAYS,
|
||||
AREA_ATTACK_MODE_SOMETIMES,
|
||||
@@ -79,4 +81,6 @@ typedef enum HurtTooMuch {
|
||||
HURT_COUNT,
|
||||
} HurtTooMuch;
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* COMBAT_AI_DEFS_H */
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#define WEAPON_CRITICAL_FAILURE_TYPE_COUNT (7)
|
||||
#define WEAPON_CRITICAL_FAILURE_EFFECT_COUNT (5)
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef enum CombatState {
|
||||
COMBAT_STATE_0x01 = 0x01,
|
||||
COMBAT_STATE_0x02 = 0x02,
|
||||
@@ -84,13 +86,6 @@ typedef enum HitLocation {
|
||||
HIT_LOCATION_SPECIFIC_COUNT = HIT_LOCATION_COUNT - 1,
|
||||
} HitLocation;
|
||||
|
||||
typedef struct STRUCT_510948 {
|
||||
Object* field_0;
|
||||
Object* field_4;
|
||||
Object* field_8;
|
||||
int field_C;
|
||||
} STRUCT_510948;
|
||||
|
||||
typedef struct STRUCT_664980 {
|
||||
Object* attacker;
|
||||
Object* defender;
|
||||
@@ -128,25 +123,54 @@ typedef struct Attack {
|
||||
int extrasKnockback[EXPLOSION_TARGET_COUNT];
|
||||
} Attack;
|
||||
|
||||
typedef enum CriticalHitDescriptionDataMember {
|
||||
CRIT_DATA_MEMBER_DAMAGE_MULTIPLIER,
|
||||
CRIT_DATA_MEMBER_FLAGS,
|
||||
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_STAT,
|
||||
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_STAT_MODIFIER,
|
||||
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_FLAGS,
|
||||
CRIT_DATA_MEMBER_MESSAGE_ID,
|
||||
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_MESSAGE_ID,
|
||||
CRIT_DATA_MEMBER_COUNT,
|
||||
} CriticalHitDescriptionDataMember;
|
||||
|
||||
// Provides metadata about critical hit effect.
|
||||
typedef struct CriticalHitDescription {
|
||||
int damageMultiplier;
|
||||
typedef union CriticalHitDescription {
|
||||
struct {
|
||||
int damageMultiplier;
|
||||
|
||||
// Damage flags that will be applied to defender.
|
||||
int flags;
|
||||
// Damage flags that will be applied to defender.
|
||||
int flags;
|
||||
|
||||
// Stat to check to upgrade this critical hit to massive critical hit or
|
||||
// -1 if there is no massive critical hit.
|
||||
int massiveCriticalStat;
|
||||
// Stat to check to upgrade this critical hit to massive critical hit or
|
||||
// -1 if there is no massive critical hit.
|
||||
int massiveCriticalStat;
|
||||
|
||||
// Bonus/penalty to massive critical stat.
|
||||
int massiveCriticalStatModifier;
|
||||
// Bonus/penalty to massive critical stat.
|
||||
int massiveCriticalStatModifier;
|
||||
|
||||
// Additional damage flags if this critical hit become massive critical.
|
||||
int massiveCriticalFlags;
|
||||
// Additional damage flags if this critical hit become massive critical.
|
||||
int massiveCriticalFlags;
|
||||
|
||||
int messageId;
|
||||
int massiveCriticalMessageId;
|
||||
int messageId;
|
||||
int massiveCriticalMessageId;
|
||||
};
|
||||
|
||||
// SFALL: Allow indexed access to the data above.
|
||||
int values[CRIT_DATA_MEMBER_COUNT];
|
||||
} CriticalHitDescription;
|
||||
|
||||
typedef enum CombatBadShot {
|
||||
COMBAT_BAD_SHOT_OK = 0,
|
||||
COMBAT_BAD_SHOT_NO_AMMO = 1,
|
||||
COMBAT_BAD_SHOT_OUT_OF_RANGE = 2,
|
||||
COMBAT_BAD_SHOT_NOT_ENOUGH_AP = 3,
|
||||
COMBAT_BAD_SHOT_ALREADY_DEAD = 4,
|
||||
COMBAT_BAD_SHOT_AIM_BLOCKED = 5,
|
||||
COMBAT_BAD_SHOT_ARM_CRIPPLED = 6,
|
||||
COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED = 7,
|
||||
} CombatBadShot;
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* COMBAT_DEFS_H */
|
||||
|
||||
115
src/config.cc
@@ -1,9 +1,5 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "db.h"
|
||||
#include "memory.h"
|
||||
#include "platform_compat.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
@@ -11,6 +7,12 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "db.h"
|
||||
#include "memory.h"
|
||||
#include "platform_compat.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
#define CONFIG_FILE_MAX_LINE_LENGTH (256)
|
||||
|
||||
// The initial number of sections (or key-value) pairs in the config.
|
||||
@@ -81,25 +83,20 @@ bool configParseCommandLineArguments(Config* config, int argc, char** argv)
|
||||
}
|
||||
|
||||
for (int arg = 0; arg < argc; arg++) {
|
||||
char* pch = argv[arg];
|
||||
char* pch;
|
||||
char* string = argv[arg];
|
||||
|
||||
// Find opening bracket.
|
||||
while (*pch != '\0' && *pch != '[') {
|
||||
pch++;
|
||||
}
|
||||
|
||||
if (*pch == '\0') {
|
||||
pch = strchr(string, '[');
|
||||
if (pch == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char* sectionKey = pch + 1;
|
||||
|
||||
// Find closing bracket.
|
||||
while (*pch != '\0' && *pch != ']') {
|
||||
pch++;
|
||||
}
|
||||
|
||||
if (*pch == '\0') {
|
||||
pch = strchr(sectionKey, ']');
|
||||
if (pch == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -188,8 +185,8 @@ bool configSetString(Config* config, const char* sectionKey, const char* key, co
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x42C05C customized: atoi() replaced with strtol()
|
||||
bool configGetInt(Config* config, const char* sectionKey, const char* key, int* valuePtr, unsigned char base /* = 0 */ )
|
||||
// 0x42C05C
|
||||
bool configGetInt(Config* config, const char* sectionKey, const char* key, int* valuePtr, unsigned char base /* = 0 */)
|
||||
{
|
||||
if (valuePtr == NULL) {
|
||||
return false;
|
||||
@@ -203,8 +200,17 @@ bool configGetInt(Config* config, const char* sectionKey, const char* key, int*
|
||||
char* end;
|
||||
errno = 0;
|
||||
long l = strtol(stringValue, &end, base); // see https://stackoverflow.com/a/6154614
|
||||
if (((errno == ERANGE && 1 == LONG_MAX) || l > INT_MAX) || ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) || (*stringValue == '\0' || *end != '\0'))
|
||||
return false;
|
||||
|
||||
// The link above says right things about converting strings to numbers,
|
||||
// however we need to maintain compatibility with atoi implementation and
|
||||
// original game data. One example of the problem is worldmap.txt where
|
||||
// frequency values expressed as percentages (Frequent=38%). If we handle
|
||||
// the result like the link above suggests (and what previous implementation
|
||||
// provided), we'll simply end up returning `false`, since there will be
|
||||
// unconverted characters left. On the other hand, this function is also
|
||||
// used to parse Sfall config values, which uses hexadecimal notation to
|
||||
// represent colors. We're not going to need any of these in the long run so
|
||||
// for now simply ignore any error that could arise during conversion.
|
||||
|
||||
*valuePtr = l;
|
||||
|
||||
@@ -224,36 +230,30 @@ bool configGetIntList(Config* config, const char* sectionKey, const char* key, i
|
||||
}
|
||||
|
||||
char temp[CONFIG_FILE_MAX_LINE_LENGTH];
|
||||
strncpy(temp, string, CONFIG_FILE_MAX_LINE_LENGTH - 1);
|
||||
string = strncpy(temp, string, CONFIG_FILE_MAX_LINE_LENGTH - 1);
|
||||
|
||||
char* beginning = temp;
|
||||
char* pch = beginning;
|
||||
while (*pch != '\0') {
|
||||
if (*pch == ',') {
|
||||
*pch = '\0';
|
||||
|
||||
*arr++ = atoi(beginning);
|
||||
|
||||
*pch = ',';
|
||||
|
||||
pch++;
|
||||
beginning = pch;
|
||||
|
||||
count--;
|
||||
|
||||
if (count < 0) {
|
||||
break;
|
||||
}
|
||||
while (1) {
|
||||
char* pch = strchr(string, ',');
|
||||
if (pch == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
pch++;
|
||||
count--;
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
*pch = '\0';
|
||||
*arr++ = atoi(string);
|
||||
string = pch + 1;
|
||||
}
|
||||
|
||||
if (count <= 1) {
|
||||
*arr = atoi(beginning);
|
||||
*arr = atoi(string);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 0x42C160
|
||||
@@ -374,30 +374,19 @@ static bool configParseLine(Config* config, char* string)
|
||||
char* pch;
|
||||
|
||||
// Find comment marker and truncate the string.
|
||||
pch = string;
|
||||
while (*pch != '\0' && *pch != ';') {
|
||||
pch++;
|
||||
}
|
||||
|
||||
if (*pch != '\0') {
|
||||
pch = strchr(string, ';');
|
||||
if (pch != NULL) {
|
||||
*pch = '\0';
|
||||
}
|
||||
|
||||
// Find opening bracket.
|
||||
pch = string;
|
||||
while (*pch != '\0' && *pch != '[') {
|
||||
pch++;
|
||||
}
|
||||
|
||||
if (*pch == '[') {
|
||||
pch = strchr(string, '[');
|
||||
if (pch != NULL) {
|
||||
char* sectionKey = pch + 1;
|
||||
|
||||
// Find closing bracket.
|
||||
while (*pch != '\0' && *pch != ']') {
|
||||
pch++;
|
||||
}
|
||||
|
||||
if (*pch == ']') {
|
||||
pch = strchr(sectionKey, ']');
|
||||
if (pch != NULL) {
|
||||
*pch = '\0';
|
||||
strcpy(gConfigLastSectionKey, sectionKey);
|
||||
return configTrimString(gConfigLastSectionKey);
|
||||
@@ -426,12 +415,8 @@ static bool configParseKeyValue(char* string, char* key, char* value)
|
||||
}
|
||||
|
||||
// Find equals character.
|
||||
char* pch = string;
|
||||
while (*pch != '\0' && *pch != '=') {
|
||||
pch++;
|
||||
}
|
||||
|
||||
if (*pch == '\0') {
|
||||
char* pch = strchr(string, '=');
|
||||
if (pch == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -564,3 +549,5 @@ bool configSetBool(Config* config, const char* sectionKey, const char* key, bool
|
||||
{
|
||||
return configSetInt(config, sectionKey, key, value ? 1 : 0);
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "dictionary.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
// A representation of .INI file.
|
||||
//
|
||||
// It's implemented as a [Dictionary] whos keys are section names of .INI file,
|
||||
@@ -31,4 +33,6 @@ bool configSetDouble(Config* config, const char* sectionKey, const char* key, do
|
||||
bool configGetBool(Config* config, const char* sectionKey, const char* key, bool* valuePtr);
|
||||
bool configSetBool(Config* config, const char* sectionKey, const char* key, bool value);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CONFIG_H */
|
||||
|
||||
654
src/core.cc
@@ -1,8 +1,13 @@
|
||||
#include "core.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "audio_engine.h"
|
||||
#include "config.h"
|
||||
#include "color.h"
|
||||
#include "config.h"
|
||||
#include "dinput.h"
|
||||
#include "draw.h"
|
||||
#include "interface.h"
|
||||
@@ -13,15 +18,15 @@
|
||||
#include "window_manager.h"
|
||||
#include "window_manager_private.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
namespace fallout {
|
||||
|
||||
// NOT USED.
|
||||
void (*_idle_func)() = NULL;
|
||||
static void idleImpl();
|
||||
|
||||
// NOT USED.
|
||||
void (*_focus_func)(int) = NULL;
|
||||
// 0x51E234
|
||||
IdleFunc* _idle_func = NULL;
|
||||
|
||||
// 0x51E238
|
||||
FocusFunc* _focus_func = NULL;
|
||||
|
||||
// 0x51E23C
|
||||
int gKeyboardKeyRepeatRate = 80;
|
||||
@@ -116,39 +121,48 @@ int gModifierKeysState = 0;
|
||||
int (*_kb_scan_to_ascii)() = keyboardDequeueLogicalKeyCode;
|
||||
|
||||
// 0x51E2F0
|
||||
STRUCT_51E2F0* _vcr_buffer = NULL;
|
||||
VcrEntry* _vcr_buffer = NULL;
|
||||
|
||||
// number of entries in _vcr_buffer
|
||||
// 0x51E2F4
|
||||
int _vcr_buffer_index = 0;
|
||||
|
||||
// 0x51E2F8
|
||||
int _vcr_state = 2;
|
||||
unsigned int gVcrState = VCR_STATE_TURNED_OFF;
|
||||
|
||||
// 0x51E2FC
|
||||
int _vcr_time = 0;
|
||||
unsigned int _vcr_time = 0;
|
||||
|
||||
// 0x51E300
|
||||
int _vcr_counter = 0;
|
||||
unsigned int _vcr_counter = 0;
|
||||
|
||||
// 0x51E304
|
||||
int _vcr_terminate_flags = 0;
|
||||
unsigned int gVcrTerminateFlags = 0;
|
||||
|
||||
// 0x51E308
|
||||
int _vcr_terminated_condition = 0;
|
||||
int gVcrPlaybackCompletionReason = VCR_PLAYBACK_COMPLETION_REASON_NONE;
|
||||
|
||||
// 0x51E30C
|
||||
int _vcr_start_time = 0;
|
||||
unsigned int _vcr_start_time = 0;
|
||||
|
||||
// 0x51E310
|
||||
int _vcr_registered_atexit = 0;
|
||||
|
||||
// 0x51E314
|
||||
File* _vcr_file = NULL;
|
||||
File* gVcrFile = NULL;
|
||||
|
||||
// 0x51E318
|
||||
int _vcr_buffer_end = 0;
|
||||
|
||||
// 0x51E31C
|
||||
VcrPlaybackCompletionCallback* gVcrPlaybackCompletionCallback = NULL;
|
||||
|
||||
// 0x51E320
|
||||
unsigned int gVcrRequestedTerminationFlags = 0;
|
||||
|
||||
// 0x51E324
|
||||
int gVcrOldKeyboardLayout = 0;
|
||||
|
||||
// A map of SDL_SCANCODE_* constants normalized for QWERTY keyboard.
|
||||
//
|
||||
// 0x6ABC70
|
||||
@@ -355,12 +369,18 @@ int gKeyboardLayout;
|
||||
// 0x6AD93C
|
||||
unsigned char gPressedPhysicalKeysCount;
|
||||
|
||||
// 0x6AD940
|
||||
VcrEntry stru_6AD940;
|
||||
|
||||
SDL_Window* gSdlWindow = NULL;
|
||||
SDL_Surface* gSdlSurface = NULL;
|
||||
SDL_Renderer* gSdlRenderer = NULL;
|
||||
SDL_Texture* gSdlTexture = NULL;
|
||||
SDL_Surface* gSdlTextureSurface = NULL;
|
||||
|
||||
static int gMouseWheelX = 0;
|
||||
static int gMouseWheelY = 0;
|
||||
|
||||
// 0x4C8A70
|
||||
int coreInit(int a1)
|
||||
{
|
||||
@@ -396,6 +416,10 @@ int coreInit(int a1)
|
||||
gTickerListHead = NULL;
|
||||
gScreenshotKeyCode = KEY_ALT_C;
|
||||
|
||||
// SFALL: Set idle function.
|
||||
// CE: Prevents frying CPU when window is not focused.
|
||||
inputSetIdleFunc(idleImpl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -446,7 +470,7 @@ void _process_bk()
|
||||
|
||||
tickersExecute();
|
||||
|
||||
if (_vcr_update() != 3) {
|
||||
if (vcrUpdate() != 3) {
|
||||
_mouse_info();
|
||||
}
|
||||
|
||||
@@ -629,31 +653,37 @@ void pauseGame()
|
||||
// 0x4C8E38
|
||||
int pauseHandlerDefaultImpl()
|
||||
{
|
||||
int len;
|
||||
int v1;
|
||||
int v2;
|
||||
int win;
|
||||
unsigned char* buf;
|
||||
int v6;
|
||||
int v7;
|
||||
int windowWidth = fontGetStringWidth("Paused") + 32;
|
||||
int windowHeight = 3 * fontGetLineHeight() + 16;
|
||||
|
||||
len = fontGetStringWidth("Paused") + 32;
|
||||
v1 = fontGetLineHeight();
|
||||
v2 = 3 * v1 + 16;
|
||||
|
||||
win = windowCreate((_scr_size.right - _scr_size.left + 1 - len) / 2, (_scr_size.bottom - _scr_size.top + 1 - v2) / 2, len, v2, 256, 20);
|
||||
int win = windowCreate((rectGetWidth(&_scr_size) - windowWidth) / 2,
|
||||
(rectGetHeight(&_scr_size) - windowHeight) / 2,
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
256,
|
||||
WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);
|
||||
if (win == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
windowDrawBorder(win);
|
||||
buf = windowGetBuffer(win);
|
||||
fontDrawText(buf + 8 * len + 16, "Paused", len, len, _colorTable[31744]);
|
||||
|
||||
v6 = v2 - 8 - v1;
|
||||
v7 = fontGetStringWidth("Done");
|
||||
// TODO: Incomplete.
|
||||
// _win_register_text_button(win, (len - v7 - 16) / 2, v6 - 6, -1, -1, -1, 27, "Done", 0);
|
||||
unsigned char* windowBuffer = windowGetBuffer(win);
|
||||
fontDrawText(windowBuffer + 8 * windowWidth + 16,
|
||||
"Paused",
|
||||
windowWidth,
|
||||
windowWidth,
|
||||
_colorTable[31744]);
|
||||
|
||||
_win_register_text_button(win,
|
||||
(windowWidth - fontGetStringWidth("Done") - 16) / 2,
|
||||
windowHeight - 8 - fontGetLineHeight() - 6,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
KEY_ESCAPE,
|
||||
"Done",
|
||||
0);
|
||||
|
||||
windowRefresh(win);
|
||||
|
||||
@@ -720,7 +750,7 @@ int screenshotHandlerDefaultImpl(int width, int height, unsigned char* data, uns
|
||||
|
||||
for (index = 0; index < 100000; index++) {
|
||||
sprintf(fileName, "scr%.5d.bmp", index);
|
||||
|
||||
|
||||
stream = compat_fopen(fileName, "rb");
|
||||
if (stream == NULL) {
|
||||
break;
|
||||
@@ -784,7 +814,7 @@ int screenshotHandlerDefaultImpl(int width, int height, unsigned char* data, uns
|
||||
// biCompression
|
||||
intValue = 0;
|
||||
fwrite(&intValue, sizeof(intValue), 1, stream);
|
||||
|
||||
|
||||
// biSizeImage
|
||||
intValue = 0;
|
||||
fwrite(&intValue, sizeof(intValue), 1, stream);
|
||||
@@ -901,6 +931,70 @@ unsigned int _get_bk_time()
|
||||
return gTickerLastTimestamp;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9418
|
||||
void inputSetKeyboardKeyRepeatRate(int value)
|
||||
{
|
||||
gKeyboardKeyRepeatRate = value;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9420
|
||||
int inputGetKeyboardKeyRepeatRate()
|
||||
{
|
||||
return gKeyboardKeyRepeatRate;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9428
|
||||
void inputSetKeyboardKeyRepeatDelay(int value)
|
||||
{
|
||||
gKeyboardKeyRepeatDelay = value;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9430
|
||||
int inputGetKeyboardKeyRepeatDelay()
|
||||
{
|
||||
return gKeyboardKeyRepeatDelay;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9438
|
||||
void inputSetFocusFunc(FocusFunc* func)
|
||||
{
|
||||
_focus_func = func;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9440
|
||||
FocusFunc* inputGetFocusFunc()
|
||||
{
|
||||
return _focus_func;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9448
|
||||
void inputSetIdleFunc(IdleFunc* func)
|
||||
{
|
||||
_idle_func = func;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x4C9450
|
||||
IdleFunc* inputGetIdleFunc()
|
||||
{
|
||||
return _idle_func;
|
||||
}
|
||||
|
||||
// 0x4C9490
|
||||
void buildNormalizedQwertyKeys()
|
||||
{
|
||||
@@ -1251,8 +1345,13 @@ void _GNW95_process_message()
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
// The data is accumulated in SDL itself and will be processed
|
||||
// in `_mouse_info`.
|
||||
case SDL_MOUSEWHEEL:
|
||||
handleMouseEvent(&e);
|
||||
break;
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERUP:
|
||||
handleTouchEvent(&e);
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
@@ -1324,10 +1423,10 @@ void _GNW95_process_key(KeyboardData* data)
|
||||
{
|
||||
data->key = gNormalizedQwertyKeys[data->key];
|
||||
|
||||
if (_vcr_state == 1) {
|
||||
if (_vcr_terminate_flags & 1) {
|
||||
_vcr_terminated_condition = 2;
|
||||
_vcr_stop();
|
||||
if (gVcrState == VCR_STATE_PLAYING) {
|
||||
if ((gVcrTerminateFlags & VCR_TERMINATE_ON_KEY_PRESS) != 0) {
|
||||
gVcrPlaybackCompletionReason = VCR_PLAYBACK_COMPLETION_REASON_TERMINATED;
|
||||
vcrStop();
|
||||
}
|
||||
} else {
|
||||
STRUCT_6ABF50* ptr = &(_GNW95_key_time_stamps[data->key]);
|
||||
@@ -1346,7 +1445,7 @@ void _GNW95_process_key(KeyboardData* data)
|
||||
void _GNW95_lost_focus()
|
||||
{
|
||||
if (_focus_func != NULL) {
|
||||
_focus_func(0);
|
||||
_focus_func(false);
|
||||
}
|
||||
|
||||
while (!gProgramIsActive) {
|
||||
@@ -1358,7 +1457,7 @@ void _GNW95_lost_focus()
|
||||
}
|
||||
|
||||
if (_focus_func != NULL) {
|
||||
_focus_func(1);
|
||||
_focus_func(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1643,10 +1742,11 @@ void _mouse_info()
|
||||
x = (int)(x * gMouseSensitivity);
|
||||
y = (int)(y * gMouseSensitivity);
|
||||
|
||||
if (_vcr_state == 1) {
|
||||
if (((_vcr_terminate_flags & 4) && buttons) || ((_vcr_terminate_flags & 2) && (x || y))) {
|
||||
_vcr_terminated_condition = 2;
|
||||
_vcr_stop();
|
||||
if (gVcrState == VCR_STATE_PLAYING) {
|
||||
if (((gVcrTerminateFlags & VCR_TERMINATE_ON_MOUSE_PRESS) != 0 && buttons != 0)
|
||||
|| ((gVcrTerminateFlags & VCR_TERMINATE_ON_MOUSE_MOVE) != 0 && (x != 0 || y != 0))) {
|
||||
gVcrPlaybackCompletionReason = VCR_PLAYBACK_COMPLETION_REASON_TERMINATED;
|
||||
vcrStop();
|
||||
return;
|
||||
}
|
||||
x = 0;
|
||||
@@ -1655,6 +1755,16 @@ void _mouse_info()
|
||||
}
|
||||
|
||||
_mouse_simulate_input(x, y, buttons);
|
||||
|
||||
// TODO: Move to `_mouse_simulate_input`.
|
||||
// TODO: Record wheel event in VCR.
|
||||
gMouseWheelX = mouseData.wheelX;
|
||||
gMouseWheelY = mouseData.wheelY;
|
||||
|
||||
if (gMouseWheelX != 0 || gMouseWheelY != 0) {
|
||||
gMouseEvent |= MOUSE_EVENT_WHEEL;
|
||||
_raw_buttons |= MOUSE_EVENT_WHEEL;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x4CA698
|
||||
@@ -1665,18 +1775,18 @@ void _mouse_simulate_input(int delta_x, int delta_y, int buttons)
|
||||
}
|
||||
|
||||
if (delta_x || delta_y || buttons != gMouseButtonsState) {
|
||||
if (_vcr_state == 0) {
|
||||
if (_vcr_buffer_index == 4095) {
|
||||
_vcr_dump_buffer();
|
||||
if (gVcrState == 0) {
|
||||
if (_vcr_buffer_index == VCR_BUFFER_CAPACITY - 1) {
|
||||
vcrDump();
|
||||
}
|
||||
|
||||
STRUCT_51E2F0* ptr = &(_vcr_buffer[_vcr_buffer_index]);
|
||||
ptr->type = 3;
|
||||
ptr->field_4 = _vcr_time;
|
||||
ptr->field_8 = _vcr_counter;
|
||||
ptr->dx = delta_x;
|
||||
ptr->dy = delta_y;
|
||||
ptr->buttons = buttons;
|
||||
VcrEntry* vcrEntry = &(_vcr_buffer[_vcr_buffer_index]);
|
||||
vcrEntry->type = VCR_ENTRY_TYPE_MOUSE_EVENT;
|
||||
vcrEntry->time = _vcr_time;
|
||||
vcrEntry->counter = _vcr_counter;
|
||||
vcrEntry->mouseEvent.dx = delta_x;
|
||||
vcrEntry->mouseEvent.dy = delta_y;
|
||||
vcrEntry->mouseEvent.buttons = buttons;
|
||||
|
||||
_vcr_buffer_index++;
|
||||
}
|
||||
@@ -1996,10 +2106,10 @@ int _GNW95_init_mode_ex(int width, int height, int bpp)
|
||||
_zero_mem = _GNW95_zero_vid_mem;
|
||||
_mouse_blit = _GNW95_ShowRect;
|
||||
} else {
|
||||
_zero_mem = NULL;
|
||||
_mouse_blit = _GNW95_MouseShowRect16;
|
||||
_mouse_blit_trans = _GNW95_MouseShowTransRect16;
|
||||
_scr_blit = _GNW95_ShowRect16;
|
||||
_zero_mem = NULL;
|
||||
_mouse_blit = _GNW95_MouseShowRect16;
|
||||
_mouse_blit_trans = _GNW95_MouseShowTransRect16;
|
||||
_scr_blit = _GNW95_ShowRect16;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -2036,7 +2146,7 @@ int _GNW95_init_window(int width, int height, bool fullscreen)
|
||||
if (gSdlRenderer == NULL) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
||||
if (SDL_RenderSetLogicalSize(gSdlRenderer, width, height) != 0) {
|
||||
goto err;
|
||||
}
|
||||
@@ -2214,7 +2324,7 @@ void directDrawSetPaletteInRange(unsigned char* palette, int start, int count)
|
||||
void directDrawSetPalette(unsigned char* palette)
|
||||
{
|
||||
if (gSdlSurface != NULL && gSdlSurface->format->palette != NULL) {
|
||||
SDL_Color colors[256];
|
||||
SDL_Color colors[256];
|
||||
|
||||
for (int index = 0; index < 256; index++) {
|
||||
colors[index].r = palette[index * 3] << 2;
|
||||
@@ -2251,7 +2361,6 @@ void directDrawSetPalette(unsigned char* palette)
|
||||
windowRefreshAll(&_scr_size);
|
||||
}
|
||||
|
||||
|
||||
if (_update_palette_func != NULL) {
|
||||
_update_palette_func();
|
||||
}
|
||||
@@ -2555,13 +2664,14 @@ int keyboardGetLayout()
|
||||
// TODO: Key type is likely short.
|
||||
void _kb_simulate_key(KeyboardData* data)
|
||||
{
|
||||
if (_vcr_state == 0) {
|
||||
if (_vcr_buffer_index != 4095) {
|
||||
STRUCT_51E2F0* ptr = &(_vcr_buffer[_vcr_buffer_index]);
|
||||
ptr->type = 2;
|
||||
ptr->type_2_field_C = data->key & 0xFFFF;
|
||||
ptr->field_4 = _vcr_time;
|
||||
ptr->field_8 = _vcr_counter;
|
||||
if (gVcrState == 0) {
|
||||
if (_vcr_buffer_index != VCR_BUFFER_CAPACITY - 1) {
|
||||
VcrEntry* vcrEntry = &(_vcr_buffer[_vcr_buffer_index]);
|
||||
vcrEntry->type = VCR_ENTRY_TYPE_KEYBOARD_EVENT;
|
||||
vcrEntry->keyboardEvent.key = data->key & 0xFFFF;
|
||||
vcrEntry->time = _vcr_time;
|
||||
vcrEntry->counter = _vcr_counter;
|
||||
|
||||
_vcr_buffer_index++;
|
||||
}
|
||||
}
|
||||
@@ -4359,9 +4469,9 @@ int keyboardPeekEvent(int index, KeyboardEvent** keyboardEventPtr)
|
||||
}
|
||||
|
||||
// 0x4D2680
|
||||
bool _vcr_record(const char* fileName)
|
||||
bool vcrRecord(const char* fileName)
|
||||
{
|
||||
if (_vcr_state != 2) {
|
||||
if (gVcrState != VCR_STATE_TURNED_OFF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4369,77 +4479,260 @@ bool _vcr_record(const char* fileName)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_vcr_buffer != NULL) {
|
||||
// NOTE: Uninline.
|
||||
if (!vcrInitBuffer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_vcr_buffer = (STRUCT_51E2F0*)internal_malloc(sizeof(*_vcr_buffer) * 4096);
|
||||
if (_vcr_buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_vcr_clear_buffer();
|
||||
|
||||
_vcr_file = fileOpen(fileName, "wb");
|
||||
if (_vcr_file == NULL) {
|
||||
if (_vcr_buffer != NULL) {
|
||||
_vcr_clear_buffer();
|
||||
internal_free(_vcr_buffer);
|
||||
_vcr_buffer = NULL;
|
||||
}
|
||||
gVcrFile = fileOpen(fileName, "wb");
|
||||
if (gVcrFile == NULL) {
|
||||
// NOTE: Uninline.
|
||||
vcrFreeBuffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_vcr_registered_atexit == 0) {
|
||||
_vcr_registered_atexit = atexit(_vcr_stop);
|
||||
_vcr_registered_atexit = atexit(vcrStop);
|
||||
}
|
||||
|
||||
STRUCT_51E2F0* entry = &(_vcr_buffer[_vcr_buffer_index]);
|
||||
entry->type = 1;
|
||||
entry->field_4 = 0;
|
||||
entry->field_8 = 0;
|
||||
entry->type_1_field_14 = keyboardGetLayout();
|
||||
VcrEntry* vcrEntry = &(_vcr_buffer[_vcr_buffer_index]);
|
||||
vcrEntry->type = VCR_ENTRY_TYPE_INITIAL_STATE;
|
||||
vcrEntry->time = 0;
|
||||
vcrEntry->counter = 0;
|
||||
vcrEntry->initial.keyboardLayout = keyboardGetLayout();
|
||||
|
||||
while (mouseGetEvent() != 0) {
|
||||
_mouse_info();
|
||||
}
|
||||
|
||||
mouseGetPosition(&(entry->type_1_field_C), &(entry->type_1_field_10));
|
||||
mouseGetPosition(&(vcrEntry->initial.mouseX), &(vcrEntry->initial.mouseY));
|
||||
|
||||
_vcr_counter = 1;
|
||||
_vcr_buffer_index++;
|
||||
_vcr_start_time = _get_time();
|
||||
keyboardReset();
|
||||
_vcr_state = 0;
|
||||
|
||||
gVcrState = VCR_STATE_RECORDING;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x4D27EC
|
||||
bool vcrPlay(const char* fileName, unsigned int terminationFlags, VcrPlaybackCompletionCallback* callback)
|
||||
{
|
||||
if (gVcrState != VCR_STATE_TURNED_OFF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fileName == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: Uninline.
|
||||
if (!vcrInitBuffer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gVcrFile = fileOpen(fileName, "rb");
|
||||
if (gVcrFile == NULL) {
|
||||
// NOTE: Uninline.
|
||||
vcrFreeBuffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vcrLoad()) {
|
||||
fileClose(gVcrFile);
|
||||
// NOTE: Uninline.
|
||||
vcrFreeBuffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
while (mouseGetEvent() != 0) {
|
||||
_mouse_info();
|
||||
}
|
||||
|
||||
keyboardReset();
|
||||
|
||||
gVcrRequestedTerminationFlags = terminationFlags;
|
||||
gVcrPlaybackCompletionCallback = callback;
|
||||
gVcrPlaybackCompletionReason = VCR_PLAYBACK_COMPLETION_REASON_COMPLETED;
|
||||
gVcrTerminateFlags = 0;
|
||||
_vcr_counter = 0;
|
||||
_vcr_time = 0;
|
||||
_vcr_start_time = _get_time();
|
||||
gVcrState = VCR_STATE_PLAYING;
|
||||
stru_6AD940.time = 0;
|
||||
stru_6AD940.counter = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x4D28F4
|
||||
void _vcr_stop()
|
||||
void vcrStop()
|
||||
{
|
||||
if (_vcr_state == 0 || _vcr_state == 1) {
|
||||
_vcr_state |= 0x80000000;
|
||||
if (gVcrState == VCR_STATE_RECORDING || gVcrState == VCR_STATE_PLAYING) {
|
||||
gVcrState |= VCR_STATE_STOP_REQUESTED;
|
||||
}
|
||||
|
||||
keyboardReset();
|
||||
}
|
||||
|
||||
// 0x4D2918
|
||||
int _vcr_status()
|
||||
int vcrGetState()
|
||||
{
|
||||
return _vcr_state;
|
||||
return gVcrState;
|
||||
}
|
||||
|
||||
// 0x4D2930
|
||||
int _vcr_update()
|
||||
int vcrUpdate()
|
||||
{
|
||||
// TODO: Incomplete.
|
||||
if ((gVcrState & VCR_STATE_STOP_REQUESTED) != 0) {
|
||||
gVcrState &= ~VCR_STATE_STOP_REQUESTED;
|
||||
|
||||
switch (gVcrState) {
|
||||
case VCR_STATE_RECORDING:
|
||||
vcrDump();
|
||||
|
||||
fileClose(gVcrFile);
|
||||
gVcrFile = NULL;
|
||||
|
||||
// NOTE: Uninline.
|
||||
vcrFreeBuffer();
|
||||
|
||||
break;
|
||||
case VCR_STATE_PLAYING:
|
||||
fileClose(gVcrFile);
|
||||
gVcrFile = NULL;
|
||||
|
||||
// NOTE: Uninline.
|
||||
vcrFreeBuffer();
|
||||
|
||||
keyboardSetLayout(gVcrOldKeyboardLayout);
|
||||
|
||||
if (gVcrPlaybackCompletionCallback != NULL) {
|
||||
gVcrPlaybackCompletionCallback(gVcrPlaybackCompletionReason);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
gVcrState = VCR_STATE_TURNED_OFF;
|
||||
}
|
||||
|
||||
switch (gVcrState) {
|
||||
case VCR_STATE_RECORDING:
|
||||
_vcr_counter++;
|
||||
_vcr_time = getTicksSince(_vcr_start_time);
|
||||
if (_vcr_buffer_index == VCR_BUFFER_CAPACITY - 1) {
|
||||
vcrDump();
|
||||
}
|
||||
break;
|
||||
case VCR_STATE_PLAYING:
|
||||
if (_vcr_buffer_index < _vcr_buffer_end || vcrLoad()) {
|
||||
VcrEntry* vcrEntry = &(_vcr_buffer[_vcr_buffer_index]);
|
||||
if (stru_6AD940.counter < vcrEntry->counter) {
|
||||
if (vcrEntry->time > stru_6AD940.time) {
|
||||
unsigned int delay = stru_6AD940.time;
|
||||
delay += (_vcr_counter - stru_6AD940.counter)
|
||||
* (vcrEntry->time - stru_6AD940.time)
|
||||
/ (vcrEntry->counter - stru_6AD940.counter);
|
||||
|
||||
while (getTicksSince(_vcr_start_time) < delay) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_vcr_counter++;
|
||||
|
||||
int rc = 0;
|
||||
while (_vcr_counter >= _vcr_buffer[_vcr_buffer_index].counter) {
|
||||
_vcr_time = getTicksSince(_vcr_start_time);
|
||||
if (_vcr_time > _vcr_buffer[_vcr_buffer_index].time + 5
|
||||
|| _vcr_time < _vcr_buffer[_vcr_buffer_index].time - 5) {
|
||||
_vcr_start_time += _vcr_time - _vcr_buffer[_vcr_buffer_index].time;
|
||||
}
|
||||
|
||||
switch (_vcr_buffer[_vcr_buffer_index].type) {
|
||||
case VCR_ENTRY_TYPE_INITIAL_STATE:
|
||||
gVcrState = VCR_STATE_TURNED_OFF;
|
||||
gVcrOldKeyboardLayout = keyboardGetLayout();
|
||||
keyboardSetLayout(_vcr_buffer[_vcr_buffer_index].initial.keyboardLayout);
|
||||
while (mouseGetEvent() != 0) {
|
||||
_mouse_info();
|
||||
}
|
||||
gVcrState = VCR_ENTRY_TYPE_INITIAL_STATE;
|
||||
mouseHideCursor();
|
||||
_mouse_set_position(_vcr_buffer[_vcr_buffer_index].initial.mouseX, _vcr_buffer[_vcr_buffer_index].initial.mouseY);
|
||||
mouseShowCursor();
|
||||
keyboardReset();
|
||||
gVcrTerminateFlags = gVcrRequestedTerminationFlags;
|
||||
_vcr_start_time = _get_time();
|
||||
_vcr_counter = 0;
|
||||
break;
|
||||
case VCR_ENTRY_TYPE_KEYBOARD_EVENT:
|
||||
if (1) {
|
||||
KeyboardData keyboardData;
|
||||
keyboardData.key = _vcr_buffer[_vcr_buffer_index].keyboardEvent.key;
|
||||
_kb_simulate_key(&keyboardData);
|
||||
}
|
||||
break;
|
||||
case VCR_ENTRY_TYPE_MOUSE_EVENT:
|
||||
rc = 3;
|
||||
_mouse_simulate_input(_vcr_buffer[_vcr_buffer_index].mouseEvent.dx, _vcr_buffer[_vcr_buffer_index].mouseEvent.dy, _vcr_buffer[_vcr_buffer_index].mouseEvent.buttons);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(&stru_6AD940, &(_vcr_buffer[_vcr_buffer_index]), sizeof(stru_6AD940));
|
||||
_vcr_buffer_index++;
|
||||
}
|
||||
|
||||
return rc;
|
||||
} else {
|
||||
// NOTE: Uninline.
|
||||
vcrStop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Inlined.
|
||||
//
|
||||
// 0x4D2C64
|
||||
bool vcrInitBuffer()
|
||||
{
|
||||
if (_vcr_buffer == NULL) {
|
||||
_vcr_buffer = (VcrEntry*)internal_malloc(sizeof(*_vcr_buffer) * VCR_BUFFER_CAPACITY);
|
||||
if (_vcr_buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Uninline.
|
||||
vcrClear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: Inlined.
|
||||
//
|
||||
// 0x4D2C98
|
||||
bool vcrFreeBuffer()
|
||||
{
|
||||
if (_vcr_buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: Uninline.
|
||||
vcrClear();
|
||||
|
||||
internal_free(_vcr_buffer);
|
||||
_vcr_buffer = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x4D2CD0
|
||||
bool _vcr_clear_buffer()
|
||||
bool vcrClear()
|
||||
{
|
||||
if (_vcr_buffer == NULL) {
|
||||
return false;
|
||||
@@ -4451,81 +4744,104 @@ bool _vcr_clear_buffer()
|
||||
}
|
||||
|
||||
// 0x4D2CF0
|
||||
int _vcr_dump_buffer()
|
||||
bool vcrDump()
|
||||
{
|
||||
if (!_vcr_buffer || !_vcr_file) {
|
||||
return 0;
|
||||
if (_vcr_buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gVcrFile == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _vcr_buffer_index; index++) {
|
||||
if (_vcr_save_record(&(_vcr_buffer[index]), _vcr_file)) {
|
||||
_vcr_buffer_index = 0;
|
||||
return 1;
|
||||
if (!vcrWriteEntry(&(_vcr_buffer[index]), gVcrFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
// NOTE: Uninline.
|
||||
if (!vcrClear()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x4D2D74
|
||||
bool vcrLoad()
|
||||
{
|
||||
if (gVcrFile == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: Uninline.
|
||||
if (!vcrClear()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (_vcr_buffer_end = 0; _vcr_buffer_end < VCR_BUFFER_CAPACITY; _vcr_buffer_end++) {
|
||||
if (!vcrReadEntry(&(_vcr_buffer[_vcr_buffer_end]), gVcrFile)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_vcr_buffer_end == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x4D2E00
|
||||
bool _vcr_save_record(STRUCT_51E2F0* ptr, File* stream)
|
||||
bool vcrWriteEntry(VcrEntry* vcrEntry, File* stream)
|
||||
{
|
||||
if (_db_fwriteLong(stream, ptr->type) == -1) goto err;
|
||||
if (_db_fwriteLong(stream, ptr->field_4) == -1) goto err;
|
||||
if (_db_fwriteLong(stream, ptr->field_8) == -1) goto err;
|
||||
|
||||
switch (ptr->type) {
|
||||
case 1:
|
||||
if (_db_fwriteLong(stream, ptr->type_1_field_C) == -1) goto err;
|
||||
if (_db_fwriteLong(stream, ptr->type_1_field_10) == -1) goto err;
|
||||
if (_db_fwriteLong(stream, ptr->type_1_field_14) == -1) goto err;
|
||||
if (fileWriteUInt32(stream, vcrEntry->type) == -1) return false;
|
||||
if (fileWriteUInt32(stream, vcrEntry->time) == -1) return false;
|
||||
if (fileWriteUInt32(stream, vcrEntry->counter) == -1) return false;
|
||||
|
||||
switch (vcrEntry->type) {
|
||||
case VCR_ENTRY_TYPE_INITIAL_STATE:
|
||||
if (fileWriteInt32(stream, vcrEntry->initial.mouseX) == -1) return false;
|
||||
if (fileWriteInt32(stream, vcrEntry->initial.mouseY) == -1) return false;
|
||||
if (fileWriteInt32(stream, vcrEntry->initial.keyboardLayout) == -1) return false;
|
||||
return true;
|
||||
case 2:
|
||||
if (fileWriteInt16(stream, ptr->type_2_field_C) == -1) goto err;
|
||||
|
||||
case VCR_ENTRY_TYPE_KEYBOARD_EVENT:
|
||||
if (fileWriteInt16(stream, vcrEntry->keyboardEvent.key) == -1) return false;
|
||||
return true;
|
||||
case 3:
|
||||
if (_db_fwriteLong(stream, ptr->dx) == -1) goto err;
|
||||
if (_db_fwriteLong(stream, ptr->dy) == -1) goto err;
|
||||
if (_db_fwriteLong(stream, ptr->buttons) == -1) goto err;
|
||||
|
||||
case VCR_ENTRY_TYPE_MOUSE_EVENT:
|
||||
if (fileWriteInt32(stream, vcrEntry->mouseEvent.dx) == -1) return false;
|
||||
if (fileWriteInt32(stream, vcrEntry->mouseEvent.dy) == -1) return false;
|
||||
if (fileWriteInt32(stream, vcrEntry->mouseEvent.buttons) == -1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
err:
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 0x4D2EE4
|
||||
bool _vcr_load_record(STRUCT_51E2F0* ptr, File* stream)
|
||||
bool vcrReadEntry(VcrEntry* vcrEntry, File* stream)
|
||||
{
|
||||
if (_db_freadInt(stream, &(ptr->type)) == -1) goto err;
|
||||
if (_db_freadInt(stream, &(ptr->field_4)) == -1) goto err;
|
||||
if (_db_freadInt(stream, &(ptr->field_8)) == -1) goto err;
|
||||
|
||||
switch (ptr->type) {
|
||||
case 1:
|
||||
if (_db_freadInt(stream, &(ptr->type_1_field_C)) == -1) goto err;
|
||||
if (_db_freadInt(stream, &(ptr->type_1_field_10)) == -1) goto err;
|
||||
if (_db_freadInt(stream, &(ptr->type_1_field_14)) == -1) goto err;
|
||||
if (fileReadUInt32(stream, &(vcrEntry->type)) == -1) return false;
|
||||
if (fileReadUInt32(stream, &(vcrEntry->time)) == -1) return false;
|
||||
if (fileReadUInt32(stream, &(vcrEntry->counter)) == -1) return false;
|
||||
|
||||
switch (vcrEntry->type) {
|
||||
case VCR_ENTRY_TYPE_INITIAL_STATE:
|
||||
if (fileReadInt32(stream, &(vcrEntry->initial.mouseX)) == -1) return false;
|
||||
if (fileReadInt32(stream, &(vcrEntry->initial.mouseY)) == -1) return false;
|
||||
if (fileReadInt32(stream, &(vcrEntry->initial.keyboardLayout)) == -1) return false;
|
||||
return true;
|
||||
case 2:
|
||||
if (fileReadInt16(stream, &(ptr->type_2_field_C)) == -1) goto err;
|
||||
|
||||
case VCR_ENTRY_TYPE_KEYBOARD_EVENT:
|
||||
if (fileReadInt16(stream, &(vcrEntry->keyboardEvent.key)) == -1) return false;
|
||||
return true;
|
||||
case 3:
|
||||
if (_db_freadInt(stream, &(ptr->dx)) == -1) goto err;
|
||||
if (_db_freadInt(stream, &(ptr->dy)) == -1) goto err;
|
||||
if (_db_freadInt(stream, &(ptr->buttons)) == -1) goto err;
|
||||
|
||||
case VCR_ENTRY_TYPE_MOUSE_EVENT:
|
||||
if (fileReadInt32(stream, &(vcrEntry->mouseEvent.dx)) == -1) return false;
|
||||
if (fileReadInt32(stream, &(vcrEntry->mouseEvent.dy)) == -1) return false;
|
||||
if (fileReadInt32(stream, &(vcrEntry->mouseEvent.buttons)) == -1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
err:
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4574,3 +4890,33 @@ bool mouseHitTestInWindow(int win, int left, int top, int right, int bottom)
|
||||
|
||||
return _mouse_click_in(left, top, right, bottom);
|
||||
}
|
||||
|
||||
void mouseGetWheel(int* x, int* y)
|
||||
{
|
||||
*x = gMouseWheelX;
|
||||
*y = gMouseWheelY;
|
||||
}
|
||||
|
||||
void convertMouseWheelToArrowKey(int* keyCodePtr)
|
||||
{
|
||||
if (*keyCodePtr == -1) {
|
||||
if ((mouseGetEvent() & MOUSE_EVENT_WHEEL) != 0) {
|
||||
int wheelX;
|
||||
int wheelY;
|
||||
mouseGetWheel(&wheelX, &wheelY);
|
||||
|
||||
if (wheelY > 0) {
|
||||
*keyCodePtr = KEY_ARROW_UP;
|
||||
} else if (wheelY < 0) {
|
||||
*keyCodePtr = KEY_ARROW_DOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void idleImpl()
|
||||
{
|
||||
SDL_Delay(125);
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
128
src/core.h
@@ -1,12 +1,14 @@
|
||||
#ifndef CORE_H
|
||||
#define CORE_H
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "db.h"
|
||||
#include "dinput.h"
|
||||
#include "geometry.h"
|
||||
#include "window.h"
|
||||
|
||||
#include <SDL.h>
|
||||
namespace fallout {
|
||||
|
||||
#define MOUSE_DEFAULT_CURSOR_WIDTH 8
|
||||
#define MOUSE_DEFAULT_CURSOR_HEIGHT 8
|
||||
@@ -26,6 +28,7 @@
|
||||
#define MOUSE_EVENT_ANY_BUTTON_UP (MOUSE_EVENT_LEFT_BUTTON_UP | MOUSE_EVENT_RIGHT_BUTTON_UP)
|
||||
#define MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT (MOUSE_EVENT_LEFT_BUTTON_DOWN | MOUSE_EVENT_LEFT_BUTTON_REPEAT)
|
||||
#define MOUSE_EVENT_RIGHT_BUTTON_DOWN_REPEAT (MOUSE_EVENT_RIGHT_BUTTON_DOWN | MOUSE_EVENT_RIGHT_BUTTON_REPEAT)
|
||||
#define MOUSE_EVENT_WHEEL 0x40
|
||||
|
||||
#define BUTTON_REPEAT_TIME 250
|
||||
|
||||
@@ -360,6 +363,44 @@ typedef enum KeyboardLayout {
|
||||
KEYBOARD_LAYOUT_SPANISH,
|
||||
} KeyboardLayout;
|
||||
|
||||
#define VCR_BUFFER_CAPACITY 4096
|
||||
|
||||
typedef enum VcrState {
|
||||
VCR_STATE_RECORDING,
|
||||
VCR_STATE_PLAYING,
|
||||
VCR_STATE_TURNED_OFF,
|
||||
} VcrState;
|
||||
|
||||
#define VCR_STATE_STOP_REQUESTED 0x80000000
|
||||
|
||||
typedef enum VcrTerminationFlags {
|
||||
// Specifies that VCR playback should stop if any key is pressed.
|
||||
VCR_TERMINATE_ON_KEY_PRESS = 0x01,
|
||||
|
||||
// Specifies that VCR playback should stop if mouse is mouved.
|
||||
VCR_TERMINATE_ON_MOUSE_MOVE = 0x02,
|
||||
|
||||
// Specifies that VCR playback should stop if any mouse button is pressed.
|
||||
VCR_TERMINATE_ON_MOUSE_PRESS = 0x04,
|
||||
} VcrTerminationFlags;
|
||||
|
||||
typedef enum VcrPlaybackCompletionReason {
|
||||
VCR_PLAYBACK_COMPLETION_REASON_NONE = 0,
|
||||
|
||||
// Indicates that VCR playback completed normally.
|
||||
VCR_PLAYBACK_COMPLETION_REASON_COMPLETED = 1,
|
||||
|
||||
// Indicates that VCR playback terminated according to termination flags.
|
||||
VCR_PLAYBACK_COMPLETION_REASON_TERMINATED = 2,
|
||||
} VcrPlaybackCompletionReason;
|
||||
|
||||
typedef enum VcrEntryType {
|
||||
VCR_ENTRY_TYPE_NONE = 0,
|
||||
VCR_ENTRY_TYPE_INITIAL_STATE = 1,
|
||||
VCR_ENTRY_TYPE_KEYBOARD_EVENT = 2,
|
||||
VCR_ENTRY_TYPE_MOUSE_EVENT = 3,
|
||||
} VcrEntryType;
|
||||
|
||||
typedef struct STRUCT_6ABF50 {
|
||||
// Time when appropriate key was pressed down or -1 if it's up.
|
||||
int tick;
|
||||
@@ -375,7 +416,9 @@ typedef struct InputEvent {
|
||||
int mouseY;
|
||||
} InputEvent;
|
||||
|
||||
typedef void TickerProc();
|
||||
typedef void(IdleFunc)();
|
||||
typedef void(FocusFunc)(bool focus);
|
||||
typedef void(TickerProc)();
|
||||
|
||||
typedef struct TickerListNode {
|
||||
int flags;
|
||||
@@ -383,26 +426,26 @@ typedef struct TickerListNode {
|
||||
struct TickerListNode* next;
|
||||
} TickerListNode;
|
||||
|
||||
typedef struct STRUCT_51E2F0 {
|
||||
int type;
|
||||
int field_4;
|
||||
int field_8;
|
||||
typedef struct VcrEntry {
|
||||
unsigned int type;
|
||||
unsigned int time;
|
||||
unsigned int counter;
|
||||
union {
|
||||
struct {
|
||||
int type_1_field_C; // mouse x
|
||||
int type_1_field_10; // mouse y
|
||||
int type_1_field_14; // keyboard layout
|
||||
};
|
||||
int mouseX;
|
||||
int mouseY;
|
||||
int keyboardLayout;
|
||||
} initial;
|
||||
struct {
|
||||
short type_2_field_C;
|
||||
};
|
||||
short key;
|
||||
} keyboardEvent;
|
||||
struct {
|
||||
int dx;
|
||||
int dy;
|
||||
int buttons;
|
||||
};
|
||||
} mouseEvent;
|
||||
};
|
||||
} STRUCT_51E2F0;
|
||||
} VcrEntry;
|
||||
|
||||
typedef struct LogicalKeyEntry {
|
||||
short field_0;
|
||||
@@ -420,9 +463,10 @@ typedef struct KeyboardEvent {
|
||||
|
||||
typedef int(PauseHandler)();
|
||||
typedef int(ScreenshotHandler)(int width, int height, unsigned char* buffer, unsigned char* palette);
|
||||
typedef void(VcrPlaybackCompletionCallback)(int reason);
|
||||
|
||||
extern void (*_idle_func)();
|
||||
extern void (*_focus_func)(int);
|
||||
extern IdleFunc* _idle_func;
|
||||
extern FocusFunc* _focus_func;
|
||||
extern int gKeyboardKeyRepeatRate;
|
||||
extern int gKeyboardKeyRepeatDelay;
|
||||
extern bool _keyboard_hooked;
|
||||
@@ -448,17 +492,20 @@ extern int gKeyboardEventQueueReadIndex;
|
||||
extern short word_51E2E8;
|
||||
extern int gModifierKeysState;
|
||||
extern int (*_kb_scan_to_ascii)();
|
||||
extern STRUCT_51E2F0* _vcr_buffer;
|
||||
extern VcrEntry* _vcr_buffer;
|
||||
extern int _vcr_buffer_index;
|
||||
extern int _vcr_state;
|
||||
extern int _vcr_time;
|
||||
extern int _vcr_counter;
|
||||
extern int _vcr_terminate_flags;
|
||||
extern int _vcr_terminated_condition;
|
||||
extern int _vcr_start_time;
|
||||
extern unsigned int gVcrState;
|
||||
extern unsigned int _vcr_time;
|
||||
extern unsigned int _vcr_counter;
|
||||
extern unsigned int gVcrTerminateFlags;
|
||||
extern int gVcrPlaybackCompletionReason;
|
||||
extern unsigned int _vcr_start_time;
|
||||
extern int _vcr_registered_atexit;
|
||||
extern File* _vcr_file;
|
||||
extern File* gVcrFile;
|
||||
extern int _vcr_buffer_end;
|
||||
extern VcrPlaybackCompletionCallback* gVcrPlaybackCompletionCallback;
|
||||
extern unsigned int gVcrRequestedTerminationFlags;
|
||||
extern int gVcrOldKeyboardLayout;
|
||||
|
||||
extern int gNormalizedQwertyKeys[SDL_NUM_SCANCODES];
|
||||
extern InputEvent gInputEventQueue[40];
|
||||
@@ -520,6 +567,7 @@ extern unsigned int _kb_idle_start_time;
|
||||
extern KeyboardEvent gLastKeyboardEvent;
|
||||
extern int gKeyboardLayout;
|
||||
extern unsigned char gPressedPhysicalKeysCount;
|
||||
extern VcrEntry stru_6AD940;
|
||||
|
||||
extern SDL_Window* gSdlWindow;
|
||||
extern SDL_Surface* gSdlSurface;
|
||||
@@ -552,6 +600,14 @@ void coreDelay(unsigned int ms);
|
||||
unsigned int getTicksSince(unsigned int a1);
|
||||
unsigned int getTicksBetween(unsigned int a1, unsigned int a2);
|
||||
unsigned int _get_bk_time();
|
||||
void inputSetKeyboardKeyRepeatRate(int value);
|
||||
int inputGetKeyboardKeyRepeatRate();
|
||||
void inputSetKeyboardKeyRepeatDelay(int value);
|
||||
int inputGetKeyboardKeyRepeatDelay();
|
||||
void inputSetFocusFunc(FocusFunc* func);
|
||||
FocusFunc* inputGetFocusFunc();
|
||||
void inputSetIdleFunc(IdleFunc* func);
|
||||
IdleFunc* inputGetIdleFunc();
|
||||
void buildNormalizedQwertyKeys();
|
||||
int _GNW95_input_init();
|
||||
void _GNW95_process_message();
|
||||
@@ -621,19 +677,27 @@ void keyboardBuildItalianConfiguration();
|
||||
void keyboardBuildSpanishConfiguration();
|
||||
void _kb_init_lock_status();
|
||||
int keyboardPeekEvent(int index, KeyboardEvent** keyboardEventPtr);
|
||||
bool _vcr_record(const char* fileName);
|
||||
void _vcr_stop();
|
||||
int _vcr_status();
|
||||
int _vcr_update();
|
||||
bool _vcr_clear_buffer();
|
||||
int _vcr_dump_buffer();
|
||||
bool _vcr_save_record(STRUCT_51E2F0* ptr, File* stream);
|
||||
bool _vcr_load_record(STRUCT_51E2F0* ptr, File* stream);
|
||||
bool vcrRecord(const char* fileName);
|
||||
bool vcrPlay(const char* fileName, unsigned int terminationFlags, VcrPlaybackCompletionCallback* callback);
|
||||
void vcrStop();
|
||||
int vcrGetState();
|
||||
int vcrUpdate();
|
||||
bool vcrInitBuffer();
|
||||
bool vcrFreeBuffer();
|
||||
bool vcrClear();
|
||||
bool vcrDump();
|
||||
bool vcrLoad();
|
||||
bool vcrWriteEntry(VcrEntry* ptr, File* stream);
|
||||
bool vcrReadEntry(VcrEntry* ptr, File* stream);
|
||||
|
||||
int screenGetWidth();
|
||||
int screenGetHeight();
|
||||
int screenGetVisibleHeight();
|
||||
void mouseGetPositionInWindow(int win, int* x, int* y);
|
||||
bool mouseHitTestInWindow(int win, int left, int top, int right, int bottom);
|
||||
void mouseGetWheel(int* x, int* y);
|
||||
void convertMouseWheelToArrowKey(int* keyCodePtr);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CORE_H */
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "credits.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "art.h"
|
||||
#include "color.h"
|
||||
#include "core.h"
|
||||
@@ -16,7 +18,7 @@
|
||||
#include "text_font.h"
|
||||
#include "window_manager.h"
|
||||
|
||||
#include <string.h>
|
||||
namespace fallout {
|
||||
|
||||
#define CREDITS_WINDOW_WIDTH (640)
|
||||
#define CREDITS_WINDOW_HEIGHT (480)
|
||||
@@ -274,3 +276,5 @@ static bool creditsFileParseNextLine(char* dest, int* font, int* color)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#ifndef CREDITS_H
|
||||
#define CREDITS_H
|
||||
|
||||
namespace fallout {
|
||||
|
||||
void creditsOpen(const char* path, int fid, bool useReversedStyle);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CREDITS_H */
|
||||
|
||||
132
src/critter.cc
@@ -1,5 +1,8 @@
|
||||
#include "critter.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "animation.h"
|
||||
#include "art.h"
|
||||
#include "character_editor.h"
|
||||
@@ -26,10 +29,9 @@
|
||||
#include "stat.h"
|
||||
#include "tile.h"
|
||||
#include "trait.h"
|
||||
#include "world_map.h"
|
||||
#include "worldmap.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
namespace fallout {
|
||||
|
||||
// Maximum length of dude's name length.
|
||||
#define DUDE_NAME_MAX_LENGTH (32)
|
||||
@@ -68,6 +70,7 @@ typedef enum RadiationLevel {
|
||||
} RadiationLevel;
|
||||
|
||||
static int _get_rad_damage_level(Object* obj, void* data);
|
||||
static int critter_kill_count_clear();
|
||||
static int _critterClearObjDrugs(Object* obj, void* data);
|
||||
|
||||
// 0x50141C
|
||||
@@ -159,7 +162,8 @@ int critterInit()
|
||||
{
|
||||
dudeResetName();
|
||||
|
||||
memset(gKillsByType, 0, sizeof(gKillsByType));
|
||||
// NOTE: Uninline;
|
||||
critter_kill_count_clear();
|
||||
|
||||
if (!messageListInit(&gCritterMessageList)) {
|
||||
debugPrint("\nError: Initing critter name message file!");
|
||||
@@ -181,7 +185,9 @@ int critterInit()
|
||||
void critterReset()
|
||||
{
|
||||
dudeResetName();
|
||||
memset(gKillsByType, 0, sizeof(gKillsByType));
|
||||
|
||||
// NOTE: Uninline;
|
||||
critter_kill_count_clear();
|
||||
}
|
||||
|
||||
// 0x42D004
|
||||
@@ -276,18 +282,18 @@ void dudeResetName()
|
||||
// 0x42D18C
|
||||
int critterGetHitPoints(Object* critter)
|
||||
{
|
||||
return (critter->pid >> 24) == OBJ_TYPE_CRITTER ? critter->data.critter.hp : 0;
|
||||
return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.hp : 0;
|
||||
}
|
||||
|
||||
// 0x42D1A4
|
||||
int critterAdjustHitPoints(Object* critter, int a2)
|
||||
int critterAdjustHitPoints(Object* critter, int hp)
|
||||
{
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int maximumHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS);
|
||||
int newHp = critter->data.critter.hp + a2;
|
||||
int newHp = critter->data.critter.hp + hp;
|
||||
|
||||
critter->data.critter.hp = newHp;
|
||||
if (maximumHp >= newHp) {
|
||||
@@ -304,7 +310,7 @@ int critterAdjustHitPoints(Object* critter, int a2)
|
||||
// 0x42D1F8
|
||||
int critterGetPoison(Object* critter)
|
||||
{
|
||||
return (critter->pid >> 24) == OBJ_TYPE_CRITTER ? critter->data.critter.poison : 0;
|
||||
return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.poison : 0;
|
||||
}
|
||||
|
||||
// Adjust critter's current poison by specified amount.
|
||||
@@ -396,7 +402,7 @@ int poisonEventProcess(Object* obj, void* data)
|
||||
// 0x42D38C
|
||||
int critterGetRadiation(Object* obj)
|
||||
{
|
||||
return (obj->pid >> 24) == OBJ_TYPE_CRITTER ? obj->data.critter.radiation : 0;
|
||||
return PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER ? obj->data.critter.radiation : 0;
|
||||
}
|
||||
|
||||
// 0x42D3A4
|
||||
@@ -416,7 +422,7 @@ int critterAdjustRadiation(Object* obj, int amount)
|
||||
}
|
||||
|
||||
if (amount > 0) {
|
||||
proto->critter.data.flags |= 0x02;
|
||||
proto->critter.data.flags |= CRITTER_FLAG_0x2;
|
||||
}
|
||||
|
||||
if (amount > 0) {
|
||||
@@ -483,7 +489,7 @@ int _critter_check_rads(Object* obj)
|
||||
|
||||
Proto* proto;
|
||||
protoGetProto(obj->pid, &proto);
|
||||
if ((proto->critter.data.flags & 0x02) == 0) {
|
||||
if ((proto->critter.data.flags & CRITTER_FLAG_0x2) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -521,10 +527,10 @@ int _critter_check_rads(Object* obj)
|
||||
|
||||
radiationEvent->radiationLevel = radiationLevel;
|
||||
radiationEvent->isHealing = 0;
|
||||
queueAddEvent(36000 * randomBetween(4, 18), obj, radiationEvent, EVENT_TYPE_RADIATION);
|
||||
queueAddEvent(GAME_TIME_TICKS_PER_HOUR * randomBetween(4, 18), obj, radiationEvent, EVENT_TYPE_RADIATION);
|
||||
}
|
||||
|
||||
proto->critter.data.flags &= ~(0x02);
|
||||
proto->critter.data.flags &= ~(CRITTER_FLAG_0x2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -568,6 +574,13 @@ void _process_rads(Object* obj, int radiationLevel, bool isHealing)
|
||||
if (obj == gDude) {
|
||||
// Radiation level message, higher is worse.
|
||||
messageListItem.num = 1000 + radiationLevelIndex;
|
||||
|
||||
// SFALL: Fix radiation message when removing radiation effects.
|
||||
if (isHealing) {
|
||||
// You feel better.
|
||||
messageListItem.num = 3003;
|
||||
}
|
||||
|
||||
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
||||
displayMonitorAddMessage(messageListItem.text);
|
||||
}
|
||||
@@ -579,15 +592,18 @@ void _process_rads(Object* obj, int radiationLevel, bool isHealing)
|
||||
critterSetBonusStat(obj, gRadiationEffectStats[effect], value);
|
||||
}
|
||||
|
||||
if ((obj->data.critter.combat.results & DAM_DEAD) == 0) {
|
||||
// Loop thru effects affecting primary stats. If any of the primary stat
|
||||
// dropped below minimal value, kill it.
|
||||
for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) {
|
||||
int base = critterGetBaseStatWithTraitModifier(obj, gRadiationEffectStats[effect]);
|
||||
int bonus = critterGetBonusStat(obj, gRadiationEffectStats[effect]);
|
||||
if (base + bonus < PRIMARY_STAT_MIN) {
|
||||
critterKill(obj, -1, 1);
|
||||
break;
|
||||
// SFALL: Prevent death when removing radiation effects.
|
||||
if (!isHealing) {
|
||||
if ((obj->data.critter.combat.results & DAM_DEAD) == 0) {
|
||||
// Loop thru effects affecting primary stats. If any of the primary stat
|
||||
// dropped below minimal value, kill it.
|
||||
for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) {
|
||||
int base = critterGetBaseStatWithTraitModifier(obj, gRadiationEffectStats[effect]);
|
||||
int bonus = critterGetBonusStat(obj, gRadiationEffectStats[effect]);
|
||||
if (base + bonus < PRIMARY_STAT_MIN) {
|
||||
critterKill(obj, -1, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,7 +613,8 @@ void _process_rads(Object* obj, int radiationLevel, bool isHealing)
|
||||
// You have died from radiation sickness.
|
||||
messageListItem.num = 1006;
|
||||
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
|
||||
displayMonitorAddMessage(messageListItem.text);
|
||||
// SFALL: Display a pop-up message box about death from radiation.
|
||||
gameShowDeathDialog(messageListItem.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,7 +674,7 @@ int radiationEventWrite(File* stream, void* data)
|
||||
// 0x42D82C
|
||||
int critterGetDamageType(Object* obj)
|
||||
{
|
||||
if ((obj->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -669,6 +686,15 @@ int critterGetDamageType(Object* obj)
|
||||
return proto->critter.data.damageType;
|
||||
}
|
||||
|
||||
// NOTE: Inlined.
|
||||
//
|
||||
// 0x42D860
|
||||
static int critter_kill_count_clear()
|
||||
{
|
||||
memset(gKillsByType, 0, sizeof(gKillsByType));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 0x42D878
|
||||
int killsIncByType(int killType)
|
||||
{
|
||||
@@ -723,7 +749,7 @@ int critterGetKillType(Object* obj)
|
||||
return KILL_TYPE_MAN;
|
||||
}
|
||||
|
||||
if ((obj->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -766,7 +792,7 @@ char* killTypeGetDescription(int killType)
|
||||
// 0x42D9F4
|
||||
int _critter_heal_hours(Object* critter, int a2)
|
||||
{
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -788,7 +814,7 @@ static int _critterClearObjDrugs(Object* obj, void* data)
|
||||
// 0x42DA64
|
||||
void critterKill(Object* critter, int anim, bool a3)
|
||||
{
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -800,20 +826,20 @@ void critterKill(Object* critter, int anim, bool a3)
|
||||
bool shouldChangeFid = false;
|
||||
int fid;
|
||||
if (_critter_is_prone(critter)) {
|
||||
int current = (critter->fid & 0xFF0000) >> 16;
|
||||
int current = FID_ANIM_TYPE(critter->fid);
|
||||
if (current == ANIM_FALL_BACK || current == ANIM_FALL_FRONT) {
|
||||
bool back = false;
|
||||
if (current == ANIM_FALL_BACK) {
|
||||
back = true;
|
||||
} else {
|
||||
fid = buildFid(1, critter->fid & 0xFFF, ANIM_FALL_FRONT_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_FRONT_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
if (!artExists(fid)) {
|
||||
back = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (back) {
|
||||
fid = buildFid(1, critter->fid & 0xFFF, ANIM_FALL_BACK_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
}
|
||||
|
||||
shouldChangeFid = true;
|
||||
@@ -828,12 +854,12 @@ void critterKill(Object* critter, int anim, bool a3)
|
||||
anim = LAST_SF_DEATH_ANIM;
|
||||
}
|
||||
|
||||
fid = buildFid(1, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
_obj_fix_violence_settings(&fid);
|
||||
if (!artExists(fid)) {
|
||||
debugPrint("\nError: Critter Kill: Can't match fid!");
|
||||
|
||||
fid = buildFid(1, critter->fid & 0xFFF, ANIM_FALL_BACK_BLOOD_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_BLOOD_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);
|
||||
_obj_fix_violence_settings(&fid);
|
||||
}
|
||||
|
||||
@@ -850,7 +876,7 @@ void critterKill(Object* critter, int anim, bool a3)
|
||||
rectUnion(&updatedRect, &tempRect, &updatedRect);
|
||||
}
|
||||
|
||||
if (!_critter_flag_check(critter->pid, 2048)) {
|
||||
if (!_critter_flag_check(critter->pid, CRITTER_FLAG_0x800)) {
|
||||
critter->flags |= OBJECT_NO_BLOCK;
|
||||
_obj_toggle_flat(critter, &tempRect);
|
||||
}
|
||||
@@ -873,7 +899,7 @@ void critterKill(Object* critter, int anim, bool a3)
|
||||
_critterClearObj = critter;
|
||||
_queue_clear_type(EVENT_TYPE_DRUG, _critterClearObjDrugs);
|
||||
|
||||
_item_destroy_all_hidden(critter);
|
||||
itemDestroyAllHidden(critter);
|
||||
|
||||
if (a3) {
|
||||
tileWindowRefreshRect(&updatedRect, elevation);
|
||||
@@ -902,7 +928,7 @@ bool critterIsActive(Object* critter)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -924,7 +950,7 @@ bool critterIsDead(Object* critter)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -946,7 +972,7 @@ bool critterIsCrippled(Object* critter)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -960,15 +986,15 @@ bool _critter_is_prone(Object* critter)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int anim = (critter->fid & 0xFF0000) >> 16;
|
||||
int anim = FID_ANIM_TYPE(critter->fid);
|
||||
|
||||
return (critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0
|
||||
|| anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM
|
||||
|| anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM;
|
||||
|| (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM)
|
||||
|| (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM);
|
||||
}
|
||||
|
||||
// critter_body_type
|
||||
@@ -980,7 +1006,7 @@ int critterGetBodyType(Object* critter)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1236,7 +1262,7 @@ int knockoutEventProcess(Object* obj, void* data)
|
||||
// 0x42E460
|
||||
int _critter_wake_clear(Object* obj, void* data)
|
||||
{
|
||||
if ((obj->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1246,7 +1272,7 @@ int _critter_wake_clear(Object* obj, void* data)
|
||||
|
||||
obj->data.critter.combat.results &= ~(DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN);
|
||||
|
||||
int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_STAND, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
||||
int fid = buildFid(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_STAND, (obj->fid & 0xF000) >> 12, obj->rotation + 1);
|
||||
objectSetFid(obj, fid, 0);
|
||||
|
||||
return 0;
|
||||
@@ -1259,12 +1285,12 @@ int _critter_set_who_hit_me(Object* a1, Object* a2)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a2 != NULL && ((a2->fid & 0xF000000) >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (a2 != NULL && FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((a1->pid >> 24) == OBJ_TYPE_CRITTER) {
|
||||
if (a2 == NULL || a1->data.critter.combat.team != a2->data.critter.combat.team || statRoll(a1, STAT_INTELLIGENCE, -1, NULL) < 2 && (!objectIsPartyMember(a1) || !objectIsPartyMember(a2))) {
|
||||
if (PID_TYPE(a1->pid) == OBJ_TYPE_CRITTER) {
|
||||
if (a2 == NULL || a1->data.critter.combat.team != a2->data.critter.combat.team || (statRoll(a1, STAT_INTELLIGENCE, -1, NULL) < 2 && (!objectIsPartyMember(a1) || !objectIsPartyMember(a2)))) {
|
||||
a1->data.critter.combat.whoHitMe = a2;
|
||||
if (a2 == gDude) {
|
||||
reactionSetValue(a1, -3);
|
||||
@@ -1279,7 +1305,7 @@ int _critter_set_who_hit_me(Object* a1, Object* a2)
|
||||
bool _critter_can_obj_dude_rest()
|
||||
{
|
||||
bool v1 = false;
|
||||
if (!_wmMapCanRestHere(gElevation)) {
|
||||
if (!wmMapCanRestHere(gElevation)) {
|
||||
v1 = true;
|
||||
}
|
||||
|
||||
@@ -1319,7 +1345,7 @@ bool _critter_can_obj_dude_rest()
|
||||
// 0x42E62C
|
||||
int critterGetMovementPointCostAdjustedForCrippledLegs(Object* critter, int actionPoints)
|
||||
{
|
||||
if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1358,7 +1384,7 @@ bool _critter_flag_check(int pid, int flag)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((pid >> 24) != OBJ_TYPE_CRITTER) {
|
||||
if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1366,3 +1392,5 @@ bool _critter_flag_check(int pid, int flag)
|
||||
protoGetProto(pid, &proto);
|
||||
return (proto->critter.data.flags & flag) != 0;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "obj_types.h"
|
||||
#include "proto_types.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef enum DudeState {
|
||||
DUDE_STATE_SNEAKING = 0,
|
||||
DUDE_STATE_LEVEL_UP_AVAILABLE = 3,
|
||||
@@ -21,7 +23,7 @@ void critterProtoDataCopy(CritterProtoData* dest, CritterProtoData* src);
|
||||
int dudeSetName(const char* name);
|
||||
void dudeResetName();
|
||||
int critterGetHitPoints(Object* critter);
|
||||
int critterAdjustHitPoints(Object* critter, int amount);
|
||||
int critterAdjustHitPoints(Object* critter, int hp);
|
||||
int critterGetPoison(Object* critter);
|
||||
int critterAdjustPoison(Object* obj, int amount);
|
||||
int poisonEventProcess(Object* obj, void* data);
|
||||
@@ -69,4 +71,6 @@ bool critterIsEncumbered(Object* critter);
|
||||
bool critterIsFleeing(Object* a1);
|
||||
bool _critter_flag_check(int pid, int flag);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CRITTER_H */
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "game_config.h"
|
||||
#include "palette.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
#define COLOR_CYCLE_PERIOD_1 (200U)
|
||||
#define COLOR_CYCLE_PERIOD_2 (142U)
|
||||
#define COLOR_CYCLE_PERIOD_3 (100U)
|
||||
@@ -332,3 +334,5 @@ void colorCycleTicker()
|
||||
paletteSetEntriesInRange(palette + 229 * 3, 229, 255);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef CYCLE_H
|
||||
#define CYCLE_H
|
||||
|
||||
namespace fallout {
|
||||
|
||||
void colorCycleInit();
|
||||
void colorCycleReset();
|
||||
void colorCycleFree();
|
||||
@@ -10,4 +12,6 @@ bool colorCycleEnabled();
|
||||
void cycleSetSpeedFactor(int value);
|
||||
void colorCycleTicker();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* CYCLE_H */
|
||||
|
||||
195
src/datafile.cc
@@ -1,10 +1,199 @@
|
||||
#include "datafile.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "color.h"
|
||||
#include "db.h"
|
||||
#include "memory_manager.h"
|
||||
#include "pcx.h"
|
||||
#include "platform_compat.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
// 0x5184AC
|
||||
DatafileLoader* gDatafileLoader = NULL;
|
||||
|
||||
// 0x5184B0
|
||||
DatafileNameMangler* gDatafileNameMangler = datafileDefaultNameManglerImpl;
|
||||
|
||||
// 0x56D7E0
|
||||
unsigned char _pal[768];
|
||||
unsigned char gDatafilePalette[768];
|
||||
|
||||
// 0x42EE70
|
||||
char* datafileDefaultNameManglerImpl(char* path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x42EE74
|
||||
void datafileSetNameMangler(DatafileNameMangler* mangler)
|
||||
{
|
||||
gDatafileNameMangler = mangler;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x42EE7C
|
||||
void datafileSetLoader(DatafileLoader* loader)
|
||||
{
|
||||
gDatafileLoader = loader;
|
||||
}
|
||||
|
||||
// 0x42EE84
|
||||
void sub_42EE84(unsigned char* data, unsigned char* palette, int width, int height)
|
||||
{
|
||||
unsigned char indexedPalette[256];
|
||||
|
||||
indexedPalette[0] = 0;
|
||||
for (int index = 1; index < 256; index++) {
|
||||
// TODO: Check.
|
||||
int r = palette[index * 3 + 2] >> 3;
|
||||
int g = palette[index * 3 + 1] >> 3;
|
||||
int b = palette[index * 3] >> 3;
|
||||
int colorTableIndex = (r << 10) | (g << 5) | b;
|
||||
indexedPalette[index] = _colorTable[colorTableIndex];
|
||||
}
|
||||
|
||||
int size = width * height;
|
||||
for (int index = 0; index < size; index++) {
|
||||
data[index] = indexedPalette[data[index]];
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x42EEF8
|
||||
void sub_42EEF8(unsigned char* data, unsigned char* palette, int width, int height)
|
||||
{
|
||||
unsigned char indexedPalette[256];
|
||||
|
||||
indexedPalette[0] = 0;
|
||||
for (int index = 1; index < 256; index++) {
|
||||
// TODO: Check.
|
||||
int r = palette[index * 3 + 2] >> 1;
|
||||
int g = palette[index * 3 + 1] >> 1;
|
||||
int b = palette[index * 3] >> 1;
|
||||
int colorTableIndex = (r << 10) | (g << 5) | b;
|
||||
indexedPalette[index] = _colorTable[colorTableIndex];
|
||||
}
|
||||
|
||||
int size = width * height;
|
||||
for (int index = 0; index < size; index++) {
|
||||
data[index] = indexedPalette[data[index]];
|
||||
}
|
||||
}
|
||||
|
||||
// 0x42EF60
|
||||
unsigned char* datafileReadRaw(char* path, int* widthPtr, int* heightPtr)
|
||||
{
|
||||
char* mangledPath = gDatafileNameMangler(path);
|
||||
char* dot = strrchr(mangledPath, '.');
|
||||
if (dot != NULL) {
|
||||
if (compat_stricmp(dot + 1, "pcx") == 0) {
|
||||
return pcxRead(mangledPath, widthPtr, heightPtr, gDatafilePalette);
|
||||
}
|
||||
}
|
||||
|
||||
if (gDatafileLoader != NULL) {
|
||||
return gDatafileLoader(mangledPath, gDatafilePalette, widthPtr, heightPtr);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 0x42EFCC
|
||||
unsigned char* datafileRead(char* path, int* widthPtr, int* heightPtr)
|
||||
{
|
||||
unsigned char* v1 = datafileReadRaw(path, widthPtr, heightPtr);
|
||||
if (v1 != NULL) {
|
||||
sub_42EE84(v1, gDatafilePalette, *widthPtr, *heightPtr);
|
||||
}
|
||||
return v1;
|
||||
}
|
||||
|
||||
// NOTE: Unused
|
||||
//
|
||||
// 0x42EFF4
|
||||
unsigned char* sub_42EFF4(char* path)
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
unsigned char* v3 = datafileReadRaw(path, &width, &height);
|
||||
if (v3 != NULL) {
|
||||
internal_free_safe(v3, __FILE__, __LINE__); // "..\\int\\DATAFILE.C", 148
|
||||
return gDatafilePalette;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x42F024
|
||||
void sub_42F024(unsigned char* data, int* widthPtr, int* heightPtr)
|
||||
{
|
||||
int width = *widthPtr;
|
||||
int height = *heightPtr;
|
||||
unsigned char* temp = (unsigned char*)internal_malloc_safe(width * height, __FILE__, __LINE__); // "..\\int\\DATAFILE.C", 157
|
||||
|
||||
// NOTE: Original code does not initialize `x`.
|
||||
int y = 0;
|
||||
int x = 0;
|
||||
unsigned char* src1 = data;
|
||||
|
||||
for (y = 0; y < height; y++) {
|
||||
if (*src1 == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned char* src2 = src1;
|
||||
for (x = 0; x < width; x++) {
|
||||
if (*src2 == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
*temp++ = *src2++;
|
||||
}
|
||||
|
||||
src1 += width;
|
||||
}
|
||||
|
||||
memcpy(data, temp, x * y);
|
||||
internal_free_safe(temp, __FILE__, __LINE__); // // "..\\int\\DATAFILE.C", 171
|
||||
}
|
||||
|
||||
// 0x42F0E4
|
||||
unsigned char* _datafileGetPalette()
|
||||
unsigned char* datafileGetPalette()
|
||||
{
|
||||
return _pal;
|
||||
return gDatafilePalette;
|
||||
}
|
||||
|
||||
// NOTE: Unused.
|
||||
//
|
||||
// 0x42F0EC
|
||||
unsigned char* datafileLoad(char* path, int* sizePtr)
|
||||
{
|
||||
const char* mangledPath = gDatafileNameMangler(path);
|
||||
File* stream = fileOpen(mangledPath, "rb");
|
||||
if (stream == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int size = fileGetSize(stream);
|
||||
unsigned char* data = (unsigned char*)internal_malloc_safe(size, __FILE__, __LINE__); // "..\\int\\DATAFILE.C", 185
|
||||
if (data == NULL) {
|
||||
// NOTE: This code is unreachable, internal_malloc_safe never fails.
|
||||
// Otherwise it leaks stream.
|
||||
*sizePtr = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fileRead(data, 1, size, stream);
|
||||
fileClose(stream);
|
||||
*sizePtr = size;
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
#ifndef DATAFILE_H
|
||||
#define DATAFILE_H
|
||||
|
||||
extern unsigned char _pal[768];
|
||||
namespace fallout {
|
||||
|
||||
unsigned char* _datafileGetPalette();
|
||||
typedef unsigned char*(DatafileLoader)(char* path, unsigned char* palette, int* widthPtr, int* heightPtr);
|
||||
typedef char*(DatafileNameMangler)(char* path);
|
||||
|
||||
extern DatafileLoader* gDatafileLoader;
|
||||
extern DatafileNameMangler* gDatafileNameMangler;
|
||||
|
||||
extern unsigned char gDatafilePalette[768];
|
||||
|
||||
char* datafileDefaultNameManglerImpl(char* path);
|
||||
void datafileSetNameMangler(DatafileNameMangler* mangler);
|
||||
void datafileSetLoader(DatafileLoader* loader);
|
||||
void sub_42EE84(unsigned char* data, unsigned char* palette, int width, int height);
|
||||
void sub_42EEF8(unsigned char* data, unsigned char* palette, int width, int height);
|
||||
unsigned char* datafileReadRaw(char* path, int* widthPtr, int* heightPtr);
|
||||
unsigned char* datafileRead(char* path, int* widthPtr, int* heightPtr);
|
||||
unsigned char* sub_42EFF4(char* path);
|
||||
void sub_42F024(unsigned char* data, int* widthPtr, int* heightPtr);
|
||||
unsigned char* datafileGetPalette();
|
||||
unsigned char* datafileLoad(char* path, int* sizePtr);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* DATAFILE_H */
|
||||
|
||||
41
src/db.cc
@@ -1,12 +1,14 @@
|
||||
#include "db.h"
|
||||
|
||||
#include "platform_compat.h"
|
||||
#include "xfile.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform_compat.h"
|
||||
#include "xfile.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
typedef struct FileList {
|
||||
XList xlist;
|
||||
struct FileList* next;
|
||||
@@ -61,20 +63,22 @@ int dbOpen(const char* filePath1, int a2, const char* filePath2, int a4)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: This function simply returns 0, but it definitely accept one parameter
|
||||
// via eax, as seen at every call site. This value is ignored. It's impossible
|
||||
// to guess it's name.
|
||||
//
|
||||
// 0x4C5D54
|
||||
int _db_current(int a1)
|
||||
int _db_select(int dbHandle)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Uncollapsed 0x4C5D54.
|
||||
int _db_current()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 0x4C5D58
|
||||
bool _db_total()
|
||||
int _db_total()
|
||||
{
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 0x4C5D60
|
||||
@@ -638,7 +642,7 @@ int fileNameListInit(const char* pattern, char*** fileNameListPtr, int a3, int a
|
||||
}
|
||||
}
|
||||
|
||||
bool v1 = *pattern == '*';
|
||||
bool isWildcard = *pattern == '*';
|
||||
|
||||
for (int index = 0; index < fileNamesLength; index += 1) {
|
||||
const char* name = xlist->fileNames[index];
|
||||
@@ -647,22 +651,13 @@ int fileNameListInit(const char* pattern, char*** fileNameListPtr, int a3, int a
|
||||
char extension[COMPAT_MAX_EXT];
|
||||
compat_splitpath(name, NULL, dir, fileName, extension);
|
||||
|
||||
bool v2 = false;
|
||||
if (v1) {
|
||||
char* pch = dir;
|
||||
while (*pch != '\0' && *pch != '\\') {
|
||||
pch++;
|
||||
}
|
||||
v2 = *pch != '\0';
|
||||
}
|
||||
|
||||
if (!v2) {
|
||||
if (!isWildcard || *dir == '\0' || strchr(dir, '\\') == NULL) {
|
||||
// NOTE: Quick and dirty fix to buffer overflow. See RE to
|
||||
// understand the problem.
|
||||
char path[COMPAT_MAX_PATH];
|
||||
sprintf(path, "%s%s", fileName, extension);
|
||||
free(xlist->fileNames[length]);
|
||||
xlist->fileNames[length] = strdup(path);
|
||||
xlist->fileNames[length] = compat_strdup(path);
|
||||
length++;
|
||||
}
|
||||
}
|
||||
@@ -745,3 +740,5 @@ int _db_list_compare(const void* p1, const void* p2)
|
||||
{
|
||||
return compat_stricmp(*(const char**)p1, *(const char**)p2);
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
11
src/db.h
@@ -1,18 +1,21 @@
|
||||
#ifndef DB_H
|
||||
#define DB_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "memory_defs.h"
|
||||
#include "xfile.h"
|
||||
|
||||
#include <stddef.h>
|
||||
namespace fallout {
|
||||
|
||||
typedef XFile File;
|
||||
typedef void FileReadProgressHandler();
|
||||
typedef char* StrdupProc(const char* string);
|
||||
|
||||
int dbOpen(const char* filePath1, int a2, const char* filePath2, int a4);
|
||||
int _db_current(int a1);
|
||||
bool _db_total();
|
||||
int _db_select(int dbHandle);
|
||||
int _db_current();
|
||||
int _db_total();
|
||||
void dbExit();
|
||||
int dbGetFileSize(const char* filePath, int* sizePtr);
|
||||
int dbGetFileContents(const char* filePath, void* ptr);
|
||||
@@ -65,4 +68,6 @@ int fileGetSize(File* stream);
|
||||
void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size);
|
||||
void _db_enable_hash_table_();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* DB_H */
|
||||
|
||||
391
src/dbox.cc
@@ -1,5 +1,10 @@
|
||||
#include "dbox.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "art.h"
|
||||
#include "character_editor.h"
|
||||
#include "color.h"
|
||||
@@ -14,10 +19,7 @@
|
||||
#include "window_manager.h"
|
||||
#include "word_wrap.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
namespace fallout {
|
||||
|
||||
#define FILE_DIALOG_LINE_COUNT 12
|
||||
|
||||
@@ -193,81 +195,67 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
: DIALOG_TYPE_MEDIUM;
|
||||
}
|
||||
|
||||
CacheEntry* backgroundHandle;
|
||||
int backgroundWidth;
|
||||
int backgroundHeight;
|
||||
int fid = buildFid(6, gDialogBoxBackgroundFrmIds[dialogType], 0, 0, 0);
|
||||
unsigned char* background = artLockFrameDataReturningSize(fid, &backgroundHandle, &backgroundWidth, &backgroundHeight);
|
||||
if (background == NULL) {
|
||||
FrmImage backgroundFrmImage;
|
||||
int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, gDialogBoxBackgroundFrmIds[dialogType], 0, 0, 0);
|
||||
if (!backgroundFrmImage.lock(backgroundFid)) {
|
||||
fontSetCurrent(savedFont);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Maintain original position in original resolution, otherwise center it.
|
||||
if (screenGetWidth() != 640) x = (screenGetWidth() - backgroundWidth) / 2;
|
||||
if (screenGetHeight() != 480) y = (screenGetHeight() - backgroundHeight) / 2;
|
||||
int win = windowCreate(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);
|
||||
if (screenGetWidth() != 640) x = (screenGetWidth() - backgroundFrmImage.getWidth()) / 2;
|
||||
if (screenGetHeight() != 480) y = (screenGetHeight() - backgroundFrmImage.getHeight()) / 2;
|
||||
int win = windowCreate(x,
|
||||
y,
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getHeight(),
|
||||
256,
|
||||
WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);
|
||||
if (win == -1) {
|
||||
artUnlock(backgroundHandle);
|
||||
fontSetCurrent(savedFont);
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char* windowBuf = windowGetBuffer(win);
|
||||
memcpy(windowBuf, background, backgroundWidth * backgroundHeight);
|
||||
memcpy(windowBuf, backgroundFrmImage.getData(), backgroundFrmImage.getWidth() * backgroundFrmImage.getHeight());
|
||||
|
||||
CacheEntry* doneBoxHandle = NULL;
|
||||
unsigned char* doneBox = NULL;
|
||||
int doneBoxWidth;
|
||||
int doneBoxHeight;
|
||||
|
||||
CacheEntry* downButtonHandle = NULL;
|
||||
unsigned char* downButton = NULL;
|
||||
int downButtonWidth;
|
||||
int downButtonHeight;
|
||||
|
||||
CacheEntry* upButtonHandle = NULL;
|
||||
unsigned char* upButton = NULL;
|
||||
FrmImage doneBoxFrmImage;
|
||||
FrmImage buttonNormalFrmImage;
|
||||
FrmImage buttonPressedFrmImage;
|
||||
|
||||
if ((flags & DIALOG_BOX_0x20) == 0) {
|
||||
int doneBoxFid = buildFid(6, 209, 0, 0, 0);
|
||||
doneBox = artLockFrameDataReturningSize(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight);
|
||||
if (doneBox == NULL) {
|
||||
artUnlock(backgroundHandle);
|
||||
int doneBoxFid = buildFid(OBJ_TYPE_INTERFACE, 209, 0, 0, 0);
|
||||
if (!doneBoxFrmImage.lock(doneBoxFid)) {
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int downButtonFid = buildFid(6, 9, 0, 0, 0);
|
||||
downButton = artLockFrameDataReturningSize(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight);
|
||||
if (downButton == NULL) {
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
int pressedFid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);
|
||||
if (!buttonPressedFrmImage.lock(pressedFid)) {
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int upButtonFid = buildFid(6, 8, 0, 0, 0);
|
||||
upButton = artLockFrameData(upButtonFid, 0, 0, &upButtonHandle);
|
||||
if (upButton == NULL) {
|
||||
artUnlock(downButtonHandle);
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
int normalFid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);
|
||||
if (!buttonNormalFrmImage.lock(normalFid)) {
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int v27 = hasTwoButtons ? _doneX[dialogType] : (backgroundWidth - doneBoxWidth) / 2;
|
||||
blitBufferToBuffer(doneBox, doneBoxWidth, doneBoxHeight, doneBoxWidth, windowBuf + backgroundWidth * _doneY[dialogType] + v27, backgroundWidth);
|
||||
int v27 = hasTwoButtons
|
||||
? _doneX[dialogType]
|
||||
: (backgroundFrmImage.getWidth() - doneBoxFrmImage.getWidth()) / 2;
|
||||
blitBufferToBuffer(doneBoxFrmImage.getData(),
|
||||
doneBoxFrmImage.getWidth(),
|
||||
doneBoxFrmImage.getHeight(),
|
||||
doneBoxFrmImage.getWidth(),
|
||||
windowBuf + backgroundFrmImage.getWidth() * _doneY[dialogType] + v27,
|
||||
backgroundFrmImage.getWidth());
|
||||
|
||||
if (!messageListInit(&messageList)) {
|
||||
artUnlock(upButtonHandle);
|
||||
artUnlock(downButtonHandle);
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
@@ -277,10 +265,6 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
sprintf(path, "%s%s", asc_5186C8, "DBOX.MSG");
|
||||
|
||||
if (!messageListLoad(&messageList, path)) {
|
||||
artUnlock(upButtonHandle);
|
||||
artUnlock(downButtonHandle);
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
fontSetCurrent(savedFont);
|
||||
// FIXME: Window is not removed.
|
||||
return -1;
|
||||
@@ -292,10 +276,26 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
// 101 - YES
|
||||
messageListItem.num = (flags & DIALOG_BOX_YES_NO) == 0 ? 100 : 101;
|
||||
if (messageListGetItem(&messageList, &messageListItem)) {
|
||||
fontDrawText(windowBuf + backgroundWidth * (_doneY[dialogType] + 3) + v27 + 35, messageListItem.text, backgroundWidth, backgroundWidth, _colorTable[18979]);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * (_doneY[dialogType] + 3) + v27 + 35,
|
||||
messageListItem.text,
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
_colorTable[18979]);
|
||||
}
|
||||
|
||||
int btn = buttonCreate(win, v27 + 13, _doneY[dialogType] + 4, downButtonWidth, downButtonHeight, -1, -1, -1, 500, upButton, downButton, NULL, BUTTON_FLAG_TRANSPARENT);
|
||||
int btn = buttonCreate(win,
|
||||
v27 + 13,
|
||||
_doneY[dialogType] + 4,
|
||||
buttonPressedFrmImage.getWidth(),
|
||||
buttonPressedFrmImage.getHeight(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
500,
|
||||
buttonNormalFrmImage.getData(),
|
||||
buttonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (btn != -1) {
|
||||
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
}
|
||||
@@ -311,61 +311,58 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
|
||||
fontSetCurrent(103);
|
||||
|
||||
blitBufferToBufferTrans(doneBox,
|
||||
doneBoxWidth,
|
||||
doneBoxHeight,
|
||||
doneBoxWidth,
|
||||
windowBuf + backgroundWidth * _doneY[dialogType] + _doneX[dialogType] + doneBoxWidth + 24,
|
||||
backgroundWidth);
|
||||
blitBufferToBufferTrans(doneBoxFrmImage.getData(),
|
||||
doneBoxFrmImage.getWidth(),
|
||||
doneBoxFrmImage.getHeight(),
|
||||
doneBoxFrmImage.getWidth(),
|
||||
windowBuf + backgroundFrmImage.getWidth() * _doneY[dialogType] + _doneX[dialogType] + doneBoxFrmImage.getWidth() + 24,
|
||||
backgroundFrmImage.getWidth());
|
||||
|
||||
fontDrawText(windowBuf + backgroundWidth * (_doneY[dialogType] + 3) + _doneX[dialogType] + doneBoxWidth + 59,
|
||||
a8, backgroundWidth, backgroundWidth, _colorTable[18979]);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * (_doneY[dialogType] + 3) + _doneX[dialogType] + doneBoxFrmImage.getWidth() + 59,
|
||||
a8,
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
_colorTable[18979]);
|
||||
|
||||
int btn = buttonCreate(win,
|
||||
doneBoxWidth + _doneX[dialogType] + 37,
|
||||
doneBoxFrmImage.getWidth() + _doneX[dialogType] + 37,
|
||||
_doneY[dialogType] + 4,
|
||||
downButtonWidth,
|
||||
downButtonHeight,
|
||||
-1, -1, -1, 501, upButton, downButton, 0, BUTTON_FLAG_TRANSPARENT);
|
||||
buttonPressedFrmImage.getWidth(),
|
||||
buttonPressedFrmImage.getHeight(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
501,
|
||||
buttonNormalFrmImage.getData(),
|
||||
buttonPressedFrmImage.getData(),
|
||||
0,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (btn != -1) {
|
||||
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
|
||||
}
|
||||
} else {
|
||||
int doneBoxFid = buildFid(6, 209, 0, 0, 0);
|
||||
unsigned char* doneBox = artLockFrameDataReturningSize(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight);
|
||||
if (doneBox == NULL) {
|
||||
artUnlock(backgroundHandle);
|
||||
int doneBoxFid = buildFid(OBJ_TYPE_INTERFACE, 209, 0, 0, 0);
|
||||
if (!doneBoxFrmImage.lock(doneBoxFid)) {
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int downButtonFid = buildFid(6, 9, 0, 0, 0);
|
||||
unsigned char* downButton = artLockFrameDataReturningSize(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight);
|
||||
if (downButton == NULL) {
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
int pressedFid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);
|
||||
if (!buttonPressedFrmImage.lock(pressedFid)) {
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int upButtonFid = buildFid(6, 8, 0, 0, 0);
|
||||
unsigned char* upButton = artLockFrameData(upButtonFid, 0, 0, &upButtonHandle);
|
||||
if (upButton == NULL) {
|
||||
artUnlock(downButtonHandle);
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
int normalFid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);
|
||||
if (!buttonNormalFrmImage.lock(normalFid)) {
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!messageListInit(&messageList)) {
|
||||
artUnlock(upButtonHandle);
|
||||
artUnlock(downButtonHandle);
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
@@ -375,38 +372,34 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
sprintf(path, "%s%s", asc_5186C8, "DBOX.MSG");
|
||||
|
||||
if (!messageListLoad(&messageList, path)) {
|
||||
artUnlock(upButtonHandle);
|
||||
artUnlock(downButtonHandle);
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(backgroundHandle);
|
||||
fontSetCurrent(savedFont);
|
||||
windowDestroy(win);
|
||||
return -1;
|
||||
}
|
||||
|
||||
blitBufferToBufferTrans(doneBox,
|
||||
doneBoxWidth,
|
||||
doneBoxHeight,
|
||||
doneBoxWidth,
|
||||
windowBuf + backgroundWidth * _doneY[dialogType] + _doneX[dialogType],
|
||||
backgroundWidth);
|
||||
blitBufferToBufferTrans(doneBoxFrmImage.getData(),
|
||||
doneBoxFrmImage.getWidth(),
|
||||
doneBoxFrmImage.getHeight(),
|
||||
doneBoxFrmImage.getWidth(),
|
||||
windowBuf + backgroundFrmImage.getWidth() * _doneY[dialogType] + _doneX[dialogType],
|
||||
backgroundFrmImage.getWidth());
|
||||
|
||||
fontSetCurrent(103);
|
||||
|
||||
fontDrawText(windowBuf + backgroundWidth * (_doneY[dialogType] + 3) + _doneX[dialogType] + 35,
|
||||
a8, backgroundWidth, backgroundWidth, _colorTable[18979]);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * (_doneY[dialogType] + 3) + _doneX[dialogType] + 35,
|
||||
a8, backgroundFrmImage.getWidth(), backgroundFrmImage.getWidth(), _colorTable[18979]);
|
||||
|
||||
int btn = buttonCreate(win,
|
||||
_doneX[dialogType] + 13,
|
||||
_doneY[dialogType] + 4,
|
||||
downButtonWidth,
|
||||
downButtonHeight,
|
||||
buttonPressedFrmImage.getWidth(),
|
||||
buttonPressedFrmImage.getHeight(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
501,
|
||||
upButton,
|
||||
downButton,
|
||||
buttonNormalFrmImage.getData(),
|
||||
buttonPressedFrmImage.getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (btn != -1) {
|
||||
@@ -428,28 +421,44 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
|
||||
if (hasTitle) {
|
||||
if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) {
|
||||
fontDrawText(windowBuf + backgroundWidth * v23 + _xtable[dialogType], title, backgroundWidth, backgroundWidth, titleColor);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * v23 + _xtable[dialogType],
|
||||
title,
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
titleColor);
|
||||
} else {
|
||||
int length = fontGetStringWidth(title);
|
||||
fontDrawText(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, title, backgroundWidth, backgroundWidth, titleColor);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * v23 + (backgroundFrmImage.getWidth() - length) / 2,
|
||||
title,
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
titleColor);
|
||||
}
|
||||
v23 += fontGetLineHeight();
|
||||
}
|
||||
|
||||
for (int v94 = 0; v94 < bodyLength; v94++) {
|
||||
int len = fontGetStringWidth(body[v94]);
|
||||
if (len <= backgroundWidth - 26) {
|
||||
if (len <= backgroundFrmImage.getWidth() - 26) {
|
||||
if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) {
|
||||
fontDrawText(windowBuf + backgroundWidth * v23 + _xtable[dialogType], body[v94], backgroundWidth, backgroundWidth, bodyColor);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * v23 + _xtable[dialogType],
|
||||
body[v94],
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
bodyColor);
|
||||
} else {
|
||||
int length = fontGetStringWidth(body[v94]);
|
||||
fontDrawText(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, body[v94], backgroundWidth, backgroundWidth, bodyColor);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * v23 + (backgroundFrmImage.getWidth() - length) / 2,
|
||||
body[v94],
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
bodyColor);
|
||||
}
|
||||
v23 += fontGetLineHeight();
|
||||
} else {
|
||||
short beginnings[WORD_WRAP_MAX_COUNT];
|
||||
short count;
|
||||
if (wordWrap(body[v94], backgroundWidth - 26, beginnings, &count) != 0) {
|
||||
if (wordWrap(body[v94], backgroundFrmImage.getWidth() - 26, beginnings, &count) != 0) {
|
||||
debugPrint("\nError: dialog_out");
|
||||
}
|
||||
|
||||
@@ -464,10 +473,18 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
string[v51] = '\0';
|
||||
|
||||
if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) {
|
||||
fontDrawText(windowBuf + backgroundWidth * v23 + _xtable[dialogType], string, backgroundWidth, backgroundWidth, bodyColor);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * v23 + _xtable[dialogType],
|
||||
string,
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
bodyColor);
|
||||
} else {
|
||||
int length = fontGetStringWidth(string);
|
||||
fontDrawText(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, string, backgroundWidth, backgroundWidth, bodyColor);
|
||||
fontDrawText(windowBuf + backgroundFrmImage.getWidth() * v23 + (backgroundFrmImage.getWidth() - length) / 2,
|
||||
string,
|
||||
backgroundFrmImage.getWidth(),
|
||||
backgroundFrmImage.getWidth(),
|
||||
bodyColor);
|
||||
}
|
||||
v23 += fontGetLineHeight();
|
||||
}
|
||||
@@ -503,13 +520,9 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
}
|
||||
|
||||
windowDestroy(win);
|
||||
artUnlock(backgroundHandle);
|
||||
fontSetCurrent(savedFont);
|
||||
|
||||
if (v86) {
|
||||
artUnlock(doneBoxHandle);
|
||||
artUnlock(downButtonHandle);
|
||||
artUnlock(upButtonHandle);
|
||||
messageListFree(&messageList);
|
||||
}
|
||||
|
||||
@@ -517,7 +530,7 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
|
||||
}
|
||||
|
||||
// 0x41DE90
|
||||
int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLength, int x, int y, int flags)
|
||||
int showLoadFileDialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags)
|
||||
{
|
||||
int oldFont = fontGetCurrent();
|
||||
|
||||
@@ -536,49 +549,34 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char* frmBuffers[FILE_DIALOG_FRM_COUNT];
|
||||
CacheEntry* frmHandles[FILE_DIALOG_FRM_COUNT];
|
||||
Size frmSizes[FILE_DIALOG_FRM_COUNT];
|
||||
FrmImage frmImages[FILE_DIALOG_FRM_COUNT];
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
int fid = buildFid(6, gLoadFileDialogFrmIds[index], 0, 0, 0);
|
||||
frmBuffers[index] = artLockFrameDataReturningSize(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height));
|
||||
if (frmBuffers[index] == NULL) {
|
||||
while (--index >= 0) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
int fid = buildFid(OBJ_TYPE_INTERFACE, gLoadFileDialogFrmIds[index], 0, 0, 0);
|
||||
if (!frmImages[index].lock(fid)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int backgroundWidth = frmSizes[FILE_DIALOG_FRM_BACKGROUND].width;
|
||||
int backgroundHeight = frmSizes[FILE_DIALOG_FRM_BACKGROUND].height;
|
||||
int backgroundWidth = frmImages[FILE_DIALOG_FRM_BACKGROUND].getWidth();
|
||||
int backgroundHeight = frmImages[FILE_DIALOG_FRM_BACKGROUND].getHeight();
|
||||
|
||||
// Maintain original position in original resolution, otherwise center it.
|
||||
if (screenGetWidth() != 640) x = (screenGetWidth() - backgroundWidth) / 2;
|
||||
if (screenGetHeight() != 480) y = (screenGetHeight() - backgroundHeight) / 2;
|
||||
int win = windowCreate(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);
|
||||
if (win == -1) {
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char* windowBuffer = windowGetBuffer(win);
|
||||
memcpy(windowBuffer, frmBuffers[FILE_DIALOG_FRM_BACKGROUND], backgroundWidth * backgroundHeight);
|
||||
memcpy(windowBuffer, frmImages[FILE_DIALOG_FRM_BACKGROUND].getData(), backgroundWidth * backgroundHeight);
|
||||
|
||||
MessageList messageList;
|
||||
MessageListItem messageListItem;
|
||||
|
||||
if (!messageListInit(&messageList)) {
|
||||
windowDestroy(win);
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -587,11 +585,6 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
|
||||
if (!messageListLoad(&messageList, path)) {
|
||||
windowDestroy(win);
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -608,14 +601,14 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
int doneBtn = buttonCreate(win,
|
||||
LOAD_FILE_DIALOG_DONE_BUTTON_X,
|
||||
LOAD_FILE_DIALOG_DONE_BUTTON_Y,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getHeight(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
500,
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (doneBtn != -1) {
|
||||
@@ -625,14 +618,14 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
int cancelBtn = buttonCreate(win,
|
||||
LOAD_FILE_DIALOG_CANCEL_BUTTON_X,
|
||||
LOAD_FILE_DIALOG_CANCEL_BUTTON_Y,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getHeight(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
501,
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (cancelBtn != -1) {
|
||||
@@ -642,14 +635,14 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
int scrollUpBtn = buttonCreate(win,
|
||||
FILE_DIALOG_SCROLL_BUTTON_X,
|
||||
FILE_DIALOG_SCROLL_BUTTON_Y,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getHeight(),
|
||||
-1,
|
||||
505,
|
||||
506,
|
||||
505,
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (scrollUpBtn != -1) {
|
||||
@@ -658,15 +651,15 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
|
||||
int scrollDownButton = buttonCreate(win,
|
||||
FILE_DIALOG_SCROLL_BUTTON_X,
|
||||
FILE_DIALOG_SCROLL_BUTTON_Y + frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].height,
|
||||
FILE_DIALOG_SCROLL_BUTTON_Y + frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getHeight(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].getHeight(),
|
||||
-1,
|
||||
503,
|
||||
504,
|
||||
503,
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (scrollUpBtn != -1) {
|
||||
@@ -708,6 +701,8 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
int scrollCounter = 0;
|
||||
bool isScrolling = false;
|
||||
|
||||
convertMouseWheelToArrowKey(&keyCode);
|
||||
|
||||
if (keyCode == 500) {
|
||||
if (fileListLength != 0) {
|
||||
strncpy(dest, fileList[selectedFileIndex + pageOffset], 16);
|
||||
@@ -887,10 +882,6 @@ int showLoadFileDialog(char *title, char** fileList, char* dest, int fileListLen
|
||||
|
||||
windowDestroy(win);
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
|
||||
messageListFree(&messageList);
|
||||
fontSetCurrent(oldFont);
|
||||
|
||||
@@ -917,48 +908,34 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char* frmBuffers[FILE_DIALOG_FRM_COUNT];
|
||||
CacheEntry* frmHandles[FILE_DIALOG_FRM_COUNT];
|
||||
Size frmSizes[FILE_DIALOG_FRM_COUNT];
|
||||
FrmImage frmImages[FILE_DIALOG_FRM_COUNT];
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
int fid = buildFid(6, gSaveFileDialogFrmIds[index], 0, 0, 0);
|
||||
frmBuffers[index] = artLockFrameDataReturningSize(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height));
|
||||
if (frmBuffers[index] == NULL) {
|
||||
while (--index >= 0) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
int fid = buildFid(OBJ_TYPE_INTERFACE, gSaveFileDialogFrmIds[index], 0, 0, 0);
|
||||
if (!frmImages[index].lock(fid)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int backgroundWidth = frmSizes[FILE_DIALOG_FRM_BACKGROUND].width;
|
||||
int backgroundHeight = frmSizes[FILE_DIALOG_FRM_BACKGROUND].height;
|
||||
int backgroundWidth = frmImages[FILE_DIALOG_FRM_BACKGROUND].getWidth();
|
||||
int backgroundHeight = frmImages[FILE_DIALOG_FRM_BACKGROUND].getHeight();
|
||||
|
||||
// Maintain original position in original resolution, otherwise center it.
|
||||
if (screenGetWidth() != 640) x = (screenGetWidth() - backgroundWidth) / 2;
|
||||
if (screenGetHeight() != 480) y = (screenGetHeight() - backgroundHeight) / 2;
|
||||
int win = windowCreate(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);
|
||||
if (win == -1) {
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char* windowBuffer = windowGetBuffer(win);
|
||||
memcpy(windowBuffer, frmBuffers[FILE_DIALOG_FRM_BACKGROUND], backgroundWidth * backgroundHeight);
|
||||
memcpy(windowBuffer, frmImages[FILE_DIALOG_FRM_BACKGROUND].getData(), backgroundWidth * backgroundHeight);
|
||||
|
||||
MessageList messageList;
|
||||
MessageListItem messageListItem;
|
||||
|
||||
if (!messageListInit(&messageList)) {
|
||||
windowDestroy(win);
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -967,11 +944,6 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
|
||||
if (!messageListLoad(&messageList, path)) {
|
||||
windowDestroy(win);
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -988,14 +960,14 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
int doneBtn = buttonCreate(win,
|
||||
SAVE_FILE_DIALOG_DONE_BUTTON_X,
|
||||
SAVE_FILE_DIALOG_DONE_BUTTON_Y,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getHeight(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
500,
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (doneBtn != -1) {
|
||||
@@ -1005,14 +977,14 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
int cancelBtn = buttonCreate(win,
|
||||
SAVE_FILE_DIALOG_CANCEL_BUTTON_X,
|
||||
SAVE_FILE_DIALOG_CANCEL_BUTTON_Y,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getHeight(),
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
501,
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (cancelBtn != -1) {
|
||||
@@ -1022,14 +994,14 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
int scrollUpBtn = buttonCreate(win,
|
||||
FILE_DIALOG_SCROLL_BUTTON_X,
|
||||
FILE_DIALOG_SCROLL_BUTTON_Y,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getHeight(),
|
||||
-1,
|
||||
505,
|
||||
506,
|
||||
505,
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (scrollUpBtn != -1) {
|
||||
@@ -1038,15 +1010,15 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
|
||||
int scrollDownButton = buttonCreate(win,
|
||||
FILE_DIALOG_SCROLL_BUTTON_X,
|
||||
FILE_DIALOG_SCROLL_BUTTON_Y + frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].width,
|
||||
frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].height,
|
||||
FILE_DIALOG_SCROLL_BUTTON_Y + frmImages[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].getHeight(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].getWidth(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].getHeight(),
|
||||
-1,
|
||||
503,
|
||||
504,
|
||||
503,
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL],
|
||||
frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED],
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL].getData(),
|
||||
frmImages[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].getData(),
|
||||
NULL,
|
||||
BUTTON_FLAG_TRANSPARENT);
|
||||
if (scrollUpBtn != -1) {
|
||||
@@ -1090,8 +1062,8 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
|
||||
char fileNameCopy[32];
|
||||
strncpy(fileNameCopy, dest, 32);
|
||||
|
||||
int fileNameCopyLength = strlen(fileNameCopy);
|
||||
|
||||
size_t fileNameCopyLength = strlen(fileNameCopy);
|
||||
fileNameCopy[fileNameCopyLength + 1] = '\0';
|
||||
fileNameCopy[fileNameCopyLength] = ' ';
|
||||
|
||||
@@ -1116,6 +1088,8 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
int scrollCounter = 0;
|
||||
bool isScrolling = false;
|
||||
|
||||
convertMouseWheelToArrowKey(&keyCode);
|
||||
|
||||
if (keyCode == 500) {
|
||||
rc = 0;
|
||||
} else if (keyCode == KEY_RETURN) {
|
||||
@@ -1371,11 +1345,6 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
|
||||
}
|
||||
|
||||
windowDestroy(win);
|
||||
|
||||
for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {
|
||||
artUnlock(frmHandles[index]);
|
||||
}
|
||||
|
||||
messageListFree(&messageList);
|
||||
fontSetCurrent(oldFont);
|
||||
|
||||
@@ -1400,3 +1369,5 @@ static void fileDialogRenderFileList(unsigned char* buffer, char** fileList, int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||