Merge pull request #347 from FunkinCrew/feature/blazin-cutscene

Blazin' Cutscene
This commit is contained in:
Cameron Taylor 2024-02-27 22:04:33 -05:00 committed by GitHub
commit b88d0b063b
45 changed files with 747 additions and 251 deletions

8
.prettierignore Normal file
View file

@ -0,0 +1,8 @@
# Ignore artifacts
export
# Ignore all asset files (including FlxAnimate JSONs)
assets
# Don't ignore data files
!assets/preload/data

26
.vscode/settings.json vendored
View file

@ -93,7 +93,7 @@
{
"label": "Windows / Debug",
"target": "windows",
"args": ["-debug"]
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug",
@ -103,7 +103,7 @@
{
"label": "Windows / Debug (FlxAnimate Test)",
"target": "windows",
"args": ["-debug", "-DANIMATE"]
"args": ["-debug", "-DANIMATE", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug (FlxAnimate Test)",
@ -113,7 +113,7 @@
{
"label": "Windows / Debug (Straight to Freeplay)",
"target": "windows",
"args": ["-debug", "-DFREEPLAY"]
"args": ["-debug", "-DFREEPLAY", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug (Straight to Freeplay)",
@ -123,7 +123,11 @@
{
"label": "Windows / Debug (Straight to Play - Bopeebo Normal)",
"target": "windows",
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
"args": [
"-debug",
"-DSONG=bopeebo -DDIFFICULTY=normal",
"-DFORCE_DEBUG_VERSION"
]
},
{
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
@ -133,7 +137,7 @@
{
"label": "Windows / Debug (Conversation Test)",
"target": "windows",
"args": ["-debug", "-DDIALOGUE"]
"args": ["-debug", "-DDIALOGUE", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug (Conversation Test)",
@ -143,7 +147,7 @@
{
"label": "Windows / Debug (Straight to Chart Editor)",
"target": "windows",
"args": ["-debug", "-DCHARTING"]
"args": ["-debug", "-DCHARTING", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug (Straight to Chart Editor)",
@ -153,7 +157,7 @@
{
"label": "Windows / Debug (Straight to Animation Editor)",
"target": "windows",
"args": ["-debug", "-DANIMDEBUG"]
"args": ["-debug", "-DANIMDEBUG", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug (Straight to Animation Editor)",
@ -163,7 +167,7 @@
{
"label": "Windows / Debug (Latency Test)",
"target": "windows",
"args": ["-debug", "-DLATENCY"]
"args": ["-debug", "-DLATENCY", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug (Latency Test)",
@ -173,7 +177,7 @@
{
"label": "Windows / Debug (Waveform Test)",
"target": "windows",
"args": ["-debug", "-DWAVEFORM"]
"args": ["-debug", "-DWAVEFORM", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HashLink / Debug (Waveform Test)",
@ -183,12 +187,12 @@
{
"label": "HTML5 / Debug",
"target": "html5",
"args": ["-debug"]
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
},
{
"label": "HTML5 / Debug (Watch)",
"target": "html5",
"args": ["-debug", "-watch"]
"args": ["-debug", "-watch", "-DFORCE_DEBUG_VERSION"]
}
],
"cmake.configureOnOpen": false,

View file

@ -108,7 +108,7 @@
<haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI -->
<haxelib name="polymod" /> <!-- Modding framework -->
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
<haxelib name="hxCodec" if="desktop release" /> <!-- Video playback -->
<haxelib name="hxCodec" if="desktop" /> <!-- Video playback -->
<haxelib name="json2object" /> <!-- JSON parsing -->
<haxelib name="thx.semver" /> <!-- Version string handling -->

2
assets

@ -1 +1 @@
Subproject commit cb0fbb56b9667f68a9776a216c16a4e2b29f7096
Subproject commit f8c2595844eff9375b522f117bfdadbdc6728c49

View file

@ -54,14 +54,14 @@
"name": "haxeui-core",
"type": "git",
"dir": null,
"ref": "8a7846b",
"ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b",
"url": "https://github.com/haxeui/haxeui-core"
},
{
"name": "haxeui-flixel",
"type": "git",
"dir": null,
"ref": "e9f880522e27134b29df4067f82df7d7e5237b70",
"ref": "63a906a6148958dbfde8c7b48d90b0693767fd95",
"url": "https://github.com/haxeui/haxeui-flixel"
},
{

View file

@ -146,12 +146,11 @@ class InitState extends FlxState
#end
// Make errors and warnings less annoying.
#if FORCE_DEBUG_VERSION
// Forcing this always since I have never been happy to have the debugger to pop up
LogStyle.ERROR.openConsole = false;
LogStyle.ERROR.errorSound = null;
LogStyle.WARNING.openConsole = false;
LogStyle.WARNING.errorSound = null;
#end
//
// FLIXEL TRANSITIONS

View file

@ -16,6 +16,20 @@ class Paths
currentLevel = name.toLowerCase();
}
public static function stripLibrary(path:String):String
{
var parts = path.split(':');
if (parts.length < 2) return path;
return parts[1];
}
public static function getLibrary(path:String):String
{
var parts = path.split(':');
if (parts.length < 2) return "preload";
return parts[0];
}
static function getPath(file:String, type:AssetType, library:Null<String>)
{
if (library != null) return getLibraryPath(file, library);

View file

@ -23,7 +23,7 @@ import openfl.utils.AssetType;
@:nullSafety
class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
{
static final MAX_VOLUME:Float = 2.0;
static final MAX_VOLUME:Float = 1.0;
static var cache(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>();
@ -40,7 +40,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
override function set_volume(value:Float):Float
{
// Uncap the volume.
fixMaxVolume();
_volume = FlxMath.bound(value, 0.0, MAX_VOLUME);
updateTransform();
return _volume;
@ -126,17 +125,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
return this;
}
function fixMaxVolume():Void
{
#if lime_openal
// This code is pretty fragile, it reaches through 5 layers of private access.
@:privateAccess
var handle = this?._channel?.__source?.__backend?.handle;
if (handle == null) return;
lime.media.openal.AL.sourcef(handle, lime.media.openal.AL.MAX_GAIN, MAX_VOLUME);
#end
}
public override function play(forceRestart:Bool = false, startTime:Float = 0, ?endTime:Float):FunkinSound
{
if (!exists) return this;

View file

@ -187,6 +187,8 @@ class WaveformData
*/
public function merge(that:WaveformData):WaveformData
{
if (that == null) return this.clone();
var result = this.clone([]);
for (channelIndex in 0...this.channels)

View file

@ -61,7 +61,16 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
for (entryCls in scriptedEntryClassNames)
{
var entry:T = createScriptedEntry(entryCls);
var entry:Null<T> = null;
try
{
entry = createScriptedEntry(entryCls);
}
catch (e:Dynamic)
{
log('Failed to create scripted entry (${entryCls})');
continue;
}
if (entry != null)
{
@ -196,6 +205,11 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
*/
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
{
if (version == null)
{
throw '[${registryId}] Entry ${id} could not be JSON-parsed or does not have a parseable version.';
}
// If a version rule is not specified, do not check against it.
if (versionRule == null || VersionUtil.validateVersion(version, versionRule))
{

View file

@ -108,8 +108,8 @@ class SongEventRegistry
public static function handleEvent(data:SongEventData):Void
{
var eventType:String = data.event;
var eventHandler:SongEvent = eventCache.get(eventType);
var eventKind:String = data.eventKind;
var eventHandler:SongEvent = eventCache.get(eventKind);
if (eventHandler != null)
{
@ -117,7 +117,7 @@ class SongEventRegistry
}
else
{
trace('WARNING: No event handler for event with id: ${eventType}');
trace('WARNING: No event handler for event with kind: ${eventKind}');
}
data.activated = true;
@ -148,6 +148,29 @@ class SongEventRegistry
});
}
/**
* The currentTime has jumped far ahead or back.
* If we moved back in time, we need to reset all the events in that space.
* If we moved forward in time, we need to skip all the events in that space.
*/
public static function handleSkippedEvents(events:Array<SongEventData>, currentTime:Float):Void
{
for (event in events)
{
// Deactivate future events.
if (event.time > currentTime)
{
event.activated = false;
}
// Skip past events.
if (event.time < currentTime)
{
event.activated = true;
}
}
}
/**
* Reset activation of all the provided events.
*/

View file

@ -110,7 +110,8 @@ class SongMetadata implements ICloneable<SongMetadata>
*/
public function serialize(pretty:Bool = true):String
{
var writer = new json2object.JsonWriter<SongMetadata>();
var ignoreNullOptionals = true;
var writer = new json2object.JsonWriter<SongMetadata>(ignoreNullOptionals);
// I believe @:jignored should be iggnored by the writer?
// var output = this.clone();
// output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer.
@ -597,7 +598,8 @@ class SongChartData implements ICloneable<SongChartData>
*/
public function serialize(pretty:Bool = true):String
{
var writer = new json2object.JsonWriter<SongChartData>();
var ignoreNullOptionals = true;
var writer = new json2object.JsonWriter<SongChartData>(ignoreNullOptionals);
return writer.write(this, pretty ? ' ' : null);
}
@ -648,7 +650,7 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
* Custom events can be added by scripts with the `ScriptedSongEvent` class.
*/
@:alias("e")
public var event:String;
public var eventKind:String;
/**
* The data for the event.
@ -668,10 +670,10 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
@:jignored
public var activated:Bool = false;
public function new(time:Float, event:String, value:Dynamic = null)
public function new(time:Float, eventKind:String, value:Dynamic = null)
{
this.time = time;
this.event = event;
this.eventKind = eventKind;
this.value = value;
}
@ -687,19 +689,19 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
public function clone():SongEventDataRaw
{
return new SongEventDataRaw(this.time, this.event, this.value);
return new SongEventDataRaw(this.time, this.eventKind, this.value);
}
}
/**
* Wrap SongEventData in an abstract so we can overload operators.
*/
@:forward(time, event, value, activated, getStepTime, clone)
@:forward(time, eventKind, value, activated, getStepTime, clone)
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
{
public function new(time:Float, event:String, value:Dynamic = null)
public function new(time:Float, eventKind:String, value:Dynamic = null)
{
this = new SongEventDataRaw(time, event, value);
this = new SongEventDataRaw(time, eventKind, value);
}
public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic
@ -726,12 +728,12 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
public inline function getHandler():Null<SongEvent>
{
return SongEventRegistry.getEvent(this.event);
return SongEventRegistry.getEvent(this.eventKind);
}
public inline function getSchema():Null<SongEventSchema>
{
return SongEventRegistry.getEventSchema(this.event);
return SongEventRegistry.getEventSchema(this.eventKind);
}
public inline function getDynamic(key:String):Null<Dynamic>
@ -784,7 +786,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
var eventHandler = getHandler();
var eventSchema = getSchema();
if (eventSchema == null) return 'Unknown Event: ${this.event}';
if (eventSchema == null) return 'Unknown Event: ${this.eventKind}';
var result = '${eventHandler.getTitle()}';
@ -809,19 +811,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
public function clone():SongEventData
{
return new SongEventData(this.time, this.event, this.value);
return new SongEventData(this.time, this.eventKind, this.value);
}
@:op(A == B)
public function op_equals(other:SongEventData):Bool
{
return this.time == other.time && this.event == other.event && this.value == other.value;
return this.time == other.time && this.eventKind == other.eventKind && this.value == other.value;
}
@:op(A != B)
public function op_notEquals(other:SongEventData):Bool
{
return this.time != other.time || this.event != other.event || this.value != other.value;
return this.time != other.time || this.eventKind != other.eventKind || this.value != other.value;
}
@:op(A > B)
@ -853,7 +855,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
*/
public function toString():String
{
return 'SongEventData(${this.time}ms, ${this.event}: ${this.value})';
return 'SongEventData(${this.time}ms, ${this.eventKind}: ${this.value})';
}
}
@ -1022,6 +1024,12 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
{
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind);
}
public function toString():String
{
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
}
}
/**

View file

@ -47,7 +47,7 @@ class SongDataUtils
public static function offsetSongEventData(events:Array<SongEventData>, offset:Float):Array<SongEventData>
{
return events.map(function(event:SongEventData):SongEventData {
return new SongEventData(event.time + offset, event.event, event.value);
return new SongEventData(event.time + offset, event.eventKind, event.value);
});
}

View file

@ -6,9 +6,23 @@ import flixel.graphics.FlxGraphic;
/**
* An FlxSprite with additional functionality.
* - A more efficient method for creating solid color sprites.
* - TODO: Better cache handling for textures.
*/
class FunkinSprite extends FlxSprite
{
/**
* An internal list of all the textures cached with `cacheTexture`.
* This excludes any temporary textures like those from `FlxText` or `makeSolidColor`.
*/
static var currentCachedTextures:Map<String, FlxGraphic> = [];
/**
* An internal list of textures that were cached in the previous state.
* We don't know whether we want to keep them cached or not.
*/
static var previousCachedTextures:Map<String, FlxGraphic> = [];
/**
* @param x Starting X position
* @param y Starting Y position
@ -18,19 +32,184 @@ class FunkinSprite extends FlxSprite
super(x, y);
}
/**
* Create a new FunkinSprite with a static texture.
* @param x The starting X position.
* @param y The starting Y position.
* @param key The key of the texture to load.
* @return The new FunkinSprite.
*/
public static function create(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
{
var sprite = new FunkinSprite(x, y);
sprite.loadTexture(key);
return sprite;
}
/**
* Create a new FunkinSprite with a Sparrow atlas animated texture.
* @param x The starting X position.
* @param y The starting Y position.
* @param key The key of the texture to load.
* @return The new FunkinSprite.
*/
public static function createSparrow(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
{
var sprite = new FunkinSprite(x, y);
sprite.loadSparrow(key);
return sprite;
}
/**
* Create a new FunkinSprite with a Packer atlas animated texture.
* @param x The starting X position.
* @param y The starting Y position.
* @param key The key of the texture to load.
* @return The new FunkinSprite.
*/
public static function createPacker(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
{
var sprite = new FunkinSprite(x, y);
sprite.loadPacker(key);
return sprite;
}
/**
* Load a static image as the sprite's texture.
* @param key The key of the texture to load.
* @return This sprite, for chaining.
*/
public function loadTexture(key:String):FunkinSprite
{
if (!isTextureCached(key)) FlxG.log.warn('Texture not cached, may experience stuttering! $key');
loadGraphic(key);
return this;
}
/**
* Load an animated texture (Sparrow atlas spritesheet) as the sprite's texture.
* @param key The key of the texture to load.
* @return This sprite, for chaining.
*/
public function loadSparrow(key:String):FunkinSprite
{
var graphicKey = Paths.image(key);
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
this.frames = Paths.getSparrowAtlas(key);
return this;
}
/**
* Load an animated texture (Packer atlas spritesheet) as the sprite's texture.
* @param key The key of the texture to load.
* @return This sprite, for chaining.
*/
public function loadPacker(key:String):FunkinSprite
{
var graphicKey = Paths.image(key);
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
this.frames = Paths.getPackerAtlas(key);
return this;
}
public static function isTextureCached(key:String):Bool
{
return FlxG.bitmap.get(key) != null;
}
public static function cacheTexture(key:String):Void
{
// We don't want to cache the same texture twice.
if (currentCachedTextures.exists(key)) return;
if (previousCachedTextures.exists(key))
{
// Move the graphic from the previous cache to the current cache.
var graphic = previousCachedTextures.get(key);
previousCachedTextures.remove(key);
currentCachedTextures.set(key, graphic);
return;
}
// Else, texture is currently uncached.
var graphic = flixel.graphics.FlxGraphic.fromAssetKey(key, false, null, true);
if (graphic == null)
{
FlxG.log.warn('Failed to cache graphic: $key');
}
else
{
trace('Successfully cached graphic: $key');
graphic.persist = true;
currentCachedTextures.set(key, graphic);
}
}
public static function cacheSparrow(key:String):Void
{
cacheTexture(Paths.image(key));
}
public static function cachePacker(key:String):Void
{
cacheTexture(Paths.image(key));
}
/**
* Call this, then `cacheTexture` to keep the textures we still need, then `purgeCache` to remove the textures that we won't be using anymore.
*/
public static function preparePurgeCache():Void
{
previousCachedTextures = currentCachedTextures;
currentCachedTextures = [];
}
public static function purgeCache():Void
{
// Everything that is in previousCachedTextures but not in currentCachedTextures should be destroyed.
for (graphicKey in previousCachedTextures.keys())
{
var graphic = previousCachedTextures.get(graphicKey);
FlxG.bitmap.remove(graphic);
graphic.destroy();
previousCachedTextures.remove(graphicKey);
}
}
static function isGraphicCached(graphic:FlxGraphic):Bool
{
if (graphic == null) return false;
var result = FlxG.bitmap.get(graphic.key);
if (result == null) return false;
if (result != graphic)
{
FlxG.log.warn('Cached graphic does not match original: ${graphic.key}');
return false;
}
return true;
}
/**
* Acts similarly to `makeGraphic`, but with improved memory usage,
* at the expense of not being able to paint onto the sprite.
* at the expense of not being able to paint onto the resulting sprite.
*
* @param width The target width of the sprite.
* @param height The target height of the sprite.
* @param color The color to fill the sprite with.
* @return This sprite, for chaining.
*/
public function makeSolidColor(width:Int, height:Int, color:FlxColor = FlxColor.WHITE):FunkinSprite
{
// Create a tiny solid color graphic and scale it up to the desired size.
var graphic:FlxGraphic = FlxG.bitmap.create(2, 2, color, false, 'solid#${color.toHexString(true, false)}');
frames = graphic.imageFrame;
scale.set(width / 2, height / 2);
scale.set(width / 2.0, height / 2.0);
updateHitbox();
return this;

View file

@ -18,7 +18,7 @@ class FlxAtlasSprite extends FlxAnimate
// ?OnComplete:Void -> Void,
ShowPivot: #if debug false #else false #end,
Antialiasing: true,
ScrollFactor: new FlxPoint(1, 1),
ScrollFactor: null,
// Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
};
@ -55,8 +55,9 @@ class FlxAtlasSprite extends FlxAnimate
*/
public function listAnimations():Array<String>
{
// return this.anim.getFrameLabels();
return [""];
if (this.anim == null) return [];
return this.anim.getFrameLabels();
// return [""];
}
/**
@ -82,8 +83,10 @@ class FlxAtlasSprite extends FlxAnimate
* @param restart Whether to restart the animation if it is already playing.
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
*/
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false):Void
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void
{
if (loop == null) loop = false;
// Skip if not allowed to play animations.
if ((!canPlayOtherAnims && !ignoreOther)) return;
@ -110,15 +113,14 @@ class FlxAtlasSprite extends FlxAnimate
return;
}
// Stop the current animation if it is playing.
// This includes removing existing frame callbacks.
if (this.currentAnimation != null) this.stopAnimation();
// Add a callback to ensure `onAnimationFinish` is dispatched.
addFrameCallback(getNextFrameLabel(id), function() {
trace('Animation finished: ' + id);
onAnimationFinish.dispatch(id);
});
anim.callback = function(_, frame:Int) {
if (frame == (anim.getFrameLabel(id).duration - 1) + anim.getFrameLabel(id).index)
{
if (loop) playAnimation(id, true, false, true);
else
onAnimationFinish.dispatch(id);
}
};
// Prevent other animations from playing if `ignoreOther` is true.
if (ignoreOther) canPlayOtherAnims = false;
@ -128,6 +130,11 @@ class FlxAtlasSprite extends FlxAnimate
this.currentAnimation = id;
}
override public function update(elapsed:Float)
{
super.update(elapsed);
}
/**
* Stops the current animation.
*/
@ -146,22 +153,22 @@ class FlxAtlasSprite extends FlxAnimate
frameLabel.add(callback);
}
inline function goToFrameLabel(label:String):Void
function goToFrameLabel(label:String):Void
{
this.anim.goToFrameLabel(label);
}
inline function getNextFrameLabel(label:String):String
function getNextFrameLabel(label:String):String
{
return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
}
inline function getLabelIndex(label:String):Int
function getLabelIndex(label:String):Int
{
return listAnimations().indexOf(label);
}
inline function goToFrameIndex(index:Int):Void
function goToFrameIndex(index:Int):Void
{
this.anim.curFrame = index;
}

View file

@ -106,12 +106,19 @@ class NoteScriptEvent extends ScriptEvent
*/
public var playSound(default, default):Bool;
/**
* A multiplier to the health gained or lost from this note.
* This affects both hits and misses. Remember that max health is 2.00.
*/
public var healthMulti:Float;
public function new(type:ScriptEventType, note:NoteSprite, comboCount:Int = 0, cancelable:Bool = false):Void
{
super(type, cancelable);
this.note = note;
this.comboCount = comboCount;
this.playSound = true;
this.healthMulti = 1.0;
}
public override function toString():String
@ -182,17 +189,17 @@ class SongEventScriptEvent extends ScriptEvent
* The note associated with this event.
* You cannot replace it, but you can edit it.
*/
public var event(default, null):funkin.data.song.SongData.SongEventData;
public var eventData(default, null):funkin.data.song.SongData.SongEventData;
public function new(event:funkin.data.song.SongData.SongEventData):Void
public function new(eventData:funkin.data.song.SongData.SongEventData):Void
{
super(SONG_EVENT, true);
this.event = event;
this.eventData = eventData;
}
public override function toString():String
{
return 'SongEventScriptEvent(event=' + event + ')';
return 'SongEventScriptEvent(event=' + eventData + ')';
}
}

View file

@ -3,6 +3,7 @@ package funkin.play;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.module.ModuleHandler;
import funkin.modding.events.ScriptEvent;
@ -214,7 +215,7 @@ class Countdown
if (spritePath == null) return;
var countdownSprite:FlxSprite = new FlxSprite(0, 0).loadGraphic(Paths.image(spritePath));
var countdownSprite:FunkinSprite = FunkinSprite.create(Paths.image(spritePath));
countdownSprite.scrollFactor.set(0, 0);
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));

View file

@ -4,16 +4,18 @@ import flixel.FlxG;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.sound.FlxSound;
import funkin.ui.story.StoryMenuState;
import funkin.audio.FunkinSound;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.graphics.FunkinSprite;
import funkin.ui.MusicBeatSubState;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.BaseCharacter;
import funkin.play.PlayState;
import funkin.ui.freeplay.FreeplayState;
import funkin.play.character.BaseCharacter;
import funkin.ui.MusicBeatSubState;
import funkin.ui.story.StoryMenuState;
import openfl.utils.Assets;
/**
* A substate which renders over the PlayState when the player dies.
@ -63,7 +65,7 @@ class GameOverSubState extends MusicBeatSubState
/**
* The music playing in the background of the state.
*/
var gameOverMusic:FlxSound = new FlxSound();
var gameOverMusic:Null<FunkinSound> = null;
/**
* Whether the player has confirmed and prepared to restart the level.
@ -71,6 +73,11 @@ class GameOverSubState extends MusicBeatSubState
*/
var isEnding:Bool = false;
/**
* Whether the death music is on its first loop.
*/
var isStarting:Bool = true;
var isChartingMode:Bool = false;
var transparent:Bool;
@ -140,14 +147,16 @@ class GameOverSubState extends MusicBeatSubState
// Set up the audio
//
// Prepare the game over music.
FlxG.sound.list.add(gameOverMusic);
gameOverMusic.stop();
// The conductor now represents the BPM of the game over music.
Conductor.instance.update(0);
}
public function resetCameraZoom():Void
{
// Apply camera zoom level from stage data.
FlxG.camera.zoom = PlayState?.instance?.currentStage?.camZoom ?? 1.0;
}
var hasStartedAnimation:Bool = false;
override function update(elapsed:Float)
@ -216,7 +225,7 @@ class GameOverSubState extends MusicBeatSubState
}
}
if (gameOverMusic.playing)
if (gameOverMusic != null && gameOverMusic.playing)
{
// Match the conductor to the music.
// This enables the stepHit and beatHit events.
@ -291,24 +300,71 @@ class GameOverSubState extends MusicBeatSubState
ScriptEventDispatcher.callEvent(boyfriend, event);
}
/**
* Rather than hardcoding stuff, we look for the presence of a music file
* with the given suffix, and strip it down until we find one that's valid.
*/
function resolveMusicPath(suffix:String, starting:Bool = false, ending:Bool = false):Null<String>
{
var basePath = 'gameplay/gameover/gameOver';
if (starting) basePath += 'Start';
else if (ending) basePath += 'End';
var musicPath = Paths.music(basePath + suffix);
while (!Assets.exists(musicPath) && suffix.length > 0)
{
suffix = suffix.split('-').slice(0, -1).join('-');
musicPath = Paths.music(basePath + suffix);
}
if (!Assets.exists(musicPath)) return null;
trace('Resolved music path: ' + musicPath);
return musicPath;
}
/**
* Starts the death music at the appropriate volume.
* @param startingVolume
*/
function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void
public function startDeathMusic(startingVolume:Float = 1, force:Bool = false):Void
{
var musicPath = Paths.music('gameplay/gameover/gameOver' + musicSuffix);
if (isEnding)
var musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
var onComplete = null;
if (isStarting)
{
musicPath = Paths.music('gameplay/gameover/gameOverEnd' + musicSuffix);
if (musicPath == null)
{
isStarting = false;
musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
}
else
{
isStarting = false;
onComplete = function() {
// We need to force to ensure that the non-starting music plays.
startDeathMusic(1.0, true);
};
}
}
if (!gameOverMusic.playing || force)
if (musicPath == null)
{
gameOverMusic.loadEmbedded(musicPath);
trace('Could not find game over music!');
return;
}
else if (gameOverMusic == null || !gameOverMusic.playing || force)
{
if (gameOverMusic != null) gameOverMusic.stop();
gameOverMusic = FunkinSound.load(musicPath);
gameOverMusic.volume = startingVolume;
gameOverMusic.looped = !isEnding;
gameOverMusic.looped = !(isEnding || isStarting);
gameOverMusic.onComplete = onComplete;
gameOverMusic.play();
}
else
{
@:privateAccess
trace('Music already playing! ${gameOverMusic?._label}');
}
}
static var blueballed:Bool = false;
@ -320,7 +376,14 @@ class GameOverSubState extends MusicBeatSubState
public static function playBlueBalledSFX()
{
blueballed = true;
FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)))
{
FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
}
else
{
FlxG.log.error('Missing blue ball sound effect: ' + Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
}
}
var playingJeffQuote:Bool = false;
@ -344,6 +407,14 @@ class GameOverSubState extends MusicBeatSubState
});
}
public override function destroy()
{
super.destroy();
if (gameOverMusic != null) gameOverMusic.stop();
gameOverMusic = null;
instance = null;
}
public override function toString():String
{
return "GameOverSubState";

View file

@ -3,6 +3,7 @@ package funkin.play;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxAtlasFrames;
import funkin.play.PlayState;
import funkin.graphics.FunkinSprite;
import funkin.ui.MusicBeatState;
import flixel.addons.transition.FlxTransitionableState;
import funkin.ui.mainmenu.MainMenuState;
@ -27,25 +28,22 @@ class GitarooPause extends MusicBeatState
{
if (FlxG.sound.music != null) FlxG.sound.music.stop();
var bg:FlxSprite = new FlxSprite().loadGraphic(Paths.image('pauseAlt/pauseBG'));
var bg:FunkinSprite = FunkinSprite.create(Paths.image('pauseAlt/pauseBG'));
add(bg);
var bf:FlxSprite = new FlxSprite(0, 30);
bf.frames = Paths.getSparrowAtlas('pauseAlt/bfLol');
var bf:FunkinSprite = FunkinSprite.createSparrow(0, 30, 'pauseAlt/bfLol');
bf.animation.addByPrefix('lol', "funnyThing", 13);
bf.animation.play('lol');
add(bf);
bf.screenCenter(X);
replayButton = new FlxSprite(FlxG.width * 0.28, FlxG.height * 0.7);
replayButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI');
replayButton = FunkinSprite.createSparrow(FlxG.width * 0.28, FlxG.height * 0.7, 'pauseAlt/pauseUI');
replayButton.animation.addByPrefix('selected', 'bluereplay', 0, false);
replayButton.animation.appendByPrefix('selected', 'yellowreplay');
replayButton.animation.play('selected');
add(replayButton);
cancelButton = new FlxSprite(FlxG.width * 0.58, replayButton.y);
cancelButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI');
cancelButton = FunkinSprite.createSparrow(FlxG.width * 0.58, replayButton.y, 'pauseAlt/pauseUI');
cancelButton.animation.addByPrefix('selected', 'bluecancel', 0, false);
cancelButton.animation.appendByPrefix('selected', 'cancelyellow');
cancelButton.animation.play('selected');

View file

@ -13,6 +13,7 @@ import flixel.util.FlxColor;
import funkin.play.PlayState;
import funkin.data.song.SongRegistry;
import funkin.ui.Alphabet;
import funkin.graphics.FunkinSprite;
class PauseSubState extends MusicBeatSubState
{
@ -72,7 +73,7 @@ class PauseSubState extends MusicBeatSubState
FlxG.sound.list.add(pauseMusic);
bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
bg = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK);
bg.alpha = 0;
bg.scrollFactor.set();
add(bg);

View file

@ -10,12 +10,14 @@ import flixel.addons.transition.Transition;
import flixel.addons.transition.Transition;
import flixel.FlxCamera;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.FlxState;
import funkin.graphics.FunkinSprite;
import flixel.FlxSubState;
import funkin.graphics.FunkinSprite;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import funkin.graphics.FunkinSprite;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
@ -213,7 +215,7 @@ class PlayState extends MusicBeatSubState
* The current gameplay camera will always follow this object. Tween its position to move the camera smoothly.
*
* It needs to be an object in the scene for the camera to be configured to follow it.
* We optionally make this an FlxSprite so we can draw a debug graphic with it.
* We optionally make this a sprite so we can draw a debug graphic with it.
*/
public var cameraFollowPoint:FlxObject;
@ -400,7 +402,7 @@ class PlayState extends MusicBeatSubState
* The background image used for the health bar.
* Emma says the image is slightly skewed so I'm leaving it as an image instead of a `createGraphic`.
*/
public var healthBarBG:FlxSprite;
public var healthBarBG:FunkinSprite;
/**
* The health icon representing the player.
@ -568,12 +570,15 @@ class PlayState extends MusicBeatSubState
if (!assertChartExists()) return;
// TODO: Add something to toggle this on!
if (false)
{
// Displays the camera follow point as a sprite for debug purposes.
cameraFollowPoint = new FlxSprite(0, 0).makeGraphic(8, 8, 0xFF00FF00);
var cameraFollowPoint = new FunkinSprite(0, 0);
cameraFollowPoint.makeSolidColor(8, 8, 0xFF00FF00);
cameraFollowPoint.visible = false;
cameraFollowPoint.zIndex = 1000000;
this.cameraFollowPoint = cameraFollowPoint;
}
else
{
@ -918,6 +923,7 @@ class PlayState extends MusicBeatSubState
{
FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation());
}
FlxG.watch.addQuick('health', health);
// TODO: Add a song event for Handle GF dance speed.
@ -981,8 +987,21 @@ class PlayState extends MusicBeatSubState
}
}
processSongEvents();
// Handle keybinds.
processInputQueue();
if (!isInCutscene && !disableKeys) debugKeyShit();
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
// Moving notes into position is now done by Strumline.update().
processNotes(elapsed);
}
function processSongEvents():Void
{
// Query and activate song events.
// TODO: Check that these work even when songPosition is less than 0.
// TODO: Check that these work appropriately even when songPosition is less than 0, to play events during countdown.
if (songEvents != null && songEvents.length > 0)
{
var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition);
@ -992,8 +1011,9 @@ class PlayState extends MusicBeatSubState
trace('Found ${songEventsToActivate.length} event(s) to activate.');
for (event in songEventsToActivate)
{
// If an event is trying to play, but it's over 5 seconds old, skip it.
if (event.time - Conductor.instance.songPosition < -5000)
// If an event is trying to play, but it's over 1 second old, skip it.
var eventAge:Float = Conductor.instance.songPosition - event.time;
if (eventAge > 1000)
{
event.activated = true;
continue;
@ -1009,14 +1029,6 @@ class PlayState extends MusicBeatSubState
}
}
}
// Handle keybinds.
processInputQueue();
if (!isInCutscene && !disableKeys) debugKeyShit();
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
// Moving notes into position is now done by Strumline.update().
processNotes(elapsed);
}
public override function dispatchEvent(event:ScriptEvent):Void
@ -1348,7 +1360,7 @@ class PlayState extends MusicBeatSubState
function initHealthBar():Void
{
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
healthBarBG = new FlxSprite(0, healthBarYPos).loadGraphic(Paths.image('healthBar'));
healthBarBG = FunkinSprite.create(0, healthBarYPos, Paths.image('healthBar'));
healthBarBG.screenCenter(X);
healthBarBG.scrollFactor.set(0, 0);
add(healthBarBG);
@ -1382,7 +1394,7 @@ class PlayState extends MusicBeatSubState
function initMinimalMode():Void
{
// Create the green background.
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
var menuBG = FunkinSprite.create(Paths.image('menuDesat'));
menuBG.color = 0xFF4CAF50;
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
menuBG.updateHitbox();
@ -1408,8 +1420,7 @@ class PlayState extends MusicBeatSubState
var event:ScriptEvent = new ScriptEvent(CREATE, false);
ScriptEventDispatcher.callEvent(currentStage, event);
// Apply camera zoom level from stage data.
defaultCameraZoom = currentStage.camZoom;
resetCameraZoom();
// Add the stage to the scene.
this.add(currentStage);
@ -1425,6 +1436,12 @@ class PlayState extends MusicBeatSubState
}
}
public function resetCameraZoom():Void
{
// Apply camera zoom level from stage data.
defaultCameraZoom = currentStage.camZoom;
}
/**
* Generates the character sprites and adds them to the stage.
*/
@ -1750,7 +1767,7 @@ class PlayState extends MusicBeatSubState
currentChart.playInst(1.0, false);
}
FlxG.sound.music.onComplete = endSong;
FlxG.sound.music.onComplete = endSong.bind(false);
// 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.
FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
@ -1978,7 +1995,7 @@ class PlayState extends MusicBeatSubState
// Judge the miss.
// NOTE: This is what handles the scoring.
trace('Missed note! ${note.noteData}');
onNoteMiss(note);
onNoteMiss(note, event.playSound, event.healthMulti);
note.handledMiss = true;
}
@ -2030,6 +2047,7 @@ class PlayState extends MusicBeatSubState
}
}
// Respawns notes that were b
playerStrumline.handleSkippedNotes();
opponentStrumline.handleSkippedNotes();
}
@ -2129,7 +2147,7 @@ class PlayState extends MusicBeatSubState
// Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) return;
popUpScore(note, input);
popUpScore(note, input, event.healthMulti);
if (note.isHoldNote && note.holdNoteSprite != null)
{
@ -2143,15 +2161,11 @@ class PlayState extends MusicBeatSubState
* Called when a note leaves the screen and is considered missed by the player.
* @param note
*/
function onNoteMiss(note:NoteSprite):Void
function onNoteMiss(note:NoteSprite, playSound:Bool = false, healthLossMulti:Float = 1.0):Void
{
// a MISS is when you let a note scroll past you!!
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Highscore.tallies.combo, true);
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) return;
// If we are here, we already CALLED the onNoteMiss script hook!
health -= Constants.HEALTH_MISS_PENALTY;
health -= Constants.HEALTH_MISS_PENALTY * healthLossMulti;
songScore -= 10;
if (!isPracticeMode)
@ -2201,7 +2215,7 @@ class PlayState extends MusicBeatSubState
Highscore.tallies.combo = comboPopUps.displayCombo(0);
}
if (event.playSound)
if (playSound)
{
vocals.playerVolume = 0;
FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2));
@ -2274,11 +2288,6 @@ class PlayState extends MusicBeatSubState
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
#end
// Eject button
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
if (FlxG.keys.justPressed.F5) debug_refreshModules();
// Open the stage editor overlaying the current state.
if (controls.DEBUG_STAGE)
{
@ -2301,7 +2310,7 @@ class PlayState extends MusicBeatSubState
#if (debug || FORCE_DEBUG_VERSION)
// 1: End the song immediately.
if (FlxG.keys.justPressed.ONE) endSong();
if (FlxG.keys.justPressed.ONE) endSong(true);
// 2: Gain 10% health.
if (FlxG.keys.justPressed.TWO) health += 0.1 * Constants.HEALTH_MAX;
@ -2328,7 +2337,7 @@ class PlayState extends MusicBeatSubState
/**
* Handles health, score, and rating popups when a note is hit.
*/
function popUpScore(daNote:NoteSprite, input:PreciseInputEvent):Void
function popUpScore(daNote:NoteSprite, input:PreciseInputEvent, healthGainMulti:Float = 1.0):Void
{
vocals.playerVolume = 1;
@ -2359,19 +2368,19 @@ class PlayState extends MusicBeatSubState
{
case 'sick':
Highscore.tallies.sick += 1;
health += Constants.HEALTH_SICK_BONUS;
health += Constants.HEALTH_SICK_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK;
case 'good':
Highscore.tallies.good += 1;
health += Constants.HEALTH_GOOD_BONUS;
health += Constants.HEALTH_GOOD_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK;
case 'bad':
Highscore.tallies.bad += 1;
health += Constants.HEALTH_BAD_BONUS;
health += Constants.HEALTH_BAD_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK;
case 'shit':
Highscore.tallies.shit += 1;
health += Constants.HEALTH_SHIT_BONUS;
health += Constants.HEALTH_SHIT_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK;
}
@ -2495,16 +2504,35 @@ class PlayState extends MusicBeatSubState
if (skipHeldTimer >= 1.5)
{
VideoCutscene.finishVideo();
skipVideoCutscene();
}
}
/**
* End the song. Handle saving high scores and transitioning to the results screen.
* Handle logic for actually skipping a video cutscene after it has been held.
*/
function endSong():Void
function skipVideoCutscene():Void
{
dispatchEvent(new ScriptEvent(SONG_END));
VideoCutscene.finishVideo();
}
/**
* End the song. Handle saving high scores and transitioning to the results screen.
*
* Broadcasts an `onSongEnd` event, which can be cancelled to prevent the song from ending (for a cutscene or something).
* Remember to call `endSong` again when the song should actually end!
* @param rightGoddamnNow If true, don't play the fancy animation where you zoom onto Girlfriend. Used after a cutscene.
*/
public function endSong(rightGoddamnNow:Bool = false):Void
{
FlxG.sound.music.volume = 0;
vocals.volume = 0;
mayPauseGame = false;
// Check if any events want to prevent the song from ending.
var event = new ScriptEvent(SONG_END, true);
dispatchEvent(event);
if (event.eventCanceled) return;
#if sys
// spitter for ravy, teehee!!
@ -2514,9 +2542,7 @@ class PlayState extends MusicBeatSubState
#end
deathCounter = 0;
mayPauseGame = false;
FlxG.sound.music.volume = 0;
vocals.volume = 0;
if (currentSong != null && currentSong.validScore)
{
// crackhead double thingie, sets whether was new highscore, AND saves the song!
@ -2603,7 +2629,14 @@ class PlayState extends MusicBeatSubState
}
else
{
moveToResultsScreen();
if (rightGoddamnNow)
{
moveToResultsScreen();
}
else
{
zoomIntoResultsScreen();
}
}
}
else
@ -2621,10 +2654,10 @@ class PlayState extends MusicBeatSubState
// TODO: Softcode this cutscene.
if (currentSong.id == 'eggnog')
{
var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom,
-FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK);
blackShit.scrollFactor.set();
add(blackShit);
var blackBG:FunkinSprite = new FunkinSprite(-FlxG.width * FlxG.camera.zoom, -FlxG.height * FlxG.camera.zoom);
blackBG.makeSolidColor(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK);
blackBG.scrollFactor.set();
add(blackBG);
camHUD.visible = false;
isInCutscene = true;
@ -2661,7 +2694,14 @@ class PlayState extends MusicBeatSubState
}
else
{
moveToResultsScreen();
if (rightGoddamnNow)
{
moveToResultsScreen();
}
else
{
zoomIntoResultsScreen();
}
}
}
}
@ -2715,9 +2755,9 @@ class PlayState extends MusicBeatSubState
}
/**
* Play the camera zoom animation and move to the results screen.
* Play the camera zoom animation and then move to the results screen once it's done.
*/
function moveToResultsScreen():Void
function zoomIntoResultsScreen():Void
{
trace('WENT TO RESULTS SCREEN!');
@ -2771,22 +2811,30 @@ class PlayState extends MusicBeatSubState
{
ease: FlxEase.expoIn,
onComplete: function(_) {
persistentUpdate = false;
vocals.stop();
camHUD.alpha = 1;
var res:ResultState = new ResultState(
{
storyMode: PlayStatePlaylist.isStoryMode,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
tallies: Highscore.tallies,
});
res.camera = camHUD;
openSubState(res);
moveToResultsScreen();
}
});
});
}
/**
* Move to the results screen right goddamn now.
*/
function moveToResultsScreen():Void
{
persistentUpdate = false;
vocals.stop();
camHUD.alpha = 1;
var res:ResultState = new ResultState(
{
storyMode: PlayStatePlaylist.isStoryMode,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
tallies: Highscore.tallies,
});
res.camera = camHUD;
openSubState(res);
}
/**
* Pauses music and vocals easily.
*/
@ -2816,14 +2864,18 @@ class PlayState extends MusicBeatSubState
*/
function changeSection(sections:Int):Void
{
FlxG.sound.music.pause();
// FlxG.sound.music.pause();
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections);
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
// Don't go back in time to before the song started.
targetTimeMs = Math.max(0, targetTimeMs);
FlxG.sound.music.time = targetTimeMs;
handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
// regenNoteData(FlxG.sound.music.time);
Conductor.instance.update(FlxG.sound.music.time);

View file

@ -4,6 +4,7 @@ import funkin.ui.story.StoryMenuState;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.FlxBasic;
import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxBitmapFont;
import flixel.group.FlxGroup.FlxTypedGroup;
@ -96,8 +97,7 @@ class ResultState extends MusicBeatSubState
bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce!
};
var gf:FlxSprite = new FlxSprite(500, 300);
gf.frames = Paths.getSparrowAtlas('resultScreen/resultGirlfriendGOOD');
var gf:FlxSprite = FunkinSprite.createSparrow(500, 300, 'resultScreen/resultGirlfriendGOOD');
gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
gf.visible = false;
gf.animation.finishCallback = _ -> {
@ -105,8 +105,7 @@ class ResultState extends MusicBeatSubState
};
add(gf);
var boyfriend:FlxSprite = new FlxSprite(640, -200);
boyfriend.frames = Paths.getSparrowAtlas('resultScreen/resultBoyfriendGOOD');
var boyfriend:FlxSprite = FunkinSprite.createSparrow(640, -200, 'resultScreen/resultBoyfriendGOOD');
boyfriend.animation.addByPrefix("fall", "Boyfriend Good", 24, false);
boyfriend.visible = false;
boyfriend.animation.finishCallback = function(_) {
@ -115,8 +114,7 @@ class ResultState extends MusicBeatSubState
add(boyfriend);
var soundSystem:FlxSprite = new FlxSprite(-15, -180);
soundSystem.frames = Paths.getSparrowAtlas("resultScreen/soundSystem");
var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem');
soundSystem.animation.addByPrefix("idle", "sound system", 24, false);
soundSystem.visible = false;
new FlxTimer().start(0.4, _ -> {
@ -162,20 +160,17 @@ class ResultState extends MusicBeatSubState
FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut, startDelay: 0.5});
add(blackTopBar);
var resultsAnim:FlxSprite = new FlxSprite(-200, -10);
resultsAnim.frames = Paths.getSparrowAtlas("resultScreen/results");
var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
resultsAnim.animation.addByPrefix("result", "results", 24, false);
resultsAnim.animation.play("result");
add(resultsAnim);
var ratingsPopin:FlxSprite = new FlxSprite(-150, 120);
ratingsPopin.frames = Paths.getSparrowAtlas("resultScreen/ratingsPopin");
var ratingsPopin:FunkinSprite = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false);
ratingsPopin.visible = false;
add(ratingsPopin);
var scorePopin:FlxSprite = new FlxSprite(-180, 520);
scorePopin.frames = Paths.getSparrowAtlas("resultScreen/scorePopin");
var scorePopin:FunkinSprite = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
scorePopin.animation.addByPrefix("score", "tally score", 24, false);
scorePopin.visible = false;
add(scorePopin);

View file

@ -305,9 +305,15 @@ class CharacterDataParser
icon = "darnell";
case "senpai-angry":
icon = "senpai";
case "tankman" | "tankman-atlas":
icon = "tankmen";
}
return Paths.image("freeplay/icons/" + icon + "pixel");
var path = Paths.image("freeplay/icons/" + icon + "pixel");
if (Assets.exists(path)) return path;
// TODO: Hardcode some additional behavior or a fallback.
return null;
}
/**

View file

@ -6,6 +6,7 @@ import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import funkin.play.character.CharacterData.CharacterDataParser;
import openfl.utils.Assets;
import funkin.graphics.FunkinSprite;
import funkin.util.MathUtil;
/**
@ -26,7 +27,7 @@ import funkin.util.MathUtil;
* @author MasterEric
*/
@:nullSafety
class HealthIcon extends FlxSprite
class HealthIcon extends FunkinSprite
{
/**
* The character this icon is representing.
@ -408,7 +409,7 @@ class HealthIcon extends FlxSprite
if (!isLegacyStyle)
{
frames = Paths.getSparrowAtlas('icons/icon-$charId');
loadSparrow('icons/icon-$charId');
loadAnimationNew();
}

View file

@ -3,6 +3,7 @@ package funkin.play.components;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.tweens.FlxTween;
import funkin.graphics.FunkinSprite;
import funkin.play.PlayState;
class PopUpStuff extends FlxTypedGroup<FlxSprite>
@ -14,17 +15,20 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
public function displayRating(daRating:String)
{
#if sys
var perfStart:Float = Sys.time();
#end
if (daRating == null) daRating = "good";
var rating:FlxSprite = new FlxSprite(0, 0);
rating.scrollFactor.set(0.2, 0.2);
rating.zIndex = 1000;
var ratingPath:String = daRating;
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
rating.loadGraphic(Paths.image(ratingPath));
var rating:FunkinSprite = FunkinSprite.create(0, 0, Paths.image(ratingPath));
rating.scrollFactor.set(0.2, 0.2);
rating.zIndex = 1000;
rating.x = FlxG.width * 0.50;
rating.x -= FlxG.camera.scroll.x * 0.2;
// make sure rating is visible lol!
@ -61,10 +65,19 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
},
startDelay: Conductor.instance.beatLengthMs * 0.001
});
#if sys
var perfEnd:Float = Sys.time();
trace("displayRating took: " + (perfEnd - perfStart));
#end
}
public function displayCombo(?combo:Int = 0):Int
{
#if sys
var perfStart:Float = Sys.time();
#end
if (combo == null) combo = 0;
var pixelShitPart1:String = "";
@ -75,7 +88,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
pixelShitPart1 = 'weeb/pixelUI/';
pixelShitPart2 = '-pixel';
}
var comboSpr:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2));
var comboSpr:FunkinSprite = FunkinSprite.create(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2));
comboSpr.y = FlxG.camera.height * 0.4 + 80;
comboSpr.x = FlxG.width * 0.50;
comboSpr.x -= FlxG.camera.scroll.x * 0.2;
@ -129,8 +142,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
var daLoop:Int = 1;
for (i in seperatedScore)
{
var numScore:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
numScore.y = comboSpr.y;
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
if (PlayState.instance.currentStageId.startsWith('school'))
{
@ -163,6 +175,11 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
daLoop++;
}
#if sys
var perfEnd:Float = Sys.time();
trace("displayCombo took: " + (perfEnd - perfStart));
#end
return combo;
}
}

View file

@ -19,13 +19,22 @@ import hxcodec.flixel.FlxVideoSprite;
class VideoCutscene
{
static var blackScreen:FlxSprite;
static var cutsceneType:CutsceneType;
#if html5
static var vid:FlxVideo;
#end
#if hxCodec
static var vid:FlxVideoSprite;
#end
/**
* Play a video cutscene.
* TODO: Currently this is hardcoded to start the countdown after the video is done.
* @param path The path to the video file. Use Paths.file(path) to get the correct path.
* @param cutseneType The type of cutscene to play, determines what the game does after. Defaults to `CutsceneType.STARTING`.
*/
public static function play(filePath:String):Void
public static function play(filePath:String, ?cutsceneType:CutsceneType = STARTING):Void
{
if (PlayState.instance == null) return;
@ -36,6 +45,8 @@ class VideoCutscene
return;
}
var rawFilePath = Paths.stripLibrary(filePath);
// Trigger the cutscene. Don't play the song in the background.
PlayState.instance.isInCutscene = true;
PlayState.instance.camHUD.visible = false;
@ -47,12 +58,14 @@ class VideoCutscene
blackScreen.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(blackScreen);
VideoCutscene.cutsceneType = cutsceneType;
#if html5
playVideoHTML5(filePath);
#end
#if hxCodec
playVideoNative(filePath);
#elseif hxCodec
playVideoNative(rawFilePath);
#else
throw "No video support for this platform!";
#end
}
@ -66,8 +79,6 @@ class VideoCutscene
}
#if html5
static var vid:FlxVideo;
static function playVideoHTML5(filePath:String):Void
{
// Video displays OVER the FlxState.
@ -92,8 +103,6 @@ class VideoCutscene
#end
#if hxCodec
static var vid:FlxVideoSprite;
static function playVideoNative(filePath:String):Void
{
// Video displays OVER the FlxState.
@ -110,6 +119,15 @@ class VideoCutscene
PlayState.instance.refresh();
vid.play(filePath, false);
// Resize videos bigger or smaller than the screen.
vid.bitmap.onTextureSetup.add(() -> {
vid.setGraphicSize(FlxG.width, FlxG.height);
vid.updateHitbox();
vid.x = 0;
vid.y = 0;
// vid.scale.set(0.5, 0.5);
});
}
else
{
@ -118,10 +136,17 @@ class VideoCutscene
}
#end
/**
* Finish the active video cutscene. Done when the video is finished or when the player skips the cutscene.
* @param transitionTime The duration of the transition to the next state. Defaults to 0.5 seconds (this time is always used when cancelling the video).
* @param finishCutscene The callback to call when the transition is finished.
*/
public static function finishVideo(?transitionTime:Float = 0.5):Void
{
trace('ALERT: Finish video cutscene called!');
var cutsceneType:CutsceneType = VideoCutscene.cutsceneType;
#if html5
if (vid != null)
{
@ -157,8 +182,32 @@ class VideoCutscene
{
ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) {
PlayState.instance.startCountdown();
onCutsceneFinish(cutsceneType);
}
});
}
/**
* The default callback used when a cutscene is finished.
* You can specify your own callback when calling `VideoCutscene#play()`.
*/
static function onCutsceneFinish(cutsceneType:CutsceneType):Void
{
switch (cutsceneType)
{
case CutsceneType.STARTING:
PlayState.instance.startCountdown();
case CutsceneType.ENDING:
PlayState.instance.endSong(true); // true = right goddamn now
case CutsceneType.MIDSONG:
throw "Not implemented!";
}
}
}
enum CutsceneType
{
STARTING; // The default cutscene type. Starts the countdown after the video is done.
MIDSONG; // TODO: Implement this!
ENDING; // Ends the song after the video is done.
}

View file

@ -35,6 +35,7 @@ class NoteSplash extends FlxSprite
*/
function setup():Void
{
if (frameCollection?.parent?.isDestroyed ?? false) frameCollection = null;
if (frameCollection == null) preloadFrames();
this.frames = frameCollection;
@ -75,6 +76,8 @@ class NoteSplash extends FlxSprite
this.playAnimation('splash${variant}Right');
}
if (animation.curAnim == null) return;
// Vary the speed of the animation a bit.
animation.curAnim.frameRate = FRAMERATE_DEFAULT + FlxG.random.int(-FRAMERATE_VARIANCE, FRAMERATE_VARIANCE);

View file

@ -4,9 +4,10 @@ import funkin.data.song.SongData.SongNoteData;
import funkin.play.notes.notestyle.NoteStyle;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.HSVShader;
class NoteSprite extends FlxSprite
class NoteSprite extends FunkinSprite
{
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red'];

View file

@ -4,6 +4,7 @@ import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.data.animation.AnimationData;
import funkin.data.IRegistryEntry;
import funkin.graphics.FunkinSprite;
import funkin.data.notestyle.NoteStyleData;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
@ -100,6 +101,14 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
function buildNoteFrames(force:Bool = false):FlxAtlasFrames
{
if (!FunkinSprite.isTextureCached(Paths.image(getNoteAssetPath())))
{
FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}');
}
// Purge the note frames if the cached atlas is invalid.
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
if (noteFrames != null && !force) return noteFrames;
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
@ -109,8 +118,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
throw 'Could not load note frames for note style: $id';
}
noteFrames.parent.persist = true;
return noteFrames;
}

View file

@ -185,9 +185,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
switch (dataProp.animType)
{
case 'packer':
propSprite.frames = Paths.getPackerAtlas(dataProp.assetPath);
propSprite.loadPacker(dataProp.assetPath);
default: // 'sparrow'
propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath);
propSprite.loadSparrow(dataProp.assetPath);
}
}
else if (isSolidColor)
@ -209,7 +209,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
else
{
// Initalize static sprite.
propSprite.loadGraphic(Paths.image(dataProp.assetPath));
propSprite.loadTexture(Paths.image(dataProp.assetPath));
// Disables calls to update() for a performance boost.
propSprite.active = false;
@ -397,15 +397,18 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
this.characters.set('bf', character);
charData = _data.characters.bf;
character.flipX = !character.getDataFlipX();
character.name = 'bf';
character.initHealthIcon(false);
case GF:
this.characters.set('gf', character);
charData = _data.characters.gf;
character.flipX = character.getDataFlipX();
character.name = 'gf';
case DAD:
this.characters.set('dad', character);
charData = _data.characters.dad;
character.flipX = character.getDataFlipX();
character.name = 'dad';
character.initHealthIcon(true);
default:
this.characters.set(character.characterId, character);

View file

@ -3408,7 +3408,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Update the event sprite's position.
eventSprite.updateEventPosition(renderedEvents);
// Update the sprite's graphic. TODO: Is this inefficient?
eventSprite.playAnimation(eventSprite.eventData.event);
eventSprite.playAnimation(eventSprite.eventData.eventKind);
}
else
{
@ -4669,9 +4669,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, eventKindToPlace, null);
if (eventKindToPlace != eventData.event)
if (eventKindToPlace != eventData.eventKind)
{
eventData.event = eventKindToPlace;
eventData.eventKind = eventKindToPlace;
}
eventData.time = cursorSnappedMs;

View file

@ -34,11 +34,11 @@ class SelectItemsCommand implements ChartEditorCommand
}
// If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event.
if (this.notes.length == 0 && this.events.length >= 1)
if (this.notes.length == 0 && this.events.length == 1)
{
var eventSelected = this.events[0];
state.eventKindToPlace = eventSelected.event;
state.eventKindToPlace = eventSelected.eventKind;
// This code is here to parse event data that's not built as a struct for some reason.
// TODO: Clean this up or get rid of it.
@ -46,7 +46,7 @@ class SelectItemsCommand implements ChartEditorCommand
var defaultKey = null;
if (eventSchema == null)
{
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
trace('[WARNING] Event schema not found for event ${eventSelected.eventKind}.');
}
else
{
@ -60,7 +60,7 @@ class SelectItemsCommand implements ChartEditorCommand
}
// If we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note.
if (this.events.length == 0 && this.notes.length >= 1)
if (this.events.length == 0 && this.notes.length == 1)
{
var noteSelected = this.notes[0];

View file

@ -31,11 +31,11 @@ class SetItemSelectionCommand implements ChartEditorCommand
state.currentEventSelection = events;
// If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event.
if (this.notes.length == 0 && this.events.length >= 1)
if (this.notes.length == 0 && this.events.length == 1)
{
var eventSelected = this.events[0];
state.eventKindToPlace = eventSelected.event;
state.eventKindToPlace = eventSelected.eventKind;
// This code is here to parse event data that's not built as a struct for some reason.
// TODO: Clean this up or get rid of it.
@ -43,7 +43,7 @@ class SetItemSelectionCommand implements ChartEditorCommand
var defaultKey = null;
if (eventSchema == null)
{
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
trace('[WARNING] Event schema not found for event ${eventSelected.eventKind}.');
}
else
{
@ -57,7 +57,7 @@ class SetItemSelectionCommand implements ChartEditorCommand
}
// IF we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note.
if (this.events.length == 0 && this.notes.length >= 1)
if (this.events.length == 0 && this.notes.length == 1)
{
var noteSelected = this.notes[0];

View file

@ -133,7 +133,7 @@ class ChartEditorEventSprite extends FlxSprite
public function playAnimation(?name:String):Void
{
if (name == null) name = eventData?.event ?? DEFAULT_EVENT;
if (name == null) name = eventData?.eventKind ?? DEFAULT_EVENT;
var correctedName = correctAnimationName(name);
this.animation.play(correctedName);
@ -160,7 +160,7 @@ class ChartEditorEventSprite extends FlxSprite
else
{
this.visible = true;
playAnimation(value.event);
playAnimation(value.eventKind);
this.eventData = value;
// Update the position to match the note data.
updateEventPosition();

View file

@ -299,16 +299,14 @@ class ChartEditorAudioHandler
*/
public static function playSound(_state:ChartEditorState, path:String, volume:Float = 1.0):Void
{
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
if (asset == null)
{
trace('WARN: Failed to play sound $path, asset not found.');
return;
}
snd.loadEmbedded(asset);
var snd:FunkinSound = FunkinSound.load(asset);
snd.autoDestroy = true;
FlxG.sound.list.add(snd);
snd.play(true);
snd.volume = volume;
}

View file

@ -90,7 +90,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
// Edit the event data of any selected events.
for (event in chartEditorState.currentEventSelection)
{
event.event = chartEditorState.eventKindToPlace;
event.eventKind = chartEditorState.eventKindToPlace;
event.value = chartEditorState.eventDataToPlace;
}
chartEditorState.saveDataDirty = true;
@ -255,7 +255,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
{
for (event in chartEditorState.currentEventSelection)
{
event.event = chartEditorState.eventKindToPlace;
event.eventKind = chartEditorState.eventKindToPlace;
event.value = chartEditorState.eventDataToPlace;
}
chartEditorState.saveDataDirty = true;

View file

@ -289,10 +289,10 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
// Build player waveform.
// waveformMusic.waveform.forceUpdate = true;
var perfStart = haxe.Timer.stamp();
var waveformData1 = playerVoice.waveformData;
var waveformData2 = opponentVoice?.waveformData ?? playerVoice.waveformData; // this null check is for songs that only have 1 vocals file!
var waveformData1 = playerVoice?.waveformData;
var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file!
var waveformData3 = chartEditorState.audioInstTrack.waveformData;
var waveformData = waveformData1.merge(waveformData2).merge(waveformData3);
var waveformData = waveformData3.merge(waveformData1).merge(waveformData2);
trace('Waveform data merging took: ${haxe.Timer.stamp() - perfStart} seconds');
waveformMusic.waveform.waveformData = waveformData;

View file

@ -270,24 +270,21 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
// Build player waveform.
// waveformPlayer.waveform.forceUpdate = true;
waveformPlayer.waveform.waveformData = playerVoice.waveformData;
waveformPlayer.waveform.waveformData = playerVoice?.waveformData;
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
waveformPlayer.waveform.duration = playerVoice.length / Constants.MS_PER_SEC;
waveformPlayer.waveform.duration = (playerVoice?.length ?? 1000) / Constants.MS_PER_SEC;
// Build opponent waveform.
// waveformOpponent.waveform.forceUpdate = true;
// note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor
// so we null check
if (opponentVoice != null)
{
waveformOpponent.waveform.waveformData = opponentVoice.waveformData;
waveformOpponent.waveform.duration = opponentVoice.length / Constants.MS_PER_SEC;
}
waveformOpponent.waveform.waveformData = opponentVoice?.waveformData;
waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000) / Constants.MS_PER_SEC;
// Build instrumental waveform.
// waveformInstrumental.waveform.forceUpdate = true;
waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData;
waveformInstrumental.waveform.duration = instTrack.length / Constants.MS_PER_SEC;
waveformInstrumental.waveform.duration = (instTrack?.length ?? 1000) / Constants.MS_PER_SEC;
addOffsetsToAudioPreview();
}

View file

@ -147,6 +147,8 @@ class ChartEditorDropdowns
dropDown.dataSource.add(value);
}
dropDown.dataSource.sort('id', ASCENDING);
return returnValue;
}

View file

@ -156,8 +156,6 @@ class DJBoyfriend extends FlxAtlasSprite
function setupAnimations():Void
{
// frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk'));
// animation.addByPrefix('intro', "boyfriend dj intro", 24, false);
addOffset('boyfriend dj intro', 8, 3);

View file

@ -23,7 +23,7 @@ class FreeplayFlames extends FlxSpriteGroup
{
var flame:FlxSprite = new FlxSprite(flameX + (flameSpreadX * i), flameY + (flameSpreadY * i));
flame.frames = Paths.getSparrowAtlas("freeplay/freeplayFlame");
flame.animation.addByPrefix("flame", "fire loop", FlxG.random.int(23, 25), false);
flame.animation.addByPrefix("flame", "fire loop full instance 1", FlxG.random.int(23, 25), false);
flame.animation.play("flame");
flame.visible = false;
flameCount = 0;

View file

@ -111,7 +111,7 @@ class ScoreNum extends FlxSprite
for (i in 0...10)
{
var stringNum:String = numToString[i];
animation.addByPrefix(stringNum, stringNum, 24, false);
animation.addByPrefix(stringNum, '$stringNum DIGITAL', 24, false);
}
this.digit = initDigit;

View file

@ -7,6 +7,7 @@ import flixel.addons.ui.FlxInputText;
import flixel.FlxCamera;
import flixel.FlxGame;
import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import flixel.FlxState;
import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup;
@ -226,17 +227,17 @@ class FreeplayState extends MusicBeatSubState
trace(FlxG.camera.initialZoom);
trace(FlxCamera.defaultZoom);
var pinkBack:FlxSprite = new FlxSprite().loadGraphic(Paths.image('freeplay/pinkBack'));
var pinkBack:FunkinSprite = FunkinSprite.create(Paths.image('freeplay/pinkBack'));
pinkBack.color = 0xFFffd4e9; // sets it to pink!
pinkBack.x -= pinkBack.width;
FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut});
add(pinkBack);
var orangeBackShit:FlxSprite = new FlxSprite(84, 440).makeGraphic(Std.int(pinkBack.width), 75, 0xFFfeda00);
var orangeBackShit:FunkinSprite = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFfeda00);
add(orangeBackShit);
var alsoOrangeLOL:FlxSprite = new FlxSprite(0, orangeBackShit.y).makeGraphic(100, Std.int(orangeBackShit.height), 0xFFffd400);
var alsoOrangeLOL:FunkinSprite = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFffd400);
add(alsoOrangeLOL);
exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL],
@ -462,7 +463,7 @@ class FreeplayState extends MusicBeatSubState
var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70);
fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore');
fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore", 24, false);
fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore small instance 1", 24, false);
fnfHighscoreSpr.visible = false;
fnfHighscoreSpr.setGraphicSize(0, Std.int(fnfHighscoreSpr.height * 1));
fnfHighscoreSpr.updateHitbox();

View file

@ -3,6 +3,7 @@ package funkin.ui.transition;
import flixel.FlxSprite;
import flixel.math.FlxMath;
import flixel.tweens.FlxEase;
import funkin.graphics.FunkinSprite;
import flixel.tweens.FlxTween;
import flixel.util.FlxTimer;
import funkin.graphics.shaders.ScreenWipeShader;
@ -44,11 +45,10 @@ class LoadingState extends MusicBeatState
override function create():Void
{
var bg:FlxSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d);
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d);
add(bg);
funkay = new FlxSprite();
funkay.loadGraphic(Paths.image('funkay'));
funkay = FunkinSprite.create(Paths.image('funkay'));
funkay.setGraphicSize(0, FlxG.height);
funkay.updateHitbox();
add(funkay);
@ -209,6 +209,43 @@ class LoadingState extends MusicBeatState
params.targetSong.cacheCharts(true);
}
// TODO: This section is a hack! Redo this later when we have a proper asset caching system.
FunkinSprite.preparePurgeCache();
FunkinSprite.cacheTexture(Paths.image('combo'));
FunkinSprite.cacheTexture(Paths.image('healthBar'));
FunkinSprite.cacheTexture(Paths.image('menuDesat'));
FunkinSprite.cacheTexture(Paths.image('combo'));
FunkinSprite.cacheTexture(Paths.image('num0'));
FunkinSprite.cacheTexture(Paths.image('num1'));
FunkinSprite.cacheTexture(Paths.image('num2'));
FunkinSprite.cacheTexture(Paths.image('num3'));
FunkinSprite.cacheTexture(Paths.image('num4'));
FunkinSprite.cacheTexture(Paths.image('num5'));
FunkinSprite.cacheTexture(Paths.image('num6'));
FunkinSprite.cacheTexture(Paths.image('num7'));
FunkinSprite.cacheTexture(Paths.image('num8'));
FunkinSprite.cacheTexture(Paths.image('num9'));
FunkinSprite.cacheTexture(Paths.image('notes', 'shared'));
FunkinSprite.cacheTexture(Paths.image('noteSplashes', 'shared'));
FunkinSprite.cacheTexture(Paths.image('noteStrumline', 'shared'));
FunkinSprite.cacheTexture(Paths.image('NOTE_hold_assets'));
FunkinSprite.cacheTexture(Paths.image('ready', 'shared'));
FunkinSprite.cacheTexture(Paths.image('set', 'shared'));
FunkinSprite.cacheTexture(Paths.image('go', 'shared'));
FunkinSprite.cacheTexture(Paths.image('sick', 'shared'));
FunkinSprite.cacheTexture(Paths.image('good', 'shared'));
FunkinSprite.cacheTexture(Paths.image('bad', 'shared'));
FunkinSprite.cacheTexture(Paths.image('shit', 'shared'));
FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this
// FunkinSprite.cacheAllNoteStyleTextures(noteStyle) // This will replace the stuff above!
// FunkinSprite.cacheAllCharacterTextures(player)
// FunkinSprite.cacheAllCharacterTextures(girlfriend)
// FunkinSprite.cacheAllCharacterTextures(opponent)
// FunkinSprite.cacheAllStageTextures(stage)
FunkinSprite.purgeCache();
FlxG.switchState(playStateCtor);
#end
}
@ -354,7 +391,7 @@ class MultiCallback
public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
{
var screenShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("shaderTransitionStuff/coolDots"));
var screenShit:FunkinSprite = FunkinSprite.create(Paths.image("shaderTransitionStuff/coolDots"));
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
screenWipeShit.funnyShit.input = screenShit.pixels;

View file

@ -3,6 +3,7 @@ package funkin.ui.transition;
import flixel.FlxSprite;
import haxe.Json;
import lime.utils.Assets;
import funkin.graphics.FunkinSprite;
// import flxtyped group
import funkin.ui.MusicBeatSubState;
import funkin.ui.story.StoryMenuState;
@ -245,6 +246,10 @@ class StickerSubState extends MusicBeatSubState
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
// TODO: Rework this asset caching stuff
FunkinSprite.preparePurgeCache();
FunkinSprite.purgeCache();
// I think this grabs the screen and puts it under the stickers?
// Leaving this commented out rather than stripping it out because it's cool...
/*
@ -301,14 +306,14 @@ class StickerSubState extends MusicBeatSubState
}
}
class StickerSprite extends FlxSprite
class StickerSprite extends FunkinSprite
{
public var timing:Float = 0;
public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void
{
super(x, y);
loadGraphic(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName));
loadTexture(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName));
updateHitbox();
scrollFactor.set();
}