Merge branch 'main' of github.com:CodenameCrew/CodenameEngine

This commit is contained in:
Frakits
2026-01-22 18:54:33 +02:00
90 changed files with 2120 additions and 936 deletions
+4 -1
View File
@@ -37,4 +37,7 @@ f8b20774e3f035eeddb9211a28a391b20db74456
# Revert a revert (scripted cutscene path)
d73f86d26ec2dbf031c01f588ba4be11842db09c
fe0ab0aa454f4ae12ced9502db8a01e7bca3e04c
fe0ab0aa454f4ae12ced9502db8a01e7bca3e04c
# Forgot the # in the commit message
484bbcb22aa8f28b62912abb6d8c89fb58e63889
+77 -9
View File
@@ -1,22 +1,23 @@
name: Linux Builds
name: Linux Build
on:
push:
workflow_dispatch:
jobs:
build:
name: Linux Build
permissions: write-all
runs-on: ubuntu-24.04
steps:
- name: Pulling the source
uses: actions/checkout@v2
- name: Pulling the new commit
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- name: Restore existing build cache for faster compilation
uses: actions/cache@v3
uses: actions/cache@v4.2.3
with:
# not caching the bin folder to prevent asset duplication and stuff like that
key: cache-build-linux
@@ -24,8 +25,8 @@ jobs:
.haxelib/
export/release/linux/haxe/
export/release/linux/obj/
restore-keys: |
cache-build-linux
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing LibVLC
run: |
sudo apt-get install libvlc-dev libvlccore-dev
@@ -67,7 +68,7 @@ jobs:
}
}
- name: Uploading new cache
uses: actions/cache@v3
uses: actions/cache@v4.2.3
with:
# caching again since for some reason it doesnt work with the first post cache shit
key: cache-build-linux
@@ -75,5 +76,72 @@ jobs:
.haxelib/
export/release/linux/haxe/
export/release/linux/obj/
restore-keys: |
cache-build-linux
# didnt compile debug in the same job or github would have said that job wasn't completed until debug was done too (debug uploads are not essential)
debug_build:
name: Linux Debug Build
permissions: write-all
runs-on: ubuntu-24.04
needs: build # since its low priority, it'll run after, so actions will concentrate first on normal builds
steps:
- name: Pulling the new commit
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- name: Restore existing build cache for faster compilation
uses: actions/cache@v4.2.3
with:
# not caching the bin folder to prevent asset duplication and stuff like that
key: cache-build-linux-debug
path: |
.haxelib/
export/debug/linux/haxe/
export/debug/linux/obj/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing LibVLC
run: |
sudo apt-get install libvlc-dev libvlccore-dev
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s
- name: Building the game
run: |
haxelib run lime build linux -debug
# - name: Tar files
# run: tar -zcvf CodenameEngine.tar.gz -C export/debug/linux/bin .
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
with:
name: Codename Engine Debug
path: export/debug/linux/bin/
- name: Clearing already existing cache
uses: actions/github-script@v6
with:
script: |
const caches = await github.rest.actions.getActionsCacheList({
owner: context.repo.owner,
repo: context.repo.repo,
})
for (const cache of caches.data.actions_caches) {
if (cache.key == "cache-build-linux-debug") {
console.log('Clearing ' + cache.key + '...')
await github.rest.actions.deleteActionsCacheById({
owner: context.repo.owner,
repo: context.repo.repo,
cache_id: cache.id,
})
console.log("Cache cleared.")
}
}
- name: Uploading new cache
uses: actions/cache@v4.2.3
with:
# caching again since for some reason it doesnt work with the first post cache shit
key: cache-build-linux-debug
path: |
.haxelib/
export/debug/linux/haxe/
export/debug/linux/obj/
+75 -10
View File
@@ -1,22 +1,23 @@
name: Mac OS Builds
name: Mac OS Build
on:
push:
workflow_dispatch:
jobs:
build:
name: Mac OS Build
permissions: write-all
runs-on: macos-13
runs-on: macos-14
steps:
- name: Pulling the new commit
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- name: Restore existing build cache for faster compilation
uses: actions/cache@v3
uses: actions/cache@v4.2.3
with:
# not caching the bin folder to prevent asset duplication and stuff like that
key: cache-build-mac
@@ -24,14 +25,14 @@ jobs:
.haxelib/
export/release/macos/haxe/
export/release/macos/obj/
restore-keys: |
cache-build-mac
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s
- name: Building the game
run: |
haxelib run lime build mac
arch -x86_64 haxelib run lime build mac
- name: Tar files
run: tar -zcvf CodenameEngine.tar.gz -C export/release/macos/bin .
- name: Uploading artifact (executable)
@@ -64,7 +65,7 @@ jobs:
}
}
- name: Uploading new cache
uses: actions/cache@v3
uses: actions/cache@v4.2.3
with:
# caching again since for some reason it doesnt work with the first post cache shit
key: cache-build-mac
@@ -72,5 +73,69 @@ jobs:
.haxelib/
export/release/macos/haxe/
export/release/macos/obj/
restore-keys: |
cache-build-mac
# didnt compile debug in the same job or github would have said that job wasn't completed until debug was done too (debug uploads are not essential)
debug_build:
name: Mac OS Debug Build
permissions: write-all
runs-on: macos-14
needs: build # since its low priority, it'll run after, so actions will concentrate first on normal builds
steps:
- name: Pulling the new commit
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- name: Restore existing build cache for faster compilation
uses: actions/cache@v4.2.3
with:
# not caching the bin folder to prevent asset duplication and stuff like that
key: cache-build-mac-debug
path: |
.haxelib/
export/debug/macos/haxe/
export/debug/macos/obj/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s
- name: Building the game
run: |
arch -x86_64 haxelib run lime build mac -debug
- name: Tar files
run: tar -zcvf CodenameEngine.tar.gz -C export/debug/macos/bin .
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
with:
name: Codename Engine Debug
path: CodenameEngine.tar.gz
- name: Clearing already existing cache
uses: actions/github-script@v6
with:
script: |
const caches = await github.rest.actions.getActionsCacheList({
owner: context.repo.owner,
repo: context.repo.repo,
})
for (const cache of caches.data.actions_caches) {
if (cache.key == "cache-build-mac-debug") {
console.log('Clearing ' + cache.key + '...')
await github.rest.actions.deleteActionsCacheById({
owner: context.repo.owner,
repo: context.repo.repo,
cache_id: cache.id,
})
console.log("Cache cleared.")
}
}
- name: Uploading new cache
uses: actions/cache@v4.2.3
with:
# caching again since for some reason it doesnt work with the first post cache shit
key: cache-build-mac-debug
path: |
.haxelib/
export/debug/macos/haxe/
export/debug/macos/obj/
+1 -1
View File
@@ -184,7 +184,7 @@ jobs:
name: Release ${{ github.event.inputs.tag_name }}
draft: true
prerelease: ${{ github.event.inputs.prerelease }}
body: ${{ steps.build_body.outputs.body }}
body: ${{ steps.build_body.outputs.body }}
generate_release_notes: true
files: |
renamed/Codename Engine-Windows.zip
+71 -8
View File
@@ -1,8 +1,9 @@
name: Windows Builds
name: Windows Build
on:
push:
workflow_dispatch:
jobs:
build:
name: Windows Build
@@ -10,13 +11,13 @@ jobs:
runs-on: windows-latest
steps:
- name: Pulling the new commit
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- name: Restore existing build cache for faster compilation
uses: actions/cache@v3
uses: actions/cache@v4.2.3
with:
# not caching the bin folder to prevent asset duplication and stuff like that
key: cache-build-windows
@@ -24,8 +25,8 @@ jobs:
.haxelib/
export/release/windows/haxe/
export/release/windows/obj/
restore-keys: |
cache-build-windows
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s --no-vscheck
@@ -62,7 +63,7 @@ jobs:
}
}
- name: Uploading new cache
uses: actions/cache@v3
uses: actions/cache@v4.2.3
with:
# caching again since for some reason it doesnt work with the first post cache shit
key: cache-build-windows
@@ -70,5 +71,67 @@ jobs:
.haxelib/
export/release/windows/haxe/
export/release/windows/obj/
restore-keys: |
cache-build-windows
# didnt compile debug in the same job or github would have said that job wasn't completed until debug was done too (debug uploads are not essential)
debug_build:
name: Windows Debug Build
permissions: write-all
runs-on: windows-latest
needs: build # since its low priority, it'll run after, so actions will concentrate first on normal builds
steps:
- name: Pulling the new commit
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- name: Restore existing build cache for faster compilation
uses: actions/cache@v4.2.3
with:
# not caching the bin folder to prevent asset duplication and stuff like that
key: cache-build-windows-debug
path: |
.haxelib/
export/debug/windows/haxe/
export/debug/windows/obj/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s --no-vscheck
- name: Building the game
run: |
haxelib run lime build windows -debug
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
with:
name: Codename Engine Debug
path: export/debug/windows/bin
- name: Clearing already existing cache
uses: actions/github-script@v6
with:
script: |
const caches = await github.rest.actions.getActionsCacheList({
owner: context.repo.owner,
repo: context.repo.repo,
})
for (const cache of caches.data.actions_caches) {
if (cache.key == "cache-build-windows-debug") {
console.log('Clearing ' + cache.key + '...')
await github.rest.actions.deleteActionsCacheById({
owner: context.repo.owner,
repo: context.repo.repo,
cache_id: cache.id,
})
console.log("Cache cleared.")
}
}
- name: Uploading new cache
uses: actions/cache@v4.2.3
with:
# caching again since for some reason it doesnt work with the first post cache shit
key: cache-build-windows-debug
path: |
.haxelib/
export/debug/windows/haxe/
export/debug/windows/obj/
+1 -1
View File
@@ -68,7 +68,7 @@ The engine uses [HaxeFlixel](https://haxeflixel.com/) and it mainly features:
</details>
<details>
<summary><h2>How to setup and build</h2></summary>
<summary><h2>How to setup and build the engine and its documentation</h2></summary>
Check out our guide [HERE](building/README.md)
</details>
-10
View File
@@ -1,10 +0,0 @@
## DEV (PRIVATE) REPO INSTRUCTIONS TO SYNC UP WITH IT INSTEAD OF SYNCING WITH THE PUBLIC ONE
- Open a Command Prompt window in the public codename repo folder
- Type those commands:
- `git remote add dev https://www.github.com/YoshiCrafter29/CodenameEngine-Dev`
- `git branch --set-upstream-to dev/main`
- GitHub desktop should be synchronized with the dev repo now. If you're unsure,
- Repo name should still say "CodenameEngine"
- Current branch should still say "main"
- "Fetch" button should say "Fetch dev"
- You shouldn't have any commit to push.
-8
View File
@@ -1,8 +0,0 @@
@ECHO OFF
cd ..
echo Building Game...
lime build windows --haxeflag="-xml docs/doc.xml" -D doc-gen -D DOCUMENTATION --no-output
echo art
echo Generated the api xml file at docs/doc.xml
echo Please put this in codename-website/api-generator/api/doc.xml
+1
View File
@@ -48,6 +48,7 @@
<str id="category.engine">Engine</str>
<group name="engine" prefix="engine.">
<str id="switchMod">Switch Mod</str>
<str id="fpsCounter">FPS Counter</str>
</group>
<str id="category.volume">Volume</str>
+2 -1
View File
@@ -46,8 +46,9 @@
<group name="engine" prefix="engine.">
<str id="switchMod">Cambiar Mod</str>
<str id="fpsCounter">Contador de FPS</str>
</group>
<str id="category.volume">Volumen</str>
<str id="volume.up">Arriba</str>
<str id="volume.down">Abajo</str>
+1
View File
@@ -46,6 +46,7 @@
<group name="engine" prefix="engine.">
<str id="switchMod">Cambia Mod</str>
<str id="fpsCounter">Contatore FPS</str>
</group>
<str id="volume.up">Su</str>
+2 -1
View File
@@ -44,8 +44,9 @@
<str id="category.engine">Silnik</str>
<group name="engine" prefix="engine.">
<str id="switchMod">Zmień Moda</str>
<str id="fpsCounter">Licznik FPS</str>
</group>
<str id="category.volume">Głośność</str>
<str id="volume.up">Głóśniej</str>
<str id="volume.down">Ciszej</str>
+1
View File
@@ -48,6 +48,7 @@
<str id="category.engine">Codename Engine</str>
<group name="engine" prefix="engine.">
<str id="switchMod">Trocar Mod</str>
<str id="fpsCounter">Contador de FPS</str>
</group>
<str id="category.volume">Volume</str>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+22 -3
View File
@@ -1,6 +1,6 @@
Here's the full guide on how to setup and compile Codename Engine!<br>
> **Open the instructions for your platform**
# Compiling Codename Engine
Do you want to turn your source code into a playable build to play? Then you want to **compile the source code**, follow this guide.
> **Open the instructions for your platform.**
<details>
<summary>Windows</summary>
@@ -38,3 +38,22 @@ Here's the full guide on how to setup and compile Codename Engine!<br>
> You can also run `./cne-windows.bat -help` or `./cne-unix.sh -help` (depending on your platform) to check out more useful commands!<br>
> For example `./cne-windows test` or `./cne-unix.sh test` builds the game and uses the source assets folder instead of the export one for easier development (although you can still use `lime test` normally).
> - If you're running the terminal from the project's main folder, use instead `./building/cne-windows.bat -COMMAND HERE` or `./building/cne-unix.sh -COMMAND HERE` depending on your platform.
# Generating Codename Engine's API documentation
**Mainly recommended if you intend to fork the engine and make your own custom version to publish.**<br>Do you want to generate an API documentation so people can understand and mod your playable build? This documentation can be uploaded to your website.<br>If you just want to compile the engine normally for your hardcoded mod or for yourself you can skip this step.
> **Select your platform to continue.**
<details>
<summary>Windows</summary>
1. Run `generate-docs-windows.bat` using cmd or double-clicking it and wait for the `doc.xml` to be generated inside the `docs` folder.
</details>
<details>
<summary>MacOS/Linux</summary>
1. Run `generate-docs-unix.sh` using the terminal or double-clicking it and wait for the `doc.xml` to be generated inside the `docs` folder.
</details>
2. You can use this `doc.xml` file to generate a full HTML documentation (that you can open in your browser for example) using Haxe's [dox](https://github.com/HaxeFoundation/dox) generator; check [Codename Engine's webiste](https://github.com/CodenameCrew/codename-website/tree/main/api-generator) for example.
> [!CAUTION]
> The doc.xml might contain some sensible paths of your computer: make sure to filter the file before publishing it for everyone if you want to keep those paths private!<br>To filter and delete those paths, you may use Codename Engine's website's [doc filter Python script](https://github.com/CodenameCrew/codename-website/blob/main/api-generator/api/filter.py) by simply running it in the same folder of your `doc.xml` file. This script will also delete everything irrelevant to the engine that was generated in your documentation, such as libraries' (like OpenFL or Flixel) APIs.
+9 -6
View File
@@ -1,14 +1,17 @@
[general]
[General]
sample-type=float32
stereo-mode=speakers
stereo-encoding=panpot
hrtf=false
cf_level=0
resampler=fast_bsinc24
front-stablizer=false
output-limiter=false
front-stabilizer=false
volume-adjust=0
period_size=441
sources=512
sends=64
dither=false
[decoder]
hq-mode=false
distance-comp=false
hq-mode=true
distance-comp=true
nfc=false
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env sh
cd "$(dirname "$0")/.."
echo "Generating documentation..."
if [ $(uname) == "Darwin" ]; then
echo "Platform is macOS"
haxelib run lime build mac --haxeflag="-xml docs/doc.xml" -D doc-gen -D DOCUMENTATION --no-output
else
echo "Platform is not macOS; assuming platform to be Linux compatible"
haxelib run lime build linux --haxeflag="-xml docs/doc.xml" -D doc-gen -D DOCUMENTATION --no-output
fi
echo "The XML file for the API documentation has been generated at docs/doc.xml."
echo "For updating the API documentation hosted at the website, please replace codename-website/api-generator/api/doc.xml with the file listed above."
+8
View File
@@ -0,0 +1,8 @@
@ECHO OFF
cd /d "%~dp0.."
echo Generating documentation...
echo Platform is based on Windows
haxelib run lime build windows --haxeflag="-xml docs/doc.xml" -D doc-gen -D DOCUMENTATION --no-output
echo The XML file for the API documentation has been generated at docs/doc.xml.
echo For updating the API documentation hosted at the website, please replace codename-website/api-generator/api/doc.xml with the file listed above.
+1 -1
View File
@@ -16,7 +16,7 @@
<git name="hscript-improved" url="https://github.com/CodenameCrew/hscript-improved" ref="codename-dev" />
<git name="flxanimate" url="https://github.com/CodenameCrew/cne-flxanimate" />
<git name="hxdiscord_rpc" url="https://github.com/CodenameCrew/cne-hxdiscord_rpc" skipDeps="true" />
<git name="funkin-modchart" url="https://github.com/TheoDevelops/FunkinModchart" ref="f30676579d8de7ccac7d12e13b664e8b2c42c6bf" skipDeps="true" />
<lib name="funkin-modchart" version="1.2.4" skipDeps="true" />
<lib name="hxvlc" version="1.9.3" skipDeps="true" />
<!-- Documentation and other features -->
+4 -1
View File
@@ -50,7 +50,7 @@
<!-- _________________________________ Engine Settings _______________________________ -->
<!-- Comment this out to disable updates !-->
<define name="UPDATE_CHECKING" unless="web || hl || neko"/>
<define name="UPDATE_CHECKING" unless="web || hl || neko || debug"/>
<!-- Comment this out to disable Discord RPC !-->
<section if="cpp">
@@ -189,6 +189,9 @@
<haxedef name="USE_ADAPTED_ASSETS" if="USE_ADAPTED_ASSETS" />
<haxedef name="openfl_dpi_aware" if="openfl_dpi_aware" />
<!-- Disable it for more concise compilation errors. -->
<haxedef name="message.reporting" value="pretty" />
<!-- _________________________________ Custom _______________________________ -->
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
+70 -39
View File
@@ -21,6 +21,7 @@ import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.system.FlxAssets.FlxSoundAsset;
import flixel.tweens.FlxTween;
import flixel.util.FlxDestroyUtil;
import flixel.util.FlxSignal;
import flixel.util.FlxStringUtil;
import flixel.FlxBasic;
@@ -136,11 +137,18 @@ class FlxSound extends FlxBasic {
public var radius:Float;
/**
* Whether the proximity alters the pan or not
* Whether the proximity alters the pan or not.
* @since raltyMod
*/
public var proximityPan:Bool;
/**
* Controls how much this object is affected by camera scrolling. `0` = no movement (e.g. a static sound),
* This is only useful if used with proximity (Initialized once proximity is used).
* @since raltyMod
*/
public var scrollFactor(default, null):FlxPoint;
/**
* Stores for how much channels are in the loaded sound.
* @since raltyMod
@@ -302,6 +310,8 @@ class FlxSound extends FlxBasic {
_lastTime = null;
if (destroySound) {
scrollFactor = FlxDestroyUtil.put(scrollFactor);
if (group != null) group.remove(this);
if (_channel != null) {
@@ -348,15 +358,24 @@ class FlxSound extends FlxBasic {
_amplitudeUpdate = true;
// Distance-based volume control (TODO for Ralty: REDO THIS)
// Distance-based volume control
if (target != null) {
var targetPosition = target.getPosition();
var radialMultiplier = targetPosition.distanceTo(FlxPoint.weak(x, y)) / radius;
targetPosition.put();
radialMultiplier = 1 - FlxMath.bound(radialMultiplier, 0, 1);
var targetPosition = target.getPosition(), position = getPosition();
var camera = camera;
if (camera != null) {
targetPosition.subtract(camera.scroll.x * target.scrollFactor.x, camera.scroll.y * target.scrollFactor.y);
if (scrollFactor != null) position.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y);
else position.subtract(camera.scroll.x, camera.scroll.y);
}
_volumeAdjust = radialMultiplier;
if (proximityPan) _panAdjust = (x - target.x) / radius;
var radialMultiplier = targetPosition.distanceTo(position) / radius;
// Make it so it affects the 3d position of the source and not just the panning?
_volumeAdjust = 1 - FlxMath.bound(radialMultiplier, 0, 1);
if (proximityPan) _panAdjust = (position.x - targetPosition.x) / radius;
targetPosition.put();
position.put();
}
else
_volumeAdjust = 1.0;
@@ -536,12 +555,15 @@ class FlxSound extends FlxBasic {
* @param Pan Whether panning should be used in addition to the volume changes.
* @return This FlxSound instance (nice for chaining stuff together, if you're into that).
*/
public function proximity(x = 0.0, y = 0.0, ?targetObject:FlxObject, ?radius:Float, pan = true):FlxSound {
public function proximity(x = 0.0, y = 0.0, ?targetObject:FlxObject, ?radius:Float, pan = true, ?scrollFactor:FlxPoint):FlxSound {
setPosition(x, y);
if (targetObject != null) this.target = targetObject;
if (radius != null) this.radius = radius;
proximityPan = pan;
if (this.scrollFactor == null) this.scrollFactor = FlxPoint.get(1, 1);
if (scrollFactor != null) this.scrollFactor.copyFrom(scrollFactor);
return this;
}
@@ -549,14 +571,28 @@ class FlxSound extends FlxBasic {
* Helper function to set the coordinates of this object.
* Sound positioning is used in conjunction with proximity/panning.
*
* @param x The new x position
* @param y The new y position
* @param x The new x position
* @param y The new y position
*/
public inline function setPosition(x = 0.0, y = 0.0):Void {
public function setPosition(x = 0.0, y = 0.0):Void {
this.x = x;
this.y = y;
}
/**
* Returns the world position of this object.
*
* @param result Optional arg for the returning point.
* @return The world position of this object.
* @since raltyMod
*/
public function getPosition(?result:FlxPoint):FlxPoint {
if (result == null)
result = FlxPoint.get();
return result.set(x, y);
}
/**
* Call this function to play the sound - also works on paused sounds.
*
@@ -682,7 +718,7 @@ class FlxSound extends FlxBasic {
_paused = false;
_time = startTime;
_lastTime = FlxG.game.getTicks();
if (_channel == null || !_channel.__isValid || _source == null #if lime_cffi || _source.__backend.disposed || _source.__backend.handle == null #end)
if (_channel == null || !_channel.__isValid || _source == null #if lime_cffi || _source.__backend.disposed #end)
makeChannel();
if (_channel != null) {
@@ -733,7 +769,8 @@ class FlxSound extends FlxBasic {
_lastTime = FlxG.game.getTicks();
_time = loopTime;
}
else _channel.loops = 999;
else if (_source != null)
_source.loops = 999;
}
/**
@@ -763,7 +800,7 @@ class FlxSound extends FlxBasic {
}
inline function get_playing():Bool @:privateAccess
return _channel != null && _channel.__isValid && _source.playing;
return _source != null && _source.playing;
inline function get_volume():Float
return _volume;
@@ -796,12 +833,12 @@ class FlxSound extends FlxBasic {
inline function get_buffer():AudioBuffer
@:privateAccess return _sound != null ? _sound.__buffer : null;
function update_amplitude():Void @:privateAccess {
if (_channel == null || !_channel.__updatePeaks(get_time()) || !_amplitudeUpdate) return;
_amplitudeUpdate = false;
_amplitudeLeft = _channel.__leftPeak;
_amplitudeRight = _channel.__rightPeak;
inline function update_amplitude():Void @:privateAccess {
if (_channel != null && _channel.__updatePeaks(get_time()) && _amplitudeUpdate) {
_amplitudeUpdate = false;
_amplitudeLeft = _channel.__leftPeak;
_amplitudeRight = _channel.__rightPeak;
}
}
inline function get_amplitudeLeft():Float {
@@ -829,30 +866,27 @@ class FlxSound extends FlxBasic {
inline function get_pitch():Float
return _pitch;
function set_pitch(v:Float):Float {
inline function set_pitch(v:Float):Float {
_realPitch = (_pitch = v) * _timeScaleAdjust;
if (_channel != null) _channel.pitch = _realPitch;
if (_source != null) _source.pitch = _realPitch;
return _pitch;
}
#end
function set_looped(v:Bool):Bool {
if (playing) {
if (v) _channel.loops = 999;
else _channel.loops = 0;
}
if (playing) _source.loops = v ? 999 : 0;
return looped = v;
}
function set_loopTime(v:Float):Float {
if (playing) _channel.loopTime = v;
if (playing) _source.loopTime = v;
return loopTime = v;
}
function set_endTime(v:Null<Float>):Null<Float> {
if (playing) {
if (v != null && v > 0 && v < _length) _channel.endTime = v;
else _channel.endTime = null;
if (v != null && v > 0 && v < _length) _source.length = v;
else _source.length = null;
}
return endTime = v;
}
@@ -864,7 +898,7 @@ class FlxSound extends FlxBasic {
return _time;
}
function get_time():Float {
if (_channel == null || @:privateAccess !_channel.__isValid || /*AudioManager.context == null*/funkin.backend.system.Main.audioDisconnected) return _time;
if (_source == null || /*AudioManager.context == null*/funkin.backend.system.Main.audioDisconnected) return _time;
final sourceTime = _source.currentTime - _source.offset - _offset;
if (!_source.playing || _realPitch <= 0) {
@@ -903,24 +937,21 @@ class FlxSound extends FlxBasic {
return _time = time;
}
inline function get_offset():Float return _offset;
inline function set_offset(offset:Float):Float {
function get_offset():Float return _offset;
function set_offset(offset:Float):Float {
if (_offset == (_offset = offset)) return offset;
//time = time + _offset;
return offset;
}
inline function get_length():Float return _length - _offset;
function get_length():Float return _length - _offset;
/*function get_latency():Float {
if (_channel != null) return _source.latency;
return 0;
}*/
//function get_latency():Float return _source != null ? _source.latency : 0;
override function toString():String {
return FlxStringUtil.getDebugString([
LabelValuePair.weak("playing", playing),
LabelValuePair.weak("time", _time),
LabelValuePair.weak("time", time),
LabelValuePair.weak("length", length),
LabelValuePair.weak("volume", volume),
LabelValuePair.weak("pitch", pitch)
+45 -20
View File
@@ -70,6 +70,8 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
@:noCompletion public var atlasPlayingAnim:String;
@:noCompletion public var atlasPath:String;
var _rect2:FlxRect;
public function new(?X:Float = 0, ?Y:Float = 0, ?SimpleGraphic:FlxGraphicAsset)
{
super(X, Y);
@@ -112,6 +114,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
spr.transformMatrix = casted.transformMatrix;
spr.matrixExposed = casted.matrixExposed;
spr.animOffsets = casted.animOffsets.copy();
spr.zoomFactor = casted.zoomFactor;
}
}
return spr;
@@ -131,6 +134,11 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
}
}
override function initVars() {
super.initVars();
_rect2 = FlxRect.get();
}
public function loadSprite(path:String, Unique:Bool = false, Key:String = null)
{
var noExt = Path.withoutExtension(path);
@@ -226,6 +234,8 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
animOffsets = null;
}
super.destroy();
_rect2 = FlxDestroyUtil.put(_rect2);
}
#end
@@ -233,27 +243,40 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
private inline function __shouldDoZoomFactor()
return zoomFactorEnabled && zoomFactor != 1;
private inline function __prepareZoomFactor(?rect:FlxRect, camera:FlxCamera):FlxRect {
if (Flags.USE_LEGACY_ZOOM_FACTOR)
return (rect ?? FlxRect.get()).set(
camera.width * 0.5,
camera.height * 0.5,
(camera.scaleX > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleX, 1, zoomFactor)),
(camera.scaleY > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleY, 1, zoomFactor))
);
else
return (rect ?? FlxRect.get()).set(
camera.width * 0.5 + camera.scroll.x * scrollFactor.x,
camera.height * 0.5 + camera.scroll.y * scrollFactor.y,
(camera.scaleX > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleX, 1, zoomFactor)),
(camera.scaleY > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleY, 1, zoomFactor))
);
}
public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect
{
if (camera == null)
camera = FlxG.camera;
var r = super.getScreenBounds(newRect, camera);
newRect = super.getScreenBounds(newRect, camera);
if(__shouldDoZoomFactor()) {
r.x -= camera.width / 2;
r.y -= camera.height / 2;
var ratio = (camera.zoom > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.zoom, 1, zoomFactor));
r.x *= ratio;
r.y *= ratio;
r.width *= ratio;
r.height *= ratio;
r.x += camera.width / 2;
r.y += camera.height / 2;
__prepareZoomFactor(_rect2, camera);
newRect.set(
(newRect.x - _rect2.x) * _rect2.width + _rect2.x,
(newRect.y - _rect2.y) * _rect2.height + _rect2.y,
newRect.width * _rect2.width,
newRect.height * _rect2.height,
);
}
return r;
return newRect;
}
override public function isOnScreen(?camera:FlxCamera):Bool
@@ -273,14 +296,16 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
// ZOOM FACTOR RENDERING
public override function doAdditionalMatrixStuff(matrix:FlxMatrix, camera:FlxCamera)
{
super.doAdditionalMatrixStuff(matrix, camera);
// no need to...
//super.doAdditionalMatrixStuff(matrix, camera);
if(__shouldDoZoomFactor()) {
matrix.translate(-camera.width / 2, -camera.height / 2);
var requestedZoom = (camera.zoom >= 0 ? Math.max : Math.min)(FlxMath.lerp(1, camera.zoom, zoomFactor), 0);
var diff = requestedZoom / camera.zoom;
matrix.scale(diff, diff);
matrix.translate(camera.width / 2, camera.height / 2);
__prepareZoomFactor(_rect, camera);
matrix.setTo(
matrix.a * _rect.width, matrix.b * _rect.height,
matrix.c * _rect.width, matrix.d * _rect.height,
(matrix.tx - _rect.x) * _rect.width + _rect.x,
(matrix.ty - _rect.y) * _rect.height + _rect.y,
);
}
}
+6
View File
@@ -275,6 +275,12 @@ class MusicBeatState extends FlxState implements IBeatCancellableReceiver
if (!e.cancelled)
super.openSubState(e.substate is FlxSubState ? cast e.substate : subState);
}
public override function closeSubState() {
var e = event("onCloseSubState", EventManager.get(StateEvent).recycle(subState));
if (!e.cancelled)
super.closeSubState();
}
public override function onResize(w:Int, h:Int) {
super.onResize(w, h);
@@ -236,6 +236,12 @@ class MusicBeatSubstate extends FlxSubState implements IBeatCancellableReceiver
super.openSubState(e.substate is FlxSubState ? cast e.substate : subState);
}
public override function closeSubState() {
var e = event("onCloseSubState", EventManager.get(StateEvent).recycle(subState));
if (!e.cancelled)
super.closeSubState();
}
public override function onResize(w:Int, h:Int) {
super.onResize(w, h);
event("onResize", EventManager.get(ResizeEvent).recycle(w, h, null, null));
+1
View File
@@ -300,6 +300,7 @@ class Chart {
var eventsPath = '$songPath/events$variantSuffix.json', events = filterEventsForSaving(chart.events, false, true);
if (events.length != 0) CoolUtil.safeSaveFile(eventsPath, Json.stringify({events: events}, null, prettyPrint));
else if (FileSystem.exists(eventsPath)) FileSystem.deleteFile(eventsPath); // If there's no events to save, then get rid of the file (if it exists already).
}
#end
+3 -1
View File
@@ -179,7 +179,8 @@ class EventsData {
finalParams.push({
name: paramData.name,
type: hscriptInterp.expr(hscriptParser.parseString(paramData.type)),
defValue: paramData.defaultValue
defValue: paramData.defaultValue,
saveIfDefault: paramData.saveIfDefault
});
} catch (e) {trace('Error parsing event param ${paramData.name} - ${eventName}: $e'); finalParams.push(null);}
}
@@ -196,6 +197,7 @@ typedef EventInfoFile = {
var name:String;
var type:String;
var defaultValue:Dynamic;
var ?saveIfDefault:Bool;
}>;
}
+1 -1
View File
@@ -60,7 +60,7 @@ class PsychParser {
note.time, // TIME
note.id, // DATA
note.sLen, // SUSTAIN LENGTH
chart.noteTypes.getDefault([""])[note.type] // NOTE TYPE
chart.noteTypes.getDefault([""])[note.type - 1] // NOTE TYPE
];
if ((swagSection.mustHitSection && strumLine.type == OPPONENT) ||
@@ -55,9 +55,6 @@ class GlobalScript {
reloading = false;
MusicBeatState.ALLOW_DEV_RELOAD = _lastAllow_Reload;
}
if (PlayerSettings.solo.controls.DEV_CONSOLE)
NativeAPI.allocConsole();
});
FlxG.signals.preDraw.add(function() {
call("preDraw");
@@ -1,14 +1,8 @@
package funkin.backend.scripting;
#if ALLOW_MULTITHREADING
import sys.thread.Thread;
#end
import hscript.IHScriptCustomBehaviour;
class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBehaviour {
var thread:#if ALLOW_MULTITHREADING Thread #else Dynamic #end;
/**
* Script being ran.
*/
@@ -46,13 +40,6 @@ class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBe
script.load();
#if ALLOW_MULTITHREADING
thread = Thread.createWithEventLoop(function() {
// Prevent the thread from being auto deleted
Thread.current().events.promise();
});
#end
__variables = Type.getInstanceFields(Type.getClass(this));
}
@@ -69,7 +56,7 @@ class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBe
public function call(func:String, args:Array<Dynamic>) {
#if ALLOW_MULTITHREADING
thread.events.run(function() {
funkin.backend.utils.ThreadUtil.execAsync(() -> {
callEnded = false;
returnValue = script.call(func, args);
callEnded = true;
@@ -85,13 +72,5 @@ class MultiThreadedScript implements IFlxDestroyable implements IHScriptCustomBe
script.call("destroy");
script.destroy();
}
#if ALLOW_MULTITHREADING
if (thread != null) {
thread.events.runPromised(function() {
// close the thing
});
}
#end
}
}
@@ -102,6 +102,7 @@ class Script extends FlxBasic implements IFlxDestroyable {
#if sys "ZipUtil" => funkin.backend.utils.ZipUtil, #end
"MarkdownUtil" => funkin.backend.utils.MarkdownUtil,
"EngineUtil" => funkin.backend.utils.EngineUtil,
"ThreadUtil" => funkin.backend.utils.ThreadUtil,
"MemoryUtil" => funkin.backend.utils.MemoryUtil,
"BitmapUtil" => funkin.backend.utils.BitmapUtil,
@@ -11,6 +11,7 @@ final class NoteHitEvent extends CancellableEvent {
@:dox(hide) public var unmuteVocals:Bool = true;
@:dox(hide) public var enableCamZooming:Bool = true;
@:dox(hide) public var autoHitLastSustain:Bool = true;
@:dox(hide) public var clipSustain:Bool = true;
/**
* Whenever a miss should be added.
@@ -145,6 +146,13 @@ final class NoteHitEvent extends CancellableEvent {
deleteNote = true;
}
/**
* Prevents the sustain from being cut. Only works if the note is a sustain.
*/
public function preventSustainClip() {
clipSustain = false;
}
/**
* Prevents the vocals volume from being set to 1 after pressing the note.
*/
@@ -30,6 +30,8 @@ class ScriptableShader extends FlxBasic implements IHScriptCustomBehaviour {
if((shader is CustomShader)) scriptName = cast(shader, CustomShader).fileName;
else throw "Missing name for shader script, please provide a scriptName, or use CustomShader";
this.shader = shader;
script = Script.create(Paths.script('shaders/$scriptName'));
script.setParent(shader);
script.set("shader", shader);
+6
View File
@@ -25,6 +25,7 @@ enum Control
CHANGE_MODE;
//CHEAT;
SWITCHMOD;
FPS_COUNTER;
// Debugs
DEV_ACCESS;
@@ -161,6 +162,11 @@ class Controls extends FlxActionSet
@:pressed("switchmod") public var SWITCHMOD_HOLD(get, set): Bool;
@:justReleased("switchmod") public var SWITCHMOD_R(get, set): Bool;
@:gamepad([])
@:justPressed("fps-counter") public var FPS_COUNTER(get, set): Bool;
@:pressed("fps-counter") public var FPS_COUNTER_HOLD(get, set): Bool;
@:justReleased("fps-counter") public var FPS_COUNTER_R(get, set): Bool;
@:devModeOnly
@:gamepad([])
@:justPressed("dev-access") public var DEV_ACCESS(get, set): Bool;
+35 -17
View File
@@ -15,14 +15,18 @@ import lime.utils.AssetType;
*/
@:build(funkin.backend.system.macros.FlagMacro.build())
class Flags {
public static var overridenFlags:Map<String, Bool> = [];
// -- Codename's Addon Config --
@:bypass public static var addonFlags:Map<String, Dynamic> = [];
public static var CURRENT_API_VERSION:Int = 2;
// -- Codename's Mod Config --
public static var MOD_NAME:String = "";
public static var MOD_DESCRIPTION:String = "";
public static var MOD_AUTHOR:String = "";
public static var MOD_API_VERSION:Int = 1;
@:lazy public static var MOD_API_VERSION:Null<Int> = null;
public static var MOD_DOWNLOAD_LINK:String = "";
public static var MOD_DEPENDENCIES:Array<String> = [];
@@ -41,12 +45,11 @@ class Flags {
@:lazy public static var SAVE_PATH:String = haxe.macro.Compiler.getDefine("SAVE_PATH");
@:lazy public static var SAVE_NAME:String = haxe.macro.Compiler.getDefine("SAVE_NAME");
public static var CURRENT_API_VERSION:Int = 1;
public static var COMMIT_NUMBER:Int = GitCommitMacro.commitNumber;
public static var COMMIT_HASH:String = GitCommitMacro.commitHash;
public static var COMMIT_MESSAGE:String = 'Commit $COMMIT_NUMBER ($COMMIT_HASH)';
@:bypass public static var WINDOW_TITLE_USE_MOD_NAME:Bool = false;
@:lazy public static var WINDOW_TITLE_USE_MOD_NAME:Null<Bool> = null;
@:lazy public static var TITLE:String = Application.current.meta.get('name');
@:lazy public static var VERSION:String = Application.current.meta.get('version');
@@ -125,12 +128,16 @@ class Flags {
@:also(funkin.game.PlayState.opponentMode)
public static var DEFAULT_OPPONENT_MODE:Bool = false;
public static var USE_LEGACY_TIMING:Null<Bool> = null;
public static var DEFAULT_NOTE_MS_LIMIT:Float = 1500;
public static var DEFAULT_NOTE_SCALE:Float = 0.7;
#if MODCHARTING_FEATURES
public static var DEFAULT_MODCHART_HOLD_SUBDIVISIONS:Int = 4;
#end
public static var SUSTAINS_AS_ONE_NOTE:Null<Bool> = null;
@:also(funkin.game.Character.FALLBACK_DEAD_CHARACTER)
public static var DEFAULT_GAMEOVER_CHARACTER:String = "bf-dead";
@@ -148,6 +155,8 @@ class Flags {
public static var DEFAULT_HUD_ZOOM_MULT:Float = 0.03;
public static var DEFAULT_CAM_ZOOM_LERP:Float = 0.05;
public static var DEFAULT_HUD_ZOOM_LERP:Float = 0.05;
public static var USE_LEGACY_ZOOM_FACTOR:Null<Bool> = null;
// Font configuration
public static var DEFAULT_FONT:String = "vcr.ttf";
@@ -277,8 +286,6 @@ class Flags {
@:bypass public static var customFlags:Map<String, String> = [];
public static function loadFromData(flags:Map<String, String>, data:String) {
WINDOW_TITLE_USE_MOD_NAME = false;
if (!(data.length > 0)) return;
var res = IniUtil.parseString(data);
@@ -295,26 +302,36 @@ class Flags {
else trace('Invalid section $name');
}
}
if (!flags.exists("WINDOW_TITLE_USE_MOD_NAME")) WINDOW_TITLE_USE_MOD_NAME = !flags.exists('TITLE') && flags.exists('MOD_NAME');
else WINDOW_TITLE_USE_MOD_NAME = parseBool(flags.get("WINDOW_TITLE_USE_MOD_NAME"));
flags.remove("WINDOW_TITLE_USE_MOD_NAME");
}
public static function loadFromDatas(datas:Array<String>) {
private static function loadPost() {
if (MOD_API_VERSION == null) MOD_API_VERSION = CURRENT_API_VERSION;
if (WINDOW_TITLE_USE_MOD_NAME == null) WINDOW_TITLE_USE_MOD_NAME = !overridenFlags.exists('TITLE') && overridenFlags.exists('MOD_NAME');
if (USE_LEGACY_TIMING == null) USE_LEGACY_TIMING = MOD_API_VERSION < 2;
if (USE_LEGACY_ZOOM_FACTOR == null) USE_LEGACY_ZOOM_FACTOR = MOD_API_VERSION < 2;
if (SUSTAINS_AS_ONE_NOTE == null) SUSTAINS_AS_ONE_NOTE = MOD_API_VERSION >= 2;
}
public static function loadFromDatas(datas:Array<String>):Map<String, String> {
var flags:Map<String, String> = [];
for(data in datas) {
if(data != null)
for (data in datas) {
if (data != null)
loadFromData(flags, data);
}
loadPost();
return flags;
}
public static function parseFlags(flags:Map<String, String>) {
for(name=>value in flags)
if(!parse(name, value))
customFlags.set(name, value);
var parsed:Bool;
for (name => value in flags) switch (name) {
case "MOD_API_VERSION":
var version = Std.parseInt(value) ?? CURRENT_API_VERSION;
if (version > MOD_API_VERSION || MOD_API_VERSION == null) MOD_API_VERSION = version;
default:
if (!(parsed = parse(name, value))) customFlags.set(name, value);
if (!overridenFlags.exists(name)) overridenFlags.set(name, parsed);
}
Options.modchartingHoldSubdivisions = DEFAULT_MODCHART_HOLD_SUBDIVISIONS;
}
@@ -365,5 +382,6 @@ class Flags {
parseFlags(flags);
}
}
loadPost();
}
}
}
+19 -27
View File
@@ -1,7 +1,5 @@
package funkin.backend.system;
import sys.io.File;
import sys.FileSystem;
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.TransitionData;
@@ -9,21 +7,22 @@ import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.ui.FlxSoundTray;
import funkin.backend.assets.AssetSource;
import funkin.backend.assets.AssetsLibraryList;
import funkin.backend.assets.ModsFolder;
import funkin.backend.assets.AssetSource;
import funkin.backend.system.framerate.Framerate;
import funkin.backend.system.framerate.SystemInfo;
import funkin.backend.system.modules.*;
import funkin.backend.utils.ThreadUtil;
import funkin.editors.SaveWarning;
import funkin.options.PlayerSettings;
import openfl.Assets;
import openfl.Lib;
import openfl.display.Sprite;
import openfl.text.TextFormat;
import openfl.utils.AssetLibrary;
#if ALLOW_MULTITHREADING
import sys.thread.Thread;
#end
import sys.FileSystem;
import sys.io.File;
#if android
import android.content.Context;
import android.os.Build;
@@ -40,7 +39,7 @@ class Main extends Sprite
public static var scaleMode:FunkinRatioScaleMode;
#if !mobile
public static var framerateSprite:funkin.backend.system.framerate.Framerate;
public static var framerateSprite:Framerate;
#end
var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels).
@@ -58,10 +57,6 @@ class Main extends Sprite
// You can pretty much ignore everything from here on - your code should go in your states.
#if ALLOW_MULTITHREADING
public static var gameThreads:Array<Thread> = [];
#end
public static function preInit() {
funkin.backend.utils.NativeAPI.registerAsDPICompatible();
funkin.backend.system.CommandLineHandler.parseCommandLine(Sys.args());
@@ -79,7 +74,7 @@ class Main extends Sprite
addChild(game = new FunkinGame(gameWidth, gameHeight, MainState, Options.framerate, Options.framerate, skipSplash, startFullscreen));
#if (!mobile && !web)
addChild(framerateSprite = new funkin.backend.system.framerate.Framerate());
addChild(framerateSprite = new Framerate());
SystemInfo.init();
#end
}
@@ -97,16 +92,8 @@ class Main extends Sprite
#end;
public static var startedFromSource:Bool = #if TEST_BUILD true #else false #end;
private static var __threadCycle:Int = 0;
public static function execAsync(func:Void->Void) {
#if ALLOW_MULTITHREADING
var thread = gameThreads[(__threadCycle++) % gameThreads.length];
thread.events.run(func);
#else
func();
#end
}
// DEPRECATED
@:dox(hide) public static function execAsync(func:Void->Void) ThreadUtil.execAsync(func);
private static function getTimer():Int {
return time = Lib.getTimer();
@@ -118,10 +105,6 @@ class Main extends Sprite
MemoryUtil.init();
@:privateAccess
FlxG.game.getTimer = getTimer;
#if ALLOW_MULTITHREADING
for(i in 0...4)
gameThreads.push(Thread.createWithEventLoop(function() {Thread.current().events.promise();}));
#end
FunkinCache.init();
Paths.assetsTree = new AssetsLibraryList();
@@ -156,6 +139,7 @@ class Main extends Sprite
FlxG.signals.focusGained.add(onFocus);
FlxG.signals.preStateSwitch.add(onStateSwitch);
FlxG.signals.postStateSwitch.add(onStateSwitchPost);
FlxG.signals.postUpdate.add(onUpdate);
FlxG.mouse.useSystemCursor = true;
#if DARK_MODE_WINDOW
@@ -210,6 +194,14 @@ class Main extends Sprite
scaleMode.resetSize();
}
public static function onUpdate() {
if (PlayerSettings.solo.controls.DEV_CONSOLE)
NativeAPI.allocConsole();
if (PlayerSettings.solo.controls.FPS_COUNTER)
Framerate.debugMode = (Framerate.debugMode + 1) % 3;
}
private static function onStateSwitchPost() {
// manual asset clearing since base openfl one does'nt clear lime one
// does'nt clear bitmaps since flixel fork does it auto
@@ -8,12 +8,20 @@ import funkin.backend.assets.IModsAssetLibrary;
import funkin.backend.assets.ScriptedAssetLibrary;
class AssetTreeInfo extends FramerateCategory {
private var lastUpdateTime:Float = 1;
public function new() {
super("Asset Libraries Tree Info");
}
public override function __enterFrame(t:Int) {
if (alpha <= 0.05) return;
if ((lastUpdateTime += FlxG.rawElapsed) < 1)
return;
lastUpdateTime = 0;
var text = 'Not initialized yet\n';
if (Paths.assetsTree != null){
text = "";
@@ -53,13 +53,6 @@ class Framerate extends Sprite {
x = 10;
y = 2;
FlxG.stage.addEventListener(KeyboardEvent.KEY_UP, function(e:KeyboardEvent) {
switch(e.keyCode) {
case #if web Keyboard.NUMBER_3 #else Keyboard.F3 #end: // 3 on web or F3 on windows, linux and other things that runs code
debugMode = (debugMode + 1) % 3;
}
});
if (__bitmap == null)
__bitmap = new BitmapData(1, 1, 0xFF000000);
@@ -9,6 +9,13 @@ class FramerateCounter extends Sprite {
public var fpsLabel:TextField;
public var lastFPS:Float = 0;
private var frameCount:Int = 0;
private var accumulatedTime:Float = openfl.Lib.getTimer();
private final updateInterval:Float = 1 / 15;
private var lastUpdateTime:Float = 0;
public function new() {
super();
@@ -27,15 +34,40 @@ class FramerateCounter extends Sprite {
}
}
public function reload() {}
public function reload() {
lastUpdateTime = 0;
}
public override function __enterFrame(t:Int) {
if (alpha <= 0.05) return;
super.__enterFrame(t);
lastFPS = CoolUtil.fpsLerp(lastFPS, FlxG.rawElapsed == 0 ? 0 : (1 / FlxG.rawElapsed), 0.25);
fpsNum.text = Std.string(Math.floor(lastFPS));
frameCount++;
if ((lastUpdateTime += FlxG.rawElapsed) < updateInterval)
{
updateLabelPosition();
return;
}
final timer = openfl.Lib.getTimer();
final time = timer - accumulatedTime;
accumulatedTime = timer;
lastFPS = FlxMath.lerp(lastFPS, time <= 0 ? 0 : (1000 / time * frameCount), 1.0 - Math.pow(0.75, time * 0.06));
fpsNum.text = Std.string(Math.round(lastFPS));
lastUpdateTime = frameCount = 0;
updateLabelPosition();
}
private inline function updateLabelPosition():Void
{
fpsLabel.x = fpsNum.x + fpsNum.width;
fpsLabel.y = (fpsNum.y + fpsNum.height) - fpsLabel.height;
}
}
}
@@ -36,11 +36,21 @@ class MemoryCounter extends Sprite {
if (alpha <= 0.05) return;
super.__enterFrame(t);
memory = MemoryUtil.currentMemUsage();
final mem = MemoryUtil.currentMemUsage();
if (mem == memory) {
updateLabelPosition();
return;
}
memory = mem;
if (memoryPeak < memory) memoryPeak = memory;
memoryText.text = CoolUtil.getSizeString(memory);
memoryPeakText.text = ' / ${CoolUtil.getSizeString(memoryPeak)}';
memoryPeakText.x = memoryText.x + memoryText.width;
updateLabelPosition();
}
private inline function updateLabelPosition():Void
memoryPeakText.x = memoryText.x + memoryText.width;
}
@@ -115,8 +115,7 @@ class SystemInfo extends FramerateCategory {
if (vRAMBytes == 1000 || vRAMBytes == 1 || vRAMBytes <= 0)
Logs.trace('Unable to grab GPU VRAM', ERROR, RED);
else {
var vRAMBytesFloat:#if cpp Float64 #else Float #end = vRAMBytes*1024;
vRAM = CoolUtil.getSizeString64(vRAMBytesFloat);
vRAM = getSizeString(vRAMBytes / 1024);
}
}
} else
@@ -155,6 +154,17 @@ class SystemInfo extends FramerateCategory {
if (totalMem != "Unknown" && memType != "Unknown") __formattedSysText += '\nTotal MEM: $totalMem $memType';
}
static function getSizeString(size:Float):String {
if (size < 1024)
return Std.int(size) + " MB";
else if (size < 1024 * 1024)
return Std.int(size / 1024) + " GB";
else {
var tb = size / (1024 * 1024);
return Std.int(tb) + "." + CoolUtil.addZeros(Std.string(Std.int((tb % 1) * 100)), 2) + " TB";
}
}
public function new() {
super("System Info");
}
@@ -58,6 +58,11 @@ class Macros {
final macroPath = 'funkin.backend.system.macros.Macros';
Compiler.addMetadata('@:build($macroPath.buildLimeAssetLibrary())', 'lime.utils.AssetLibrary');
//Adds Compat for #if hscript blocks when you have hscript improved
if (Context.defined("hscript_improved") && !Context.defined("hscript")) {
Compiler.define('hscript');
}
}
public static function buildLimeAssetLibrary():Array<Field> {
@@ -2,22 +2,30 @@ package funkin.backend.system.updating;
import funkin.backend.system.github.GitHub;
import funkin.backend.system.github.GitHubRelease;
#if ALLOW_MULTITHREADING
import funkin.backend.utils.ThreadUtil;
#end
import lime.app.Application;
import sys.thread.Mutex;
import sys.thread.Thread;
import sys.FileSystem;
import haxe.io.Path;
#if (target.threaded)
import sys.thread.Thread;
import sys.thread.Mutex;
#end
using funkin.backend.system.github.GitHub;
class UpdateUtil {
public static var lastUpdateCheck:Null<UpdateCheckCallback>;
#if (target.threaded)
private static var __waitCallbacks:Array<UpdateCheckCallback->Void>;
private static var __mutex:Mutex;
#end
public static function init() {
// deletes old bak file if it exists
@@ -26,26 +34,34 @@ class UpdateUtil {
if (FileSystem.exists(bakPath)) FileSystem.deleteFile(bakPath);
#end
#if (target.threaded)
__waitCallbacks = [];
__mutex = new Mutex();
Thread.create(checkForUpdates.bind(true, false));
#if ALLOW_MULTITHREADING ThreadUtil.execAsync #else Thread.create #end(checkForUpdates.bind(true, false));
#end
}
public static function waitForUpdates(force = false, callback:UpdateCheckCallback->Void, lazy = false) {
#if (target.threaded)
if (__mutex.tryAcquire()) {
__mutex.release();
if (__shouldCheck(lazy) || force) {
__waitCallbacks.push(callback);
Thread.create(checkForUpdates.bind(force, false));
#if ALLOW_MULTITHREADING ThreadUtil.execAsync #else Thread.create #end(checkForUpdates.bind(force, false));
}
else
callback(lastUpdateCheck);
}
else
__waitCallbacks.push(callback);
#else
callback(checkForUpdates(true, false));
#end
}
public static function checkForUpdates(force = false, lazy = false):UpdateCheckCallback {
#if (target.threaded)
var wasAcquired = !__mutex.tryAcquire();
if (wasAcquired) __mutex.acquire();
@@ -60,8 +76,19 @@ class UpdateUtil {
FlxG.signals.preUpdate.addOnce(__callWaitCallbacks);
return lastUpdateCheck;
#else
if (!__shouldCheck(lazy)) return lastUpdateCheck;
return lastUpdateCheck = __checkForUpdates();
#end
}
#if (target.threaded)
static function __callWaitCallbacks() {
for (callback in __waitCallbacks) callback(lastUpdateCheck);
__waitCallbacks.resize(0);
}
#end
static function __checkForUpdates():UpdateCheckCallback {
var curTag = 'v' + (Flags.VERSION == null ? Application.current.meta.get('version') : Flags.VERSION), error = false;
var newUpdates = __doReleaseFiltering(GitHub.getReleases(Flags.REPO_OWNER, Flags.REPO_NAME, (e) -> {
@@ -82,11 +109,6 @@ class UpdateUtil {
static function __shouldCheck(lazy:Bool):Bool
return lastUpdateCheck == null || !lazy && (!lastUpdateCheck.newUpdate || Date.now().getTime() - lastUpdateCheck.date.getTime() > 1800000);
static function __callWaitCallbacks() {
for (callback in __waitCallbacks) callback(lastUpdateCheck);
__waitCallbacks.resize(0);
}
static function __doReleaseFiltering(releases:Array<GitHubRelease>, currentVersionTag:String) {
releases = releases.filterReleases(Options.betaUpdates, false);
if (releases.length <= 0)
+20 -20
View File
@@ -20,8 +20,7 @@ typedef AudioAnalyzerCallback = Int->Int->Void;
* An utility that analyze FlxSounds,
* can be used to make waveform or real-time audio visualizer.
*
* FlxSound.amplitude does work in CNE so if any case if your only checking for peak of current
* time, use that instead.
* FlxSound.amplitude works so if any case if your only checking for peak of current time, use that instead.
*/
final class AudioAnalyzer {
/**
@@ -96,9 +95,9 @@ final class AudioAnalyzer {
static var __twiddleImags:Array<Array<Float>> = [];
static var __freqReals:Array<Array<Float>> = [];
static var __freqImags:Array<Array<Float>> = [];
static var __freqCalculating:Int = 0;
#if (target.threaded)
static var __mutex:Mutex = new Mutex();
static var __freqCalculating:Int = 0;
#end
/**
@@ -296,7 +295,7 @@ final class AudioAnalyzer {
__check();
}
function __check() if (sound.buffer != buffer) {
function __check() if (sound != null && sound.buffer != buffer) {
byteSize = 1 << ((buffer = sound.buffer).bitsPerSample - 1);
#if (lime_cffi && lime_vorbis)
@@ -324,7 +323,7 @@ final class AudioAnalyzer {
* @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended).
* @return Output of levels/bars that ranges from 0 to 1.
*/
public function getLevels(startPos:Float, ?volume:Float, barCount:Int, ?levels:Array<Float>, ?ratio:Float, ?minDb:Float, ?maxDb:Float, ?minFreq:Float, ?maxFreq:Float):Array<Float>
public function getLevels(?startPos:Float, ?volume:Float, barCount:Int, ?levels:Array<Float>, ?ratio:Float, ?minDb:Float, ?maxDb:Float, ?minFreq:Float, ?maxFreq:Float):Array<Float>
return inline getLevelsFromFrequencies(__frequencies = getFrequencies(startPos, volume, __frequencies), buffer.sampleRate, barCount, levels, ratio, minDb, maxDb, minFreq, maxFreq);
/**
@@ -334,8 +333,8 @@ final class AudioAnalyzer {
* @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional).
* @return Output of frequencies.
*/
public function getFrequencies(startPos:Float, ?volume:Float, ?frequencies:Array<Float>):Array<Float>
return inline getFrequenciesFromSamples(__freqSamples = getSamples(startPos, fftN, true, -1, volume, __freqSamples), fftN, useWindowingFFT, frequencies);
public function getFrequencies(?startPos:Float, ?volume:Float, ?frequencies:Array<Float>):Array<Float>
return inline getFrequenciesFromSamples(__freqSamples = getSamples(startPos != null ? startPos : sound.time, fftN, true, -1, volume, __freqSamples), fftN, useWindowingFFT, frequencies);
/**
* Analyzes an attached FlxSound from startPos to endPos in milliseconds to get the amplitudes.
@@ -467,36 +466,37 @@ final class AudioAnalyzer {
@:privateAccess return sound._source != null && sound._source.__backend != null && sound._source.__backend.playing;
inline function __readStream(startPos:Float, endPos:Float, callback:AudioAnalyzerCallback):Float @:privateAccess {
var backend = sound._source.__backend;
var i = backend.bufferSizes.length - backend.queuedBuffers;
var time = backend.bufferTimes[i] * 1000;
final backend = sound._source.__backend;
// TODO: Wrap it with try until i figured it out an effective way to do this...
// So... sometimes it just uses the decoder even if it looks good?? please help
var n = Math.floor((endPos - startPos) * __toBits);
if (startPos >= time && startPos < backend.bufferTimes[backend.bufferSizes.length - 1] * 1000) {
var pos = Math.floor((startPos - time) * __toBits), buf = backend.bufferDatas[i].buffer, size = backend.bufferSizes[i], c = 0;
var i = backend.bufferLengths.length - backend.requestBuffers - 1, time:Float;
while (++i < backend.bufferLengths.length) if (startPos >= (time = backend.bufferTimes[i] * 1000)) {
var pos = Math.floor((startPos - time) * __toBits), buf = backend.bufferDatas[i].buffer, size = backend.bufferLengths[i], c = 0;
while (pos >= size) {
if (++i >= backend.bufferSizes.length) {
n = 0;
break;
}
if (++i >= backend.bufferLengths.length) break;
pos -= size;
buf = backend.bufferDatas[i].buffer;
size = backend.bufferSizes[i];
size = backend.bufferLengths[i];
}
pos -= pos % __sampleSize;
if (i >= backend.bufferLengths.length) break;
if ((pos -= pos % __sampleSize) < 0) pos = 0;
n -= pos % __sampleSize;
while (n > 0) {
callback(getByte(buf, pos, __wordSize), c);
if (++c > buffer.channels) c = 0;
if ((pos += __wordSize) >= size) {
if (++i >= backend.bufferSizes.length) break;
if (++i >= backend.bufferLengths.length) break;
pos = 0;
buf = backend.bufferDatas[i].buffer;
size = backend.bufferSizes[i];
size = backend.bufferLengths[i];
}
n -= __wordSize;
}
break;
}
return endPos - (n / __toBits);
@@ -38,7 +38,7 @@ class FunkinParentDisabler extends FlxBasic {
for(c in __cameras) c.paused = true;
// sounds
__sounds = [for(s in FlxG.sound.list) if (s.playing) s];
__sounds = [for(s in FlxG.sound.list) if (s.playing && !s.persist) s];
for(s in __sounds) s.pause();
}
}
@@ -71,4 +71,4 @@ class FunkinParentDisabler extends FlxBasic {
for(s in __sounds) s.play();
}
}
}
}
+84 -19
View File
@@ -1,32 +1,97 @@
package funkin.backend.utils;
#if ALLOW_MULTITHREADING
#if (target.threaded)
import sys.thread.Deque;
import sys.thread.Thread;
import sys.thread.Mutex;
#else
private typedef Thread = Dynamic;
#end
#if !macro
import funkin.backend.system.Logs;
#end
final class ThreadUtil {
inline static function error(text:String) {
#if macro
trace(text);
#else
FlxG.signals.preUpdate.addOnce(Logs.error.bind(text));
#end
}
/**
* Creates a new Thread with an error handler.
* @param func Function to execute
* @param autoRestart Whenever the thread should auto restart itself after crashing.
*/
public static function createSafe(func:Void->Void, autoRestart:Bool = false) {
if (autoRestart) {
return sys.thread.Thread.create(function() {
while(true) {
try {
func();
} catch(e) {
trace(e.details());
}
}
});
} else {
return sys.thread.Thread.create(function() {
try {
public static function createSafe(func:Void->Void, autoRestart:Bool = false):Thread {
#if (target.threaded)
try {
return if (autoRestart) Thread.create(() -> {
var restart = true;
while (restart) try {
func();
} catch(e) {
trace(e.details());
restart = false;
}
catch (e) error(e.details());
})
else Thread.create(() -> {
try {func();}
catch (e) error(e.details());
});
}
catch (e) error("Failed to safely create a thread: " + e.details());
#end
return null;
}
}
#end
#if ALLOW_MULTITHREADING
public static var maxThreads:Int = 4;
static var __threads:Array<Thread> = [];
static var __pendingExecs:Deque<Void->Void> = new Deque();
static var __threadMutex:Mutex = new Mutex();
static var __threadUsed:Int = 0;
static function __threadExecAsync() {
var callback:Void->Void;
while ((callback = __pendingExecs.pop(true)) != null) {
__threadMutex.acquire();
__threadUsed++;
__threadMutex.release();
callback();
__threadMutex.acquire();
__threadUsed--;
__threadMutex.release();
}
__threadMutex.acquire();
__threads.remove(Thread.current());
__threadMutex.release();
}
#end
public static function execAsync(func:Void->Void) {
if (func == null) return;
#if (ALLOW_MULTITHREADING && !macro)
__pendingExecs.add(func);
if (__threadUsed >= __threads.length) {
if (__threads.length == maxThreads) return;
__threadMutex.acquire();
try {
var thread = Thread.create(__threadExecAsync);
__threads.push(thread);
}
catch (e) Logs.warn(e.details());
__threadMutex.release();
}
#else
func();
#end
}
}
@@ -204,6 +204,9 @@ final class Windows {
freopen("CONIN$", "r", stdin);
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
SetConsoleOutputCP(65001);
SetConsoleCP(65001);
')
public static function allocConsole() {
}
+2 -1
View File
@@ -41,7 +41,8 @@ class Week {
weekObj.songs.push({
name: name,
hide: song.getAtt('hide').getDefault('false') == "true",
displayName: song.getAtt('displayName')
displayName: song.getAtt('displayName'),
variation: song.getAtt('variation')
});
} catch(e) {
Logs.trace('Song at index ${k} in week $weekName cannot contain any other XML nodes in its name.', WARNING);
+2 -2
View File
@@ -9,7 +9,7 @@ class ModConfigWarning extends UIState {
var library:ModsFolderLibrary = null;
var goToState:Class<FlxState>;
public static inline var defaultModConfigText =
public static var defaultModConfigText =
'[Common] # This section applies the \'MOD_\' prefix to the flags so you don\'t have to.
NAME="YOUR MOD NAME HERE"
DESCRIPTION="YOUR MOD DESCRIPTION HERE"
@@ -17,7 +17,7 @@ AUTHOR="YOU/YOUR TEAM HERE"
VERSION="YOUR MOD\'S VERSION HERE"
# DO NOT EDIT!! this is used to check for version compatibility!
API_VERSION=1
API_VERSION=${Flags.CURRENT_API_VERSION}
DOWNLOAD_LINK="YOUR MOD PAGE LINK HERE"
@@ -104,6 +104,7 @@ class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> {
if (character.getAnimName() == button.anim)
@:privateAccess CharacterEditor.instance._animation_down(null);
character.ghosts.remove(button.anim);
character.removeAnimation(button.anim);
if (character.animOffsets.exists(button.anim)) character.animOffsets.remove(button.anim);
if (character.animDatas.exists(button.anim)) character.animDatas.remove(button.anim);
@@ -37,7 +37,7 @@ class CharacterGhost extends Character {
alpha = 0.4; color = 0xFFAEAEAE;
var flxanim:FlxAnimation = animation._animations.get(anim);
var frameIndex:Int = flxanim.frames.getDefault([0])[0];
var frameIndex:Int = flxanim.frames.getDefault([0])[flxanim.frames.length - 1];
frame = frames.frames[frameIndex];
setAnimOffset(anim);
+4 -3
View File
@@ -80,6 +80,7 @@ class Charter extends UIState {
public var metronome:FlxSound;
public var vocals:FlxSound;
public var voicesMuted:Bool = false;
public var quant:Int = 16;
public var quants:Array<Int> = [4, 8, 12, 16, 20, 24, 32, 48, 64, 192]; // different quants
@@ -1812,9 +1813,9 @@ class Charter extends UIState {
t.icon = 1 - Std.int(Math.ceil(FlxG.sound.music.volume));
}
function _song_mutevoices(t) {
vocals.volume = vocals.volume > 0 ? 0 : 1;
for (strumLine in strumLines.members) strumLine.vocals.volume = strumLine.vocals.volume > 0 ? 0 : 1;
t.icon = 1 - Std.int(Math.ceil(vocals.volume));
vocals.volume = (voicesMuted = !voicesMuted) ? 0 : 1;
for (strumLine in strumLines.members) strumLine.updateVoicesVolume();
t.icon = voicesMuted ? 1 : 0;
}
function _playback_back(_) {
if (FlxG.sound.music.playing) return;
+42 -5
View File
@@ -82,7 +82,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
}
/**
* Pack data is a list of 4 strings separated by `________PACKSEP________`
* Pack data is a list of 5 strings separated by `________PACKSEP________`
* [0] Event Name
* [1] Event Script
* [2] Event JSON Info
@@ -276,10 +276,47 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
}
case "Camera Movement":
// camera movement, use health icon
if(event.params != null) {
var icon = getIconFromStrumline(event.params[0]);
if(icon != null) return icon;
var shouldDoArrow:Bool = false;
var icon:Null<FlxSprite> = null;
if (event.params != null) {
shouldDoArrow = event.params[1] && event.params[3] != "CLASSIC"; // is Tweened and isnt Lerped
icon = getIconFromStrumline(event.params[0]); // camera movement, use health icon
}
if (icon == null) icon = generateDefaultIcon(event.name);
if(event.params != null && shouldDoArrow && !inMenu) {
var group = new EventIconGroup();
group.add(icon);
group.members[0].x -= 8;
group.members[0].y -= 8;
generateEventIconDurationArrow(group, event.params[2]);
return group;
} else
return icon;
case "Camera Position":
var shouldDoArrow:Bool = false;
if (event.params != null)
shouldDoArrow = event.params[2] && event.params[4] != "CLASSIC"; // is Tweened and isnt Lerped
if(event.params != null && shouldDoArrow && !inMenu) {
var group = new EventIconGroup();
group.add(generateDefaultIcon(event.name));
generateEventIconDurationArrow(group, event.params[3]);
return group;
}
case "Camera Zoom":
var shouldDoArrow:Bool = false;
if (event.params != null)
shouldDoArrow = event.params[0];
if(event.params != null && shouldDoArrow && !inMenu) {
var group = new EventIconGroup();
group.add(generateDefaultIcon(event.name));
generateEventIconDurationArrow(group, event.params[3]);
return group;
}
}
return generateDefaultIcon(event.name);
@@ -17,7 +17,7 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> {
if (autoSort && members.length != __lastSort)
sortEvents();
eventsRowText.y = FlxMath.lerp(eventsRowText.y, -40 + (members[0] != null ? Math.min(members[0].y, 0) : 0), 1/20);
eventsRowText.y = FlxMath.lerp(eventsRowText.y, -40 + (members[0] != null ? Math.min(members[0].y, 0) : 0), 1/10);
}
public override function remove(v:CharterEvent, force:Bool = true):CharterEvent {
@@ -40,7 +40,7 @@ class CharterSelectionScreen extends EditorTreeMenuScreen {
var screen = new EditorTreeMenuScreen((first || !isVariant) ? (s.name + (isVariant ? ' (${s.variant})' : '')) : s.variant, getID('selectDifficulty'));
for (d in s.difficulties) if (d != '') screen.add(makeChartOption(d, isVariant ? s.variant : null, s.name));
screen.add(new Separator());
if (s.difficulties.length > 0 && s.variants.length > 0) screen.add(new Separator()); // Create a separator only when there are both difficulty and variant options available.
for (v in s.variants) if (s.metas.get(v) != null) screen.add(makeVariationOption(s.metas.get(v)));
#if sys
@@ -48,13 +48,13 @@ class CharterSelectionScreen extends EditorTreeMenuScreen {
parent.openSubState(new ChartCreationScreen(saveChart));
}));
if (!first) screen.curSelected = 1;
if (!first) screen.curSelected = (s.difficulties.length + s.variants.length) > 0 ? 1 : 0;
else {
cast(screen.members[0], NewOption).itemHeight = 120;
screen.insert(1, new NewOption(getID('newVariation'), getID('newVariationDesc'), () -> {
parent.openSubState(new VariationCreationScreen(s, saveSong));
}));
screen.curSelected = 2;
screen.curSelected = (s.difficulties.length + s.variants.length) > 0 ? 2 : 1;
}
#end
@@ -22,6 +22,7 @@ class CharterStrumline extends UISprite {
public var curMenu:UIContextMenu = null;
public var vocals:FlxSound;
public var voicesMuted:Bool = false;
public var keyCount:Int = 4;
public var startingID(get, null):Int;
@@ -147,6 +148,10 @@ class CharterStrumline extends UISprite {
}
vocals.group = FlxG.sound.defaultMusicGroup;
}
public function updateVoicesVolume() {
vocals.volume = (voicesMuted || (Charter.instance?.voicesMuted ?? false)) ? 0 : 1;
}
}
class CharterStrumlineOptions extends UITopMenuButton {
@@ -176,9 +181,10 @@ class CharterStrumlineOptions extends UITopMenuButton {
{
label: TU.translate("charter.strumLine.muteVocals"),
onSelect: function(_) {
strLine.vocals.volume = strLine.vocals.volume > 0 ? 0 : 1;
strLine.voicesMuted = !strLine.voicesMuted;
strLine.updateVoicesVolume();
},
icon: strLine.vocals.volume > 0 ? 0 : 1
icon: strLine.voicesMuted ? 1 : 0
},
null,
{
@@ -435,7 +435,7 @@ class SongCreationScreen extends UISubstateWindow {
color: colorWheel.curColor,
opponentModeAllowed: opponentModeCheckbox.checked,
coopAllowed: coopAllowedCheckbox.checked,
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()]
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) if (diff.length > 0) diff.trim()]
});
if (onSave != null) onSave({
+12 -4
View File
@@ -108,11 +108,19 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
}
public function swapLeftRightAnimations() {
CoolUtil.switchAnimFrames(animation.getByName('singRIGHT'), animation.getByName('singLEFT'));
CoolUtil.switchAnimFrames(animation.getByName('singRIGHTmiss'), animation.getByName('singLEFTmiss'));
// Find all "alternate" poses
var variants = ['']; // Pre-fill with empty string
var pose = 'singRIGHT'; // Any "sing" animation string could work, really
for (a in xml.nodes.anim) {
if (a.att.name != pose && StringTools.startsWith(a.att.name, pose)) {
variants.push(a.att.name.substring(pose.length));
}
}
switchOffset('singLEFT', 'singRIGHT');
switchOffset('singLEFTmiss', 'singRIGHTmiss');
for (i in variants) {
CoolUtil.switchAnimFrames(animation.getByName('singRIGHT$i'), animation.getByName('singLEFT$i'));
switchOffset('singLEFT$i', 'singRIGHT$i');
}
__swappedLeftRightAnims = true;
}
+15 -2
View File
@@ -60,6 +60,15 @@ class Note extends FlxSprite
*/
public var sustainParent:Null<Note>;
/**
* Number of active sustain pieces attached to this note
*
* Increases by 1 every time a hold piece is initialized.
*
* Decreases by 1 every time a hold piece gets destroyed.
*/
public var tailCount:Int = 0;
/**
* Name of the splash.
*/
@@ -74,6 +83,7 @@ class Note extends FlxSprite
public var sustainLength:Float = 0;
public var isSustainNote:Bool = false;
public var noSustainClip:Bool = false;
public var flipSustain:Bool = true;
public var noteTypeID:Int = 0;
@@ -104,6 +114,8 @@ class Note extends FlxSprite
public var animSuffix:String = null;
// Deprecated?
@:dox(hide) public var tripTimer:Float = 0; // ranges from 0 to 1
private static function customTypePathExists(path:String) {
if (__customNoteTypeExists.exists(path))
@@ -211,6 +223,7 @@ class Note extends FlxSprite
public var lastScrollSpeed:Null<Float> = null;
public var gapFix:SingleOrFloat = 0;
public var useAntialiasingFix(get, set):Bool;
inline function set_useAntialiasingFix(v:Bool) {
if(v != useAntialiasingFix) {
gapFix = v ? 1 : 0;
@@ -297,8 +310,8 @@ class Note extends FlxSprite
updateSustainClip();
}
public function updateSustainClip() if (wasGoodHit) {
var t = FlxMath.bound((Conductor.songPosition - strumTime) / height * 0.45 * lastScrollSpeed, 0, 1);
public function updateSustainClip() if (wasGoodHit && !noSustainClip) {
var t = CoolUtil.bound((Conductor.songPosition - strumTime) / height * 0.45 * lastScrollSpeed, 0, 1);
var rect = clipRect == null ? FlxRect.get() : clipRect;
clipRect = rect.set(0, frameHeight * t, frameWidth, frameHeight * (1 - t));
}
+9 -14
View File
@@ -53,14 +53,11 @@ class NoteGroup extends FlxTypedGroup<Note> {
public override function update(elapsed:Float) {
i = length-1;
__loopSprite = null;
__time = __getSongPos();
__time = __getSongPos() + limit;
while(i >= 0) {
__loopSprite = members[i--];
if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.active) {
continue;
}
if (__loopSprite.strumTime - __time > limit)
break;
if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.active) continue;
if (__loopSprite.strumTime > __time) break;
__loopSprite.update(elapsed);
}
}
@@ -74,12 +71,11 @@ class NoteGroup extends FlxTypedGroup<Note> {
i = length-1;
__loopSprite = null;
__time = __getSongPos();
__time = __getSongPos() + limit;
while(i >= 0) {
__loopSprite = members[i--];
if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.visible)
continue;
if (__loopSprite.strumTime - __time > limit) break;
if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.visible) continue;
if (__loopSprite.strumTime > __time) break;
__loopSprite.draw();
}
__currentlyLooping = oldCur;
@@ -97,16 +93,15 @@ class NoteGroup extends FlxTypedGroup<Note> {
public override function forEach(noteFunc:Note->Void, recursive:Bool = false) {
i = length-1;
__loopSprite = null;
__time = __getSongPos();
__time = __getSongPos() + limit;
var oldCur = __currentlyLooping;
__currentlyLooping = true;
while(i >= 0) {
__loopSprite = members[i--];
if (__loopSprite == null || !__loopSprite.exists)
continue;
if (__loopSprite.strumTime - __time > limit) break;
if (__loopSprite == null || !__loopSprite.exists) continue;
if (__loopSprite.strumTime > __time) break;
noteFunc(__loopSprite);
}
__currentlyLooping = oldCur;
+97 -48
View File
@@ -29,6 +29,8 @@ import funkin.editors.charter.Charter;
import funkin.editors.charter.CharterSelection;
import funkin.game.SplashHandler;
import funkin.game.cutscenes.*;
import funkin.game.scoring.*;
import funkin.game.scoring.RatingManager.Rating;
import funkin.menus.*;
import funkin.backend.week.WeekData;
import funkin.savedata.FunkinSave;
@@ -330,6 +332,10 @@ class PlayState extends MusicBeatState
* The total accuracy amount.
*/
public var totalAccuracyAmount:Float = 0;
/**
* Tracks how much of each rating was received.
*/
public var hits:Map<String, Int> = [];
/**
* FunkinText that shows your score.
@@ -362,6 +368,11 @@ class PlayState extends MusicBeatState
public static var campaignAccuracyTotal:Float = 0;
public static var campaignAccuracyCount:Float = 0;
/**
* Number of each rating received for the current week.
*/
public static var campaignHits:Map<String, Int> = [];
/**
* Camera zoom at which the game lerps to.
*/
@@ -529,6 +540,10 @@ class PlayState extends MusicBeatState
* Group containing all of the combo sprites.
*/
public var comboGroup:RotatingSpriteGroup;
/**
* Manager that helps judge note hits to return ratings.
*/
public var ratingManager:RatingManager = new RatingManager();
/**
* Whenever the Rating sprites should be shown or not.
*
@@ -551,10 +566,11 @@ class PlayState extends MusicBeatState
public var noteTypesArray:Array<String> = [null];
/**
* Hit window, in milliseconds. Defaults to 250ms unless changed in options.
* Base game hit window is 175ms.
* Hit window, in milliseconds. A Legacy CNE Hit window configuration,
* Don't use this, it's for mods that still uses the old judgement timing, instead use ratingManager.
*/
public var hitWindow:Float = Options.hitWindow; // is calculated in create(), is safeFrames in milliseconds.
public var hitWindow:Float = Options.hitWindow;
@:noCompletion @:dox(hide) private var _legacyRating:Rating = {name: "", window: 0, accuracy: 0, score: 0};
@:noCompletion @:dox(hide) private var _startCountdownCalled:Bool = false;
@:noCompletion @:dox(hide) private var _endSongCalled:Bool = false;
@@ -673,6 +689,8 @@ class PlayState extends MusicBeatState
detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay";
for (rating in [for (i in ratingManager.ratingData) i.name]) hits.set(rating, 0); // Ensure all keys exist as to prevent null errors.
// Checks if cutscene files exists
var cutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene');
var endCutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene-end');
@@ -1237,12 +1255,13 @@ class PlayState extends MusicBeatState
}
@:dox(hide)
function resyncVocals():Void
inline function resyncVocals():Void
{
var time = Conductor.songPosition + Conductor.songOffset;
for (strumLine in strumLines.members) strumLine.vocals.play(true, time);
vocals.play(true, time);
final time = Conductor.songPosition + Conductor.songOffset;
if (!inst.playing) inst.play(true, time);
vocals.play(true, time);
for (strumLine in strumLines.members) strumLine.vocals.play(true, time);
gameAndCharsCall("onVocalsResync");
}
@@ -1401,13 +1420,12 @@ class PlayState extends MusicBeatState
else if (FlxG.sound.music != null && (__vocalSyncTimer -= elapsed) < 0) {
__vocalSyncTimer = 1;
var instTime = FlxG.sound.music.getActualTime();
var isOffsync:Bool = vocals.loaded && Math.abs(instTime - vocals.getActualTime()) > 100;
if (!isOffsync) {
for (strumLine in strumLines.members) {
if ((isOffsync = strumLine.vocals.loaded && Math.abs(instTime - strumLine.vocals.getActualTime()) > 100)) break;
}
}
final instTime = FlxG.sound.music.getActualTime();
var isOffsync:Bool = vocals.loaded && Math.abs(instTime - vocals.getActualTime()) > 12;
if (!isOffsync)
for (strumLine in strumLines.members)
if ((isOffsync = strumLine.vocals.loaded && Math.abs(instTime - strumLine.vocals.getActualTime()) > 12))
break;
if (isOffsync) resyncVocals();
}
@@ -1587,7 +1605,14 @@ class PlayState extends MusicBeatState
var camera:FlxCamera = event.params[1] == "camHUD" ? camHUD : camGame;
camera.zoom += event.params[0];
case "Camera Bop":
camZoomingMult += event.params[0];
if (Options.camZoomOnBeat) {
if (useCamZoomMult) {
camZoomingMult += event.params[0];
} else {
FlxG.camera.zoom += event.params[0] * camZoomingStrength;
camHUD.zoom += event.params[0] * camZoomingStrength;
}
}
case "Camera Zoom":
var cam = event.params[2] == "camHUD" ? camHUD : camGame;
var name = (event.params[2] == "camHUD" ? "camHUD" : "camGame") + ".zoom"; // avoiding having different values from these 2 - Nex
@@ -1717,7 +1742,7 @@ class PlayState extends MusicBeatState
score: songScore,
misses: misses,
accuracy: accuracy,
hits: [],
hits: hits,
date: Date.now().toString()
}, getSongChanges());
#end
@@ -1744,6 +1769,7 @@ class PlayState extends MusicBeatState
campaignMisses += misses;
campaignAccuracyTotal += accuracy;
campaignAccuracyCount++;
for (k => v in hits) campaignHits[k] += v;
storyPlaylist.shift();
storyVariations.shift();
@@ -1757,7 +1783,7 @@ class PlayState extends MusicBeatState
score: campaignScore,
misses: campaignMisses,
accuracy: campaignAccuracy,
hits: [],
hits: campaignHits,
date: Date.now().toString()
});
#end
@@ -1807,11 +1833,25 @@ class PlayState extends MusicBeatState
*/
public function noteMiss(strumLine:StrumLine, note:Note, ?direction:Int, ?player:Int):Void
{
var playerID:Null<Int> = note == null ? player : strumLines.members.indexOf(strumLine);
var directionID:Null<Int> = note == null ? direction : note.strumID;
var hasNote:Bool = note != null;
var playerID:Null<Int> = hasNote ? strumLines.members.indexOf(strumLine) : player;
var directionID:Null<Int> = hasNote ? note.strumID : direction;
if (playerID == null || directionID == null || playerID == -1) return;
var event:NoteMissEvent = gameAndCharsEvent("onPlayerMiss", EventManager.get(NoteMissEvent).recycle(note, -10, 1, muteVocalsOnMiss, note != null ? -0.0475 : -0.04, Paths.sound(FlxG.random.getObject(Flags.DEFAULT_MISS_SOUNDS)), FlxG.random.float(0.1, 0.2), note == null, combo > 5, "sad", true, true, "miss", strumLines.members[playerID].characters, playerID, note != null ? note.noteType : null, directionID, 0));
if (hasNote) {
if (Flags.SUSTAINS_AS_ONE_NOTE && note.isSustainNote) {
strumLine.deleteNote(note);
if (note.sustainParent.wasGoodHit) {
note.sustainParent.wasGoodHit = false;
note.sustainParent.tooLate = true;
note = note.sustainParent;
}
else
return;
}
}
var event:NoteMissEvent = gameAndCharsEvent("onPlayerMiss", EventManager.get(NoteMissEvent).recycle(note, -10, 1, muteVocalsOnMiss, hasNote ? ((note.isSustainNote && Flags.SUSTAINS_AS_ONE_NOTE) ? -0.1425 : -0.0475) : -0.04, Paths.sound(FlxG.random.getObject(Flags.DEFAULT_MISS_SOUNDS)), FlxG.random.float(0.1, 0.2), !hasNote, combo > 5, "sad", true, true, "miss", strumLines.members[playerID].characters, playerID, hasNote ? note.noteType : null, directionID, 0));
strumLine.onMiss.dispatch(event);
if (event.cancelled) {
gameAndCharsEvent("onPostPlayerMiss", event);
@@ -1850,7 +1890,7 @@ class PlayState extends MusicBeatState
}
}
if (event.deleteNote && strumLine != null && note != null)
if (event.deleteNote && strumLine != null && hasNote)
strumLine.deleteNote(note);
gameAndCharsEvent("onPostPlayerMiss", event);
@@ -1871,43 +1911,49 @@ class PlayState extends MusicBeatState
note.wasGoodHit = true;
/**
* CALCULATES RATING
*/
var noteDiff = Math.abs(Conductor.songPosition - note.strumTime);
var daRating:String = "sick";
var score:Int = 300;
var accuracy:Float = 1;
if (noteDiff > hitWindow * 0.9)
{
daRating = 'shit';
score = 50;
accuracy = 0.25;
}
else if (noteDiff > hitWindow * 0.75)
{
daRating = 'bad';
score = 100;
accuracy = 0.45;
}
else if (noteDiff > hitWindow * 0.2)
{
daRating = 'good';
score = 200;
accuracy = 0.75;
var noteDiff = Math.abs(Conductor.songPosition - note.strumTime), rating:Rating;
if (!Flags.USE_LEGACY_TIMING) rating = ratingManager.judgeNote(noteDiff);
else {
(rating = _legacyRating).splash = false;
if (noteDiff > hitWindow * 0.9) {
rating.window = hitWindow;
rating.name = "shit";
rating.score = 50;
rating.accuracy = 0.25;
}
else if (noteDiff > hitWindow * 0.75) {
rating.window = hitWindow * 0.9;
rating.name = "bad";
rating.score = 100;
rating.accuracy = 0.45;
}
else if (noteDiff > hitWindow * 0.2) {
rating.window = hitWindow * 0.75;
rating.name = "good";
rating.score = 200;
rating.accuracy = 0.75;
}
else {
rating.window = hitWindow * 0.2;
rating.name = "sick";
rating.score = 300;
rating.accuracy = 1;
rating.splash = true;
}
}
var event:NoteHitEvent;
if (strumLine != null && !strumLine.cpu)
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick", 0.5, true, 0.7, true, true, iconP1);
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, rating.score, note.isSustainNote ? null : rating.accuracy, 0.023, rating.name, Options.splashesEnabled && !note.isSustainNote && rating.splash, 0.5, true, 0.7, true, true, iconP1);
else
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false, 0.5, true, 0.7, true, true, iconP2);
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, rating.name, false, 0.5, true, 0.7, true, true, iconP2);
event.deleteNote = !note.isSustainNote; // work around, to allow sustain notes to be deleted
event = scripts.event(strumLine != null && !strumLine.cpu ? "onPlayerHit" : "onDadHit", event);
strumLine.onHit.dispatch(event);
gameAndCharsEvent("onNoteHit", event);
note.noSustainClip = !event.clipSustain;
if (!event.cancelled) {
if (!note.isSustainNote) {
if (event.countScore) songScore += event.score;
@@ -1925,6 +1971,7 @@ class PlayState extends MusicBeatState
displayRating(event.rating, event);
ratingNum += 1;
}
if (event.player) hits[rating.name] += 1;
}
if (strumLine != null) strumLine.addHealth(event.healthGain);
@@ -1953,6 +2000,7 @@ class PlayState extends MusicBeatState
}
if (event.deleteNote) strumLine.deleteNote(note);
else note.updateSustainClip();
gameAndCharsEvent("onPostNoteHit", event);
}
@@ -2160,6 +2208,7 @@ class PlayState extends MusicBeatState
campaignMisses = 0;
campaignAccuracyTotal = 0;
campaignAccuracyCount = 0;
campaignHits = [];
chartingMode = coopMode = opponentMode = false;
__loadSong(storyPlaylist[0], difficulty, storyVariations[0]);
}
+8 -8
View File
@@ -121,7 +121,7 @@ class Strum extends FlxSprite {
public override function update(elapsed:Float) {
super.update(elapsed);
if (cpu) {
if (lastHit + (Conductor.crochet / 2) < Conductor.songPosition && getAnim() == "confirm") {
if (lastHit + (Conductor.crochet * 0.5) < Conductor.songPosition && getAnim() == "confirm") {
playAnim("static");
}
}
@@ -133,7 +133,7 @@ class Strum extends FlxSprite {
}
@:noCompletion public static inline final PIX180:Float = 565.4866776461628; // 180 * Math.PI
@:noCompletion public static final N_WIDTHDIV2:Float = Note.swagWidth / 2;
@:noCompletion public static final N_WIDTHDIV2:Float = Note.swagWidth / 2; // DEPRECATED
/**
* Updates the position of a note.
@@ -162,16 +162,16 @@ class Strum extends FlxSprite {
if (shouldX || shouldY) {
if (daNote.strumRelativePos) {
if (shouldX) daNote.x = (this.width - daNote.width) / 2;
if (shouldX) daNote.x = (this.width - daNote.width) * 0.5;
if (shouldY) {
daNote.y = (daNote.strumTime - Conductor.songPosition) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100));
if (daNote.isSustainNote) daNote.y += N_WIDTHDIV2;
daNote.y = (daNote.strumTime - Conductor.songPosition) * (0.45 * getScrollSpeed(daNote));
if (daNote.isSustainNote) daNote.y += height * 0.5;
}
} else {
var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)));
var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * getScrollSpeed(daNote)));
var realOffset = FlxPoint.get(0, 0);
if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2;
if (daNote.isSustainNote) offset.y -= height * 0.5;
if (Std.int(daNote.__noteAngle % 360) != 0) {
var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180);
@@ -257,4 +257,4 @@ class Strum extends FlxSprite {
public inline function getAnim() {
return animation.name;
}
}
}
+51 -38
View File
@@ -161,6 +161,9 @@ class StrumLine extends FlxTypedGroup<Strum> {
curLen = Math.min(len, Conductor.stepCrochet);
notes.members[total-(il++)-1] = prev = new Note(this, note, true, curLen, note.sLen - len, prev);
len -= curLen;
if (prev != null && prev.sustainParent != null)
prev.sustainParent.tailCount++;
}
}
}
@@ -213,7 +216,7 @@ class StrumLine extends FlxTypedGroup<Strum> {
if (__updateNote_event.cancelled) return;
if (__updateNote_event.__updateHitWindow) {
var hitWindow = PlayState.instance.hitWindow;
var hitWindow = Flags.USE_LEGACY_TIMING ? PlayState.instance.hitWindow : PlayState.instance.ratingManager.lastHitWindow;
daNote.canBeHit = (daNote.strumTime > __updateNote_songPos - (hitWindow * daNote.latePressWindow)
&& daNote.strumTime < __updateNote_songPos + (hitWindow * daNote.earlyPressWindow));
@@ -221,9 +224,10 @@ class StrumLine extends FlxTypedGroup<Strum> {
daNote.tooLate = true;
}
if (cpu && __updateNote_event.__autoCPUHit && !daNote.avoid && !daNote.wasGoodHit && daNote.strumTime < __updateNote_songPos) PlayState.instance.goodNoteHit(this, daNote);
if (cpu && __updateNote_event.__autoCPUHit && !daNote.avoid && !daNote.wasGoodHit && daNote.strumTime < __updateNote_songPos)
PlayState.instance.goodNoteHit(this, daNote);
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + daNote.sustainLength < __updateNote_songPos) {
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + daNote.sustainLength < __updateNote_songPos && !daNote.noSustainClip) {
deleteNote(daNote);
return;
}
@@ -236,29 +240,38 @@ class StrumLine extends FlxTypedGroup<Strum> {
if (__updateNote_event.strum == null) return;
if (__updateNote_event.__reposNote) __updateNote_event.strum.updateNotePosition(daNote);
if (daNote.isSustainNote)
if (daNote.isSustainNote) {
daNote.updateSustain(__updateNote_event.strum);
}
}
var __funcsToExec:Array<Note->Void> = [];
var __pressed:Array<Bool> = [];
var __justPressed:Array<Bool> = [];
var __justReleased:Array<Bool> = [];
var __notePerStrum:Array<Note> = [];
function __inputProcessPressed(note:Note) {
if (__pressed[note.strumID] && note.isSustainNote && note.strumTime < __updateNote_songPos && !note.wasGoodHit) {
if (__pressed[note.strumID] && note.isSustainNote && note.strumTime < __updateNote_songPos && !note.wasGoodHit && note.sustainParent.wasGoodHit) {
PlayState.instance.goodNoteHit(this, note);
note.updateSustainClip();
}
}
function __inputProcessJustPressed(note:Note) {
if (__justPressed[note.strumID] && !note.isSustainNote && !note.wasGoodHit && note.canBeHit) {
if (__notePerStrum[note.strumID] == null) __notePerStrum[note.strumID] = note;
else if (Math.abs(__notePerStrum[note.strumID].strumTime - note.strumTime) <= 2) deleteNote(note);
else if (note.strumTime < __notePerStrum[note.strumID].strumTime) __notePerStrum[note.strumID] = note;
var cur = __notePerStrum[note.strumID];
var songPos = __updateNote_songPos;
var noteDist = Math.abs(note.strumTime - songPos);
var curDist = cur != null ? Math.abs(cur.strumTime - songPos) : 999999;
var notePenalty = note.avoid ? 1 : 0;
var curPenalty = (cur != null && cur.avoid) ? 1 : 0;
if (cur == null
|| notePenalty < curPenalty
|| (notePenalty == curPenalty && noteDist < curDist))
__notePerStrum[note.strumID] = note;
}
}
@@ -271,15 +284,16 @@ class StrumLine extends FlxTypedGroup<Strum> {
if (cpu) return;
__funcsToExec.clear();
__pressed.clear();
__justPressed.clear();
__justReleased.clear();
if (__pressed.length != members.length) {
__pressed.resize(members.length);
__justPressed.resize(members.length);
__justReleased.resize(members.length);
}
for(s in members) {
__pressed.push(s.__getPressed(this));
__justPressed.push(s.__getJustPressed(this));
__justReleased.push(s.__getJustReleased(this));
for (i in 0...members.length) {
__pressed[i] = members[i].__getPressed(this);
__justPressed[i] = members[i].__getJustPressed(this);
__justReleased[i] = members[i].__getJustReleased(this);
}
var event = EventManager.get(InputSystemEvent).recycle(__pressed, __justPressed, __justReleased, this, id);
@@ -290,34 +304,31 @@ class StrumLine extends FlxTypedGroup<Strum> {
__justPressed = CoolUtil.getDefault(event.justPressed, []);
__justReleased = CoolUtil.getDefault(event.justReleased, []);
__notePerStrum = cast new haxe.ds.Vector(members.length);//[for(_ in 0...members.length) null];
__notePerStrum = cast new haxe.ds.Vector(members.length); // [for(_ in 0...members.length) null];
if (__pressed.contains(true)) {
for(c in characters)
if (__justPressed.contains(true)) {
notes.forEachAlive(__inputProcessJustPressed);
if (!ghostTapping) for (k => pr in __justPressed) if (pr && __notePerStrum[k] == null)
PlayState.instance.noteMiss(this, null, k, ID); // FUCK YOU
for (e in __notePerStrum)
if (e != null)
PlayState.instance.goodNoteHit(this, e);
}
for (c in characters)
if (c.lastAnimContext != DANCE)
c.__lockAnimThisFrame = true;
__funcsToExec.push(__inputProcessPressed);
notes.forEachAlive(__inputProcessPressed);
}
if (__justPressed.contains(true))
__funcsToExec.push(__inputProcessJustPressed);
if (__funcsToExec.length > 0) {
notes.forEachAlive(function(note:Note) {
for(e in __funcsToExec) if (e != null) e(note);
});
}
if (!ghostTapping) for(k=>pr in __justPressed) if (pr && __notePerStrum[k] == null) {
// FUCK YOU
PlayState.instance.noteMiss(this, null, k, ID);
}
for(e in __notePerStrum) if (e != null) PlayState.instance.goodNoteHit(this, e);
forEach(function(str:Strum) {
str.updatePlayerInput(str.__getPressed(this), str.__getJustPressed(this), str.__getJustReleased(this));
str.updatePlayerInput(__pressed[str.ID], __justPressed[str.ID], __justReleased[str.ID]);
});
PlayState.instance.gameAndCharsCall("onPostInputUpdate");
}
@@ -411,6 +422,8 @@ class StrumLine extends FlxTypedGroup<Strum> {
var event:SimpleNoteEvent = EventManager.get(SimpleNoteEvent).recycle(note);
onNoteDelete.dispatch(event);
if (!event.cancelled) {
if (note.isSustainNote && note.sustainParent != null && note.sustainParent.tailCount > 0)
note.sustainParent.tailCount--;
note.kill();
notes.remove(note, true);
note.destroy();
@@ -95,18 +95,20 @@ class ScriptedCutscene extends Cutscene {
script.call("stepHit", [curStep]);
}
public override function openSubState(sub:FlxSubState)
public override function openSubState(subState:FlxSubState)
{
var event = EventManager.get(StateEvent).recycle(sub);
script.call("onSubstateClose", [event]);
if(!event.cancelled) super.openSubState(event.substate is FlxSubState ? cast event.substate : sub);
var event = EventManager.get(StateEvent).recycle(subState);
script.call(Flags.MOD_API_VERSION <= 1 ? "onSubstateClose" /*Remove this entirely*/ : "onOpenSubState", [event]);
if (!event.cancelled)
super.openSubState(event.substate is FlxSubState ? cast event.substate : subState);
}
public override function closeSubState()
{
var event = EventManager.get(StateEvent).recycle(subState);
script.call("onSubstateOpen", [event]);
if(!event.cancelled) super.closeSubState();
script.call(Flags.MOD_API_VERSION <= 1 ? "onSubstateOpen" /*Remove this entirely*/ : "onCloseSubState", [event]);
if (!event.cancelled)
super.closeSubState();
}
public override function destroy() {
@@ -60,7 +60,9 @@ class VideoCutscene extends Cutscene {
add(video = new FlxVideoSprite());
video.antialiasing = true;
#if (hxvlc < version("2.0.0"))
video.autoPause = false; // Imma handle it better inside this class, mainly because of the pause menu - Nex
#end
video.bitmap.onEndReached.add(close);
video.bitmap.onFormatSetup.add(function() if (video.bitmap != null && video.bitmap.bitmapData != null) {
final width = video.bitmap.bitmapData.width;
@@ -205,6 +207,7 @@ class VideoCutscene extends Cutscene {
}
}
#if (hxvlc < version("2.0.0"))
@:dox(hide) override public function onFocus() {
if(FlxG.autoPause && !paused) video.resume();
super.onFocus();
@@ -214,6 +217,7 @@ class VideoCutscene extends Cutscene {
if(FlxG.autoPause && !paused) video.pause();
super.onFocusLost();
}
#end
public override function pauseCutscene() {
video.pause();
@@ -0,0 +1,76 @@
package funkin.game.scoring;
import haxe.ds.StringMap;
class HitWindowData
{
public static function getWindows(preset:WindowPreset):StringMap<Float>
{
var map = new StringMap<Float>();
switch (preset) {
// Old Codename, really forgiving inputs (hard to get bad ratings)
case CNE_CLASSIC:
map.set("sick", 50.0);
map.set("good", 187.5);
map.set("bad", 225.0);
map.set("shit", 250.0);
// Week 7
case FNF_CLASSIC:
map.set("sick", 33.334);
map.set("good", 125.0025);
map.set("bad", 150.003);
map.set("shit", 166.67);
// V-Slice
case FNF_VSLICE:
map.set("sick", 45.0);
map.set("good", 90.0);
map.set("bad", 135.4);
map.set("shit", 180.0);
// Default, taken from Etterna
case _:
map.set("sick", 37.8);
map.set("good", 75.6);
map.set("bad", 113.4);
map.set("shit", 180.0);
}
return map;
}
public static var JUDGE_SCALES:Array<Float> = [1.5, 1.33, 1.16, 1.0, 0.84, 0.66, 0.5, 0.33, 0.2];
public static function scaleWindows(windows:StringMap<Float>, scale:Float):StringMap<Float>
{
var scaled = new StringMap<Float>();
for (k in windows.keys())
scaled.set(k, windows.get(k) * scale);
return scaled;
}
public static function offsetWindows(windows:StringMap<Float>, offset:Float):StringMap<Float>
{
var adjusted = new StringMap<Float>();
for (k in windows.keys())
adjusted.set(k, windows.get(k) + offset);
return adjusted;
}
}
enum abstract WindowPreset(Int) from Int to Int
{
var DEFAULT = 0;
var CNE_CLASSIC = 1;
var FNF_CLASSIC = 2;
var FNF_VSLICE = 3;
public function toString():String
{
return switch (cast this : WindowPreset)
{
case CNE_CLASSIC: "Codename (Classic)";
case FNF_CLASSIC: "Funkin' (Week 7)";
case FNF_VSLICE: "Funkin' (V-Slice)";
case _: "Default";
}
}
}
+138
View File
@@ -0,0 +1,138 @@
package funkin.game.scoring;
import funkin.game.scoring.*;
import funkin.game.scoring.HitWindowData.WindowPreset;
import haxe.ds.StringMap;
/**
* Judges note hits and returns a rating.
*/
class RatingManager
{
public var hitWindows:StringMap<Float>;
public var ratingData:Array<Rating> = [];
public var lastHitWindow:Float = -1;
public function new(?preset:WindowPreset):Void
{
var usedPreset = preset != null ? preset : WindowPreset.DEFAULT;
hitWindows = HitWindowData.getWindows(usedPreset);
initDefaultData(hitWindows);
}
/**
* Returns a rating based on a wimdow of time.
* @param time The timing window to judge.
*/
public function judgeNote(time:Float):Rating
{
for (i => rating in ratingData)
{
if (rating.hittable && rating.window > -1 && time <= rating.window)
{
return rating;
}
}
return ratingData.last();
}
/**
* Initializes the default rating data containing the four judgements.
*
* "Sick", "Good", "Bad", "Shit"
*/
public function initDefaultData(windows:StringMap<Float>)
{
inline function getWindow(name:String):Float
{
return windows.exists(name) ? windows.get(name) : -1;
}
addRating({name: "sick", window: getWindow("sick"), accuracy: 1, score: 300, splash: true});
addRating({name: "good", window: getWindow("good"), accuracy: 0.75, score: 200});
addRating({name: "bad", window: getWindow("bad"), accuracy: 0.45, score: 100});
addRating({name: "shit", window: getWindow("shit"), accuracy: 0.25, score: 50});
}
public function addRating(data:Dynamic)
{
if (data == null || data.name == null) return;
var name = data.name.toLowerCase();
var window = data.window != null
? data.window
: (hitWindows.exists(name) ? hitWindows.get(name) : -1);
if (window > lastHitWindow) lastHitWindow = window;
var newRating:Rating = {
name: name,
window: window,
accuracy: data.accuracy != null ? data.accuracy : 1,
score: data.score != null ? data.score : 0,
splash: data.splash == true,
hittable: data.hittable != null ? data.hittable : true
};
var existingIndex = -1;
for (i in 0...ratingData.length)
if (ratingData[i].name == name)
existingIndex = i;
if (existingIndex >= 0)
ratingData[existingIndex] = newRating;
else
ratingData.push(newRating);
ratingData.sort((a, b) -> Reflect.compare(a.window, b.window));
}
public function removeRating(name:String):Void
{
if (name == null) return;
name = name.toLowerCase();
ratingData = ratingData.filter(r -> r.name != name);
}
public function getHitWindow(name:String):Float
{
return hitWindows.exists(name) ? hitWindows.get(name) : -1;
}
}
@:structInit
final class Rating
{
/**
* Name of rating.
*
* Also used for the image file name of the rating.
*/
public var name:String = "unknown";
/**
* Amount of accuracy given when earning this rating.
*/
public var accuracy:Float = 0.0;
/**
* MS Timing Window to hit the rating.
*/
public var window:Float = -1;
/**
* Amount of score given when earning this rating.
*/
public var score:Int = 0;
/**
* If this rating was hit, a note splash will appear.
*/
@:optional public var splash:Bool = false;
/**
* Whether the rating is hittable or not.
*/
@:optional public var hittable:Bool = true;
}
+2 -2
View File
@@ -81,8 +81,8 @@ class PauseSubState extends MusicBeatSubstate
bg.scrollFactor.set();
add(bg);
var multiplayerInfo:String = PlayState.opponentMode ? 'pause.coopMode' :
PlayState.coopMode ? 'pause.opponentMode' :
var multiplayerInfo:String = PlayState.opponentMode ? 'pause.opponentMode' :
PlayState.coopMode ? 'pause.coopMode' :
null;
levelInfo = new FunkinText(20, 15, 0, PlayState.SONG.meta.displayName, 32, false);
+1 -1
View File
@@ -245,7 +245,7 @@ class TitleState extends MusicBeatState
if (parentFolder != "" && !parentFolder.endsWith("/")) parentFolder += "/";
for(sprNode in node.elements) {
var spr = XMLUtil.createSpriteFromXML(sprNode, parentFolder);
switch(node.name) {
switch(sprNode.name) {
case "press-enter":
titleText = spr;
default:
+4
View File
@@ -345,6 +345,10 @@ class Alphabet extends FlxSprite {
}
function drawLetter(camera) {
// i'll have to improve this with blit rendering. not sure how.
// i can't just store all the bitmaps in the component since it's also responsable for flips and colors.
if (FlxG.renderBlit)
updateFramePixels();
_frame.prepareMatrix(_matrix, ANGLE_0, checkFlipX() != camera.flipX, checkFlipY() != camera.flipY);
_matrix.translate(_frame.frame.width * -0.5, _frame.frame.height * -0.5);
+13 -6
View File
@@ -38,13 +38,13 @@ class Options
public static var devMode:Bool = false;
public static var betaUpdates:Bool = false;
public static var splashesEnabled:Bool = true;
public static var hitWindow:Float = 250;
@:dox(hide) @:doNotSave public static var hitWindow:Float = 250; // DEPRECATED
public static var songOffset:Float = 0;
public static var framerate:Int = 120;
public static var gpuOnlyBitmaps:Bool = #if (mac || web) false #else true #end; // causes issues on mac and web
public static var language = "en"; // default to english, Flags.DEFAULT_LANGUAGE should not modify this
public static var streamedMusic:Bool = true;
public static var streamedVocals:Bool = false;
public static var streamedVocals:Bool = true;
public static var quality:Int = 1;
public static var allowConfigWarning:Bool = true;
#if MODCHARTING_FEATURES
@@ -126,6 +126,7 @@ class Options
public static var P1_VOLUME_UP:Array<FlxKey> = [PLUS];
public static var P1_VOLUME_DOWN:Array<FlxKey> = [MINUS];
public static var P1_VOLUME_MUTE:Array<FlxKey> = [ZERO];
public static var P1_FPS_COUNTER:Array<FlxKey> = [#if web THREE #else F3 #end]; // 3 on web or F3 on windows, linux and other things that runs code
// Debugs
public static var P1_DEV_ACCESS:Array<FlxKey> = [SEVEN];
@@ -158,6 +159,7 @@ class Options
public static var P2_VOLUME_UP:Array<FlxKey> = [NUMPADPLUS];
public static var P2_VOLUME_DOWN:Array<FlxKey> = [NUMPADMINUS];
public static var P2_VOLUME_MUTE:Array<FlxKey> = [NUMPADZERO];
public static var P2_FPS_COUNTER:Array<FlxKey> = [];
// Debugs
public static var P2_DEV_ACCESS:Array<FlxKey> = [];
@@ -190,6 +192,7 @@ class Options
public static var SOLO_VOLUME_UP(get, null):Array<FlxKey>;
public static var SOLO_VOLUME_DOWN(get, null):Array<FlxKey>;
public static var SOLO_VOLUME_MUTE(get, null):Array<FlxKey>;
public static var SOLO_FPS_COUNTER(get, null):Array<FlxKey>;
// Debugs
public static var SOLO_DEV_ACCESS(get, null):Array<FlxKey>;
@@ -223,7 +226,15 @@ class Options
public static function applySettings() {
applyKeybinds();
applyQuality();
FlxG.sound.defaultMusicGroup.volume = volumeMusic;
FlxG.autoPause = autoPause;
if (FlxG.updateFramerate < framerate) FlxG.drawFramerate = FlxG.updateFramerate = framerate;
else FlxG.updateFramerate = FlxG.drawFramerate = framerate;
}
public static function applyQuality() {
switch (quality) {
case 0:
antialiasing = false;
@@ -235,11 +246,7 @@ class Options
gameplayShaders = true;
}
FlxG.sound.defaultMusicGroup.volume = volumeMusic;
FlxG.game.stage.quality = (FlxG.enableAntialiasing = antialiasing) ? BEST : LOW;
FlxG.autoPause = autoPause;
if (FlxG.updateFramerate < framerate) FlxG.drawFramerate = FlxG.updateFramerate = framerate;
else FlxG.updateFramerate = FlxG.drawFramerate = framerate;
}
public static function applyKeybinds() {
+5 -1
View File
@@ -162,6 +162,10 @@ class OptionsMenu extends TreeMenu {
}
var name = node.getAtt("name");
var desc = node.getAtt("desc").getDefault("optionsMenu.desc-missing");
if (screen.prefix?.length > 0) {
name = screen.prefix + name;
if (node.has.desc) desc = screen.prefix + desc;
}
switch(node.name) {
case "checkbox":
@@ -210,7 +214,7 @@ class OptionsMenu extends TreeMenu {
options.push(new SliderOption(name, desc, Std.parseFloat(node.att.min), Std.parseFloat(node.att.max), step, segments, node.att.id, Std.parseInt(node.att.barWidth), null, FlxG.save.data));
case "menu":
options.push(new TextOption(name, desc, ' >', () -> {
var screen = new TreeMenuScreen(name, desc);
var screen = new TreeMenuScreen(name, desc, node.getAtt("prefix").getDefault(""));
for (o in parseOptionsFromXML(screen, node)) screen.add(o);
addMenu(screen);
}));
+8 -8
View File
@@ -60,7 +60,7 @@ class TreeMenuScreen extends FlxSpriteGroup {
var curFloatOption:ITreeFloatOption;
var __firstFrame:Bool = true;
public function new(name:String, desc:String, ?prefix:String, ?objects:Array<FlxSprite>) {
public function new(name:String, desc:String, prefix:String = "", ?objects:Array<FlxSprite>) {
super();
this.prefix = prefix;
rawName = name;
@@ -123,18 +123,18 @@ class TreeMenuScreen extends FlxSpriteGroup {
updateItems();
}
dynamic function updateItem(object:FlxSprite, itemHeight:Float, centerY:Float, lerpRatio:Float) {
object.y = CoolUtil.fpsLerp(object.y, centerY - itemHeight * 0.5, lerpRatio);
object.x = x + 100 - Math.pow(Math.abs((object.y - (FlxG.height - itemHeight) * 0.5) / itemHeight / FlxG.height * FlxG.initialHeight), 1.6) * 15;
}
public function updateItems(force = false) {
var r = force ? 1 : 0.25, initY = FlxG.height * 0.5;
var i = curSelected, y = initY, object:FlxSprite = null, itemHeight:Float = 0;
inline function updateItem() {
object.y = CoolUtil.fpsLerp(object.y, y - itemHeight * 0.5, r);
object.x = x + 100 - Math.pow(Math.abs((object.y - (FlxG.height - itemHeight) * 0.5) / itemHeight / FlxG.height * FlxG.initialHeight), 1.6) * 15;
}
while (i < length) if ((object = members[i++]) != null) {
itemHeight = object.height;
updateItem();
updateItem(object, itemHeight, y, r);
y += itemHeight;
}
@@ -142,7 +142,7 @@ class TreeMenuScreen extends FlxSpriteGroup {
i = curSelected;
while (i-- > 0) if ((object = members[i]) != null) {
y -= (itemHeight = object.height);
updateItem();
updateItem(object, itemHeight, y, r);
}
}
@@ -48,12 +48,29 @@ class AdvancedAppearanceOptions extends TreeMenuScreen {
}
private function updateQualityOptions() {
for (option in qualityOptions) option.locked = Options.quality != 2;
for (option in qualityOptions) {
option.locked = Options.quality != 2;
if (option is Checkbox) {
final checkbox:Checkbox = cast option;
checkbox.checked = Reflect.field(checkbox.parent, checkbox.optionName);
}
else if (option is SliderOption) {
final slider:SliderOption = cast option;
slider.currentValue = Reflect.field(slider.parent, slider.optionName);
}
else if (option is NumOption) {
final num:NumOption = cast option;
num.currentValue = Reflect.field(num.parent, num.optionName);
}
else if (option is ArrayOption) {
final array:ArrayOption = cast option;
array.currentSelection = Reflect.field(array.parent, array.optionName);
}
}
}
private function __changeQuality(value:Dynamic) {
var antialiasing = value == 0 ? false : (value == 1 ? true : Options.antialiasing);
FlxG.game.stage.quality = (FlxG.enableAntialiasing = antialiasing) ? BEST : LOW;
Options.applyQuality();
updateQualityOptions();
}
@@ -32,7 +32,7 @@ class KeybindSetting extends FlxTypedSpriteGroup<FlxSprite> {
option1 = controlArrayP1[0];
option2 = controlArrayP2[0];
for(i in 1...3) {
for (i in 1...3) {
var b = null;
var bx = FlxG.width * (0.25 * (i+1)) - x;
if (i == 1)
@@ -60,6 +60,11 @@ class KeybindSetting extends FlxTypedSpriteGroup<FlxSprite> {
title.setPosition(100, 0);
}
if (title.x + title.width > bind1.x - 20) {
title.scale.x = (bind1.x - 20 - title.x) / title.width;
title.updateHitbox();
}
setPosition(x, y);
}
+125 -112
View File
@@ -9,9 +9,7 @@ class KeybindsOptions extends MusicBeatSubstate {
public static var instance:KeybindsOptions;
public function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("KeybindsOptions." + id, args);
public var categories:Array<ControlsCategory> = [];
return TU.translate(id, args);
public var settingCam:FlxCamera;
@@ -29,126 +27,131 @@ class KeybindsOptions extends MusicBeatSubstate {
];
public var camFollow:FlxObject = new FlxObject(0, 0, 2, 2);
var isSubState:Bool = false;
public override function create() {
categories = [
{
name: translate("category.notes"),
settings: [
{
sparrowIcon: "game/notes/default",
sparrowAnim: "purple0",
name: translate("left"),
control: 'NOTE_LEFT'
},
{
sparrowIcon: "game/notes/default",
sparrowAnim: "blue0",
name: translate("down"),
control: 'NOTE_DOWN'
},
{
sparrowIcon: "game/notes/default",
sparrowAnim: "green0",
name: translate("up"),
control: 'NOTE_UP'
},
{
sparrowIcon: "game/notes/default",
sparrowAnim: "red0",
name: translate("right"),
control: 'NOTE_RIGHT'
},
]
},
{
name: translate("category.ui"),
settings: [
{
name: translate("left"),
control: 'LEFT'
},
{
name: translate("down"),
control: 'DOWN'
},
{
name: translate("up"),
control: 'UP'
},
{
name: translate("right"),
control: 'RIGHT'
},
{
name: translate("ui.accept"),
control: 'ACCEPT'
},
{
name: translate("ui.back"),
control: 'BACK'
},
{
name: translate("ui.reset"),
control: 'RESET'
},
{
name: translate("ui.pause"),
control: 'PAUSE'
},
{
name: translate("ui.changeMode"),
control: 'CHANGE_MODE'
},
]
},
public var categories:Array<ControlsCategory> = [];
public static var defaultCategories:Array<ControlsCategory> = [
{
name: translate("category.volume"),
name: "category.notes",
settings: [
{
name: translate("volume.up"),
sparrowIcon: "game/notes/default",
sparrowAnim: "purple0",
name: "left",
control: 'NOTE_LEFT'
},
{
sparrowIcon: "game/notes/default",
sparrowAnim: "blue0",
name: "down",
control: 'NOTE_DOWN'
},
{
sparrowIcon: "game/notes/default",
sparrowAnim: "green0",
name: "up",
control: 'NOTE_UP'
},
{
sparrowIcon: "game/notes/default",
sparrowAnim: "red0",
name: "right",
control: 'NOTE_RIGHT'
},
]
},
{
name: "category.ui",
settings: [
{
name: "left",
control: 'LEFT'
},
{
name: "down",
control: 'DOWN'
},
{
name: "up",
control: 'UP'
},
{
name: "right",
control: 'RIGHT'
},
{
name: "ui.accept",
control: 'ACCEPT'
},
{
name: "ui.back",
control: 'BACK'
},
{
name: "ui.reset",
control: 'RESET'
},
{
name: "ui.pause",
control: 'PAUSE'
},
{
name: "ui.changeMode",
control: 'CHANGE_MODE'
},
]
},
{
name: "category.volume",
settings: [
{
name: "volume.up",
control: 'VOLUME_UP'
},
{
name: translate("volume.down"),
name: "volume.down",
control: 'VOLUME_DOWN'
},
{
name: translate("volume.mute"),
name: "volume.mute",
control: 'VOLUME_MUTE'
},
]
},
{
name: translate("category.engine"),
settings: [
{
name: translate("engine.switchMod"),
control: 'SWITCHMOD'
},
]
},
{
name: translate("category.developer"),
devModeOnly: true,
settings: [
{
name: translate("developer.devMenus"),
control: 'DEV_ACCESS'
},
{
name: translate("developer.openConsole"),
control: 'DEV_CONSOLE'
},
{
name: translate("developer.reloadState"),
control: 'DEV_RELOAD'
},
]
}
];
{
name: "category.engine",
settings: [
{
name: "engine.switchMod",
control: 'SWITCHMOD'
},
{
name: "engine.fpsCounter",
control: 'FPS_COUNTER'
},
]
},
{
name: "category.developer",
devModeOnly: true,
settings: [
{
name: "developer.devMenus",
control: 'DEV_ACCESS'
},
{
name: "developer.openConsole",
control: 'DEV_CONSOLE'
},
{
name: "developer.reloadState",
control: 'DEV_RELOAD'
},
]
}
];
public var isSubState:Bool = false;
public override function create() {
super.create();
instance = this;
@@ -180,6 +183,8 @@ class KeybindsOptions extends MusicBeatSubstate {
FlxG.camera.follow(camFollow, LOCKON, 0.125);
}
for (category in defaultCategories) categories.push(category);
var customCategories = loadCustomCategories();
for (i in customCategories) categories.push(i);
@@ -188,18 +193,24 @@ class KeybindsOptions extends MusicBeatSubstate {
if (category.devModeOnly && !Options.devMode) continue;
k++;
var title = new Alphabet(0, k * 75, category.name, "bold");
var translationPrefix:String = (category.custom != null) ? '' : 'KeybindsOptions.';
var categoryToTranslate:String = translationPrefix + category.name;
var translatedCategory:String = TU.exists(categoryToTranslate) ? translate(categoryToTranslate) : category.name;
var title = new Alphabet(0, k * 75, translatedCategory, "bold");
title.screenCenter(X);
add(title);
k++;
for(e in category.settings) {
for (e in category.settings) {
var sparrowIcon:String = null;
var sparrowAnim:String = null;
if (e.sparrowIcon != null) sparrowIcon = e.sparrowIcon;
if (e.sparrowAnim != null) sparrowAnim = e.sparrowAnim;
var text = new KeybindSetting(100, k * 75, e.name, e.control, sparrowIcon, sparrowAnim, e.custom == null ? false : e.custom);
var nameToTranslate:String = translationPrefix + e.name;
var translatedName:String = TU.exists(nameToTranslate) ? translate(nameToTranslate) : e.name;
var text = new KeybindSetting(100, k * 75, translatedName, e.control, sparrowIcon, sparrowAnim, e.custom == null ? false : e.custom);
if (!isSubState)
text.bind1.color = text.bind2.color = FlxColor.BLACK;
alphabets.add(text);
@@ -318,6 +329,7 @@ class KeybindsOptions extends MusicBeatSubstate {
var cat:ControlsCategory = {
name: category.getAtt("name"),
custom: true,
settings: []
};
@@ -354,4 +366,5 @@ typedef ControlsCategory = {
var name:String;
var settings:Array<KeybindSettingData>;
var ?devModeOnly:Bool;
var ?custom:Bool;
}
+7 -2
View File
@@ -5,13 +5,19 @@ class ArrayOption extends TextOption {
public var options:Array<Dynamic>;
public var displayOptions:Array<String>;
public var currentSelection:Int;
public var currentSelection(default, set):Int;
public var parent:Dynamic;
public var optionName:String;
var __selectionText:Alphabet;
function set_currentSelection(v:Int):Int {
currentSelection = v;
if (__selectionText != null) __selectionText.text = formatTextOption();
return v;
}
override function set_text(v:String) {
super.set_text(v);
__selectionText.x = __text.x + __text.width + 12;
@@ -53,7 +59,6 @@ class ArrayOption extends TextOption {
override function changeSelection(change:Int) {
if (locked || currentSelection == (currentSelection = CoolUtil.boundInt(currentSelection + change, 0, options.length - 1))) return;
__selectionText.text = formatTextOption();
CoolUtil.playMenuSFX(SCROLL);
if (optionName != null) Reflect.setField(parent, optionName, options[currentSelection]);
+7 -3
View File
@@ -10,14 +10,19 @@ class NumOption extends TextOption {
public var max:Float;
public var step:Float;
public var currentValue:Float;
public var currentValue(default, set):Float;
public var parent:Dynamic;
public var optionName:String;
var __number:Alphabet;
override function set_text(v:String) {
function set_currentValue(v:Float):Float {
if (__number != null) __number.text = ': $v';
return currentValue = v;
}
override function set_text(v:String):String {
super.set_text(v);
__number.x = __text.x + __text.width + 12;
return v;
@@ -41,7 +46,6 @@ class NumOption extends TextOption {
override function changeSelection(change:Int):Void {
if (locked) return;
if (currentValue == (currentValue = FlxMath.bound(currentValue + change * step, min, max))) return;
__number.text = ': $currentValue';
Reflect.setField(parent, optionName, currentValue);
if (changedCallback != null) changedCallback(currentValue);
+15 -8
View File
@@ -97,29 +97,36 @@ class FunkinSave {
*/
public static inline function getSongHighscore(name:String, diff:String, ?variation:String, ?changes:Array<HighscoreChange>) {
if (changes == null) changes = [];
return safeGetHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), variation, changes));
return safeGetHighscore(getSongEntry(name, diff, variation, changes));
}
public static inline function setSongHighscore(name:String, diff:String, ?variation:String, highscore:SongScore, ?changes:Array<HighscoreChange>) {
public static inline function setSongHighscore(name:String, diff:String, ?variation:String, highscore:SongScore, ?changes:Array<HighscoreChange>, ?force:Bool) {
if (changes == null) changes = [];
if (safeRegisterHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), variation, changes), highscore)) {
if (safeRegisterHighscore(getSongEntry(name, diff, variation, changes), highscore, force)) {
flush();
return true;
}
return false;
}
public static inline function getSongEntry(name:String, diff:String, ?variation:String, ?changes:Array<HighscoreChange>):HighscoreEntry
return HSongEntry(name.toLowerCase(), diff.toLowerCase(), variation, changes);
public static inline function getWeekHighscore(name:String, diff:String)
return safeGetHighscore(HWeekEntry(name.toLowerCase(), diff.toLowerCase()));
return safeGetHighscore(getWeekEntry(name, diff));
public static inline function setWeekHighscore(name:String, diff:String, highscore:SongScore) {
if (safeRegisterHighscore(HWeekEntry(name.toLowerCase(), diff.toLowerCase()), highscore)) {
public static inline function setWeekHighscore(name:String, diff:String, highscore:SongScore, ?force:Bool) {
if (safeRegisterHighscore(getWeekEntry(name, diff), highscore, force)) {
flush();
return true;
}
return false;
}
public static inline function getWeekEntry(name, diff:String):HighscoreEntry
return HWeekEntry(name.toLowerCase(), diff.toLowerCase());
private static function safeGetHighscore(entry:HighscoreEntry):SongScore {
if (!highscores.exists(entry)) {
return {
@@ -133,9 +140,9 @@ class FunkinSave {
return highscores.get(entry);
}
private static function safeRegisterHighscore(entry:HighscoreEntry, highscore:SongScore) {
private static function safeRegisterHighscore(entry:HighscoreEntry, highscore:SongScore, force = false) {
var oldHigh = safeGetHighscore(entry);
if (oldHigh.date == null || oldHigh.score < highscore.score) {
if (force || oldHigh.date == null || oldHigh.score < highscore.score) {
highscores.set(entry, highscore);
return true;
}
@@ -251,16 +251,6 @@ class HTML5AudioSource
public function getPosition():Vector4
{
#if lime_howlerjs
// This should work, but it returns null (But checking the inside of the howl, the _pos is actually null... so ¯\_(ツ)_/¯)
/*
var arr = parent.buffer.__srcHowl.pos())
position.x = arr[0];
position.y = arr[1];
position.z = arr[2];
*/
#end
return position;
}
@@ -278,4 +268,16 @@ class HTML5AudioSource
return position;
}
public function getPan():Float
{
return position.x;
}
public function setPan(value:Float):Float
{
position.setTo(value, 0, -Math.sqrt(1 - value * value));
if (parent.buffer != null && parent.buffer.__srcHowl != null && parent.buffer.__srcHowl.stereo != null) parent.buffer.__srcHowl.stereo(value, id);
return value;
}
}
File diff suppressed because it is too large Load Diff
+20 -16
View File
@@ -8,7 +8,10 @@ import lime.app.Future;
import lime.app.Promise;
import lime.media.openal.AL;
import lime.media.openal.ALBuffer;
#if lime_vorbis
import lime.media.vorbis.Vorbis;
import lime.media.vorbis.VorbisFile;
#end
import lime.net.HTTPRequest;
import lime.utils.Log;
import lime.utils.UInt8Array;
@@ -101,7 +104,10 @@ class AudioBuffer
__srcHowl = null;
#end
#if lime_cffi
if (__srcBuffer != null) AL.deleteBuffer(__srcBuffer);
if (__srcBuffer != null) {
AL.bufferData(__srcBuffer, 0, null, 0, 0);
AL.deleteBuffer(__srcBuffer);
}
__srcBuffer = null;
#end
#if lime_vorbis
@@ -178,11 +184,9 @@ class AudioBuffer
return audioBuffer;
#elseif (lime_cffi && !macro)
#if lime_vorbis // CNE
if (funkin.options.Options.streamedMusic) {
var vorbisFile = VorbisFile.fromBytes(bytes);
if (vorbisFile != null) return fromVorbisFile(vorbisFile);
}
#if lime_vorbis
var vorbisFile = VorbisFile.fromBytes(bytes);
if (vorbisFile != null) return fromVorbisFile(vorbisFile);
#end
#if !cs
var audioBuffer = new AudioBuffer();
@@ -312,29 +316,29 @@ class AudioBuffer
if (vorbisFile == null) return null;
var info = vorbisFile.info();
if (info == null) return null;
var audioBuffer = new AudioBuffer();
audioBuffer.channels = info.channels;
audioBuffer.sampleRate = info.rate;
audioBuffer.bitsPerSample = 16;
if (!vorbisFile.seekable() ||
vorbisFile.pcmTotal() < #if lime_cffi @:privateAccess lime._internal.backend.native.NativeAudioSource.STREAM_BUFFER_SAMPLES #else 0x4000 #end)
{
// convert it to static if its too short or unseekable.
final pcmTotal = vorbisFile.pcmTotal(-1);
if (!vorbisFile.seekable() || pcmTotal < (audioBuffer.sampleRate << 2)) {
vorbisFile.rawSeek(0);
var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN;
var bytes:Bytes = Bytes.alloc(Std.int(haxe.Int64.toInt(vorbisFile.pcmTotal()) * info.channels * 2));
final isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN;
final bytes = Bytes.alloc(Std.int((pcmTotal.high * 4294967296. + (pcmTotal.low >> 0)) * info.channels * (audioBuffer.bitsPerSample >> 3)));
var total = 0, result = 0;
do {
total += (result = vorbisFile.read(bytes, total, 0x1000, isBigEndian, 2, true));
} while (result > 0);
result = vorbisFile.read(bytes, total, 0x1000, isBigEndian, 2, true);
total += result;
} while (result > 0 || result == Vorbis.HOLE);
audioBuffer.data = new UInt8Array(bytes);
vorbisFile.clear();
}
else audioBuffer.__srcVorbisFile = vorbisFile;
else
audioBuffer.__srcVorbisFile = vorbisFile;
return audioBuffer;
}
+16 -1
View File
@@ -80,6 +80,11 @@ class AudioSource
**/
public var position(get, set):Vector4;
/**
The stereo pan of the audio source.
**/
public var pan(get, set):Float;
/**
The latency of the audio source.
**/
@@ -126,7 +131,7 @@ class AudioSource
@:noCompletion inline private function init():Void
{
__backend.init();
activeSources.push(this);
if (!activeSources.contains(this)) activeSources.push(this);
}
/**
@@ -229,6 +234,16 @@ class AudioSource
return __backend.setPosition(value);
}
@:noCompletion inline private function get_pan():Float
{
return __backend.getPan();
}
@:noCompletion inline private function set_pan(value:Float):Float
{
return __backend.setPan(value);
}
@:noCompletion inline private function get_latency():Float
{
return __backend.getLatency();
+7 -18
View File
@@ -137,7 +137,6 @@ import lime.media.openal.AL;
#if lime
__source.onComplete.remove(source_onComplete);
__source.onLoop.remove(source_onLoop);
__source.dispose();
__source = null;
#end
@@ -165,14 +164,14 @@ import lime.media.openal.AL;
#if lime_cffi
var backend = __source.__backend, i = 0;
if (backend.streamed) {
size = backend.bufferSizes[i = backend.bufferSizes.length - backend.queuedBuffers];
size = backend.bufferLengths[i = backend.bufferLengths.length - backend.requestBuffers];
buf = backend.bufferDatas[i].buffer;
pos -= Math.floor(backend.bufferTimes[i] * buffer.sampleRate * buffer.channels * wordSize);
while (pos > size) {
if (++i >= backend.bufferSizes.length) return false;
if (++i >= backend.bufferLengths.length) return false;
pos -= size;
buf = backend.bufferDatas[i].buffer;
size = backend.bufferSizes[i];
size = backend.bufferLengths[i];
}
}
else
@@ -189,10 +188,10 @@ import lime.media.openal.AL;
if (c % 2 == 0) ((b > leftMax) ? (leftMax = b) : (if ((b = -b) > leftMin) (leftMin = b)));
else ((b > rightMax) ? (rightMax = b) : (if ((b = -b) > rightMin) (rightMin = b)));
if ((pos += wordSize) >= size) #if lime_cffi {
if (!backend.streamed || ++i >= backend.bufferSizes.length) break;
if (!backend.streamed || ++i >= backend.bufferLengths.length) break;
pos = 0;
buf = backend.bufferDatas[i].buffer;
size = backend.bufferSizes[i];
size = backend.bufferLengths[i];
}
#else break; #end
@@ -222,7 +221,6 @@ import lime.media.openal.AL;
}
__source.onComplete.add(source_onComplete);
__source.onLoop.add(source_onLoop);
__isValid = true;
__source.play();
@@ -273,13 +271,9 @@ import lime.media.openal.AL;
if (__isValid)
{
#if lime
// TODO: implement SoundTransform.leftToRight, etc. with Native setAngles?
__source.gain = volume;
var position = __source.position;
position.x = pan;
position.z = -1 * Math.sqrt(1 - Math.pow(pan, 2));
__source.position = position;
__source.pan = pan;
return value;
#end
}
@@ -398,11 +392,6 @@ import lime.media.openal.AL;
dispatchEvent(new Event(Event.SOUND_COMPLETE));
}
@:noCompletion private function source_onLoop():Void
{
//dispatchEvent(new Event(Event.SOUND_LOOP));
}
@:noCompletion private function get___audioSource():AudioSource return __source;
@:noCompletion private function set___audioSource(source:AudioSource):AudioSource return __source = source;
}