some experimentation
149
.appveyor.yml
|
@ -1,149 +0,0 @@
|
|||
version: "0.99.0.{build}-{branch}"
|
||||
|
||||
skip_commits:
|
||||
files:
|
||||
- README.md
|
||||
|
||||
environment:
|
||||
global:
|
||||
PROJECT_NAME: doukutsu-rs
|
||||
matrix:
|
||||
- channel: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
target_name: win64
|
||||
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: x86_64-unknown-linux-gnu
|
||||
target_name: linux
|
||||
job_name: linux-x64
|
||||
appveyor_build_worker_image: Ubuntu
|
||||
- channel: stable
|
||||
target: x86_64-apple-darwin
|
||||
target_name: mac-intel
|
||||
job_name: mac-x64
|
||||
appveyor_build_worker_image: macos-monterey
|
||||
- channel: stable
|
||||
target: aarch64-apple-darwin
|
||||
target_name: mac-m1
|
||||
job_name: mac-arm64
|
||||
appveyor_build_worker_image: macos-monterey
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- appveyor_build_worker_image: Visual Studio 2019
|
||||
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %channel% --default-host %target%
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustup update
|
||||
- rustup default %channel%
|
||||
- rustc -vV
|
||||
- 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'
|
||||
|
||||
build_script:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- appveyor_build_worker_image: macos-monterey
|
||||
|
||||
install:
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -yv --default-toolchain $channel
|
||||
- export PATH=$PATH:$HOME/.cargo/bin
|
||||
- rustup update
|
||||
- rustup default $channel
|
||||
- rustup target add $target
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
- 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'
|
||||
|
||||
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
|
||||
- CARGO_INCREMENTAL=1 cargo bundle --release --target $target
|
||||
- mkdir release
|
||||
- cp LICENSE ./release/LICENSE
|
||||
- cp -a target/$target/release/bundle/osx/doukutsu-rs.app ./release/doukutsu-rs.app
|
||||
- cd release
|
||||
- codesign -s - -f ./doukutsu-rs.app/Contents/MacOS/doukutsu-rs
|
||||
- 7z a ../doukutsu-rs_$target_name.zip *
|
||||
- appveyor PushArtifact ../doukutsu-rs_$target_name.zip
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- appveyor_build_worker_image: Ubuntu
|
||||
|
||||
install:
|
||||
- sudo apt-get update && sudo apt-get -y install libasound2-dev libudev-dev libgl1-mesa-dev pkg-config
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -yv --default-toolchain $channel --default-host $target
|
||||
- export PATH=$PATH:$HOME/.cargo/bin
|
||||
- rustup update
|
||||
- rustup default $channel
|
||||
- rustc -vV
|
||||
- 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'
|
||||
|
||||
build_script:
|
||||
- export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION
|
||||
- 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
|
|
@ -1,6 +0,0 @@
|
|||
[target.aarch64-linux-android]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-lc++_static",
|
||||
"-C", "link-arg=-lc++abi",
|
||||
"-C", "link-arg=-lEGL",
|
||||
]
|
|
@ -60,3 +60,23 @@ ehthumbs_vista.db
|
|||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
### CMake ###
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
|
||||
### CMake Patch ###
|
||||
# External projects
|
||||
*-prefix/
|
||||
|
||||
build/
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
project(doukutsu_rs CXX)
|
||||
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
||||
|
||||
add_executable(doukutsu_rs
|
||||
src/common.cpp
|
||||
src/main.cpp
|
||||
src/physics.cpp
|
||||
src/shared_game_state.cpp
|
||||
src/texture_set.cpp)
|
103
Cargo.toml
|
@ -1,103 +0,0 @@
|
|||
[package]
|
||||
name = "doukutsu-rs"
|
||||
description = "A re-implementation of Cave Story (Doukutsu Monogatari) engine"
|
||||
version = "0.99.0"
|
||||
authors = ["Alula", "dawnDus"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
||||
[[bin]]
|
||||
name = "doukutsu-rs"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
bench = false
|
||||
required-features = ["exe"]
|
||||
|
||||
[profile.release]
|
||||
lto = "off"
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "doukutsu-rs"
|
||||
identifier = "io.github.doukutsu_rs"
|
||||
version = "0.99.0"
|
||||
resources = ["data"]
|
||||
copyright = "Copyright (c) 2020-2022 doukutsu-rs dev team"
|
||||
category = "Game"
|
||||
osx_minimum_system_version = "10.12"
|
||||
|
||||
[features]
|
||||
default = ["default-base", "backend-sdl", "render-opengl", "exe"]
|
||||
default-base = ["scripting-lua", "ogg-playback", "netplay"]
|
||||
ogg-playback = ["lewton"]
|
||||
backend-sdl = ["sdl2", "sdl2-sys"]
|
||||
backend-glutin = ["winit", "glutin", "render-opengl"]
|
||||
render-opengl = []
|
||||
scripting-lua = ["lua-ffi"]
|
||||
netplay = ["tokio", "serde_cbor"]
|
||||
editor = []
|
||||
hooks = ["libc"]
|
||||
exe = []
|
||||
android = []
|
||||
|
||||
[dependencies]
|
||||
#glutin = { path = "./3rdparty/glutin/glutin", optional = true }
|
||||
#lua-ffi = { path = "./3rdparty/luajit-rs", optional = true }
|
||||
#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"
|
||||
byteorder = "1.4"
|
||||
case_insensitive_hashmap = "1.0.0"
|
||||
chrono = "0.4"
|
||||
cpal = "0.13"
|
||||
directories = "3"
|
||||
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"] }
|
||||
itertools = "0.10"
|
||||
lazy_static = "1.4.0"
|
||||
lewton = { version = "0.10.2", optional = true }
|
||||
libc = { version = "0.2", 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"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_derive = "1"
|
||||
serde_cbor = { version = "0.11.2", 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 }
|
||||
# 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"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.7"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk = "0.3"
|
||||
ndk-glue = "0.3"
|
||||
ndk-sys = "0.2"
|
||||
jni = "0.19"
|
|
@ -1,233 +0,0 @@
|
|||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,android
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,gradle,android
|
||||
|
||||
### Android ###
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
### Android Patch ###
|
||||
gen-external-apklibs
|
||||
output.json
|
||||
|
||||
# Replacement of .externalNativeBuild directories introduced
|
||||
# with Android Studio 3.5.
|
||||
|
||||
### Gradle ###
|
||||
.gradle
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
### Gradle Patch ###
|
||||
**/build/
|
||||
|
||||
### AndroidStudio ###
|
||||
# Covers files to be ignored for android development using Android Studio.
|
||||
|
||||
# Built application files
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
|
||||
# Java class files
|
||||
|
||||
# Generated files
|
||||
|
||||
# Gradle files
|
||||
|
||||
# Signing files
|
||||
.signing/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
|
||||
# Log Files
|
||||
|
||||
# Android Studio
|
||||
/*/build/
|
||||
/*/local.properties
|
||||
/*/out
|
||||
/*/*/build
|
||||
/*/*/production
|
||||
*.ipr
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Android Patch
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
|
||||
# NDK
|
||||
obj/
|
||||
*.so
|
||||
|
||||
# IntelliJ IDEA
|
||||
*.iws
|
||||
/out/
|
||||
|
||||
# User-specific configurations
|
||||
.idea/caches/
|
||||
.idea/libraries/
|
||||
.idea/shelf/
|
||||
.idea/.name
|
||||
.idea/compiler.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
.idea/encodings.xml
|
||||
.idea/misc.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/vcs.xml
|
||||
.idea/jsLibraryMappings.xml
|
||||
.idea/datasources.xml
|
||||
.idea/dataSources.ids
|
||||
.idea/sqlDataSources.xml
|
||||
.idea/dynamic.xml
|
||||
.idea/uiDesigner.xml
|
||||
.idea/jarRepositories.xml
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Legacy Eclipse project files
|
||||
.classpath
|
||||
.project
|
||||
.cproject
|
||||
.settings/
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
|
||||
hs_err_pid*
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/mongoSettings.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
### AndroidStudio Patch ###
|
||||
|
||||
!/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,android
|
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,106 +0,0 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'com.github.willir.rust.cargo-ndk-android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
ndkVersion "22.1.7171670"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.doukutsu_rs"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
ndk {
|
||||
abiFilters 'x86', 'arm64-v8a', 'armeabi-v7a'
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_STL=c++_shared"
|
||||
}
|
||||
}
|
||||
|
||||
def documentsAuthorityValue = applicationId + ".documents"
|
||||
|
||||
manifestPlaceholders =
|
||||
[documentsAuthority: documentsAuthorityValue]
|
||||
|
||||
buildConfigField "String",
|
||||
"DOCUMENTS_AUTHORITY",
|
||||
"\"${documentsAuthorityValue}\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
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 = []
|
||||
verbose = true
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
buildType = "release"
|
||||
}
|
||||
debug {
|
||||
buildType = "debug"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name == 'javaPreCompileDebug') {
|
||||
task.dependsOn 'buildCargoNdkDebug'
|
||||
}
|
||||
|
||||
if (task.name == 'javaPreCompileRelease') {
|
||||
task.dependsOn 'buildCargoNdkRelease'
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.github.doukutsu_rs">
|
||||
<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"
|
||||
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">
|
||||
<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"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,53 +0,0 @@
|
|||
# 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)
|
||||
|
||||
# Copy shared STL files to Android Studio output directory so they can be
|
||||
# packaged in the APK.
|
||||
# Usage:
|
||||
#
|
||||
# find_package(ndk-stl REQUIRED)
|
||||
#
|
||||
# or
|
||||
#
|
||||
# find_package(ndk-stl REQUIRED PATHS ".")
|
||||
|
||||
#if(NOT ${ANDROID_STL} MATCHES "_shared")
|
||||
# return()
|
||||
#endif()
|
||||
|
||||
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"
|
||||
"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${so_base}.so"
|
||||
COPYONLY)
|
||||
endfunction()
|
||||
|
||||
if("${ANDROID_STL}" STREQUAL "libstdc++")
|
||||
# The default minimal system C++ runtime library.
|
||||
elseif("${ANDROID_STL}" STREQUAL "gabi++_shared")
|
||||
# The GAbi++ runtime (shared).
|
||||
message(FATAL_ERROR "gabi++_shared was not configured by ndk-stl package")
|
||||
elseif("${ANDROID_STL}" STREQUAL "stlport_shared")
|
||||
# The STLport runtime (shared).
|
||||
configure_shared_stl("stlport" "stlport_shared")
|
||||
elseif("${ANDROID_STL}" STREQUAL "gnustl_shared")
|
||||
# The GNU STL (shared).
|
||||
configure_shared_stl("gnu-libstdc++/4.9" "gnustl_shared")
|
||||
elseif("${ANDROID_STL}" STREQUAL "c++_shared")
|
||||
# The LLVM libc++ runtime (static).
|
||||
configure_shared_stl("llvm-libc++" "c++_shared")
|
||||
else()
|
||||
message(FATAL_ERROR "STL configuration ANDROID_STL=${ANDROID_STL} is not supported")
|
||||
endif()
|
||||
|
||||
# Specifies a library name, specifies whether the library is STATIC or
|
||||
# SHARED, and provides relative paths to the source code. You can
|
||||
# define multiple libraries by adding multiple add_library() commands,
|
||||
# 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)
|
|
@ -1,2 +0,0 @@
|
|||
void drs_dummy_export() {
|
||||
}
|
Before Width: | Height: | Size: 178 KiB |
|
@ -1,279 +0,0 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public class DoukutsuDocumentsProvider extends DocumentsProvider {
|
||||
private final static String[] DEFAULT_ROOT_PROJECTION =
|
||||
new String[]{
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_AVAILABLE_BYTES,
|
||||
Root.COLUMN_FLAGS
|
||||
};
|
||||
|
||||
private final static String[] DEFAULT_DOCUMENT_PROJECTION =
|
||||
new String[]{
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_SIZE,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_FLAGS
|
||||
};
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||
File file = getContext().getFilesDir();
|
||||
String id = file.getAbsolutePath();
|
||||
Log.d(DoukutsuDocumentsProvider.class.getName(), "files dir location: " + id);
|
||||
|
||||
MatrixCursor result = new MatrixCursor(projection != null ?
|
||||
projection : DEFAULT_ROOT_PROJECTION);
|
||||
|
||||
RowBuilder row = result.newRow();
|
||||
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, id);
|
||||
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));
|
||||
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);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
Log.d("dupa", "queryDocument: " + documentId);
|
||||
pushFile(result, new File(documentId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||
MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
File root = new File(parentDocumentId);
|
||||
Log.d("dupa", "doc id:" + parentDocumentId);
|
||||
|
||||
if (!root.exists()) {
|
||||
Log.d("dupa", "no such file");
|
||||
throw new FileNotFoundException("No such file: " + root.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (!root.isDirectory()) {
|
||||
Log.d("dupa", "not a directory");
|
||||
return null;
|
||||
}
|
||||
|
||||
File[] files = root.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
pushFile(result, file);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
|
||||
File file = new File(documentId);
|
||||
int imode = ParcelFileDescriptor.parseMode(mode);
|
||||
return ParcelFileDescriptor.open(file, imode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
|
||||
File file = new File(parentDocumentId, displayName);
|
||||
|
||||
if (file.exists()) {
|
||||
int nextId = 1;
|
||||
|
||||
while (file.exists()) {
|
||||
// maybe let's put the id before extension?
|
||||
file = new File(parentDocumentId, String.format("%s (%d)", displayName, nextId));
|
||||
|
||||
++nextId;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (mimeType != null && mimeType.equals(Document.MIME_TYPE_DIR)) {
|
||||
if (!file.mkdir()) {
|
||||
throw new FileNotFoundException("Couldn't create directory: " + file.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
if (!file.createNewFile()) {
|
||||
throw new FileNotFoundException("Couldn't create file: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Couldn't create file: " + e.getMessage());
|
||||
}
|
||||
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
File file = new File(documentId);
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
deleteRecursive(file);
|
||||
// todo refresh this shit
|
||||
// getContext().getContentResolver().refresh()
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
File file = new File(documentId);
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
|
||||
} else if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else if (file.isFile()) {
|
||||
return getMimeType(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
|
||||
File file = new File(documentId);
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
File newPath = new File(file.getParentFile().getAbsolutePath() + "/" + displayName);
|
||||
|
||||
try {
|
||||
Files.move(file.toPath(), newPath.toPath());
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException(e.getMessage());
|
||||
}
|
||||
|
||||
return newPath.getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException {
|
||||
deleteDocument(documentId);
|
||||
}
|
||||
|
||||
private static void deleteRecursive(File file) {
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
deleteRecursive(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.delete();
|
||||
}
|
||||
|
||||
|
||||
private static String getMimeType(String url) {
|
||||
String type = null;
|
||||
String extension = MimeTypeMap.getFileExtensionFromUrl(url.toLowerCase());
|
||||
|
||||
if (extension != null) {
|
||||
switch (extension) {
|
||||
case "pbm":
|
||||
type = "image/bmp";
|
||||
break;
|
||||
case "yml":
|
||||
type = "text/x-yaml";
|
||||
break;
|
||||
default:
|
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private void pushFile(MatrixCursor result, File file) throws FileNotFoundException {
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
String mimeType = "application/octet-stream";
|
||||
int flags = 0;
|
||||
|
||||
if (file.isDirectory()) {
|
||||
mimeType = Document.MIME_TYPE_DIR;
|
||||
|
||||
if (file.canWrite()) {
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
}
|
||||
} else if (file.isFile()) {
|
||||
mimeType = getMimeType(file.getAbsolutePath());
|
||||
|
||||
if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
if (file.getParentFile().canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
|
||||
}
|
||||
|
||||
RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, file.getAbsolutePath());
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
package io.github.doukutsu_rs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class DownloadActivity extends AppCompatActivity {
|
||||
private TextView txtProgress;
|
||||
private ProgressBar progressBar;
|
||||
private DownloadThread downloadThread;
|
||||
private String basePath;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_download);
|
||||
txtProgress = findViewById(R.id.txtProgress);
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
basePath = getFilesDir().getAbsolutePath() + "/data/";
|
||||
|
||||
downloadThread = new DownloadThread();
|
||||
downloadThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
downloadThread.interrupt();
|
||||
}
|
||||
|
||||
private class DownloadThread extends Thread {
|
||||
private static final String DOWNLOAD_URL = "https://github.com/doukutsu-rs/game-data/archive/refs/heads/master.zip";
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(DOWNLOAD_URL);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
throw new IllegalStateException("Bad HTTP response code: " + connection.getResponseCode());
|
||||
}
|
||||
|
||||
int fileLength = connection.getContentLength();
|
||||
if (fileLength == 0) {
|
||||
progressBar.setIndeterminate(true);
|
||||
}
|
||||
|
||||
byte[] zipFile;
|
||||
{
|
||||
InputStream input = new BufferedInputStream(connection.getInputStream());
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
int downloadedLast = 0;
|
||||
int downloaded = 0;
|
||||
byte[] buffer = new byte[4096];
|
||||
int count;
|
||||
long last = System.currentTimeMillis();
|
||||
|
||||
while ((count = input.read(buffer)) != -1) {
|
||||
downloaded += count;
|
||||
|
||||
output.write(buffer, 0, count);
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
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);
|
||||
|
||||
txtProgress.setText(text);
|
||||
|
||||
downloadedLast = downloaded;
|
||||
last = now;
|
||||
}
|
||||
}
|
||||
|
||||
output.flush();
|
||||
zipFile = output.toByteArray();
|
||||
output.close();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// strip prefix
|
||||
if (entryName.startsWith("game-data-master/")) {
|
||||
entryName = entryName.substring("game-data-master/".length());
|
||||
}
|
||||
|
||||
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();
|
||||
} catch (Exception e) {
|
||||
if (txtProgress != null) txtProgress.setText(e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (connection != null) connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
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 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;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
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?", () -> {
|
||||
Intent intent = new Intent(this, DownloadActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
|
||||
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();
|
||||
} 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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void messageBox(String title, String message, Runnable callback) {
|
||||
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.show();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -1,170 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
|
@ -1,42 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.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=".DownloadActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Downloading game data"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Display1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtProgress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="..."
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 34 KiB |
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.Doukutsurs" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">
|
||||
shortEdges
|
||||
</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
|
@ -1,3 +0,0 @@
|
|||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FD7F44</color>
|
||||
</resources>
|
|
@ -1,4 +0,0 @@
|
|||
<resources>
|
||||
<string name="app_name">doukutsu-rs</string>
|
||||
<string name="document_provider_name">doukutsu-rs game data</string>
|
||||
</resources>
|
|
@ -1,15 +0,0 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Doukutsurs" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Theme.Doukutsurs.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Doukutsurs.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="Theme.Doukutsurs.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
</resources>
|
|
@ -1,28 +0,0 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:4.2.0"
|
||||
classpath "gradle.plugin.com.github.willir.rust:plugin:0.3.4"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
|
@ -1,6 +0,0 @@
|
|||
#Wed Feb 17 23:16:31 CET 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
|
@ -1,172 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -1,84 +0,0 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -1,2 +0,0 @@
|
|||
rootProject.name = "doukutsu-rs"
|
||||
include ':app'
|
32
build.rs
|
@ -1,32 +0,0 @@
|
|||
use std::env;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winres;
|
||||
|
||||
fn main() {
|
||||
// let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
|
||||
let target = env::var("TARGET").unwrap_or_else(|e| panic!("{}", e));
|
||||
let is_android = cfg!(target_os = "android") || (cfg!(target_os = "linux") && target.contains("android")); // hack
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("res/sue.ico");
|
||||
res.compile().unwrap();
|
||||
}
|
||||
|
||||
if target.contains("darwin") {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15");
|
||||
println!("cargo:rustc-link-arg=-weak_framework");
|
||||
println!("cargo:rustc-link-arg=GameController");
|
||||
println!("cargo:rustc-link-arg=-weak_framework");
|
||||
println!("cargo:rustc-link-arg=CoreHaptics");
|
||||
}
|
||||
|
||||
if is_android {
|
||||
println!("cargo:rustc-link-lib=dylib=GLESv2");
|
||||
println!("cargo:rustc-link-lib=dylib=EGL");
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
[package]
|
||||
name = "drsandroid"
|
||||
version = "0.1.0"
|
||||
authors = ["Alula"]
|
||||
edition = "2018"
|
||||
|
||||
[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"] }
|
|
@ -1,7 +0,0 @@
|
|||
#[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 };
|
||||
|
||||
doukutsu_rs::init(options).unwrap();
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
edition = "2018"
|
||||
max_width = 120
|
||||
use_small_heuristics = "Max"
|
||||
newline_style = "Unix"
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
mpsc_channel.hpp
|
||||
A simple C++ implementation of the 'channel' in rust language (std::mpsc).
|
||||
|
||||
# Usage
|
||||
Use `mpsc::make_channel<T>` to create a channel. `make_channel<T>` will return a tuple of (Sender<T>, Receiver<T>).
|
||||
|
||||
For example:
|
||||
```c++
|
||||
// Create.
|
||||
auto [ sender, receiver ] = mpsc::make_channel<int>();
|
||||
|
||||
// Send.
|
||||
sender.send(3);
|
||||
|
||||
// Receive (both returns a std::optional<T>.)
|
||||
receiver.receive(); // Blocking when there is nothing present in the channel.
|
||||
receiver.try_receive(); // Not blocking. Return immediately.
|
||||
|
||||
// close() and closed()
|
||||
sender.close();
|
||||
bool result = sender.closed();
|
||||
assert(result == receiver.closed());
|
||||
|
||||
// You can use range-based for loop to receive from the channel.
|
||||
for (int v: receiver) {
|
||||
// do something with v
|
||||
// The loop will stop immedately after the sender called close().
|
||||
// Only sender can call close().
|
||||
}
|
||||
```
|
||||
|
||||
Note: `mpsc` stands for Multi-Producer Single-Consumer. So `Sender` can be either copied and moved, but `Receiver` can only be moved.
|
||||
|
||||
Feel free to explore the `tests.cpp`. The tests are also examples of the usage.
|
||||
|
||||
Read the source if you need more information. Sorry for the lack of comments. ~( ̄▽ ̄~)~
|
||||
|
||||
Copyright (c) 2019 liuchibing.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <type_traits>
|
||||
#include <iterator>
|
||||
|
||||
namespace mpsc
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
class Sender;
|
||||
template <typename T>
|
||||
class Receiver;
|
||||
|
||||
template <typename T>
|
||||
std::tuple<Sender<T>, Receiver<T>> make_channel();
|
||||
|
||||
class channel_closed_exception : std::logic_error
|
||||
{
|
||||
public:
|
||||
channel_closed_exception() : std::logic_error("This channel has been closed.") {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Channel
|
||||
{ // Do NOT use this class directly.
|
||||
public:
|
||||
void send(T &&value);
|
||||
void send(const T &value);
|
||||
|
||||
std::optional<T> receive();
|
||||
std::optional<T> try_receive();
|
||||
|
||||
void close();
|
||||
bool closed();
|
||||
|
||||
Channel(const Channel<T> &) = delete;
|
||||
Channel(Channel<T> &&) = delete;
|
||||
|
||||
Channel<T> &operator=(const Channel<T> &) = delete;
|
||||
Channel<T> &operator=(Channel<T> &&) = delete;
|
||||
|
||||
private:
|
||||
Channel(){};
|
||||
|
||||
std::queue<T, std::list<T>> queue;
|
||||
std::mutex mutex;
|
||||
std::condition_variable condvar;
|
||||
bool need_notify = false;
|
||||
bool _closed = false;
|
||||
|
||||
friend std::tuple<Sender<T>, Receiver<T>> make_channel<T>();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Sender
|
||||
{
|
||||
public:
|
||||
Sender<T> &send(T &&value)
|
||||
{
|
||||
validate();
|
||||
channel->send(std::move(value));
|
||||
return *this;
|
||||
}
|
||||
Sender<T> &send(const T &value)
|
||||
{
|
||||
validate();
|
||||
channel->send(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
validate();
|
||||
channel->close();
|
||||
}
|
||||
|
||||
bool closed()
|
||||
{
|
||||
validate();
|
||||
return channel->closed();
|
||||
}
|
||||
|
||||
operator bool() const { return static_cast<bool>(channel); }
|
||||
|
||||
Sender(const Sender<T> &) = default;
|
||||
Sender(Sender<T> &&) = default;
|
||||
|
||||
Sender<T> &operator=(const Sender<T> &) = default;
|
||||
Sender<T> &operator=(Sender<T> &&) = default;
|
||||
|
||||
private:
|
||||
Sender(std::shared_ptr<Channel<T>> channel) : channel(channel){};
|
||||
|
||||
std::shared_ptr<Channel<T>> channel;
|
||||
|
||||
void validate()
|
||||
{
|
||||
if (!channel)
|
||||
{
|
||||
throw std::invalid_argument("This sender has been moved out.");
|
||||
}
|
||||
}
|
||||
|
||||
friend std::tuple<Sender<T>, Receiver<T>> make_channel<T>();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Receiver
|
||||
{
|
||||
public:
|
||||
std::optional<T> receive()
|
||||
{
|
||||
validate();
|
||||
return channel->receive();
|
||||
}
|
||||
|
||||
std::optional<T> try_receive()
|
||||
{
|
||||
validate();
|
||||
return channel->try_receive();
|
||||
}
|
||||
|
||||
bool closed()
|
||||
{
|
||||
validate();
|
||||
return channel->closed();
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return static_cast<bool>(channel);
|
||||
}
|
||||
|
||||
Receiver(Receiver<T> &&) = default;
|
||||
Receiver<T> &operator=(Receiver<T> &&) = default;
|
||||
|
||||
Receiver(const Receiver<T> &) = delete;
|
||||
Receiver<T> &operator=(const Receiver<T> &) = delete;
|
||||
|
||||
private:
|
||||
Receiver(std::shared_ptr<Channel<T>> channel) : channel(channel){};
|
||||
|
||||
std::shared_ptr<Channel<T>> channel;
|
||||
|
||||
void validate()
|
||||
{
|
||||
if (!channel)
|
||||
{
|
||||
throw std::invalid_argument("This receiver has been moved out.");
|
||||
}
|
||||
}
|
||||
|
||||
friend std::tuple<Sender<T>, Receiver<T>> make_channel<T>();
|
||||
|
||||
public:
|
||||
class iterator
|
||||
: public std::iterator<std::input_iterator_tag, T>
|
||||
{
|
||||
private:
|
||||
typedef std::iterator<std::input_iterator_tag, T> BaseIter;
|
||||
|
||||
public:
|
||||
using typename BaseIter::difference_type;
|
||||
using typename BaseIter::iterator_category;
|
||||
using typename BaseIter::pointer;
|
||||
using typename BaseIter::reference;
|
||||
using typename BaseIter::value_type;
|
||||
|
||||
iterator() : receiver(nullptr) {}
|
||||
iterator(Receiver<T> &receiver) : receiver(&receiver)
|
||||
{
|
||||
if (this->receiver->closed())
|
||||
this->receiver = nullptr;
|
||||
else
|
||||
next();
|
||||
}
|
||||
|
||||
reference operator*() { return current.value(); }
|
||||
pointer operator->() { return ¤t.value(); }
|
||||
iterator &operator++()
|
||||
{
|
||||
next();
|
||||
return *this;
|
||||
}
|
||||
iterator operator++(int) = delete;
|
||||
bool operator==(iterator &other)
|
||||
{
|
||||
if (receiver == nullptr && other.receiver == nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool operator!=(iterator &other)
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
Receiver<T> *receiver;
|
||||
std::optional<T> current = std::nullopt;
|
||||
void next();
|
||||
};
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return iterator(*this);
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return iterator();
|
||||
}
|
||||
};
|
||||
|
||||
/* ======== Implementations ========= */
|
||||
|
||||
template <typename T>
|
||||
std::tuple<Sender<T>, Receiver<T>> make_channel()
|
||||
{
|
||||
static_assert(std::is_copy_constructible_v<T> || std::is_move_constructible_v<T>, "T should be copy-constructible or move-constructible.");
|
||||
std::shared_ptr<Channel<T>> channel{new Channel<T>()};
|
||||
Sender<T> sender{channel};
|
||||
Receiver<T> receiver{channel};
|
||||
return std::tuple<Sender<T>, Receiver<T>>{
|
||||
std::move(sender),
|
||||
std::move(receiver)};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Channel<T>::send(T &&value)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
if (_closed)
|
||||
{
|
||||
throw channel_closed_exception();
|
||||
}
|
||||
queue.push(std::move(value));
|
||||
if (need_notify)
|
||||
{
|
||||
need_notify = false;
|
||||
lock.unlock();
|
||||
condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Channel<T>::send(const T &value)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
if (_closed)
|
||||
{
|
||||
throw channel_closed_exception();
|
||||
}
|
||||
queue.push(value);
|
||||
if (need_notify)
|
||||
{
|
||||
need_notify = false;
|
||||
lock.unlock();
|
||||
condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> Channel<T>::receive()
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
if (_closed)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
if (queue.empty())
|
||||
{
|
||||
need_notify = true;
|
||||
condvar.wait(lock, [this]
|
||||
{ return !queue.empty() || _closed; });
|
||||
}
|
||||
if (_closed)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
T result = std::move(queue.front());
|
||||
queue.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> Channel<T>::try_receive()
|
||||
{
|
||||
if (mutex.try_lock())
|
||||
{
|
||||
std::unique_lock lock(mutex, std::adopt_lock);
|
||||
if (_closed)
|
||||
return std::nullopt;
|
||||
if (queue.empty())
|
||||
return std::nullopt;
|
||||
T result = std::move(queue.front());
|
||||
queue.pop();
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Channel<T>::close()
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
_closed = true;
|
||||
if (need_notify)
|
||||
{
|
||||
need_notify = false;
|
||||
lock.unlock();
|
||||
condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Channel<T>::closed()
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
return _closed;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Receiver<T>::iterator::next()
|
||||
{
|
||||
if (!receiver)
|
||||
return;
|
||||
while (true)
|
||||
{
|
||||
if (receiver->closed())
|
||||
{
|
||||
receiver = nullptr;
|
||||
current.reset();
|
||||
return;
|
||||
}
|
||||
std::optional<T> tmp = receiver->receive();
|
||||
if (!tmp.has_value())
|
||||
continue;
|
||||
current.emplace(std::move(tmp.value()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,276 +0,0 @@
|
|||
# AngelCode Bitmap Font Generator configuration file
|
||||
fileVersion=1
|
||||
|
||||
# font settings
|
||||
fontName=JF Dot k12x10
|
||||
fontFile=JF-Dot-k12x10.ttf
|
||||
charSet=0
|
||||
fontSize=10
|
||||
aa=1
|
||||
scaleH=100
|
||||
useSmoothing=0
|
||||
isBold=0
|
||||
isItalic=0
|
||||
useUnicode=1
|
||||
disableBoxChars=1
|
||||
outputInvalidCharGlyph=1
|
||||
dontIncludeKerningPairs=0
|
||||
useHinting=1
|
||||
renderFromOutline=0
|
||||
useClearType=1
|
||||
autoFitNumPages=0
|
||||
autoFitFontSizeMin=0
|
||||
autoFitFontSizeMax=0
|
||||
|
||||
# character alignment
|
||||
paddingDown=0
|
||||
paddingUp=0
|
||||
paddingRight=0
|
||||
paddingLeft=0
|
||||
spacingHoriz=1
|
||||
spacingVert=1
|
||||
useFixedHeight=0
|
||||
forceZero=0
|
||||
widthPaddingFactor=0.00
|
||||
|
||||
# output file
|
||||
outWidth=512
|
||||
outHeight=512
|
||||
outBitDepth=8
|
||||
fontDescFormat=2
|
||||
fourChnlPacked=0
|
||||
textureFormat=png
|
||||
textureCompression=0
|
||||
alphaChnl=0
|
||||
redChnl=4
|
||||
greenChnl=4
|
||||
blueChnl=4
|
||||
invA=0
|
||||
invR=0
|
||||
invG=0
|
||||
invB=0
|
||||
|
||||
# outline
|
||||
outlineThickness=0
|
||||
|
||||
# selected chars
|
||||
chars=32-126,160,167-168,176-177,180,182,215,247,913-929,931-937,945-961,963-969,1025,1040-1103,1105,8208
|
||||
chars=8213,8216-8217,8229-8230,8251,8451,8470,8481,8491,8544-8553,8592-8595,8658,8660,8722,8730,8733-8734
|
||||
chars=9312-9331,9472-9475,9484,9487-9488,9491-9492,9495-9496,9499-9501,9504,9507-9509,9512,9515-9516,9519
|
||||
chars=9520,9523-9524,9527-9528,9531-9532,9535,9538,9547,9632-9633,9650-9651,9660-9661,9670-9671,9675,9678
|
||||
chars=9679,9711,9733-9734,9792,9794,9834,9837,9839,12288-12291,12293-12309,12316-12317,12319,12353-12435
|
||||
chars=12443-12446,12449-12534,12539-12542,12849-12850,12857,12964-12968,13059,13069,13076,13080,13090
|
||||
chars=13091,13094-13095,13099,13110,13115,13129-13130,13133,13137,13143,13179-13182,13198-13199,13212
|
||||
chars=13213-13214,13217,13252,13261,19968-19969,19971,19975-19979,19981-19982,19984-19985,19988-19993
|
||||
chars=19998,20001,20006,20010,20013,20017-20018,20022,20024-20025,20027-20028,20031,20034-20035,20037
|
||||
chars=20043,20045-20047,20053-20057,20061-20063,20066,20081,20083,20094,20096,20098,20101-20102,20104
|
||||
chars=20105-20108,20110,20113-20114,20116-20117,20120-20121,20123-20124,20126-20130,20132-20134,20136
|
||||
chars=20139-20142,20144,20147,20150,20154,20160-20162,20164,20166-20167,20170-20171,20173-20175,20180
|
||||
chars=20181-20185,20189-20191,20195-20197,20205-20206,20208,20210,20214-20215,20219,20225,20233-20234
|
||||
chars=20237-20241,20250,20252-20253,20271-20272,20276,20278,20280,20282,20284-20285,20291,20294-20295
|
||||
chars=20301-20305,20307,20309,20311,20313-20318,20329,20335-20336,20339,20341-20342,20347-20348,20351
|
||||
chars=20355,20358,20360,20363,20365,20367,20369,20374,20376,20379,20381,20384-20385,20395,20397-20399
|
||||
chars=20405-20406,20415,20418-20420,20426,20430,20432-20433,20436,20439-20440,20442-20443,20445,20447
|
||||
chars=20449,20451-20453,20462-20463,20467,20469-20470,20472,20474,20478,20485-20486,20489,20491,20493
|
||||
chars=20495,20497-20498,20500,20502,20505-20506,20511,20513,20515-20518,20520-20525,20534,20537,20547
|
||||
chars=20551-20553,20559-20560,20565-20566,20570,20572,20581,20588,20594,20596-20598,20600,20605,20608
|
||||
chars=20613,20621,20625,20632-20634,20652-20653,20658-20661,20663,20670,20674,20677,20681-20682,20685
|
||||
chars=20687,20689,20693-20694,20698,20702,20707,20709,20711,20717-20718,20725,20729,20731,20736-20738
|
||||
chars=20740,20745,20754,20756-20758,20760,20762,20767,20769,20778,20786,20791,20794-20796,20799-20801
|
||||
chars=20803-20809,20811-20814,20816,20818,20820,20826,20828,20834,20837,20840-20846,20849,20853-20856
|
||||
chars=20860,20864,20866,20869-20870,20873-20874,20876-20877,20879-20883,20885-20887,20889,20896,20898
|
||||
chars=20900-20902,20904-20908,20912-20919,20925,20932-20934,20937,20939-20941,20950,20955-20957,20960
|
||||
chars=20961,20966-20967,20969-20970,20973,20976-20977,20981-20982,20984-20986,20989-20990,20992,20995
|
||||
chars=20996,20998-21000,21002-21003,21006,21009,21012,21015,21021,21028-21029,21031,21033-21034,21038
|
||||
chars=21040,21043,21046-21051,21059-21060,21063,21066-21069,21071,21076,21078,21083,21086,21091-21093
|
||||
chars=21097-21098,21103-21109,21117,21119,21123,21127-21129,21133,21137-21138,21140,21147,21151-21152
|
||||
chars=21155,21161-21165,21169,21172-21173,21177,21180,21182,21185,21187,21189,21191,21193,21197,21202
|
||||
chars=21205,21207-21209,21213-21216,21218-21220,21222-21223,21234-21235,21237,21240-21242,21246-21247
|
||||
chars=21249-21250,21253-21254,21256,21261,21263-21264,21269-21271,21273-21274,21277,21280-21281,21283
|
||||
chars=21290,21295,21297,21299,21304-21307,21311-21313,21315,21317-21322,21325,21329-21332,21335-21336
|
||||
chars=21338,21340,21342,21344,21350,21353,21358-21361,21363-21365,21367-21368,21371,21375,21378,21380
|
||||
chars=21398,21400,21402,21407-21408,21413-21414,21416-21417,21421-21422,21424,21427,21430,21435,21442
|
||||
chars=21443,21448-21454,21460,21462-21463,21465,21467,21471,21473-21477,21480-21491,21494-21496,21498
|
||||
chars=21505,21507-21508,21512-21521,21531,21533,21535-21536,21542,21545,21547-21550,21558,21560-21561
|
||||
chars=21563-21566,21568,21570,21574,21576-21578,21582,21585,21599,21608,21610,21616-21617,21619,21621
|
||||
chars=21622-21623,21627-21629,21632,21636,21638,21643-21644,21646-21648,21650,21666,21668-21669,21672
|
||||
chars=21675-21676,21679,21682-21683,21688,21692-21694,21696-21698,21700,21703-21705,21720,21729-21730
|
||||
chars=21733-21734,21736-21737,21741-21742,21746,21754,21757,21764,21766-21767,21775-21776,21780,21782
|
||||
chars=21806-21807,21809,21811,21816-21817,21822,21824,21828-21830,21836,21839,21843,21846-21847,21852
|
||||
chars=21853,21859,21883-21884,21886,21888,21891-21892,21895,21897-21899,21912-21914,21916-21919,21927
|
||||
chars=21928-21932,21934,21936,21942,21956-21957,21959,21972,21978,21980,21983,21987-21988,22007,22009
|
||||
chars=22013-22014,22022,22025,22036,22038-22040,22043,22057,22063,22065-22066,22068,22070,22072,22082
|
||||
chars=22092,22094,22096,22107,22116,22120,22122-22124,22132,22136,22138,22144,22150-22151,22154,22159
|
||||
chars=22164,22176,22178,22181,22190,22196,22198,22204,22208-22211,22216,22222,22225,22227,22231-22232
|
||||
chars=22234-22235,22238,22240,22243,22254,22256,22258-22259,22265-22266,22269,22271-22272,22275-22276
|
||||
chars=22280-22281,22283,22285,22287,22290-22291,22294,22296,22300,22303,22310-22312,22317,22320,22327
|
||||
chars=22328,22331,22336,22338,22343,22346,22350-22353,22369,22372,22374,22377-22378,22399,22402,22408
|
||||
chars=22409,22411,22419,22432,22434-22436,22442,22448,22451,22464,22467,22470,22475,22478,22482-22484
|
||||
chars=22486,22492,22495-22496,22499,22516,22519,22521-22522,22524,22528,22530,22533-22534,22538-22539
|
||||
chars=22549,22553,22557,22561,22564,22570,22575-22577,22580-22581,22586,22589,22592-22593,22602-22603
|
||||
chars=22609-22610,22612,22615-22618,22622,22626,22633,22635,22640,22642,22645,22649,22654,22659,22661
|
||||
chars=22675,22679,22684,22687,22696,22699,22702,22707,22712-22715,22718,22721,22725,22727,22730,22732
|
||||
chars=22737,22739,22741,22743-22745,22748,22750-22751,22756-22757,22763-22764,22766-22770,22775,22777
|
||||
chars=22778-22781,22786,22793-22794,22799-22800,22805-22806,22808-22812,22818,22821,22823,22825-22830
|
||||
chars=22833-22834,22839-22840,22846,22852,22855-22857,22862-22865,22868-22869,22871-22872,22874,22880
|
||||
chars=22882,22885,22887-22890,22892,22894,22899-22900,22904,22909,22913-22916,22922,22925,22931,22934
|
||||
chars=22937,22939,22941,22947,22949,22952,22956,22962,22969,22971,22974,22982,22985,22987,22992-22993
|
||||
chars=22995-22996,23001-23002,23004,23013-23014,23016,23018-23019,23030,23035,23039,23041,23043,23049
|
||||
chars=23057,23064,23066,23068,23071-23072,23077,23081,23087,23093-23094,23100,23104-23105,23110,23113
|
||||
chars=23130,23138,23142,23146,23148,23167,23186,23194-23195,23228-23230,23233-23234,23241,23243-23244
|
||||
chars=23248,23254-23255,23265,23267,23270,23273,23290-23291,23305,23307-23308,23318,23330,23338,23340
|
||||
chars=23344,23346,23350,23358,23360,23363,23365,23376-23377,23380-23381,23383-23384,23386-23389,23391
|
||||
chars=23395-23398,23401,23403,23408-23409,23411,23413,23416,23418,23424,23427,23429,23431-23433,23435
|
||||
chars=23436-23437,23439,23445,23447-23453,23455,23458-23462,23470,23472,23475-23478,23480-23481,23487
|
||||
chars=23490-23495,23497,23500,23504,23506-23508,23515,23517-23519,23521-23522,23524-23529,23531,23534
|
||||
chars=23536,23539,23541-23542,23544,23546,23550-23551,23553-23554,23556-23563,23565-23567,23569,23571
|
||||
chars=23574,23578,23584,23586,23588,23592,23597,23601,23608-23617,23621-23622,23624,23626-23627,23629
|
||||
chars=23630-23633,23635,23637,23646,23648-23649,23652-23653,23660,23662-23663,23665,23670,23673,23692
|
||||
chars=23696-23697,23700,23713,23720-23721,23723-23724,23729,23731,23734-23736,23739-23740,23742,23749
|
||||
chars=23751,23769,23776-23777,23784-23786,23789,23791-23792,23798,23802-23803,23805,23815,23819,23822
|
||||
chars=23825,23828-23835,23839,23842,23849,23883-23884,23886,23888,23890,23900,23913,23916,23919,23923
|
||||
chars=23926,23938,23940,23943,23947-23948,23952,23965,23970,23980,23982,23991,23994,23996-23997,24009
|
||||
chars=24012-24013,24018-24019,24022,24027,24029-24030,24033,24035,24037-24040,24043,24046,24049-24053
|
||||
chars=24055,24059,24061-24062,24066-24067,24070,24075-24076,24081,24086,24089-24091,24093,24101,24107
|
||||
chars=24109,24111-24112,24115,24118-24120,24125,24128,24131-24133,24135,24140,24142,24148-24149,24151
|
||||
chars=24159,24161-24164,24178-24182,24184-24191,24193,24195-24196,24199,24202,24207,24213-24215,24218
|
||||
chars=24220,24224,24230-24231,24235,24237,24245-24248,24257-24259,24264-24266,24271-24272,24275,24278
|
||||
chars=24282-24283,24285,24287-24291,24296-24297,24300,24304-24305,24307-24308,24310-24312,24314-24316
|
||||
chars=24318-24319,24321,24323-24324,24329-24333,24335-24337,24339-24344,24347,24351,24357-24359,24361
|
||||
chars=24365,24367,24369,24373,24375-24376,24380,24382,24385,24392,24394,24396,24398,24401,24403,24406
|
||||
chars=24407,24409,24412-24413,24417-24418,24422,24425-24429,24432-24433,24435,24439,24441,24444,24447
|
||||
chars=24448-24453,24455-24456,24458-24460,24464-24467,24471-24473,24478,24480-24481,24488-24490,24493
|
||||
chars=24494,24499-24500,24505,24508-24509,24515,24517,24524-24525,24534-24537,24540-24541,24544,24548
|
||||
chars=24555,24560-24561,24565,24568,24571,24573,24575,24590-24592,24594,24597-24598,24601,24603-24605
|
||||
chars=24608-24609,24613-24619,24623,24625,24634,24641-24643,24646,24650-24651,24653,24656,24658,24661
|
||||
chars=24665-24666,24671-24672,24674-24677,24680-24685,24687-24688,24693,24695,24705,24707-24708,24713
|
||||
chars=24715-24717,24722,24724,24726-24727,24730-24731,24735-24736,24739,24742-24743,24745-24746,24754
|
||||
chars=24755-24758,24760,24764-24765,24773-24775,24785,24787,24792,24794,24796,24799-24801,24803,24807
|
||||
chars=24808,24816-24817,24819-24820,24822-24823,24825-24827,24832-24833,24835,24838,24840-24841,24845
|
||||
chars=24846-24847,24853,24858-24859,24863,24865,24871-24872,24876,24884,24892-24895,24898,24900,24903
|
||||
chars=24904,24906-24910,24915,24917,24920-24922,24925,24927,24930-24931,24933,24935-24936,24939,24942
|
||||
chars=24943-24945,24947-24951,24958,24962,24967,24970,24974,24976-24977,24980,24982,24985-24986,24996
|
||||
chars=24999,25001,25003-25004,25006,25010,25014,25018,25022,25027,25030-25037,25040,25059,25062,25074
|
||||
chars=25076,25078-25080,25082,25084-25088,25096-25098,25100-25102,25104-25106,25108,25110,25114-25115
|
||||
chars=25117-25119,25121,25126,25130,25134-25136,25138-25140,25144,25147,25151-25153,25159-25161,25163
|
||||
chars=25165-25166,25171,25173,25176,25179,25182,25184,25187,25192,25198,25201,25206,25209,25212,25214
|
||||
chars=25215-25216,25218-25220,25225-25226,25233-25240,25243-25244,25246,25259-25260,25265,25269,25273
|
||||
chars=25275-25277,25282,25285-25290,25292-25293,25295-25300,25303-25305,25307-25309,25312-25313,25324
|
||||
chars=25325-25327,25329,25331,25333-25335,25342-25343,25345-25346,25351-25353,25356,25361,25369,25375
|
||||
chars=25383-25384,25387,25391,25402,25405-25407,25417,25420-25421,25423-25424,25429,25431,25436,25447
|
||||
chars=25448-25449,25451,25454,25458,25462-25463,25466-25467,25472,25475,25480-25481,25484,25486-25487
|
||||
chars=25490,25494,25496,25499,25503-25507,25509,25511-25516,25522,25524-25525,25531,25534,25536,25539
|
||||
chars=25540,25542,25545,25551-25552,25554,25558,25562-25563,25569,25571,25577,25582,25588,25590,25594
|
||||
chars=25606,25613,25615,25619,25622-25623,25628,25638,25640,25644-25645,25652,25654,25658,25662,25666
|
||||
chars=25678,25688,25703,25705,25711,25718,25720,25722,25731,25736,25746-25747,25749,25754,25758,25764
|
||||
chars=25765,25769,25771,25773-25774,25776,25778,25785,25787-25788,25793-25794,25797,25799,25805,25810
|
||||
chars=25812,25816,25818,25824-25827,25830-25831,25836,25839,25841-25842,25844,25846,25850,25853-25854
|
||||
chars=25856,25861,25880,25884-25885,25891-25892,25898-25900,25903,25908-25913,25915,25918-25919,25925
|
||||
chars=25928,25933,25935,25937,25941-25945,25949-25950,25954-25955,25958,25964,25968,25970,25972-25973
|
||||
chars=25975-25976,25986-25987,25991-25993,25996,25998,26000-26001,26007,26009,26011-26012,26015,26017
|
||||
chars=26020-26021,26023,26027-26029,26031-26032,26039,26041,26044-26045,26049,26051-26054,26059-26060
|
||||
chars=26063,26066,26071,26073,26075,26080-26082,26085-26089,26092-26093,26097,26106-26107,26114-26115
|
||||
chars=26118-26119,26122,26124,26126-26127,26131-26132,26140,26143-26144,26149,26151-26152,26157,26159
|
||||
chars=26164-26166,26172,26175,26177-26180,26185,26187,26191,26194,26205-26207,26210,26212,26214-26217
|
||||
chars=26222-26224,26228,26230,26234,26241,26243-26244,26247-26249,26254,26257,26262-26264,26269,26274
|
||||
chars=26278,26283,26286,26292,26296-26297,26300,26302,26305,26308,26311,26313,26326,26329-26330,26332
|
||||
chars=26333,26336,26342,26345,26352,26354-26357,26359-26361,26364-26368,26371,26376-26377,26379,26381
|
||||
chars=26383,26388-26391,26395,26397-26399,26406-26408,26410-26414,26417,26420,26422-26424,26426,26429
|
||||
chars=26431,26433,26438,26441,26446-26449,26451,26454,26457,26460,26462-26469,26474,26477,26479-26483
|
||||
chars=26485,26487,26492,26494-26495,26501,26503,26505,26507-26508,26512,26517,26519,26522,26524-26525
|
||||
chars=26528-26530,26534,26537,26543,26547-26548,26550-26553,26561,26564,26566,26570,26574-26577,26579
|
||||
chars=26580,26584,26586,26589-26590,26594,26596,26599,26601,26604,26606-26607,26609,26611-26613,26619
|
||||
chars=26622-26623,26626-26628,26643,26646-26647,26654,26657-26658,26665-26667,26674,26676,26680-26681
|
||||
chars=26684-26685,26688-26691,26694,26696,26701-26702,26704-26705,26707-26708,26713,26716-26717,26719
|
||||
chars=26723,26727,26740,26742-26743,26750-26751,26753,26755,26757,26765,26767,26771-26772,26775,26779
|
||||
chars=26781,26783-26784,26786,26790-26792,26797,26799-26801,26803,26805-26806,26809-26810,26812,26820
|
||||
chars=26822,26825-26827,26829,26834,26836-26837,26839-26840,26842,26847-26849,26851,26855,26862-26863
|
||||
chars=26866,26873-26874,26880-26881,26884-26885,26888,26891-26895,26898,26905-26908,26913-26915,26917
|
||||
chars=26918,26920,26922,26928,26932,26934,26937,26941,26943,26954,26963-26965,26969-26970,26972-26974
|
||||
chars=26976-26978,26986-26987,26989-26991,26995-26997,26999-27001,27004-27006,27009-27010,27018,27022
|
||||
chars=27025,27028-27029,27035-27036,27040,27047,27054,27057-27058,27060,27067,27070-27071,27073,27075
|
||||
chars=27079,27082-27086,27088,27091,27096-27097,27101-27102,27111-27112,27115,27117,27122,27129,27131
|
||||
chars=27133,27135,27138,27141,27146-27148,27154-27156,27159,27161,27163,27166-27167,27169-27171,27177
|
||||
chars=27178-27179,27182,27189-27190,27192-27194,27197,27204,27207-27208,27211,27224-27225,27231,27233
|
||||
chars=27234,27238,27250,27256,27263-27264,27268,27277-27278,27280,27287,27292,27296,27298-27299,27306
|
||||
chars=27308,27310,27315,27320,27323,27329-27331,27345,27347,27354-27355,27358-27359,27368,27370,27386
|
||||
chars=27387,27396-27397,27402,27410,27414,27421,27423-27425,27427,27431,27442,27447-27450,27453-27454
|
||||
chars=27459,27463,27465,27468,27470,27472,27475-27476,27481,27483,27487,27489-27492,27494,27497-27498
|
||||
chars=27503,27507-27508,27512-27513,27515,27519-27520,27523-27524,27526,27529-27531,27533,27541-27542
|
||||
chars=27544,27550,27556,27562-27563,27567,27569-27573,27575,27578-27580,27583-27584,27589-27590,27595
|
||||
chars=27597-27598,27602-27604,27608,27611,27615,27627-27628,27631,27635,27656,27663,27665,27667-27668
|
||||
chars=27671,27675,27683-27684,27700,27703-27704,27710,27712-27714,27726,27728,27733,27735,27738,27741
|
||||
chars=27742-27744,27746,27752,27754,27760,27762-27763,27770,27773-27774,27777-27779,27784,27788-27789
|
||||
chars=27792,27794-27795,27798,27801-27803,27809-27810,27819,27822,27825,27827,27832-27839,27841,27844
|
||||
chars=27845,27849-27850,27852,27859,27861,27863,27865,27867,27869,27873-27875,27877,27880,27882,27887
|
||||
chars=27888-27889,27891,27915,27922,27927,27934,27941,27946,27963,27966,27969,27972-27973,27996,28006
|
||||
chars=28009-28010,28014,28020,28023-28024,28040,28057,28079,28082,28092,28096,28129,28145,28147,28151
|
||||
chars=28155,28165,28167-28169,28171,28187,28193,28198,28201,28204,28207,28246,28248,28263,28271,28286
|
||||
chars=28287-28288,28304,28310,28316-28317,28322,28342,28357,28363,28369,28381-28382,28402,28404,28417
|
||||
chars=28418,28431,28436,28448,28450,28459-28460,28500,28508,28511,28516,28526,28528,28548,28580,28608
|
||||
chars=28609,28611,28641,28655,28716,28779,28783-28784,28796-28797,28809-28810,28814,28818,28844-28845
|
||||
chars=28857-28858,28872,28954,28961,28966,28982,28988,29017,29031,29033,29038,29053,29066,29087,29105
|
||||
chars=29123,29157,29165,29173,29190,29226,29238,29242,29245,29255-29256,29273,29275,29282,29287,29289
|
||||
chars=29298,29305,29344,29356,29359,29366,29378,29401,29417,29420-29421,29436,29467,29471,29482-29483
|
||||
chars=29486,29494,29503,29508,29539,29554,29572,29575,29577,29579,29645,29664,29677,29694,29699,29702
|
||||
chars=29705,29730,29748,29790,29863,29872,29926,29942,29976,29983,29987,29992,30000-30003,30007,30010
|
||||
chars=30011,30028,30033,30041,30044,30053,30058,30064,30067,30079,30094,30097,30123,30130,30142,30149
|
||||
chars=30151,30169,30171,30185,30196,30202,30221,30274,30284,30290,30294,30330-30331,30333-30334,30340
|
||||
chars=30342-30343,30382,30394,30399,30406,30410,30423,30427,30431,30435-30436,30439,30446,30450,30452
|
||||
chars=30456,30462,30465,30473,30475-30476,30494-30496,30505,30522,30524,30528,30561,30563,30568,30609
|
||||
chars=30636,30643,30652,30683,30690,30693,30697,30701,30703,30707,30722,30740-30741,30770,30772,30813
|
||||
chars=30828,30849,30906,30913,30952,30990,31034,31036,31038,31048-31049,31062,31069-31070,31077,31080
|
||||
chars=31085,31105,31119,31168-31169,31179,31185-31186,31192,31199,31209,31216,31227,31232,31243,31246
|
||||
chars=31258,31278,31282,31292-31293,31295,31298,31309,31311,31339,31348,31350,31354,31361,31363,31379
|
||||
chars=31391,31406,31435,31452,31456,31461,31471,31478,31481,31499,31505,31515,31520,31526,31532,31545
|
||||
chars=31558,31560-31561,31563,31570,31572,31574,31623,31639,31649,31665,31672,31680,31684,31687,31689
|
||||
chars=31761,31777,31807,31821,31840,31859,31881,31883,31890,31896,31911,31934,31958,31966,31975,31992
|
||||
chars=31995,32000,32004-32005,32011,32013,32016,32020,32025-32027,32032,32034,32043,32047-32048,32051
|
||||
chars=32057-32058,32066,32068,32070,32076,32080,32094,32097,32102,32113,32117-32118,32121,32153-32154
|
||||
chars=32173,32177-32178,32180,32186-32187,32190-32191,32202,32207,32209-32210,32218,32224,32232-32233
|
||||
chars=32239,32244,32257,32260,32283,32294,32302,32318,32321,32330-32331,32340,32368,32566,32608,32618
|
||||
chars=32622,32624,32626,32645,32650,32654,32676,32680-32681,32701,32709,32716,32722,32724,32763-32764
|
||||
chars=32769,32771,32773,32784,32791,32819,32854,32862,32865,32884,32887,32905,32908,32918,32925,32929
|
||||
chars=32930,32933,32937-32938,32943,32946,32954,32963,32966,32972,32974,32990,32993,33016,33021,33026
|
||||
chars=33029-33032,33050,33073,33075,33104,33107,33109,33131,33136,33144-33146,33178,33180-33181,33192
|
||||
chars=33222,33235,33251,33256,33258,33261,33267-33268,33288,33292,33294,33296,33303,33307,33310-33311
|
||||
chars=33322,33324,33337,33351,33382,33391,33394,33398,33437,33446,33455,33457,33459,33464,33469,33495
|
||||
chars=33499,33509-33510,33521,33538,33540,33576,33590,33609,33618,33624,33655,33707,33733,33738,33740
|
||||
chars=33747,33756,33775,33777,33804,33853,33865,33879,33883,33891,33900,33945,33976,33980,33988,33995
|
||||
chars=34001,34030,34101,34109,34126,34180,34183,34196,34199,34214,34219-34220,34253,34276,34281,34299
|
||||
chars=34311,34349,34382,34384,34394,34411,34425,34442,34503,34509,34521,34678,34701,34811,34821,34880
|
||||
chars=34886,34892,34899,34903,34907,34909,34913,34915,34920,34928,34955,34966,34987,35009-35010,35013
|
||||
chars=35023,35029,35036,35064,35069-35070,35079,35090,35186,35199,35201,35206-35207,35211,35215,35222
|
||||
chars=35223,35226,35239,35242,35251,35282,35299,35302,35328,35330,35336,35338,35342,35347,35351-35352
|
||||
chars=35357,35359,35363,35370,35373,35377,35379-35380,35386,35388,35408,35413,35422,35430,35433,35435
|
||||
chars=35438,35440-35443,35463,35465,35468-35469,35475,35477,35480,35486,35488,35492,35500-35501,35504
|
||||
chars=35506,35519,35527,35531,35542,35558,35565-35566,35576,35582,35584,35598,35609,35611,35613,35617
|
||||
chars=35672,35676,35686,35696,35698,35703,35728,35895,35910,35914,35930,35937,35946,35980,35997-35998
|
||||
chars=36000-36002,36007-36009,36011-36012,36015-36016,36020,36022-36024,36027-36028,36031-36032,36035
|
||||
chars=36039,36042,36049,36059,36062,36064,36066,36074,36077,36092,36101,36104,36196,36198,36208,36212
|
||||
chars=36215,36229,36234,36259,36275,36317,36321,36335,36339,36341,36362,36367,36468,36487,36490,36493
|
||||
chars=36523,36554,36556-36557,36562,36575,36578,36600,36605,36611,36617,36628,36637,36649-36650,36664
|
||||
chars=36676,36763,36766,36784-36786,36794-36796,36799,36804-36805,36814,36817,36820,36843,36848,36855
|
||||
chars=36861,36864-36865,36867,36870,36879,36884,36889-36890,36893,36895-36896,36899,36910,36913-36914
|
||||
chars=36920,36930,36933,36935,36938-36939,36941-36942,36947-36949,36960-36961,36963,36965,36969,36973
|
||||
chars=36974,36981,36983-36984,36986,36989,36991,36996,37027,37030,37034,37048,37066,37070,37089,37096
|
||||
chars=37101,37109,37111,37117,37197-37198,37202,37204,37218,37228,37237,37239-37240,37266,37276,37284
|
||||
chars=37320,37324-37327,37329,37340-37341,37347,37351,37389,37428,37444,37474,37489,37504,37507,37509
|
||||
chars=37528,37549,37613,37628,37679,37682,37707,37723,37749,37772,37782,37806,37857,37912,37969,38263
|
||||
chars=38272,38275,38281,38283,38291,38306-38307,38309,38322,38343,38346,38360,38428,38442,38450,38459
|
||||
chars=38463-38464,38477,38480,38498-38501,38506,38512,38515,38518,38520,38522,38525,38533-38534,38538
|
||||
chars=38542-38543,38548,38553,38555-38556,38560,38563,38583,38588,38592,38596-38599,38609,38626-38627
|
||||
chars=38632,38634,38640,38642,38646-38647,38651,38656,38663,38666,38686,38695,38706,38738,38742,38745
|
||||
chars=38750,38754,38761,38772,38788,38867,38899,38907,38911,38913-38915,38917-38918,38920,38928-38929
|
||||
chars=38931,38936,38956-38957,38967,38971-38972,38988-38990,38996-38997,39000,39006,39015,39080,39082
|
||||
chars=39131,39135,39138,39151,39154,39156,39164-39166,39171,39173,39178,39180,39208,39318,39321,39340
|
||||
chars=39348,39364-39366,39376,39378,39423,39438,39442-39443,39449,39472,39514,39592,39608,39640,39658
|
||||
chars=39661,39706,39729,39740,39745-39746,39749,39764,39770,39791,39854,39881,40165,40169,40180,40335
|
||||
chars=40372,40441,40474-40475,40478,40565,40568-40569,40573,40575,40577,40584,40587-40588,40593-40595
|
||||
chars=40597,40599,40605,40607,40613-40614,40617-40618,40621,40632-40636,40638-40639,40644,40652-40656
|
||||
chars=40658,40660,40664-40665,40667-40670,40672,40677,40680,40687,40692,40694-40695,40697,40699-40701
|
||||
chars=40711-40712,40718,40723,40725,40736-40737,40748,40763,40766,40778-40779,40782-40783,40786,40788
|
||||
chars=40799-40803,40806-40807,40810,40812,40818,40822-40823,40845,40853,40860-40861,40864,65281,65283
|
||||
chars=65284-65286,65288-65374,65377-65439,65504-65507,65509
|
||||
|
||||
# imported icon images
|
Before Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.1 KiB |
|
@ -1,102 +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"
|
||||
},
|
||||
|
||||
"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:"
|
||||
},
|
||||
|
||||
"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,102 +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": "ベストプレイを再生"
|
||||
},
|
||||
|
||||
"options_menu": {
|
||||
"graphics": "グラフィック",
|
||||
"graphics_menu": {
|
||||
"lighting_effects": "ライティング効果:",
|
||||
"weapon_light_cone": "兵器のライトコーン:",
|
||||
"motion_interpolation": "モーション補間:",
|
||||
"subpixel_scrolling": "サブピクセルスクロール:",
|
||||
"original_textures": "オリジナルテクスチャ:",
|
||||
"seasonal_textures": "季節ものテクスチャ:",
|
||||
"renderer": "レンダラ:"
|
||||
},
|
||||
|
||||
"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} を押し続け、カットシーンをスキップ"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 16 KiB |
|
@ -1,253 +0,0 @@
|
|||
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};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
||||
|
||||
impl BuiltinFile {
|
||||
pub fn from(buf: &'static [u8]) -> Box<dyn VFile> {
|
||||
Box::new(BuiltinFile(Cursor::new(buf)))
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for BuiltinFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for BuiltinFile {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
self.0.seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for BuiltinFile {
|
||||
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
|
||||
Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only."))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only."))
|
||||
}
|
||||
}
|
||||
|
||||
struct BuiltinMetadata {
|
||||
is_dir: bool,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl VMetadata for BuiltinMetadata {
|
||||
fn is_dir(&self) -> bool {
|
||||
self.is_dir
|
||||
}
|
||||
|
||||
fn is_file(&self) -> bool {
|
||||
!self.is_dir
|
||||
}
|
||||
|
||||
fn len(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum FSNode {
|
||||
File(&'static str, &'static [u8]),
|
||||
Directory(&'static str, Vec<FSNode>),
|
||||
}
|
||||
|
||||
impl FSNode {
|
||||
fn get_name(&self) -> &'static str {
|
||||
match self {
|
||||
FSNode::File(name, _) => name,
|
||||
FSNode::Directory(name, _) => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_file(&self) -> GameResult<Box<dyn VFile>> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => Ok(BuiltinFile::from(buf)),
|
||||
FSNode::Directory(name, _) => Err(FilesystemError(format!("{} is a directory.", name))),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_metadata(&self) -> Box<dyn VMetadata> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => Box::new(BuiltinMetadata { is_dir: false, size: buf.len() as u64 }),
|
||||
FSNode::Directory(_, _) => Box::new(BuiltinMetadata { is_dir: true, size: 0 }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuiltinFS {
|
||||
root: Vec<FSNode>,
|
||||
}
|
||||
|
||||
impl BuiltinFS {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
root: vec![FSNode::Directory(
|
||||
"builtin",
|
||||
vec![
|
||||
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(
|
||||
"organya-wavetable-doukutsu.bin",
|
||||
include_bytes!("builtin/organya-wavetable-doukutsu.bin"),
|
||||
),
|
||||
FSNode::File("touch.png", include_bytes!("builtin/touch.png")),
|
||||
FSNode::Directory(
|
||||
"shaders",
|
||||
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::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")),
|
||||
],
|
||||
),
|
||||
],
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_node(&self, path: &Path) -> GameResult<FSNode> {
|
||||
let mut iter = path.components().peekable();
|
||||
|
||||
if let Some(Component::RootDir) = iter.next() {
|
||||
let mut curr_dir = &self.root;
|
||||
|
||||
if iter.peek().is_none() {
|
||||
return Ok(FSNode::Directory("", self.root.clone()));
|
||||
}
|
||||
|
||||
while let Some(comp) = iter.next() {
|
||||
let comp_name = comp.as_os_str().to_string_lossy();
|
||||
|
||||
for file in curr_dir {
|
||||
match file {
|
||||
FSNode::File(name, _) if comp_name.eq(name) => {
|
||||
return if iter.peek().is_some() {
|
||||
Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path)))
|
||||
} else {
|
||||
Ok(file.clone())
|
||||
};
|
||||
}
|
||||
FSNode::Directory(name, contents) if comp_name.eq(name) => {
|
||||
if iter.peek().is_some() {
|
||||
curr_dir = contents;
|
||||
break;
|
||||
} else {
|
||||
return Ok(file.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(FilesystemError("Path must be absolute.".to_string()));
|
||||
}
|
||||
|
||||
Err(FilesystemError("File not found.".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BuiltinFS {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "<BuiltinFS>")
|
||||
}
|
||||
}
|
||||
|
||||
impl VFS for BuiltinFS {
|
||||
fn open_options(&self, path: &Path, open_options: OpenOptions) -> GameResult<Box<dyn VFile>> {
|
||||
if open_options.write || open_options.create || open_options.append || open_options.truncate {
|
||||
let msg = format!("Cannot alter file {:?} in root {:?}, filesystem read-only", path, self);
|
||||
return Err(FilesystemError(msg));
|
||||
}
|
||||
|
||||
self.get_node(path)?.to_file()
|
||||
}
|
||||
|
||||
fn mkdir(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to make directory {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn rm(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to remove file {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn rmrf(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to remove file/dir {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn exists(&self, path: &Path) -> bool {
|
||||
self.get_node(path).is_ok()
|
||||
}
|
||||
|
||||
fn metadata(&self, path: &Path) -> GameResult<Box<dyn VMetadata>> {
|
||||
self.get_node(path).map(|v| v.to_metadata())
|
||||
}
|
||||
|
||||
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();
|
||||
for node in contents {
|
||||
vec.push(Ok(PathBuf::from(node.get_name())))
|
||||
}
|
||||
|
||||
Ok(Box::new(vec.into_iter()))
|
||||
}
|
||||
Ok(FSNode::File(_, _)) => Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path))),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_path_buf(&self) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builtin_fs() {
|
||||
let fs = BuiltinFS {
|
||||
root: vec![
|
||||
FSNode::File("test.txt", &[]),
|
||||
FSNode::Directory(
|
||||
"memes",
|
||||
vec![
|
||||
FSNode::File("nothing.txt", &[]),
|
||||
FSNode::Directory("secret stuff", vec![FSNode::File("passwords.txt", b"12345678")]),
|
||||
],
|
||||
),
|
||||
FSNode::File("test2.txt", &[]),
|
||||
],
|
||||
};
|
||||
|
||||
println!("{:?}", fs.get_node(Path::new("/")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/test.txt")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap());
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
namespace doukutsu_rs::caret
|
||||
{
|
||||
class Caret {
|
||||
|
||||
};
|
||||
};
|
409
src/caret.rs
|
@ -1,409 +0,0 @@
|
|||
use crate::common::{Condition, Direction, Rect, CDEG_RAD};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::rng::RNG;
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum CaretType {
|
||||
None,
|
||||
Bubble,
|
||||
ProjectileDissipation,
|
||||
Shoot,
|
||||
SnakeAfterimage,
|
||||
Zzz,
|
||||
SnakeAfterimage2,
|
||||
Exhaust,
|
||||
DrownedQuote,
|
||||
QuestionMark,
|
||||
LevelUp,
|
||||
HurtParticles,
|
||||
Explosion,
|
||||
LittleParticles,
|
||||
Unknown,
|
||||
SmallProjectileDissipation,
|
||||
EmptyText,
|
||||
PushJumpKey,
|
||||
}
|
||||
|
||||
impl CaretType {
|
||||
pub fn from_int(id: usize) -> Option<CaretType> {
|
||||
match id {
|
||||
0 => Some(CaretType::None),
|
||||
1 => Some(CaretType::Bubble),
|
||||
2 => Some(CaretType::ProjectileDissipation),
|
||||
3 => Some(CaretType::Shoot),
|
||||
4 => Some(CaretType::SnakeAfterimage),
|
||||
5 => Some(CaretType::Zzz),
|
||||
6 => Some(CaretType::SnakeAfterimage2),
|
||||
7 => Some(CaretType::Exhaust),
|
||||
8 => Some(CaretType::DrownedQuote),
|
||||
9 => Some(CaretType::QuestionMark),
|
||||
10 => Some(CaretType::LevelUp),
|
||||
11 => Some(CaretType::HurtParticles),
|
||||
12 => Some(CaretType::Explosion),
|
||||
13 => Some(CaretType::LittleParticles),
|
||||
14 => Some(CaretType::Unknown),
|
||||
15 => Some(CaretType::SmallProjectileDissipation),
|
||||
16 => Some(CaretType::EmptyText),
|
||||
17 => Some(CaretType::PushJumpKey),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Caret {
|
||||
pub ctype: CaretType,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub vel_x: i32,
|
||||
pub vel_y: i32,
|
||||
pub offset_x: i32,
|
||||
pub offset_y: i32,
|
||||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
pub cond: Condition,
|
||||
pub direction: Direction,
|
||||
pub anim_rect: Rect<u16>,
|
||||
action_num: u16,
|
||||
anim_num: u16,
|
||||
anim_counter: u16,
|
||||
}
|
||||
|
||||
impl Caret {
|
||||
pub fn new(x: i32, y: i32, ctype: CaretType, direct: Direction, constants: &EngineConstants) -> Caret {
|
||||
let (offset_x, offset_y) = constants.caret.offsets[ctype as usize];
|
||||
|
||||
Caret {
|
||||
ctype,
|
||||
x,
|
||||
y,
|
||||
vel_x: 0,
|
||||
vel_y: 0,
|
||||
offset_x,
|
||||
offset_y,
|
||||
prev_x: x,
|
||||
prev_y: y,
|
||||
cond: Condition(0x80),
|
||||
direction: direct,
|
||||
anim_rect: Rect::new(0, 0, 0, 0),
|
||||
action_num: 0,
|
||||
anim_num: 0,
|
||||
anim_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, rng: &dyn RNG, constants: &EngineConstants) {
|
||||
match self.ctype {
|
||||
CaretType::None => {}
|
||||
CaretType::Bubble => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.vel_x = rng.range(-0x400..0x400);
|
||||
self.vel_y = rng.range(-0x400..0);
|
||||
}
|
||||
|
||||
self.vel_y += 0x40;
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 5 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > 3 {
|
||||
self.cond.set_alive(false);
|
||||
self.anim_num = 3;
|
||||
}
|
||||
}
|
||||
|
||||
match self.direction {
|
||||
Direction::Left => self.anim_rect = constants.caret.bubble_left_rects[self.anim_num as usize],
|
||||
Direction::Right => self.anim_rect = constants.caret.bubble_right_rects[self.anim_num as usize],
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
CaretType::ProjectileDissipation => match self.direction {
|
||||
Direction::Left => {
|
||||
self.vel_y -= 0x10;
|
||||
self.y += self.vel_y;
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 5 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.projectile_dissipation_left_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.anim_rect = constants.caret.projectile_dissipation_left_rects[self.anim_num as usize];
|
||||
}
|
||||
Direction::Up => {
|
||||
self.anim_counter += 1;
|
||||
|
||||
if self.anim_counter > 24 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
|
||||
let len = constants.caret.projectile_dissipation_up_rects.len();
|
||||
self.anim_rect =
|
||||
constants.caret.projectile_dissipation_up_rects[(self.anim_num as usize / 2) % len];
|
||||
}
|
||||
Direction::Right => {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.projectile_dissipation_right_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.anim_rect = constants.caret.projectile_dissipation_right_rects[self.anim_num as usize];
|
||||
}
|
||||
Direction::Bottom => {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
Direction::FacingPlayer => unreachable!(),
|
||||
},
|
||||
CaretType::Shoot => {
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_rect = constants.caret.shoot_rects[self.anim_num as usize];
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.shoot_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
CaretType::SnakeAfterimage | CaretType::SnakeAfterimage2 => {} // dupe, unused
|
||||
CaretType::Zzz => {
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_rect = constants.caret.zzz_rects[self.anim_num as usize];
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 4 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.zzz_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.x += 0x80; // 0.4fix9
|
||||
self.y -= 0x80;
|
||||
}
|
||||
CaretType::Exhaust => {
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_rect = constants.caret.exhaust_rects[self.anim_num as usize];
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 1 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.exhaust_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match self.direction {
|
||||
Direction::Left => self.x -= 0x400,
|
||||
Direction::Up => self.y -= 0x400,
|
||||
Direction::Right => self.x += 0x400,
|
||||
Direction::Bottom => self.y += 0x400,
|
||||
Direction::FacingPlayer => (),
|
||||
}
|
||||
}
|
||||
CaretType::DrownedQuote => {
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_counter = 1;
|
||||
|
||||
match self.direction {
|
||||
Direction::Left => self.anim_rect = constants.caret.drowned_quote_left_rect,
|
||||
Direction::Right => self.anim_rect = constants.caret.drowned_quote_right_rect,
|
||||
Direction::FacingPlayer => unreachable!(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
CaretType::QuestionMark => {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter < 5 {
|
||||
self.y -= 0x800; // 4.0fix9
|
||||
}
|
||||
|
||||
if self.anim_counter == 32 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
|
||||
self.anim_rect = match self.direction {
|
||||
Direction::Left => constants.caret.question_left_rect,
|
||||
Direction::Right => constants.caret.question_right_rect,
|
||||
_ => self.anim_rect,
|
||||
}
|
||||
}
|
||||
CaretType::LevelUp => {
|
||||
self.anim_counter += 1;
|
||||
|
||||
if self.anim_counter == 80 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
|
||||
match self.direction {
|
||||
Direction::Left => {
|
||||
if self.anim_counter < 20 {
|
||||
self.y -= 0x400; // 2.0fix9
|
||||
}
|
||||
|
||||
let count = constants.caret.level_up_rects.len();
|
||||
self.anim_rect = constants.caret.level_up_rects[self.anim_counter as usize / 2 % count]
|
||||
}
|
||||
Direction::Right => {
|
||||
if self.anim_counter < 20 {
|
||||
self.y -= 0x200; // 2.0fix9
|
||||
}
|
||||
|
||||
let count = constants.caret.level_down_rects.len();
|
||||
self.anim_rect = constants.caret.level_down_rects[self.anim_counter as usize / 2 % count]
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
CaretType::HurtParticles => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
let angle = rng.range(0..255) as f64 * CDEG_RAD;
|
||||
self.vel_x = (angle.cos() * 1024.0) as i32;
|
||||
self.vel_y = (angle.sin() * 1024.0) as i32;
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_rect = constants.caret.hurt_particles_rects[self.anim_num as usize];
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.hurt_particles_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
CaretType::Explosion => {
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_rect = constants.caret.explosion_rects[self.anim_num as usize];
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.explosion_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
CaretType::LittleParticles => {
|
||||
if self.anim_num == 0 {
|
||||
match self.direction {
|
||||
Direction::Left => {
|
||||
self.vel_x = rng.range(-0x600..0x600) as i32;
|
||||
self.vel_y = rng.range(-0x200..0x200) as i32;
|
||||
}
|
||||
Direction::Up => {
|
||||
self.vel_y = rng.range(-3..-1) as i32 * 0x200;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.direction == Direction::Left {
|
||||
self.vel_x = (self.vel_x * 4) / 5;
|
||||
self.vel_y = (self.vel_y * 4) / 5;
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 20 {
|
||||
self.cond.set_alive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let len = constants.caret.little_particles_rects.len();
|
||||
self.anim_rect = constants.caret.little_particles_rects[self.anim_num as usize / 2 % len];
|
||||
|
||||
if self.direction == Direction::Right {
|
||||
self.x -= 0x800;
|
||||
}
|
||||
}
|
||||
CaretType::Unknown => {
|
||||
// not implemented because it was apparently broken in og game?
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
CaretType::SmallProjectileDissipation => {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
self.anim_counter = 0;
|
||||
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > 3 {
|
||||
self.cond.set_alive(false);
|
||||
self.anim_num = 3;
|
||||
}
|
||||
}
|
||||
|
||||
self.anim_rect = constants.caret.small_projectile_dissipation[self.anim_num as usize];
|
||||
}
|
||||
CaretType::EmptyText => {
|
||||
self.anim_counter += 1;
|
||||
|
||||
if self.anim_counter < 10 {
|
||||
self.y -= 0x400;
|
||||
}
|
||||
|
||||
if self.anim_counter == 40 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
|
||||
self.anim_rect = constants.caret.empty_text[self.anim_counter as usize / 2 % 2];
|
||||
}
|
||||
// Completely unused but whatever
|
||||
CaretType::PushJumpKey => {
|
||||
self.anim_counter += 1;
|
||||
|
||||
if self.anim_counter >= 40 {
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
self.anim_rect = constants.caret.push_jump_key[if self.anim_counter < 30 { 1 } else { 0 }];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_dead(&self) -> bool {
|
||||
!self.cond.alive()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#include "common.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace doukutsu_rs;
|
||||
|
||||
float common::interpolate_fix9_scale(int old_val, int val, float frame_delta)
|
||||
{
|
||||
if (std::abs(old_val - val) > 0x1800)
|
||||
{
|
||||
return fix9_scale(val);
|
||||
}
|
||||
|
||||
return fix9_scale(old_val) * (1.0f - frame_delta) + fix9_scale(val) * frame_delta;
|
||||
}
|
||||
|
||||
uint64_t common::get_timestamp()
|
||||
{
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
|
||||
}
|
|
@ -0,0 +1,413 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace doukutsu_rs::texture_set
|
||||
{
|
||||
extern float I_MAG;
|
||||
extern float G_MAG;
|
||||
};
|
||||
|
||||
namespace doukutsu_rs::common
|
||||
{
|
||||
struct Flag
|
||||
{
|
||||
uint32_t value;
|
||||
|
||||
constexpr Flag() : value(0) {}
|
||||
constexpr Flag(uint32_t value) : value(value) {}
|
||||
|
||||
bool operator==(const Flag &other) const { return value == other.value; }
|
||||
bool operator!=(const Flag &other) const { return value != other.value; }
|
||||
|
||||
constexpr bool hit_left_wall() const { return value & 0x01; }
|
||||
constexpr bool hit_top_wall() const { return value & 0x02; }
|
||||
constexpr bool hit_right_wall() const { return value & 0x04; }
|
||||
constexpr bool hit_bottom_wall() const { return value & 0x08; }
|
||||
constexpr bool hit_right_slope() const { return value & 0x10; }
|
||||
constexpr bool hit_left_slope() const { return value & 0x20; }
|
||||
constexpr bool hit_upper_right_slope() const { return value & 0x40; }
|
||||
constexpr bool hit_upper_left_slope() const { return value & 0x80; }
|
||||
constexpr bool in_water() const { return value & 0x100; }
|
||||
constexpr bool weapon_hit_block() const { return value & 0x200; }
|
||||
constexpr bool hit_by_spike() const { return value & 0x400; }
|
||||
constexpr bool water_splash_facing_right() const { return value & 0x800; }
|
||||
constexpr bool force_left() const { return value & 0x1000; }
|
||||
constexpr bool force_up() const { return value & 0x2000; }
|
||||
constexpr bool force_right() const { return value & 0x4000; }
|
||||
constexpr bool force_down() const { return value & 0x8000; }
|
||||
constexpr bool hit_left_higher_half() const { return value & 0x10000; }
|
||||
constexpr bool hit_left_lower_half() const { return value & 0x20000; }
|
||||
constexpr bool hit_right_lower_half() const { return value & 0x40000; }
|
||||
constexpr bool hit_right_higher_half() const { return value & 0x80000; }
|
||||
|
||||
constexpr void set_hit_left_wall(bool value) { this->value = value ? (this->value | 0x01) : (this->value & ~0x01); }
|
||||
constexpr void set_hit_top_wall(bool value) { this->value = value ? (this->value | 0x02) : (this->value & ~0x02); }
|
||||
constexpr void set_hit_right_wall(bool value) { this->value = value ? (this->value | 0x04) : (this->value & ~0x04); }
|
||||
constexpr void set_hit_bottom_wall(bool value) { this->value = value ? (this->value | 0x08) : (this->value & ~0x08); }
|
||||
constexpr void set_hit_right_slope(bool value) { this->value = value ? (this->value | 0x10) : (this->value & ~0x10); }
|
||||
constexpr void set_hit_left_slope(bool value) { this->value = value ? (this->value | 0x20) : (this->value & ~0x20); }
|
||||
constexpr void set_hit_upper_right_slope(bool value) { this->value = value ? (this->value | 0x40) : (this->value & ~0x40); }
|
||||
constexpr void set_hit_upper_left_slope(bool value) { this->value = value ? (this->value | 0x80) : (this->value & ~0x80); }
|
||||
constexpr void set_in_water(bool value) { this->value = value ? (this->value | 0x100) : (this->value & ~0x100); }
|
||||
constexpr void set_weapon_hit_block(bool value) { this->value = value ? (this->value | 0x200) : (this->value & ~0x200); }
|
||||
constexpr void set_hit_by_spike(bool value) { this->value = value ? (this->value | 0x400) : (this->value & ~0x400); }
|
||||
constexpr void set_water_splash_facing_right(bool value) { this->value = value ? (this->value | 0x800) : (this->value & ~0x800); }
|
||||
constexpr void set_force_left(bool value) { this->value = value ? (this->value | 0x1000) : (this->value & ~0x1000); }
|
||||
constexpr void set_force_up(bool value) { this->value = value ? (this->value | 0x2000) : (this->value & ~0x2000); }
|
||||
constexpr void set_force_right(bool value) { this->value = value ? (this->value | 0x4000) : (this->value & ~0x4000); }
|
||||
constexpr void set_force_down(bool value) { this->value = value ? (this->value | 0x8000) : (this->value & ~0x8000); }
|
||||
constexpr void set_hit_left_higher_half(bool value) { this->value = value ? (this->value | 0x10000) : (this->value & ~0x10000); }
|
||||
constexpr void set_hit_left_lower_half(bool value) { this->value = value ? (this->value | 0x20000) : (this->value & ~0x20000); }
|
||||
constexpr void set_hit_right_lower_half(bool value) { this->value = value ? (this->value | 0x40000) : (this->value & ~0x40000); }
|
||||
constexpr void set_hit_right_higher_half(bool value) { this->value = value ? (this->value | 0x80000) : (this->value & ~0x80000); }
|
||||
|
||||
constexpr bool any_flag() const { return value != 0; }
|
||||
constexpr bool hit_anything() const { return (value & 0x2ff) != 0; }
|
||||
};
|
||||
|
||||
struct Equipment
|
||||
{
|
||||
uint16_t value;
|
||||
|
||||
constexpr Equipment() : value(0) {}
|
||||
constexpr Equipment(uint16_t value) : value(value) {}
|
||||
|
||||
constexpr bool has_booster_0_8() const { return value & 0x01; }
|
||||
constexpr bool has_map() const { return value & 0x02; }
|
||||
constexpr bool has_arms_barrier() const { return value & 0x04; }
|
||||
constexpr bool has_turbocharge() const { return value & 0x08; }
|
||||
constexpr bool has_air_tank() const { return value & 0x10; }
|
||||
constexpr bool has_booster_2_0() const { return value & 0x20; }
|
||||
constexpr bool has_mimiga_mask() const { return value & 0x40; }
|
||||
constexpr bool has_whimsical_star() const { return value & 0x80; }
|
||||
constexpr bool has_nikumaru() const { return value & 0x100; }
|
||||
|
||||
constexpr void set_booster_0_8(bool value) { this->value = value ? (this->value | 0x01) : (this->value & ~0x01); }
|
||||
constexpr void set_map(bool value) { this->value = value ? (this->value | 0x02) : (this->value & ~0x02); }
|
||||
constexpr void set_arms_barrier(bool value) { this->value = value ? (this->value | 0x04) : (this->value & ~0x04); }
|
||||
constexpr void set_turbocharge(bool value) { this->value = value ? (this->value | 0x08) : (this->value & ~0x08); }
|
||||
constexpr void set_air_tank(bool value) { this->value = value ? (this->value | 0x10) : (this->value & ~0x10); }
|
||||
constexpr void set_booster_2_0(bool value) { this->value = value ? (this->value | 0x20) : (this->value & ~0x20); }
|
||||
constexpr void set_mimiga_mask(bool value) { this->value = value ? (this->value | 0x40) : (this->value & ~0x40); }
|
||||
constexpr void set_whimsical_star(bool value) { this->value = value ? (this->value | 0x80) : (this->value & ~0x80); }
|
||||
constexpr void set_nikumaru(bool value) { this->value = value ? (this->value | 0x100) : (this->value & ~0x100); }
|
||||
};
|
||||
|
||||
struct Condition
|
||||
{
|
||||
uint16_t value;
|
||||
|
||||
constexpr Condition() : value(0) {}
|
||||
constexpr Condition(uint16_t value) : value(value) {}
|
||||
|
||||
constexpr bool interacted() const { return value & 0x01; }
|
||||
constexpr bool hidden() const { return value & 0x02; }
|
||||
constexpr bool fallen() const { return value & 0x04; }
|
||||
constexpr bool explode_die() const { return value & 0x08; }
|
||||
constexpr bool damage_boss() const { return value & 0x10; }
|
||||
constexpr bool increase_acceleration() const { return value & 0x20; }
|
||||
constexpr bool cond_x40() const { return value & 0x40; }
|
||||
constexpr bool alive() const { return value & 0x80; }
|
||||
constexpr bool drs_novanish() const { return value & 0x4000; }
|
||||
constexpr bool drs_boss() const { return value & 0x8000; }
|
||||
|
||||
constexpr void set_interacted(bool value) { this->value = value ? (this->value | 0x01) : (this->value & ~0x01); }
|
||||
constexpr void set_hidden(bool value) { this->value = value ? (this->value | 0x02) : (this->value & ~0x02); }
|
||||
constexpr void set_fallen(bool value) { this->value = value ? (this->value | 0x04) : (this->value & ~0x04); }
|
||||
constexpr void set_explode_die(bool value) { this->value = value ? (this->value | 0x08) : (this->value & ~0x08); }
|
||||
constexpr void set_damage_boss(bool value) { this->value = value ? (this->value | 0x10) : (this->value & ~0x10); }
|
||||
constexpr void set_increase_acceleration(bool value) { this->value = value ? (this->value | 0x20) : (this->value & ~0x20); }
|
||||
constexpr void set_cond_x40(bool value) { this->value = value ? (this->value | 0x40) : (this->value & ~0x40); }
|
||||
constexpr void set_alive(bool value) { this->value = value ? (this->value | 0x80) : (this->value & ~0x80); }
|
||||
constexpr void set_drs_novanish(bool value) { this->value = value ? (this->value | 0x4000) : (this->value & ~0x4000); }
|
||||
constexpr void set_drs_boss(bool value) { this->value = value ? (this->value | 0x8000) : (this->value & ~0x8000); }
|
||||
};
|
||||
|
||||
struct ControlFlags
|
||||
{
|
||||
uint16_t value;
|
||||
constexpr ControlFlags() : value(0) {}
|
||||
constexpr ControlFlags(uint16_t value) : value(value) {}
|
||||
|
||||
constexpr bool tick_world() const { return value & 0x01; }
|
||||
constexpr bool control_enabled() const { return value & 0x02; }
|
||||
constexpr bool interactions_disabled() const { return value & 0x04; }
|
||||
constexpr bool credits_running() const { return value & 0x08; }
|
||||
constexpr bool ok_button_disabled() const { return value & 0x10; }
|
||||
constexpr bool friendly_fire() const { return value & 0x4000; }
|
||||
|
||||
constexpr void set_tick_world(bool value) { this->value = value ? (this->value | 0x01) : (this->value & ~0x01); }
|
||||
constexpr void set_control_enabled(bool value) { this->value = value ? (this->value | 0x02) : (this->value & ~0x02); }
|
||||
constexpr void set_interactions_disabled(bool value) { this->value = value ? (this->value | 0x04) : (this->value & ~0x04); }
|
||||
constexpr void set_credits_running(bool value) { this->value = value ? (this->value | 0x08) : (this->value & ~0x08); }
|
||||
constexpr void set_ok_button_disabled(bool value) { this->value = value ? (this->value | 0x10) : (this->value & ~0x10); }
|
||||
constexpr void set_friendly_fire(bool value) { this->value = value ? (this->value | 0x4000) : (this->value & ~0x4000); }
|
||||
};
|
||||
|
||||
struct BulletFlag
|
||||
{
|
||||
uint8_t value;
|
||||
|
||||
constexpr BulletFlag() : value(0) {}
|
||||
constexpr BulletFlag(uint8_t value) : value(value) {}
|
||||
|
||||
// 0x01, nowhere in code?
|
||||
constexpr bool flag_x01() const { return value & 0x01; }
|
||||
// 0x02, nowhere in code?
|
||||
constexpr bool flag_x02() const { return value & 0x02; }
|
||||
// 0x04, if set, bullet will pass through blocks.
|
||||
constexpr bool no_collision_checks() const { return value & 0x04; }
|
||||
// 0x08, if set, bullet will bounce off walls.
|
||||
constexpr bool bounce_from_walls() const { return value & 0x08; }
|
||||
// 0x10, if set, bullet will not produce projectile dissipation effect when it hits a NPC or boss.
|
||||
constexpr bool no_proj_dissipation() const { return value & 0x10; }
|
||||
// 0x20, if set, performs checks in block collision check procedure. Kills the bullet if flag 0x40 isn't set.
|
||||
constexpr bool check_block_hit() const { return value & 0x20; }
|
||||
// 0x40, if set, bullet will destroy snack blocks on hit.
|
||||
constexpr bool can_destroy_snack() const { return value & 0x40; }
|
||||
// 0x80, nowhere in code?
|
||||
constexpr bool flag_x80() const { return value & 0x80; }
|
||||
|
||||
constexpr void set_flag_x01(bool value) { this->value = value ? (this->value | 0x01) : (this->value & ~0x01); }
|
||||
constexpr void set_flag_x02(bool value) { this->value = value ? (this->value | 0x02) : (this->value & ~0x02); }
|
||||
constexpr void set_no_collision_checks(bool value) { this->value = value ? (this->value | 0x04) : (this->value & ~0x04); }
|
||||
constexpr void set_bounce_from_walls(bool value) { this->value = value ? (this->value | 0x08) : (this->value & ~0x08); }
|
||||
constexpr void set_no_proj_dissipation(bool value) { this->value = value ? (this->value | 0x10) : (this->value & ~0x10); }
|
||||
constexpr void set_check_block_hit(bool value) { this->value = value ? (this->value | 0x20) : (this->value & ~0x20); }
|
||||
constexpr void set_can_destroy_snack(bool value) { this->value = value ? (this->value | 0x40) : (this->value & ~0x40); }
|
||||
constexpr void set_flag_x80(bool value) { this->value = value ? (this->value | 0x80) : (this->value & ~0x80); }
|
||||
};
|
||||
|
||||
class FadeDirection
|
||||
{
|
||||
public:
|
||||
enum Value : uint8_t
|
||||
{
|
||||
Left = 0,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Center,
|
||||
};
|
||||
|
||||
FadeDirection() = default;
|
||||
constexpr FadeDirection(Value val) : value(val) {}
|
||||
|
||||
constexpr operator Value() const { return value; }
|
||||
explicit operator bool() = delete;
|
||||
|
||||
static constexpr std::optional<FadeDirection> from_int(int val)
|
||||
{
|
||||
return val == 0
|
||||
? std::make_optional(FadeDirection(Left))
|
||||
: val == 1 ? std::make_optional(FadeDirection(Up))
|
||||
: val == 2 ? std::make_optional(FadeDirection(Right))
|
||||
: val == 3 ? std::make_optional(FadeDirection(Down))
|
||||
: val == 4 ? std::make_optional(FadeDirection(Center))
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
constexpr FadeDirection opposite() const
|
||||
{
|
||||
return value == Left ? Right
|
||||
: value == Up ? Down
|
||||
: value == Right ? Left
|
||||
: value == Down ? Up
|
||||
: Center;
|
||||
}
|
||||
|
||||
private:
|
||||
Value value;
|
||||
};
|
||||
|
||||
class FadeState
|
||||
{
|
||||
public:
|
||||
};
|
||||
|
||||
class Direction
|
||||
{
|
||||
public:
|
||||
enum Value : uint8_t
|
||||
{
|
||||
Left = 0,
|
||||
Up,
|
||||
Right,
|
||||
Bottom,
|
||||
FacingPlayer,
|
||||
};
|
||||
|
||||
Direction() = default;
|
||||
constexpr Direction(Value val) : value(val) {}
|
||||
|
||||
constexpr operator Value() const { return value; }
|
||||
explicit operator bool() = delete;
|
||||
|
||||
constexpr bool operator==(const Direction &other) const { return value == other.value; }
|
||||
constexpr bool operator!=(const Direction &other) const { return value != other.value; }
|
||||
|
||||
constexpr bool operator==(Value other) const { return value == other; }
|
||||
constexpr bool operator!=(Value other) const { return value != other; }
|
||||
|
||||
static constexpr std::optional<Direction> from_int(int val)
|
||||
{
|
||||
return val == 0
|
||||
? std::make_optional(Direction(Left))
|
||||
: val == 1 ? std::make_optional(Direction(Up))
|
||||
: val == 2 ? std::make_optional(Direction(Right))
|
||||
: val == 3 ? std::make_optional(Direction(Bottom))
|
||||
: val == 4 ? std::make_optional(Direction(FacingPlayer))
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
constexpr Direction opposite() const
|
||||
{
|
||||
return value == Left ? Right
|
||||
: value == Up ? Bottom
|
||||
: value == Right ? Left
|
||||
: value == Bottom ? Up
|
||||
: FacingPlayer;
|
||||
}
|
||||
|
||||
constexpr int vector_x() const
|
||||
{
|
||||
return value == Left ? -1
|
||||
: value == Up ? 0
|
||||
: value == Right ? 1
|
||||
: value == Bottom ? 0
|
||||
: 0;
|
||||
}
|
||||
|
||||
constexpr int vector_y() const
|
||||
{
|
||||
return value == Left ? 0
|
||||
: value == Up ? -1
|
||||
: value == Right ? 0
|
||||
: value == Bottom ? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Value value;
|
||||
};
|
||||
|
||||
// use is_arithmetic to avoid the need for a separate impl for f32
|
||||
template <typename T>
|
||||
struct Rect
|
||||
{
|
||||
T left;
|
||||
T top;
|
||||
T right;
|
||||
T bottom;
|
||||
|
||||
constexpr Rect() = default;
|
||||
constexpr Rect(T left, T top, T right, T bottom) : left(left), top(top), right(right), bottom(bottom) {}
|
||||
|
||||
constexpr Rect new_size(T x, T y, T width, T height)
|
||||
{
|
||||
return Rect(x, y, x + width, y + height);
|
||||
}
|
||||
|
||||
constexpr bool has_point(T x, T y) const
|
||||
{
|
||||
return left <= x && x <= right && top <= y && y <= bottom;
|
||||
}
|
||||
|
||||
constexpr T width() const
|
||||
{
|
||||
return left > right ? left - right : right - left;
|
||||
}
|
||||
|
||||
constexpr T height() const
|
||||
{
|
||||
return top > bottom ? top - bottom : bottom - top;
|
||||
}
|
||||
};
|
||||
|
||||
inline float fix9_scale(int val)
|
||||
{
|
||||
return (float)val * doukutsu_rs::texture_set::G_MAG / 512.0f;
|
||||
}
|
||||
|
||||
inline double lerp_f64(double v1, double v2, double t)
|
||||
{
|
||||
return v1 * (1.0 - t) + v2 * t;
|
||||
}
|
||||
|
||||
float interpolate_fix9_scale(int old_val, int val, float frame_delta);
|
||||
|
||||
uint64_t get_timestamp();
|
||||
|
||||
struct Color
|
||||
{
|
||||
float r, g, b, a;
|
||||
|
||||
constexpr Color() : r(0.0f), g(0.0f), b(0.0f), a(0.0f) {}
|
||||
constexpr Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) {}
|
||||
|
||||
constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
return Color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||
}
|
||||
|
||||
constexpr Color from_rgb(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
return Color(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
constexpr Color from_rgba_u32(uint32_t c)
|
||||
{
|
||||
return Color::from_rgba(
|
||||
(uint8_t)(c >> 24),
|
||||
(uint8_t)(c >> 16),
|
||||
(uint8_t)(c >> 8),
|
||||
(uint8_t)c);
|
||||
}
|
||||
|
||||
constexpr Color from_rgb_u32(uint32_t c)
|
||||
{
|
||||
return Color::from_rgb(
|
||||
(uint8_t)(c >> 16),
|
||||
(uint8_t)(c >> 8),
|
||||
(uint8_t)c);
|
||||
}
|
||||
|
||||
constexpr std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> to_rgba()
|
||||
{
|
||||
return std::make_tuple(
|
||||
(uint8_t)(r * 255.0f),
|
||||
(uint8_t)(g * 255.0f),
|
||||
(uint8_t)(b * 255.0f),
|
||||
(uint8_t)(a * 255.0f));
|
||||
}
|
||||
|
||||
constexpr std::tuple<uint8_t, uint8_t, uint8_t> to_rgb()
|
||||
{
|
||||
return std::make_tuple(
|
||||
(uint8_t)(r * 255.0f),
|
||||
(uint8_t)(g * 255.0f),
|
||||
(uint8_t)(b * 255.0f));
|
||||
}
|
||||
|
||||
constexpr uint32_t to_rgba_u32()
|
||||
{
|
||||
const auto [r, g, b, a] = to_rgba();
|
||||
return (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b;
|
||||
}
|
||||
|
||||
constexpr uint32_t to_rgb_u32()
|
||||
{
|
||||
const auto [r, g, b] = to_rgb();
|
||||
return (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b;
|
||||
}
|
||||
};
|
||||
|
||||
[[noreturn]]
|
||||
inline void unreachable() {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
};
|
568
src/common.rs
|
@ -1,568 +0,0 @@
|
|||
use std::fmt;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::{abs, Num};
|
||||
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;
|
||||
|
||||
/// Multiply cave story degrees (0-255, which corresponds to 0°-360°) with this constant to get
|
||||
/// respective value in radians.
|
||||
pub const CDEG_RAD: f64 = std::f64::consts::PI / 128.0;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref VERSION_BANNER: String = {
|
||||
let version = option_env!("DRS_BUILD_VERSION_OVERRIDE").unwrap_or(env!("CARGO_PKG_VERSION"));
|
||||
format!("doukutsu-rs {}", version)
|
||||
};
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Flag(u32);
|
||||
impl Debug;
|
||||
|
||||
/// Set if left wall was hit. (corresponds to flag & 0x01)
|
||||
pub hit_left_wall, set_hit_left_wall: 0;
|
||||
/// Set if top wall was hit. (corresponds to flag & 0x02)
|
||||
pub hit_top_wall, set_hit_top_wall: 1;
|
||||
/// Set if right wall was hit. (corresponds to flag & 0x04)
|
||||
pub hit_right_wall, set_hit_right_wall: 2;
|
||||
/// Set if bottom wall was hit. (corresponds to flag & 0x08)
|
||||
pub hit_bottom_wall, set_hit_bottom_wall: 3;
|
||||
/// Set if entity stays on right slope. (corresponds to flag & 0x10)
|
||||
pub hit_right_slope, set_hit_right_slope: 4;
|
||||
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
|
||||
pub hit_left_slope, set_hit_left_slope: 5;
|
||||
/// Used only in bullet code, set if a bullet hits upper right slope (corresponds to flag & 0x40)
|
||||
pub hit_upper_right_slope, set_hit_upper_right_slope: 6;
|
||||
/// Used only in bullet code, set if a bullet hits upper left slope (corresponds to flag & 0x80)
|
||||
pub hit_upper_left_slope, set_hit_upper_left_slope: 7;
|
||||
/// Set if entity is in water. (corresponds to flag & 0x100)
|
||||
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 force_left, set_force_left: 12; // 0x1000
|
||||
pub force_up, set_force_up: 13; // 0x2000
|
||||
pub force_right, set_force_right: 14; // 0x4000
|
||||
pub force_down, set_force_down: 15; // 0x8000
|
||||
pub hit_left_higher_half, set_hit_left_higher_half: 16; // 0x10000
|
||||
pub hit_left_lower_half, set_hit_left_lower_half: 17; // 0x20000
|
||||
pub hit_right_lower_half, set_hit_right_lower_half: 18; // 0x40000
|
||||
pub hit_right_higher_half, set_hit_right_higher_half: 19; // 0x80000
|
||||
}
|
||||
|
||||
impl Flag {
|
||||
pub fn any_flag(&self) -> bool {
|
||||
self.0 != 0
|
||||
}
|
||||
|
||||
pub fn hit_anything(&self) -> bool {
|
||||
(self.0 & 0x2ff) != 0
|
||||
}
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Equipment(u16);
|
||||
impl Debug;
|
||||
|
||||
pub has_booster_0_8, set_booster_0_8: 0; // 0x01 / 0001
|
||||
pub has_map, set_map: 1; // 0x02 / 0002
|
||||
pub has_arms_barrier, set_arms_barrier: 2; // 0x04 / 0004
|
||||
pub has_turbocharge, set_turbocharge: 3; // 0x08 / 0008
|
||||
pub has_air_tank, set_air_tank: 4; // 0x10 / 0016
|
||||
pub has_booster_2_0, set_booster_2_0: 5; // 0x20 / 0032
|
||||
pub has_mimiga_mask, set_mimiga_mask: 6; // 0x40 / 0064
|
||||
pub has_whimsical_star, set_whimsical_star: 7; // 0x080 / 0128
|
||||
pub has_nikumaru, set_nikumaru: 8; // 0x100 / 0256
|
||||
// for custom equips
|
||||
pub unused_1, set_unused_1: 9; // 0x200 / 0512
|
||||
pub unused_2, set_unused_2: 10; // 0x400 / 1024
|
||||
pub unused_3, set_unused_3: 11; // 0x800 / 2048
|
||||
pub unused_4, set_unused_4: 12; // 0x1000 / 4096
|
||||
pub unused_5, set_unused_5: 13; // 0x2000 / 8192
|
||||
// bit 14 and 15 aren't accessible via TSC without abusing overflows (won't work in strict mode)
|
||||
pub unused_6, set_unused_6: 14; // 0x4000 / @384
|
||||
pub unused_7, set_unused_7: 15; // 0x8000 / P768
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Condition(u16);
|
||||
impl Debug;
|
||||
|
||||
pub interacted, set_interacted: 0; // 0x01
|
||||
pub hidden, set_hidden: 1; // 0x02
|
||||
pub fallen, set_fallen: 2; // 0x04
|
||||
pub explode_die, set_explode_die: 3; // 0x08
|
||||
pub damage_boss, set_damage_boss: 4; // 0x10
|
||||
pub increase_acceleration, set_increase_acceleration: 5; // 0x20
|
||||
pub cond_x40, set_cond_x40: 6; // 0x40
|
||||
pub alive, set_alive: 7; // 0x80
|
||||
|
||||
// engine specific flags
|
||||
pub drs_novanish, set_drs_novanish: 14;
|
||||
pub drs_boss, set_drs_boss: 15;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
pub struct ControlFlags(u16);
|
||||
impl Debug;
|
||||
|
||||
pub tick_world, set_tick_world: 0; // 0x01
|
||||
pub control_enabled, set_control_enabled: 1; // 0x02
|
||||
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
|
||||
pub credits_running, set_credits_running: 3; // 0x08
|
||||
// cs+ switch specific, according to peri:
|
||||
// Flag 0x10 prevents the OK button from restarting the item description event (resets when the cursor is moved)
|
||||
// (it does not prevent the cancel button from exiting the inventory, however)
|
||||
pub ok_button_disabled, set_ok_button_disabled: 4; // 0x10
|
||||
// engine specific flags
|
||||
pub friendly_fire, set_friendly_fire: 14;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct BulletFlag(u8);
|
||||
impl Debug;
|
||||
pub flag_x01, set_flag_x01: 0; // 0x01, nowhere in code?
|
||||
pub flag_x02, set_flag_x02: 1; // 0x02, nowhere in code?
|
||||
/// Corresponds to flag & 0x04. If set, bullet will pass through blocks.
|
||||
pub no_collision_checks, set_no_collision_checks: 2;
|
||||
/// Corresponds to flag & 0x08. IF set, bullet will bounce off walls.
|
||||
pub bounce_from_walls, set_bounce_from_walls: 3;
|
||||
/// Corresponds to flag & 0x10. IF set, bullet will not produce projectile dissipation effect when it hits a NPC or boss.
|
||||
pub no_proj_dissipation, set_no_proj_dissipation: 4;
|
||||
/// Corresponds to flag & 0x20. If set, performs checks in block collision check procedure. Kills the bullet if flag 0x40 isn't set.
|
||||
pub check_block_hit, set_check_block_hit: 5;
|
||||
/// Corresponds to flag & 0x40. If set, bullet will destroy snack blocks on hit.
|
||||
pub can_destroy_snack, set_can_destroy_snack: 6;
|
||||
pub flag_x80, set_flag_x80: 7; // 0x80, nowhere in code?
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum FadeDirection {
|
||||
Left = 0,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Center,
|
||||
}
|
||||
|
||||
impl FadeDirection {
|
||||
pub const fn from_int(val: usize) -> Option<FadeDirection> {
|
||||
match val {
|
||||
0 => Some(FadeDirection::Left),
|
||||
1 => Some(FadeDirection::Up),
|
||||
2 => Some(FadeDirection::Right),
|
||||
3 => Some(FadeDirection::Down),
|
||||
4 => Some(FadeDirection::Center),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn opposite(&self) -> FadeDirection {
|
||||
match self {
|
||||
FadeDirection::Left => FadeDirection::Right,
|
||||
FadeDirection::Up => FadeDirection::Down,
|
||||
FadeDirection::Right => FadeDirection::Left,
|
||||
FadeDirection::Down => FadeDirection::Up,
|
||||
FadeDirection::Center => FadeDirection::Center,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum FadeState {
|
||||
Visible,
|
||||
FadeIn(i8, FadeDirection),
|
||||
Hidden,
|
||||
FadeOut(i8, FadeDirection),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum Direction {
|
||||
Left = 0,
|
||||
Up,
|
||||
Right,
|
||||
Bottom,
|
||||
FacingPlayer,
|
||||
}
|
||||
|
||||
pub const FILE_TYPES: [&str; 3] = [".png", ".bmp", ".pbm"];
|
||||
|
||||
impl Direction {
|
||||
pub const fn from_int(val: usize) -> Option<Direction> {
|
||||
match val {
|
||||
0 => Some(Direction::Left),
|
||||
1 => Some(Direction::Up),
|
||||
2 => Some(Direction::Right),
|
||||
3 => Some(Direction::Bottom),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_int_facing(val: usize) -> Option<Direction> {
|
||||
match val {
|
||||
0 => Some(Direction::Left),
|
||||
1 => Some(Direction::Up),
|
||||
2 => Some(Direction::Right),
|
||||
3 => Some(Direction::Bottom),
|
||||
4 => Some(Direction::FacingPlayer),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn opposite(&self) -> Direction {
|
||||
match self {
|
||||
Direction::Left => Direction::Right,
|
||||
Direction::Up => Direction::Bottom,
|
||||
Direction::Right => Direction::Left,
|
||||
Direction::Bottom => Direction::Up,
|
||||
Direction::FacingPlayer => Direction::FacingPlayer,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn vector_x(&self) -> i32 {
|
||||
match self {
|
||||
Direction::Left => -1,
|
||||
Direction::Up => 0,
|
||||
Direction::Right => 1,
|
||||
Direction::Bottom => 0,
|
||||
Direction::FacingPlayer => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn vector_y(&self) -> i32 {
|
||||
match self {
|
||||
Direction::Left => 0,
|
||||
Direction::Up => -1,
|
||||
Direction::Right => 0,
|
||||
Direction::Bottom => 1,
|
||||
Direction::FacingPlayer => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Rect<T: Num + PartialOrd + Copy = isize> {
|
||||
pub left: T,
|
||||
pub top: T,
|
||||
pub right: T,
|
||||
pub bottom: T,
|
||||
}
|
||||
|
||||
impl<T: Num + PartialOrd + Copy> Rect<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(left: T, top: T, right: T, bottom: T) -> Rect<T> {
|
||||
Rect { left, top, right, bottom }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn new_size(x: T, y: T, width: T, height: T) -> Rect<T> {
|
||||
Rect { left: x, top: y, right: x.add(width), bottom: y.add(height) }
|
||||
}
|
||||
|
||||
pub fn has_point(&self, x: T, y: T) -> bool {
|
||||
self.left.ge(&x) && self.right.le(&x) && self.top.ge(&y) && self.bottom.le(&y)
|
||||
}
|
||||
|
||||
pub fn width(&self) -> T {
|
||||
if self.left.gt(&self.right) {
|
||||
self.left.sub(self.right)
|
||||
} else {
|
||||
self.right.sub(self.left)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> T {
|
||||
if self.top.gt(&self.bottom) {
|
||||
self.top.sub(self.bottom)
|
||||
} else {
|
||||
self.bottom.sub(self.top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Num + PartialOrd + Copy + Serialize> Serialize for Rect<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_tuple_struct("Rect", 4)?;
|
||||
state.serialize_field(&self.left)?;
|
||||
state.serialize_field(&self.top)?;
|
||||
state.serialize_field(&self.right)?;
|
||||
state.serialize_field(&self.bottom)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Num + PartialOrd + Copy + Serialize> Default for Rect<T> {
|
||||
fn default() -> Self {
|
||||
Rect {
|
||||
left: num_traits::zero(),
|
||||
top: num_traits::zero(),
|
||||
right: num_traits::zero(),
|
||||
bottom: num_traits::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! rect_deserialize {
|
||||
($num_type: ident) => {
|
||||
impl<'de> Deserialize<'de> for Rect<$num_type> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Rect<$num_type>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct RectVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RectVisitor {
|
||||
type Value = Rect<$num_type>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Expected Rect structure.")
|
||||
}
|
||||
|
||||
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: SeqAccess<'de>,
|
||||
{
|
||||
let invalid_length = || de::Error::invalid_length(0, &self);
|
||||
|
||||
let left = seq.next_element()?.ok_or_else(invalid_length)?;
|
||||
let top = seq.next_element()?.ok_or_else(invalid_length)?;
|
||||
let right = seq.next_element()?.ok_or_else(invalid_length)?;
|
||||
let bottom = seq.next_element()?.ok_or_else(invalid_length)?;
|
||||
|
||||
Ok(Rect { left, top, right, bottom })
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_tuple_struct("Rect", 4, RectVisitor)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
rect_deserialize!(u8);
|
||||
rect_deserialize!(u16);
|
||||
rect_deserialize!(i32);
|
||||
rect_deserialize!(isize);
|
||||
rect_deserialize!(usize);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn fix9_scale(val: i32) -> f32 {
|
||||
unsafe {
|
||||
let mag = G_MAG as f32;
|
||||
(val as f32 * mag / 512.0).floor() / mag
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lerp_f64(v1: f64, v2: f64, t: f64) -> f64 {
|
||||
v1 * (1.0 - t) + v2 * t
|
||||
}
|
||||
|
||||
pub fn interpolate_fix9_scale(old_val: i32, val: i32, frame_delta: f64) -> f32 {
|
||||
if abs(old_val - val) > 0x1800 {
|
||||
return val as f32 / 512.0;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let interpolated = lerp_f64(old_val as f64, val as f64, frame_delta) as f32;
|
||||
let mag = G_MAG as f32;
|
||||
(interpolated * mag / 512.0).floor() / mag
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_timestamp() -> u64 {
|
||||
let now = SystemTime::now();
|
||||
now.duration_since(UNIX_EPOCH).unwrap().as_secs() as u64
|
||||
}
|
||||
|
||||
/// A RGBA color in the `sRGB` color space represented as `f32`'s in the range `[0.0-1.0]`
|
||||
///
|
||||
/// For convenience, [`WHITE`](constant.WHITE.html) and [`BLACK`](constant.BLACK.html) are provided.
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Color {
|
||||
/// Red component
|
||||
pub r: f32,
|
||||
/// Green component
|
||||
pub g: f32,
|
||||
/// Blue component
|
||||
pub b: f32,
|
||||
/// Alpha component
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Create a new `Color` from four `f32`'s in the range `[0.0-1.0]`
|
||||
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
Color { r, g, b, a }
|
||||
}
|
||||
|
||||
/// Create a new `Color` from four `u8`'s in the range `[0-255]`
|
||||
pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Color {
|
||||
Color::from((r, g, b, a))
|
||||
}
|
||||
|
||||
/// Create a new `Color` from three u8's in the range `[0-255]`,
|
||||
/// with the alpha component fixed to 255 (opaque)
|
||||
pub fn from_rgb(r: u8, g: u8, b: u8) -> Color {
|
||||
Color::from((r, g, b))
|
||||
}
|
||||
|
||||
/// Return a tuple of four `u8`'s in the range `[0-255]` with the `Color`'s
|
||||
/// components.
|
||||
pub fn to_rgba(self) -> (u8, u8, u8, u8) {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// Return a tuple of three `u8`'s in the range `[0-255]` with the `Color`'s
|
||||
/// components.
|
||||
pub fn to_rgb(self) -> (u8, u8, u8) {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// Convert a packed `u32` containing `0xRRGGBBAA` into a `Color`
|
||||
pub fn from_rgba_u32(c: u32) -> Color {
|
||||
let c = c.to_be_bytes();
|
||||
|
||||
Color::from((c[0], c[1], c[2], c[3]))
|
||||
}
|
||||
|
||||
/// Convert a packed `u32` containing `0x00RRGGBB` into a `Color`.
|
||||
/// This lets you do things like `Color::from_rgb_u32(0xCD09AA)` easily if you want.
|
||||
pub fn from_rgb_u32(c: u32) -> Color {
|
||||
let c = c.to_be_bytes();
|
||||
|
||||
Color::from((c[1], c[2], c[3]))
|
||||
}
|
||||
|
||||
/// Convert a `Color` into a packed `u32`, containing `0xRRGGBBAA` as bytes.
|
||||
pub fn to_rgba_u32(self) -> u32 {
|
||||
let (r, g, b, a): (u8, u8, u8, u8) = self.into();
|
||||
|
||||
u32::from_be_bytes([r, g, b, a])
|
||||
}
|
||||
|
||||
/// Convert a `Color` into a packed `u32`, containing `0x00RRGGBB` as bytes.
|
||||
pub fn to_rgb_u32(self) -> u32 {
|
||||
let (r, g, b, _a): (u8, u8, u8, u8) = self.into();
|
||||
|
||||
u32::from_be_bytes([0, r, g, b])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8, u8)> for Color {
|
||||
/// Convert a `(R, G, B, A)` tuple of `u8`'s in the range `[0-255]` into a `Color`
|
||||
fn from(val: (u8, u8, u8, u8)) -> Self {
|
||||
let (r, g, b, a) = val;
|
||||
let rf = (f32::from(r)) / 255.0;
|
||||
let gf = (f32::from(g)) / 255.0;
|
||||
let bf = (f32::from(b)) / 255.0;
|
||||
let af = (f32::from(a)) / 255.0;
|
||||
Color::new(rf, gf, bf, af)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8)> for Color {
|
||||
/// Convert a `(R, G, B)` tuple of `u8`'s in the range `[0-255]` into a `Color`,
|
||||
/// with a value of 255 for the alpha element (i.e., no transparency.)
|
||||
fn from(val: (u8, u8, u8)) -> Self {
|
||||
let (r, g, b) = val;
|
||||
Color::from((r, g, b, 255))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 4]> for Color {
|
||||
/// Turns an `[R, G, B, A] array of `f32`'s into a `Color` with no format changes.
|
||||
/// All inputs should be in the range `[0.0-1.0]`.
|
||||
fn from(val: [f32; 4]) -> Self {
|
||||
Color::new(val[0], val[1], val[2], val[3])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f32, f32, f32)> for Color {
|
||||
/// Convert a `(R, G, B)` tuple of `f32`'s in the range `[0.0-1.0]` into a `Color`,
|
||||
/// with a value of 1.0 to for the alpha element (ie, no transparency.)
|
||||
fn from(val: (f32, f32, f32)) -> Self {
|
||||
let (r, g, b) = val;
|
||||
Color::new(r, g, b, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f32, f32, f32, f32)> for Color {
|
||||
/// Convert a `(R, G, B, A)` tuple of `f32`'s in the range `[0.0-1.0]` into a `Color`
|
||||
fn from(val: (f32, f32, f32, f32)) -> Self {
|
||||
let (r, g, b, a) = val;
|
||||
Color::new(r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for (u8, u8, u8, u8) {
|
||||
/// Convert a `Color` into a `(R, G, B, A)` tuple of `u8`'s in the range of `[0-255]`.
|
||||
fn from(color: Color) -> Self {
|
||||
let r = (color.r * 255.0) as u8;
|
||||
let g = (color.g * 255.0) as u8;
|
||||
let b = (color.b * 255.0) as u8;
|
||||
let a = (color.a * 255.0) as u8;
|
||||
(r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for (u8, u8, u8) {
|
||||
/// Convert a `Color` into a `(R, G, B)` tuple of `u8`'s in the range of `[0-255]`,
|
||||
/// ignoring the alpha term.
|
||||
fn from(color: Color) -> Self {
|
||||
let (r, g, b, _) = color.into();
|
||||
(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for [f32; 4] {
|
||||
/// Convert a `Color` into an `[R, G, B, A]` array of `f32`'s in the range of `[0.0-1.0]`.
|
||||
fn from(color: Color) -> Self {
|
||||
[color.r, color.g, color.b, color.a]
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SliceExt {
|
||||
type Item;
|
||||
|
||||
fn get_two_mut(&mut self, a: usize, b: usize) -> Option<(&mut Self::Item, &mut Self::Item)>;
|
||||
}
|
||||
|
||||
impl<T> SliceExt for [T] {
|
||||
type Item = T;
|
||||
|
||||
fn get_two_mut(&mut self, a: usize, b: usize) -> Option<(&mut Self::Item, &mut Self::Item)> {
|
||||
if a == b || a >= self.len() || b >= self.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ar = &mut *(self.get_unchecked_mut(a) as *mut _);
|
||||
let br = &mut *(self.get_unchecked_mut(b) as *mut _);
|
||||
Some((ar, br))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
use crate::common::{Color, Rect};
|
||||
use crate::frame::Frame;
|
||||
use crate::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
use crate::{graphics, Context, GameResult, SharedGameState};
|
||||
|
||||
pub struct Background {
|
||||
pub tick: usize,
|
||||
pub prev_tick: usize,
|
||||
}
|
||||
|
||||
impl Background {
|
||||
pub fn new() -> Self {
|
||||
Background { tick: 0, prev_tick: 0 }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> GameResult<()> {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_tick(&mut self) -> GameResult<()> {
|
||||
self.prev_tick = self.tick;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
frame: &Frame,
|
||||
textures: &StageTexturePaths,
|
||||
stage: &Stage,
|
||||
) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &textures.background)?;
|
||||
let scale = state.scale;
|
||||
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
|
||||
match stage.data.background_type {
|
||||
BackgroundType::TiledStatic => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
|
||||
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
|
||||
let count_x = state.canvas_size.0 as i32 / bg_width + 1;
|
||||
let count_y = state.canvas_size.1 as i32 / bg_height + 1;
|
||||
|
||||
for y in -1..count_y {
|
||||
for x in -1..count_x {
|
||||
batch.add((x * bg_width) as f32, (y * bg_height) as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackgroundType::TiledParallax | BackgroundType::Tiled | BackgroundType::Waterway => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
|
||||
let (off_x, off_y) = if stage.data.background_type == BackgroundType::Tiled {
|
||||
(frame_x % (batch.width() as f32), frame_y % (batch.height() as f32))
|
||||
} else {
|
||||
(
|
||||
((frame_x / 2.0 * scale).floor() / scale) % (batch.width() as f32),
|
||||
((frame_y / 2.0 * scale).floor() / scale) % (batch.height() as f32),
|
||||
)
|
||||
};
|
||||
|
||||
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
|
||||
let count_x = state.canvas_size.0 as i32 / bg_width + 2;
|
||||
let count_y = state.canvas_size.1 as i32 / bg_height + 2;
|
||||
|
||||
for y in -1..count_y {
|
||||
for x in -1..count_x {
|
||||
batch.add((x * bg_width) as f32 - off_x, (y * bg_height) as f32 - off_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackgroundType::Water => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
}
|
||||
BackgroundType::Black => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
}
|
||||
BackgroundType::Scrolling => {
|
||||
graphics::clear(ctx, stage.data.background_color);
|
||||
|
||||
let (bg_width, bg_height) = (batch.width() as i32, batch.height() as i32);
|
||||
let offset_x = self.tick as f32 % (bg_width as f32 / 3.0);
|
||||
let interp_x = (offset_x * (1.0 - state.frame_time as f32)
|
||||
+ (offset_x + 1.0) * state.frame_time as f32)
|
||||
* 3.0
|
||||
* scale;
|
||||
|
||||
let count_x = state.canvas_size.0 as i32 / bg_width + 6;
|
||||
let count_y = state.canvas_size.1 as i32 / bg_height + 1;
|
||||
|
||||
for y in -1..count_y {
|
||||
for x in -1..count_x {
|
||||
batch.add((x * bg_width) as f32 - interp_x, (y * bg_height) as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackgroundType::OutsideWind | BackgroundType::Outside | BackgroundType::OutsideUnknown => {
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 0));
|
||||
|
||||
let offset_x = (self.tick % 640) as i32;
|
||||
let offset_y = ((state.canvas_size.1 - 240.0) / 2.0).floor();
|
||||
|
||||
for x in (0..(state.canvas_size.0 as i32)).step_by(100) {
|
||||
batch.add_rect(x as f32, offset_y, &Rect::new_size(128, 0, 100, 88));
|
||||
}
|
||||
|
||||
// top / bottom edges
|
||||
if offset_y > 0.0 {
|
||||
let scale = offset_y;
|
||||
|
||||
for x in (0..(state.canvas_size.0 as i32)).step_by(100) {
|
||||
batch.add_rect_scaled(x as f32, 0.0, 1.0, scale, &Rect::new_size(128, 0, 100, 1));
|
||||
}
|
||||
|
||||
batch.add_rect_scaled(
|
||||
(state.canvas_size.0 - 320.0) / 2.0,
|
||||
0.0,
|
||||
1.0,
|
||||
scale,
|
||||
&Rect::new_size(0, 0, 320, 1),
|
||||
);
|
||||
|
||||
for x in ((-offset_x * 4)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect_scaled(
|
||||
x as f32,
|
||||
offset_y + 240.0,
|
||||
1.0,
|
||||
scale + 4.0,
|
||||
&Rect::new_size(0, 239, 320, 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
if !state.constants.is_switch {
|
||||
batch.add_rect((state.canvas_size.0 - 320.0) / 2.0, offset_y, &Rect::new_size(0, 0, 320, 88));
|
||||
} else {
|
||||
batch.add_rect((state.canvas_size.0 - 427.0) / 2.0, offset_y, &Rect::new_size(0, 0, 427, 88));
|
||||
}
|
||||
|
||||
for x in ((-offset_x / 2)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 88.0, &Rect::new_size(0, 88, 320, 35));
|
||||
}
|
||||
|
||||
for x in ((-offset_x % 320)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 123.0, &Rect::new_size(0, 123, 320, 23));
|
||||
}
|
||||
|
||||
for x in ((-offset_x * 2)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 146.0, &Rect::new_size(0, 146, 320, 30));
|
||||
}
|
||||
|
||||
for x in ((-offset_x * 4)..(state.canvas_size.0 as i32)).step_by(320) {
|
||||
batch.add_rect(x as f32, offset_y + 176.0, &Rect::new_size(0, 176, 320, 64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
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;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
enum BossLifeTarget {
|
||||
None,
|
||||
NPC(u16),
|
||||
Boss,
|
||||
}
|
||||
|
||||
pub struct BossLifeBar {
|
||||
target: BossLifeTarget,
|
||||
life: u16,
|
||||
max_life: u16,
|
||||
prev_life: u16,
|
||||
counter: u16,
|
||||
}
|
||||
|
||||
impl BossLifeBar {
|
||||
pub fn new() -> BossLifeBar {
|
||||
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) {
|
||||
if let Some(npc) = npc_list.get_npc(npc_id as usize) {
|
||||
self.target = BossLifeTarget::NPC(npc.id);
|
||||
self.life = npc.life;
|
||||
self.max_life = self.life;
|
||||
self.prev_life = self.life;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_boss_target(&mut self, boss: &BossNPC) {
|
||||
self.target = BossLifeTarget::Boss;
|
||||
self.life = boss.parts[0].life;
|
||||
self.max_life = self.life;
|
||||
self.prev_life = self.life;
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, (npc_list, boss): (&NPCList, &BossNPC)) -> GameResult<()> {
|
||||
match self.target {
|
||||
BossLifeTarget::NPC(npc_id) => {
|
||||
if let Some(npc) = npc_list.get_npc(npc_id as usize) {
|
||||
self.life = npc.life;
|
||||
}
|
||||
}
|
||||
BossLifeTarget::Boss => {
|
||||
self.life = boss.parts[0].life;
|
||||
}
|
||||
_ => {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if self.life == 0 {
|
||||
self.target = BossLifeTarget::None;
|
||||
} else if self.prev_life > self.life {
|
||||
self.counter += 1;
|
||||
if self.counter > 30 {
|
||||
self.prev_life = self.prev_life.saturating_sub(1);
|
||||
}
|
||||
} else {
|
||||
self.counter = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult<()> {
|
||||
if self.max_life == 0 || self.target == BossLifeTarget::None {
|
||||
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(())
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
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;
|
||||
|
||||
pub struct Credits {}
|
||||
|
||||
impl Credits {
|
||||
pub fn new() -> Credits {
|
||||
Credits {}
|
||||
}
|
||||
|
||||
pub fn draw_tick(&mut self, state: &mut SharedGameState) {
|
||||
match state.textscript_vm.illustration_state {
|
||||
IllustrationState::FadeIn(mut x) => {
|
||||
x += 40.0 * state.frame_time as f32;
|
||||
|
||||
state.textscript_vm.illustration_state =
|
||||
if x >= 0.0 { IllustrationState::Shown } else { IllustrationState::FadeIn(x) };
|
||||
}
|
||||
IllustrationState::FadeOut(mut x) => {
|
||||
x -= 40.0 * state.frame_time as f32;
|
||||
|
||||
state.textscript_vm.illustration_state =
|
||||
if x <= -160.0 { IllustrationState::Hidden } else { IllustrationState::FadeOut(x) };
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for Credits {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let rect = Rect::new(0, 0, (state.screen_size.0 / 2.0) as _, state.screen_size.1 as _);
|
||||
graphics::draw_rect(ctx, rect, Color::from_rgb(0, 0, 32))?;
|
||||
|
||||
if state.textscript_vm.illustration_state != IllustrationState::Hidden {
|
||||
let x = match state.textscript_vm.illustration_state {
|
||||
IllustrationState::FadeIn(x) | IllustrationState::FadeOut(x) => x,
|
||||
_ => 0.0,
|
||||
};
|
||||
|
||||
if let Some(tex) = &state.textscript_vm.current_illustration {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex)?;
|
||||
batch.add(x, 0.0);
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
if state.creditscript_vm.lines.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Casts")?;
|
||||
for line in &state.creditscript_vm.lines {
|
||||
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);
|
||||
}
|
||||
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,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
use crate::common::Rect;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum Alignment {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
pub fn draw_number(x: f32, y: f32, val: usize, align: Alignment, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let n = val.to_string();
|
||||
let align_offset = if align == Alignment::Right { n.len() as f32 * 8.0 } else { 0.0 };
|
||||
|
||||
for (offset, chr) in n.chars().enumerate() {
|
||||
let idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(x - align_offset + offset as f32 * 8.0, y, &Rect::new_size(idx * 8, 56, 8, 8));
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_number_zeros(x: f32, y: f32, val: usize, align: Alignment, zeros: usize, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let n = format!("{:01$}", val, zeros);
|
||||
let align_offset = if align == Alignment::Right { n.len() as f32 * 8.0 } else { 0.0 };
|
||||
|
||||
for (offset, chr) in n.chars().enumerate() {
|
||||
let idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(x - align_offset + offset as f32 * 8.0, y, &Rect::new_size(idx * 8, 56, 8, 8));
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
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;
|
||||
|
||||
pub struct Fade;
|
||||
|
||||
impl Fade {
|
||||
pub fn new() -> Self {
|
||||
Fade
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for Fade {
|
||||
fn tick(&mut self, state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
let fade_ticks = state.constants.textscript.fade_ticks;
|
||||
match state.fade_state {
|
||||
FadeState::FadeOut(tick, direction) if tick < fade_ticks => {
|
||||
state.fade_state = FadeState::FadeOut(tick + 1, direction);
|
||||
}
|
||||
FadeState::FadeOut(tick, _) if tick == fade_ticks => {
|
||||
state.fade_state = FadeState::Hidden;
|
||||
}
|
||||
FadeState::FadeIn(tick, direction) if tick > -fade_ticks => {
|
||||
state.fade_state = FadeState::FadeIn(tick - 1, direction);
|
||||
}
|
||||
FadeState::FadeIn(tick, _) if tick == -fade_ticks => {
|
||||
state.fade_state = FadeState::Visible;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
match state.fade_state {
|
||||
FadeState::Visible => {
|
||||
return Ok(());
|
||||
}
|
||||
FadeState::Hidden => {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Fade")?;
|
||||
let mut rect = Rect::new(0, 0, 16, 16);
|
||||
let frame = 15;
|
||||
rect.left = frame * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
for x in 0..(state.canvas_size.0 as i32 / 16 + 1) {
|
||||
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
|
||||
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
FadeState::FadeIn(tick, direction) | FadeState::FadeOut(tick, direction) => {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Fade")?;
|
||||
let mut rect = Rect::new(0, 0, 16, 16);
|
||||
|
||||
match direction {
|
||||
FadeDirection::Left | FadeDirection::Right => {
|
||||
let mut frame = tick;
|
||||
|
||||
for x in 0..(state.canvas_size.0 as i32 / 16 + 1) {
|
||||
if frame >= 15 {
|
||||
frame = 15;
|
||||
} else {
|
||||
frame += 1;
|
||||
}
|
||||
|
||||
if frame >= 0 {
|
||||
rect.left = frame.abs() as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
|
||||
if direction == FadeDirection::Left {
|
||||
batch.add_rect(
|
||||
state.canvas_size.0 - x as f32 * 16.0 - 16.0,
|
||||
y as f32 * 16.0,
|
||||
&rect,
|
||||
);
|
||||
} else {
|
||||
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FadeDirection::Up | FadeDirection::Down => {
|
||||
let mut frame = tick;
|
||||
|
||||
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
|
||||
if frame >= 15 {
|
||||
frame = 15;
|
||||
} else {
|
||||
frame += 1;
|
||||
}
|
||||
|
||||
if frame >= 0 {
|
||||
rect.left = frame.abs() as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
for x in 0..(state.canvas_size.0 as i32 / 16 + 1) {
|
||||
if direction == FadeDirection::Down {
|
||||
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
|
||||
} else {
|
||||
batch.add_rect(x as f32 * 16.0, state.canvas_size.1 - y as f32 * 16.0, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FadeDirection::Center => {
|
||||
let center_x = (state.canvas_size.0 / 2.0 - 8.0) as i32;
|
||||
let center_y = (state.canvas_size.1 / 2.0 - 8.0) as i32;
|
||||
let mut start_frame = tick;
|
||||
|
||||
for x in 0..(center_x / 16 + 2) {
|
||||
let mut frame = start_frame;
|
||||
|
||||
for y in 0..(center_y / 16 + 2) {
|
||||
if frame >= 15 {
|
||||
frame = 15;
|
||||
} else {
|
||||
frame += 1;
|
||||
}
|
||||
|
||||
if frame >= 0 {
|
||||
rect.left = frame.abs() as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
|
||||
batch.add_rect((center_x - x * 16) as f32, (center_y + y * 16) as f32, &rect);
|
||||
batch.add_rect((center_x - x * 16) as f32, (center_y - y * 16) as f32, &rect);
|
||||
batch.add_rect((center_x + x * 16) as f32, (center_y + y * 16) as f32, &rect);
|
||||
batch.add_rect((center_x + x * 16) as f32, (center_y - y * 16) as f32, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
start_frame += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
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;
|
||||
|
||||
pub struct FallingIsland {}
|
||||
|
||||
impl FallingIsland {
|
||||
pub fn new() -> FallingIsland {
|
||||
FallingIsland {}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for FallingIsland {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let (pos_x, pos_y) =
|
||||
if let TextScriptExecutionState::FallingIsland(_, _, pos_x, pos_y, _, _) = state.textscript_vm.state {
|
||||
(pos_x, pos_y)
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let off_x = (state.canvas_size.0 - 320.0) * 0.5;
|
||||
let clip_rect: Rect = Rect::new_size(
|
||||
((off_x + 80.0) * state.scale) as _,
|
||||
(80.0 * state.scale) as _,
|
||||
(160.0 * state.scale) as _,
|
||||
(80.0 * state.scale) as _,
|
||||
);
|
||||
|
||||
graphics::clear(ctx, Color::from_rgb(0, 0, 32));
|
||||
graphics::set_clip_rect(ctx, Some(clip_rect))?;
|
||||
|
||||
static RECT_BG: Rect<u16> = Rect { left: 0, top: 0, right: 160, bottom: 80 };
|
||||
static RECT_ISLAND: Rect<u16> = Rect { left: 160, top: 0, right: 200, bottom: 24 };
|
||||
static RECT_TERRAIN: Rect<u16> = Rect { left: 160, top: 48, right: 320, bottom: 80 };
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(
|
||||
ctx,
|
||||
&state.constants,
|
||||
&state.npc_table.stage_textures.deref().borrow().npc1,
|
||||
)?;
|
||||
batch.add_rect(off_x + 80.0, 80.0, &RECT_BG);
|
||||
batch.add_rect(off_x + (pos_x as f32 / 512.0) - 20.0, (pos_y as f32 / 512.0) - 12.0, &RECT_ISLAND);
|
||||
batch.add_rect(off_x + 80.0, 128.0, &RECT_TERRAIN);
|
||||
batch.draw(ctx)?;
|
||||
|
||||
graphics::set_clip_rect(ctx, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
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;
|
||||
|
||||
pub enum FlashState {
|
||||
None,
|
||||
Cross(i32, i32, u16),
|
||||
Blink(u16),
|
||||
}
|
||||
|
||||
pub struct Flash {
|
||||
state: FlashState,
|
||||
}
|
||||
|
||||
impl Flash {
|
||||
pub fn new() -> Flash {
|
||||
Flash {
|
||||
state: FlashState::None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cross(&mut self, x: i32, y: i32) {
|
||||
self.state = FlashState::Cross(x, y, 0);
|
||||
}
|
||||
|
||||
pub fn set_blink(&mut self) {
|
||||
self.state = FlashState::Blink(0);
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
self.state = FlashState::None;
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for Flash {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult<()> {
|
||||
match self.state {
|
||||
FlashState::None => {}
|
||||
FlashState::Cross(x, y, tick) => {
|
||||
self.state = if tick > 128 {
|
||||
FlashState::None
|
||||
} else {
|
||||
FlashState::Cross(x, y, tick + 1)
|
||||
};
|
||||
}
|
||||
FlashState::Blink(tick) => {
|
||||
self.state = if tick > 20 {
|
||||
FlashState::None
|
||||
} else {
|
||||
FlashState::Blink(tick + 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> {
|
||||
const WHITE: Color = Color::new(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
match self.state {
|
||||
FlashState::None => {}
|
||||
FlashState::Cross(x, y, tick) => {
|
||||
let tick = tick as f32 + state.frame_time as f32;
|
||||
let frame_pos = frame.xy_interpolated(state.frame_time);
|
||||
|
||||
let (cen_x, cen_y) = (
|
||||
(x as f32 / 512.0) - frame_pos.0,
|
||||
(y as f32 / 512.0) - frame_pos.1
|
||||
);
|
||||
|
||||
let width = if tick > 100.0 {
|
||||
(1.0 - (tick - 100.0).max(0.0) / 28.0).powf(2.0) * state.canvas_size.0
|
||||
} else {
|
||||
(1.0 - (0.97f32).powf(tick)).max(0.0) * state.canvas_size.0
|
||||
};
|
||||
|
||||
|
||||
let mut rect = Rect {
|
||||
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
|
||||
};
|
||||
|
||||
graphics::draw_rect(ctx, rect, WHITE)?;
|
||||
|
||||
if tick <= 100.0 {
|
||||
rect = Rect {
|
||||
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
|
||||
};
|
||||
|
||||
graphics::draw_rect(ctx, rect, WHITE)?;
|
||||
}
|
||||
}
|
||||
FlashState::Blink(tick) => {
|
||||
if tick / 2 % 2 != 0 {
|
||||
graphics::clear(ctx, WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,310 +0,0 @@
|
|||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
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;
|
||||
|
||||
pub struct HUD {
|
||||
pub alignment: Alignment,
|
||||
pub weapon_x_pos: usize,
|
||||
pub visible: bool,
|
||||
pub has_player2: bool,
|
||||
ammo: u16,
|
||||
max_ammo: u16,
|
||||
xp: u16,
|
||||
max_xp: u16,
|
||||
xp_bar_counter: u8,
|
||||
max_level: bool,
|
||||
life: u16,
|
||||
max_life: u16,
|
||||
life_bar: u16,
|
||||
life_bar_counter: u16,
|
||||
air: u16,
|
||||
air_counter: u16,
|
||||
current_level: usize,
|
||||
weapon_count: usize,
|
||||
current_weapon: isize,
|
||||
weapon_types: [u8; 16],
|
||||
shock: bool,
|
||||
}
|
||||
|
||||
impl HUD {
|
||||
pub fn new(alignment: Alignment) -> HUD {
|
||||
HUD {
|
||||
alignment,
|
||||
weapon_x_pos: 16,
|
||||
visible: false,
|
||||
has_player2: false,
|
||||
ammo: 0,
|
||||
max_ammo: 0,
|
||||
xp: 0,
|
||||
max_xp: 0,
|
||||
xp_bar_counter: 0,
|
||||
max_level: false,
|
||||
life: 0,
|
||||
max_life: 0,
|
||||
life_bar: 0,
|
||||
life_bar_counter: 0,
|
||||
air: 0,
|
||||
air_counter: 0,
|
||||
current_level: 0,
|
||||
weapon_count: 0,
|
||||
current_weapon: 0,
|
||||
weapon_types: [0; 16],
|
||||
shock: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (player, inventory): (&Player, &mut Inventory)) -> GameResult {
|
||||
let (ammo, max_ammo) = inventory.get_current_ammo();
|
||||
let (xp, max_xp, max_level) = inventory.get_current_max_exp(&state.constants);
|
||||
|
||||
self.ammo = ammo;
|
||||
self.max_ammo = max_ammo;
|
||||
self.xp = xp;
|
||||
self.max_xp = max_xp;
|
||||
self.xp_bar_counter = if player.xp_counter != 0 { self.xp_bar_counter.wrapping_add(1) } else { 0 };
|
||||
self.max_level = max_level;
|
||||
|
||||
self.life = player.life;
|
||||
self.max_life = player.max_life;
|
||||
self.air = player.air;
|
||||
self.air_counter = player.air_counter;
|
||||
self.shock = player.shock_counter / 2 % 2 != 0;
|
||||
self.weapon_count = inventory.get_weapon_count();
|
||||
self.current_weapon = inventory.get_current_weapon_idx() as isize;
|
||||
|
||||
self.current_level = inventory.get_current_level() as usize;
|
||||
|
||||
for (a, slot) in self.weapon_types.iter_mut().enumerate() {
|
||||
*slot = if let Some(weapon) = inventory.get_weapon(a) { weapon.wtype as u8 } else { 0 };
|
||||
}
|
||||
|
||||
// update health bar
|
||||
if self.life_bar < self.life as u16 {
|
||||
self.life_bar = self.life as u16;
|
||||
}
|
||||
|
||||
if self.life_bar > self.life as u16 {
|
||||
self.life_bar_counter += 1;
|
||||
if self.life_bar_counter > 30 {
|
||||
self.life_bar -= 1;
|
||||
}
|
||||
} else {
|
||||
self.life_bar_counter = 0;
|
||||
}
|
||||
|
||||
if self.weapon_x_pos > 16 {
|
||||
self.weapon_x_pos -= 2;
|
||||
} else if self.weapon_x_pos < 16 {
|
||||
self.weapon_x_pos += 2;
|
||||
}
|
||||
|
||||
if player.cond.alive() {
|
||||
if player.controller.trigger_next_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.next_weapon();
|
||||
self.weapon_x_pos = 32;
|
||||
}
|
||||
|
||||
if player.controller.trigger_prev_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.prev_weapon();
|
||||
self.weapon_x_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// touch handler
|
||||
if state.settings.touch_controls && self.weapon_count != 0 {
|
||||
let mut rect;
|
||||
let weapon_offset = match self.alignment {
|
||||
Alignment::Left => 0,
|
||||
Alignment::Right => (state.canvas_size.0 - 104.0) as isize,
|
||||
};
|
||||
|
||||
for a in 0..self.weapon_count {
|
||||
let mut pos_x = ((a as isize - self.current_weapon) * 16) + self.weapon_x_pos as isize;
|
||||
|
||||
if pos_x < 8 {
|
||||
pos_x += 48 + self.weapon_count as isize * 16;
|
||||
} else if pos_x >= 24 {
|
||||
pos_x += 48;
|
||||
}
|
||||
|
||||
if pos_x >= 72 + ((self.weapon_count as isize - 1) * 16) {
|
||||
pos_x -= 48 + self.weapon_count as isize * 16;
|
||||
} else if pos_x < 72 && pos_x >= 24 {
|
||||
pos_x -= 48;
|
||||
}
|
||||
|
||||
let wtype = self.weapon_types[a];
|
||||
if wtype != 0 {
|
||||
rect = Rect::new_size(pos_x + weapon_offset - 4, 16 + (4 * state.scale as isize), 24, 24);
|
||||
|
||||
if state.touch_controls.consume_click_in(rect) {
|
||||
state.sound_manager.play_sfx(4);
|
||||
inventory.current_weapon = a as u16;
|
||||
self.weapon_x_pos = 32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !self.visible {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (left, top, right, bottom) = screen_insets_scaled(ctx, state.scale);
|
||||
|
||||
// none
|
||||
let weap_x = self.weapon_x_pos as f32;
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let (bar_offset, num_offset, weapon_offset) = match self.alignment {
|
||||
Alignment::Left => (left, left, left),
|
||||
Alignment::Right => (
|
||||
state.canvas_size.0 - 112.0 - right,
|
||||
state.canvas_size.0 - 48.0 - right,
|
||||
state.canvas_size.0 - 40.0 - right,
|
||||
),
|
||||
};
|
||||
let air_offset = if self.has_player2 {
|
||||
50.0 * match self.alignment {
|
||||
Alignment::Left => -1.0,
|
||||
Alignment::Right => 1.0,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
if self.max_ammo == 0 {
|
||||
batch.add_rect(bar_offset + weap_x + 48.0, 16.0 + top, &Rect::new_size(80, 48, 16, 8));
|
||||
batch.add_rect(bar_offset + weap_x + 48.0, 24.0 + top, &Rect::new_size(80, 48, 16, 8));
|
||||
}
|
||||
|
||||
if !self.shock {
|
||||
// per
|
||||
batch.add_rect(bar_offset + weap_x + 32.0, 24.0 + top, &Rect::new_size(72, 48, 8, 8));
|
||||
// lv
|
||||
batch.add_rect(num_offset + weap_x, 32.0 + top, &Rect::new_size(80, 80, 16, 8));
|
||||
// xp box
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(0, 72, 40, 8));
|
||||
|
||||
if self.max_level {
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(40, 72, 40, 8));
|
||||
} else if self.max_xp > 0 {
|
||||
// xp bar
|
||||
let bar_width = (self.xp as f32 / self.max_xp as f32 * 40.0) as u16;
|
||||
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(0, 80, bar_width, 8));
|
||||
}
|
||||
|
||||
if (self.xp_bar_counter & 0x02) != 0 {
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(40, 80, 40, 8));
|
||||
}
|
||||
|
||||
if self.max_life != 0 {
|
||||
let yellow_bar_width = (self.life_bar as f32 / self.max_life as f32 * 39.0) as u16;
|
||||
let bar_width = (self.life as f32 / self.max_life as f32 * 39.0) as u16;
|
||||
|
||||
// heart/hp number box
|
||||
batch.add_rect(num_offset + 16.0, 40.0 + top, &Rect::new_size(0, 40, 24, 8));
|
||||
// life box
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(24, 40, 40, 8));
|
||||
// yellow bar
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(0, 32, yellow_bar_width, 8));
|
||||
// life
|
||||
batch.add_rect(bar_offset + 40.0, 40.0 + top, &Rect::new_size(0, 24, bar_width, 8));
|
||||
}
|
||||
}
|
||||
|
||||
if self.air_counter > 0 {
|
||||
let rect = if self.air % 30 > 10 { Rect::new_size(112, 72, 32, 8) } else { Rect::new_size(112, 80, 32, 8) };
|
||||
|
||||
batch.add_rect(
|
||||
left + ((state.canvas_size.0 - left - right) / 2.0).floor() - 40.0 + air_offset,
|
||||
top + ((state.canvas_size.1 - top - bottom) / 2.0).floor(),
|
||||
&rect,
|
||||
);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||
|
||||
if self.weapon_count != 0 {
|
||||
let mut rect = Rect::new(0, 0, 0, 16);
|
||||
|
||||
// First frame of animation is off by one weapon
|
||||
// There's probably a more elegant solution than this
|
||||
let first_frame_offset = if self.weapon_x_pos == 32 {
|
||||
-1
|
||||
} else if self.weapon_x_pos == 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
for a in 0..self.weapon_count {
|
||||
let mut pos_x = ((a as isize - self.current_weapon + first_frame_offset) as f32 * 16.0) + weap_x;
|
||||
|
||||
if pos_x < 8.0 {
|
||||
pos_x += 48.0 + self.weapon_count as f32 * 16.0;
|
||||
} else if pos_x >= 24.0 {
|
||||
pos_x += 48.0;
|
||||
}
|
||||
|
||||
if pos_x >= 72.0 + ((self.weapon_count - 1) as f32 * 16.0) {
|
||||
pos_x -= 48.0 + self.weapon_count as f32 * 16.0;
|
||||
} else if pos_x < 72.0 && pos_x >= 24.0 {
|
||||
pos_x -= 48.0;
|
||||
}
|
||||
|
||||
if self.alignment == Alignment::Right && pos_x > 32.0 {
|
||||
pos_x -= 96.0 + self.weapon_count as f32 * 16.0;
|
||||
}
|
||||
|
||||
let wtype = self.weapon_types[a];
|
||||
if wtype != 0 {
|
||||
rect.left = wtype as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
batch.add_rect(pos_x + weapon_offset, 16.0 + top, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
if self.air_counter > 0 && self.air_counter % 6 < 4 {
|
||||
draw_number(
|
||||
left + ((state.canvas_size.0 - left - right) / 2.0).floor() + 8.0 + air_offset,
|
||||
top + ((state.canvas_size.1 - top - bottom) / 2.0).floor(),
|
||||
(self.air / 10) as usize,
|
||||
Alignment::Left,
|
||||
state,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
if self.max_ammo != 0 {
|
||||
draw_number(bar_offset + weap_x + 64.0, 16.0 + top, self.ammo as usize, Alignment::Right, state, ctx)?;
|
||||
draw_number(bar_offset + weap_x + 64.0, 24.0 + top, self.max_ammo as usize, Alignment::Right, state, ctx)?;
|
||||
}
|
||||
if !self.shock {
|
||||
draw_number(num_offset + weap_x + 24.0, 32.0 + top, self.current_level, Alignment::Right, state, ctx)?;
|
||||
draw_number(num_offset + 40.0, 40.0 + top, self.life_bar as usize, Alignment::Right, state, ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,429 +0,0 @@
|
|||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
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};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
enum InventoryFocus {
|
||||
None,
|
||||
Weapons,
|
||||
Items,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct InvWeaponData {
|
||||
wtype: WeaponType,
|
||||
level: WeaponLevel,
|
||||
ammo: u16,
|
||||
max_ammo: u16,
|
||||
}
|
||||
|
||||
pub struct InventoryUI {
|
||||
tick: usize,
|
||||
text_y_pos: u16,
|
||||
selected_weapon: u16,
|
||||
selected_item: u16,
|
||||
weapon_count: u16,
|
||||
item_count: u16,
|
||||
weapon_data: [InvWeaponData; 8],
|
||||
item_data: [(u16, u16); 32],
|
||||
focus: InventoryFocus,
|
||||
}
|
||||
|
||||
impl InventoryUI {
|
||||
pub fn new() -> InventoryUI {
|
||||
InventoryUI {
|
||||
text_y_pos: 16,
|
||||
tick: 0,
|
||||
selected_weapon: 0,
|
||||
selected_item: 0,
|
||||
weapon_count: 0,
|
||||
item_count: 0,
|
||||
weapon_data: [InvWeaponData { wtype: WeaponType::None, level: WeaponLevel::None, ammo: 0, max_ammo: 0 }; 8],
|
||||
item_data: [(0u16, 0u16); 32],
|
||||
focus: InventoryFocus::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_item_event_number(&self, inventory: &Inventory) -> u16 {
|
||||
inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 5000).unwrap_or(5000)
|
||||
}
|
||||
|
||||
fn get_item_event_number_action(&self, inventory: &Inventory) -> u16 {
|
||||
inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 6000).unwrap_or(6000)
|
||||
}
|
||||
|
||||
fn exit(&mut self, state: &mut SharedGameState, _player: &mut Player, inventory: &mut Inventory) {
|
||||
self.focus = InventoryFocus::None;
|
||||
inventory.current_item = 0;
|
||||
self.text_y_pos = 16;
|
||||
state.textscript_vm.reset();
|
||||
state.textscript_vm.set_mode(ScriptMode::Map);
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|
||||
fn tick(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
(ctx, player, inventory): (&mut Context, &mut Player, &mut Inventory),
|
||||
) -> GameResult<()> {
|
||||
let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
let mut slot_rect =
|
||||
Rect::new_size(state.canvas_size.0 as isize - 34 - off_right as isize, 8 + off_top as isize, 26, 26);
|
||||
|
||||
state.touch_controls.control_type = TouchControlType::None;
|
||||
|
||||
if state.control_flags.control_enabled()
|
||||
&& (player.controller.trigger_inventory()
|
||||
|| player.controller.trigger_menu_back()
|
||||
|| (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);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.text_y_pos > 8 {
|
||||
self.text_y_pos -= 1;
|
||||
}
|
||||
|
||||
self.weapon_count = 0;
|
||||
for (idx, weapon) in self.weapon_data.iter_mut().enumerate() {
|
||||
if let Some(weapon_data) = inventory.get_weapon(idx) {
|
||||
weapon.wtype = weapon_data.wtype;
|
||||
weapon.level = weapon_data.level;
|
||||
weapon.ammo = weapon_data.ammo;
|
||||
weapon.max_ammo = weapon_data.max_ammo;
|
||||
|
||||
self.weapon_count += 1;
|
||||
} else {
|
||||
weapon.wtype = WeaponType::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.item_count = 0;
|
||||
for (idx, (item_id, amount)) in self.item_data.iter_mut().enumerate() {
|
||||
if let Some(item_data) = inventory.get_item_idx(idx) {
|
||||
*item_id = item_data.0;
|
||||
*amount = item_data.1;
|
||||
self.item_count += 1;
|
||||
} else {
|
||||
*item_id = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_weapon_event_number(inventory: &Inventory) -> u16 {
|
||||
inventory.get_current_weapon().map(|w| w.wtype as u16 + 1000).unwrap_or(1000)
|
||||
}
|
||||
|
||||
self.selected_item = inventory.current_item;
|
||||
self.selected_weapon = inventory.current_weapon;
|
||||
|
||||
let count_x = state.constants.textscript.inventory_item_count_x as u16;
|
||||
|
||||
match self.focus {
|
||||
InventoryFocus::None => {
|
||||
self.focus = InventoryFocus::Weapons;
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
}
|
||||
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(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(get_weapon_event_number(inventory));
|
||||
}
|
||||
|
||||
if player.controller.trigger_up() || player.controller.trigger_down() {
|
||||
self.focus = InventoryFocus::Items;
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
}
|
||||
InventoryFocus::Items if self.item_count != 0 && state.control_flags.control_enabled() => {
|
||||
if player.controller.trigger_left() {
|
||||
state.sound_manager.play_sfx(1);
|
||||
|
||||
if (self.selected_item % count_x) != 0 {
|
||||
self.selected_item -= 1;
|
||||
} else {
|
||||
self.selected_item += count_x - 1;
|
||||
}
|
||||
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
|
||||
if player.controller.trigger_right() {
|
||||
match () {
|
||||
_ if self.selected_item == self.item_count + 1 => {
|
||||
self.selected_item = count_x * (self.selected_item / count_x);
|
||||
}
|
||||
_ if (self.selected_item % count_x) + 1 == count_x => {
|
||||
self.selected_item = self.selected_item.saturating_sub(count_x) + 1;
|
||||
}
|
||||
_ => self.selected_item += 1,
|
||||
}
|
||||
|
||||
state.sound_manager.play_sfx(1);
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
|
||||
if player.controller.trigger_up() {
|
||||
if self.selected_item < count_x {
|
||||
self.focus = InventoryFocus::Weapons;
|
||||
|
||||
state.sound_manager.play_sfx(4);
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
} else {
|
||||
self.selected_item -= count_x;
|
||||
|
||||
state.sound_manager.play_sfx(1);
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
}
|
||||
|
||||
if player.controller.trigger_down() {
|
||||
if self.selected_item / 6 == self.item_count.saturating_sub(1) / 6 {
|
||||
self.focus = InventoryFocus::Weapons;
|
||||
|
||||
state.sound_manager.play_sfx(4);
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
} else {
|
||||
self.selected_item += count_x;
|
||||
|
||||
state.sound_manager.play_sfx(1);
|
||||
state.control_flags.set_ok_button_disabled(false);
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
}
|
||||
|
||||
if !state.control_flags.ok_button_disabled() && player.controller.trigger_menu_ok() {
|
||||
state.textscript_vm.start_script(self.get_item_event_number_action(inventory));
|
||||
}
|
||||
|
||||
self.selected_item = self.selected_item.min(self.item_count - 1);
|
||||
inventory.current_item = self.selected_item;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if state.settings.touch_controls && state.control_flags.control_enabled() {
|
||||
let x = ((((state.canvas_size.0 - off_left - off_right) - 244.0) / 2.0).floor() + off_left) as isize;
|
||||
let y = 8 + off_top as isize;
|
||||
|
||||
for i in 0..self.weapon_count {
|
||||
slot_rect = Rect::new_size(x + 12 + i as isize * 40, y + 16, 40, 40);
|
||||
|
||||
if state.touch_controls.consume_click_in(slot_rect) {
|
||||
self.focus = InventoryFocus::Weapons;
|
||||
state.sound_manager.play_sfx(4);
|
||||
self.selected_weapon = i;
|
||||
inventory.current_weapon = i;
|
||||
state.textscript_vm.start_script(get_weapon_event_number(inventory));
|
||||
self.exit(state, player, inventory);
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..self.item_count {
|
||||
slot_rect =
|
||||
Rect::new_size(x + 12 + (i % count_x) as isize * 32, y + 68 + (i / count_x) as isize * 16, 32, 16);
|
||||
|
||||
if state.touch_controls.consume_click_in(slot_rect) {
|
||||
state.sound_manager.play_sfx(1);
|
||||
|
||||
if self.focus == InventoryFocus::Items && inventory.current_item == i {
|
||||
state.textscript_vm.start_script(self.get_item_event_number_action(inventory));
|
||||
} else {
|
||||
self.selected_item = i;
|
||||
inventory.current_item = self.selected_item;
|
||||
self.focus = InventoryFocus::Items;
|
||||
state.textscript_vm.start_script(self.get_item_event_number(inventory));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult<()> {
|
||||
if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut tmp_rect = Rect { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
let x = (((state.canvas_size.0 - off_left - off_right) - 244.0) / 2.0).floor() + off_left;
|
||||
let y = 8.0 + off_top;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
for i in 0..=18 {
|
||||
let rect = match i {
|
||||
0 => &state.constants.textscript.inventory_rect_top,
|
||||
18 => &state.constants.textscript.inventory_rect_bottom,
|
||||
_ => &state.constants.textscript.inventory_rect_middle,
|
||||
};
|
||||
|
||||
batch.add_rect(x, y + i as f32 * 8.0, rect);
|
||||
}
|
||||
|
||||
batch.add_rect(x + 12.0, y + self.text_y_pos as f32, &state.constants.textscript.inventory_text_arms);
|
||||
|
||||
batch.add_rect(x + 12.0, y + 52.0 + self.text_y_pos as f32, &state.constants.textscript.inventory_text_item);
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
batch.add_rect(
|
||||
x + 12.0 + self.selected_weapon as f32 * 40.0,
|
||||
y + 16.0,
|
||||
&state.constants.textscript.cursor_inventory_weapon[weapon_cursor_frame],
|
||||
);
|
||||
|
||||
let count_x = state.constants.textscript.inventory_item_count_x as usize;
|
||||
batch.add_rect(
|
||||
x + 12.0 + (self.selected_item as usize % count_x) as f32 * 32.0,
|
||||
y + 68.0 + (self.selected_item as usize / count_x) as f32 * 16.0,
|
||||
&state.constants.textscript.cursor_inventory_item[item_cursor_frame],
|
||||
);
|
||||
|
||||
for (idx, weapon) in self.weapon_data.iter().enumerate() {
|
||||
if weapon.wtype == WeaponType::None {
|
||||
break;
|
||||
}
|
||||
|
||||
// lv
|
||||
batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 32.0, &Rect::new_size(80, 80, 16, 8));
|
||||
// per
|
||||
batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 48.0, &Rect::new_size(72, 48, 8, 8));
|
||||
|
||||
if weapon.max_ammo == 0 {
|
||||
batch.add_rect(x + 28.0 + idx as f32 * 40.0, y + 40.0, &Rect::new_size(80, 48, 16, 8));
|
||||
batch.add_rect(x + 28.0 + idx as f32 * 40.0, y + 48.0, &Rect::new_size(80, 48, 16, 8));
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||
for (idx, weapon) in self.weapon_data.iter().enumerate() {
|
||||
if weapon.wtype == WeaponType::None {
|
||||
break;
|
||||
}
|
||||
|
||||
tmp_rect.left = (weapon.wtype as u16 % 16) * 16;
|
||||
tmp_rect.top = (weapon.wtype as u16 / 16) * 16;
|
||||
tmp_rect.right = tmp_rect.left + 16;
|
||||
tmp_rect.bottom = tmp_rect.top + 16;
|
||||
|
||||
batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 16.0, &tmp_rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ItemImage")?;
|
||||
|
||||
for (idx, (item_id, _amount)) in self.item_data.iter().enumerate() {
|
||||
if *item_id == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
tmp_rect.left = (*item_id % 8) * 32;
|
||||
tmp_rect.top = (*item_id / 8) * 16;
|
||||
tmp_rect.right = tmp_rect.left + 32;
|
||||
tmp_rect.bottom = tmp_rect.top + 16;
|
||||
|
||||
batch.add_rect(
|
||||
x + 12.0 + (idx % count_x) as f32 * 32.0,
|
||||
y + 68.0 + (idx / count_x) as f32 * 16.0,
|
||||
&tmp_rect,
|
||||
);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
for (idx, (item_id, amount)) in self.item_data.iter().enumerate() {
|
||||
if *item_id == 0 || *amount == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if *amount > 1 {
|
||||
draw_number(
|
||||
x + 12.0 + (idx % count_x) as f32 * 32.0 + 32.0,
|
||||
y + 68.0 + (idx / count_x) as f32 * 16.0,
|
||||
*amount as usize,
|
||||
Alignment::Right,
|
||||
state,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, weapon) in self.weapon_data.iter().enumerate() {
|
||||
if weapon.wtype == WeaponType::None {
|
||||
break;
|
||||
}
|
||||
|
||||
draw_number(x + 44.0 + idx as f32 * 40.0, y + 32.0, weapon.level as usize, Alignment::Right, state, ctx)?;
|
||||
|
||||
if weapon.max_ammo != 0 {
|
||||
draw_number(
|
||||
x + 44.0 + idx as f32 * 40.0,
|
||||
y + 40.0,
|
||||
weapon.ammo as usize,
|
||||
Alignment::Right,
|
||||
state,
|
||||
ctx,
|
||||
)?;
|
||||
draw_number(
|
||||
x + 44.0 + idx as f32 * 40.0,
|
||||
y + 48.0,
|
||||
weapon.max_ammo as usize,
|
||||
Alignment::Right,
|
||||
state,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if state.settings.touch_controls {
|
||||
let close_rect = Rect { left: 110, top: 110, right: 128, bottom: 128 };
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/touch")?;
|
||||
|
||||
batch.add_rect(state.canvas_size.0 - off_right - 30.0, 12.0 + off_top, &close_rect);
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
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::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 {
|
||||
Hidden,
|
||||
FadeInBox(u16),
|
||||
FadeInLine(u16),
|
||||
Visible,
|
||||
FadeOutBox(u16),
|
||||
}
|
||||
|
||||
pub struct MapSystem {
|
||||
texture: RefCell<Option<Box<dyn BackendTexture>>>,
|
||||
has_map_data: RefCell<bool>,
|
||||
last_size: (u16, u16),
|
||||
tick: u16,
|
||||
state: MapSystemState,
|
||||
}
|
||||
|
||||
impl MapSystem {
|
||||
pub fn new() -> MapSystem {
|
||||
MapSystem {
|
||||
texture: RefCell::new(None),
|
||||
has_map_data: RefCell::new(false),
|
||||
last_size: (0, 0),
|
||||
tick: 0,
|
||||
state: MapSystemState::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_map(&self, state: &mut SharedGameState, ctx: &mut Context, stage: &Stage) -> GameResult {
|
||||
if self.texture.borrow().is_none() {
|
||||
*self.has_map_data.borrow_mut() = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
*self.has_map_data.borrow_mut() = true;
|
||||
|
||||
graphics::set_render_target(ctx, self.texture.borrow().as_ref())?;
|
||||
graphics::clear(ctx, Color::new(0.0, 0.0, 0.0, 1.0));
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
for y in 0..stage.map.height {
|
||||
for x in 0..stage.map.width {
|
||||
const RECTS: [Rect<u16>; 4] = [
|
||||
Rect { left: 240, top: 24, right: 241, bottom: 25 },
|
||||
Rect { left: 241, top: 24, right: 242, bottom: 25 },
|
||||
Rect { left: 242, top: 24, right: 243, bottom: 25 },
|
||||
Rect { left: 243, top: 24, right: 244, bottom: 25 },
|
||||
];
|
||||
|
||||
let attr = stage.map.get_attribute(x as _, y as _);
|
||||
|
||||
let layer = match attr {
|
||||
0 => 0,
|
||||
0x01 | 0x02 | 0x40 | 0x44 | 0x51 | 0x52 | 0x55 | 0x56 | 0x60 | 0x71 | 0x72 | 0x75 | 0x76 | 0x80
|
||||
| 0x81 | 0x82 | 0x83 | 0xA0 | 0xA1 | 0xA2 | 0xA3 => 1,
|
||||
0x43 | 0x50 | 0x53 | 0x54 | 0x57 | 0x63 | 0x70 | 0x73 | 0x74 | 0x77 => 2,
|
||||
_ => 3,
|
||||
};
|
||||
|
||||
batch.add_rect(x as _, y as _, &RECTS[layer]);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
graphics::set_render_target(ctx, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
stage: &Stage,
|
||||
players: [&Player; 2],
|
||||
) -> GameResult {
|
||||
if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
|
||||
if self.state == MapSystemState::Hidden {
|
||||
state.control_flags.set_control_enabled(false);
|
||||
self.state = MapSystemState::FadeInBox(0);
|
||||
}
|
||||
} else {
|
||||
self.state = MapSystemState::Hidden;
|
||||
}
|
||||
|
||||
if self.state == MapSystemState::Hidden {
|
||||
self.tick = 0;
|
||||
*self.has_map_data.borrow_mut() = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
|
||||
let width = (stage.map.width as f32 * state.scale) as u16;
|
||||
let height = (stage.map.height as f32 * state.scale) as u16;
|
||||
|
||||
if self.last_size != (width, height) {
|
||||
self.last_size = (width, height);
|
||||
*self.texture.borrow_mut() = graphics::create_texture_mutable(ctx, width, height).ok();
|
||||
*self.has_map_data.borrow_mut() = false;
|
||||
}
|
||||
|
||||
match self.state {
|
||||
MapSystemState::FadeInBox(tick) => {
|
||||
if tick >= 8 {
|
||||
self.state = MapSystemState::FadeInLine(0);
|
||||
} else {
|
||||
self.state = MapSystemState::FadeInBox(tick + 1);
|
||||
}
|
||||
}
|
||||
MapSystemState::FadeOutBox(tick) => {
|
||||
if tick == 0 {
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_control_enabled(true);
|
||||
state.textscript_vm.state = TextScriptExecutionState::Ended;
|
||||
self.state = MapSystemState::Hidden;
|
||||
} else {
|
||||
self.state = MapSystemState::FadeOutBox(tick - 1);
|
||||
}
|
||||
}
|
||||
MapSystemState::FadeInLine(tick) => {
|
||||
if (tick + 2) < stage.map.height {
|
||||
self.state = MapSystemState::FadeInLine(tick + 2);
|
||||
} else {
|
||||
self.state = MapSystemState::Visible;
|
||||
}
|
||||
|
||||
for player in &players {
|
||||
if player.controller.trigger_jump() || player.controller.trigger_shoot() {
|
||||
self.state = MapSystemState::FadeOutBox(8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
MapSystemState::Visible => {
|
||||
for player in &players {
|
||||
if player.controller.trigger_jump() || player.controller.trigger_shoot() {
|
||||
self.state = MapSystemState::FadeOutBox(8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
stage: &Stage,
|
||||
players: [&Player; 2],
|
||||
) -> GameResult {
|
||||
if self.state == MapSystemState::Hidden {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !*self.has_map_data.borrow() {
|
||||
self.render_map(state, ctx, stage)?;
|
||||
}
|
||||
|
||||
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 rect_black_bar = Rect::new_size(
|
||||
0,
|
||||
(7.0 * state.scale) as _,
|
||||
state.screen_size.0 as _,
|
||||
((text_height + 4.0) * state.scale) as _,
|
||||
);
|
||||
|
||||
if !state.constants.is_switch {
|
||||
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()
|
||||
} else {
|
||||
stage.data.name.chars()
|
||||
};
|
||||
|
||||
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)?;
|
||||
|
||||
let mut map_rect = Rect::new(0.0, 0.0, self.last_size.0 as f32, self.last_size.1 as f32);
|
||||
|
||||
match self.state {
|
||||
MapSystemState::FadeInBox(tick) | MapSystemState::FadeOutBox(tick) => {
|
||||
let width = (state.scale * tick as f32 * stage.map.width as f32 / 16.0) as isize;
|
||||
let height = (state.scale * tick as f32 * stage.map.height as f32 / 16.0) as isize;
|
||||
|
||||
let rect = Rect::new_size(
|
||||
(scr_w / 2.0) as isize - width,
|
||||
(scr_h / 2.0) as isize - height,
|
||||
width * 2,
|
||||
height * 2,
|
||||
);
|
||||
|
||||
graphics::draw_rect(ctx, rect, Color::new(0.0, 0.0, 0.0, 1.0))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
MapSystemState::FadeInLine(line) => {
|
||||
map_rect.bottom = state.scale * (line as f32 + 1.0);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let width_border = state.scale * (stage.map.width as f32 + 2.0);
|
||||
let height_border = state.scale * (stage.map.height as f32 + 2.0);
|
||||
|
||||
let rect = Rect::new_size(
|
||||
((scr_w - width_border) / 2.0) as isize,
|
||||
((scr_h - height_border) / 2.0) as isize,
|
||||
width_border as isize,
|
||||
height_border as isize,
|
||||
);
|
||||
|
||||
graphics::draw_rect(ctx, rect, Color::new(0.0, 0.0, 0.0, 1.0))?;
|
||||
|
||||
if let Some(tex) = self.texture.borrow_mut().as_mut() {
|
||||
let width = state.scale * stage.map.width as f32;
|
||||
let height = state.scale * stage.map.height as f32;
|
||||
|
||||
tex.clear();
|
||||
tex.add(SpriteBatchCommand::DrawRect(
|
||||
map_rect,
|
||||
Rect::new_size((scr_w - width) / 2.0, (scr_h - height) / 2.0, map_rect.width(), map_rect.height()),
|
||||
));
|
||||
tex.draw()?;
|
||||
}
|
||||
|
||||
if (self.tick & 8) != 0 {
|
||||
const PLAYER_RECT: Rect<u16> = Rect { left: 0, top: 57, right: 1, bottom: 58 };
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
let x_offset = (state.canvas_size.0 - stage.map.width as f32) / 2.0;
|
||||
let y_offset = (state.canvas_size.1 - stage.map.height as f32) / 2.0;
|
||||
let tile_div = stage.map.tile_size.as_int() * 0x200;
|
||||
|
||||
for player in &players {
|
||||
if !player.cond.alive() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let plr_x = x_offset + (player.x / tile_div) as f32;
|
||||
let plr_y = y_offset + (player.y / tile_div) as f32;
|
||||
|
||||
batch.add_rect(plr_x, plr_y, &PLAYER_RECT);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
pub mod background;
|
||||
pub mod boss_life_bar;
|
||||
pub mod credits;
|
||||
pub mod draw_common;
|
||||
pub mod fade;
|
||||
pub mod falling_island;
|
||||
pub mod flash;
|
||||
pub mod hud;
|
||||
pub mod inventory;
|
||||
pub mod map_system;
|
||||
pub mod nikumaru;
|
||||
pub mod number_popup;
|
||||
pub mod replay;
|
||||
pub mod stage_select;
|
||||
pub mod text_boxes;
|
||||
pub mod tilemap;
|
||||
pub mod water_renderer;
|
||||
pub mod whimsical_star;
|
|
@ -1,166 +0,0 @@
|
|||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{draw_number, draw_number_zeros, Alignment};
|
||||
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};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NikumaruCounter {
|
||||
pub tick: usize,
|
||||
pub shown: bool,
|
||||
}
|
||||
|
||||
impl NikumaruCounter {
|
||||
pub fn new() -> NikumaruCounter {
|
||||
NikumaruCounter { tick: 0, shown: false }
|
||||
}
|
||||
|
||||
fn load_time(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<u32> {
|
||||
if let Ok(mut data) = filesystem::user_open(ctx, [state.get_rec_filename(), ".rec".to_string()].join("")) {
|
||||
let mut ticks: [u32; 4] = [0; 4];
|
||||
|
||||
for iter in 0..=3 {
|
||||
ticks[iter] = data.read_u32::<LE>()?;
|
||||
}
|
||||
|
||||
let random = data.read_u32::<LE>()?;
|
||||
let random_list: [u8; 4] = random.to_le_bytes();
|
||||
|
||||
for iter in 0..=3 {
|
||||
ticks[iter] = u32::from_le_bytes([
|
||||
ticks[iter].to_le_bytes()[0].wrapping_sub(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[1].wrapping_sub(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[2].wrapping_sub(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[3].wrapping_sub(random_list[iter] / 2),
|
||||
]);
|
||||
}
|
||||
|
||||
if ticks[0] == ticks[1] && ticks[0] == ticks[2] {
|
||||
return Ok(ticks[0]);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Failed to open 290 record.");
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn save_time(&mut self, new_time: u32, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut data) = filesystem::open_options(
|
||||
ctx,
|
||||
[state.get_rec_filename(), ".rec".to_string()].join(""),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
let mut ticks: [u32; 4] = [new_time; 4];
|
||||
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;
|
||||
|
||||
ticks[iter] = u32::from_le_bytes([
|
||||
ticks[iter].to_le_bytes()[0].wrapping_add(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[1].wrapping_add(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[2].wrapping_add(random_list[iter]),
|
||||
ticks[iter].to_le_bytes()[3].wrapping_add(random_list[iter] / 2),
|
||||
]);
|
||||
|
||||
data.write_u32::<LE>(ticks[iter])?;
|
||||
}
|
||||
|
||||
data.write_u32::<LE>(u32::from_le_bytes(random_list))?;
|
||||
} else {
|
||||
log::warn!("Failed to write 290 record.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_counter(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.tick = self.load_time(state, ctx)? as usize;
|
||||
if self.tick > 0 {
|
||||
self.shown = true;
|
||||
} else {
|
||||
self.shown = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_counter(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<bool> {
|
||||
let old_record = self.load_time(state, ctx)? as usize;
|
||||
if self.tick < old_record || old_record == 0 {
|
||||
self.save_time(self.tick as u32, state, ctx)?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<&Player> for NikumaruCounter {
|
||||
fn tick(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
if !player.equip.has_nikumaru() {
|
||||
self.tick = 0;
|
||||
self.shown = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.shown = true;
|
||||
|
||||
if state.control_flags.control_enabled() {
|
||||
self.tick += 1;
|
||||
}
|
||||
|
||||
if self.tick >= 300000 {
|
||||
self.tick = 300000;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !self.shown {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let x = 16.0;
|
||||
let y = 8.0;
|
||||
|
||||
const CLOCK_RECTS: [Rect<u16>; 2] = [
|
||||
Rect { left: 112, top: 104, right: 120, bottom: 112 },
|
||||
Rect { left: 120, top: 104, right: 128, bottom: 112 },
|
||||
];
|
||||
const PRIME: Rect<u16> = Rect { left: 128, top: 104, right: 160, bottom: 112 };
|
||||
|
||||
let (one_tenth, second, minute) = match state.settings.timing_mode {
|
||||
TimingMode::_60Hz => (6, 60, 3600),
|
||||
_ => (5, 50, 3000),
|
||||
};
|
||||
|
||||
if self.tick % 30 <= 10 {
|
||||
batch.add_rect(x, y, &CLOCK_RECTS[1]);
|
||||
} else {
|
||||
batch.add_rect(x, y, &CLOCK_RECTS[0]);
|
||||
}
|
||||
batch.add_rect(x + 30.0, y, &PRIME);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
draw_number(x + 32.0, y, self.tick / minute, Alignment::Right, state, ctx)?;
|
||||
draw_number_zeros(x + 52.0, y, (self.tick / second) % 60, Alignment::Right, 2, state, ctx)?;
|
||||
draw_number(x + 64.0, y, (self.tick / one_tenth) % 10, Alignment::Right, state, ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
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;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct NumberPopup {
|
||||
pub value: i16,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
counter: u16,
|
||||
}
|
||||
|
||||
impl NumberPopup {
|
||||
pub fn new() -> NumberPopup {
|
||||
NumberPopup { value: 0, x: 0, y: 0, prev_x: 0, prev_y: 0, counter: 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);
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for NumberPopup {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult<()> {
|
||||
if self.value == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.counter += 1;
|
||||
if self.counter == 80 {
|
||||
self.counter = 0;
|
||||
self.value = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> {
|
||||
if self.value == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// tick 0 - 32 - move up by 0.5 pixels
|
||||
// tick 33 - 72 - stay
|
||||
// tick 73 - 80 - fade up
|
||||
let y_offset = self.counter.min(32) as f32 * 0.5;
|
||||
let clip = self.counter.max(72) - 72;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
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 n = format!("{:+}", self.value);
|
||||
|
||||
let x = x - n.len() as f32 * 4.0;
|
||||
|
||||
for (offset, chr) in n.chars().enumerate() {
|
||||
match chr {
|
||||
'+' => {
|
||||
batch.add_rect(x + offset as f32 * 8.0, y, &Rect::new_size(32, 48 + clip, 8, 8 - clip));
|
||||
}
|
||||
'-' => {
|
||||
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 idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(
|
||||
x + offset as f32 * 8.0,
|
||||
y,
|
||||
&Rect::new_size(idx * 8, number_set + clip, 8, 8 - clip),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
use std::io::{Cursor, Read};
|
||||
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||
|
||||
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::input::replay_player_controller::{KeyState, ReplayController};
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::{ReplayState, SharedGameState};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Replay {
|
||||
replay_version: u16,
|
||||
keylist: Vec<u16>,
|
||||
last_input: KeyState,
|
||||
rng_seed: u64,
|
||||
pub controller: ReplayController,
|
||||
tick: usize,
|
||||
resume_tick: usize,
|
||||
}
|
||||
|
||||
impl Replay {
|
||||
pub fn new() -> Replay {
|
||||
Replay {
|
||||
replay_version: 0,
|
||||
keylist: Vec::new(),
|
||||
last_input: KeyState(0),
|
||||
rng_seed: 0,
|
||||
controller: ReplayController::new(),
|
||||
tick: 0,
|
||||
resume_tick: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_recording(&mut self, state: &mut SharedGameState) {
|
||||
self.rng_seed = state.game_rng.dump_state();
|
||||
}
|
||||
|
||||
pub fn stop_recording(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
state.replay_state = ReplayState::None;
|
||||
self.write_replay(state, ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_playback(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
state.replay_state = ReplayState::Playback;
|
||||
self.read_replay(state, ctx)?;
|
||||
state.game_rng.load_state(self.rng_seed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_replay(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(mut file) = filesystem::open_options(
|
||||
ctx,
|
||||
[state.get_rec_filename(), ".rep".to_string()].join(""),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
file.write_u16::<LE>(0)?; // Space for versioning replay files
|
||||
file.write_u64::<LE>(self.rng_seed)?;
|
||||
for input in &self.keylist {
|
||||
file.write_u16::<LE>(*input)?;
|
||||
}
|
||||
}
|
||||
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("")) {
|
||||
self.replay_version = file.read_u16::<LE>()?;
|
||||
self.rng_seed = file.read_u64::<LE>()?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
|
||||
let count = data.len() / 2;
|
||||
let mut inputs = Vec::new();
|
||||
let mut f = Cursor::new(data);
|
||||
|
||||
for _ in 0..count {
|
||||
inputs.push(f.read_u16::<LE>()?);
|
||||
}
|
||||
|
||||
self.keylist = inputs;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&mut Context, &mut Player)> for Replay {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (ctx, player): (&mut Context, &mut Player)) -> GameResult {
|
||||
match state.replay_state {
|
||||
ReplayState::Recording => {
|
||||
// This mimics the KeyState bitfield
|
||||
let inputs = player.controller.move_left() as u16
|
||||
+ ((player.controller.move_right() as u16) << 1)
|
||||
+ ((player.controller.move_up() as u16) << 2)
|
||||
+ ((player.controller.move_down() as u16) << 3)
|
||||
+ ((player.controller.trigger_map() as u16) << 4)
|
||||
+ ((player.controller.trigger_inventory() as u16) << 5)
|
||||
+ (((player.controller.jump() || player.controller.trigger_menu_ok()) as u16) << 6)
|
||||
+ (((player.controller.shoot() || player.controller.trigger_menu_back()) as u16) << 7)
|
||||
+ ((player.controller.next_weapon() as u16) << 8)
|
||||
+ ((player.controller.prev_weapon() as u16) << 9)
|
||||
+ ((player.controller.trigger_menu_ok() as u16) << 11)
|
||||
+ ((player.controller.skip() as u16) << 12)
|
||||
+ ((player.controller.strafe() as u16) << 13);
|
||||
|
||||
self.keylist.push(inputs);
|
||||
}
|
||||
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) };
|
||||
|
||||
self.controller.state = KeyState(next_input);
|
||||
self.controller.old_state = self.last_input;
|
||||
player.controller = Box::new(self.controller);
|
||||
|
||||
if !pause {
|
||||
self.last_input = KeyState(next_input);
|
||||
self.tick += 1;
|
||||
} else {
|
||||
self.resume_tick = self.tick;
|
||||
};
|
||||
|
||||
if self.tick >= self.keylist.len() {
|
||||
state.replay_state = ReplayState::None;
|
||||
player.controller = state.settings.create_player1_controller();
|
||||
}
|
||||
}
|
||||
ReplayState::None => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let x = state.canvas_size.0 - 32.0;
|
||||
let y = 8.0 + if state.settings.fps_counter { 12.0 } else { 0.0 };
|
||||
|
||||
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::Recording => {
|
||||
state.font.draw_text_with_shadow("REC".chars(), x, y, &state.constants, &mut state.texture_set, ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::scripting::tsc::text_script::ScriptMode;
|
||||
|
||||
pub struct StageSelect {
|
||||
pub current_teleport_slot: u8,
|
||||
prev_teleport_slot: u8,
|
||||
stage_select_text_y_pos: usize,
|
||||
tick: usize,
|
||||
}
|
||||
|
||||
impl StageSelect {
|
||||
pub fn new() -> StageSelect {
|
||||
StageSelect {
|
||||
current_teleport_slot: 0,
|
||||
prev_teleport_slot: 0,
|
||||
stage_select_text_y_pos: 54,
|
||||
tick: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.stage_select_text_y_pos = 54;
|
||||
self.tick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&mut Context, &Player, &Player)> for StageSelect {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (ctx, player1, player2): (&mut Context, &Player, &Player)) -> GameResult {
|
||||
state.touch_controls.control_type = TouchControlType::None;
|
||||
|
||||
let slot_count = state.teleporter_slots.iter()
|
||||
.filter(|&&(index, _event_num)| index != 0)
|
||||
.count();
|
||||
|
||||
if slot_count <= self.current_teleport_slot as usize {
|
||||
self.current_teleport_slot = 0;
|
||||
}
|
||||
|
||||
if self.stage_select_text_y_pos > 46 {
|
||||
self.stage_select_text_y_pos -= 1;
|
||||
}
|
||||
|
||||
let left_pressed = player1.controller.trigger_left() || player2.controller.trigger_left();
|
||||
let right_pressed = player1.controller.trigger_right() || player2.controller.trigger_right();
|
||||
let mut ok_pressed = player1.controller.trigger_jump() || player1.controller.trigger_menu_ok()
|
||||
|| player2.controller.trigger_jump() || player2.controller.trigger_menu_ok();
|
||||
let mut cancel_pressed = player1.controller.trigger_shoot() || player2.controller.trigger_shoot();
|
||||
|
||||
if left_pressed {
|
||||
if self.current_teleport_slot == 0 {
|
||||
self.current_teleport_slot = slot_count.saturating_sub(1) as u8;
|
||||
} else {
|
||||
self.current_teleport_slot -= 1;
|
||||
}
|
||||
} else if right_pressed {
|
||||
if self.current_teleport_slot == slot_count.saturating_sub(1) as u8 {
|
||||
self.current_teleport_slot = 0;
|
||||
} else {
|
||||
self.current_teleport_slot += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.prev_teleport_slot != self.current_teleport_slot {
|
||||
self.prev_teleport_slot = self.current_teleport_slot;
|
||||
state.sound_manager.play_sfx(1);
|
||||
if let Some(&(index, _event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) {
|
||||
state.textscript_vm.start_script(1000 + index);
|
||||
} else {
|
||||
state.textscript_vm.start_script(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if state.settings.touch_controls {
|
||||
let slot_offset = ((state.canvas_size.0 - 40.0 * slot_count as f32) / 2.0).floor();
|
||||
let mut slot_rect;
|
||||
|
||||
for i in 0..slot_count {
|
||||
slot_rect = Rect::new_size(slot_offset as isize + i as isize * 40 - 2, 64 - 8, 36, 32);
|
||||
|
||||
if state.touch_controls.consume_click_in(slot_rect) {
|
||||
if self.current_teleport_slot as usize == i {
|
||||
ok_pressed = true;
|
||||
} else {
|
||||
state.sound_manager.play_sfx(1);
|
||||
self.current_teleport_slot = i as u8;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let (_, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
slot_rect = Rect::new_size(state.canvas_size.0 as isize - 34 - off_right as isize, 8 + off_top as isize, 26, 26);
|
||||
|
||||
if state.touch_controls.consume_click_in(slot_rect) {
|
||||
state.sound_manager.play_sfx(5);
|
||||
cancel_pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ok_pressed || cancel_pressed {
|
||||
self.reset();
|
||||
state.textscript_vm.set_mode(ScriptMode::Map);
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_control_enabled(true);
|
||||
state.control_flags.set_interactions_disabled(false);
|
||||
|
||||
if ok_pressed {
|
||||
if let Some(&(_index, event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) {
|
||||
state.textscript_vm.start_script(event_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.tick += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "StageImage")?;
|
||||
|
||||
let slot_count = state.teleporter_slots.iter()
|
||||
.filter(|&&(index, _event_num)| index != 0)
|
||||
.count();
|
||||
let slot_offset = ((state.canvas_size.0 - 40.0 * slot_count as f32) / 2.0).floor();
|
||||
let mut slot_rect = Rect::new(0, 0, 0, 0);
|
||||
|
||||
for i in 0..slot_count {
|
||||
let index = state.teleporter_slots[i].0;
|
||||
|
||||
slot_rect.left = 32 * (index as u16 % 8);
|
||||
slot_rect.top = 16 * (index as u16 / 8);
|
||||
slot_rect.right = slot_rect.left + 32;
|
||||
slot_rect.bottom = slot_rect.top + 16;
|
||||
|
||||
batch.add_rect(slot_offset + i as f32 * 40.0, 64.0, &slot_rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
batch.add_rect((state.canvas_size.0 / 2.0) - 32.0, self.stage_select_text_y_pos as f32, &state.constants.textscript.stage_select_text);
|
||||
if slot_count > 0 {
|
||||
batch.add_rect(slot_offset + self.current_teleport_slot as f32 * 40.0, 64.0, &state.constants.textscript.cursor[self.tick / 2 % 2]);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
if state.settings.touch_controls {
|
||||
let (_, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
|
||||
let close_rect = Rect { left: 110, top: 110, right: 128, bottom: 128 };
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/touch")?;
|
||||
|
||||
batch.add_rect(state.canvas_size.0 - off_right - 30.0, 12.0 + off_top, &close_rect);
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,279 +0,0 @@
|
|||
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;
|
||||
|
||||
pub struct TextBoxes {
|
||||
pub slide_in: u8,
|
||||
pub anim_counter: usize,
|
||||
animated_face: AnimatedFace,
|
||||
}
|
||||
|
||||
const FACE_TEX: &str = "Face";
|
||||
const SWITCH_FACE_TEX: [&str; 5] = ["Face1", "Face2", "Face3", "Face4", "Face5"];
|
||||
|
||||
impl TextBoxes {
|
||||
pub fn new() -> TextBoxes {
|
||||
TextBoxes {
|
||||
slide_in: 7,
|
||||
anim_counter: 0,
|
||||
animated_face: AnimatedFace { face_id: 0, anim_id: 0, anim_frames: vec![(0, 0)] },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for TextBoxes {
|
||||
fn tick(&mut self, state: &mut SharedGameState, _custom: ()) -> GameResult {
|
||||
if state.textscript_vm.face != 0 {
|
||||
self.slide_in = self.slide_in.saturating_sub(1);
|
||||
self.anim_counter = self.anim_counter.wrapping_add(1);
|
||||
|
||||
let face_num = state.textscript_vm.face % 100;
|
||||
let animation = state.textscript_vm.face % 1000 / 100;
|
||||
|
||||
if state.constants.textscript.animated_face_pics
|
||||
&& (self.animated_face.anim_id != animation || self.animated_face.face_id != face_num)
|
||||
{
|
||||
self.animated_face = state
|
||||
.constants
|
||||
.animated_face_table
|
||||
.clone()
|
||||
.into_iter()
|
||||
.find(|face| face.face_id == face_num && face.anim_id == animation)
|
||||
.unwrap_or_else(|| AnimatedFace { face_id: face_num, anim_id: 0, anim_frames: vec![(0, 0)] });
|
||||
}
|
||||
|
||||
if self.anim_counter > self.animated_face.anim_frames.first().unwrap().1 as usize {
|
||||
self.animated_face.anim_frames.rotate_left(1);
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !state.textscript_vm.flags.render() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (off_left, off_top, off_right, off_bottom) =
|
||||
crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
|
||||
|
||||
let center = ((state.canvas_size.0 - off_left - off_right) / 2.0).floor();
|
||||
let top_pos = if state.textscript_vm.flags.position_top() {
|
||||
32.0 + off_top
|
||||
} else {
|
||||
state.canvas_size.1 as f32 - off_bottom - 66.0
|
||||
};
|
||||
let left_pos = off_left + center - 122.0;
|
||||
|
||||
{
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
if state.textscript_vm.flags.background_visible() {
|
||||
batch.add_rect(left_pos, top_pos, &state.constants.textscript.textbox_rect_top);
|
||||
for i in 1..7 {
|
||||
batch.add_rect(left_pos, top_pos + i as f32 * 8.0, &state.constants.textscript.textbox_rect_middle);
|
||||
}
|
||||
batch.add_rect(left_pos, top_pos + 56.0, &state.constants.textscript.textbox_rect_bottom);
|
||||
}
|
||||
|
||||
if state.textscript_vm.item != 0 {
|
||||
batch.add_rect(
|
||||
center - 40.0,
|
||||
state.canvas_size.1 - off_bottom - 112.0,
|
||||
&state.constants.textscript.get_item_top_left,
|
||||
);
|
||||
batch.add_rect(
|
||||
center - 40.0,
|
||||
state.canvas_size.1 - off_bottom - 96.0,
|
||||
&state.constants.textscript.get_item_bottom_left,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 112.0,
|
||||
&state.constants.textscript.get_item_top_right,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 104.0,
|
||||
&state.constants.textscript.get_item_right,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 96.0,
|
||||
&state.constants.textscript.get_item_right,
|
||||
);
|
||||
batch.add_rect(
|
||||
center + 32.0,
|
||||
state.canvas_size.1 - off_bottom - 88.0,
|
||||
&state.constants.textscript.get_item_bottom_right,
|
||||
);
|
||||
}
|
||||
|
||||
if let TextScriptExecutionState::WaitConfirmation(_, _, _, wait, selection) = state.textscript_vm.state {
|
||||
let pos_y = if wait > 14 {
|
||||
state.canvas_size.1 - off_bottom - 96.0 + 4.0 * (17 - wait) as f32
|
||||
} else {
|
||||
state.canvas_size.1 - off_bottom - 96.0
|
||||
};
|
||||
|
||||
batch.add_rect(center + 56.0, pos_y, &state.constants.textscript.textbox_rect_yes_no);
|
||||
|
||||
if wait == 0 {
|
||||
let pos_x = if selection == ConfirmSelection::No { 41.0 } else { 0.0 };
|
||||
|
||||
batch.add_rect(
|
||||
center + 51.0 + pos_x,
|
||||
pos_y + 10.0,
|
||||
&state.constants.textscript.textbox_rect_cursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
if state.textscript_vm.face != 0 {
|
||||
let clip_rect = Rect::new_size(
|
||||
((left_pos + 14.0) * state.scale) as isize,
|
||||
((top_pos + 8.0) * state.scale) as isize,
|
||||
(48.0 * state.scale) as isize,
|
||||
(48.0 * state.scale) as isize,
|
||||
);
|
||||
|
||||
graphics::set_clip_rect(ctx, Some(clip_rect))?;
|
||||
|
||||
// switch version uses 1xxx flag to show a flipped version of face
|
||||
let flip = state.textscript_vm.face > 1000;
|
||||
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 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),
|
||||
);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
graphics::set_clip_rect(ctx, None)?;
|
||||
}
|
||||
|
||||
if state.textscript_vm.item != 0 {
|
||||
let mut rect = Rect::new(0, 0, 0, 0);
|
||||
|
||||
if state.textscript_vm.item < 1000 {
|
||||
let item_id = state.textscript_vm.item as u16;
|
||||
|
||||
rect.left = (item_id % 16) * 16;
|
||||
rect.right = rect.left + 16;
|
||||
rect.top = (item_id / 16) * 16;
|
||||
rect.bottom = rect.top + 16;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||
batch.add_rect((center - 12.0).floor(), state.canvas_size.1 - off_bottom - 104.0, &rect);
|
||||
batch.draw(ctx)?;
|
||||
} else {
|
||||
let item_id = state.textscript_vm.item as u16 - 1000;
|
||||
|
||||
rect.left = (item_id % 8) * 32;
|
||||
rect.right = rect.left + 32;
|
||||
rect.top = (item_id / 8) * 16;
|
||||
rect.bottom = rect.top + 16;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ItemImage")?;
|
||||
batch.add_rect((center - 20.0).floor(), state.canvas_size.1 - off_bottom - 104.0, &rect);
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
let text_offset = if state.textscript_vm.face == 0 { 0.0 } else { 56.0 };
|
||||
|
||||
let y_offset = if let TextScriptExecutionState::MsgNewLine(_, _, _, _, counter) = state.textscript_vm.state {
|
||||
16.0 - counter as f32 * 4.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let lines = [&state.textscript_vm.line_1, &state.textscript_vm.line_2, &state.textscript_vm.line_3];
|
||||
|
||||
let clip_rect = Rect::new_size(
|
||||
0,
|
||||
((top_pos + 6.0) * state.scale) as isize,
|
||||
state.screen_size.0 as isize,
|
||||
(48.0 * state.scale) as isize,
|
||||
);
|
||||
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
graphics::set_clip_rect(ctx, None)?;
|
||||
|
||||
if let TextScriptExecutionState::WaitInput(_, _, tick) = state.textscript_vm.state {
|
||||
if tick > 10 {
|
||||
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,
|
||||
),
|
||||
};
|
||||
x += left_pos + text_offset + 14.0;
|
||||
|
||||
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,
|
||||
),
|
||||
Color::from_rgb(255, 255, 255),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
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};
|
||||
|
||||
pub struct Tilemap {
|
||||
tick: u32,
|
||||
prev_tick: u32,
|
||||
pub no_water: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TileLayer {
|
||||
Background,
|
||||
Middleground,
|
||||
Foreground,
|
||||
Snack,
|
||||
}
|
||||
|
||||
impl Tilemap {
|
||||
pub fn new() -> Self {
|
||||
Tilemap { tick: 0, prev_tick: 0, no_water: false }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> GameResult {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_prev(&mut self) -> GameResult {
|
||||
self.prev_tick = self.tick;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
frame: &Frame,
|
||||
layer: TileLayer,
|
||||
textures: &StageTexturePaths,
|
||||
stage: &Stage,
|
||||
) -> GameResult {
|
||||
if stage.map.tile_size == TileSize::Tile8x8 && layer == TileLayer::Snack {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tex = match layer {
|
||||
TileLayer::Snack => "Npc/NpcSym",
|
||||
TileLayer::Background => &textures.tileset_bg,
|
||||
TileLayer::Middleground => &textures.tileset_mg,
|
||||
TileLayer::Foreground => &textures.tileset_fg,
|
||||
};
|
||||
|
||||
let (layer_offset, layer_width, layer_height, uses_layers) = if let Some(pxpack_data) = &stage.data.pxpack_data
|
||||
{
|
||||
match layer {
|
||||
TileLayer::Background => {
|
||||
(pxpack_data.offset_bg as usize, pxpack_data.size_bg.0, pxpack_data.size_bg.1, true)
|
||||
}
|
||||
TileLayer::Middleground => {
|
||||
(pxpack_data.offset_mg as usize, pxpack_data.size_mg.0, pxpack_data.size_mg.1, true)
|
||||
}
|
||||
_ => (0, pxpack_data.size_fg.0, pxpack_data.size_fg.1, true),
|
||||
}
|
||||
} else {
|
||||
(0, stage.map.width, stage.map.height, false)
|
||||
};
|
||||
|
||||
if !uses_layers && layer == TileLayer::Middleground {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tile_size = state.tile_size.as_int();
|
||||
let tile_sizef = state.tile_size.as_float();
|
||||
let halft = tile_size / 2;
|
||||
let halftf = tile_sizef / 2.0;
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex)?;
|
||||
let mut rect = Rect::new(0, 0, tile_size as u16, tile_size as u16);
|
||||
let (mut frame_x, mut frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
|
||||
if let Some(pxpack_data) = &stage.data.pxpack_data {
|
||||
let (fx, fy) = match layer {
|
||||
TileLayer::Background => pxpack_data.scroll_bg.transform_camera_pos(frame_x, frame_y),
|
||||
TileLayer::Middleground => pxpack_data.scroll_mg.transform_camera_pos(frame_x, frame_y),
|
||||
_ => pxpack_data.scroll_fg.transform_camera_pos(frame_x, frame_y),
|
||||
};
|
||||
|
||||
frame_x = fx;
|
||||
frame_y = fy;
|
||||
}
|
||||
|
||||
let tile_start_x = (frame_x as i32 / tile_size).clamp(0, layer_width as i32) as usize;
|
||||
let tile_start_y = (frame_y as i32 / tile_size).clamp(0, layer_height as i32) as usize;
|
||||
let tile_end_x =
|
||||
((frame_x as i32 + 8 + state.canvas_size.0 as i32) / tile_size + 1).clamp(0, layer_width as i32) as usize;
|
||||
let tile_end_y = ((frame_y as i32 + halft + state.canvas_size.1 as i32) / tile_size + 1)
|
||||
.clamp(0, layer_height as i32) as usize;
|
||||
|
||||
if layer == TileLayer::Snack {
|
||||
rect = state.constants.world.snack_rect;
|
||||
}
|
||||
|
||||
for y in tile_start_y..tile_end_y {
|
||||
for x in tile_start_x..tile_end_x {
|
||||
let tile = *stage.map.tiles.get((y * layer_width as usize) + x + layer_offset).unwrap();
|
||||
match layer {
|
||||
_ if uses_layers => {
|
||||
if tile == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Background => {
|
||||
if stage.map.attrib[tile as usize] >= 0x20 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Foreground => {
|
||||
let attr = stage.map.attrib[tile as usize];
|
||||
|
||||
if attr < 0x40 || attr >= 0x80 || attr == 0x43 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_size = tile_size as u16;
|
||||
rect.left = (tile as u16 % 16) * tile_size;
|
||||
rect.top = (tile as u16 / 16) * tile_size;
|
||||
rect.right = rect.left + tile_size;
|
||||
rect.bottom = rect.top + tile_size;
|
||||
}
|
||||
TileLayer::Snack => {
|
||||
if stage.map.attrib[tile as usize] != 0x43 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
batch.add_rect(
|
||||
(x as f32 * tile_sizef - halftf) - frame_x,
|
||||
(y as f32 * tile_sizef - halftf) - frame_y,
|
||||
&rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
if !self.no_water && layer == TileLayer::Foreground && stage.data.background_type == BackgroundType::Water {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &textures.background)?;
|
||||
let rect_top = Rect { left: 0, top: 0, right: 32, bottom: 16 };
|
||||
let rect_middle = Rect { left: 0, top: 16, right: 32, bottom: 48 };
|
||||
|
||||
let tile_start_x = frame_x as i32 / 32;
|
||||
let tile_end_x = (frame_x + 16.0 + state.canvas_size.0) as i32 / 32 + 1;
|
||||
let water_y = state.water_level as f32 / 512.0;
|
||||
let tile_count_y = (frame_y + 16.0 + state.canvas_size.1 - water_y) as i32 / 32 + 1;
|
||||
|
||||
for x in tile_start_x..tile_end_x {
|
||||
batch.add_rect((x as f32 * 32.0) - frame_x, water_y - frame_y, &rect_top);
|
||||
|
||||
for y in 0..tile_count_y {
|
||||
batch.add_rect((x as f32 * 32.0) - frame_x, (y as f32 * 32.0) + water_y - frame_y, &rect_middle);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
if layer == TileLayer::Foreground {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?;
|
||||
|
||||
for y in tile_start_y..tile_end_y {
|
||||
for x in tile_start_x..tile_end_x {
|
||||
let tile = *stage.map.tiles.get((y * layer_width as usize) + x + layer_offset).unwrap();
|
||||
let attr = stage.map.attrib[tile as usize];
|
||||
|
||||
if ![0x80, 0x81, 0x82, 0x83, 0xA0, 0xA1, 0xA2, 0xA3].contains(&attr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let shift =
|
||||
((self.tick as f64 + (self.tick - self.prev_tick) as f64 * state.frame_time) * 2.0) as u16 % 16;
|
||||
let mut push_rect = state.constants.world.water_push_rect;
|
||||
|
||||
match attr {
|
||||
0x80 | 0xA0 => {
|
||||
push_rect.left = push_rect.left + shift;
|
||||
push_rect.right = push_rect.right + shift;
|
||||
}
|
||||
0x81 | 0xA1 => {
|
||||
push_rect.top = push_rect.top + shift;
|
||||
push_rect.bottom = push_rect.bottom + shift;
|
||||
}
|
||||
0x82 | 0xA2 => {
|
||||
push_rect.left = push_rect.left - shift + state.tile_size.as_int() as u16;
|
||||
push_rect.right = push_rect.right - shift + state.tile_size.as_int() as u16;
|
||||
}
|
||||
0x83 | 0xA3 => {
|
||||
push_rect.top = push_rect.top - shift + state.tile_size.as_int() as u16;
|
||||
push_rect.bottom = push_rect.bottom - shift + state.tile_size.as_int() as u16;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
batch.add_rect(
|
||||
(x as f32 * tile_sizef - halftf) - frame_x,
|
||||
(y as f32 * tile_sizef - halftf) - frame_y,
|
||||
&push_rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,359 +0,0 @@
|
|||
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};
|
||||
|
||||
const TENSION: f32 = 0.03;
|
||||
const DAMPENING: f32 = 0.01;
|
||||
const SPREAD: f32 = 0.02;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum WaterLayer {
|
||||
Front,
|
||||
Back,
|
||||
}
|
||||
|
||||
struct DynamicWaterColumn {
|
||||
target_height: f32,
|
||||
height: f32,
|
||||
speed: f32,
|
||||
}
|
||||
|
||||
impl DynamicWaterColumn {
|
||||
pub fn new() -> DynamicWaterColumn {
|
||||
DynamicWaterColumn { target_height: 8.0, height: 8.0, speed: 0.0 }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, dampening: f32, tension: f32) {
|
||||
self.speed += tension * (self.target_height - self.height) - self.speed * dampening;
|
||||
self.height += self.speed;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynamicWater {
|
||||
x: f32,
|
||||
y: f32,
|
||||
end_x: f32,
|
||||
columns: Vec<DynamicWaterColumn>,
|
||||
color: WaterParamEntry,
|
||||
}
|
||||
|
||||
impl DynamicWater {
|
||||
pub fn new(x: u16, y: u16, length: u16, color: WaterParamEntry) -> DynamicWater {
|
||||
let mut columns = Vec::new();
|
||||
let count = length as usize * 8 + 1;
|
||||
|
||||
for _ in 0..count {
|
||||
columns.push(DynamicWaterColumn::new());
|
||||
}
|
||||
|
||||
DynamicWater { x: x as f32 * 16.0, y: y as f32 * 16.0, end_x: (x + length) as f32 * 16.0, columns, color }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
for col in &mut self.columns {
|
||||
col.tick(DAMPENING, TENSION);
|
||||
}
|
||||
|
||||
static mut L_DELTAS: Vec<f32> = Vec::new();
|
||||
static mut R_DELTAS: Vec<f32> = Vec::new();
|
||||
|
||||
// we assume tick() is never called from other threads.
|
||||
unsafe {
|
||||
L_DELTAS.resize(self.columns.len(), 0.0);
|
||||
R_DELTAS.resize(self.columns.len(), 0.0);
|
||||
|
||||
for _ in 0..2 {
|
||||
for i in 0..self.columns.len() {
|
||||
if i > 0 {
|
||||
L_DELTAS[i] = SPREAD * (self.columns[i].height - self.columns[i - 1].height);
|
||||
self.columns[i - 1].speed += L_DELTAS[i];
|
||||
}
|
||||
|
||||
if i < self.columns.len() - 1 {
|
||||
R_DELTAS[i] = SPREAD * (self.columns[i].height - self.columns[i + 1].height);
|
||||
self.columns[i + 1].speed += R_DELTAS[i];
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..self.columns.len() {
|
||||
if i > 0 {
|
||||
self.columns[i - 1].height += L_DELTAS[i];
|
||||
}
|
||||
|
||||
if i < self.columns.len() - 1 {
|
||||
self.columns[i + 1].height += R_DELTAS[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interact(&mut self, players: &[&Player], npc_list: &NPCList) {
|
||||
let cols_i32 = self.columns.len() as i32;
|
||||
|
||||
let mut tick_object = |obj: &dyn PhysicalEntity| {
|
||||
let obj_x = obj.x() as f32 / 512.0 + 8.0;
|
||||
let obj_y = obj.y() as f32 / 512.0 + 8.0;
|
||||
|
||||
if (obj.vel_y() > 0x80 || obj.vel_y() < -0x80)
|
||||
&& obj_x > self.x
|
||||
&& obj_x < self.end_x as f32
|
||||
&& obj_y > self.y - 5.0
|
||||
&& obj_y < self.y + 4.0
|
||||
{
|
||||
let col_idx_center = (((obj_x - self.x) / 2.0) as i32).clamp(0, cols_i32);
|
||||
let col_idx_left =
|
||||
(col_idx_center - (obj.hit_bounds().left as i32 / (8 * 0x200))).clamp(0, cols_i32) as usize;
|
||||
let col_idx_right =
|
||||
(col_idx_center + (obj.hit_bounds().left as i32 / (8 * 0x200))).clamp(0, cols_i32) as usize;
|
||||
|
||||
for col in &mut self.columns[col_idx_left..=col_idx_right] {
|
||||
col.speed = (obj.vel_y() as f32 / 512.0) * (obj.hit_rect_size() as f32 * 0.25).clamp(0.1, 1.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for player in players {
|
||||
tick_object(*player);
|
||||
}
|
||||
|
||||
for npc in npc_list.iter_alive() {
|
||||
static NO_COLL_NPCS: [u16; 6] = [0, 3, 4, 18, 191, 195];
|
||||
if NO_COLL_NPCS.contains(&npc.npc_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tick_object(npc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DepthRegion {
|
||||
rect: Rect<f32>,
|
||||
color: WaterParamEntry,
|
||||
}
|
||||
|
||||
impl DepthRegion {
|
||||
pub fn new_tile(rect: Rect<u16>, color: WaterParamEntry) -> DepthRegion {
|
||||
DepthRegion {
|
||||
rect: Rect {
|
||||
left: rect.left as f32 * 16.0,
|
||||
top: rect.top as f32 * 16.0,
|
||||
right: rect.right as f32 * 16.0,
|
||||
bottom: rect.bottom as f32 * 16.0,
|
||||
},
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(rect: Rect<f32>, color: WaterParamEntry) -> DepthRegion {
|
||||
DepthRegion { rect, color }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaterRenderer {
|
||||
depth_regions: Vec<DepthRegion>,
|
||||
water_surfaces: Vec<DynamicWater>,
|
||||
core_water: Option<(DynamicWater, DepthRegion)>,
|
||||
t: RefCell<u32>,
|
||||
}
|
||||
|
||||
impl WaterRenderer {
|
||||
pub fn new() -> WaterRenderer {
|
||||
WaterRenderer { depth_regions: Vec::new(), water_surfaces: Vec::new(), core_water: None, t: RefCell::new(0) }
|
||||
}
|
||||
|
||||
pub fn initialize(
|
||||
&mut self,
|
||||
regions: Vec<(WaterRegionType, Rect<u16>, u8)>,
|
||||
water_params: &WaterParams,
|
||||
stage: &Stage,
|
||||
) {
|
||||
for (reg_type, bounds, color_idx) in regions {
|
||||
let color = water_params.get_entry(color_idx);
|
||||
|
||||
match reg_type {
|
||||
WaterRegionType::WaterLine => {
|
||||
self.water_surfaces.push(DynamicWater::new(bounds.left, bounds.top, bounds.width() + 1, *color));
|
||||
}
|
||||
WaterRegionType::WaterDepth => {
|
||||
self.depth_regions.push(DepthRegion::new_tile(bounds, *color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if stage.data.background_type == BackgroundType::Water {
|
||||
let core_water_color = water_params.get_entry(0);
|
||||
self.core_water = Some((
|
||||
DynamicWater::new(0, 32768, stage.map.width, *core_water_color),
|
||||
DepthRegion::new(
|
||||
Rect {
|
||||
left: 0.0,
|
||||
top: stage.map.height as f32 * 16.0,
|
||||
right: stage.map.width as f32 * 16.0,
|
||||
bottom: stage.map.height as f32 * 16.0 + 1.0,
|
||||
},
|
||||
*core_water_color,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, state: &mut SharedGameState, (players, npc_list): (&[&Player], &NPCList)) -> GameResult<()> {
|
||||
for surf in &mut self.water_surfaces {
|
||||
surf.interact(players, npc_list);
|
||||
surf.tick();
|
||||
}
|
||||
|
||||
if let Some((ref mut core_water, ref mut core_depth)) = &mut self.core_water {
|
||||
let level = state.water_level as f32 / 512.0 + 8.0;
|
||||
core_water.y = level;
|
||||
core_depth.rect.top = (level + 16.0).min(core_depth.rect.bottom);
|
||||
|
||||
core_water.interact(players, npc_list);
|
||||
core_water.tick();
|
||||
}
|
||||
|
||||
let mut t_ref = self.t.borrow_mut();
|
||||
*t_ref = t_ref.wrapping_add(1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&self,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
frame: &Frame,
|
||||
layer: WaterLayer,
|
||||
) -> GameResult<()> {
|
||||
if !graphics::supports_vertex_draw(ctx)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
graphics::set_render_target(ctx, state.lightmap_canvas.as_ref())?;
|
||||
graphics::clear(ctx, Color::from_rgba(0, 0, 0, 0));
|
||||
graphics::set_blend_mode(ctx, BlendMode::None)?;
|
||||
|
||||
let (o_x, o_y) = frame.xy_interpolated(state.frame_time);
|
||||
let uv = (0.0, 0.0);
|
||||
let t = *self.t.borrow_mut() as f32 + state.frame_time as f32;
|
||||
let shader = BackendShader::WaterFill(state.scale, t, (o_x, o_y));
|
||||
let mut vertices = Vec::new();
|
||||
|
||||
{
|
||||
let mut draw_region = |region: &DepthRegion| -> GameResult {
|
||||
let color_mid_rgba = region.color.color_middle.to_rgba();
|
||||
let color_btm_rgba = region.color.color_bottom.to_rgba();
|
||||
vertices.clear();
|
||||
vertices.reserve(6);
|
||||
|
||||
let left = (region.rect.left - o_x - 8.0) * state.scale;
|
||||
let top = (region.rect.top - o_y - 8.0) * state.scale;
|
||||
let right = (region.rect.right - o_x + 8.0) * state.scale;
|
||||
let bottom = (region.rect.bottom - o_y + 8.0) * state.scale;
|
||||
|
||||
vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (left, top), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (right, bottom), uv, color: color_btm_rgba });
|
||||
|
||||
graphics::draw_triangle_list(ctx, &vertices, None, shader)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if layer == WaterLayer::Back {
|
||||
for region in &self.depth_regions {
|
||||
draw_region(region)?;
|
||||
}
|
||||
} else if let Some((_, ref core_depth)) = &self.core_water {
|
||||
draw_region(core_depth)?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut draw_region = |surf: &DynamicWater| -> GameResult {
|
||||
let pos_x = surf.x;
|
||||
let pos_y = surf.y;
|
||||
let color_top_rgba = surf.color.color_top.to_rgba();
|
||||
let color_mid_rgba = surf.color.color_middle.to_rgba();
|
||||
let color_btm_rgba = surf.color.color_bottom.to_rgba();
|
||||
|
||||
if (pos_x - o_x - 16.0) > state.canvas_size.0
|
||||
|| (pos_x - o_x + 16.0 + surf.end_x) < 0.0
|
||||
|| (pos_y - o_y - 16.0) > state.canvas_size.1
|
||||
|| (pos_y - o_y + 16.0) < 0.0
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
vertices.clear();
|
||||
vertices.reserve(12 * surf.columns.len());
|
||||
|
||||
let bottom = (pos_y - o_y + 8.0) * state.scale;
|
||||
for i in 1..surf.columns.len() {
|
||||
let x_right = (pos_x - 8.0 - o_x + i as f32 * 2.0) * state.scale;
|
||||
let x_left = x_right - 2.0 * state.scale;
|
||||
let top_left = (pos_y - o_y - 13.0 + surf.columns[i - 1].height) * state.scale;
|
||||
let top_right = (pos_y - o_y - 13.0 + surf.columns[i].height) * state.scale;
|
||||
let middle_left = top_left + 6.0 * state.scale;
|
||||
let middle_right = top_left + 6.0 * state.scale;
|
||||
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, top_left), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba });
|
||||
vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba });
|
||||
vertices.push(VertexData { position: (x_right, bottom), uv, color: color_btm_rgba });
|
||||
}
|
||||
|
||||
graphics::draw_triangle_list(ctx, &vertices, None, shader)?;
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if layer == WaterLayer::Back {
|
||||
for surf in &self.water_surfaces {
|
||||
draw_region(surf)?;
|
||||
}
|
||||
} else if let Some((ref surf, _)) = &self.core_water {
|
||||
draw_region(surf)?;
|
||||
}
|
||||
}
|
||||
|
||||
graphics::set_blend_mode(ctx, BlendMode::Alpha)?;
|
||||
graphics::set_render_target(ctx, None)?;
|
||||
|
||||
{
|
||||
let canvas = state.lightmap_canvas.as_mut().unwrap();
|
||||
let rect = Rect { left: 0.0, top: 0.0, right: state.screen_size.0, bottom: state.screen_size.1 };
|
||||
|
||||
canvas.clear();
|
||||
canvas.add(SpriteBatchCommand::DrawRect(rect, rect));
|
||||
canvas.draw()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
use crate::common::{interpolate_fix9_scale, Direction, 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};
|
||||
|
||||
pub struct WhimsicalStar {
|
||||
pub star: [Star; 3],
|
||||
pub tex: String,
|
||||
pub star_count: u8,
|
||||
pub equipped: bool,
|
||||
pub active_star: u8,
|
||||
}
|
||||
|
||||
pub struct Star {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
pub vel_x: i32,
|
||||
pub vel_y: i32,
|
||||
pub rect: Rect<u16>,
|
||||
}
|
||||
|
||||
impl Star {
|
||||
fn new(vel_x: i32, vel_y: i32) -> Star {
|
||||
Star { x: 0, y: 0, vel_x, vel_y, prev_x: 0, prev_y: 0, rect: Rect::new(0, 0, 0, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl WhimsicalStar {
|
||||
pub fn new() -> WhimsicalStar {
|
||||
WhimsicalStar {
|
||||
star: [Star::new(0x400, -0x200), Star::new(-0x200, 0x400), Star::new(0x200, 0x200)],
|
||||
tex: "MyChar".to_string(),
|
||||
star_count: 0,
|
||||
equipped: false,
|
||||
active_star: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, player: &Player) {
|
||||
self.tex = player.skin.get_skin_texture_name().to_string();
|
||||
for (iter, star) in &mut self.star.iter_mut().enumerate() {
|
||||
star.rect = player.skin.get_whimsical_star_rect(iter);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prev(&mut self) {
|
||||
for star in &mut self.star {
|
||||
star.prev_x = star.x;
|
||||
star.prev_y = star.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&Player, &mut BulletManager)> for WhimsicalStar {
|
||||
fn tick(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
(player, bullet_manager): (&Player, &mut BulletManager),
|
||||
) -> GameResult {
|
||||
if !self.equipped && player.equip.has_whimsical_star() {
|
||||
for star in &mut self.star {
|
||||
star.x = player.x;
|
||||
star.y = player.y;
|
||||
}
|
||||
self.equipped = true;
|
||||
}
|
||||
|
||||
if !player.equip.has_whimsical_star() {
|
||||
self.equipped = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.star_count = player.stars;
|
||||
|
||||
let mut prev_x = player.x;
|
||||
let mut prev_y = player.y;
|
||||
|
||||
for star in &mut self.star {
|
||||
star.vel_x += if prev_x >= star.x { 0x80 } else { -0x80 };
|
||||
star.vel_y += if prev_y >= star.y { 0xAA } else { -0xAA };
|
||||
|
||||
star.vel_x = star.vel_x.clamp(-0xA00, 0xA00);
|
||||
star.vel_y = star.vel_y.clamp(-0xA00, 0xA00);
|
||||
|
||||
star.x += star.vel_x;
|
||||
star.y += star.vel_y;
|
||||
|
||||
prev_x = star.x;
|
||||
prev_y = star.y;
|
||||
}
|
||||
|
||||
// Only one star can deal damage per tick
|
||||
self.active_star += 1;
|
||||
self.active_star %= 3;
|
||||
|
||||
if self.active_star < self.star_count && state.control_flags.control_enabled() {
|
||||
let bullet = Bullet::new(
|
||||
self.star[self.active_star as usize].x,
|
||||
self.star[self.active_star as usize].y,
|
||||
45,
|
||||
TargetPlayer::Player1,
|
||||
Direction::Left,
|
||||
&state.constants,
|
||||
);
|
||||
bullet_manager.push_bullet(bullet);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
|
||||
if !self.equipped {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
|
||||
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &self.tex)?;
|
||||
|
||||
let (active_stars, _) = self.star.split_at(self.star_count as usize);
|
||||
|
||||
for star in active_stars {
|
||||
let x = interpolate_fix9_scale(star.prev_x as i32, star.x as i32, state.frame_time) - frame_x;
|
||||
let y = interpolate_fix9_scale(star.prev_y as i32, star.y as i32, state.frame_time) - frame_y;
|
||||
batch.add_rect(x, y, &star.rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,285 +0,0 @@
|
|||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use imgui::{Image, MouseButton, Window, WindowFlags};
|
||||
|
||||
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};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum CurrentTool {
|
||||
Move,
|
||||
Brush,
|
||||
Fill,
|
||||
Rectangle,
|
||||
}
|
||||
|
||||
pub struct EditorInstance {
|
||||
pub stage: Stage,
|
||||
pub stage_id: usize,
|
||||
pub frame: Frame,
|
||||
pub background: Background,
|
||||
pub stage_textures: Rc<RefCell<StageTexturePaths>>,
|
||||
pub tilemap: Tilemap,
|
||||
pub zoom: f32,
|
||||
pub current_tile: u8,
|
||||
pub mouse_pos: (f32, f32),
|
||||
pub want_capture_mouse: bool,
|
||||
}
|
||||
|
||||
impl EditorInstance {
|
||||
pub fn new(stage_id: usize, stage: Stage) -> EditorInstance {
|
||||
let stage_textures = {
|
||||
let mut textures = StageTexturePaths::new();
|
||||
textures.update(&stage);
|
||||
Rc::new(RefCell::new(textures))
|
||||
};
|
||||
let mut frame = Frame::new();
|
||||
frame.x = -16 * 0x200;
|
||||
frame.y = -48 * 0x200;
|
||||
|
||||
EditorInstance {
|
||||
stage,
|
||||
stage_id,
|
||||
frame,
|
||||
background: Background::new(),
|
||||
stage_textures,
|
||||
tilemap: Tilemap::new(),
|
||||
zoom: 2.0,
|
||||
current_tile: 0,
|
||||
mouse_pos: (0.0, 0.0),
|
||||
want_capture_mouse: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&mut self, state: &mut SharedGameState, ctx: &mut Context, ui: &mut imgui::Ui, tool: CurrentTool) {
|
||||
self.frame.prev_x = self.frame.x;
|
||||
self.frame.prev_y = self.frame.y;
|
||||
self.mouse_pos = (ui.io().mouse_pos[0], ui.io().mouse_pos[1]);
|
||||
self.want_capture_mouse = ui.io().want_capture_mouse;
|
||||
|
||||
let mut drag = false;
|
||||
|
||||
match tool {
|
||||
CurrentTool::Move => {
|
||||
if ui.io().want_capture_mouse {
|
||||
return;
|
||||
}
|
||||
|
||||
drag |= ui.is_mouse_down(MouseButton::Left) || ui.is_mouse_down(MouseButton::Right);
|
||||
}
|
||||
CurrentTool::Brush => {
|
||||
self.palette_window(state, ctx, ui);
|
||||
|
||||
if ui.io().want_capture_mouse {
|
||||
return;
|
||||
}
|
||||
|
||||
drag |= ui.is_mouse_down(MouseButton::Right);
|
||||
|
||||
if !drag && ui.is_mouse_down(MouseButton::Left) {
|
||||
let tile_size = self.stage.map.tile_size.as_int();
|
||||
let halft = tile_size / 2;
|
||||
let stage_mouse_x = (self.frame.x / 0x200) + halft + (self.mouse_pos.0 / self.zoom) as i32;
|
||||
let stage_mouse_y = (self.frame.y / 0x200) + halft + (self.mouse_pos.1 / self.zoom) as i32;
|
||||
let tile_x = stage_mouse_x / tile_size;
|
||||
let tile_y = stage_mouse_y / tile_size;
|
||||
|
||||
if tile_x >= 0
|
||||
&& tile_y >= 0
|
||||
&& tile_x < self.stage.map.width as i32
|
||||
&& tile_y < self.stage.map.height as i32
|
||||
{
|
||||
self.stage.change_tile(tile_x as usize, tile_y as usize, self.current_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
CurrentTool::Fill => {
|
||||
self.palette_window(state, ctx, ui);
|
||||
drag |= ui.is_mouse_down(MouseButton::Right);
|
||||
}
|
||||
CurrentTool::Rectangle => {
|
||||
self.palette_window(state, ctx, ui);
|
||||
drag |= ui.is_mouse_down(MouseButton::Right);
|
||||
}
|
||||
}
|
||||
|
||||
if drag {
|
||||
self.frame.x -= (512.0 * ui.io().mouse_delta[0] as f32 / self.zoom) as i32;
|
||||
self.frame.y -= (512.0 * ui.io().mouse_delta[1] as f32 / self.zoom) as i32;
|
||||
self.frame.prev_x = self.frame.x;
|
||||
self.frame.prev_y = self.frame.y;
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_cursor(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if self.want_capture_mouse {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tile_size = self.stage.map.tile_size.as_int();
|
||||
let halft = tile_size / 2;
|
||||
let stage_mouse_x = (self.frame.x / 0x200) + halft + (self.mouse_pos.0 / self.zoom) as i32;
|
||||
let stage_mouse_y = (self.frame.y / 0x200) + halft + (self.mouse_pos.1 / self.zoom) as i32;
|
||||
let tile_x = stage_mouse_x / tile_size;
|
||||
let tile_y = stage_mouse_y / tile_size;
|
||||
let frame_x = self.frame.x as f32 / 512.0;
|
||||
let frame_y = self.frame.y as f32 / 512.0;
|
||||
|
||||
if tile_x < 0 || tile_y < 0 || tile_x >= self.stage.map.width as i32 || tile_y >= self.stage.map.height as i32 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let name = &self.stage_textures.deref().borrow().tileset_fg;
|
||||
|
||||
if let Ok(batch) = state.texture_set.get_or_load_batch(ctx, &state.constants, name) {
|
||||
let tile_size16 = tile_size as u16;
|
||||
let rect = Rect::new_size(
|
||||
(self.current_tile as u16 % 16) * tile_size16,
|
||||
(self.current_tile as u16 / 16) * tile_size16,
|
||||
tile_size16,
|
||||
tile_size16,
|
||||
);
|
||||
|
||||
batch.add_rect_tinted(
|
||||
(tile_x * tile_size - halft) as f32 - frame_x,
|
||||
(tile_y * tile_size - halft) as f32 - frame_y,
|
||||
(255, 255, 255, 192),
|
||||
&rect,
|
||||
);
|
||||
|
||||
batch.draw(ctx)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn palette_window(&mut self, state: &mut SharedGameState, ctx: &mut Context, ui: &imgui::Ui) {
|
||||
Window::new("Palette")
|
||||
.size([260.0, 260.0], imgui::Condition::Always)
|
||||
.position(ui.io().display_size, imgui::Condition::FirstUseEver)
|
||||
.position_pivot([1.0, 1.0])
|
||||
.resizable(false)
|
||||
.build(ui, || {
|
||||
let name = &self.stage_textures.deref().borrow().tileset_fg;
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, name);
|
||||
|
||||
let pos = ui.cursor_screen_pos();
|
||||
let tile_size = self.stage.map.tile_size.as_float();
|
||||
|
||||
if let Ok(batch) = batch {
|
||||
let (scale_x, scale_y) = batch.scale();
|
||||
if let Some(tex) = batch.get_texture() {
|
||||
let (width, height) = tex.dimensions();
|
||||
let (width, height) = (width as f32 / scale_x, height as f32 / scale_y);
|
||||
|
||||
if let Ok(tex_id) = graphics::imgui_texture_id(ctx, tex) {
|
||||
Image::new(tex_id, [width, height]).build(ui);
|
||||
}
|
||||
|
||||
ui.set_cursor_screen_pos(pos);
|
||||
ui.invisible_button("##tiles", [width, height]);
|
||||
}
|
||||
}
|
||||
|
||||
let draw_list = ui.get_window_draw_list();
|
||||
let cur_pos1 = [
|
||||
pos[0].floor() + tile_size * (self.current_tile % 16) as f32,
|
||||
pos[1].floor() + tile_size * (self.current_tile / 16) as f32,
|
||||
];
|
||||
let cur_pos2 = [cur_pos1[0] + tile_size, cur_pos1[1] + tile_size];
|
||||
draw_list.add_rect(cur_pos1, cur_pos2, [1.0, 0.0, 0.0, 1.0]).thickness(2.0).build();
|
||||
|
||||
if ui.is_mouse_down(MouseButton::Left) {
|
||||
let mouse_pos = ui.io().mouse_pos;
|
||||
let x = (mouse_pos[0] - pos[0]) / tile_size;
|
||||
let y = (mouse_pos[1] - pos[1]) / tile_size;
|
||||
|
||||
if x >= 0.0 && x < 16.0 && y >= 0.0 && y < 16.0 {
|
||||
self.current_tile = (y as u8 * 16 + x as u8) as u8;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, tool: CurrentTool) -> GameResult {
|
||||
let old_scale = state.scale;
|
||||
set_scale(state, self.zoom);
|
||||
|
||||
let paths = self.stage_textures.deref().borrow();
|
||||
self.background.draw(state, ctx, &self.frame, &*paths, &self.stage)?;
|
||||
|
||||
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Background, &*paths, &self.stage)?;
|
||||
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Middleground, &*paths, &self.stage)?;
|
||||
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Foreground, &*paths, &self.stage)?;
|
||||
self.tilemap.draw(state, ctx, &self.frame, TileLayer::Snack, &*paths, &self.stage)?;
|
||||
|
||||
self.draw_black_bars(state, ctx)?;
|
||||
|
||||
match tool {
|
||||
CurrentTool::Move => (),
|
||||
CurrentTool::Brush | CurrentTool::Fill | CurrentTool::Rectangle => {
|
||||
self.tile_cursor(state, ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
set_scale(state, old_scale);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_black_bars(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let color = Color::from_rgba(0, 0, 0, 128);
|
||||
let (x, y) = self.frame.xy_interpolated(state.frame_time);
|
||||
let (x, y) = (x * state.scale, y * state.scale);
|
||||
let canvas_w_scaled = state.canvas_size.0 as f32 * state.scale;
|
||||
let canvas_h_scaled = state.canvas_size.1 as f32 * state.scale;
|
||||
let level_width = (self.stage.map.width as f32 - 1.0) * self.stage.map.tile_size.as_float();
|
||||
let level_height = (self.stage.map.height as f32 - 1.0) * self.stage.map.tile_size.as_float();
|
||||
let left_side = -x;
|
||||
let right_side = -x + level_width * state.scale;
|
||||
let upper_side = -y;
|
||||
let lower_side = -y + level_height * state.scale;
|
||||
|
||||
if left_side > 0.0 {
|
||||
let rect = Rect::new(0, upper_side as isize, left_side as isize, lower_side as isize);
|
||||
graphics::draw_rect(ctx, rect, color)?;
|
||||
}
|
||||
|
||||
if right_side < canvas_w_scaled {
|
||||
let rect = Rect::new(
|
||||
right_side as isize,
|
||||
upper_side as isize,
|
||||
(state.canvas_size.0 * state.scale) as isize,
|
||||
lower_side as isize,
|
||||
);
|
||||
graphics::draw_rect(ctx, rect, color)?;
|
||||
}
|
||||
|
||||
if upper_side > 0.0 {
|
||||
let rect = Rect::new(0, 0, canvas_w_scaled as isize, upper_side as isize);
|
||||
graphics::draw_rect(ctx, rect, color)?;
|
||||
}
|
||||
|
||||
if lower_side < canvas_h_scaled {
|
||||
let rect = Rect::new(0, lower_side as isize, canvas_w_scaled as isize, canvas_h_scaled as isize);
|
||||
graphics::draw_rect(ctx, rect, color)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn set_scale(state: &mut SharedGameState, scale: f32) {
|
||||
state.scale = scale;
|
||||
|
||||
unsafe {
|
||||
I_MAG = state.scale;
|
||||
state.canvas_size = (state.screen_size.0 / state.scale, state.screen_size.1 / state.scale);
|
||||
}
|
||||
}
|
7371
src/encoding.rs
|
@ -1,53 +0,0 @@
|
|||
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;
|
||||
|
||||
pub trait GameEntity<C> {
|
||||
fn tick(&mut self, state: &mut SharedGameState, custom: C) -> GameResult;
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult;
|
||||
}
|
||||
|
||||
pub trait Interpolatable {
|
||||
fn position_x(&self) -> i32;
|
||||
|
||||
fn position_y(&self) -> i32;
|
||||
|
||||
fn prev_position_x(&self) -> i32;
|
||||
|
||||
fn prev_position_y(&self) -> i32;
|
||||
|
||||
fn draw_tick(&mut self);
|
||||
|
||||
#[inline]
|
||||
fn interpolate_x(&self, frame_delta: f64) -> f32 {
|
||||
interpolate_fix9_scale(self.prev_position_x(), self.position_x(), frame_delta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn interpolate_y(&self, frame_delta: f64) -> f32 {
|
||||
interpolate_fix9_scale(self.prev_position_y(), self.position_y(), frame_delta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn interpolate(&self, frame_delta: f64) -> (f32, f32) {
|
||||
(self.interpolate_x(frame_delta), self.interpolate_y(frame_delta))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn interpolate_relative_x(&self, target: &dyn Interpolatable, frame_delta: f64) -> f32 {
|
||||
interpolate_fix9_scale(self.prev_position_x() - target.prev_position_x(), self.position_x() - target.position_x(), frame_delta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn interpolate_relative_y(&self, target: &dyn Interpolatable, frame_delta: f64) -> f32 {
|
||||
interpolate_fix9_scale(self.prev_position_y() - target.prev_position_y(), self.position_y() - target.position_y(), frame_delta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn interpolate_relative(&self, target: &dyn Interpolatable, frame_delta: f64) -> (f32, f32) {
|
||||
(self.interpolate_relative_x(target, frame_delta), self.interpolate_relative_y(target, frame_delta))
|
||||
}
|
||||
}
|
148
src/frame.rs
|
@ -1,148 +0,0 @@
|
|||
use crate::common::{fix9_scale, interpolate_fix9_scale};
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum UpdateTarget {
|
||||
Player,
|
||||
NPC(u16),
|
||||
Boss(u16),
|
||||
}
|
||||
|
||||
pub struct Frame {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub prev_x: i32,
|
||||
pub prev_y: i32,
|
||||
pub update_target: UpdateTarget,
|
||||
pub target_x: i32,
|
||||
pub target_y: i32,
|
||||
pub wait: i32,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn new() -> Frame {
|
||||
Frame {
|
||||
x: 0,
|
||||
y: 0,
|
||||
prev_x: 0,
|
||||
prev_y: 0,
|
||||
update_target: UpdateTarget::Player,
|
||||
target_x: 0,
|
||||
target_y: 0,
|
||||
wait: 16,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xy_interpolated(&self, frame_time: f64) -> (f32, f32) {
|
||||
if self.prev_x == self.x && self.prev_y == self.y {
|
||||
return (fix9_scale(self.x), fix9_scale(self.y));
|
||||
}
|
||||
|
||||
let x = interpolate_fix9_scale(self.prev_x, self.x, frame_time);
|
||||
let y = interpolate_fix9_scale(self.prev_y, self.y, frame_time);
|
||||
|
||||
(x, y)
|
||||
}
|
||||
|
||||
pub fn immediate_update(&mut self, state: &mut SharedGameState, stage: &Stage) {
|
||||
let mut screen_width = state.canvas_size.0;
|
||||
if state.constants.is_switch && stage.map.width <= 54 {
|
||||
screen_width += 10.0; // hack for scrolling
|
||||
}
|
||||
|
||||
let tile_size = state.tile_size.as_int();
|
||||
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < screen_width as usize {
|
||||
self.x = -(((screen_width as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.x = self.target_x - (screen_width as i32 * 0x200 / 2);
|
||||
|
||||
if self.x < 0 {
|
||||
self.x = 0;
|
||||
}
|
||||
|
||||
let max_x = (((stage.map.width as i32 - 1) * tile_size) - screen_width as i32) * 0x200;
|
||||
if self.x > max_x {
|
||||
self.x = max_x;
|
||||
}
|
||||
}
|
||||
|
||||
if (stage.map.height as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.1 as usize {
|
||||
self.y = -(((state.canvas_size.1 as i32 - (stage.map.height as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.y = self.target_y - (state.canvas_size.1 as i32 * 0x200 / 2);
|
||||
|
||||
if self.y < 0 {
|
||||
self.y = 0;
|
||||
}
|
||||
|
||||
let max_y = (((stage.map.height as i32 - 1) * tile_size) - state.canvas_size.1 as i32) * 0x200;
|
||||
if self.y > max_y {
|
||||
self.y = max_y;
|
||||
}
|
||||
}
|
||||
|
||||
self.prev_x = self.x;
|
||||
self.prev_y = self.y;
|
||||
}
|
||||
|
||||
pub fn update(&mut self, state: &mut SharedGameState, stage: &Stage) {
|
||||
let mut screen_width = state.canvas_size.0;
|
||||
if state.constants.is_switch && stage.map.width <= 54 {
|
||||
screen_width += 10.0;
|
||||
}
|
||||
|
||||
if self.wait == 0 { // prevent zero division
|
||||
self.wait = 1;
|
||||
}
|
||||
|
||||
let tile_size = state.tile_size.as_int();
|
||||
|
||||
if (stage.map.width as usize).saturating_sub(1) * (tile_size as usize) < screen_width as usize {
|
||||
self.x = -(((screen_width as i32 - (stage.map.width as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.x += (self.target_x - (screen_width as i32 * 0x200 / 2) - self.x) / self.wait;
|
||||
|
||||
if self.x < 0 {
|
||||
self.x = 0;
|
||||
}
|
||||
|
||||
let max_x = (((stage.map.width as i32 - 1) * tile_size) - screen_width as i32) * 0x200;
|
||||
if self.x > max_x {
|
||||
self.x = max_x;
|
||||
}
|
||||
}
|
||||
|
||||
if (stage.map.height as usize).saturating_sub(1) * (tile_size as usize) < state.canvas_size.1 as usize {
|
||||
self.y = -(((state.canvas_size.1 as i32 - (stage.map.height as i32 - 1) * tile_size) * 0x200) / 2);
|
||||
} else {
|
||||
self.y += (self.target_y - (state.canvas_size.1 as i32 * 0x200 / 2) - self.y) / self.wait;
|
||||
|
||||
if self.y < 0 {
|
||||
self.y = 0;
|
||||
}
|
||||
|
||||
let max_y = (((stage.map.height as i32 - 1) * tile_size) - state.canvas_size.1 as i32) * 0x200;
|
||||
if self.y > max_y {
|
||||
self.y = max_y;
|
||||
}
|
||||
}
|
||||
|
||||
if state.super_quake_counter > 0 {
|
||||
state.super_quake_counter -= 1;
|
||||
|
||||
self.x += state.effect_rng.range(-0x300..0x300) * 5 as i32;
|
||||
self.y += state.effect_rng.range(-0x300..0x300) * 3 as i32;
|
||||
}
|
||||
|
||||
if state.quake_counter > 0 {
|
||||
state.quake_counter -= 1;
|
||||
|
||||
self.x += state.effect_rng.range(-0x300..0x300) as i32;
|
||||
self.y += state.effect_rng.range(-0x300..0x300) as i32;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
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;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct VertexData {
|
||||
pub position: (f32, f32),
|
||||
pub color: (u8, u8, u8, u8),
|
||||
pub uv: (f32, f32),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum BackendShader {
|
||||
/// (scale, t, (frame_x, frame_y))
|
||||
WaterFill(f32, f32, (f32, f32)),
|
||||
Fill,
|
||||
Texture,
|
||||
}
|
||||
|
||||
pub trait Backend {
|
||||
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>>;
|
||||
}
|
||||
|
||||
pub trait BackendEventLoop {
|
||||
fn run(&mut self, game: &mut Game, ctx: &mut Context);
|
||||
|
||||
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>>;
|
||||
}
|
||||
|
||||
pub trait BackendRenderer {
|
||||
fn renderer_name(&self) -> String;
|
||||
|
||||
fn clear(&mut self, color: Color);
|
||||
|
||||
fn present(&mut self) -> GameResult;
|
||||
|
||||
fn prepare_draw(&mut self, _width: f32, _height: f32) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>>;
|
||||
|
||||
fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult<Box<dyn BackendTexture>>;
|
||||
|
||||
fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult;
|
||||
|
||||
fn set_render_target(&mut self, texture: Option<&Box<dyn BackendTexture>>) -> GameResult;
|
||||
|
||||
fn draw_rect(&mut self, rect: Rect, color: Color) -> GameResult;
|
||||
|
||||
fn draw_outline_rect(&mut self, rect: Rect, line_width: usize, color: Color) -> GameResult;
|
||||
|
||||
fn set_clip_rect(&mut self, rect: Option<Rect>) -> GameResult;
|
||||
|
||||
fn imgui(&self) -> GameResult<&mut imgui::Context>;
|
||||
|
||||
fn imgui_texture_id(&self, texture: &Box<dyn BackendTexture>) -> GameResult<imgui::TextureId>;
|
||||
|
||||
fn prepare_imgui(&mut self, ui: &imgui::Ui) -> GameResult;
|
||||
|
||||
fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult;
|
||||
|
||||
fn supports_vertex_draw(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn draw_triangle_list(
|
||||
&mut self,
|
||||
vertices: &[VertexData],
|
||||
texture: Option<&Box<dyn BackendTexture>>,
|
||||
shader: BackendShader,
|
||||
) -> GameResult;
|
||||
}
|
||||
|
||||
pub trait BackendTexture {
|
||||
fn dimensions(&self) -> (u16, u16);
|
||||
|
||||
fn add(&mut self, command: SpriteBatchCommand);
|
||||
|
||||
fn clear(&mut self);
|
||||
|
||||
fn draw(&mut self) -> GameResult;
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
#[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-glutin"))]
|
||||
{
|
||||
return crate::framework::backend_glutin::GlutinBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend-sdl")]
|
||||
{
|
||||
return crate::framework::backend_sdl2::SDL2Backend::new(size_hint);
|
||||
}
|
||||
|
||||
log::warn!("No backend compiled in, using null backend instead.");
|
||||
crate::framework::backend_null::NullBackend::new()
|
||||
}
|
||||
|
||||
pub enum SpriteBatchCommand {
|
||||
DrawRect(Rect<f32>, Rect<f32>),
|
||||
DrawRectFlip(Rect<f32>, Rect<f32>, bool, bool),
|
||||
DrawRectTinted(Rect<f32>, Rect<f32>, Color),
|
||||
}
|
|
@ -1,533 +0,0 @@
|
|||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::ffi::c_void;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
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 imgui::{DrawCmdParams, DrawData, DrawIdx, DrawVert};
|
||||
|
||||
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::gl;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
|
||||
use crate::input::touch_controls::TouchPoint;
|
||||
|
||||
pub struct GlutinBackend;
|
||||
|
||||
impl GlutinBackend {
|
||||
pub fn new() -> GameResult<Box<dyn Backend>> {
|
||||
Ok(Box::new(GlutinBackend))
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for GlutinBackend {
|
||||
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> {
|
||||
#[cfg(target_os = "android")]
|
||||
loop {
|
||||
match ndk_glue::native_window().as_ref() {
|
||||
Some(_) => {
|
||||
log::info!("NativeWindow Found: {:?}", ndk_glue::native_window());
|
||||
break;
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new(GlutinEventLoop { refs: Rc::new(UnsafeCell::new(None)) }))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlutinEventLoop {
|
||||
refs: Rc<UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>>,
|
||||
}
|
||||
|
||||
impl GlutinEventLoop {
|
||||
fn get_context(&self, 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)
|
||||
.with_gl_debug_flag(false)
|
||||
.with_pixel_format(24, 8)
|
||||
.with_vsync(true);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use glutin::platform::windows::WindowBuilderExtWindows;
|
||||
window = window.with_drag_and_drop(false);
|
||||
}
|
||||
|
||||
window = window.with_title("doukutsu-rs");
|
||||
|
||||
let windowed_context = windowed_context.build_windowed(window, event_loop).unwrap();
|
||||
|
||||
let windowed_context = unsafe { windowed_context.make_current().unwrap() };
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
if let Some(nwin) = ndk_glue::native_window().as_ref() {
|
||||
unsafe {
|
||||
windowed_context.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
refs.replace(windowed_context);
|
||||
}
|
||||
|
||||
refs.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn request_android_redraw() {
|
||||
match ndk_glue::native_window().as_ref() {
|
||||
Some(native_window) => {
|
||||
let a_native_window: *mut ndk_sys::ANativeWindow = native_window.ptr().as_ptr();
|
||||
let a_native_activity: *mut ndk_sys::ANativeActivity = ndk_glue::native_activity().ptr().as_ptr();
|
||||
unsafe {
|
||||
match (*(*a_native_activity).callbacks).onNativeWindowRedrawNeeded {
|
||||
Some(callback) => callback(a_native_activity, a_native_window),
|
||||
None => (),
|
||||
};
|
||||
};
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn get_insets() -> GameResult<(f32, f32, f32, f32)> {
|
||||
unsafe {
|
||||
let vm_ptr = ndk_glue::native_activity().vm();
|
||||
let vm = unsafe { jni::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 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());
|
||||
|
||||
Ok((elements[0] as f32, elements[1] as f32, elements[2] as f32, elements[3] as f32))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_scaled_size(width: u32, height: u32) -> (f32, f32) {
|
||||
let scaled_height = ((height / 480).max(1) * 480) as f32;
|
||||
let scaled_width = (width as f32 * (scaled_height as f32 / height as f32)).floor();
|
||||
|
||||
(scaled_width, scaled_height)
|
||||
}
|
||||
|
||||
impl BackendEventLoop for GlutinEventLoop {
|
||||
fn run(&mut self, game: &mut Game, ctx: &mut Context) {
|
||||
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)) };
|
||||
|
||||
{
|
||||
let size = window.window().inner_size();
|
||||
ctx.real_screen_size = (size.width, size.height);
|
||||
ctx.screen_size = get_scaled_size(size.width.max(1), size.height.max(1));
|
||||
state_ref.handle_resize(ctx).unwrap();
|
||||
}
|
||||
|
||||
// it won't ever return
|
||||
let (game, ctx): (&'static mut Game, &'static mut Context) =
|
||||
unsafe { (std::mem::transmute(game), std::mem::transmute(ctx)) };
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, window_id }
|
||||
if window_id == window.window().id() =>
|
||||
{
|
||||
state_ref.shutdown();
|
||||
}
|
||||
Event::Resumed => {
|
||||
{
|
||||
let mut mutex = GAME_SUSPENDED.lock().unwrap();
|
||||
*mutex = false;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
if let Some(nwin) = ndk_glue::native_window().as_ref() {
|
||||
state_ref.graphics_reset();
|
||||
unsafe {
|
||||
window.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void);
|
||||
request_android_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
state_ref.sound_manager.resume();
|
||||
}
|
||||
Event::Suspended => {
|
||||
{
|
||||
let mut mutex = GAME_SUSPENDED.lock().unwrap();
|
||||
*mutex = true;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
unsafe {
|
||||
window.surface_destroyed();
|
||||
}
|
||||
|
||||
state_ref.sound_manager.pause();
|
||||
}
|
||||
Event::WindowEvent { event: WindowEvent::Resized(size), window_id }
|
||||
if window_id == window.window().id() =>
|
||||
{
|
||||
if let Some(renderer) = &ctx.renderer {
|
||||
if let Ok(imgui) = renderer.imgui() {
|
||||
imgui.io_mut().display_size = [size.width as f32, size.height as f32];
|
||||
}
|
||||
|
||||
ctx.real_screen_size = (size.width, size.height);
|
||||
ctx.screen_size = get_scaled_size(size.width.max(1), size.height.max(1));
|
||||
state_ref.handle_resize(ctx).unwrap();
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event: WindowEvent::Touch(touch), window_id }
|
||||
if window_id == window.window().id() =>
|
||||
{
|
||||
let mut controls = &mut state_ref.touch_controls;
|
||||
let scale = state_ref.scale as f64;
|
||||
let loc_x = (touch.location.x * ctx.screen_size.0 as f64 / ctx.real_screen_size.0 as f64) / scale;
|
||||
let loc_y = (touch.location.y * ctx.screen_size.1 as f64 / ctx.real_screen_size.1 as f64) / scale;
|
||||
|
||||
match touch.phase {
|
||||
TouchPhase::Started | TouchPhase::Moved => {
|
||||
if let Some(point) = controls.points.iter_mut().find(|p| p.id == touch.id) {
|
||||
point.last_position = point.position;
|
||||
point.position = (loc_x, loc_y);
|
||||
} else {
|
||||
controls.touch_id_counter = controls.touch_id_counter.wrapping_add(1);
|
||||
|
||||
let point = TouchPoint {
|
||||
id: touch.id,
|
||||
touch_id: controls.touch_id_counter,
|
||||
position: (loc_x, loc_y),
|
||||
last_position: (0.0, 0.0),
|
||||
};
|
||||
controls.points.push(point);
|
||||
|
||||
if touch.phase == TouchPhase::Started {
|
||||
controls.clicks.push(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
TouchPhase::Ended | TouchPhase::Cancelled => {
|
||||
controls.points.retain(|p| p.id != touch.id);
|
||||
controls.clicks.retain(|p| p.id != touch.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, window_id }
|
||||
if window_id == window.window().id() =>
|
||||
{
|
||||
if let Some(keycode) = input.virtual_keycode {
|
||||
if let Some(drs_scan) = conv_keycode(keycode) {
|
||||
let key_state = match input.state {
|
||||
ElementState::Pressed => true,
|
||||
ElementState::Released => false,
|
||||
};
|
||||
|
||||
ctx.keyboard_context.set_key(drs_scan, key_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(id) if id == window.window().id() => {
|
||||
{
|
||||
let mutex = GAME_SUSPENDED.lock().unwrap();
|
||||
if *mutex {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
if let Err(err) = game.draw(ctx) {
|
||||
log::error!("Failed to draw frame: {}", err);
|
||||
}
|
||||
|
||||
window.window().request_redraw();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
request_android_redraw();
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
if state_ref.shutdown {
|
||||
log::info!("Shutting down...");
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let mutex = GAME_SUSPENDED.lock().unwrap();
|
||||
if *mutex {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
game.update(ctx).unwrap();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
match get_insets() {
|
||||
Ok(insets) => {
|
||||
ctx.screen_insets = insets;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to update insets: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = game.draw(ctx) {
|
||||
log::error!("Failed to draw frame: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if state_ref.next_scene.is_some() {
|
||||
mem::swap(&mut game.scene, &mut state_ref.next_scene);
|
||||
state_ref.next_scene = None;
|
||||
game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap();
|
||||
game.loops = 0;
|
||||
state_ref.frame_time = 0.0;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
|
||||
let mut imgui = imgui::Context::create();
|
||||
imgui.io_mut().display_size = [640.0, 480.0];
|
||||
|
||||
let refs = self.refs.clone();
|
||||
let user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
|
||||
unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void {
|
||||
let refs = Rc::from_raw(*user_data as *mut UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>);
|
||||
|
||||
let result = {
|
||||
let refs = &mut *refs.get();
|
||||
|
||||
if let Some(refs) = refs {
|
||||
refs.get_proc_address(name)
|
||||
} else {
|
||||
std::ptr::null()
|
||||
}
|
||||
};
|
||||
|
||||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
unsafe fn swap_buffers(user_data: &mut *mut c_void) {
|
||||
let refs = Rc::from_raw(*user_data as *mut UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>);
|
||||
|
||||
{
|
||||
let refs = &mut *refs.get();
|
||||
|
||||
if let Some(refs) = refs {
|
||||
refs.swap_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
}
|
||||
|
||||
let gl_context = GLContext { gles2_mode: true, get_proc_address, swap_buffers, user_data };
|
||||
|
||||
Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))))
|
||||
}
|
||||
}
|
||||
|
||||
fn conv_keycode(code: VirtualKeyCode) -> Option<ScanCode> {
|
||||
match code {
|
||||
VirtualKeyCode::Key1 => Some(ScanCode::Key1),
|
||||
VirtualKeyCode::Key2 => Some(ScanCode::Key2),
|
||||
VirtualKeyCode::Key3 => Some(ScanCode::Key3),
|
||||
VirtualKeyCode::Key4 => Some(ScanCode::Key4),
|
||||
VirtualKeyCode::Key5 => Some(ScanCode::Key5),
|
||||
VirtualKeyCode::Key6 => Some(ScanCode::Key6),
|
||||
VirtualKeyCode::Key7 => Some(ScanCode::Key7),
|
||||
VirtualKeyCode::Key8 => Some(ScanCode::Key8),
|
||||
VirtualKeyCode::Key9 => Some(ScanCode::Key9),
|
||||
VirtualKeyCode::Key0 => Some(ScanCode::Key0),
|
||||
VirtualKeyCode::A => Some(ScanCode::A),
|
||||
VirtualKeyCode::B => Some(ScanCode::B),
|
||||
VirtualKeyCode::C => Some(ScanCode::C),
|
||||
VirtualKeyCode::D => Some(ScanCode::D),
|
||||
VirtualKeyCode::E => Some(ScanCode::E),
|
||||
VirtualKeyCode::F => Some(ScanCode::F),
|
||||
VirtualKeyCode::G => Some(ScanCode::G),
|
||||
VirtualKeyCode::H => Some(ScanCode::H),
|
||||
VirtualKeyCode::I => Some(ScanCode::I),
|
||||
VirtualKeyCode::J => Some(ScanCode::J),
|
||||
VirtualKeyCode::K => Some(ScanCode::K),
|
||||
VirtualKeyCode::L => Some(ScanCode::L),
|
||||
VirtualKeyCode::M => Some(ScanCode::M),
|
||||
VirtualKeyCode::N => Some(ScanCode::N),
|
||||
VirtualKeyCode::O => Some(ScanCode::O),
|
||||
VirtualKeyCode::P => Some(ScanCode::P),
|
||||
VirtualKeyCode::Q => Some(ScanCode::Q),
|
||||
VirtualKeyCode::R => Some(ScanCode::R),
|
||||
VirtualKeyCode::S => Some(ScanCode::S),
|
||||
VirtualKeyCode::T => Some(ScanCode::T),
|
||||
VirtualKeyCode::U => Some(ScanCode::U),
|
||||
VirtualKeyCode::V => Some(ScanCode::V),
|
||||
VirtualKeyCode::W => Some(ScanCode::W),
|
||||
VirtualKeyCode::X => Some(ScanCode::X),
|
||||
VirtualKeyCode::Y => Some(ScanCode::Y),
|
||||
VirtualKeyCode::Z => Some(ScanCode::Z),
|
||||
VirtualKeyCode::Escape => Some(ScanCode::Escape),
|
||||
VirtualKeyCode::F1 => Some(ScanCode::F1),
|
||||
VirtualKeyCode::F2 => Some(ScanCode::F2),
|
||||
VirtualKeyCode::F3 => Some(ScanCode::F3),
|
||||
VirtualKeyCode::F4 => Some(ScanCode::F4),
|
||||
VirtualKeyCode::F5 => Some(ScanCode::F5),
|
||||
VirtualKeyCode::F6 => Some(ScanCode::F6),
|
||||
VirtualKeyCode::F7 => Some(ScanCode::F7),
|
||||
VirtualKeyCode::F8 => Some(ScanCode::F8),
|
||||
VirtualKeyCode::F9 => Some(ScanCode::F9),
|
||||
VirtualKeyCode::F10 => Some(ScanCode::F10),
|
||||
VirtualKeyCode::F11 => Some(ScanCode::F11),
|
||||
VirtualKeyCode::F12 => Some(ScanCode::F12),
|
||||
VirtualKeyCode::F13 => Some(ScanCode::F13),
|
||||
VirtualKeyCode::F14 => Some(ScanCode::F14),
|
||||
VirtualKeyCode::F15 => Some(ScanCode::F15),
|
||||
VirtualKeyCode::F16 => Some(ScanCode::F16),
|
||||
VirtualKeyCode::F17 => Some(ScanCode::F17),
|
||||
VirtualKeyCode::F18 => Some(ScanCode::F18),
|
||||
VirtualKeyCode::F19 => Some(ScanCode::F19),
|
||||
VirtualKeyCode::F20 => Some(ScanCode::F20),
|
||||
VirtualKeyCode::F21 => Some(ScanCode::F21),
|
||||
VirtualKeyCode::F22 => Some(ScanCode::F22),
|
||||
VirtualKeyCode::F23 => Some(ScanCode::F23),
|
||||
VirtualKeyCode::F24 => Some(ScanCode::F24),
|
||||
VirtualKeyCode::Snapshot => Some(ScanCode::Snapshot),
|
||||
VirtualKeyCode::Scroll => Some(ScanCode::Scroll),
|
||||
VirtualKeyCode::Pause => Some(ScanCode::Pause),
|
||||
VirtualKeyCode::Insert => Some(ScanCode::Insert),
|
||||
VirtualKeyCode::Home => Some(ScanCode::Home),
|
||||
VirtualKeyCode::Delete => Some(ScanCode::Delete),
|
||||
VirtualKeyCode::End => Some(ScanCode::End),
|
||||
VirtualKeyCode::PageDown => Some(ScanCode::PageDown),
|
||||
VirtualKeyCode::PageUp => Some(ScanCode::PageUp),
|
||||
VirtualKeyCode::Left => Some(ScanCode::Left),
|
||||
VirtualKeyCode::Up => Some(ScanCode::Up),
|
||||
VirtualKeyCode::Right => Some(ScanCode::Right),
|
||||
VirtualKeyCode::Down => Some(ScanCode::Down),
|
||||
VirtualKeyCode::Back => Some(ScanCode::Back),
|
||||
VirtualKeyCode::Return => Some(ScanCode::Return),
|
||||
VirtualKeyCode::Space => Some(ScanCode::Space),
|
||||
VirtualKeyCode::Compose => Some(ScanCode::Compose),
|
||||
VirtualKeyCode::Caret => Some(ScanCode::Caret),
|
||||
VirtualKeyCode::Numlock => Some(ScanCode::Numlock),
|
||||
VirtualKeyCode::Numpad0 => Some(ScanCode::Numpad0),
|
||||
VirtualKeyCode::Numpad1 => Some(ScanCode::Numpad1),
|
||||
VirtualKeyCode::Numpad2 => Some(ScanCode::Numpad2),
|
||||
VirtualKeyCode::Numpad3 => Some(ScanCode::Numpad3),
|
||||
VirtualKeyCode::Numpad4 => Some(ScanCode::Numpad4),
|
||||
VirtualKeyCode::Numpad5 => Some(ScanCode::Numpad5),
|
||||
VirtualKeyCode::Numpad6 => Some(ScanCode::Numpad6),
|
||||
VirtualKeyCode::Numpad7 => Some(ScanCode::Numpad7),
|
||||
VirtualKeyCode::Numpad8 => Some(ScanCode::Numpad8),
|
||||
VirtualKeyCode::Numpad9 => Some(ScanCode::Numpad9),
|
||||
VirtualKeyCode::NumpadAdd => Some(ScanCode::NumpadAdd),
|
||||
VirtualKeyCode::NumpadDivide => Some(ScanCode::NumpadDivide),
|
||||
VirtualKeyCode::NumpadDecimal => Some(ScanCode::NumpadDecimal),
|
||||
VirtualKeyCode::NumpadComma => Some(ScanCode::NumpadComma),
|
||||
VirtualKeyCode::NumpadEnter => Some(ScanCode::NumpadEnter),
|
||||
VirtualKeyCode::NumpadEquals => Some(ScanCode::NumpadEquals),
|
||||
VirtualKeyCode::NumpadMultiply => Some(ScanCode::NumpadMultiply),
|
||||
VirtualKeyCode::NumpadSubtract => Some(ScanCode::NumpadSubtract),
|
||||
VirtualKeyCode::AbntC1 => Some(ScanCode::AbntC1),
|
||||
VirtualKeyCode::AbntC2 => Some(ScanCode::AbntC2),
|
||||
VirtualKeyCode::Apostrophe => Some(ScanCode::Apostrophe),
|
||||
VirtualKeyCode::Apps => Some(ScanCode::Apps),
|
||||
VirtualKeyCode::Asterisk => Some(ScanCode::Asterisk),
|
||||
VirtualKeyCode::At => Some(ScanCode::At),
|
||||
VirtualKeyCode::Ax => Some(ScanCode::Ax),
|
||||
VirtualKeyCode::Backslash => Some(ScanCode::Backslash),
|
||||
VirtualKeyCode::Calculator => Some(ScanCode::Calculator),
|
||||
VirtualKeyCode::Capital => Some(ScanCode::Capital),
|
||||
VirtualKeyCode::Colon => Some(ScanCode::Colon),
|
||||
VirtualKeyCode::Comma => Some(ScanCode::Comma),
|
||||
VirtualKeyCode::Convert => Some(ScanCode::Convert),
|
||||
VirtualKeyCode::Equals => Some(ScanCode::Equals),
|
||||
VirtualKeyCode::Grave => Some(ScanCode::Grave),
|
||||
VirtualKeyCode::Kana => Some(ScanCode::Kana),
|
||||
VirtualKeyCode::Kanji => Some(ScanCode::Kanji),
|
||||
VirtualKeyCode::LAlt => Some(ScanCode::LAlt),
|
||||
VirtualKeyCode::LBracket => Some(ScanCode::LBracket),
|
||||
VirtualKeyCode::LControl => Some(ScanCode::LControl),
|
||||
VirtualKeyCode::LShift => Some(ScanCode::LShift),
|
||||
VirtualKeyCode::LWin => Some(ScanCode::LWin),
|
||||
VirtualKeyCode::Mail => Some(ScanCode::Mail),
|
||||
VirtualKeyCode::MediaSelect => Some(ScanCode::MediaSelect),
|
||||
VirtualKeyCode::MediaStop => Some(ScanCode::MediaStop),
|
||||
VirtualKeyCode::Minus => Some(ScanCode::Minus),
|
||||
VirtualKeyCode::Mute => Some(ScanCode::Mute),
|
||||
VirtualKeyCode::MyComputer => Some(ScanCode::MyComputer),
|
||||
VirtualKeyCode::NavigateForward => Some(ScanCode::NavigateForward),
|
||||
VirtualKeyCode::NavigateBackward => Some(ScanCode::NavigateBackward),
|
||||
VirtualKeyCode::NextTrack => Some(ScanCode::NextTrack),
|
||||
VirtualKeyCode::NoConvert => Some(ScanCode::NoConvert),
|
||||
VirtualKeyCode::OEM102 => Some(ScanCode::OEM102),
|
||||
VirtualKeyCode::Period => Some(ScanCode::Period),
|
||||
VirtualKeyCode::PlayPause => Some(ScanCode::PlayPause),
|
||||
VirtualKeyCode::Plus => Some(ScanCode::Plus),
|
||||
VirtualKeyCode::Power => Some(ScanCode::Power),
|
||||
VirtualKeyCode::PrevTrack => Some(ScanCode::PrevTrack),
|
||||
VirtualKeyCode::RAlt => Some(ScanCode::RAlt),
|
||||
VirtualKeyCode::RBracket => Some(ScanCode::RBracket),
|
||||
VirtualKeyCode::RControl => Some(ScanCode::RControl),
|
||||
VirtualKeyCode::RShift => Some(ScanCode::RShift),
|
||||
VirtualKeyCode::RWin => Some(ScanCode::RWin),
|
||||
VirtualKeyCode::Semicolon => Some(ScanCode::Semicolon),
|
||||
VirtualKeyCode::Slash => Some(ScanCode::Slash),
|
||||
VirtualKeyCode::Sleep => Some(ScanCode::Sleep),
|
||||
VirtualKeyCode::Stop => Some(ScanCode::Stop),
|
||||
VirtualKeyCode::Sysrq => Some(ScanCode::Sysrq),
|
||||
VirtualKeyCode::Tab => Some(ScanCode::Tab),
|
||||
VirtualKeyCode::Underline => Some(ScanCode::Underline),
|
||||
VirtualKeyCode::Unlabeled => Some(ScanCode::Unlabeled),
|
||||
VirtualKeyCode::VolumeDown => Some(ScanCode::VolumeDown),
|
||||
VirtualKeyCode::VolumeUp => Some(ScanCode::VolumeUp),
|
||||
VirtualKeyCode::Wake => Some(ScanCode::Wake),
|
||||
VirtualKeyCode::WebBack => Some(ScanCode::WebBack),
|
||||
VirtualKeyCode::WebFavorites => Some(ScanCode::WebFavorites),
|
||||
VirtualKeyCode::WebForward => Some(ScanCode::WebForward),
|
||||
VirtualKeyCode::WebHome => Some(ScanCode::WebHome),
|
||||
VirtualKeyCode::WebRefresh => Some(ScanCode::WebRefresh),
|
||||
VirtualKeyCode::WebSearch => Some(ScanCode::WebSearch),
|
||||
VirtualKeyCode::WebStop => Some(ScanCode::WebStop),
|
||||
VirtualKeyCode::Yen => Some(ScanCode::Yen),
|
||||
VirtualKeyCode::Copy => Some(ScanCode::Copy),
|
||||
VirtualKeyCode::Paste => Some(ScanCode::Paste),
|
||||
VirtualKeyCode::Cut => Some(ScanCode::Cut),
|
||||
}
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
|
||||
use imgui::{DrawData, TextureId, Ui};
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::backend::{
|
||||
Backend, BackendEventLoop, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData,
|
||||
};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use crate::Game;
|
||||
|
||||
pub struct NullBackend;
|
||||
|
||||
impl NullBackend {
|
||||
pub fn new() -> GameResult<Box<dyn Backend>> {
|
||||
Ok(Box::new(NullBackend))
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for NullBackend {
|
||||
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> {
|
||||
Ok(Box::new(NullEventLoop))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NullEventLoop;
|
||||
|
||||
impl BackendEventLoop for NullEventLoop {
|
||||
fn run(&mut self, game: &mut Game, ctx: &mut Context) {
|
||||
let state_ref = unsafe { &mut *game.state.get() };
|
||||
|
||||
ctx.screen_size = (640.0, 480.0);
|
||||
state_ref.handle_resize(ctx).unwrap();
|
||||
|
||||
loop {
|
||||
game.update(ctx).unwrap();
|
||||
|
||||
if state_ref.shutdown {
|
||||
log::info!("Shutting down...");
|
||||
break;
|
||||
}
|
||||
|
||||
if state_ref.next_scene.is_some() {
|
||||
mem::swap(&mut game.scene, &mut state_ref.next_scene);
|
||||
state_ref.next_scene = None;
|
||||
game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap();
|
||||
game.loops = 0;
|
||||
state_ref.frame_time = 0.0;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
|
||||
game.draw(ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
|
||||
let mut imgui = imgui::Context::create();
|
||||
imgui.io_mut().display_size = [640.0, 480.0];
|
||||
imgui.fonts().build_alpha8_texture();
|
||||
|
||||
Ok(Box::new(NullRenderer(RefCell::new(imgui))))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NullTexture(u16, u16);
|
||||
|
||||
impl BackendTexture for NullTexture {
|
||||
fn dimensions(&self) -> (u16, u16) {
|
||||
(self.0, self.1)
|
||||
}
|
||||
|
||||
fn add(&mut self, _command: SpriteBatchCommand) {}
|
||||
|
||||
fn clear(&mut self) {}
|
||||
|
||||
fn draw(&mut self) -> GameResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NullRenderer(RefCell<imgui::Context>);
|
||||
|
||||
impl BackendRenderer for NullRenderer {
|
||||
fn renderer_name(&self) -> String {
|
||||
"Null".to_owned()
|
||||
}
|
||||
|
||||
fn clear(&mut self, _color: Color) {}
|
||||
|
||||
fn present(&mut self) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>> {
|
||||
Ok(Box::new(NullTexture(width, height)))
|
||||
}
|
||||
|
||||
fn create_texture(&mut self, width: u16, height: u16, _data: &[u8]) -> GameResult<Box<dyn BackendTexture>> {
|
||||
Ok(Box::new(NullTexture(width, height)))
|
||||
}
|
||||
|
||||
fn set_blend_mode(&mut self, _blend: BlendMode) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_render_target(&mut self, _texture: Option<&Box<dyn BackendTexture>>) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_rect(&mut self, _rect: Rect<isize>, _color: Color) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_outline_rect(&mut self, _rect: Rect<isize>, _line_width: usize, _color: Color) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_clip_rect(&mut self, _rect: Option<Rect>) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn imgui(&self) -> GameResult<&mut imgui::Context> {
|
||||
unsafe { Ok(&mut *self.0.as_ptr()) }
|
||||
}
|
||||
|
||||
fn imgui_texture_id(&self, _texture: &Box<dyn BackendTexture>) -> GameResult<TextureId> {
|
||||
Ok(TextureId::from(0))
|
||||
}
|
||||
|
||||
fn prepare_imgui(&mut self, _ui: &Ui) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_imgui(&mut self, _draw_data: &DrawData) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_triangle_list(
|
||||
&mut self,
|
||||
_vertices: &[VertexData],
|
||||
_texture: Option<&Box<dyn BackendTexture>>,
|
||||
_shader: BackendShader,
|
||||
) -> GameResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|