1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-12-12 08:25:13 +00:00

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

View file

@ -108,7 +108,7 @@
<haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI --> <haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI -->
<haxelib name="polymod" /> <!-- Modding framework --> <haxelib name="polymod" /> <!-- Modding framework -->
<haxelib name="flxanimate" /> <!-- Texture atlas rendering --> <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="json2object" /> <!-- JSON parsing -->
<haxelib name="thx.semver" /> <!-- Version string handling --> <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", "name": "haxeui-core",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "8a7846b", "ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b",
"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": "e9f880522e27134b29df4067f82df7d7e5237b70", "ref": "63a906a6148958dbfde8c7b48d90b0693767fd95",
"url": "https://github.com/haxeui/haxeui-flixel" "url": "https://github.com/haxeui/haxeui-flixel"
}, },
{ {

View file

@ -146,12 +146,11 @@ class InitState extends FlxState
#end #end
// Make errors and warnings less annoying. // 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.openConsole = false;
LogStyle.ERROR.errorSound = null; LogStyle.ERROR.errorSound = null;
LogStyle.WARNING.openConsole = false; LogStyle.WARNING.openConsole = false;
LogStyle.WARNING.errorSound = null; LogStyle.WARNING.errorSound = null;
#end
// //
// FLIXEL TRANSITIONS // FLIXEL TRANSITIONS

View file

@ -16,6 +16,20 @@ class Paths
currentLevel = name.toLowerCase(); 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>) static function getPath(file:String, type:AssetType, library:Null<String>)
{ {
if (library != null) return getLibraryPath(file, library); if (library != null) return getLibraryPath(file, library);

View file

@ -23,7 +23,7 @@ import openfl.utils.AssetType;
@:nullSafety @:nullSafety
class FunkinSound extends FlxSound implements ICloneable<FunkinSound> 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>(); 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 override function set_volume(value:Float):Float
{ {
// Uncap the volume. // Uncap the volume.
fixMaxVolume();
_volume = FlxMath.bound(value, 0.0, MAX_VOLUME); _volume = FlxMath.bound(value, 0.0, MAX_VOLUME);
updateTransform(); updateTransform();
return _volume; return _volume;
@ -126,17 +125,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
return this; 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 public override function play(forceRestart:Bool = false, startTime:Float = 0, ?endTime:Float):FunkinSound
{ {
if (!exists) return this; if (!exists) return this;

View file

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

View file

@ -61,7 +61,16 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
for (entryCls in scriptedEntryClassNames) 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) 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> 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 a version rule is not specified, do not check against it.
if (versionRule == null || VersionUtil.validateVersion(version, versionRule)) if (versionRule == null || VersionUtil.validateVersion(version, versionRule))
{ {

View file

@ -108,8 +108,8 @@ class SongEventRegistry
public static function handleEvent(data:SongEventData):Void public static function handleEvent(data:SongEventData):Void
{ {
var eventType:String = data.event; var eventKind:String = data.eventKind;
var eventHandler:SongEvent = eventCache.get(eventType); var eventHandler:SongEvent = eventCache.get(eventKind);
if (eventHandler != null) if (eventHandler != null)
{ {
@ -117,7 +117,7 @@ class SongEventRegistry
} }
else else
{ {
trace('WARNING: No event handler for event with id: ${eventType}'); trace('WARNING: No event handler for event with kind: ${eventKind}');
} }
data.activated = true; 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. * 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 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? // I believe @:jignored should be iggnored by the writer?
// var output = this.clone(); // var output = this.clone();
// output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer. // 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 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); 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. * Custom events can be added by scripts with the `ScriptedSongEvent` class.
*/ */
@:alias("e") @:alias("e")
public var event:String; public var eventKind:String;
/** /**
* The data for the event. * The data for the event.
@ -668,10 +670,10 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
@:jignored @:jignored
public var activated:Bool = false; 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.time = time;
this.event = event; this.eventKind = eventKind;
this.value = value; this.value = value;
} }
@ -687,19 +689,19 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
public function clone():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. * 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 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 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> public inline function getHandler():Null<SongEvent>
{ {
return SongEventRegistry.getEvent(this.event); return SongEventRegistry.getEvent(this.eventKind);
} }
public inline function getSchema():Null<SongEventSchema> public inline function getSchema():Null<SongEventSchema>
{ {
return SongEventRegistry.getEventSchema(this.event); return SongEventRegistry.getEventSchema(this.eventKind);
} }
public inline function getDynamic(key:String):Null<Dynamic> public inline function getDynamic(key:String):Null<Dynamic>
@ -784,7 +786,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
var eventHandler = getHandler(); var eventHandler = getHandler();
var eventSchema = getSchema(); var eventSchema = getSchema();
if (eventSchema == null) return 'Unknown Event: ${this.event}'; if (eventSchema == null) return 'Unknown Event: ${this.eventKind}';
var result = '${eventHandler.getTitle()}'; var result = '${eventHandler.getTitle()}';
@ -809,19 +811,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
public function clone():SongEventData 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) @:op(A == B)
public function op_equals(other:SongEventData):Bool 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) @:op(A != B)
public function op_notEquals(other:SongEventData):Bool 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) @:op(A > B)
@ -853,7 +855,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
*/ */
public function toString():String 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); 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> public static function offsetSongEventData(events:Array<SongEventData>, offset:Float):Array<SongEventData>
{ {
return events.map(function(event:SongEventData):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. * An FlxSprite with additional functionality.
* - A more efficient method for creating solid color sprites.
* - TODO: Better cache handling for textures.
*/ */
class FunkinSprite extends FlxSprite 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 x Starting X position
* @param y Starting Y position * @param y Starting Y position
@ -18,19 +32,184 @@ class FunkinSprite extends FlxSprite
super(x, y); 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, * 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 width The target width of the sprite.
* @param height The target height of the sprite. * @param height The target height of the sprite.
* @param color The color to fill the sprite with. * @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 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)}'); var graphic:FlxGraphic = FlxG.bitmap.create(2, 2, color, false, 'solid#${color.toHexString(true, false)}');
frames = graphic.imageFrame; frames = graphic.imageFrame;
scale.set(width / 2, height / 2); scale.set(width / 2.0, height / 2.0);
updateHitbox(); updateHitbox();
return this; return this;

View file

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

View file

@ -106,12 +106,19 @@ class NoteScriptEvent extends ScriptEvent
*/ */
public var playSound(default, default):Bool; 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 public function new(type:ScriptEventType, note:NoteSprite, comboCount:Int = 0, cancelable:Bool = false):Void
{ {
super(type, cancelable); super(type, cancelable);
this.note = note; this.note = note;
this.comboCount = comboCount; this.comboCount = comboCount;
this.playSound = true; this.playSound = true;
this.healthMulti = 1.0;
} }
public override function toString():String public override function toString():String
@ -182,17 +189,17 @@ class SongEventScriptEvent extends ScriptEvent
* The note associated with this event. * The note associated with this event.
* You cannot replace it, but you can edit it. * 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); super(SONG_EVENT, true);
this.event = event; this.eventData = eventData;
} }
public override function toString():String 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.FlxEase;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.FlxSprite; import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.module.ModuleHandler; import funkin.modding.module.ModuleHandler;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
@ -214,7 +215,7 @@ class Countdown
if (spritePath == null) return; 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); countdownSprite.scrollFactor.set(0, 0);
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE)); 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.FlxObject;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.sound.FlxSound; import flixel.sound.FlxSound;
import funkin.ui.story.StoryMenuState; import funkin.audio.FunkinSound;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;
import funkin.ui.MusicBeatSubState;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.BaseCharacter;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.ui.freeplay.FreeplayState; 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. * 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. * 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. * Whether the player has confirmed and prepared to restart the level.
@ -71,6 +73,11 @@ class GameOverSubState extends MusicBeatSubState
*/ */
var isEnding:Bool = false; var isEnding:Bool = false;
/**
* Whether the death music is on its first loop.
*/
var isStarting:Bool = true;
var isChartingMode:Bool = false; var isChartingMode:Bool = false;
var transparent:Bool; var transparent:Bool;
@ -140,14 +147,16 @@ class GameOverSubState extends MusicBeatSubState
// Set up the audio // 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. // The conductor now represents the BPM of the game over music.
Conductor.instance.update(0); 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; var hasStartedAnimation:Bool = false;
override function update(elapsed:Float) 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. // Match the conductor to the music.
// This enables the stepHit and beatHit events. // This enables the stepHit and beatHit events.
@ -291,24 +300,71 @@ class GameOverSubState extends MusicBeatSubState
ScriptEventDispatcher.callEvent(boyfriend, event); 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. * Starts the death music at the appropriate volume.
* @param startingVolume * @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); var musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
if (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.volume = startingVolume;
gameOverMusic.looped = !isEnding; gameOverMusic.looped = !(isEnding || isStarting);
gameOverMusic.onComplete = onComplete;
gameOverMusic.play(); gameOverMusic.play();
} }
else
{
@:privateAccess
trace('Music already playing! ${gameOverMusic?._label}');
}
} }
static var blueballed:Bool = false; static var blueballed:Bool = false;
@ -320,7 +376,14 @@ class GameOverSubState extends MusicBeatSubState
public static function playBlueBalledSFX() public static function playBlueBalledSFX()
{ {
blueballed = true; 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; 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 public override function toString():String
{ {
return "GameOverSubState"; return "GameOverSubState";

View file

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

View file

@ -13,6 +13,7 @@ import flixel.util.FlxColor;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.ui.Alphabet; import funkin.ui.Alphabet;
import funkin.graphics.FunkinSprite;
class PauseSubState extends MusicBeatSubState class PauseSubState extends MusicBeatSubState
{ {
@ -72,7 +73,7 @@ class PauseSubState extends MusicBeatSubState
FlxG.sound.list.add(pauseMusic); 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.alpha = 0;
bg.scrollFactor.set(); bg.scrollFactor.set();
add(bg); add(bg);

View file

@ -10,12 +10,14 @@ import flixel.addons.transition.Transition;
import flixel.addons.transition.Transition; import flixel.addons.transition.Transition;
import flixel.FlxCamera; import flixel.FlxCamera;
import flixel.FlxObject; import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.FlxState; import flixel.FlxState;
import funkin.graphics.FunkinSprite;
import flixel.FlxSubState; import flixel.FlxSubState;
import funkin.graphics.FunkinSprite;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import flixel.math.FlxPoint; import flixel.math.FlxPoint;
import flixel.math.FlxRect; import flixel.math.FlxRect;
import funkin.graphics.FunkinSprite;
import flixel.text.FlxText; import flixel.text.FlxText;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween; 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. * 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. * 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; public var cameraFollowPoint:FlxObject;
@ -400,7 +402,7 @@ class PlayState extends MusicBeatSubState
* The background image used for the health bar. * 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`. * 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. * The health icon representing the player.
@ -568,12 +570,15 @@ class PlayState extends MusicBeatSubState
if (!assertChartExists()) return; if (!assertChartExists()) return;
// TODO: Add something to toggle this on!
if (false) if (false)
{ {
// Displays the camera follow point as a sprite for debug purposes. // 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.visible = false;
cameraFollowPoint.zIndex = 1000000; cameraFollowPoint.zIndex = 1000000;
this.cameraFollowPoint = cameraFollowPoint;
} }
else else
{ {
@ -918,6 +923,7 @@ class PlayState extends MusicBeatSubState
{ {
FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation());
} }
FlxG.watch.addQuick('health', health);
// TODO: Add a song event for Handle GF dance speed. // 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. // 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) if (songEvents != null && songEvents.length > 0)
{ {
var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition); 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.'); trace('Found ${songEventsToActivate.length} event(s) to activate.');
for (event in songEventsToActivate) for (event in songEventsToActivate)
{ {
// If an event is trying to play, but it's over 5 seconds old, skip it. // If an event is trying to play, but it's over 1 second old, skip it.
if (event.time - Conductor.instance.songPosition < -5000) var eventAge:Float = Conductor.instance.songPosition - event.time;
if (eventAge > 1000)
{ {
event.activated = true; event.activated = true;
continue; 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 public override function dispatchEvent(event:ScriptEvent):Void
@ -1348,7 +1360,7 @@ class PlayState extends MusicBeatSubState
function initHealthBar():Void function initHealthBar():Void
{ {
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9; 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.screenCenter(X);
healthBarBG.scrollFactor.set(0, 0); healthBarBG.scrollFactor.set(0, 0);
add(healthBarBG); add(healthBarBG);
@ -1382,7 +1394,7 @@ class PlayState extends MusicBeatSubState
function initMinimalMode():Void function initMinimalMode():Void
{ {
// Create the green background. // Create the green background.
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); var menuBG = FunkinSprite.create(Paths.image('menuDesat'));
menuBG.color = 0xFF4CAF50; menuBG.color = 0xFF4CAF50;
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
menuBG.updateHitbox(); menuBG.updateHitbox();
@ -1408,8 +1420,7 @@ class PlayState extends MusicBeatSubState
var event:ScriptEvent = new ScriptEvent(CREATE, false); var event:ScriptEvent = new ScriptEvent(CREATE, false);
ScriptEventDispatcher.callEvent(currentStage, event); ScriptEventDispatcher.callEvent(currentStage, event);
// Apply camera zoom level from stage data. resetCameraZoom();
defaultCameraZoom = currentStage.camZoom;
// Add the stage to the scene. // Add the stage to the scene.
this.add(currentStage); 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. * Generates the character sprites and adds them to the stage.
*/ */
@ -1750,7 +1767,7 @@ class PlayState extends MusicBeatSubState
currentChart.playInst(1.0, false); 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. // 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.time = startTimestamp - Conductor.instance.instrumentalOffset; FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
@ -1978,7 +1995,7 @@ class PlayState extends MusicBeatSubState
// Judge the miss. // Judge the miss.
// NOTE: This is what handles the scoring. // NOTE: This is what handles the scoring.
trace('Missed note! ${note.noteData}'); trace('Missed note! ${note.noteData}');
onNoteMiss(note); onNoteMiss(note, event.playSound, event.healthMulti);
note.handledMiss = true; note.handledMiss = true;
} }
@ -2030,6 +2047,7 @@ class PlayState extends MusicBeatSubState
} }
} }
// Respawns notes that were b
playerStrumline.handleSkippedNotes(); playerStrumline.handleSkippedNotes();
opponentStrumline.handleSkippedNotes(); opponentStrumline.handleSkippedNotes();
} }
@ -2129,7 +2147,7 @@ class PlayState extends MusicBeatSubState
// Calling event.cancelEvent() skips all the other logic! Neat! // Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) return; if (event.eventCanceled) return;
popUpScore(note, input); popUpScore(note, input, event.healthMulti);
if (note.isHoldNote && note.holdNoteSprite != null) 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. * Called when a note leaves the screen and is considered missed by the player.
* @param note * @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!! // If we are here, we already CALLED the onNoteMiss script hook!
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;
health -= Constants.HEALTH_MISS_PENALTY; health -= Constants.HEALTH_MISS_PENALTY * healthLossMulti;
songScore -= 10; songScore -= 10;
if (!isPracticeMode) if (!isPracticeMode)
@ -2201,7 +2215,7 @@ class PlayState extends MusicBeatSubState
Highscore.tallies.combo = comboPopUps.displayCombo(0); Highscore.tallies.combo = comboPopUps.displayCombo(0);
} }
if (event.playSound) if (playSound)
{ {
vocals.playerVolume = 0; vocals.playerVolume = 0;
FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); 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; if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
#end #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. // Open the stage editor overlaying the current state.
if (controls.DEBUG_STAGE) if (controls.DEBUG_STAGE)
{ {
@ -2301,7 +2310,7 @@ class PlayState extends MusicBeatSubState
#if (debug || FORCE_DEBUG_VERSION) #if (debug || FORCE_DEBUG_VERSION)
// 1: End the song immediately. // 1: End the song immediately.
if (FlxG.keys.justPressed.ONE) endSong(); if (FlxG.keys.justPressed.ONE) endSong(true);
// 2: Gain 10% health. // 2: Gain 10% health.
if (FlxG.keys.justPressed.TWO) health += 0.1 * Constants.HEALTH_MAX; 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. * 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; vocals.playerVolume = 1;
@ -2359,19 +2368,19 @@ class PlayState extends MusicBeatSubState
{ {
case 'sick': case 'sick':
Highscore.tallies.sick += 1; Highscore.tallies.sick += 1;
health += Constants.HEALTH_SICK_BONUS; health += Constants.HEALTH_SICK_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK; isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK;
case 'good': case 'good':
Highscore.tallies.good += 1; Highscore.tallies.good += 1;
health += Constants.HEALTH_GOOD_BONUS; health += Constants.HEALTH_GOOD_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK; isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK;
case 'bad': case 'bad':
Highscore.tallies.bad += 1; Highscore.tallies.bad += 1;
health += Constants.HEALTH_BAD_BONUS; health += Constants.HEALTH_BAD_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK; isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK;
case 'shit': case 'shit':
Highscore.tallies.shit += 1; Highscore.tallies.shit += 1;
health += Constants.HEALTH_SHIT_BONUS; health += Constants.HEALTH_SHIT_BONUS * healthGainMulti;
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK;
} }
@ -2495,16 +2504,35 @@ class PlayState extends MusicBeatSubState
if (skipHeldTimer >= 1.5) 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 #if sys
// spitter for ravy, teehee!! // spitter for ravy, teehee!!
@ -2514,9 +2542,7 @@ class PlayState extends MusicBeatSubState
#end #end
deathCounter = 0; deathCounter = 0;
mayPauseGame = false;
FlxG.sound.music.volume = 0;
vocals.volume = 0;
if (currentSong != null && currentSong.validScore) if (currentSong != null && currentSong.validScore)
{ {
// crackhead double thingie, sets whether was new highscore, AND saves the song! // crackhead double thingie, sets whether was new highscore, AND saves the song!
@ -2603,7 +2629,14 @@ class PlayState extends MusicBeatSubState
} }
else else
{ {
moveToResultsScreen(); if (rightGoddamnNow)
{
moveToResultsScreen();
}
else
{
zoomIntoResultsScreen();
}
} }
} }
else else
@ -2621,10 +2654,10 @@ class PlayState extends MusicBeatSubState
// TODO: Softcode this cutscene. // TODO: Softcode this cutscene.
if (currentSong.id == 'eggnog') if (currentSong.id == 'eggnog')
{ {
var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom, var blackBG:FunkinSprite = new FunkinSprite(-FlxG.width * FlxG.camera.zoom, -FlxG.height * FlxG.camera.zoom);
-FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); blackBG.makeSolidColor(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK);
blackShit.scrollFactor.set(); blackBG.scrollFactor.set();
add(blackShit); add(blackBG);
camHUD.visible = false; camHUD.visible = false;
isInCutscene = true; isInCutscene = true;
@ -2661,7 +2694,14 @@ class PlayState extends MusicBeatSubState
} }
else 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!'); trace('WENT TO RESULTS SCREEN!');
@ -2771,22 +2811,30 @@ class PlayState extends MusicBeatSubState
{ {
ease: FlxEase.expoIn, ease: FlxEase.expoIn,
onComplete: function(_) { onComplete: function(_) {
persistentUpdate = false; moveToResultsScreen();
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);
} }
}); });
}); });
} }
/**
* 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. * Pauses music and vocals easily.
*/ */
@ -2816,14 +2864,18 @@ class PlayState extends MusicBeatSubState
*/ */
function changeSection(sections:Int):Void 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); 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; FlxG.sound.music.time = targetTimeMs;
handleSkippedNotes(); handleSkippedNotes();
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
// regenNoteData(FlxG.sound.music.time); // regenNoteData(FlxG.sound.music.time);
Conductor.instance.update(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 funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.FlxBasic; import flixel.FlxBasic;
import flixel.FlxSprite; import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxBitmapFont; import flixel.graphics.frames.FlxBitmapFont;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
@ -96,8 +97,7 @@ class ResultState extends MusicBeatSubState
bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce! bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce!
}; };
var gf:FlxSprite = new FlxSprite(500, 300); var gf:FlxSprite = FunkinSprite.createSparrow(500, 300, 'resultScreen/resultGirlfriendGOOD');
gf.frames = Paths.getSparrowAtlas('resultScreen/resultGirlfriendGOOD');
gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false); gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
gf.visible = false; gf.visible = false;
gf.animation.finishCallback = _ -> { gf.animation.finishCallback = _ -> {
@ -105,8 +105,7 @@ class ResultState extends MusicBeatSubState
}; };
add(gf); add(gf);
var boyfriend:FlxSprite = new FlxSprite(640, -200); var boyfriend:FlxSprite = FunkinSprite.createSparrow(640, -200, 'resultScreen/resultBoyfriendGOOD');
boyfriend.frames = Paths.getSparrowAtlas('resultScreen/resultBoyfriendGOOD');
boyfriend.animation.addByPrefix("fall", "Boyfriend Good", 24, false); boyfriend.animation.addByPrefix("fall", "Boyfriend Good", 24, false);
boyfriend.visible = false; boyfriend.visible = false;
boyfriend.animation.finishCallback = function(_) { boyfriend.animation.finishCallback = function(_) {
@ -115,8 +114,7 @@ class ResultState extends MusicBeatSubState
add(boyfriend); add(boyfriend);
var soundSystem:FlxSprite = new FlxSprite(-15, -180); var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem');
soundSystem.frames = Paths.getSparrowAtlas("resultScreen/soundSystem");
soundSystem.animation.addByPrefix("idle", "sound system", 24, false); soundSystem.animation.addByPrefix("idle", "sound system", 24, false);
soundSystem.visible = false; soundSystem.visible = false;
new FlxTimer().start(0.4, _ -> { 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}); FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut, startDelay: 0.5});
add(blackTopBar); add(blackTopBar);
var resultsAnim:FlxSprite = new FlxSprite(-200, -10); var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
resultsAnim.frames = Paths.getSparrowAtlas("resultScreen/results");
resultsAnim.animation.addByPrefix("result", "results", 24, false); resultsAnim.animation.addByPrefix("result", "results", 24, false);
resultsAnim.animation.play("result"); resultsAnim.animation.play("result");
add(resultsAnim); add(resultsAnim);
var ratingsPopin:FlxSprite = new FlxSprite(-150, 120); var ratingsPopin:FunkinSprite = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
ratingsPopin.frames = Paths.getSparrowAtlas("resultScreen/ratingsPopin");
ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false); ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false);
ratingsPopin.visible = false; ratingsPopin.visible = false;
add(ratingsPopin); add(ratingsPopin);
var scorePopin:FlxSprite = new FlxSprite(-180, 520); var scorePopin:FunkinSprite = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
scorePopin.frames = Paths.getSparrowAtlas("resultScreen/scorePopin");
scorePopin.animation.addByPrefix("score", "tally score", 24, false); scorePopin.animation.addByPrefix("score", "tally score", 24, false);
scorePopin.visible = false; scorePopin.visible = false;
add(scorePopin); add(scorePopin);

View file

@ -305,9 +305,15 @@ class CharacterDataParser
icon = "darnell"; icon = "darnell";
case "senpai-angry": case "senpai-angry":
icon = "senpai"; 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 flixel.math.FlxPoint;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
import openfl.utils.Assets; import openfl.utils.Assets;
import funkin.graphics.FunkinSprite;
import funkin.util.MathUtil; import funkin.util.MathUtil;
/** /**
@ -26,7 +27,7 @@ import funkin.util.MathUtil;
* @author MasterEric * @author MasterEric
*/ */
@:nullSafety @:nullSafety
class HealthIcon extends FlxSprite class HealthIcon extends FunkinSprite
{ {
/** /**
* The character this icon is representing. * The character this icon is representing.
@ -408,7 +409,7 @@ class HealthIcon extends FlxSprite
if (!isLegacyStyle) if (!isLegacyStyle)
{ {
frames = Paths.getSparrowAtlas('icons/icon-$charId'); loadSparrow('icons/icon-$charId');
loadAnimationNew(); loadAnimationNew();
} }

View file

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

View file

@ -19,13 +19,22 @@ import hxcodec.flixel.FlxVideoSprite;
class VideoCutscene class VideoCutscene
{ {
static var blackScreen:FlxSprite; 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. * Play a video cutscene.
* TODO: Currently this is hardcoded to start the countdown after the video is done. * 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 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; if (PlayState.instance == null) return;
@ -36,6 +45,8 @@ class VideoCutscene
return; return;
} }
var rawFilePath = Paths.stripLibrary(filePath);
// Trigger the cutscene. Don't play the song in the background. // Trigger the cutscene. Don't play the song in the background.
PlayState.instance.isInCutscene = true; PlayState.instance.isInCutscene = true;
PlayState.instance.camHUD.visible = false; PlayState.instance.camHUD.visible = false;
@ -47,12 +58,14 @@ class VideoCutscene
blackScreen.cameras = [PlayState.instance.camCutscene]; blackScreen.cameras = [PlayState.instance.camCutscene];
PlayState.instance.add(blackScreen); PlayState.instance.add(blackScreen);
VideoCutscene.cutsceneType = cutsceneType;
#if html5 #if html5
playVideoHTML5(filePath); playVideoHTML5(filePath);
#end #elseif hxCodec
playVideoNative(rawFilePath);
#if hxCodec #else
playVideoNative(filePath); throw "No video support for this platform!";
#end #end
} }
@ -66,8 +79,6 @@ class VideoCutscene
} }
#if html5 #if html5
static var vid:FlxVideo;
static function playVideoHTML5(filePath:String):Void static function playVideoHTML5(filePath:String):Void
{ {
// Video displays OVER the FlxState. // Video displays OVER the FlxState.
@ -92,8 +103,6 @@ class VideoCutscene
#end #end
#if hxCodec #if hxCodec
static var vid:FlxVideoSprite;
static function playVideoNative(filePath:String):Void static function playVideoNative(filePath:String):Void
{ {
// Video displays OVER the FlxState. // Video displays OVER the FlxState.
@ -110,6 +119,15 @@ class VideoCutscene
PlayState.instance.refresh(); PlayState.instance.refresh();
vid.play(filePath, false); 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 else
{ {
@ -118,10 +136,17 @@ class VideoCutscene
} }
#end #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 public static function finishVideo(?transitionTime:Float = 0.5):Void
{ {
trace('ALERT: Finish video cutscene called!'); trace('ALERT: Finish video cutscene called!');
var cutsceneType:CutsceneType = VideoCutscene.cutsceneType;
#if html5 #if html5
if (vid != null) if (vid != null)
{ {
@ -157,8 +182,32 @@ class VideoCutscene
{ {
ease: FlxEase.quadInOut, ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) { 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 function setup():Void
{ {
if (frameCollection?.parent?.isDestroyed ?? false) frameCollection = null;
if (frameCollection == null) preloadFrames(); if (frameCollection == null) preloadFrames();
this.frames = frameCollection; this.frames = frameCollection;
@ -75,6 +76,8 @@ class NoteSplash extends FlxSprite
this.playAnimation('splash${variant}Right'); this.playAnimation('splash${variant}Right');
} }
if (animation.curAnim == null) return;
// Vary the speed of the animation a bit. // Vary the speed of the animation a bit.
animation.curAnim.frameRate = FRAMERATE_DEFAULT + FlxG.random.int(-FRAMERATE_VARIANCE, FRAMERATE_VARIANCE); 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 funkin.play.notes.notestyle.NoteStyle;
import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxAtlasFrames;
import flixel.FlxSprite; import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.HSVShader;
class NoteSprite extends FlxSprite class NoteSprite extends FunkinSprite
{ {
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red']; 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 flixel.graphics.frames.FlxFramesCollection;
import funkin.data.animation.AnimationData; import funkin.data.animation.AnimationData;
import funkin.data.IRegistryEntry; import funkin.data.IRegistryEntry;
import funkin.graphics.FunkinSprite;
import funkin.data.notestyle.NoteStyleData; import funkin.data.notestyle.NoteStyleData;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
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 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; if (noteFrames != null && !force) return noteFrames;
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary()); noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
@ -109,8 +118,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
throw 'Could not load note frames for note style: $id'; throw 'Could not load note frames for note style: $id';
} }
noteFrames.parent.persist = true;
return noteFrames; return noteFrames;
} }

View file

@ -185,9 +185,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
switch (dataProp.animType) switch (dataProp.animType)
{ {
case 'packer': case 'packer':
propSprite.frames = Paths.getPackerAtlas(dataProp.assetPath); propSprite.loadPacker(dataProp.assetPath);
default: // 'sparrow' default: // 'sparrow'
propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath); propSprite.loadSparrow(dataProp.assetPath);
} }
} }
else if (isSolidColor) else if (isSolidColor)
@ -209,7 +209,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
else else
{ {
// Initalize static sprite. // Initalize static sprite.
propSprite.loadGraphic(Paths.image(dataProp.assetPath)); propSprite.loadTexture(Paths.image(dataProp.assetPath));
// Disables calls to update() for a performance boost. // Disables calls to update() for a performance boost.
propSprite.active = false; propSprite.active = false;
@ -397,15 +397,18 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
this.characters.set('bf', character); this.characters.set('bf', character);
charData = _data.characters.bf; charData = _data.characters.bf;
character.flipX = !character.getDataFlipX(); character.flipX = !character.getDataFlipX();
character.name = 'bf';
character.initHealthIcon(false); character.initHealthIcon(false);
case GF: case GF:
this.characters.set('gf', character); this.characters.set('gf', character);
charData = _data.characters.gf; charData = _data.characters.gf;
character.flipX = character.getDataFlipX(); character.flipX = character.getDataFlipX();
character.name = 'gf';
case DAD: case DAD:
this.characters.set('dad', character); this.characters.set('dad', character);
charData = _data.characters.dad; charData = _data.characters.dad;
character.flipX = character.getDataFlipX(); character.flipX = character.getDataFlipX();
character.name = 'dad';
character.initHealthIcon(true); character.initHealthIcon(true);
default: default:
this.characters.set(character.characterId, character); 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. // Update the event sprite's position.
eventSprite.updateEventPosition(renderedEvents); eventSprite.updateEventPosition(renderedEvents);
// Update the sprite's graphic. TODO: Is this inefficient? // Update the sprite's graphic. TODO: Is this inefficient?
eventSprite.playAnimation(eventSprite.eventData.event); eventSprite.playAnimation(eventSprite.eventData.eventKind);
} }
else 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); 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; 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 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]; 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. // 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. // TODO: Clean this up or get rid of it.
@ -46,7 +46,7 @@ class SelectItemsCommand implements ChartEditorCommand
var defaultKey = null; var defaultKey = null;
if (eventSchema == 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 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 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]; var noteSelected = this.notes[0];

View file

@ -31,11 +31,11 @@ class SetItemSelectionCommand implements ChartEditorCommand
state.currentEventSelection = events; 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 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]; 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. // 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. // TODO: Clean this up or get rid of it.
@ -43,7 +43,7 @@ class SetItemSelectionCommand implements ChartEditorCommand
var defaultKey = null; var defaultKey = null;
if (eventSchema == 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 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 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]; var noteSelected = this.notes[0];

View file

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

View file

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

View file

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

View file

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

View file

@ -270,24 +270,21 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
// Build player waveform. // Build player waveform.
// waveformPlayer.waveform.forceUpdate = true; // 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. // 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. // Build opponent waveform.
// waveformOpponent.waveform.forceUpdate = true; // waveformOpponent.waveform.forceUpdate = true;
// note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor // note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor
// so we null check // so we null check
if (opponentVoice != null) waveformOpponent.waveform.waveformData = opponentVoice?.waveformData;
{ waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000) / Constants.MS_PER_SEC;
waveformOpponent.waveform.waveformData = opponentVoice.waveformData;
waveformOpponent.waveform.duration = opponentVoice.length / Constants.MS_PER_SEC;
}
// Build instrumental waveform. // Build instrumental waveform.
// waveformInstrumental.waveform.forceUpdate = true; // waveformInstrumental.waveform.forceUpdate = true;
waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData; 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(); addOffsetsToAudioPreview();
} }

View file

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

View file

@ -156,8 +156,6 @@ class DJBoyfriend extends FlxAtlasSprite
function setupAnimations():Void function setupAnimations():Void
{ {
// frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk'));
// animation.addByPrefix('intro', "boyfriend dj intro", 24, false); // animation.addByPrefix('intro', "boyfriend dj intro", 24, false);
addOffset('boyfriend dj intro', 8, 3); 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)); var flame:FlxSprite = new FlxSprite(flameX + (flameSpreadX * i), flameY + (flameSpreadY * i));
flame.frames = Paths.getSparrowAtlas("freeplay/freeplayFlame"); 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.animation.play("flame");
flame.visible = false; flame.visible = false;
flameCount = 0; flameCount = 0;

View file

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

View file

@ -7,6 +7,7 @@ import flixel.addons.ui.FlxInputText;
import flixel.FlxCamera; import flixel.FlxCamera;
import flixel.FlxGame; import flixel.FlxGame;
import flixel.FlxSprite; import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import flixel.FlxState; import flixel.FlxState;
import flixel.group.FlxGroup; import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
@ -226,17 +227,17 @@ class FreeplayState extends MusicBeatSubState
trace(FlxG.camera.initialZoom); trace(FlxG.camera.initialZoom);
trace(FlxCamera.defaultZoom); 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.color = 0xFFffd4e9; // sets it to pink!
pinkBack.x -= pinkBack.width; pinkBack.x -= pinkBack.width;
FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut}); FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut});
add(pinkBack); 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); 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); add(alsoOrangeLOL);
exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL], exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL],
@ -462,7 +463,7 @@ class FreeplayState extends MusicBeatSubState
var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70); var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70);
fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore'); 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.visible = false;
fnfHighscoreSpr.setGraphicSize(0, Std.int(fnfHighscoreSpr.height * 1)); fnfHighscoreSpr.setGraphicSize(0, Std.int(fnfHighscoreSpr.height * 1));
fnfHighscoreSpr.updateHitbox(); fnfHighscoreSpr.updateHitbox();

View file

@ -3,6 +3,7 @@ package funkin.ui.transition;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import funkin.graphics.FunkinSprite;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.graphics.shaders.ScreenWipeShader; import funkin.graphics.shaders.ScreenWipeShader;
@ -44,11 +45,10 @@ class LoadingState extends MusicBeatState
override function create():Void 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); add(bg);
funkay = new FlxSprite(); funkay = FunkinSprite.create(Paths.image('funkay'));
funkay.loadGraphic(Paths.image('funkay'));
funkay.setGraphicSize(0, FlxG.height); funkay.setGraphicSize(0, FlxG.height);
funkay.updateHitbox(); funkay.updateHitbox();
add(funkay); add(funkay);
@ -209,6 +209,43 @@ class LoadingState extends MusicBeatState
params.targetSong.cacheCharts(true); 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); FlxG.switchState(playStateCtor);
#end #end
} }
@ -354,7 +391,7 @@ class MultiCallback
public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2) 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(); var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
screenWipeShit.funnyShit.input = screenShit.pixels; screenWipeShit.funnyShit.input = screenShit.pixels;

View file

@ -3,6 +3,7 @@ package funkin.ui.transition;
import flixel.FlxSprite; import flixel.FlxSprite;
import haxe.Json; import haxe.Json;
import lime.utils.Assets; import lime.utils.Assets;
import funkin.graphics.FunkinSprite;
// import flxtyped group // import flxtyped group
import funkin.ui.MusicBeatSubState; import funkin.ui.MusicBeatSubState;
import funkin.ui.story.StoryMenuState; import funkin.ui.story.StoryMenuState;
@ -245,6 +246,10 @@ class StickerSubState extends MusicBeatSubState
FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = 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? // 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... // 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 var timing:Float = 0;
public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void
{ {
super(x, y); super(x, y);
loadGraphic(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName)); loadTexture(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName));
updateHitbox(); updateHitbox();
scrollFactor.set(); scrollFactor.set();
} }