some experimentation

This commit is contained in:
Alula 2022-03-26 17:47:33 +01:00
parent 55e80b4c69
commit f6b631011b
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
254 changed files with 2057 additions and 91722 deletions

View File

@ -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

View File

@ -1,6 +0,0 @@
[target.aarch64-linux-android]
rustflags = [
"-C", "link-arg=-lc++_static",
"-C", "link-arg=-lc++abi",
"-C", "link-arg=-lEGL",
]

20
.gitignore vendored
View File

@ -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/

13
CMakeLists.txt Normal file
View File

@ -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)

View File

@ -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"

233
app/.gitignore vendored
View File

@ -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
app/app/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -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'
}
}

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -1,2 +0,0 @@
void drs_dummy_export() {
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

View File

@ -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);
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
});
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -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>

View File

@ -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>

View File

@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FD7F44</color>
</resources>

View File

@ -1,4 +0,0 @@
<resources>
<string name="app_name">doukutsu-rs</string>
<string name="document_provider_name">doukutsu-rs game data</string>
</resources>

View File

@ -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>

View File

@ -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
}

View File

@ -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

Binary file not shown.

View File

@ -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

172
app/gradlew vendored
View File

@ -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" "$@"

84
app/gradlew.bat vendored
View File

@ -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

View File

@ -1,2 +0,0 @@
rootProject.name = "doukutsu-rs"
include ':app'

View File

@ -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");
}
}

View File

@ -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"] }

View File

@ -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();
}

View File

@ -1,4 +0,0 @@
edition = "2018"
max_width = 120
use_small_heuristics = "Max"
newline_style = "Unix"

393
src/3rdparty/mpsc_channel.h vendored Normal file
View File

@ -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 &current.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;
}
}
}

View File

@ -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 })
}
}

View File

@ -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)
}
}

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -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"
}
}

View File

@ -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} を押し続け、カットシーンをスキップ"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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());
}

8
src/caret.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
namespace doukutsu_rs::caret
{
class Caret {
};
};

View File

@ -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()
}
}

21
src/common.cpp Normal file
View File

@ -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();
}

413
src/common.h Normal file
View File

@ -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();
}
};

View File

@ -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))
}
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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;

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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))
}
}

View File

@ -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;
}
}
}

View File

@ -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),
}

View File

@ -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),
}
}

View File

@ -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(())
}
}

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