Merge remote-tracking branch 'upstream/main'

This commit is contained in:
lunarcleint
2024-02-04 00:58:19 -07:00
94 changed files with 1323 additions and 394 deletions
+13 -5
View File
@@ -1,5 +1,6 @@
package funkin.backend;
import funkin.backend.utils.XMLUtil.BeatAnim;
import funkin.backend.utils.XMLUtil.AnimData;
import funkin.backend.utils.XMLUtil.IXMLEvents;
import flixel.system.FlxAssets.FlxGraphicAsset;
@@ -34,13 +35,17 @@ abstract XMLAnimType(Int)
class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements IOffsetCompatible implements IXMLEvents
{
public var spriteAnimType:XMLAnimType = NONE;
public var beatAnims:Array<String> = [];
public var beatAnims:Array<BeatAnim> = [];
public var name:String;
public var zoomFactor:Float = 1;
public var initialZoom:Float = 1;
public var debugMode:Bool = false;
public var animDatas:Map<String, AnimData> = [];
public var beatInterval:Int = 1;
public var beatOffset:Int = 0;
public var skipNegativeBeats:Bool = false;
public var animateAtlas:FlxAnimate;
@:noCompletion public var atlasPlayingAnim:String;
@:noCompletion public var atlasPath:String;
@@ -108,13 +113,16 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
moves = true;
}
private var countedBeat = 0;
public function beatHit(curBeat:Int)
{
if (beatAnims.length > 0)
if (beatAnims.length > 0 && (curBeat + beatOffset) % beatInterval == 0)
{
var anim = beatAnims[FlxMath.wrap(curBeat, 0, beatAnims.length - 1)];
if (anim != null && anim != "null" && anim != "none")
playAnim(anim);
if(skipNegativeBeats && curBeat < 0) return;
// TODO: find a solution without countedBeat
var anim = beatAnims[FlxMath.wrap(countedBeat++, 0, beatAnims.length - 1)];
if (anim.name != null && anim.name != "null" && anim.name != "none")
playAnim(anim.name, anim.forced);
}
}
+10 -1
View File
@@ -60,6 +60,15 @@ class BaseGameParser {
});
}
if (section.altAnim == null) section.altAnim = false;
if (altAnims != (altAnims = section.altAnim)) {
result.events.push({
time: curTime,
name: "Alt Animation Toggle",
params: [0, altAnims]
});
}
if (section.sectionNotes != null) for(note in section.sectionNotes) {
if (note[1] < 0) continue;
@@ -129,6 +138,6 @@ typedef SwagSection =
var mustHitSection:Bool;
var bpm:Float;
var changeBPM:Bool;
var altAnim:Bool;
var altAnim:Null<Bool>;
@:optional var camTarget:Null<Float>;
}
+80 -6
View File
@@ -9,8 +9,8 @@ import haxe.Http;
class GitHub {
/**
* Gets all the releases from a specific GitHub repository using the GitHub API.
* @param user
* @param repository
* @param user The user/group that owns the repository
* @param repository The repository name
* @return Releases
*/
public static function getReleases(user:String, repository:String, ?onError:Exception->Void):Array<GitHubRelease> {
@@ -29,6 +29,12 @@ class GitHub {
return [];
}
/**
* Gets the contributors list from a specific GitHub repository using the GitHub API.
* @param user The user/group that owns the repository
* @param repository The repository name
* @return Contributors List
*/
public static function getContributors(user:String, repository:String, ?onError:Exception->Void):Array<GitHubContributor> {
try {
var url = 'https://api.github.com/repos/${user}/${repository}/contributors';
@@ -45,6 +51,27 @@ class GitHub {
return [];
}
/**
* Gets a specific GitHub user/group using the GitHub API.
* @param user The user/group to get
* @return User/Group
*/
public static function getUser(user:String, ?onError:Exception->Void):GitHubUser {
try {
var url = 'https://api.github.com/users/$user';
var data = Json.parse(__requestOnGitHubServers(url));
if (Reflect.hasField(data, "documentation_url"))
throw __parseGitHubException(data);
return data;
} catch(e) {
if (onError != null)
onError(e);
}
return null;
}
/**
* Filters all releases gotten by `getReleases`
* @param releases Releases
@@ -58,29 +85,52 @@ class GitHub {
public static function __requestOnGitHubServers(url:String) {
var h = new Http(url);
h.setHeader("User-Agent", "request");
var r = null;
h.onStatus = function(s) {
if(isRedirect(s))
r = __requestOnGitHubServers(h.responseHeaders.get("Location"));
};
h.onData = function(d) {
r = d;
if(r == null) r = d;
}
h.onError = function(e) {
throw e;
}
h.request(false);
return r;
}
public static function __requestBytesOnGitHubServers(url:String) {
var h = new Http(url);
h.setHeader("User-Agent", "request");
var r = null;
h.onStatus = function(s) {
if(isRedirect(s))
r = __requestBytesOnGitHubServers(h.responseHeaders.get("Location"));
};
h.onBytes = function(d) {
r = d;
if(r == null) r = d;
}
h.onError = function(e) {
throw e;
}
h.request(false);
return r;
}
private static function isRedirect(status:Int):Bool {
switch (status) {
// 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex_isDumb
case 301 | 302 | 307 | 308 :
trace("Redirected with status code: " + status);
return true;
}
return false;
}
private static function __parseGitHubException(obj:Dynamic):GitHubException {
var msg:String = "(No message)";
var url:String = "(No API url)";
@@ -99,18 +149,33 @@ import haxe.Exception;
class GitHub {
/**
* Gets all the releases from a specific GitHub repository using the GitHub API.
* @param user
* @param repository
* @param user The user/group that owns the repository
* @param repository The repository name
* @return Releases
*/
public static function getReleases(user:String, repository:String, ?onError:Exception->Void):Array<GitHubRelease> {
return [];
}
/**
* Gets the contributors list from a specific GitHub repository using the GitHub API.
* @param user The user/group that owns the repository
* @param repository The repository name
* @return Contributors List
*/
public static function getContributors(user:String, repository:String, ?onError:Exception->Void):Array<GitHubContributor> {
return [];
}
/**
* Gets a specific GitHub user/group using the GitHub API.
* @param user The user/group to get
* @return User/Group
*/
public static function getUser(user:String, ?onError:Exception->Void):GitHubUser {
return null;
}
/**
* Filters all releases gotten by `getReleases`
* @param releases Releases
@@ -130,5 +195,14 @@ class GitHub {
private static function __parseGitHubException(obj:Dynamic):GitHubException {
return null;
}
private static function isRedirect(status:Int):Bool {
switch (status) {
// 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex_isDumb
case 301 | 302 | 307 | 308 :
trace("Redirected with status code: " + status);
return true;
}
return false;
}
}
#end
@@ -20,7 +20,7 @@ class ScriptsMacro {
"flixel.addons.plugin", "flixel.addons.text", "flixel.addons.tile", "flixel.addons.transition",
"flixel.addons.util",
// OTHER LIBRARIES & STUFF
"away3d", "flx3d",
#if THREE_D_SUPPORT "away3d", "flx3d", #end
#if VIDEO_CUTSCENES "hxvlc.flixel", "hxvlc.openfl", #end
// BASE HAXE
"DateTools", "EReg", "Lambda", "StringBuf", "haxe.crypto", "haxe.display", "haxe.exceptions", "haxe.extern", "scripting"
+20 -1
View File
@@ -28,6 +28,16 @@ class CoolUtil
public static function getLastExceptionStack():String {
return CallStack.toString(CallStack.exceptionStack());
}
/*
* Returns `v` if not null
* @param v The value
* @return A bool value
*/
public static inline function isNotNull(v:Null<Dynamic>):Bool {
return v != null && !isNaN(v);
}
/*
* Returns `v` if not null, `defaultValue` otherwise.
* @param v The value
@@ -135,7 +145,7 @@ class CoolUtil
/**
* Returns a string representation of a size, following this format: `1.02 GB`, `134.00 MB`
* @param size Size to convert ot string
* @param size Size to convert to string
* @return String Result string representation
*/
public static function getSizeString(size:Float):String {
@@ -149,6 +159,15 @@ class CoolUtil
return '${Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)}${labels[label]}';
}
/**
* Replaces in a string any kind of IP with `[Your IP]` making the string safer to trace.
* @param msg String to check and edit
* @return String Result without any kind of IP
*/
public static inline function removeIP(msg:String):String {
return ~/\d+.\d+.\d+.\d+/.replace(msg, "[Your IP]"); // For now its just IPs but who knows in the future.. - Nex_isDumb
}
/**
* Alternative linear interpolation function for each frame use, without worrying about framerate changes.
* @param v1 Begin value
+79 -30
View File
@@ -21,7 +21,7 @@ class XMLUtil {
*/
public static function applyXMLProperty(object:Dynamic, property:Access):ErrorCode {
if (!property.has.name || !property.has.type || !property.has.value) {
Logs.trace('Failed to apply XML property: XML Element misses name, type, or value attributes.', WARNING);
Logs.trace('Failed to apply XML property: XML Element is missing name, type, or value attributes.', WARNING);
return MISSING_PROPERTY;
}
@@ -72,54 +72,86 @@ class XMLUtil {
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite")}', null, true));
if (spr.frames != null && spr.frames.frames != null) {
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], 24, true);
spr.animation.play("idle");
spr.spriteAnimType = defaultAnimType;
if (node.has.type) {
spr.spriteAnimType = XMLAnimType.fromString(node.att.type, spr.spriteAnimType);
}
spr.spriteAnimType = defaultAnimType;
if (node.has.type)
spr.spriteAnimType = XMLAnimType.fromString(node.att.type, spr.spriteAnimType);
var x:Null<Float> = node.has.x ? Std.parseFloat(node.att.x) : null;
var y:Null<Float> = node.has.y ? Std.parseFloat(node.att.y) : null;
if (x != null) spr.x = x;
if (y != null) spr.y = y;
if(node.has.x) {
var x:Null<Float> = Std.parseFloat(node.att.x);
if (x.isNotNull()) spr.x = x;
}
if(node.has.y) {
var y:Null<Float> = Std.parseFloat(node.att.y);
if (y.isNotNull()) spr.y = y;
}
if (node.has.scroll) {
var scroll:Null<Float> = Std.parseFloat(node.att.scroll);
if (scroll != null) spr.scrollFactor.set(scroll, scroll);
if (scroll.isNotNull()) spr.scrollFactor.set(scroll, scroll);
} else {
if (node.has.scrollx) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrollx);
if (scroll != null) spr.scrollFactor.x = scroll;
if (scroll.isNotNull()) spr.scrollFactor.x = scroll;
}
if (node.has.scrolly) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrolly);
if (scroll != null) spr.scrollFactor.y = scroll;
if (scroll.isNotNull()) spr.scrollFactor.y = scroll;
}
}
if (node.has.skewx) {
var skew:Null<Float> = Std.parseFloat(node.att.skewx);
if (skew != null) spr.skew.x = skew;
if (skew.isNotNull()) spr.skew.x = skew;
}
if (node.has.skewy) {
var skew:Null<Float> = Std.parseFloat(node.att.skewy);
if (skew != null) spr.skew.y = skew;
if (skew.isNotNull()) spr.skew.y = skew;
}
if (node.has.antialiasing) spr.antialiasing = node.att.antialiasing == "true";
if (node.has.scale) {
var scale:Null<Float> = Std.parseFloat(node.att.scale);
if (scale != null) spr.scale.set(scale, scale);
if (scale.isNotNull()) spr.scale.set(scale, scale);
}
if (node.has.scalex) {
var scale:Null<Float> = Std.parseFloat(node.att.scalex);
if (scale.isNotNull()) spr.scale.x = scale;
}
if (node.has.scaley) {
var scale:Null<Float> = Std.parseFloat(node.att.scaley);
if (scale.isNotNull()) spr.scale.y = scale;
}
if (node.has.updateHitbox && node.att.updateHitbox == "true") spr.updateHitbox();
spr.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefault(spr.zoomFactor);
if(node.has.zoomfactor)
spr.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefault(spr.zoomFactor);
if (node.has.alpha)
spr.alpha = Std.parseFloat(node.getAtt("alpha")).getDefault(spr.alpha);
for(anim in node.nodes.anim)
addXMLAnimation(spr, anim);
if (node.has.playOnCountdown)
spr.skipNegativeBeats = node.att.playOnCountdown == "true";
if (node.has.beatInterval)
spr.beatInterval = Std.parseInt(node.att.beatInterval);
if (node.has.beatOffset)
spr.beatOffset = Std.parseInt(node.att.beatOffset);
if(node.hasNode.anim) {
for(anim in node.nodes.anim)
addXMLAnimation(spr, anim);
} else {
if (spr.frames != null && spr.frames.frames != null) {
addAnimToSprite(spr, {
name: "idle",
anim: null,
fps: 24,
loop: spr.spriteAnimType == LOOP,
animType: spr.spriteAnimType,
x: 0,
y: 0,
indices: [for(i in 0...spr.frames.frames.length) i],
forced: (node.has.forced && node.att.forced == "true") || (!node.has.forced && spr.spriteAnimType == BEAT)
});
}
}
return spr;
}
@@ -133,16 +165,18 @@ class XMLUtil {
animType: animType,
x: 0,
y: 0,
indices: []
indices: [],
forced: false,
};
if (anim.has.name) animData.name = anim.att.name;
if (anim.has.type) animData.animType = XMLAnimType.fromString(anim.att.type, animData.animType);
if (anim.has.anim) animData.anim = anim.att.anim;
if (anim.has.fps) animData.fps = Std.parseFloat(anim.att.fps);
if (anim.has.x) animData.x = Std.parseFloat(anim.att.x);
if (anim.has.y) animData.y = Std.parseFloat(anim.att.y);
if (anim.has.fps) animData.fps = Std.parseFloat(anim.att.fps).getDefault(animData.fps);
if (anim.has.x) animData.x = Std.parseFloat(anim.att.x).getDefault(animData.x);
if (anim.has.y) animData.y = Std.parseFloat(anim.att.y).getDefault(animData.y);
if (anim.has.loop) animData.loop = anim.att.loop == "true";
if (anim.has.forced) animData.forced = anim.att.forced == "true";
if (anim.has.indices) {
var indicesSplit = anim.att.indices.split(",");
for(indice in indicesSplit) {
@@ -161,24 +195,30 @@ class XMLUtil {
*/
public static function addXMLAnimation(sprite:FlxSprite, anim:Access, loop:Bool = false):ErrorCode {
var animType:XMLAnimType = NONE;
if (sprite is FunkinSprite)
if (sprite is FunkinSprite) {
animType = cast(sprite, FunkinSprite).spriteAnimType;
}
return addAnimToSprite(sprite, extractAnimFromXML(anim, animType, loop));
}
public static function addAnimToSprite(sprite:FlxSprite, animData:AnimData):ErrorCode {
if (animData.name != null && animData.anim != null) {
if (animData.name != null) {
if (animData.fps <= 0 #if web || animData.fps == null #end) animData.fps = 24;
if (sprite is FunkinSprite && cast(sprite, FunkinSprite).animateAtlas != null) {
var animateAnim = cast(sprite, FunkinSprite).animateAtlas.anim;
if(animData.anim == null)
return MISSING_PROPERTY;
if (animData.indices.length > 0)
animateAnim.addBySymbolIndices(animData.name, animData.anim, animData.indices, animData.fps, animData.loop);
else
animateAnim.addBySymbol(animData.name, animData.anim, animData.fps, animData.loop);
} else {
if (animData.indices.length > 0)
if (animData.anim == null && animData.indices.length > 0)
sprite.animation.add(animData.name, animData.indices, animData.fps, animData.loop);
else if (animData.indices.length > 0)
sprite.animation.addByIndices(animData.name, animData.anim, animData.indices, "", animData.fps, animData.loop);
else
sprite.animation.addByPrefix(animData.name, animData.anim, animData.fps, animData.loop);
@@ -191,9 +231,12 @@ class XMLUtil {
var xmlSpr = cast(sprite, FunkinSprite);
switch(animData.animType) {
case BEAT:
xmlSpr.beatAnims.push(animData.name);
xmlSpr.beatAnims.push({
name: animData.name,
forced: animData.forced.getDefault(false)
});
case LOOP:
xmlSpr.animation.play(animData.name);
xmlSpr.animation.play(animData.name, animData.forced.getDefault(false));
default:
// nothing
}
@@ -219,6 +262,12 @@ typedef AnimData = {
var y:Float;
var indices:Array<Int>;
var animType:XMLAnimType;
var ?forced:Bool;
}
typedef BeatAnim = {
var name:String;
var forced:Bool;
}
interface IXMLEvents {
+1
View File
@@ -110,6 +110,7 @@ class GameOverSubstate extends MusicBeatSubstate
if (controls.BACK)
{
PlayState.resetSongInfos();
if (FlxG.sound.music != null)
FlxG.sound.music.stop();
FlxG.sound.music = null;
+51 -26
View File
@@ -89,6 +89,10 @@ class PlayState extends MusicBeatState
*/
public var strumLines:FlxTypedGroup<StrumLine> = new FlxTypedGroup<StrumLine>();
/**
* Death counter on current week (or song if from freeplay).
*/
public static var deathCounter:Int = 0;
/**
* Game Over Song. (assets/music/gameOver.ogg)
*/
@@ -365,11 +369,14 @@ class PlayState extends MusicBeatState
* Whenever the game is currently in a cutscene or not.
*/
public var inCutscene:Bool = false;
/**
* Whenever the game should play the cutscenes. Defaults to whenever the game is currently in Story Mode or not.
*/
public var playCutscenes:Bool = isStoryMode;
/**
* Whenever the game has already played a specific cutscene for the current song. Check `startCutscene` for more details.
*/
public static var seenCutscene:Bool = false;
/**
* Cutscene script path.
*/
@@ -447,6 +454,7 @@ class PlayState extends MusicBeatState
public var hitWindow:Float = Options.hitWindow; // is calculated in create(), is safeFrames in milliseconds
@:noCompletion @:dox(hide) private var _startCountdownCalled:Bool = false;
@:noCompletion @:dox(hide) private var _endSongCalled:Bool = false;
@:dox(hide)
var __vocalOffsetViolation:Float = 0;
@@ -746,11 +754,15 @@ class PlayState extends MusicBeatState
}
@:dox(hide) public override function createPost() {
startCutscene("", cutscene);
startCutscene("", cutscene, null, true);
super.createPost();
updateDiscordPresence();
// Make icons appear in the correct spot during cutscenes
healthBar.update(0);
updateIconPositions();
__updateNote_event = EventManager.get(NoteUpdateEvent);
scripts.call("postCreate");
@@ -768,10 +780,15 @@ class PlayState extends MusicBeatState
* @param prefix Custom prefix. Using `midsong-` will require you to for example rename your video cutscene to `songs/song/midsong-cutscene.mp4` instead of `songs/song/cutscene.mp4`
* @param cutsceneScriptPath Optional: Custom script path.
* @param callback Callback called after the cutscene ended. If equals to `null`, `startCountdown` will be called.
* @param checkSeen Bool that by default is false, if true and `seenCutscene` is also true, it won't play the cutscene but directly call `callback` (PS: `seenCutscene` becomes true if the cutscene gets played and `checkSeen` was true)
*/
public function startCutscene(prefix:String = "", ?cutsceneScriptPath:String, ?callback:Void->Void) {
if (callback == null)
callback = startCountdown;
public function startCutscene(prefix:String = "", ?cutsceneScriptPath:String, ?callback:Void->Void, checkSeen:Bool = false) {
if (callback == null) callback = startCountdown;
if (checkSeen && seenCutscene) {
callback();
return;
}
if (cutsceneScriptPath == null)
cutsceneScriptPath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/${prefix}cutscene');
@@ -781,28 +798,25 @@ class PlayState extends MusicBeatState
var videoCutsceneAlt = Paths.file('songs/${PlayState.SONG.meta.name.toLowerCase()}/${prefix}cutscene.mp4');
var dialogue = Paths.file('songs/${PlayState.SONG.meta.name.toLowerCase()}/${prefix}dialogue.xml');
persistentUpdate = true;
var toCall:Void->Void = function() {
if(checkSeen) seenCutscene = true;
callback();
}
if (cutsceneScriptPath != null && Assets.exists(cutsceneScriptPath)) {
openSubState(new ScriptedCutscene(cutsceneScriptPath, function() {
callback();
}));
openSubState(new ScriptedCutscene(cutsceneScriptPath, toCall));
} else if (Assets.exists(dialogue)) {
MusicBeatState.skipTransIn = true;
openSubState(new DialogueCutscene(dialogue, function() {
callback();
}));
openSubState(new DialogueCutscene(dialogue, toCall));
} else if (Assets.exists(videoCutsceneAlt)) {
MusicBeatState.skipTransIn = true;
persistentUpdate = false;
openSubState(new VideoCutscene(videoCutsceneAlt, function() {
callback();
}));
openSubState(new VideoCutscene(videoCutsceneAlt, toCall));
persistentDraw = false;
} else if (Assets.exists(videoCutscene)) {
MusicBeatState.skipTransIn = true;
persistentUpdate = false;
openSubState(new VideoCutscene(videoCutscene, function() {
callback();
}));
openSubState(new VideoCutscene(videoCutscene, toCall));
persistentDraw = false;
} else
callback();
@@ -857,6 +871,7 @@ class PlayState extends MusicBeatState
sprite.scale.set(event.scale, event.scale);
sprite.updateHitbox();
sprite.screenCenter();
sprite.antialiasing = event.antialiasing;
add(sprite);
tween = FlxTween.tween(sprite, {y: sprite.y + 100, alpha: 0}, Conductor.crochet / 1000, {
ease: FlxEase.cubeInOut,
@@ -931,6 +946,11 @@ class PlayState extends MusicBeatState
Note.__customNoteTypeExists = [];
}
public static function resetSongInfos() {
deathCounter = 0;
seenCutscene = false;
}
@:dox(hide) private function generateSong(?songData:ChartData):Void
{
if (songData == null) songData = SONG;
@@ -1100,6 +1120,18 @@ class PlayState extends MusicBeatState
updateDiscordPresence();
}
function updateIconPositions() {
var iconOffset:Int = 26;
iconP1.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0)) - iconOffset);
iconP2.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0))) - (iconP2.width - iconOffset);
health = FlxMath.bound(health, 0, maxHealth);
iconP1.health = healthBar.percent / 100;
iconP2.health = 1 - (healthBar.percent / 100);
}
@:dox(hide)
override public function update(elapsed:Float)
{
@@ -1145,15 +1177,7 @@ class PlayState extends MusicBeatState
iconP1.updateHitbox();
iconP2.updateHitbox();
var iconOffset:Int = 26;
iconP1.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0)) - iconOffset);
iconP2.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0))) - (iconP2.width - iconOffset);
health = FlxMath.bound(health, 0, maxHealth);
iconP1.health = healthBar.percent / 100;
iconP2.health = 1 - (healthBar.percent / 100);
updateIconPositions();
if (startingSong)
{
@@ -1359,6 +1383,7 @@ class PlayState extends MusicBeatState
}
startCutscene("end-", endCutscene, nextSong);
resetSongInfos();
}
private static inline function getSongChanges():Array<HighscoreChange> {
+3 -3
View File
@@ -78,7 +78,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
case "sprite" | "spr" | "sparrow":
if (!node.has.sprite || !node.has.name) continue;
var spr = XMLUtil.createSpriteFromXML(node, spritesParentFolder, BEAT);
var spr = XMLUtil.createSpriteFromXML(node, spritesParentFolder, LOOP);
if (!node.has.zoomfactor && PlayState.instance != null)
spr.initialZoom = PlayState.instance.defaultCamZoom;
@@ -90,8 +90,8 @@ class Stage extends FlxBasic implements IBeatReceiver {
if ( !node.has.name || !node.has.width || !node.has.height) continue;
var spr = new FlxSprite(
(node.has.x) ? Std.parseFloat(node.att.x) : 0,
(node.has.y) ? Std.parseFloat(node.att.y) : 0
(node.has.x) ? Std.parseFloat(node.att.x).getDefault(0) : 0,
(node.has.y) ? Std.parseFloat(node.att.y).getDefault(0) : 0
);
(node.name == "solid" ? spr.makeSolid : spr.makeGraphic)(
+3 -1
View File
@@ -305,7 +305,9 @@ class StrumLine extends FlxTypedGroup<Strum> {
if(data.scrollSpeed != null)
babyArrow.scrollSpeed = data.scrollSpeed;
var event = PlayState.instance.scripts.event("onStrumCreation", EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix));
var event = EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix);
event.__doAnimation = !MusicBeatState.skipTransIn;
event = PlayState.instance.scripts.event("onStrumCreation", event);
if (!event.cancelled) {
babyArrow.frames = Paths.getFrames(event.sprite);
@@ -23,7 +23,7 @@ class ScriptedCutscene extends Cutscene {
public override function create() {
super.create();
trace("fuck");
trace("fuck you");
script.call("create");
if (Std.isOfType(script, DummyScript)) {
Logs.trace('Could not find script for scripted cutscene at ${scriptPath}', ERROR, RED);
+1
View File
@@ -61,6 +61,7 @@ class GitarooPause extends MusicBeatState
}
else
{
PlayState.resetSongInfos();
FlxG.switchState(new MainMenuState());
}
}
+6 -3
View File
@@ -72,15 +72,17 @@ class PauseSubState extends MusicBeatSubstate
add(bg);
var levelInfo:FlxText = new FlxText(20, 15, 0, PlayState.SONG.meta.displayName, 32);
var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, PlayState.difficulty.toUpperCase(), 32);
var multiplayerText:FlxText = new FlxText(20, 15 + 32 + 32, 0, PlayState.opponentMode ? 'OPPONENT MODE' : (PlayState.coopMode ? 'CO-OP MODE' : ''), 32);
var levelDifficulty:FlxText = new FlxText(20, 15, 0, PlayState.difficulty.toUpperCase(), 32);
var deathCounter:FlxText = new FlxText(20, 15, 0, "Blue balled: " + PlayState.deathCounter, 32);
var multiplayerText:FlxText = new FlxText(20, 15, 0, PlayState.opponentMode ? 'OPPONENT MODE' : (PlayState.coopMode ? 'CO-OP MODE' : ''), 32);
for(k=>label in [levelInfo, levelDifficulty, multiplayerText]) {
for(k=>label in [levelInfo, levelDifficulty, deathCounter, multiplayerText]) {
label.scrollFactor.set();
label.setFormat(Paths.font('vcr.ttf'), 32);
label.updateHitbox();
label.alpha = 0;
label.x = FlxG.width - (label.width + 20);
label.y = 15 + (32 * k);
FlxTween.tween(label, {alpha: 1, y: label.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.3 * (k+1)});
add(label);
}
@@ -156,6 +158,7 @@ class PauseSubState extends MusicBeatSubstate
case "Exit to charter":
FlxG.switchState(new funkin.editors.charter.Charter(PlayState.SONG.meta.name, PlayState.difficulty, false));
case "Exit to menu":
PlayState.resetSongInfos();
CoolUtil.playMenuSong();
FlxG.switchState(PlayState.isStoryMode ? new StoryMenuState() : new FreeplayState());
}
+1
View File
@@ -75,6 +75,7 @@ class StoryMenuState extends MusicBeatState {
arrow.animation.addByPrefix('idle', 'arrow $dir');
arrow.animation.addByPrefix('press', 'arrow push $dir', 24, false);
arrow.animation.play('idle');
arrow.antialiasing = true;
add(arrow);
}
rightArrow.x -= rightArrow.width;
+10 -11
View File
@@ -1,11 +1,13 @@
package funkin.menus.credits;
import funkin.backend.system.github.GitHub;
import funkin.options.type.PortraitOption;
import funkin.options.type.GithubIconOption;
using StringTools;
class CreditsCodename extends funkin.options.OptionsScreen {
public var error:Bool = false;
public override function new()
{
super("Codename Engine", "All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes).");
@@ -21,17 +23,16 @@ class CreditsCodename extends funkin.options.OptionsScreen {
super.update(elapsed);
}
var error:Bool = false;
public function checkUpdate():Bool {
var curTime:Float = Date.now().getTime();
if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits - Nex_isDumb
if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits
Options.lastUpdated = curTime;
error = false;
//Main.execAsync(function() {
var idk = GitHub.getContributors("FNF-CNE-Devs", "CodenameEngine", function(e) {
error = true;
var errMsg:String = ~/\d+.\d+.\d+.\d+/.replace(e.message, "[Your IP]"); // Removing sensitive stuff - Nex_isDumb
var errMsg:String = ~/\d+.\d+.\d+.\d+/.replace(e.message, "[Your IP]"); // Removing sensitive stuff
errMsg = 'Error while trying to download contributors list:\n$errMsg';
Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR);
@@ -39,7 +40,7 @@ class CreditsCodename extends funkin.options.OptionsScreen {
});
//});
if(error) return false;
Options.contributors = idk;
trace('List Updated!');
return true;
@@ -49,7 +50,7 @@ class CreditsCodename extends funkin.options.OptionsScreen {
//if (curSelected > Options.contributors.length - 1) changeSelection(-(curSelected - (Options.contributors.length - 1)));
if (curSelected > Options.contributors.length - 1) curSelected = Options.contributors.length - 1;
changeSelection(0, true);
while (members.length > 0) {
members[0].destroy();
remove(members[0], true);
@@ -58,12 +59,10 @@ class CreditsCodename extends funkin.options.OptionsScreen {
var totalContributions = 0;
for(c in Options.contributors) totalContributions += c.contributions;
for(c in Options.contributors) {
var opt:PortraitOption = new PortraitOption(
c.login,
'Total Contributions: ${c.contributions} / ${totalContributions} (${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%) - Select to open GitHub account',
function() CoolUtil.openURL(c.html_url)
var opt:GithubIconOption = new GithubIconOption(
c,
'Total Contributions: ${c.contributions} / ${totalContributions} (${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%) - Select to open GitHub account'
);
if(!error) opt.loadFromGithub(c);
add(opt);
}
}
+38 -16
View File
@@ -1,5 +1,6 @@
package funkin.menus.credits;
import funkin.backend.system.github.GitHub;
import funkin.options.OptionsScreen;
import funkin.options.type.*;
import funkin.options.TreeMenu;
@@ -27,7 +28,7 @@ class CreditsMain extends TreeMenu {
} catch(e) {
Logs.trace('Error while parsing credits.xml: ${Std.string(e)}', ERROR);
}
if (access != null)
for(c in parseCreditsFromXML(access, source))
selectables.push(c);
@@ -51,24 +52,45 @@ class CreditsMain extends TreeMenu {
var credsMenus:Array<OptionType> = [];
for(node in xml.elements) {
if (!node.has.name) {
Logs.trace("A credit node requires a name attribute.", WARNING);
continue;
}
var name = node.getAtt("name");
var desc = node.getAtt("desc").getDefault("No Description");
switch(node.name) {
case "credit":
credsMenus.push(new PortraitOption(name, desc, function() if(node.has.url) CoolUtil.openURL(node.att.url),
node.has.icon && Paths.assetsTree.existsSpecific(Paths.image('credits/${node.att.icon}'), "IMAGE", source) ?
FlxG.bitmap.add(Paths.image('credits/${node.att.icon}')) : null, node.has.size ? Std.parseInt(node.att.size) : 96
));
if (node.name == "github") {
if (!node.has.user) {
Logs.trace("A github node requires a user attribute.", WARNING);
continue;
}
case "menu":
credsMenus.push(new TextOption(name + " >", desc, function() {
optionsTree.add(new OptionsScreen(name, desc, parseCreditsFromXML(node, source)));
}));
var username = node.getAtt("user");
var user = { // Kind of forcing - Nex_isDumb
login: username,
html_url: 'https://github.com/$username',
avatar_url: 'https://github.com/$username.png'
};
credsMenus.push(new GithubIconOption(user, desc, null,
node.has.customName ? node.att.customName : null, node.has.size ? Std.parseInt(node.att.size) : 96,
node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true
));
} else {
if (!node.has.name) {
Logs.trace("A credit node requires a name attribute.", WARNING);
continue;
}
var name = node.getAtt("name");
switch(node.name) {
case "credit":
credsMenus.push(new PortraitOption(name, desc, function() if(node.has.url) CoolUtil.openURL(node.att.url),
node.has.icon && Paths.assetsTree.existsSpecific(Paths.image('credits/${node.att.icon}'), "IMAGE", source) ?
FlxG.bitmap.add(Paths.image('credits/${node.att.icon}')) : null, node.has.size ? Std.parseInt(node.att.size) : 96,
node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true
));
case "menu":
credsMenus.push(new TextOption(name + " >", desc, function() {
optionsTree.add(new OptionsScreen(name, desc, parseCreditsFromXML(node, source)));
}));
}
}
}
+4 -1
View File
@@ -27,6 +27,7 @@ class Options
public static var antialiasing:Bool = true;
public static var volume:Float = 1;
public static var week6PixelPerfect:Bool = true;
public static var gameplayShaders:Bool = true;
public static var colorHealthBar:Bool = true;
public static var lowMemoryMode:Bool = false;
public static var betaUpdates:Bool = false;
@@ -54,7 +55,9 @@ class Options
public static var contributors:Array<funkin.backend.system.github.GitHubContributor> = [];
public static var lastUpdated:Null<Float>;
// CHARTER
/**
* CHARTER
*/
public static var charterMetronomeEnabled:Bool = false;
public static var charterShowSections:Bool = true;
public static var charterShowBeats:Bool = true;
@@ -23,6 +23,10 @@ class AppearanceOptions extends OptionsScreen {
"Pixel Perfect Effect",
"If checked, Week 6 will have a pixel perfect effect to it enabled, aligning every pixel on the screen.",
"week6PixelPerfect"));
add(new Checkbox(
"Gameplay Shaders",
"If unchecked, gameplay shaders (visual effects like Thorns's Chromatic Aberration) wont be loaded; this may be helpful on weak devices.",
"gameplayShaders"));
add(new Checkbox(
"Flashing Menu",
"If unchecked, will disable menu flashing when you select an option in the Main Menu, and other flashes will be slower.",
@@ -0,0 +1,103 @@
package funkin.options.type;
import openfl.display.BitmapData;
import flixel.graphics.FlxGraphic;
import funkin.backend.shaders.CustomShader;
import funkin.backend.system.github.GitHub;
import flixel.util.FlxColor;
class GithubIconOption extends TextOption
{
public var user(default, null):Dynamic; // Can possibly be GitHubUser or GitHubContributor - Nex_isDumb
public var icon:GithubUserIcon = null;
public var usePortrait(default, set) = true;
public function set_usePortrait(value:Bool)
{
if (icon == null) return usePortrait = false;
icon.shader = (value ? new CustomShader('engine/circleProfilePicture') : null);
return usePortrait = value;
}
public function new(user:Dynamic, desc:String, ?callback:Void->Void, ?customName:String, size:Int = 96, usePortrait:Bool = true) {
super(customName == null ? user.login : customName, desc, callback == null ? function() CoolUtil.openURL(user.html_url) : callback);
this.user = user;
this.icon = new GithubUserIcon(user, size);
this.usePortrait = usePortrait;
add(icon);
}
}
class GithubUserIcon extends FlxSprite
{
private var loading:Bool = false;
private var user:Dynamic;
private var size:Int;
public override function new(user:Dynamic, size:Int = 96) {
this.user = user;
this.size = size;
super();
makeGraphic(size, size, FlxColor.TRANSPARENT);
antialiasing = true;
}
final mutex = new sys.thread.Mutex();
override function drawComplex(camera:FlxCamera):Void { // Making the image downlaod only if the player actually sees it on the screeeeen - Nex_isDumb
if(!loading) {
loading = true;
Main.execAsync(function() {
var key:String = 'GITHUB-USER:${user.login}';
var bmap:Dynamic = FlxG.bitmap.get(key);
if(bmap == null) {
trace('Downloading avatar: ${user.login}');
var unfLink:Bool = StringTools.endsWith(user.avatar_url, '.png');
var planB:Bool = true;
var bytes = null;
if(unfLink) {
try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}?size=$size')
catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)} - (Retrying using the api..)', RED)], ERROR);
if(bytes != null) {
bmap = BitmapData.fromBytes(bytes);
planB = false;
}
}
if(planB) {
if(unfLink) user = GitHub.getUser(user.login, function(e) Logs.traceColored([Logs.logText('Failed to download github user info for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR)); // Api part - Nex_isDumb
try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}&size=$size')
catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR);
if(bytes != null) bmap = BitmapData.fromBytes(bytes);
}
if(bmap != null) try {
mutex.acquire(); // Avoiding critical section - Nex_isDumb
var leGraphic:FlxGraphic = FlxG.bitmap.add(bmap, false, key);
leGraphic.persist = true;
updateDaFunni(leGraphic);
bmap = null;
mutex.release();
} catch(e) {
Logs.traceColored([Logs.logText('Failed to update the pfp for ${user.login}: ${e.message}', RED)], ERROR);
}
} else {
mutex.acquire();
updateDaFunni(bmap);
mutex.release();
}
});
}
super.drawComplex(camera);
}
public inline function updateDaFunni(graphic:FlxGraphic) {
loadGraphic(graphic);
this.setUnstretchedGraphicSize(size, size, false);
updateHitbox();
x += 90 - width;
}
}
+1 -2
View File
@@ -24,5 +24,4 @@ class OptionType extends FlxSpriteGroup {
super.update(elapsed);
alpha = (selected ? 1 : 0.6);
}
}
}
+4 -15
View File
@@ -1,24 +1,21 @@
package funkin.options.type;
import openfl.display.BitmapData;
import flixel.graphics.FlxGraphic;
import funkin.backend.shaders.CustomShader;
import funkin.backend.system.github.GitHubContributor;
import funkin.backend.system.github.GitHub;
class PortraitOption extends TextOption {
public var portrait:FlxSprite = null;
public function new(name:String, desc:String, callback:Void->Void, ?graphic:FlxGraphic, size:Int = 96) {
public function new(name:String, desc:String, callback:Void->Void, ?graphic:FlxGraphic, size:Int = 96, usePortrait:Bool = true) {
super(name, desc, callback);
if (graphic != null) addPortrait(graphic, size);
if (graphic != null) addPortrait(graphic, size, usePortrait);
}
public function addPortrait(graphic:FlxGraphic, size:Int = 96) {
public function addPortrait(graphic:FlxGraphic, size:Int = 96, usePortrait:Bool = true) {
if (portrait == null) {
portrait = new FlxSprite();
portrait.antialiasing = true;
portrait.shader = new CustomShader('engine/circleProfilePicture');
if(usePortrait) portrait.shader = new CustomShader('engine/circleProfilePicture');
add(portrait);
}
portrait.loadGraphic(graphic);
@@ -26,12 +23,4 @@ class PortraitOption extends TextOption {
portrait.updateHitbox();
portrait.setPosition(90 - portrait.width, 0);
}
public function loadFromGithub(user:GitHubContributor, size:Int = 96) {
//Main.execAsync(function() {
var bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}&size=$size');
var bmap = BitmapData.fromBytes(bytes);
addPortrait(FlxG.bitmap.add(bmap, false, 'GITHUB-USER:${user.login}'), size);
//});
}
}