Compare commits
260 Commits
0.99.0-bet
...
master
Author | SHA1 | Date |
---|---|---|
Laura K | b572fdfd47 | |
Laura K | 94b9f930ce | |
莯凛 | 01ec93dd27 | |
biroder | ca5361cc58 | |
poly000 | 08f086bfc4 | |
poly000 | 1f288e2a39 | |
poly000 | 0e0cd66564 | |
József Sallai | 9c95b20f5c | |
Alula | 7630a9b60e | |
Alula | ae909878c4 | |
Alula | 7785513e8b | |
Alula | 7f33f7b6c8 | |
József Sallai | 6c9b4d9a54 | |
Edward Stuckey | af9947b931 | |
Laura K | b275816e76 | |
Laura K | 8e3ccea8a1 | |
biroder | 76ec4771b3 | |
biroder | 3b77cdf0c5 | |
biroder | 397cd8f584 | |
biroder | 21c255efb4 | |
Sobakin | e1fb118910 | |
Laura K | c56bd2e8ae | |
periwinkle | e09fbf5c63 | |
biroder | c4cffd54a8 | |
biroder | 99f13a746e | |
biroder | 690c97e44d | |
biroder | 311ca8b12f | |
biroder | 152e31966a | |
biroder | b4ede5bcad | |
biroder | a5f49c07e4 | |
biroder | fc66b84d8f | |
biroder | 87cc7f12e3 | |
biroder | e9cf548cc2 | |
biroder | 3af1d61e5b | |
Edward Stuckey | 3b14adf949 | |
biroder | 46710dd31a | |
biroder | 10e4d3efff | |
biroder | 2b1411787b | |
biroder | 0e33bcaaf9 | |
biroder | 45443dfa23 | |
biroder | 06ae269b7b | |
periwinkle | 12a305becb | |
periwinkle | bd203cfddb | |
periwinkle | c2ad3dd643 | |
biroder | 3468bcf5fd | |
biroder | 21221d80e7 | |
József Sallai | a45c630116 | |
biroder | 73295a6351 | |
biroder | ab87862646 | |
József Sallai | 5f24ee52b0 | |
biroder | 02e1763e1d | |
biroder | 44c6af2146 | |
periwinkle | d1d008edda | |
periwinkle | 4d7dfd0266 | |
biroder | b70f0007b1 | |
biroder | f7148edd96 | |
biroder | 425a26b3a0 | |
periwinkle | f6caffd624 | |
biroder | b294f65656 | |
karnak | a5ed1a370e | |
karnak | f07228895c | |
periwinkle | b3007c10e3 | |
periwinkle | 7caff84e04 | |
Alula | dcdb4e9d39 | |
biroder | 596c7b8aff | |
biroder | ed2c5f510a | |
biroder | 0ba5aad8af | |
biroder | 50ff888141 | |
biroder | 72e74e2113 | |
biroder | 47a43467d0 | |
biroder | 19e0da519f | |
biroder | 721a0c907a | |
biroder | 2a3341e3c5 | |
biroder | 63f903104a | |
biroder | 277f5d957b | |
biroder | 9409bb35fe | |
biroder | 9e09d56b76 | |
biroder | 40767c021b | |
biroder | 0e164a44df | |
biroder | 38338ae551 | |
biroder | dbb72ff6c1 | |
biroder | 0a45332f3f | |
biroder | aa27680baf | |
biroder | 67fb32499f | |
biroder | 5821f06928 | |
biroder | 5f93658f0f | |
biroder | cd671cce48 | |
biroder | 2a162d948f | |
biroder | 72a2ada0b2 | |
biroder | 0ccbbefbed | |
biroder | bc91d9f366 | |
Alula | beb290dafb | |
biroder | 5f7bae8f4f | |
biroder | 11eafb8d03 | |
biroder | f99b452073 | |
Alula | 51834be404 | |
Alula | b2fb548584 | |
biroder | 1633eef3dd | |
biroder | e57c9cdb27 | |
biroder | 07b1550cf4 | |
biroder | a15fd4d190 | |
biroder | b8af9aed25 | |
biroder | 8992889e94 | |
biroder | f91e793062 | |
biroder | 3fbe94ecd1 | |
biroder | 5bd0dcd564 | |
József Sallai | 41b840d13f | |
József Sallai | 95e09ded99 | |
József Sallai | 6bccf59f5b | |
József Sallai | 890c0596ed | |
József Sallai | b22ca8b35e | |
József Sallai | e77241cd56 | |
József Sallai | 3dbe56690a | |
József Sallai | 1810bf6d5b | |
József Sallai | d6715bccea | |
alula | 30c85c2f8b | |
biroder | 979909faa8 | |
Alula | 90df8faa7a | |
Daedliy | 6d3c127912 | |
Alula | 515a0a7fe7 | |
Alula | 7fe96780de | |
Alula | f1e5d11b2a | |
Alula | 3739e9f170 | |
József Sallai | 3c79582fed | |
József Sallai | a187942cff | |
József Sallai | 4ae085f7e2 | |
József Sallai | 153b1b4962 | |
Alula | 2774eae66f | |
Alula | 4928ce4682 | |
Alula | 64290ae5a3 | |
Alula | 1457aa3caa | |
Alula | ee58c1c8af | |
Alula | a5bb130e73 | |
Alula | bbd20a003b | |
Alula | ef1c2a5930 | |
Alula | 074af609bc | |
Alula | efa8a47b8d | |
Alula | bc3616d073 | |
Devon W | 510442490e | |
József Sallai | 0a7fd4dc47 | |
József Sallai | 4fa7069e82 | |
József Sallai | 212d7b915b | |
József Sallai | cb95db1e89 | |
József Sallai | 334e64f499 | |
József Sallai | 90e58649a7 | |
Alula | e845c87738 | |
alula | 2cb36de715 | |
alula | c2a8bf52e9 | |
József Sallai | e975a75ec4 | |
Alula | 5854735392 | |
József Sallai | 356f4230b5 | |
József Sallai | 4be3dd518b | |
József Sallai | 5ed2d40e23 | |
alula | a246d1a2f9 | |
Alula | e567dd6296 | |
Alula | 066389a29f | |
Alula | 67979a03ea | |
Alula | f91513edd2 | |
Alula | 5d92cafe67 | |
Alula | d87bbf2b46 | |
Alula | 2860938b9a | |
Alula | e74b586dd1 | |
alula | 0fca898c54 | |
Alula | df467e8764 | |
Alula | 17e1156850 | |
Alula | 9fd04ed47a | |
Alula | c9e6dd7181 | |
dawnDus | 0330cf3b2b | |
dawnDus | 2be1c422d6 | |
Awesomegamer6566 | 0f79474aae | |
dawnDus | 713e704b9b | |
dawnDus | d42632f973 | |
dawnDus | 57b2be5211 | |
Alula | 3a756e0ac4 | |
József Sallai | 6607d2fc15 | |
József Sallai | 8ebe210105 | |
József Sallai | f0949f49cf | |
dawnDus | 2b9a0198cb | |
József Sallai | 8684dd8448 | |
Awesomegamer6566 | 72c268647f | |
Daedliy | e9d2099f42 | |
Sallai József | f7d635a3d7 | |
Sallai József | 029d6d52e4 | |
Sallai József | dec913dd65 | |
Sallai József | 028c60157d | |
dawnDus | 3d86995feb | |
Sallai József | 670e6891c1 | |
dawnDus | a25dc297ef | |
Sallai József | 4ed7ba66b8 | |
Sallai József | b1b3b131e2 | |
Sallai József | f1b3c680c9 | |
Sallai József | ca1fa7b7c0 | |
dawnDus | 3cc9d75681 | |
dawnDus | 92bc887663 | |
Sallai József | 59da01b7b9 | |
Sallai József | e07207b40c | |
Daedliy | 1688f4bcbc | |
Sallai József | 8c70e1a13d | |
Sallai József | b1d578c0b4 | |
Sallai József | 290068dd37 | |
Sallai József | 7b359ae4c1 | |
Sallai József | 56631201c8 | |
dawnDus | 81755d4ad9 | |
Sallai József | f1542246c6 | |
Sallai József | b29c375a7a | |
Sallai József | bfd9c8c343 | |
Sallai József | 1883045f75 | |
Sallai József | 4cfbcc50ac | |
Sallai József | f74ec19cb5 | |
Sallai József | c68fedaa50 | |
Sallai József | 8a4201f381 | |
Sallai József | ffaf12cca8 | |
Sallai József | 03e9c9db0c | |
dawnDus | c9ba05c948 | |
Sallai József | 914555eac0 | |
Sallai József | fee63f2600 | |
alula | 2a792db797 | |
Sallai József | a598f31716 | |
Sallai József | ef040a393c | |
Sallai József | 4a6b2c4400 | |
Sallai József | 398f610c09 | |
Sallai József | fb4ac0dae8 | |
Sallai József | 0415f917f8 | |
Sallai József | 6f95e6109c | |
dawnDus | cdd3a37754 | |
József Sallai | f00f35ca2c | |
Sallai József | eee0f9eff9 | |
Sallai József | 84d9dbf877 | |
Sallai József | 9932b1209f | |
dawnDus | 6d8e58090a | |
Sallai József | 93e567981e | |
József Sallai | 7693a4ff20 | |
Daedliy | 86f89e7522 | |
Sallai József | 3297b151ad | |
Sallai József | aaecafcb72 | |
Sallai József | 444539405a | |
Sallai József | 2177382b5a | |
Sallai József | a1d0f2dc63 | |
Sallai József | 75b077c772 | |
Sallai József | d8636bc693 | |
József Sallai | 9d7c63571d | |
Sallai József | 3faf99b535 | |
Sallai József | 1440a91fba | |
dawnDus | 6d08eb716e | |
dawnDus | 69fdc7d3d2 | |
IruzzArcana | 18a7670248 | |
József Sallai | d7face2544 | |
dawnDus | 987c857b1c | |
dawnDus | 3f8c66db0f | |
dawnDus | dc2476c9dd | |
dawnDus | 8a2e9fa569 | |
dawndus | 588b0d53dd | |
dawnDus | 7c07986b5d | |
dawnDus | 02a9cac305 | |
dawnDus | af39130fed | |
dawnDus | 2d2e712eab | |
dawnDus | acad65d233 | |
Alula | 24762a1c45 | |
dawnDus | 75f5e9f364 | |
dawnDus | 7e793e09a8 |
|
@ -1,8 +1,12 @@
|
|||
version: "0.99.0.{build}-{branch}"
|
||||
version: "0.101.0-{build}-{branch}"
|
||||
|
||||
skip_commits:
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- app/
|
||||
- drsandroid/
|
||||
- drshorizon/
|
||||
|
||||
environment:
|
||||
global:
|
||||
|
@ -11,12 +15,15 @@ environment:
|
|||
- channel: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
target_name: win64
|
||||
arch_name: x86_64
|
||||
job_name: windows-x64
|
||||
appveyor_build_worker_image: Visual Studio 2019
|
||||
# - channel: stable
|
||||
# target: i686-pc-windows-msvc
|
||||
# target_name: win32
|
||||
# job_name: windows-x32
|
||||
- channel: stable
|
||||
target: i686-pc-windows-msvc
|
||||
target_name: win32
|
||||
arch_name: i686
|
||||
job_name: windows-x32
|
||||
appveyor_build_worker_image: Visual Studio 2019
|
||||
- channel: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
target_name: linux
|
||||
|
@ -52,25 +59,23 @@ for:
|
|||
- cargo -vV
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cache -> Cargo.toml'
|
||||
- '%USERPROFILE%\.cargo\bin -> Cargo.toml'
|
||||
- '%USERPROFILE%\.cargo\registry\index -> Cargo.toml'
|
||||
- '%USERPROFILE%\.cargo\registry\cache -> Cargo.toml'
|
||||
- '%USERPROFILE%\.cargo\git\db -> Cargo.toml'
|
||||
- '%USERPROFILE%\.rustup -> Cargo.toml'
|
||||
- 'target -> Cargo.toml'
|
||||
- '%USERPROFILE%\.cache'
|
||||
- '%USERPROFILE%\.cargo\bin'
|
||||
- '%USERPROFILE%\.cargo\registry\index'
|
||||
- '%USERPROFILE%\.cargo\registry\cache'
|
||||
- '%USERPROFILE%\.cargo\git\db'
|
||||
- '%USERPROFILE%\.rustup'
|
||||
- 'target'
|
||||
|
||||
build_script:
|
||||
- set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION%
|
||||
#- set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION%
|
||||
- if "%APPVEYOR_REPO_TAG%" == "true" (set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_REPO_TAG_NAME%) else (set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION%)
|
||||
- set CARGO_INCREMENTAL=1
|
||||
- cargo build --release --bin doukutsu-rs
|
||||
- mkdir release
|
||||
- copy LICENSE release\LICENSE
|
||||
- copy target\release\doukutsu-rs.exe release\doukutsu-rs.x86_64.exe
|
||||
- copy target\release\doukutsu-rs.exe release\doukutsu-rs.%arch_name%.exe
|
||||
- cd release
|
||||
- appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip
|
||||
- 7z x ../game-data.zip
|
||||
- rename game-data-master data
|
||||
- 7z a ../doukutsu-rs_%target_name%.zip *
|
||||
- appveyor PushArtifact ../doukutsu-rs_%target_name%.zip
|
||||
|
||||
|
@ -78,7 +83,14 @@ for:
|
|||
matrix:
|
||||
only:
|
||||
- appveyor_build_worker_image: macos-monterey
|
||||
|
||||
|
||||
init:
|
||||
- ps: |
|
||||
if ($env:APPVEYOR_REPO_TAG -eq "true")
|
||||
{
|
||||
Update-AppveyorBuild -Version "$env:APPVEYOR_REPO_TAG_NAME"
|
||||
}
|
||||
|
||||
install:
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -yv --default-toolchain $channel
|
||||
- export PATH=$PATH:$HOME/.cargo/bin
|
||||
|
@ -90,19 +102,17 @@ for:
|
|||
- cargo install cargo-bundle --force
|
||||
|
||||
cache:
|
||||
- '$HOME/.cache -> Cargo.toml'
|
||||
- '$HOME/.cargo/bin -> Cargo.toml'
|
||||
- '$HOME/.cargo/registry/index -> Cargo.toml'
|
||||
- '$HOME/.cargo/registry/cache -> Cargo.toml'
|
||||
- '$HOME/.cargo/git/db -> Cargo.toml'
|
||||
- '$HOME/.rustup -> Cargo.toml'
|
||||
- 'target -> Cargo.toml'
|
||||
- '$HOME/.cache'
|
||||
- '$HOME/.cargo/bin'
|
||||
- '$HOME/.cargo/registry/index'
|
||||
- '$HOME/.cargo/registry/cache'
|
||||
- '$HOME/.cargo/git/db'
|
||||
- '$HOME/.rustup'
|
||||
- 'target'
|
||||
|
||||
build_script:
|
||||
- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
- appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip
|
||||
- 7z x ../game-data.zip
|
||||
- mv game-data-master data
|
||||
#- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
- if [ "$APPVEYOR_REPO_TAG" = "true" ]; then export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_REPO_TAG_NAME; else export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION; fi
|
||||
- CARGO_INCREMENTAL=1 cargo bundle --release --target $target
|
||||
- mkdir release
|
||||
- cp LICENSE ./release/LICENSE
|
||||
|
@ -127,23 +137,22 @@ for:
|
|||
- cargo -vV
|
||||
|
||||
cache:
|
||||
- '$HOME/.cache -> Cargo.toml'
|
||||
- '$HOME/.cargo/bin -> Cargo.toml'
|
||||
- '$HOME/.cargo/registry/index -> Cargo.toml'
|
||||
- '$HOME/.cargo/registry/cache -> Cargo.toml'
|
||||
- '$HOME/.cargo/git/db -> Cargo.toml'
|
||||
- '$HOME/.rustup -> Cargo.toml'
|
||||
- 'target -> Cargo.toml'
|
||||
- '$HOME/.cache'
|
||||
- '$HOME/.cargo/bin'
|
||||
- '$HOME/.cargo/registry/index'
|
||||
- '$HOME/.cargo/registry/cache'
|
||||
- '$HOME/.cargo/git/db'
|
||||
- '$HOME/.rustup'
|
||||
- 'target'
|
||||
|
||||
build_script:
|
||||
- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
#- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
- if [ "$APPVEYOR_REPO_TAG" = "true" ]; then export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_REPO_TAG_NAME; else export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION; fi
|
||||
- RUSTFLAGS="-C link-arg=-s" CARGO_INCREMENTAL=1 cargo build --release --bin doukutsu-rs
|
||||
- mkdir release
|
||||
- cp LICENSE ./release/LICENSE
|
||||
- cp -a target/release/doukutsu-rs ./release/doukutsu-rs.x86_64.elf
|
||||
- cd release
|
||||
- appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip
|
||||
- 7z x ../game-data.zip
|
||||
- mv game-data-master data
|
||||
- 7z a ../doukutsu-rs_$target_name.zip *
|
||||
- appveyor PushArtifact ../doukutsu-rs_$target_name.zip
|
||||
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- cpp-rewrite
|
||||
- horizon-os
|
||||
paths-ignore:
|
||||
- '.gitignore'
|
||||
- '.github/*'
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- 'drshorizon/**'
|
||||
- 'res/**'
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
VERSION: "0.101.0"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux x86_64
|
||||
os: ubuntu-latest
|
||||
channel: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
target_name: linux-x64
|
||||
arch_name: x86_64
|
||||
- name: Windows x64
|
||||
os: windows-latest
|
||||
channel: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
target_name: windows-x64
|
||||
arch_name: x86_64
|
||||
- name: Windows x32
|
||||
os: windows-latest
|
||||
channel: stable
|
||||
target: i686-pc-windows-msvc
|
||||
target_name: windows-x32
|
||||
arch_name: i686
|
||||
- name: macOS x64 (Intel Macs)
|
||||
os: macos-latest
|
||||
channel: stable
|
||||
target: x86_64-apple-darwin
|
||||
target_name: mac-x64
|
||||
- name: macOS ARM64 (M1 Macs)
|
||||
os: macos-latest
|
||||
channel: stable
|
||||
target: aarch64-apple-darwin
|
||||
target_name: mac-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: sudo apt install libasound2-dev libudev-dev libgl1-mesa-dev
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
target
|
||||
key: ${{ matrix.target_name }}-cargo
|
||||
|
||||
- name: Setup rust toolchain
|
||||
run: |
|
||||
rustup default ${{ matrix.channel }}
|
||||
rustup target add ${{ matrix.target }}
|
||||
|
||||
rustc -vV
|
||||
cargo -vV
|
||||
|
||||
if [ "${{ runner.os }}" == "macOS" ]; then
|
||||
cargo install cargo-bundle
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="{{ github.ref_name }}"
|
||||
elif [ "${{ github.ref_name }}" == "master"]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-$((${{ github.run_number }} + 654))"
|
||||
else
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-${GITHUB_SHA:0:7}"
|
||||
fi
|
||||
|
||||
mkdir release
|
||||
cp LICENSE release
|
||||
|
||||
if [ "${{ runner.os }}" == "macOS" ]; then
|
||||
CARGO_INCREMENTAL=1 cargo bundle --release --target ${{ matrix.target }}
|
||||
cp -a ./target/${{ matrix.target }}/release/bundle/osx/doukutsu-rs.app release/doukutsu-rs.app
|
||||
codesign -s - -f ./release/doukutsu-rs.app/Contents/MacOS/doukutsu-rs
|
||||
elif [ "${{ runner.os }}" == "Windows" ]; then
|
||||
CARGO_INCREMENTAL=1 cargo build --release --bin doukutsu-rs --target ${{ matrix.target }}
|
||||
cp ./target/${{ matrix.target }}/release/doukutsu-rs.exe release/doukutsu-rs.${{ matrix.arch_name }}.exe
|
||||
elif [ "${{ runner.os }}" == "Linux" ]; then
|
||||
RUSTFLAGS="-C link-args=-s" CARGO_INCREMENTAL=1 cargo build --release --bin doukutsu-rs
|
||||
cp -a ./target/release/doukutsu-rs release/doukutsu-rs.${{ matrix.arch_name }}.elf
|
||||
fi
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: doukutsu-rs_${{ matrix.target_name }}
|
||||
path: ./release/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Save cache
|
||||
if: ${{ github.ref_name == 'master' || github.ref_type == 'tag' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
target
|
||||
key: ${{ matrix.target_name }}-cargo
|
||||
|
||||
build_android:
|
||||
name: Android build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
APP_OUTPUTS_DIR: "app/app/build/outputs/apk/release"
|
||||
strategy:
|
||||
fail-fast: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
~/.gradle
|
||||
app/app/.cxx
|
||||
app/app/build
|
||||
drsandroid/target
|
||||
key: android-cargo
|
||||
|
||||
- name: Setup rust toolchain
|
||||
run: |
|
||||
rustup default stable
|
||||
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android
|
||||
rustc -vV
|
||||
cargo -vV
|
||||
cargo install cargo-ndk
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="{{ github.ref_name }}"
|
||||
elif [ "${{ github.ref_name }}" == "master"]; then
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-$((${{ github.run_number }} + 654))"
|
||||
else
|
||||
export DRS_BUILD_VERSION_OVERRIDE="${{ env.VERSION }}-${GITHUB_SHA:0:7}"
|
||||
fi
|
||||
|
||||
cd app
|
||||
chmod +x ./gradlew
|
||||
./gradlew assembleRelease
|
||||
|
||||
- name: Sign app
|
||||
run: |
|
||||
BUILD_TOOLS=$ANDROID_HOME/build-tools/33.0.0
|
||||
|
||||
echo "${{ secrets.ANDROID_SIGNING_KEYSTORE }}" | base64 --decode > keystore.jks
|
||||
if [ "${{ secrets.ANDROID_SIGNING_KEY_PASS }}" != "" ]; then
|
||||
$BUILD_TOOLS/apksigner sign --ks ./keystore.jks --ks-key-alias "${{ secrets.ANDROID_SIGNING_ALIAS }}" --ks-pass "pass:${{ secrets.ANDROID_SIGNING_KEYSTORE_PASS }}" --key-pass "pass:${{ secrets.ANDROID_SIGNING_KEY_PASS }}" --out $APP_OUTPUTS_DIR/app-signed.apk $APP_OUTPUTS_DIR/app-release-unsigned.apk
|
||||
else
|
||||
$BUILD_TOOLS/apksigner sign --ks ./keystore.jks --ks-key-alias "${{ secrets.ANDROID_SIGNING_ALIAS }}" --ks-pass "pass:${{ secrets.ANDROID_SIGNING_KEYSTORE_PASS }}" --out $APP_OUTPUTS_DIR/app-signed.apk $APP_OUTPUTS_DIR/app-release-unsigned.apk
|
||||
fi
|
||||
|
||||
rm keystore.jks
|
||||
|
||||
- name: Prepare artifact
|
||||
run: |
|
||||
mkdir release
|
||||
mv $APP_OUTPUTS_DIR/app-signed.apk release/doukutsu-rs.apk
|
||||
cp LICENSE ./release
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: doukutsu-rs_android
|
||||
path: ./release/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Save cache
|
||||
if: ${{ github.ref_name == 'master' || github.ref_type == 'tag' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
~/.cargo
|
||||
~/.rustup
|
||||
~/.gradle
|
||||
app/app/.cxx
|
||||
app/app/build
|
||||
drsandroid/target
|
||||
key: android-cargo
|
||||
|
||||
update_metadata:
|
||||
name: Update metadata
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref_type != 'tag' && always() }}
|
||||
needs: [build, build_android]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: doukutsu-rs/metadata
|
||||
token: ${{ secrets.METADATA_USER_TOKEN }}
|
||||
|
||||
- name: Update metadata
|
||||
id: metadata
|
||||
run: |
|
||||
export FILE="./metadata/nightly.json"
|
||||
if [ "${{ github.ref_name }}" == "master" ]; then
|
||||
export VERSION="${{ env.VERSION }}-$((${{ github.run_number }} + 654))"
|
||||
else
|
||||
export VERSION="${{ env.VERSION }}-${GITHUB_SHA:0:7}"
|
||||
fi
|
||||
|
||||
if [ "${{ needs.build.result }}" == "success" ]; then
|
||||
node ./metadata.js --os linux --arch x86_64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_linux-x64.zip $FILE
|
||||
node ./metadata.js --os windows --arch x86_64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_windows-x64.zip $FILE
|
||||
node ./metadata.js --os windows --arch i686 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_windows-x32.zip $FILE
|
||||
node ./metadata.js --os macos --arch x64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_mac-x64.zip $FILE
|
||||
node ./metadata.js --os macos --arch arm64 --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_mac-arm64.zip $FILE
|
||||
fi
|
||||
|
||||
if [ "${{ needs.build_android.result }}" == "success" ]; then
|
||||
node ./metadata.js --os android --version $VERSION --commit $GITHUB_SHA --link https://nightly.link/doukutsu-rs/doukutsu-rs/actions/runs/${{ github.run_id }}/doukutsu-rs_android.zip $FILE
|
||||
fi
|
||||
|
||||
echo "file=$FILE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload metadata
|
||||
run: |
|
||||
git config user.name ${{ vars.METADATA_USER_NAME }}
|
||||
git config user.email ${{ vars.METADATA_USER_EMAIL }}
|
||||
|
||||
git add ${{ steps.metadata.outputs.file }}
|
||||
git commit -m "Update nightly builds metadata(CI)"
|
||||
git push
|
|
@ -0,0 +1,46 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- released
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
update_metadata:
|
||||
name: Update metadata
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: doukutsu-rs/metadata
|
||||
token: ${{ secrets.METADATA_USER_TOKEN }}
|
||||
- name: Update metadata
|
||||
id: metadata
|
||||
run: |
|
||||
export VERSION="${{ github.event.release.tag_name }}"
|
||||
export FILE="./metadata/stable.json"
|
||||
|
||||
node ./metadata.js --os windows --arch x86_64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.windows.x86_64.$VERSION.exe $FILE
|
||||
node ./metadata.js --os windows --arch i686 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.windows.i686.$VERSION.exe $FILE
|
||||
node ./metadata.js --os macos --arch x86_64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.macos.x86_64.$VERSION.zip $FILE
|
||||
node ./metadata.js --os macos --arch arm64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.macos.arm64.$VERSION.zip $FILE
|
||||
node ./metadata.js --os linux --arch x86_64 --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.linux.x86_64.$VERSION.elf $FILE
|
||||
node ./metadata.js --os android --version $VERSION --link https://github.com/doukutsu-rs/doukutsu-rs/releases/download/$VERSION/doukutsu-rs.android.$VERSION.apk $FILE
|
||||
|
||||
echo "file=$FILE" >> "$GITHUB_OUTPUT"
|
||||
- name: Upload metadata
|
||||
run: |
|
||||
git config user.name ${{ vars.METADATA_USER_NAME }}
|
||||
git config user.email ${{ vars.METADATA_USER_EMAIL }}
|
||||
|
||||
git add ${{ steps.metadata.outputs.file }}
|
||||
git commit -m "Update stable builds metadata(CI)"
|
||||
git push
|
||||
|
||||
|
|
@ -2,13 +2,16 @@
|
|||
.vscode/
|
||||
|
||||
# Cave Story (copyrighted) data files
|
||||
data/
|
||||
/data/
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Shader binary files
|
||||
*.dksh
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
@ -60,3 +63,5 @@ ehthumbs_vista.db
|
|||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
*.log
|
||||
|
|
89
Cargo.toml
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "doukutsu-rs"
|
||||
description = "A re-implementation of Cave Story (Doukutsu Monogatari) engine"
|
||||
version = "0.99.0"
|
||||
version = "0.101.0"
|
||||
authors = ["Alula", "dawnDus"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@ -18,30 +19,36 @@ required-features = ["exe"]
|
|||
[profile.release]
|
||||
lto = "off"
|
||||
panic = "abort"
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
split-debuginfo = "packed"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
codegen-units = 256
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "doukutsu-rs"
|
||||
identifier = "io.github.doukutsu_rs"
|
||||
version = "0.99.0"
|
||||
version = "0.101.0"
|
||||
resources = ["data"]
|
||||
copyright = "Copyright (c) 2020-2022 doukutsu-rs dev team"
|
||||
copyright = "Copyright (c) 2020-2023 doukutsu-rs contributors"
|
||||
category = "Game"
|
||||
osx_minimum_system_version = "10.12"
|
||||
|
||||
[features]
|
||||
default = ["default-base", "backend-sdl", "render-opengl", "exe"]
|
||||
default-base = ["scripting-lua", "ogg-playback", "netplay"]
|
||||
default = ["default-base", "backend-sdl", "render-opengl", "exe", "webbrowser", "discord-rpc"]
|
||||
default-base = ["ogg-playback"]
|
||||
ogg-playback = ["lewton"]
|
||||
backend-sdl = ["sdl2", "sdl2-sys"]
|
||||
backend-glutin = ["winit", "glutin", "render-opengl"]
|
||||
backend-horizon = []
|
||||
render-opengl = []
|
||||
scripting-lua = ["lua-ffi"]
|
||||
netplay = ["tokio", "serde_cbor"]
|
||||
discord-rpc = ["discord-rich-presence"]
|
||||
netplay = ["serde_cbor"]
|
||||
editor = []
|
||||
hooks = ["libc"]
|
||||
exe = []
|
||||
android = []
|
||||
|
||||
|
@ -51,41 +58,47 @@ android = []
|
|||
#winit = { path = "./3rdparty/winit", optional = true, default_features = false, features = ["x11"] }
|
||||
#sdl2 = { path = "./3rdparty/rust-sdl2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
#sdl2-sys = { path = "./3rdparty/rust-sdl2/sdl2-sys", optional = true, features = ["bundled", "static-link"] }
|
||||
bitvec = "0.20"
|
||||
#cpal = { path = "./3rdparty/cpal" }
|
||||
byteorder = "1.4"
|
||||
case_insensitive_hashmap = "1.0.0"
|
||||
chrono = "0.4"
|
||||
cpal = "0.13"
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
|
||||
cpal = { git = "https://github.com/doukutsu-rs/cpal", rev = "9d269d8724102404e73a61e9def0c0cbc921b676" }
|
||||
directories = "3"
|
||||
discord-rich-presence = { version = "0.2", optional = true }
|
||||
downcast = "0.11"
|
||||
funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105
|
||||
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] }
|
||||
imgui = "0.8.0"
|
||||
image = { version = "0.23", default-features = false, features = ["png", "bmp"] }
|
||||
encoding_rs = "0.8.33"
|
||||
fern = "0.6.2"
|
||||
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "2dd95f042e6e090d36f577cbea125560dd99bd27", optional = true, default_features = false, features = ["x11"] }
|
||||
imgui = "0.8"
|
||||
image = { version = "0.24", default-features = false, features = ["png", "bmp"] }
|
||||
itertools = "0.10"
|
||||
lazy_static = "1.4.0"
|
||||
lewton = { version = "0.10.2", optional = true }
|
||||
libc = { version = "0.2", optional = true }
|
||||
lazy_static = "1.4"
|
||||
lewton = { version = "0.10", optional = true }
|
||||
log = "0.4"
|
||||
lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "e0b2ff5960f7ef9974aa9675cebe4907bee0134f", optional = true }
|
||||
num-derive = "0.3.2"
|
||||
num-traits = "0.2.12"
|
||||
paste = "1.0.0"
|
||||
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["bundled", "static-link"] }
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
open = "3.2"
|
||||
paste = "1.0"
|
||||
pelite = { version = ">=0.9.2", default-features = false, features = ["std"] }
|
||||
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "f2f1e29a416bcc22f2faf411866db2c8d9536308", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "f2f1e29a416bcc22f2faf411866db2c8d9536308", optional = true, features = ["bundled", "static-link"] }
|
||||
rc-box = "1.2.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_derive = "1"
|
||||
serde_cbor = { version = "0.11.2", optional = true }
|
||||
serde_cbor = { version = "0.11", optional = true }
|
||||
serde_json = "1.0"
|
||||
simple_logger = { version = "1.16", features = ["colors", "threads"] }
|
||||
strum = "0.20"
|
||||
strum_macros = "0.20"
|
||||
tokio = { version = "1.12.0", features = ["net"], optional = true }
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
# remove and replace when drain_filter is in stable
|
||||
vec_mut_scan = "0.4"
|
||||
webbrowser = "0.5.5"
|
||||
winit = { git = "https://github.com/alula/winit.git", rev = "6acf76ff192dd8270aaa119b9f35716c03685f9f", optional = true, default_features = false, features = ["x11"] }
|
||||
xmltree = "0.10.3"
|
||||
webbrowser = { version = "0.8.6", optional = true }
|
||||
winit = { git = "https://github.com/doukutsu-rs/winit.git", rev = "878f206d19af01b0977277929eee5e32667453c0", optional = true, default_features = false, features = ["x11"] }
|
||||
xmltree = "0.10"
|
||||
|
||||
#hack to not link SDL_image on Windows(causes a linker error)
|
||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "f2f1e29a416bcc22f2faf411866db2c8d9536308", optional = true, features = ["image", "unsafe_textures", "bundled", "static-link"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
|
@ -94,10 +107,14 @@ winapi = { version = "0.3", features = ["winuser"] }
|
|||
winres = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.7"
|
||||
objc = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk = "0.3"
|
||||
ndk-glue = "0.3"
|
||||
ndk-sys = "0.2"
|
||||
jni = "0.19"
|
||||
ndk = "0.7"
|
||||
ndk-glue = "0.7"
|
||||
ndk-sys = "0.4"
|
||||
jni = "0.20"
|
||||
|
||||
[target.'cfg(target_os = "horizon")'.dependencies]
|
||||
#deko3d = { path = "./3rdparty/deko3d" }
|
||||
deko3d = { git = "https://github.com/doukutsu-rs/deko3d-rs", branch = "master" }
|
||||
|
|
181
README.md
|
@ -1,82 +1,78 @@
|
|||
![doukutsu-rs](./res/sue_crab_banner_github.png)
|
||||
|
||||
A fully playable re-implementation of Cave Story (Doukutsu Monogatari) engine written
|
||||
A fully playable re-implementation of the Cave Story (Doukutsu Monogatari) engine written
|
||||
in [Rust](https://www.rust-lang.org/).
|
||||
|
||||
[Join the Discord server](https://discord.gg/fbRsNNB)
|
||||
|
||||
![https://ci.appveyor.com/api/projects/status/github/doukutsu-rs/doukutsu-rs](https://ci.appveyor.com/api/projects/status/github/doukutsu-rs/doukutsu-rs)
|
||||
[![CI](https://github.com/doukutsu-rs/doukutsu-rs/actions/workflows/ci.yml/badge.svg?branch=master)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master?preview)
|
||||
|
||||
- [Get nightly builds](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master?preview) (recommended for now, has latest fixes and improvements)
|
||||
|
||||
- [Get nightly builds from AppVeyor](https://ci.appveyor.com/project/alula/doukutsu-rs) (recommended for now, has latest fixes and improvements)
|
||||
|
||||
Permalinks to latest builds from `master` branch:
|
||||
- [Windows (x86_64)](https://ci.appveyor.com/api/projects/alula/doukutsu-rs/artifacts/doukutsu-rs_win64.zip?branch=master&job=windows-x64)
|
||||
- [macOS (Intel, 64-bit, 10.14+)](https://ci.appveyor.com/api/projects/alula/doukutsu-rs/artifacts/doukutsu-rs_mac-intel.zip?branch=master&job=mac-x64)
|
||||
- [macOS (Apple M1, 11.0+)](https://ci.appveyor.com/api/projects/alula/doukutsu-rs/artifacts/doukutsu-rs_mac-m1.zip?branch=master&job=mac-arm64)
|
||||
- [Linux (x86_64)](https://ci.appveyor.com/api/projects/alula/doukutsu-rs/artifacts/doukutsu-rs_linux.zip?branch=master&job=linux-x64)
|
||||
|
||||
- [Windows (64-bit)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_windows-x64.zip)
|
||||
- [Windows (32-bit)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_windows-x32.zip)
|
||||
- [macOS (Intel, 64-bit, 10.14+)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_mac-x64.zip)
|
||||
- [macOS (Apple M1, 11.0+)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_mac-arm64.zip)
|
||||
- [Linux (64-bit)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_linux-x64.zip)
|
||||
- [Android (armv7/arm64/x86)](https://nightly.link/doukutsu-rs/doukutsu-rs/workflows/ci/master/doukutsu-rs_android.zip)
|
||||
|
||||
- [Get stable/beta builds from GitHub Releases](https://github.com/doukutsu-rs/doukutsu-rs/releases)
|
||||
|
||||
**macOS note:** If you get a `"doukutsu-rs" can't be opened` message, right-click doukutsu-rs.app and click open.
|
||||
|
||||
- [Get stable/beta builds from GitHub Releases](https://github.com/doukutsu-rs/doukutsu-rs/releases) (executables only,
|
||||
no data files bundled, see below for instructions)
|
||||
> [!NOTE]
|
||||
> macOS note: If you get a `"doukutsu-rs" can't be opened` message, right-click doukutsu-rs.app and click open.
|
||||
|
||||
> [!NOTE]
|
||||
> If you get issues with Epic Games Store version, scroll down for instructions.
|
||||
|
||||
#### Data files
|
||||
|
||||
In order to work doukutsu-rs needs to be paired with supported data files. This repository does not contain any data
|
||||
files.
|
||||
|
||||
doukutsu-rs works fine with pre-extracted freeware data from [this repository](https://github.com/doukutsu-rs/game-data)
|
||||
builds or [NXEngine(-evo)](https://github.com/nxengine/nxengine-evo) or from a supported copy
|
||||
of [Cave Story+](https://www.nicalis.com/games/cavestory+).
|
||||
doukutsu-rs works fine with freeware data files or [NXEngine(-evo)](https://github.com/nxengine/nxengine-evo) or from a
|
||||
supported copy of [Cave Story+](https://www.nicalis.com/games/cavestory+).
|
||||
|
||||
<details>
|
||||
<summary>How to set up data files on Android</summary>
|
||||
|
||||
If your phone has an app called **"Files"**:
|
||||
|
||||
1. Launch this app.
|
||||
2. Press **☰** on the top left corner.
|
||||
3. Tap on **"doukutsu-rs game data"**.
|
||||
4. Copy your game data files to the opened folder.
|
||||
|
||||
|
||||
If your phone does not have this app:
|
||||
|
||||
1. Install the **"Material Files"** app from *Hai Zhang* and launch it([Google Play](https://play.google.com/store/apps/details?id=me.zhanghai.android.files) | [F-Droid](https://f-droid.org/en/packages/me.zhanghai.android.files/) | [Github Releases](https://github.com/zhanghai/MaterialFiles/releases)).
|
||||
2. Press **☰** on the top left corner.
|
||||
3. Press **"+ Add storage"**.
|
||||
4. In the window that pops up, press **"External storage"**.
|
||||
5. Press **☰** on the top left corner.
|
||||
6. Tap on **"doukutsu-rs game data"**.
|
||||
7. Press the large blue button at the bottom labelled **"USE THIS FOLDER"**.
|
||||
8. Then click on **☰** in the top left corner again and open.
|
||||
9. Tap on **"files"** above **"+ Add storage"**.
|
||||
10. Copy your game data files to the opened folder.
|
||||
</details>
|
||||
|
||||
#### Supported game editions and data file acquisition guides
|
||||
|
||||
**Freeware**
|
||||
|
||||
- Vanilla freeware can't be just used without additional work, because some important data files are embedded inside the
|
||||
executable. An automatic extractor might be available in future.
|
||||
|
||||
doukutsu-rs works out of the box when it's placed in the same directory as the original Doukutsu.exe executable. On the initial
|
||||
startup, doukutsu-rs will automatically extract the additional resources that are embedded in the vanilla game into the `data`
|
||||
directory. Until that is done, both doukutsu-rs and the vanilla executable have to exist in the directory.
|
||||
|
||||
<details>
|
||||
<summary>Manual extraction guide</summary>
|
||||
<summary>Example root directory</summary>
|
||||
|
||||
Tools required:
|
||||
- Windows version of the game (1.0.0.6), original Japanese or with Aeon Genesis patch.
|
||||
- [Resource Hacker](http://www.angusj.com/resourcehacker/#download)
|
||||
- [Booster's Lab](https://www.cavestory.org/download/editors.php)
|
||||
|
||||
1. Open Doukutsu.exe in Resource Hacker.
|
||||
2. Click on `ORG` group, select `Action` -> `Save [ORG] group to an .RC file`.
|
||||
3. Navigate to `data` folder and create a folder named `Org` and save the .RC file there.
|
||||
4. Click on `BITMAP` group, select `Action` -> `Save [BITMAP] group to an .RC file`.
|
||||
5. Save them in `data` folder (**NOT** in `Org` folder).
|
||||
6. Go to file explorer and navigate to `data` folder.
|
||||
7. Delete Bitmap.rc
|
||||
8. Go to `Org` folder.
|
||||
9. Delete Org.rc
|
||||
10. Rename extension of all files from `.bin` to `.org` - you won't have music if you don't do that!
|
||||
11. Close Resource Hacker.
|
||||
12. Open Booster's Lab
|
||||
13. Load `Doukutsu.exe` in Booster's Lab - you can ignore the fact it tries to apply any patches or renames .pbm to .bmp, d-rs doesn't care.
|
||||
14. Select `File` -> `Export mapdata` -> `stage.tbl`
|
||||
15. Close Booster's Lab, saving isn't necessary.
|
||||
16. Optionally delete leftover files and folders - `.boostlab`, `ScriptSource`, `tsc_def.txt`
|
||||
17. That's all, you have everything to use it with doukutsu-rs now.
|
||||
|
||||
If you followed the above steps, the directory structure should look like this:
|
||||
|
||||
`data/`:
|
||||
|
||||
![files in /data/](https://media.discordapp.net/attachments/745322954660905103/947915770376102008/unknown.png?width=844&height=629)
|
||||
|
||||
`data/Org`:
|
||||
![files in /data/Org/](https://media.discordapp.net/attachments/745322954660905103/947915770690687016/unknown.png)
|
||||
![example root directory with doukutsu-rs and vanilla Cave Story](https://i.imgur.com/3dJ7WMB.png)
|
||||
|
||||
</details>
|
||||
|
||||
- https://github.com/doukutsu-rs/game-data - Pre-extracted freeware game data, graphics converted to .png, already
|
||||
distributed with AppVeyor builds for your convenience. (recommended)
|
||||
- https://github.com/nxengine/nxengine-evo/releases/download/v2.6.5-1/NXEngine-Evo-v2.6.5-1-Win64.zip -
|
||||
copy `NXEngine-evo-2.6.5-1-xxx/data` from the archive to runtime directory
|
||||
|
||||
**Cave Story+**
|
||||
|
||||
|
@ -93,8 +89,8 @@ If you want to use doukutsu-rs as a substitute for Mac version of Cave Story+ (w
|
|||
on 10.15+ anymore), do the following:
|
||||
|
||||
1. Find the doukutsu-rs executable:
|
||||
- In AppVeyor builds, it's in `doukutsu-rs.app/Contents/MacOS/doukutsu-rs`
|
||||
- In your own builds, it's in `target/(release|debug)/doukutsu-rs`
|
||||
- In AppVeyor builds, it's in `doukutsu-rs.app/Contents/MacOS/doukutsu-rs`
|
||||
- In your own builds, it's in `target/(release|debug)/doukutsu-rs`
|
||||
2. Open Steam Library, select `Cave Story+`, press the `Manage` button (gear icon) and select `Properties...`
|
||||
3. Select `Local Files` and press `Browse...`.
|
||||
4. Open the `Cave Story+.app` bundle and navigate to `Contents/MacOS` directory.
|
||||
|
@ -103,14 +99,25 @@ on 10.15+ anymore), do the following:
|
|||
7. Launch the game from Steam and enjoy!
|
||||
|
||||
![image](https://user-images.githubusercontent.com/53099651/155904982-eb6032d8-7a4d-4af7-ae6f-b69041ecfaa4.png)
|
||||
|
||||
</details>
|
||||
|
||||
> [!WARNING]
|
||||
> **EPIC GAMES STORE VERSION WARNING**
|
||||
>
|
||||
> Nicalis for some reason ships a stray `opengl32.dll` DLL from Windows 7 with the Epic Games Store copies of Cave Story+.
|
||||
>
|
||||
> However as the game is 32-bit and the dll is 64-bit it has no effect on the original version, but as it's a core Windows DLL and doukutsu-rs ships 64-bit builds and uses OpenGL, it's makes the game crash on startup.
|
||||
>
|
||||
> The fix is to simply delete `opengl32.dll`, as it's not used anyway.
|
||||
|
||||
<details>
|
||||
<summary>Epic Games Store</summary>
|
||||
|
||||
Check your default installation directory.
|
||||
|
||||
![image](https://user-images.githubusercontent.com/53099651/155905035-0080eace-bd98-4cf5-9628-c98334ea768c.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
@ -119,6 +126,7 @@ Check your default installation directory.
|
|||
Check your default installation directory.
|
||||
|
||||
![image](https://user-images.githubusercontent.com/53099651/155906494-1e53f174-f12f-41be-ab53-8745cdf735b5.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
@ -127,48 +135,64 @@ Check your default installation directory.
|
|||
The archive from Humble Bundle contains the necessary `data` folder, in the same folder as `CaveStory+.exe`.
|
||||
|
||||
![image](https://user-images.githubusercontent.com/96957561/156861929-7fa03951-442b-4277-b673-474189411103.png)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>WiiWare</summary>
|
||||
|
||||
Extract the `.wad` in order to get valid [Cave Story assets](https://user-images.githubusercontent.com/53099651/159585593-43fead24-b041-48f4-8332-be50d712310d.png)
|
||||
1. [Dump Your WiiWare `.wad`](https://wii.guide/dump-wads.html)
|
||||
2. [Extract and decompress the `data` folder](https://docs.google.com/document/d/1hDNDgNl0cUDlFOQ_BUOq3QCGb7S0xfUxRoob-hfM-DY)
|
||||
Example of a [valid uncompressed `data` folder](https://user-images.githubusercontent.com/53099651/159585593-43fead24-b041-48f4-8332-be50d712310d.png)
|
||||
|
||||
</details>
|
||||
|
||||
**Remastered version (first released in 2017 on Switch)**
|
||||
|
||||
Note that this version is **incompatible** with saves from the original version.
|
||||
|
||||
Interchanging the save files may result in spawning in wrong locations, softlocks, graphical glitches, or other issues.
|
||||
> [!NOTE]
|
||||
> This version is **incompatible** with saves from the original version.
|
||||
>
|
||||
> Interchanging the save files may result in spawning in wrong locations, softlocks, graphical glitches, or other issues.
|
||||
|
||||
<details>
|
||||
<summary>Nintendo Switch</summary>
|
||||
|
||||
Extract the `data` folder directly from the ROM.
|
||||
Extract the `data` folder (contained in `romfs`) from your console using tool such as [nxdumptool](https://github.com/DarkMatterCore/nxdumptool).
|
||||
|
||||
**Important notes:**
|
||||
|
||||
- doukutsu-rs doesn't rely on the original ROM or executable, you just need the data files, go to `RomFS options` menu to just extract the files to SD card so you don't need to do any extra steps.
|
||||
- Ensure you're dumping the files **with update included** (`Use update/DLC` option), as 1.0 isn't supported.
|
||||
|
||||
**Nintendo Switch homebrew port specific info**
|
||||
|
||||
If you're running the homebrew port (drshorizon.nro) on your Switch, you can avoid the dumping step, doukutsu-rs will
|
||||
automatically detect and mount the data files if you run it over Cave Story+ in Title Override mode (hold `R` while starting CS+ and launch d-rs from hbmenu).
|
||||
|
||||
You can put your own data files in `/switch/doukutsu-rs/data` directory on SD Card, which will be overlayed over RomFS if
|
||||
you run it in setup described above.
|
||||
|
||||
</details>
|
||||
|
||||
#### Controls
|
||||
|
||||
Same controls as the default for freeware and Cave Story+ keyboard.
|
||||
Same controls as the default for freeware and Cave Story+ keyboard.
|
||||
|
||||
To change, edit `doukutsu-rs\data\settings.json` within your user directory.
|
||||
To change, use the control customization menu or edit `doukutsu-rs\data\settings.json` within your user directory.
|
||||
|
||||
| | P1 | P2 |
|
||||
| ------------------------- | --------- | --------- |
|
||||
| Movement | `← ↑ ↓ →` | `, L . /` |
|
||||
| Jump | `Z` | `B` |
|
||||
| Shoot | `X` | `N` |
|
||||
| Cycle Weapon | `A and S` | `G and H` |
|
||||
| Inventory / Skip cutscene | `Q` | `T` |
|
||||
| Map | `W` | `Y` |
|
||||
| Strafe | `LShift` | `RShift` |
|
||||
|
||||
| | P1 | P2 |
|
||||
|--------------|-----------|-----------|
|
||||
| Movement | `← ↑ ↓ →` | `, L . /` |
|
||||
| Jump | `Z` | `B` |
|
||||
| Shoot | `X` | `N` |
|
||||
| Cycle Weapon | `A and S` | `G and H` |
|
||||
| Inventory | `Q` | `T` |
|
||||
| Map | `W` | `Y` |
|
||||
| Strafe | `LShift` | `RShift` |
|
||||
|
||||
- `Alt + Enter` - Toggle Fullscreen
|
||||
- `F2` (While paused) - Quick Restart
|
||||
|
||||
|
||||
#### Screenshots
|
||||
|
||||
<details>
|
||||
|
@ -198,9 +222,9 @@ To change, edit `doukutsu-rs\data\settings.json` within your user directory.
|
|||
|
||||
![Balcony Switch](https://user-images.githubusercontent.com/53099651/155918810-063c0f06-2d48-485f-8367-6337525deab7.png)
|
||||
|
||||
![Dogs Switch](https://media.discordapp.net/attachments/745322954660905103/947895408196202617/unknown.png)
|
||||
![Dogs Switch](https://github.com/doukutsu-rs/doukutsu-rs/assets/6276139/30ba01ae-375d-4488-98c4-98e3e8c7f187)
|
||||
|
||||
![Almond Switch](https://media.discordapp.net/attachments/745322954660905103/947898268631826492/unknown.png)
|
||||
![Almond Switch](https://github.com/doukutsu-rs/doukutsu-rs/assets/6276139/42d4b6a3-4fc5-4aaf-9535-462c4c484dc7)
|
||||
|
||||
![Hell Switch](https://user-images.githubusercontent.com/53099651/155918602-62268274-c529-41c2-a87e-0c31e7874b94.png)
|
||||
|
||||
|
@ -212,4 +236,5 @@ To change, edit `doukutsu-rs\data\settings.json` within your user directory.
|
|||
- [@Daedily](https://twitter.com/Daedliy) - brand artwork (Icon / Banner / Server), screenshots for this guide.
|
||||
- [ggez](https://github.com/ggez/ggez) - parts of it are used in `crate::framework`, notably the VFS code.
|
||||
- [Clownacy](https://github.com/Clownacy) - widescreen camera code.
|
||||
- [LunarLambda for organism](https://gitdab.com/LunarLambda/organism) - used as basis for our Organya playback engine.
|
||||
- [LunarLambda for organism](https://github.com/doukutsu-rs/organism) - used as basis for our Organya playback engine.
|
||||
- [Zoroyoshi](http://z.apps.atjp.jp/k12x10/) - k12x10 font we use as built-in font.
|
||||
|
|
|
@ -231,3 +231,4 @@ fabric.properties
|
|||
!/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,android
|
||||
app/release/
|
|
@ -4,21 +4,23 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
ndkVersion "22.1.7171670"
|
||||
namespace "io.github.doukutsu_rs"
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
ndkVersion "25.2.9519653"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.doukutsu_rs"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
targetSdkVersion 33
|
||||
versionCode 2
|
||||
versionName "0.101.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
ndk {
|
||||
abiFilters 'x86', 'arm64-v8a', 'armeabi-v7a'
|
||||
abiFilters 'arm64-v8a'
|
||||
stl = "c++_shared"
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
|
@ -41,6 +43,15 @@ android {
|
|||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
|
||||
ndk {
|
||||
abiFilters 'x86', 'arm64-v8a', 'armeabi-v7a'
|
||||
stl = "c++_shared"
|
||||
}
|
||||
}
|
||||
debug {
|
||||
jniDebuggable true
|
||||
renderscriptDebuggable true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,28 +69,28 @@ android {
|
|||
path "src/main/cpp/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
excludes += "**/dummy.so"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.support:support-annotations:28.0.0'
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
implementation 'com.android.support:design:28.0.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
|
||||
implementation 'android.arch.navigation:navigation-fragment:1.0.0'
|
||||
implementation 'android.arch.navigation:navigation-ui:1.0.0'
|
||||
implementation 'androidx.annotation:annotation:1.5.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'androidx.core:core:1.9.0'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
}
|
||||
|
||||
println("cargo target: ${project.buildDir.getAbsolutePath()}/rust-target")
|
||||
println("ndk dir: ${android.ndkDirectory}")
|
||||
|
||||
cargoNdk {
|
||||
targets = [
|
||||
"x86",
|
||||
"arm",
|
||||
"arm64"
|
||||
]
|
||||
librariesNames = ["libdrsandroid.so"]
|
||||
//targetDirectory = "${project.buildDir.getAbsolutePath()}/rust-target"
|
||||
module = "../drsandroid/"
|
||||
extraCargoEnv = ["ANDROID_NDK_HOME": android.ndkDirectory]
|
||||
extraCargoBuildArguments = []
|
||||
|
@ -88,9 +99,17 @@ cargoNdk {
|
|||
buildTypes {
|
||||
release {
|
||||
buildType = "release"
|
||||
targets = [
|
||||
"x86",
|
||||
"arm",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
debug {
|
||||
buildType = "debug"
|
||||
targets = [
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.github.doukutsu_rs">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application android:allowBackup="true" android:extractNativeLibs="true" android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:appCategory="game"
|
||||
android:description="@string/app_description"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:isGame="true"
|
||||
android:label="@string/app_name"
|
||||
android:resizeableActivity="false"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Doukutsurs">
|
||||
<activity android:name=".DownloadActivity" android:label="Download" android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/Theme.Doukutsurs.NoActionBar"></activity>
|
||||
<activity android:name=".MainActivity" android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="doukutsu-rs" android:launchMode="standard" android:screenOrientation="sensorLandscape">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:exported="true"
|
||||
android:label="doukutsu-rs"
|
||||
android:launchMode="standard"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/Theme.Doukutsurs.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="drsandroid" />
|
||||
</activity>
|
||||
|
||||
<provider android:name=".DoukutsuDocumentsProvider" android:authorities="${documentsAuthority}"
|
||||
android:exported="true" android:grantUriPermissions="true"
|
||||
<activity
|
||||
android:name=".DownloadActivity"
|
||||
android:label="Download"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/Theme.Doukutsurs.NoActionBar" />
|
||||
|
||||
<activity
|
||||
android:name=".GameActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="standard"
|
||||
android:screenOrientation="sensorLandscape">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="drsandroid" />
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name=".DoukutsuDocumentsProvider"
|
||||
android:authorities="${documentsAuthority}"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Sets the minimum version of CMake required to build your native library.
|
||||
# This ensures that a certain set of CMake features is available to
|
||||
# your build.
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(doukutsu-rs)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
# Copy shared STL files to Android Studio output directory so they can be
|
||||
# packaged in the APK.
|
||||
|
@ -21,7 +21,7 @@ cmake_minimum_required(VERSION 3.10)
|
|||
function(configure_shared_stl lib_path so_base)
|
||||
message("Configuring STL ${so_base} for ${ANDROID_ABI}")
|
||||
configure_file(
|
||||
"${ANDROID_NDK}/sources/cxx-stl/${lib_path}/libs/${ANDROID_ABI}/lib${so_base}.so"
|
||||
"${ANDROID_NDK}/toolchains/llvm/prebuilt/${ANDROID_HOST_TAG}/sysroot/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/lib${so_base}.so"
|
||||
"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${so_base}.so"
|
||||
COPYONLY)
|
||||
endfunction()
|
||||
|
@ -50,4 +50,4 @@ endif()
|
|||
# and CMake builds them for you. When you build your app, Gradle
|
||||
# automatically packages shared libraries with your APK.
|
||||
|
||||
#add_library(dummy SHARED dummy.cpp)
|
||||
add_library(dummy SHARED dummy.cpp)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.Window;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
public class ActivityUtils {
|
||||
public static void hideSystemBars(Activity activity) {
|
||||
Window window = activity.getWindow();
|
||||
WindowInsetsControllerCompat windowInsetsController =
|
||||
WindowCompat.getInsetsController(window, window.getDecorView());
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
|
||||
}
|
||||
}
|
|
@ -1,21 +1,31 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Path;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
||||
private final static String[] DEFAULT_ROOT_PROJECTION =
|
||||
|
@ -54,7 +64,7 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
row.add(Root.COLUMN_ROOT_ID, id);
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
row.add(Root.COLUMN_TITLE,
|
||||
getContext().getString(R.string.document_provider_name));
|
||||
getContext().getString(R.string.app_name));
|
||||
row.add(Root.COLUMN_MIME_TYPES, "*/*");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, file.getFreeSpace());
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
|
||||
|
@ -95,7 +105,9 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
pushFile(result, file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result.setNotificationUri(getContext().getContentResolver(), DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, parentDocumentId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -134,7 +146,16 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Couldn't create file: " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_INSERT);
|
||||
} else {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
@ -147,8 +168,38 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
}
|
||||
|
||||
deleteRecursive(file);
|
||||
// todo refresh this shit
|
||||
// getContext().getContentResolver().refresh()
|
||||
|
||||
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_DELETE);
|
||||
} else {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) throws FileNotFoundException {
|
||||
if (parentDocumentId == null) {
|
||||
parentDocumentId = getContext().getFilesDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
File childFile = new File(childDocumentId);
|
||||
if (!childFile.exists()) {
|
||||
throw new FileNotFoundException(childFile.getAbsolutePath()+" doesn't exist");
|
||||
} else if (!isChildDocument(parentDocumentId, childDocumentId)) {
|
||||
throw new FileNotFoundException(childDocumentId+" is not child of "+parentDocumentId);
|
||||
}
|
||||
|
||||
LinkedList<String> path = new LinkedList<>();
|
||||
while (childFile != null && isChildDocument(parentDocumentId, childFile.getAbsolutePath())) {
|
||||
path.addFirst(childFile.getAbsolutePath());
|
||||
childFile = childFile.getParentFile();
|
||||
}
|
||||
|
||||
return new Path(parentDocumentId, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,11 +238,26 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
File newPath = new File(file.getParentFile().getAbsolutePath() + "/" + displayName);
|
||||
|
||||
try {
|
||||
Files.move(file.toPath(), newPath.toPath());
|
||||
if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Files.move(file.toPath(), newPath.toPath());
|
||||
} else {
|
||||
if (!file.renameTo(newPath)) {
|
||||
throw new IOException("Couldn't rename file: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_UPDATE);
|
||||
} else {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
return newPath.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
@ -205,8 +271,18 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
|||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
deleteRecursive(f);
|
||||
if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
deleteRecursive(f);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (!f.getAbsolutePath().equals(f.getCanonicalPath())) {
|
||||
deleteRecursive(f);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ package io.github.doukutsu_rs;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Handler;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.ArrayList;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
|
@ -18,6 +19,7 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
private ProgressBar progressBar;
|
||||
private DownloadThread downloadThread;
|
||||
private String basePath;
|
||||
private Handler handler = new Handler();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -26,7 +28,9 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
setContentView(R.layout.activity_download);
|
||||
txtProgress = findViewById(R.id.txtProgress);
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
basePath = getFilesDir().getAbsolutePath() + "/data/";
|
||||
ActivityUtils.hideSystemBars(this);
|
||||
|
||||
basePath = getFilesDir().getAbsolutePath() + "/";
|
||||
|
||||
downloadThread = new DownloadThread();
|
||||
downloadThread.start();
|
||||
|
@ -39,23 +43,37 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private class DownloadThread extends Thread {
|
||||
private static final String DOWNLOAD_URL = "https://github.com/doukutsu-rs/game-data/archive/refs/heads/master.zip";
|
||||
private final ArrayList<DownloadEntry> urls = new ArrayList<>();
|
||||
|
||||
private final ArrayList<String> filesWhitelist = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.filesWhitelist.add("data/");
|
||||
this.filesWhitelist.add("Doukutsu.exe");
|
||||
|
||||
// DON'T SET `true` VALUE FOR TRANSLATIONS
|
||||
this.urls.add(new DownloadEntry(R.string.download_entries_base, "https://www.cavestory.org/downloads/cavestoryen.zip", true));
|
||||
|
||||
for (DownloadEntry entry : this.urls) {
|
||||
this.download(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void download(DownloadEntry downloadEntry) {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(DOWNLOAD_URL);
|
||||
URL url = new URL(downloadEntry.url);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
throw new IllegalStateException("Bad HTTP response code: " + connection.getResponseCode());
|
||||
throw new IllegalStateException(getString(R.string.download_status_error_http, connection.getResponseCode()));
|
||||
}
|
||||
|
||||
int fileLength = connection.getContentLength();
|
||||
if (fileLength == 0) {
|
||||
progressBar.setIndeterminate(true);
|
||||
handler.post(() -> progressBar.setIndeterminate(true));
|
||||
}
|
||||
|
||||
byte[] zipFile;
|
||||
|
@ -78,10 +96,10 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
if (last + 1000 >= now) {
|
||||
int speed = (int) ((downloaded - downloadedLast) / 1024.0);
|
||||
String text = (fileLength > 0)
|
||||
? String.format(Locale.ENGLISH, "Downloading... %d%% (%d/%d KiB, %d KiB/s)", downloaded * 100 / fileLength, downloaded / 1024, fileLength / 1024, speed)
|
||||
: String.format(Locale.ENGLISH, "Downloading... --%% (%d KiB, %d KiB/s)", downloaded / 1024, speed);
|
||||
? getString(R.string.download_status_downloading, downloadEntry.name, downloaded * 100 / fileLength, downloaded / 1024, fileLength / 1024, speed)
|
||||
: getString(R.string.download_status_downloading_null, downloadEntry.name, downloaded / 1024, speed);
|
||||
|
||||
txtProgress.setText(text);
|
||||
handler.post(() -> txtProgress.setText(text));
|
||||
|
||||
downloadedLast = downloaded;
|
||||
last = now;
|
||||
|
@ -94,46 +112,90 @@ public class DownloadActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
new File(basePath).mkdirs();
|
||||
try (ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zipFile))) {
|
||||
ZipEntry entry;
|
||||
byte[] buffer = new byte[4096];
|
||||
while ((entry = in.getNextEntry()) != null) {
|
||||
String entryName = entry.getName();
|
||||
this.unpack(zipFile, downloadEntry.isBase);
|
||||
|
||||
// strip prefix
|
||||
if (entryName.startsWith("game-data-master/")) {
|
||||
entryName = entryName.substring("game-data-master/".length());
|
||||
}
|
||||
handler.post(() -> txtProgress.setText(getString(R.string.download_status_done)));
|
||||
|
||||
txtProgress.setText("Unpacking: " + entryName);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
new File(basePath + entryName).mkdirs();
|
||||
} else {
|
||||
try (FileOutputStream fos = new FileOutputStream(basePath + entryName)) {
|
||||
int count;
|
||||
while ((count = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
txtProgress.setText("Done!");
|
||||
|
||||
Intent intent = new Intent(DownloadActivity.this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
|
||||
startActivity(intent);
|
||||
DownloadActivity.this.finish();
|
||||
handler.post(() -> {
|
||||
Intent intent = new Intent(DownloadActivity.this, GameActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
DownloadActivity.this.finish();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
if (txtProgress != null) txtProgress.setText(e.getMessage());
|
||||
handler.post(() -> {
|
||||
if (txtProgress != null)
|
||||
txtProgress.setText(e.getMessage());
|
||||
});
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (connection != null) connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void unpack(byte[] zipFile, boolean isBase) throws IOException {
|
||||
ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zipFile));
|
||||
ZipEntry entry;
|
||||
byte[] buffer = new byte[4096];
|
||||
while ((entry = in.getNextEntry()) != null) {
|
||||
String entryName = entry.getName();
|
||||
|
||||
// strip prefix
|
||||
if (entryName.startsWith("CaveStory/")) {
|
||||
entryName = entryName.substring("CaveStory/".length());
|
||||
}
|
||||
|
||||
if (!this.entryInWhitelist(entryName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
final String s = entryName;
|
||||
handler.post(() -> txtProgress.setText(
|
||||
getString(R.string.download_status_unpacking, s)
|
||||
));
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
new File(basePath + entryName).mkdirs();
|
||||
} else {
|
||||
try (FileOutputStream fos = new FileOutputStream(basePath + entryName)) {
|
||||
int count;
|
||||
while ((count = in.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean entryInWhitelist(String entry) {
|
||||
for (String file : this.filesWhitelist) {
|
||||
if (entry.startsWith(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadEntry {
|
||||
public String name; //e.g. "Polish translation", "Base data files"
|
||||
public String url;
|
||||
public boolean isBase = false;
|
||||
|
||||
DownloadEntry(String name, String url, boolean isBase) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
this.isBase = isBase;
|
||||
}
|
||||
|
||||
DownloadEntry(int name, String url, boolean isBase) {
|
||||
this.name = getString(name);
|
||||
this.url = url;
|
||||
this.isBase = isBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.SensorManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class GameActivity extends NativeActivity {
|
||||
private int[] displayInsets = new int[]{0, 0, 0, 0};
|
||||
private OrientationEventListener listener;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActivityUtils.hideSystemBars(this);
|
||||
|
||||
listener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
GameActivity.this.updateCutouts();
|
||||
}
|
||||
};
|
||||
|
||||
if (listener.canDetectOrientation()) {
|
||||
listener.enable();
|
||||
} else {
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (listener != null) {
|
||||
listener.disable();
|
||||
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
private void updateCutouts() {
|
||||
this.displayInsets[0] = 0;
|
||||
this.displayInsets[1] = 0;
|
||||
this.displayInsets[2] = 0;
|
||||
this.displayInsets[3] = 0;
|
||||
|
||||
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
|
||||
|
||||
if (insets != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], insets.getStableInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[1], insets.getStableInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[2], insets.getStableInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[3], insets.getStableInsetBottom());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.P) {
|
||||
android.view.DisplayCutout cutout = insets.getDisplayCutout();
|
||||
|
||||
if (cutout != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], cutout.getSafeInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[0], cutout.getSafeInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[0], cutout.getSafeInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[0], cutout.getSafeInsetBottom());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void openDir(String path) {
|
||||
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, path);
|
||||
|
||||
File file = new File(path);
|
||||
if (!file.isDirectory()) {
|
||||
Toast.makeText(getApplicationContext(), R.string.dir_not_found, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setDataAndType(uri, DocumentsContract.Document.MIME_TYPE_DIR);
|
||||
intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch(ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.no_app_found_to_open_dir, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,118 +1,50 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.NativeActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.WindowInsets;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
public class MainActivity extends NativeActivity {
|
||||
private int[] displayInsets = new int[]{0, 0, 0, 0};
|
||||
private OrientationEventListener listener;
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
ActivityUtils.hideSystemBars(this);
|
||||
|
||||
File f = new File(getFilesDir().getAbsolutePath() + "/data/");
|
||||
String[] list = f.list();
|
||||
if (!f.exists() || (list != null && list.length == 0)) {
|
||||
messageBox("Missing data files", "No data files found, would you like to download them?", () -> {
|
||||
messageBox(getString(R.string.missing_data_title), getString(R.string.missing_data_desc), () -> {
|
||||
Intent intent = new Intent(this, DownloadActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
this.finish();
|
||||
});
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
listener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
MainActivity.this.updateCutouts();
|
||||
}
|
||||
};
|
||||
|
||||
if (listener.canDetectOrientation()) {
|
||||
listener.enable();
|
||||
}, this::launchGame);
|
||||
} else {
|
||||
listener = null;
|
||||
launchGame();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (listener != null) {
|
||||
listener.disable();
|
||||
|
||||
listener = null;
|
||||
}
|
||||
private void launchGame() {
|
||||
Intent intent = new Intent(this, GameActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
this.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
this.updateCutouts();
|
||||
}
|
||||
|
||||
private void updateCutouts() {
|
||||
this.displayInsets[0] = 0;
|
||||
this.displayInsets[1] = 0;
|
||||
this.displayInsets[2] = 0;
|
||||
this.displayInsets[3] = 0;
|
||||
|
||||
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
|
||||
|
||||
if (insets != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], insets.getStableInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[1], insets.getStableInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[2], insets.getStableInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[3], insets.getStableInsetBottom());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDK_INT >= Build.VERSION_CODES.P) {
|
||||
android.view.DisplayCutout cutout = insets.getDisplayCutout();
|
||||
|
||||
if (cutout != null) {
|
||||
this.displayInsets[0] = Math.max(this.displayInsets[0], cutout.getSafeInsetLeft());
|
||||
this.displayInsets[1] = Math.max(this.displayInsets[0], cutout.getSafeInsetTop());
|
||||
this.displayInsets[2] = Math.max(this.displayInsets[0], cutout.getSafeInsetRight());
|
||||
this.displayInsets[3] = Math.max(this.displayInsets[0], cutout.getSafeInsetBottom());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void messageBox(String title, String message, Runnable callback) {
|
||||
private void messageBox(String title, String message, Runnable yesCallback, Runnable noCallback) {
|
||||
this.runOnUiThread(() -> {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(message);
|
||||
alert.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
||||
callback.run();
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.no, (dialog, whichButton) -> {
|
||||
// hide
|
||||
});
|
||||
alert.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> yesCallback.run());
|
||||
alert.setNegativeButton(android.R.string.no, (dialog, whichButton) -> noCallback.run());
|
||||
alert.setCancelable(false);
|
||||
alert.show();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -21,7 +22,7 @@
|
|||
android:id="@+id/txtTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Downloading game data"
|
||||
android:text="@string/download_title"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Display1" />
|
||||
|
||||
|
@ -39,4 +40,4 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,4 +1,21 @@
|
|||
<resources>
|
||||
<string name="app_name">doukutsu-rs</string>
|
||||
<string name="document_provider_name">doukutsu-rs game data</string>
|
||||
<string name="app_name" translatable="false">doukutsu-rs</string>
|
||||
<string name="app_description">A faithful and open-source remake of Cave Story engine written in Rust.</string>
|
||||
|
||||
<string name="missing_data_title">Missing data files</string>
|
||||
<string name="missing_data_desc">No data files found, would you like to download them?</string>
|
||||
|
||||
<string name="download_title">Downloading game data</string>
|
||||
<string name="download_status_error_http">Bad HTTP response code: %d</string>
|
||||
<!-- Downloading {entry_name}… {progress}% ({downloaded_size}/{total_size} KiB, {speed} KiB/s) -->
|
||||
<string name="download_status_downloading">Downloading %1$s… %2$d%% (%3$d/%4$d KiB, %5$d KiB/s)</string>
|
||||
<string name="download_status_downloading_null">Downloading %1$s… --%% (%2$d KiB, %3$d KiB/s)</string>
|
||||
<string name="download_status_unpacking">Unpacking: %s</string>
|
||||
<string name="download_status_done">Done!</string>
|
||||
|
||||
<!-- Look {entry_name} on 9th line -->
|
||||
<string name="download_entries_base">base game files</string>
|
||||
|
||||
<string name="dir_not_found">Dir not found</string>
|
||||
<string name="no_app_found_to_open_dir">No app found to open dir</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:4.2.0"
|
||||
classpath "com.android.tools.build:gradle:7.3.1"
|
||||
classpath "gradle.plugin.com.github.willir.rust:plugin:0.3.4"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
@ -19,7 +19,7 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Wed Feb 17 23:16:31 CET 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
6
build.rs
|
@ -15,6 +15,12 @@ fn main() {
|
|||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("res/sue.ico");
|
||||
res.compile().unwrap();
|
||||
|
||||
if target.contains("i686") {
|
||||
// yet another hack
|
||||
println!("cargo:rustc-link-arg=/FORCE:MULTIPLE");
|
||||
println!("cargo:rustc-link-lib=shlwapi");
|
||||
}
|
||||
}
|
||||
|
||||
if target.contains("darwin") {
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
[package]
|
||||
name = "drsandroid"
|
||||
description = "doukutsu-rs targeted for Android"
|
||||
version = "0.1.0"
|
||||
authors = ["Alula"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "off"
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ndk = "0.3"
|
||||
ndk-glue = "0.3"
|
||||
ndk-sys = "0.2"
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin"] }
|
||||
ndk = "0.7"
|
||||
ndk-glue = "0.7"
|
||||
ndk-sys = "0.4"
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin", "webbrowser"] }
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#[cfg(target_os = "android")]
|
||||
#[cfg_attr(target_os = "android", ndk_glue::main())]
|
||||
pub fn android_main() {
|
||||
let options = doukutsu_rs::LaunchOptions { server_mode: false, editor: false };
|
||||
let resource_dir = std::path::PathBuf::from(ndk_glue::native_activity().internal_data_path().to_string_lossy().to_string());
|
||||
|
||||
doukutsu_rs::init(options).unwrap();
|
||||
std::env::set_current_dir(&resource_dir).unwrap();
|
||||
|
||||
let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false };
|
||||
|
||||
doukutsu_rs::game::init(options).unwrap();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
[build]
|
||||
target = ["aarch64-nintendo-switch"]
|
||||
|
||||
[target.aarch64-nintendo-switch]
|
||||
cc = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc"}
|
||||
cxx = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-g++"}
|
||||
ar = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-ar"
|
||||
ranlib = {path = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-ranlib"}
|
||||
linker = "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc"
|
|
@ -0,0 +1,31 @@
|
|||
[package]
|
||||
name = "drshorizon"
|
||||
description = "doukutsu-rs targeted for Nintendo Switch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
lto = "off"
|
||||
overflow-checks = false
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
incremental = true
|
||||
|
||||
[profile.dev.package."doukutsu-rs"]
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
codegen-units = 256
|
||||
incremental = true
|
||||
|
||||
[dependencies]
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-horizon"] }
|
|
@ -0,0 +1,3 @@
|
|||
Experimental.
|
||||
|
||||
ld script and .specs taken from devkitPro
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"arch": "aarch64",
|
||||
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
||||
"dynamic-linking": true,
|
||||
"disable-redzone": true,
|
||||
"env": "newlib",
|
||||
"executables": true,
|
||||
"exe-suffix": ".elf",
|
||||
"features": "+a57,+strict-align,+crc,+crypto",
|
||||
"has-rpath": false,
|
||||
"has-thread-local": false,
|
||||
"linker": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc",
|
||||
"linker-flavor": "gcc",
|
||||
"llvm-target": "aarch64-unknown-none",
|
||||
"max-atomic-width": 128,
|
||||
"no-default-libraries": false,
|
||||
"os": "horizon",
|
||||
"panic-strategy": "abort",
|
||||
"position-independent-executables": true,
|
||||
"pre-link-args": {
|
||||
"gcc": [
|
||||
"-fPIC",
|
||||
"-specs",
|
||||
"aarch64_nintendo_switch.specs",
|
||||
"-T",
|
||||
"aarch64_nintendo_switch.ld",
|
||||
"-L",
|
||||
"/opt/devkitpro/portlibs/switch/lib",
|
||||
"-L",
|
||||
"/opt/devkitpro/libnx/lib",
|
||||
"-I",
|
||||
"/opt/devkitpro/libnx/include"
|
||||
]
|
||||
},
|
||||
"relocation-model": "pic",
|
||||
"requires-uwtable": true,
|
||||
"target-c-int-width": "32",
|
||||
"target-endian": "little",
|
||||
"target-family": [
|
||||
"unix"
|
||||
],
|
||||
"target-pointer-width": "64",
|
||||
"trap-unreachable": true,
|
||||
"vendor": "nintendo"
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
OUTPUT_ARCH(aarch64)
|
||||
ENTRY(_start)
|
||||
|
||||
PHDRS
|
||||
{
|
||||
code PT_LOAD FLAGS(5) /* Read | Execute */;
|
||||
rodata PT_LOAD FLAGS(4) /* Read */;
|
||||
data PT_LOAD FLAGS(6) /* Read | Write */;
|
||||
dyn PT_DYNAMIC;
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* =========== CODE section =========== */
|
||||
PROVIDE(__start__ = 0x0);
|
||||
. = __start__;
|
||||
__code_start = . ;
|
||||
|
||||
.text :
|
||||
{
|
||||
HIDDEN(__text_start = .);
|
||||
KEEP (*(.crt0))
|
||||
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
|
||||
*(.text.exit .text.exit.*)
|
||||
*(.text.startup .text.startup.*)
|
||||
*(.text.hot .text.hot.*)
|
||||
*(.text .stub .text.* .gnu.linkonce.t.*)
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
.init :
|
||||
{
|
||||
KEEP( *(.init) )
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
.plt :
|
||||
{
|
||||
*(.plt)
|
||||
*(.iplt)
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
.fini :
|
||||
{
|
||||
KEEP( *(.fini) )
|
||||
. = ALIGN(8);
|
||||
} :code
|
||||
|
||||
/* =========== RODATA section =========== */
|
||||
. = ALIGN(0x1000);
|
||||
__rodata_start = . ;
|
||||
|
||||
.nx-module-name : { KEEP (*(.nx-module-name)) } :rodata
|
||||
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata .rodata.* .gnu.linkonce.r.*)
|
||||
. = ALIGN(8);
|
||||
} :rodata
|
||||
|
||||
.eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } :rodata
|
||||
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } :rodata
|
||||
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } :rodata
|
||||
.gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } : rodata
|
||||
|
||||
HIDDEN(__dynamic_start = .);
|
||||
.dynamic : { *(.dynamic) } :rodata :dyn
|
||||
.dynsym : { *(.dynsym) } :rodata
|
||||
.dynstr : { *(.dynstr) } :rodata
|
||||
.rela.dyn : { *(.rela.*) } :rodata
|
||||
.interp : { *(.interp) } :rodata
|
||||
.hash : { *(.hash) } :rodata
|
||||
.gnu.hash : { *(.gnu.hash) } :rodata
|
||||
.gnu.version : { *(.gnu.version) } :rodata
|
||||
.gnu.version_d : { *(.gnu.version_d) } :rodata
|
||||
.gnu.version_r : { *(.gnu.version_r) } :rodata
|
||||
.note.gnu.build-id : { *(.note.gnu.build-id) } :rodata
|
||||
|
||||
/* =========== DATA section =========== */
|
||||
. = ALIGN(0x1000);
|
||||
__data_start = . ;
|
||||
|
||||
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } :data
|
||||
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } :data
|
||||
.gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } : data
|
||||
.exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } :data
|
||||
|
||||
.tdata ALIGN(8) :
|
||||
{
|
||||
__tdata_lma = .;
|
||||
*(.tdata .tdata.* .gnu.linkonce.td.*)
|
||||
. = ALIGN(8);
|
||||
__tdata_lma_end = .;
|
||||
} :data
|
||||
|
||||
.tbss ALIGN(8) :
|
||||
{
|
||||
*(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
|
||||
. = ALIGN(8);
|
||||
} :data
|
||||
|
||||
.preinit_array ALIGN(8) :
|
||||
{
|
||||
PROVIDE (__preinit_array_start = .);
|
||||
KEEP (*(.preinit_array))
|
||||
PROVIDE (__preinit_array_end = .);
|
||||
} :data
|
||||
|
||||
.init_array ALIGN(8) :
|
||||
{
|
||||
PROVIDE (__init_array_start = .);
|
||||
KEEP( *(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)) )
|
||||
KEEP( *(.init_array .ctors) )
|
||||
PROVIDE (__init_array_end = .);
|
||||
} :data
|
||||
|
||||
.fini_array ALIGN(8) :
|
||||
{
|
||||
PROVIDE (__fini_array_start = .);
|
||||
KEEP( *(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)) )
|
||||
KEEP( *(.fini_array .dtors) )
|
||||
PROVIDE (__fini_array_end = .);
|
||||
} :data
|
||||
|
||||
__got_start__ = .;
|
||||
|
||||
.got : { *(.got) *(.igot) } :data
|
||||
.got.plt : { *(.got.plt) *(.igot.plt) } :data
|
||||
|
||||
__got_end__ = .;
|
||||
|
||||
.data ALIGN(8) :
|
||||
{
|
||||
*(.data .data.* .gnu.linkonce.d.*)
|
||||
SORT(CONSTRUCTORS)
|
||||
} :data
|
||||
|
||||
__bss_start__ = .;
|
||||
.bss ALIGN(8) :
|
||||
{
|
||||
HIDDEN(__bss_start = .);
|
||||
*(.dynbss)
|
||||
*(.bss .bss.* .gnu.linkonce.b.*)
|
||||
*(COMMON)
|
||||
. = ALIGN(8);
|
||||
|
||||
/* Reserve space for the TLS segment of the main thread */
|
||||
__tls_start = .;
|
||||
. += + SIZEOF(.tdata) + SIZEOF(.tbss);
|
||||
__tls_end = .;
|
||||
HIDDEN(__bss_end = .);
|
||||
} :data
|
||||
__bss_end__ = .;
|
||||
|
||||
__end__ = ABSOLUTE(.) ;
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
__argdata__ = ABSOLUTE(.) ;
|
||||
|
||||
/* ==================
|
||||
==== Metadata ====
|
||||
================== */
|
||||
|
||||
/* Discard sections that difficult post-processing */
|
||||
/DISCARD/ : { *(.group .comment .note) }
|
||||
|
||||
/* Stabs debugging sections. */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
|
||||
/* DWARF debug sections.
|
||||
Symbols in the DWARF debugging sections are relative to the beginning
|
||||
of the section so we begin them at 0. */
|
||||
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
%rename link old_link
|
||||
|
||||
*link:
|
||||
%(old_link) -pie --no-dynamic-linker --spare-dynamic-tags=0 -z text -z nodynamic-undefined-weak --build-id=sha1 --nx-module-name
|
||||
|
||||
*startfile:
|
||||
crti%O%s crtbegin%O%s --require-defined=main
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
println!("cargo:rustc-link-lib=dylib=nx");
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")" || exit
|
||||
set -e
|
||||
|
||||
DARK_GRAY=$(tput setaf 8)
|
||||
YELLOW=$(tput bold)$(tput setaf 3)
|
||||
RESET=$(tput sgr0)
|
||||
|
||||
function message() {
|
||||
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
|
||||
}
|
||||
|
||||
message "Compiling shaders..."
|
||||
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
|
||||
|
||||
message "Building crate..."
|
||||
rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json
|
||||
|
||||
rm -f target/aarch64-nintendo-switch/debug/drshorizon.nro
|
||||
rm -f target/aarch64-nintendo-switch/debug/drshorizon.nacp
|
||||
|
||||
message "Creating NACP..."
|
||||
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.101.0' target/aarch64-nintendo-switch/debug/drshorizon.nacp
|
||||
|
||||
message "Running elf2nro..."
|
||||
elf2nro target/aarch64-nintendo-switch/debug/drshorizon.elf target/aarch64-nintendo-switch/debug/drshorizon.nro \
|
||||
--icon=../res/nx_icon.jpg \
|
||||
--nacp=target/aarch64-nintendo-switch/debug/drshorizon.nacp
|
||||
|
||||
message "done!"
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")" || exit
|
||||
set -e
|
||||
|
||||
DARK_GRAY=$(tput setaf 8)
|
||||
YELLOW=$(tput bold)$(tput setaf 3)
|
||||
RESET=$(tput sgr0)
|
||||
|
||||
function message() {
|
||||
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
|
||||
}
|
||||
|
||||
message "Compiling shaders..."
|
||||
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
|
||||
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
|
||||
|
||||
message "Building crate..."
|
||||
rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json --release
|
||||
|
||||
rm -f target/aarch64-nintendo-switch/release/drshorizon.nro
|
||||
rm -f target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "Creating NACP..."
|
||||
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.101.0' target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "Running elf2nro..."
|
||||
elf2nro target/aarch64-nintendo-switch/release/drshorizon.elf target/aarch64-nintendo-switch/release/drshorizon.nro \
|
||||
--icon=../res/nx_icon.jpg \
|
||||
--nacp=target/aarch64-nintendo-switch/release/drshorizon.nacp
|
||||
|
||||
message "done!"
|
|
@ -0,0 +1,47 @@
|
|||
//#![feature(restricted_std)]
|
||||
|
||||
#[repr(C)]
|
||||
pub struct PrintConsole {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ApmCpuBoostMode {
|
||||
Normal = 0,
|
||||
FastLoad = 1,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn consoleInit(unk: *mut PrintConsole) -> *mut PrintConsole;
|
||||
fn consoleUpdate(unk: *mut PrintConsole);
|
||||
|
||||
fn socketInitialize(unk: *const std::ffi::c_void) -> u32;
|
||||
fn nxlinkConnectToHost(redir_stdout: bool, redir_stderr: bool) -> i32;
|
||||
|
||||
fn appletSetCpuBoostMode(mode: ApmCpuBoostMode) -> u32;
|
||||
|
||||
static __text_start: u32;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
// if socketInitialize(std::ptr::null()) == 0 {
|
||||
// nxlinkConnectToHost(true, true);
|
||||
// }
|
||||
|
||||
// appletSetCpuBoostMode(ApmCpuBoostMode::FastLoad);
|
||||
|
||||
std::env::set_var("RUST_BACKTRACE", "full");
|
||||
|
||||
println!("__text_start = {:#x}", (&__text_start) as *const _ as usize);
|
||||
|
||||
let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false };
|
||||
let result = doukutsu_rs::game::init(options);
|
||||
|
||||
if let Err(e) = result {
|
||||
println!("Initialization error: {}", e);
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// <reference types="node" />
|
||||
|
||||
const readline = require('readline');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
terminal: false
|
||||
});
|
||||
|
||||
let textStart = 0;
|
||||
const textStartRegex = /__text_start = 0x([0-9a-f]+)/i;
|
||||
let symbolize = false;
|
||||
|
||||
if (process.argv.length <= 2) {
|
||||
console.error('Usage: node symbolize.js <path to ELF file>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const elfPath = process.argv[2];
|
||||
|
||||
rl.on('line', (line) => {
|
||||
if (textStart === 0) {
|
||||
const match = textStartRegex.exec(line);
|
||||
if (match) {
|
||||
textStart = parseInt(match[1], 16);
|
||||
}
|
||||
}
|
||||
|
||||
if (line.includes("stack backtrace:")) {
|
||||
symbolize = true;
|
||||
}
|
||||
|
||||
if (symbolize) {
|
||||
const match = /0x([0-9a-f]+) - \<unknown\>/.exec(line);
|
||||
if (match) {
|
||||
const addr = parseInt(match[1], 16);
|
||||
const relative = addr - textStart;
|
||||
// run addr2line on the address
|
||||
const addr2line = childProcess.spawnSync('addr2line', ['-e', elfPath, '-j', '.text', '-f', '-C', '0x' + relative.toString(16)]);
|
||||
if (addr2line.status === 0) {
|
||||
const output = addr2line.stdout.toString();
|
||||
const lines = output.split('\n');
|
||||
const [func, file] = lines;
|
||||
line = line.replace(match[0], `0x${addr.toString(16)} - ${func} (${file})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(line);
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
curl -o ./src/data/builtin/gamecontrollerdb.txt https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -1,4 +1,4 @@
|
|||
edition = "2018"
|
||||
edition = "2021"
|
||||
max_width = 120
|
||||
use_small_heuristics = "Max"
|
||||
newline_style = "Unix"
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BmChar {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub xoffset: i16,
|
||||
pub yoffset: i16,
|
||||
pub xadvance: i16,
|
||||
pub page: u8,
|
||||
pub chnl: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BMFont {
|
||||
pub pages: u16,
|
||||
pub font_size: i16,
|
||||
pub line_height: u16,
|
||||
pub base: u16,
|
||||
pub chars: HashMap<char, BmChar>,
|
||||
}
|
||||
|
||||
const MAGIC: [u8; 4] = [b'B', b'M', b'F', 3];
|
||||
|
||||
impl BMFont {
|
||||
pub fn load_from<R: io::Read + io::Seek>(mut data: R) -> GameResult<Self> {
|
||||
let mut magic = [0u8; 4];
|
||||
let mut pages = 0u16;
|
||||
let mut chars = HashMap::with_capacity(128);
|
||||
let mut font_size = 0i16;
|
||||
let mut line_height = 0u16;
|
||||
let mut base = 0u16;
|
||||
|
||||
data.read_exact(&mut magic)?;
|
||||
|
||||
if magic != MAGIC {
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
while let Ok(block_type) = data.read_u8() {
|
||||
let length = data.read_u32::<LE>()?;
|
||||
match block_type {
|
||||
1 => {
|
||||
font_size = data.read_i16::<LE>()?;
|
||||
|
||||
data.seek(io::SeekFrom::Current(length as i64 - 2))?;
|
||||
}
|
||||
2 => {
|
||||
line_height = data.read_u16::<LE>()?;
|
||||
base = data.read_u16::<LE>()?;
|
||||
data.seek(io::SeekFrom::Current(4))?;
|
||||
pages = data.read_u16::<LE>()?;
|
||||
|
||||
data.seek(io::SeekFrom::Current(length as i64 - 10))?;
|
||||
}
|
||||
3 | 5 => {
|
||||
data.seek(io::SeekFrom::Current(length as i64))?;
|
||||
}
|
||||
4 => {
|
||||
let count = length / 20;
|
||||
for _ in 0..count {
|
||||
let id = data.read_u32::<LE>()?;
|
||||
let x = data.read_u16::<LE>()?;
|
||||
let y = data.read_u16::<LE>()?;
|
||||
let width = data.read_u16::<LE>()?;
|
||||
let height = data.read_u16::<LE>()?;
|
||||
let xoffset = data.read_i16::<LE>()?;
|
||||
let yoffset = data.read_i16::<LE>()?;
|
||||
let xadvance = data.read_i16::<LE>()?;
|
||||
let page = data.read_u8()?;
|
||||
let chnl = data.read_u8()?;
|
||||
|
||||
if let Some(chr) = std::char::from_u32(id) {
|
||||
chars.insert(chr, BmChar { x, y, width, height, xoffset, yoffset, xadvance, page, chnl });
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ResourceLoadError("Unknown block type.".to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { pages, font_size, line_height, base, chars })
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::bmfont::BMFont;
|
||||
use crate::common::{Rect, FILE_TYPES};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::texture_set::TextureSet;
|
||||
|
||||
pub struct BMFontRenderer {
|
||||
font: BMFont,
|
||||
pages: Vec<String>,
|
||||
}
|
||||
|
||||
impl BMFontRenderer {
|
||||
pub fn load(roots: &Vec<String>, desc_path: &str, ctx: &mut Context) -> GameResult<BMFontRenderer> {
|
||||
let full_path = PathBuf::from(desc_path);
|
||||
let desc_stem =
|
||||
full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?;
|
||||
let stem = full_path.parent().unwrap_or(&full_path).join(desc_stem);
|
||||
|
||||
let font = BMFont::load_from(filesystem::open_find(ctx, roots, &full_path)?)?;
|
||||
let mut pages = Vec::new();
|
||||
|
||||
let (zeros, _, _) = FILE_TYPES
|
||||
.iter()
|
||||
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
|
||||
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
|
||||
.or_else(|| {
|
||||
FILE_TYPES
|
||||
.iter()
|
||||
.map(|ext| (2, ext, format!("{}_00{}", stem.to_string_lossy(), ext)))
|
||||
.find(|(_, _, path)| filesystem::exists_find(ctx, roots, &path))
|
||||
})
|
||||
.ok_or_else(|| ResourceLoadError(format!("Cannot find glyph atlas 0 for font: {:?}", desc_path)))?;
|
||||
|
||||
for i in 0..font.pages {
|
||||
let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros);
|
||||
|
||||
pages.push(page_path);
|
||||
}
|
||||
|
||||
Ok(Self { font, pages })
|
||||
}
|
||||
|
||||
pub fn line_height(&self, constants: &EngineConstants) -> f32 {
|
||||
self.font.line_height as f32 * constants.font_scale
|
||||
}
|
||||
|
||||
pub fn text_width<I: Iterator<Item = char>>(&self, iter: I, constants: &EngineConstants) -> f32 {
|
||||
let mut offset_x = 0.0;
|
||||
|
||||
for chr in iter {
|
||||
if let Some(glyph) = self.font.chars.get(&chr) {
|
||||
offset_x += glyph.xadvance as f32 * constants.font_scale;
|
||||
}
|
||||
}
|
||||
|
||||
offset_x
|
||||
}
|
||||
|
||||
pub fn draw_text<I: Iterator<Item = char>>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_text_with_shadow<I: Iterator<Item = char> + Clone>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text(iter.clone(), x + 1.0, y + 1.0, (0, 0, 0, 150), constants, texture_set, ctx)?;
|
||||
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_colored_text_with_shadow_scaled<I: Iterator<Item = char> + Clone>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
scale: f32,
|
||||
color: (u8, u8, u8, u8),
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text_scaled(
|
||||
iter.clone(),
|
||||
x + scale,
|
||||
y + scale,
|
||||
scale,
|
||||
(0, 0, 0, 150),
|
||||
constants,
|
||||
texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
self.draw_colored_text_scaled(iter, x, y, scale, color, constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
pub fn draw_colored_text_scaled<I: Iterator<Item = char>>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
scale: f32,
|
||||
color: (u8, u8, u8, u8),
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
if self.pages.len() == 1 {
|
||||
let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?;
|
||||
let mut offset_x = x;
|
||||
|
||||
for chr in iter {
|
||||
if let Some(glyph) = self.font.chars.get(&chr) {
|
||||
batch.add_rect_scaled_tinted(
|
||||
offset_x + (glyph.xoffset as f32 * constants.font_scale),
|
||||
y + (glyph.yoffset as f32 * constants.font_scale),
|
||||
color,
|
||||
constants.font_scale,
|
||||
constants.font_scale,
|
||||
&Rect::new_size(glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16),
|
||||
);
|
||||
|
||||
offset_x += glyph.xadvance as f32 * constants.font_scale;
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
} else {
|
||||
let mut pages = HashSet::new();
|
||||
let mut chars = Vec::new();
|
||||
|
||||
for chr in iter {
|
||||
if let Some(glyph) = self.font.chars.get(&chr) {
|
||||
pages.insert(glyph.page);
|
||||
chars.push((chr, glyph));
|
||||
}
|
||||
}
|
||||
|
||||
for page in pages {
|
||||
let page_tex = if let Some(p) = self.pages.get(page as usize) {
|
||||
p
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let batch = texture_set.get_or_load_batch(ctx, constants, page_tex)?;
|
||||
let mut offset_x = x;
|
||||
|
||||
for (_chr, glyph) in chars.iter() {
|
||||
if glyph.page == page {
|
||||
batch.add_rect_scaled_tinted(
|
||||
offset_x + (glyph.xoffset as f32 * constants.font_scale),
|
||||
y + (glyph.yoffset as f32 * constants.font_scale),
|
||||
color,
|
||||
constants.font_scale * scale,
|
||||
constants.font_scale * scale,
|
||||
&Rect::new_size(glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16),
|
||||
);
|
||||
}
|
||||
|
||||
offset_x += scale * (glyph.xadvance as f32 * constants.font_scale);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_colored_text<I: Iterator<Item = char>>(
|
||||
&self,
|
||||
iter: I,
|
||||
x: f32,
|
||||
y: f32,
|
||||
color: (u8, u8, u8, u8),
|
||||
constants: &EngineConstants,
|
||||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text_scaled(iter, x, y, 1.0, color, constants, texture_set, ctx)
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
{
|
||||
"common": {
|
||||
"name": "doukutsu-rs",
|
||||
"back": "< Back",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"on": "ON",
|
||||
"off": "OFF"
|
||||
},
|
||||
|
||||
"menus": {
|
||||
"main_menu": {
|
||||
"start": "Start Game",
|
||||
"challenges": "Challenges",
|
||||
"options": "Options",
|
||||
"editor": "Editor",
|
||||
"jukebox": "Jukebox",
|
||||
"quit": "Quit"
|
||||
},
|
||||
|
||||
"pause_menu": {
|
||||
"resume": "Resume",
|
||||
"retry": "Retry",
|
||||
"options": "Options",
|
||||
"title": "Title",
|
||||
"title_confirm": "Title?",
|
||||
"quit": "Quit",
|
||||
"quit_confirm": "Quit?"
|
||||
},
|
||||
|
||||
"save_menu": {
|
||||
"new": "New Save",
|
||||
"delete_info": "Press Right to Delete",
|
||||
"delete_confirm": "Delete?"
|
||||
},
|
||||
|
||||
"difficulty_menu": {
|
||||
"title": "Select Difficulty",
|
||||
"easy": "Easy",
|
||||
"normal": "Normal",
|
||||
"hard": "Hard"
|
||||
},
|
||||
|
||||
"challenge_menu": {
|
||||
"start": "Start",
|
||||
"no_replay": "No Replay",
|
||||
"replay_best": "Replay Best",
|
||||
"delete_replay": "Delete Replay"
|
||||
},
|
||||
|
||||
"options_menu": {
|
||||
"graphics": "Graphics...",
|
||||
"graphics_menu": {
|
||||
"lighting_effects": "Lighting effects:",
|
||||
"weapon_light_cone": "Weapon light cone:",
|
||||
"motion_interpolation": "Motion interpolation:",
|
||||
"subpixel_scrolling": "Subpixel scrolling:",
|
||||
"original_textures": "Original textures:",
|
||||
"seasonal_textures": "Seasonal textures:",
|
||||
"renderer": "Renderer:",
|
||||
"vsync_mode": {
|
||||
"entry": "V-Sync:",
|
||||
"uncapped": "Uncapped",
|
||||
"uncapped_desc": "V-Sync Off.",
|
||||
"vsync": "Enabled",
|
||||
"vsync_desc": "V-Sync On.",
|
||||
"vrr_1x": "Variable Refresh Rate (1x)",
|
||||
"vrr_1x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_2x": "Variable Refresh Rate (2x)",
|
||||
"vrr_2x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_3x": "Variable Refresh Rate (3x)",
|
||||
"vrr_3x_desc": "Uses (G-/Free)Sync if available."
|
||||
}
|
||||
},
|
||||
|
||||
"sound": "Sound...",
|
||||
"sound_menu": {
|
||||
"music_volume": "Music Volume",
|
||||
"effects_volume": "Effects Volume",
|
||||
"bgm_interpolation": {
|
||||
"entry": "BGM Interpolation:",
|
||||
"linear": "Linear",
|
||||
"linear_desc": "Fast, similar to freeware on Vista+",
|
||||
"cosine": "Cosine",
|
||||
"cosine_desc": "Cosine interpolation",
|
||||
"cubic": "Cubic",
|
||||
"cubic_desc": "Cubic interpolation",
|
||||
"linear_lp": "Linear+LP",
|
||||
"linear_lp_desc": "Slowest, similar to freeware on XP",
|
||||
"nearest": "Nearest",
|
||||
"nearest_desc": "Fastest, lowest quality"
|
||||
},
|
||||
"soundtrack": "Soundtrack: {soundtrack}"
|
||||
},
|
||||
|
||||
"language": "Language...",
|
||||
|
||||
"game_timing": {
|
||||
"entry": "Game timing:",
|
||||
"50tps": "50tps (freeware)",
|
||||
"60tps": "60tps (CS+)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"soundtrack": {
|
||||
"organya": "Organya",
|
||||
"remastered": "Remastered",
|
||||
"new": "New",
|
||||
"famitracks": "Famitracks"
|
||||
},
|
||||
|
||||
"game": {
|
||||
"cutscene_skip": "Hold {key} to skip the cutscene"
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
{
|
||||
"common": {
|
||||
"name": "doukutsu-rs",
|
||||
"back": "< 戻る",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"on": "オン",
|
||||
"off": "オフ"
|
||||
},
|
||||
"menus": {
|
||||
"main_menu": {
|
||||
"start": "ゲームスタート",
|
||||
"challenges": "チャレンジ",
|
||||
"options": "設定",
|
||||
"editor": "レベルエディタ",
|
||||
"jukebox": "ジュークボックス",
|
||||
"quit": "辞める"
|
||||
},
|
||||
"pause_menu": {
|
||||
"resume": "再開",
|
||||
"retry": "リトライ",
|
||||
"options": "設定",
|
||||
"title": "メインメニュー",
|
||||
"title_confirm": "メインメニュー?",
|
||||
"quit": "辞める",
|
||||
"quit_confirm": "辞める?"
|
||||
},
|
||||
"save_menu": {
|
||||
"new": "新しいデータ",
|
||||
"delete_info": "右矢印キーで削除",
|
||||
"delete_confirm": "消去?"
|
||||
},
|
||||
"difficulty_menu": {
|
||||
"title": "難易度選択",
|
||||
"easy": "簡単",
|
||||
"normal": "普通",
|
||||
"hard": "難しい"
|
||||
},
|
||||
"challenge_menu": {
|
||||
"start": "スタート",
|
||||
"no_replay": "ノーリプレイ",
|
||||
"replay_best": "ベストプレイを再生",
|
||||
"delete_replay": "リプレイを削除"
|
||||
},
|
||||
"options_menu": {
|
||||
"graphics": "グラフィック",
|
||||
"graphics_menu": {
|
||||
"lighting_effects": "ライティング効果:",
|
||||
"weapon_light_cone": "兵器のライトコーン:",
|
||||
"motion_interpolation": "モーション補間:",
|
||||
"subpixel_scrolling": "サブピクセルスクロール:",
|
||||
"original_textures": "オリジナルテクスチャ:",
|
||||
"seasonal_textures": "季節ものテクスチャ:",
|
||||
"renderer": "レンダラ:",
|
||||
"vsync_mode": {
|
||||
"entry": "V-Sync:",
|
||||
"uncapped": "Uncapped",
|
||||
"uncapped_desc": "V-Sync Off.",
|
||||
"vsync": "Enabled",
|
||||
"vsync_desc": "V-Sync On.",
|
||||
"vrr_1x": "Variable Refresh Rate (1x)",
|
||||
"vrr_1x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_2x": "Variable Refresh Rate (2x)",
|
||||
"vrr_2x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_3x": "Variable Refresh Rate (3x)",
|
||||
"vrr_3x_desc": "Uses (G-/Free)Sync if available."
|
||||
}
|
||||
},
|
||||
"sound": "サウンド",
|
||||
"sound_menu": {
|
||||
"music_volume": "BGM音量",
|
||||
"effects_volume": "サウンド音量",
|
||||
"bgm_interpolation": {
|
||||
"entry": "BGM内挿:",
|
||||
"linear": "線形補間",
|
||||
"linear_desc": "速い、フリーウェア版に近い(Vista+)",
|
||||
"cosine": "余弦",
|
||||
"cosine_desc": "余弦補間",
|
||||
"cubic": "立方体",
|
||||
"cubic_desc": "立方体補間",
|
||||
"linear_lp": "線形補間+LP",
|
||||
"linear_lp_desc": "最も遅い、フリーウェア版に近い(XP)",
|
||||
"nearest": "最近傍",
|
||||
"nearest_desc": "最速、最低品質"
|
||||
},
|
||||
"soundtrack": "サウンドトラック: {soundtrack}"
|
||||
},
|
||||
"language": "言語",
|
||||
"game_timing": {
|
||||
"entry": "ゲームのタイミング:",
|
||||
"50tps": "50tps (freeware)",
|
||||
"60tps": "60tps (CS+)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"soundtrack": {
|
||||
"organya": "オルガーニャ",
|
||||
"remastered": "リマスター",
|
||||
"new": "新",
|
||||
"famitracks": "ファミトラック"
|
||||
},
|
||||
"game": {
|
||||
"cutscene_skip": "{key} を押し続け、カットシーンをスキップ"
|
||||
}
|
||||
}
|
|
@ -3,12 +3,12 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::{abs, Num};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::de::{SeqAccess, Visitor};
|
||||
use serde::ser::SerializeTupleStruct;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::bitfield;
|
||||
use crate::texture_set::G_MAG;
|
||||
use crate::graphics::texture_set::G_MAG;
|
||||
|
||||
/// Multiply cave story degrees (0-255, which corresponds to 0°-360°) with this constant to get
|
||||
/// respective value in radians.
|
||||
|
@ -47,7 +47,7 @@ bitfield! {
|
|||
pub in_water, set_in_water: 8;
|
||||
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
|
||||
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
||||
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
||||
pub bloody_droplets, set_bloody_droplets: 11; // 0x800
|
||||
pub force_left, set_force_left: 12; // 0x1000
|
||||
pub force_up, set_force_up: 13; // 0x2000
|
||||
pub force_right, set_force_right: 14; // 0x4000
|
||||
|
@ -302,8 +302,8 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
|||
|
||||
impl<T: Num + PartialOrd + Copy + Serialize> Serialize for Rect<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_tuple_struct("Rect", 4)?;
|
||||
state.serialize_field(&self.left)?;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::frame::Frame;
|
||||
use crate::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
use crate::{graphics, Context, GameResult, SharedGameState};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
|
||||
pub struct Background {
|
||||
pub tick: usize,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::common::Rect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::npc::boss::BossNPC;
|
||||
use crate::game::npc::list::NPCList;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
|
@ -25,13 +25,7 @@ pub struct BossLifeBar {
|
|||
|
||||
impl BossLifeBar {
|
||||
pub fn new() -> BossLifeBar {
|
||||
BossLifeBar {
|
||||
target: BossLifeTarget::None,
|
||||
life: 0,
|
||||
max_life: 0,
|
||||
prev_life: 0,
|
||||
counter: 0,
|
||||
}
|
||||
BossLifeBar { target: BossLifeTarget::None, life: 0, max_life: 0, prev_life: 0, counter: 0 }
|
||||
}
|
||||
|
||||
pub fn set_npc_target(&mut self, npc_id: u16, npc_list: &NPCList) {
|
||||
|
@ -49,6 +43,91 @@ impl BossLifeBar {
|
|||
self.max_life = self.life;
|
||||
self.prev_life = self.life;
|
||||
}
|
||||
|
||||
fn draw_regular(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let box_length = 256;
|
||||
let bar_length = box_length - 58;
|
||||
|
||||
let text_rect = Rect::new_size(0, 48, 32, 8);
|
||||
let box_rect1 = Rect::new_size(0, 0, 244, 8);
|
||||
let box_rect2 = Rect::new_size(0, 16, 244, 8);
|
||||
let mut rect_prev_bar = Rect::new_size(0, 32, 232, 8);
|
||||
let mut rect_life_bar = Rect::new_size(0, 24, 232, 8);
|
||||
|
||||
rect_prev_bar.right = ((self.prev_life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
rect_life_bar.right = ((self.life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0,
|
||||
&box_rect1,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 12.0,
|
||||
&box_rect2,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0,
|
||||
&box_rect1,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0,
|
||||
&rect_prev_bar,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0,
|
||||
&rect_life_bar,
|
||||
);
|
||||
batch.add_rect(
|
||||
((state.canvas_size.0 - box_length as f32) / 2.0 + 8.0).floor(),
|
||||
state.canvas_size.1 - 16.0,
|
||||
&text_rect,
|
||||
);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_nx(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let box_length = 148;
|
||||
let bar_length = box_length - 52;
|
||||
|
||||
let text_rect = Rect::new_size(0, 48, 32, 8);
|
||||
let box_rect1 = Rect::new_size(0, 0, 136, 8);
|
||||
let box_rect2 = Rect::new_size(0, 16, 136, 8);
|
||||
let box_rect3 = Rect::new_size(238, 0, 6, 8);
|
||||
let box_rect4 = Rect::new_size(238, 16, 6, 8);
|
||||
let mut rect_prev_bar = Rect::new_size(0, 32, 124, 8);
|
||||
let mut rect_life_bar = Rect::new_size(0, 24, 124, 8);
|
||||
|
||||
rect_prev_bar.right = ((self.prev_life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
rect_life_bar.right = ((self.life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
|
||||
let base_x = state.canvas_size.0 - box_length as f32;
|
||||
|
||||
batch.add_rect((base_x - 6.0).floor(), state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect((base_x - 6.0).floor(), state.canvas_size.1 - 12.0, &box_rect2);
|
||||
batch.add_rect((base_x - 6.0).floor(), state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect((state.canvas_size.0 - 18.0).floor(), state.canvas_size.1 - 20.0, &box_rect3);
|
||||
batch.add_rect((state.canvas_size.0 - 18.0).floor(), state.canvas_size.1 - 12.0, &box_rect4);
|
||||
batch.add_rect((state.canvas_size.0 - 18.0).floor(), state.canvas_size.1 - 20.0, &box_rect3);
|
||||
batch.add_rect((base_x + 34.0).floor(), state.canvas_size.1 - 16.0, &rect_prev_bar);
|
||||
batch.add_rect((base_x + 34.0).floor(), state.canvas_size.1 - 16.0, &rect_life_bar);
|
||||
batch.add_rect((base_x + 2.0).floor(), state.canvas_size.1 - 16.0, &text_rect);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar {
|
||||
|
@ -86,35 +165,9 @@ impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let box_length = 256;
|
||||
let bar_length = box_length - 58;
|
||||
|
||||
let text_rect = Rect::new_size(0, 48, 32, 8);
|
||||
let box_rect1 = Rect::new_size(0, 0, 244, 8);
|
||||
let box_rect2 = Rect::new_size(0, 16, 244, 8);
|
||||
let mut rect_prev_bar = Rect::new_size(0, 32, 232, 8);
|
||||
let mut rect_life_bar = Rect::new_size(0, 24, 232, 8);
|
||||
|
||||
rect_prev_bar.right = ((self.prev_life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
rect_life_bar.right = ((self.life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 12.0, &box_rect2);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0, &rect_prev_bar);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(),
|
||||
state.canvas_size.1 - 16.0, &rect_life_bar);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 8.0).floor(),
|
||||
state.canvas_size.1 - 16.0, &text_rect);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
match state.constants.is_switch {
|
||||
true => self.draw_nx(state, ctx, _frame),
|
||||
false => self.draw_regular(state, ctx, _frame),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
use crate::entity::GameEntity;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::graphics::font::Font;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CompactJukebox {
|
||||
song_id: usize,
|
||||
shown: bool,
|
||||
}
|
||||
|
||||
impl CompactJukebox {
|
||||
pub fn new() -> CompactJukebox {
|
||||
CompactJukebox { song_id: 0, shown: false }
|
||||
}
|
||||
|
||||
pub fn change_song(&mut self, song_id: usize, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.song_id = song_id;
|
||||
|
||||
if self.song_id == state.sound_manager.current_song() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx, false);
|
||||
}
|
||||
|
||||
pub fn next_song(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let mut new_song_id = if self.song_id == state.constants.music_table.len() - 1 { 1 } else { self.song_id + 1 };
|
||||
|
||||
// skip ika if soundtrack is not set to new
|
||||
if self.is_ika_unavailable(new_song_id, state) {
|
||||
new_song_id = 1;
|
||||
}
|
||||
|
||||
self.change_song(new_song_id, state, ctx)
|
||||
}
|
||||
|
||||
pub fn prev_song(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let mut new_song_id = if self.song_id == 1 { state.constants.music_table.len() - 1 } else { self.song_id - 1 };
|
||||
|
||||
// skip ika if soundtrack is not set to new
|
||||
if self.is_ika_unavailable(new_song_id, state) {
|
||||
new_song_id = 42;
|
||||
}
|
||||
|
||||
self.change_song(new_song_id, state, ctx)
|
||||
}
|
||||
|
||||
pub fn show(&mut self) {
|
||||
self.shown = true;
|
||||
}
|
||||
|
||||
pub fn is_shown(&self) -> bool {
|
||||
self.shown
|
||||
}
|
||||
|
||||
fn is_ika_unavailable(&self, song_id: usize, state: &SharedGameState) -> bool {
|
||||
song_id == 43 && state.settings.soundtrack != "New"
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<&mut Context> for CompactJukebox {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !self.shown {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let text = format!("< {:02} >", self.song_id);
|
||||
|
||||
let font_builder = state.font.builder();
|
||||
let text_width = font_builder.compute_width(&text);
|
||||
|
||||
let x = state.canvas_size.0 as f32 - text_width - 15.0;
|
||||
let y = state.canvas_size.1 as f32 - 15.0;
|
||||
|
||||
font_builder.x(x).y(y).shadow(true).draw(&text, ctx, &state.constants, &mut state.texture_set)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::scripting::tsc::text_script::IllustrationState;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::scripting::tsc::text_script::IllustrationState;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::graphics::font::Font;
|
||||
|
||||
pub struct Credits {}
|
||||
|
||||
|
@ -65,18 +66,59 @@ impl GameEntity<()> for Credits {
|
|||
let y = ((line.cast_id / 13) & 0xff) * 24;
|
||||
let rect = Rect::new_size(x, y, 24, 24);
|
||||
|
||||
batch.add_rect(line.pos_x - 24.0, line.pos_y - 8.0, &rect);
|
||||
if state.more_rust && line.cast_id == 1 {
|
||||
// sue with more rust
|
||||
batch.add_rect_tinted(line.pos_x - 24.0, line.pos_y - 8.0, (200, 200, 255, 255), &rect);
|
||||
} else {
|
||||
batch.add_rect(line.pos_x - 24.0, line.pos_y - 8.0, &rect);
|
||||
}
|
||||
}
|
||||
batch.draw(ctx)?;
|
||||
|
||||
if state.more_rust {
|
||||
// draw sue's headband separately because rust doesn't let me mutate the texture set multiple times at once
|
||||
|
||||
let headband_spritesheet = {
|
||||
let base = if state.settings.original_textures { "ogph" } else { "plus" };
|
||||
format!("headband/{}/Casts", base)
|
||||
};
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, headband_spritesheet.as_str())?;
|
||||
|
||||
for line in &state.creditscript_vm.lines {
|
||||
if line.cast_id != 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let x = (line.cast_id % 13) * 24;
|
||||
let y = ((line.cast_id / 13) & 0xff) * 24;
|
||||
let rect = Rect::new_size(x, y, 24, 24);
|
||||
|
||||
batch.add_rect(line.pos_x - 24.0, line.pos_y - 8.0, &rect);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
for line in &state.creditscript_vm.lines {
|
||||
state.font.draw_text_with_shadow(
|
||||
line.text.chars(),
|
||||
line.pos_x,
|
||||
line.pos_y,
|
||||
let mut text_ovr = None;
|
||||
|
||||
if state.more_rust {
|
||||
text_ovr = Some(line.text.replace("Sue Sakamoto", "Crabby Sue"));
|
||||
}
|
||||
|
||||
let mut text = line.text.as_str();
|
||||
if let Some(ovr) = text_ovr.as_ref() {
|
||||
text = ovr.as_str();
|
||||
}
|
||||
|
||||
state.font.builder().position(line.pos_x, line.pos_y).shadow(true).draw(
|
||||
text,
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::Rect;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum Alignment {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::common::{FadeDirection, FadeState, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
pub struct Fade;
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@ use std::ops::Deref;
|
|||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::graphics;
|
||||
use crate::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
|
||||
pub struct FallingIsland {}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
pub enum FlashState {
|
||||
None,
|
||||
|
@ -83,7 +83,7 @@ impl GameEntity<()> for Flash {
|
|||
left: 0,
|
||||
top: ((cen_y - width) * state.scale) as isize,
|
||||
right: (state.canvas_size.0 * state.scale) as isize,
|
||||
bottom: ((cen_y + width) * state.scale) as isize
|
||||
bottom: ((cen_y + width) * state.scale) as isize,
|
||||
};
|
||||
|
||||
graphics::draw_rect(ctx, rect, WHITE)?;
|
||||
|
@ -93,7 +93,7 @@ impl GameEntity<()> for Flash {
|
|||
left: ((cen_x - width) * state.scale) as isize,
|
||||
top: 0,
|
||||
right: ((cen_x + width) * state.scale) as isize,
|
||||
bottom: (state.canvas_size.1 * state.scale) as isize
|
||||
bottom: (state.canvas_size.1 * state.scale) as isize,
|
||||
};
|
||||
|
||||
graphics::draw_rect(ctx, rect, WHITE)?;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::components::draw_common::{Alignment, draw_number};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::screen_insets_scaled;
|
||||
use crate::inventory::Inventory;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::inventory::Inventory;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::player::Player;
|
||||
use crate::game::weapon::WeaponType;
|
||||
|
||||
pub struct HUD {
|
||||
pub alignment: Alignment,
|
||||
|
@ -111,12 +112,26 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
if player.controller.trigger_next_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.next_weapon();
|
||||
|
||||
if let Some(weapon) = inventory.get_current_weapon_mut() {
|
||||
if weapon.wtype == WeaponType::Spur {
|
||||
weapon.reset_xp();
|
||||
}
|
||||
}
|
||||
|
||||
self.weapon_x_pos = 32;
|
||||
}
|
||||
|
||||
if player.controller.trigger_prev_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.prev_weapon();
|
||||
|
||||
if let Some(weapon) = inventory.get_current_weapon_mut() {
|
||||
if weapon.wtype == WeaponType::Spur {
|
||||
weapon.reset_xp();
|
||||
}
|
||||
}
|
||||
|
||||
self.weapon_x_pos = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::components::draw_common::{Alignment, draw_number};
|
||||
use crate::components::hud::HUD;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::inventory::Inventory;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::Inventory;
|
||||
use crate::player::Player;
|
||||
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::weapon::{WeaponLevel, WeaponType};
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState};
|
||||
use crate::game::weapon::{WeaponLevel, WeaponType};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
|
@ -91,9 +91,9 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory, &mut HUD)> for Inven
|
|||
|
||||
if state.control_flags.control_enabled()
|
||||
&& (player.controller.trigger_inventory()
|
||||
|| player.controller.trigger_menu_back()
|
||||
|| (player.controller.trigger_menu_ok() && self.focus == InventoryFocus::Weapons)
|
||||
|| (state.settings.touch_controls && state.touch_controls.consume_click_in(slot_rect)))
|
||||
|| player.controller.trigger_menu_back()
|
||||
|| (player.controller.trigger_menu_ok() && self.focus == InventoryFocus::Weapons)
|
||||
|| (state.settings.touch_controls && state.touch_controls.consume_click_in(slot_rect)))
|
||||
{
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
self.exit(state, player, inventory, hud);
|
||||
|
@ -140,24 +140,36 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory, &mut HUD)> for Inven
|
|||
InventoryFocus::None => {
|
||||
self.focus = InventoryFocus::Weapons;
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
// check weapon count (0 count means we run item script)
|
||||
let event = if self.weapon_count > 0 {
|
||||
self.get_weapon_event_number(inventory)
|
||||
} else {
|
||||
self.get_item_event_number(inventory)
|
||||
};
|
||||
state.textscript_vm.start_script(event);
|
||||
}
|
||||
InventoryFocus::Weapons if state.control_flags.control_enabled() => {
|
||||
if player.controller.trigger_left() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.prev_weapon();
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
|
||||
// if we have no weapons, the TSC should not be refreshed with L/R keystrokes
|
||||
if self.weapon_count > 0
|
||||
{
|
||||
if player.controller.trigger_left() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.prev_weapon();
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
}
|
||||
|
||||
if player.controller.trigger_right() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.next_weapon();
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
}
|
||||
}
|
||||
|
||||
if player.controller.trigger_right() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.next_weapon();
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
|
||||
}
|
||||
|
||||
if player.controller.trigger_up() || player.controller.trigger_down() {
|
||||
// we should not move from the weapon row if there are no items
|
||||
if (player.controller.trigger_up() || player.controller.trigger_down()) && self.item_count > 0 {
|
||||
self.focus = InventoryFocus::Items;
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
|
@ -310,8 +322,8 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory, &mut HUD)> for Inven
|
|||
|
||||
let (item_cursor_frame, weapon_cursor_frame) = match self.focus {
|
||||
InventoryFocus::None => (1, 1),
|
||||
InventoryFocus::Weapons => (1, self.tick & 1),
|
||||
InventoryFocus::Items => (self.tick & 1, 1),
|
||||
InventoryFocus::Weapons => (1, (self.tick / 2) % 2), //every-other frame (& 1): this is not what we want, we want every 2 frames.
|
||||
InventoryFocus::Items => ((self.tick / 2) % 2, 1),
|
||||
};
|
||||
|
||||
batch.add_rect(
|
||||
|
|
|
@ -4,12 +4,13 @@ use crate::common::{Color, Rect};
|
|||
use crate::framework::backend::{BackendTexture, SpriteBatchCommand};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::graphics;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::stage::Stage;
|
||||
use crate::graphics::font::Font;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::player::Player;
|
||||
use crate::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::shared_game_state::{Language, SharedGameState};
|
||||
use crate::stage::Stage;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum MapSystemState {
|
||||
|
@ -185,7 +186,7 @@ impl MapSystem {
|
|||
}
|
||||
|
||||
let (scr_w, scr_h) = (state.canvas_size.0 * state.scale, state.canvas_size.1 * state.scale);
|
||||
let text_height = state.font.line_height(&state.constants);
|
||||
let text_height = state.font.line_height();
|
||||
let rect_black_bar = Rect::new_size(
|
||||
0,
|
||||
(7.0 * state.scale) as _,
|
||||
|
@ -197,16 +198,18 @@ impl MapSystem {
|
|||
graphics::draw_rect(ctx, rect_black_bar, Color::new(0.0, 0.0, 0.0, 1.0))?;
|
||||
}
|
||||
|
||||
let map_name = if state.settings.locale == Language::Japanese {
|
||||
stage.data.name_jp.chars()
|
||||
let map_name = if state.constants.is_cs_plus && state.settings.locale == "jp" {
|
||||
stage.data.name_jp.as_str()
|
||||
} else {
|
||||
stage.data.name.chars()
|
||||
stage.data.name.as_str()
|
||||
};
|
||||
|
||||
let map_name_width = state.font.text_width(map_name.clone(), &state.constants);
|
||||
let map_name_off_x = (state.canvas_size.0 - map_name_width) / 2.0;
|
||||
|
||||
state.font.draw_text(map_name, map_name_off_x, 9.0, &state.constants, &mut state.texture_set, ctx)?;
|
||||
state.font.builder().center(state.canvas_size.0).y(9.0).draw(
|
||||
map_name,
|
||||
ctx,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
)?;
|
||||
|
||||
let mut map_rect = Rect::new(0.0, 0.0, self.last_size.0 as f32, self.last_size.1 as f32);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub mod background;
|
||||
pub mod boss_life_bar;
|
||||
pub mod compact_jukebox;
|
||||
pub mod credits;
|
||||
pub mod draw_common;
|
||||
pub mod fade;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||
use byteorder::{LE, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, draw_number_zeros, Alignment};
|
||||
use crate::components::draw_common::{Alignment, draw_number, draw_number_zeros};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::vfs::OpenOptions;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::shared_game_state::{SharedGameState, TimingMode};
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::{SharedGameState, TimingMode};
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::util::rng::RNG;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NikumaruCounter {
|
||||
|
@ -63,7 +63,7 @@ impl NikumaruCounter {
|
|||
let mut random_list: [u8; 4] = [0; 4];
|
||||
|
||||
for iter in 0..=3 {
|
||||
random_list[iter] = state.game_rng.range(0..250) as u8 + iter as u8;
|
||||
random_list[iter] = state.effect_rng.range(0..250) as u8 + iter as u8;
|
||||
|
||||
ticks[iter] = u32::from_le_bytes([
|
||||
ticks[iter].to_le_bytes()[0].wrapping_add(random_list[iter]),
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::common::{interpolate_fix9_scale, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct NumberPopup {
|
||||
|
@ -13,43 +13,48 @@ pub struct NumberPopup {
|
|||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
counter: u16,
|
||||
value_display: i16,
|
||||
}
|
||||
|
||||
impl NumberPopup {
|
||||
pub fn new() -> NumberPopup {
|
||||
NumberPopup { value: 0, x: 0, y: 0, prev_x: 0, prev_y: 0, counter: 0 }
|
||||
NumberPopup { value: 0, x: 0, y: 0, prev_x: 0, prev_y: 0, counter: 0, value_display: 0 }
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, value: i16) {
|
||||
if self.counter > 32 {
|
||||
self.counter = 32;
|
||||
}
|
||||
|
||||
self.value = value;
|
||||
}
|
||||
|
||||
pub fn add_value(&mut self, value: i16) {
|
||||
self.set_value(self.value + value);
|
||||
}
|
||||
|
||||
pub fn update_displayed_value(&mut self) {
|
||||
if self.counter > 32 {
|
||||
self.counter = 32;
|
||||
}
|
||||
self.value_display += self.value;
|
||||
self.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for NumberPopup {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult<()> {
|
||||
if self.value == 0 {
|
||||
if self.value_display == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.counter += 1;
|
||||
if self.counter == 80 {
|
||||
self.counter = 0;
|
||||
self.value = 0;
|
||||
self.value_display = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> {
|
||||
if self.value == 0 {
|
||||
if self.value_display == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -63,9 +68,10 @@ impl GameEntity<()> for NumberPopup {
|
|||
|
||||
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
let x = interpolate_fix9_scale(self.prev_x, self.x, state.frame_time) - frame_x;
|
||||
let y = interpolate_fix9_scale(self.prev_y, self.y, state.frame_time) - frame_y - y_offset;
|
||||
let y = interpolate_fix9_scale(self.prev_y, self.y, state.frame_time) - frame_y - y_offset
|
||||
- 3.0f32; // This is supposed to be -4, but for some reason -3 looks more accurate
|
||||
|
||||
let n = format!("{:+}", self.value);
|
||||
let n = format!("{:+}", self.value_display);
|
||||
|
||||
let x = x - n.len() as f32 * 4.0;
|
||||
|
||||
|
@ -78,7 +84,7 @@ impl GameEntity<()> for NumberPopup {
|
|||
batch.add_rect(x + offset as f32 * 8.0, y, &Rect::new_size(40, 48 + clip, 8, 8 - clip));
|
||||
}
|
||||
'0'..='9' => {
|
||||
let number_set = if self.value < 0 { 64 } else { 56 };
|
||||
let number_set = if self.value_display < 0 { 64 } else { 56 };
|
||||
let idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(
|
||||
x + offset as f32 * 8.0,
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
use std::io::{Cursor, Read};
|
||||
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||
use byteorder::{LE, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::framework::vfs::OpenOptions;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::{ReplayKind, ReplayState, SharedGameState};
|
||||
use crate::input::replay_player_controller::{KeyState, ReplayController};
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::{ReplayState, SharedGameState};
|
||||
use crate::game::player::Player;
|
||||
use crate::graphics::font::Font;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Replay {
|
||||
|
@ -46,26 +47,42 @@ impl Replay {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn stop_recording(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
pub fn stop_recording(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
is_new_record: bool,
|
||||
) -> GameResult {
|
||||
state.replay_state = ReplayState::None;
|
||||
self.write_replay(state, ctx)?;
|
||||
|
||||
self.write_replay(state, ctx, ReplayKind::Last)?;
|
||||
|
||||
if is_new_record {
|
||||
self.write_replay(state, ctx, ReplayKind::Best)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initialize_playback(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
pub fn initialize_playback(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
replay_kind: ReplayKind,
|
||||
) -> GameResult {
|
||||
if !self.is_active {
|
||||
state.replay_state = ReplayState::Playback;
|
||||
self.read_replay(state, ctx)?;
|
||||
state.replay_state = ReplayState::Playback(replay_kind);
|
||||
self.read_replay(state, ctx, replay_kind)?;
|
||||
state.game_rng.load_state(self.rng_seed);
|
||||
self.is_active = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
fn write_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context, replay_kind: ReplayKind) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::open_options(
|
||||
ctx,
|
||||
[state.get_rec_filename(), ".rep".to_string()].join(""),
|
||||
[state.get_rec_filename(), replay_kind.get_suffix()].join(""),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
file.write_u16::<LE>(0)?; // Space for versioning replay files
|
||||
|
@ -77,8 +94,9 @@ impl Replay {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn read_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::user_open(ctx, [state.get_rec_filename(), ".rep".to_string()].join("")) {
|
||||
fn read_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context, replay_kind: ReplayKind) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::user_open(ctx, [state.get_rec_filename(), replay_kind.get_suffix()].join(""))
|
||||
{
|
||||
self.replay_version = file.read_u16::<LE>()?;
|
||||
self.rng_seed = file.read_u64::<LE>()?;
|
||||
|
||||
|
@ -120,7 +138,7 @@ impl GameEntity<(&mut Context, &mut Player)> for Replay {
|
|||
|
||||
self.keylist.push(inputs);
|
||||
}
|
||||
ReplayState::Playback => {
|
||||
ReplayState::Playback(_) => {
|
||||
let pause = ctx.keyboard_context.is_key_pressed(ScanCode::Escape) && (self.tick - self.resume_tick > 3);
|
||||
|
||||
let next_input = if pause { 1 << 10 } else { *self.keylist.get(self.tick).unwrap_or(&0) };
|
||||
|
@ -153,18 +171,15 @@ impl GameEntity<(&mut Context, &mut Player)> for Replay {
|
|||
|
||||
match state.replay_state {
|
||||
ReplayState::None => {}
|
||||
ReplayState::Playback => {
|
||||
state.font.draw_text_with_shadow(
|
||||
"PLAY".chars(),
|
||||
x,
|
||||
y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
ReplayState::Playback(_) => {
|
||||
state.font.builder()
|
||||
.position(x, y)
|
||||
.draw("PLAY", ctx, &state.constants, &mut state.texture_set)?;
|
||||
}
|
||||
ReplayState::Recording => {
|
||||
state.font.draw_text_with_shadow("REC".chars(), x, y, &state.constants, &mut state.texture_set, ctx)?;
|
||||
state.font.builder()
|
||||
.position(x, y)
|
||||
.draw("REC", ctx, &state.constants, &mut state.texture_set)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::scripting::tsc::text_script::ScriptMode;
|
||||
use crate::game::player::Player;
|
||||
use crate::game::scripting::tsc::text_script::ScriptMode;
|
||||
|
||||
pub struct StageSelect {
|
||||
pub current_teleport_slot: u8,
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::engine_constants::AnimatedFace;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::graphics;
|
||||
use crate::graphics::draw_rect;
|
||||
use crate::scripting::tsc::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptLine};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::scripting::tsc::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptLine};
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::graphics::font::{Font, Symbols};
|
||||
|
||||
pub struct TextBoxes {
|
||||
pub slide_in: u8,
|
||||
|
@ -38,6 +38,7 @@ impl GameEntity<()> for TextBoxes {
|
|||
let animation = state.textscript_vm.face % 1000 / 100;
|
||||
|
||||
if state.constants.textscript.animated_face_pics
|
||||
&& !state.settings.original_textures
|
||||
&& (self.animated_face.anim_id != animation || self.animated_face.face_id != face_num)
|
||||
{
|
||||
self.animated_face = state
|
||||
|
@ -154,19 +155,25 @@ impl GameEntity<()> for TextBoxes {
|
|||
let face_num = state.textscript_vm.face % 100;
|
||||
let animation_frame = self.animated_face.anim_frames.first().unwrap().0 as usize;
|
||||
|
||||
let tex_name =
|
||||
if state.constants.textscript.animated_face_pics { SWITCH_FACE_TEX[animation_frame] } else { FACE_TEX };
|
||||
let tex_name = if state.constants.textscript.animated_face_pics && !state.settings.original_textures {
|
||||
SWITCH_FACE_TEX[animation_frame]
|
||||
} else {
|
||||
FACE_TEX
|
||||
};
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex_name)?;
|
||||
|
||||
let face_x = (4.0 + (6 - self.slide_in) as f32 * 8.0) - 52.0;
|
||||
|
||||
batch.add_rect_flip(
|
||||
left_pos + 14.0 + face_x,
|
||||
top_pos + 8.0,
|
||||
flip,
|
||||
false,
|
||||
&Rect::new_size((face_num as u16 % 6) * 48, (face_num as u16 / 6) * 48, 48, 48),
|
||||
);
|
||||
let final_x = left_pos + 14.0 + face_x;
|
||||
let final_y = top_pos + 8.0;
|
||||
let rect = Rect::new_size((face_num as u16 % 6) * 48, (face_num as u16 / 6) * 48, 48, 48);
|
||||
|
||||
if face_num >= 1 && face_num <= 4 && state.more_rust {
|
||||
// sue
|
||||
batch.add_rect_flip_tinted(final_x, final_y, flip, false, (200, 200, 255, 255), &rect);
|
||||
} else {
|
||||
batch.add_rect_flip(final_x, final_y, flip, false, &rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
graphics::set_clip_rect(ctx, None)?;
|
||||
|
@ -220,54 +227,46 @@ impl GameEntity<()> for TextBoxes {
|
|||
graphics::set_clip_rect(ctx, Some(clip_rect))?;
|
||||
for (idx, line) in lines.iter().enumerate() {
|
||||
if !line.is_empty() {
|
||||
if state.constants.textscript.text_shadow {
|
||||
state.font.draw_text_with_shadow(
|
||||
line.iter().copied(),
|
||||
left_pos + text_offset + 14.0,
|
||||
top_pos + 10.0 + idx as f32 * 16.0 - y_offset,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
} else {
|
||||
state.font.draw_text(
|
||||
line.iter().copied(),
|
||||
left_pos + text_offset + 14.0,
|
||||
top_pos + 10.0 + idx as f32 * 16.0 - y_offset,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
let symbols = Symbols { symbols: &state.textscript_vm.substitution_rect_map, texture: "TextBox" };
|
||||
|
||||
state
|
||||
.font
|
||||
.builder()
|
||||
.position(left_pos + text_offset + 14.0, top_pos + 10.0 + idx as f32 * 16.0 - y_offset)
|
||||
.shadow(state.constants.textscript.text_shadow)
|
||||
.with_symbols(Some(symbols))
|
||||
.draw_iter(line.iter().copied(), ctx, &state.constants, &mut state.texture_set)?;
|
||||
}
|
||||
}
|
||||
graphics::set_clip_rect(ctx, None)?;
|
||||
|
||||
if let TextScriptExecutionState::WaitInput(_, _, tick) = state.textscript_vm.state {
|
||||
if tick > 10 {
|
||||
let builder = state
|
||||
.font
|
||||
.builder()
|
||||
.with_symbols(Some(Symbols { symbols: &state.textscript_vm.substitution_rect_map, texture: "" }));
|
||||
|
||||
let (mut x, y) = match state.textscript_vm.current_line {
|
||||
TextScriptLine::Line1 => (
|
||||
state.font.text_width(state.textscript_vm.line_1.iter().copied(), &state.constants),
|
||||
top_pos + 10.0,
|
||||
),
|
||||
TextScriptLine::Line2 => (
|
||||
state.font.text_width(state.textscript_vm.line_2.iter().copied(), &state.constants),
|
||||
top_pos + 10.0 + 16.0,
|
||||
),
|
||||
TextScriptLine::Line3 => (
|
||||
state.font.text_width(state.textscript_vm.line_3.iter().copied(), &state.constants),
|
||||
top_pos + 10.0 + 32.0,
|
||||
),
|
||||
TextScriptLine::Line1 => {
|
||||
(builder.compute_width_iter(state.textscript_vm.line_1.iter().copied()), top_pos + 10.0)
|
||||
}
|
||||
TextScriptLine::Line2 => {
|
||||
(builder.compute_width_iter(state.textscript_vm.line_2.iter().copied()), top_pos + 10.0 + 16.0)
|
||||
}
|
||||
TextScriptLine::Line3 => {
|
||||
(builder.compute_width_iter(state.textscript_vm.line_3.iter().copied()), top_pos + 10.0 + 32.0)
|
||||
}
|
||||
};
|
||||
x += left_pos + text_offset + 14.0;
|
||||
|
||||
draw_rect(
|
||||
graphics::draw_rect(
|
||||
ctx,
|
||||
Rect::new_size(
|
||||
(x * state.scale) as isize,
|
||||
(y * state.scale) as isize,
|
||||
(5.0 * state.scale) as isize,
|
||||
(state.font.line_height(&state.constants) * state.scale) as isize,
|
||||
(state.font.line_height() * state.scale) as isize,
|
||||
),
|
||||
Color::from_rgb(255, 255, 255),
|
||||
)?;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::common::Rect;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::game::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
|
||||
pub struct Tilemap {
|
||||
tick: u32,
|
||||
|
@ -133,7 +133,7 @@ impl Tilemap {
|
|||
TileLayer::Foreground => {
|
||||
let attr = stage.map.attrib[tile as usize];
|
||||
|
||||
if attr < 0x40 || attr >= 0x80 || attr == 0x43 {
|
||||
if attr < 0x40 || attr >= 0x80 {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::backend::{BackendShader, SpriteBatchCommand, VertexData};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::graphics::BlendMode;
|
||||
use crate::map::{WaterParamEntry, WaterParams, WaterRegionType};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::{BackgroundType, Stage};
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::map::{WaterParamEntry, WaterParams, WaterRegionType};
|
||||
use crate::game::physics::PhysicalEntity;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::stage::{BackgroundType, Stage};
|
||||
use crate::game::npc::list::NPCList;
|
||||
use crate::game::player::Player;
|
||||
|
||||
const TENSION: f32 = 0.03;
|
||||
const DAMPENING: f32 = 0.01;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::common::{interpolate_fix9_scale, Direction, Rect};
|
||||
use crate::common::{Direction, interpolate_fix9_scale, Rect};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::weapon::bullet::{Bullet, BulletManager};
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::player::{Player, TargetPlayer};
|
||||
use crate::game::weapon::bullet::{Bullet, BulletManager};
|
||||
|
||||
pub struct WhimsicalStar {
|
||||
pub star: [Star; 3],
|
||||
|
|
After Width: | Height: | Size: 276 KiB |
After Width: | Height: | Size: 447 B |
After Width: | Height: | Size: 456 B |
After Width: | Height: | Size: 562 B |
After Width: | Height: | Size: 621 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,198 @@
|
|||
{
|
||||
"name": "English",
|
||||
"font": "csfont.fnt",
|
||||
"font_scale": "0.5",
|
||||
"common": {
|
||||
"name": "doukutsu-rs",
|
||||
"back": "< Back",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"on": "ON",
|
||||
"off": "OFF"
|
||||
},
|
||||
"menus": {
|
||||
"main_menu": {
|
||||
"start": "Start Game",
|
||||
"challenges": "Challenges",
|
||||
"options": "Options",
|
||||
"editor": "Editor",
|
||||
"jukebox": "Jukebox",
|
||||
"quit": "Quit"
|
||||
},
|
||||
"pause_menu": {
|
||||
"resume": "Resume",
|
||||
"retry": "Retry",
|
||||
"options": "Options",
|
||||
"title": "Title",
|
||||
"title_confirm": "Title?",
|
||||
"quit": "Quit",
|
||||
"quit_confirm": "Quit?",
|
||||
"add_player2": "Add Player 2",
|
||||
"drop_player2": "Drop Player 2"
|
||||
},
|
||||
"save_menu": {
|
||||
"new": "New Save",
|
||||
"delete_info": "Press Right to Delete",
|
||||
"delete_confirm": "Delete?",
|
||||
"invalid_save": "Invalid Save"
|
||||
},
|
||||
"difficulty_menu": {
|
||||
"title": "Select Difficulty",
|
||||
"easy": "Easy",
|
||||
"normal": "Normal",
|
||||
"hard": "Hard",
|
||||
"difficulty_name": "Difficulty: {difficulty}",
|
||||
"unknown": "(unknown)"
|
||||
},
|
||||
"coop_menu": {
|
||||
"title": "Select Number of Players",
|
||||
"one": "Single Player",
|
||||
"two": "Two Players"
|
||||
},
|
||||
"skin_menu": {
|
||||
"title": "Select Player 2's appearance",
|
||||
"label": "Appearance:"
|
||||
},
|
||||
"challenge_menu": {
|
||||
"start": "Start",
|
||||
"no_replay": "No Replay",
|
||||
"replay_best": "Replay Best",
|
||||
"replay_last": "Replay Last",
|
||||
"delete_replay": "Delete Best Replay"
|
||||
},
|
||||
"options_menu": {
|
||||
"graphics": "Graphics...",
|
||||
"graphics_menu": {
|
||||
"window_mode": {
|
||||
"entry": "Display mode:",
|
||||
"windowed": "Windowed",
|
||||
"fullscreen": "Fullscreen"
|
||||
},
|
||||
"lighting_effects": "Lighting effects:",
|
||||
"weapon_light_cone": "Weapon light cone:",
|
||||
"screen_shake": {
|
||||
"entry": "Screen shake intensity:",
|
||||
"full": "1x",
|
||||
"half": "0.5x",
|
||||
"off": "Off"
|
||||
},
|
||||
"motion_interpolation": "Motion interpolation:",
|
||||
"subpixel_scrolling": "Subpixel scrolling:",
|
||||
"original_textures": "Original textures:",
|
||||
"seasonal_textures": "Seasonal textures:",
|
||||
"renderer": "Renderer:",
|
||||
"vsync_mode": {
|
||||
"entry": "V-Sync:",
|
||||
"uncapped": "Uncapped",
|
||||
"uncapped_desc": "V-Sync Off.",
|
||||
"vsync": "Enabled",
|
||||
"vsync_desc": "V-Sync On.",
|
||||
"vrr_1x": "Variable Refresh Rate (1x)",
|
||||
"vrr_1x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_2x": "Variable Refresh Rate (2x)",
|
||||
"vrr_2x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_3x": "Variable Refresh Rate (3x)",
|
||||
"vrr_3x_desc": "Uses (G-/Free)Sync if available."
|
||||
}
|
||||
},
|
||||
"sound": "Sound...",
|
||||
"sound_menu": {
|
||||
"music_volume": "Music Volume",
|
||||
"effects_volume": "Effects Volume",
|
||||
"bgm_interpolation": {
|
||||
"entry": "BGM Interpolation:",
|
||||
"linear": "Linear",
|
||||
"linear_desc": "Fast, similar to freeware on Vista+",
|
||||
"cosine": "Cosine",
|
||||
"cosine_desc": "Cosine interpolation",
|
||||
"cubic": "Cubic",
|
||||
"cubic_desc": "Cubic interpolation",
|
||||
"linear_lp": "Linear+LP",
|
||||
"linear_lp_desc": "Slowest, similar to freeware on XP",
|
||||
"nearest": "Nearest",
|
||||
"nearest_desc": "Fastest, lowest quality"
|
||||
},
|
||||
"soundtrack": "Soundtrack: {soundtrack}"
|
||||
},
|
||||
"controls": "Controls...",
|
||||
"controls_menu": {
|
||||
"display_touch_controls": "Display touch controls:"
|
||||
},
|
||||
"language": "Language...",
|
||||
"behavior": "Behavior...",
|
||||
"behavior_menu": {
|
||||
"game_timing": {
|
||||
"entry": "Game timing:",
|
||||
"50tps": "50tps (freeware)",
|
||||
"60tps": "60tps (CS+)"
|
||||
},
|
||||
"pause_on_focus_loss": "Pause on focus loss:",
|
||||
"cutscene_skip_method": {
|
||||
"entry": "Cutscene Skip:",
|
||||
"hold": "Hold to Skip",
|
||||
"fastforward": "Fast-Forward",
|
||||
"auto": "Auto"
|
||||
},
|
||||
"discord_rpc": "Discord Rich Presence:",
|
||||
"allow_strafe": "Allow strafe:"
|
||||
},
|
||||
"links": "Links...",
|
||||
"advanced": "Advanced...",
|
||||
"advanced_menu": {
|
||||
"open_user_data": "Open user data directory",
|
||||
"open_game_data": "Open game data directory",
|
||||
"make_portable": "Make portable user directory"
|
||||
},
|
||||
"portable_menu": {
|
||||
"explanation": "This will create a local user data directory and copy your settings and save files.",
|
||||
"restart_question": "Reload the game to use the new location?",
|
||||
"restart": "Save and return to title",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
},
|
||||
"controls_menu": {
|
||||
"select_player": {
|
||||
"entry": "Select player:",
|
||||
"player_1": "Player 1",
|
||||
"player_2": "Player 2"
|
||||
},
|
||||
"controller": {
|
||||
"entry": "Controller...",
|
||||
"keyboard": "Keyboard"
|
||||
},
|
||||
"rebind": "Rebind...",
|
||||
"rebind_menu": {
|
||||
"up": "Up",
|
||||
"down": "Down",
|
||||
"left": "Left",
|
||||
"right": "Right",
|
||||
"jump": "Jump",
|
||||
"shoot": "Shoot",
|
||||
"prev_weapon": "Previous weapon",
|
||||
"next_weapon": "Next weapon",
|
||||
"inventory": "Inventory",
|
||||
"map": "Map system",
|
||||
"skip": "Skip",
|
||||
"strafe": "Strafe",
|
||||
"menu_ok": "Menu select/confirm",
|
||||
"menu_back": "Menu back/cancel"
|
||||
},
|
||||
"rebind_confirm_menu": {
|
||||
"title": "Press button for \"{control}\"",
|
||||
"cancel": "(Esc to cancel)"
|
||||
},
|
||||
"rumble": "Rumble:",
|
||||
"reset_confirm": "Reset...",
|
||||
"reset_confirm_menu_title": "Reset controls?"
|
||||
}
|
||||
},
|
||||
"soundtrack": {
|
||||
"organya": "Organya",
|
||||
"remastered": "Remastered",
|
||||
"new": "New",
|
||||
"famitracks": "Famitracks"
|
||||
},
|
||||
"game": {
|
||||
"cutscene_skip": "Hold {key} to skip the cutscene"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
{
|
||||
"name": "Japanese",
|
||||
"font": "csfontjp.fnt",
|
||||
"font_scale": "0.5",
|
||||
"common": {
|
||||
"name": "doukutsu-rs",
|
||||
"back": "< 戻る",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"on": "オン",
|
||||
"off": "オフ"
|
||||
},
|
||||
"menus": {
|
||||
"main_menu": {
|
||||
"start": "ゲームスタート",
|
||||
"challenges": "チャレンジ",
|
||||
"options": "オプション",
|
||||
"editor": "レベルエディタ",
|
||||
"jukebox": "ジュークボックス",
|
||||
"quit": "辞める"
|
||||
},
|
||||
"pause_menu": {
|
||||
"resume": "再開",
|
||||
"retry": "リトライ",
|
||||
"options": "設定",
|
||||
"title": "メインメニュー",
|
||||
"title_confirm": "メインメニュー?",
|
||||
"quit": "辞める",
|
||||
"quit_confirm": "辞める?",
|
||||
"add_player2": "プレーヤー2を追加",
|
||||
"drop_player2": "プレーヤー2を削除"
|
||||
},
|
||||
"save_menu": {
|
||||
"new": "新しいデータ",
|
||||
"delete_info": "右矢印キーで削除",
|
||||
"delete_confirm": "消去?",
|
||||
"invalid_save": "無効な保存"
|
||||
},
|
||||
"difficulty_menu": {
|
||||
"title": "難易度選択",
|
||||
"easy": "簡単",
|
||||
"normal": "普通",
|
||||
"hard": "難しい",
|
||||
"difficulty_name": "難易度: {difficulty}",
|
||||
"unknown": "(未知)"
|
||||
},
|
||||
"coop_menu": {
|
||||
"title": "プレイヤー数を選択",
|
||||
"one": "1人プレイ",
|
||||
"two": "2人プレイ"
|
||||
},
|
||||
"skin_menu": {
|
||||
"title": "プレーヤー2の外観を選択します",
|
||||
"label": "外観:"
|
||||
},
|
||||
"challenge_menu": {
|
||||
"start": "スタート",
|
||||
"no_replay": "ノーリプレイ",
|
||||
"replay_best": "ベストプレイを再生",
|
||||
"replay_last": "最後のプレイを再生",
|
||||
"delete_replay": "ベストリプレイを削除"
|
||||
},
|
||||
"options_menu": {
|
||||
"graphics": "グラフィック",
|
||||
"graphics_menu": {
|
||||
"window_mode": {
|
||||
"entry": "画面表示:",
|
||||
"windowed": "ウィンドウ",
|
||||
"fullscreen": "フルスクリーン"
|
||||
},
|
||||
"lighting_effects": "ライティング効果:",
|
||||
"weapon_light_cone": "兵器のライトコーン:",
|
||||
"screen_shake": {
|
||||
"entry": "画面の揺れ:",
|
||||
"full": "1x",
|
||||
"half": "0.5x",
|
||||
"off": "オフ"
|
||||
},
|
||||
"motion_interpolation": "モーション補間:",
|
||||
"subpixel_scrolling": "サブピクセルスクロール:",
|
||||
"original_textures": "オリジナルテクスチャ:",
|
||||
"seasonal_textures": "季節ものテクスチャ:",
|
||||
"renderer": "レンダラ:",
|
||||
"vsync_mode": {
|
||||
"entry": "V-Sync:",
|
||||
"uncapped": "Uncapped",
|
||||
"uncapped_desc": "V-Sync Off.",
|
||||
"vsync": "Enabled",
|
||||
"vsync_desc": "V-Sync On.",
|
||||
"vrr_1x": "Variable Refresh Rate (1x)",
|
||||
"vrr_1x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_2x": "Variable Refresh Rate (2x)",
|
||||
"vrr_2x_desc": "Uses (G-/Free)Sync if available.",
|
||||
"vrr_3x": "Variable Refresh Rate (3x)",
|
||||
"vrr_3x_desc": "Uses (G-/Free)Sync if available."
|
||||
}
|
||||
},
|
||||
"sound": "サウンド",
|
||||
"sound_menu": {
|
||||
"music_volume": "BGM音量",
|
||||
"effects_volume": "サウンド音量",
|
||||
"bgm_interpolation": {
|
||||
"entry": "BGM内挿:",
|
||||
"linear": "線形補間",
|
||||
"linear_desc": "速い、フリーウェア版に近い(Vista+)",
|
||||
"cosine": "余弦",
|
||||
"cosine_desc": "余弦補間",
|
||||
"cubic": "立方体",
|
||||
"cubic_desc": "立方体補間",
|
||||
"linear_lp": "線形補間+LP",
|
||||
"linear_lp_desc": "最も遅い、フリーウェア版に近い(XP)",
|
||||
"nearest": "最近傍",
|
||||
"nearest_desc": "最速、最低品質"
|
||||
},
|
||||
"soundtrack": "サウンドトラック: {soundtrack}"
|
||||
},
|
||||
"controls": "ボタン変更",
|
||||
"controls_menu": {
|
||||
"display_touch_controls": "タッチコントロールを表示する: "
|
||||
},
|
||||
"language": "言語",
|
||||
"behavior": "動作",
|
||||
"behavior_menu": {
|
||||
"game_timing": {
|
||||
"entry": "ゲームのタイミング:",
|
||||
"50tps": "50tps (freeware)",
|
||||
"60tps": "60tps (CS+)"
|
||||
},
|
||||
"pause_on_focus_loss": "フォーカスが外れた時のポーズ:",
|
||||
"cutscene_skip_method": {
|
||||
"entry": "カットシーンをスキップ",
|
||||
"hold": "を押し続け",
|
||||
"fastforward": "はやおくり"
|
||||
},
|
||||
"discord_rpc": "Discord Rich Presence:",
|
||||
"allow_strafe": "ストレイフを許可する:"
|
||||
},
|
||||
"links": "リンク",
|
||||
"advanced": "詳細設定",
|
||||
"advanced_menu": {
|
||||
"open_user_data": "ユーザープロファイルを開く",
|
||||
"open_game_data": "ゲームファイルを開く",
|
||||
"make_portable": "ポータブルユーザーディレクトリを作成する"
|
||||
},
|
||||
"portable_menu": {
|
||||
"explanation": "ローカルのユーザーデータディレクトリが作成され、設定とセーブファイルがそこにコピーされます。",
|
||||
"restart_question": "新しい場所を使うには、ゲームを再起動しますか?",
|
||||
"restart": "保存してタイトルに戻る",
|
||||
"cancel": "キャンセル"
|
||||
}
|
||||
},
|
||||
"controls_menu": {
|
||||
"select_player": {
|
||||
"entry": "プレイヤーを選択:",
|
||||
"player_1": "プレーヤー 1",
|
||||
"player_2": "プレーヤー 2"
|
||||
},
|
||||
"controller": {
|
||||
"entry": "コントローラ",
|
||||
"keyboard": "キーボード"
|
||||
},
|
||||
"rebind": "再バインド",
|
||||
"rebind_menu": {
|
||||
"up": "うえ",
|
||||
"down": "した",
|
||||
"left": "ひだり",
|
||||
"right": "みぎ",
|
||||
"jump": "ジャンプ",
|
||||
"shoot": "ショット",
|
||||
"prev_weapon": "前の武器",
|
||||
"next_weapon": "次の武器",
|
||||
"inventory": "在庫",
|
||||
"map": "マップシステム",
|
||||
"skip": "スキップ",
|
||||
"strafe": "ストレイフ",
|
||||
"menu_ok": "メニュー選択/OK",
|
||||
"menu_back": "メニュー残す/キャンセル"
|
||||
},
|
||||
"rebind_confirm_menu": {
|
||||
"title": "新しい「ジャンプ」ボタンを押す",
|
||||
"cancel": "(Escキーを押してキャンセル)"
|
||||
},
|
||||
"rumble": "ランブル",
|
||||
"reset_confirm": "リセット",
|
||||
"reset_confirm_menu_title": "ボタンをリセットしますか?"
|
||||
}
|
||||
},
|
||||
"soundtrack": {
|
||||
"organya": "オルガーニャ",
|
||||
"remastered": "リマスター",
|
||||
"new": "新",
|
||||
"famitracks": "ファミトラック"
|
||||
},
|
||||
"game": {
|
||||
"cutscene_skip": "{key} を押し続け、カットシーンをスキップ"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 130 B |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
@ -1,13 +1,13 @@
|
|||
use std::{fmt, io};
|
||||
use std::fmt::Debug;
|
||||
use std::io::Cursor;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::{fmt, io};
|
||||
|
||||
use crate::framework::error::GameError::FilesystemError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS};
|
||||
use crate::framework::vfs::{OpenOptions, VFile, VFS, VMetadata};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
||||
|
@ -101,31 +101,100 @@ impl BuiltinFS {
|
|||
FSNode::File("builtin_font.fnt", include_bytes!("builtin/builtin_font.fnt")),
|
||||
FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")),
|
||||
FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")),
|
||||
FSNode::File("gamecontrollerdb.txt", include_bytes!("builtin/gamecontrollerdb.txt")),
|
||||
FSNode::File("icon.bmp", include_bytes!("../../res/sue.bmp")),
|
||||
FSNode::File(
|
||||
"organya-wavetable-doukutsu.bin",
|
||||
include_bytes!("builtin/organya-wavetable-doukutsu.bin"),
|
||||
),
|
||||
FSNode::File("touch.png", include_bytes!("builtin/touch.png")),
|
||||
FSNode::Directory(
|
||||
"shaders",
|
||||
"builtin_data",
|
||||
vec![
|
||||
// FSNode::File("basic_150.vert.glsl", include_bytes!("builtin/shaders/basic_150.vert.glsl")),
|
||||
// FSNode::File("water_150.frag.glsl", include_bytes!("builtin/shaders/water_150.frag.glsl")),
|
||||
// FSNode::File("basic_es300.vert.glsl", include_bytes!("builtin/shaders/basic_es300.vert.glsl")),
|
||||
// FSNode::File("water_es300.frag.glsl", include_bytes!("builtin/shaders/water_es300.frag.glsl")),
|
||||
FSNode::File("buttons.png", include_bytes!("builtin/builtin_data/buttons.png")),
|
||||
FSNode::File("triangles.png", include_bytes!("builtin/builtin_data/triangles.png")),
|
||||
FSNode::Directory(
|
||||
"headband",
|
||||
vec![
|
||||
FSNode::Directory(
|
||||
"ogph",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"Casts.png",
|
||||
include_bytes!("builtin/builtin_data/headband/ogph/Casts.png"),
|
||||
),
|
||||
FSNode::Directory(
|
||||
"Npc",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"NpcGuest.png",
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/ogph/Npc/NpcGuest.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcMiza.png",
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/ogph/Npc/NpcMiza.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcRegu.png",
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/ogph/Npc/NpcRegu.png"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
FSNode::Directory(
|
||||
"plus",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"Casts.png",
|
||||
include_bytes!("builtin/builtin_data/headband/plus/casts.png"),
|
||||
),
|
||||
FSNode::Directory(
|
||||
"Npc",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"NpcGuest.png",
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/plus/npc/npcguest.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcMiza.png",
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/plus/npc/npcmiza.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcRegu.png",
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/plus/npc/npcregu.png"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
FSNode::Directory(
|
||||
"locale",
|
||||
vec![
|
||||
FSNode::File("en.json", include_bytes!("builtin/builtin_data/locale/en.json")),
|
||||
FSNode::File("jp.json", include_bytes!("builtin/builtin_data/locale/jp.json")),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
FSNode::Directory(
|
||||
"lightmap",
|
||||
vec![FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png"))],
|
||||
),
|
||||
FSNode::Directory(
|
||||
"locale",
|
||||
vec![
|
||||
FSNode::File("en.json", include_bytes!("builtin/locale/en.json")),
|
||||
FSNode::File("jp.json", include_bytes!("builtin/locale/jp.json")),
|
||||
],
|
||||
),
|
||||
],
|
||||
)],
|
||||
}
|
||||
|
@ -209,7 +278,7 @@ impl VFS for BuiltinFS {
|
|||
self.get_node(path).map(|v| v.to_metadata())
|
||||
}
|
||||
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>> {
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||
match self.get_node(path) {
|
||||
Ok(FSNode::Directory(_, contents)) => {
|
||||
let mut vec = Vec::new();
|
|
@ -0,0 +1,133 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use pelite::{
|
||||
image::RT_BITMAP,
|
||||
pe32::{headers::SectionHeaders, Pe, PeFile},
|
||||
resources::{Directory, Entry, Name, Resources},
|
||||
};
|
||||
|
||||
use crate::framework::error::{GameError::ParseError, GameResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataFile {
|
||||
pub bytes: Vec<u8>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl DataFile {
|
||||
pub fn from(name: String, bytes: Vec<u8>) -> Self {
|
||||
Self { name, bytes }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExeResourceDirectory {
|
||||
pub name: String,
|
||||
pub data_files: Vec<DataFile>,
|
||||
}
|
||||
|
||||
impl ExeResourceDirectory {
|
||||
pub fn new(name: String) -> Self {
|
||||
Self { name, data_files: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExeParser<'a> {
|
||||
pub image_base: u32,
|
||||
pub resources: Resources<'a>,
|
||||
pub section_headers: Box<&'a SectionHeaders>,
|
||||
}
|
||||
|
||||
impl<'a> ExeParser<'a> {
|
||||
pub fn from(file: &'a Vec<u8>) -> GameResult<Self> {
|
||||
let pe = PeFile::from_bytes(file);
|
||||
|
||||
return match pe {
|
||||
Ok(pe) => {
|
||||
let resources = pe.resources();
|
||||
|
||||
if resources.is_err() {
|
||||
return Err(ParseError("Failed to parse resources.".to_string()));
|
||||
}
|
||||
|
||||
let section_headers = pe.section_headers();
|
||||
let image_base = pe.nt_headers().OptionalHeader.ImageBase;
|
||||
|
||||
Ok(Self {
|
||||
image_base,
|
||||
resources: resources.unwrap(),
|
||||
section_headers: Box::new(section_headers)
|
||||
})
|
||||
}
|
||||
Err(_) => Err(ParseError("Failed to parse PE file".to_string())),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_resource_dir(&self, name: String) -> GameResult<ExeResourceDirectory> {
|
||||
let mut dir_data = ExeResourceDirectory::new(name.to_owned());
|
||||
|
||||
let path = format!("/{}", name.to_owned());
|
||||
let dir = self.resources.find_dir(&path);
|
||||
|
||||
return match dir {
|
||||
Ok(dir) => {
|
||||
self.read_dir(dir, &mut dir_data, "unknown".to_string());
|
||||
Ok(dir_data)
|
||||
}
|
||||
Err(_) => return Err(ParseError("Failed to find resource directory.".to_string())),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_bitmap_dir(&self) -> GameResult<ExeResourceDirectory> {
|
||||
let mut dir_data = ExeResourceDirectory::new("Bitmap".to_string());
|
||||
|
||||
let root = self.resources.root().unwrap();
|
||||
let dir = root.get_dir(Name::Id(RT_BITMAP.into()));
|
||||
|
||||
return match dir {
|
||||
Ok(dir) => {
|
||||
self.read_dir(dir, &mut dir_data, "unknown".to_string());
|
||||
Ok(dir_data)
|
||||
}
|
||||
Err(_) => return Err(ParseError("Failed to open bitmap directory.".to_string())),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_named_section_byte_range(&self, name: String) -> GameResult<Option<Range<u32>>> {
|
||||
let section_header = self.section_headers.by_name(name.as_bytes());
|
||||
return match section_header {
|
||||
Some(section_header) => Ok(Some(section_header.file_range())),
|
||||
None => Ok(None),
|
||||
};
|
||||
}
|
||||
|
||||
fn read_dir(&self, directory: Directory, dir_data: &mut ExeResourceDirectory, last_dir_name: String) {
|
||||
for dir in directory.entries() {
|
||||
let raw_entry = dir.entry();
|
||||
|
||||
if raw_entry.is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Entry::Directory(entry) = raw_entry.unwrap() {
|
||||
let dir_name = dir.name();
|
||||
let name = match dir_name {
|
||||
Ok(name) => name.to_string(),
|
||||
Err(_) => last_dir_name.to_owned(),
|
||||
};
|
||||
self.read_dir(entry, dir_data, name);
|
||||
}
|
||||
|
||||
if let Entry::DataEntry(entry) = raw_entry.unwrap() {
|
||||
let entry_bytes = entry.bytes();
|
||||
if entry_bytes.is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let bytes = entry_bytes.unwrap();
|
||||
let data_file = DataFile::from(last_dir_name.to_owned(), bytes.to_vec());
|
||||
dir_data.data_files.push(data_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod builtin_fs;
|
||||
pub mod exe_parser;
|
||||
pub mod vanilla;
|
|
@ -0,0 +1,268 @@
|
|||
use std::{
|
||||
env,
|
||||
io::{Read, Write},
|
||||
ops::Range,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use byteorder::{LE, WriteBytesExt};
|
||||
|
||||
use crate::data::exe_parser::ExeParser;
|
||||
use crate::framework::{
|
||||
context::Context,
|
||||
error::{GameError::ParseError, GameResult},
|
||||
filesystem,
|
||||
};
|
||||
|
||||
pub struct VanillaExtractor {
|
||||
exe_buffer: Vec<u8>,
|
||||
data_base_dir: String,
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
const VANILLA_STAGE_COUNT: u32 = 95;
|
||||
const VANILLA_STAGE_ENTRY_SIZE: u32 = 0xC8;
|
||||
const VANILLA_STAGE_TABLE_SIZE: u32 = VANILLA_STAGE_COUNT * VANILLA_STAGE_ENTRY_SIZE;
|
||||
|
||||
trait RangeExt {
|
||||
fn to_usize(&self) -> std::ops::Range<usize>;
|
||||
}
|
||||
|
||||
impl RangeExt for Range<u32> {
|
||||
fn to_usize(&self) -> std::ops::Range<usize> {
|
||||
(self.start as usize)..(self.end as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl VanillaExtractor {
|
||||
pub fn from(ctx: &mut Context, exe_name: String, data_base_dir: String) -> Option<Self> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
|
||||
let mut vanilla_exe_path = env::current_dir().unwrap();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
let mut vanilla_exe_path = PathBuf::from(ndk_glue::native_activity().internal_data_path().to_string_lossy().to_string());
|
||||
|
||||
#[cfg(target_os = "horizon")]
|
||||
let mut vanilla_exe_path = PathBuf::from("sdmc:/switch/doukutsu-rs/");
|
||||
|
||||
vanilla_exe_path.push(&exe_name);
|
||||
|
||||
log::info!("Looking for vanilla game executable at {:?}", vanilla_exe_path);
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
|
||||
if !vanilla_exe_path.is_file() {
|
||||
vanilla_exe_path = env::current_exe().unwrap();
|
||||
vanilla_exe_path.pop();
|
||||
vanilla_exe_path.push(&exe_name);
|
||||
}
|
||||
|
||||
if !vanilla_exe_path.is_file() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut root = vanilla_exe_path.clone();
|
||||
root.pop();
|
||||
|
||||
log::info!("Found vanilla game executable, attempting to extract resources.");
|
||||
|
||||
if filesystem::exists(ctx, format!("{}/stage.sect", data_base_dir.clone())) {
|
||||
log::info!("Vanilla resources are already extracted, not proceeding.");
|
||||
return None;
|
||||
}
|
||||
|
||||
let file = std::fs::File::open(vanilla_exe_path);
|
||||
if file.is_err() {
|
||||
log::error!("Failed to open vanilla game executable: {}", file.unwrap_err());
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut exe_buffer = Vec::new();
|
||||
let result = file.unwrap().read_to_end(&mut exe_buffer);
|
||||
if result.is_err() {
|
||||
log::error!("Failed to read vanilla game executable: {}", result.unwrap_err());
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self { exe_buffer, data_base_dir, root })
|
||||
}
|
||||
|
||||
pub fn extract_data(&self) -> GameResult {
|
||||
let parser = ExeParser::from(&self.exe_buffer);
|
||||
if parser.is_err() {
|
||||
return Err(ParseError("Failed to create vanilla parser.".to_string()));
|
||||
}
|
||||
|
||||
let parser = parser.unwrap();
|
||||
|
||||
self.extract_organya(&parser)?;
|
||||
self.extract_bitmaps(&parser)?;
|
||||
self.extract_stage_table(&parser)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deep_create_dir_if_not_exists(&self, path: PathBuf) -> GameResult {
|
||||
if path.is_dir() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let result = std::fs::create_dir_all(path);
|
||||
if result.is_err() {
|
||||
return Err(ParseError(format!("Failed to create directory structure: {}", result.unwrap_err())));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_organya(&self, parser: &ExeParser) -> GameResult {
|
||||
let orgs = parser.get_resource_dir("ORG".to_string());
|
||||
|
||||
if orgs.is_err() {
|
||||
return Err(ParseError("Failed to retrieve Organya resource directory.".to_string()));
|
||||
}
|
||||
|
||||
for org in orgs.unwrap().data_files {
|
||||
let mut org_path = self.root.clone();
|
||||
org_path.push(self.data_base_dir.clone());
|
||||
org_path.push("Org/");
|
||||
|
||||
if self.deep_create_dir_if_not_exists(org_path.clone()).is_err() {
|
||||
return Err(ParseError("Failed to create directory structure.".to_string()));
|
||||
}
|
||||
|
||||
org_path.push(format!("{}.org", org.name));
|
||||
|
||||
let mut org_file = match std::fs::File::create(org_path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
return Err(ParseError("Failed to create organya file.".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let result = org_file.write_all(&org.bytes);
|
||||
if result.is_err() {
|
||||
return Err(ParseError("Failed to write organya file.".to_string()));
|
||||
}
|
||||
|
||||
log::info!("Extracted organya file: {}", org.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_bitmaps(&self, parser: &ExeParser) -> GameResult {
|
||||
let bitmaps = parser.get_bitmap_dir();
|
||||
|
||||
if bitmaps.is_err() {
|
||||
return Err(ParseError("Failed to retrieve bitmap directory.".to_string()));
|
||||
}
|
||||
|
||||
for bitmap in bitmaps.unwrap().data_files {
|
||||
let mut data_path = self.root.clone();
|
||||
data_path.push(self.data_base_dir.clone());
|
||||
|
||||
if self.deep_create_dir_if_not_exists(data_path.clone()).is_err() {
|
||||
return Err(ParseError("Failed to create data directory structure.".to_string()));
|
||||
}
|
||||
|
||||
data_path.push(format!("{}.pbm", bitmap.name));
|
||||
|
||||
let file = std::fs::File::create(data_path);
|
||||
if file.is_err() {
|
||||
return Err(ParseError("Failed to create bitmap file.".to_string()));
|
||||
}
|
||||
|
||||
let mut file = file.unwrap();
|
||||
|
||||
file.write_u8(0x42)?; // B
|
||||
file.write_u8(0x4D)?; // M
|
||||
file.write_u32::<LE>(bitmap.bytes.len() as u32 + 0xE)?; // Size of BMP file
|
||||
file.write_u32::<LE>(0)?; // unused null bytes
|
||||
file.write_u32::<LE>(0x76)?; // Bitmap data offset (hardcoded for now, might wanna get the actual offset)
|
||||
|
||||
let result = file.write_all(&bitmap.bytes);
|
||||
if result.is_err() {
|
||||
return Err(ParseError("Failed to write bitmap file.".to_string()));
|
||||
}
|
||||
|
||||
log::info!("Extracted bitmap file: {}", bitmap.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_stage_table_offset(&self, parser: &ExeParser) -> Option<Range<u32>> {
|
||||
let range = parser.get_named_section_byte_range(".csmap".to_string());
|
||||
if range.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let pattern = [
|
||||
// add esp, 8
|
||||
0x83u8, 0xc4, 0x08,
|
||||
// mov eax, [ebp+arg_0]
|
||||
0x8b, 0x45, 0x08,
|
||||
// imul eax, 0C8h
|
||||
0x69, 0xc0, 0xc8, 0x00, 0x00, 0x00,
|
||||
// add eax, offset gTMT
|
||||
0x05, // 0x??, 0x??, 0x??, 0x??
|
||||
];
|
||||
|
||||
let text = parser.section_headers.by_name(".text")?;
|
||||
let text_range = text.file_range().to_usize();
|
||||
let text_range_start = text_range.start;
|
||||
let offset = self.exe_buffer[text_range]
|
||||
.windows(pattern.len())
|
||||
.position(|window| window == pattern)?;
|
||||
let offset = text_range_start + offset;
|
||||
let offset = u32::from_le_bytes([
|
||||
self.exe_buffer[offset + 13],
|
||||
self.exe_buffer[offset + 14],
|
||||
self.exe_buffer[offset + 15],
|
||||
self.exe_buffer[offset + 16],
|
||||
]);
|
||||
log::info!("Found stage table offset: 0x{:X}", offset);
|
||||
|
||||
let section = parser.section_headers.by_rva(offset - parser.image_base)?;
|
||||
let offset_inside_range = offset.checked_sub(section.VirtualAddress + parser.image_base)?;
|
||||
let range = section.file_range();
|
||||
|
||||
let data_start = range.start + offset_inside_range;
|
||||
let data_end = data_start + VANILLA_STAGE_TABLE_SIZE;
|
||||
Some(data_start..data_end)
|
||||
}
|
||||
|
||||
fn extract_stage_table(&self, parser: &ExeParser) -> GameResult {
|
||||
let range = self.find_stage_table_offset(parser);
|
||||
let range = match range {
|
||||
Some(range) => range,
|
||||
None => return Err(ParseError("Failed to retrieve stage table from executable.".to_string())),
|
||||
};
|
||||
let range = range.to_usize();
|
||||
|
||||
let byte_slice = &self.exe_buffer[range];
|
||||
|
||||
let mut stage_tbl_path = self.root.clone();
|
||||
stage_tbl_path.push(self.data_base_dir.clone());
|
||||
|
||||
if self.deep_create_dir_if_not_exists(stage_tbl_path.clone()).is_err() {
|
||||
return Err(ParseError("Failed to create data directory structure.".to_string()));
|
||||
}
|
||||
|
||||
stage_tbl_path.push("stage.sect");
|
||||
|
||||
let mut stage_tbl_file = match std::fs::File::create(stage_tbl_path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
return Err(ParseError("Failed to create stage table file.".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let result = stage_tbl_file.write_all(byte_slice);
|
||||
if result.is_err() {
|
||||
return Err(ParseError("Failed to write to stage table file.".to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
use std::sync::Mutex;
|
||||
|
||||
use discord_rich_presence::{
|
||||
activity::{Activity, Assets, Button},
|
||||
DiscordIpc, DiscordIpcClient,
|
||||
};
|
||||
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::game::{player::Player, shared_game_state::GameDifficulty, stage::StageData};
|
||||
|
||||
pub enum DiscordRPCState {
|
||||
Initializing,
|
||||
Idling,
|
||||
InGame,
|
||||
Jukebox,
|
||||
}
|
||||
|
||||
pub struct DiscordRPC {
|
||||
pub enabled: bool,
|
||||
pub ready: bool,
|
||||
|
||||
client: DiscordIpcClient,
|
||||
state: DiscordRPCState,
|
||||
life: u16,
|
||||
max_life: u16,
|
||||
stage_name: String,
|
||||
difficulty: Option<GameDifficulty>,
|
||||
|
||||
can_update: Mutex<bool>,
|
||||
}
|
||||
|
||||
impl DiscordRPC {
|
||||
pub fn new(app_id: &str) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
ready: false,
|
||||
|
||||
client: DiscordIpcClient::new(app_id).unwrap(),
|
||||
state: DiscordRPCState::Idling,
|
||||
life: 0,
|
||||
max_life: 0,
|
||||
stage_name: String::new(),
|
||||
difficulty: None,
|
||||
|
||||
can_update: Mutex::new(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> GameResult {
|
||||
log::info!("Starting Discord RPC client...");
|
||||
|
||||
let mut can_update = self.can_update.lock().unwrap();
|
||||
*can_update = false;
|
||||
|
||||
match self.client.connect() {
|
||||
Ok(_) => {
|
||||
self.ready = true;
|
||||
*can_update = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to start Discord RPC client (maybe Discord is not running?): {}", e);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self) -> GameResult {
|
||||
if !self.enabled || !self.ready {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut can_update = self.can_update.lock().unwrap();
|
||||
|
||||
if !*can_update {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
*can_update = false;
|
||||
|
||||
let (state, details) = match self.state {
|
||||
DiscordRPCState::Initializing => ("Initializing...".to_owned(), "Just started playing".to_owned()),
|
||||
DiscordRPCState::Idling => ("In the menus".to_owned(), "Idling".to_owned()),
|
||||
DiscordRPCState::InGame => {
|
||||
(format!("Currently in: {}", self.stage_name), format!("HP: {} / {}", self.life, self.max_life))
|
||||
}
|
||||
DiscordRPCState::Jukebox => ("In the menus".to_owned(), "Listening to the soundtrack".to_owned()),
|
||||
};
|
||||
|
||||
log::debug!("Updating Discord RPC state: {} - {}", state, details);
|
||||
|
||||
let mut activity_assets = Assets::new().large_image("drs");
|
||||
|
||||
if self.difficulty.is_some() {
|
||||
let difficulty = self.difficulty.unwrap();
|
||||
|
||||
let asset_name = match difficulty {
|
||||
GameDifficulty::Easy => "deasy",
|
||||
GameDifficulty::Normal => "dnormal",
|
||||
GameDifficulty::Hard => "dhard",
|
||||
};
|
||||
|
||||
let asset_label = match difficulty {
|
||||
GameDifficulty::Easy => "Easy",
|
||||
GameDifficulty::Normal => "Normal",
|
||||
GameDifficulty::Hard => "Hard",
|
||||
};
|
||||
|
||||
activity_assets = activity_assets.small_image(asset_name).small_text(asset_label);
|
||||
}
|
||||
|
||||
let activity = Activity::new()
|
||||
.state(state.as_str())
|
||||
.details(details.as_str())
|
||||
.assets(activity_assets)
|
||||
.buttons(vec![Button::new("doukutsu-rs on GitHub", "https://github.com/doukutsu-rs/doukutsu-rs")]);
|
||||
|
||||
match self.client.set_activity(activity) {
|
||||
Ok(()) => {
|
||||
*can_update = true;
|
||||
log::debug!("Discord RPC state updated successfully");
|
||||
}
|
||||
Err(e) => log::error!("Failed to update Discord RPC state: {}", e),
|
||||
};
|
||||
|
||||
Ok(()) // whatever
|
||||
}
|
||||
|
||||
pub fn update_stage(&mut self, stage: &StageData) -> GameResult {
|
||||
self.stage_name = stage.name.clone();
|
||||
self.update()
|
||||
}
|
||||
|
||||
pub fn update_hp(&mut self, player: &Player) -> GameResult {
|
||||
self.life = player.life;
|
||||
self.max_life = player.max_life;
|
||||
self.update()
|
||||
}
|
||||
|
||||
pub fn update_difficulty(&mut self, difficulty: GameDifficulty) -> GameResult {
|
||||
self.difficulty = Some(difficulty);
|
||||
self.update()
|
||||
}
|
||||
|
||||
pub fn set_initializing(&mut self) -> GameResult {
|
||||
self.set_state(DiscordRPCState::Initializing)
|
||||
}
|
||||
|
||||
pub fn set_idling(&mut self) -> GameResult {
|
||||
self.difficulty = None;
|
||||
self.set_state(DiscordRPCState::Idling)
|
||||
}
|
||||
|
||||
pub fn set_in_game(&mut self) -> GameResult {
|
||||
self.set_state(DiscordRPCState::InGame)
|
||||
}
|
||||
|
||||
pub fn set_in_jukebox(&mut self) -> GameResult {
|
||||
self.set_state(DiscordRPCState::Jukebox)
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, state: DiscordRPCState) -> GameResult {
|
||||
self.state = state;
|
||||
self.update()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) -> GameResult {
|
||||
if !self.ready {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ = self.client.clear_activity();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dispose(&mut self) {
|
||||
if !self.ready {
|
||||
return;
|
||||
}
|
||||
|
||||
let can_update = self.can_update.lock();
|
||||
if can_update.is_ok() {
|
||||
*can_update.unwrap() = false;
|
||||
}
|
||||
|
||||
let _ = self.client.close();
|
||||
}
|
||||
}
|
|
@ -2,14 +2,18 @@ use std::cell::RefCell;
|
|||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use imgui::{Image, MouseButton, Window, WindowFlags};
|
||||
use imgui::{Image, MouseButton, Window};
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::components::background::Background;
|
||||
use crate::components::tilemap::{TileLayer, Tilemap};
|
||||
use crate::frame::Frame;
|
||||
use crate::stage::{Stage, StageTexturePaths};
|
||||
use crate::{graphics, Context, GameResult, SharedGameState, I_MAG};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::stage::{Stage, StageTexturePaths};
|
||||
use crate::graphics::texture_set::I_MAG;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum CurrentTool {
|
||||
|
|
7371
src/encoding.rs
|
@ -11,11 +11,12 @@ use crate::engine_constants::npcs::NPCConsts;
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::gamepad::{Axis, Button};
|
||||
use crate::game::player::ControlMode;
|
||||
use crate::game::scripting::tsc::text_script::TextScriptEncoding;
|
||||
use crate::game::settings::Settings;
|
||||
use crate::game::shared_game_state::{FontData, Season};
|
||||
use crate::i18n::Locale;
|
||||
use crate::player::ControlMode;
|
||||
use crate::scripting::tsc::text_script::TextScriptEncoding;
|
||||
use crate::settings::Settings;
|
||||
use crate::shared_game_state::{Language, Season};
|
||||
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
|
||||
use crate::sound::SoundManager;
|
||||
|
||||
|
@ -66,7 +67,7 @@ pub struct GameConsts {
|
|||
pub tile_offset_x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CaretConsts {
|
||||
pub offsets: [(i32, i32); 18],
|
||||
pub bubble_left_rects: Vec<Rect<u16>>,
|
||||
|
@ -96,34 +97,6 @@ pub struct TextureSizeTable {
|
|||
sizes: HashMap<String, (u16, u16)>,
|
||||
}
|
||||
|
||||
impl Clone for CaretConsts {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
offsets: self.offsets,
|
||||
bubble_left_rects: self.bubble_left_rects.clone(),
|
||||
bubble_right_rects: self.bubble_right_rects.clone(),
|
||||
projectile_dissipation_left_rects: self.projectile_dissipation_left_rects.clone(),
|
||||
projectile_dissipation_right_rects: self.projectile_dissipation_right_rects.clone(),
|
||||
projectile_dissipation_up_rects: self.projectile_dissipation_up_rects.clone(),
|
||||
shoot_rects: self.shoot_rects.clone(),
|
||||
zzz_rects: self.zzz_rects.clone(),
|
||||
drowned_quote_left_rect: self.drowned_quote_left_rect,
|
||||
drowned_quote_right_rect: self.drowned_quote_right_rect,
|
||||
level_up_rects: self.level_up_rects.clone(),
|
||||
level_down_rects: self.level_down_rects.clone(),
|
||||
hurt_particles_rects: self.hurt_particles_rects.clone(),
|
||||
explosion_rects: self.explosion_rects.clone(),
|
||||
little_particles_rects: self.little_particles_rects.clone(),
|
||||
exhaust_rects: self.exhaust_rects.clone(),
|
||||
question_left_rect: self.question_left_rect,
|
||||
question_right_rect: self.question_right_rect,
|
||||
small_projectile_dissipation: self.small_projectile_dissipation.clone(),
|
||||
empty_text: self.empty_text.clone(),
|
||||
push_jump_key: self.push_jump_key.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct BulletData {
|
||||
pub damage: u8,
|
||||
|
@ -174,23 +147,13 @@ pub struct BulletRects {
|
|||
pub b042_spur_trail_l3: [Rect<u16>; 6],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WeaponConsts {
|
||||
pub bullet_table: Vec<BulletData>,
|
||||
pub bullet_rects: BulletRects,
|
||||
pub level_table: [[u16; 3]; 14],
|
||||
}
|
||||
|
||||
impl Clone for WeaponConsts {
|
||||
fn clone(&self) -> WeaponConsts {
|
||||
WeaponConsts {
|
||||
bullet_table: self.bullet_table.clone(),
|
||||
bullet_rects: self.bullet_rects,
|
||||
level_table: self.level_table,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WorldConsts {
|
||||
pub snack_rect: Rect<u16>,
|
||||
|
@ -206,7 +169,7 @@ pub struct AnimatedFace {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtraSoundtrack {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
pub path: String,
|
||||
pub available: bool,
|
||||
}
|
||||
|
@ -222,6 +185,7 @@ pub struct TextScriptConsts {
|
|||
pub textbox_rect_bottom: Rect<u16>,
|
||||
pub textbox_rect_yes_no: Rect<u16>,
|
||||
pub textbox_rect_cursor: Rect<u16>,
|
||||
pub textbox_item_marker_rect: Rect<u16>,
|
||||
pub inventory_rect_top: Rect<u16>,
|
||||
pub inventory_rect_middle: Rect<u16>,
|
||||
pub inventory_rect_bottom: Rect<u16>,
|
||||
|
@ -243,10 +207,11 @@ pub struct TextScriptConsts {
|
|||
pub fade_ticks: i8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TitleConsts {
|
||||
pub intro_text: String,
|
||||
pub logo_rect: Rect<u16>,
|
||||
pub logo_splash_rect: Rect<u16>,
|
||||
pub menu_left_top: Rect<u16>,
|
||||
pub menu_right_top: Rect<u16>,
|
||||
pub menu_left_bottom: Rect<u16>,
|
||||
|
@ -263,35 +228,32 @@ pub struct TitleConsts {
|
|||
pub cursor_sue: [Rect<u16>; 4],
|
||||
}
|
||||
|
||||
impl Clone for TitleConsts {
|
||||
fn clone(&self) -> TitleConsts {
|
||||
TitleConsts {
|
||||
intro_text: self.intro_text.clone(),
|
||||
logo_rect: self.logo_rect,
|
||||
menu_left_top: self.menu_left_top,
|
||||
menu_right_top: self.menu_right_top,
|
||||
menu_left_bottom: self.menu_left_bottom,
|
||||
menu_right_bottom: self.menu_right_bottom,
|
||||
menu_top: self.menu_top,
|
||||
menu_bottom: self.menu_bottom,
|
||||
menu_middle: self.menu_middle,
|
||||
menu_left: self.menu_left,
|
||||
menu_right: self.menu_right,
|
||||
cursor_quote: self.cursor_quote,
|
||||
cursor_curly: self.cursor_curly,
|
||||
cursor_toroko: self.cursor_toroko,
|
||||
cursor_king: self.cursor_king,
|
||||
cursor_sue: self.cursor_sue,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GamepadConsts {
|
||||
pub button_rects: HashMap<Button, [Rect<u16>; 4]>,
|
||||
pub axis_rects: HashMap<Axis, [Rect<u16>; 4]>,
|
||||
}
|
||||
|
||||
impl GamepadConsts {
|
||||
fn rects(base: Rect<u16>) -> [Rect<u16>; 4] {
|
||||
[
|
||||
base,
|
||||
Rect::new(base.left + 64, base.top, base.right + 64, base.bottom),
|
||||
Rect::new(base.left + 128, base.top, base.right + 128, base.bottom),
|
||||
Rect::new(base.left + 64, base.top + 128, base.right + 64, base.bottom + 128),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConstants {
|
||||
pub base_paths: Vec<String>,
|
||||
pub is_cs_plus: bool,
|
||||
pub is_switch: bool,
|
||||
pub is_demo: bool,
|
||||
pub supports_og_textures: bool,
|
||||
pub has_difficulty_menu: bool,
|
||||
pub supports_two_player: bool,
|
||||
pub game: GameConsts,
|
||||
pub player: PlayerConsts,
|
||||
pub booster: BoosterConsts,
|
||||
|
@ -304,49 +266,18 @@ pub struct EngineConstants {
|
|||
pub title: TitleConsts,
|
||||
pub inventory_dim_color: Color,
|
||||
pub font_path: String,
|
||||
pub font_scale: f32,
|
||||
pub font_space_offset: f32,
|
||||
pub soundtracks: Vec<ExtraSoundtrack>,
|
||||
pub music_table: Vec<String>,
|
||||
pub organya_paths: Vec<String>,
|
||||
pub credit_illustration_paths: Vec<String>,
|
||||
pub player_skin_paths: Vec<String>,
|
||||
pub animated_face_table: Vec<AnimatedFace>,
|
||||
pub string_table: HashMap<String, String>,
|
||||
pub missile_flags: Vec<u16>,
|
||||
pub locales: HashMap<String, Locale>,
|
||||
}
|
||||
|
||||
impl Clone for EngineConstants {
|
||||
fn clone(&self) -> EngineConstants {
|
||||
EngineConstants {
|
||||
base_paths: self.base_paths.clone(),
|
||||
is_cs_plus: self.is_cs_plus,
|
||||
is_switch: self.is_switch,
|
||||
supports_og_textures: self.supports_og_textures,
|
||||
game: self.game,
|
||||
player: self.player,
|
||||
booster: self.booster,
|
||||
caret: self.caret.clone(),
|
||||
world: self.world,
|
||||
npc: self.npc,
|
||||
weapon: self.weapon.clone(),
|
||||
tex_sizes: self.tex_sizes.clone(),
|
||||
textscript: self.textscript,
|
||||
title: self.title.clone(),
|
||||
inventory_dim_color: self.inventory_dim_color,
|
||||
font_path: self.font_path.clone(),
|
||||
font_scale: self.font_scale,
|
||||
font_space_offset: self.font_space_offset,
|
||||
soundtracks: self.soundtracks.clone(),
|
||||
music_table: self.music_table.clone(),
|
||||
organya_paths: self.organya_paths.clone(),
|
||||
credit_illustration_paths: self.credit_illustration_paths.clone(),
|
||||
animated_face_table: self.animated_face_table.clone(),
|
||||
string_table: self.string_table.clone(),
|
||||
missile_flags: self.missile_flags.clone(),
|
||||
locales: self.locales.clone(),
|
||||
}
|
||||
}
|
||||
pub locales: Vec<Locale>,
|
||||
pub gamepad: GamepadConsts,
|
||||
pub stage_encoding: Option<TextScriptEncoding>,
|
||||
}
|
||||
|
||||
impl EngineConstants {
|
||||
|
@ -355,7 +286,10 @@ impl EngineConstants {
|
|||
base_paths: Vec::new(),
|
||||
is_cs_plus: false,
|
||||
is_switch: false,
|
||||
is_demo: false,
|
||||
supports_og_textures: false,
|
||||
has_difficulty_menu: true,
|
||||
supports_two_player: cfg!(not(target_os = "android")),
|
||||
game: GameConsts {
|
||||
intro_stage: 72,
|
||||
intro_event: 100,
|
||||
|
@ -1357,6 +1291,7 @@ impl EngineConstants {
|
|||
"bkSunset" => (320, 240), // nxengine
|
||||
"bkSunset480fix" => (480, 272), // nxengine
|
||||
"bkWater" => (32, 48),
|
||||
"buttons" => (256, 256),
|
||||
"Bullet" => (320, 176),
|
||||
"Caret" => (320, 240),
|
||||
"casts" => (320, 240),
|
||||
|
@ -1391,9 +1326,18 @@ impl EngineConstants {
|
|||
"Face4" => (288, 240), // switch
|
||||
"Face5" => (288, 240), // switch
|
||||
"Fade" => (256, 32),
|
||||
"headband/ogph/Casts" => (320, 240),
|
||||
"headband/ogph/Npc/NpcGuest" => (320, 184),
|
||||
"headband/ogph/Npc/NpcMiza" => (320, 240),
|
||||
"headband/ogph/Npc/NpcRegu" => (320, 240),
|
||||
"headband/plus/Casts" => (320, 240),
|
||||
"headband/plus/Npc/NpcGuest" => (320, 184),
|
||||
"headband/plus/Npc/NpcMiza" => (320, 240),
|
||||
"headband/plus/Npc/NpcRegu" => (320, 240),
|
||||
"ItemImage" => (256, 128),
|
||||
"Loading" => (64, 8),
|
||||
"MyChar" => (200, 64),
|
||||
"mychar_p2" => (200, 384), // switch
|
||||
"Npc/Npc0" => (32, 32),
|
||||
"Npc/NpcAlmo1" => (320, 240),
|
||||
"Npc/NpcAlmo2" => (320, 240),
|
||||
|
@ -1475,6 +1419,7 @@ impl EngineConstants {
|
|||
"Stage/PrtWhite" => (256, 240),
|
||||
"TextBox" => (244, 144),
|
||||
"Title" => (320, 48),
|
||||
"triangles" => (20, 5),
|
||||
},
|
||||
textscript: TextScriptConsts {
|
||||
encoding: TextScriptEncoding::ShiftJIS,
|
||||
|
@ -1486,6 +1431,7 @@ impl EngineConstants {
|
|||
textbox_rect_bottom: Rect { left: 0, top: 16, right: 244, bottom: 24 },
|
||||
textbox_rect_yes_no: Rect { left: 152, top: 48, right: 244, bottom: 80 },
|
||||
textbox_rect_cursor: Rect { left: 112, top: 88, right: 128, bottom: 104 },
|
||||
textbox_item_marker_rect: Rect { left: 64, top: 48, right: 70, bottom: 54 },
|
||||
inventory_rect_top: Rect { left: 0, top: 0, right: 244, bottom: 8 },
|
||||
inventory_rect_middle: Rect { left: 0, top: 8, right: 244, bottom: 16 },
|
||||
inventory_rect_bottom: Rect { left: 0, top: 16, right: 244, bottom: 24 },
|
||||
|
@ -1518,6 +1464,7 @@ impl EngineConstants {
|
|||
title: TitleConsts {
|
||||
intro_text: "Studio Pixel presents".to_owned(),
|
||||
logo_rect: Rect { left: 0, top: 0, right: 144, bottom: 40 },
|
||||
logo_splash_rect: Rect { left: 0, top: 0, right: 0, bottom: 0 }, //Hidden so patches can display splash art / subtitle
|
||||
menu_left_top: Rect { left: 0, top: 0, right: 8, bottom: 8 },
|
||||
menu_right_top: Rect { left: 236, top: 0, right: 244, bottom: 8 },
|
||||
menu_left_bottom: Rect { left: 0, top: 16, right: 8, bottom: 24 },
|
||||
|
@ -1560,13 +1507,12 @@ impl EngineConstants {
|
|||
},
|
||||
inventory_dim_color: Color::from_rgba(0, 0, 0, 0),
|
||||
font_path: "csfont.fnt".to_owned(),
|
||||
font_scale: 1.0,
|
||||
font_space_offset: 0.0,
|
||||
soundtracks: vec![
|
||||
ExtraSoundtrack { name: "Remastered".to_owned(), path: "/base/Ogg11/".to_owned(), available: false },
|
||||
ExtraSoundtrack { name: "New".to_owned(), path: "/base/Ogg/".to_owned(), available: false },
|
||||
ExtraSoundtrack { name: "Famitracks".to_owned(), path: "/base/ogg17/".to_owned(), available: false },
|
||||
ExtraSoundtrack { name: "Ridiculon".to_owned(), path: "/base/ogg_ridic/".to_owned(), available: false },
|
||||
ExtraSoundtrack { id: "remastered".to_owned(), path: "/base/Ogg11/".to_owned(), available: false },
|
||||
ExtraSoundtrack { id: "new".to_owned(), path: "/base/Ogg/".to_owned(), available: false },
|
||||
ExtraSoundtrack { id: "famitracks".to_owned(), path: "/base/ogg17/".to_owned(), available: false },
|
||||
ExtraSoundtrack { id: "ridiculon".to_owned(), path: "/base/ogg_ridic/".to_owned(), available: false },
|
||||
],
|
||||
music_table: vec![
|
||||
"xxxx".to_owned(),
|
||||
|
@ -1620,18 +1566,46 @@ impl EngineConstants {
|
|||
"/Resource/ORG/".to_owned(), // CSE2E
|
||||
],
|
||||
credit_illustration_paths: vec![
|
||||
"".to_owned(),
|
||||
String::new(),
|
||||
"Resource/BITMAP/".to_owned(), // CSE2E
|
||||
"endpic/".to_owned(), // NXEngine
|
||||
],
|
||||
player_skin_paths: vec!["MyChar".to_owned()],
|
||||
animated_face_table: vec![AnimatedFace { face_id: 0, anim_id: 0, anim_frames: vec![(0, 0)] }],
|
||||
string_table: HashMap::new(),
|
||||
missile_flags: vec![200, 201, 202, 218, 550, 766, 880, 920, 1551],
|
||||
locales: HashMap::new(),
|
||||
locales: Vec::new(),
|
||||
gamepad: GamepadConsts {
|
||||
button_rects: HashMap::from([
|
||||
(Button::North, GamepadConsts::rects(Rect::new(0, 0, 32, 16))),
|
||||
(Button::South, GamepadConsts::rects(Rect::new(0, 16, 32, 32))),
|
||||
(Button::East, GamepadConsts::rects(Rect::new(0, 32, 32, 48))),
|
||||
(Button::West, GamepadConsts::rects(Rect::new(0, 48, 32, 64))),
|
||||
(Button::DPadDown, GamepadConsts::rects(Rect::new(0, 64, 32, 80))),
|
||||
(Button::DPadUp, GamepadConsts::rects(Rect::new(0, 80, 32, 96))),
|
||||
(Button::DPadRight, GamepadConsts::rects(Rect::new(0, 96, 32, 112))),
|
||||
(Button::DPadLeft, GamepadConsts::rects(Rect::new(0, 112, 32, 128))),
|
||||
(Button::LeftShoulder, GamepadConsts::rects(Rect::new(32, 32, 64, 48))),
|
||||
(Button::RightShoulder, GamepadConsts::rects(Rect::new(32, 48, 64, 64))),
|
||||
(Button::Start, GamepadConsts::rects(Rect::new(32, 96, 64, 112))),
|
||||
(Button::Back, GamepadConsts::rects(Rect::new(32, 112, 64, 128))),
|
||||
(Button::LeftStick, GamepadConsts::rects(Rect::new(32, 0, 64, 16))),
|
||||
(Button::RightStick, GamepadConsts::rects(Rect::new(32, 16, 64, 32))),
|
||||
]),
|
||||
axis_rects: HashMap::from([
|
||||
(Axis::LeftX, GamepadConsts::rects(Rect::new(32, 0, 64, 16))),
|
||||
(Axis::LeftY, GamepadConsts::rects(Rect::new(32, 0, 64, 16))),
|
||||
(Axis::RightX, GamepadConsts::rects(Rect::new(32, 16, 64, 32))),
|
||||
(Axis::RightY, GamepadConsts::rects(Rect::new(32, 16, 64, 32))),
|
||||
(Axis::TriggerLeft, GamepadConsts::rects(Rect::new(32, 64, 64, 80))),
|
||||
(Axis::TriggerRight, GamepadConsts::rects(Rect::new(32, 80, 64, 96))),
|
||||
]),
|
||||
},
|
||||
stage_encoding: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_csplus_patches(&mut self, sound_manager: &SoundManager) {
|
||||
pub fn apply_csplus_patches(&mut self, sound_manager: &mut SoundManager) {
|
||||
log::info!("Applying Cave Story+ constants patches...");
|
||||
|
||||
self.is_cs_plus = true;
|
||||
|
@ -1641,7 +1615,7 @@ impl EngineConstants {
|
|||
self.tex_sizes.insert("Npc/NpcRegu".to_owned(), (320, 410));
|
||||
self.tex_sizes.insert("ui".to_owned(), (128, 32));
|
||||
self.textscript.reset_invicibility_on_any_script = false;
|
||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 50 };
|
||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 216, bottom: 48 };
|
||||
|
||||
self.title.menu_left_top = Rect { left: 0, top: 0, right: 4, bottom: 4 };
|
||||
self.title.menu_right_top = Rect { left: 12, top: 0, right: 16, bottom: 4 };
|
||||
|
@ -1682,6 +1656,10 @@ impl EngineConstants {
|
|||
let _ = sound_manager.set_sample_params(2, typewriter_sample);
|
||||
}
|
||||
|
||||
pub fn is_base(&self) -> bool {
|
||||
!self.is_switch && !self.is_cs_plus && !self.is_demo
|
||||
}
|
||||
|
||||
pub fn apply_csplus_nx_patches(&mut self) {
|
||||
log::info!("Applying Switch-specific Cave Story+ constants patches...");
|
||||
|
||||
|
@ -1702,10 +1680,23 @@ impl EngineConstants {
|
|||
self.textscript.fade_ticks = 21;
|
||||
self.game.tile_offset_x = 3;
|
||||
self.game.new_game_player_pos = (13, 8);
|
||||
self.player_skin_paths.push("mychar_p2".to_owned());
|
||||
}
|
||||
|
||||
pub fn apply_csdemo_patches(&mut self) {
|
||||
log::info!("Applying Wiiware DEMO-specific Cave Story+ constants patches...");
|
||||
|
||||
self.is_demo = true;
|
||||
self.supports_og_textures = true;
|
||||
self.game.new_game_stage = 11;
|
||||
self.game.new_game_event = 302;
|
||||
self.game.new_game_player_pos = (8, 6);
|
||||
self.title.logo_splash_rect = Rect { left: 224, top: 0, right: 320, bottom: 48 };
|
||||
}
|
||||
|
||||
pub fn rebuild_path_list(&mut self, mod_path: Option<String>, season: Season, settings: &Settings) {
|
||||
self.base_paths.clear();
|
||||
self.base_paths.push("/builtin/builtin_data/".to_owned());
|
||||
self.base_paths.push("/".to_owned());
|
||||
|
||||
if self.is_cs_plus {
|
||||
|
@ -1721,12 +1712,12 @@ impl EngineConstants {
|
|||
}
|
||||
}
|
||||
|
||||
if settings.locale != Language::English {
|
||||
self.base_paths.insert(0, format!("/base/{}/", settings.locale.to_language_code()));
|
||||
if settings.locale != "en".to_string() {
|
||||
self.base_paths.insert(0, format!("/base/{}/", settings.locale));
|
||||
}
|
||||
} else {
|
||||
if settings.locale != Language::English {
|
||||
self.base_paths.insert(0, format!("/{}/", settings.locale.to_language_code()));
|
||||
if settings.locale != "en".to_string() {
|
||||
self.base_paths.insert(0, format!("/{}/", settings.locale));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1792,9 +1783,29 @@ impl EngineConstants {
|
|||
}
|
||||
|
||||
pub fn load_locales(&mut self, ctx: &mut Context) -> GameResult {
|
||||
for language in Language::values() {
|
||||
self.locales.insert(language.to_string(), Locale::new(ctx, language.to_language_code(), language.font()));
|
||||
log::info!("Loaded locale {} ({}).", language.to_string(), language.to_language_code());
|
||||
self.locales.clear();
|
||||
|
||||
let locale_files = filesystem::read_dir_find(ctx, &self.base_paths, "locale/");
|
||||
|
||||
for locale_file in locale_files.unwrap() {
|
||||
if locale_file.extension().unwrap() != "json" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let locale_code = {
|
||||
let filename = locale_file.file_name().unwrap().to_string_lossy();
|
||||
let mut parts = filename.split('.');
|
||||
parts.next().unwrap().to_string()
|
||||
};
|
||||
|
||||
let mut locale = Locale::new(ctx, &self.base_paths, &locale_code);
|
||||
|
||||
if locale_code == "jp" && filesystem::exists(ctx, "/base/credit_jp.tsc") {
|
||||
locale.set_font(FontData::new("csfontjp.fnt".to_owned(), 0.5, 0.0));
|
||||
}
|
||||
|
||||
self.locales.push(locale.clone());
|
||||
log::info!("Loaded locale {} ({})", locale_code, locale.name.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1803,7 +1814,7 @@ impl EngineConstants {
|
|||
pub fn apply_constant_json_files(&mut self) {}
|
||||
|
||||
pub fn load_texture_size_hints(&mut self, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(file) = filesystem::open_find(ctx, &self.base_paths, "/texture_sizes.json") {
|
||||
if let Ok(file) = filesystem::open_find(ctx, &self.base_paths, "texture_sizes.json") {
|
||||
match serde_json::from_reader::<_, TextureSizeTable>(file) {
|
||||
Ok(tex_overrides) => {
|
||||
for (key, (x, y)) in tex_overrides.sizes {
|
||||
|
@ -1820,7 +1831,7 @@ impl EngineConstants {
|
|||
/// even though they match vanilla 1:1, we should load them for completeness
|
||||
/// or if any crazy person uses it for a CS+ mod...
|
||||
pub fn load_csplus_tables(&mut self, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::open_find(ctx, &self.base_paths, "/bullet.tbl") {
|
||||
if let Ok(mut file) = filesystem::open_find(ctx, &self.base_paths, "bullet.tbl") {
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
let bullets = data.len() / 0x2A;
|
||||
|
@ -1851,7 +1862,7 @@ impl EngineConstants {
|
|||
log::info!("Loaded bullet.tbl.");
|
||||
}
|
||||
|
||||
if let Ok(mut file) = filesystem::open_find(ctx, &self.base_paths, "/arms_level.tbl") {
|
||||
if let Ok(mut file) = filesystem::open_find(ctx, &self.base_paths, "arms_level.tbl") {
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
let mut f = Cursor::new(data);
|
||||
|
@ -1879,7 +1890,7 @@ impl EngineConstants {
|
|||
// Bugfix for Malco cutscene - this face should be used but the original tsc has the wrong ID
|
||||
self.animated_face_table.push(AnimatedFace { face_id: 5, anim_id: 4, anim_frames: vec![(4, 0)] });
|
||||
|
||||
if let Ok(file) = filesystem::open_find(ctx, &self.base_paths, "/faceanm.dat") {
|
||||
if let Ok(file) = filesystem::open_find(ctx, &self.base_paths, "faceanm.dat") {
|
||||
let buf = BufReader::new(file);
|
||||
let mut face_id = 1;
|
||||
let mut anim_id = 0;
|
||||
|
|
|
@ -2,8 +2,8 @@ use std::fmt::Debug;
|
|||
use std::marker::PhantomData;
|
||||
use std::ops::Index;
|
||||
|
||||
use serde::de::{Error, SeqAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde::de::{Error, SeqAccess, Visitor};
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::macros::fmt::Formatter;
|
||||
|
@ -14,8 +14,8 @@ pub struct SafeNPCRect<const T: usize>(pub [Rect<u16>; T]);
|
|||
impl<const T: usize> Serialize for SafeNPCRect<T> {
|
||||
#[inline]
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ impl<'de, const T: usize> Visitor<'de> for SafeNPCRectArrayVisitor<T> {
|
|||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut rects = [Rect::default(); T];
|
||||
for (i, rect) in rects.iter_mut().enumerate() {
|
||||
|
@ -46,8 +46,8 @@ impl<'de, const T: usize> Visitor<'de> for SafeNPCRectArrayVisitor<T> {
|
|||
|
||||
impl<'de, const T: usize> Deserialize<'de> for SafeNPCRect<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(SafeNPCRectArrayVisitor(PhantomData)).map(SafeNPCRect)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::common::interpolate_fix9_scale;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::game::frame::Frame;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
|
||||
pub trait GameEntity<C> {
|
||||
fn tick(&mut self, state: &mut SharedGameState, custom: C) -> GameResult;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use std::any::Any;
|
||||
|
||||
use imgui::DrawData;
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use crate::Game;
|
||||
use crate::graphics::VSyncMode;
|
||||
use crate::framework::graphics::{BlendMode, VSyncMode};
|
||||
use crate::game::Game;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -25,13 +25,17 @@ pub enum BackendShader {
|
|||
}
|
||||
|
||||
pub trait Backend {
|
||||
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>>;
|
||||
fn create_event_loop(&self, ctx: &Context) -> GameResult<Box<dyn BackendEventLoop>>;
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub trait BackendEventLoop {
|
||||
fn run(&mut self, game: &mut Game, ctx: &mut Context);
|
||||
|
||||
fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>>;
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub trait BackendRenderer {
|
||||
|
@ -41,7 +45,9 @@ pub trait BackendRenderer {
|
|||
|
||||
fn present(&mut self) -> GameResult;
|
||||
|
||||
fn set_vsync_mode(&mut self, _mode: VSyncMode) -> GameResult { Ok(()) }
|
||||
fn set_vsync_mode(&mut self, _mode: VSyncMode) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_draw(&mut self, _width: f32, _height: f32) -> GameResult {
|
||||
Ok(())
|
||||
|
@ -79,6 +85,8 @@ pub trait BackendRenderer {
|
|||
texture: Option<&Box<dyn BackendTexture>>,
|
||||
shader: BackendShader,
|
||||
) -> GameResult;
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub trait BackendTexture {
|
||||
|
@ -93,12 +101,23 @@ pub trait BackendTexture {
|
|||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub trait BackendGamepad {
|
||||
fn set_rumble(&mut self, low_freq: u16, high_freq: u16, duration_ms: u32) -> GameResult;
|
||||
|
||||
fn instance_id(&self) -> u32;
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn Backend>> {
|
||||
if headless {
|
||||
return crate::framework::backend_null::NullBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend-horizon"))]
|
||||
{
|
||||
return crate::framework::backend_horizon::HorizonBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend-glutin"))]
|
||||
{
|
||||
return crate::framework::backend_glutin::GlutinBackend::new();
|
||||
|
@ -117,4 +136,5 @@ pub enum SpriteBatchCommand {
|
|||
DrawRect(Rect<f32>, Rect<f32>),
|
||||
DrawRectFlip(Rect<f32>, Rect<f32>, bool, bool),
|
||||
DrawRectTinted(Rect<f32>, Rect<f32>, Color),
|
||||
DrawRectFlipTinted(Rect<f32>, Rect<f32>, bool, bool, Color),
|
||||
}
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
use std::any::Any;
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::ffi::c_void;
|
||||
use std::io::Read;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::vec::Vec;
|
||||
|
||||
use glutin::{Api, ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext};
|
||||
use glutin::event::{ElementState, Event, TouchPhase, VirtualKeyCode, WindowEvent};
|
||||
use glutin::event_loop::{ControlFlow, EventLoop};
|
||||
use glutin::window::WindowBuilder;
|
||||
use glutin::{Api, ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext};
|
||||
use imgui::{DrawCmdParams, DrawData, DrawIdx, DrawVert};
|
||||
use winit::window::Icon;
|
||||
|
||||
use crate::{Game, GAME_SUSPENDED};
|
||||
use crate::common::Rect;
|
||||
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::gl;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
|
||||
use crate::game::Game;
|
||||
use crate::game::GAME_SUSPENDED;
|
||||
use crate::input::touch_controls::TouchPoint;
|
||||
|
||||
pub struct GlutinBackend;
|
||||
|
@ -29,7 +35,7 @@ impl GlutinBackend {
|
|||
}
|
||||
|
||||
impl Backend for GlutinBackend {
|
||||
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> {
|
||||
fn create_event_loop(&self, _ctx: &Context) -> GameResult<Box<dyn BackendEventLoop>> {
|
||||
#[cfg(target_os = "android")]
|
||||
loop {
|
||||
match ndk_glue::native_window().as_ref() {
|
||||
|
@ -43,6 +49,10 @@ impl Backend for GlutinBackend {
|
|||
|
||||
Ok(Box::new(GlutinEventLoop { refs: Rc::new(UnsafeCell::new(None)) }))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlutinEventLoop {
|
||||
|
@ -50,17 +60,19 @@ pub struct GlutinEventLoop {
|
|||
}
|
||||
|
||||
impl GlutinEventLoop {
|
||||
fn get_context(&self, event_loop: &EventLoop<()>) -> &mut WindowedContext<PossiblyCurrent> {
|
||||
fn get_context(&self, ctx: &Context, event_loop: &EventLoop<()>) -> &mut WindowedContext<PossiblyCurrent> {
|
||||
let mut refs = unsafe { &mut *self.refs.get() };
|
||||
|
||||
if refs.is_none() {
|
||||
let mut window = WindowBuilder::new();
|
||||
|
||||
let windowed_context = ContextBuilder::new();
|
||||
let windowed_context = windowed_context.with_gl(GlRequest::Specific(Api::OpenGl, (3, 0)));
|
||||
#[cfg(target_os = "android")]
|
||||
let windowed_context = windowed_context.with_gl(GlRequest::Specific(Api::OpenGlEs, (2, 0)));
|
||||
|
||||
let windowed_context = windowed_context.with_gl_profile(GlProfile::Core)
|
||||
let windowed_context = windowed_context
|
||||
.with_gl_profile(GlProfile::Core)
|
||||
.with_gl_debug_flag(false)
|
||||
.with_pixel_format(24, 8)
|
||||
.with_vsync(true);
|
||||
|
@ -72,7 +84,24 @@ impl GlutinEventLoop {
|
|||
}
|
||||
|
||||
window = window.with_title("doukutsu-rs");
|
||||
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "android", target_os = "horizon")))]
|
||||
{
|
||||
let mut file = filesystem::open(&ctx, "/builtin/icon.bmp").unwrap();
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
file.read_to_end(&mut buf);
|
||||
|
||||
let mut img = match image::load_from_memory_with_format(buf.as_slice(), image::ImageFormat::Bmp) {
|
||||
Ok(image) => image.into_rgba8(),
|
||||
Err(e) => panic!("Cannot set window icon")
|
||||
};
|
||||
|
||||
let (width, height) = img.dimensions();
|
||||
let icon = Icon::from_rgba(img.into_raw(), width, height).unwrap();
|
||||
|
||||
window = window.with_window_icon(Some(icon));
|
||||
}
|
||||
|
||||
let windowed_context = windowed_context.build_windowed(window, event_loop).unwrap();
|
||||
|
||||
let windowed_context = unsafe { windowed_context.make_current().unwrap() };
|
||||
|
@ -111,18 +140,24 @@ fn request_android_redraw() {
|
|||
#[cfg(target_os = "android")]
|
||||
fn get_insets() -> GameResult<(f32, f32, f32, f32)> {
|
||||
unsafe {
|
||||
use jni::objects::JObject;
|
||||
use jni::JavaVM;
|
||||
|
||||
let vm_ptr = ndk_glue::native_activity().vm();
|
||||
let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?;
|
||||
let vm = JavaVM::from_raw(vm_ptr)?;
|
||||
let vm_env = vm.attach_current_thread()?;
|
||||
|
||||
//let class = vm_env.find_class("io/github/doukutsu_rs/MainActivity")?;
|
||||
let class = vm_env.new_global_ref(ndk_glue::native_activity().activity())?;
|
||||
let class = vm_env.new_global_ref(JObject::from_raw(ndk_glue::native_activity().activity()))?;
|
||||
let field = vm_env.get_field(class.as_obj(), "displayInsets", "[I")?.to_jni().l as jni::sys::jintArray;
|
||||
|
||||
let mut elements = [0; 4];
|
||||
vm_env.get_int_array_region(field, 0, &mut elements)?;
|
||||
|
||||
vm_env.delete_local_ref(field.into());
|
||||
vm_env.delete_local_ref(JObject::from_raw(field));
|
||||
|
||||
//Game always runs with horizontal orientation so top and bottom cutouts not needed and only wastes piece of the screen
|
||||
elements[1] = 0;
|
||||
elements[3] = 0;
|
||||
|
||||
Ok((elements[0] as f32, elements[1] as f32, elements[2] as f32, elements[3] as f32))
|
||||
}
|
||||
|
@ -140,8 +175,7 @@ impl BackendEventLoop for GlutinEventLoop {
|
|||
let event_loop = EventLoop::new();
|
||||
let state_ref = unsafe { &mut *game.state.get() };
|
||||
let window: &'static mut WindowedContext<PossiblyCurrent> =
|
||||
unsafe { std::mem::transmute(self.get_context(&event_loop)) };
|
||||
|
||||
unsafe { std::mem::transmute(self.get_context(&ctx, &event_loop)) };
|
||||
{
|
||||
let size = window.window().inner_size();
|
||||
ctx.real_screen_size = (size.width, size.height);
|
||||
|
@ -288,6 +322,17 @@ impl BackendEventLoop for GlutinEventLoop {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
|
||||
{
|
||||
if state_ref.settings.window_mode.get_glutin_fullscreen_type() != window.window().fullscreen() {
|
||||
let fullscreen_type = state_ref.settings.window_mode.get_glutin_fullscreen_type();
|
||||
let cursor_visible = state_ref.settings.window_mode.should_display_mouse_cursor();
|
||||
|
||||
window.window().set_fullscreen(fullscreen_type);
|
||||
window.window().set_cursor_visible(cursor_visible);
|
||||
}
|
||||
}
|
||||
|
||||
game.update(ctx).unwrap();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
|
@ -319,7 +364,7 @@ impl BackendEventLoop for GlutinEventLoop {
|
|||
});
|
||||
}
|
||||
|
||||
fn new_renderer(&self, _ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
|
||||
fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
|
||||
let mut imgui = imgui::Context::create();
|
||||
imgui.io_mut().display_size = [640.0, 480.0];
|
||||
|
||||
|
@ -338,7 +383,7 @@ impl BackendEventLoop for GlutinEventLoop {
|
|||
std::ptr::null()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
|
||||
result
|
||||
|
@ -358,10 +403,14 @@ impl BackendEventLoop for GlutinEventLoop {
|
|||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
}
|
||||
|
||||
let gl_context = GLContext { gles2_mode: true, get_proc_address, swap_buffers, user_data };
|
||||
let gl_context = GLContext { gles2_mode: true, is_sdl: false, get_proc_address, swap_buffers, user_data, ctx };
|
||||
|
||||
Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn conv_keycode(code: VirtualKeyCode) -> Option<ScanCode> {
|
||||
|
|