Merge branch 'main' into new-editors

This commit is contained in:
Ne_Eo
2024-11-15 18:50:48 +01:00
165 changed files with 2887 additions and 1558 deletions
+4
View File
@@ -230,6 +230,8 @@ class StringTools {
return (cast s).startsWith(start);
#elseif lua
return untyped __lua__("{0}:sub(1, #{1}) == {1}", s, start);
#elseif cpp
return untyped s.__StartsWith(start);
#else
return (s.length >= start.length && s.indexOf(start, 0) == 0);
#end
@@ -255,6 +257,8 @@ class StringTools {
return (cast s).endsWith(end);
#elseif lua
return end == "" || untyped __lua__("{0}:sub(-#{1}) == {1}", s, end);
#elseif cpp
return untyped s.__EndsWith(end);
#else
var elen = end.length;
var slen = s.length;
+11 -2
View File
@@ -8,7 +8,7 @@ import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.util.typeLimit.OneOfTwo;
import flxanimate.animate.FlxAnim.FlxSymbolAnimation;
import funkin.backend.scripting.events.PlayAnimContext;
import funkin.backend.scripting.events.sprite.PlayAnimContext;
import funkin.backend.system.interfaces.IBeatReceiver;
import funkin.backend.system.interfaces.IOffsetCompatible;
import funkin.backend.utils.XMLUtil.AnimData;
@@ -56,6 +56,8 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
public var animEnabled:Bool = true;
public var zoomFactorEnabled:Bool = true;
public var globalCurFrame(get, set):Int;
/**
* ODD interval -> asynced; EVEN interval -> synced
*/
@@ -142,7 +144,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
public function beatHit(curBeat:Int)
{
if(!animEnabled) return;
if (beatAnims.length > 0 && (curBeat + beatOffset) % beatInterval == 0)
if (lastAnimContext != LOCK && beatAnims.length > 0 && (curBeat + beatOffset) % beatInterval == 0)
{
if(skipNegativeBeats && curBeat < 0) return;
// TODO: find a solution without countedBeat
@@ -392,4 +394,11 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
return beatInterval = v;
}
@:noCompletion private inline function get_globalCurFrame() {
return animateAtlas != null ? (animateAtlas.anim.curFrame) : (animation.curAnim != null ? animation.curAnim.curFrame : 0);
}
@:noCompletion private inline function set_globalCurFrame(val:Int) {
return animateAtlas != null ? (animateAtlas.anim.curFrame = val) : (animation.curAnim != null ? animation.curAnim.curFrame = val : val);
}
}
+30 -6
View File
@@ -164,10 +164,36 @@ class MusicBeatState extends FlxState implements IBeatReceiver
persistentUpdate = true;
call("postCreate");
if (!skipTransIn)
openSubState(new MusicBeatTransition(null));
startTransition(null);
skipTransIn = false;
skipTransOut = false;
}
public function startTransition(?newState:FlxState, skipSubStates:Bool = false):Bool {
if (!skipSubStates) {
var curCheckingSub:FlxSubState = subState;
var found:FlxSubState = null; // always gets the last one - Nex
while (curCheckingSub != null) {
if (curCheckingSub is MusicBeatTransition) {
found = null; // avoiding infinite loops - Nex
break;
}
if (curCheckingSub is MusicBeatSubstate && cast(curCheckingSub, MusicBeatSubstate).canOpenCustomTransition) found = curCheckingSub;
curCheckingSub = curCheckingSub.subState;
}
if (found != null) {
found.openSubState(new MusicBeatTransition(newState));
return true; // "Started from a substate?"
}
}
openSubState(new MusicBeatTransition(newState));
return false;
}
public function call(name:String, ?args:Array<Dynamic>, ?defaultVal:Dynamic):Dynamic {
// calls the function on the assigned script
if(stateScripts != null)
@@ -232,7 +258,7 @@ class MusicBeatState extends FlxState implements IBeatReceiver
public override function openSubState(subState:FlxSubState) {
var e = event("onOpenSubState", EventManager.get(StateEvent).recycle(subState));
if (!e.cancelled)
super.openSubState(subState);
super.openSubState(e.substate is FlxSubState ? cast e.substate : subState);
}
public override function onResize(w:Int, h:Int) {
@@ -260,11 +286,9 @@ class MusicBeatState extends FlxState implements IBeatReceiver
if (e.cancelled)
return false;
if (skipTransOut)
if (skipTransOut || (subState is MusicBeatTransition && cast(subState, MusicBeatTransition).newState != null))
return true;
if (subState is MusicBeatTransition && cast(subState, MusicBeatTransition).newState != null)
return true;
openSubState(new MusicBeatTransition(nextState));
startTransition(e.substate);
persistentUpdate = false;
return false;
}
+9 -10
View File
@@ -16,6 +16,10 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
private var lastBeat:Float = 0;
private var lastStep:Float = 0;
/**
* Whether this specifc substate can open custom transitions
*/
public var canOpenCustomTransition:Bool = false;
/**
* Current step
*/
@@ -221,7 +225,7 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
public override function openSubState(subState:FlxSubState) {
var e = event("onOpenSubState", EventManager.get(StateEvent).recycle(subState));
if (!e.cancelled)
super.openSubState(subState);
super.openSubState(e.substate is FlxSubState ? cast e.substate : subState);
}
public override function onResize(w:Int, h:Int) {
@@ -239,7 +243,7 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
var e = event("onStateSwitch", EventManager.get(StateEvent).recycle(nextState));
if (e.cancelled)
return false;
return super.switchTo(nextState);
return super.switchTo(e.substate);
}
public override function onFocus() {
@@ -254,19 +258,14 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
public var parent:FlxState;
public function onSubstateOpen() {
}
public function onSubstateOpen() {}
public override function resetSubState() {
super.resetSubState();
if (subState != null && subState is MusicBeatSubstate) {
var subState:MusicBeatSubstate = cast subState;
subState.parent = this;
super.resetSubState();
if (subState != null)
cast(subState, MusicBeatSubstate).onSubstateOpen();
return;
subState.onSubstateOpen();
}
super.resetSubState();
}
}
+43 -46
View File
@@ -13,12 +13,6 @@ using StringTools;
class Paths
{
/**
* Preferred sound extension for the game's audio files.
* Currently is set to `mp3` for web targets, and `ogg` for other targets.
*/
inline public static final SOUND_EXT = #if web "mp3" #else "ogg" #end;
public static var assetsTree:AssetsLibraryList;
public static var tempFramesCache:Map<String, FlxFramesCollection> = [];
@@ -32,8 +26,8 @@ class Paths
public static inline function getPath(file:String, ?library:String)
return library != null ? '$library:assets/$library/$file' : 'assets/$file';
public static inline function video(key:String, ?ext:String = "mp4")
return getPath('videos/$key.$ext');
public static inline function video(key:String, ?ext:String)
return getPath('videos/$key.${ext != null ? ext : Flags.VIDEO_EXT}');
public static inline function ndll(key:String)
return getPath('ndlls/$key.ndll');
@@ -65,31 +59,33 @@ class Paths
inline static public function ps1(key:String, ?library:String)
return getPath('data/$key.ps1', library);
static public function sound(key:String, ?library:String)
return getPath('sounds/$key.$SOUND_EXT', library);
static public function sound(key:String, ?library:String, ?ext:String)
return getPath('sounds/$key.${ext != null ? ext : Flags.SOUND_EXT}', library);
inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String)
return sound(key + FlxG.random.int(min, max), library);
inline static public function music(key:String, ?library:String)
return getPath('music/$key.$SOUND_EXT', library);
inline static public function music(key:String, ?library:String, ?ext:String)
return getPath('music/$key.${ext != null ? ext : Flags.SOUND_EXT}', library);
inline static public function voices(song:String, difficulty:String = "normal", ?prefix:String = "")
{
inline static public function voices(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
if (ext == null) ext = Flags.SOUND_EXT;
song = song.toLowerCase();
var diff = getPath('songs/${song}/song/Voices$prefix-${difficulty.toLowerCase()}.$SOUND_EXT', null);
return OpenFlAssets.exists(diff) ? diff : getPath('songs/${song}/song/Voices$prefix.$SOUND_EXT', null);
var diff = getPath('songs/$song/song/Voices$prefix-${difficulty.toLowerCase()}.${ext}', null);
return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Voices$prefix.${ext}', null);
}
inline static public function inst(song:String, difficulty:String = "normal", ?prefix:String = "")
{
inline static public function inst(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
if (ext == null) ext = Flags.SOUND_EXT;
song = song.toLowerCase();
var diff = getPath('songs/${song}/song/Inst$prefix-${difficulty.toLowerCase()}.$SOUND_EXT', null);
return OpenFlAssets.exists(diff) ? diff : getPath('songs/${song}/song/Inst$prefix.$SOUND_EXT', null);
var diff = getPath('songs/$song/song/Inst$prefix-${difficulty.toLowerCase()}.${ext}', null);
return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Inst$prefix.${ext}', null);
}
static public function image(key:String, ?library:String, checkForAtlas:Bool = false, ?ext:String = "png")
{
static public function image(key:String, ?library:String, checkForAtlas:Bool = false, ?ext:String) {
if (ext == null) ext = Flags.IMAGE_EXT;
if (checkForAtlas) {
var atlasPath = getPath('images/$key/spritemap.$ext', library);
var multiplePath = getPath('images/$key/1.$ext', library);
@@ -113,9 +109,9 @@ class Paths
return scriptPath;
}
static public function chart(song:String, ?difficulty:String = "normal"):String
static public function chart(song:String, ?difficulty:String):String
{
difficulty = difficulty.toLowerCase();
difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY).toLowerCase();
song = song.toLowerCase();
return getPath('songs/$song/charts/$difficulty.json', null);
@@ -160,33 +156,33 @@ class Paths
return getPath('models/$key.awd');
}
inline static public function getSparrowAtlas(key:String, ?library:String)
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
inline static public function getSparrowAtlas(key:String, ?library:String, ?ext:String)
return FlxAtlasFrames.fromSparrow(image(key, library, ext), file('images/$key.xml', library));
inline static public function getSparrowAtlasAlt(key:String)
return FlxAtlasFrames.fromSparrow('$key.png', '$key.xml');
inline static public function getSparrowAtlasAlt(key:String, ?ext:String)
return FlxAtlasFrames.fromSparrow('$key.${ext != null ? ext : Flags.IMAGE_EXT}', '$key.xml');
inline static public function getPackerAtlas(key:String, ?library:String)
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library));
inline static public function getPackerAtlas(key:String, ?library:String, ?ext:String)
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library, ext), file('images/$key.txt', library));
inline static public function getPackerAtlasAlt(key:String)
return FlxAtlasFrames.fromSpriteSheetPacker('$key.png', '$key.txt');
inline static public function getPackerAtlasAlt(key:String, ?ext:String)
return FlxAtlasFrames.fromSpriteSheetPacker('$key.${ext != null ? ext : Flags.IMAGE_EXT}', '$key.txt');
inline static public function getAsepriteAtlas(key:String, ?library:String)
return FlxAtlasFrames.fromAseprite(image(key, library), file('images/$key.json', library));
inline static public function getAsepriteAtlas(key:String, ?library:String, ?ext:String)
return FlxAtlasFrames.fromAseprite(image(key, library, ext), file('images/$key.json', library));
inline static public function getAsepriteAtlasAlt(key:String)
return FlxAtlasFrames.fromAseprite('$key.png', '$key.json');
inline static public function getAsepriteAtlasAlt(key:String, ?ext:String)
return FlxAtlasFrames.fromAseprite('$key.${ext != null ? ext : Flags.IMAGE_EXT}', '$key.json');
inline static public function getAssetsRoot():String
return ModsFolder.currentModFolder != null ? '${ModsFolder.modsPath}${ModsFolder.currentModFolder}' : './assets';
return ModsFolder.currentModFolder != null ? '${ModsFolder.modsPath}${ModsFolder.currentModFolder}' : #if (sys && TEST_BUILD) './${Main.pathBack}assets/' #else './assets' #end;
/**
* Gets frames at specified path.
* @param key Path to the frames
* @param library (Additional) library to load the frames from.
*/
public static function getFrames(key:String, assetsPath:Bool = false, ?library:String) {
public static function getFrames(key:String, assetsPath:Bool = false, ?library:String, ?ext:String = null) {
if (tempFramesCache.exists(key)) {
var frames = tempFramesCache[key];
if (frames != null && frames.parent != null && frames.parent.bitmap != null && frames.parent.bitmap.readable)
@@ -194,7 +190,7 @@ class Paths
else
tempFramesCache.remove(key);
}
return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true));
return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true, ext), false, null, false, ext);
}
@@ -206,10 +202,11 @@ class Paths
* @param SkipAtlasCheck Whenever the atlas check should be skipped.
* @return FlxFramesCollection Frames
*/
static function loadFrames(path:String, Unique:Bool = false, Key:String = null, SkipAtlasCheck:Bool = false):FlxFramesCollection {
static function loadFrames(path:String, Unique:Bool = false, Key:String = null, SkipAtlasCheck:Bool = false, ?Ext:String = null):FlxFramesCollection {
var noExt = Path.withoutExtension(path);
var ext = Ext != null ? Ext : Flags.IMAGE_EXT;
if (Assets.exists('$noExt/1.png')) {
if (Assets.exists('$noExt/1.${ext}')) {
// MULTIPLE SPRITESHEETS!!
var graphic = FlxG.bitmap.add("flixel/images/logo/default.png", false, '$noExt/mult');
@@ -220,18 +217,18 @@ class Paths
trace("no frames yet for multiple atlases!!");
var cur = 1;
var finalFrames = new MultiFramesCollection(graphic);
while(Assets.exists('$noExt/$cur.png')) {
var spr = loadFrames('$noExt/$cur.png');
while(Assets.exists('$noExt/$cur.${ext}')) {
var spr = loadFrames('$noExt/$cur.${ext}');
finalFrames.addFrames(spr);
cur++;
}
return finalFrames;
} else if (Assets.exists('$noExt.xml')) {
return Paths.getSparrowAtlasAlt(noExt);
return Paths.getSparrowAtlasAlt(noExt, ext);
} else if (Assets.exists('$noExt.txt')) {
return Paths.getPackerAtlasAlt(noExt);
return Paths.getPackerAtlasAlt(noExt, ext);
} else if (Assets.exists('$noExt.json')) {
return Paths.getAsepriteAtlasAlt(noExt);
return Paths.getAsepriteAtlasAlt(noExt, ext);
}
var graph:FlxGraphic = FlxG.bitmap.add(path, Unique, Key);
@@ -0,0 +1,176 @@
package funkin.backend.chart;
import funkin.backend.chart.ChartData.ChartMetaData;
// These new structures are kinda a mess to port, i love and hate them at the same time; why the hell are every difficulty in the same file??? - Nex
class BaseGameParser {
public static function parseChart(data:Dynamic, metaData:Dynamic, result:ChartData) {
// TO DO
}
public static function parseMeta(data:Dynamic, result:ChartMetaData) {
var data:SwagMetadata = data;
result.name = data.songName;
result.artist = data.artist;
result.customValues = {};
result.customValues.timeChanges = data.timeChanges;
var firstTimeChng:SwagTimeChange = data.timeChanges[0];
result.bpm = firstTimeChng.bpm;
result.beatsPerMeasure = firstTimeChng.b != null ? firstTimeChng.b : Flags.DEFAULT_BEATS_PER_MEASURE;
result.difficulties = data.playData.difficulties.concat(data.playData.songVariations);
Reflect.deleteField(data.playData, "difficulties");
for (field in Reflect.fields(data.playData))
Reflect.setProperty(result.customValues, field, Reflect.getProperty(data.playData, field));
}
public static function encodeMeta(meta:ChartMetaData, ?chart:ChartData):SwagMetadata {
var addVars:Dynamic = meta.customValues;
var defStage:String = addVars.stage != null ? addVars.stage : Flags.DEFAULT_STAGE;
var defChars:SwagCharactersList = addVars.characters != null ? addVars.characters : {player: Flags.DEFAULT_CHARACTER, girlfriend: Flags.DEFAULT_GIRLFRIEND, opponent: Flags.DEFAULT_OPPONENT};
var defTimeCh:Array<SwagTimeChange>;
if (addVars.timeChanges != null && addVars.timeChanges.length > 0) {
defTimeCh = addVars.timeChanges;
defTimeCh[0].bpm = meta.bpm;
}
else defTimeCh = [{bpm: meta.bpm, t: -1}];
if (chart != null) {
defStage = chart.stage;
var done:Array<Bool> = [false, false, false];
for (strumLine in chart.strumLines) switch (strumLine.type) {
case OPPONENT:
if (!done[0]) {
done[0] = true;
defChars.opponent = strumLine.characters.getDefault([defChars.opponent])[0];
}
case PLAYER:
if (!done[1]) {
done[1] = true;
defChars.player = strumLine.characters.getDefault([defChars.player])[0];
}
case ADDITIONAL:
if (!done[3]) {
done[3] = true;
defChars.girlfriend = strumLine.characters.getDefault([defChars.girlfriend])[0];
}
}
}
var result:SwagMetadata = {
songName: meta.name,
timeFormat: MILLISECONDS,
artist: meta.artist,
timeChanges: defTimeCh,
looped: false,
generatedBy: 'Basegame Chart Importer (Codename Engine)',
version: Flags.BASEGAME_SONG_METADATA_VERSION,
playData: {
stage: defStage,
characters: defChars,
songVariations: addVars.songVariations != null ? [for (i in 0...addVars.songVariations.length) {meta.difficulties.remove(addVars.songVariations[i]); addVars.songVariations[i];}] : [],
difficulties: meta.difficulties,
noteStyle: addVars.noteStyle != null ? addVars.noteStyle : Flags.BASEGAME_DEFAULT_NOTE_STYLE,
album: addVars.album != null ? addVars.album : Flags.BASEGAME_DEFAULT_ALBUM_ID,
previewStart: addVars.previewStart != null ? addVars.previewStart : Flags.BASEGAME_DEFAULT_PREVIEW_START,
previewEnd: addVars.previewEnd != null ? addVars.previewEnd : Flags.BASEGAME_DEFAULT_PREVIEW_END
},
};
return result;
return null;
}
public static function encodeChart(chart:ChartData):NewSwagSong {
// TO DO
return null;
}
}
// METADATA STRUCTURES
typedef SwagMetadata =
{
var timeFormat:SwagTimeFormat;
var artist:String;
var songName:String;
var playData:SwagPlayData;
var timeChanges:Array<SwagTimeChange>;
var generatedBy:String;
var looped:Bool;
var version:String;
var ?divisions:Int;
var ?offsets:SwagSongOffsets;
}
enum abstract SwagTimeFormat(String) from String to String
{
var TICKS = 'ticks';
var FLOAT = 'float';
var MILLISECONDS = 'ms';
}
typedef SwagTimeChange =
{
var ?d:Int; // Time Signature Den
var ?n:Int; // Time Signature Num
var t:Int; // Time Stamp
var ?b:Int; // Beat Time
var ?bt:Array<Int>; // Beat Tuplets
var bpm:Float;
}
typedef SwagPlayData =
{
var album:String;
var previewStart:Float;
var previewEnd:Float;
var stage:String;
var characters:SwagCharactersList;
var songVariations:Array<String>;
var difficulties:Array<String>;
var noteStyle:String;
}
typedef SwagCharactersList =
{
var player:String;
var girlfriend:String;
var opponent:String;
}
typedef SwagSongOffsets =
{
var ?instrumental:Float;
var ?altInstrumentals:Dynamic;
var ?vocals:Dynamic;
}
// CHART STRUCTURE
typedef NewSwagSong =
{
var version:String;
var scrollSpeed:Dynamic; // Map<String, Float>
var events:Array<SwagEvent>;
var notes:Dynamic; // Map<String, Array<SwagNote>>
var generatedBy:String;
}
typedef SwagEvent =
{
var t:Float; // Time
var e:String; // Event Kind
var v:Dynamic; // Value (Map<String, Dynamic>)
}
typedef SwagNote =
{
var t:Float; // Time
var d:Int; // Data
var l:Float; // Length
var k:String; // Kind
}
+20 -20
View File
@@ -12,11 +12,6 @@ import sys.FileSystem;
using StringTools;
final class Chart {
/**
* Default background colors for songs without bg color
*/
public inline static var defaultColor:FlxColor = 0xFF9271FD;
public static final version:String = "1.6.0";
public static function loadEventsJson(songName:String) {
@@ -32,7 +27,9 @@ final class Chart {
return data;
}
public static function loadChartMeta(songName:String, difficulty:String = "normal", fromMods:Bool = true) {
public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true) {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
var metaPath = Paths.file('songs/${songName.toLowerCase()}/meta.json');
var metaDiffPath = Paths.file('songs/${songName.toLowerCase()}/meta-${difficulty.toLowerCase()}.json');
@@ -52,19 +49,19 @@ final class Chart {
if (data == null)
data = {
name: songName,
bpm: 100
name: songName
};
data.setFieldDefault("name", songName);
data.setFieldDefault("beatsPerMeasure", 4);
data.setFieldDefault("stepsPerBeat", 4);
data.setFieldDefault("bpm", Flags.DEFAULT_BPM);
data.setFieldDefault("beatsPerMeasure", Flags.DEFAULT_BEATS_PER_MEASURE);
data.setFieldDefault("stepsPerBeat", Flags.DEFAULT_STEPS_PER_BEAT);
data.setFieldDefault("needsVoices", true);
data.setFieldDefault("icon", "face");
data.setFieldDefault("icon", Flags.DEFAULT_HEALTH_ICON);
data.setFieldDefault("difficulties", []);
data.setFieldDefault("coopAllowed", false);
data.setFieldDefault("opponentModeAllowed", false);
data.setFieldDefault("coopAllowed", Flags.DEFAULT_COOP_ALLOWED);
data.setFieldDefault("opponentModeAllowed", Flags.DEFAULT_OPPONENT_MODE_ALLOWED);
data.setFieldDefault("displayName", data.name);
data.setFieldDefault("parsedColor", data.color.getColorFromDynamic().getDefault(defaultColor));
data.setFieldDefault("parsedColor", data.color.getColorFromDynamic().getDefault(Flags.DEFAULT_COLOR));
if (data.difficulties.length <= 0) {
data.difficulties = [for(f in Paths.getFolderContent('songs/${songName.toLowerCase()}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
@@ -88,7 +85,9 @@ final class Chart {
return data;
}
public static function parse(songName:String, difficulty:String = "normal"):ChartData {
public static function parse(songName:String, ?difficulty:String):ChartData {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
var chartPath = Paths.chart(songName, difficulty);
var base:ChartData = {
strumLines: [],
@@ -97,8 +96,8 @@ final class Chart {
meta: {
name: null
},
scrollSpeed: 2,
stage: "stage",
scrollSpeed: Flags.DEFAULT_SCROLL_SPEED,
stage: Flags.DEFAULT_STAGE,
codenameChart: true,
fromMods: Paths.assetsTree.existsSpecific(chartPath, "TEXT", MODS)
};
@@ -202,7 +201,8 @@ final class Chart {
* @param saveSettings
* @return Filtered chart used for saving.
*/
public static function save(songFolderPath:String, chart:ChartData, difficulty:String = "normal", ?saveSettings:ChartSaveSettings):ChartData {
public static function save(songFolderPath:String, chart:ChartData, ?difficulty:String, ?saveSettings:ChartSaveSettings):ChartData {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
if (saveSettings == null) saveSettings = {};
if (saveSettings.saveMetaInChart == null) saveSettings.saveMetaInChart = true;
@@ -220,11 +220,11 @@ final class Chart {
var chartPath = '${songFolderPath}/$saveFolder/${difficulty.trim()}.json';
var metaPath = '${songFolderPath}/meta.json';
CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? "\t" : null));
CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null));
// idk how null reacts to it so better be sure
if (saveSettings.overrideExistingMeta == true || !FileSystem.exists(metaPath))
CoolUtil.safeSaveFile(metaPath, Json.stringify(meta, null, saveSettings.prettyPrint == true ? "\t" : null));
CoolUtil.safeSaveFile(metaPath, Json.stringify(meta, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null));
#end
return filteredChart;
}
+2 -1
View File
@@ -20,7 +20,7 @@ typedef ChartMetaData = {
public var ?bpm:Float;
public var ?displayName:String;
public var ?beatsPerMeasure:Float;
public var ?stepsPerBeat:Float;
public var ?stepsPerBeat:Int;
public var ?icon:String;
public var ?color:Dynamic;
public var ?difficulties:Array<String>;
@@ -28,6 +28,7 @@ typedef ChartMetaData = {
public var ?opponentModeAllowed:Bool;
public var ?customValues:Dynamic;
public var ?needsVoices:Bool;
public var ?artist:String;
// NOT TO BE EXPORTED
public var ?parsedColor:FlxColor;
@@ -45,7 +45,7 @@ class FNFLegacyParser {
var camFocusedBF:Bool = false;
var altAnims:Bool = false;
var beatsPerMeasure:Float = data.beatsPerMeasure.getDefault(4);
var beatsPerMeasure:Float = data.beatsPerMeasure.getDefault(Flags.DEFAULT_BEATS_PER_MEASURE);
var curBPM:Float = data.bpm;
var curTime:Float = 0;
var curCrochet:Float = ((60 / curBPM) * 1000);
@@ -132,10 +132,10 @@ class FNFLegacyParser {
if ((swagSection.mustHitSection && strumLine.type == OPPONENT) ||
(!swagSection.mustHitSection && strumLine.type == PLAYER))
sectionNote[1] += 4;
swagSection.sectionNotes.push(sectionNote);
swagSection.sectionNotes.push(sectionNote);
}
}
return {song: base};
}
@@ -156,9 +156,9 @@ class FNFLegacyParser {
for (strumLine in chart.strumLines)
switch (strumLine.type) {
case OPPONENT:
if (base.player2 == null) base.player2 = strumLine.characters.getDefault(["dad"])[0];
if (base.player2 == null) base.player2 = strumLine.characters.getDefault([Flags.DEFAULT_OPPONENT])[0];
case PLAYER:
if (base.player1 == null) base.player1 = strumLine.characters.getDefault(["bf"])[0];
if (base.player1 == null) base.player1 = strumLine.characters.getDefault([Flags.DEFAULT_CHARACTER])[0];
case ADDITIONAL: // do nothing
}
@@ -221,7 +221,7 @@ typedef SwagSong =
// ADDITIONAL STUFF THAT MAY NOT BE PRESENT IN CHART
var ?maxHealth:Float;
var ?beatsPerMeasure:Float;
var ?stepsPerBeat:Float;
var ?stepsPerBeat:Int;
}
typedef SwagSection =
+3 -3
View File
@@ -32,7 +32,7 @@ class PsychParser {
for (note in strumLine.notes) {
var section:Int = Math.floor(Conductor.getStepForTime(note.time) / Conductor.getMeasureLength());
var swagSection:SwagSection = base.notes[section];
if (section >= 0 && section < base.notes.length) {
var sectionNote:Array<Dynamic> = [
note.time, // TIME
@@ -47,7 +47,7 @@ class PsychParser {
swagSection.sectionNotes.push(sectionNote);
}
}
var groupedEvents:Array<Array<ChartEvent>> = [];
var __last:Array<ChartEvent> = null;
var __lastTime:Float = Math.NaN;
@@ -106,7 +106,7 @@ class PsychParser {
for (psychEvent in psychEvents)
for (i in 1...3) // Turn both vals into strings
if (!(psychEvent[i] is String))
if (!(psychEvent[i] is String))
psychEvent[i] = Std.string(psychEvent[i]);
base.events.push([events[0].time, psychEvents]);
+97 -52
View File
@@ -19,89 +19,134 @@ class Script extends FlxBasic implements IFlxDestroyable {
public static function getDefaultVariables(?script:Script):Map<String, Dynamic> {
return [
// Haxe related stuff
"Std" => Std,
"Math" => Math,
"Reflect" => Reflect,
"StringTools" => StringTools,
"Json" => haxe.Json,
"Std" => Std,
"Math" => Math,
"Reflect" => Reflect,
"StringTools" => StringTools,
"Json" => haxe.Json,
// OpenFL & Lime related stuff
"Assets" => openfl.utils.Assets,
"Application" => lime.app.Application,
"Application" => lime.app.Application,
"Main" => funkin.backend.system.Main,
"window" => lime.app.Application.current.window,
// Flixel related stuff
"FlxG" => flixel.FlxG,
"FlxSprite" => flixel.FlxSprite,
"FlxBasic" => flixel.FlxBasic,
"FlxCamera" => flixel.FlxCamera,
"state" => flixel.FlxG.state,
"FlxEase" => flixel.tweens.FlxEase,
"FlxTween" => flixel.tweens.FlxTween,
"FlxSound" => flixel.sound.FlxSound,
"FlxAssets" => flixel.system.FlxAssets,
"FlxMath" => flixel.math.FlxMath,
"FlxGroup" => flixel.group.FlxGroup,
"FlxTypedGroup" => flixel.group.FlxGroup.FlxTypedGroup,
"state" => flixel.FlxG.state,
"FlxG" => flixel.FlxG,
"FlxSprite" => flixel.FlxSprite,
"FlxBasic" => flixel.FlxBasic,
"FlxCamera" => flixel.FlxCamera,
"FlxEase" => flixel.tweens.FlxEase,
"FlxTween" => flixel.tweens.FlxTween,
"FlxSound" => flixel.sound.FlxSound,
"FlxAssets" => flixel.system.FlxAssets,
"FlxMath" => flixel.math.FlxMath,
"FlxGroup" => flixel.group.FlxGroup,
"FlxTypedGroup" => flixel.group.FlxGroup.FlxTypedGroup,
"FlxSpriteGroup" => flixel.group.FlxSpriteGroup,
"FlxTypeText" => flixel.addons.text.FlxTypeText,
"FlxText" => flixel.text.FlxText,
"FlxTimer" => flixel.util.FlxTimer,
"FlxPoint" => CoolUtil.getMacroAbstractClass("flixel.math.FlxPoint"),
"FlxAxes" => CoolUtil.getMacroAbstractClass("flixel.util.FlxAxes"),
"FlxColor" => CoolUtil.getMacroAbstractClass("flixel.util.FlxColor"),
"FlxTypeText" => flixel.addons.text.FlxTypeText,
"FlxText" => flixel.text.FlxText,
"FlxTimer" => flixel.util.FlxTimer,
"FlxPoint" => CoolUtil.getMacroAbstractClass("flixel.math.FlxPoint"),
"FlxAxes" => CoolUtil.getMacroAbstractClass("flixel.util.FlxAxes"),
"FlxColor" => CoolUtil.getMacroAbstractClass("flixel.util.FlxColor"),
// Engine related stuff
"engine" => {
commit: funkin.backend.system.macros.GitCommitMacro.commitNumber,
hash: funkin.backend.system.macros.GitCommitMacro.commitHash,
commit: Flags.COMMIT_NUMBER,
hash: Flags.COMMIT_HASH,
build: 2675, // 2675 being the last build num before it was removed
name: "Codename Engine"
},
"ModState" => funkin.backend.scripting.ModState,
"ModSubState" => funkin.backend.scripting.ModSubState,
"PlayState" => funkin.game.PlayState,
"GameOverSubstate" => funkin.game.GameOverSubstate,
"ModState" => funkin.backend.scripting.ModState,
"ModSubState" => funkin.backend.scripting.ModSubState,
"PlayState" => funkin.game.PlayState,
"GameOverSubstate" => funkin.game.GameOverSubstate,
"HealthIcon" => funkin.game.HealthIcon,
"HudCamera" => funkin.game.HudCamera,
"Note" => funkin.game.Note,
"Strum" => funkin.game.Strum,
"StrumLine" => funkin.game.StrumLine,
"Character" => funkin.game.Character,
"Boyfriend" => funkin.game.Character, // for compatibility
"PauseSubstate" => funkin.menus.PauseSubState,
"FreeplayState" => funkin.menus.FreeplayState,
"MainMenuState" => funkin.menus.MainMenuState,
"PauseSubState" => funkin.menus.PauseSubState,
"HudCamera" => funkin.game.HudCamera,
"Note" => funkin.game.Note,
"Strum" => funkin.game.Strum,
"StrumLine" => funkin.game.StrumLine,
"Character" => funkin.game.Character,
"Boyfriend" => funkin.game.Character, // for compatibility
"PauseSubstate" => funkin.menus.PauseSubState,
"FreeplayState" => funkin.menus.FreeplayState,
"MainMenuState" => funkin.menus.MainMenuState,
"PauseSubState" => funkin.menus.PauseSubState,
"StoryMenuState" => funkin.menus.StoryMenuState,
"TitleState" => funkin.menus.TitleState,
"Options" => funkin.options.Options,
"Paths" => funkin.backend.assets.Paths,
"Conductor" => funkin.backend.system.Conductor,
"FunkinShader" => funkin.backend.shaders.FunkinShader,
"CustomShader" => funkin.backend.shaders.CustomShader,
"Options" => funkin.options.Options,
"Paths" => funkin.backend.assets.Paths,
"Conductor" => funkin.backend.system.Conductor,
"FunkinShader" => funkin.backend.shaders.FunkinShader,
"CustomShader" => funkin.backend.shaders.CustomShader,
"FunkinText" => funkin.backend.FunkinText,
"FlxAnimate" => funkin.backend.FlxAnimate,
"FunkinSprite" => funkin.backend.FunkinSprite,
"Alphabet" => funkin.menus.ui.Alphabet,
"Alphabet" => funkin.menus.ui.Alphabet,
"CoolUtil" => funkin.backend.utils.CoolUtil,
"IniUtil" => funkin.backend.utils.IniUtil,
"XMLUtil" => funkin.backend.utils.XMLUtil,
#if sys "ZipUtil" => funkin.backend.utils.ZipUtil, #end
"MarkdownUtil" => funkin.backend.utils.MarkdownUtil,
"CoolUtil" => funkin.backend.utils.CoolUtil,
"IniUtil" => funkin.backend.utils.IniUtil,
"XMLUtil" => funkin.backend.utils.XMLUtil,
#if sys "ZipUtil" => funkin.backend.utils.ZipUtil, #end
"MarkdownUtil" => funkin.backend.utils.MarkdownUtil,
"EngineUtil" => funkin.backend.utils.EngineUtil,
"MemoryUtil" => funkin.backend.utils.MemoryUtil,
"BitmapUtil" => funkin.backend.utils.BitmapUtil,
];
}
/**
* Used internally to keep backwards compatibility with old scripts.
* This gets set on `hscript.Interp.importRedirects`,
* if you wanna modify it, please edit `hscript.Interp.importRedirects` directly.
**/
public static function getDefaultImportRedirects():Map<String, String> {
var redirects:Map<String, String> = [
"funkin.savedata.FunkinSave" => "funkin.backend.utils.FunkinSave"
];
final events = "funkin.backend.scripting.events.";
redirects[events + "CharacterNodeEvent"] = events + "character.CharacterNodeEvent";
redirects[events + "CharacterXMLEvent"] = events + "character.CharacterXMLEvent";
redirects[events + "DanceEvent"] = events + "character.DanceEvent";
redirects[events + "DirectionAnimEvent"] = events + "character.DirectionAnimEvent";
redirects[events + "DiscordPresenceUpdateEvent"] = events + "discord.DiscordPresenceUpdateEvent";
redirects[events + "GameOverCreationEvent"] = events + "gameover.GameOverCreationEvent";
redirects[events + "CamMoveEvent"] = events + "gameplay.CamMoveEvent";
redirects[events + "CountdownEvent"] = events + "gameplay.CountdownEvent";
redirects[events + "EventGameEvent"] = events + "gameplay.EventGameEvent";
redirects[events + "GameOverEvent"] = events + "gameplay.GameOverEvent";
redirects[events + "RatingUpdateEvent"] = events + "gameplay.RatingUpdateEvent";
redirects[events + "HealthIconChangeEvent"] = events + "healthicon.HealthIconChangeEvent";
redirects[events + "FreeplayAlphaUpdateEvent"] = events + "menu.freeplay.FreeplayAlphaUpdateEvent";
redirects[events + "FreeplaySongSelectEvent"] = events + "menu.freeplay.FreeplaySongSelectEvent";
redirects[events + "MenuChangeEvent"] = events + "menu.MenuChangeEvent";
redirects[events + "PauseCreationEvent"] = events + "menu.pause.PauseCreationEvent";
redirects[events + "WeekSelectEvent"] = events + "menu.storymenu.WeekSelectEvent";
redirects[events + "InputSystemEvent"] = events + "note.InputSystemEvent";
redirects[events + "NoteCreationEvent"] = events + "note.NoteCreationEvent";
redirects[events + "NoteHitEvent"] = events + "note.NoteHitEvent";
redirects[events + "NoteMissEvent"] = events + "note.NoteMissEvent";
redirects[events + "NoteUpdateEvent"] = events + "note.NoteUpdateEvent";
redirects[events + "SimpleNoteEvent"] = events + "note.SimpleNoteEvent";
redirects[events + "StrumCreationEvent"] = events + "note.StrumCreationEvent";
redirects[events + "SplashShowEvent"] = events + "splash.SplashShowEvent";
redirects[events + "PlayAnimContext"] = events + "sprite.PlayAnimContext";
redirects[events + "PlayAnimEvent"] = events + "sprite.PlayAnimEvent";
redirects[events + "StageNodeEvent"] = events + "stage.StageNodeEvent";
redirects[events + "StageXMLEvent"] = events + "stage.StageXMLEvent";
return redirects;
}
public static function getDefaultPreprocessors():Map<String, Dynamic> {
var defines = funkin.backend.system.macros.DefinesMacro.defines;
defines.set("CODENAME_ENGINE", true);
defines.set("CODENAME_VER", Application.current.meta.get('version'));
defines.set("CODENAME_BUILD", 2675); // 2675 being the last build num before it was removed
defines.set("CODENAME_COMMIT", funkin.backend.system.macros.GitCommitMacro.commitNumber);
defines.set("CODENAME_COMMIT", Flags.COMMIT_NUMBER);
return defines;
}
/**
@@ -5,9 +5,4 @@ final class AmountEvent extends CancellableEvent {
* Amount
*/
public var amount:Null<Int>;
/**
* Shows whether or not psych users complained about this class
*/
public var psychUsersComplained:Bool = true;
}
@@ -1,4 +1,5 @@
package funkin.backend.scripting.events;
final class DrawEvent extends CancellableEvent {
// temp empty for now - Nex
}
@@ -0,0 +1,7 @@
## NOTE
Keep in mind that a certain criteria was used to place events in their corresponding folders which is simply what the events themselves are mostly related to.
For example:
- `GameOverEvent` inside of the `gameplay` folder instead of the `gameover` folder since it gets called in the `PlayState` class (that owns the `gameplay` folder) instead of the `GameOverSubstate` class itself;
- `NoteHitEvent` gets indeed called by the `PlayState` class but it's also used in the `StrumLine` (For those who didn't really know, Strumlines are the Input Notes) class and since the event's name itself starts with the word `Note` it was more accurate to place it in the `note` folder instead of the `gameplay` folder that `PlayState` owns.
@@ -4,7 +4,7 @@ import flixel.FlxState;
final class StateEvent extends CancellableEvent {
/**
* Substate that is about to be opened/closed
* Substate or State that is about to be opened/closed
*/
public var substate:FlxState;
public var substate:FlxState; // WHY is it named substate :sob: - Nex
}
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.character;
import funkin.game.Character;
import haxe.xml.Access;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.character;
import funkin.game.Character;
import haxe.xml.Access;
@@ -7,7 +7,7 @@ final class CharacterXMLEvent extends CancellableEvent {
/**
* The character instance
*/
public var character:Character;
public var character:Character;
/**
* The xml
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.character;
final class DanceEvent extends CancellableEvent {
public var danced:Bool;
@@ -1,4 +1,6 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.character;
import funkin.backend.scripting.events.sprite.PlayAnimContext;
final class DirectionAnimEvent extends CancellableEvent {
/**
@@ -0,0 +1,14 @@
package funkin.backend.scripting.events.dialogue;
/**
* CANCEL this event to prevent continuing with the next dialogue.
*
* **NOTE**: To access the current, past or next lines, use `curLine`, `lastLine` or also `dialogueLines` in the `DialogueCutscene` class (they're globals and can be accessed at any time as long as the Dialogue Cutscene is active)!
*/
final class DialogueNextLineEvent extends CancellableEvent
{
/**
* If the dialogue has just been opened.
*/
public var playFirst:Bool = false;
}
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.discord;
final class DiscordPresenceUpdateEvent extends CancellableEvent {
/**
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.gameover;
/**
* CANCEL this event to prevent default behaviour!
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.gameplay;
import flixel.math.FlxPoint;
import funkin.game.StrumLine;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.gameplay;
import flixel.sound.FlxSound;
import flixel.tweens.FlxTween;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.gameplay;
import funkin.backend.chart.ChartData;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.gameplay;
import funkin.game.Character;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.gameplay;
final class RatingUpdateEvent extends CancellableEvent {
/**
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.healthicon;
final class HealthIconChangeEvent extends CancellableEvent {
/**
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.menu;
final class MenuChangeEvent extends CancellableEvent {
/**
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.menu.freeplay;
final class FreeplayAlphaUpdateEvent extends CancellableEvent {
/**
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.menu.freeplay;
final class FreeplaySongSelectEvent extends CancellableEvent {
/**
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.menu.pause;
/**
* CANCEL this event to prevent default behaviour!
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.menu.storymenu;
import funkin.menus.StoryMenuState.WeekData;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.note;
import funkin.game.StrumLine;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.note;
import funkin.game.Note;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.note;
import funkin.game.Character;
import funkin.game.Note;
@@ -24,9 +24,19 @@ final class NoteHitEvent extends CancellableEvent {
*/
public var countScore:Bool = true;
/**
* Whenever ratings should be shown or not.
* Whenever ratings (Rating sprite, Digits sprites and Combo sprite) should be shown or not.
*
* NOTE: Whether it's `true` use `displayRating` and `displayCombo` (plus `minDigitDisplay` in the PlayState class) to change what's going to pop up!
*/
public var showRating:Null<Bool> = null;
/**
* Whenever the Rating sprites should be shown or not.
*/
public var displayRating:Bool;
/**
* Whenever the Combo sprite should be shown or not (like old Week 7 patches).
*/
public var displayCombo:Bool;
/**
* Note that has been pressed
*/
@@ -123,6 +133,13 @@ final class NoteHitEvent extends CancellableEvent {
@:dox(hide)
public function cancelDeletion() {preventDeletion();}
/**
* Forces the note to be deleted.
**/
public function forceDeletion() {
deleteNote = true;
}
/**
* Prevents the vocals volume from being set to 1 after pressing the note.
*/
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.note;
import funkin.game.Character;
import funkin.game.Note;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.note;
import funkin.game.Note;
import funkin.game.Strum;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.note;
import funkin.game.Note;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.note;
import funkin.game.Strum;
@@ -0,0 +1,8 @@
package funkin.backend.scripting.events.shader;
final class ShaderProcessEvent extends CancellableEvent
{
public var source:String;
public var storageType:String;
}
@@ -0,0 +1,6 @@
package funkin.backend.scripting.events.soundtray;
final class SoundTrayShowEvent extends CancellableEvent
{
public var up:Bool;
}
@@ -0,0 +1,12 @@
package funkin.backend.scripting.events.soundtray;
final class SoundTrayTextEvent extends CancellableEvent
{
public var checkIfNull:Bool;
public var reloadDefaultTextFormat:Bool;
public var displayTxt:String;
public var y:Float;
}
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.splash;
import funkin.game.SplashGroup;
import funkin.game.Strum;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.sprite;
/**
Contains all contexts possible for `PlayAnimEvent`.
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.sprite;
final class PlayAnimEvent extends CancellableEvent {
/**
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.stage;
import funkin.game.Stage;
import haxe.xml.Access;
@@ -1,4 +1,4 @@
package funkin.backend.scripting.events;
package funkin.backend.scripting.events.stage;
import funkin.game.Stage;
import haxe.xml.Access;
@@ -25,6 +25,10 @@ class CustomShader extends FunkinShader {
var fragCode = Assets.exists(fragShaderPath) ? Assets.getText(fragShaderPath) : null;
var vertCode = Assets.exists(vertShaderPath) ? Assets.getText(vertShaderPath) : null;
fileName = name;
fragFileName = fragShaderPath;
vertFileName = vertShaderPath;
path = fragShaderPath+vertShaderPath;
if (fragCode == null && vertCode == null)
+366 -202
View File
@@ -1,15 +1,17 @@
package funkin.backend.shaders;
import flixel.system.FlxAssets.FlxShader;
import flixel.util.FlxSignal.FlxTypedSignal;
import haxe.Exception;
import hscript.IHScriptCustomBehaviour;
import flixel.system.FlxAssets.FlxShader;
import lime.utils.Float32Array;
import openfl.display.ShaderParameterType;
import openfl.display.ShaderParameter;
import openfl.display.BitmapData;
import openfl.display.ShaderInput;
import openfl.display.ShaderParameter;
import openfl.display.ShaderParameterType;
import openfl.display3D._internal.GLProgram;
import openfl.display3D._internal.GLShader;
import openfl.utils._internal.Log;
using StringTools;
@:access(openfl.display3D.Context3D)
@@ -18,8 +20,20 @@ using StringTools;
@:access(openfl.display.ShaderParameter)
class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
private static var __instanceFields = Type.getInstanceFields(FunkinShader);
private static var FRAGMENT_SHADER = 0;
private static var VERTEX_SHADER = 1;
public var onGLUpdate:FlxTypedSignal<Void->Void> = new FlxTypedSignal<Void->Void>();
public var onProcessGLData:FlxTypedSignal<(String, String)->Void> = new FlxTypedSignal<(String, String)->Void>();
public var glslVer:String = "120";
public var fileName:String = "FunkinShader";
public var fragFileName:String = "FunkinShader";
public var vertFileName:String = "FunkinShader";
public var shaderPrefix:String = "";
public var fragmentPrefix:String = "";
public var vertexPrefix:String = "";
/**
* Creates a new shader from the specified fragment and vertex source.
@@ -38,11 +52,171 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
super();
}
static var IMPORT_REGEX = ~/#import\s+<(.*)>/;
private function processImports(value:String, type:Int):String
{
while(IMPORT_REGEX.match(value))
{
var importPath = IMPORT_REGEX.matched(1);
var importSource = Assets.getText("assets/shaders/" + importPath);
if(importSource == null) {
var fileName = type == FRAGMENT_SHADER ? fragFileName : vertFileName;
Logs.traceColored([
Logs.logText('[Shader] ', RED),
Logs.logText('Failed to import shader ${importPath} in ${fileName}', RED),
]);
} else {
value = value.replace(IMPORT_REGEX.matched(0), importSource);
}
}
return value;
}
static var ERROR_POS_REGEX = ~/(\d+):(\d+): (.*)/g;
static var ERROR_REGEX = ~/ERROR: (\d+):(\d+): (.*)/g;
static var ERROR_REGEX_2 = ~/(\d+)\((\d+)\) : error ([^:]+): (.*)/g;
@:noCompletion private override function __createGLShader(source:String, type:Int):GLShader
{
var gl = __context.gl;
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var shaderInfoLog = gl.getShaderInfoLog(shader);
var hasInfoLog = shaderInfoLog != null && StringTools.trim(shaderInfoLog) != "";
var compileStatus = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (hasInfoLog || compileStatus == 0)
{
var isVertexShader = type == gl.VERTEX_SHADER;
var messageBuf = new StringBuf();
messageBuf.add((compileStatus == 0) ? "Error" : "Info");
if(isVertexShader) {
messageBuf.add(" compiling vertex shader");
if(vertFileName != null && vertFileName.length > 0) {
messageBuf.add(" (" + vertFileName + ")");
}
} else {
messageBuf.add(" compiling fragment shader");
if(fragFileName != null && fragFileName.length > 0) {
messageBuf.add(" (" + fragFileName + ")");
}
}
messageBuf.add("\n");
var errorPositions = [];
var regex = null;
var tmp = shaderInfoLog;
if(shaderInfoLog.contains(" : error ")) {
regex = ERROR_REGEX_2;
while(regex.match(tmp)) {
errorPositions.push(new ShaderErrorPosition(regex.matched(2), regex.matched(1), regex.matched(4)));
tmp = regex.matchedRight();
}
} else if(shaderInfoLog.contains("ERROR: ")) {
regex = ERROR_REGEX;
while(regex.match(tmp)) {
errorPositions.push(new ShaderErrorPosition(regex.matched(2), regex.matched(1), regex.matched(3)));
tmp = regex.matchedRight();
}
} else {
regex = ERROR_POS_REGEX;
while(regex.match(tmp)) {
errorPositions.push(new ShaderErrorPosition(regex.matched(2), regex.matched(1), regex.matched(3)));
tmp = regex.matchedRight();
}
}
var splitSource = source.split("\n");
for(error in errorPositions) {
messageBuf.add("ERROR: Line: " + error.line);
if(error.column > 0) {
messageBuf.add(", Column: " + error.column);
}
messageBuf.add(", " + error.message);
if(error.line < splitSource.length) {
messageBuf.add("\nLine: ");
messageBuf.add(splitSource[error.line-1].trim());
}
messageBuf.add("\n\n");
}
var hasErrorPosition = errorPositions.length > 0;
if(hasErrorPosition) {
messageBuf.add("Raw shader info log:\n");
}
messageBuf.add(shaderInfoLog);
messageBuf.add("\n");
messageBuf.add(source);
var message = messageBuf.toString();
if (compileStatus == 0) Log.error(message);
else if (hasInfoLog) Log.debug(message);
}
return shader;
}
@:noCompletion private override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
{
var program:GLProgram = null;
try
{
var gl = __context.gl;
var vertexShader = __createGLShader(vertexSource, gl.VERTEX_SHADER);
var fragmentShader = __createGLShader(fragmentSource, gl.FRAGMENT_SHADER);
program = gl.createProgram();
// Fix support for drivers that don't draw if attribute 0 is disabled
for (param in __paramFloat)
{
if (param.name.indexOf("Position") > -1 && StringTools.startsWith(param.name, "openfl_"))
{
gl.bindAttribLocation(program, 0, param.name);
break;
}
}
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (gl.getProgramParameter(program, gl.LINK_STATUS) == 0)
{
var messageBuf = new StringBuf();
messageBuf.add("Unable to initialize the shader program");
messageBuf.add("\n");
messageBuf.add(gl.getProgramInfoLog(program));
var message = messageBuf.toString();
Log.error(message);
}
}
catch (error:Dynamic)
{
Logs.traceColored([
Logs.logText('[Shader] ', BLUE),
Logs.logText('Failed to compile shader ${fileName}: ', RED),
Logs.logText(Std.string(error))
], TRACE);
}
return program;
return program;
}
var glRawFragmentSource:String;
var glRawVertexSource:String;
@:noCompletion override private function set_glFragmentSource(value:String):String
{
if(value == null)
value = ShaderTemplates.defaultFragmentSource;
glRawFragmentSource = value;
value = value.replace("#pragma header", ShaderTemplates.fragHeader).replace("#pragma body", ShaderTemplates.fragBody);
value = processImports(value, FRAGMENT_SHADER);
if (value != __glFragmentSource)
{
__glSourceDirty = true;
@@ -55,7 +229,9 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
{
if(value == null)
value = ShaderTemplates.defaultVertexSource;
glRawVertexSource = value;
value = value.replace("#pragma header", ShaderTemplates.vertHeader).replace("#pragma body", ShaderTemplates.vertBody);
value = processImports(value, VERTEX_SHADER);
if (value != __glVertexSource)
{
__glSourceDirty = true;
@@ -64,6 +240,11 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
return __glVertexSource = value;
}
override function __updateGL():Void {
onGLUpdate.dispatch();
super.__updateGL();
}
@:noCompletion private override function __initGL():Void
{
if (__glSourceDirty || __paramBool == null)
@@ -83,23 +264,28 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
if (__context != null && program == null)
{
var prefix = '#version ${glslVer}\n';
var prefixBuf = new StringBuf();
prefixBuf.add('#version ${glslVer}\n');
prefixBuf.add(shaderPrefix);
var gl = __context.gl;
prefix += "#ifdef GL_ES
"
+ (precisionHint == FULL ? "#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif" : "precision lowp float;")
+ "
#endif
";
prefixBuf.add("#ifdef GL_ES\n");
if (precisionHint == FULL) {
prefixBuf.add("#ifdef GL_FRAGMENT_PRECISION_HIGH\n");
prefixBuf.add("precision highp float;\n");
prefixBuf.add("#else\n");
prefixBuf.add("precision mediump float;\n");
prefixBuf.add("#endif\n");
} else {
prefixBuf.add("precision lowp float;\n");
}
prefixBuf.add("#endif\n");
var vertex = prefix + glVertexSource;
var fragment = prefix + glFragmentSource;
var prefix = prefixBuf.toString();
var vertex = prefix + vertexPrefix + glVertexSource;
var fragment = prefix + fragmentPrefix + glFragmentSource;
var id = vertex + fragment;
@@ -110,9 +296,7 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
else
{
program = __context.createProgram(GLSL);
program.__glProgram = __createGLProgram(vertex, fragment);
__context.__programs.set(id, program);
}
@@ -120,56 +304,35 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
{
glProgram = program.__glProgram;
for (input in __inputBitmapData)
{
for (input in __inputBitmapData) {
if (input.__isUniform)
{
if (input.__isUniform) {
input.index = gl.getUniformLocation(glProgram, input.name);
}
else
{
} else {
input.index = gl.getAttribLocation(glProgram, input.name);
}
}
for (parameter in __paramBool)
{
if (parameter.__isUniform)
{
for (parameter in __paramBool) {
if (parameter.__isUniform) {
parameter.index = gl.getUniformLocation(glProgram, parameter.name);
}
else
{
} else {
parameter.index = gl.getAttribLocation(glProgram, parameter.name);
}
}
for (parameter in __paramFloat)
{
if (parameter.__isUniform)
{
for (parameter in __paramFloat) {
if (parameter.__isUniform) {
parameter.index = gl.getUniformLocation(glProgram, parameter.name);
}
else
{
} else {
parameter.index = gl.getAttribLocation(glProgram, parameter.name);
}
}
for (parameter in __paramInt)
{
if (parameter.__isUniform)
{
for (parameter in __paramInt) {
if (parameter.__isUniform) {
parameter.index = gl.getUniformLocation(glProgram, parameter.name);
}
else
{
} else {
parameter.index = gl.getAttribLocation(glProgram, parameter.name);
}
}
@@ -178,8 +341,12 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
}
}
var __cancelNextProcessGLData:Bool = false;
@:noCompletion private override function __processGLData(source:String, storageType:String):Void
{
onProcessGLData.dispatch(source, storageType);
if (__cancelNextProcessGLData != (__cancelNextProcessGLData = false))
return;
var lastMatch = 0, position, regex, name, type;
if (storageType == "uniform")
@@ -202,160 +369,146 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
}
var isUniform = (storageType == "uniform");
if (StringTools.startsWith(type, "sampler"))
{
var input = new ShaderInput<BitmapData>();
input.name = name;
@:privateAccess
input.__isUniform = isUniform;
__inputBitmapData.push(input);
switch (name)
{
case "openfl_Texture":
__texture = input;
case "bitmap":
__bitmap = input;
default:
}
Reflect.setField(__data, name, input);
try{Reflect.setField(this, name, input);} catch(e) {}
}
else if (!Reflect.hasField(__data, name) || Reflect.field(__data, name) == null)
{
var parameterType:ShaderParameterType = switch (type)
{
case "bool": BOOL;
case "double", "float": FLOAT;
case "int", "uint": INT;
case "bvec2": BOOL2;
case "bvec3": BOOL3;
case "bvec4": BOOL4;
case "ivec2", "uvec2": INT2;
case "ivec3", "uvec3": INT3;
case "ivec4", "uvec4": INT4;
case "vec2", "dvec2": FLOAT2;
case "vec3", "dvec3": FLOAT3;
case "vec4", "dvec4": FLOAT4;
case "mat2", "mat2x2": MATRIX2X2;
case "mat2x3": MATRIX2X3;
case "mat2x4": MATRIX2X4;
case "mat3x2": MATRIX3X2;
case "mat3", "mat3x3": MATRIX3X3;
case "mat3x4": MATRIX3X4;
case "mat4x2": MATRIX4X2;
case "mat4x3": MATRIX4X3;
case "mat4", "mat4x4": MATRIX4X4;
default: null;
}
var length = switch (parameterType)
{
case BOOL2, INT2, FLOAT2: 2;
case BOOL3, INT3, FLOAT3: 3;
case BOOL4, INT4, FLOAT4, MATRIX2X2: 4;
case MATRIX3X3: 9;
case MATRIX4X4: 16;
default: 1;
}
var arrayLength = switch (parameterType)
{
case MATRIX2X2: 2;
case MATRIX3X3: 3;
case MATRIX4X4: 4;
default: 1;
}
switch (parameterType)
{
case BOOL, BOOL2, BOOL3, BOOL4:
var parameter = new ShaderParameter<Bool>();
parameter.name = name;
@:privateAccess
parameter.type = parameterType;
@:privateAccess
parameter.__arrayLength = arrayLength;
@:privateAccess
parameter.__isBool = true;
@:privateAccess
parameter.__isUniform = isUniform;
@:privateAccess
parameter.__length = length;
__paramBool.push(parameter);
if (name == "openfl_HasColorTransform")
{
__hasColorTransform = parameter;
}
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
case INT, INT2, INT3, INT4:
var parameter = new ShaderParameter<Int>();
parameter.name = name;
@:privateAccess
parameter.type = parameterType;
@:privateAccess
parameter.__arrayLength = arrayLength;
@:privateAccess
parameter.__isInt = true;
@:privateAccess
parameter.__isUniform = isUniform;
@:privateAccess
parameter.__length = length;
@:privateAccess
__paramInt.push(parameter);
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
default:
var parameter = new ShaderParameter<Float>();
parameter.name = name;
@:privateAccess
parameter.type = parameterType;
@:privateAccess
parameter.__arrayLength = arrayLength;
#if lime
@:privateAccess
if (arrayLength > 0) parameter.__uniformMatrix = new Float32Array(arrayLength * arrayLength);
#end
@:privateAccess
parameter.__isFloat = true;
@:privateAccess
parameter.__isUniform = isUniform;
@:privateAccess
parameter.__length = length;
__paramFloat.push(parameter);
if (StringTools.startsWith(name, "openfl_"))
{
switch (name)
{
case "openfl_Alpha": __alpha = parameter;
case "openfl_ColorMultiplier": __colorMultiplier = parameter;
case "openfl_ColorOffset": __colorOffset = parameter;
case "openfl_Matrix": __matrix = parameter;
case "openfl_Position": __position = parameter;
case "openfl_TextureCoord": __textureCoord = parameter;
case "openfl_TextureSize": __textureSize = parameter;
default:
}
}
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
}
}
registerParameter(name, type, isUniform);
position = regex.matchedPos();
lastMatch = position.pos + position.len;
}
}
function registerParameter(name:String, type:String, isUniform:Bool):Void
{
if (StringTools.startsWith(type, "sampler"))
{
var input = new ShaderInput<BitmapData>();
input.name = name;
input.__isUniform = isUniform;
__inputBitmapData.push(input);
switch (name)
{
case "openfl_Texture":
__texture = input;
case "bitmap":
__bitmap = input;
default:
}
Reflect.setField(__data, name, input);
try{Reflect.setField(this, name, input);} catch(e) {}
}
else if (!Reflect.hasField(__data, name) || Reflect.field(__data, name) == null)
{
var parameterType:ShaderParameterType = switch (type)
{
case "bool": BOOL;
case "double", "float": FLOAT;
case "int", "uint": INT;
case "bvec2": BOOL2;
case "bvec3": BOOL3;
case "bvec4": BOOL4;
case "ivec2", "uvec2": INT2;
case "ivec3", "uvec3": INT3;
case "ivec4", "uvec4": INT4;
case "vec2", "dvec2": FLOAT2;
case "vec3", "dvec3": FLOAT3;
case "vec4", "dvec4": FLOAT4;
case "mat2", "mat2x2": MATRIX2X2;
case "mat2x3": MATRIX2X3;
case "mat2x4": MATRIX2X4;
case "mat3x2": MATRIX3X2;
case "mat3", "mat3x3": MATRIX3X3;
case "mat3x4": MATRIX3X4;
case "mat4x2": MATRIX4X2;
case "mat4x3": MATRIX4X3;
case "mat4", "mat4x4": MATRIX4X4;
default: null;
}
var length = switch (parameterType)
{
case BOOL2, INT2, FLOAT2: 2;
case BOOL3, INT3, FLOAT3: 3;
case BOOL4, INT4, FLOAT4, MATRIX2X2: 4;
case MATRIX3X3: 9;
case MATRIX4X4: 16;
default: 1;
}
var arrayLength = switch (parameterType)
{
case MATRIX2X2: 2;
case MATRIX3X3: 3;
case MATRIX4X4: 4;
default: 1;
}
switch (parameterType)
{
case BOOL, BOOL2, BOOL3, BOOL4:
var parameter = new ShaderParameter<Bool>();
parameter.name = name;
parameter.type = parameterType;
parameter.__arrayLength = arrayLength;
parameter.__isBool = true;
parameter.__isUniform = isUniform;
parameter.__length = length;
__paramBool.push(parameter);
if (name == "openfl_HasColorTransform")
{
__hasColorTransform = parameter;
}
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
case INT, INT2, INT3, INT4:
var parameter = new ShaderParameter<Int>();
parameter.name = name;
parameter.type = parameterType;
parameter.__arrayLength = arrayLength;
parameter.__isInt = true;
parameter.__isUniform = isUniform;
parameter.__length = length;
__paramInt.push(parameter);
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
default:
var parameter = new ShaderParameter<Float>();
parameter.name = name;
parameter.type = parameterType;
parameter.__arrayLength = arrayLength;
#if lime
if (arrayLength > 0) parameter.__uniformMatrix = new Float32Array(arrayLength * arrayLength);
#end
parameter.__isFloat = true;
parameter.__isUniform = isUniform;
parameter.__length = length;
__paramFloat.push(parameter);
if (StringTools.startsWith(name, "openfl_"))
{
switch (name)
{
case "openfl_Alpha": __alpha = parameter;
case "openfl_ColorMultiplier": __colorMultiplier = parameter;
case "openfl_ColorOffset": __colorOffset = parameter;
case "openfl_Matrix": __matrix = parameter;
case "openfl_Position": __position = parameter;
case "openfl_TextureCoord": __textureCoord = parameter;
case "openfl_TextureSize": __textureSize = parameter;
default:
}
}
Reflect.setField(__data, name, parameter);
try{Reflect.setField(this, name, parameter);} catch(e) {}
}
}
}
public function hget(name:String):Dynamic {
if (__instanceFields.contains(name) || __instanceFields.contains('get_${name}'))
return Reflect.getProperty(this, name);
@@ -394,7 +547,6 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
var cl:String = Type.getClassName(Type.getClass(field));
// cant do "field is ShaderInput" for some reason
if (cl.startsWith("openfl.display.ShaderParameter")) {
@:privateAccess
if (field.__length <= 1) {
if ((val is Array))
return field.value = val;
@@ -546,4 +698,16 @@ class ShaderTypeException extends Exception {
this.name = name;
super('ShaderTypeException - Tried to set the shader uniform "${name}" as a ${Type.getClassName(has)}, but the shader uniform is a ${Std.string(want)}.');
}
}
class ShaderErrorPosition {
public var column:Int;
public var line:Int;
public var message:String;
public function new(line:String, column:String, message:String) {
this.line = Std.parseInt(line);
this.column = Std.parseInt(column);
this.message = message;
}
}
@@ -0,0 +1,111 @@
package funkin.backend.shaders;
import hscript.IHScriptCustomBehaviour;
import funkin.backend.scripting.events.shader.ShaderProcessEvent;
import funkin.backend.scripting.ScriptPack;
import funkin.backend.scripting.Script;
@:structInit
class FieldInfo {
public var get:Void->Dynamic;
public var set:Dynamic->Dynamic;
}
@:access(funkin.backend.shaders.FunkinShader)
class ScriptableShader extends FlxBasic implements IHScriptCustomBehaviour {
private static var __instanceFields = Type.getInstanceFields(ScriptableShader);
public var shader:FunkinShader;
public var script:Script;
var fields:Map<String, FieldInfo> = [];
public function new(shader:FunkinShader, ?scriptName:String, ?parentScriptPack:ScriptPack) {
super();
if(parentScriptPack == null && PlayState.instance != null)
parentScriptPack = PlayState.instance.scripts;
if(scriptName == null)
if((shader is CustomShader)) scriptName = cast(shader, CustomShader).fileName;
else throw "Missing name for shader script, please provide a scriptName, or use CustomShader";
script = Script.create(Paths.script('shaders/$scriptName'));
script.setParent(shader);
script.set("shader", shader);
script.set("registerParameter", shader.registerParameter);
script.set("registerField", (name:String, get: Void->Dynamic, set: Dynamic->Dynamic) -> {
registerField(name, get, set);
});
script.set("registerShaderField", (name:String, shaderField:String) -> {
var get = () -> shader.hget(shaderField);
var set = (value:Dynamic) -> {
var val = shader.hset(shaderField, value);
script.set(name, val);
return val;
};
registerField(name, get, set);
var gg = script.get(name);
if(gg != null) {
shader.hset(shaderField, gg);
}
});
if(parentScriptPack != null)
script.setPublicMap(parentScriptPack.publicVariables);
script.load();
script.call("create");
shader.onGLUpdate.add(updateGL);
shader.onProcessGLData.add(processGLData);
}
function registerField(name:String, get: Void->Dynamic, set: Dynamic->Dynamic) {
fields.set(name, {
get: get,
set: set
});
script.set("get_"+name, get);
script.set("set_"+name, set);
}
public override function update(elapsed:Float) script.call("update", [elapsed]);
public override function draw() script.call("draw", []);
public function updateGL() script.call("updateGL", []);
public function processGLData(source:String, storageType:String) {
var event = EventManager.get(ShaderProcessEvent).recycle(source, storageType);
script.call("processGLData", [event]);
if(event.cancelled)
shader.__cancelNextProcessGLData = true;
script.call("processGLDataPost", [event]);
}
public override function destroy() {
shader.onGLUpdate.remove(updateGL);
shader.onProcessGLData.remove(processGLData);
if(script != null) {
script.call("destroy");
script.destroy();
}
fields = null;
super.destroy();
}
public function hget(name:String):Dynamic {
if(fields.exists(name))
return fields.get(name).get();
if (__instanceFields.contains(name) || __instanceFields.contains('get_${name}'))
return Reflect.getProperty(this, name);
return shader.hget(name);
}
public function hset(name:String, val:Dynamic):Dynamic {
if (fields.exists(name)) {
return fields.get(name).set(val);
}
if (__instanceFields.contains(name) || __instanceFields.contains('set_${name}')) {
Reflect.setProperty(this, name, val);
return val;
}
return shader.hset(name, val);
}
}
+2 -2
View File
@@ -45,7 +45,7 @@ class Conductor
/**
* Number of steps per beat (bottom number in time signature). Defaults to 4.
*/
public static var stepsPerBeat:Float = 4;
public static var stepsPerBeat:Int = 4;
/**
@@ -262,7 +262,7 @@ class Conductor
}
}
public static function changeBPM(newBpm:Float, beatsPerMeasure:Float = 4, stepsPerBeat:Float = 4)
public static function changeBPM(newBpm:Float, beatsPerMeasure:Float = 4, stepsPerBeat:Int = 4)
{
bpm = newBpm;
+121 -2
View File
@@ -2,7 +2,10 @@ package funkin.backend.system;
import lime.utils.AssetLibrary;
import lime.utils.AssetType;
import flixel.util.FlxColor;
import lime.app.Application;
import funkin.backend.system.macros.GitCommitMacro;
import funkin.backend.assets.IModsAssetLibrary;
import funkin.backend.assets.ScriptedAssetLibrary;
@@ -11,13 +14,129 @@ import funkin.backend.assets.ScriptedAssetLibrary;
*/
@:build(funkin.backend.system.macros.FlagMacro.build())
class Flags {
// -- Codename's Default Flags --
public static var COMMIT_NUMBER:Int = GitCommitMacro.commitNumber;
public static var COMMIT_HASH:String = GitCommitMacro.commitHash;
public static var COMMIT_MESSAGE:String = 'Commit $COMMIT_NUMBER ($COMMIT_HASH)';
@:lazy public static var VERSION_MESSAGE:String = 'Codename Engine v${Application.current.meta.get('version')}';
public static var REPO_NAME:String = "CodenameEngine";
public static var REPO_OWNER:String = "FNF-CNE-Devs";
public static var REPO_URL:String = 'https://github.com/$REPO_OWNER/$REPO_NAME';
// make this empty once you guys are done with the project.
// good luck /gen <3 @crowplexus
public static var RELEASE_CYCLE:String = "Beta";
/**
* Preferred sound extension for the game's audio files.
* Currently is set to `mp3` for web targets, and `ogg` for other targets.
*/
public static var SOUND_EXT:String = #if web "mp3" #else "ogg" #end; // we also support wav
public static var VIDEO_EXT:String = "mp4";
public static var IMAGE_EXT:String = "png"; // we also support jpg
public static var DEFAULT_DISCORD_LOGO_KEY:String = "icon";
public static var DEFAULT_DISCORD_CLIENT_ID:String = "1027994136193810442";
public static var DEFAULT_DISCORD_LOGO_TEXT:String = "Codename Engine";
@:also(funkin.game.Character.FALLBACK_CHARACTER)
public static var DEFAULT_CHARACTER:String = "bf";
public static var DEFAULT_GIRLFRIEND:String = "gf";
public static var DEFAULT_OPPONENT:String = "dad";
@:also(funkin.game.PlayState.difficulty)
public static var DEFAULT_DIFFICULTY:String = "normal";
public static var DEFAULT_STAGE:String = "stage";
public static var DEFAULT_SCROLL_SPEED:Float = 2.0;
public static var DEFAULT_HEALTH_ICON:String = "face";
public static var SONGS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override";
public static var WEEKS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override";
public static var CHARTER_ADVANCED_SETTINGS:Bool = false;
public static var DEFAULT_BPM:Float = 100.0;
public static var DEFAULT_BEATS_PER_MEASURE:Float = 4;
public static var DEFAULT_STEPS_PER_BEAT:Int = 4;
// Internal stuff
public static var SUPPORTED_CHART_RUNTIME_FORMATS:Array<String> = ["Legacy", "Psych Engine"];
public static var SUPPORTED_CHART_FORMATS:Array<String> = ["BaseGame"];
public static var BASEGAME_SONG_METADATA_VERSION:String = "2.2.2";
public static var BASEGAME_SONG_CHART_DATA_VERSION:String = "2.0.0";
public static var BASEGAME_DEFAULT_NOTE_STYLE:String = 'funkin';
public static var BASEGAME_DEFAULT_ALBUM_ID:String = 'volume1';
public static var BASEGAME_DEFAULT_PREVIEW_START:Float = 0;
public static var BASEGAME_DEFAULT_PREVIEW_END:Float = 15000;
/**
* Default background colors for songs without bg color
*/
public static var DEFAULT_COLOR:FlxColor = 0xFF9271FD;
public static var DEFAULT_COOP_ALLOWED:Bool = false;
public static var DEFAULT_OPPONENT_MODE_ALLOWED:Bool = false;
@:also(funkin.game.PlayState.coopMode)
public static var DEFAULT_COOP_MODE:Bool = false; // used in playstate if it doesnt find it
@:also(funkin.game.PlayState.opponentMode)
public static var DEFAULT_OPPONENT_MODE:Bool = false;
public static var DEFAULT_NOTE_MS_LIMIT:Float = 1500;
public static var DEFAULT_NOTE_SCALE:Float = 0.7;
@:also(funkin.game.Character.FALLBACK_DEAD_CHARACTER)
public static var DEFAULT_GAMEOVER_CHARACTER:String = "bf-dead";
public static var DEFAULT_GAMEOVER_SONG:String = "gameOver";
public static var DEFAULT_GAMEOVER_LOSS_SFX:String = "gameOverSFX";
public static var DEFAULT_GAMEOVER_RETRY_SFX:String = "gameOverEnd";
public static var DEFAULT_CAM_ZOOM_INTERVAL:Int = 4;
public static var DEFAULT_CAM_ZOOM_STRENGTH:Int = 1;
public static var DEFAULT_CAM_ZOOM:Float = 1.05; // what zoom level it defaults to
public static var DEFAULT_HUD_ZOOM:Float = 1.0;
public static var MAX_CAMERA_ZOOM:Float = 1.35;
public static var DEFAULT_PAUSE_ITEMS:Array<String> = ['Resume', 'Restart Song', 'Change Controls', 'Change Options', 'Exit to menu', "Exit to charter"];
public static var DEFAULT_CUTSCENE_PAUSE_ITEMS:Array<String> = ['Resume Cutscene', 'Skip Cutscene', 'Restart Cutscene', 'Exit to menu'];
public static var DEFAULT_GITAROO:Bool = true;
public static var GITAROO_CHANCE:Float = 0.1;
public static var DEFAULT_CAN_ACCESS_DEBUG_MENUS:Bool = true;
public static var DEFAULT_MUTE_VOCALS_ON_MISS:Bool = true;
public static var DEFAULT_MAX_HEALTH:Float = 2.0;
public static var DEFAULT_HEALTH:Null<Float> = null;//DEFAULT_MAX_HEALTH / 2.0;
public static var BOP_ICON_SCALE:Float = 1.2;
public static var ICON_OFFSET:Float = 26;
public static var ICON_LERP:Float = 0.33;
public static var CAM_BOP_STRENGTH:Float = 0.015;
public static var HUD_BOP_STRENGTH:Float = 0.03;
public static var DEFAULT_CAM_ZOOM_LERP:Float = 0.05;
public static var DEFAULT_HUD_ZOOM_LERP:Float = 0.05;
public static var MAX_SPLASHES:Int = 8;
public static var STUNNED_TIME:Float = 5 / 60;
@:also(funkin.game.PlayState.daPixelZoom)
public static var PIXEL_ART_SCALE:Float = 6.0;
public static var DEFAULT_COMBO_GROUP_MAX_SIZE:Int = 25;
public static var DEFAULT_STRUM_AMOUNT:Int = 4;
public static var DEFAULT_CAMERA_FOLLOW_SPEED:Float = 0.04;
public static var VOCAL_OFFSET_VIOLATION_THRESHOLD:Float = 25;
// Usage: Codename Credits
public static var MAIN_DEVS_COLOR:FlxColor = 0xFF9C35D5;
public static var MIN_CONTRIBUTIONS_COLOR:FlxColor = 0xFFB4A7DA;
public static var UNDO_PREFIX:String = "* ";
public static var JSON_PRETTY_PRINT:String = "\t";
// -- End of Codename's Default Flags --
/**
* Flags that Codename couldn't recognize as it's own defaults (they can only be `string`!).
*/
@:bypass public static var customFlags:Map<String, String> = [];
public static function load() {
+66 -24
View File
@@ -11,8 +11,7 @@ final class Logs {
public static var nativeTrace = Log.trace;
public static function init() {
Log.trace = function(v : Dynamic, ?infos : Null<haxe.PosInfos>) {
Log.trace = function(v:Dynamic, ?infos:Null<haxe.PosInfos>) {
var data = [
logText('${infos.fileName}:${infos.lineNumber}: ', CYAN),
logText(Std.string(v))
@@ -28,22 +27,48 @@ final class Logs {
__showInConsole(prepareColoredTrace(data, TRACE));
};
LogFrontEnd.onLogs = function(Data, Style, FireOnce) {
LogFrontEnd.onLogs = function(data, style, fireOnce) {
var prefix = "[FLIXEL]";
var color:ConsoleColor = LIGHTGRAY;
var level:Level = INFO;
if (Style == LogStyle.CONSOLE) {prefix = "> "; color = WHITE; level = INFO; } else
if (Style == LogStyle.ERROR) {prefix = "[FLIXEL]"; color = RED; level = ERROR; } else
if (Style == LogStyle.NORMAL) {prefix = "[FLIXEL]"; color = WHITE; level = INFO; } else
if (Style == LogStyle.NOTICE) {prefix = "[FLIXEL]"; color = GREEN; level = VERBOSE;} else
if (Style == LogStyle.WARNING) {prefix = "[FLIXEL]"; color = YELLOW; level = WARNING;}
if (style == LogStyle.CONSOLE) // cant place a switch here as these arent inline values - Nex
{
prefix = "> ";
color = WHITE;
level = INFO;
}
else if (style == LogStyle.ERROR)
{
prefix = "[FLIXEL]";
color = RED;
level = ERROR;
}
else if (style == LogStyle.NORMAL)
{
prefix = "[FLIXEL]";
color = WHITE;
level = INFO;
}
else if (style == LogStyle.NOTICE)
{
prefix = "[FLIXEL]";
color = GREEN;
level = VERBOSE;
}
else if (style == LogStyle.WARNING)
{
prefix = "[FLIXEL]";
color = YELLOW;
level = WARNING;
}
var d:Dynamic = Data;
var d:Dynamic = data;
if (!(d is Array))
d = [d];
var a:Array<Dynamic> = d;
var strs = [for(e in a) Std.string(e)];
for(e in strs) {
for(e in strs)
{
Logs.trace('$prefix $e', level, color);
}
};
@@ -55,12 +80,18 @@ final class Logs {
logText('[ '),
logText('${Std.string(time.getHours()).addZeros(2)}:${Std.string(time.getMinutes()).addZeros(2)}:${Std.string(time.getSeconds()).addZeros(2)}', DARKMAGENTA),
logText(' |'),
switch(level) {
case WARNING: logText(' WARNING ', DARKYELLOW);
case ERROR: logText(' ERROR ', DARKRED);
case TRACE: logText(' TRACE ', GRAY);
case VERBOSE: logText(' VERBOSE ', DARKMAGENTA);
default: logText(' INFORMATION ', CYAN);
switch (level)
{
case WARNING:
logText(' WARNING ', DARKYELLOW);
case ERROR:
logText(' ERROR ', DARKRED);
case TRACE:
logText(' TRACE ', GRAY);
case VERBOSE:
logText(' VERBOSE ', DARKMAGENTA);
default:
logText(' INFORMATION ', CYAN);
},
logText('] ')
];
@@ -77,7 +108,7 @@ final class Logs {
}
public static function __showInConsole(text:Array<LogText>) {
#if sys
#if (sys && !mobile)
while(__showing) {
Sys.sleep(0.05);
}
@@ -89,20 +120,30 @@ final class Logs {
NativeAPI.setConsoleColors();
Sys.print("\r\n");
__showing = false;
#elseif mobile
while(__showing) {
Sys.sleep(0.05);
}
__showing = true;
@:privateAccess
Sys.print([for(t in text) t.text].join(""));
__showing = false;
#else
@:privateAccess
nativeTrace([for(t in text) t.text].join(""));
#end
}
public static function traceColored(text:Array<LogText>, level:Level = INFO) {
public static function traceColored(text:Array<LogText>, level:Level = INFO)
__showInConsole(prepareColoredTrace(text, level));
}
public static function trace(text:String, level:Level = INFO, color:ConsoleColor = LIGHTGRAY) {
traceColored([{
text: text,
color: color
}], level);
traceColored([
{
text: text,
color: color
}
], level);
}
}
@@ -113,7 +154,8 @@ enum abstract Level(Int) {
var TRACE = 3;
var VERBOSE = 4;
}
typedef LogText = {
var text:String;
var color:ConsoleColor;
}
}
+19 -11
View File
@@ -30,10 +30,6 @@ import android.os.Build;
class Main extends Sprite
{
// make this empty once you guys are done with the project.
// good luck /gen <3 @crowplexus
public static final releaseCycle:String = "Beta";
public static var instance:Main;
public static var modToLoad:String = null;
@@ -123,6 +119,9 @@ class Main extends Sprite
ShaderResizeFix.init();
Logs.init();
Paths.init();
hscript.Interp.importRedirects = funkin.backend.scripting.Script.getDefaultImportRedirects();
#if GLOBAL_SCRIPT
funkin.backend.scripting.GlobalScript.init();
#end
@@ -145,7 +144,7 @@ class Main extends Sprite
Assets.registerLibrary('default', lib);
funkin.options.PlayerSettings.init();
funkin.savedata.FunkinSave.init();
funkin.backend.utils.FunkinSave.init();
Options.load();
FlxG.fixedTimestep = false;
@@ -171,13 +170,22 @@ class Main extends Sprite
initTransition();
}
public static function refreshAssets() {
FlxSoundTray.volumeChangeSFX = Paths.sound('menu/volume');
FlxSoundTray.volumeUpChangeSFX = null;
FlxSoundTray.volumeDownChangeSFX = null;
public static function refreshAssets() @:privateAccess {
WindowUtils.resetTitle();
if (FlxG.game.soundTray != null)
FlxG.game.soundTray.text.setTextFormat(new TextFormat(Paths.font("vcr.ttf")));
var game = FlxG.game;
var daSndTray = Type.createInstance(game._customSoundTray = funkin.menus.ui.FunkinSoundTray, []);
var index:Int = game.numChildren - 1;
if(game.soundTray != null)
{
var newIndex:Int = game.getChildIndex(game.soundTray);
if(newIndex != -1) index = newIndex;
game.removeChild(game.soundTray);
game.soundTray.__cleanup();
}
game.addChildAt(game.soundTray = daSndTray, index);
}
public static function initTransition() {
+7 -3
View File
@@ -18,8 +18,10 @@ class MainState extends FlxState {
public static var betaWarningShown:Bool = false;
public override function create() {
super.create();
if (!initiated)
if (!initiated) {
Main.loadGameSettings();
}
initiated = true;
#if sys
@@ -55,9 +57,9 @@ class MainState extends FlxState {
}
#end
Main.refreshAssets();
ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder);
Flags.load();
ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder); // Loads global.hx
Main.refreshAssets();
DiscordUtil.init();
EventsData.reloadEvents();
ControlsUtil.loadCustomControls();
@@ -73,6 +75,8 @@ class MainState extends FlxState {
betaWarningShown = true;
}
#if sys
CoolUtil.safeAddAttributes('./.temp/', NativeAPI.FileAttribute.HIDDEN);
#end
}
}
@@ -13,5 +13,5 @@ class CodenameBuildField extends TextField {
}
public function reload()
text = 'Codename Engine ${Main.releaseCycle}\nCommit ${GitCommitMacro.commitNumber} (${GitCommitMacro.commitHash})';
text = 'Codename Engine ${Flags.RELEASE_CYCLE}\n${Flags.COMMIT_MESSAGE}';
}
@@ -1,5 +1,7 @@
package funkin.backend.system.framerate;
import funkin.backend.scripting.ModState;
class FlixelInfo extends FramerateCategory {
public function new() {
super("Flixel Info");
@@ -14,7 +16,12 @@ class FlixelInfo extends FramerateCategory {
for(_ in FlxG.bitmap._cache.keys())
c++;
_text = 'State: ${Type.getClassName(Type.getClass(FlxG.state))}';
if((FlxG.state is ModState)) {
var state:ModState = cast FlxG.state;
_text = "Mod State: " + state.scriptName;
} else {
_text = 'State: ${Type.getClassName(Type.getClass(FlxG.state))}';
}
_text += '\nObject Count: ${FlxG.state.members.length}';
_text += '\nCamera Count: ${FlxG.cameras.list.length}';
_text += '\nBitmaps Count: ${c}';
@@ -22,9 +29,9 @@ class FlixelInfo extends FramerateCategory {
_text += '\nFlxG.game Childs Count: ${FlxG.game.numChildren}';
// _text += '\nCached objects count: ${cachedObjects}';
#if FLX_POINT_POOL
var points = flixel.math.FlxPoint.FlxBasePoint.pool;
//var points = flixel.math.FlxPoint.FlxBasePoint.pool;
//_text += '\nPoint Count: ${points._count} | +${points.made} | -${points.gotten} | ${points.balance} | >${points.putted}';
_text += '\nPoint Count: ${points._count}';
//_text += '\nPoint Count: ${points._count}';
#end
}
@@ -4,6 +4,7 @@ import haxe.Exception;
#if GITHUB_API
import haxe.Json;
#end
import haxe.Exception;
// TODO: Document further and perhaps make this a Haxelib.
class GitHub {
@@ -82,7 +83,7 @@ class GitHub {
* @param onError Error Callback
* @return Members List
*/
public static function getOrganizationMembers(org:String, ?onError:Exception->Void):Array<GitHubContributor> {
public static function getOrganizationMembers(org:String, ?onError:Exception->Void):Array<GitHubContributor> {
#if GITHUB_API
try {
var data = Json.parse(HttpUtil.requestText('https://api.github.com/orgs/$org/members'));
@@ -105,7 +106,7 @@ class GitHub {
* @param onError Error Callback
* @return User/Organization
*/
public static function getUser(user:String, ?onError:Exception->Void):GitHubUser {
public static function getUser(user:String, ?onError:Exception->Void):GitHubUser {
#if GITHUB_API
try {
var url = 'https://api.github.com/users/$user';
@@ -6,13 +6,21 @@ using StringTools;
class BuildInfo {
public static function printBuildInfo() {
if(haxe.macro.Context.defined("display")) return;
var haxeVersion = haxe.macro.Context.definedValue("haxe");
Sys.println('--- BUILD INFO ---');
var haxeVersion = haxe.macro.Context.definedValue("haxe").trim();
Sys.println('[ BUILD INFO ]');
Sys.println('Haxe Version: ${haxeVersion}');
try {
var lastBuiltWith:Null<String> = null;
var compiling = #if final "final" #elseif debug "debug" #else "release" #end;
var target = #if windows "windows" #elseif mac "macos" #elseif linux "linux" #elseif android "android" #elseif ios "ios" #elseif html5 "html5" #else "" #end;
var compiling = #if final "release" #elseif debug "debug" #else "release" #end;
var target = #if hl "hl"
#elseif html5 "html5"
#elseif ios "ios"
#elseif android "android"
#elseif windows "windows"
#elseif (mac || macos) "macos"
#elseif linux "linux"
#else ""
#end;
if(target == "") throw "Unknown target";
var exportPath = Sys.getCwd() + "/export/" + compiling + "/" + target + "/";
exportPath += "obj/Options.txt";
@@ -20,24 +28,27 @@ class BuildInfo {
var options = sys.io.File.getContent(exportPath);
for(option in options.split("\n")) {
if(option.startsWith("haxe=")) {
lastBuiltWith = option.substr(5);
lastBuiltWith = option.substr(5).trim();
break;
}
}
if(lastBuiltWith != null && lastBuiltWith != haxeVersion)
if(lastBuiltWith != null && lastBuiltWith.length > 0 && lastBuiltWith != haxeVersion)
Sys.println('Last Built With Haxe: ${lastBuiltWith} [!!!! MAKE SURE IF YOU SWITCHED VERSIONS YOU DELETE EXPORT FOLDERS !!!!]');
} catch(e) {}
Sys.println('Target Platform: ${
#if windows "Windows"
#elseif mac "Mac"
#elseif linux "Linux"
#elseif android "Android"
var targetPlatform = #if html5 "Web (HTML5)"
#elseif ios "iOS"
#elseif html5 "HTML5"
#elseif android "Android"
#elseif windows "Windows"
#elseif (mac || macos) "Mac"
#elseif linux "Linux"
#else "Unknown"
#end
}');
#end;
if(haxe.macro.Context.defined("hl")) {
targetPlatform = "Hashlink (" + targetPlatform + ")";
}
Sys.println('Target Platform: ' + targetPlatform);
Sys.println('Build Date: ${Date.now().toString()}');
Sys.println('');
}
@@ -7,7 +7,18 @@ using StringTools;
using haxe.macro.Tools;
using funkin.backend.system.macros.FlagMacro;
class FlagMacro {
private static function hasMeta(meta:Array<MetadataEntry>, name:String):Bool {
for(m in meta) {
if(m.name == name) {
return true;
}
}
return false;
}
public static function build():Array<Field> {
var fields = Context.getBuildFields();
@@ -21,26 +32,50 @@ class FlagMacro {
var parserExprs:Array<Expr> = [];
for (field in fields) {
var skip = false;
for(meta in field.meta) {
if(meta.name == ":bypass") {
skip = true;
break;
}
}
var skip = field.meta.hasMeta(":bypass");
var hasLazy = field.meta.hasMeta(":lazy");
if(skip) continue;
switch (field.kind) {
case FVar(type, expr):
var isNullable = false;
switch(type) {
case TPath({name: "Null", pack: [], params: [TPType(p)]}):
type = p;
isNullable = true;
default:
}
var alsos:Array<Expr> = [];
for(meta in field.meta) {
if(meta.name == ":also") {
for(param in meta.params) {
switch(param.expr) {
case EField(_, _):
alsos.push(param);
default:
Context.error("Invalid :also parameter", param.pos);
}
}
}
}
if(expr == null)
Context.error('Flag ' + field.name + ' must have a default value', field.pos);
switch(expr.expr) {
case EConst(CIdent("true")) | EConst(CIdent("false")):
type = macro: Bool;
default:
if(type == null)
if(type == null) {
switch(expr.expr) {
case EConst(CIdent("true")) | EConst(CIdent("false")):
type = macro: Bool;
case EConst(CInt(_)):
type = macro: Int;
case EConst(CFloat(_)):
type = macro: Float;
case EConst(CString(_)):
type = macro: String;
default:
Context.error('Flag ' + field.name + ' must have a type', field.pos);
}
}
var parser:Expr = null;
@@ -52,7 +87,7 @@ class FlagMacro {
parser = macro value.split(",").map((e) -> e.trim());
case macro: Array<String>:
parser = macro value.split(",");
case macro: Array<Int>:
case (macro: Array<Int>) | (macro: Array<FlxColor>):
parser = macro value.split(",").map((e) -> Std.parseInt(e));
case macro: Array<Float>:
parser = macro value.split(",").map((e) -> Std.parseFloat(e));
@@ -61,12 +96,15 @@ class FlagMacro {
e = e.trim();
e == "true" || e == "t" || e == "1";
});
case macro: Int:
case macro: TrimmedString:
field.kind = FVar(macro: String, expr);
parser = macro value.trim();
case macro: String:
parser = macro value;
case (macro: Int) | (macro: FlxColor):
parser = macro Std.parseInt(value);
case macro: Float:
parser = macro Std.parseFloat(value);
case macro: String:
parser = macro value;
case macro: Bool:
parser = macro value == "true" || value == "t" || value == "1";
case TPath({name: "Allow", pack: [], params: params}):
@@ -128,8 +166,7 @@ class FlagMacro {
if(chosenType == STRING) {
parser = macro value;
field.kind = FVar(macro: String, expr);
}
else if(chosenType == INT) {
} else if(chosenType == INT) {
parser = macro Std.parseInt(value);
field.kind = FVar(macro: Int, expr);
} else {
@@ -147,7 +184,7 @@ class FlagMacro {
}
case TPath({name: "Array", pack: []}):
Context.error("Flag " + field.name + " cannot be an Array that isnt a String or Bool or TrimmedString", field.pos);
Context.error("Flag " + field.name + " cannot be an Array that isnt a String or Bool or TrimmedString or Int or Float", field.pos);
case TPath({name: "Map", pack: []}):
Context.error("Flag " + field.name + " cannot be a Map<K, V>", field.pos);
default:
@@ -159,6 +196,14 @@ class FlagMacro {
continue;
}
if(isNullable) {
switch(field.kind) {
case FVar(t, e):
field.kind = FVar(TPath({name: "Null", pack: [], params: [TPType(t)]}), e);
default:
}
}
resetExprs.push(macro $i{field.name} = ${expr});
// parse(name: String, value: String)
@@ -166,16 +211,39 @@ class FlagMacro {
if(customCheck != null) {
parserExprs.push(customCheck);
} else {
parserExprs.push(macro @:mergeBlock {
if(name == $v{field.name}) {
$i{field.name} = $parser;
return true;
}
});
var alsoExpr = alsos.length > 0 ? macro @:mergeBlock $b{alsos.map((e) -> return macro $e = val)} : macro {};
if(isNullable) {
parserExprs.push(macro @:mergeBlock {
if(name == $v{field.name}) {
var val = value == "NULL" ? null : $parser;
$i{field.name} = val;
$alsoExpr;
return true;
}
});
} else {
parserExprs.push(macro @:mergeBlock {
if(name == $v{field.name}) {
var val = $parser;
$i{field.name} = val;
$alsoExpr;
return true;
}
});
}
}
default:
// nothing
}
if(hasLazy) {
switch(field.kind) {
case FVar(t, expr):
field.kind = FVar(t, null);
default:
}
}
}
fields.push({
@@ -209,6 +277,9 @@ class FlagMacro {
});
//var printer = new haxe.macro.Printer();
//for(field in fields) {
// trace(printer.printField(field));
//}
//trace(printer.printField(fields[fields.length - 2]));
//trace(printer.printField(fields[fields.length - 1]));
@@ -42,26 +42,12 @@ class HashLinkFixer {
var cl = clRef.get();
if (cl.isAbstract || cl.isExtern || cl.isInterface) return fields;
if (!cl.name.endsWith("_Impl_") && !cl.name.endsWith("_HSX") && !cl.name.endsWith("_HSC") && !cl.name.endsWith("_HLFHelper")) {
var metas = cl.meta.get();
if (!cl.name.endsWith("_Impl_") && !cl.name.endsWith("_HSX") && !cl.name.endsWith("_HSC")) {
if(cl.params.length > 0)
return fields;
if(cl.module == "EReg") return fields; // private typedef in same class
if(cl.module == "hl.Format") return fields; // enum in same class
var definedFields = [];
var helperClass = macro class {
};
helperClass.pos = cl.pos;
var module = cl.module + "_HLFHelper";
var hcClassName = cl.name + "_HLFHelper";
for(f in fields.copy()) {
if (f == null)
continue;
@@ -70,10 +56,12 @@ class HashLinkFixer {
if(definedFields.contains(f.name)) continue; // no duplicate fields
var hlNativeMeta = null;
var hasHlNative = false;
for(m in f.meta)
if (m.name == ":hlNative") {
hasHlNative = true;
hlNativeMeta = m;
break;
}
@@ -92,16 +80,39 @@ class HashLinkFixer {
var overrideExpr:Expr;
var returns:Bool = !fun.ret.match(TPath({name: "Void"}));
var printer = new haxe.macro.Printer();
if(cl.module == "hl.Gc" && fun.ret == null) returns = false; // fix since they dont explicitly set :Void
if(cl.module == "hl.Format" && fun.ret == null) returns = false; // fix since they dont explicitly set :Void
var name = 'hlf_${f.name}';
var arguments = fun.args == null ? [] : [for(a in fun.args) macro $i{a.name}];
var funcExpr:Expr = returns ? {
//macro return $i{name}($a{arguments});
macro return @:privateAccess $i{hcClassName}.$name($a{arguments});
} : {
macro @:privateAccess $i{hcClassName}.$name($a{arguments});
};
var funcExpr:Expr = macro @:privateAccess $i{name}($a{arguments});
if(returns) funcExpr = macro return $funcExpr;
var cleanMeta = f.meta.copy().filter(function(m) return m.name != ":hlNative");
var hasBareMeta = hlNativeMeta.params.length == 0;
var meta = f.meta.copy();
switch hlNativeMeta {
case {params: []}:
meta = [{name: ":hlNative", params: [macro "std", macro $v{f.name}], pos: Context.currentPos()}].concat(cleanMeta);
case {params: [_.expr => EConst(CString(name))]}:
meta = [{name: ":hlNative", params: [macro "std", macro $v{name}], pos: Context.currentPos()}].concat(cleanMeta);
case {params: [_.expr => EConst(CFloat(version))]}:
var curVersion = Context.definedValue("hl_ver");
if(curVersion == null) curVersion = "";
if(version > curVersion) {
meta = cleanMeta;
if(f.meta.filter((m) -> m.name == ":noExpr").length > 0)
Context.error("Missing function body", f.pos);
funcExpr = fun.expr; // restore to default behaviour
} else {
meta = [{name: ":hlNative", params: [macro "std", macro $v{f.name}], pos: Context.currentPos()}].concat(cleanMeta);
}
default:
}
var fiel:Field = {
name: name,
@@ -109,50 +120,32 @@ class HashLinkFixer {
kind: FFun({
ret: fun.ret,
params: fun.params.copy(),
expr: fun.expr,
expr: funcExpr,
args: fun.args.copy()
}),
access: f.access.copy(),
meta: f.meta.copy()
access: f.access.copy().filter(function(a) return a != APublic && a != APrivate).concat([APrivate]),
meta: meta
};
helperClass.fields.push(fiel);
fields.push(fiel);
definedFields.push(f.name);
// Remove meta from original function
for(m in f.meta.copy())
if (m.name == ":hlNative") {
f.meta.remove(m);
}
fun.expr = funcExpr;
default:
}
}
helperClass.pack = cl.pack.copy();
helperClass.pos = cl.pos;
helperClass.name = hcClassName;
if(definedFields.length > 0) {
/*if(definedFields.length > 0) {
trace(cl.module);
/*for(m in metas.copy()) {
trace(" " + m.name);
if(m.name == ":coreApi") {
metas.remove(m);
}
}*/
var imports = Context.getLocalImports().copy();
Context.defineModule(module, [helperClass], imports);
Context.getLocalImports().push({
path: [for(m in module.split(".")) {
name: m,
pos: Context.currentPos()
}],
mode: INormal
});
}
var printer = new haxe.macro.Printer();
for(field in fields) if(field.name.startsWith("hlf_"))
Sys.println(printer.printField(field));
}*/
}
return fields;
@@ -99,7 +99,7 @@ class AsyncUpdater {
progress.files = files.length;
progress.step = DOWNLOADING_ASSETS;
trace('starting assets download');
doFile([for(e in files) e], fileNames.copy(), function() {
doFile(files.copy(), fileNames.copy(), function() {
progress.curFile = -1;
progress.curFileName = null;
progress.files = 1;
@@ -56,7 +56,7 @@ class UpdateScreen extends MusicBeatState {
generalProgress.alignment = CENTER;
add(generalProgress);
overSound = FlxG.sound.load(Paths.sound('gameOverEnd'));
overSound = FlxG.sound.load(Paths.sound(Flags.DEFAULT_GAMEOVER_RETRY_SFX));
updater.execute();
+29 -6
View File
@@ -122,11 +122,15 @@ final class CoolUtil
* @return The attributes through the `FileAttributeWrapper`
*/
@:noUsing public static inline function safeGetAttributes(path:String, useAbsol:Bool = true):FileAttributeWrapper {
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.getFileAttributes(path, useAbsol);
if(result.isNothing) Logs.trace('The file where it has been tried to get the attributes from, might be corrupted or inexistent (code: ${result.getValue()})', WARNING);
return result;
#else
return 0;
#end
}
/**
@@ -139,11 +143,15 @@ final class CoolUtil
*/
@:noUsing public static inline function safeSetAttributes(path:String, attrib:OneOfThree<NativeAPI.FileAttribute, FileAttributeWrapper, Int>, useAbsol:Bool = true):Int {
// yes, i'm aware that FileAttribute is also an Int so need to include it too, but at least like this we don't have to make cast sometimes while passing the arguments - Nex
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.setFileAttributes(path, attrib, useAbsol);
if(result == 0) Logs.trace('Failed to set attributes to $path with a code of: $result', WARNING);
return result;
#else
return 0;
#end
}
/**
@@ -155,11 +163,15 @@ final class CoolUtil
* @return The result code: `0` means that it failed setting
*/
@:noUsing public static inline function safeAddAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsol:Bool = true):Int {
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.addFileAttributes(path, attrib, useAbsol);
if(result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
return result;
#else
return 0;
#end
}
/**
@@ -171,11 +183,15 @@ final class CoolUtil
* @return The result code: `0` means that it failed setting
*/
@:noUsing public static inline function safeRemoveAttributes(path:String, attrib:OneOfTwo<NativeAPI.FileAttribute, Int>, useAbsol:Bool = true):Int {
#if sys
addMissingFolders(Path.directory(path));
var result = NativeAPI.removeFileAttributes(path, attrib, useAbsol);
if(result == 0) Logs.trace('Failed to remove attributes to $path with a code of: $result', WARNING);
return result;
#else
return 0;
#end
}
/**
@@ -393,7 +409,7 @@ final class CoolUtil
* @param Looped Whenever the music loops (true)
* @param Group A group that this music belongs to (default)
*/
@:noUsing public static function playMusic(path:String, Persist:Bool = false, Volume:Float = 1, Looped:Bool = true, DefaultBPM:Int = 102, ?Group:FlxSoundGroup) {
@:noUsing public static function playMusic(path:String, Persist:Bool = false, Volume:Float = 1, Looped:Bool = true, DefaultBPM:Float = 102, ?Group:FlxSoundGroup) {
Conductor.reset();
FlxG.sound.playMusic(path, Volume, Looped, Group);
if (FlxG.sound.music != null) {
@@ -404,17 +420,17 @@ final class CoolUtil
if (Assets.exists(infoPath)) {
var musicInfo = IniUtil.parseAsset(infoPath, [
"BPM" => null,
"TimeSignature" => "4/4"
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT
]);
var timeSignParsed:Array<Null<Float>> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)];
var beatsPerMeasure:Float = 4;
var stepsPerBeat:Float = 4;
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE;
var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT;
// Check later, i dont think timeSignParsed can contain null, only nan
if (timeSignParsed.length == 2 && !timeSignParsed.contains(null)) {
beatsPerMeasure = timeSignParsed[0] == null || timeSignParsed[0] <= 0 ? 4 : cast timeSignParsed[0];
stepsPerBeat = timeSignParsed[1] == null || timeSignParsed[1] <= 0 ? 4 : cast timeSignParsed[1];
beatsPerMeasure = timeSignParsed[0] == null || timeSignParsed[0] <= 0 ? Flags.DEFAULT_BEATS_PER_MEASURE : cast timeSignParsed[0];
stepsPerBeat = timeSignParsed[1] == null || timeSignParsed[1] <= 0 ? Flags.DEFAULT_STEPS_PER_BEAT : cast timeSignParsed[1];
}
var bpm:Null<Float> = Std.parseFloat(musicInfo["BPM"]).getDefault(DefaultBPM);
@@ -762,7 +778,14 @@ final class CoolUtil
* @param url
*/
@:noUsing public static inline function openURL(url:String) {
#if linux
// generally `xdg-open` should work in every distro
var cmd = Sys.command("xdg-open", [url]);
// run old command JUST IN CASE it fails, which it shouldn't
if (cmd != 0) cmd = Sys.command("/usr/bin/xdg-open", [url]);
#else
FlxG.openURL(url);
#end
}
/**
+5 -5
View File
@@ -5,7 +5,7 @@ import flixel.sound.FlxSound;
import flixel.util.typeLimit.OneOfTwo;
import funkin.backend.scripting.*; // lazy
import funkin.backend.scripting.events.CancellableEvent;
import funkin.backend.scripting.events.DiscordPresenceUpdateEvent;
import funkin.backend.scripting.events.discord.*;
import funkin.backend.system.macros.Utils;
import haxe.Json;
import openfl.display.BitmapData;
@@ -86,9 +86,9 @@ final class DiscordUtil
if (config == null)
config = {};
config.logoKey = config.logoKey.getDefault("icon");
config.logoText = config.logoText.getDefault(Application.current.meta.get('title'));
config.clientID = config.clientID.getDefault("1027994136193810442");
config.logoKey = config.logoKey.getDefault(Flags.DEFAULT_DISCORD_LOGO_KEY);
config.logoText = config.logoText.getDefault(Flags.DEFAULT_DISCORD_LOGO_TEXT);
config.clientID = config.clientID.getDefault(Flags.DEFAULT_DISCORD_CLIENT_ID);
currentID = config.clientID;
#end
}
@@ -525,7 +525,7 @@ typedef DPresence =
var ?button1Url:String; /* max 512 bytes */
var ?button2Label:String; /* max 32 bytes */
var ?button2Url:String; /* max 512 bytes */
var ?activityType:ActivityType;
var ?activityType:#if DISCORD_RPC ActivityType #else Dynamic #end;
var ?streamUrl:String; /* max 512 bytes */
}
@@ -1,4 +1,4 @@
package funkin.savedata;
package funkin.backend.utils;
import flixel.util.FlxSave;
import lime.app.Application;
+4 -2
View File
@@ -140,14 +140,16 @@ final class MemoryUtil {
reg.match(process.stdout.readAll().toString());
if (process.exitCode() == 0) return reg.matched(1);
#elseif linux
var process = new HiddenProcess("sudo", ["dmidecode", "--type", "17"]);
/*var process = new HiddenProcess("sudo", ["dmidecode", "--type", "17"]);
if (process.exitCode() != 0) return "Unknown";
var lines = process.stdout.readAll().toString().split("\n");
for (line in lines) {
if (line.startsWith("Type:")) {
return line.substring("Type:".length).trim();
}
}
}*/
// TODO: sort of unsafe? also requires users to use `sudo`
// when launching the engine through the CLI, REIMPLEMENT LATER.
#end
return "Unknown";
}
+2
View File
@@ -147,6 +147,8 @@ final class XMLUtil {
var graphicSizey:Null<Int> = Std.parseInt(node.att.graphicSizey);
if (graphicSizey.isNotNull()) spr.setGraphicSize(0, graphicSizey);
}
if (node.has.flipX && node.att.flipX == "true") spr.flipX = true;
if (node.has.updateHitbox && node.att.updateHitbox == "true") spr.updateHitbox();
if (node.has.zoomfactor)
@@ -362,7 +362,7 @@ class CharacterEditor extends UIState {
}
animationText.text = '"${character.getAnimName()}"';
if (Options.characterDragging)
if (Options.characterDragging)
handleMouseOffsets();
super.update(elapsed);
@@ -73,7 +73,7 @@ class ChartCreationScreen extends UISubstateWindow {
strumLineList.addButton.callback = function() {
strumLineList.add(new StrumLineButton(strumLineList.buttons.length, {
characters: ["dad"],
characters: [Flags.DEFAULT_OPPONENT],
type: 0,
notes: null,
position: "DAD",
@@ -84,7 +84,7 @@ class ChartCreationScreen extends UISubstateWindow {
// DEFAULTS
strumLineList.add(new StrumLineButton(0, {
characters: ["dad"],
characters: [Flags.DEFAULT_OPPONENT],
type: 0,
notes: null,
position: "DAD",
@@ -93,7 +93,7 @@ class ChartCreationScreen extends UISubstateWindow {
scrollSpeed: 1,
}, strumLineList));
strumLineList.add(new StrumLineButton(1, {
characters: ["bf"],
characters: [Flags.DEFAULT_CHARACTER],
type: 1,
notes: null,
position: "BOYFRIEND",
@@ -102,7 +102,7 @@ class ChartCreationScreen extends UISubstateWindow {
scrollSpeed: 1,
}, strumLineList));
strumLineList.add(new StrumLineButton(2, {
characters: ["gf"],
characters: [Flags.DEFAULT_GIRLFRIEND],
type: 2,
notes: null,
position: "GIRLFRIEND",
@@ -333,7 +333,7 @@ class CompactCharacterButton extends UIButton {
char = funkin.game.Character.getIconFromCharName(char);
var image = Paths.image("icons/" + char);
if(!Assets.exists(image))
image = Paths.image("icons/face");
image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON);
charIcon.loadGraphic(image, true, 150, 150);
charIcon.updateHitbox();
}
+13 -13
View File
@@ -1325,7 +1325,7 @@ class Charter extends UIState {
if (strumLines != null)
strumlineLockButton.button.animation.play(strumLines.draggable ? "1" : "0", true);
WindowUtils.prefix = undos.unsaved ? "* " : "";
WindowUtils.prefix = undos.unsaved ? Flags.UNDO_PREFIX : "";
SaveWarning.showWarning = undos.unsaved;
__firstFrame = false;
@@ -1382,7 +1382,7 @@ class Charter extends UIState {
}
function _file_saveas(_) {
openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false), null, Options.editorPrettyPrint ? "\t" : null), {
openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
defaultSaveFile: '${__diff.toLowerCase()}.json'
}));
undos.save();
@@ -1398,7 +1398,7 @@ class Charter extends UIState {
}
function _file_saveas_no_events(_) {
openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false, false), null, Options.editorPrettyPrint ? "\t" : null), {
openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false, false), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
defaultSaveFile: '${__diff.toLowerCase()}.json'
}));
undos.save();
@@ -1408,7 +1408,7 @@ class Charter extends UIState {
#if sys
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json',
Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, "\t")
Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT)
);
#else
_file_meta_saveas(_);
@@ -1416,20 +1416,20 @@ class Charter extends UIState {
}
function _file_meta_saveas(_) {
openSubState(new SaveSubstate(Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, "\t"), { // always pretty print meta
openSubState(new SaveSubstate(Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT), { // always pretty print meta
defaultSaveFile: 'meta.json'
}));
}
function _file_saveas_fnflegacy(_) {
openSubState(new SaveSubstate(Json.stringify(FNFLegacyParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? "\t" : null), {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json',
openSubState(new SaveSubstate(Json.stringify(FNFLegacyParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json',
}));
}
function _file_saveas_psych(_) {
openSubState(new SaveSubstate(Json.stringify(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? "\t" : null), {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json',
openSubState(new SaveSubstate(Json.stringify(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == Flags.DEFAULT_DIFFICULTY ? "" : '-${__diff.toLowerCase()}'}.json',
}));
}
@@ -1437,7 +1437,7 @@ class Charter extends UIState {
#if sys
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json',
Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? "\t" : null)
Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null)
);
#else
_file_events_saveas(_);
@@ -1446,7 +1446,7 @@ class Charter extends UIState {
function _file_events_saveas(_) {
#if sys
openSubState(new SaveSubstate(Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? "\t" : null), {
openSubState(new SaveSubstate(Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? Flags.JSON_PRETTY_PRINT : null), {
defaultSaveFile: 'events.json'
}));
#end
@@ -1775,7 +1775,7 @@ class Charter extends UIState {
changeNoteSustain(-1);
function _note_selectall(_) {
selection = [for (note in notesGroup.members) note];
selection = cast notesGroup.members.copy();
}
function _note_selectmeasure(_) {
@@ -164,7 +164,11 @@ class CharterEventScreen extends UISubstateWindow {
colorWheel;
case TDropDown(options):
addLabel();
var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value))));
var optionIndex = options.indexOf(cast value);
if(optionIndex < 0) {
optionIndex = 0;
}
var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, optionIndex);
paramsPanel.add(dropdown); paramsFields.push(dropdown);
dropdown;
case TCharacter:
@@ -129,7 +129,7 @@ class CharterMetaDataScreen extends UISubstateWindow {
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/face');
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
@@ -109,9 +109,9 @@ class CharterSelection extends EditorTreeMenu {
sys.FileSystem.createDirectory('$songFolder/charts');
// Save Files
CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(creation.meta, "\t"));
if (creation.instBytes != null) sys.io.File.saveBytes('$songFolder/song/Inst.${Paths.SOUND_EXT}', creation.instBytes);
if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices.${Paths.SOUND_EXT}', creation.voicesBytes);
CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(creation.meta, Flags.JSON_PRETTY_PRINT));
if (creation.instBytes != null) sys.io.File.saveBytes('$songFolder/song/Inst.${Flags.SOUND_EXT}', creation.instBytes);
if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices.${Flags.SOUND_EXT}', creation.voicesBytes);
#end
var option = new EditorIconOption(creation.meta.name, "Press ACCEPT to choose a difficulty to edit.", creation.meta.icon, function() {
@@ -147,7 +147,7 @@ class CharterSelection extends EditorTreeMenu {
var songFolder:String = '${Paths.getAssetsRoot()}/songs/${curSong.name}';
// Save Files
CoolUtil.safeSaveFile('$songFolder/charts/${name}.json', Json.stringify(data, "\t"));
CoolUtil.safeSaveFile('$songFolder/charts/${name}.json', Json.stringify(data, Flags.JSON_PRETTY_PRINT));
// Add to List
curSong.difficulties.push(name);
@@ -48,7 +48,7 @@ class CharterStrumlineScreen extends UISubstateWindow {
if (creatingStrumLine)
strumLine = {
characters: ["dad"],
characters: [Flags.DEFAULT_OPPONENT],
type: OPPONENT,
notes: [],
position: "dad",
@@ -256,7 +256,7 @@ class CharacterButton extends UIButton {
char = Character.getIconFromCharName(char);
var image = Paths.image("icons/" + char);
if(!Assets.exists(image))
image = Paths.image("icons/face");
image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON);
charIcon.loadGraphic(image, true, 150, 150);
charIcon.updateHitbox();
}
@@ -80,7 +80,9 @@ class SongCreationScreen extends UISubstateWindow {
stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54);
songDataGroup.add(stepsPerBeatStepper);
instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Paths.SOUND_EXT, function (res) {
var voicesUIText:UIText = null;
instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (res) {
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(instExplorer.x + 8, instExplorer.y + 8, res);
instExplorer.members.push(audioPlayer);
instExplorer.uiElement = audioPlayer;
@@ -90,7 +92,7 @@ class SongCreationScreen extends UISubstateWindow {
"Inst Audio File $* Required$",
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Paths.SOUND_EXT, function (res) {
voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Flags.SOUND_EXT, function (res) {
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(voicesExplorer.x + 8, voicesExplorer.y + 8, res);
voicesExplorer.members.push(audioPlayer);
voicesExplorer.uiElement = audioPlayer;
@@ -211,7 +213,7 @@ class SongCreationScreen extends UISubstateWindow {
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/face');
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
+14 -1
View File
@@ -1,5 +1,8 @@
package funkin.editors.ui;
import openfl.display.ShaderParameter;
import openfl.display.ShaderInput;
import openfl.filters.ShaderFilter;
import flixel.tweens.FlxTween;
import funkin.backend.shaders.CustomShader;
import openfl.filters.ShaderFilter;
@@ -7,7 +10,17 @@ import openfl.filters.ShaderFilter;
// TODO: make UIWarningSubstate extend this
class UISubstateWindow extends MusicBeatSubstate {
var camShaders:Array<FlxCamera> = [];
var blurShader:CustomShader = new CustomShader(Options.intensiveBlur ? "engine/editorBlur" : "engine/editorBlurFast");
var blurShader:CustomShader = {
var _ = new CustomShader(Options.intensiveBlur ? "engine/editorBlur" : "engine/editorBlurFast");
if(!Options.intensiveBlur) {
var noiseTexture:ShaderInput<openfl.display.BitmapData> = _.data.noiseTexture;
noiseTexture.input = Assets.getBitmapData("assets/shaders/noise256.png");
noiseTexture.wrap = REPEAT;
var noiseTextureSize:ShaderParameter<Float> = _.data.noiseTextureSize;
noiseTextureSize.value = [noiseTexture.input.width, noiseTexture.input.height];
}
_;
};
var titleSpr:UIText;
var messageSpr:UIText;
+14 -1
View File
@@ -1,5 +1,8 @@
package funkin.editors.ui;
import openfl.display.ShaderParameter;
import openfl.display.ShaderInput;
import openfl.filters.ShaderFilter;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import funkin.backend.shaders.CustomShader;
@@ -7,7 +10,17 @@ import openfl.filters.ShaderFilter;
class UIWarningSubstate extends MusicBeatSubstate {
var camShaders:Array<FlxCamera> = [];
var blurShader:CustomShader = new CustomShader(Options.intensiveBlur ? "engine/editorBlur" : "engine/editorBlurFast");
var blurShader:CustomShader = {
var _ = new CustomShader(Options.intensiveBlur ? "engine/editorBlur" : "engine/editorBlurFast");
if(!Options.intensiveBlur) {
var noiseTexture:ShaderInput<openfl.display.BitmapData> = _.data.noiseTexture;
noiseTexture.input = Assets.getBitmapData("assets/shaders/noise256.png");
noiseTexture.wrap = REPEAT;
var noiseTextureSize:ShaderParameter<Float> = _.data.noiseTextureSize;
noiseTextureSize.value = [noiseTexture.input.width, noiseTexture.input.height];
}
_;
};
var title:String;
var message:String;
+29 -18
View File
@@ -12,13 +12,11 @@ import funkin.backend.FunkinSprite;
import funkin.backend.scripting.DummyScript;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.ScriptPack;
import funkin.backend.scripting.events.CharacterNodeEvent;
import funkin.backend.scripting.events.CharacterXMLEvent;
import funkin.backend.scripting.events.DanceEvent;
import funkin.backend.scripting.events.DirectionAnimEvent;
import funkin.backend.scripting.events.PlayAnimContext;
import funkin.backend.scripting.events.PlayAnimEvent;
import funkin.backend.scripting.events.CancellableEvent;
import funkin.backend.scripting.events.character.*;
import funkin.backend.scripting.events.sprite.*;
import funkin.backend.scripting.events.PointEvent;
import funkin.backend.scripting.events.DrawEvent;
import funkin.backend.system.Conductor;
import funkin.backend.system.interfaces.IBeatReceiver;
import funkin.backend.system.interfaces.IOffsetCompatible;
@@ -36,8 +34,8 @@ using StringTools;
@:allow(funkin.game.PlayState)
class Character extends FunkinSprite implements IBeatReceiver implements IOffsetCompatible implements IPrePostDraw {
public var isPlayer:Bool = false;
public var curCharacter:String = 'bf';
public var sprite:String = 'bf';
public var curCharacter:String = Flags.DEFAULT_CHARACTER;
public var sprite:String = Flags.DEFAULT_CHARACTER;
public var lastHit:Float = Math.NEGATIVE_INFINITY;
public var holdTime:Float = 4;
@@ -68,12 +66,12 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
@:noCompletion var __switchAnims:Bool = true;
public function new(x:Float, y:Float, ?character:String = "bf", isPlayer:Bool = false, switchAnims:Bool = true, disableScripts:Bool = false)
public function new(x:Float, y:Float, ?character:String, isPlayer:Bool = false, switchAnims:Bool = true, disableScripts:Bool = false)
{
super(x, y);
animOffsets = new Map<String, FlxPoint>();
curCharacter = character;
curCharacter = character != null ? character : Flags.DEFAULT_CHARACTER;
this.isPlayer = isPlayer;
__switchAnims = switchAnims;
@@ -130,7 +128,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
scripts.call("update", [elapsed]);
if (stunned) {
__stunnedTime += elapsed;
if (__stunnedTime > 5 / 60)
if (__stunnedTime > Flags.STUNNED_TIME)
stunned = false;
}
@@ -156,6 +154,11 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
}
public function tryDance() {
var event = new CancellableEvent();
script.call("onTryDance", [event]);
if (event.cancelled)
return;
switch (lastAnimContext) {
case SING | MISS:
if (lastHit + (Conductor.stepCrochet * holdTime) < Conductor.songPosition)
@@ -182,6 +185,9 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
tryDance();
}
public override function measureHit(curMeasure:Int)
script.call("measureHit", [curMeasure]);
public override function stepHit(curStep:Int)
scripts.call("stepHit", [curStep]);
@@ -234,6 +240,9 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
public var ghostDraw:Bool = false;
public override function draw() {
var e = EventManager.get(DrawEvent).recycle();
script.call("draw", [e]);
preDraw();
super.draw();
postDraw();
@@ -242,6 +251,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
if (debugHitbox) drawHitbox();
if (debugCamera) drawCamera();
}
script.call("postDraw", [e]);
}
public var debugHitbox:Bool = false;
@@ -353,15 +364,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
@:noCompletion var __reverseTrailProcedure:Bool = false;
// When using trails on characters you should do `trail.beforeCache = char.beforeTrailCache;`
// When using trails on characters you should do `trail.beforeCache = char.beforeTrailCache;` and `trail.afterCache = char.afterTrailCache;`
public dynamic function beforeTrailCache()
if (isFlippedOffsets()) {
flipX = !flipX;
scale.x *= -1;
__reverseTrailProcedure = true;
}
// When using trails on characters you should do `trail.afterCache = char.afterTrailCache;`
public dynamic function afterTrailCache()
if (__reverseTrailProcedure) {
flipX = !flipX;
@@ -540,6 +550,9 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
@:noCompletion private function get_danceInterval()
return beatInterval;
public static var FALLBACK_CHARACTER:String = Flags.DEFAULT_CHARACTER;
public static var FALLBACK_DEAD_CHARACTER:String = Flags.DEFAULT_GAMEOVER_CHARACTER;
private function set_script(script:Script):Script {
if (scripts == null) (scripts = new ScriptPack("Character")).setParent(this);
@@ -557,8 +570,6 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
// ---- end of Backwards compat ----
public static var FALLBACK_CHARACTER:String = "bf";
public static var FALLBACK_DEAD_CHARACTER:String = "bf-dead";
public static function getXMLFromCharName(character:OneOfTwo<String, Character>):Access {
var char:Character = null;
if (character is Character) {
@@ -595,7 +606,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
}
public static function getIconFromCharName(?character:String, ?defaultIcon:String = null) {
if(character == null) return "face";
if(character == null) return Flags.DEFAULT_HEALTH_ICON;
if(defaultIcon == null) defaultIcon = character;
var icon:String = defaultIcon;
@@ -618,4 +629,4 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
list.push(CoolUtil.getFilename(path));
return list;
}
}
}
+10 -12
View File
@@ -5,7 +5,7 @@ import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.CancellableEvent;
import funkin.backend.scripting.events.GameOverCreationEvent;
import funkin.backend.scripting.events.gameover.*;
import funkin.backend.system.Conductor;
import funkin.editors.charter.Charter;
import funkin.menus.FreeplayState;
@@ -28,6 +28,7 @@ class GameOverSubstate extends MusicBeatSubstate
public var gameoverScript:Script;
public var game:PlayState = PlayState.instance; // shortcut
public static var instance:GameOverSubstate = null;
private var __cancelDefault:Bool = false;
@@ -36,20 +37,16 @@ class GameOverSubstate extends MusicBeatSubstate
public var lossSFX:FlxSound;
public function new(x:Float, y:Float, character:String = null, player:Bool = true, gameOverSong:String = "gameOver", lossSFX:String = "gameOverSFX", retrySFX:String = "gameOverEnd")
public function new(x:Float, y:Float, ?character:String, player:Bool = true, ?gameOverSong:String, ?lossSFX:String, ?retrySFX:String)
{
super();
this.x = x;
this.y = y;
characterName = character;
if (characterName == null)
characterName = Character.FALLBACK_DEAD_CHARACTER;
this.player = player;
this.gameOverSong = gameOverSong;
this.lossSFXName = lossSFX;
this.retrySFX = retrySFX;
this.characterName = character != null ? character : Flags.DEFAULT_GAMEOVER_CHARACTER;
this.gameOverSong = gameOverSong != null ? gameOverSong : Flags.DEFAULT_GAMEOVER_SONG;
this.lossSFXName = lossSFX != null ? lossSFX : Flags.DEFAULT_GAMEOVER_LOSS_SFX;
this.retrySFX = retrySFX != null ? retrySFX : Flags.DEFAULT_GAMEOVER_RETRY_SFX;
}
public override function create()
@@ -60,7 +57,7 @@ class GameOverSubstate extends MusicBeatSubstate
FlxG.sound.music.stop();
gameoverScript = Script.create(Paths.script(script));
gameoverScript.setParent(this);
gameoverScript.setParent(instance = this);
gameoverScript.load();
var event = EventManager.get(GameOverCreationEvent).recycle(x, y, characterName, player, gameOverSong, gameOverSongBPM, lossSFXName, retrySFX);
@@ -131,7 +128,7 @@ class GameOverSubstate extends MusicBeatSubstate
if (!isEnding && ((!lossSFX.playing) || (character.getAnimName() == "firstDeath" && character.isAnimFinished())) && (FlxG.sound.music == null || !FlxG.sound.music.playing))
{
CoolUtil.playMusic(Paths.music(gameOverSong), false, 1, true, 100);
CoolUtil.playMusic(Paths.music(gameOverSong), false, 1, true, Flags.DEFAULT_BPM);
beatHit(0);
}
}
@@ -202,5 +199,6 @@ class GameOverSubstate extends MusicBeatSubstate
gameoverScript.destroy();
super.destroy();
instance = null;
}
}
+8 -10
View File
@@ -27,7 +27,7 @@ class HealthIcon extends FlxSprite
/**
* current animation state
*/
public var curAnimState:Int = -1;
public var curAnimState:Int = -1;
/**
* Helper for HScript who can't make maps
@@ -48,12 +48,12 @@ class HealthIcon extends FlxSprite
];
}
public function new(char:String = 'bf', isPlayer:Bool = false)
public function new(?char:String, isPlayer:Bool = false)
{
super();
health = 0.5;
this.isPlayer = isPlayer;
setIcon(char);
setIcon(char != null ? char : Flags.DEFAULT_CHARACTER);
scrollFactor.set();
}
@@ -62,7 +62,7 @@ class HealthIcon extends FlxSprite
if(curCharacter != char || this.width != width || this.height != height) {
curCharacter = char;
var path = Paths.image('icons/$char');
if (!Assets.exists(path)) path = Paths.image('icons/face');
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
loadGraphic(path, true, width, height);
@@ -90,14 +90,12 @@ class HealthIcon extends FlxSprite
if (animation.curAnim != null) {
var i:Int = -1;
var oldKey:Int = -1;
for(k=>icon in healthSteps) {
if (k > oldKey && k < health * 100) {
oldKey = k;
i = icon;
}
for (k=>icon in healthSteps) if (k > oldKey && k <= health * 100) {
oldKey = k;
i = icon;
}
if (i >= 0 && curAnimState != i) {
var event = EventManager.get(funkin.backend.scripting.events.HealthIconChangeEvent).recycle(i, this);
var event = EventManager.get(funkin.backend.scripting.events.healthicon.HealthIconChangeEvent).recycle(i, this);
funkin.backend.scripting.GlobalScript.event("onHealthIconAnimChange", event);
if (!event.cancelled)
animation.curAnim.curFrame = event.amount;
+7 -10
View File
@@ -3,7 +3,7 @@ package funkin.game;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import funkin.backend.chart.ChartData;
import funkin.backend.scripting.events.*;
import funkin.backend.scripting.events.note.NoteCreationEvent;
import funkin.backend.system.Conductor;
using StringTools;
@@ -86,7 +86,7 @@ class Note extends FlxSprite
return PlayState.instance.getNoteType(noteTypeID);
}
public static var swagWidth:Float = 160 * 0.7;
public static var swagWidth:Float = 160 * 0.7; // TODO: remove this
private static var __customNoteTypeExists:Map<String, Bool> = [];
@@ -117,11 +117,8 @@ class Note extends FlxSprite
this.isSustainNote = sustain;
this.sustainLength = sustainLength;
this.strumLine = strumLine;
for(field in Reflect.fields(noteData)) {
if(!DEFAULT_FIELDS.contains(field)) {
this.extra.set(field, Reflect.field(noteData, field));
}
}
for(field in Reflect.fields(noteData)) if(!DEFAULT_FIELDS.contains(field))
this.extra.set(field, Reflect.field(noteData, field));
x += 50;
// MAKE SURE ITS DEFINITELY OFF SCREEN?
@@ -132,10 +129,10 @@ class Note extends FlxSprite
var customType = Paths.image('game/notes/${this.noteType}');
var event = EventManager.get(NoteCreationEvent).recycle(this, strumID, this.noteType, noteTypeID, PlayState.instance.strumLines.members.indexOf(strumLine), mustPress,
(this.noteType != null && customTypePathExists(customType)) ? 'game/notes/${this.noteType}' : 'game/notes/default', @:privateAccess strumLine.strumScale * 0.7, animSuffix);
(this.noteType != null && customTypePathExists(customType)) ? 'game/notes/${this.noteType}' : 'game/notes/default', @:privateAccess strumLine.strumScale * Flags.DEFAULT_NOTE_SCALE, animSuffix);
if (PlayState.instance != null)
event = PlayState.instance.scripts.event("onNoteCreation", event);
event = PlayState.instance.gameAndCharsEvent("onNoteCreation", event);
this.animSuffix = event.animSuffix;
if (!event.cancelled) {
@@ -189,7 +186,7 @@ class Note extends FlxSprite
if (PlayState.instance != null) {
PlayState.instance.splashHandler.getSplashGroup(splash);
PlayState.instance.scripts.event("onPostNoteCreation", event);
PlayState.instance.gameAndCharsEvent("onPostNoteCreation", event);
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ class NoteGroup extends FlxTypedGroup<Note> {
/**
* How many ms it should show a note before it should be hit
**/
public var limit:Float = 1500;
public var limit:Float = Flags.DEFAULT_NOTE_MS_LIMIT;
/**
* Preallocates the members array with nulls, but if theres anything in the array already it clears it
+131 -78
View File
@@ -18,6 +18,8 @@ import funkin.backend.chart.EventsData;
import funkin.backend.scripting.DummyScript;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.ScriptPack;
import funkin.backend.scripting.events.gameplay.*;
import funkin.backend.scripting.events.note.*;
import funkin.backend.scripting.events.*;
import funkin.backend.system.Conductor;
import funkin.backend.system.RotatingSpriteGroup;
@@ -28,7 +30,7 @@ import funkin.game.SplashHandler;
import funkin.game.cutscenes.*;
import funkin.menus.*;
import funkin.menus.StoryMenuState.WeekData;
import funkin.savedata.FunkinSave;
import funkin.backend.utils.FunkinSave;
import haxe.io.Path;
using StringTools;
@@ -61,7 +63,7 @@ class PlayState extends MusicBeatState
/**
* The selected difficulty name
*/
public static var difficulty:String = "normal";
public static var difficulty:String = Flags.DEFAULT_DIFFICULTY;
/**
* Whenever the week is coming from the mods folder or not.
*/
@@ -73,11 +75,11 @@ class PlayState extends MusicBeatState
/**
* Whenever the song has been started with opponent mode on.
*/
public static var opponentMode:Bool = false;
public static var opponentMode:Bool = Flags.DEFAULT_OPPONENT_MODE;
/**
* Whenever the song has been started with co-op mode on.
*/
public static var coopMode:Bool = false;
public static var coopMode:Bool = Flags.DEFAULT_COOP_MODE;
/**
* Script Pack of all the scripts being ran.
@@ -96,15 +98,15 @@ class PlayState extends MusicBeatState
/**
* Game Over Song. (assets/music/gameOver.ogg)
*/
public var gameOverSong:String = "gameOver";
public var gameOverSong:String = Flags.DEFAULT_GAMEOVER_SONG;
/**
* Game Over Song. (assets/sounds/gameOverSFX.ogg)
*/
public var lossSFX:String = "gameOverSFX";
public var lossSFX:String = Flags.DEFAULT_GAMEOVER_LOSS_SFX;
/**
* Game Over End SFX, used when retrying. (assets/sounds/gameOverEnd.ogg)
*/
public var retrySFX:String = "gameOverEnd";
public var retrySFX:String = Flags.DEFAULT_GAMEOVER_RETRY_SFX;
/**
* Current Stage.
@@ -209,15 +211,15 @@ class PlayState extends MusicBeatState
/**
* Whenever the vocals should be muted when a note is missed.
*/
public var muteVocalsOnMiss:Bool = true;
public var muteVocalsOnMiss:Bool = Flags.DEFAULT_MUTE_VOCALS_ON_MISS;
/**
* Whenever the player can press 7, 8 or 9 to access the debug menus.
*/
public var canAccessDebugMenus:Bool = true;
public var canAccessDebugMenus:Bool = Flags.DEFAULT_CAN_ACCESS_DEBUG_MENUS;
/**
* Whether or not to show the secret gitaroo pause.
*/
public var allowGitaroo:Bool = true;
public var allowGitaroo:Bool = Flags.DEFAULT_GITAROO;
/**
* Whether or not to bop the icons on beat.
*/
@@ -231,19 +233,23 @@ class PlayState extends MusicBeatState
* Interval of cam zooming (beats).
* For example: if set to 4, the camera will zoom every 4 beats.
*/
public var camZoomingInterval:Int = 4;
public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL;
/**
* How strong the cam zooms should be (defaults to 1)
*/
public var camZoomingStrength:Float = 1;
public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH;
/**
* Maximum amount of zoom for the camera.
*/
public var maxCamZoom:Float = 1.35;
public var maxCamZoom:Float = Flags.MAX_CAMERA_ZOOM;
/**
* Current song name (lowercase)
*/
public var curSong:String = "";
/**
* Current song name (lowercase and spaces to dashes)
*/
public var curSongID:String = "";
/**
* Current stage name
*/
@@ -262,7 +268,7 @@ class PlayState extends MusicBeatState
/**
* Maximum health the player can have. Defaults to 2.
*/
@:isVar public var maxHealth(get, set):Float = 2;
@:isVar public var maxHealth(get, set):Float = Flags.DEFAULT_MAX_HEALTH;
/**
* Current combo.
*/
@@ -362,25 +368,25 @@ class PlayState extends MusicBeatState
/**
* Camera zoom at which the game lerps to.
*/
public var defaultCamZoom:Float = 1.05;
public var defaultCamZoom:Float = Flags.DEFAULT_CAM_ZOOM;
/**
* Speed at which the game camera zoom lerps to.
*/
public var camGameZoomLerp:Float = 0.05;
public var camGameZoomLerp:Float = Flags.DEFAULT_CAM_ZOOM_LERP;
/**
* Camera zoom at which the hud lerps to.
*/
public var defaultHudZoom:Float = 1.0;
public var defaultHudZoom:Float = Flags.DEFAULT_HUD_ZOOM;
/**
* Speed at which the hud camera zoom lerps to.
*/
public var camHUDZoomLerp:Float = 0.05;
public var camHUDZoomLerp:Float = Flags.DEFAULT_HUD_ZOOM_LERP;
/**
* Zoom for the pixel assets.
*/
public static var daPixelZoom:Float = 6;
public static var daPixelZoom:Float = Flags.PIXEL_ART_SCALE;
/**
* Whenever the game is currently in a cutscene or not.
@@ -459,6 +465,22 @@ class PlayState extends MusicBeatState
* Group containing all of the combo sprites.
*/
public var comboGroup:RotatingSpriteGroup;
/**
* Whenever the Rating sprites should be shown or not.
*
* NOTE: This is just a default value for the final value, the final value can be changed through notes hit events.
*/
public var defaultDisplayRating:Bool = true;
/**
* Whenever the Combo sprite should be shown or not (like old Week 7 patches).
*
* NOTE: This is just a default value for the final value, the final value can be changed through notes hit events.
*/
public var defaultDisplayCombo:Bool = false;
/**
* Minimum Combo Count to display the combo digits. Anything less than 0 means it won't be shown.
*/
public var minDigitDisplay:Int = 10;
/**
* Array containing all of the note types names.
*/
@@ -516,7 +538,7 @@ class PlayState extends MusicBeatState
if (e.percent <= acc && (rating == null || rating.percent < e.percent))
rating = e;
var event = scripts.event("onRatingUpdate", EventManager.get(RatingUpdateEvent).recycle(rating, curRating));
var event = gameAndCharsEvent("onRatingUpdate", EventManager.get(RatingUpdateEvent).recycle(rating, curRating));
if (!event.cancelled)
curRating = event.rating;
}
@@ -538,6 +560,24 @@ class PlayState extends MusicBeatState
return name;
}
public inline function callOnCharacters(func:String, ?parameters:Array<Dynamic>) {
if(strumLines != null) strumLines.forEachAlive(function (strLine:StrumLine) {
if (strLine.characters != null) for (character in strLine.characters)
if (character != null) character.script.call(func, parameters);
});
}
public inline function gameAndCharsCall(func:String, ?parameters:Array<Dynamic>, ?charsFunc:String) {
scripts.call(func, parameters);
callOnCharacters(charsFunc != null ? charsFunc : func, parameters);
}
public inline function gameAndCharsEvent<T:CancellableEvent>(func:String, ?event:T, ?charsFunc:String):T {
scripts.event(func, event);
callOnCharacters(charsFunc != null ? charsFunc : func, [event]);
return event;
}
@:dox(hide) override public function create()
{
Note.__customNoteTypeExists = [];
@@ -581,7 +621,7 @@ class PlayState extends MusicBeatState
// CHARACTER INITIALIZATION
#if REGION
comboGroup = new RotatingSpriteGroup(FlxG.width * 0.55, (FlxG.height * 0.5) - 60);
comboGroup.maxSize = 25;
comboGroup.maxSize = Flags.DEFAULT_COMBO_GROUP_MAX_SIZE;
#end
// CAMERA FOLLOW, SCRIPTS & STAGE INITIALIZATION
@@ -589,7 +629,7 @@ class PlayState extends MusicBeatState
camFollow = new FlxObject(0, 0, 2, 2);
add(camFollow);
if (SONG.stage == null || SONG.stage.trim() == "") SONG.stage = "stage";
if (SONG.stage == null || SONG.stage.trim() == "") SONG.stage = Flags.DEFAULT_STAGE;
add(stage = new Stage(SONG.stage));
if (!chartingMode || Options.charterEnablePlaytestScripts) {
@@ -702,15 +742,15 @@ class PlayState extends MusicBeatState
// HUD INITIALIZATION & CAMERA INITIALIZATION
#if REGION
var event = EventManager.get(AmountEvent).recycle(null);
if (!scripts.event("onPreGenerateStrums", event).cancelled) {
if (!gameAndCharsEvent("onPreGenerateStrums", event).cancelled) {
generateStrums(event.amount);
scripts.event("onPostGenerateStrums", event);
gameAndCharsEvent("onPostGenerateStrums", event);
}
for(str in strumLines)
str.generate(str.data, (chartingMode && Charter.startHere) ? Charter.startTime : null);
FlxG.camera.follow(camFollow, LOCKON, 0.04);
FlxG.camera.follow(camFollow, LOCKON, Flags.DEFAULT_CAMERA_FOLLOW_SPEED);
FlxG.camera.zoom = defaultCamZoom;
// camHUD.zoom = defaultHudZoom;
@@ -739,10 +779,11 @@ class PlayState extends MusicBeatState
healthBar.createFilledBar(leftColor, rightColor);
add(healthBar);
health = maxHealth / 2;
if (Flags.DEFAULT_HEALTH != null) health = Flags.DEFAULT_HEALTH;
else health = maxHealth / 2;
iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : "face", true);
iconP2 = new HealthIcon(dad != null ? dad.getIcon() : "face", false);
iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true);
iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false);
for(icon in [iconP1, iconP2]) {
icon.y = healthBar.y - (icon.height / 2);
add(icon);
@@ -779,7 +820,7 @@ class PlayState extends MusicBeatState
FlxG.sound.load(Paths.sound(s));
if (chartingMode) {
WindowUtils.prefix = Charter.undos.unsaved ? "* " : "";
WindowUtils.prefix = Charter.undos.unsaved ? Flags.UNDO_PREFIX : "";
WindowUtils.suffix = " (Chart Playtesting)";
SaveWarning.showWarning = Charter.undos.unsaved;
@@ -804,7 +845,7 @@ class PlayState extends MusicBeatState
__updateNote_event = EventManager.get(NoteUpdateEvent);
scripts.call("postCreate");
gameAndCharsCall("postCreate", null, "gamePostCreate");
}
/**
@@ -835,7 +876,7 @@ class PlayState extends MusicBeatState
inCutscene = true;
var videoCutscene = Paths.video('$songName-${prefix}cutscene');
var videoCutsceneAlt = Paths.file('songs/$songName/${prefix}cutscene.mp4');
var videoCutsceneAlt = Paths.file('songs/$songName/${prefix}cutscene.${Flags.VIDEO_EXT}');
var dialogue = Paths.file('songs/$songName/${prefix}dialogue.xml');
persistentUpdate = true;
var toCall:Void->Void = function() {
@@ -868,7 +909,7 @@ class PlayState extends MusicBeatState
_startCountdownCalled = true;
inCutscene = false;
if (scripts.event("onStartCountdown", new CancellableEvent()).cancelled) return;
if (gameAndCharsEvent("onStartCountdown", new CancellableEvent()).cancelled) return;
}
startedCountdown = true;
@@ -881,14 +922,14 @@ class PlayState extends MusicBeatState
countdown(swagCounter++);
}, introLength);
}
scripts.call("onPostStartCountdown");
gameAndCharsCall("onPostStartCountdown");
}
/**
* Creates a fake countdown.
*/
public function countdown(swagCounter:Int) {
var event:CountdownEvent = scripts.event("onCountdown", EventManager.get(CountdownEvent).recycle(
var event:CountdownEvent = gameAndCharsEvent("onCountdown", EventManager.get(CountdownEvent).recycle(
swagCounter,
1,
introSounds[swagCounter],
@@ -931,12 +972,12 @@ class PlayState extends MusicBeatState
event.spriteTween = tween;
event.cancelled = false;
scripts.event("onPostCountdown", event);
gameAndCharsEvent("onPostCountdown", event);
}
@:dox(hide) function startSong():Void
{
scripts.call("onSongStart");
gameAndCharsCall("onSongStart");
startingSong = false;
inst.onComplete = endSong;
@@ -963,7 +1004,7 @@ class PlayState extends MusicBeatState
updateDiscordPresence();
scripts.call("onStartSong");
gameAndCharsCall("onStartSong");
}
public override function destroy() {
@@ -1010,11 +1051,15 @@ class PlayState extends MusicBeatState
return FlxSort.byValues(FlxSort.DESCENDING, p1.time, p2.time);
});
camZoomingInterval = cast songData.meta.beatsPerMeasure.getDefault(4);
var beatsPerMeasure:Float = songData.meta.beatsPerMeasure.getDefault(Flags.DEFAULT_BEATS_PER_MEASURE);
var stepsPerBeat:Int = songData.meta.stepsPerBeat.getDefault(Flags.DEFAULT_STEPS_PER_BEAT);
Conductor.changeBPM(songData.meta.bpm, cast songData.meta.beatsPerMeasure.getDefault(4), cast songData.meta.stepsPerBeat.getDefault(4));
camZoomingInterval = Std.int(beatsPerMeasure);
Conductor.changeBPM(songData.meta.bpm, beatsPerMeasure, stepsPerBeat);
curSong = songData.meta.name.toLowerCase();
curSongID = curSong.replace(" ", "-");
inst = FlxG.sound.load(Paths.inst(SONG.meta.name, difficulty));
if (SONG.meta.needsVoices != false && Assets.exists(Paths.voices(SONG.meta.name, difficulty))) // null or true
@@ -1044,7 +1089,7 @@ class PlayState extends MusicBeatState
@:dox(hide)
override function openSubState(SubState:FlxSubState)
{
var event = scripts.event("onSubstateOpen", EventManager.get(StateEvent).recycle(SubState));
var event = gameAndCharsEvent("onSubstateOpen", EventManager.get(StateEvent).recycle(SubState));
if (!postCreated)
MusicBeatState.skipTransIn = true;
@@ -1064,13 +1109,13 @@ class PlayState extends MusicBeatState
startTimer.active = false;
}
super.openSubState(SubState);
super.openSubState(event.substate is FlxSubState ? cast event.substate : SubState);
}
@:dox(hide)
override function closeSubState()
{
var event = scripts.event("onSubstateClose", EventManager.get(StateEvent).recycle(subState));
var event = gameAndCharsEvent("onSubstateClose", EventManager.get(StateEvent).recycle(subState));
if (event.cancelled) return;
if (paused)
@@ -1104,7 +1149,7 @@ class PlayState extends MusicBeatState
inst.resume();
vocals.resume();
}
scripts.call("onFocus");
gameAndCharsCall("onFocus");
updateDiscordPresence();
super.onFocus();
}
@@ -1117,7 +1162,7 @@ class PlayState extends MusicBeatState
inst.pause();
vocals.pause();
}
scripts.call("onFocusLost");
gameAndCharsCall("onFocusLost");
updateDiscordPresence();
super.onFocusLost();
}
@@ -1136,14 +1181,14 @@ class PlayState extends MusicBeatState
strumLine.vocals.play();
}
vocals.play();
scripts.call("onVocalsResync");
gameAndCharsCall("onVocalsResync");
}
/**
* Pauses the game.
*/
public function pauseGame() {
var e = scripts.event("onGamePause", new CancellableEvent());
var e = gameAndCharsEvent("onGamePause", new CancellableEvent());
if (e.cancelled) return;
persistentUpdate = false;
@@ -1151,7 +1196,7 @@ class PlayState extends MusicBeatState
paused = true;
// 1 / 1000 chance for Gitaroo Man easter egg
if (allowGitaroo && FlxG.random.bool(0.1))
if (allowGitaroo && FlxG.random.bool(Flags.GITAROO_CHANCE))
{
// gitaroo man easter egg
FlxG.switchState(new GitarooPause());
@@ -1208,7 +1253,7 @@ class PlayState extends MusicBeatState
}
function updateIconPositions() {
var iconOffset:Int = 26;
var iconOffset = Flags.ICON_OFFSET;
var center:Float = healthBar.x + healthBar.width * FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0);
@@ -1267,8 +1312,9 @@ class PlayState extends MusicBeatState
}
if (doIconBop) {
iconP1.scale.set(lerp(iconP1.scale.x, 1, 0.33), lerp(iconP1.scale.y, 1, 0.33));
iconP2.scale.set(lerp(iconP2.scale.x, 1, 0.33), lerp(iconP2.scale.y, 1, 0.33));
var iconLerp = Flags.ICON_LERP;
iconP1.scale.set(lerp(iconP1.scale.x, 1, iconLerp), lerp(iconP1.scale.y, 1, iconLerp));
iconP2.scale.set(lerp(iconP2.scale.x, 1, iconLerp), lerp(iconP2.scale.y, 1, iconLerp));
iconP1.updateHitbox();
iconP2.updateHitbox();
@@ -1287,7 +1333,7 @@ class PlayState extends MusicBeatState
var instTime = FlxG.sound.music.time;
var isOffsync = vocals.time != instTime || [for(strumLine in strumLines.members) strumLine.vocals.time != instTime].contains(true);
__vocalOffsetViolation = Math.max(0, __vocalOffsetViolation + (isOffsync ? elapsed : -elapsed / 2));
if (__vocalOffsetViolation > 25) {
if (__vocalOffsetViolation > Flags.VOCAL_OFFSET_VIOLATION_THRESHOLD) {
resyncVocals();
__vocalOffsetViolation = 0;
}
@@ -1312,7 +1358,7 @@ class PlayState extends MusicBeatState
pos.x /= r;
pos.y /= r;
var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], r));
var event = gameAndCharsEvent("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], r));
if (!event.cancelled)
camFollow.setPosition(pos.x, pos.y);
}
@@ -1357,10 +1403,12 @@ class PlayState extends MusicBeatState
public var scrollSpeedTween:FlxTween = null;
public function executeEvent(event:ChartEvent) @:privateAccess {
if (event == null) return;
if (event.params == null) return;
if (event == null || event.params == null) return;
if (scripts.event("onEvent", EventManager.get(EventGameEvent).recycle(event)).cancelled) return;
var e = EventManager.get(EventGameEvent).recycle(event);
gameAndCharsEvent("onEvent", e);
if (e.cancelled) return;
var event = e.event;
switch(event.name) {
case "HScript Call":
@@ -1420,11 +1468,11 @@ class PlayState extends MusicBeatState
*/
public function gameOver(?character:Character, ?deathCharID:String, ?gameOverSong:String, ?lossSFX:String, ?retrySFX:String) {
var charToUse:Character = character.getDefault(opponentMode ? dad : boyfriend); // Imma still make it check null later just in case dad or bf are also null for some weird scripts - Nex
var event:GameOverEvent = scripts.event("onGameOver", EventManager.get(GameOverEvent).recycle(
var event:GameOverEvent = gameAndCharsEvent("onGameOver", EventManager.get(GameOverEvent).recycle(
charToUse == null ? 0 : charToUse.x,
charToUse == null ? 0 : charToUse.y,
charToUse,
deathCharID.getDefault(charToUse != null ? charToUse.gameOverCharacter : Character.FALLBACK_DEAD_CHARACTER),
deathCharID.getDefault(charToUse != null ? charToUse.gameOverCharacter : Flags.DEFAULT_GAMEOVER_CHARACTER),
charToUse != null ? charToUse.isPlayer : true,
gameOverSong.getDefault(this.gameOverSong),
lossSFX.getDefault(this.lossSFX),
@@ -1449,7 +1497,7 @@ class PlayState extends MusicBeatState
openSubState(new GameOverSubstate(event.x, event.y, event.deathCharID, event.isPlayer, event.gameOverSong, event.lossSFX, event.retrySFX));
scripts.event("onPostGameOver", event);
gameAndCharsEvent("onPostGameOver", event);
}
/**
@@ -1457,7 +1505,7 @@ class PlayState extends MusicBeatState
*/
public function endSong():Void
{
scripts.call("onSongEnd");
gameAndCharsCall("onSongEnd");
canPause = false;
inst.volume = 0;
vocals.volume = 0;
@@ -1578,7 +1626,7 @@ class PlayState extends MusicBeatState
var directionID:Null<Int> = note == null ? direction : note.strumID;
if (playerID == null || directionID == null || playerID == -1) return;
var event:NoteMissEvent = scripts.event("onPlayerMiss", EventManager.get(NoteMissEvent).recycle(note, -10, 1, muteVocalsOnMiss, note != null ? -0.0475 : -0.04, Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2), note == null, combo > 5, "sad", true, true, "miss", strumLines.members[playerID].characters, playerID, note != null ? note.noteType : null, directionID, 0));
var event:NoteMissEvent = gameAndCharsEvent("onPlayerMiss", EventManager.get(NoteMissEvent).recycle(note, -10, 1, muteVocalsOnMiss, note != null ? -0.0475 : -0.04, Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2), note == null, combo > 5, "sad", true, true, "miss", strumLines.members[playerID].characters, playerID, note != null ? note.noteType : null, directionID, 0));
strumLine.onMiss.dispatch(event);
if (event.cancelled) return;
@@ -1662,11 +1710,13 @@ class PlayState extends MusicBeatState
var event:NoteHitEvent;
if (strumLine != null && !strumLine.cpu)
event = scripts.event("onPlayerHit", EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick"));
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick");
else
event = scripts.event("onDadHit", EventManager.get(NoteHitEvent).recycle(false, false, false, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false));
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false);
event.deleteNote = !note.isSustainNote; // work around, to allow sustain notes to be deleted
event = scripts.event(strumLine != null && !strumLine.cpu ? "onPlayerHit" : "onDadHit", event);
strumLine.onHit.dispatch(event);
scripts.event("onNoteHit", event);
gameAndCharsEvent("onNoteHit", event);
if (!event.cancelled) {
if (!note.isSustainNote) {
@@ -1681,7 +1731,8 @@ class PlayState extends MusicBeatState
if (event.showRating || (event.showRating == null && event.player))
{
displayCombo(event);
displayRating(event.rating, event);
if (event.displayRating)
displayRating(event.rating, event);
ratingNum += 1;
}
}
@@ -1711,7 +1762,7 @@ class PlayState extends MusicBeatState
}
}
if (event.deleteNote && !note.isSustainNote) strumLine.deleteNote(note);
if (event.deleteNote) strumLine.deleteNote(note);
}
public function displayRating(myRating:String, ?evt:NoteHitEvent = null):Void {
@@ -1740,14 +1791,12 @@ class PlayState extends MusicBeatState
}
public function displayCombo(?evt:NoteHitEvent = null):Void {
var hasEvent = evt != null;
var pre:String = hasEvent ? evt.ratingPrefix : "";
var suf:String = hasEvent ? evt.ratingSuffix : "";
if (minDigitDisplay >= 0 && (combo == 0 || combo >= minDigitDisplay)) {
var hasEvent = evt != null;
var pre:String = hasEvent ? evt.ratingPrefix : "";
var suf:String = hasEvent ? evt.ratingSuffix : "";
var separatedScore:String = Std.string(combo).addZeros(3);
if (combo == 0 || combo >= 10) {
if (combo >= 10) {
if (evt.displayCombo) {
var comboSpr:FlxSprite = comboGroup.recycleLoop(FlxSprite).loadAnimatedGraphic(Paths.image('${pre}combo${suf}'));
comboSpr.resetSprite(comboGroup.x, comboGroup.y);
comboSpr.acceleration.y = 600;
@@ -1769,6 +1818,7 @@ class PlayState extends MusicBeatState
});
}
var separatedScore:String = Std.string(combo).addZeros(3);
for (i in 0...separatedScore.length)
{
var numScore:FlxSprite = comboGroup.recycleLoop(FlxSprite).loadAnimatedGraphic(Paths.image('${pre}num${separatedScore.charAt(i)}${suf}'));
@@ -1820,14 +1870,15 @@ class PlayState extends MusicBeatState
if (camZoomingInterval < 1) camZoomingInterval = 1;
if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom && curBeat % camZoomingInterval == 0)
{
FlxG.camera.zoom += 0.015 * camZoomingStrength;
camHUD.zoom += 0.03 * camZoomingStrength;
FlxG.camera.zoom += Flags.CAM_BOP_STRENGTH * camZoomingStrength;
camHUD.zoom += Flags.HUD_BOP_STRENGTH * camZoomingStrength;
}
if (doIconBop)
{
iconP1.scale.set(1.2, 1.2);
iconP2.scale.set(1.2, 1.2);
var iconScale = Flags.BOP_ICON_SCALE;
iconP1.scale.set(iconScale, iconScale);
iconP2.scale.set(iconScale, iconScale);
iconP1.updateHitbox();
iconP2.updateHitbox();
@@ -1915,7 +1966,8 @@ class PlayState extends MusicBeatState
* @param weekData Week Data
* @param difficulty Week Difficulty
*/
public static function loadWeek(weekData:WeekData, difficulty:String = "normal") {
public static function loadWeek(weekData:WeekData, ?difficulty:String) {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
storyWeek = weekData;
storyPlaylist = [for(e in weekData.songs) e.name];
isStoryMode = true;
@@ -1935,7 +1987,8 @@ class PlayState extends MusicBeatState
* @param opponentMode Whenever opponent mode is on
* @param coopMode Whenever co-op mode is on.
*/
public static function loadSong(_name:String, _difficulty:String = "normal", _opponentMode:Bool = false, _coopMode:Bool = false) {
public static function loadSong(_name:String, ?_difficulty:String, _opponentMode:Bool = false, _coopMode:Bool = false) {
if (_difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
isStoryMode = false;
opponentMode = _opponentMode;
chartingMode = false;
+1 -1
View File
@@ -43,7 +43,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
Logs.trace('Couldn\'t parse splash data for "${path}": ${e.toString()}', ERROR);
valid = false;
}
maxSize = 8;
maxSize = Flags.MAX_SPLASHES;
}
var _scale:Float = 1.0;
+5 -3
View File
@@ -1,6 +1,6 @@
package funkin.game;
import funkin.backend.scripting.events.SplashShowEvent;
import funkin.backend.scripting.events.splash.*;
class SplashHandler extends FlxTypedGroup<FunkinSprite> {
/**
@@ -42,13 +42,15 @@ class SplashHandler extends FlxTypedGroup<FunkinSprite> {
var __grp:SplashGroup;
public function showSplash(name:String, strum:Strum) {
__grp = getSplashGroup(name);
var event = EventManager.get(SplashShowEvent).recycle(name, __grp.showOnStrum(strum), strum, __grp);
event = PlayState.instance.scripts.event("onSplashShown", event);
event = PlayState.instance.gameAndCharsEvent("onSplashShown", event);
if (!event.cancelled)
add(event.splash);
// max 8 rendered splashes
while(members.length > 8)
while(members.length > Flags.MAX_SPLASHES)
remove(members[0], true);
}
}
+4 -6
View File
@@ -8,8 +8,7 @@ import funkin.backend.utils.XMLUtil.XMLImportedScriptInfo;
import funkin.backend.system.interfaces.IBeatReceiver;
import funkin.backend.scripting.DummyScript;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.StageNodeEvent;
import funkin.backend.scripting.events.StageXMLEvent;
import funkin.backend.scripting.events.stage.*;
import funkin.backend.system.interfaces.IBeatReceiver;
import haxe.io.Path;
import haxe.xml.Access;
@@ -106,8 +105,8 @@ class Stage extends FlxBasic implements IBeatReceiver {
}
if (PlayState.instance == state) {
event = EventManager.get(StageXMLEvent).recycle(this, xml, elems);
elems = PlayState.instance.scripts.event("onStageXMLParsed", event).elems;
event = EventManager.get(StageXMLEvent).recycle(this, stageXML, elems);
elems = PlayState.instance.gameAndCharsEvent("onStageXMLParsed", event).elems;
}
if(onXMLLoaded != null) {
elems = onXMLLoaded(xml, elems);
@@ -163,7 +162,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
}
if(PlayState.instance == state) {
sprite = PlayState.instance.scripts.event("onStageNodeParsed", EventManager.get(StageNodeEvent).recycle(this, node, sprite, node.name)).sprite;
sprite = PlayState.instance.gameAndCharsEvent("onStageNodeParsed", EventManager.get(StageNodeEvent).recycle(this, node, sprite, node.name)).sprite;
}
if(onNodeLoaded != null) {
sprite = onNodeLoaded(node, sprite);
@@ -213,7 +212,6 @@ class Stage extends FlxBasic implements IBeatReceiver {
if(onXMLPostLoaded != null) {
elems = onXMLPostLoaded(xml, elems);
}
}
public static function getDefaultPos(name:String):StageCharPosInfo {
+13 -9
View File
@@ -5,7 +5,7 @@ import flixel.sound.FlxSound;
import flixel.tweens.FlxTween;
import flixel.util.FlxSignal.FlxTypedSignal;
import funkin.backend.chart.ChartData;
import funkin.backend.scripting.events.*;
import funkin.backend.scripting.events.note.*;
import funkin.backend.system.Conductor;
import funkin.backend.system.Controls;
@@ -153,9 +153,11 @@ class StrumLine extends FlxTypedGroup<Strum> {
if(scrollSpeed == null) if (PlayState.instance != null) scrollSpeed = PlayState.instance.scrollSpeed;
if(scrollSpeed == null) scrollSpeed = 1;
// TODOD: Make this work by accounting zoom and scrollspeed changes - Nex
// TODO: Make this work by accounting zoom and scrollspeed changes - Nex
/*var limit = FlxG.height / 0.45;
notes.limit = limit / scrollSpeed;*/
notes.limit = limit / scrollSpeed;
OR
notes.limit = Flags.DEFAULT_NOTE_MS_LIMIT / scrollSpeed;*/
}
public override function update(elapsed:Float) {
@@ -250,7 +252,8 @@ class StrumLine extends FlxTypedGroup<Strum> {
__justReleased.push(s.__getJustReleased(this));
}
var event = PlayState.instance.scripts.event("onInputUpdate", EventManager.get(InputSystemEvent).recycle(__pressed, __justPressed, __justReleased, this, id));
var event = EventManager.get(InputSystemEvent).recycle(__pressed, __justPressed, __justReleased, this, id);
if (PlayState.instance != null) PlayState.instance.gameAndCharsEvent("onInputUpdate", event);
if (event.cancelled) return;
__pressed = CoolUtil.getDefault(event.pressed, []);
@@ -285,13 +288,14 @@ class StrumLine extends FlxTypedGroup<Strum> {
forEach(function(str:Strum) {
str.updatePlayerInput(str.__getPressed(this), str.__getJustPressed(this), str.__getJustReleased(this));
});
PlayState.instance.scripts.call("onPostInputUpdate");
PlayState.instance.gameAndCharsCall("onPostInputUpdate");
}
public inline function addHealth(health:Float)
PlayState.instance.health += health * (opponentSide ? -1 : 1);
public inline function generateStrums(amount:Int = 4) {
public inline function generateStrums(amount:Int = -1) {
if(amount == -1) amount = Flags.DEFAULT_STRUM_AMOUNT;
for (i in 0...amount)
add(createStrum(i));
}
@@ -319,7 +323,7 @@ class StrumLine extends FlxTypedGroup<Strum> {
var event = EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix);
event.__doAnimation = !MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true);
event = PlayState.instance.scripts.event("onStrumCreation", event);
if (PlayState.instance != null) event = PlayState.instance.gameAndCharsEvent("onStrumCreation", event);
if (!event.cancelled) {
babyArrow.frames = Paths.getFrames(event.sprite);
@@ -329,7 +333,7 @@ class StrumLine extends FlxTypedGroup<Strum> {
babyArrow.animation.addByPrefix('red', 'arrowRIGHT');
babyArrow.antialiasing = true;
babyArrow.setGraphicSize(Std.int((babyArrow.width * 0.7) * strumScale));
babyArrow.setGraphicSize(Std.int((babyArrow.width * Flags.DEFAULT_NOTE_SCALE) * strumScale));
babyArrow.animation.addByPrefix('static', 'arrow${event.animPrefix.toUpperCase()}');
babyArrow.animation.addByPrefix('pressed', '${event.animPrefix} press', 24, false);
@@ -350,7 +354,7 @@ class StrumLine extends FlxTypedGroup<Strum> {
insert(i, babyArrow);
PlayState.instance.scripts.event("onPostStrumCreation", event);
if (PlayState.instance != null) PlayState.instance.gameAndCharsEvent("onPostStrumCreation", event);
return babyArrow;
}
+62 -3
View File
@@ -1,18 +1,77 @@
package funkin.game.cutscenes;
import funkin.backend.scripting.events.NameEvent;
import funkin.menus.PauseSubState;
/**
* Substate made for cutscenes.
*/
class Cutscene extends MusicBeatSubstate {
var __callback:Void->Void;
var __pausable:Bool;
var game:PlayState = PlayState.instance;
public function new(callback:Void->Void) {
super();
__callback = callback;
public var paused:Bool = false;
public var pauseItems:Array<String>;
public var skippable(default, set):Bool;
public function set_skippable(val:Bool):Bool {
if(!val) pauseItems.remove('Skip Cutscene');
else if(!pauseItems.contains('Skip Cutscene')) pauseItems.insert(1, 'Skip Cutscene');
return skippable = val;
}
public function new(callback:Void->Void, allowPause:Bool = true, canSkip:Bool = true) {
super();
canOpenCustomTransition = true;
__callback = callback;
__pausable = allowPause;
pauseItems = Flags.DEFAULT_CUTSCENE_PAUSE_ITEMS;
skippable = canSkip;
}
public override function update(elapsed:Float) {
super.update(elapsed);
if (pauseCheck()) pauseCutscene();
}
public function pauseCheck():Bool
return controls.PAUSE && __pausable;
var _before:Array<Bool> = [false, true];
public function pauseCutscene() {
_before = [game.persistentUpdate, game.persistentDraw];
game.persistentUpdate = persistentUpdate = false;
game.persistentDraw = persistentDraw = true;
paused = true;
openSubState(new PauseSubState(pauseItems, selectPauseOption));
}
public override function closeSubState() {
if (paused) {
game.persistentUpdate = _before[0];
game.persistentDraw = _before[1];
paused = false;
}
super.closeSubState();
}
public function onSkipCutscene(event:NameEvent) close();
public function onRestartCutscene(event:NameEvent) event.name = "Restart Song"; // Making the whole PlayState restart just for precaution - Nex
public function onResumeCutscene(event:NameEvent) event.name = "Resume";
public function selectPauseOption(event:NameEvent) {
switch(event.name) {
case 'Skip Cutscene': onSkipCutscene(event);
case 'Restart Cutscene': onRestartCutscene(event);
case 'Resume Cutscene': onResumeCutscene(event);
}
return true;
}
public override function close() {
@@ -13,7 +13,7 @@ import haxe.xml.Access;
/**
* Substate made for dialogue cutscenes. To use it in a scripted cutscene, call `startDialogue`.
*/
class DialogueCutscene extends Cutscene {
class DialogueCutscene extends ScriptedCutscene {
public var dialoguePath:String;
public var dialogueData:Access;
@@ -28,23 +28,29 @@ class DialogueCutscene extends Cutscene {
public var curMusic:FlxSound = null;
public static var cutscene:DialogueCutscene;
public var dialogueScript:Script;
public var dialogueScript(get, set):Script;
public function set_curLine(val:DialogueLine) {
lastLine = curLine;
return curLine = val;
}
// Backwards comapt funcs - Nex
public function set_dialogueScript(val:Script) return script = val;
public function get_dialogueScript() return script;
public function new(dialoguePath:String, callback:Void->Void) {
super(callback);
this.dialoguePath = dialoguePath;
super(this.dialoguePath = dialoguePath, callback);
camera = dialogueCamera = new FlxCamera();
dialogueCamera.bgColor = 0;
FlxG.cameras.add(dialogueCamera, false);
dialogueScript = Script.create(Paths.script(Path.withoutExtension(dialoguePath), null, dialoguePath.startsWith('assets')));
dialogueScript.setParent(cutscene = this);
dialogueScript.load();
cutscene = this;
}
public override function onErrorScriptLoading() {
Logs.trace('Could not find script for dialogue cutscene at "${scriptPath}"', WARNING, YELLOW);
}
var parentDisabler:FunkinParentDisabler;
@@ -89,7 +95,7 @@ class DialogueCutscene extends Cutscene {
musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.speed).getDefault(0.8)) : null,
changeMusic: node.has.changeMusic ? FlxG.sound.load(Paths.music(node.att.changeMusic), volume, true) : null,
playSound: node.has.playSound ? FlxG.sound.load(Paths.sound(node.att.playSound)) : null,
nextSound: node.has.nextSound ? FlxG.sound.load(Paths.music(node.att.nextSound)) : null,
nextSound: node.has.nextSound ? FlxG.sound.load(Paths.sound(node.att.nextSound)) : null,
textSound: null
};
@@ -110,22 +116,21 @@ class DialogueCutscene extends Cutscene {
next(true);
} catch(e) {
Logs.trace('Error while loading dialogue at ${dialoguePath}: ${e.toString()}', ERROR);
trace(CoolUtil.getLastExceptionStack());
var message:String = e.toString();
Logs.trace('Error while loading dialogue at ${dialoguePath}: $message', ERROR, RED);
dialogueScript.call("loadingError", [message]);
close();
}
dialogueScript.call("postCreate");
}
public override function beatHit(curBeat:Int) {
super.beatHit(curBeat);
dialogueScript.call("beatHit", [curBeat]);
public override function pauseCheck():Bool {
return super.pauseCheck() && !controls.ACCEPT; // Avoids next() and pause() being called at the same time - Nex
}
public override function update(elapsed:Float) {
super.update(elapsed);
dialogueScript.call("update", [elapsed]);
if(controls.ACCEPT) {
if(dialogueBox.dialogueEnded) next();
else dialogueBox.text.skip();
@@ -138,8 +143,8 @@ class DialogueCutscene extends Cutscene {
public var canProceed:Bool = true;
public function next(playFirst:Bool = false) {
var event = EventManager.get(DynamicEvent).recycle(playFirst);
dialogueScript.call("next", [playFirst]);
var event = EventManager.get(DialogueNextLineEvent).recycle(playFirst);
dialogueScript.call("next", [event]);
if(event.cancelled || !canProceed) return;
if ((curLine = dialogueLines.shift()) == null) {
@@ -154,7 +159,7 @@ class DialogueCutscene extends Cutscene {
}
if (curLine.callback != null)
dialogueScript.call(curLine.callback, [playFirst]);
dialogueScript.call(curLine.callback, [event.playFirst]);
for (k=>c in charMap)
if (k != curLine.char)
@@ -167,8 +172,8 @@ class DialogueCutscene extends Cutscene {
dialogueBox.popupChar(char, force);
}
var finalSuffix:String = playFirst && dialogueBox.hasAnimation(curLine.bubble + "-firstOpen") ? "-firstOpen" : dialogueBox.hasAnimation(curLine.bubble + "-open") ? "-open" : "";
dialogueBox.playBubbleAnim(curLine.bubble, finalSuffix, curLine.text, curLine.format, curLine.speed, curLine.nextSound, curLine.textSound != null ? [curLine.textSound] : null, finalSuffix == "-firstOpen" || finalSuffix == "-open", !playFirst);
var finalSuffix:String = event.playFirst && dialogueBox.hasAnimation(curLine.bubble + "-firstOpen") ? "-firstOpen" : dialogueBox.hasAnimation(curLine.bubble + "-open") ? "-open" : "";
dialogueBox.playBubbleAnim(curLine.bubble, finalSuffix, curLine.text, curLine.format, curLine.speed, curLine.nextSound, curLine.textSound != null ? [curLine.textSound] : null, finalSuffix == "-firstOpen" || finalSuffix == "-open", !event.playFirst);
if(curLine.playSound != null) curLine.playSound.play();
if(curLine.changeMusic != null) {
@@ -178,7 +183,7 @@ class DialogueCutscene extends Cutscene {
curMusic.fadeIn(1, 0, curMusic.volume);
} else if(curLine.musicVolume != null && curMusic != null) curMusic.volume = curLine.musicVolume;
dialogueScript.call("postNext", [playFirst]);
dialogueScript.call("postNext", [event]);
}
public override function close() {
@@ -193,8 +198,6 @@ class DialogueCutscene extends Cutscene {
public override function destroy() {
if(curMusic != null && !curMusic.persist) curMusic.destroy();
dialogueScript.call("destroy");
dialogueScript.destroy();
super.destroy();
cutscene = null;
@@ -215,4 +218,4 @@ typedef DialogueLine = {
var nextSound:FlxSound;
var textSound:FlxSound;
var changeDefAnim:String;
}
}
@@ -1,20 +1,25 @@
package funkin.game.cutscenes;
import haxe.io.Path;
import flixel.FlxSubState;
import funkin.backend.scripting.DummyScript;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.CancellableEvent;
import funkin.backend.scripting.events.NameEvent;
import funkin.backend.scripting.events.StateEvent;
/**
* Substate made for scripted cutscenes.
* To add cutscenes to your songs, add a `cutscene.hx` file in your song's directory (ex: `songs/song/cutscene.hx`)
*/
class ScriptedCutscene extends Cutscene {
var scriptPath:String;
var script:Script;
var scriptPath:String;
public function new(scriptPath:String, callback:Void->Void) {
super(callback);
script = Script.create(this.scriptPath = scriptPath);
script = Script.create(this.scriptPath = Paths.script(Path.withoutExtension(scriptPath), null, scriptPath.startsWith('assets')));
script.setPublicMap(PlayState.instance.scripts.publicVariables);
script.setParent(this);
script.load();
@@ -22,32 +27,91 @@ class ScriptedCutscene extends Cutscene {
public override function create() {
super.create();
trace("fuck you");
script.call("create");
if (Std.isOfType(script, DummyScript)) {
Logs.trace('Could not find script for scripted cutscene at ${scriptPath}', ERROR, RED);
close();
}
if(Std.isOfType(script, DummyScript))
onErrorScriptLoading();
}
public override function update(elapsed:Float) {
public function onErrorScriptLoading() {
Logs.trace('Could not find script for scripted cutscene at "${scriptPath}"', ERROR, RED);
close();
}
public override function pauseCheck():Bool
{
var shouldClose = super.pauseCheck();
script.call("pauseCheck", [shouldClose]);
return shouldClose;
}
public override function update(elapsed:Float)
{
script.call("update", [elapsed]);
super.update(elapsed);
script.call("postUpdate", [elapsed]);
}
public override function beatHit(curBeat:Int) {
public override function pauseCutscene()
{
var event = new CancellableEvent();
script.call("pauseCutscene", [event]);
if(!event.cancelled) super.pauseCutscene();
}
public override function onSkipCutscene(event:NameEvent)
{
script.call("onSkipCutscene", [event]);
if(!event.cancelled) super.onSkipCutscene(event);
}
public override function onRestartCutscene(event:NameEvent)
{
script.call("onRestartCutscene", [event]);
if(!event.cancelled) super.onRestartCutscene(event);
}
public override function onResumeCutscene(event:NameEvent)
{
script.call("onResumeCutscene", [event]);
if(!event.cancelled) super.onResumeCutscene(event);
}
public override function measureHit(curMeasure:Int)
{
super.measureHit(curMeasure);
script.call("measureHit", [curMeasure]);
}
public override function beatHit(curBeat:Int)
{
super.beatHit(curBeat);
script.call("beatHit", [curBeat]);
}
public override function stepHit(curStep:Int) {
public override function stepHit(curStep:Int)
{
super.stepHit(curStep);
script.call("stepHit", [curStep]);
}
public override function openSubState(sub:FlxSubState)
{
var event = EventManager.get(StateEvent).recycle(sub);
script.call("onSubstateClose", [event]);
if(!event.cancelled) super.openSubState(event.substate is FlxSubState ? cast event.substate : sub);
}
public override function closeSubState()
{
var event = EventManager.get(StateEvent).recycle(subState);
script.call("onSubstateOpen", [event]);
if(!event.cancelled) super.closeSubState();
}
public override function destroy() {
script.call("destroy");
script.destroy();
super.destroy();
}
+93 -83
View File
@@ -1,5 +1,6 @@
package funkin.game.cutscenes;
import flixel.tweens.FlxTween;
import flixel.addons.display.FlxBackdrop;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
@@ -12,7 +13,7 @@ import haxe.xml.Access;
import sys.io.File;
#end
#if VIDEO_CUTSCENES
import hxvlc.flixel.FlxVideo;
import hxvlc.flixel.FlxVideoSprite;
#end
/**
@@ -24,22 +25,24 @@ class VideoCutscene extends Cutscene {
var localPath:String;
#if VIDEO_CUTSCENES
var video:FlxVideo;
public var skippable:Bool = true;
#end
var video:FlxVideoSprite;
final mutex = new sys.thread.Mutex();
var cutsceneCamera:FlxCamera;
var text:FunkinText;
var loadingBackdrop:FlxBackdrop;
var videoReady:Bool = false;
private var __loaded:Bool = false;
var bg:FlxSprite;
var subtitle:FunkinText;
public var subtitles:Array<CutsceneSubtitle> = [];
var curSubtitle:Int = 0;
#end
public function new(path:String, callback:Void->Void) {
super(callback);
super(callback, false);
localPath = Assets.getPath(this.path = path);
}
@@ -48,21 +51,17 @@ class VideoCutscene extends Cutscene {
// TODO: get vlc to stop autoloading those goddamn subtitles (use different file ext??)
#if VIDEO_CUTSCENES
cutsceneCamera = new FlxCamera();
cutsceneCamera.bgColor = 0;
FlxG.cameras.add(cutsceneCamera, false);
#if VIDEO_CUTSCENES
parseSubtitles();
video = new FlxVideo();
video.onEndReached.add(function()
{
video.dispose();
FlxG.removeChild(video);
});
video.onEndReached.add(close);
FlxG.addChildBelowMouse(video);
add(video = new FlxVideoSprite());
video.antialiasing = true;
video.autoPause = false; // Imma handle it better inside this class, mainly because of the pause menu - Nex
video.bitmap.onEndReached.add(close);
//cover = new FlxSprite(0, FlxG.height * 0.85).makeSolid(FlxG.width + 50, FlxG.height + 50, 0xFF000000);
//cover.scrollFactor.set(0, 0);
@@ -77,41 +76,44 @@ class VideoCutscene extends Cutscene {
subtitle.alignment = CENTER;
subtitle.visible = false;
if (localPath.startsWith("[ZIP]")) {
text = new FunkinText(10, 10, Std.int(FlxG.width / 2), "Loading video...");
text.cameras = [cutsceneCamera];
text.visible = false;
@:privateAccess
text.regenGraphic();
add(text);
text = new FunkinText(10, 10, Std.int(FlxG.width / 2), "Loading video...");
text.cameras = [cutsceneCamera];
text.visible = false;
@:privateAccess
text.regenGraphic();
add(text);
loadingBackdrop = new FlxBackdrop(text.graphic, X);
loadingBackdrop.y = FlxG.height - 20 - loadingBackdrop.height;
loadingBackdrop.cameras = [cutsceneCamera];
add(loadingBackdrop);
loadingBackdrop = new FlxBackdrop(text.graphic, X);
loadingBackdrop.y = FlxG.height - 20 - loadingBackdrop.height;
loadingBackdrop.velocity.x = 70;
loadingBackdrop.cameras = [cutsceneCamera];
add(loadingBackdrop);
// ZIP PATH: EXPORT
// TODO: this but better and more ram friendly
localPath = './.temp/video-${curVideo++}.mp4';
Main.execAsync(function() {
loadingBackdrop.alpha = 0;
FlxTween.tween(loadingBackdrop, {alpha: 1}, 0.5, {ease: FlxEase.sineInOut});
Main.execAsync(function() {
if (localPath.startsWith("[ZIP]")) {
// ZIP PATH: EXPORT
// TODO: this but better and more ram friendly
localPath = './.temp/video-${curVideo++}.mp4';
File.saveBytes(localPath, Assets.getBytes(path));
videoReady = true;
});
} else {
if (video.load(localPath))
new FlxTimer().start(0.001, function(tmr:FlxTimer) {
video.play();
});
else
close();
}
}
if(video.load(localPath)) new FlxTimer().start(0.001, function(_) {mutex.acquire(); onReady(); mutex.release();});
else { mutex.acquire(); close(); mutex.release(); }
});
add(bg);
add(subtitle);
#end
cameras = [cutsceneCamera];
#else
close();
#end
}
#if VIDEO_CUTSCENES
public function parseSubtitles() {
var subtitlesPath = '${Path.withoutExtension(path)}.srt';
@@ -141,27 +143,23 @@ class VideoCutscene extends Cutscene {
}
if (subtitleText.length <= 0) continue; // empty subtitle, skipping
var lastSub = subtitles.last();
if (lastSub != null && lastSub.subtitle == "" && lastSub.time > beginTime)
if (lastSub != null && lastSub.subtitle == "" && lastSub.time >= beginTime)
subtitles.pop(); // remove last subtitle auto reset to prevent bugs
subtitles.push({
subtitle: subtitleText.join("."),
time: beginTime * 1000,
color: 0xFFFFFFFF // todo
subtitle: subtitleText.join(""),
time: beginTime * 1000
});
subtitles.push({
subtitle: "",
time: endTime * 1000,
color: 0xFFFFFFFF
time: endTime * 1000
});
}
}
trace(subtitles);
}
public static function splitTime(str:String):Float {
if (str == null || str.trim() == "") return -1;
var multipliers:Array<Float> = [1, 60, 3600, 86400]; // no way a cutscene will last longer than days
var multipliers:Array<Float> = [1, 60, 3600, 86400, 31536000]; // no way a cutscene will last longer than years
var timeSplit:Array<Null<Float>> = [for(e in str.split(":")) Std.parseFloat(e.replace(",", "."))];
var time:Float = 0;
@@ -173,50 +171,62 @@ class VideoCutscene extends Cutscene {
return time;
}
public inline function onReady() {
FlxTween.cancelTweensOf(loadingBackdrop);
FlxTween.tween(loadingBackdrop, {alpha: 0}, 0.7, {ease: FlxEase.sineInOut, onComplete: function(_) {
loadingBackdrop.destroy();
text.destroy();
}});
__pausable = __loaded = true;
video.play();
}
public override function update(elapsed:Float) {
super.update(elapsed);
#if VIDEO_CUTSCENES
if (videoReady) {
videoReady = false;
if (video.load(localPath))
new FlxTimer().start(0.001, function(tmr:FlxTimer) {
video.play();
});
else
{
close();
if(loadingBackdrop != null) return;
if (loadingBackdrop != null)
loadingBackdrop.visible = false;
return;
}
if (loadingBackdrop != null)
loadingBackdrop.visible = false;
}
@:privateAccess
var time:Int64 = video.time;
var time:Int64 = video.bitmap.time;
var time:Float = FPHelper.i64ToDouble(time.low, time.high);
while (subtitles.length > 0 && subtitles[0].time < Math.round(FPHelper.i64ToDouble(time.low, time.high)))
setSubtitle(subtitles.shift());
if(curSubtitle < subtitles.length && subtitles[curSubtitle].time < time) {
setSubtitle(subtitles[curSubtitle]);
curSubtitle++;
}
}
if (loadingBackdrop != null) {
loadingBackdrop.x -= elapsed * FlxG.width * 0.5;
}
if (skippable && video.isPlaying && controls.ACCEPT) {
video.onEndReached.dispatch();
}
#else
close();
#end
@:dox(hide) override public function onFocus() {
if(FlxG.autoPause && !paused) video.resume();
super.onFocus();
}
@:dox(hide) override public function onFocusLost() {
if(FlxG.autoPause && !paused) video.pause();
super.onFocusLost();
}
public override function pauseCutscene() {
video.pause();
super.pauseCutscene();
}
public override function onResumeCutscene(event) {
video.resume();
super.onResumeCutscene(event);
}
public override function onRestartCutscene(event) {
closeSubState();
video.bitmap.position = curSubtitle = 0;
setSubtitle({time: -1, subtitle: ""});
video.resume();
}
public function setSubtitle(sub:CutsceneSubtitle) {
if (bg.visible = subtitle.visible = (sub.subtitle.length > 0)) {
subtitle.text = sub.subtitle;
subtitle.color = sub.color;
subtitle.screenCenter(X);
bg.scale.set(subtitle.width + 8, subtitle.height + 8);
bg.updateHitbox();
@@ -228,10 +238,10 @@ class VideoCutscene extends Cutscene {
FlxG.cameras.remove(cutsceneCamera, true);
super.destroy();
}
#end
}
typedef CutsceneSubtitle = {
var time:Float; // time in ms
var subtitle:String; // subtitle text
var color:FlxColor;
}
@@ -3,8 +3,7 @@ package funkin.game.cutscenes.dialogue;
import flixel.addons.text.FlxTypeText;
import flixel.sound.FlxSound;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.PlayAnimContext;
import funkin.backend.scripting.events.PlayAnimEvent;
import funkin.backend.scripting.events.sprite.*;
import funkin.backend.scripting.events.dialogue.*;
import haxe.xml.Access;
@@ -87,8 +86,11 @@ class DialogueBox extends FunkinSprite {
}
} catch(e) {
active = false;
Logs.trace('Couldn\'t load dialogue box "$name": ${e.toString()}', ERROR);
var message:String = e.toString();
Logs.trace('Couldn\'t load dialogue box "$name": $message', ERROR, RED);
dialogueBoxScript.call("loadingError", [message]);
}
defaultTextTypeSFX = [FlxG.sound.load(textTypeSFX)];
FlxG.sound.cache(nextSFX);
dialogueBoxScript.call("postCreate");
@@ -2,8 +2,7 @@ package funkin.game.cutscenes.dialogue;
import flixel.tweens.FlxTween;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.PlayAnimContext;
import funkin.backend.scripting.events.PlayAnimEvent;
import funkin.backend.scripting.events.sprite.*;
import funkin.backend.scripting.events.dialogue.*;
import haxe.xml.Access;
@@ -54,9 +53,10 @@ class DialogueCharacter extends FunkinSprite {
x = 0; y = 0;
} catch(e) {
var message:String = e.toString();
Logs.trace('Failed to load dialogue character $name: ${message}', ERROR);
Logs.trace('Failed to load dialogue character $name: ${message}', ERROR, RED);
dialogueCharScript.call("loadingError", [message]);
}
visible = false;
dialogueCharScript.call("postCreate");
}
+1 -1
View File
@@ -19,7 +19,7 @@ class BetaWarningState extends MusicBeatState {
disclaimer = new FunkinText(16, titleAlphabet.y + titleAlphabet.height + 10, FlxG.width - 32, "", 32);
disclaimer.alignment = CENTER;
disclaimer.applyMarkup('This engine is still in a *${Main.releaseCycle}* state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.\n\nPress ENTER to continue',
disclaimer.applyMarkup('This engine is still in a *${Flags.RELEASE_CYCLE}* state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.\n\nPress ENTER to continue',
[
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*")
]
+4 -3
View File
@@ -4,9 +4,10 @@ import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.backend.chart.Chart;
import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.backend.scripting.events.*;
import funkin.backend.scripting.events.menu.MenuChangeEvent;
import funkin.backend.scripting.events.menu.freeplay.*;
import funkin.game.HealthIcon;
import funkin.savedata.FunkinSave;
import funkin.backend.utils.FunkinSave;
using StringTools;
@@ -458,7 +459,7 @@ class FreeplaySonglist {
if (songsFound.length > 0) {
for(s in songsFound)
songs.push(Chart.loadChartMeta(s, "normal", source == MODS));
songs.push(Chart.loadChartMeta(s, Flags.DEFAULT_DIFFICULTY, source == MODS));
return false;
}
return true;
+5 -4
View File
@@ -4,7 +4,8 @@ import flixel.FlxState;
import flixel.effects.FlxFlicker;
import flixel.tweens.FlxTween;
import funkin.backend.FunkinText;
import funkin.backend.scripting.events.*;
import funkin.backend.scripting.events.menu.MenuChangeEvent;
import funkin.backend.scripting.events.NameEvent;
import funkin.menus.credits.CreditsMain;
import funkin.options.OptionsMenu;
import lime.app.Application;
@@ -24,7 +25,7 @@ class MainMenuState extends MusicBeatState
var camFollow:FlxObject;
var versionText:FunkinText;
public var canAccessDebugMenus:Bool = true;
public var canAccessDebugMenus:Bool = Flags.DEFAULT_CAN_ACCESS_DEBUG_MENUS;
override function create()
{
@@ -72,7 +73,7 @@ class MainMenuState extends MusicBeatState
FlxG.camera.follow(camFollow, null, 0.06);
versionText = new FunkinText(5, FlxG.height - 2, 0, 'Codename Engine v${Application.current.meta.get('version')}\nCommit ${funkin.backend.system.macros.GitCommitMacro.commitNumber} (${funkin.backend.system.macros.GitCommitMacro.commitHash})\n[${controls.getKeyName(SWITCHMOD)}] Open Mods menu\n');
versionText = new FunkinText(5, FlxG.height - 2, 0, '${Flags.VERSION_MESSAGE}\n${Flags.COMMIT_MESSAGE}\n[${controls.getKeyName(SWITCHMOD)}] Open Mods menu\n');
versionText.y -= versionText.height;
versionText.scrollFactor.set();
add(versionText);
@@ -158,7 +159,7 @@ class MainMenuState extends MusicBeatState
var event = event("onSelectItem", EventManager.get(NameEvent).recycle(daChoice));
if (event.cancelled) return;
switch (daChoice)
switch (event.name)
{
case 'story mode': FlxG.switchState(new StoryMenuState());
case 'freeplay': FlxG.switchState(new FreeplayState());
+13 -10
View File
@@ -5,15 +5,16 @@ import flixel.text.FlxText;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.events.MenuChangeEvent;
import funkin.backend.scripting.events.menu.MenuChangeEvent;
import funkin.backend.scripting.events.menu.pause.*;
import funkin.backend.scripting.events.NameEvent;
import funkin.backend.scripting.events.PauseCreationEvent;
import funkin.backend.system.Conductor;
import funkin.backend.utils.FunkinParentDisabler;
import funkin.editors.charter.Charter;
import funkin.menus.StoryMenuState;
import funkin.options.OptionsMenu;
import funkin.options.keybinds.KeybindsOptions;
import funkin.options.TreeMenu;
class PauseSubState extends MusicBeatSubstate
{
@@ -21,19 +22,22 @@ class PauseSubState extends MusicBeatSubstate
var grpMenuShit:FlxTypedGroup<Alphabet>;
var menuItems:Array<String> = ['Resume', 'Restart Song', 'Change Controls', 'Change Options', 'Exit to menu', "Exit to charter"];
var menuItems:Array<String>;
var curSelected:Int = 0;
var pauseMusic:FlxSound;
public var pauseScript:Script;
public var selectCall:NameEvent->Void; // Mainly for extern stuff that aren't scripts - Nex
public var game:PlayState = PlayState.instance; // shortcut
private var __cancelDefault:Bool = false;
public function new(x:Float = 0, y:Float = 0) {
public function new(?items:Array<String>, ?selectCall:NameEvent->Void) {
super();
menuItems = items != null ? items : Flags.DEFAULT_PAUSE_ITEMS;
this.selectCall = selectCall;
}
var parentDisabler:FunkinParentDisabler;
@@ -135,13 +139,11 @@ class PauseSubState extends MusicBeatSubstate
public function selectOption() {
var event = EventManager.get(NameEvent).recycle(menuItems[curSelected]);
if (selectCall != null) selectCall(event);
pauseScript.call("onSelectOption", [event]);
if (event.cancelled) return;
var daSelected:String = event.name;
switch (daSelected)
switch (event.name)
{
case "Resume":
close();
@@ -153,9 +155,10 @@ class PauseSubState extends MusicBeatSubstate
persistentDraw = false;
openSubState(new KeybindsOptions());
case "Change Options":
TreeMenu.lastState = PlayState;
FlxG.switchState(new OptionsMenu());
case "Exit to charter":
FlxG.switchState(new funkin.editors.charter.Charter(PlayState.SONG.meta.name, PlayState.difficulty, false));
FlxG.switchState(new Charter(PlayState.SONG.meta.name, PlayState.difficulty, false));
case "Exit to menu":
if (PlayState.chartingMode && Charter.undos.unsaved)
game.saveWarn(false);
@@ -206,4 +209,4 @@ class PauseSubState extends MusicBeatSubstate
item.alpha = 0.6;
}
}
}
}
+70 -86
View File
@@ -6,15 +6,20 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.backend.FunkinText;
import funkin.backend.scripting.events.*;
import funkin.savedata.FunkinSave;
import funkin.backend.scripting.events.menu.MenuChangeEvent;
import funkin.backend.scripting.events.menu.storymenu.*;
import funkin.backend.scripting.events.CancellableEvent;
import funkin.backend.utils.FunkinSave;
import haxe.io.Path;
import haxe.xml.Access;
class StoryMenuState extends MusicBeatState {
public var characters:Map<String, MenuCharacter> = [];
public var characters:Map<String, Access> = [];
public var weeks:Array<WeekData> = [];
// yes it supports parameters - Nex
public var scoreMessage:String = "WEEK SCORE:{0}";
public var scoreText:FlxText;
public var tracklist:FlxText;
public var weekTitle:FlxText;
@@ -23,18 +28,21 @@ class StoryMenuState extends MusicBeatState {
public var curWeek:Int = 0;
public var difficultySprites:Map<String, FlxSprite> = [];
public var weekBG:FlxSprite;
public var leftArrow:FlxSprite;
public var rightArrow:FlxSprite;
public var blackBar:FlxSprite;
public var weekBG:FlxSprite;
public var defColor:String = "#F9CF51";
public var interpColor:FlxInterpolateColor;
public var lerpScore:Float = 0;
public var intendedScore:Int = 0;
public var canSelect:Bool = true;
public var weekSprites:FlxTypedGroup<MenuItem>;
public var characterSprites:FlxTypedGroup<MenuCharacterSprite>;
public var characterSprites:FlxTypedGroup<FunkinSprite>;
//public var charFrames:Map<String, FlxFramesCollection> = [];
@@ -56,7 +64,7 @@ class StoryMenuState extends MusicBeatState {
weekTitle.alpha = 0.7;
weekBG = new FlxSprite(0, 56).makeSolid(FlxG.width, 400, 0xFFFFFFFF);
weekBG.color = 0xFFF9CF51;
weekBG.color = weeks.length > 0 ? weeks[0].bgColor : FlxColor.fromString(defColor);
weekBG.updateHitbox();
weekSprites = new FlxTypedGroup<MenuItem>();
@@ -85,15 +93,12 @@ class StoryMenuState extends MusicBeatState {
add(weekSprites);
for(e in [blackBar, scoreText, weekTitle, weekBG, tracklist]) {
e.antialiasing = true;
e.scrollFactor.set();
add(e);
}
characterSprites = new FlxTypedGroup<MenuCharacterSprite>();
for(i in 0...3) {
characterSprites.add(new MenuCharacterSprite(i));
}
add(characterSprites);
add(characterSprites = new FlxTypedGroup<FunkinSprite>());
for(i=>week in weeks) {
var spr:MenuItem = new MenuItem(0, (i * 120) + 480, 'menus/storymenu/weeks/${week.sprite}');
@@ -114,6 +119,14 @@ class StoryMenuState extends MusicBeatState {
}
}
interpColor = new FlxInterpolateColor(weekBG.color);
// default difficulty should be the middle difficulty in the array
// to be consistent with base game and whatnot, you know the drill
curDifficulty = Math.floor(weeks[0].difficulties.length * 0.5);
// debug stuff lol
Logs.trace('Middle Difficulty for Week 1 is ${weeks[0].difficulties[curDifficulty]} (ID: $curDifficulty)');
changeWeek(0, true);
DiscordUtil.call("onMenuLoaded", ["Story Menu"]);
@@ -125,7 +138,7 @@ class StoryMenuState extends MusicBeatState {
super.update(elapsed);
lerpScore = lerp(lerpScore, intendedScore, 0.5);
scoreText.text = 'WEEK SCORE:${Math.round(lerpScore)}';
scoreText.text = scoreMessage.replace("{0}", Std.string(Math.round(lerpScore)));
if (canSelect) {
if (leftArrow != null && leftArrow.exists) leftArrow.animation.play(controls.LEFT ? 'press' : 'idle');
@@ -145,6 +158,9 @@ class StoryMenuState extends MusicBeatState {
if (e != null && e.exists)
e.animation.play('idle');
}
interpColor.fpsLerpTo(weeks[curWeek].bgColor, 0.0625);
weekBG.color = interpColor.color;
}
public function goBack() {
@@ -163,18 +179,43 @@ class StoryMenuState extends MusicBeatState {
if (!force) CoolUtil.playMenuSFX();
for(k=>e in weekSprites.members) {
e.targetY = k - curWeek;
e.alpha = k == curWeek ? 1.0 : 0.6;
}
tracklist.text = 'TRACKS\n\n${[for(e in weeks[curWeek].songs) if (!e.hide) e.name.toUpperCase()].join('\n')}';
tracklist.text = 'TRACKS\n\n${[for(e in weeks[curWeek].songs) if (!e.hide) e.name].join('\n')}';
weekTitle.text = weeks[curWeek].name.getDefault("");
for(i in 0...3)
characterSprites.members[i].changeCharacter(characters[weeks[curWeek].chars[i]]);
if(characterSprites != null) for(i in 0...3) {
var curChar:FunkinSprite; var newChar:Access = characters[weeks[curWeek].chars[i]];
if(newChar == null) modifyCharacterAt(i);
else if((curChar = characterSprites.members[i]) == null || newChar.getAtt("name") != curChar.name) modifyCharacterAt(i, newChar);
}
changeDifficulty(0, true);
MemoryUtil.clearMinor();
}
public function modifyCharacterAt(i:Int, ?node:Access):FunkinSprite {
var old = characterSprites.members[i];
if(old != null) {
characterSprites.remove(old);
old.destroy();
}
if(node == null) return null;
var curChar:FunkinSprite = XMLUtil.createSpriteFromXML(node, "", BEAT);
curChar.offset.x += curChar.x; curChar.offset.y += curChar.y;
curChar.setPosition((FlxG.width * 0.25) * (1 + i) - 150, 70);
if(characterSprites != null) characterSprites.insert(i, curChar); // Making so many null checks abt this group just in case if mods destroy it - Nex
curChar.playAnim("idle", true, DANCE);
return curChar;
}
public override function beatHit(curBeat:Int) {
super.beatHit(curBeat);
if(characterSprites != null) characterSprites.forEachAlive(function(spr) spr.beatHit(curBeat));
}
var __oldDiffName = null;
public function changeDifficulty(change:Int, force:Bool = false) {
if (change == 0 && !force) return;
@@ -238,7 +279,8 @@ class StoryMenuState extends MusicBeatState {
sprite: week.getAtt('sprite').getDefault(weekName),
chars: [null, null, null],
songs: [],
difficulties: ['easy', 'normal', 'hard']
difficulties: ['easy', 'normal', 'hard'],
bgColor: FlxColor.fromString(week.getAtt("bgColor").getDefault(defColor))
};
var diffNodes = week.nodes.difficulty;
@@ -292,19 +334,14 @@ class StoryMenuState extends MusicBeatState {
} catch(e) {
Logs.trace('Story Menu: Cannot parse character "$charName.xml": ${Std.string(e)}`', ERROR);
}
if (char == null) return;
if (characters[charName] != null) return;
var charObj:MenuCharacter = {
spritePath: Paths.image(char.getAtt('sprite').getDefault('menus/storymenu/characters/${charName}')),
scale: Std.parseFloat(char.getAtt('scale')).getDefault(1),
xml: char,
offset: FlxPoint.get(
Std.parseFloat(char.getAtt('x')).getDefault(0),
Std.parseFloat(char.getAtt('y')).getDefault(0)
)
};
characters[charName] = charObj;
if(char != null && characters[charName] == null) {
if(!char.x.exists("name")) char.x.set("name", charName);
if(!char.x.exists("sprite")) char.x.set("sprite", 'menus/storymenu/characters/${charName}');
if(!char.x.exists("beatInterval")) char.x.set("beatInterval", "1");
if(!char.x.exists("updateHitbox")) char.x.set("updateHitbox", "true");
characters[charName] = char;
}
}
public function getWeeksFromSource(weeks:Array<String>, source:funkin.backend.assets.AssetsLibraryList.AssetSource) {
@@ -325,13 +362,6 @@ class StoryMenuState extends MusicBeatState {
return true;
}
public override function destroy() {
super.destroy();
for(e in characters)
if (e != null && e.offset != null)
e.offset.put();
}
public function selectWeek() {
var event = event("onWeekSelect", EventManager.get(WeekSelectEvent).recycle(weeks[curWeek], weeks[curWeek].difficulties[curDifficulty], curWeek, curDifficulty));
if (event.cancelled) return;
@@ -339,17 +369,16 @@ class StoryMenuState extends MusicBeatState {
canSelect = false;
CoolUtil.playMenuSFX(CONFIRM);
for(char in characterSprites)
if (char.animation.exists("confirm"))
char.animation.play("confirm");
if(characterSprites != null)
characterSprites.forEachAlive(function(spr) spr.playAnim("confirm", true, LOCK));
PlayState.loadWeek(weeks[curWeek], weeks[curWeek].difficulties[curDifficulty]);
PlayState.loadWeek(event.week, event.difficulty);
new FlxTimer().start(1, function(tmr:FlxTimer)
{
FlxG.switchState(new PlayState());
});
weekSprites.members[curWeek].startFlashing();
weekSprites.members[event.weekID].startFlashing();
}
}
@@ -360,6 +389,7 @@ typedef WeekData = {
var chars:Array<String>;
var songs:Array<WeekSong>;
var difficulties:Array<String>;
var bgColor:FlxColor;
}
typedef WeekSong = {
@@ -367,52 +397,6 @@ typedef WeekSong = {
var hide:Bool;
}
typedef MenuCharacter = {
var spritePath:String;
var xml:Access;
var scale:Float;
var offset:FlxPoint;
// var frames:FlxFramesCollection;
}
class MenuCharacterSprite extends FlxSprite
{
public var character:String;
var pos:Int;
public function new(pos:Int) {
super(0, 70);
this.pos = pos;
visible = false;
antialiasing = true;
}
public var oldChar:MenuCharacter = null;
public function changeCharacter(data:MenuCharacter) {
visible = (data != null);
if (!visible)
return;
if (oldChar != (oldChar = data)) {
CoolUtil.loadAnimatedGraphic(this, data.spritePath);
for(e in data.xml.nodes.anim) {
if (e.getAtt("name") == "idle")
animation.remove("idle");
XMLUtil.addXMLAnimation(this, e);
}
animation.play("idle");
scale.set(data.scale, data.scale);
updateHitbox();
offset.x += data.offset.x;
offset.y += data.offset.y;
x = (FlxG.width * 0.25) * (1 + pos) - 150;
}
}
}
class MenuItem extends FlxSprite
{
public var targetY:Float = 0;

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