mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-15 11:22:55 +00:00
Merge branch 'rewrite/master' into anysad/custom-countdowns
This commit is contained in:
commit
e105f933ac
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit c4bd5281880ac2a1e26016c1219824d2f4247536
|
Subproject commit 0c9ed04f108de63a0821e7ba45c2ac91905699cc
|
34
hmm.json
34
hmm.json
|
@ -23,8 +23,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel-text-input",
|
"name": "flixel-text-input",
|
||||||
"type": "haxelib",
|
"type": "git",
|
||||||
"version": "1.1.0"
|
"dir": null,
|
||||||
|
"ref": "951a0103a17bfa55eed86703ce50b4fb0d7590bc",
|
||||||
|
"url": "https://github.com/FunkinCrew/flixel-text-input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel-ui",
|
"name": "flixel-ui",
|
||||||
|
@ -75,14 +77,14 @@
|
||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "5dc4c933bdc029f6139a47962e3b8c754060f210",
|
"ref": "22f7c5a8ffca90d4677cffd6e570f53761709fbc",
|
||||||
"url": "https://github.com/haxeui/haxeui-core"
|
"url": "https://github.com/haxeui/haxeui-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "haxeui-flixel",
|
"name": "haxeui-flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "57c1604d6b5174839d7e0e012a4dd5dcbfc129da",
|
"ref": "28bb710d0ae5d94b5108787593052165be43b980",
|
||||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -102,7 +104,7 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"url": "https://github.com/HaxeFoundation/hxcpp",
|
"url": "https://github.com/HaxeFoundation/hxcpp",
|
||||||
"ref": "01cfee282a9a783e10c5a7774a3baaf547e6b0a7"
|
"ref": "8dc8020f8465027de6c2aaaed90718bc693651ed"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxcpp-debug-server",
|
"name": "hxcpp-debug-server",
|
||||||
|
@ -123,11 +125,25 @@
|
||||||
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
||||||
"url": "https://github.com/FunkinCrew/json2object"
|
"url": "https://github.com/FunkinCrew/json2object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "jsonpatch",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "f9b83215acd586dc28754b4ae7f69d4c06c3b4d3",
|
||||||
|
"url": "https://github.com/EliteMasterEric/jsonpatch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "jsonpath",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "7a24193717b36393458c15c0435bb7c4470ecdda",
|
||||||
|
"url": "https://github.com/EliteMasterEric/jsonpath"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "872ff6db2f2d27c0243d4ff76802121ded550dd7",
|
"ref": "f6153ffcb1ffcf733f91d531eac5fda4189e07f7",
|
||||||
"url": "https://github.com/FunkinCrew/lime"
|
"url": "https://github.com/FunkinCrew/lime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -162,21 +178,21 @@
|
||||||
"name": "openfl",
|
"name": "openfl",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134",
|
"ref": "8306425c497766739510ab29e876059c96f77bd2",
|
||||||
"url": "https://github.com/FunkinCrew/openfl"
|
"url": "https://github.com/FunkinCrew/openfl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "polymod",
|
"name": "polymod",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "bfbe30d81601b3543d80dce580108ad6b7e182c7",
|
"ref": "98945c6c7f5ecde01a32c4623d3515bf012a023a",
|
||||||
"url": "https://github.com/larsiusprime/polymod"
|
"url": "https://github.com/larsiusprime/polymod"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "thx.core",
|
"name": "thx.core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "6240b6e136f7490d9298edbe8c1891374bd7cdf2",
|
"ref": "76d87418fadd92eb8e1b61f004cff27d656e53dd",
|
||||||
"url": "https://github.com/fponticelli/thx.core"
|
"url": "https://github.com/fponticelli/thx.core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,7 +54,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
public function initAnalyzer()
|
public function initAnalyzer()
|
||||||
{
|
{
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 40);
|
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
|
||||||
|
|
||||||
#if desktop
|
#if desktop
|
||||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||||
|
|
|
@ -117,7 +117,7 @@ class VisShit
|
||||||
{
|
{
|
||||||
// Math.pow3
|
// Math.pow3
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
var buf = snd._channel.__source.buffer;
|
var buf = snd._channel.__audioSource.buffer;
|
||||||
|
|
||||||
// @:privateAccess
|
// @:privateAccess
|
||||||
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
|
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
|
||||||
|
|
|
@ -16,7 +16,7 @@ class WaveformDataParser
|
||||||
|
|
||||||
// Method 1. This only works if the sound has been played before.
|
// Method 1. This only works if the sound has been played before.
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__source?.buffer;
|
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__audioSource?.buffer;
|
||||||
|
|
||||||
if (soundBuffer == null)
|
if (soundBuffer == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SongEventRegistry
|
||||||
|
|
||||||
if (event != null)
|
if (event != null)
|
||||||
{
|
{
|
||||||
trace(' Loaded built-in song event: (${event.id})');
|
trace(' Loaded built-in song event: ${event.id}');
|
||||||
eventCache.set(event.id, event);
|
eventCache.set(event.id, event);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -59,9 +59,9 @@ class SongEventRegistry
|
||||||
static function registerScriptedEvents()
|
static function registerScriptedEvents()
|
||||||
{
|
{
|
||||||
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
||||||
|
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||||
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
|
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
|
||||||
|
|
||||||
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
|
||||||
for (eventCls in scriptedEventClassNames)
|
for (eventCls in scriptedEventClassNames)
|
||||||
{
|
{
|
||||||
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
||||||
|
|
|
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.2.4]
|
||||||
|
### Added
|
||||||
|
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
||||||
|
- If the value isn't present, it will use the `playData.characters.opponent`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the opponent)
|
||||||
|
- Added `playData.characters.playerVocals` to specify which vocal track(s) to play for the player.
|
||||||
|
- If the value isn't present, it will use the `playData.characters.player`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the player)
|
||||||
|
|
||||||
## [2.2.3]
|
## [2.2.3]
|
||||||
### Added
|
### Added
|
||||||
- Added `charter` field to denote authorship of a chart.
|
- Added `charter` field to denote authorship of a chart.
|
||||||
|
|
|
@ -529,12 +529,26 @@ class SongCharacterData implements ICloneable<SongCharacterData>
|
||||||
@:default([])
|
@:default([])
|
||||||
public var altInstrumentals:Array<String> = [];
|
public var altInstrumentals:Array<String> = [];
|
||||||
|
|
||||||
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '')
|
@:optional
|
||||||
|
public var opponentVocals:Null<Array<String>> = null;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
public var playerVocals:Null<Array<String>> = null;
|
||||||
|
|
||||||
|
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '', ?altInstrumentals:Array<String>,
|
||||||
|
?opponentVocals:Array<String>, ?playerVocals:Array<String>)
|
||||||
{
|
{
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.girlfriend = girlfriend;
|
this.girlfriend = girlfriend;
|
||||||
this.opponent = opponent;
|
this.opponent = opponent;
|
||||||
this.instrumental = instrumental;
|
this.instrumental = instrumental;
|
||||||
|
|
||||||
|
this.altInstrumentals = altInstrumentals;
|
||||||
|
this.opponentVocals = opponentVocals;
|
||||||
|
this.playerVocals = playerVocals;
|
||||||
|
|
||||||
|
if (opponentVocals == null) this.opponentVocals = [opponent];
|
||||||
|
if (playerVocals == null) this.playerVocals = [player];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clone():SongCharacterData
|
public function clone():SongCharacterData
|
||||||
|
@ -722,18 +736,6 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||||
{
|
{
|
||||||
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap SongEventData in an abstract so we can overload operators.
|
|
||||||
*/
|
|
||||||
@:forward(time, eventKind, value, activated, getStepTime, clone)
|
|
||||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
|
||||||
{
|
|
||||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
|
||||||
{
|
|
||||||
this = new SongEventDataRaw(time, eventKind, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||||
{
|
{
|
||||||
|
@ -757,27 +759,27 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getHandler():Null<SongEvent>
|
public function getHandler():Null<SongEvent>
|
||||||
{
|
{
|
||||||
return SongEventRegistry.getEvent(this.eventKind);
|
return SongEventRegistry.getEvent(this.eventKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getSchema():Null<SongEventSchema>
|
public function getSchema():Null<SongEventSchema>
|
||||||
{
|
{
|
||||||
return SongEventRegistry.getEventSchema(this.eventKind);
|
return SongEventRegistry.getEventSchema(this.eventKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getDynamic(key:String):Null<Dynamic>
|
public function getDynamic(key:String):Null<Dynamic>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : Reflect.field(this.value, key);
|
return this.value == null ? null : Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getBool(key:String):Null<Bool>
|
public function getBool(key:String):Null<Bool>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getInt(key:String):Null<Int>
|
public function getInt(key:String):Null<Int>
|
||||||
{
|
{
|
||||||
if (this.value == null) return null;
|
if (this.value == null) return null;
|
||||||
var result = Reflect.field(this.value, key);
|
var result = Reflect.field(this.value, key);
|
||||||
|
@ -787,7 +789,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
return cast result;
|
return cast result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getFloat(key:String):Null<Float>
|
public function getFloat(key:String):Null<Float>
|
||||||
{
|
{
|
||||||
if (this.value == null) return null;
|
if (this.value == null) return null;
|
||||||
var result = Reflect.field(this.value, key);
|
var result = Reflect.field(this.value, key);
|
||||||
|
@ -797,17 +799,17 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
return cast result;
|
return cast result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getString(key:String):String
|
public function getString(key:String):String
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getArray(key:String):Array<Dynamic>
|
public function getArray(key:String):Array<Dynamic>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getBoolArray(key:String):Array<Bool>
|
public function getBoolArray(key:String):Array<Bool>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
@ -839,6 +841,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap SongEventData in an abstract so we can overload operators.
|
||||||
|
*/
|
||||||
|
@:forward(time, eventKind, value, activated, getStepTime, clone, getHandler, getSchema, getDynamic, getBool, getInt, getFloat, getString, getArray,
|
||||||
|
getBoolArray, buildTooltip, valueAsStruct)
|
||||||
|
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||||
|
{
|
||||||
|
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||||
|
{
|
||||||
|
this = new SongEventDataRaw(time, eventKind, value);
|
||||||
|
}
|
||||||
|
|
||||||
public function clone():SongEventData
|
public function clone():SongEventData
|
||||||
{
|
{
|
||||||
|
|
|
@ -93,8 +93,8 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||||
public function listBaseGameStageIds():Array<String>
|
public function listBaseGameStageIds():Array<String>
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"mainStage", "spookyMansion", "phillyTrain", "limoRide", "mallXmas", "mallEvil", "school", "schoolEvil", "tankmanBattlefield", "phillyStreets",
|
"mainStage", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallEvil", "school", "schoolEvil",
|
||||||
"phillyBlazin",
|
"tankmanBattlefield", "phillyStreets", "phillyBlazin",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
55
source/funkin/graphics/shaders/AdjustColorShader.hx
Normal file
55
source/funkin/graphics/shaders/AdjustColorShader.hx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
import funkin.Paths;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
|
class AdjustColorShader extends FlxRuntimeShader
|
||||||
|
{
|
||||||
|
public var hue(default, set):Float;
|
||||||
|
public var saturation(default, set):Float;
|
||||||
|
public var brightness(default, set):Float;
|
||||||
|
public var contrast(default, set):Float;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super(Assets.getText(Paths.frag('adjustColor')));
|
||||||
|
// FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'brightness', 'contrast']));
|
||||||
|
hue = 0;
|
||||||
|
saturation = 0;
|
||||||
|
brightness = 0;
|
||||||
|
contrast = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_hue(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('hue', value);
|
||||||
|
this.hue = value;
|
||||||
|
|
||||||
|
return this.hue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_saturation(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('saturation', value);
|
||||||
|
this.saturation = value;
|
||||||
|
|
||||||
|
return this.saturation;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_brightness(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('brightness', value);
|
||||||
|
this.brightness = value;
|
||||||
|
|
||||||
|
return this.brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_contrast(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('contrast', value);
|
||||||
|
this.contrast = value;
|
||||||
|
|
||||||
|
return this.contrast;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
import flixel.FlxG;
|
import flixel.FlxG;
|
||||||
|
import flixel.graphics.frames.FlxFrame;
|
||||||
import flixel.addons.display.FlxRuntimeShader;
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
import lime.graphics.opengl.GLProgram;
|
import lime.graphics.opengl.GLProgram;
|
||||||
import lime.utils.Log;
|
import lime.utils.Log;
|
||||||
|
@ -32,6 +33,9 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
// equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom)
|
// equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom)
|
||||||
uniform vec4 uCameraBounds;
|
uniform vec4 uCameraBounds;
|
||||||
|
|
||||||
|
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||||
|
uniform vec4 uFrameBounds;
|
||||||
|
|
||||||
// screen coord -> world coord conversion
|
// screen coord -> world coord conversion
|
||||||
// returns world coord in px
|
// returns world coord in px
|
||||||
vec2 screenToWorld(vec2 screenCoord) {
|
vec2 screenToWorld(vec2 screenCoord) {
|
||||||
|
@ -56,6 +60,25 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
return (worldCoord - offset) / scale;
|
return (worldCoord - offset) / scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// screen coord -> frame coord conversion
|
||||||
|
// returns normalized frame coord
|
||||||
|
vec2 screenToFrame(vec2 screenCoord) {
|
||||||
|
float left = uFrameBounds.x;
|
||||||
|
float top = uFrameBounds.y;
|
||||||
|
float right = uFrameBounds.z;
|
||||||
|
float bottom = uFrameBounds.w;
|
||||||
|
float width = right - left;
|
||||||
|
float height = bottom - top;
|
||||||
|
|
||||||
|
float clampedX = clamp(screenCoord.x, left, right);
|
||||||
|
float clampedY = clamp(screenCoord.y, top, bottom);
|
||||||
|
|
||||||
|
return vec2(
|
||||||
|
(clampedX - left) / (width),
|
||||||
|
(clampedY - top) / (height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// internally used to get the maximum `openfl_TextureCoordv`
|
// internally used to get the maximum `openfl_TextureCoordv`
|
||||||
vec2 bitmapCoordScale() {
|
vec2 bitmapCoordScale() {
|
||||||
return openfl_TextureCoordv / screenCoord;
|
return openfl_TextureCoordv / screenCoord;
|
||||||
|
@ -80,6 +103,8 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
super(fragmentSource, null, glVersion);
|
super(fragmentSource, null, glVersion);
|
||||||
uScreenResolution.value = [FlxG.width, FlxG.height];
|
uScreenResolution.value = [FlxG.width, FlxG.height];
|
||||||
|
uCameraBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||||
|
uFrameBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||||
}
|
}
|
||||||
|
|
||||||
// basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good
|
// basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good
|
||||||
|
@ -89,6 +114,12 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom];
|
uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateFrameInfo(frame:FlxFrame)
|
||||||
|
{
|
||||||
|
// NOTE: uv.width is actually the right pos and uv.height is the bottom pos
|
||||||
|
uFrameBounds.value = [frame.uv.x, frame.uv.y, frame.uv.width, frame.uv.height];
|
||||||
|
}
|
||||||
|
|
||||||
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -32,6 +32,14 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
||||||
return time = value;
|
return time = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var spriteMode(default, set):Bool = false;
|
||||||
|
|
||||||
|
function set_spriteMode(value:Bool):Bool
|
||||||
|
{
|
||||||
|
this.setBool('uSpriteMode', value);
|
||||||
|
return spriteMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
// The scale of the rain depends on the world coordinate system, so higher resolution makes
|
// The scale of the rain depends on the world coordinate system, so higher resolution makes
|
||||||
// the raindrops smaller. This parameter can be used to adjust the total scale of the scene.
|
// the raindrops smaller. This parameter can be used to adjust the total scale of the scene.
|
||||||
// The size of the raindrops is proportional to the value of this parameter.
|
// The size of the raindrops is proportional to the value of this parameter.
|
||||||
|
|
|
@ -234,6 +234,8 @@ class PolymodHandler
|
||||||
// NOTE: Scripted classes are automatically aliased to their parent class.
|
// NOTE: Scripted classes are automatically aliased to their parent class.
|
||||||
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
||||||
|
|
||||||
|
Polymod.addImportAlias('funkin.data.event.SongEventSchema', funkin.data.event.SongEventSchema.SongEventSchemaRaw);
|
||||||
|
|
||||||
// Add blacklisting for prohibited classes and packages.
|
// Add blacklisting for prohibited classes and packages.
|
||||||
|
|
||||||
// `Sys`
|
// `Sys`
|
||||||
|
|
|
@ -306,7 +306,7 @@ class PauseSubState extends MusicBeatSubState
|
||||||
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
||||||
if (PlayState.instance?.currentDifficulty != null)
|
if (PlayState.instance?.currentDifficulty != null)
|
||||||
{
|
{
|
||||||
metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase();
|
metadataDifficulty.text += PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase();
|
||||||
}
|
}
|
||||||
metadataDifficulty.scrollFactor.set(0, 0);
|
metadataDifficulty.scrollFactor.set(0, 0);
|
||||||
metadata.add(metadataDifficulty);
|
metadata.add(metadataDifficulty);
|
||||||
|
|
|
@ -580,6 +580,8 @@ class PlayState extends MusicBeatSubState
|
||||||
// TODO: Refactor or document
|
// TODO: Refactor or document
|
||||||
var generatedMusic:Bool = false;
|
var generatedMusic:Bool = false;
|
||||||
|
|
||||||
|
var skipEndingTransition:Bool = false;
|
||||||
|
|
||||||
static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK;
|
static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1362,17 +1364,6 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (isGamePaused) return false;
|
if (isGamePaused) return false;
|
||||||
|
|
||||||
if (!startingSong
|
|
||||||
&& FlxG.sound.music != null
|
|
||||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200
|
|
||||||
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200))
|
|
||||||
{
|
|
||||||
trace("VOCALS NEED RESYNC");
|
|
||||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
|
||||||
trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
|
||||||
resyncVocals();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep));
|
if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep));
|
||||||
if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep));
|
if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep));
|
||||||
|
|
||||||
|
@ -1394,6 +1385,17 @@ class PlayState extends MusicBeatSubState
|
||||||
// activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
|
// activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!startingSong
|
||||||
|
&& FlxG.sound.music != null
|
||||||
|
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100
|
||||||
|
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100))
|
||||||
|
{
|
||||||
|
trace("VOCALS NEED RESYNC");
|
||||||
|
if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||||
|
trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||||
|
resyncVocals();
|
||||||
|
}
|
||||||
|
|
||||||
// Only bop camera if zoom level is below 135%
|
// Only bop camera if zoom level is below 135%
|
||||||
if (Preferences.zoomCamera
|
if (Preferences.zoomCamera
|
||||||
&& FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom)
|
&& FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom)
|
||||||
|
@ -1936,7 +1938,9 @@ class PlayState extends MusicBeatSubState
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.sound.music.onComplete = endSong.bind(false);
|
FlxG.sound.music.onComplete = function() {
|
||||||
|
endSong(skipEndingTransition);
|
||||||
|
};
|
||||||
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||||
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||||
FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset);
|
FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset);
|
||||||
|
@ -1976,13 +1980,15 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||||
if (!FlxG.sound.music.playing) return;
|
if (!FlxG.sound.music.playing) return;
|
||||||
|
var timeToPlayAt:Float = Conductor.instance.songPosition - Conductor.instance.instrumentalOffset;
|
||||||
|
FlxG.sound.music.pause();
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
|
|
||||||
FlxG.sound.music.play(FlxG.sound.music.time);
|
FlxG.sound.music.time = timeToPlayAt;
|
||||||
|
FlxG.sound.music.play(false, timeToPlayAt);
|
||||||
|
|
||||||
vocals.time = FlxG.sound.music.time;
|
vocals.time = timeToPlayAt;
|
||||||
vocals.play(false, FlxG.sound.music.time);
|
vocals.play(false, timeToPlayAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -118,22 +118,6 @@ class BaseCharacter extends Bopper
|
||||||
*/
|
*/
|
||||||
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
|
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
|
||||||
|
|
||||||
override function set_animOffsets(value:Array<Float>):Array<Float>
|
|
||||||
{
|
|
||||||
if (animOffsets == null) value = [0, 0];
|
|
||||||
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
|
|
||||||
|
|
||||||
// Make sure animOffets are halved when scale is 0.5.
|
|
||||||
var xDiff = (animOffsets[0] * this.scale.x / (this.isPixel ? 6 : 1)) - value[0];
|
|
||||||
var yDiff = (animOffsets[1] * this.scale.y / (this.isPixel ? 6 : 1)) - value[1];
|
|
||||||
|
|
||||||
// Call the super function so that camera focus point is not affected.
|
|
||||||
super.set_x(this.x + xDiff);
|
|
||||||
super.set_y(this.y + yDiff);
|
|
||||||
|
|
||||||
return animOffsets = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the x position changes, other than via changing the animation offset,
|
* If the x position changes, other than via changing the animation offset,
|
||||||
* then we need to update the camera focus point.
|
* then we need to update the camera focus point.
|
||||||
|
|
|
@ -277,7 +277,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
// If there are no difficulties in the metadata, there's a problem.
|
// If there are no difficulties in the metadata, there's a problem.
|
||||||
if (metadata.playData.difficulties.length == 0)
|
if (metadata.playData.difficulties.length == 0)
|
||||||
{
|
{
|
||||||
trace('[WARN] Song $id has no difficulties listed in metadata!');
|
trace('[SONG] Warning: Song $id (variation ${metadata.variation}) has no difficulties listed in metadata!');
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// There may be more difficulties in the chart file than in the metadata,
|
// There may be more difficulties in the chart file than in the metadata,
|
||||||
|
@ -494,6 +495,24 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
return diffFiltered;
|
return diffFiltered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function listSuffixedDifficulties(variationIds:Array<String>, ?showLocked:Bool, ?showHidden:Bool):Array<String>
|
||||||
|
{
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
for (variation in variationIds)
|
||||||
|
{
|
||||||
|
var difficulties = listDifficulties(variation, null, showLocked, showHidden);
|
||||||
|
for (difficulty in difficulties)
|
||||||
|
{
|
||||||
|
var suffixedDifficulty = (variation != Constants.DEFAULT_VARIATION
|
||||||
|
&& variation != 'erect') ? '$difficulty-${variation}' : difficulty;
|
||||||
|
result.push(suffixedDifficulty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public function hasDifficulty(diffId:String, ?variationId:String, ?variationIds:Array<String>):Bool
|
public function hasDifficulty(diffId:String, ?variationId:String, ?variationIds:Array<String>):Bool
|
||||||
{
|
{
|
||||||
if (variationIds == null) variationIds = [];
|
if (variationIds == null) variationIds = [];
|
||||||
|
@ -706,10 +725,11 @@ class SongDifficulty
|
||||||
* Cache the vocals for a given character.
|
* Cache the vocals for a given character.
|
||||||
* @param id The character we are about to play.
|
* @param id The character we are about to play.
|
||||||
*/
|
*/
|
||||||
public inline function cacheVocals():Void
|
public function cacheVocals():Void
|
||||||
{
|
{
|
||||||
for (voice in buildVoiceList())
|
for (voice in buildVoiceList())
|
||||||
{
|
{
|
||||||
|
trace('Caching vocal track: $voice');
|
||||||
FlxG.sound.cache(voice);
|
FlxG.sound.cache(voice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -721,6 +741,20 @@ class SongDifficulty
|
||||||
* @param id The character we are about to play.
|
* @param id The character we are about to play.
|
||||||
*/
|
*/
|
||||||
public function buildVoiceList():Array<String>
|
public function buildVoiceList():Array<String>
|
||||||
|
{
|
||||||
|
var result:Array<String> = [];
|
||||||
|
result = result.concat(buildPlayerVoiceList());
|
||||||
|
result = result.concat(buildOpponentVoiceList());
|
||||||
|
if (result.length == 0)
|
||||||
|
{
|
||||||
|
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||||
|
// Try to use `Voices.ogg` if no other voices are found.
|
||||||
|
if (Assets.exists(Paths.voices(this.song.id, ''))) result.push(Paths.voices(this.song.id, '$suffix'));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildPlayerVoiceList():Array<String>
|
||||||
{
|
{
|
||||||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||||
|
|
||||||
|
@ -728,62 +762,88 @@ class SongDifficulty
|
||||||
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
|
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
|
||||||
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
|
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
|
||||||
|
|
||||||
var playerId:String = characters.player;
|
if (characters.playerVocals == null)
|
||||||
var voicePlayer:String = Paths.voices(this.song.id, '-$playerId$suffix');
|
|
||||||
while (voicePlayer != null && !Assets.exists(voicePlayer))
|
|
||||||
{
|
{
|
||||||
// Remove the last suffix.
|
var playerId:String = characters.player;
|
||||||
// For example, bf-car becomes bf.
|
var playerVoice:String = Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||||
playerId = playerId.split('-').slice(0, -1).join('-');
|
|
||||||
// Try again.
|
while (playerVoice != null && !Assets.exists(playerVoice))
|
||||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
|
||||||
}
|
|
||||||
if (voicePlayer == null)
|
|
||||||
{
|
|
||||||
// Try again without $suffix.
|
|
||||||
playerId = characters.player;
|
|
||||||
voicePlayer = Paths.voices(this.song.id, '-${playerId}');
|
|
||||||
while (voicePlayer != null && !Assets.exists(voicePlayer))
|
|
||||||
{
|
{
|
||||||
// Remove the last suffix.
|
// Remove the last suffix.
|
||||||
|
// For example, bf-car becomes bf.
|
||||||
playerId = playerId.split('-').slice(0, -1).join('-');
|
playerId = playerId.split('-').slice(0, -1).join('-');
|
||||||
// Try again.
|
// Try again.
|
||||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||||
|
}
|
||||||
|
if (playerVoice == null)
|
||||||
|
{
|
||||||
|
// Try again without $suffix.
|
||||||
|
playerId = characters.player;
|
||||||
|
playerVoice = Paths.voices(this.song.id, '-${playerId}');
|
||||||
|
while (playerVoice != null && !Assets.exists(playerVoice))
|
||||||
|
{
|
||||||
|
// Remove the last suffix.
|
||||||
|
playerId = playerId.split('-').slice(0, -1).join('-');
|
||||||
|
// Try again.
|
||||||
|
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var opponentId:String = characters.opponent;
|
return playerVoice != null ? [playerVoice] : [];
|
||||||
var voiceOpponent:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
|
|
||||||
while (voiceOpponent != null && !Assets.exists(voiceOpponent))
|
|
||||||
{
|
|
||||||
// Remove the last suffix.
|
|
||||||
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
|
||||||
// Try again.
|
|
||||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
|
||||||
}
|
}
|
||||||
if (voiceOpponent == null)
|
else
|
||||||
{
|
{
|
||||||
// Try again without $suffix.
|
// The metadata explicitly defines the list of voices.
|
||||||
opponentId = characters.opponent;
|
var playerIds:Array<String> = characters?.playerVocals ?? [characters.player];
|
||||||
voiceOpponent = Paths.voices(this.song.id, '-${opponentId}');
|
var playerVoices:Array<String> = playerIds.map((id) -> Paths.voices(this.song.id, '-$id$suffix'));
|
||||||
while (voiceOpponent != null && !Assets.exists(voiceOpponent))
|
|
||||||
|
return playerVoices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOpponentVoiceList():Array<String>
|
||||||
|
{
|
||||||
|
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||||
|
|
||||||
|
// Automatically resolve voices by removing suffixes.
|
||||||
|
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
|
||||||
|
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
|
||||||
|
|
||||||
|
if (characters.opponentVocals == null)
|
||||||
|
{
|
||||||
|
var opponentId:String = characters.opponent;
|
||||||
|
var opponentVoice:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||||
|
while (opponentVoice != null && !Assets.exists(opponentVoice))
|
||||||
{
|
{
|
||||||
// Remove the last suffix.
|
// Remove the last suffix.
|
||||||
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
||||||
// Try again.
|
// Try again.
|
||||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
opponentVoice = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||||
|
}
|
||||||
|
if (opponentVoice == null)
|
||||||
|
{
|
||||||
|
// Try again without $suffix.
|
||||||
|
opponentId = characters.opponent;
|
||||||
|
opponentVoice = Paths.voices(this.song.id, '-${opponentId}');
|
||||||
|
while (opponentVoice != null && !Assets.exists(opponentVoice))
|
||||||
|
{
|
||||||
|
// Remove the last suffix.
|
||||||
|
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
||||||
|
// Try again.
|
||||||
|
opponentVoice = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var result:Array<String> = [];
|
return opponentVoice != null ? [opponentVoice] : [];
|
||||||
if (voicePlayer != null) result.push(voicePlayer);
|
}
|
||||||
if (voiceOpponent != null) result.push(voiceOpponent);
|
else
|
||||||
if (voicePlayer == null && voiceOpponent == null)
|
{
|
||||||
{
|
// The metadata explicitly defines the list of voices.
|
||||||
// Try to use `Voices.ogg` if no other voices are found.
|
var opponentIds:Array<String> = characters?.opponentVocals ?? [characters.opponent];
|
||||||
if (Assets.exists(Paths.voices(this.song.id, ''))) result.push(Paths.voices(this.song.id, '$suffix'));
|
var opponentVoices:Array<String> = opponentIds.map((id) -> Paths.voices(this.song.id, '-$id$suffix'));
|
||||||
|
|
||||||
|
return opponentVoices;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -795,26 +855,19 @@ class SongDifficulty
|
||||||
{
|
{
|
||||||
var result:VoicesGroup = new VoicesGroup();
|
var result:VoicesGroup = new VoicesGroup();
|
||||||
|
|
||||||
var voiceList:Array<String> = buildVoiceList();
|
var playerVoiceList:Array<String> = this.buildPlayerVoiceList();
|
||||||
|
var opponentVoiceList:Array<String> = this.buildOpponentVoiceList();
|
||||||
if (voiceList.length == 0)
|
|
||||||
{
|
|
||||||
trace('Could not find any voices for song ${this.song.id}');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add player vocals.
|
// Add player vocals.
|
||||||
if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(voiceList[0]));
|
for (playerVoice in playerVoiceList)
|
||||||
// Add opponent vocals.
|
|
||||||
if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(voiceList[1]));
|
|
||||||
|
|
||||||
// Add additional vocals.
|
|
||||||
if (voiceList.length > 2)
|
|
||||||
{
|
{
|
||||||
for (i in 2...voiceList.length)
|
result.addPlayerVoice(FunkinSound.load(playerVoice));
|
||||||
{
|
}
|
||||||
result.add(FunkinSound.load(Assets.getSound(voiceList[i])));
|
|
||||||
}
|
// Add opponent vocals.
|
||||||
|
for (opponentVoice in opponentVoiceList)
|
||||||
|
{
|
||||||
|
result.addOpponentVoice(FunkinSound.load(opponentVoice));
|
||||||
}
|
}
|
||||||
|
|
||||||
result.playerVoicesOffset = offsets.getVocalOffset(characters.player);
|
result.playerVoicesOffset = offsets.getVocalOffset(characters.player);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package funkin.play.stage;
|
package funkin.play.stage;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
import flixel.FlxCamera;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||||
|
@ -79,11 +80,6 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
if (globalOffsets == null) globalOffsets = [0, 0];
|
if (globalOffsets == null) globalOffsets = [0, 0];
|
||||||
if (globalOffsets == value) return value;
|
if (globalOffsets == value) return value;
|
||||||
|
|
||||||
var xDiff = globalOffsets[0] - value[0];
|
|
||||||
var yDiff = globalOffsets[1] - value[1];
|
|
||||||
|
|
||||||
this.x += xDiff;
|
|
||||||
this.y += yDiff;
|
|
||||||
return globalOffsets = value;
|
return globalOffsets = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,12 +93,6 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
if (animOffsets == null) animOffsets = [0, 0];
|
if (animOffsets == null) animOffsets = [0, 0];
|
||||||
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
|
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
|
||||||
|
|
||||||
var xDiff = animOffsets[0] - value[0];
|
|
||||||
var yDiff = animOffsets[1] - value[1];
|
|
||||||
|
|
||||||
this.x += xDiff;
|
|
||||||
this.y += yDiff;
|
|
||||||
|
|
||||||
return animOffsets = value;
|
return animOffsets = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,14 +310,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
function applyAnimationOffsets(name:String):Void
|
function applyAnimationOffsets(name:String):Void
|
||||||
{
|
{
|
||||||
var offsets = animationOffsets.get(name);
|
var offsets = animationOffsets.get(name);
|
||||||
if (offsets != null && !(offsets[0] == 0 && offsets[1] == 0))
|
this.animOffsets = offsets;
|
||||||
{
|
|
||||||
this.animOffsets = [offsets[0] + globalOffsets[0], offsets[1] + globalOffsets[1]];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.animOffsets = globalOffsets;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAnimationFinished():Bool
|
public function isAnimationFinished():Bool
|
||||||
|
@ -351,6 +334,15 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
return this.animation.curAnim.name;
|
return this.animation.curAnim.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// override getScreenPosition (used by FlxSprite's draw method) to account for animation offsets.
|
||||||
|
override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
|
||||||
|
{
|
||||||
|
var output:FlxPoint = super.getScreenPosition(result, camera);
|
||||||
|
output.x -= (animOffsets[0] - globalOffsets[0]) * this.scale.x;
|
||||||
|
output.y -= (animOffsets[1] - globalOffsets[1]) * this.scale.y;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
public function onPause(event:PauseScriptEvent) {}
|
public function onPause(event:PauseScriptEvent) {}
|
||||||
|
|
||||||
public function onResume(event:ScriptEvent) {}
|
public function onResume(event:ScriptEvent) {}
|
||||||
|
|
|
@ -152,6 +152,32 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWidth():Int
|
||||||
|
{
|
||||||
|
var width = 0;
|
||||||
|
for (char in this.text.split(""))
|
||||||
|
{
|
||||||
|
switch (char)
|
||||||
|
{
|
||||||
|
case " ":
|
||||||
|
{
|
||||||
|
width += 40;
|
||||||
|
}
|
||||||
|
case "\n":
|
||||||
|
{}
|
||||||
|
case char:
|
||||||
|
{
|
||||||
|
var sprite = new AtlasChar(atlas, char);
|
||||||
|
sprite.revive();
|
||||||
|
sprite.char = char;
|
||||||
|
sprite.alpha = 1;
|
||||||
|
width += Std.int(sprite.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
override function toString()
|
override function toString()
|
||||||
{
|
{
|
||||||
return "InputItem, " + FlxStringUtil.getDebugString([
|
return "InputItem, " + FlxStringUtil.getDebugString([
|
||||||
|
|
|
@ -5701,11 +5701,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
case 'mainStage':
|
case 'mainStage':
|
||||||
PlayStatePlaylist.campaignId = 'week1';
|
PlayStatePlaylist.campaignId = 'week1';
|
||||||
case 'spookyMansion':
|
case 'spookyMansion' | 'spookyMansionErect':
|
||||||
PlayStatePlaylist.campaignId = 'week2';
|
PlayStatePlaylist.campaignId = 'week2';
|
||||||
case 'phillyTrain':
|
case 'phillyTrain' | 'phillyTrainErect':
|
||||||
PlayStatePlaylist.campaignId = 'week3';
|
PlayStatePlaylist.campaignId = 'week3';
|
||||||
case 'limoRide':
|
case 'limoRide' | 'limoRideErect':
|
||||||
PlayStatePlaylist.campaignId = 'week4';
|
PlayStatePlaylist.campaignId = 'week4';
|
||||||
case 'mallXmas' | 'mallEvil':
|
case 'mallXmas' | 'mallEvil':
|
||||||
PlayStatePlaylist.campaignId = 'week5';
|
PlayStatePlaylist.campaignId = 'week5';
|
||||||
|
|
|
@ -192,7 +192,7 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
|
||||||
var paramStepper:NumberStepper = new NumberStepper();
|
var paramStepper:NumberStepper = new NumberStepper();
|
||||||
paramStepper.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? 0.0;
|
paramStepper.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? 0.0;
|
||||||
paramStepper.percentWidth = 100;
|
paramStepper.percentWidth = 100;
|
||||||
paramStepper.step = param.data?.step ?? 1;
|
paramStepper.step = param.data?.step ?? 1.0;
|
||||||
|
|
||||||
// this check should be unnecessary but for some reason
|
// this check should be unnecessary but for some reason
|
||||||
// even when these are null it will set it to 0
|
// even when these are null it will set it to 0
|
||||||
|
@ -283,7 +283,7 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var heightToSet:Int = Std.int(Math.max(DIALOG_HEIGHT, (toolboxNotesGrid?.height ?? 50) + HEIGHT_OFFSET)) + MINIMIZE_FIX;
|
var heightToSet:Int = Std.int(Math.max(DIALOG_HEIGHT, (toolboxNotesGrid?.height ?? 50.0) + HEIGHT_OFFSET)) + MINIMIZE_FIX;
|
||||||
if (this.height != heightToSet)
|
if (this.height != heightToSet)
|
||||||
{
|
{
|
||||||
this.height = heightToSet;
|
this.height = heightToSet;
|
||||||
|
|
|
@ -339,7 +339,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
// Only display songs which actually have available difficulties for the current character.
|
// Only display songs which actually have available difficulties for the current character.
|
||||||
var displayedVariations = song.getVariationsByCharacter(currentCharacter);
|
var displayedVariations = song.getVariationsByCharacter(currentCharacter);
|
||||||
trace('Displayed Variations (${songId}): $displayedVariations');
|
trace('Displayed Variations (${songId}): $displayedVariations');
|
||||||
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
|
var availableDifficultiesForSong:Array<String> = song.listSuffixedDifficulties(displayedVariations, false, false);
|
||||||
trace('Available Difficulties: $availableDifficultiesForSong');
|
trace('Available Difficulties: $availableDifficultiesForSong');
|
||||||
if (availableDifficultiesForSong.length == 0) continue;
|
if (availableDifficultiesForSong.length == 0) continue;
|
||||||
|
|
||||||
|
@ -1120,7 +1120,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
// NOW we can interact with the menu
|
// NOW we can interact with the menu
|
||||||
busy = false;
|
busy = false;
|
||||||
grpCapsules.members[curSelected].sparkle.alpha = 0.7;
|
capsule.sparkle.alpha = 0.7;
|
||||||
playCurSongPreview(capsule);
|
playCurSongPreview(capsule);
|
||||||
}, null);
|
}, null);
|
||||||
|
|
||||||
|
@ -1527,7 +1527,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
var moveDataX = funnyMoveShit.x ?? spr.x;
|
var moveDataX = funnyMoveShit.x ?? spr.x;
|
||||||
var moveDataY = funnyMoveShit.y ?? spr.y;
|
var moveDataY = funnyMoveShit.y ?? spr.y;
|
||||||
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
|
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
|
||||||
var moveDataWait = funnyMoveShit.wait ?? 0;
|
var moveDataWait = funnyMoveShit.wait ?? 0.0;
|
||||||
|
|
||||||
FlxTween.tween(spr, {x: moveDataX, y: moveDataY}, moveDataSpeed, {ease: FlxEase.expoIn});
|
FlxTween.tween(spr, {x: moveDataX, y: moveDataY}, moveDataSpeed, {ease: FlxEase.expoIn});
|
||||||
|
|
||||||
|
@ -1674,6 +1674,9 @@ class FreeplayState extends MusicBeatSubState
|
||||||
songCapsule.init(null, null, null);
|
songCapsule.init(null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the song preview in case we changed variations (normal->erect etc)
|
||||||
|
playCurSongPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the album graphic and play the animation if relevant.
|
// Set the album graphic and play the animation if relevant.
|
||||||
|
@ -1912,8 +1915,10 @@ class FreeplayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playCurSongPreview(daSongCapsule:SongMenuItem):Void
|
public function playCurSongPreview(?daSongCapsule:SongMenuItem):Void
|
||||||
{
|
{
|
||||||
|
if (daSongCapsule == null) daSongCapsule = grpCapsules.members[curSelected];
|
||||||
|
|
||||||
if (curSelected == 0)
|
if (curSelected == 0)
|
||||||
{
|
{
|
||||||
FunkinSound.playMusic('freeplayRandom',
|
FunkinSound.playMusic('freeplayRandom',
|
||||||
|
@ -2145,7 +2150,7 @@ class FreeplaySongData
|
||||||
|
|
||||||
function updateValues(variations:Array<String>):Void
|
function updateValues(variations:Array<String>):Void
|
||||||
{
|
{
|
||||||
this.songDifficulties = song.listDifficulties(null, variations, false, false);
|
this.songDifficulties = song.listSuffixedDifficulties(variations, false, false);
|
||||||
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||||
|
|
||||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, null, variations);
|
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, null, variations);
|
||||||
|
@ -2207,15 +2212,26 @@ class DifficultySprite extends FlxSprite
|
||||||
|
|
||||||
difficultyId = diffId;
|
difficultyId = diffId;
|
||||||
|
|
||||||
if (Assets.exists(Paths.file('images/freeplay/freeplay${diffId}.xml')))
|
var assetDiffId:String = diffId;
|
||||||
|
while (!Assets.exists(Paths.image('freeplay/freeplay${assetDiffId}')))
|
||||||
{
|
{
|
||||||
this.frames = Paths.getSparrowAtlas('freeplay/freeplay${diffId}');
|
// Remove the last suffix of the difficulty id until we find an asset or there are no more suffixes.
|
||||||
|
var assetDiffIdParts:Array<String> = assetDiffId.split('-');
|
||||||
|
assetDiffIdParts.pop();
|
||||||
|
if (assetDiffIdParts.length == 0) break;
|
||||||
|
assetDiffId = assetDiffIdParts.join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for an XML to use an animation instead of an image.
|
||||||
|
if (Assets.exists(Paths.file('images/freeplay/freeplay${assetDiffId}.xml')))
|
||||||
|
{
|
||||||
|
this.frames = Paths.getSparrowAtlas('freeplay/freeplay${assetDiffId}');
|
||||||
this.animation.addByPrefix('idle', 'idle0', 24, true);
|
this.animation.addByPrefix('idle', 'idle0', 24, true);
|
||||||
if (Preferences.flashingLights) this.animation.play('idle');
|
if (Preferences.flashingLights) this.animation.play('idle');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.loadGraphic(Paths.image('freeplay/freeplay' + diffId));
|
this.loadGraphic(Paths.image('freeplay/freeplay' + assetDiffId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ class SongMenuItem extends FlxSpriteGroup
|
||||||
|
|
||||||
sparkle = new FlxSprite(ranking.x, ranking.y);
|
sparkle = new FlxSprite(ranking.x, ranking.y);
|
||||||
sparkle.frames = Paths.getSparrowAtlas('freeplay/sparkle');
|
sparkle.frames = Paths.getSparrowAtlas('freeplay/sparkle');
|
||||||
sparkle.animation.addByPrefix('sparkle', 'sparkle', 24, false);
|
sparkle.animation.addByPrefix('sparkle', 'sparkle Export0', 24, false);
|
||||||
sparkle.animation.play('sparkle', true);
|
sparkle.animation.play('sparkle', true);
|
||||||
sparkle.scale.set(0.8, 0.8);
|
sparkle.scale.set(0.8, 0.8);
|
||||||
sparkle.blend = BlendMode.ADD;
|
sparkle.blend = BlendMode.ADD;
|
||||||
|
@ -523,7 +523,6 @@ class SongMenuItem extends FlxSpriteGroup
|
||||||
checkWeek(songData?.songId);
|
checkWeek(songData?.songId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var frameInTicker:Float = 0;
|
var frameInTicker:Float = 0;
|
||||||
var frameInTypeBeat:Int = 0;
|
var frameInTypeBeat:Int = 0;
|
||||||
|
|
||||||
|
|
10
source/funkin/ui/options/MenuItemEnums.hx
Normal file
10
source/funkin/ui/options/MenuItemEnums.hx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package funkin.ui.options;
|
||||||
|
|
||||||
|
// Add enums for use with `EnumPreferenceItem` here!
|
||||||
|
/* Example:
|
||||||
|
class MyOptionEnum
|
||||||
|
{
|
||||||
|
public static inline var YuhUh = "true"; // "true" is the value's ID
|
||||||
|
public static inline var NuhUh = "false";
|
||||||
|
}
|
||||||
|
*/
|
|
@ -8,6 +8,11 @@ import funkin.ui.AtlasText.AtlasFont;
|
||||||
import funkin.ui.options.OptionsState.Page;
|
import funkin.ui.options.OptionsState.Page;
|
||||||
import funkin.graphics.FunkinCamera;
|
import funkin.graphics.FunkinCamera;
|
||||||
import funkin.ui.TextMenuList.TextMenuItem;
|
import funkin.ui.TextMenuList.TextMenuItem;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
|
import funkin.ui.options.MenuItemEnums;
|
||||||
|
import funkin.ui.options.items.CheckboxPreferenceItem;
|
||||||
|
import funkin.ui.options.items.NumberPreferenceItem;
|
||||||
|
import funkin.ui.options.items.EnumPreferenceItem;
|
||||||
|
|
||||||
class PreferencesMenu extends Page
|
class PreferencesMenu extends Page
|
||||||
{
|
{
|
||||||
|
@ -69,11 +74,51 @@ class PreferencesMenu extends Page
|
||||||
}, Preferences.autoPause);
|
}, Preferences.autoPause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override function update(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
// Indent the selected item.
|
||||||
|
items.forEach(function(daItem:TextMenuItem) {
|
||||||
|
var thyOffset:Int = 0;
|
||||||
|
|
||||||
|
// Initializing thy text width (if thou text present)
|
||||||
|
var thyTextWidth:Int = 0;
|
||||||
|
if (Std.isOfType(daItem, EnumPreferenceItem)) thyTextWidth = cast(daItem, EnumPreferenceItem).lefthandText.getWidth();
|
||||||
|
else if (Std.isOfType(daItem, NumberPreferenceItem)) thyTextWidth = cast(daItem, NumberPreferenceItem).lefthandText.getWidth();
|
||||||
|
|
||||||
|
if (thyTextWidth != 0)
|
||||||
|
{
|
||||||
|
// Magic number because of the weird offset thats being added by default
|
||||||
|
thyOffset += thyTextWidth - 75;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.selectedItem == daItem)
|
||||||
|
{
|
||||||
|
thyOffset += 150;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
thyOffset += 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
daItem.x = thyOffset;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// - Preference item creation methods -
|
||||||
|
// Should be moved into a separate PreferenceItems class but you can't access PreferencesMenu.items and PreferencesMenu.preferenceItems from outside.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a pref item that works with booleans
|
||||||
|
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||||
|
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||||
|
*/
|
||||||
function createPrefItemCheckbox(prefName:String, prefDesc:String, onChange:Bool->Void, defaultValue:Bool):Void
|
function createPrefItemCheckbox(prefName:String, prefDesc:String, onChange:Bool->Void, defaultValue:Bool):Void
|
||||||
{
|
{
|
||||||
var checkbox:CheckboxPreferenceItem = new CheckboxPreferenceItem(0, 120 * (items.length - 1 + 1), defaultValue);
|
var checkbox:CheckboxPreferenceItem = new CheckboxPreferenceItem(0, 120 * (items.length - 1 + 1), defaultValue);
|
||||||
|
|
||||||
items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function() {
|
items.createItem(0, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function() {
|
||||||
var value = !checkbox.currentValue;
|
var value = !checkbox.currentValue;
|
||||||
onChange(value);
|
onChange(value);
|
||||||
checkbox.currentValue = value;
|
checkbox.currentValue = value;
|
||||||
|
@ -82,62 +127,54 @@ class PreferencesMenu extends Page
|
||||||
preferenceItems.add(checkbox);
|
preferenceItems.add(checkbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
/**
|
||||||
|
* Creates a pref item that works with general numbers
|
||||||
|
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||||
|
* @param valueFormatter Will get called every time the game needs to display the float value; use this to change how the displayed value looks
|
||||||
|
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||||
|
* @param min Minimum value (example: 0)
|
||||||
|
* @param max Maximum value (example: 10)
|
||||||
|
* @param step The value to increment/decrement by (default = 0.1)
|
||||||
|
* @param precision Rounds decimals up to a `precision` amount of digits (ex: 4 -> 0.1234, 2 -> 0.12)
|
||||||
|
*/
|
||||||
|
function createPrefItemNumber(prefName:String, prefDesc:String, onChange:Float->Void, ?valueFormatter:Float->String, defaultValue:Int, min:Int, max:Int,
|
||||||
|
step:Float = 0.1, precision:Int):Void
|
||||||
{
|
{
|
||||||
super.update(elapsed);
|
var item = new NumberPreferenceItem(0, (120 * items.length) + 30, prefName, defaultValue, min, max, step, precision, onChange, valueFormatter);
|
||||||
|
items.addItem(prefName, item);
|
||||||
|
preferenceItems.add(item.lefthandText);
|
||||||
|
}
|
||||||
|
|
||||||
// Indent the selected item.
|
/**
|
||||||
// TODO: Only do this on menu change?
|
* Creates a pref item that works with number percentages
|
||||||
items.forEach(function(daItem:TextMenuItem) {
|
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||||
if (items.selectedItem == daItem) daItem.x = 150;
|
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||||
else
|
* @param min Minimum value (default = 0)
|
||||||
daItem.x = 120;
|
* @param max Maximum value (default = 100)
|
||||||
});
|
*/
|
||||||
}
|
function createPrefItemPercentage(prefName:String, prefDesc:String, onChange:Int->Void, defaultValue:Int, min:Int = 0, max:Int = 100):Void
|
||||||
}
|
{
|
||||||
|
var newCallback = function(value:Float) {
|
||||||
class CheckboxPreferenceItem extends FlxSprite
|
onChange(Std.int(value));
|
||||||
{
|
};
|
||||||
public var currentValue(default, set):Bool;
|
var formatter = function(value:Float) {
|
||||||
|
return '${value}%';
|
||||||
public function new(x:Float, y:Float, defaultValue:Bool = false)
|
};
|
||||||
{
|
var item = new NumberPreferenceItem(0, (120 * items.length) + 30, prefName, defaultValue, min, max, 10, 0, newCallback, formatter);
|
||||||
super(x, y);
|
items.addItem(prefName, item);
|
||||||
|
preferenceItems.add(item.lefthandText);
|
||||||
frames = Paths.getSparrowAtlas('checkboxThingie');
|
}
|
||||||
animation.addByPrefix('static', 'Check Box unselected', 24, false);
|
|
||||||
animation.addByPrefix('checked', 'Check Box selecting animation', 24, false);
|
/**
|
||||||
|
* Creates a pref item that works with enums
|
||||||
setGraphicSize(Std.int(width * 0.7));
|
* @param values Maps enum values to display strings _(ex: `NoteHitSoundType.PingPong => "Ping pong"`)_
|
||||||
updateHitbox();
|
* @param onChange Gets called every time the player changes the value; use this to apply the value
|
||||||
|
* @param defaultValue The value that is loaded in when the pref item is created (usually your Preferences.settingVariable)
|
||||||
this.currentValue = defaultValue;
|
*/
|
||||||
}
|
function createPrefItemEnum(prefName:String, prefDesc:String, values:Map<String, String>, onChange:String->Void, defaultValue:String):Void
|
||||||
|
{
|
||||||
override function update(elapsed:Float)
|
var item = new EnumPreferenceItem(0, (120 * items.length) + 30, prefName, values, defaultValue, onChange);
|
||||||
{
|
items.addItem(prefName, item);
|
||||||
super.update(elapsed);
|
preferenceItems.add(item.lefthandText);
|
||||||
|
|
||||||
switch (animation.curAnim.name)
|
|
||||||
{
|
|
||||||
case 'static':
|
|
||||||
offset.set();
|
|
||||||
case 'checked':
|
|
||||||
offset.set(17, 70);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_currentValue(value:Bool):Bool
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
animation.play('checked', true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
animation.play('static');
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentValue = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
49
source/funkin/ui/options/items/CheckboxPreferenceItem.hx
Normal file
49
source/funkin/ui/options/items/CheckboxPreferenceItem.hx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package funkin.ui.options.items;
|
||||||
|
|
||||||
|
import flixel.FlxSprite.FlxSprite;
|
||||||
|
|
||||||
|
class CheckboxPreferenceItem extends FlxSprite
|
||||||
|
{
|
||||||
|
public var currentValue(default, set):Bool;
|
||||||
|
|
||||||
|
public function new(x:Float, y:Float, defaultValue:Bool = false)
|
||||||
|
{
|
||||||
|
super(x, y);
|
||||||
|
|
||||||
|
frames = Paths.getSparrowAtlas('checkboxThingie');
|
||||||
|
animation.addByPrefix('static', 'Check Box unselected', 24, false);
|
||||||
|
animation.addByPrefix('checked', 'Check Box selecting animation', 24, false);
|
||||||
|
|
||||||
|
setGraphicSize(Std.int(width * 0.7));
|
||||||
|
updateHitbox();
|
||||||
|
|
||||||
|
this.currentValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function update(elapsed:Float)
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
switch (animation.curAnim.name)
|
||||||
|
{
|
||||||
|
case 'static':
|
||||||
|
offset.set();
|
||||||
|
case 'checked':
|
||||||
|
offset.set(17, 70);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_currentValue(value:Bool):Bool
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
animation.play('checked', true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
animation.play('static');
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentValue = value;
|
||||||
|
}
|
||||||
|
}
|
84
source/funkin/ui/options/items/EnumPreferenceItem.hx
Normal file
84
source/funkin/ui/options/items/EnumPreferenceItem.hx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package funkin.ui.options.items;
|
||||||
|
|
||||||
|
import funkin.ui.TextMenuList;
|
||||||
|
import funkin.ui.AtlasText;
|
||||||
|
import funkin.input.Controls;
|
||||||
|
import funkin.ui.options.MenuItemEnums;
|
||||||
|
import haxe.EnumTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference item that allows the player to pick a value from an enum (list of values)
|
||||||
|
*/
|
||||||
|
class EnumPreferenceItem extends TextMenuItem
|
||||||
|
{
|
||||||
|
function controls():Controls
|
||||||
|
{
|
||||||
|
return PlayerSettings.player1.controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var lefthandText:AtlasText;
|
||||||
|
|
||||||
|
public var currentValue:String;
|
||||||
|
public var onChangeCallback:Null<String->Void>;
|
||||||
|
public var map:Map<String, String>;
|
||||||
|
public var keys:Array<String> = [];
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
public function new(x:Float, y:Float, name:String, map:Map<String, String>, defaultValue:String, ?callback:String->Void)
|
||||||
|
{
|
||||||
|
super(x, y, name, function() {
|
||||||
|
callback(this.currentValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateHitbox();
|
||||||
|
|
||||||
|
this.map = map;
|
||||||
|
this.currentValue = defaultValue;
|
||||||
|
this.onChangeCallback = callback;
|
||||||
|
|
||||||
|
var i:Int = 0;
|
||||||
|
for (key in map.keys())
|
||||||
|
{
|
||||||
|
this.keys.push(key);
|
||||||
|
if (this.currentValue == key) index = i;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lefthandText = new AtlasText(15, y, formatted(defaultValue), AtlasFont.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function update(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
// var fancyTextFancyColor:Color;
|
||||||
|
if (selected)
|
||||||
|
{
|
||||||
|
var shouldDecrease:Bool = controls().UI_LEFT_P;
|
||||||
|
var shouldIncrease:Bool = controls().UI_RIGHT_P;
|
||||||
|
|
||||||
|
if (shouldDecrease) index -= 1;
|
||||||
|
if (shouldIncrease) index += 1;
|
||||||
|
|
||||||
|
if (index > keys.length - 1) index = 0;
|
||||||
|
if (index < 0) index = keys.length - 1;
|
||||||
|
|
||||||
|
currentValue = keys[index];
|
||||||
|
if (onChangeCallback != null && (shouldIncrease || shouldDecrease))
|
||||||
|
{
|
||||||
|
onChangeCallback(currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lefthandText.text = formatted(currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatted(value:String):String
|
||||||
|
{
|
||||||
|
// FIXME: Can't add arrows around the text because the font doesn't support < >
|
||||||
|
// var leftArrow:String = selected ? '<' : '';
|
||||||
|
// var rightArrow:String = selected ? '>' : '';
|
||||||
|
return '${map.get(value) ?? value}';
|
||||||
|
}
|
||||||
|
}
|
136
source/funkin/ui/options/items/NumberPreferenceItem.hx
Normal file
136
source/funkin/ui/options/items/NumberPreferenceItem.hx
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package funkin.ui.options.items;
|
||||||
|
|
||||||
|
import funkin.ui.TextMenuList;
|
||||||
|
import funkin.ui.AtlasText;
|
||||||
|
import funkin.input.Controls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference item that allows the player to pick a value between min and max
|
||||||
|
*/
|
||||||
|
class NumberPreferenceItem extends TextMenuItem
|
||||||
|
{
|
||||||
|
function controls():Controls
|
||||||
|
{
|
||||||
|
return PlayerSettings.player1.controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widgets
|
||||||
|
public var lefthandText:AtlasText;
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
static final HOLD_DELAY:Float = 0.3; // seconds
|
||||||
|
static final CHANGE_RATE:Float = 0.08; // seconds
|
||||||
|
|
||||||
|
// Constructor-initialized variables
|
||||||
|
public var currentValue:Float;
|
||||||
|
public var min:Float;
|
||||||
|
public var max:Float;
|
||||||
|
public var step:Float;
|
||||||
|
public var precision:Int;
|
||||||
|
public var onChangeCallback:Null<Float->Void>;
|
||||||
|
public var valueFormatter:Null<Float->String>;
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
var holdDelayTimer:Float = HOLD_DELAY; // seconds
|
||||||
|
var changeRateTimer:Float = 0.0; // seconds
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param min Minimum value (example: 0)
|
||||||
|
* @param max Maximum value (example: 100)
|
||||||
|
* @param step The value to increment/decrement by (example: 10)
|
||||||
|
* @param callback Will get called every time the user changes the setting; use this to apply/save the setting.
|
||||||
|
* @param valueFormatter Will get called every time the game needs to display the float value; use this to change how the displayed string looks
|
||||||
|
*/
|
||||||
|
public function new(x:Float, y:Float, name:String, defaultValue:Float, min:Float, max:Float, step:Float, precision:Int, ?callback:Float->Void,
|
||||||
|
?valueFormatter:Float->String):Void
|
||||||
|
{
|
||||||
|
super(x, y, name, function() {
|
||||||
|
callback(this.currentValue);
|
||||||
|
});
|
||||||
|
lefthandText = new AtlasText(15, y, formatted(defaultValue), AtlasFont.DEFAULT);
|
||||||
|
|
||||||
|
updateHitbox();
|
||||||
|
|
||||||
|
this.currentValue = defaultValue;
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
this.step = step;
|
||||||
|
this.precision = precision;
|
||||||
|
this.onChangeCallback = callback;
|
||||||
|
this.valueFormatter = valueFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function update(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
// var fancyTextFancyColor:Color;
|
||||||
|
if (selected)
|
||||||
|
{
|
||||||
|
holdDelayTimer -= elapsed;
|
||||||
|
if (holdDelayTimer <= 0.0)
|
||||||
|
{
|
||||||
|
changeRateTimer -= elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jpLeft:Bool = controls().UI_LEFT_P;
|
||||||
|
var jpRight:Bool = controls().UI_RIGHT_P;
|
||||||
|
|
||||||
|
if (jpLeft || jpRight)
|
||||||
|
{
|
||||||
|
holdDelayTimer = HOLD_DELAY;
|
||||||
|
changeRateTimer = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldDecrease:Bool = jpLeft;
|
||||||
|
var shouldIncrease:Bool = jpRight;
|
||||||
|
|
||||||
|
if (controls().UI_LEFT && holdDelayTimer <= 0.0 && changeRateTimer <= 0.0)
|
||||||
|
{
|
||||||
|
shouldDecrease = true;
|
||||||
|
changeRateTimer = CHANGE_RATE;
|
||||||
|
}
|
||||||
|
else if (controls().UI_RIGHT && holdDelayTimer <= 0.0 && changeRateTimer <= 0.0)
|
||||||
|
{
|
||||||
|
shouldIncrease = true;
|
||||||
|
changeRateTimer = CHANGE_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually increasing/decreasing the value
|
||||||
|
if (shouldDecrease)
|
||||||
|
{
|
||||||
|
var isBelowMin:Bool = currentValue - step < min;
|
||||||
|
currentValue = (currentValue - step).clamp(min, max);
|
||||||
|
if (onChangeCallback != null && !isBelowMin) onChangeCallback(currentValue);
|
||||||
|
}
|
||||||
|
else if (shouldIncrease)
|
||||||
|
{
|
||||||
|
var isAboveMax:Bool = currentValue + step > max;
|
||||||
|
currentValue = (currentValue + step).clamp(min, max);
|
||||||
|
if (onChangeCallback != null && !isAboveMax) onChangeCallback(currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lefthandText.text = formatted(currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Turns the float into a string */
|
||||||
|
function formatted(value:Float):String
|
||||||
|
{
|
||||||
|
var float:Float = toFixed(value);
|
||||||
|
if (valueFormatter != null)
|
||||||
|
{
|
||||||
|
return valueFormatter(float);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return '${float}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFixed(value:Float):Float
|
||||||
|
{
|
||||||
|
var multiplier:Float = Math.pow(10, precision);
|
||||||
|
return Math.floor(value * multiplier) / multiplier;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ class LevelProp extends Bopper
|
||||||
this.propData = value;
|
this.propData = value;
|
||||||
|
|
||||||
this.visible = this.propData != null;
|
this.visible = this.propData != null;
|
||||||
danceEvery = this.propData?.danceEvery ?? 0;
|
danceEvery = this.propData?.danceEvery ?? 0.0;
|
||||||
|
|
||||||
applyData();
|
applyData();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue