diff --git a/.gitignore b/.gitignore
index 8b905e1643..3138e6d0ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ addons/*
dev-libs/
*.sum
.vs/
+.env
\ No newline at end of file
diff --git a/assets/data/config/menuItems.txt b/assets/data/config/menuItems.txt
index a8ec55b16e..677cc54593 100644
--- a/assets/data/config/menuItems.txt
+++ b/assets/data/config/menuItems.txt
@@ -1,4 +1,5 @@
story mode
freeplay
options
-credits
\ No newline at end of file
+credits
+gamejolt
\ No newline at end of file
diff --git a/assets/data/editors/layouts/stage/characterEditScreen.xml b/assets/data/editors/layouts/stage/characterEditScreen.xml
index fe1bf3ba65..05268bc86d 100644
--- a/assets/data/editors/layouts/stage/characterEditScreen.xml
+++ b/assets/data/editors/layouts/stage/characterEditScreen.xml
@@ -8,19 +8,10 @@
return 20 + (150 + XOFFSET) * c;
}
previewSprite = null;
- if(!char.isAnimate) {
+ if(char.animateAtlas == null) {
previewSprite = new FunkinSprite(0, 0);
previewSprite.frame = char.frame;
- self.add(previewSprite);
- } else {
- previewSprite = new FunkinSprite(0, 0);
- previewSprite.frames = char.frames;
- previewSprite.applyStageMatrix = false;
- // quite a bit of hacking to it buuuuuut
- previewSprite.animation._curAnim = char.animation._curAnim;
- previewSprite.animation.frameIndex = char.animation.frameIndex;
-
self.add(previewSprite);
}
@@ -62,10 +53,8 @@
-
@@ -89,7 +78,7 @@
-
+
import funkin.editors.ui.UIAutoCompleteTextBox;
import funkin.game.Character;
@@ -121,6 +110,9 @@
setEx("highMemory", highMemoryRadio.checked);
setEx("lowMemory", lowMemoryRadio.checked);
+ if (!StringTools.startsWith(char.name, 'NO_DELETE_'))
+ char.name = charName.field.text;
+
char.x = xStepper.value;
char.y = yStepper.value;
char.scrollFactor.set(scrollXStepper.value, scrollYStepper.value);
@@ -143,7 +135,6 @@
char.zoomFactor = zoomFactorStepper.value;
char.angle = angleStepper.value;
- var posName = StringTools.startsWith(char.name, 'NO_DELETE_') ? char.name : charName.label.text;
var newCharName = curCharBox.label.text;
if (newCharName != char.curCharacter) {
char.curCharacter = newCharName;
@@ -151,41 +142,12 @@
char.applyXML(char.xml);
var anim = char.getAnimOrder()[0];
- char.playAnim(anim, true, 0, true, 0);
- char.stopAnim();
- }
- char.name = posName;
-
- function saveXY(name:String, x:UINumericStepper, y:UINumericStepper) {
- button.xml.remove(name + "");
- button.xml.remove(name + "x");
- button.xml.remove(name + "y");
-
- if(x.value == y.value)
- button.xml.set(name, x.value);
- else {
- button.xml.set(name + "x", x.value);
- button.xml.set(name + "y", y.value);
- }
+ char.playAnim(anim, true);
+ var lastIndx = char.animation.curAnim.numFrames - 1;
+ char.playAnim(anim, true, null, false, lastIndx);
+ char.stopAnimation();
}
- if (!StringTools.startsWith(char.name, "NO_DELETE_"))
- button.xml.set("name", char.name);
- saveXY("", xStepper, yStepper);
- button.xml.set("camxoffset", camxStepper.value);
- button.xml.set("camyoffset", camyStepper.value);
-
- saveXY("scale", scaleXStepper, scaleYStepper);
- saveXY("scroll", scrollXStepper, scrollYStepper);
- button.xml.set("zoomfactor", zoomFactorStepper.value);
- button.xml.set("alpha", alphaStepper.value);
- button.xml.set("angle", angleStepper.value);
- button.xml.set("skewx", skewXStepper.value);
- button.xml.set("skewy", skewYStepper.value);
-
- button.xml.remove("flip");
- button.xml.remove("flipX");
- button.xml.set("flip", flipXCheckbox.checked);
}
diff --git a/assets/data/editors/layouts/stage/solidEditScreen.xml b/assets/data/editors/layouts/stage/solidEditScreen.xml
deleted file mode 100644
index c79ec0f172..0000000000
--- a/assets/data/editors/layouts/stage/solidEditScreen.xml
+++ /dev/null
@@ -1,156 +0,0 @@
-
-
-
-
-
-
-
-
- function col(c:Int) {
- return MARGIN + (200 + XOFFSET) * c;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- var prevID = nameTextBox.label.text;
- var ratio = sprite.width / sprite.height;
-
- function onUpdate() {
- if (nameTextBox.label.text != prevID)
- self.winTitle = self.titleSpr.text = translate('win-sprite-title', [nameTextBox.label.text]);
- prevID = nameTextBox.label.text;
-
- /*if(previewSprite == null) return;
- previewSprite.color = 0xFFFFFFFF; // TODO: Color Wheel
- previewSprite.blend = BlendMode.fromString(blendMode.value);
- previewSprite.skew.x = skewXStepper.value;
- previewSprite.skew.y = skewYStepper.value;
- previewSprite.angle = angleStepper.value;
- previewSprite.alpha = alphaStepper.value;
- previewSprite.antialiasing = antialiasingCheckbox.checked;
- previewSprite.setGraphicSize(85 * scaleXStepper.value * ratio, 85 * scaleYStepper.value);
- previewSprite.updateHitbox();*/
- }
-
- function onSave() {
- sprite.name = nameTextBox.label.text;
-
- setEx("highMemory", highMemoryRadio.checked);
- setEx("lowMemory", lowMemoryRadio.checked);
-
- (isSolid ? sprite.makeSolid : sprite.makeGraphic)(sizeXStepper.value, sizeYStepper.value, colorWheel.curColor);
- sprite.x = xStepper.value;
- sprite.y = yStepper.value;
- sprite.zoomFactor = zoomFactorStepper.value;
- sprite.angle = angleStepper.value;
- sprite.alpha = alphaStepper.value;
- sprite.blend = BlendMode.fromString(blendMode.value);
-
- function saveXY(obj:Dynamic, name:String, x:UINumericStepper, y:UINumericStepper) {
- button.xml.remove(name + "");
- button.xml.remove(name + "x");
- button.xml.remove(name + "y");
-
- obj.x = x.value;
- obj.y = y.value;
-
- if(x.value == y.value)
- button.xml.set(name, x.value);
- else {
- button.xml.set(name + "x", x.value);
- button.xml.set(name + "y", y.value);
- }
- }
-
- button.xml.set("name", nameTextBox.label.text);
- button.xml.set("x", xStepper.value);
- button.xml.set("y", yStepper.value);
- button.xml.set("zoomfactor", zoomFactorStepper.value);
- button.xml.set("angle", angleStepper.value);
- button.xml.set("alpha", alphaStepper.value);
- button.xml.remove("blend");
- if (sprite.blend != null)
- button.xml.set("blend", blendMode.value);
-
- saveXY(sprite.scrollFactor, "scroll", scrollXStepper, scrollYStepper);
- saveXY(sprite.skew, "skew", skewXStepper, skewYStepper);
- if (isSolid)
- button.xml.remove("updateHitbox");
- for (att in ["width", "height", "color"])
- button.xml.remove(att);
- XMLUtil.loadSpriteFromXML(sprite, button.xml, "", 0, false);
- button.xml.set("width", sizeXStepper.value);
- button.xml.set("height", sizeYStepper.value);
- button.xml.set("color", colorWheel.curColorString);
- button.updateInfo();
- setEx('node', button.xml);
- }
-
-
\ No newline at end of file
diff --git a/assets/data/editors/layouts/stage/spriteEditScreen.xml b/assets/data/editors/layouts/stage/spriteEditScreen.xml
index 16f53219be..aa557c4043 100644
--- a/assets/data/editors/layouts/stage/spriteEditScreen.xml
+++ b/assets/data/editors/layouts/stage/spriteEditScreen.xml
@@ -182,7 +182,6 @@
if(previewSprite == null) return;
previewSprite.color = 0xFFFFFFFF; // TODO: Color Wheel
- previewSprite.blend = BlendMode.fromString(blendMode.value);
previewSprite.skew.x = skewXStepper.value;
previewSprite.skew.y = skewYStepper.value;
previewSprite.angle = angleStepper.value;
@@ -207,7 +206,6 @@
sprite.spriteAnimType = animTypeToString(animType.value);
sprite.angle = angleStepper.value;
sprite.alpha = alphaStepper.value;
- sprite.blend = BlendMode.fromString(blendMode.value);
function saveXY(obj:Dynamic, name:String, x:UINumericStepper, y:UINumericStepper) {
button.xml.remove(name + "");
@@ -229,18 +227,18 @@
button.xml.set(name, value);
}
+ // TODO: remove old attributes to prevent possible errors
+ // button.xml.remove();
+
button.xml.set("name", nameTextBox.label.text);
button.xml.set("sprite", spriteTextBox.label.text);
button.xml.set("x", xStepper.value);
button.xml.set("y", yStepper.value);
- button.xml.set("zoomfactor", zoomFactorStepper.value);
+ button.xml.set("zoom", zoomFactorStepper.value);
button.xml.set("type", animTypeToString(animType.value));
button.xml.set("antialiasing", antialiasingCheckbox.checked);
button.xml.set("angle", angleStepper.value);
button.xml.set("alpha", alphaStepper.value);
- button.xml.remove("blend");
- if (sprite.blend != null)
- button.xml.set("blend", blendMode.value);
getEx('anims').clear();
var allowedAnims:Array<String> = [for (b in animations.buttons.members) b.animData.name];
diff --git a/assets/data/stages/test.xml b/assets/data/stages/test.xml
new file mode 100644
index 0000000000..697711bbc4
--- /dev/null
+++ b/assets/data/stages/test.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/data/states/StageEditor.hx b/assets/data/states/StageEditor.hx
index 983b6bc7f1..af9f6cbed1 100644
--- a/assets/data/states/StageEditor.hx
+++ b/assets/data/states/StageEditor.hx
@@ -7,13 +7,11 @@
* - Angle + Scale (like already at a angle)
*/
-import flixel.FlxSprite;
import funkin.editors.stage.StageEditor;
import funkin.editors.ui.UIWarningSubstate;
import funkin.backend.utils.WindowUtils;
import funkin.backend.utils.MatrixUtil;
import flixel.math.FlxAngle;
-import flixel.math.FlxRect;
import openfl.Lib;
var exID = StageEditor.exID;
@@ -32,7 +30,59 @@ function tryUpdateHitbox(sprite) {
return false;
}
+var allowClose = false;
+function create() {
+ WindowUtils.preventClosing = true;
+ WindowUtils.resetClosing();
+ WindowUtils.onClosing = function() {
+ if(!allowClose)
+ Lib.application.window.onClose.cancel();
+
+ var substate;
+ substate = new UIWarningSubstate("nuh uh", "you dont need to close goofus doofus", [
+ {
+ label: "Actually Exit",
+ onClick: function(t) {
+ allowClose = true;
+ Lib.application.window.close();
+ }
+ },
+ {
+ label: "Reload State",
+ onClick: function(t) {
+ FlxG.switchState(new StageEditor(StageEditor.__stage));
+ }
+ },
+ {
+ label: "oh whoops",
+ onClick: function(t) {
+ substate.close();
+ }
+ }
+ ]);
+
+ openSubState(substate);
+ }
+}
+
+function destroy() {
+ WindowUtils.preventClosing = false;
+ WindowUtils.resetClosing();
+ WindowUtils.onClosing = null;
+}
+
+function update() {
+ if (FlxG.mouse.justPressed)
+ lastRelative.set();
+
+ if(FlxG.keys.justPressed.R) {
+ trace("reloading");
+ FlxG.switchState(new StageEditor(StageEditor.__stage));
+ }
+}
+
function genericScale(sprite, relative, doX, doY) {
+
var relativeMult = 1 / (FlxMath.lerp(1, stageCamera.zoom, sprite.zoomFactor) / stageCamera.zoom) * (FlxG.keys.pressed.ALT ? 2 : 1);
relative.x *= relativeMult;
relative.y *= relativeMult;
@@ -74,27 +124,16 @@ function genericOppositeScale(sprite, relative, scaleX, scaleY, repositionX, rep
}
}
-function mouseModeChanged(sprite) {
- // SKEW_TOP = 10, LEFT = 11, RIGHT = 12, BOTTOM = 13
- if (mouseMode == 10 || mouseMode == 13) {
- skewPoint1.set(0, 0);
- skewPoint2.set(0, 1);
- gimmeSkewBounds(sprite);
- applySkewPoints(sprite);
- lastSkewSize = skewSize.x;
- } else if (mouseMode == 11 || mouseMode == 12) {
- skewPoint1.set(0, 0);
- skewPoint2.set(1, 0);
- gimmeSkewBounds(sprite);
- applySkewPoints(sprite);
- lastSkewSize = skewSize.y;
- }
+var oldSpritePos = FlxPoint.get();
+function SCALE_BOTTOM_RIGHT(sprite, relative) {
+ preRotBullshit(sprite, relative);
+ genericScale(sprite, relative, true, true);
+ postRotBullshit(sprite, relative);
}
/**
ROTATION
**/
-var oldSpritePos = FlxPoint.get();
function preRotBullshit(sprite, relative) {
if (sprite.angle != 0)
relative = rotateByDegrees(relative, -sprite.angle);
@@ -135,26 +174,20 @@ function rotateByDegrees(p, angle) {
**/
-function MOVE_CENTER(sprite, relative) {
- sprite.x = storedPos.x-relative.x;
- sprite.y = storedPos.y-relative.y;
-}
-
function SCALE_TOP_RIGHT(sprite, relative) {
preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, true, true, false, true);
postRotBullshit(sprite, relative);
}
-function SCALE_TOP_LEFT(sprite, relative) {
- preRotBullshit(sprite, relative);
- genericOppositeScale(sprite, relative, true, true, true, true);
- postRotBullshit(sprite, relative);
+function MOVE_CENTER(sprite, relative) {
+ sprite.x = storedPos.x-relative.x;
+ sprite.y = storedPos.y-relative.y;
}
-function SCALE_BOTTOM_RIGHT(sprite, relative) {
+function SCALE_TOP_LEFT(sprite, relative) {
preRotBullshit(sprite, relative);
- genericScale(sprite, relative, true, true);
+ genericOppositeScale(sprite, relative, true, true, true, true);
postRotBullshit(sprite, relative);
}
@@ -188,84 +221,76 @@ function SCALE_BOTTOM(sprite, relative) {
postRotBullshit(sprite, relative);
}
-var toDeg = 180 / Math.PI; // the constants stopped working
-var toRad = Math.PI / 180;
-var skewInfo = FlxRect.get();
-var skewSize = FlxPoint.get();
-var lastSkewSize = 0;
+var lastRelative = FlxPoint.get();
var skewPoint1 = FlxPoint.get();
var skewPoint2 = FlxPoint.get();
function SKEW_LEFT(sprite, relative) {
+ preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT) {
- var lastX = relative.x;
- var lastY = relative.y;
- preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, true, false, true, false);
postRotBullshit(sprite, relative);
- relative.set(lastX, lastY);
}
- sprite.skew.y = CoolUtil.bound(Math.atan2(
- skewPoint2.y - (skewPoint1.y - relative.y),
- skewPoint2.x - (skewPoint1.x - (FlxG.keys.pressed.SHIFT ? 0 : relative.x))
- ) * toDeg, -89, 89);
- gimmeSkewBounds(sprite);
- sprite.y = storedPos.y - (skewSize.y - lastSkewSize) * 0.5;
+ skewPoint1.set(0, 0);
+ skewPoint2.set(1, 0);
+ gimmeSkewCorners(sprite, 0, 1);
+
+ sprite.y = storedPos.y - relative.y * 0.5;
+ sprite.skew.y += ((relative.y - lastRelative.y) * 0.02);
+
+ lastRelative.set((FlxG.keys.pressed.SHIFT ? lastRelative.x : relative.x), relative.y);
}
function SKEW_BOTTOM(sprite, relative) {
+ preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT) {
- var lastX = relative.x;
- var lastY = relative.y;
- preRotBullshit(sprite, relative);
genericScale(sprite, relative, false, true);
postRotBullshit(sprite, relative);
- relative.set(lastX, lastY);
}
- sprite.skew.x = CoolUtil.bound(Math.atan2(
- (skewPoint2.x - relative.x) - skewPoint1.x,
- (skewPoint2.y - (FlxG.keys.pressed.SHIFT ? 0 : relative.y)) - skewPoint1.y
- ) * toDeg, -89, 89);
- gimmeSkewBounds(sprite);
- sprite.x = storedPos.x + (skewSize.x - lastSkewSize) * 0.5;
+ skewPoint1.set(0, 0);
+ skewPoint2.set(0, 1);
+ gimmeSkewCorners(sprite, 0, 2);
+
+ sprite.x = storedPos.x - relative.x * 0.4;
+ sprite.skew.x -= ((relative.x - lastRelative.x) * 0.03);
+
+ lastRelative.set(relative.x, (FlxG.keys.pressed.SHIFT ? lastRelative.y : relative.y));
}
function SKEW_TOP(sprite, relative) {
+ preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT) {
- var lastX = relative.x;
- var lastY = relative.y;
- preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, false, true, false, true);
postRotBullshit(sprite, relative);
- relative.set(lastX, lastY);
}
- sprite.skew.x = CoolUtil.bound(Math.atan2(
- skewPoint2.x - (skewPoint1.x - relative.x),
- skewPoint2.y - (skewPoint1.y - (FlxG.keys.pressed.SHIFT ? 0 : relative.y))
- ) * toDeg, -89, 89);
- gimmeSkewBounds(sprite);
- sprite.x = storedPos.x - (skewSize.x - lastSkewSize) * 0.5;
+ skewPoint1.set(0, 0);
+ skewPoint2.set(0, 1);
+ gimmeSkewCorners(sprite, 0, 2);
+
+ sprite.x = storedPos.x - relative.x * 0.4;
+ sprite.skew.x += ((relative.x - lastRelative.x) * 0.03);
+
+ lastRelative.set(relative.x, (FlxG.keys.pressed.SHIFT ? lastRelative.y : relative.y));
}
function SKEW_RIGHT(sprite, relative) {
+ preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT) {
- var lastX = relative.x;
- var lastY = relative.y;
- preRotBullshit(sprite, relative);
genericScale(sprite, relative, true, false);
postRotBullshit(sprite, relative);
- relative.set(lastX, lastY);
}
- sprite.skew.y = CoolUtil.bound(Math.atan2(
- (skewPoint2.y - relative.y) - skewPoint1.y,
- (skewPoint2.x - (FlxG.keys.pressed.SHIFT ? 0 : relative.x)) - skewPoint1.x
- ) * toDeg, -89, 89);
- gimmeSkewBounds(sprite);
- sprite.y = storedPos.y + (skewSize.y - lastSkewSize) * 0.5;
+ skewPoint1.set(0, 0);
+ skewPoint2.set(1, 0);
+ gimmeSkewCorners(sprite, 0, 1);
+
+ sprite.y = storedPos.y - relative.y * 0.5;
+ sprite.skew.y -= ((relative.y - lastRelative.y) * 0.02);
+
+ lastRelative.set((FlxG.keys.pressed.SHIFT ? lastRelative.x : relative.x), relative.y);
}
function ROTATE(sprite, relative) {
@@ -282,25 +307,22 @@ function ROTATE(sprite, relative) {
}
-function gimmeSkewBounds(sprite) {
- skewInfo.set(
- sprite.x + sprite.offset.x + sprite.frameWidth * 0.5,
- sprite.y + sprite.offset.y + sprite.frameHeight * 0.5,
- sprite.frameWidth * sprite.scale.x * sprite._cosAngle + sprite.frameHeight * sprite.scale.y * sprite._sinAngle,
- sprite.frameWidth * sprite.scale.x * sprite._sinAngle + sprite.frameHeight * sprite.scale.y * sprite._cosAngle
- );
- skewSize.set(
- skewInfo.height * Math.tan(sprite.skew.x * toRad),
- skewInfo.width * Math.tan(sprite.skew.y * toRad)
- );
-}
-function applySkewPoints(sprite) {
- skewPoint1.set(
- (skewInfo.x + skewSize.x * (skewPoint1.y - 0.5)) + skewInfo.width * skewPoint1.x,
- (skewInfo.y + skewSize.y * (skewPoint1.x - 0.5)) + skewInfo.height * skewPoint1.y
- );
- skewPoint2.set(
- (skewInfo.x + skewSize.x * (skewPoint2.y - 0.5)) + skewInfo.width * skewPoint2.x,
- (skewInfo.y + skewSize.y * (skewPoint2.x - 0.5)) + skewInfo.height * skewPoint2.y
- );
+/*function postDraw() {
+ if(storedCenter.x != 0 || storedCenter.y != 0) {
+ drawLine(storedCenter, storedRelative, 0.3);
+ }
+}*/
+
+function gimmeSkewCorners(sprite, index1, index2) {
+ if (sprite.angle == 0) {
+ var corners = sprite.extra.get(exID("buttonBoxes"));
+ skewPoint1.set(corners[index1].x, corners[index1].y);
+ skewPoint2.set(corners[index2].x, corners[index2].y);
+ return;
+ }
+
+ var ogAngle = sprite.angle;
+ sprite.angle = 0;
+ MatrixUtil.getMatrixPosition(sprite, [skewPoint1, skewPoint2], sprite.camera, sprite.frameWidth, sprite.frameHeight);
+ sprite.angle = ogAngle;
}
\ No newline at end of file
diff --git a/assets/images/menus/gamejolt-icon.png b/assets/images/menus/gamejolt-icon.png
new file mode 100644
index 0000000000..70b5055600
Binary files /dev/null and b/assets/images/menus/gamejolt-icon.png differ
diff --git a/assets/images/menus/gamejolt/bronze-secret.png b/assets/images/menus/gamejolt/bronze-secret.png
new file mode 100644
index 0000000000..4f7ba8b542
Binary files /dev/null and b/assets/images/menus/gamejolt/bronze-secret.png differ
diff --git a/assets/images/menus/gamejolt/bronze.png b/assets/images/menus/gamejolt/bronze.png
new file mode 100644
index 0000000000..4404e2533c
Binary files /dev/null and b/assets/images/menus/gamejolt/bronze.png differ
diff --git a/assets/images/menus/gamejolt/gold-secret.png b/assets/images/menus/gamejolt/gold-secret.png
new file mode 100644
index 0000000000..e72d9adcbc
Binary files /dev/null and b/assets/images/menus/gamejolt/gold-secret.png differ
diff --git a/assets/images/menus/gamejolt/gold.png b/assets/images/menus/gamejolt/gold.png
new file mode 100644
index 0000000000..1b94e02a50
Binary files /dev/null and b/assets/images/menus/gamejolt/gold.png differ
diff --git a/assets/images/menus/gamejolt/platinum-secret.png b/assets/images/menus/gamejolt/platinum-secret.png
new file mode 100644
index 0000000000..f9fbf53e25
Binary files /dev/null and b/assets/images/menus/gamejolt/platinum-secret.png differ
diff --git a/assets/images/menus/gamejolt/platinum.png b/assets/images/menus/gamejolt/platinum.png
new file mode 100644
index 0000000000..2d4763efb3
Binary files /dev/null and b/assets/images/menus/gamejolt/platinum.png differ
diff --git a/assets/images/menus/gamejolt/silver-secret.png b/assets/images/menus/gamejolt/silver-secret.png
new file mode 100644
index 0000000000..e5915817e9
Binary files /dev/null and b/assets/images/menus/gamejolt/silver-secret.png differ
diff --git a/assets/images/menus/gamejolt/silver.png b/assets/images/menus/gamejolt/silver.png
new file mode 100644
index 0000000000..ece17d4db6
Binary files /dev/null and b/assets/images/menus/gamejolt/silver.png differ
diff --git a/assets/languages/en/Editors.xml b/assets/languages/en/Editors.xml
index 87138fcaec..ba1c05bb54 100644
--- a/assets/languages/en/Editors.xml
+++ b/assets/languages/en/Editors.xml
@@ -608,12 +608,10 @@
Editing Character "{0}"
Editing Sprite "{0}"
- Editing Box "{0}"
Editing XML <{0} />
Character Data
Sprite Data
- Box Data
XML Data
Element Name
@@ -621,8 +619,6 @@
Image File\n(in "{0}")
Scroll {0}
Scale {0}
- Width
- Height
Camera {0}
Spacing (X, Y)
Skew {0}
@@ -637,7 +633,6 @@
Attributes
Transform Preview:
Animations
- Color
Animation Type
@@ -645,40 +640,6 @@
BEAT
LOOP
-
- Blend Mode
-
-
- NONE
- ADD
- ALPHA
- DARKEN
- DIFFERENCE
- ERASE
- HARDLIGHT
- INVERT
- LAYER
- LIGHTEN
- MULTIPLY
-
- OVERLAY
- SCREEN
- SHADER
- SUBTRACT
- COLORDODGE
- COLORBURN
- SOFTLIGHT
- EXCLUSION
- HUE
- SATURATION
- COLOR
- LUMINOSITY
-
-
Tip: To access custom properties,\nHold Shift when clicking edit
Name on Anim Data
@@ -749,9 +710,4 @@
Created mod config!
Your mod config file has been created at {0}!
-
-
- Outdated API Version! (v{0} < v{1})
- Your mod's config runs on an outdated API Version! This WILL cause compatiblilty and code issues!\n\nIt is recommended that you make changes to your mod and its config file to fit the new version.\n\nMeanwhile, the game will try its best to make your mod compatible, but you should still update your mod.\n\n(PS: If this is not your mod please disable Developer mode, and notify the mod developers if needed.)
-
\ No newline at end of file
diff --git a/assets/languages/en/Options.xml b/assets/languages/en/Options.xml
index 0227aeb67a..5d13610950 100644
--- a/assets/languages/en/Options.xml
+++ b/assets/languages/en/Options.xml
@@ -68,13 +68,6 @@
Framerate
Pretty self explanatory, isn't it?
- Unlimited
-
- FPS Counter
- If checked, displays the Framerate and Memory counter in the top left.
-
- More Options
- Use this menu to toggle the debug info categories on the FPS counter.
@@ -104,23 +97,6 @@
VRAM-Only Sprites
If checked, will only store the bitmaps in the GPU, freeing a LOT of memory (EXPERIMENTAL). Turning this off will consume a lot of memory, especially on bigger sprites. If you aren't sure, leave this on.
-
-
- Show Conductor Info
- If checked, will display information relating to the conductor on the debug FPS Counter menu.
-
- Show Flixel Info
- If checked, will display information relating to Flixel on the debug FPS Counter menu.
-
- Show System Info
- If checked, will display information relating to your system on the debug FPS Counter menu.
-
- Show Assets Tree Info
- If checked, will display information relating to the assets tree on the debug FPS Counter menu.
-
- Show Stats Info
- If checked, will Fuck idk
-
diff --git a/assets/languages/es/Editors.xml b/assets/languages/es/Editors.xml
index 7b0c3853ac..8e4da69a0f 100644
--- a/assets/languages/es/Editors.xml
+++ b/assets/languages/es/Editors.xml
@@ -639,17 +639,4 @@
Guardar y Volver al Menú
Cancelar
-
-
- ¡Falta la configuración del mod!
- ¡Tu mod actualmente no tiene un archivo de configuración!\n\n¿Quisieras generar uno automáticamente?\n\n(PD: Si este no es tu mod por favor deshabilita el Modo Desarrollador para que no aparezca esta ventana.)
-
- ¡Configuración creada!
- ¡Tu archivo de configuración se creó en {0}!
-
-
-
- ¡Versión de API desactualizada! (v{0} < v{1})
- ¡La configuración de tu mod usa una versión API antigua! ¡Esto CAUSARÁ problemas con código y compatibilidad!\n\nSe recomienda que cambies el código y configuración de tu mod para encajar con la versión nueva.\n\nMientras tanto, se hará lo mejor posible para compatibilizar el código actual, pero aún se recomienda actualizarlo.\n\n(PD: Si este no es tu mod por favor deshabilita el Modo Desarrollador, y notifique a los desarrolladores del mod si es necesario.)
-
\ No newline at end of file
diff --git a/assets/languages/es/Options.xml b/assets/languages/es/Options.xml
index ea1e235a7c..60ad120cd9 100644
--- a/assets/languages/es/Options.xml
+++ b/assets/languages/es/Options.xml
@@ -65,7 +65,6 @@
FPS
Yo creo que se explica sólo.
- Ilimitado
@@ -77,12 +76,6 @@
Si está activado, Week 6 tendrá un efecto que alinea todos los píxeles en la pantalla.
- CALIDAD
- Modos de calidad del cual elegir, "BAJA" = Quitar efectos fuertes del juego para correr mejor, "ALTA" = Habilita todos los efectos, a costo de rendimiento, "PERSONALIZADA" = Deja cambiar todas las opciones a su gusto.
- BAJA
- ALTA
- PERSONALIZADA
-
Si está desactivado, desactivará el antialiasing en todos los sprites, lo cual puede aumentar el rendimiento pero hará los visuales más pixelados.
Modo de Baja Memoria
@@ -187,9 +180,6 @@
Esto controla por cuánto tiempo te advertirá el editor antes de autoguardar (en segundos..., 0 para desactivar)
Carpeta de Autoguardados
- Si está activado, el autoguardado se guardará en otra carpeta con su fecha en vez de sobreescribir el archivo actual. (song/autosaves/)
-
- Contador de Memoria Clásico
- Si está activado, el contador de memoria mostrará el uso de memoria del GC de Haxe y su máximo (el comportamiento original) en vez de la memoria de trabajo vista en el Administrador de Tareas.
+ Si está activado, el autoguardado se guardará en otra carpeta con su fecha en vez de sobreescribir el archivo actual. (song/autosaves/)
\ No newline at end of file
diff --git a/assets/languages/es/assets/images/menus/mainmenu/credits.png b/assets/languages/es/assets/images/menus/mainmenu/credits.png
deleted file mode 100644
index 6566abf03a..0000000000
Binary files a/assets/languages/es/assets/images/menus/mainmenu/credits.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/mainmenu/credits.xml b/assets/languages/es/assets/images/menus/mainmenu/credits.xml
deleted file mode 100644
index f0f099009c..0000000000
--- a/assets/languages/es/assets/images/menus/mainmenu/credits.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/languages/es/assets/images/menus/mainmenu/donate.png b/assets/languages/es/assets/images/menus/mainmenu/donate.png
deleted file mode 100644
index 6f99980992..0000000000
Binary files a/assets/languages/es/assets/images/menus/mainmenu/donate.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/mainmenu/donate.xml b/assets/languages/es/assets/images/menus/mainmenu/donate.xml
deleted file mode 100644
index 00a63957d4..0000000000
--- a/assets/languages/es/assets/images/menus/mainmenu/donate.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/languages/es/assets/images/menus/mainmenu/freeplay.png b/assets/languages/es/assets/images/menus/mainmenu/freeplay.png
deleted file mode 100644
index c097c9d9b2..0000000000
Binary files a/assets/languages/es/assets/images/menus/mainmenu/freeplay.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/mainmenu/freeplay.xml b/assets/languages/es/assets/images/menus/mainmenu/freeplay.xml
deleted file mode 100644
index 6d02092675..0000000000
--- a/assets/languages/es/assets/images/menus/mainmenu/freeplay.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/languages/es/assets/images/menus/mainmenu/options.png b/assets/languages/es/assets/images/menus/mainmenu/options.png
deleted file mode 100644
index f8b702ec10..0000000000
Binary files a/assets/languages/es/assets/images/menus/mainmenu/options.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/mainmenu/options.xml b/assets/languages/es/assets/images/menus/mainmenu/options.xml
deleted file mode 100644
index 8a79171307..0000000000
--- a/assets/languages/es/assets/images/menus/mainmenu/options.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/languages/es/assets/images/menus/mainmenu/story mode.png b/assets/languages/es/assets/images/menus/mainmenu/story mode.png
deleted file mode 100644
index 0b8b75bebc..0000000000
Binary files a/assets/languages/es/assets/images/menus/mainmenu/story mode.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/mainmenu/story mode.xml b/assets/languages/es/assets/images/menus/mainmenu/story mode.xml
deleted file mode 100644
index 287dfeeaac..0000000000
--- a/assets/languages/es/assets/images/menus/mainmenu/story mode.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/languages/es/assets/images/menus/storymenu/difficulties/easy.png b/assets/languages/es/assets/images/menus/storymenu/difficulties/easy.png
deleted file mode 100644
index 3dddf63d97..0000000000
Binary files a/assets/languages/es/assets/images/menus/storymenu/difficulties/easy.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/storymenu/difficulties/hard.png b/assets/languages/es/assets/images/menus/storymenu/difficulties/hard.png
deleted file mode 100644
index cdeb552300..0000000000
Binary files a/assets/languages/es/assets/images/menus/storymenu/difficulties/hard.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/titlescreen/TitleEnter.png b/assets/languages/es/assets/images/menus/titlescreen/TitleEnter.png
deleted file mode 100644
index 207479278a..0000000000
Binary files a/assets/languages/es/assets/images/menus/titlescreen/TitleEnter.png and /dev/null differ
diff --git a/assets/languages/es/assets/images/menus/titlescreen/TitleEnter.xml b/assets/languages/es/assets/images/menus/titlescreen/TitleEnter.xml
deleted file mode 100644
index 99fedfb51f..0000000000
--- a/assets/languages/es/assets/images/menus/titlescreen/TitleEnter.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/languages/es/config.ini b/assets/languages/es/config.ini
index beb09edbd8..2a3e1384f9 100644
--- a/assets/languages/es/config.ini
+++ b/assets/languages/es/config.ini
@@ -1,3 +1,3 @@
name="Español"
-credits="Terionic, Zoe/Draw & Betopia"
-version="1.1.0"
\ No newline at end of file
+credits="Terionic"
+version="1.0.0"
\ No newline at end of file
diff --git a/assets/languages/it/Options.xml b/assets/languages/it/Options.xml
index 42b9c1ce9e..ed24a8ee31 100644
--- a/assets/languages/it/Options.xml
+++ b/assets/languages/it/Options.xml
@@ -199,8 +199,5 @@
Offset Editor Chart
Se abilitato, questo attiverà l'opzione Offset Canzone anche nel Editor Chart.
-
- Contatore Memoria Classico
- Se attivato, il contatore di memoria mostrerà l'uso di memoria del GC di Haxe e il suo picco (il comportamento originale) invece della memoria di lavoro vista nel Task Manager.
diff --git a/assets/languages/pl/Options.xml b/assets/languages/pl/Options.xml
index 9a518f35cd..f14687f33f 100644
--- a/assets/languages/pl/Options.xml
+++ b/assets/languages/pl/Options.xml
@@ -175,9 +175,6 @@
Folder Autozapisów
Jeśli włączone, Edytor będzie zapisywać pliki w osobnym folderze, zamiast nadpisywania aktualnego pliku. (song/autosaves/)
-
- Klasyczny Licznik Pamięci
- Jeśli włączone, licznik pamięci pokaże użycie pamięci GC Haxe i jego szczyt (oryginalne zachowanie) zamiast pamięci roboczej widocznej w Menedżerze Zadań.
diff --git a/assets/languages/pt/Editors.xml b/assets/languages/pt/Editors.xml
index e7ff96be96..2d0a05208d 100644
--- a/assets/languages/pt/Editors.xml
+++ b/assets/languages/pt/Editors.xml
@@ -695,9 +695,4 @@
Config. de mod criada!
As configurações do seu mod foram criadas em {0}!
-
-
- Versão de API desatualizada! (v{0} < v{1})
- A configuração do seu mod está a usar uma versão do API velha! Isto IRÁ causar problemas de compatibilidade e código!\n\nÉ recomendado que faça mudanças ao seu mod e o seu ficheiro de configuração para ajustar à versão mais recente.\n\nPor enquanto, o jogo irá tentar o seu melhor para fazer o seu mod compatível, mas deverá na mesma atualizá-lo.\n\n(PS: Se este mod não for seu, por favor desative o modo de desenvolvedor, e notifique os desenvolvedores deste mod se necessário.)
-
\ No newline at end of file
diff --git a/assets/languages/pt/Options.xml b/assets/languages/pt/Options.xml
index 4b418925ec..82ce97c7d7 100644
--- a/assets/languages/pt/Options.xml
+++ b/assets/languages/pt/Options.xml
@@ -206,9 +206,6 @@
Atraso no Mapeador
Se marcado, irá ativar o Atraso da Música no Editor de Mapas, também.
-
- Contador de Memória Clássico
- Se marcado, o contador de memória mostrará o uso de memória do GC de Haxe e o seu máximo (o comportamento original) em vez da memória de trabalho vista no Gerenciador de Tarefas.
\ No newline at end of file
diff --git a/building/libs.xml b/building/libs.xml
index 993d4a89d8..5b59b5efd5 100644
--- a/building/libs.xml
+++ b/building/libs.xml
@@ -26,6 +26,7 @@
+
diff --git a/project.xml b/project.xml
index 585f8b1a6a..462001db02 100644
--- a/project.xml
+++ b/project.xml
@@ -52,6 +52,9 @@
+
+
+
@@ -141,6 +144,7 @@
+
diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx
index 5a4d54eacf..b652052416 100644
--- a/source/funkin/backend/FunkinSprite.hx
+++ b/source/funkin/backend/FunkinSprite.hx
@@ -78,7 +78,6 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
public var animateSettings:FlxAnimateSettings = {};
- // originally used for zoom factor, now unused
var _rect2:FlxRect;
public function new(?X:Float = 0, ?Y:Float = 0, ?SimpleGraphic:FlxGraphicAsset)
@@ -190,6 +189,7 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
// ANIMATE ATLAS DRAWING
#if REGION
+
public override function destroy()
{
if (animOffsets != null) {
@@ -207,6 +207,48 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
}
#end
+ // ZOOM FACTOR
+ private inline function __shouldDoZoomFactor()
+ return zoomFactorEnabled && zoomFactor != 1;
+
+ private inline function __prepareZoomFactor(?rect:FlxRect, camera:FlxCamera):FlxRect {
+ if (Flags.USE_LEGACY_ZOOM_FACTOR)
+ return (rect ?? FlxRect.get()).set(
+ camera.width * 0.5,
+ camera.height * 0.5,
+ (camera.scaleX > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleX, 1, zoomFactor)),
+ (camera.scaleY > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleY, 1, zoomFactor))
+ );
+ else
+ return (rect ?? FlxRect.get()).set(
+ camera.width * 0.5 + camera.scroll.x * scrollFactor.x,
+ camera.height * 0.5 + camera.scroll.y * scrollFactor.y,
+ (camera.scaleX > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleX, 1, zoomFactor)),
+ (camera.scaleY > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleY, 1, zoomFactor))
+ );
+ }
+
+ override public function isOnScreen(?camera:FlxCamera):Bool
+ {
+ if (forceIsOnScreen)
+ return true;
+
+ if (camera == null)
+ camera = FlxG.camera;
+
+ var bounds = getScreenBounds(_rect, camera);
+ if (bounds.width == 0 && bounds.height == 0)
+ return false;
+ return camera.containsRect(bounds);
+ }
+
+ private inline function __shouldDoAngleFactor()
+ return angleFactorEnabled && angleFactor != 1;
+
+ private inline function __prepareAngleFactor(camera:FlxCamera):Float {
+ return FlxMath.lerp(-camera.angle, 0, angleFactor);
+ }
+
// OFFSETTING
#if REGION
public var animOffsets:Map = new Map();
@@ -336,23 +378,22 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
override function prepareDrawMatrix(matrix:FlxMatrix, camera:FlxCamera):Void {
super.prepareDrawMatrix(matrix, camera);
- final ox = camera.width * 0.5, oy = camera.height * 0.5;
- final sx = (camera.scaleX > 0.0 ? Math.max : Math.min)(0.0, (1.0 - zoomFactor) / camera.scaleX + zoomFactor);
- final sy = (camera.scaleY > 0.0 ? Math.max : Math.min)(0.0, (1.0 - zoomFactor) / camera.scaleY + zoomFactor);
+ if (__shouldDoZoomFactor() || __shouldDoAngleFactor()) {
+ __prepareZoomFactor(_rect2, camera);
- if (zoomFactorEnabled && zoomFactor != 1) {
- matrix.setTo(
- matrix.a * sx, matrix.b * sy,
- matrix.c * sx, matrix.d * sy,
- (matrix.tx - ox) * sx + ox,
- (matrix.ty - oy) * sy + oy
- );
- }
-
- if (angleFactorEnabled && angleFactor != 1) {
- matrix.translate(-ox, -oy);
- matrix.rotate(-camera.angle * FlxAngle.TO_RAD * (1.0 - angleFactor));
- matrix.translate(ox, oy);
+ if (__shouldDoZoomFactor()) {
+ matrix.setTo(
+ matrix.a * _rect2.width, matrix.b * _rect2.height,
+ matrix.c * _rect2.width, matrix.d * _rect2.height,
+ (matrix.tx - _rect2.x) * _rect2.width + _rect2.x,
+ (matrix.ty - _rect2.y) * _rect2.height + _rect2.y
+ );
+ }
+ if (__shouldDoAngleFactor()) {
+ matrix.translate(-_rect2.x, -_rect2.y);
+ matrix.rotate(FlxAngle.asRadians(__prepareAngleFactor(camera)));
+ matrix.translate(_rect2.x, _rect2.y);
+ }
}
}
diff --git a/source/funkin/backend/FunkinText.hx b/source/funkin/backend/FunkinText.hx
index 76da9f66c6..d9a18dd161 100644
--- a/source/funkin/backend/FunkinText.hx
+++ b/source/funkin/backend/FunkinText.hx
@@ -1,6 +1,5 @@
package funkin.backend;
-import animate.FlxAnimate;
import flixel.FlxCamera;
import flixel.FlxG;
import flixel.math.FlxMath;
@@ -10,11 +9,8 @@ import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.text.FlxText;
import flixel.util.FlxColor;
-import flixel.graphics.frames.FlxFrame;
-
import funkin.backend.system.Flags;
-@:access(animate.FlxAnimate)
class FunkinText extends FlxText
{
public var zoomFactor:Float = 1;
diff --git a/source/funkin/backend/assets/AssetsLibraryList.hx b/source/funkin/backend/assets/AssetsLibraryList.hx
index 6d2415fae3..10c1d33208 100644
--- a/source/funkin/backend/assets/AssetsLibraryList.hx
+++ b/source/funkin/backend/assets/AssetsLibraryList.hx
@@ -54,85 +54,70 @@ class AssetsLibraryList extends AssetLibrary {
return lib;
}
- var assetPathCacheLibrary:Map, Map>> = [];
- var assetPathCacheTime:Map, Map>> = [];
+ var existsSpecificCacheLibrary:Map, Map>> = [];
+ var existsSpecificCacheTime:Map, Map>> = [];
- private function shouldSkipLib(lib:AssetLibrary, source:AssetSource) {
- if (source == BOTH || lib.tag == BOTH) return false;
- return source != lib.tag;
- }
-
- public function getAssetPathLibrary(id:String, type:Null, source:AssetSource = BOTH):Null {
- var cacheLibraryPaths:Map = null;
- if (Flags.PATHS_CACHE_LIFETIME != 0) {
- // Prevent massive lags on repetitive usage, primarily with getting note sprite sheets in mania charts (usually 2k+ notes)
- final time = haxe.Timer.stamp();
+ public function existsSpecific(id:String, type:String, source:AssetSource = BOTH) {
+ if (!id.startsWith("assets/") && existsSpecific('assets/$id', type, source))
+ return true;
- var cacheLibraryTypes = assetPathCacheLibrary.get(source), cacheTimeTypes = assetPathCacheTime.get(source);
- if (cacheLibraryTypes == null) {
- assetPathCacheLibrary.set(source, cacheLibraryTypes = []);
- assetPathCacheTime.set(source, cacheTimeTypes = []);
- }
+ // Prevent massive lags on repetitive usage, primarily with getting note sprite sheets in mania charts (usually 2k+ notes)
+ final time = haxe.Timer.stamp();
- cacheLibraryPaths = cacheLibraryTypes.get(type);
- var cacheTimePaths = cacheTimeTypes.get(type);
- if (cacheLibraryPaths == null) {
- cacheLibraryTypes.set(type, cacheLibraryPaths = []);
- cacheTimeTypes.set(type, cacheTimePaths = []);
- }
-
- if (cacheTimePaths.exists(id)) {
- final library = cacheLibraryPaths.get(id);
-
- if (Flags.PATHS_CACHE_LIFETIME != null) {
- final cacheSafeTime = cacheTimePaths.get(id) + Flags.PATHS_CACHE_LIFETIME;
+ var cacheLibraryTypes = existsSpecificCacheLibrary.get(source), cacheTimeTypes = existsSpecificCacheTime.get(source);
+ if (cacheLibraryTypes == null) {
+ existsSpecificCacheLibrary.set(source, cacheLibraryTypes = []);
+ existsSpecificCacheTime.set(source, cacheTimeTypes = []);
+ }
- if (library != null) {
- if (time < cacheSafeTime) return library;
- else if (!shouldSkipLib(library, source)
- && (type == null ? library.exists(id, @:privateAccess library.types.get(id)) : library.exists(id, type))
- ) {
- cacheTimePaths.set(id, time);
- return library;
- }
+ var cacheLibraryPaths = cacheLibraryTypes.get(type), cacheTimePaths = cacheTimeTypes.get(type);
+ if (cacheLibraryPaths == null) {
+ cacheLibraryTypes.set(type, cacheLibraryPaths = []);
+ cacheTimeTypes.set(type, cacheTimePaths = []);
+ }
- cacheLibraryPaths.remove(id);
- }
- else if (time < cacheSafeTime) {
- return null;
- }
+ if (cacheTimePaths.exists(id)) {
+ final cacheSafeTime = cacheTimePaths.get(id) + 6, library = cacheLibraryPaths.get(id);
+ if (library != null) {
+ if (time < cacheSafeTime) return true;
+ else if (!shouldSkipLib(library, source) && library.exists(id, type)) {
+ cacheTimePaths.set(id, time);
+ return true;
}
- else
- return library;
- }
- cacheTimePaths.set(id, time);
+ cacheLibraryPaths.remove(id);
+ }
+ else if (time < cacheSafeTime) {
+ return false;
+ }
}
+ cacheTimePaths.set(id, time);
+
for (library in libraries) {
if (shouldSkipLib(library, source)) continue;
- if (type == null ? library.exists(id, @:privateAccess library.types.get(id)) : library.exists(id, type)) {
- if (cacheLibraryPaths != null) cacheLibraryPaths.set(id, library);
- return library;
+ if (library.exists(id, type)) {
+ cacheLibraryPaths.set(id, library);
+ return true;
}
}
- return null;
+ return false;
}
-
- public function existsSpecific(id:String, type:String, source:AssetSource = BOTH)
- return (!id.startsWith("assets/") && existsSpecific('assets/$id', type, source)) || getAssetPathLibrary(id, type, source) != null;
-
public override inline function exists(id:String, type:String):Bool
return existsSpecific(id, type, BOTH);
- public function getSpecificPath(id:String, source:AssetSource = BOTH):Null {
- final library = getAssetPathLibrary(id, null, source);
- if (library != null) {
- final path = library.getPath(id);
- if (path != null) return path;
- }
+ public function getSpecificPath(id:String, source:AssetSource = BOTH) {
+ for(k=>e in libraries) {
+ if (shouldSkipLib(e, source)) continue;
+ @:privateAccess
+ if (e.exists(id, e.types.get(id))) {
+ var path = e.getPath(id);
+ if (path != null)
+ return path;
+ }
+ }
return null;
}
@@ -178,20 +163,36 @@ class AssetsLibraryList extends AssetLibrary {
}
public function getSpecificAsset(id:String, type:String, source:AssetSource = BOTH):Dynamic {
- if (!id.startsWith("assets/")) {
- final asset = getSpecificAsset('assets/$id', type, source);
- if (asset != null) return asset;
- }
+ try {
+ if (!id.startsWith("assets/")) {
+ var ass = getSpecificAsset('assets/$id', type, source);
+ if (ass != null) {
+ return ass;
+ }
+ }
+ for(k=>l in libraries) {
+ if (shouldSkipLib(l, source)) continue;
- final library = getAssetPathLibrary(id, type, source);
- if (library != null) {
- final asset = library.getAsset(id, type);
- if (asset != null) return asset;
+ @:privateAccess
+ if (l.exists(id, l.types.get(id))) {
+ var asset = l.getAsset(id, type);
+ if (asset != null) {
+ return asset;
+ }
+ }
+ }
+ return null;
+ } catch(e) {
+ // TODO: trace the error
+ throw e;
}
-
return null;
}
+ private function shouldSkipLib(lib:AssetLibrary, source:AssetSource) {
+ if (source == BOTH || lib.tag == BOTH) return false;
+ return source != lib.tag;
+ }
public override inline function getAsset(id:String, type:String):Dynamic
return getSpecificAsset(id, type, BOTH);
@@ -253,7 +254,13 @@ class AssetsLibraryList extends AssetLibrary {
public function reset() {
unloadLibraries();
- resetAssetPathCache();
+
+ for(source in [AssetSource.SOURCE, AssetSource.MODS, AssetSource.BOTH]) {
+ existsSpecificCacheLibrary[source]?.clear();
+ existsSpecificCacheTime[source]?.clear();
+ }
+ existsSpecificCacheLibrary.clear();
+ existsSpecificCacheTime.clear();
libraries.resize(0);
@@ -261,15 +268,6 @@ class AssetsLibraryList extends AssetLibrary {
for (d in __defaultLibraries) addLibrary(d);
}
- public function resetAssetPathCache() {
- for (source in [AssetSource.SOURCE, AssetSource.MODS, AssetSource.BOTH]) {
- assetPathCacheLibrary[source]?.clear();
- assetPathCacheTime[source]?.clear();
- }
- assetPathCacheLibrary.clear();
- assetPathCacheTime.clear();
- }
-
public function addLibrary(lib:AssetLibrary, ?tag:AssetSource, ?addTransLib:Bool = true) {
libraries.insert(0, lib);
if (tag != null) lib.tag = tag;
diff --git a/source/funkin/backend/assets/ModsFolder.hx b/source/funkin/backend/assets/ModsFolder.hx
index 0e33f999ed..e1db271b7b 100644
--- a/source/funkin/backend/assets/ModsFolder.hx
+++ b/source/funkin/backend/assets/ModsFolder.hx
@@ -3,6 +3,7 @@ package funkin.backend.assets;
import flixel.util.FlxSignal.FlxTypedSignal;
import funkin.backend.system.MainState;
import funkin.backend.utils.CoolUtil;
+import funkin.backend.utils.GJUtil;
import haxe.ds.StringMap;
import haxe.io.Path;
import lime.text.Font;
@@ -75,9 +76,17 @@ class ModsFolder {
}
public static function reloadMods() {
- if (!__firstTime)
+ if (!__firstTime) {
+ #if GAMEJOLT_API
+ if (GJUtil.active)
+ GJUtil.logout();
+ #end
+
FlxG.switchState(new MainState());
+ }
__firstTime = false;
+
+
}
/**
diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx
index 53fddc1c6b..adcb6bce79 100644
--- a/source/funkin/backend/assets/Paths.hx
+++ b/source/funkin/backend/assets/Paths.hx
@@ -1,9 +1,5 @@
package funkin.backend.assets;
-import haxe.io.Path;
-
-import lime.utils.AssetLibrary;
-
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFramesCollection;
@@ -14,6 +10,10 @@ import animate.FlxAnimateFrames.FlxAnimateSpritemapCollection;
import funkin.backend.assets.ModsFolder;
import funkin.backend.scripting.Script;
+import haxe.io.Path;
+import lime.utils.AssetLibrary;
+import openfl.utils.Assets as OpenFlAssets;
+import animate.FlxAnimateFrames;
using StringTools;
@@ -29,70 +29,28 @@ class Paths
});
}
- static function getExistingPath(path:String, prefix:String, nullFail:Bool):Null {
- var fixedPath = prefix + path;
-
+ public static inline function getPath(file:String, ?library:String) {
+ var returnedPath:String = library != null ? '$library:assets/$library/$file' : 'assets/$file';
#if (sys && !windows)
- if (Assets.exists(fixedPath)) return fixedPath;
- else if (Flags.PATHS_UNIX_FIX) {
- final isFile = path.lastIndexOf(".") != -1, parts = path.split("/");
- final n = parts.length - 1;
-
- fixedPath = prefix;
- for (i => part in parts) {
- final partIsFile = isFile && i == n;
- final lower = part.toLowerCase(), entries = partIsFile ? assetsTree.getFiles(fixedPath) : assetsTree.getFolders(fixedPath);
- var pass = false;
-
- for (entry in entries) if (entry.toLowerCase() == lower) {
- pass = true;
- if (partIsFile) fixedPath += entry;
- else fixedPath += entry + "/";
+ returnedPath = Path.normalize(returnedPath);
+ if (OpenFlAssets.exists(returnedPath)) return returnedPath;
+ var fixedPath:String = library != null ? '$library:assets/$library/' : 'assets/';
+ var parts:Array = returnedPath.split("/");
+ for (it=>part in parts) {
+ if (it == 0) continue;
+ var entries:Array = null;
+ if (Path.extension(part) == "") entries = assetsTree.getFolders(fixedPath);
+ else entries = assetsTree.getFiles(fixedPath);
+ for (entry in entries) {
+ if (entry.toLowerCase() == part.toLowerCase()) {
+ fixedPath += entry + (it != parts.length - 1 ? "/" : "");
break;
}
-
- if (!pass) {
- if (nullFail) return null;
- else fixedPath += part;
- }
}
-
- return fixedPath;
}
- else if (!nullFail) return fixedPath;
- #else
- if (!nullFail || Assets.exists(fixedPath)) return fixedPath;
+ if (returnedPath.toLowerCase() == fixedPath.toLowerCase()) returnedPath = fixedPath;
#end
-
- return null;
- }
-
- public static function getPath(file:String, ?library:String, ?exts:OneOfTwo>) {
- if (exts == null)
- return library == null ? getExistingPath(file, 'assets/', false) : getExistingPath('$library/$file', '$library:assets/', false);
-
- var idx = file.lastIndexOf("/");
- var p:Null = idx == -1 ? "" : file.substr(0, idx);
- file = file.substr(idx + 1);
-
- final e:Array = (exts is String) ? [exts] : (cast exts);
-
- idx = file.lastIndexOf(".");
- if (idx != -1) {
- e.unshift(file.substr(idx + 1));
- file = file.substr(0, idx);
- }
-
- p = library == null ? getExistingPath(p, 'assets/', true) : getExistingPath('$library/$p', '$library:assets/', true);
- if (p == null) return library == null ? 'assets/$file.${e[0]}' : '$library:assets/$library/$file.${e[0]}';
- else p += "/";
-
- for (extension in e) {
- final path = getExistingPath('$file.$extension', p, true);
- if (path != null) return path;
- }
-
- return '$p$file.${e[0]}';
+ return returnedPath;
}
public static inline function video(key:String, ?ext:OneOfTwo>)
@@ -163,10 +121,10 @@ class Paths
public static inline function script(key:String, ?library:String, isAssetsPath:Bool = false) {
var scriptPath = isAssetsPath ? key : getPath(key, library);
- if (!Assets.exists(scriptPath)) {
+ if (!OpenFlAssets.exists(scriptPath)) {
var p:String;
for(ex in Script.scriptExtensions) {
- if (Assets.exists(p = scriptPath + '.' + ex)) {
+ if (OpenFlAssets.exists(p = scriptPath + '.' + ex)) {
scriptPath = p;
break;
}
@@ -191,7 +149,7 @@ class Paths
* @param font The font's path (if it's already passed as a font name, the same name will be returned)
*/
inline static public function getFontName(font:String) {
- return Assets.exists(font, FONT) ? Assets.getFont(font).fontName : font;
+ return OpenFlAssets.exists(font, FONT) ? OpenFlAssets.getFont(font).fontName : font;
}
public static inline function font(key:String) {
diff --git a/source/funkin/backend/scripting/GlobalScript.hx b/source/funkin/backend/scripting/GlobalScript.hx
index ee58344a99..5c2365c737 100644
--- a/source/funkin/backend/scripting/GlobalScript.hx
+++ b/source/funkin/backend/scripting/GlobalScript.hx
@@ -71,8 +71,6 @@ class GlobalScript {
FlxG.signals.preStateSwitch.add(function() {
call("preStateSwitch");
- if (Flags.PATHS_CACHE_RESET_ON_SWITCH_STATE) Paths.assetsTree.resetAssetPathCache();
-
var stateName = Type.getClassName(Type.getClass(@:privateAccess FlxG.game._requestedState));
stateName = stateName.substring(stateName.lastIndexOf(".") + 1);
if (Flags.MOD_REDIRECT_STATES.exists(stateName)) {
diff --git a/source/funkin/backend/scripting/events/gameplay/BopZoomEvent.hx b/source/funkin/backend/scripting/events/gameplay/BopZoomEvent.hx
deleted file mode 100644
index d9a6a54793..0000000000
--- a/source/funkin/backend/scripting/events/gameplay/BopZoomEvent.hx
+++ /dev/null
@@ -1,18 +0,0 @@
-package funkin.backend.scripting.events.gameplay;
-
-final class BopZoomEvent extends CancellableEvent {
- /**
- Whether to use the zoomMultiplier property in the camera.
- **/
- public var useZoomMultiplier:Bool;
-
- /**
- If higher than this, the zoom will be skipped.
- **/
- public var maxZoomMultiplier:Float;
-
- /**
- The zoom's strength multiplier.
- **/
- public var zoomStrength:Float;
-}
\ No newline at end of file
diff --git a/source/funkin/backend/system/CommandLineHandler.hx b/source/funkin/backend/system/CommandLineHandler.hx
index a0e83dc7d2..e6d74007fc 100644
--- a/source/funkin/backend/system/CommandLineHandler.hx
+++ b/source/funkin/backend/system/CommandLineHandler.hx
@@ -10,18 +10,16 @@ final class CommandLineHandler {
case null:
break;
case "-h" | "-help" | "help":
- Sys.println("------------------------ Codename Engine Command Line help ------------------------");
- Sys.println("-help | Show this help");
+ Sys.println("-- Codename Engine Command Line help --");
+ Sys.println("-help | Show this help");
#if MOD_SUPPORT
- Sys.println("-mod [mod name] | Load a specific mod");
- Sys.println("-modfolder [path] | Sets the mod folder path");
- Sys.println("-addonsfolder [path] | Sets the addons folder path");
+ Sys.println("-mod [mod name] | Load a specific mod");
+ Sys.println("-modfolder [path] | Sets the mod folder path");
+ Sys.println("-addonsfolder [path] | Sets the addons folder path");
#end
- Sys.println("-song [song] [difficulty] [variation] | Go to PlayState with that song");
- Sys.println("-chart [song] [difficulty] [variation] | Go to Charter with that song");
- Sys.println("-nocolor | Disables colors in the terminal");
- Sys.println("-nogpubitmap | Forces GPU only bitmaps off");
- Sys.println("-nocwdfix | Turns off automatic working directory fix");
+ Sys.println("-nocolor | Disables colors in the terminal");
+ Sys.println("-nogpubitmap | Forces GPU only bitmaps off");
+ Sys.println("-nocwdfix | Turns off automatic working directory fix");
Sys.exit(0);
#if MOD_SUPPORT
case "-m" | "-mod" | "-currentmod":
@@ -58,41 +56,6 @@ final class CommandLineHandler {
Sys.exit(1);
}
#end
- case "-song":
- i++;
- var arg = cmd[i];
- if (arg == null) {
- Sys.println("[ERROR] You need to specify the song name");
- Sys.exit(1);
- } else {
- Main.goToSong = arg;
- if ((arg = cmd[i + 1]).charAt(0) != "-") {
- i++;
- Main.goToDifficulty = arg;
- if ((arg = cmd[i + 1]).charAt(0) != "-") {
- i++;
- Main.goToVariation = arg;
- }
- }
- }
- case "-chart":
- i++;
- var arg = cmd[i];
- if (arg == null) {
- Sys.println("[ERROR] You need to specify the song name");
- Sys.exit(1);
- } else {
- Main.goToSong = arg;
- Main.goToCharter = true;
- if ((arg = cmd[i + 1]).charAt(0) != "-") {
- i++;
- Main.goToDifficulty = arg;
- if ((arg = cmd[i + 1]).charAt(0) != "-") {
- i++;
- Main.goToVariation = arg;
- }
- }
- }
case "-nocolor":
Main.noTerminalColor = true;
case "-nogpubitmap":
@@ -104,7 +67,7 @@ final class CommandLineHandler {
case "-v" | "-verbose" | "--verbose":
Main.verbose = true;
default:
- Sys.println('Unknown command "${cmd[i]}"');
+ Sys.println("Unknown command");
}
i++;
}
diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx
index d04aa7e7f1..4d382644d8 100644
--- a/source/funkin/backend/system/Flags.hx
+++ b/source/funkin/backend/system/Flags.hx
@@ -28,6 +28,7 @@ class Flags {
public static var MOD_NAME:String = "";
public static var MOD_DESCRIPTION:String = "";
public static var MOD_AUTHOR:String = "";
+ public static var MOD_VERSION:String = "";
@:lazy public static var MOD_API_VERSION:Null = null;
public static var MOD_DOWNLOAD_LINK:String = "";
public static var MOD_DEPENDENCIES:Array = [];
@@ -43,6 +44,11 @@ class Flags {
public static var MOD_REDIRECT_STATES:Map = [];
+ @:also(funkin.backend.system.gamejolt.GameJoltSecurity.gameId)
+ public static var MOD_GAMEJOLT_GAME_ID:String = '';
+ @:also(funkin.backend.system.gamejolt.GameJoltSecurity.encryptedGameToken)
+ public static var MOD_GAMEJOLT_ENCRYPTED_TOKEN:String = '';
+
// -- Codename's Default Flags --
@:lazy public static var SAVE_PATH:String = haxe.macro.Compiler.getDefine("SAVE_PATH");
@:lazy public static var SAVE_NAME:String = haxe.macro.Compiler.getDefine("SAVE_NAME");
@@ -61,10 +67,6 @@ class Flags {
public static var REPO_OWNER:String = "CodenameCrew";
public static var REPO_URL:String = 'https://github.com/$REPO_OWNER/$REPO_NAME';
- @:lazy public static var PATHS_CACHE_LIFETIME:Null = null;
- public static var PATHS_CACHE_RESET_ON_SWITCH_STATE:Bool = true;
- public static var PATHS_UNIX_FIX:Bool = true;
-
/**
* Preferred file extensions for the game's audio files.
*/
@@ -159,8 +161,6 @@ class Flags {
public static var USE_LEGACY_CENTER_CAM:Null = null;
public static var USE_LEGACY_FLXANIMATE_STAGE_MATRIX:Null = null;
- public static var CHANGE_WINDOW_TITLE_PLAYSTATE:Bool = true;
-
@:also(funkin.game.Character.FALLBACK_DEAD_CHARACTER)
public static var DEFAULT_GAMEOVER_CHARACTER:String = "bf-dead";
@@ -179,6 +179,8 @@ class Flags {
public static var DEFAULT_CAM_ZOOM_LERP:Float = 0.05;
public static var DEFAULT_HUD_ZOOM_LERP:Float = 0.05;
+ public static var USE_LEGACY_ZOOM_FACTOR:Null = null;
+
// Font configuration
public static var DEFAULT_FONT:String = "vcr.ttf";
public static var DEFAULT_FONT_SIZE:Int = 16;
@@ -331,6 +333,7 @@ class Flags {
if (MOD_API_VERSION == null) MOD_API_VERSION = CURRENT_API_VERSION;
if (WINDOW_TITLE_USE_MOD_NAME == null) WINDOW_TITLE_USE_MOD_NAME = !overridenFlags.exists('TITLE') && overridenFlags.exists('MOD_NAME');
if (USE_LEGACY_TIMING == null) USE_LEGACY_TIMING = MOD_API_VERSION < 2;
+ if (USE_LEGACY_ZOOM_FACTOR == null) USE_LEGACY_ZOOM_FACTOR = MOD_API_VERSION < 2;
if (SUSTAINS_AS_ONE_NOTE == null) SUSTAINS_AS_ONE_NOTE = MOD_API_VERSION >= 2;
if (DEFAULT_GLSL_VERSION == null) {
if (MOD_API_VERSION < 2) {
diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx
index 40d4a84802..8bf1a18805 100644
--- a/source/funkin/backend/system/Main.hx
+++ b/source/funkin/backend/system/Main.hx
@@ -44,10 +44,7 @@ class Main extends Sprite
public static var forceGPUOnlyBitmapsOff:Bool = false;
public static var noTerminalColor:Bool = false;
public static var verbose:Bool = false;
- public static var goToSong:String = null;
- public static var goToDifficulty:String = null;
- public static var goToVariation:String = null;
- public static var goToCharter:Bool = false;
+ public static var goToGJConfirm:Bool = false;
public static var scaleMode:FunkinRatioScaleMode;
#if !mobile
@@ -239,7 +236,7 @@ class Main extends Sprite
NativeAPI.allocConsole();
#end
- if (PlayerSettings.solo.controls.FPS_COUNTER && Options.fpsCounter)
+ if (PlayerSettings.solo.controls.FPS_COUNTER)
Framerate.debugMode = (Framerate.debugMode + 1) % 3;
}
diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx
index 97e812c485..776453b4f7 100644
--- a/source/funkin/backend/system/MainState.hx
+++ b/source/funkin/backend/system/MainState.hx
@@ -11,8 +11,11 @@ import funkin.backend.assets.ModsFolderLibrary;
import funkin.backend.assets.ZipFolderLibrary;
import funkin.backend.chart.EventsData;
import funkin.backend.system.framerate.Framerate;
+import funkin.backend.system.gamejolt.GameJoltData;
+import funkin.backend.utils.GJUtil;
import funkin.editors.ModConfigWarning;
import funkin.menus.TitleState;
+import funkin.menus.gamejolt.GameJoltCompleteScreen;
import haxe.io.Path;
@@ -29,7 +32,11 @@ class MainState extends FlxState {
public static var initiated:Bool = false;
public override function create() {
super.create();
- if (!initiated) Main.loadGameSettings();
+ if (!initiated) {
+ Main.loadGameSettings();
+ }
+
+ initiated = true;
#if sys
CoolUtil.deleteFolder('./.temp/'); // delete temp folder
@@ -161,16 +168,9 @@ class MainState extends FlxState {
if (cast(lib, ZipFolderLibrary).PRELOAD_VIDEOS) cast(lib, ZipFolderLibrary).precacheVideos();
}
- if (!initiated) {
- if (Main.goToSong != null) {
- if (Main.goToCharter) FlxG.switchState(new funkin.editors.charter.Charter(Main.goToSong, Main.goToDifficulty, Main.goToVariation));
- else {
- PlayState.loadSong(Main.goToSong, Main.goToDifficulty, Main.goToVariation);
- FlxG.switchState(new PlayState());
- }
- }
- }
- initiated = true;
+ #if GAMEJOLT_API
+ GJUtil.init();
+ #end
if (@:privateAccess FlxG.game._nextState == null) {
var startState:Class = Flags.DISABLE_WARNING_SCREEN ? TitleState : funkin.menus.WarningState;
@@ -187,8 +187,21 @@ class MainState extends FlxState {
return;
}
}
+ }
- FlxG.switchState(cast Type.createInstance(startState, []));
+ // In this case if the mod we just loaded a compressed modpack, we can't edit or modify files without decompressing it.
+ if (Options.devMode && Options.allowConfigWarning && !isZipMod) {
+ var lib:ModsFolderLibrary;
+ for (e in Paths.assetsTree.libraries) if ((lib = cast AssetsLibraryList.getCleanLibrary(e)) is ModsFolderLibrary
+ && lib.modName == ModsFolder.currentModFolder)
+ {
+ if (lib.exists(Paths.ini("config/modpack"), lime.utils.AssetType.TEXT)) break;
+
+ FlxG.switchState(new ModConfigWarning(lib, startState));
+ return;
+ }
}
+
+ FlxG.switchState(cast Type.createInstance(startState, []));
}
}
diff --git a/source/funkin/backend/system/framerate/AssetTreeInfo.hx b/source/funkin/backend/system/framerate/AssetTreeInfo.hx
index 50984bd885..b7647261c8 100644
--- a/source/funkin/backend/system/framerate/AssetTreeInfo.hx
+++ b/source/funkin/backend/system/framerate/AssetTreeInfo.hx
@@ -13,7 +13,6 @@ class AssetTreeInfo extends FramerateCategory {
public function new() {
super("Asset Libraries Tree Info");
- visible = Options.fpsCounterAssets;
}
public override function __enterFrame(t:Float) {
diff --git a/source/funkin/backend/system/framerate/ConductorInfo.hx b/source/funkin/backend/system/framerate/ConductorInfo.hx
index c1c68daefe..3fb213d3dd 100644
--- a/source/funkin/backend/system/framerate/ConductorInfo.hx
+++ b/source/funkin/backend/system/framerate/ConductorInfo.hx
@@ -5,7 +5,6 @@ import funkin.backend.system.macros.StringMacro;
class ConductorInfo extends FramerateCategory {
public function new() {
super("Conductor Info");
- visible = Options.fpsCounterConductor;
}
public override function __enterFrame(t:Float) {
diff --git a/source/funkin/backend/system/framerate/FlixelInfo.hx b/source/funkin/backend/system/framerate/FlixelInfo.hx
index 42d3347f6b..675bd2facc 100644
--- a/source/funkin/backend/system/framerate/FlixelInfo.hx
+++ b/source/funkin/backend/system/framerate/FlixelInfo.hx
@@ -6,7 +6,6 @@ import funkin.backend.system.macros.StringMacro;
class FlixelInfo extends FramerateCategory {
public function new() {
super("Flixel Info");
- visible = Options.fpsCounterFlixel;
}
public override function __enterFrame(t:Float) {
diff --git a/source/funkin/backend/system/framerate/Framerate.hx b/source/funkin/backend/system/framerate/Framerate.hx
index 8787478563..0d04c14bfb 100644
--- a/source/funkin/backend/system/framerate/Framerate.hx
+++ b/source/funkin/backend/system/framerate/Framerate.hx
@@ -19,13 +19,6 @@ class Framerate extends Sprite {
#if SHOW_BUILD_ON_FPS
public static var codenameBuildField:CodenameBuildField;
#end
- public static var conductorInfo:ConductorInfo;
- public static var flixelInfo:FlixelInfo;
- public static var systemInfo:SystemInfo;
- public static var assetInfo:AssetTreeInfo;
- #if (gl_stats && !disable_cffi && (!html5 || !canvas))
- public static var statsInfo:StatsInfo;
- #end
public static var fontName:String = #if windows '${Sys.getEnv("windir")}\\Fonts\\consola.ttf' #else "_typewriter" #end;
@@ -34,7 +27,7 @@ class Framerate extends Sprite {
* 1: FPS VISIBLE
* 2: FPS & DEBUG INFO VISIBLE
*/
- public static var debugMode:Int = Options.fpsCounter ? 1 : 0;
+ public static var debugMode:Int = 1;
public static var offset:FlxPoint = new FlxPoint();
public var bgSprite:Bitmap;
@@ -72,13 +65,13 @@ class Framerate extends Sprite {
#if SHOW_BUILD_ON_FPS
__addToList(codenameBuildField = new CodenameBuildField());
#end
- __addCategory(conductorInfo = new ConductorInfo());
- __addCategory(flixelInfo = new FlixelInfo());
- __addCategory(systemInfo = new SystemInfo());
- __addCategory(assetInfo = new AssetTreeInfo());
+ __addCategory(new ConductorInfo());
+ __addCategory(new FlixelInfo());
+ __addCategory(new SystemInfo());
+ __addCategory(new AssetTreeInfo());
#if (gl_stats && !disable_cffi && (!html5 || !canvas))
- __addCategory(statsInfo = new StatsInfo());
+ __addCategory(new StatsInfo());
#end
}
@@ -135,7 +128,6 @@ class Framerate extends Sprite {
var y:Float = height + 4;
for(c in categories) {
- if (!c.visible) continue;
c.title.selectable = c.text.selectable = selectable;
c.alpha = debugAlpha;
c.x = FlxMath.lerp(-c.width - offset.x, 0, debugAlpha);
diff --git a/source/funkin/backend/system/framerate/FramerateCategory.hx b/source/funkin/backend/system/framerate/FramerateCategory.hx
index 9a87f4783b..3bc28e3e56 100644
--- a/source/funkin/backend/system/framerate/FramerateCategory.hx
+++ b/source/funkin/backend/system/framerate/FramerateCategory.hx
@@ -36,6 +36,7 @@ class FramerateCategory extends Sprite {
this.title.multiline = this.title.wordWrap = false;
this.text.multiline = true;
+
this.text.y = this.title.y + this.title.height + 2;
}
diff --git a/source/funkin/backend/system/framerate/FramerateCounter.hx b/source/funkin/backend/system/framerate/FramerateCounter.hx
index cebfa9f51f..3ada08c0e4 100644
--- a/source/funkin/backend/system/framerate/FramerateCounter.hx
+++ b/source/funkin/backend/system/framerate/FramerateCounter.hx
@@ -7,7 +7,6 @@ import openfl.text.TextFormat;
class FramerateCounter extends Sprite {
public var fpsNum:TextField;
public var fpsLabel:TextField;
- public var lowFPS:TextField;
public var lastFPS:Float = 0;
private var frameCount:Int = 0;
@@ -17,17 +16,13 @@ class FramerateCounter extends Sprite {
private final updateInterval:Float = 1 / 15;
private var lastUpdateTime:Float = 0;
- private final history:Array = [];
-
public function new() {
super();
fpsNum = new TextField();
fpsLabel = new TextField();
- lowFPS = new TextField();
- history.resize(15);
- for (label in [fpsNum, fpsLabel, lowFPS]) {
+ for(label in [fpsNum, fpsLabel]) {
label.autoSize = LEFT;
label.x = 0;
label.y = 0;
@@ -37,12 +32,9 @@ class FramerateCounter extends Sprite {
label.selectable = false;
addChild(label);
}
-
- lowFPS.alpha = 0.5;
}
public function reload() {
- for (label in [fpsNum, fpsLabel, lowFPS]) label.defaultTextFormat = new TextFormat(Framerate.fontName, label == fpsNum ? 18 : 12, -1);
lastUpdateTime = 0;
}
@@ -60,15 +52,12 @@ class FramerateCounter extends Sprite {
}
final timer = openfl.Lib.getTimer();
- final delta = timer - accumulatedTime;
- accumulatedTime = timer;
- history.shift();
- history.push(lastFPS = FlxMath.lerp(lastFPS, delta <= 0 ? 0 : (1000.0 / delta * frameCount), 1.0 - Math.pow(0.75, delta * 0.06)));
+ final time = timer - accumulatedTime;
- var lowest = history[0];
- for (f in history) if (f < lowest || f == 0) lowest = f;
- lowFPS.text = "/1%: " + Math.round(lowest);
+ accumulatedTime = timer;
+
+ lastFPS = FlxMath.lerp(lastFPS, time <= 0 ? 0 : (1000 / time * frameCount), 1.0 - Math.pow(0.75, time * 0.06));
fpsNum.text = Std.string(Math.round(lastFPS));
lastUpdateTime = frameCount = 0;
@@ -80,8 +69,5 @@ class FramerateCounter extends Sprite {
{
fpsLabel.x = fpsNum.x + fpsNum.width;
fpsLabel.y = (fpsNum.y + fpsNum.height) - fpsLabel.height;
-
- lowFPS.x = fpsLabel.x + fpsLabel.width;
- lowFPS.y = (fpsLabel.y + fpsLabel.height) - lowFPS.height;
}
}
\ No newline at end of file
diff --git a/source/funkin/backend/system/framerate/MemoryCounter.hx b/source/funkin/backend/system/framerate/MemoryCounter.hx
index 0530d0bb7b..622dc4e9b3 100644
--- a/source/funkin/backend/system/framerate/MemoryCounter.hx
+++ b/source/funkin/backend/system/framerate/MemoryCounter.hx
@@ -28,9 +28,6 @@ class MemoryCounter extends Sprite {
addChild(label);
}
memoryPeakText.alpha = 0.5;
- #if !(cpp && (windows || mac || linux))
- memoryPeakText.visible = false;
- #end
}
public function reload() {
@@ -43,44 +40,6 @@ class MemoryCounter extends Sprite {
if (alpha <= 0.05) return;
super.__enterFrame(t);
- #if (cpp && (windows || mac || linux))
- var legacy = funkin.options.Options.legacyMemoryCounter;
-
- if (legacy) {
- if (legacy != usingLegacy) {
- usingLegacy = legacy;
- memoryPeak = 0;
- }
-
- final mem = MemoryUtil.currentMemUsage();
- if (memoryPeak < mem) memoryPeak = mem;
- if (mem == memory) {
- updateLabelPosition();
- return;
- }
-
- memory = mem;
- memoryPeakText.visible = true;
- memoryText.text = CoolUtil.getSizeString(memory);
- memoryPeakText.text = ' / ${CoolUtil.getSizeString(memoryPeak)}';
- } else {
- if (legacy != usingLegacy) usingLegacy = legacy;
-
- final gcMem = MemoryUtil.currentMemUsage();
- final osMem = MemoryUtil.currentProcessMemUsage();
-
- if (gcMem == memory && osMem == memoryPeak) {
- updateLabelPosition();
- return;
- }
-
- memory = gcMem;
- memoryPeak = osMem;
- memoryPeakText.visible = true;
- memoryText.text = CoolUtil.getSizeString(gcMem);
- memoryPeakText.text = ' / ${CoolUtil.getSizeString(osMem)}';
- }
- #else
final mem = MemoryUtil.currentMemUsage();
if (mem == memory) {
@@ -89,8 +48,9 @@ class MemoryCounter extends Sprite {
}
memory = mem;
- memoryText.text = CoolUtil.getSizeString(mem);
- #end
+ if (memoryPeak < memory) memoryPeak = memory;
+ memoryText.text = CoolUtil.getSizeString(memory);
+ memoryPeakText.text = ' / ${CoolUtil.getSizeString(memoryPeak)}';
updateLabelPosition();
}
diff --git a/source/funkin/backend/system/framerate/StatsInfo.hx b/source/funkin/backend/system/framerate/StatsInfo.hx
index 63ef47270c..134c2649fc 100644
--- a/source/funkin/backend/system/framerate/StatsInfo.hx
+++ b/source/funkin/backend/system/framerate/StatsInfo.hx
@@ -8,7 +8,6 @@ import funkin.backend.system.macros.StringMacro;
class StatsInfo extends FramerateCategory {
public function new() {
super("Asset Libraries Tree Info");
- visible = Options.fpsCounterStats;
}
public override function __enterFrame(t:Float) {
diff --git a/source/funkin/backend/system/framerate/SystemInfo.hx b/source/funkin/backend/system/framerate/SystemInfo.hx
index 603c06477a..1cda22ff07 100644
--- a/source/funkin/backend/system/framerate/SystemInfo.hx
+++ b/source/funkin/backend/system/framerate/SystemInfo.hx
@@ -179,7 +179,6 @@ class SystemInfo extends FramerateCategory {
public function new() {
super("System Info");
- visible = Options.fpsCounterSystem;
}
public override function __enterFrame(t:Float) {
diff --git a/source/funkin/backend/system/gamejolt/GameJoltData.hx b/source/funkin/backend/system/gamejolt/GameJoltData.hx
new file mode 100644
index 0000000000..08a601ec10
--- /dev/null
+++ b/source/funkin/backend/system/gamejolt/GameJoltData.hx
@@ -0,0 +1,755 @@
+package funkin.backend.system.gamejolt;
+
+import flixel.util.FlxSave;
+import haxe.xml.Access;
+import haxe.io.Bytes;
+import haxe.crypto.Aes;
+import haxe.crypto.mode.Mode;
+import haxe.crypto.padding.Padding;
+import haxe.Json;
+import sys.io.File;
+import funkin.backend.utils.GJUtil;
+import funkin.backend.system.gamejolt.GameJoltSecurity;
+import funkin.backend.assets.AssetSource;
+import funkin.savedata.FunkinSave;
+
+//region Typedefs
+/**
+ * Global data store format. Used to provide a unanimous
+ * structure to data loading.
+ */
+typedef CNEGameJoltData = {
+ ownerU:String,
+ ownerI:Int,
+ defTrophies:String,
+ cusTrophies:String,
+ leaderboards:String,
+ addlData:String,
+}
+
+/**
+ * User data store format. Used to provide a unanimous
+ * structure to data loading.
+ */
+typedef GameJoltUserData = {
+ options:Dynamic,
+ scores:Dynamic,
+ miscItems:Dynamic,
+}
+
+/**
+ * Trophy data. This is not the data pulled from the GameJolt API -
+ * instead, it allows for things like trophy pre-requisites
+ * or anything that should be excluded from requirements.
+ */
+typedef GJTrophyData = {
+ id:Int,
+ ?require:Array,
+ ?except:Array,
+ ?hidden:Bool,
+ ?weekName:String,
+ ?songName:String,
+}
+//endregion
+/**
+ * Data class for GameJolt.
+ *
+ * Holds, sets, and modifies variables from the global data store (which can
+ * only be set by the owner of the GameJolt game) or the user-specific
+ * data store.
+ */
+@:noCustomClass
+class GameJoltData
+{
+ //region Variables
+ /**
+ * The username of the owner of the GameJolt page.
+ * Set using the global data store.
+ */
+ public static var ownerUsername:Null = null;
+
+ /**
+ * The ID of the owner of the GameJolt page.
+ * Cross-checks this alongside the current user to
+ * determine access to global variables.
+ */
+ public static var ownerUserId:Null = null;
+
+ /**
+ * Leaderboards from the global data store.
+ */
+ public static var leaderboards(default, null):Map = new Map();
+
+ /**
+ * Any trophies defined specifically for use in hardcoding.
+ * Set using the global data store.
+ * To set trophies in hardcode, use the `definitions` var as the `def`
+ * attribute in the node, then set the parameters and trophy ID.
+ */
+ public static var definedTrophies(default, null):Map = new Map();
+
+ /**
+ * Any valid definitions that can be used in hardcoding.
+ */
+ public static var definitions(default, null):Array = ['open-first', 'friday-night', 'week', 'song', 'complete-all', 'fc-first', 'fc-all', 'death-first'];
+
+ /**
+ * Custom trophies that mods may want to implement outside of the usual suspects.
+ * Unfortunately, these could be easy to cheese.
+ */
+ public static var customTrophies(default, null):Map = new Map();
+
+ /**
+ * Any trophies that the user already earned.
+ * Exists to not make an insane amount of calls per game, even if
+ * they are async.
+ */
+ public static var earnedTrophies:Map = new Map();
+
+ /**
+ * Any pieces of the save data that the game should save in user data.
+ * Set using the global data store.
+ */
+ public static var dataToInclude:Map> = new Map>();
+
+ /**
+ * Path of the gamejolt.xml.
+ * Mainly here to prevent ghost variables where possible.
+ */
+ static var xmlPath:String = Paths.xml('config/gamejolt');
+
+ /**
+ * Help text to be printed in the XML file.
+ * Mainly here to prevent ghost varialbes where possible.
+ */
+ static var helpText:String = 'XML for gamejolt setup.
+This stores it to the global database of the game, under the
+key CNE_DATASTORE.
+By storing it there, only the owner of the game can modify
+that data or delete it if necessary.
+
+TO SETUP GAMEJOLT FOR YOUR GAME:
+- Ensure that the flag `GAMEJOLT_GAME_ID` in the General section
+is set to your game ID.
+- Input the following nodes into this xml:
+
+
+ game-private-key-here
+- Run the mod. It will start a session with the owner\'s username, encrypt
+your game key, and inject the data into the global data store.';
+ //endregion
+
+ #if GAMEJOLT_API
+ /**
+ * Used for initialization of the global data store and
+ * GameJolt integrations.
+ * Only called if the system doesn't recognize a game
+ * security key.
+ */
+ public static function loadAdminData()
+ {
+ // get xml
+ var access = getGJX();
+
+ if (access == null) {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("Unable to locate "),
+ Logs.logText("gamejolt.xml", GREEN),
+ Logs.logText(' in '),
+ Logs.logText("data/config", GREEN),
+ Logs.logText(' folder!'),
+ ], ERROR);
+ return;
+ }
+
+ // return if owner and gamekey nodes are absent
+ if (!access.hasNode.owner || !access.hasNode.gamekey) {
+ Logs.trace('Missing owner or gamekey node from gamejolt.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ return;
+ }
+
+ if (!access.node.owner.has.username || !access.node.owner.has.token) {
+ Logs.trace('Missing username or user token in gamejolt.xml owner node!', ERROR, LIGHTGRAY, 'GameJolt');
+ return;
+ }
+
+ // encrypt gamekey
+ encryptToken(access.node.gamekey.innerData);
+
+ //
+ GJUtil.attemptLogin(access.node.owner.att.username, access.node.owner.att.token, (bl) -> {
+ if (!bl) {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("Unable to log in user "),
+ Logs.logText(access.node.owner.att.username, GREEN),
+ Logs.logText(' for data upload.')
+ ], ERROR);
+ } else {
+ ownerUsername = access.node.owner.att.username;
+ ownerUserId = GameJoltSecurity.userId;
+
+ setGlobalData(true, (bl) -> {
+ GJUtil.logout(false, true);
+ if (bl) {
+ if (buildGamejoltXml()) {
+ Main.goToGJConfirm = true;
+ }
+ }
+ }, access);
+ }
+ }, true, true);
+ }
+
+ //region CNE Globals
+ /**
+ * Loads the variables from the global data store.
+ * @param callback Function to run on failure (false) or success
+ * (true) to load global data.
+ */
+ public static function loadGlobalData(?callback:Bool->Void)
+ {
+ GameJoltSecurity.sendTrusted(DATA_FETCH('CNE_DATASTORE', false), true, function(err) {
+ Logs.trace('Unable to obtain global mod data: $err GameJolt API turning off automatically.', ERROR, LIGHTGRAY, 'GameJolt');
+ GJUtil.logout(false, true);
+ if (callback != null) callback(false);
+ }, function(resp) {
+ var daDat:CNEGameJoltData = cast Json.parse(resp.data);
+
+ ownerUsername = daDat.ownerU;
+ ownerUserId = daDat.ownerI;
+ if (daDat.defTrophies != 'null') {
+ var itms:Array = daDat.defTrophies.split('---');
+ for (indiv in itms) {
+ var details:Array = indiv.split('-V:');
+ definedTrophies.set(details[0], cast Json.parse(details[1]));
+ }
+ }
+ if (daDat.cusTrophies != 'null') {
+ var itms:Array = daDat.cusTrophies.split('---');
+ for (indiv in itms) {
+ var details:Array = indiv.split('-V:');
+ customTrophies.set(details[0], cast Json.parse(details[1]));
+ }
+ }
+ if (daDat.leaderboards != 'null') {
+ var itms:Array = daDat.leaderboards.split('---');
+ for (indiv in itms) {
+ var details:Array = indiv.split('-V:');
+ leaderboards.set(details[0], Std.parseInt(details[1]));
+ }
+ }
+ if (daDat.addlData != 'null') {
+ var itms:Array = daDat.addlData.split('---');
+ for (indiv in itms) {
+ var details:Array = indiv.split('-V:');
+ dataToInclude.set(details[0], details[1].split('-vVv-'));
+ }
+ }
+
+ if (callback != null) callback(true);
+ });
+ }
+
+ /**
+ * Sets the variables located in the global data store.
+ * @param cleanSet Whether or not this is the first time we're setting
+ * these variables in the global data store. If we're replacing a data
+ * store that already exists, this should be `false`.
+ * @param callback Function to run on failure (false) or success
+ * (true) to set global data.
+ * @param data Access data to set global data store to. Loads gamejolt.xml
+ * by default.
+ */
+ public static function setGlobalData(cleanSet:Bool = false, ?callback:Bool->Void, ?data:Access)
+ {
+ if (data == null) {
+ data = getGJX();
+ if (data == null) {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("Unable to locate "),
+ Logs.logText("gamejolt.xml", GREEN),
+ Logs.logText(' in '),
+ Logs.logText("data/config", GREEN),
+ Logs.logText(' folder!'),
+ ], ERROR);
+ return;
+ }
+ }
+
+ var trophyDefArray:Array = [];
+ var trophyCusArray:Array = [];
+ var leaderArray:Array = [];
+ var dataArray:Array = [];
+
+ // With all of these nodes, it's important to make sure that
+ // what we're importing a) exists, and b) isn't just a blank string.
+
+ if (data.hasNode.trophies) {
+ if (data.node.trophies.hasNode.defined) for (trophyDef in data.node.trophies.nodes.defined) {
+ if (!trophyDef.has.def || !trophyDef.has.id || trophyDef.att.def == '' || trophyDef.att.id == '')
+ continue;
+
+ if (!definitions.contains(trophyDef.att.def))
+ customTrophies.set(trophyDef.att.def, {
+ id: Std.parseInt(trophyDef.att.id),
+ require: (trophyDef.has.require && trophyDef.att.require != '') ? [for (trop in trophyDef.att.require.split('//')) Std.parseInt(trop.trim())] : null,
+ except: (trophyDef.has.except && trophyDef.att.except != '') ? [for (trop in trophyDef.att.except.split('//')) trop.trim()] : null,
+ hidden: (trophyDef.has.hidden && trophyDef.att.hidden != '') ? (trophyDef.att.hidden == 'true') : null,
+ weekName: null,
+ });
+
+ if (trophyDef.att.def == 'week' && (!trophyDef.has.weekName || trophyDef.att.weekName == ''))
+ continue;
+
+ if (trophyDef.att.def == 'song' && (!trophyDef.has.songName || trophyDef.att.songName == ''))
+ continue;
+
+ definedTrophies.set(trophyDef.att.def + (trophyDef.att.def == 'week' ? '-${trophyDef.att.weekName}' : (trophyDef.att.def == 'song' ? '-${trophyDef.att.songName}': '')), {
+ id: Std.parseInt(trophyDef.att.id),
+ require: (trophyDef.has.require && trophyDef.att.require != '') ? [for (trop in trophyDef.att.require.split(',')) Std.parseInt(trop.trim())] : null,
+ except: (trophyDef.has.except && trophyDef.att.except != '') ? [for (trop in trophyDef.att.except.split('//')) trop.trim()] : null,
+ hidden: (trophyDef.has.hidden && trophyDef.att.hidden != '') ? (trophyDef.att.hidden == 'true') : null,
+ weekName: (trophyDef.att.def == 'week' && trophyDef.has.weekName && trophyDef.att.weekName != '') ? trophyDef.att.weekName : null,
+ songName: (trophyDef.att.def == 'song' && trophyDef.has.songName && trophyDef.att.songName != '') ? trophyDef.att.songName : null,
+ });
+ }
+
+ if (data.node.trophies.hasNode.custom) for (trophyDef in data.node.trophies.nodes.custom) {
+ if (!trophyDef.has.def || !trophyDef.has.id)
+ continue;
+
+ customTrophies.set(trophyDef.att.def, {
+ id: Std.parseInt(trophyDef.att.id),
+ require: (trophyDef.has.require && trophyDef.att.require != '') ? [for (trop in trophyDef.att.require.split(',')) Std.parseInt(trop.trim())] : null,
+ except: (trophyDef.has.except && trophyDef.att.except != '') ? [for (trop in trophyDef.att.except.split('//')) trop.trim()] : null,
+ hidden: (trophyDef.has.hidden && trophyDef.att.hidden != '') ? (trophyDef.att.hidden == 'true') : null,
+ weekName: null,
+ });
+ }
+ }
+
+ if (data.hasNode.leaderboards) {
+ if (data.node.leaderboards.hasNode.song) for (leaderboard in data.node.leaderboards.nodes.song) {
+ if (!leaderboard.has.id || !leaderboard.has.name || leaderboard.att.name == '' || leaderboard.att.id == '')
+ continue;
+
+ leaderboards.set('song-' + leaderboard.att.name + (leaderboard.has.diff ? '--D:${leaderboard.att.diff}' : '') + ' (V:${leaderboard.has.vari ? leaderboard.att.vari : 'Default'})', Std.parseInt(leaderboard.att.id));
+ }
+
+ if (data.node.leaderboards.hasNode.week) for (leaderboard in data.node.leaderboards.nodes.week) {
+ if (!leaderboard.has.id || !leaderboard.has.name || leaderboard.att.name == '' || leaderboard.att.id == '')
+ continue;
+
+ leaderboards.set('week-' + leaderboard.att.name + (leaderboard.has.diff ? '--D:${leaderboard.att.diff}' : ''), Std.parseInt(leaderboard.att.id));
+ }
+ }
+
+ if (data.hasNode.data) for (dat in data.node.data.nodes.value) {
+ if (!dat.has.name || dat.att.name == '')
+ continue;
+
+ var location:String = (dat.has.inSave && dat.att.inSave != '') ? dat.att.inSave : "FlxG";
+ var curVars:Array = dataToInclude.exists(location) ? dataToInclude.get(location) : [];
+ curVars.push(dat.att.name);
+ dataToInclude.set(location, curVars);
+ }
+
+ for (key => value in definedTrophies)
+ trophyDefArray.push('$key-V:${Json.stringify(value)}');
+
+ for (key => value in customTrophies)
+ trophyCusArray.push('$key-V:${Json.stringify(value)}');
+
+ for (key => value in leaderboards)
+ leaderArray.push('$key-V:$value');
+
+ for (key => value in dataToInclude)
+ dataArray.push('$key-V:${value.join('-vVv-')}');
+
+ var sendOut:CNEGameJoltData = {
+ ownerU: ownerUsername,
+ ownerI: ownerUserId,
+ defTrophies: (trophyDefArray.length > 0 ? trophyDefArray.join('---') : "null"),
+ cusTrophies: (trophyCusArray.length > 0 ? trophyCusArray.join('---') : "null"),
+ leaderboards: (leaderArray.length > 0 ? leaderArray.join('---') : "null"),
+ addlData: (dataArray.length > 0 ? dataArray.join('---') : "null"),
+ };
+
+ Logs.trace('Sending global data...', INFO, LIGHTGRAY, "GameJolt");
+
+ GameJoltSecurity.sendTrusted(DATA_SET('CNE_DATASTORE', Json.stringify(sendOut), false), !cleanSet, function(err) {
+ Logs.trace('Unable to upload global GameJolt data: ${err}', ERROR, LIGHTGRAY, 'GameJolt');
+ if (cleanSet) {
+ reset();
+ GJUtil.logout(false, true);
+ } else if (callback != null)
+ callback(false);
+ }, function(resp) {
+ Logs.trace('Global data set successfully!', SUCCESS, LIGHTGRAY, "GameJolt");
+ if (callback != null) callback(true);
+ });
+ }
+
+ /**
+ * Wipes global data store from the GameJolt cloud data.
+ * @param callback Function to run on failure (false) or success
+ * (true) to wipe global data.
+ */
+ public static function wipeGlobalData(?callback:Bool->Void)
+ {
+ GameJoltSecurity.sendTrusted(DATA_REMOVE('CNE_DATASTORE', false), true, function(err) {
+ Logs.trace('Unable to wipe global mod data: $err', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }, function(resp) {
+ if (callback != null) callback(true);
+ });
+ }
+ //endregion
+
+ //region User Data
+ /**
+ * Saves user-specific data in the game's user-specific data store.
+ * @param callback Function to run on failure (false) or success
+ * (true) to save user data.
+ */
+ public static function setUserData(?callback:Bool->Void)
+ {
+ FunkinSave.flush();
+
+ var addlStuff = {};
+
+ if (dataToInclude != null) for (elem in dataToInclude.keys()) {
+ var daSave:FlxSave = Reflect.field(elem, "save");
+ if (daSave == null)
+ continue;
+ var addlSaveItems = {};
+ for (itm in dataToInclude.get(elem)) {
+ var itmVal = Reflect.field(daSave.data, itm);
+ if (itmVal == null)
+ continue;
+ Reflect.setField(addlSaveItems, itm, itmVal);
+ }
+ Reflect.setField(addlStuff, elem, addlSaveItems);
+ }
+
+ var dataToSend:GameJoltUserData = {
+ options: Options.__save.data,
+ scores: FunkinSave.save.data.highscores,
+ miscItems: addlStuff,
+ };
+
+
+ var daId:Null = GameJoltSecurity.userId;
+ if (daId == null) {
+ if (callback != null) callback(false);
+ } else {
+ Logs.trace('Sending user data...', INFO, LIGHTGRAY, "GameJolt");
+
+ GameJoltSecurity.sendTrusted(DATA_SET('USER_$daId', Json.stringify(dataToSend), true), true, function(err) {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("Unable to set data for user "),
+ Logs.logText(GJUtil.userName, GREEN),
+ Logs.logText(': ${err}')
+ ], ERROR);
+ if (callback != null)
+ callback(false);
+ }, function(resp) {
+ Logs.trace('User data sent successfully!', SUCCESS, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(true);
+ });
+ }
+ }
+
+ /**
+ * Loads user-specific data in game's user-specific data store.
+ * @param callback Function to run on failure (false) or success
+ * (true) to load user data.
+ */
+ public static function loadUserData(?callback:Bool->Void)
+ {
+ var daId:Null = GameJoltSecurity.userId;
+ if (daId == null) {
+ if (callback != null) callback(false);
+ } else {
+ Logs.trace('Fetching user data...', INFO, LIGHTGRAY, "GameJolt");
+
+ GameJoltSecurity.sendTrusted(DATA_FETCH('USER_$daId', true), true, function(err) {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("Unable to set data for user "),
+ Logs.logText(GJUtil.userName, GREEN),
+ Logs.logText(': ${err}')
+ ], ERROR);
+ if (callback != null) callback(false);
+ }, function(resp) {
+ Logs.trace('User data collected successfully; now setting save data to obtained user data...', INFO, LIGHTGRAY, "GameJolt");
+ var daData:GameJoltUserData = cast Json.parse(resp.data);
+ Options.__save.mergeData(daData.options, true);
+ FunkinSave.save.data.highscores = daData.scores;
+ if (daData.miscItems != {}) {
+ // I hate how much reflecting is in this code.
+ var locs:Array = Reflect.fields(daData.miscItems);
+ for (location in locs) {
+ if (location == null || location == '')
+ continue;
+ var daSve:FlxSave = Reflect.field(location, "save");
+ if (daSve == null)
+ continue;
+ for (daItm in Reflect.fields(location)) {
+ Reflect.setField(daSve.data, daItm, Reflect.field(location, daItm));
+ }
+
+ daSve.flush();
+ }
+ }
+ if (callback != null) callback(true);
+ });
+ }
+ }
+
+ /**
+ * Wipes user-specific data in game's user-specific data store.
+ * @param callback Function to run on failure (false) or success
+ * (true) to wipe user data.
+ */
+ public static function wipeUserData(?callback:Bool->Void)
+ {
+ var daId:Null = GameJoltSecurity.userId;
+ if (daId == null) {
+ if (callback != null) callback(false);
+ } else {
+ GameJoltSecurity.sendTrusted(DATA_REMOVE('USER_$daId', true), true, function(err) {
+ Logs.trace('Unable to wipe user mod data: $err', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }, function(resp) {
+ if (callback != null) callback(true);
+ });
+ }
+ }
+ //endregion
+
+ /**
+ * Resets the data in this class.
+ * @param fullWipe Whether or not to wipe earnedTrophies only
+ * (`false`) or all data in this class (`true`).
+ */
+ public static function reset(fullWipe:Bool = false)
+ {
+ earnedTrophies.clear();
+ if (fullWipe) {
+ ownerUsername = null;
+ ownerUserId = null;
+ leaderboards.clear();
+ definedTrophies.clear();
+ customTrophies.clear();
+ dataToInclude.clear();
+ }
+ }
+
+ /**
+ * Creates an XML file from the data provided in this class.
+ * @return Bool Whether or not the creation was successful.
+ */
+ static function buildGamejoltXml():Bool
+ {
+ final bodyNode:Xml = Xml.createElement('gamejolt');
+ final trophyNodes:Xml = Xml.createElement('trophies');
+ final leaderNodes:Xml = Xml.createElement('leaderboards');
+ final dataNodes:Xml = Xml.createElement('data');
+ final ret:Xml = Xml.createDocument();
+
+ for (key => trop in definedTrophies) {
+ var nodeToTrophy:Xml = Xml.createElement('defined');
+ nodeToTrophy.set('def', key.substring(0, trop.weekName != null ? 4 : null));
+ if (trop.require != null)
+ nodeToTrophy.set('require', trop.require.join(','));
+ if (trop.except != null)
+ nodeToTrophy.set('except', trop.except.join(','));
+ if (trop.hidden != null)
+ nodeToTrophy.set('hidden', '${trop.hidden}');
+ if (trop.weekName != null)
+ nodeToTrophy.set('week', trop.weekName);
+ nodeToTrophy.set('id', '${trop.id}');
+ trophyNodes.addChild(nodeToTrophy);
+ }
+
+ for (key => trop in customTrophies) {
+ var nodeToTrophy:Xml = Xml.createElement('custom');
+ nodeToTrophy.set('def', key);
+ if (trop.require != null)
+ nodeToTrophy.set('require', trop.require.join(','));
+ if (trop.except != null)
+ nodeToTrophy.set('except', trop.except.join(','));
+ if (trop.hidden != null)
+ nodeToTrophy.set('hidden', '${trop.hidden}');
+ nodeToTrophy.set('id', '${trop.id}');
+ trophyNodes.addChild(nodeToTrophy);
+ }
+
+ for (key => board in leaderboards) {
+ var nodeToLeader:Xml = Xml.createElement('song');
+ var diffInd:Int = key.indexOf('--D:');
+ var variInd:Int = key.indexOf('(V:');
+ var nameSplice = key.substring(0, diffInd != -1 ? diffInd : variInd).trim();
+ nodeToLeader.set('name', nameSplice);
+ if (diffInd != -1) {
+ var diffSplice:String = key.substring(diffInd + 4, variInd).trim();
+ nodeToLeader.set('diff', diffSplice);
+ }
+ var variSplice:String = key.substring(variInd + 3, key.lastIndexOf(')')).trim();
+ nodeToLeader.set('vari', variSplice);
+ nodeToLeader.set('id', '$board');
+ leaderNodes.addChild(nodeToLeader);
+ }
+
+ for (key => loc in dataToInclude) {
+ for (itm in loc) {
+ var nodeToData:Xml = Xml.createElement('value');
+ nodeToData.set('name', itm);
+ nodeToData.set('inSave', key);
+ dataNodes.addChild(nodeToData);
+ }
+ }
+
+ bodyNode.addChild(trophyNodes);
+ bodyNode.addChild(leaderNodes);
+ bodyNode.addChild(dataNodes);
+ ret.addChild(bodyNode);
+
+ var finalBool:Bool = true;
+ try {
+ File.saveContent(Paths.assetsTree.getSpecificPath(xmlPath, AssetSource.MODS), '\n' + XMLUtil.fixXMLText(ret.toString()));
+ } catch(e) {
+ finalBool = false;
+ Logs.trace('Error creating new XML file: ${e}', ERROR, LIGHTGRAY, "GameJolt");
+ }
+
+ return finalBool;
+ }
+
+ /**
+ * Gets the gamejolt.xml file from the specified static location.
+ * @return Null If the data load was successful, returns an
+ * Access of the XML data; if unsuccessful, returns `null`.
+ */
+ static function getGJX():Null
+ {
+ if (!Paths.assetsTree.existsSpecific(xmlPath, "TEXT", AssetSource.MODS))
+ return null;
+
+ var access:Access = null;
+ try {
+ access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", AssetSource.MODS)).firstElement());
+ } catch(e) {
+ Logs.trace('Error while parsing gamejolt.xml: ${Std.string(e)}', ERROR, LIGHTGRAY, 'GameJolt');
+ }
+ return access;
+ }
+
+ /**
+ * Encrypts a GameJolt game security key using AES encryption. It then
+ * sets the `GAMEJOLT_ENCRYPTED_TOKEN` flag to this newly-encrypted token.
+ *
+ * ## A note about the key and IV for AES
+ * The AES key should be kept hidden, and is done so using classes
+ * inaccessible to Hscript and a .env file that is hidden to open source.
+ * This .env file generates a random key for a build if the AES key is
+ * missing - meaning if it gets lost, mods have to re-encrypt their
+ * game security keys.
+ * It is common real-world practice however to include the IV alongside the
+ * encrypted text, as done here. Both the key and IV are required to unlock an
+ * AES-encrypted text. The key though should be kept as secret as possible,
+ * as it does most of the heavy lifting in encryption; the IV mainly
+ * obscures the key and first block of encrypted text. Think of the IV as
+ * icing on the cake.
+ *
+ * And yes - I did academic research for an FNF engine, why do you ask.
+ * @param token Token to encrypt.
+ */
+ static function encryptToken(token:String)
+ {
+ var validHex:String = "0123456789abcdef";
+
+ var dateString:String = Date.now().toString();
+
+ var hexString:String = '';
+
+ for (i in 0...dateString.length - 1) {
+ if (dateString.charAt(i) == ' ') continue;
+ if (hexString.length == 32) break;
+ var charCode:Int = StringTools.fastCodeAt(dateString, i);
+ var charString:String = StringTools.hex(charCode);
+ hexString += charString.substr(0, Std.int(Math.min(charString.length, 32 - hexString.length)));
+ }
+
+ for (i in 0...(32 - hexString.length)) {
+ if (FlxG.random.bool()) {
+ hexString += validHex.charAt(FlxG.random.int(0, validHex.length - 1));
+ } else {
+ hexString = validHex.charAt(FlxG.random.int(0, validHex.length - 1)) + hexString;
+ }
+ }
+ var iv:Bytes = Bytes.ofHex(hexString);
+
+ @:privateAccess
+ var aes:Aes = new Aes(Bytes.ofHex(GameJoltSecurity.CODENAME_AES_KEY), iv);
+ var encryp:Bytes = aes.encrypt(Mode.OFB, Bytes.ofString(token), Padding.NoPadding);
+
+ GameJoltSecurity.encryptedGameToken = Flags.MOD_GAMEJOLT_ENCRYPTED_TOKEN = hexString.toLowerCase() + encryp.toHex();
+ }
+ #else
+ //region GJ API Inaccessible
+ public static function loadAdminData()
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ return;
+ }
+
+ public static function loadGlobalData(?callback:Bool->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }
+
+ public static function setGlobalData(cleanSet:Bool = false, ?callback:Bool->Void, ?data:Access)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }
+
+ public static function wipeGlobalData(?callback:Bool->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }
+
+ public static function setUserData(?callback:Bool->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }
+
+ public static function loadUserData(?callback:Bool->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }
+
+ public static function wipeUserData(?callback:Bool->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }
+
+ public static function reset(fullWipe:Bool = false)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ return;
+ }
+ //endregion
+ #end
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/gamejolt/GameJoltSecurity.hx b/source/funkin/backend/system/gamejolt/GameJoltSecurity.hx
new file mode 100644
index 0000000000..d3c2f7164b
--- /dev/null
+++ b/source/funkin/backend/system/gamejolt/GameJoltSecurity.hx
@@ -0,0 +1,508 @@
+package funkin.backend.system.gamejolt;
+
+import hscript.IHScriptCustomAccessBehaviour;
+import funkin.backend.utils.GJUtil;
+import funkin.backend.utils.GJUtil.RequestType;
+import funkin.backend.system.gamejolt.GameJoltData;
+import haxe.crypto.*;
+import haxe.crypto.mode.Mode;
+import haxe.crypto.padding.Padding;
+import haxe.io.Bytes;
+import funkin.backend.utils.GJUtil.*;
+import haxe.Http;
+import haxe.Json;
+import openfl.events.*;
+#if ALLOW_MULTITHREADING
+import funkin.backend.utils.ThreadUtil;
+#end
+#if (target.threaded)
+import sys.thread.Thread;
+#end
+
+
+
+/**
+ * # A BIG MOTHERFUCKING WARNING
+ *
+ * This class handles the raw game keys for GameJolt keys.
+ * If the raw game keys are made public, people can mess with leaderboards, data, achievements,
+ * or whatever else is on the game page.
+ *
+ * As such, Codename Engine requires players to encrypt keys using the AES protocol, and
+ * the key to decrypt such keys is non-disclosable (hence why it's in a .env file).
+ *
+ * Also for security purposes, this class is unattainable via HScript.
+ *
+ * ~ SplatterDash
+ */
+
+@:noCustomClass @:build(funkin.backend.system.macros.SecretMacro.build())
+class GameJoltSecurity implements IHScriptCustomAccessBehaviour
+{
+ /**
+ * Token for the user if they're logged in.
+ */
+ public static var user_token:String = '';
+
+ /**
+ * ID number for the current mod.
+ */
+ public static var gameId:String = '';
+
+ /**
+ * ID number for the currently logged in user.
+ */
+ public static var userId:Null = null;
+ /**
+ * The encrypted game token. Set using GAMEJOLT_ENCRYPTED_TOKEN in ini file.
+ */
+ public static var encryptedGameToken(default, set):String;
+
+ /**
+ * The unencrypted game token. It's insanely hard to get this variable.
+ */
+ @:noPrivateAccess private static var revealedGameToken:String;
+
+ /**
+ * URL sent to GameJolt per request.
+ */
+ @:noPrivateAccess private static var url(get, never):String;
+
+ /**
+ * The previous GJResponse created by the API client. Usually for just storage purposes.
+ */
+ private static var lastResponse:GJResponse = {success: false, message: "No response yet."};
+
+ /**
+ * The current call being processed.
+ */
+ private static var curCall:Null = null;
+
+ /**
+ * The secret key to decrypt GameJolt keys using AES. This cannot be traced or located in any way.
+ */
+ @:envField
+ private static final CODENAME_AES_KEY:Null;
+
+ #if target.threaded
+ static final mutex = new sys.thread.Mutex();
+ #end
+
+ // hscript - thanks LJ :D
+
+ //region IHScriptCustomAccessBehaviour implementation
+ public var __allowSetGet:Bool = false;
+
+ public function hget(name:String):Dynamic
+ return null;
+
+ public function hset(name:String, val:Dynamic):Dynamic
+ return null;
+
+ public function __callGetter(name:String):Dynamic
+ return null;
+
+ public function __callSetter(name:String, val:Dynamic):Dynamic
+ return null;
+ //endregion
+
+ #if GAMEJOLT_API
+ /**
+ * This is a copy of GJUtil's "send" request, but kept here since Hscript can't get here.
+ * Any calls here are guaranteed to be from hardcoding.
+ * @param call The RequestType call to make. Can make any type of call.
+ * @param async Whether or not the call should be asyncronous.
+ * @param onError Callback function if an error occurs. Gives error string.
+ * @param onComplete Callback function on successful completion of the call. Gives response data.
+ * @param onProgress Callback function for progress on async calls. Gives a progress float array.
+ */
+ public static function sendTrusted(call:RequestType, async:Bool = false, ?onError:String->Void, ?onComplete:GJResponse->Void, ?onProgress:Array->Void)
+ {
+ @:privateAccess {
+ if (GJUtil.executing || !GJUtil.active)
+ return;
+ GJUtil.executing = true;
+ }
+
+ handleRequest(async, call, function(errstr) {
+ @:privateAccess
+ GJUtil.executing = false;
+ if (onError != null) onError(errstr);
+ }, function(resp) {
+ @:privateAccess {
+ GJUtil.executing = false;
+ if (onComplete != null) onComplete(GJUtil.formatImages(resp));
+ }
+ }, onProgress);
+ }
+
+ /**
+ * Unlocks a trophy from the `definedTrophies` map - in other words,
+ * any trophy that is specifically defined in hardcode.
+ * @param def Key/def of trophy in `definedTrophies` map.
+ * @param callback Function running on completion of unlock attempt. Returns
+ * trophy data if successful, `null` if unsuccessful.
+ */
+ public static function unlockDefinedTrophy(def:String, ?callback:Null->Void)
+ {
+ if (GameJoltData.definedTrophies == null || !GameJoltData.definedTrophies.exists(def)) {
+ Logs.error('No defined trophy exists with the key "$def".', RED, 'GameJolt');
+ if (callback != null) callback(null);
+ } else {
+ var daTrophy:GJTrophyData = GameJoltData.definedTrophies.get(def);
+
+ if (GameJoltData.earnedTrophies.exists(def)) {
+ Logs.error('User already earned trophy with key "$def"!', RED, 'GameJolt');
+ if (callback != null) callback(null);
+ } else {
+ var meetsReqs:Bool = true;
+ if (daTrophy.require != null) {
+ var reqsMet:Array = [];
+ for (earned in GameJoltData.earnedTrophies) {
+ if (daTrophy.require.contains(earned.id)) reqsMet.push(earned.id);
+ }
+
+ if (reqsMet.length != daTrophy.require.length)
+ meetsReqs = false;
+ }
+
+ if (!meetsReqs) {
+ Logs.error('User does not meet requirements for trophy with the key "$def".', RED, 'GameJolt');
+ if(callback != null) callback(null);
+ } else {
+ sendTrusted(BATCH(false, true, [TROPHIES_FETCH(false, daTrophy.id), TROPHIES_ADD(daTrophy.id)]), true, function(err) {
+ Logs.trace('Trophy unlock error: ${err}', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(null);
+ }, function(resp) {
+ GameJoltData.earnedTrophies.set(def, daTrophy);
+ if(resp.responses[0].trophies[0] == null) {
+ Logs.trace('Trophy with key $def already unlocked!', WARNING, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(null);
+ } else {
+ Logs.trace('Trophy unlock: ${resp.responses[0].trophies[0].title}!', SUCCESS, LIGHTGRAY, 'GameJolt');
+ if (GJUtil.onTrophyUnlock != null) GJUtil.onTrophyUnlock(resp.responses[0].trophies[0]);
+ if (callback != null) callback(resp.responses[0].trophies[0]);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Handled the main request without giving out any compromisable data.
+ * @param async Whether or not the call should be asyncronous.
+ * @param data The RequestType call to make. Can make any type of call.
+ * @param onError Callback function if an error occurs. Gives error string.
+ * @param onComplete Callback function on successful completion of the call. Gives response data.
+ * @param onProgress Callback function for progress on async calls. Gives a progress float array.
+ */
+ static function handleRequest(async:Bool = false, data:RequestType, ?onError:String->Void, ?onComplete:GJResponse->Void, ?onProgress:Array->Void)
+ {
+ if (encryptedGameToken == null || gameId == null) {
+ lastResponse = {success: false, message: 'Missing game token and/or game ID.'};
+ curCall = null;
+ if (onError != null) onError(lastResponse.message);
+ }
+
+ curCall = data;
+
+ if (async) {
+ #if ALLOW_MULTITHREADING ThreadUtil.execAsync#elseif (target.threaded) Thread.create#end (() -> {
+ var loader = new openfl.net.URLLoader();
+ loader.addEventListener(Event.COMPLETE, function(complete) {
+ lastResponse = Json.parse(cast(loader.data, String)).response;
+ if (lastResponse.message != null) {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText('Response Error: ${lastResponse.message}')
+ ], ERROR);
+ curCall = null;
+ if (onError != null) onError(lastResponse.message);
+ } else {
+ curCall = null;
+ if (onComplete != null) onComplete(lastResponse);
+ }
+ });
+ loader.addEventListener(ProgressEvent.PROGRESS, progress -> { if (onProgress != null) onProgress([progress.bytesLoaded, progress.bytesTotal]);});
+ loader.addEventListener(IOErrorEvent.IO_ERROR, function(ioError) {
+ lastResponse = {success: false, message: 'IO Error: ${ioError.text}'};
+ curCall = null;
+ if (onError != null) onError(lastResponse.message);
+ });
+ loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, (securityError) -> {
+ lastResponse = {success: false, message: 'Security Error: ${securityError.text}'};
+ curCall = null;
+ if (onError != null) onError(lastResponse.message);
+ });
+ loader.load(new openfl.net.URLRequest(url));
+ });
+ } else {
+ var loader:Http = new Http(url);
+ loader.onData = function(data) {
+ lastResponse = cast Json.parse(data).response;
+ if (lastResponse.message != null) {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText('Response Error: ${lastResponse.message}')
+ ], ERROR);
+ curCall = null;
+ if (onError != null) onError(lastResponse.message);
+ } else {
+ curCall = null;
+ if (onComplete != null) onComplete(lastResponse);
+ }
+ };
+ loader.onError = function(error) {
+ lastResponse = {success: false, message: 'Request Error: ${error}'};
+ curCall = null;
+ if (onError != null) onError(lastResponse.message);
+ };
+ loader.request(false);
+ }
+ }
+
+ /**
+ * Parses data from a request to be sent via OpenFL or HTTP request.
+ * @param request RequestType to be formatted.
+ * @param signed Whether or not the request should be "signed" using the game token.
+ * @return String Request link.
+ */
+ static function parseType(request:RequestType, signed:Bool = false):String {
+ var command:String = "";
+ var action:String = "";
+ var params:Array<{name:String, value:String}> = [];
+
+ switch (request) {
+ case BATCH(parallel, breakOnError, requests):
+ command = "batch";
+ params.push({name: "parallel", value: '$parallel'});
+ params.push({name: "break_on_error", value: '$breakOnError'});
+ for (req in requests) params.push({name: "requests[]", value: parseType(req, true)});
+ case DATA_FETCH(key, fromUser):
+ command = "data-store";
+ params.push({name: "key", value: key.urlEncode()});
+ if (fromUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_GETKEYS(fromUser, pattern):
+ command = "data-store";
+ action = "get-keys";
+ if (pattern != null && pattern != "")
+ params.push({name: "pattern", value: pattern.urlEncode()});
+ if (fromUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_REMOVE(key, fromUser):
+ command = "data-store";
+ action = "remove";
+ params.push({name: "key", value: key.urlEncode()});
+ if (fromUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_SET(key, data, toUser):
+ command = "data-store";
+ action = "set";
+ params.push({name: "key", value: key.urlEncode()});
+ params.push({name: "data", value: data.urlEncode()});
+ if (toUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_UPDATE(key, operation, toUser):
+ command = "data-store";
+ action = "update";
+ params.push({name: "key", value: key.urlEncode()});
+ if (toUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ switch (operation) {
+ case Add(n):
+ params.push({name: 'operation', value: 'add'});
+ params.push({name: 'value', value: '$n'});
+ case Substract(n):
+ params.push({name: 'operation', value: 'substract'});
+ params.push({name: 'value', value: '$n'});
+ case Multiply(n):
+ params.push({name: 'operation', value: 'multiply'});
+ params.push({name: 'value', value: '$n'});
+ case Divide(n):
+ params.push({name: 'operation', value: 'divide'});
+ params.push({name: 'value', value: '$n'});
+ case Append(t):
+ params.push({name: 'operation', value: 'append'});
+ params.push({name: 'value', value: t.urlEncode()});
+ case Prepend(t):
+ params.push({name: 'operation', value: 'prepend'});
+ params.push({name: 'value', value: t.urlEncode()});
+ }
+ case FRIENDS:
+ command = "friends";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case TIME:
+ command = "time";
+ case USER_AUTH:
+ command = "users";
+ action = "auth";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case USER_FETCH(userOrID):
+ command = "users";
+ var letters:Array = "ABCDEFGHIJKLMNÑOPQRSTUVWXYZ_-".split("");
+ if (letters.filter(l -> userOrID.contains(l.toUpperCase()) || userOrID.contains(l.toLowerCase())).length > 0)
+ params.push({name: "username", value: userOrID});
+ else
+ params.push({name: "user_id", value: userOrID.replace(",", "%2C")});
+ case SESSION_OPEN:
+ command = "sessions";
+ action = "open";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SESSION_PING(active):
+ command = "sessions";
+ action = "ping";
+ params.push({name: "status", value: active ? "active" : "idle"});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SESSION_CHECK:
+ command = "sessions";
+ action = "check";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SESSION_CLOSE:
+ command = "sessions";
+ action = "close";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SCORES_ADD(score, sort, extra_data, table_id):
+ command = "scores";
+ action = "add";
+ params.push({name: "score", value: score});
+ params.push({name: "sort", value: '$sort'});
+ if (extra_data != null && extra_data != "")
+ params.push({name: "extra_data", value: extra_data.urlEncode()});
+ if (table_id != null)
+ params.push({name: "table_id", value: '$table_id'});
+ if (user_token != "") {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ } else
+ params.push({name: "guest", value: GJUtil.userName});
+ case SCORES_GETRANK(sort, table_id):
+ command = "scores";
+ action = "get-rank";
+ params.push({name: "sort", value: '$sort'});
+ if (table_id != null)
+ params.push({name: "table_id", value: '$table_id'});
+ case SCORES_FETCH(fromUser, table_id, limit, betterThan):
+ command = "scores";
+ if (table_id != null)
+ params.push({name: "table_id", value: '$table_id'});
+ if (limit != null)
+ params.push({name: "limit", value: '$limit'});
+ if (betterThan != null)
+ params.push({name: betterThan < 0 ? "worse_than" : "better_than", value: '${Math.abs(betterThan)}'});
+ if (fromUser) {
+ if (user_token != "") {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ } else
+ params.push({name: "guest", value: GJUtil.userName});
+ }
+ case SCORES_TABLES:
+ command = "scores";
+ action = "tables";
+ case TROPHIES_FETCH(achieved, trophy_id):
+ command = "trophies";
+ if (achieved != null)
+ params.push({name: "achieved", value: '$achieved'});
+ if (trophy_id != null)
+ params.push({name: "trophy_id", value: '$trophy_id'});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case TROPHIES_ADD(trophy_id):
+ command = "trophies";
+ action = "add-achieved";
+ params.push({name: "trophy_id", value: '$trophy_id'});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case TROPHIES_REMOVE(trophy_id):
+ command = "trophies";
+ action = "remove-achieved";
+ params.push({name: "trophy_id", value: '$trophy_id'});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+
+ var urlSection:String = '/$command${action != "" ? '/$action' : ""}?game_id=${gameId}${[for (p in params) '&${p.name}=${p.value}'].join("")}';
+ if (signed)
+ urlSection = sign(urlSection).urlEncode();
+ return urlSection;
+ }
+
+ /**
+ * Signs a piece of URL with Md5.
+ * @param daUrl The old URL piece.
+ * @return The new URL piece.
+ */
+ static function sign(daUrl:String):String {
+ var urlToEncode:String = daUrl + revealedGameToken;
+ return '$daUrl&signature=${Md5.encode(urlToEncode)}';
+ }
+
+ /**
+ * Setter function for encrypted game token. Also sets revealed game token.
+ * @param tok New string to set for `encryptedGameToken`.
+ */
+ static function set_encryptedGameToken(tok:String)
+ {
+ if (tok != null && CODENAME_AES_KEY != null) {
+ var iv:String = tok.substr(0, 32);
+ var theK:String = tok.substr(32);
+
+ var aes:Aes = new Aes(Bytes.ofHex(CODENAME_AES_KEY), Bytes.ofHex(iv.toUpperCase()));
+
+ var dat:String = aes.decrypt(Mode.OFB, Bytes.ofHex(theK), Padding.NoPadding).toString();
+
+ revealedGameToken = dat;
+ } else {
+ revealedGameToken = null;
+ }
+ return encryptedGameToken = tok;
+ }
+
+ static function get_url():String
+ {
+ return sign('https://api.gamejolt.com/api/game/v1_2${parseType(curCall)}');
+ }
+ #else
+ //region No API Integrations
+ public static function sendTrusted(call:RequestType, async:Bool = false, ?onError:String->Void, ?onComplete:GJResponse->Void, ?onProgress:Array->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (onError != null) onError('GameJolt API not set in Project.xml!');
+ }
+
+ public static function unlockDefinedTrophy(def:String, ?callback:Null->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(null);
+ }
+
+ static function set_encryptedGameToken(tok:String)
+ {
+ return encryptedGameToken = tok;
+ }
+
+ static function get_url():String
+ {
+ return null;
+ }
+ //endregion
+ #end
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/macros/SecretMacro.hx b/source/funkin/backend/system/macros/SecretMacro.hx
new file mode 100644
index 0000000000..3385db3735
--- /dev/null
+++ b/source/funkin/backend/system/macros/SecretMacro.hx
@@ -0,0 +1,104 @@
+package funkin.backend.system.macros;
+
+#if macro
+import sys.io.File;
+import sys.FileSystem;
+import Sys;
+import haxe.macro.Expr;
+import haxe.macro.Expr.Field;
+import haxe.macro.Context;
+#end
+
+@:dox(hide) class SecretMacro {
+ static var envPath:String = '.env';
+ // A little bit taken from Funkin's base code. Not too much though.
+ public static macro function build():Array
+ {
+ var contents:Null = !FileSystem.exists(envPath) ? buildEnv() : File.getContent(envPath);
+
+ if (contents == null)
+ contents = buildEnv();
+
+ final indivVars:Array = contents.split('\n');
+
+ final envMap:Map = [];
+
+ for (varE in indivVars) {
+ // If there's no equal sign, or multiple equal signs, we don't process this variable due to errors.
+ if (varE.indexOf('=') == -1 || varE.indexOf('=') != varE.lastIndexOf('='))
+ continue;
+
+ var titAndVal:Array = varE.split('=');
+ envMap.set(titAndVal[0], titAndVal[1]);
+ }
+
+ // Now we look at the actual fields to try and match things up.
+ final buildFields:Array = Context.getBuildFields();
+
+ for (fld in buildFields) {
+ if (fld.access.contains(AStatic)) switch(fld.kind) {
+ case FVar(t, e):
+ for (meta in fld.meta) {
+ if (meta.name != ':envField')
+ continue;
+
+ // Not gonna do the 'mandatoryIfDefined' stuff because that's not necessary!
+ // But because we're not fixing what isn't broken, time for null string checks.
+ var isNullString:Bool = false;
+ switch (t) {
+ case TPath(tp):
+ if (tp.name == 'Null' && tp.params != null && tp.params.length == 1)
+ switch (tp.params[0]) {
+ case TPType(TPath(tptp)):
+ if (tptp.name == 'String')
+ {
+ isNullString = true;
+ }
+ case _:
+ }
+ case _:
+ }
+
+ if (!isNullString)
+ Context.fatalError('Field ${fld.name} must be of type Null to use :envField', fld.pos)
+ else {
+ if (envMap.exists(fld.name))
+ buildFields[buildFields.indexOf(fld)].kind = FVar(t, macro $v{envMap.get(fld.name)});
+ else
+ Sys.println('WARNING: Value for environment variable "${fld.name}" not found.');
+ }
+ }
+ case _:
+ // nothing lol
+ }
+ }
+ return buildFields;
+ }
+
+ private static function buildEnv():Null
+ {
+ if (sys.FileSystem.exists(envPath))
+ return sys.io.File.getContent(envPath);
+
+ // For future peoples: to append an item to .env, just add it as a new item in this array.
+ // Use the format 'NAME_ALL_CAPS=value' - like an ini file!
+ var items:Array = ['CODENAME_AES_KEY=${generateKey()}'];
+ var finalProd:String = items.join('\n');
+
+ sys.io.File.saveContent(Sys.getCwd() + '\\.env', finalProd);
+
+ return finalProd;
+ }
+
+ private static function generateKey():String
+ {
+ var validChars:String = "0123456789ABCDEF";
+ var outputStr:String = '';
+
+ for (i in 0...64)
+ {
+ outputStr += validChars.charAt(Math.round(Math.random() * 15));
+ }
+ return outputStr;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/updating/AsyncUpdater.hx b/source/funkin/backend/system/updating/AsyncUpdater.hx
index 8ddc34237e..36b79ad40f 100644
--- a/source/funkin/backend/system/updating/AsyncUpdater.hx
+++ b/source/funkin/backend/system/updating/AsyncUpdater.hx
@@ -25,8 +25,19 @@ class AsyncUpdater {
}
#end
- public static var executableName:String = UpdateUtil.getNameOfExecutable();
- public static var executableGitHubName:String = UpdateUtil.getNameOfUpdateExecutable();
+
+ #if windows
+ public static var executableGitHubName:String = "update-windows.exe";
+ public static var executableName:String = "CodenameEngine.exe";
+ #end
+ #if linux
+ public static var executableGitHubName:String = "update-linux";
+ public static var executableName:String = "CodenameEngine";
+ #end
+ #if mac
+ public static var executableGitHubName:String = "update-mac";
+ public static var executableName:String = "CodenameEngine";
+ #end
public var releases:Array;
public var progress:UpdaterProgress = new UpdaterProgress();
diff --git a/source/funkin/backend/system/updating/UpdateUtil.hx b/source/funkin/backend/system/updating/UpdateUtil.hx
index 6470add49c..6a3bf7b429 100644
--- a/source/funkin/backend/system/updating/UpdateUtil.hx
+++ b/source/funkin/backend/system/updating/UpdateUtil.hx
@@ -42,19 +42,6 @@ class UpdateUtil {
#end
}
- public static function getNameOfExecutable():String
- {
- return #if windows "CodenameEngine.exe" #else "CodenameEngine" #end;
- }
-
- public static function getNameOfUpdateExecutable():String
- {
- var target:String = #if windows "windows.exe" #end
- #if mac "mac" #end
- #if linux "linux" #end;
- return 'update-${target}';
- }
-
public static function waitForUpdates(force = false, callback:UpdateCheckCallback->Void, lazy = false) {
#if (target.threaded)
if (__mutex.tryAcquire()) {
diff --git a/source/funkin/backend/utils/GJUtil.hx b/source/funkin/backend/utils/GJUtil.hx
new file mode 100644
index 0000000000..8911b7124d
--- /dev/null
+++ b/source/funkin/backend/utils/GJUtil.hx
@@ -0,0 +1,763 @@
+package funkin.backend.utils;
+
+import lime.graphics.Image;
+import haxe.Timer;
+import flixel.graphics.FlxGraphic;
+import openfl.display.BitmapData;
+import funkin.backend.system.gamejolt.GameJoltData;
+import funkin.backend.system.gamejolt.GameJoltSecurity;
+
+#if ALLOW_MULTITHREADING
+import funkin.backend.utils.ThreadUtil;
+#end
+#if (target.threaded)
+import sys.thread.Thread;
+import sys.thread.Mutex;
+#end
+
+/**
+ * This is how GameJolt API responses are formatted.
+ */
+typedef GJResponse = {
+ // General
+ success:Bool,
+ ?message:String,
+ // User Fetching
+ ?users:Array,
+ // Trophies Fetching
+ ?trophies:Array,
+ // Scores Fetching
+ ?scores:Array,
+ ?tables:Array,
+ ?rank:Int,
+ // Friends Fetching
+ ?friends:Array<{friend_id:Int}>,
+ // Data Store Fetching
+ ?keys:Array<{key:String}>,
+ ?data:String,
+ // Time Fetching
+ ?timestamp:Int,
+ ?timezone:String,
+ ?year:Int,
+ ?month:Int,
+ ?day:Int,
+ ?hour:Int,
+ ?minute:Int,
+ ?second:Int,
+ // Batch Reception
+ ?responses:Array
+}
+
+/**
+ * The way the scores are fetched from your game API.
+ *
+ * @param score The display text of the Score.
+ * @param sort The Score value.
+ * @param extra_data If some extra data is attached to this Score, it'll be shown here.
+ * @param user The username of the User who achieved this Score, if it's a registered User.
+ * @param user_id The user ID of the User who achieved this Score, if it's a registered User.
+ * @param guest The name of the user who achieved this Score, if it's a guest user.
+ * @param stored A short description about when the Score was achieved by the User or Guest.
+ * @param stored_timestamp A long time stamp (in seconds) of when the Score was achieved by the User or Guest.
+ */
+typedef Score = {
+ score:String,
+ sort:Int,
+ extra_data:String,
+ user:String,
+ user_id:Int,
+ guest:String,
+ stored:String,
+ stored_timestamp:Int
+}
+
+/**
+ * The way the score tables are fetched from your game API.
+ *
+ * @param id The ID of the Score Table.
+ * @param name The name of the Score Table.
+ * @param description The description of the Score Table.
+ * @param primary Whether if this is the Primary Score Table in your game (1) or not (0).
+ */
+typedef ScoreTable = {
+ id:Int,
+ name:String,
+ description:String,
+ primary:Bool
+}
+
+/**
+ * The way the trophies are fetched from your game API.
+ *
+ * @param id The ID of the Trophy.
+ * @param title The title of the Trophy.
+ * @param description The description of the Trophy.
+ * @param difficulty The difficulty rank of the Trophy.
+ * @param image_url The link of the image that represents the Trophy.
+ * @param achieved Whether this Trophy was achieved or not, it can be a string if it was (with info about how much time ago it was achieved) or bool if not (false).
+ */
+typedef Trophy = {
+ id:Int,
+ title:String,
+ description:String,
+ difficulty:String,
+ image_url:String,
+ achieved:String
+}
+
+/**
+ * The way the user data is fetched from the GameJolt API.
+ *
+ * @param id The ID of the User.
+ * @param type The category the User is cataloged like in GameJolt.
+ * @param username The username of the User. (Also available for guests).
+ * @param avatar_url The link of the avatar of the User.
+ * @param signed_up A short description about how long the User have been in GameJolt.
+ * @param signed_up_timestamp A long time stamp (in seconds) of when the User signed up.
+ * @param last_logged_in A short description about the last time the User was found active in GameJolt.
+ * @param last_logged_in_timestamp A long time stamp (in seconds) of the last time the User logged in GameJolt.
+ * @param status The actual status of the User.
+ * @param developer_name The display name of the User. (Also available for guests).
+ * @param developer_website The website of the User.
+ * @param developer_description The description of the User.
+ */
+typedef User = {
+ id:Int,
+ type:String,
+ username:String,
+ avatar_url:String,
+ signed_up:String,
+ signed_up_timestamp:Int,
+ last_logged_in:String,
+ last_logged_in_timestamp:Int,
+ status:String,
+ developer_name:String,
+ developer_website:String,
+ developer_description:String
+}
+
+/**
+ * An enum class to clasify Data Store update functions.
+ */
+enum DataUpdateType {
+ Add(n:Int);
+ Substract(n:Int);
+ Multiply(n:Int);
+ Divide(n:Int);
+ Append(t:String);
+ Prepend(t:String);
+}
+
+/**
+ * An enum of every single command currently available to request to GameJolt API.
+ */
+enum RequestType {
+ BATCH(parallel:Bool, breakOnError:Bool, requests:Array);
+ DATA_FETCH(key:String, fromUser:Bool);
+ DATA_GETKEYS(fromUser:Bool, ?pattern:String);
+ DATA_REMOVE(key:String, fromUser:Bool);
+ DATA_SET(key:String, data:String, toUser:Bool);
+ DATA_UPDATE(key:String, operation:DataUpdateType, toUser:Bool);
+ FRIENDS;
+ TIME;
+ USER_AUTH;
+ USER_FETCH(userOrID:String);
+ SESSION_OPEN;
+ SESSION_PING(active:Bool);
+ SESSION_CHECK;
+ SESSION_CLOSE;
+ SCORES_ADD(score:String, sort:Int, ?extra_data:String, ?table_id:Int);
+ SCORES_GETRANK(sort:Int, ?table_id:Int);
+ SCORES_FETCH(fromUser:Bool, ?table_id:Int, ?limit:Int, ?betterThan:Int);
+ SCORES_TABLES;
+ TROPHIES_FETCH(?achieved:Bool, ?trophy_id:Int);
+ TROPHIES_ADD(trophy_id:Int);
+ TROPHIES_REMOVE(trophy_id:Int);
+}
+
+/**
+ * GameJolt utility to help with GameJolt functionality. Use this class to determine if your player is logged into GameJolt,
+ * unlock custom trophies, or do anything that is safe to accomplish with softcoding. Will not do anything if there is no
+ * provided GameJolt token or game ID.
+ *
+ * # IMPORTANT
+ * If you wish to setup GameJolt API integrations, please follow these steps:
+ * 1. Place your game's game ID (NOT your game's security key) into your `modpack.ini`
+ * for the flag `GAMEJOLT_GAME_ID`.
+ * 2. Create a file in `data/config` named `gamejolt.xml`.
+ * 3. Create nodes for the following:
+ * ```xml
+ *
+ * your-game-security-key-here
+ * ```
+ * 4. Create nodes for trophies and leaderboards.
+ * 5. Create nodes for data - any specific items in save files you want to be saved in a user's data store.
+ * 6. Run the mod in Codename Engine (preferred: run the EXE file from a Command Prompt).
+ * If successful, you will see a screen with more information; if unsuccessful, you'll see
+ * an error in the console.
+ * 7. If successful, press the "Copy" button and place the copied output into your modpack.ini,
+ * preferrably right under the `GAMEJOLT_GAME_ID` flag.
+ * 8. Discard the overwritten `gamejolt.xml` file or keep it for any updates you want to make to
+ * the global data store.
+ *
+ * ## DO NOT PLACE YOUR SECURITY KEY RIGHT INTO THE MODPACK.INI!!!! THAT IS A SECURITY ISSUE!!!!
+ */
+class GJUtil
+{
+ /**
+ * Boolean to determine if our player logged in.
+ */
+ public static var loggedIn:Bool = false;
+
+ /**
+ * The username of the logged in user.
+ */
+ public static var userName(default, set):String;
+
+ /**
+ * The avatar of the logged in user.
+ */
+ public static var userAvatarUrl(default, null):String;
+
+ /**
+ * The profile markdown description of the logged in user.
+ */
+ public static var userDescription(default, null):String;
+
+ /**
+ * Whether or not the GameJolt utility is operational.
+ * This cannot be set other than load operations.
+ */
+ public static var active(default, null):Bool = false;
+
+ /**
+ * Helper function in case the session is lost in the middle of the game.
+ */
+ public static var onLostSession:NullVoid> = null;
+
+ /**
+ * Helper function that runs when an achievement is unlocked.
+ * Can be useful for notifications.
+ */
+ public static var onTrophyUnlock:NullVoid> = null;
+
+ /**
+ * Whether or not the utility is executing a call.
+ */
+ static var executing:Bool = false;
+
+ /**
+ * The timer for calling the session ping. Runs every 10 seconds.
+ */
+ static var daTimer:Null = null;
+
+ #if (target.threaded)
+ static final mutex = new Mutex();
+ #end
+
+ #if GAMEJOLT_API
+ /**
+ * Initializes the GJUtil class and attempts to log in.
+ * If needed, also attempts to initialize global data.
+ */
+ public static function init()
+ {
+ if (Flags.MOD_GAMEJOLT_GAME_ID == '')
+ return;
+
+ if (Flags.MOD_GAMEJOLT_ENCRYPTED_TOKEN == '') {
+ GameJoltData.loadAdminData();
+ if (Flags.MOD_GAMEJOLT_ENCRYPTED_TOKEN == '')
+ return;
+ } else if (FlxG.save.data.gameJoltArray != null) {
+ var gjDat:Array = FlxG.save.data.gameJoltArray;
+ GJUtil.attemptLogin(gjDat[0], gjDat[1]);
+ }
+ }
+
+ /**
+ * Helper function to simplify the login process.
+ * @param name Username of user attempting to login.
+ * @param token User token of user attempting to login.
+ * @param callback Function to run when attempt is complete. Return bool
+ * determines if attempt was successful or unsuccessful.
+ * @param checkCreds Whether or not to check and make sure the user actually
+ * exists. Helpful for new login attempts (instead of confirmed attempts, like
+ * those coming from the save file).
+ * @param tempLogin Whether or not the login is temporary (i.e. first-time global
+ * data upload) or permanent.
+ */
+ public static function attemptLogin(name:String, token:String, ?callback:Bool->Void, checkCreds:Bool = false, tempLogin:Bool = false)
+ {
+ if(Flags.MOD_GAMEJOLT_GAME_ID != '' && Flags.MOD_GAMEJOLT_ENCRYPTED_TOKEN != '') {
+ active = true;
+
+ userName = name;
+ GameJoltSecurity.user_token = token;
+ var batchCalls:Array = [SESSION_OPEN, USER_FETCH(name)];
+ if (checkCreds)
+ batchCalls.unshift(USER_AUTH);
+ if (!tempLogin) {
+ batchCalls.push(TROPHIES_FETCH());
+ }
+
+ send(RequestType.BATCH(true, false, batchCalls), !tempLogin, function(err) {
+ userName = null;
+ if (callback != null) callback(false);
+ }, function(resp) {
+ GameJoltSecurity.userId = resp.responses[checkCreds ? 2 : 1].users[0].id;
+ if (!tempLogin) {
+ userAvatarUrl = resp.responses[checkCreds ? 2 : 1].users[0].avatar_url;
+ userDescription = resp.responses[checkCreds ? 2 : 1].users[0].developer_description;
+ GameJoltData.loadGlobalData((bl) -> {
+ if (bl) {
+ for (trop in resp.responses[checkCreds ? 3 : 2].trophies) {
+ for (key => value in GameJoltData.definedTrophies) {
+ if (value.id == trop.id && trop.achieved != "false")
+ GameJoltData.earnedTrophies.set(key, value);
+ }
+ for (key => value in GameJoltData.customTrophies) {
+ if (value.id == trop.id && trop.achieved != "false")
+ GameJoltData.earnedTrophies.set(key, value);
+ }
+ }
+
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("Successfully logged in user "),
+ Logs.logText(userName, GREEN),
+ Logs.logText('!')
+ ], SUCCESS);
+ openfl.Lib.application.onExit.add(onExitApp);
+ daTimer = new Timer(10000);
+ daTimer.run = pingSession;
+ if (checkCreds) {
+ FlxG.save.data.gameJoltArray = [userName, token];
+ FlxG.save.flush();
+ }
+ GameJoltSecurity.unlockDefinedTrophy('open-first');
+ if (Date.now().getDay() == 5 && Date.now().getHours() >= 18)
+ GameJoltSecurity.unlockDefinedTrophy('friday-night');
+ if (callback != null) callback(true);
+ } else {
+ Logs.trace('Unable to obtain global data. Logging out of GameJolt.', ERROR, LIGHTGRAY, 'GameJolt');
+ logout(false, true); // so that it doesn't remove functions that don't exist
+ if (callback != null) callback(false);
+ }
+ });
+ } else if (callback != null)
+ callback(true);
+ });
+ }
+ else
+ if (callback != null) callback(false);
+ }
+
+ /**
+ * Logs out a user from GameJolt.
+ * @param wipeSave Whether or not to wipe the user credentials from
+ * the save file.
+ * @param tempLogin Whether or not the login was temporary (i.e. first-time global
+ * data upload) or permanent.
+ */
+ public static function logout(wipeSave:Bool = false, tempLogin:Bool = false)
+ {
+ if (!active)
+ return;
+ if (!tempLogin)
+ shutdownFunctions();
+ send(RequestType.SESSION_CLOSE, !tempLogin, null, function(resp) {
+ if (!tempLogin)
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("User "),
+ Logs.logText(userName, GREEN),
+ Logs.logText(' logged out successfully.')
+ ], VERBOSE);
+ userName = null;
+ if (wipeSave) {
+ FlxG.save.data.gameJoltArray = null;
+ FlxG.save.flush();
+ }
+ });
+ }
+
+ /**
+ * Make a GameJolt API call that is safe to make via softcoding.
+ * @param call The RequestType call to make. Currently only supports:
+ * `FRIENDS`, `TIME`, `USER_FETCH`, `SCORES_GETRANK`, and `TROPHIES_FETCH`.
+ * @param async Whether or not the call should be asyncronous.
+ * @param onError Callback function if an error occurs. Gives error string.
+ * @param onComplete Callback function on successful completion of the call. Gives response data.
+ * @param onProgress Callback function for progress on async calls. Gives a progress float array.
+ */
+ public static function makeCall(call:RequestType, async:Bool = false, ?onError:String->Void, ?onComplete:GJResponse->Void, ?onProgress:Array->Void)
+ {
+ switch(call) {
+ case BATCH(parallel, breakOnError, requests):
+ return;
+
+ case DATA_GETKEYS(fromUser, pattern):
+ return;
+
+ case DATA_REMOVE(key, fromUser):
+ return;
+
+ case DATA_SET(key, data, toUser):
+ return;
+
+ case DATA_UPDATE(key, operation, toUser):
+ return;
+
+ case USER_AUTH:
+ return;
+
+ case SESSION_OPEN:
+ return;
+
+ case SESSION_PING(active):
+ return;
+
+ case SESSION_CHECK:
+ return;
+
+ case SESSION_CLOSE:
+ return;
+
+ case SCORES_ADD(score, sort, extra_data, table_id):
+ return;
+
+ case TROPHIES_ADD(trophy_id):
+ return;
+
+ case TROPHIES_REMOVE(trophy_id):
+ return;
+
+ case _:
+ send(call, async, onError, onComplete, onProgress);
+ }
+ }
+
+ /**
+ * Unlocks a trophy from the `customTrophies` map - in other words,
+ * any trophy that isn't specifically defined in hardcode.
+ * @param custom Key/def of trophy in `customTrophies` map.
+ * @param callback Function running on completion of unlock attempt. Returns
+ * trophy data if successful, `null` if unsuccessful.
+ */
+ public static function unlockCustomTrophy(custom:String, ?callback:Null->Void)
+ {
+ if (!GameJoltData.customTrophies.exists(custom))
+ if (callback != null) callback(null)
+ else {
+ var daTrophy:GJTrophyData = GameJoltData.customTrophies.get(custom);
+
+ if (GameJoltData.earnedTrophies.exists(custom))
+ if (callback != null) callback(null)
+ else {
+ var meetsReqs:Bool = true;
+ if (daTrophy.require != null) {
+ var reqsMet:Array = [];
+ for (earned in GameJoltData.earnedTrophies)
+ if (daTrophy.require.contains(earned.id)) reqsMet.push(earned.id);
+
+ if (reqsMet.length != daTrophy.require.length)
+ meetsReqs = false;
+ }
+
+ if (!meetsReqs)
+ if(callback != null) callback(null)
+ else {
+ send(TROPHIES_ADD(daTrophy.id), true, function(err) {
+ Logs.trace('Trophy unlock error: ${err}', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(null);
+ }, function(resp) {
+ GameJoltData.earnedTrophies.set(custom, daTrophy);
+ Logs.trace('Trophy unlock: ${resp.responses[0].trophies[0].title}!');
+ if (onTrophyUnlock != null) onTrophyUnlock(resp.responses[0].trophies[0]);
+ if (callback != null) callback(resp.trophies[0]);
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Fetches image of user avatar (using same method as fetching GitHub profile images).
+ * @param image Sprite to apply the bitmap to.
+ * @param link Link to get image from. Defaults to logged in user's link if null.
+ * @param addlCallback Callback function to run on successful completion of attempt.
+ */
+ public static function getAvatarImage(image:FlxSprite, ?link:String, ?addlCallback:Void->Void)
+ {
+ #if ALLOW_MULTITHREADING ThreadUtil.execAsync#elseif (target.threaded) Thread.create#end(function() {
+ var key:String = 'GAMEJOLT-LINK:${link != null ? link : userName}';
+ var bmap:Dynamic = FlxG.bitmap.get(key);
+
+ if(bmap == null) {
+ Logs.trace('Downloading avatar: ${link != null ? link : userName}', INFO, LIGHTGRAY, 'GameJolt');
+ var unfLink:Bool = StringTools.endsWith(link != null ? link : userAvatarUrl, '.png');
+
+ var bytes = null;
+ if(unfLink) {
+ try bytes = HttpUtil.requestBytes(link != null ? link : userAvatarUrl)
+ catch(e) Logs.error('Failed to download GameJolt pfp for ${link != null ? link : userName}: ${e.message} - (Retrying using the api..)', RED, 'GameJolt');
+
+ if(bytes != null) {
+ bmap = BitmapData.fromBytes(bytes);
+ }
+ }
+
+ var leGraphic:FlxGraphic = null;
+ if(bmap != null) try {
+ #if (target.threaded)
+ mutex.acquire();
+ #end
+ leGraphic = FlxG.bitmap.add(bmap, false, key);
+ leGraphic.persist = true;
+ bmap = null;
+ image.loadGraphic(leGraphic);
+ if (addlCallback != null) addlCallback();
+ #if (target.threaded)
+ mutex.release();
+ #end
+ } catch(e) {
+ Logs.error('Failed to update the pfp for ${userName}: ${e.message}', RED, 'GameJolt');
+ }
+ } else {
+ image.loadGraphic(bmap);
+ if (addlCallback != null) addlCallback();
+ }
+ });
+ }
+
+ /**
+ * Fetches image of a trophy (using same method as fetching GitHub profile images).
+ * If null, it will set the graphic to one of the default graphics stored in the
+ * Assets file.
+ *
+ * You can also create your own default trophy images by just making "bronze",
+ * "silver", "gold", and "platnum" trophy images, then placing those in
+ * "images/menus/gamejolt".
+ * @param image Sprite to apply the bitmap to.
+ * @param link Link to get image from. Defaults to logged in user's link if null.
+ * @param addlCallback Callback function to run on successful completion of attempt.
+ */
+ public static function getTrophyImage(image:FlxSprite, trophy:Trophy, ?addlCallback:Void->Void)
+ {
+ #if ALLOW_MULTITHREADING ThreadUtil.execAsync#elseif (target.threaded) Thread.create#end(function() {
+ var key:String = 'GAMEJOLT-TROP:${trophy.image_url}';
+ var bmap:Dynamic = FlxG.bitmap.get(key);
+
+ if(bmap == null) {
+ Logs.trace('Downloading trophy image for trophy: ${trophy.title}', INFO, LIGHTGRAY, 'GameJolt');
+ var unfLink:Bool = StringTools.endsWith(trophy.image_url, '.png');
+
+ if(unfLink) {
+ Image.loadFromFile(trophy.image_url).onComplete((img:Image) -> {
+ try {
+ #if (target.threaded)
+ mutex.acquire();
+ #end
+ var leGraphic:FlxGraphic = FlxG.bitmap.add(BitmapData.fromImage(img), false, key);
+ leGraphic.persist = true;
+ image.loadGraphic(leGraphic);
+ if (addlCallback != null) addlCallback();
+ #if (target.threaded)
+ mutex.release();
+ #end
+ } catch(e) {
+ Logs.error('Failed to update the image for ${trophy.title}: ${e.message}', RED, 'GameJolt');
+ }
+ }).onError((e) -> {
+ Logs.error('Failed to download trophy image for ${trophy.title}: ${e.message} - (Obtaining default image from files instead...)', RED, 'GameJolt');
+ try {
+ #if (target.threaded)
+ mutex.acquire();
+ #end
+ var secret:Bool = false;
+ for (t in GameJoltData.definedTrophies) {
+ if (t.id == trophy.id && t.hidden != null && !t.hidden)
+ secret = true;
+ }
+
+ for (t in GameJoltData.customTrophies) {
+ if (t.id == trophy.id && t.hidden != null && !t.hidden)
+ secret = true;
+ }
+ var leGraphic:FlxGraphic = FlxG.bitmap.add(BitmapData.fromFile(Paths.image('menus/gamejolt/' + trophy.difficulty.toLowerCase() + (secret ? "-secret" : ""))), false, key);
+ leGraphic.persist = true;
+ image.loadGraphic(leGraphic);
+ if (addlCallback != null) addlCallback();
+ #if (target.threaded)
+ mutex.release();
+ #end
+ } catch(e) {
+ Logs.error('Failed to update the image for ${trophy.title}: ${e.message}', RED, 'GameJolt');
+ }
+ });
+ } else {
+ Logs.error('Image for ${trophy.title} is not a .png image; obtaining default image from files instead...', RED, 'GameJolt');
+ try {
+ #if (target.threaded)
+ mutex.acquire();
+ #end
+ var leGraphic:FlxGraphic = FlxG.bitmap.add(BitmapData.fromFile(Paths.image('menus/gamejolt/' + trophy.difficulty.toLowerCase())), false, key);
+ leGraphic.persist = true;
+ image.loadGraphic(leGraphic);
+ if (addlCallback != null) addlCallback();
+ #if (target.threaded)
+ mutex.release();
+ #end
+ } catch(e) {
+ Logs.error('Failed to update the image for ${trophy.title}: ${e.message}', RED, 'GameJolt');
+ }
+ }
+ } else {
+ image.loadGraphic(bmap);
+ if (addlCallback != null) addlCallback();
+ }
+ });
+ }
+
+ /**
+ * Function to ping session on regular basis.
+ */
+ static function pingSession()
+ {
+ send(SESSION_PING(true), true, (str) -> {
+ if (onLostSession != null) onLostSession();
+ shutdownFunctions();
+ Logs.trace('Session lost ($str); GJUtil shut down successfully.', WARNING, LIGHTGRAY, 'GameJolt');
+ active = false;
+ });
+ }
+
+ /**
+ * Function to log out of GameJolt on application exit.
+ * @param i idk man.
+ */
+ static function onExitApp(i:Int)
+ {
+ logout();
+ }
+
+ /**
+ * Functions to run when safely logging out user from GameJolt.
+ */
+ static function shutdownFunctions()
+ {
+ Logs.traceColored([
+ Logs.getPrefix("GameJolt"),
+ Logs.logText("Logging out user "),
+ Logs.logText(userName, GREEN),
+ Logs.logText('...')
+ ], INFO);
+ daTimer.stop();
+ daTimer = null;
+ openfl.Lib.application.onExit.remove(onExitApp);
+ onLostSession = null;
+ GameJoltData.reset();
+ }
+
+ /**
+ * Main send function for calls and requests using GameJolt API.
+ * @param call Type of request to send.
+ * @param async Whether or not the request is asynchronous.
+ * @param onError Function to run on error. Returns error message as string.
+ * @param onComplete Function to run on success. Returns response data as typedef `Response`.
+ * @param onProgress Function to run on async progress. Returns array of floats.
+ */
+ @:noPrivateAccess static function send(call:RequestType, async:Bool = false, ?onError:String->Void, ?onComplete:GJResponse->Void, ?onProgress:Array->Void)
+ {
+ if (executing || !active)
+ return;
+ executing = true;
+
+ @:privateAccess
+ GameJoltSecurity.handleRequest(async, call, function(errmsg) {
+ executing = false;
+ if (onError != null) onError(errmsg);
+ }, function(resp) {
+ executing = false;
+ if (onComplete != null) onComplete(formatImages(resp));
+ }, onProgress);
+ }
+
+ /**
+ * Formats any images received to proper file types
+ * @param res Response to format.
+ * @return GJResponse Response with formatted images.
+ */
+ static function formatImages(res:GJResponse):GJResponse {
+ if (res.users != null) {
+ for (u in res.users) {
+ u.avatar_url = '${u.avatar_url.substring(0, 32)}1000${u.avatar_url.substr(34)}'.replace(".jpg", ".png").replace(".webp", ".png");
+ }
+ }
+ if (res.trophies != null && res.trophies[0] != null) {
+ for (t in res.trophies) {
+ var newUrl:String = "";
+ if (t.image_url.startsWith('https://m.'))
+ newUrl = '${t.image_url.substring(0, 37)}1000${t.image_url.substr(40)}'.replace(".jpg", ".png").replace(".webp", ".png");
+ else {
+ newUrl = 'https://s.gjcdn.net/assets/${t.image_url.substr(24)}';
+ }
+ t.image_url = newUrl;
+ };
+ }
+ if (res.responses != null) for (res2 in res.responses) res2 = formatImages(res2);
+ return res;
+ }
+
+ static function set_userName(name:String):String
+ {
+ loggedIn = (name != null && name != '');
+ if (name == null) {
+ userAvatarUrl = null;
+ userDescription = null;
+ GameJoltSecurity.userId = null;
+ GameJoltSecurity.user_token = null;
+
+ }
+ return userName = name;
+ }
+ #else
+ public static function init()
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ }
+
+ public static function attemptLogin(name:String, token:String, ?callback:Bool->Void, checkCreds:Bool = false, tempLogin:Bool = false)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(false);
+ }
+
+ public static function logout(wipeSave:Bool = false, tempLogin:Bool = false)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ }
+
+ public static function makeCall(call:RequestType, async:Bool = false, ?onError:String->Void, ?onComplete:GJResponse->Void, ?onProgress:Array->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ }
+
+ public static function unlockCustomTrophy(custom:String, ?callback:Null->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ if (callback != null) callback(null);
+ }
+
+ public static function getAvatarImage(image:FlxSprite, ?addlCallback:Void->Void)
+ {
+ Logs.trace('GameJolt API not set in Project.xml!', ERROR, LIGHTGRAY, 'GameJolt');
+ }
+
+ static function set_userName(name:String):String
+ {
+ return userName = name;
+ }
+ #end
+}
\ No newline at end of file
diff --git a/source/funkin/backend/utils/MemoryUtil.hx b/source/funkin/backend/utils/MemoryUtil.hx
index 87d75fdd15..2de56184c1 100644
--- a/source/funkin/backend/utils/MemoryUtil.hx
+++ b/source/funkin/backend/utils/MemoryUtil.hx
@@ -119,23 +119,6 @@ final class MemoryUtil {
#end
}
- /**
- * Gets the current process memory usage (Working Set / RSS) from the OS.
- * This is much more granular than Haxe GC memory and updates more frequently.
- * Returns the value in bytes, matching what you see in Task Manager / Activity Monitor.
- */
- public static inline function currentProcessMemUsage():Float {
- #if (cpp && windows)
- return funkin.backend.utils.native.Windows.getCurrentProcessMemory();
- #elseif (cpp && mac)
- return funkin.backend.utils.native.Mac.getCurrentProcessMemory();
- #elseif (cpp && linux)
- return funkin.backend.utils.native.Linux.getCurrentProcessMemory();
- #else
- return currentMemUsage();
- #end
- }
-
/**
* Gets the memory type of the system.
diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx
index 46f4086465..71353cb119 100644
--- a/source/funkin/backend/utils/XMLUtil.hx
+++ b/source/funkin/backend/utils/XMLUtil.hx
@@ -1,7 +1,6 @@
package funkin.backend.utils;
import flixel.util.FlxColor;
-import openfl.display.BlendMode;
import flixel.util.typeLimit.OneOfTwo;
import funkin.backend.FunkinSprite.XMLAnimType;
import funkin.backend.FunkinSprite;
@@ -243,10 +242,6 @@ final class XMLUtil {
if(node.has.color)
spr.color = FlxColor.fromString(node.getAtt("color")).getDefault(0xFFFFFFFF);
- // no need to getDefault here ? since null blend acts like normal blend
- @:privateAccess if(node.has.blend)
- spr.blend = BlendMode.fromString(node.getAtt("blend"));
-
if(node.has.angle)
spr.angle = Std.parseFloat(node.getAtt("angle")).getDefault(spr.angle);
diff --git a/source/funkin/backend/utils/native/Linux.hx b/source/funkin/backend/utils/native/Linux.hx
index e33ad805dc..661c584267 100644
--- a/source/funkin/backend/utils/native/Linux.hx
+++ b/source/funkin/backend/utils/native/Linux.hx
@@ -1,7 +1,7 @@
package funkin.backend.utils.native;
#if linux
-@:cppFileCode("#include \n#include ")
+@:cppFileCode("#include ")
@:dox(hide)
final class Linux {
@:functionCode('
@@ -28,38 +28,5 @@ final class Linux {
{
return 0;
}
-
- @:functionCode('
- struct timespec now;
- clock_gettime(CLOCK_MONOTONIC, &now);
- long long nowMs = now.tv_sec * 1000LL + now.tv_nsec / 1000000LL;
-
- // cache the value and only re-read /proc every 500ms, to avoid polling the kernel file every frame
- static long long lastReadMs = 0;
- static double cached = 0.0;
-
- if (cached != 0.0 && (nowMs - lastReadMs) < 500)
- return cached;
-
- lastReadMs = nowMs;
-
- FILE *status = fopen("/proc/self/status", "r");
- if (status == NULL) return cached;
-
- char line[256];
- while (fgets(line, sizeof(line), status)) {
- unsigned long long rss; // use 64-bit so the kB -> bytes conversion never overflows on 32-bit builds
- if (sscanf(line, "VmRSS: %llu kB", &rss) == 1) {
- fclose(status);
- return cached = (double)rss * 1024.0; // kB -> bytes
- }
- }
- fclose(status);
- return cached;
-')
- public static function getCurrentProcessMemory():Float
- {
- return 0;
- }
}
#end
\ No newline at end of file
diff --git a/source/funkin/backend/utils/native/Mac.hx b/source/funkin/backend/utils/native/Mac.hx
index 86924dc2eb..afd9d58981 100644
--- a/source/funkin/backend/utils/native/Mac.hx
+++ b/source/funkin/backend/utils/native/Mac.hx
@@ -5,7 +5,6 @@ import funkin.backend.utils.NativeAPI.CodeCursor;
import openfl.ui.Mouse;
@:headerInclude('sys/sysctl.h')
-@:headerInclude('mach/mach.h')
@:dox(hide)
final class Mac
{
@@ -24,19 +23,6 @@ final class Mac
return 0;
}
- @:functionCode('
- task_basic_info_data_t info;
- mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
- if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS) {
- return (double)info.resident_size;
- }
- return 0.0;
- ')
- public static function getCurrentProcessMemory():Float
- {
- return 0;
- }
-
public static function setMouseCursorIcon(icon:CodeCursor):Void
{
final valid:Bool = external.ExternalMac.setCursorIcon(icon.toInt(), null, 0, 0);
diff --git a/source/funkin/backend/utils/native/Windows.hx b/source/funkin/backend/utils/native/Windows.hx
index 026521597d..1e78874bd8 100644
--- a/source/funkin/backend/utils/native/Windows.hx
+++ b/source/funkin/backend/utils/native/Windows.hx
@@ -171,17 +171,5 @@ final class Windows {
{
return 0;
}
-
- @:functionCode("
- PROCESS_MEMORY_COUNTERS_EX pmc;
- if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) {
- return (double)pmc.WorkingSetSize;
- }
- return 0.0;
- ")
- public static function getCurrentProcessMemory():Float
- {
- return 0;
- }
}
#end
\ No newline at end of file
diff --git a/source/funkin/editors/ModConfigWarning.hx b/source/funkin/editors/ModConfigWarning.hx
index 6e04663da8..a1dc4dd5c9 100644
--- a/source/funkin/editors/ModConfigWarning.hx
+++ b/source/funkin/editors/ModConfigWarning.hx
@@ -8,7 +8,6 @@ class ModConfigWarning extends UIState {
var library:ModsFolderLibrary = null;
var goToState:Class;
- var useAPIWarning:Bool = false;
public static var defaultModConfigText =
'[Common] # This section applies the \'MOD_\' prefix to the flags so you don\'t have to.
@@ -17,9 +16,7 @@ DESCRIPTION="YOUR MOD DESCRIPTION HERE"
AUTHOR="YOU/YOUR TEAM HERE"
VERSION="YOUR MOD\'S VERSION HERE"
-# This is used to check for version compatibility!
-# By default, the current API version is used.
-# Mods with a lower API version will need to get updated if needed!!!!!!!!!!!!!!
+# DO NOT EDIT!! this is used to check for version compatibility!
API_VERSION=${Flags.CURRENT_API_VERSION}
DOWNLOAD_LINK="YOUR MOD PAGE LINK HERE"
@@ -50,36 +47,23 @@ LOGO_TEXT=""
[StateRedirects.force] # Use this if you want to override redirects set by subsequent addons/mods
';
- public function new(library:ModsFolderLibrary, ?goToState:Class, ?useAPIWarning:Bool = false) {
+ public function new(library:ModsFolderLibrary, ?goToState:Class) {
super();
this.library = library;
this.goToState = goToState != null ? goToState : funkin.menus.TitleState;
- this.useAPIWarning = useAPIWarning;
- }
-
- public function goBack() {
- MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false;
- FlxG.switchState(cast Type.createInstance(goToState, []));
}
override function createPost() {
super.createPost();
hadPopup = true;
- var substate = useAPIWarning ? new UIWarningSubstate(TU.translate("modApiWarning.warningTitle", [Flags.MOD_API_VERSION != null ? Std.string(Flags.MOD_API_VERSION) : '???', Flags.CURRENT_API_VERSION]), TU.translate("modApiWarning.warningDesc"), [
- {
- label: TU.translate("editor.ok"),
- color: 0x969533,
- onClick: function (_) {
- goBack();
- }
- }
- ], false) : new UIWarningSubstate(TU.translate("modConfigWarning.warningTitle"), TU.translate("modConfigWarning.warningDesc"), [
+ var substate = new UIWarningSubstate(TU.translate("modConfigWarning.warningTitle"), TU.translate("modConfigWarning.warningDesc"), [
{
label: TU.translate("editor.notNow"),
color: 0x969533,
onClick: function (_) {
- goBack();
+ MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false;
+ FlxG.switchState(cast Type.createInstance(goToState, []));
}
},
{
@@ -91,7 +75,8 @@ LOGO_TEXT=""
{
label: TU.translate("editor.ok"),
onClick: function (_) {
- goBack();
+ MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false;
+ FlxG.switchState(cast Type.createInstance(goToState, []));
}
},
], false));
diff --git a/source/funkin/editors/alphabet/AlphabetEditor.hx b/source/funkin/editors/alphabet/AlphabetEditor.hx
index 7f740155de..374e56f29c 100644
--- a/source/funkin/editors/alphabet/AlphabetEditor.hx
+++ b/source/funkin/editors/alphabet/AlphabetEditor.hx
@@ -193,8 +193,6 @@ class AlphabetEditor extends UIState {
curLetter = tape.manualLetters.indexOf(lastChar) + charsForDefault.length;
changeLetter(0);
} else {
- for (i in 0...tape.loaded.length)
- tape.loaded[i].remove(lastChar);
tape.manualLetters.push(lastChar);
tape.text = "";
for (def in charsForDefault)
@@ -210,16 +208,13 @@ class AlphabetEditor extends UIState {
tape.letterData.set(lastChar, {
isDefault: false,
advance: Math.NaN,
- advanceStyle: AUTO,
+ advanceEmpty: true,
components: [],
startIndex: 0
});
curLetter = tape.manualLetters.length - 1 + charsForDefault.length;
changeLetter(0);
-
- deleteGlyph.selectable = tape.manualLetters.contains(lastChar);
- confirmGlyph.field.text = translate("glyph.editGlyph");
}
}, glyphChar.bWidth);
confirmGlyph.selectable = false;
@@ -250,7 +245,7 @@ class AlphabetEditor extends UIState {
infoWindow = new GlyphInfoWindow();
uiGroup.add(infoWindow);
- componentList = new UIButtonList(15, 720 - 170 - 15, 230, 170, "Components:", FlxPoint.get(230, 50), FlxPoint.get(0, 0), 0);
+ componentList = new UIButtonList(0, 720 - 170 - 30, 230, 170, "Components:", FlxPoint.get(230, 50), FlxPoint.get(0, 0), 0);
componentList.dragCallback = (button, oldID, newID) -> {
queueReorder = true; // not do it for every button reordered
}
@@ -537,7 +532,6 @@ class ComponentButton extends UIButton {
super(0, 0, component.anim, function() {
AlphabetEditor.instance.curSelectedComponent = component;
AlphabetEditor.instance.findOutline();
- AlphabetEditor.instance.infoWindow.button = this;
AlphabetEditor.instance.infoWindow.updateInfo();
}, 230, 50);
this.component = component;
@@ -566,7 +560,6 @@ class ComponentButton extends UIButton {
data.components.remove(component);
AlphabetEditor.instance.curSelectedComponent = (AlphabetEditor.instance.curSelectedComponent == component) ? null : AlphabetEditor.instance.curSelectedComponent;
- AlphabetEditor.instance.infoWindow.button = (AlphabetEditor.instance.infoWindow.button == this) ? null : AlphabetEditor.instance.infoWindow.button;
AlphabetEditor.instance.findOutline();
AlphabetEditor.instance.infoWindow.updateInfo();
diff --git a/source/funkin/editors/alphabet/AlphabetMainDataScreen.hx b/source/funkin/editors/alphabet/AlphabetMainDataScreen.hx
index 743a20d865..17581a0922 100644
--- a/source/funkin/editors/alphabet/AlphabetMainDataScreen.hx
+++ b/source/funkin/editors/alphabet/AlphabetMainDataScreen.hx
@@ -190,7 +190,7 @@ UPPERLETTER and LOWERLETTTER can be used in any backup prefix.";
final newData:AlphabetLetterData = {
isDefault: true,
advance: 0.0,
- advanceStyle: EMPTY,
+ advanceEmpty: true,
components: [{
anim: name,
diff --git a/source/funkin/editors/alphabet/GlyphInfoWindow.hx b/source/funkin/editors/alphabet/GlyphInfoWindow.hx
index c942672cde..f6018ea834 100644
--- a/source/funkin/editors/alphabet/GlyphInfoWindow.hx
+++ b/source/funkin/editors/alphabet/GlyphInfoWindow.hx
@@ -1,6 +1,5 @@
package funkin.editors.alphabet;
-import funkin.editors.alphabet.AlphabetEditor.ComponentButton;
import flixel.math.FlxAngle;
class GlyphInfoWindow extends UIWindow {
@@ -30,15 +29,13 @@ class GlyphInfoWindow extends UIWindow {
public var outlineItems:Array;
public var initialY:Array = [];
- public var button:ComponentButton;
var compon(get, never):AlphabetComponent;
var data(get, never):AlphabetLetterData;
- var advanceStyle(get, never):AdvanceMode;
public function new() {
var width = 360;
var height = 255;
- var margin = 15;
+ var margin = 30;
var itemMargin = 15; // there was already a buncha variables so i thought why not follow
var labelOffset = 24;
super(FlxG.width - width - margin, FlxG.height - height - margin, width, height, "Glyph Info");
@@ -51,12 +48,8 @@ class GlyphInfoWindow extends UIWindow {
prefixBox = new UITextBox(x + itemMargin, y + itemMargin + labelOffset * 2, "", 330);
prefixBox.onChange = function(val) {
- if (button != null)
- button.field.text = val;
-
var index = data.components.indexOf(compon);
compon.anim = val;
- data.advanceStyle = advanceStyle;
var anim = AlphabetEditor.instance.bigLetter.text + Std.string(index);
AlphabetEditor.instance.bigLetter.animation.remove(anim);
AlphabetEditor.instance.tape.animation.remove(anim);
@@ -68,7 +61,6 @@ class GlyphInfoWindow extends UIWindow {
xBox = new UINumericStepper(prefixBox.x, prefixBox.y + prefixBox.bHeight + itemMargin + labelOffset, 0, 1, 2, null, null, 80);
xBox.onChange = valueSet.bind(xBox, function(val) { // kinda dumb but blame cne ui
compon.x = -val;
- data.advanceStyle = advanceStyle;
});
members.push(xBox);
@@ -86,7 +78,6 @@ class GlyphInfoWindow extends UIWindow {
compon.scaleX = val;
if (outlineCheck.checked)
data.components[compon.outIndex].scaleX = val;
- data.advanceStyle = advanceStyle;
});
members.push(scaleXBox);
@@ -144,7 +135,7 @@ class GlyphInfoWindow extends UIWindow {
compon.outIndex = AlphabetEditor.instance.outlineIdx;
var newOutline:AlphabetComponent = {
- refIndex: data.components.indexOf(compon) - data.startIndex,
+ refIndex: data.components.indexOf(compon),
anim: outlineBox.label.text,
x: outlineXBox.value,
@@ -168,18 +159,11 @@ class GlyphInfoWindow extends UIWindow {
++AlphabetEditor.instance.outlineIdx;
++data.startIndex;
} else {
- var toRemove = compon.outIndex;
compon.outIndex = null;
- for (i in data.startIndex...data.components.length) {
- final component = data.components[i];
- if (component.outIndex != null && component.outIndex >= toRemove)
- --component.outIndex;
- }
- data.components.splice(toRemove, 1);
+ data.components.splice(AlphabetEditor.instance.outlineIdx - 1, 1);
--AlphabetEditor.instance.outlineIdx;
--data.startIndex;
}
- data.advanceStyle = advanceStyle;
}
members.push(outlineCheck);
@@ -208,7 +192,6 @@ class GlyphInfoWindow extends UIWindow {
outlineBox = new UITextBox(colorModeDrop.x, colorModeDrop.y + colorModeDrop.bHeight + itemMargin + labelOffset * 2 + 3, "", 190);
outlineBox.onChange = function(val) {
data.components[compon.outIndex].anim = val;
- data.advanceStyle = advanceStyle;
var anim = AlphabetEditor.instance.bigLetter.text + Std.string(compon.outIndex);
AlphabetEditor.instance.bigLetter.animation.remove(anim);
AlphabetEditor.instance.tape.animation.remove(anim);
@@ -221,7 +204,6 @@ class GlyphInfoWindow extends UIWindow {
outlineXBox.onChange = valueSet.bind(outlineXBox, function(val) { // kinda dumb but blame cne ui
if (outlineCheck.checked)
data.components[compon.outIndex].x = val;
- data.advanceStyle = advanceStyle;
});
members.push(outlineXBox);
@@ -309,13 +291,10 @@ class GlyphInfoWindow extends UIWindow {
func(item.value);
}
- inline function get_compon() {
+ function get_compon() {
return AlphabetEditor.instance.curSelectedComponent;
}
- inline function get_data() {
+ function get_data() {
return AlphabetEditor.instance.curSelectedData;
}
- function get_advanceStyle() {
- return data.advanceStyle == CALCULATED ? AUTO : data.advanceStyle;
- }
}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx
index e10a0f9216..3656429239 100644
--- a/source/funkin/editors/charter/Charter.hx
+++ b/source/funkin/editors/charter/Charter.hx
@@ -495,7 +495,6 @@ class Charter extends UIState {
songPosInfo = new UIText(FlxG.width - 30 - 400, scrollBar.y + 10, 400, "00:00 / 00:00\nBeat: 0\nStep: 0\nMeasure: 0\nBPM: 0\nTime Signature: 4/4");
songPosInfo.alignment = RIGHT;
- songPosInfo.fieldHeight = 115; // ?????
uiGroup.add(songPosInfo);
playBackSlider = new UISlider(FlxG.width - 160 - 26 - 20, (23/2) - (12/2), 160, 1, [{start: 0.25, end: 1, size: 0.5}, {start: 1, end: 2, size: 0.5}], true);
@@ -687,7 +686,6 @@ class Charter extends UIState {
for (note in strL.notes) {
var n = new CharterNote();
var t = Conductor.getStepForTime(note.time);
- CharterNote.callScriptOnNote('onCharterNoteCreation', n);
n.updatePos(t, note.id, Conductor.getStepForTime(note.time + note.sLen) - t, note.type, strumLines.members[i]);
notesGroup.members[notesCreated++] = n;
}
@@ -1050,7 +1048,6 @@ class Charter extends UIState {
if (mouseOnGrid && mousePos.y > 0 && mousePos.y < (__endStep)*40) {
var note = new CharterNote();
var targetStrumline = strumLines.getStrumlineFromID(id);
- CharterNote.callScriptOnNote('onCharterNoteCreation', note);
note.updatePos(
CoolUtil.bound(FlxG.keys.pressed.SHIFT ? ((mousePos.y-20) / 40) : quantStep(mousePos.y/40), 0, __endStep-1),
(id-targetStrumline.startingID) % targetStrumline.keyCount, 0, noteType, targetStrumline
@@ -1082,7 +1079,6 @@ class Charter extends UIState {
if (s is CharterNote) {
var n:CharterNote = cast s;
var newNote = new CharterNote();
- CharterNote.callScriptOnNote('onCharterNoteCreation', newNote);
newNote.updatePos(n.step, n.id, n.susLength, n.type, n.strumLine);
notesGroup.add(newNote);
newSelection.push(newNote);
@@ -1140,7 +1136,6 @@ class Charter extends UIState {
if (n.hovered || n.sustainDraggable) {
deletedNotes.push(n);
deleteSingleSelection(n, false);
- UIState.playEditorSound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND);
if (selection.contains(n)) selection.remove(n);
noteDeleteAnims.deleteNotes.push({
@@ -1215,6 +1210,7 @@ class Charter extends UIState {
if (selected == null) return selected;
if (selected is CharterNote) {
+ UIState.playEditorSound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND);
var note:CharterNote = cast selected;
note.strumLineID = strumLines.members.indexOf(note.strumLine);
note.strumLine = null; // For static undos :D
@@ -1272,7 +1268,6 @@ class Charter extends UIState {
else member++;
}
}
- UIState.playEditorSound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND);
notesGroup.sortNotes();
notesGroup.autoSort = true;
@@ -1314,7 +1309,6 @@ class Charter extends UIState {
var toBeCreated:Selection = [];
for(note in strL.notes) {
var n = new CharterNote();
- CharterNote.callScriptOnNote('onCharterNoteCreation', n);
var t = Conductor.getStepForTime(note.time);
n.updatePos(t, note.id, Conductor.getStepForTime(note.time + note.sLen) - t, note.type, cStr);
notesGroup.add(n);
@@ -1371,7 +1365,7 @@ class Charter extends UIState {
FlxG.state.openSubState(new CharterStrumlineScreen(strumLines.members.length, null, (_) -> {
if (_ != null) {
createStrumline(strumLines.members.length, _);
- strumlineAddButton.button.setColorTransform(1, 1, 1, strumlineAddButton.button.alpha);
+
strumlineAddButton.textTweenColor.color = 0xFF00FF00;
strumlineAddButton.pressAnimation(true);
}
@@ -1412,18 +1406,6 @@ class Charter extends UIState {
var __crochet:Float;
var __firstFrame:Bool = true;
var __timer:Float = 0;
- var ugly:Array = [];
-
- // dynamic in case scripts want to add more text
- public dynamic function getSongPosInfoText(songLength:Float, curChange):String {
- return'${CoolUtil.timeToStr(Conductor.songPosition)} / ${CoolUtil.timeToStr(songLength)}'
- +'\n'+SONGPOSINFO_STEP.format({ugly[0]=curStep;ugly;})
- +'\n'+SONGPOSINFO_BEAT.format({ugly[0]=curBeat;ugly;})
- +'\n'+SONGPOSINFO_MEASURE.format({ugly[0]=curMeasure;ugly;})
- +'\n'+SONGPOSINFO_BPM.format({ugly[0]=(curChange != null && curChange.continuous && curChange.endSongTime > songPos) ? FlxMath.roundDecimal(Conductor.bpm, 3) : Conductor.bpm;ugly;})
- +'\n'+SONGPOSINFO_TIMESIGNATURE.format({ugly[0]=Conductor.beatsPerMeasure;ugly[1]=Conductor.denominator;ugly;});
- }
-
public override function update(elapsed:Float) {
if (Options.charterRainbowWaveforms) {
__timer += elapsed/8;
@@ -1486,8 +1468,7 @@ class Charter extends UIState {
noteTypeText.x = noteTopButton.x + noteTopButton.bWidth + 6;
noteTypeText.y = Std.int((noteTopButton.bHeight - noteTypeText.height) / 2);
}
- var targetNoteText = '($noteType) ' + (noteTypes[noteType-1] == null ? translate("noteTypes.default") : noteTypes[noteType-1]);
- if (noteTypeText.text != targetNoteText) noteTypeText.text = targetNoteText;
+ noteTypeText.text = '($noteType) ' + (noteTypes[noteType-1] == null ? translate("noteTypes.default") : noteTypes[noteType-1]);
super.update(elapsed);
@@ -1529,8 +1510,16 @@ class Charter extends UIState {
for (strumLine in strumLines.members) strumLine.vocals.pause();
}
- var targetText = getSongPosInfoText(songLength, Conductor.curChange);
- if (songPosInfo.text != targetText) songPosInfo.text = targetText;
+ var curChange = Conductor.curChange;
+ songPosInfo.text = [
+ // no need to translate the time text since it has no text only numbers
+ '${CoolUtil.timeToStr(Conductor.songPosition)} / ${CoolUtil.timeToStr(songLength)}',
+ SONGPOSINFO_STEP.format([curStep]),
+ SONGPOSINFO_BEAT.format([curBeat]),
+ SONGPOSINFO_MEASURE.format([curMeasure]),
+ SONGPOSINFO_BPM.format([(curChange != null && curChange.continuous && curChange.endSongTime > songPos) ? FlxMath.roundDecimal(Conductor.bpm, 3) : Conductor.bpm]),
+ SONGPOSINFO_TIMESIGNATURE.format([Conductor.beatsPerMeasure, Conductor.denominator])
+ ].join("\n");
if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, __firstFrame ? 1 : 0.125)))
updateDisplaySprites();
@@ -1538,15 +1527,6 @@ class Charter extends UIState {
if (strumLines != null)
strumlineLockButton.button.animation.play(strumLines.draggable ? "1" : "0", true);
- if (strumLines.members.length <= 0) {
- final glow = (Math.sin(FlxG.game.ticks * 0.004) + 1) * 0.5;
- strumlineAddButton.button.setColorTransform(
- 1 - (glow * 0.5), 1 - (glow * 0.5), 1 - (glow * 0.5),
- strumlineAddButton.button.alpha,
- Std.int(glow * 255), Std.int(glow * 255), Std.int(glow * 255), 0
- );
- }
-
WindowUtils.prefix = undos.unsaved ? Flags.UNDO_PREFIX : "";
SaveWarning.showWarning = undos.unsaved;
@@ -1754,7 +1734,6 @@ class Charter extends UIState {
switch(c) {
case CNote(step, id, strumLineID, susLength, type):
var note = new CharterNote();
- CharterNote.callScriptOnNote('onCharterNoteCreation', note);
note.updatePos(minStep + step, id, susLength, type, strumLines.members[CoolUtil.boundInt(strumLineID, 0, strumLines.length-1)]);
notesGroup.add(note);
sObjects.push(note);
diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx
index 39d66b1785..d021c66bcf 100644
--- a/source/funkin/editors/charter/CharterBackdropGroup.hx
+++ b/source/funkin/editors/charter/CharterBackdropGroup.hx
@@ -66,9 +66,7 @@ class CharterBackdropGroup extends FlxTypedGroup {
super.update(elapsed);
- var i = 0;
- while (i < strumLineGroup.members.length) {
- final strumLine = strumLineGroup.members[i];
+ for (i => strumLine in strumLineGroup.members) {
if (strumLine == null) continue;
if (members[i] == null)
@@ -93,7 +91,6 @@ class CharterBackdropGroup extends FlxTypedGroup {
grid.active = grid.visible = true;
grid.updateSprites();
- i++;
}
}
@@ -142,15 +139,21 @@ class NotesDrawGroup extends FlxFastTypedGroup {
if (note != null && note.exists && note.visible) {
if (note.snappedToGrid) note.x = (note.strumLine != null ? note.strumLine.x : 0) + (note.id % (note.strumLine != null ? note.strumLine.keyCount : 4)) * 40;
note.drawMembers();
- note.drawSuper();
}
}
+
i = 0; note = null;
while (i < length) {
note = members[i++];
- if (note != null && note.exists && note.visible) {
+ if (note != null && note.exists && note.visible)
+ note.drawSuper();
+ }
+
+ i = 0; note = null;
+ while (i < length) {
+ note = members[i++];
+ if (note != null && note.exists && note.visible)
note.drawNoteTypeText();
- }
}
FlxCamera._defaultCameras = oldDefaultCameras;
@@ -177,9 +180,6 @@ class CharterBackdrop extends FlxTypedGroup {
public var cameraHighlight:CameraHighlight;
- private var __spriteMembers:Array;
- private var __separatorBars:Array;
-
public function new() {
super();
@@ -242,15 +242,8 @@ class CharterBackdrop extends FlxTypedGroup {
conductorFollowerSpr.scale.set(4 * 40, 4);
conductorFollowerSpr.updateHitbox();
add(conductorFollowerSpr);
-
- __spriteMembers = [gridBackDrop, beatSeparator, topLimit, bottomLimit, topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite, cameraHighlight];
- __separatorBars = [conductorFollowerSpr, beatSeparator, topSeparator, bottomSeparator];
}
- private final __shader_pixelOffset:Array = [0];
- private final __shader_textureRes:Array = [0, 0];
- private final __shader_playerPosition:Array = [0];
-
public function updateSprites() {
var x:Float = 0; // fuck you
var alpha:Float = 0.9;
@@ -263,7 +256,8 @@ class CharterBackdrop extends FlxTypedGroup {
cameraHighlight.color = strumLine.highlightColor;
} else alpha = 0.9;
- for (spr in __spriteMembers) {
+ for (spr in [gridBackDrop, beatSeparator, topLimit, bottomLimit,
+ topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite, cameraHighlight]) {
spr.x = x; if (spr != waveformSprite) spr.alpha = alpha;
spr.cameras = this.cameras;
}
@@ -280,7 +274,7 @@ class CharterBackdrop extends FlxTypedGroup {
bottomLimit.scale.set(keyCount * 40, Math.ceil(FlxG.height / cameras[0].zoom));
bottomLimit.updateHitbox();
- for (spr in __separatorBars) {
+ for (spr in [conductorFollowerSpr, beatSeparator, topSeparator, bottomSeparator]) {
spr.scale.x = keyCount * 40;
spr.updateHitbox();
}
@@ -303,14 +297,9 @@ class CharterBackdrop extends FlxTypedGroup {
}
waveformSprite.updateHitbox();
- __shader_pixelOffset[0] = Math.max(conductorFollowerSpr.y - ((FlxG.height * (1/cameras[0].zoom)) * 0.5), 0);
- __shader_textureRes[0] = waveformSprite.width;
- __shader_textureRes[1] = waveformSprite.height;
- __shader_playerPosition[0] = conductorFollowerSpr.y;
-
- waveformSprite.shader.data.pixelOffset.value = __shader_pixelOffset;
- waveformSprite.shader.data.textureRes.value = __shader_textureRes;
- waveformSprite.shader.data.playerPosition.value = __shader_playerPosition;
+ waveformSprite.shader.data.pixelOffset.value = [Math.max(conductorFollowerSpr.y - ((FlxG.height * (1/cameras[0].zoom)) * 0.5), 0)];
+ waveformSprite.shader.data.textureRes.value = [waveformSprite.width, waveformSprite.height];
+ waveformSprite.shader.data.playerPosition.value = [conductorFollowerSpr.y];
}
}
diff --git a/source/funkin/editors/charter/CharterDeleteAnim.hx b/source/funkin/editors/charter/CharterDeleteAnim.hx
index f6ade916c2..2e23d38a76 100644
--- a/source/funkin/editors/charter/CharterDeleteAnim.hx
+++ b/source/funkin/editors/charter/CharterDeleteAnim.hx
@@ -54,50 +54,12 @@ class CharterDeleteAnim extends CharterNote {
garbageCircle.alpha = __garbageAlpha*.6;
}
- static var __stupidScriptParamArray:Array = [];
-
- static function callScriptOnNotes(event:String, note1:CharterNote, note2:CharterNote) {
- if (Charter.instance != null) {
- __stupidScriptParamArray[0] = note1;
- __stupidScriptParamArray[1] = note2;
- Charter.instance.stateScripts.call(event, __stupidScriptParamArray);
- }
- }
-
public override function draw() @:privateAccess {
- // kill me
- final __lastFrame:flixel.graphics.frames.FlxFrame = this.frame;
- final __lastSX:Float = this.scale.x;
- final __lastSY:Float = this.scale.y;
- final __lastW:Float = this.width;
- final __lastH:Float = this.height;
- final __lastOX:Float = this.offset.x;
- final __lastOY:Float = this.offset.y;
- final __lastRX:Float = this.origin.x;
- final __lastRY:Float = this.origin.y;
- final __lastFX:Float = this.frameOffset.x;
- final __lastFY:Float = this.frameOffset.y;
- final __lastColor:flixel.util.FlxColor = this.color;
-
for (deleteData in deleteNotes) {
y = deleteData.note.y + (deleteData.time>deleteTime*.5 ? (deleteData.time/deleteTime)*FlxG.random.float(-1.1, 1.1) : 0); // lunar when no shake :((
x = deleteData.note.x + (deleteData.time>deleteTime*.5 ? (deleteData.time/deleteTime)*FlxG.random.float(-1.1, 1.1) : 0); // lunar when no shake :((
- angle = 0;
- setSize(deleteData.note.width, deleteData.note.height);
- scale.set(deleteData.note.scale.x, deleteData.note.scale.y);
- offset.set(deleteData.note.offset.x, deleteData.note.offset.y);
- origin.set(deleteData.note.origin.x, deleteData.note.origin.y);
- frameOffset.set(deleteData.note.frameOffset.x, deleteData.note.frameOffset.y);
-
- if (!deleteData.note.noDefaultAnims) {
- frame = __lastFrame;
- angle = deleteData.note.angle;
- animation.curAnim.curFrame = 3;
- color = __lastColor;
- } else {
- frame = deleteData.note.frame;
- }
- alpha = 1;
+ angle = deleteData.note.angle; alpha = 1;
+ animation.curAnim.curFrame = 3;
sustainSpr.scale.set(10, (40 * deleteData.note.susLength) + (height/2));
sustainSpr.updateHitbox(); sustainSpr.follow(this, 15, 20);
@@ -115,19 +77,9 @@ class CharterDeleteAnim extends CharterNote {
member.alpha *= mult;
typeAlpha *= mult;
- callScriptOnNotes('onCharterNoteDeleteUpdate', deleteData.note, this);
-
super.draw();
}
- this.frame = __lastFrame;
- this.scale.set(__lastSX, __lastSY);
- this.setSize(__lastW, __lastH);
- this.offset.set(__lastOX, __lastOY);
- this.origin.set(__lastRX, __lastRY);
- this.frameOffset.set(__lastFX, __lastFY);
- this.color = __lastColor;
-
if (garbageIcon.alpha > 0) garbageIcon.draw();
if (garbageCircle.alpha > 0) garbageCircle.draw();
}
diff --git a/source/funkin/editors/charter/CharterEventAdd.hx b/source/funkin/editors/charter/CharterEventAdd.hx
index 8396507d5a..ee7ec48ec7 100644
--- a/source/funkin/editors/charter/CharterEventAdd.hx
+++ b/source/funkin/editors/charter/CharterEventAdd.hx
@@ -63,29 +63,19 @@ class CharterEventAdd extends UISliceSprite {
curCharterEvent = null;
this.step = step;
this.y = (step * 40) - (bHeight / 2);
+ text.text = TU.translate("charter.addEvent");
framesOffset = 0; bWidth = 37 + Math.ceil(text.width);
- updateStuff(global, false);
+ x = (global != Options.charterSwapEventSides) ? Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].x + (40*Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].keyCount) : -(bWidth);
+ sideText.text = TU.translate("charter.eventType-" + (global ? "global" : "local"));
}
public function updateEdit(event:CharterEvent) {
if (FlxG.state.subState != null) return;
curCharterEvent = event;
this.y = event.y;
+ text.text = TU.translate("charter.editEvent");
framesOffset = 9; bWidth = 27 + Math.ceil(text.width) + event.bWidth;
- updateStuff(event.global, true);
- }
-
- private function updateStuff(global:Bool = false, edit:Bool = false) {
- final lastStrumline = Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1];
- if (lastStrumline != null)
- x = (global != Options.charterSwapEventSides) ? lastStrumline.x + (40*lastStrumline.keyCount) : -(bWidth);
- else
- x = (global != Options.charterSwapEventSides) ? 0 : -(bWidth);
-
- final target = TU.translate("charter.eventType-" + (global ? "global" : "local"));
- if (sideText.text != target) sideText.text = target;
-
- final targetButtonText = TU.translate("charter." + (edit ? "edit" : "add") + "Event");
- if (text.text != targetButtonText) text.text = targetButtonText;
+ x = (event.global != Options.charterSwapEventSides) ? Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].x + (40*Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].keyCount) : -(bWidth);
+ sideText.text = TU.translate("charter.eventType-" + (event.global ? "global" : "local"));
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/CharterNote.hx b/source/funkin/editors/charter/CharterNote.hx
index cf00afe953..8bece2b4af 100644
--- a/source/funkin/editors/charter/CharterNote.hx
+++ b/source/funkin/editors/charter/CharterNote.hx
@@ -20,7 +20,6 @@ class CharterNote extends UISprite implements ICharterSelectable {
0xFFF9393F
];
- public var noDefaultAnims:Bool = false;
public var sustainSpr:UISprite;
public var tempSusLength:Float = 0;
public var sustainDraggable:Bool = false;
@@ -29,17 +28,8 @@ class CharterNote extends UISprite implements ICharterSelectable {
public var selected:Bool = false;
public var draggable:Bool = true;
- public var extra:Map = [];
static var noteTypeTexts:Array = [];
- static var __stupidScriptParamArray:Array = [];
-
- public static function callScriptOnNote(event:String, note:CharterNote) {
- if (Charter.instance != null) {
- __stupidScriptParamArray[0] = note;
- Charter.instance.stateScripts.call(event, __stupidScriptParamArray);
- }
- }
public function new() {
super();
@@ -59,11 +49,6 @@ class CharterNote extends UISprite implements ICharterSelectable {
cursor = sustainSpr.cursor = CLICK;
moves = false;
-
- // this would also call it on the hoverer and deleter and . i dont think i want that
- // for unsuspecting users so it will be comented out but can still be edited after
- // in a charter state script
- // callScriptOnNote('onCharterNoteCreation', this);
}
public override function updateButtonHandler() {
@@ -115,11 +100,6 @@ class CharterNote extends UISprite implements ICharterSelectable {
if (angleTween != null) angleTween.cancel();
- if (noDefaultAnims) {
- // angle = 0;
- return callScriptOnNote('onCharterNoteUpdatePos', this);
- }
-
var destAngle:Float = switch(animation.curAnim.curFrame = (id % 4)) {
case 0: 270;
case 1: 180;
@@ -132,10 +112,10 @@ class CharterNote extends UISprite implements ICharterSelectable {
if (!__doAnim) {
angle = destAngle;
- return callScriptOnNote('onCharterNoteUpdatePos', this);
+ return;
}
- if (angle == destAngle) return callScriptOnNote('onCharterNoteUpdatePos', this);
+ if (angle == destAngle) return;
if(angleTween != null)
angleTween.cancel();
@@ -145,30 +125,25 @@ class CharterNote extends UISprite implements ICharterSelectable {
angleTween = FlxTween.angle(this, angle, destAngle, (2/3)/__animSpeed, {ease: function(t) {
return ((Math.sin(t * Math.PI) * 0.35) * 3 * t * Math.sqrt(1 - t)) + t;
}});
-
- callScriptOnNote('onCharterNoteUpdatePos', this);
}
public override function kill() {
- if (!noDefaultAnims) {
- if (angleTween != null) {
- angleTween.cancel();
- angleTween = null;
- angle = switch(animation.curAnim.curFrame = (id % 4)) {
- case 0: 270;
- case 1: 180;
- case 2: 0;
- case 3: 90;
- default: 0; // how is that even possible
- };
- __doAnim = false;
- }
+ if (angleTween != null) {
+ angleTween.cancel();
+ angleTween = null;
+ angle = switch(animation.curAnim.curFrame = (id % 4)) {
+ case 0: 270;
+ case 1: 180;
+ case 2: 0;
+ case 3: 90;
+ default: 0; // how is that even possible
+ };
+ __doAnim = false;
}
super.kill();
}
var __passed:Bool = false;
- var __selected:Bool = false;
public override function update(elapsed:Float) {
super.update(elapsed);
@@ -188,7 +163,6 @@ class CharterNote extends UISprite implements ICharterSelectable {
if (__passed != (__passed = step < Conductor.curStepFloat + (Options.songOffsetAffectEditors ? (Conductor.songOffset / Conductor.stepCrochet) : 0))) {
if (__passed && FlxG.sound.music.playing) {
Charter.instance.playHitsound(strumLineID);
- callScriptOnNote('onCharterNoteHit', this);
}
}
@@ -198,12 +172,9 @@ class CharterNote extends UISprite implements ICharterSelectable {
typeAlpha = !isVisible ? (__passed ? 0.4 : 0.6) : (__passed ? 0.8 : 1);
}
- if (__selected != (__selected = selected)) {
- colorTransform.redMultiplier = colorTransform.greenMultiplier = colorTransform.blueMultiplier = selected ? 0.75 : 1;
- colorTransform.redOffset = colorTransform.greenOffset = selected ? 96 : 0;
- colorTransform.blueOffset = selected ? 168 : 0;
- callScriptOnNote('onCharterNoteSelect', this);
- }
+ colorTransform.redMultiplier = colorTransform.greenMultiplier = colorTransform.blueMultiplier = selected ? 0.75 : 1;
+ colorTransform.redOffset = colorTransform.greenOffset = selected ? 96 : 0;
+ colorTransform.blueOffset = selected ? 168 : 0;
__doAnim = true;
}
diff --git a/source/funkin/editors/charter/CharterNoteHoverer.hx b/source/funkin/editors/charter/CharterNoteHoverer.hx
index 51a2d299c9..3f7146ba86 100644
--- a/source/funkin/editors/charter/CharterNoteHoverer.hx
+++ b/source/funkin/editors/charter/CharterNoteHoverer.hx
@@ -20,14 +20,13 @@ class CharterNoteHoverer extends CharterNote {
if ((__mousePos.x > 0 && __mousePos.x < Charter.instance.strumLines.totalKeyCount * 40 && inBoundsY) && showHoverer) {
step = CoolUtil.bound(FlxG.keys.pressed.SHIFT ? ((__mousePos.y-20) / 40) : Charter.instance.quantStep(__mousePos.y/40), 0, Charter.instance.__endStep-1);
id = Math.floor(__mousePos.x / 40); y = step * 40; x = id * 40; visible = true; sustainSpr.visible = typeVisible = false;
- if (!noDefaultAnims)
- angle = switch(animation.curAnim.curFrame = ((id - Charter.instance.strumLines.getStrumlineFromID(id).startingID) % 4)) {
- case 0: -90;
- case 1: 180;
- case 2: 0;
- case 3: 90;
- default: 0; // how is that even possible
- };
+ angle = switch(animation.curAnim.curFrame = ((id - Charter.instance.strumLines.getStrumlineFromID(id).startingID) % 4)) {
+ case 0: -90;
+ case 1: 180;
+ case 2: 0;
+ case 3: 90;
+ default: 0; // how is that even possible
+ };
} else
visible = false;
case NOTE_DRAG:
@@ -38,20 +37,6 @@ class CharterNoteHoverer extends CharterNote {
}
public override function draw() @:privateAccess {
- // kill me
- final __lastFrame:flixel.graphics.frames.FlxFrame = this.frame;
- final __lastSX:Float = this.scale.x;
- final __lastSY:Float = this.scale.y;
- final __lastW:Float = this.width;
- final __lastH:Float = this.height;
- final __lastOX:Float = this.offset.x;
- final __lastOY:Float = this.offset.y;
- final __lastRX:Float = this.origin.x;
- final __lastRY:Float = this.origin.y;
- final __lastFX:Float = this.frameOffset.x;
- final __lastFY:Float = this.frameOffset.y;
- final __lastColor:flixel.util.FlxColor = this.color;
-
switch (Charter.instance.gridActionType) {
case NONE:
super.draw();
@@ -71,28 +56,16 @@ class CharterNoteHoverer extends CharterNote {
var newID:Int = CoolUtil.boundInt(draggingNote.fullID + horizontalChange, 0, Charter.instance.strumLines.totalKeyCount-1);
x = (id=newID) * 40; y = CoolUtil.bound(y, 0, (Charter.instance.__endStep*40) - height);
- angle = 0;
- setSize(draggingNote.width, draggingNote.height);
- scale.set(draggingNote.scale.x, draggingNote.scale.y);
- offset.set(draggingNote.offset.x, draggingNote.offset.y);
- origin.set(draggingNote.origin.x, draggingNote.origin.y);
- frameOffset.set(draggingNote.frameOffset.x, draggingNote.frameOffset.y);
- color = draggingNote.color;
- if (!draggingNote.noDefaultAnims) {
- frame = __lastFrame;
- angle = switch(animation.curAnim.curFrame = (draggingNote.id % 4)) {
- case 0: -90;
- case 1: 180;
- case 2: 0;
- case 3: 90;
- default: 0; // how is that even possible
- };
- } else {
- frame = draggingNote.frame;
- }
+ angle = switch(animation.curAnim.curFrame = (draggingNote.id % 4)) {
+ case 0: -90;
+ case 1: 180;
+ case 2: 0;
+ case 3: 90;
+ default: 0; // how is that even possible
+ };
sustainSpr.scale.set(10, (40 * draggingNote.susLength) + (height/2));
- sustainSpr.color = draggingNote.noDefaultAnims ? draggingNote.sustainSpr.color : CharterNote.colors[animation.curAnim.curFrame];
+ sustainSpr.color = CharterNote.colors[animation.curAnim.curFrame];
sustainSpr.updateHitbox(); sustainSpr.alpha = alpha; sustainSpr.follow(this, 15, 20);
sustainSpr.exists = draggingNote.susLength != 0;
@@ -107,13 +80,6 @@ class CharterNoteHoverer extends CharterNote {
}
default: // do nothing
}
- this.frame = __lastFrame;
- this.scale.set(__lastSX, __lastSY);
- this.setSize(__lastW, __lastH);
- this.offset.set(__lastOX, __lastOY);
- this.origin.set(__lastRX, __lastRY);
- this.frameOffset.set(__lastFX, __lastFY);
- this.color = __lastColor;
}
public override function destroy() {
diff --git a/source/funkin/editors/charter/CharterStrumLineGroup.hx b/source/funkin/editors/charter/CharterStrumLineGroup.hx
index 5145417a2b..2a0f42b617 100644
--- a/source/funkin/editors/charter/CharterStrumLineGroup.hx
+++ b/source/funkin/editors/charter/CharterStrumLineGroup.hx
@@ -30,8 +30,7 @@ class CharterStrumLineGroup extends FlxTypedGroup {
for (strumLine in members) {
if (strumLine == null) continue;
strumLine.draggable = draggable;
- if (draggable && FlxG.mouse.justPressed) {
- if (!UIState.state.isOverlapping(strumLine.draggingSprite, @:privateAccess strumLine.draggingSprite.__rect)) continue;
+ if (draggable && UIState.state.isOverlapping(strumLine.draggingSprite, @:privateAccess strumLine.draggingSprite.__rect) && FlxG.mouse.justPressed) {
draggingObj = strumLine;
strumLine.dragging = true;
@@ -47,39 +46,29 @@ class CharterStrumLineGroup extends FlxTypedGroup {
refreshStrumlineIDs();
}
- var minX:Float = (members.length > 0) ? (FlxG.width + cameras[0].scroll.x) : 0;
- var maxX:Float = 0;
- for (i=>strum in members) {
- if (strum != null && !strum.dragging) strum.x = CoolUtil.fpsLerp(strum.x, 40 * strum.startingID, 0.225);
- minX = Math.min(minX, strum.x);
- maxX = Math.max(maxX, strum.x + (40 * strum.keyCount));
- }
+ for (i=>strum in members)
+ if (strum != null && !strum.dragging) strum.x = CoolUtil.fpsLerp(strum.x, 40*strum.startingID, 0.225);
- final firstStrumline = members[0];
- final lastStrumline = members[members.length - 1];
- final c = Charter.instance;
- if (c.leftEventsBackdrop != null) {
- c.leftEventsBackdrop.x = minX - c.leftEventsBackdrop.width;
- c.leftEventsBackdrop.alpha = ((firstStrumline == null) || ((firstStrumline != null) && (firstStrumline.strumLine.visible))) ? 0.9 : 0.4;
+ if (Charter.instance.leftEventsBackdrop != null && members[0] != null) {
+ Charter.instance.leftEventsBackdrop.x = members[0].button.x - Charter.instance.leftEventsBackdrop.width;
+ Charter.instance.leftEventsBackdrop.alpha = members[0].strumLine.visible ? 0.9 : 0.4;
- if (c.leftEventRowText != null)
- c.leftEventRowText.x = -c.leftEventRowText.width - 42;
+ if (Charter.instance.leftEventRowText != null)
+ Charter.instance.leftEventRowText.x = members[0].button.x - Charter.instance.leftEventRowText.width - 42;
}
- if (c.strumlineLockButton != null)
- c.strumlineLockButton.x = minX - 160;
-
- if (c.strumlineAddButton != null)
- c.strumlineAddButton.x = maxX;
+ if (Charter.instance.rightEventsBackdrop != null && members[CoolUtil.maxInt(0, members.length-1)] != null) {
+ Charter.instance.rightEventsBackdrop.x = members[members.length-1].x + (40*members[members.length-1].keyCount);
+ Charter.instance.rightEventsBackdrop.alpha = members[CoolUtil.maxInt(0, members.length-1)].strumLine.visible ? 0.9 : 0.4;
- if (c.rightEventsBackdrop != null) {
- c.rightEventsBackdrop.x = maxX;
- c.rightEventsBackdrop.alpha = ((lastStrumline == null) || ((lastStrumline != null) && (lastStrumline.strumLine.visible))) ? 0.9 : 0.4;
- c.rightEventsBackdrop.flipY = totalKeyCount % 2 == 0;
-
- if (c.rightEventRowText != null)
- c.rightEventRowText.x = c.rightEventsBackdrop.x + 42;
+ if (Charter.instance.rightEventRowText != null)
+ Charter.instance.rightEventRowText.x = Charter.instance.rightEventsBackdrop.x + 42;
}
+
+ if (Charter.instance.strumlineLockButton != null && members[0] != null)
+ Charter.instance.strumlineLockButton.x = members[0].x - (160);
+ if (Charter.instance.strumlineAddButton != null && members[CoolUtil.maxInt(0, members.length-1)] != null)
+ Charter.instance.strumlineAddButton.x = members[members.length-1].x + (40*members[members.length-1].keyCount);
if ((FlxG.mouse.justReleased || !draggable) && isDragging)
finishDrag();
diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx
index 614e84f07e..dabcab6a64 100644
--- a/source/funkin/editors/charter/CharterStrumline.hx
+++ b/source/funkin/editors/charter/CharterStrumline.hx
@@ -68,7 +68,17 @@ class CharterStrumline extends UISprite {
healthIcons = new FlxSpriteGroup(x, y);
- regenerateIcons(icons);
+ var maxCol = icons.length < 4 ? icons.length : 4;
+ var maxRow = Math.floor((icons.length-1) / 4) + 1;
+ for (i=>icon in icons) {
+ var healthIcon = new HealthIcon(Character.getIconFromCharName(icon));
+ healthIcon.scale.x = healthIcon.scale.y = Math.max((0.6 - (icons.length / 20)), 0.35);
+ healthIcon.updateHitbox();
+ healthIcon.x = FlxMath.lerp(0, Math.min(icons.length * 20, 120), (maxCol-1 != 0 ? (i % 4) / (maxCol-1) : 0));
+ healthIcon.y = (draggable ? 29 : 7) + FlxMath.lerp(0, Math.min(maxRow * 15, 60), (maxRow-1 != 0 ? Math.floor(i / 4) / (maxRow-1) : 0));
+ healthIcon.alpha = strumLine.visible ? 1 : 0.4;
+ healthIcons.add(healthIcon);
+ }
members.push(healthIcons);
@@ -88,22 +98,6 @@ class CharterStrumline extends UISprite {
selectedWaveform = -1;
}
- public function regenerateIcons(icons:Array) {
- if (healthIcons.length > 0) healthIcons.clear();
-
- var maxCol = icons.length < 4 ? icons.length : 4;
- var maxRow = Math.floor((icons.length - 1) / 4) + 1;
- for (i => icon in icons) {
- var healthIcon = new HealthIcon(Character.getIconFromCharName(icon));
- healthIcon.scale.x = healthIcon.scale.y = Math.max((0.6 - (icons.length / 20)), 0.35) * (150 / Math.max(healthIcon.frameWidth, healthIcon.frameHeight));
- healthIcon.updateHitbox();
- healthIcon.x = FlxMath.lerp(0, Math.min(icons.length * 20, 120), (maxCol-1 != 0 ? (i % 4) / (maxCol-1) : 0));
- healthIcon.y = (draggable ? 14 : 7) + FlxMath.lerp(0, Math.min(maxRow * 15, 60), (maxRow-1 != 0 ? Math.floor(i / 4) / (maxRow-1) : 0));
- healthIcon.alpha = strumLine.visible ? 1 : 0.4;
- healthIcons.add(healthIcon);
- }
- }
-
private var __healthYOffset:Float = 0;
private var __draggingYOffset:Float = 0;
@@ -113,18 +107,15 @@ class CharterStrumline extends UISprite {
healthIcons.follow(this, ((40 * keyCount) - healthIcons.width) / 2, 7 + (__healthYOffset = FlxMath.lerp(__healthYOffset, draggable ? 8 : 0, 1/20)));
draggingSprite.selectable = draggable;
- if (draggable) {
- draggingSprite.updateSpriteRect();
+ draggingSprite.updateSpriteRect();
- var fullAlpha:Float = UIState.state.isOverlapping(draggingSprite, @:privateAccess draggingSprite.__rect) || dragging ? 0.9 : 0.35;
- draggingSprite.alpha = FlxMath.lerp(draggingSprite.alpha, fullAlpha, 1 / 12);
- } else {
- draggingSprite.alpha = FlxMath.lerp(draggingSprite.alpha, 0, 1 / 12);
- }
- var dragScale:Float = FlxMath.lerp(draggingSprite.scale.x, draggable ? 1 : 0.8, 1 / 16);
+ var dragScale:Float = FlxMath.lerp(draggingSprite.scale.x, draggable ? 1 : 0.8, 1/16);
draggingSprite.scale.set(dragScale, dragScale);
draggingSprite.updateHitbox();
+
draggingSprite.follow(this, ((keyCount*40)/2) - (draggingSprite.width/2), 6 + (__draggingYOffset = FlxMath.lerp(__draggingYOffset, draggable ? 3 : 0, 1/12)));
+ var fullAlpha:Float = UIState.state.isOverlapping(draggingSprite, @:privateAccess draggingSprite.__rect) || dragging ? 0.9 : 0.35;
+ draggingSprite.alpha = FlxMath.lerp(draggingSprite.alpha, draggable ? fullAlpha : 0, 1/12);
button.follow(this, 0, 95);
super.update(elapsed);
@@ -135,7 +126,19 @@ class CharterStrumline extends UISprite {
keyCount = strumLine.keyCount != null ? strumLine.keyCount : 4;
- regenerateIcons(icons);
+ healthIcons.clear();
+
+ var maxCol = icons.length < 4 ? icons.length : 4;
+ var maxRow = Math.floor((icons.length-1) / 4) + 1;
+ for (i=>icon in icons) {
+ var healthIcon = new HealthIcon(Character.getIconFromCharName(icon));
+ healthIcon.scale.x = healthIcon.scale.y = Math.max((0.6 - (icons.length / 20)), 0.35) * (150 / Math.max(healthIcon.frameWidth, healthIcon.frameHeight));
+ healthIcon.updateHitbox();
+ healthIcon.x = FlxMath.lerp(0, Math.min(icons.length * 20, 120), (maxCol-1 != 0 ? (i % 4) / (maxCol-1) : 0));
+ healthIcon.y = (draggable ? 14 : 7) + FlxMath.lerp(0, Math.min(maxRow * 15, 60), (maxRow-1 != 0 ? Math.floor(i / 4) / (maxRow-1) : 0));
+ healthIcon.alpha = strumLine.visible ? 1 : 0.4;
+ healthIcons.add(healthIcon);
+ }
var asset = strumLine.vocalsSuffix.length > 0 ? Assets.getSound(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : null;
diff --git a/source/funkin/editors/stage/StageEditor.hx b/source/funkin/editors/stage/StageEditor.hx
index 9753891ab3..5e3d80b723 100644
--- a/source/funkin/editors/stage/StageEditor.hx
+++ b/source/funkin/editors/stage/StageEditor.hx
@@ -17,7 +17,6 @@ import haxe.xml.Access;
import haxe.xml.Printer;
import lime.ui.MouseCursor;
import openfl.ui.Mouse;
-import openfl.display.BlendMode;
using funkin.backend.utils.MatrixUtil;
@@ -377,10 +376,12 @@ class StageEditor extends UIState {
char.name = charName;
char.debugMode = true;
char.useRenderTexture = true;
- // Play first anim, and make it the last frame by reversing and stopping.
+ // Play first anim, and make it the last frame
var animToPlay = char.getAnimOrder()[0];
- char.playAnim(animToPlay, true, NONE, true, 0);
- char.stopAnim();
+ char.playAnim(animToPlay, true, NONE);
+ var lastIndx = char.animation.curAnim.numFrames - 1;
+ char.playAnim(animToPlay, true, NONE, false, lastIndx);
+ char.stopAnimation();
// Add it to the stage
char.visible = true;
@@ -555,31 +556,6 @@ class StageEditor extends UIState {
openSubState(substate);
}
- function _solid_new(_) {
- var node:Access = new Access(Xml.createElement("solid"));
- stage.stageXML.x.addChild(node.x);
- node.att.name = "solid_" + stageSpritesWindow.buttons.members.length;
-
- var sprite:FunkinSprite = new FunkinSprite();
- insert(members.indexOf(stage), sprite);
- sprite.extra.set(exID("node"), node);
- sprite.extra.set(exID("type"), node.name);
- sprite.extra.set(exID("imageFile"), '');
- sprite.extra.set(exID("parentNode"), stage.stageXML.x);
- sprite.extra.set(exID("highMemory"), false);
- sprite.extra.set(exID("lowMemory"), false);
- sprite.antialiasing = true;
- xmlMap.set(sprite, node);
-
- var button:StageSpriteButton = new StageSolidButton(0, 0, sprite, node);
- sprite.extra.set(exID("button"), button);
- stageSpritesWindow.add(button);
-
- var substate = new StageSpriteEditScreen(button, "layouts/stage/solidEditScreen");
- substate.newSprite = true;
- openSubState(substate);
- }
-
function _character_new(_) {
var node:Access = new Access(Xml.createElement("char"));
stage.stageXML.x.addChild(node.x);
@@ -647,7 +623,6 @@ class StageEditor extends UIState {
saveToXml(xml, "folder", stage.spritesParentFolder);
saveToXml(xml, "startCamPosX", stage.startCam.x, 0);
saveToXml(xml, "startCamPosY", stage.startCam.y, 0);
- xml.attributeOrder = ["name", "folder", "zoom", "startCamPosX", "startCamPosY"];
for(prop in stage.extra.keys())
if(!Stage.DEFAULT_ATTRIBUTES.contains(prop) && !prop.startsWith("stageEditor."))
@@ -656,19 +631,93 @@ class StageEditor extends UIState {
var group:Xml = null;
var curGroup:String = null;
- for(button in stageSpritesWindow.buttons.members) {
- button.cleanupXML();
+ for(sprite in getSprites()) {
+ var button:StageElementButton = sprite.extra.get(exID("button"));
+ var newNode:Xml = null;
var sprite:FunkinSprite = button.getSprite();
- var newNode:Xml = button.xml.x;
+ if(button is StageSolidButton) {
+ var button:StageSolidButton = cast button;
+ var node:Access = cast sprite.extra.get(exID("node"));
+ Logs.trace("SOLID / BOX isnt implemented yet!");
+ } else if(button is StageSpriteButton) {
+ var button:StageSpriteButton = cast button;
+ var node:Access = cast sprite.extra.get(exID("node"));
+ var spriteXML = Xml.createElement("sprite");
+ saveToXml(spriteXML, "name", sprite.name);
+ saveToXml(spriteXML, "x", sprite.x, 0);
+ saveToXml(spriteXML, "y", sprite.y, 0);
+ saveToXml(spriteXML, "sprite", sprite.extra.get(exID("imageFile")));
+ savePointToXml(spriteXML, "scale", sprite.scale, 1);
+ savePointToXml(spriteXML, "scroll", sprite.scrollFactor, 1);
+ saveToXml(spriteXML, "skewx", sprite.skew.x, 0);
+ saveToXml(spriteXML, "skewy", sprite.skew.y, 0);
+ saveToXml(spriteXML, "alpha", sprite.alpha, 1);
+ saveToXml(spriteXML, "angle", sprite.angle, 0);
+ //saveToXml(spriteXML, "graphicSize", sprite.width, sprite.width);
+ //saveToXml(spriteXML, "graphicSizex", sprite.height, sprite.height);
+ //saveToXml(spriteXML, "graphicSizey", sprite.height, sprite.height);
+ saveToXml(spriteXML, "zoomfactor", sprite.zoomFactor, 1);
+ saveToXml(spriteXML, "updateHitbox", getBoolOfNode(node, "updateHitbox"), false);
+ saveToXml(spriteXML, "antialiasing", sprite.antialiasing, true);
+ //saveToXml(spriteXML, "width", sprite.width);
+ //saveToXml(spriteXML, "height", sprite.height);
+ saveToXml(spriteXML, "playOnCountdown", getBoolOfNode(node, "playOnCountdown"), false);
+ saveToXml(spriteXML, "interval", node.getAtt("beatInterval"), 2);
+ saveToXml(spriteXML, "interval", node.getAtt("interval"), 2);
+ saveToXml(spriteXML, "beatOffset", node.getAtt("beatOffset"), 0);
+ if(sprite.spriteAnimType != LOOP)
+ spriteXML.set("type", sprite.spriteAnimType.toString());
+ saveToXml(spriteXML, "color", sprite.color.toWebString(), "#FFFFFF");
+ // TODO: save custom parameters
+ //saveToXml(spriteXML, "flipX", sprite.flipX, false);
+ if (node.hasNode.anim) for (animNode in node.nodes.anim)
+ spriteXML.addChild(animNode.x);
+ newNode = spriteXML;
+ } else if(button is StageCharacterButton) {
+ var button:StageCharacterButton = cast button;
+ var char:Character = button.char;
+ var node:Access = cast char.extra.get(exID("node"));
+ var defaultPos = Stage.getDefaultPos(char.name.replace("NO_DELETE_", ""));
+ var charXML:Xml = Xml.createElement(node.name);
+ if(!char.name.startsWith("NO_DELETE_"))
+ saveToXml(charXML, "name", char.name);
+ saveToXml(charXML, "x", char.x, defaultPos.x);
+ saveToXml(charXML, "y", char.y, defaultPos.y);
+ saveToXml(charXML, "camxoffset", char.extra.get(exID("camX")), 0);
+ saveToXml(charXML, "camyoffset", char.extra.get(exID("camY")), 0);
+ saveToXml(charXML, "skewx", char.skew.x, 0);
+ saveToXml(charXML, "skewy", char.skew.y, 0);
+ saveToXml(charXML, "spacingx", char.extra.get(exID("spacingX")), 20);
+ saveToXml(charXML, "spacingy", char.extra.get(exID("spacingY")), 0);
+ saveToXml(charXML, "alpha", char.alpha / 0.75, 1);
+ saveToXml(charXML, "angle", char.angle, 0);
+ saveToXml(charXML, "zoomfactor", char.zoomFactor, 1);
+ saveToXml(charXML, "flipX", char.isPlayer, defaultPos.flip);
+ savePointToXml(charXML, "scroll", char.scrollFactor, defaultPos.scroll);
+ savePointToXml(charXML, "scale", char.scale.scaleNew(button.charScale), 1);
+ // TODO: save custom parameters
+ newNode = charXML;
+ } else if(button is StageUnknownButton) {
+ var button:StageUnknownButton = cast button;
+ newNode = button.xml.x;
+ }
+ else {
+ Logs.trace("Unknown Stage Type : " + Type.getClassName(Type.getClass(button)));
+ Logs.trace("> Sprite : " + Type.getClassName(Type.getClass(sprite)));
+ }
if(newNode != null && sprite != null) {
- var groupName = (sprite == null ? null :
- (
- (sprite.extra.get(exID("lowMemory")) == true) ? "low-memory" :
- (sprite.extra.get(exID("highMemory")) == true) ? "high-memory" : null
- )
- );
+ var isLowMemory = sprite.extra.get(exID("lowMemory")) == true;
+ var isHighMemory = sprite.extra.get(exID("highMemory")) == true;
+ /* // Only if this compiled :sob:
+ var groupName:String = null;
+ if ((groupName = isLowMemory ? "low-memory" : isHighMemory ? "high-memory" : null) != null) {
+ var a = group != null && groupName != curGroup && ((group = cast xml.addChild(group)) != null);
+ (group = (group == null ? Xml.createElement(curGroup = groupName) : group)).addChild(newNode);
+ }else xml.addChild(newNode);
+ */
+ var groupName = isLowMemory ? "low-memory" : isHighMemory ? "high-memory" : null;
if(group != null && groupName != curGroup) {
xml.addChild(group);
group = null;
@@ -1141,9 +1190,7 @@ class StageEditor extends UIState {
}
mouseMode = (FlxG.mouse.justReleased) ? NONE : mouseMode;
- if (prevMode != mouseMode)
- call("mouseModeChanged", [sprite]);
-
+
if (prevMode == NONE && mouseMode == NONE) return;
if (prevMode != NONE && mouseMode == NONE) {
diff --git a/source/funkin/editors/stage/StageSpritesWindow.hx b/source/funkin/editors/stage/StageSpritesWindow.hx
index 63183a284f..25a930ea6f 100644
--- a/source/funkin/editors/stage/StageSpritesWindow.hx
+++ b/source/funkin/editors/stage/StageSpritesWindow.hx
@@ -34,7 +34,10 @@ class StageSpritesWindow extends UIButtonList {
},
{
label: translate("box"),
- onSelect: StageEditor.instance._solid_new,
+ onSelect: function(_) {
+ UIState.state.displayNotification(new UIBaseNotification(translate("warnings.not-implemented"), 2, BOTTOM_LEFT));
+ CoolUtil.playMenuSFX(WARNING, 0.45);
+ },
color: 0xFF00FF00,
icon: 2
},
diff --git a/source/funkin/editors/stage/elements/StageCharacterButton.hx b/source/funkin/editors/stage/elements/StageCharacterButton.hx
index a35563a180..afd13840e8 100644
--- a/source/funkin/editors/stage/elements/StageCharacterButton.hx
+++ b/source/funkin/editors/stage/elements/StageCharacterButton.hx
@@ -41,51 +41,6 @@ class StageCharacterButton extends StageElementButton {
return char;
}
- override public function getDefaults():Map {
- return [
- "flip" => char.name == "NO_DELETE_boyfriend",
- "flipX" => char.name == "NO_DELETE_boyfriend",
- "camxoffset" => 0,
- "camyoffset" => 0,
- "spacingx" => 0,
- "spacingy" => 0,
- "scale" => 1,
- "scroll" => 1,
- "zoomfactor" => 1,
- "alpha" => 1,
- "angle" => 0,
- "skewx" => 0,
- "skewy" => 0
- ];
- }
-
- override public function getPointAttributes():Array {
- return ["scale", "scroll"];
- }
-
- override public function getAttributeOrder():Array {
- return [
- "name",
- "x",
- "y",
- "camxoffset",
- "camyoffset",
- "scale",
- "scalex",
- "scaley",
- "scroll",
- "scrollx",
- "scrolly",
- "zoomfactor",
- "alpha",
- "angle",
- "skewx",
- "skewy",
- "flip",
- "flipX"
- ];
- }
-
public override function onSelect() {
StageEditor.instance.selectSprite(char);
}
diff --git a/source/funkin/editors/stage/elements/StageElementButton.hx b/source/funkin/editors/stage/elements/StageElementButton.hx
index 64ac592532..4ba56ec882 100644
--- a/source/funkin/editors/stage/elements/StageElementButton.hx
+++ b/source/funkin/editors/stage/elements/StageElementButton.hx
@@ -184,18 +184,6 @@ class StageElementButton extends UIButton {
return "UNKNOWN";
}
- public function getDefaults():Map {
- return [];
- }
-
- public function getPointAttributes():Array {
- return [];
- }
-
- public function getAttributeOrder():Array {
- return ["name"];
- }
-
public function onSelect() {
// TODO: implement
}
@@ -229,32 +217,4 @@ class StageElementButton extends UIButton {
pos.put();
return text;
}
-
- public function cleanupXML() {
- var defaults = getDefaults();
- var queuedPoints = [];
- xml.x.attributeOrder = getAttributeOrder();
- for (a in xml.x.attributes()) {
- var attribIsPoint:Bool = isPoint(a);
- var attrib = attribIsPoint ? a.substring(0, a.length - 1) : a;
-
- var def:Dynamic = defaults[attrib];
- def = def is Bool ? Std.string(def) : def;
-
- var value:Dynamic = def is Float ? Std.parseFloat(xml.x.get(attrib)) : xml.x.get(attrib);
- if (value == def || (def is Float && Math.abs(def - value) < 0.001)) {
- if (!attribIsPoint || queuedPoints.contains(attrib)) {
- xml.x.remove(a);
- if (attribIsPoint)
- xml.x.remove(attrib + (a.charCodeAt(a.length - 1) == 'x'.code ? 'y' : 'x'));
- } else
- queuedPoints.push(attrib);
- }
- }
- }
- public function isPoint(attrib:String):Bool {
- var lastChar = attrib.charCodeAt(attrib.length - 1);
- var points = getPointAttributes();
- return (lastChar == 'x'.code || lastChar == 'y'.code) && points.contains(attrib.substring(0, attrib.length - 1));
- }
}
\ No newline at end of file
diff --git a/source/funkin/editors/stage/elements/StageSolidButton.hx b/source/funkin/editors/stage/elements/StageSolidButton.hx
index db59566f8f..49ffd1925b 100644
--- a/source/funkin/editors/stage/elements/StageSolidButton.hx
+++ b/source/funkin/editors/stage/elements/StageSolidButton.hx
@@ -1,7 +1,5 @@
package funkin.editors.stage.elements;
-import funkin.editors.stage.elements.StageSpriteButton;
-import funkin.editors.stage.StageEditor;
import flixel.util.FlxColor;
import haxe.xml.Access;
@@ -14,10 +12,6 @@ class StageSolidButton extends StageSpriteButton {
}
public override function onEdit() {
- if(!FlxG.keys.pressed.SHIFT) {
- FlxG.state.openSubState(new StageSpriteEditScreen(this, "layouts/stage/solidEditScreen"));
- } else {
- FlxG.state.openSubState(new StageXMLEditScreen(this.xml, updateInfo, "Solid"));
- }
+ // TODO: implement
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/stage/elements/StageSpriteButton.hx b/source/funkin/editors/stage/elements/StageSpriteButton.hx
index 35ddaec06a..81b609c21c 100644
--- a/source/funkin/editors/stage/elements/StageSpriteButton.hx
+++ b/source/funkin/editors/stage/elements/StageSpriteButton.hx
@@ -32,64 +32,6 @@ class StageSpriteButton extends StageElementButton {
return sprite;
}
- override public function getDefaults():Map {
- return [
- "scale" => 1,
- "scroll" => 1,
- "zoomfactor" => 1,
- "updateHitbox" => false,
- "antialiasing" => true,
- "alpha" => 1,
- "angle" => 0,
- "skew" => 0,
- "type" => "loop",
- "beatOffset" => 0,
- "beatInterval" => 2,
- "interval" => 2,
- "flipX" => false,
- "flipY" => false
- ];
- }
-
- override public function getPointAttributes():Array {
- return ["scale", "scroll", "skew"];
- }
-
- override public function getAttributeOrder():Array {
- return [
- "name",
- "sprite",
- "x",
- "y",
- "scale",
- "scalex",
- "scaley",
- "updateHitbox",
- "scroll",
- "scrollx",
- "scrolly",
- "zoomfactor",
- "antialiasing",
- "alpha",
- "blend",
- "angle",
- "skewx",
- "skewy",
- "color",
- "flipX",
- "flipY",
- "width",
- "height",
- "graphicSize",
- "graphicSizex",
- "graphicSizey",
- "beatInterval",
- "interval",
- "beatOffset",
- "playOnCountdown"
- ];
- }
-
public override function onSelect() {
StageEditor.instance.selectSprite(sprite);
}
@@ -137,10 +79,10 @@ class StageSpriteEditScreen extends UISoftcodedWindow {
inline function translate(id:String, ?args:Array)
return TU.translate("stageElementEditScreen." + id, args);
- public function new(button:StageSpriteButton, ?windowPath:String = "layouts/stage/spriteEditScreen") {
+ public function new(button:StageSpriteButton) {
this.button = button;
this.sprite = button.getSprite();
- super(windowPath, [
+ super("layouts/stage/spriteEditScreen", [
"stage" => StageEditor.instance.stage,
"sprite" => sprite,
"button" => button,
diff --git a/source/funkin/editors/ui/UINumericStepper.hx b/source/funkin/editors/ui/UINumericStepper.hx
index 73e12126f5..ff640414c9 100644
--- a/source/funkin/editors/ui/UINumericStepper.hx
+++ b/source/funkin/editors/ui/UINumericStepper.hx
@@ -37,9 +37,7 @@ class UINumericStepper extends UITextBox {
} else if (max != null) {
v = Math.min(v, max);
}
- // charter leak fix
- final targetText = Std.string(FlxMath.roundDecimal(v, precision));
- if (label.text != targetText) label.text = targetText;
+ label.text = Std.string(FlxMath.roundDecimal(v, precision));
return value = v;
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/ui/UISoftcodedWindow.hx b/source/funkin/editors/ui/UISoftcodedWindow.hx
index 873ebeb4d2..4b4f9e6585 100644
--- a/source/funkin/editors/ui/UISoftcodedWindow.hx
+++ b/source/funkin/editors/ui/UISoftcodedWindow.hx
@@ -232,13 +232,6 @@ class UISoftcodedWindow extends UISubstateWindow {
add(dropdown);
addLabelOn(dropdown, label);
dropdown;
- case "colorwheel":
- var color = CoolUtil.getColorFromDynamic(execAtt(el, "color", 0xFFFFFFFF));
-
- var wheel = new UIColorwheel(x, y, color);
- add(wheel);
- addLabelOn(wheel, label);
- wheel;
case "buttonlist":
//x:Float, y:Float, width:Int, height:Int, windowName:String, buttonSize:FlxPoint, ?buttonOffset:FlxPoint, ?buttonSpacing:Float
var buttonlist = new UIButtonList(
@@ -269,7 +262,9 @@ class UISoftcodedWindow extends UISubstateWindow {
addLabelOn(buttonlist, label);
set("buttonlist", old);
buttonlist;
+ // UIColorwheel
// UIAudioPlayer
+ // UIButtonList
// UIFileExplorer
// UIAutoCompleteTextbox
default: {
diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx
index 6f203a7415..5d4d8740a6 100644
--- a/source/funkin/game/PlayState.hx
+++ b/source/funkin/game/PlayState.hx
@@ -24,6 +24,7 @@ import funkin.backend.scripting.events.gameplay.*;
import funkin.backend.scripting.events.note.*;
import funkin.backend.system.Conductor;
import funkin.backend.system.RotatingSpriteGroup;
+import funkin.backend.system.gamejolt.*;
import funkin.editors.SaveWarning;
import funkin.editors.charter.Charter;
import funkin.editors.charter.CharterSelection;
@@ -935,10 +936,8 @@ class PlayState extends MusicBeatState
FlxG.sound.load(Paths.sound(s));
if (chartingMode) {
- if (Flags.CHANGE_WINDOW_TITLE_PLAYSTATE) {
- WindowUtils.prefix = Charter.undos.unsaved ? Flags.UNDO_PREFIX : "";
- WindowUtils.suffix = TU.translate("playtesting.chartPlaytesting");
- }
+ WindowUtils.prefix = Charter.undos.unsaved ? Flags.UNDO_PREFIX : "";
+ WindowUtils.suffix = TU.translate("playtesting.chartPlaytesting");
SaveWarning.showWarning = Charter.undos.unsaved;
SaveWarning.selectionClass = CharterSelection;
@@ -1132,7 +1131,7 @@ class PlayState extends MusicBeatState
super.destroy();
- if (Flags.CHANGE_WINDOW_TITLE_PLAYSTATE) WindowUtils.resetAffixes();
+ WindowUtils.resetAffixes();
SaveWarning.reset();
instance = null;
@@ -1418,8 +1417,13 @@ class PlayState extends MusicBeatState
var beat = Conductor.getBeats(camZoomingEvery, camZoomingInterval, camZoomingOffset);
if (camZoomingLastBeat != beat) {
camZoomingLastBeat = beat;
-
- doBopZoom();
+ if (useCamZoomMult) {
+ if (camZoomingMult < maxCamZoomMult) camZoomingMult += camZoomingStrength;
+ }
+ else if (FlxG.camera.zoom < maxCamZoom) {
+ FlxG.camera.zoom += camGameZoomMult * camZoomingStrength;
+ camHUD.zoom += camHUDZoomMult * camZoomingStrength;
+ }
}
}
@@ -1499,29 +1503,6 @@ class PlayState extends MusicBeatState
scripts.event("postDraw", e);
}
- public function doBopZoom()
- {
- var event:BopZoomEvent = EventManager.get(BopZoomEvent).recycle(useCamZoomMult, maxCamZoomMult, camZoomingStrength);
- gameAndCharsEvent("onBopZoom", event);
-
- if (event.cancelled)
- {
- gameAndCharsEvent("onPostBopZoom", event);
- return;
- }
-
- if (event.useZoomMultiplier) {
- if (camZoomingMult < event.maxZoomMultiplier)
- camZoomingMult += event.zoomStrength;
- }
- else if (FlxG.camera.zoom < maxCamZoom) {
- FlxG.camera.zoom += camGameZoomMult * event.zoomStrength;
- camHUD.zoom += camHUDZoomMult * event.zoomStrength;
- }
-
- gameAndCharsEvent("onPostBopZoom", event);
- }
-
public function moveCamera() if (strumLines.members[curCameraTarget] != null) {
var data:CamPosData = getStrumlineCamPos(curCameraTarget);
data.pos.add(cameraFocusOffset.x, cameraFocusOffset.y);
@@ -1774,6 +1755,8 @@ class PlayState extends MusicBeatState
deathCounter++;
+ GameJoltSecurity.unlockDefinedTrophy('death-first');
+
openSubState(new GameOverSubstate(event.x, event.y, event.deathCharID, event.isPlayer, event.gameOverSong, event.lossSFX, event.retrySFX));
gameAndCharsEvent("onPostGameOver", event);
diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx
index 7bb81c284e..2868ed2e10 100644
--- a/source/funkin/game/Stage.hx
+++ b/source/funkin/game/Stage.hx
@@ -119,14 +119,6 @@ class Stage extends FlxBasic implements IBeatReceiver {
elems = onXMLLoaded(xml, elems);
}
- var curRemoved:Map = [];
- inline function tempRemove(xml:Xml, att:String) {
- if (xml.exists(att)) {
- curRemoved.set(att, xml.get(att));
- xml.remove(att);
- }
- }
-
for(node in elems) {
var sprite:Dynamic = switch(node.name) {
case "sprite" | "spr" | "sparrow":
@@ -148,15 +140,10 @@ class Stage extends FlxBasic implements IBeatReceiver {
(node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : -1
);
- if (isSolid) tempRemove(node.x, "updateHitbox");
- for (a in ["width", "height", "color"]) tempRemove(node.x, a);
+ if (isSolid) node.x.remove("updateHitbox");
+ for (a in ["width", "height", "color"]) node.x.remove(a);
XMLUtil.loadSpriteFromXML(spr, node, "", NONE, false);
- // mainly for the stage editor
- for (k => v in curRemoved)
- node.x.set(k, v);
- curRemoved.clear();
-
stageSprites.set(spr.name, spr);
addSprite(spr);
case "boyfriend" | "bf" | "player":
@@ -412,7 +399,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
for (path in Paths.getFolderContent(folder, false, mods ? MODS : BOTH)) {
var extension = Path.extension(path);
if (extensions.contains(extension)) {
- // list.pushOnce("test");
+ list.pushOnce("test");
list.pushOnce(Path.withoutExtension(path));
}
}
diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx
index aa4b314ffb..4feb1e9221 100644
--- a/source/funkin/menus/FreeplayState.hx
+++ b/source/funkin/menus/FreeplayState.hx
@@ -210,16 +210,6 @@ class FreeplayState extends MusicBeatState
changeDiff((controls.LEFT_P ? -1 : 0) + (controls.RIGHT_P ? 1 : 0));
changeCoopMode((controls.CHANGE_MODE ? 1 : 0)); // TODO: make this configurable
// putting it before so that its actually smooth
-
- if (FlxG.mouse.justPressed && grpSongs != null) {
- for (index => sprite in grpSongs.members) {
- if (curSelected != index && FlxG.mouse.overlaps(sprite)) {
- changeSelection(index - curSelected);
- break;
- }
- }
- }
-
updateOptionsAlpha();
}
@@ -249,7 +239,6 @@ class FreeplayState extends MusicBeatState
&& FlxG.mouse.overlaps(grpSongs.members[curSelected]))))
{
select();
- }
}
var __opponentMode:Bool = false;
diff --git a/source/funkin/menus/MainMenuState.hx b/source/funkin/menus/MainMenuState.hx
index c876bbd0dc..0690c039d7 100644
--- a/source/funkin/menus/MainMenuState.hx
+++ b/source/funkin/menus/MainMenuState.hx
@@ -7,6 +7,7 @@ import funkin.backend.FunkinText;
import funkin.backend.scripting.events.menu.MenuChangeEvent;
import funkin.backend.scripting.events.NameEvent;
import funkin.menus.credits.CreditsMain;
+import funkin.menus.gamejolt.GameJoltMenu;
import funkin.options.OptionsMenu;
import lime.app.Application;
@@ -62,16 +63,33 @@ class MainMenuState extends MusicBeatState
for (i=>option in optionShit)
{
- var menuItem:FlxSprite = new FlxSprite(0, 60 + (i * 160));
- menuItem.frames = Paths.getFrames('menus/mainmenu/${option}');
- menuItem.animation.addByPrefix('idle', option + " basic", 24);
- menuItem.animation.addByPrefix('selected', option + " white", 24);
- menuItem.animation.play('idle');
- menuItem.ID = i;
- menuItem.screenCenter(X);
- menuItems.add(menuItem);
- menuItem.scrollFactor.set();
- menuItem.antialiasing = true;
+ if (option == 'gamejolt') {
+ #if GAMEJOLT_API
+ var menuItem:FlxSprite = new FlxSprite(FlxG.width - 138, FlxG.height - 138).loadGraphic(Paths.image('menus/gamejolt-icon'));
+ menuItem.setGraphicSize(128);
+ menuItem.updateHitbox();
+ menuItem.animation.add('idle', [0], 1, false);
+ menuItem.animation.add('selected', [0], 1, false);
+ menuItem.animation.play('idle');
+ menuItem.ID = i;
+ menuItems.add(menuItem);
+ menuItem.scrollFactor.set();
+ menuItem.antialiasing = true;
+ #else
+ continue;
+ #end
+ } else {
+ var menuItem:FlxSprite = new FlxSprite(0, 60 + (i * 160));
+ menuItem.frames = Paths.getFrames('menus/mainmenu/${option}');
+ menuItem.animation.addByPrefix('idle', option + " basic", 24);
+ menuItem.animation.addByPrefix('selected', option + " white", 24);
+ menuItem.animation.play('idle');
+ menuItem.ID = i;
+ menuItem.screenCenter(X);
+ menuItems.add(menuItem);
+ menuItem.scrollFactor.set();
+ menuItem.antialiasing = true;
+ }
}
FlxG.camera.follow(camFollow, null, 0.06);
@@ -143,7 +161,7 @@ class MainMenuState extends MusicBeatState
FlxG.switchState(new TitleState());
#if MOD_SUPPORT
- if (controls.SWITCHMOD || (FlxG.mouse.justPressed && versionText != null && FlxG.mouse.overlaps(versionText))) {
+ if (controls.SWITCHMOD) {
openSubState(new ModSwitchMenu());
persistentUpdate = false;
persistentDraw = true;
@@ -152,26 +170,15 @@ class MainMenuState extends MusicBeatState
if (controls.ACCEPT)
selectItem();
-
- if (FlxG.mouse.justPressed && menuItems != null) {
- for (index => sprite in menuItems.members) {
- if (FlxG.mouse.overlaps(sprite)) {
- if (curSelected != index) changeItem(index - curSelected);
- else selectItem();
- break;
- }
- }
- }
}
super.update(elapsed);
- if (forceCenterX) {
- menuItems.forEach(function(spr:FlxSprite)
- {
- spr.screenCenter(X);
- });
- }
+ if (forceCenterX)
+ menuItems.forEach(function(spr:FlxSprite)
+ {
+ spr.screenCenter(X);
+ });
}
public override function switchTo(nextState:FlxState):Bool {
@@ -201,6 +208,7 @@ class MainMenuState extends MusicBeatState
case 'freeplay': FlxG.switchState(new FreeplayState());
case 'donate', 'credits': FlxG.switchState(new CreditsMain()); // kept donate for not breaking scripts, if you don't want donate to bring you to the credits menu, thats easy softcodable - Nex
case 'options': FlxG.switchState(new OptionsMenu());
+ case 'gamejolt': FlxG.switchState(new GameJoltMenu());
}
});
}
diff --git a/source/funkin/menus/StoryMenuState.hx b/source/funkin/menus/StoryMenuState.hx
index 95d98db414..1a4c9de31d 100644
--- a/source/funkin/menus/StoryMenuState.hx
+++ b/source/funkin/menus/StoryMenuState.hx
@@ -146,34 +146,16 @@ class StoryMenuState extends MusicBeatState {
if (leftArrow != null && leftArrow.exists) leftArrow.animation.play(controls.LEFT ? 'press' : 'idle');
if (rightArrow != null && rightArrow.exists) rightArrow.animation.play(controls.RIGHT ? 'press' : 'idle');
- if (controls.BACK || FlxG.mouse.justPressedRight) {
+ if (controls.BACK) {
goBack();
}
changeDifficulty((controls.LEFT_P ? -1 : 0) + (controls.RIGHT_P ? 1 : 0));
changeWeek((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0) - FlxG.mouse.wheel);
- if (FlxG.mouse.justPressed) {
- if (leftArrow != null && leftArrow.exists && FlxG.mouse.overlaps(leftArrow)) {
- leftArrow.animation.play('press');
- changeDifficulty(-1);
- }
- else if (rightArrow != null && rightArrow.exists && FlxG.mouse.overlaps(rightArrow)) {
- rightArrow.animation.play('press');
- changeDifficulty(1);
- }
- else if (weekSprites?.members[curWeek] != null && FlxG.mouse.overlaps(weekSprites.members[curWeek])) {
- selectWeek();
- }
- else if (weekSprites?.members[curWeek + 1] != null && FlxG.mouse.overlaps(weekSprites.members[curWeek + 1])) {
- changeWeek(1);
- }
- }
-
if (controls.ACCEPT)
selectWeek();
- }
- else {
+ } else {
for(e in [leftArrow, rightArrow])
if (e != null && e.exists)
e.animation.play('idle');
diff --git a/source/funkin/menus/gamejolt/GameJoltCompleteScreen.hx b/source/funkin/menus/gamejolt/GameJoltCompleteScreen.hx
new file mode 100644
index 0000000000..f00a906f95
--- /dev/null
+++ b/source/funkin/menus/gamejolt/GameJoltCompleteScreen.hx
@@ -0,0 +1,67 @@
+package funkin.menus.gamejolt;
+
+import funkin.editors.ui.*;
+import openfl.desktop.Clipboard;
+import flixel.addons.display.FlxBackdrop;
+import funkin.backend.system.gamejolt.*;
+import funkin.backend.utils.GJUtil;
+import funkin.menus.TitleState;
+
+class GameJoltCompleteScreen extends UIState
+{
+ var bg:FlxBackdrop;
+ var mainText:UIText;
+ var copyText:UIText;
+ var copyButton:UIButton;
+ var continueText:UIText;
+
+ override public function create()
+ {
+ super.create();
+
+ add(bg = new FlxBackdrop());
+ bg.loadGraphic(Paths.image('editors/bgs/default'));
+ bg.antialiasing = true;
+ bg.rotation = -5;
+ bg.velocity.set(85, 0).degrees = bg.rotation;
+
+ add(mainText = new UIText(0, 80, FlxG.width,
+ 'Hey there funkhead!
+ Your GameJolt data was recognized and registered to your game\'s data store successfully.
+ From here on, only ${GameJoltData.ownerUsername} will be able to change the global data. As for the XML - for security purposes, we took out the login info and game token. Feel free to discard the XML entirely, or keep it as a souvenir - your globals load from your game\'s data store now!
+ Before you go: it\'s important to press the "Copy" button below and copy the following text into your mod\'s modpack.ini file. This is your game\'s security key encrypted uniquely for this build. Paste it in the "Common" section.',
+ 30));
+
+ mainText.alignment = CENTER;
+ mainText.antialiasing = true;
+
+ add(copyText = new UIText(0, mainText.height + 120, FlxG.width, 'GAMEJOLT_ENCRYPTED_TOKEN=\'${Flags.MOD_GAMEJOLT_ENCRYPTED_TOKEN}\'', 16));
+ copyText.alignment = CENTER;
+ copyText.antialiasing = true;
+
+ add(copyButton = new UIButton(0, copyText.y + copyText.height + 20, "Copy", () -> {
+ Clipboard.generalClipboard.setData(TEXT_FORMAT, copyText.text);
+ }));
+ copyButton.color = 0xFF3F3FFF;
+ copyButton.x = (FlxG.width / 2) - (copyButton.bWidth / 2);
+
+ add(continueText = new UIText(0, copyButton.y + copyButton.bHeight + 30, FlxG.width, '~ Press ${controls.getKeyName(ACCEPT)} to continue ~'));
+ continueText.alignment = CENTER;
+
+ CoolUtil.playMusic(Paths.music('breakfast'));
+ }
+
+ override public function update(elapsed:Float)
+ {
+ super.update(elapsed);
+ if (controls.ACCEPT) {
+ CoolUtil.playMenuSFX(CONFIRM);
+ if (FlxG.save.data.gameJoltArray != null) {
+ var gjDat:Array = FlxG.save.data.gameJoltArray;
+ GJUtil.attemptLogin(gjDat[0], gjDat[1]);
+ }
+ Main.goToGJConfirm = false;
+ FlxG.switchState(new TitleState());
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/menus/gamejolt/GameJoltConfirmationWindow.hx b/source/funkin/menus/gamejolt/GameJoltConfirmationWindow.hx
new file mode 100644
index 0000000000..3fbacf8810
--- /dev/null
+++ b/source/funkin/menus/gamejolt/GameJoltConfirmationWindow.hx
@@ -0,0 +1,53 @@
+package funkin.menus.gamejolt;
+
+import funkin.editors.ui.*;
+
+class GameJoltConfirmationWindow extends UISubstateWindow
+{
+ public var actionType:String;
+ public var description:String;
+ public var callback:NullVoid>;
+ public var closeOnConfirm:Bool;
+ public var yesText:String;
+ public var noText:String;
+
+ public var yesButton:UIButton;
+ public var noButton:UIButton;
+
+ override public function new(actionType:String = 'Logout', description:String = 'No description provided.', ?callback:Void->Void, closeOnConfirm:Bool = true, ?yesText:String, ?noText:String)
+ {
+ super();
+ this.actionType = actionType;
+ this.description = description;
+ this.callback = callback;
+ this.closeOnConfirm = closeOnConfirm;
+ this.yesText = yesText != null ? yesText : TU.translate('editor.yes');
+ this.noText = noText != null ? noText : TU.translate('editor.cancel');
+ }
+
+ override public function create()
+ {
+ winWidth = 360;
+
+ winTitle = 'Confirm $actionType';
+
+ super.create();
+
+ add(messageSpr = new UIText(windowSpr.x + 20, windowSpr.y + 46, windowSpr.bWidth - 40, description));
+ messageSpr.alignment = CENTER;
+
+ windowSpr.resize(winWidth, Std.int(messageSpr.y + messageSpr.height + 68));
+
+ add(yesButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) - 130, windowSpr.y + windowSpr.bHeight - 48, yesText, confirmAndClose, 125));
+ add(noButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) + 5, windowSpr.y + windowSpr.bHeight - 48, noText, close, 125));
+ noButton.color = 0xFFFF0000;
+ }
+
+ function confirmAndClose()
+ {
+ if (callback != null)
+ callback();
+ if (closeOnConfirm)
+ close();
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/menus/gamejolt/GameJoltDataWindow.hx b/source/funkin/menus/gamejolt/GameJoltDataWindow.hx
new file mode 100644
index 0000000000..78262feb75
--- /dev/null
+++ b/source/funkin/menus/gamejolt/GameJoltDataWindow.hx
@@ -0,0 +1,119 @@
+package funkin.menus.gamejolt;
+
+import funkin.backend.system.gamejolt.*;
+import funkin.editors.ui.*;
+import funkin.menus.gamejolt.*;
+import funkin.menus.MainMenuState;
+
+enum abstract DataWindowType(String) {
+ var USER = 'User';
+ var GLOBAL = 'Global';
+}
+
+class GameJoltDataWindow extends UISubstateWindow {
+ var loadButton:UIButton;
+ var saveButton:UIButton;
+ var closeButton:UIButton;
+ var resetButton:UIButton;
+
+ var type:DataWindowType;
+
+ var daX:Float;
+
+ override public function new(type:DataWindowType)
+ {
+ super();
+ // Prevent people from manipulating global data
+ this.type = ((GameJoltSecurity.userId == GameJoltData.ownerUserId && GameJoltSecurity.userId != null) ? type : USER);
+ }
+
+ override public function create()
+ {
+ winTitle = '${type} Data Actions';
+
+ winWidth = 756;
+
+ super.create();
+
+ daX = windowSpr.x + 20;
+
+ // Resize window height automatically with height of unused messageSpr.
+ add(messageSpr = new UIText(daX, windowSpr.y + 46, windowSpr.bWidth - ((daX - windowSpr.x) * 2), type == USER ? "User data includes base Codename Engine options (including base controls), current scores, and any other items defined by this mod's developers." : "Remember that gamejolt.xml file in data/global you used to set up the global items like leaderboards and trophy info? You can retrieve that here or overwrite the current globals here."));
+ messageSpr.alignment = CENTER;
+ windowSpr.resize(winWidth, Std.int(messageSpr.y + messageSpr.height + 68));
+
+ // Load button for loading user/global data.
+ add(loadButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) - 265, windowSpr.y + windowSpr.bHeight - 48, "Load", () -> {
+ if (type == USER)
+ openSubState(new GameJoltConfirmationWindow('User Data Load', 'This will load, and overwrite, any user-specific data saved in this mod\'s data store to the local save.\nAre you sure you want to do this?', () -> {
+ GameJoltData.loadUserData((bl) -> {
+ if (bl)
+ openSubState(new GameJoltInfoWindow('User Data Load Success', 'User data loaded successfully.', close));
+ else
+ openSubState(new GameJoltInfoWindow('User Data Load Error', 'Unable to load user data. Please check console for more information.', close));
+ });
+ }, false));
+ else
+ openSubState(new GameJoltConfirmationWindow('Global Data Load', 'This will load all global data saved in this mod\'s data store. It will then write it to this mod\'s data/config/gamejolt.xml\nAre you sure you want to do this?', () -> {
+ GameJoltData.loadGlobalData((bl) -> {
+ if (bl)
+ openSubState(new GameJoltInfoWindow('Global Data Load Success', 'Global data loaded successfully into data/config/gamejolt.xml.', close));
+ else
+ openSubState(new GameJoltInfoWindow('Global Data Load Error', 'Unable to load global data. Please check console for more information.', close));
+ });
+ }, false));
+ }, 125));
+
+ // Save button for writing user/global data.
+ add(saveButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) - 130, windowSpr.y + windowSpr.bHeight - 48, TU.translate("editor.save"), () -> {
+ if (type == USER)
+ openSubState(new GameJoltConfirmationWindow('User Data Save', 'This will save any user-specific data saved in this mod\'s data store. If a save exists for this user in the data store, it will overwrite that save.\nAre you sure you want to do this?', () -> {
+ GameJoltData.setUserData((bl) -> {
+ if (bl)
+ openSubState(new GameJoltInfoWindow('User Data Save Success', 'User data saved successfully.', close));
+ else
+ openSubState(new GameJoltInfoWindow('User Data Save Error', 'Unable to save user data. Please check console for more information.', close));
+ });
+ }, false));
+ else
+ openSubState(new GameJoltConfirmationWindow('Global Data Save', 'This will transfer all global data in data/config/gamejolt.xml to this mod\'s data store. It will overwrite any previous values.\nAre you sure you want to do this?', () -> {
+ GameJoltData.setGlobalData(false, (bl) -> {
+ if (bl)
+ openSubState(new GameJoltInfoWindow('Global Data Save Success', 'Global data saved successfully.', close));
+ else
+ openSubState(new GameJoltInfoWindow('Global Data Save Error', 'Unable to save global data. Please check console for more information.', close));
+ });
+ }, false));
+ }, 125));
+
+ // Reset button for removing user/global data.
+ add(resetButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) + 5, saveButton.y, "Remove " + (type == USER ? "User Data" : "GJ Integration"), () -> {
+ if (type == USER)
+ openSubState(new GameJoltConfirmationWindow('Remove User Data', '!! WARNING !!\nThis will remove ALL user data from this mod\'s data store. This is an irreversible action.\nARE YOU SURE YOU WANT TO DO THIS?', () -> {
+ GameJoltData.wipeUserData((bl) -> {
+ if (bl)
+ openSubState(new GameJoltInfoWindow('User Data Wipe Success', 'User data wiped successfully.', close));
+ else
+ openSubState(new GameJoltInfoWindow('User Data Wipe Error', 'Error wiping user data.', close));
+ });
+ }, false));
+ else
+ openSubState(new GameJoltConfirmationWindow('Remove GameJolt Integration', '!! WARNING !!\nThis will remove ALL global data from your game\'s data store. You will not be able to use GameJolt integrations for this mod until you open the mod with a properly configured gamejolt.xml file in data/config.\nARE YOU SURE YOU WANT TO DO THIS?', () -> {
+ GameJoltData.wipeGlobalData((bl) -> {
+ if (bl)
+ openSubState(new GameJoltInfoWindow('Global Data Wipe Success', 'Global data wiped successfully. This mod no longer has GameJolt integrations. Now redirecting to the Main Menu.', () -> {
+ FlxG.state.closeSubState();
+ FlxG.switchState(new MainMenuState());
+ }));
+ else
+ openSubState(new GameJoltInfoWindow('Global Data Wipe Error', 'Error wiping global data.', close));
+ });
+ }, false));
+ }, 125));
+ resetButton.color = 0xFFFF0000;
+
+ // The humble "close window" button.
+ add(closeButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) + 140, windowSpr.y + windowSpr.bHeight - 48, TU.translate("editor.close"), close, 125));
+ closeButton.color = 0xFFFF0000;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/menus/gamejolt/GameJoltInfoWindow.hx b/source/funkin/menus/gamejolt/GameJoltInfoWindow.hx
new file mode 100644
index 0000000000..38eef3f43c
--- /dev/null
+++ b/source/funkin/menus/gamejolt/GameJoltInfoWindow.hx
@@ -0,0 +1,48 @@
+package funkin.menus.gamejolt;
+
+import funkin.editors.ui.*;
+
+class GameJoltInfoWindow extends UISubstateWindow {
+ public var infoTitle:String;
+ public var description:String;
+ public var closeText:String;
+ public var callback:NullVoid>;
+
+ public var closeButton:UIButton;
+
+ public var daX:Float;
+
+ override public function new(infoTitle:String = 'General', description:String = "No description provided.", ?callback:Void->Void, ?closeText:String)
+ {
+ super();
+ this.infoTitle = infoTitle;
+ this.description = description;
+ this.closeText = closeText != null ? closeText : TU.translate('editor.ok');
+ this.callback = callback;
+ }
+
+ public override function create() {
+ //TODO: get translations for text
+ winTitle = 'Info - $infoTitle';
+
+ winWidth = 756;
+
+ super.create();
+
+ daX = windowSpr.x + 20;
+
+ add(messageSpr = new UIText(daX, windowSpr.y + 46, windowSpr.bWidth - ((daX - windowSpr.x) * 2), description));
+ messageSpr.alignment = CENTER;
+
+ windowSpr.resize(winWidth, Std.int(messageSpr.y + messageSpr.height + 68));
+
+ //add(new UISprite(daX, text.y + text.height + 10).loadGraphic(Paths.image()))
+ add(closeButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) - 62, windowSpr.y + windowSpr.bHeight - 48, TU.translate("editor.close"), close, 125));
+ }
+
+ public override function close()
+ {
+ if (callback != null) callback();
+ super.close();
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/menus/gamejolt/GameJoltLoginWindow.hx b/source/funkin/menus/gamejolt/GameJoltLoginWindow.hx
new file mode 100644
index 0000000000..f166c4103c
--- /dev/null
+++ b/source/funkin/menus/gamejolt/GameJoltLoginWindow.hx
@@ -0,0 +1,55 @@
+package funkin.menus.gamejolt;
+
+import funkin.editors.ui.*;
+import funkin.menus.gamejolt.*;
+
+class GameJoltLoginWindow extends UISubstateWindow {
+ public var usernameBox:UITextBox;
+ public var userTokenLabel:UIText;
+ public var userTokenInfo:UIButton;
+ public var userTokenBox:UITextBox;
+
+ public var closeButton:UIButton;
+ public var loginButton:UIButton;
+
+ public var daX:Float;
+
+ public override function create() {
+ //TODO: get translations for text
+ winTitle = "Login with GameJolt...";
+
+ winWidth = 360;
+ winHeight = 250;
+
+ super.create();
+
+ daX = windowSpr.x + 20;
+
+ add(usernameBox = new UITextBox(daX, windowSpr.y + 60, ""));
+ usernameBox.members.push(new UIText(daX, usernameBox.y - 24, 0, "Username"));
+
+ add(userTokenBox = new UITextBox(daX, usernameBox.y + usernameBox.height + 60, ""));
+ userTokenBox.members.push(userTokenLabel = new UIText(daX, userTokenBox.y - 24, 0, "User Token"));
+ userTokenBox.members.push(userTokenInfo = new UIButton(daX + userTokenLabel.width, userTokenLabel.y - 4, "?", () -> {
+ openSubState(new GameJoltInfoWindow('GameJolt User Token', "!! THIS IS NOT YOUR GAMEJOLT ACCOUNT PASSWORD !!\n\nTo access your game token, click on your profile icon, then click on \"Game Token\"."));
+ }, 24, 24));
+ userTokenInfo.color = 0xFF3737FF;
+ userTokenBox.label.textField.displayAsPassword = true;
+
+ add(loginButton = new UIButton(windowSpr.x + (windowSpr.bWidth / 2) + 20, windowSpr.y + windowSpr.bHeight - 48, "Login", function() {
+ GJUtil.attemptLogin(usernameBox.label.text, userTokenBox.label.text, (bl) -> {
+ openSubState(new GameJoltInfoWindow(bl ? "Login Success!" : "Login Error", bl ? 'You have successfully logged in as ${GJUtil.userName}!' : 'Could not log into GameJolt.', () -> {
+ if (bl) {
+ close();
+ FlxG.resetState();
+ }
+ }, bl ? 'Sweet!' : TU.translate('editor.ok')));
+ }, true);
+ }, 125));
+
+ add(closeButton = new UIButton(loginButton.x - loginButton.bWidth - 20, loginButton.y, TU.translate("editor.cancel"), function() {
+ close();
+ }, 125));
+ closeButton.color = 0xFFFF0000;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/menus/gamejolt/GameJoltMenu.hx b/source/funkin/menus/gamejolt/GameJoltMenu.hx
new file mode 100644
index 0000000000..82a3c7075f
--- /dev/null
+++ b/source/funkin/menus/gamejolt/GameJoltMenu.hx
@@ -0,0 +1,653 @@
+package funkin.menus.gamejolt;
+
+import funkin.backend.utils.GJUtil.GJResponse;
+import funkin.backend.utils.GJUtil.RequestType;
+import openfl.display.BitmapData;
+import lime.graphics.Image;
+import flixel.graphics.FlxGraphic;
+import flixel.addons.display.FlxBackdrop;
+import flixel.math.FlxRect;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
+import funkin.backend.system.gamejolt.*;
+import funkin.editors.ui.*;
+import funkin.menus.gamejolt.*;
+
+class GameJoltMenu extends UIState
+{
+ //region Variables
+ /**
+ * How many entries the API will get from any given leaderboard.
+ */
+ public static var leaderboardLimit:Int = 50;
+
+ /**
+ * The space, in pixels, between each achievement image.
+ */
+ public static var achRowSpacing:Int = 10;
+
+ /**
+ * The amount of achievements shown per row.
+ */
+ public static var achRowAmount:Int = 5;
+
+ /**
+ * How large, in pixels, the border of each achievement/leaderboard
+ * content box should be.
+ */
+ public static var boxContentBorderSize:Int = 5;
+
+ /**
+ * The scale of the achievement image when selected.
+ */
+ public static var achievementSelectScale:Float = 1.2;
+
+ /**
+ * The ease function for the previously selected and newly selected
+ * achievements.
+ */
+ public static var achEase:Float->Float = FlxEase.elasticOut;
+
+ /**
+ * How long, in seconds, the transition between the previously selected
+ * and newly selected achievements should last.
+ */
+ public static var achScaleTime:Float = 1.5;
+
+ /**
+ * Whether any hidden trophies should be included on the trophy list.
+ */
+ public static var displayHiddenTrophies:Bool = false;
+
+ // Main page variables
+ var bg:FlxBackdrop;
+ var userBorder:UISprite;
+ var userBackground:UISprite;
+ var mainTitleText:UIText;
+ var userPhoto:UISprite;
+ var subTitleText:UIText;
+ var trophyOrLoginButton:UIButton;
+ var leaderOrRegisterButton:UIButton;
+ var userDataButton:UIButton;
+ var ownerDataButton(default, null):UIButton;
+ var logoutButton:UIButton;
+
+ // Secondary page variables
+ var secondaryTitleText:UIText;
+ var subBoxBacking:UISprite;
+ var mainBoxBacking:UISprite;
+ var mainBoxScroll:UIScrollBar;
+ var subBoxAch:FlxTypedSpriteGroup;
+ var mainBoxAch:FlxTypedSpriteGroup;
+ var missingTextAch:Null = null;
+ var subBoxLead:FlxTypedSpriteGroup;
+ var mainBoxLead:FlxTypedSpriteGroup;
+ var missingTextLead:Null = null;
+ var trophyTitle:UIText;
+ var trophyDesc:UIText;
+ var noScoresLead:UIText;
+ var rankLead:UIText;
+
+ var onHome(default, set):Bool;
+ @:bypassAccessor var daInd(default, set):Int = 0;
+ var daMax:Int = 0;
+ var secondaryPage(default, set):String;
+ var isDaOwner(default, null):Bool = (GameJoltSecurity.userId == GameJoltData.ownerUserId && GameJoltSecurity.userId != null);
+ var scoresMap:Map = new Map();
+ var trophiesMap:Map = new Map();
+ var daSprites:FlxTypedSpriteGroup = new FlxTypedSpriteGroup();
+ var noScaleTween:Bool = false;
+ var inTween:FlxTween = null;
+ var outTween:FlxTween = null;
+ //endregion
+
+ override function create()
+ {
+ super.create();
+
+ add(bg = new FlxBackdrop());
+ bg.loadGraphic(Paths.image('editors/bgs/default'));
+ bg.antialiasing = true;
+ bg.rotation = -5;
+ bg.velocity.set(85, 0).degrees = bg.rotation;
+
+ add(daSprites);
+
+ userBorder = new UISprite();
+ userBorder.makeGraphic(264, 264, FlxColor.BLACK);
+ userBorder.screenCenter(X);
+ daSprites.add(userBorder);
+
+ userBackground = new UISprite();
+ userBackground.makeGraphic(256, 256, FlxColor.GRAY);
+ userBackground.screenCenter(X);
+ daSprites.add(userBackground);
+
+ mainTitleText = new UIText(0, 80, FlxG.width, (GJUtil.loggedIn ? "GAMEJOLT: " + GJUtil.userName : "NOT LOGGED IN"), 60);
+ mainTitleText.alignment = CENTER;
+ userBorder.y = mainTitleText.y + mainTitleText.height + 20;
+ userBackground.y = userBorder.y + 4;
+ daSprites.add(mainTitleText);
+
+ userPhoto = new UISprite(userBackground.x, userBackground.y);
+
+ if (GJUtil.loggedIn) {
+ //region Main Page
+ GJUtil.getAvatarImage(userPhoto, null, () -> {
+ userPhoto.setGraphicSize(256, 256);
+ userPhoto.updateHitbox();
+ userPhoto.screenCenter(X);
+ userPhoto.visible = true;
+ });
+ userPhoto.screenCenter(X);
+ daSprites.add(userPhoto);
+ userPhoto.visible = false;
+
+ subTitleText = new UIText(20, userBorder.y + userBorder.height + 20, FlxG.width - 40, '"${GJUtil.userDescription}"', 16);
+ subTitleText.alignment = CENTER;
+ subTitleText.italic = true;
+ daSprites.add(subTitleText);
+
+ trophyOrLoginButton = new UIButton((FlxG.width / 2) - (isDaOwner ? 470 : 390), FlxG.height - 128, "Achievements", () -> {
+ secondaryPage = 'achievements';
+ onHome = false;
+ }, 180, 48);
+ // trophyOrLoginButton.color = 0xFF31FF31;
+ trophyOrLoginButton.field.size = 23;
+ daSprites.add(trophyOrLoginButton);
+
+ leaderOrRegisterButton = new UIButton((FlxG.width / 2) - (isDaOwner ? 280 : 190), FlxG.height - 128, "Leaderboards", () -> {
+ secondaryPage = 'leaderboards';
+ onHome = false;
+ }, 180, 48);
+ // leaderOrRegisterButton.color = 0xFF31FF31;
+ leaderOrRegisterButton.field.size = 23;
+ daSprites.add(leaderOrRegisterButton);
+
+ userDataButton = new UIButton((FlxG.width / 2) - (isDaOwner ? 90 : -10), FlxG.height - 128, "User Data", () -> {
+ // UI Window - WIP
+ openSubState(new GameJoltDataWindow(USER));
+ }, 180, 48);
+ // userDataButton.color = 0xFF31FF31;
+ userDataButton.field.size = 23;
+ daSprites.add(userDataButton);
+
+ if(isDaOwner) {
+ ownerDataButton = new UIButton((FlxG.width / 2) + 100, FlxG.height - 128, "Global Data", () -> {
+ // UI Window - WIP
+ openSubState(new GameJoltDataWindow(GLOBAL));
+ }, 180, 48);
+ // ownerDataButton.color = 0xFF31FF31;
+ ownerDataButton.field.size = 23;
+ daSprites.add(ownerDataButton);
+ }
+
+ logoutButton = new UIButton((FlxG.width / 2) + (isDaOwner ? 290 : 210), FlxG.height - 128, "Logout", () -> {
+ openSubState(new GameJoltConfirmationWindow('Logout', 'Are you sure you want to log out of GameJolt? This will log you out of ALL mods on this build of CNE!', () -> {
+ closeSubState();
+ GJUtil.logout(true);
+ FlxG.resetState();
+ }));
+ }, 180, 48);
+ logoutButton.color = 0xFFFF0000;
+ logoutButton.field.size = 23;
+ daSprites.add(logoutButton);
+ //endregion
+
+ //region Secondary Page
+ secondaryTitleText = new UIText(FlxG.width, 80, FlxG.width, "", 60);
+ secondaryTitleText.alignment = CENTER;
+ daSprites.add(secondaryTitleText);
+
+ subBoxBacking = new UISprite(FlxG.width + 100, secondaryTitleText.height + 100);
+ subBoxBacking.makeGraphic(Std.int(((FlxG.width - 200) / 4) - 25), Std.int(FlxG.height - subBoxBacking.y - 100), FlxColor.GRAY);
+ daSprites.add(subBoxBacking);
+
+ mainBoxBacking = new UISprite(subBoxBacking.width + subBoxBacking.x + 50, subBoxBacking.y);
+ mainBoxBacking.makeGraphic(Std.int((subBoxBacking.width * 3) + 25), Std.int(subBoxBacking.height), FlxColor.GRAY);
+ daSprites.add(mainBoxBacking);
+
+ // for achievements, put the main box on the left and the sub box on the right
+ mainBoxAch = new FlxTypedSpriteGroup(subBoxBacking.x + boxContentBorderSize, subBoxBacking.y + boxContentBorderSize);
+ mainBoxAch.clipRect = new FlxRect(0, 0, mainBoxBacking.width - (boxContentBorderSize * 4), mainBoxBacking.height - (boxContentBorderSize * 4));
+ daSprites.add(mainBoxAch);
+
+ subBoxAch = new FlxTypedSpriteGroup(subBoxBacking.x + mainBoxBacking.width + boxContentBorderSize + 50, subBoxBacking.y + boxContentBorderSize);
+ subBoxAch.clipRect = new FlxRect(0, 0, subBoxBacking.width - (boxContentBorderSize * 3), subBoxBacking.height - (boxContentBorderSize * 4));
+ daSprites.add(subBoxAch);
+
+ subBoxLead = new FlxTypedSpriteGroup(subBoxBacking.x + boxContentBorderSize, subBoxBacking.y + boxContentBorderSize);
+ subBoxLead.clipRect = new FlxRect(0, 0, subBoxBacking.width - (boxContentBorderSize * 3), subBoxBacking.height - (boxContentBorderSize * 4));
+ daSprites.add(subBoxLead);
+
+ mainBoxLead = new FlxTypedSpriteGroup(mainBoxBacking.x + boxContentBorderSize, mainBoxBacking.y + boxContentBorderSize);
+ mainBoxLead.clipRect = new FlxRect(0, 0, mainBoxBacking.width - (boxContentBorderSize * 4), mainBoxBacking.height - (boxContentBorderSize * 4));
+ daSprites.add(mainBoxLead);
+
+ var reqsToMake:Array = [];
+ if (GameJoltData.definedTrophies.toString() == '[]' && GameJoltData.customTrophies.toString() == '[]')
+ daSprites.add(missingTextAch = new UIText(mainBoxAch.x, mainBoxAch.y, mainBoxAch.clipRect.width, "NO TROPHIES FOUND FOR THIS MOD!", 48));
+ else
+ reqsToMake.push(TROPHIES_FETCH());
+
+ if (GameJoltData.leaderboards.toString() == '[]')
+ daSprites.add(missingTextLead = new UIText(mainBoxLead.x, mainBoxLead.y, mainBoxLead.clipRect.width, "NO LEADERBOARDS AVAILABLE FOR THIS MOD!", 48));
+ else
+ reqsToMake.push(SCORES_TABLES);
+
+ // do this in a batch to make sure we get everything
+ GameJoltSecurity.sendTrusted(BATCH(true, false, reqsToMake), true, function(err) {
+ daSprites.add(missingTextAch = new UIText(mainBoxAch.x, mainBoxAch.y, mainBoxAch.clipRect.width, "UNABLE TO FETCH TROPHIES: " + err, 48));
+ daSprites.add(missingTextLead = new UIText(mainBoxLead.x, mainBoxLead.y, mainBoxLead.clipRect.width, "UNABLE TO FETCH LEADERBOARDS: " + err, 48));
+ }, function(resp) {
+ // seeing if we have a response for trophies or scoreboards
+ var trophyResp:Null = null;
+ var scoresResp:Null = null;
+ if (resp.responses.length > 1) {
+ trophyResp = resp.responses[0];
+ scoresResp = resp.responses[1];
+ } else {
+ if (resp.responses[0].trophies == null || resp.responses[0].trophies.length <= 0)
+ trophyResp = resp.responses[0]
+ else if (resp.responses[0].tables == null || resp.responses[0].tables.length <= 0)
+ scoresResp = resp.responses[0];
+ }
+
+ // achievement trophy image loading
+ if (trophyResp == null) {
+ daSprites.add(missingTextAch = new UIText(mainBoxAch.x, mainBoxAch.y, mainBoxAch.clipRect.width, "NO TROPHIES FOUND FOR THIS MOD!", 48));
+ } else {
+ var placeInd:Int = 0;
+ var photoSize:Int = Std.int((mainBoxAch.clipRect.width - (achRowSpacing * Math.max(0, achRowAmount - 1))) / achRowAmount);
+ for (trop in trophyResp.trophies) {
+ var canDisplay:Bool = false;
+ var hiddenTrop:Bool = false;
+ for (t in GameJoltData.definedTrophies) {
+ if ('${t.id}' == '${trop.id}') {
+ canDisplay = true;
+ if (t.hidden != null && t.hidden)
+ hiddenTrop = true;
+ }
+ }
+
+ for (t in GameJoltData.customTrophies) {
+ if ('${t.id}' == '${trop.id}') {
+ canDisplay = true;
+ if (t.hidden != null && t.hidden)
+ hiddenTrop = true;
+ }
+ }
+
+ if (!canDisplay || (hiddenTrop && !displayHiddenTrophies))
+ continue;
+
+ var tropSprite:UISprite = new UISprite(((achRowSpacing + photoSize) * (placeInd % achRowAmount)) + (achRowSpacing / 2), ((photoSize + achRowSpacing) * Math.floor(placeInd / achRowAmount)) + (achRowSpacing / 2));
+ GJUtil.getTrophyImage(tropSprite, trop, () -> {
+ tropSprite.setGraphicSize(photoSize, photoSize);
+ tropSprite.updateHitbox();
+ });
+ tropSprite.ID = placeInd;
+ if (trop.achieved == "false")
+ tropSprite.color = FlxColor.GRAY;
+ trophiesMap.set(placeInd, trop);
+ mainBoxAch.add(tropSprite);
+ placeInd++;
+ }
+
+ if (placeInd == 0)
+ daSprites.add(missingTextAch = new UIText(mainBoxAch.x, mainBoxAch.y, mainBoxAch.clipRect.width, "NO TROPHIES FOUND FOR THIS MOD!", 48));
+ else {
+ subBoxAch.add(trophyTitle = new UIText(achRowSpacing / 2, achRowSpacing / 2, subBoxAch.clipRect.width - (boxContentBorderSize * 2), "", 38));
+ trophyTitle.alignment = CENTER;
+ subBoxAch.add(trophyDesc = new UIText(achRowSpacing / 2, achRowSpacing / 2, subBoxAch.clipRect.width - (boxContentBorderSize * 2), "", 20));
+ trophyDesc.alignment = CENTER;
+ }
+ }
+
+ // leaderboard loading
+ if (scoresResp == null)
+ daSprites.add(missingTextLead = new UIText(mainBoxLead.x, mainBoxLead.y, mainBoxLead.clipRect.width, "NO LEADERBOARDS AVAILABLE FOR THIS MOD!", 48));
+ else {
+ var amount:Int = 0;
+ for (board in scoresResp.tables) {
+ for (b in GameJoltData.leaderboards) {
+ if ('$b' == '${board.id}') {
+ scoresMap.set(board.name, board.id);
+ amount++;
+ }
+ }
+ }
+
+ if (amount == 0) {
+ daSprites.add(missingTextLead = new UIText(mainBoxLead.x, mainBoxLead.y, mainBoxLead.clipRect.width, "NO LEADERBOARDS AVAILABLE FOR THIS MOD!", 48));
+ } else {
+ var daY:Float = 0;
+ for (name in scoresMap.keys()) {
+ var daText:UIText = new UIText(0, daY, subBoxLead.clipRect.width, name, 24);
+ daText.textField.background = true;
+ daText.textField.backgroundColor = 0x0011AA00;
+ daText.wordWrap = false;
+ daText.autoSize = false;
+ subBoxLead.add(daText);
+ daY += daText.height;
+ }
+
+ for (i in 0...leaderboardLimit) {
+ var anEntry:GameJoltLeaderboardItem = new GameJoltLeaderboardItem();
+ mainBoxLead.add(anEntry);
+ }
+
+ daSprites.add(noScoresLead = new UIText(mainBoxLead.x, mainBoxLead.y, mainBoxLead.clipRect.width, "NO SCORES AVAILABLE FOR THIS LEADERBOARD!", 48));
+ noScoresLead.alignment = CENTER;
+ daSprites.add(rankLead = new UIText(mainBoxLead.x, mainBoxLead.y, mainBoxLead.clipRect.width, "YOUR RANK: --- (NOT SCORED YET!)", 32));
+ rankLead.alignment = CENTER;
+ }
+ }
+ });
+
+ if (missingTextAch != null)
+ missingTextAch.alignment = CENTER;
+
+ if (missingTextLead != null)
+ missingTextLead.alignment = CENTER;
+ //endregion
+ } else {
+ //region Login Page
+ userPhoto.loadGraphic(Paths.image('menus/gamejolt-icon'));
+ userPhoto.setGraphicSize(248, 248);
+ userPhoto.updateHitbox();
+ userPhoto.screenCenter(X);
+ userPhoto.y += 4;
+ daSprites.add(userPhoto);
+
+ subTitleText = new UIText(0, userPhoto.y + userPhoto.height + 30, FlxG.width, "Login with your GameJolt account to see leaderboards, get achievements, save your data, and more!", 35);
+ subTitleText.alignment = CENTER;
+ daSprites.add(subTitleText);
+
+ trophyOrLoginButton = new UIButton((FlxG.width / 2) + 10, FlxG.height - 128, "Login", () -> {
+ openSubState(new GameJoltLoginWindow());
+ }, 180, 48);
+ trophyOrLoginButton.color = 0xFF31FF31;
+ trophyOrLoginButton.field.size = 23;
+ daSprites.add(trophyOrLoginButton);
+
+ leaderOrRegisterButton = new UIButton((FlxG.width / 2) - 190, FlxG.height - 128, "Register", () -> {
+ CoolUtil.openURL('https://gamejolt.com/join');
+ }, 180, 48);
+ leaderOrRegisterButton.color = 0xFF31FF31;
+ leaderOrRegisterButton.field.size = 23;
+ daSprites.add(leaderOrRegisterButton);
+ //endregion
+ }
+
+ @:bypassAccessor onHome = true;
+ secondaryPage = 'achievements';
+ }
+
+ /**
+ * Input handler.
+ * @param elapsed
+ */
+ override function update(elapsed:Float)
+ {
+ super.update(elapsed);
+
+ var scroll = FlxG.mouse.wheel;
+ var upP = controls.UP_P;
+ var downP = controls.DOWN_P;
+ var leftP = controls.LEFT_P;
+ var rightP = controls.RIGHT_P;
+
+ if (!onHome) switch (secondaryPage) {
+ case "leaderboards":
+ if (subBoxLead.members.length > 1 && (upP || downP || scroll != 0))
+ daInd = FlxMath.wrap(daInd + (upP ? -1 : 0) + (downP ? 1 : 0) - scroll, 0, mainBoxLead.members.length - 1);
+ case "achievements":
+ if (mainBoxAch.members.length > 1 && (leftP || rightP || scroll != 0))
+ daInd = FlxMath.wrap(daInd + (leftP ? -1 : 0) + (rightP ? 1 : 0) - scroll, 0, mainBoxAch.members.length - 1);
+ case _:
+
+ }
+ if (controls.BACK) {
+ CoolUtil.playMenuSFX(CANCEL, 0.7);
+ if (onHome)
+ FlxG.switchState(new MainMenuState());
+ else
+ onHome = true;
+ }
+ }
+
+ /**
+ * Setter to determine if we're on home page or a secondary page.
+ * Default secondary pages are 'achievements' and 'leaderboards'.
+ * @param onH
+ * @return Bool
+ */
+ function set_onHome(onH:Bool):Bool
+ {
+ FlxTween.num(0, FlxG.width, 1, { }, function(num:Float) {
+ var prcnt:Float = num / FlxG.width;
+ bg.velocity.set(85 * (1 + (Math.sin(Math.PI * prcnt) * (onH ? -1 : 1))));
+ daSprites.x = FlxEase.elasticInOut(onH ? (1 - prcnt) : prcnt) * -FlxG.width;
+ });
+ return onHome = onH;
+ }
+
+ /**
+ * Changes the currently selected item.
+ * @param newInd
+ * @return Int
+ */
+ function set_daInd(newInd:Int):Int
+ {
+ if (secondaryTitleText != null) switch(secondaryTitleText.text) {
+ case "LEADERBOARDS":
+ if (mainBoxLead.members.length > 0) {
+ for (member in mainBoxLead.members)
+ member.clearData();
+
+ if (daInd != newInd && subBoxLead.members[daInd] != null)
+ subBoxLead.members[daInd].textField.backgroundColor = 0x0011AA00;
+ subBoxLead.members[newInd].textField.backgroundColor = 0xff11AA00;
+ var daId:Int = scoresMap.get(subBoxLead.members[newInd].text);
+ GameJoltSecurity.sendTrusted(BATCH(true, false, [SCORES_FETCH(false, daId, leaderboardLimit), SCORES_FETCH(true, daId)]), true, function(err) {
+ Logs.error("Leaderboard fetch error: " + err, RED, 'GameJolt');
+ }, function(resp) {
+ if (resp.responses[0].scores == null || resp.responses[0].scores.length <= 0) {
+ noScoresLead.visible = true;
+ rankLead.visible = false;
+ } else {
+ rankLead.visible = true;
+ noScoresLead.visible = false;
+ for (i in 0...resp.responses[0].scores.length) {
+ var usr = resp.responses[0].scores[i];
+ mainBoxLead.members[i].setData(Std.int(mainBoxLead.clipRect.width), Std.int(mainBoxLead.clipRect.height / 8.5), usr.user, usr.score, i);
+ // fun fact: wanted to try and include pfp's, but the calls would've been a bit much, at least imo. ~SPD
+ }
+
+ if(resp.responses[1].scores[0] != null)
+ GameJoltSecurity.sendTrusted(SCORES_GETRANK(resp.responses[1].scores[0].sort, daId), true, function(err) {
+ Logs.error("Error fetching user rank: " + err, RED, 'GameJolt');
+ rankLead.text = 'YOUR RANK: --- (??????)';
+ }, function(respSec) {
+ rankLead.text = 'YOUR RANK: ${respSec.rank != null ? '${respSec.rank}' : '---'} (${resp.responses[1].scores[0].score})';
+ })
+ else
+ rankLead.text = 'YOUR RANK: --- (NOT SCORED YET!)';
+ }
+ });
+ }
+
+ case "ACHIEVEMENTS":
+ if (mainBoxAch.members.length > 0) {
+ if (noScaleTween) {
+ mainBoxAch.members[daInd].scale.set(1, 1);
+ mainBoxAch.members[daInd].scale.set(achievementSelectScale, achievementSelectScale);
+ } else {
+ FlxTween.cancelTweensOf(mainBoxAch.members[daInd].scale);
+ FlxTween.cancelTweensOf(mainBoxAch.members[newInd].scale);
+ FlxTween.tween(mainBoxAch.members[daInd].scale, { x: 1, y: 1 }, achScaleTime, { ease: achEase });
+ FlxTween.tween(mainBoxAch.members[newInd].scale, { x: achievementSelectScale, y: achievementSelectScale }, achScaleTime, { ease: achEase });
+ }
+
+ trophyTitle.text = trophiesMap.get(mainBoxAch.members[newInd].ID).title.toUpperCase();
+ trophyDesc.y = trophyTitle.y + trophyTitle.height + achRowSpacing;
+ trophyDesc.text = trophiesMap.get(mainBoxAch.members[newInd].ID).description;
+ }
+ }
+ return daInd = newInd;
+ }
+
+ /**
+ * Changing the secondary page to one of the two defaults.
+ * @param type
+ * @return String
+ */
+ function set_secondaryPage(type:String):String
+ {
+ if (secondaryTitleText != null) switch(type) {
+ case "leaderboards":
+ secondaryTitleText.text = type.toUpperCase();
+ subBoxAch.active = subBoxAch.visible = mainBoxAch.active = mainBoxAch.visible = false;
+ if (missingTextAch != null)
+ missingTextAch.visible = false;
+ subBoxLead.active = subBoxLead.visible = mainBoxLead.active = mainBoxLead.visible = true;
+ if (missingTextLead != null)
+ missingTextLead.visible = true;
+ subBoxBacking.x = FlxG.width + 100;
+ mainBoxBacking.x = subBoxBacking.width + subBoxBacking.x + 50;
+ noScaleTween = true;
+ daInd = 0;
+ noScaleTween = false;
+
+ case "achievements":
+ secondaryTitleText.text = type.toUpperCase();
+ subBoxAch.active = subBoxAch.visible = mainBoxAch.active = mainBoxAch.visible = true;
+ if (missingTextAch != null)
+ missingTextAch.visible = true;
+ subBoxLead.active = subBoxLead.visible = mainBoxLead.active = mainBoxLead.visible = false;
+ if (missingTextLead != null)
+ missingTextLead.visible = false;
+ if (noScoresLead != null)
+ noScoresLead.visible = false;
+ if (rankLead != null)
+ rankLead.visible = false;
+ mainBoxBacking.x = FlxG.width + 100;
+ subBoxBacking.x = mainBoxBacking.width + mainBoxBacking.x + 50;
+ noScaleTween = true;
+ daInd = 0;
+ noScaleTween = false;
+ }
+
+ return secondaryPage = type;
+ }
+}
+
+//region Leaderboard Items
+class GameJoltLeaderboardItem extends FlxTypedSpriteGroup
+{
+ public var selected(default, set):Bool;
+
+ var name(default, set):String;
+ var score(default, set):String;
+ var rank(default, set):Int;
+
+ var daWidth(default, set):Int;
+ var daHeight(default, set):Int;
+
+ var nameText:UIText;
+ var scoreText:UIText;
+ var rankText:UIText;
+ var profilePic:UISprite;
+ var background:UISprite;
+
+ override public function new()
+ {
+ super();
+
+ add(background = new UISprite());
+ background.alpha = 0.6;
+ background.visible = false;
+
+ add(nameText = new UIText(0, 0, width, ""));
+ add(scoreText = new UIText(0, 0, width, ""));
+ add(rankText = new UIText(0, 0, width, ""));
+ add(profilePic = new UISprite());
+
+ @:bypassAccessor selected = false;
+ }
+
+ public function setData(width:Int = 200, height:Int = 200, name:String, score:String, rank:Int, ?image:FlxGraphic)
+ {
+ this.name = name;
+ this.score = score;
+ this.rank = rank;
+
+ if (image != null)
+ profilePic.loadGraphic(image);
+
+ this.height = height;
+ this.width = width;
+
+ active = visible = true;
+ }
+
+ public function clearData()
+ {
+ name = "";
+ score = "";
+ rank = 0;
+ height = 0;
+ width = 0;
+ active = visible = false;
+ }
+
+ inline function set_selected(sel:Bool):Bool
+ {
+ background.visible = sel;
+ return selected = sel;
+ }
+
+ inline function set_name(n:String):String
+ {
+ nameText.text = n;
+ return name = n;
+ }
+
+ inline function set_score(s:String):String
+ {
+ scoreText.text = s;
+ return score = s;
+ }
+
+ inline function set_rank(r:Int):Int
+ {
+ rankText.text = '$r';
+ return rank = r;
+ }
+
+ inline function set_daWidth(w:Int):Int
+ {
+ if (w != 0) {
+ background.makeGraphic(w, daHeight, FlxColor.YELLOW);
+ profilePic.x = Std.int(w * 0.01);
+ rankText.x = Std.int(profilePic.x + profilePic.width + (w * 0.02));
+ nameText.x = Std.int(w / 2);
+ scoreText.fieldWidth = Std.int(w * 0.99);
+ }
+ return daWidth = w;
+ }
+
+ inline function set_daHeight(h:Int):Int
+ {
+ if (h != 0) {
+ if (profilePic.graphic != null) {
+ background.makeGraphic(daWidth, h, FlxColor.YELLOW);
+ profilePic.setGraphicSize(Std.int(h * 0.9), Std.int(h * 0.9));
+ profilePic.updateHitbox();
+ }
+ }
+ return daHeight = h;
+ }
+}
+//endregion
\ No newline at end of file
diff --git a/source/funkin/menus/ui/Alphabet.hx b/source/funkin/menus/ui/Alphabet.hx
index 6acbc98cf9..6e6fcaa289 100644
--- a/source/funkin/menus/ui/Alphabet.hx
+++ b/source/funkin/menus/ui/Alphabet.hx
@@ -48,7 +48,7 @@ final class AlphabetComponent {
final class AlphabetLetterData {
@:optional public var isDefault:Bool = false;
public var advance:Float;
- public var advanceStyle:AdvanceMode;
+ public var advanceEmpty:Bool;
public var components:Array;
public var startIndex:Int = 0;
}
@@ -117,13 +117,6 @@ enum abstract AlphabetRenderMode(ByteUInt) from ByteUInt to ByteUInt {
var MONOSPACE = 1;
}
-enum abstract AdvanceMode(ByteUInt) from ByteUInt to ByteUInt {
- var EMPTY;
- var GIVEN;
- var AUTO;
- var CALCULATED;
-}
-
@:allow(funkin.editors.alphabet.AlphabetEditor)
@:allow(funkin.editors.alphabet.AlphabetMainDataScreen)
class Alphabet extends FlxSprite {
@@ -281,11 +274,14 @@ class Alphabet extends FlxSprite {
continue;
}
- var advance:Float = getAdvance(letter, data);
+ var advance:Float = Math.NaN;
for (i in 0...data.components.length) {
__component = data.components[i];
var anim = getLetterAnim(letter, data, __component, i);
+ //if (cantrace)
+ //trace(anim.name + " | " + __component.anim + " | " + frames.frames[anim.frames[0]]);
+ advance = (Math.isNaN(advance) && i >= data.startIndex) ? getAdvance(letter, anim, data) : advance;
if (anim == null || __renderData.alpha <= 0.0)
continue;
@@ -432,38 +428,21 @@ class Alphabet extends FlxSprite {
continue;
}
- final data = getData(letter);
- __laneWidths[curLine] += renderMode == MONOSPACE ? defaultAdvance : getAdvance(letter, data);
+ var data = getData(letter);
+ __laneWidths[curLine] += (data != null && data.components.length > 0) ? getAdvance(letter, getLetterAnim(letter, data, data.components[data.startIndex], data.startIndex), data) : defaultAdvance;
@:bypassAccessor textWidth = Math.max(textWidth, __laneWidths[curLine]);
}
origin.set(textWidth * 0.5 + originOffset.x, textHeight * 0.5 + originOffset.y);
}
- function getAdvance(letter:String, data:AlphabetLetterData):Float {
- if (data == null || frames.numFrames <= 0)
+ function getAdvance(letter:String, anim:FlxAnimation, data:AlphabetLetterData):Float {
+ if (anim == null)
return defaultAdvance;
- if (data.advanceStyle & GIVEN != 0) // just return if GIVEN or CALCULATED
- return data.advance;
-
- var result = 0.0;
-
- for (i in 0...data.components.length) {
- final compon = data.components[i];
- final anim = getLetterAnim(letter, data, compon, i);
- if (anim == null || anim.numFrames <= 0)
- continue;
-
- final wid = frames.frames[anim.frames[0]].sourceSize.x;
- result = Math.max(wid + (wid * compon.scaleX - wid) * 0.5 - compon.x, result);
- }
-
- if (data.advanceStyle == AUTO) {
- data.advance = result;
- data.advanceStyle = CALCULATED;
- }
- return result;
+ if (data.advanceEmpty && !data.isDefault)
+ data.advance = frames.frames[anim.frames[0]].sourceSize.x;
+ return (data.isDefault) ? frames.frames[anim.frames[0]].sourceSize.x : data.advance;
}
private function fastGetData(char:String):AlphabetLetterData {
@@ -553,7 +532,7 @@ class Alphabet extends FlxSprite {
var res:AlphabetLetterData = {
isDefault: true,
advance: 0.0,
- advanceStyle: EMPTY,
+ advanceEmpty: true,
components: [{
anim: node.firstChild().nodeValue.trim(),
@@ -657,7 +636,7 @@ class Alphabet extends FlxSprite {
letterData.set(char, {
isDefault: false,
advance: advance,
- advanceStyle: Math.isNaN(advance) ? AUTO : GIVEN,
+ advanceEmpty: Math.isNaN(advance),
components: components,
startIndex: startIndex
});
@@ -732,7 +711,7 @@ class Alphabet extends FlxSprite {
letterData.set(char, {
isDefault: false,
advance: advance,
- advanceStyle: Math.isNaN(advance) ? AUTO : GIVEN,
+ advanceEmpty: Math.isNaN(advance),
components: components,
startIndex: (node.get("hasOutline") == "true") ? 1 : 0
});
@@ -836,7 +815,7 @@ class Alphabet extends FlxSprite {
var data = fastGetData(let);
var node = Xml.createElement(data.components.length - data.startIndex > 1 ? "composite" : "anim");
node.set("char", let);
- if (data.advanceStyle == GIVEN)
+ if (!data.advanceEmpty)
node.set("advance", Std.string(data.advance));
for (i in data.startIndex...data.components.length) {
diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx
index b959e5e998..eea9226ed4 100644
--- a/source/funkin/options/Options.hx
+++ b/source/funkin/options/Options.hx
@@ -31,11 +31,6 @@ class Options
public static var flashingMenu:Bool = true;
public static var camZoomOnBeat:Bool = true;
public static var fpsCounter:Bool = true;
- public static var fpsCounterConductor:Bool = true;
- public static var fpsCounterFlixel:Bool = true;
- public static var fpsCounterSystem:Bool = true;
- public static var fpsCounterAssets:Bool = true;
- public static var fpsCounterStats:Bool = true;
public static var autoPause:Bool = true;
public static var antialiasing:Bool = true;
public static var volume:Float = 1;
diff --git a/source/funkin/options/categories/AppearanceOptions.hx b/source/funkin/options/categories/AppearanceOptions.hx
index 9472ccf83d..f1458b69fe 100644
--- a/source/funkin/options/categories/AppearanceOptions.hx
+++ b/source/funkin/options/categories/AppearanceOptions.hx
@@ -1,7 +1,5 @@
package funkin.options.categories;
-import funkin.backend.system.framerate.Framerate;
-
class AppearanceOptions extends TreeMenuScreen {
var framerateOption:NumOption; // use for changing the text
var fpsAdvancedOption:TextOption;
@@ -23,8 +21,6 @@ class AppearanceOptions extends TreeMenuScreen {
add(new Separator());
add(new TextOption('optionsMenu.advanced', 'optionsTree.appearance.advanced-desc', ' >', () ->
parent.addMenu(new AdvancedAppearanceOptions())));
-
- __changeFPSCounter();
}
private function __changeFPS(value:Float) {
@@ -36,21 +32,6 @@ class AppearanceOptions extends TreeMenuScreen {
if (FlxG.updateFramerate < framerate) FlxG.drawFramerate = FlxG.updateFramerate = framerate;
else FlxG.updateFramerate = FlxG.drawFramerate = framerate;
}
-
- private function __changeFPSCounter() {
- if (Framerate.debugMode != 0 && !Options.fpsCounter) lastFPSDebugMode = Framerate.debugMode;
- Framerate.debugMode = Options.fpsCounter ? (lastFPSDebugMode ?? 1) : 0;
-
- if (Options.fpsCounter) {
- if (fpsAdvancedOption == null) {
- insert(2, fpsAdvancedOption = new TextOption(getNameID('fpsAdvanced'), getDescID('fpsAdvanced'), ' >', () -> parent.addMenu(new FramerateAppearanceOptions())));
- }
- } else if (fpsAdvancedOption != null) {
- remove(fpsAdvancedOption, true);
- fpsAdvancedOption = flixel.util.FlxDestroyUtil.destroy(fpsAdvancedOption);
- if (curSelected >= length) changeSelection(0, true);
- }
- }
}
class AdvancedAppearanceOptions extends TreeMenuScreen {
@@ -107,17 +88,3 @@ class AdvancedAppearanceOptions extends TreeMenuScreen {
FlxG.game.stage.quality = (FlxG.enableAntialiasing = Options.antialiasing) ? BEST : LOW;
}
}
-
-class FramerateAppearanceOptions extends TreeMenuScreen {
- public function new() {
- super('AppearanceOptions.fpsAdvanced-name', 'AppearanceOptions.fpsAdvanced-desc', 'AppearanceOptions.Advanced.');
-
- add(new Checkbox(getNameID('fpsCounterConductor'), getDescID('fpsCounterConductor'), 'fpsCounterConductor', () -> Framerate.conductorInfo.visible = Options.fpsCounterConductor));
- add(new Checkbox(getNameID('fpsCounterFlixel'), getDescID('fpsCounterFlixel'), 'fpsCounterFlixel', () -> Framerate.flixelInfo.visible = Options.fpsCounterFlixel));
- add(new Checkbox(getNameID('fpsCounterSystem'), getDescID('fpsCounterSystem'), 'fpsCounterSystem', () -> Framerate.systemInfo.visible = Options.fpsCounterSystem));
- add(new Checkbox(getNameID('fpsCounterAssets'), getDescID('fpsCounterAssets'), 'fpsCounterAssets', () -> Framerate.assetInfo.visible = Options.fpsCounterAssets));
- #if (gl_stats && !disable_cffi && (!html5 || !canvas))
- add(new Checkbox(getNameID('fpsCounterStats'), 'optionsMenu.desc-missing', 'fpsCounterStats', () -> Framerate.statsInfo.visible = Options.fpsCounterStats));
- #end
- }
-}
diff --git a/source/funkin/options/type/ArrayOption.hx b/source/funkin/options/type/ArrayOption.hx
index 11de445192..5f1f559964 100644
--- a/source/funkin/options/type/ArrayOption.hx
+++ b/source/funkin/options/type/ArrayOption.hx
@@ -1,10 +1,6 @@
package funkin.options.type;
class ArrayOption extends TextOption {
- public static var EMPTY_ARROW_STRING:String = ' ';
- public static var LEFT_ARROW_STRING:String = '< ';
- public static var RIGHT_ARROW_STRING:String = ' >';
-
public var changedCallback:String->Void;
public var options:Array;
@@ -49,14 +45,14 @@ class ArrayOption extends TextOption {
}
function formatTextOption() {
- var s = TextOption.OPTION_VALUE_PREFIX;
+ var s = ": ";
- if (currentSelection > 0) s += LEFT_ARROW_STRING;
- else s += EMPTY_ARROW_STRING;
+ if (currentSelection > 0) s += "< ";
+ else s += " ";
s += TU.exists(displayOptions[currentSelection]) ? TU.translate(displayOptions[currentSelection]) : displayOptions[currentSelection];
- if (currentSelection < options.length - 1) s += RIGHT_ARROW_STRING;
+ if (currentSelection < options.length - 1) s += " >";
return s;
}
diff --git a/source/funkin/options/type/NumOption.hx b/source/funkin/options/type/NumOption.hx
index 203b68b87c..906c9d8ce9 100644
--- a/source/funkin/options/type/NumOption.hx
+++ b/source/funkin/options/type/NumOption.hx
@@ -18,7 +18,7 @@ class NumOption extends TextOption {
var __number:Alphabet;
function set_currentValue(v:Float):Float {
- if (__number != null && v != currentValue) __number.text = TextOption.OPTION_VALUE_PREFIX + Std.string(v);
+ if (__number != null) __number.text = ': $v';
return currentValue = v;
}
@@ -37,7 +37,8 @@ class NumOption extends TextOption {
this.parent = parent = parent != null ? parent : Options;
if (Reflect.field(parent, optionName) != null) currentValue = Reflect.field(parent, optionName);
- __number = new Alphabet(0, 20, TextOption.OPTION_VALUE_PREFIX + Std.string(currentValue), 'bold');
+
+ __number = new Alphabet(0, 20, ': $currentValue', 'bold');
super(text, desc);
add(__number);
}
diff --git a/source/funkin/options/type/TextOption.hx b/source/funkin/options/type/TextOption.hx
index cce5327588..64b93b2524 100644
--- a/source/funkin/options/type/TextOption.hx
+++ b/source/funkin/options/type/TextOption.hx
@@ -6,8 +6,6 @@ import flixel.effects.FlxFlicker;
* Option type that has text.
**/
class TextOption extends OptionType {
- public static var OPTION_VALUE_PREFIX:String = ': ';
-
public var suffix(default, set):String;
public var selectCallback:Void->Void;
diff --git a/source/funkin/savedata/FunkinSave.hx b/source/funkin/savedata/FunkinSave.hx
index b72184ed74..7c58fbea9d 100644
--- a/source/funkin/savedata/FunkinSave.hx
+++ b/source/funkin/savedata/FunkinSave.hx
@@ -1,7 +1,10 @@
package funkin.savedata;
+import funkin.backend.chart.ChartData.ChartMetaData;
+import funkin.menus.FreeplayState.FreeplaySonglist;
+import funkin.backend.system.gamejolt.GameJoltData.GJTrophyData;
import flixel.util.FlxSave;
-import lime.app.Application;
+import funkin.backend.system.gamejolt.*;
import openfl.Lib;
import haxe.Serializer;
import haxe.Unserializer;
@@ -144,6 +147,137 @@ class FunkinSave {
var oldHigh = safeGetHighscore(entry);
if (force || oldHigh.date == null || oldHigh.score < highscore.score) {
highscores.set(entry, highscore);
+
+ // GameJolt is only logged in if GAMEJOLT_API is on, otherwise it's always false.
+ if (GJUtil.loggedIn) {
+ switch(entry) {
+ case HSongEntry(songName, difficulty, variation, changes):
+ if (changes.length == 0) {
+ if (GameJoltData.leaderboards.exists('song-$songName--D:$difficulty (V:${variation != null ? variation : 'Default'})'))
+ GameJoltSecurity.sendTrusted(SCORES_ADD('${highscore.score}', highscore.score, 'accuracy=${highscore.accuracy};date=${highscore.date}', GameJoltData.leaderboards.get('song-$songName--D:$difficulty (V:${variation != null ? variation : 'Default'})')), true, function(err) {
+ Logs.error('Could not post score to leaderboard: $err', RED, 'GameJolt');
+ }, function(resp) {
+ Logs.trace('Successfully posted score to leaderboard associated with song $songName.', SUCCESS, LIGHTGRAY, 'GameJolt');
+ })
+ else if (GameJoltData.leaderboards.exists('song-$songName (V:${variation != null ? variation : 'Default'})'))
+ GameJoltSecurity.sendTrusted(SCORES_ADD('${highscore.score}', highscore.score, 'accuracy=${highscore.accuracy};date=${highscore.date}', GameJoltData.leaderboards.get('song-$songName (V:${variation != null ? variation : 'Default'})')), true, function(err) {
+ Logs.error('Could not post score to leaderboard: $err', RED, 'GameJolt');
+ }, function(resp) {
+ Logs.trace('Successfully posted score to leaderboard associated with song $songName.', SUCCESS, LIGHTGRAY, 'GameJolt');
+ });
+
+ // song trophy and exception check
+ if (GameJoltData.definedTrophies.exists('song-$songName')) {
+ var tropData:GJTrophyData = GameJoltData.definedTrophies.get('song-$songName');
+ var hasException:Bool = false;
+ if (tropData.except != null) {
+ for (itms in tropData.except) {
+ var indic:String = itms.substr(0, 3);
+ var dat:Array = itms.substr(3).split(',');
+ for (d in dat) d.trim();
+ switch (indic) {
+ case "=D:": //difficulty exception
+ if (dat.contains(difficulty)) hasException = true;
+
+ case "=V:": //variation exception
+ if (dat.contains(variation)) hasException = true;
+
+ case _:
+ // nothing lol
+ }
+ }
+ }
+ if (!hasException) GameJoltSecurity.unlockDefinedTrophy('song-$songName');
+ }
+
+ // first fc check
+ if (highscore.misses == 0 && GameJoltData.definedTrophies.exists('fc-first')) {
+ if (!GameJoltData.definedTrophies.get('fc-first').except.contains(songName))
+ GameJoltSecurity.unlockDefinedTrophy('fc-first');
+ }
+
+ // complete all & fc all achievement check
+ if (GameJoltData.definedTrophies.exists('complete-all') || GameJoltData.definedTrophies.exists('fc-all')) {
+ var songList:Array = FreeplaySonglist.get().songs;
+ var completedSongs:Array = [];
+ var fcData:Array = [];
+ for (s in highscores.keys()) {
+ if (highscores.get(s).score == 0)
+ continue
+ else switch (s) {
+ case HSongEntry(songName, difficulty, variation, changes):
+ completedSongs.push(songName);
+ fcData.push(highscores.get(s));
+ default:
+ // oop
+ }
+ }
+
+ if (GameJoltData.definedTrophies.exists('complete-all'))
+ {
+ var daTrophy:GJTrophyData = GameJoltData.definedTrophies.get('complete-all');
+ var songAmount:Int = completedSongs.length;
+ for (sng in completedSongs) {
+ if (daTrophy.except.contains(sng))
+ songAmount--;
+ }
+ if (songAmount == (completedSongs.length - daTrophy.except.length))
+ GameJoltSecurity.unlockDefinedTrophy('complete-all');
+ }
+
+ if (GameJoltData.definedTrophies.exists('fc-all'))
+ {
+ var daTrophy:GJTrophyData = GameJoltData.definedTrophies.get('fc-all');
+ var songAmount:Int = 0;
+ for (sng in completedSongs) {
+ if (daTrophy.except.contains(sng))
+ continue;
+
+ if (fcData[completedSongs.indexOf(sng)].misses == 0)
+ songAmount++;
+ }
+ if (songAmount == (completedSongs.length - daTrophy.except.length))
+ GameJoltSecurity.unlockDefinedTrophy('fc-all');
+ }
+ }
+
+ }
+ case HWeekEntry(weekName, difficulty):
+ if (GameJoltData.leaderboards.exists('week-$weekName--D:$difficulty'))
+ GameJoltSecurity.sendTrusted(SCORES_ADD('${highscore.score}', highscore.score, 'accuracy=${highscore.accuracy};date=${highscore.date}', GameJoltData.leaderboards.get('week-$weekName--D:$difficulty')), true, function(err) {
+ Logs.error('Could not post score to leaderboard: $err', RED, 'GameJolt');
+ }, function(resp) {
+ Logs.trace('Successfully posted score to leaderboard associated with week $weekName.', SUCCESS, LIGHTGRAY, 'GameJolt');
+ })
+ else if (GameJoltData.leaderboards.exists('week-$weekName'))
+ GameJoltSecurity.sendTrusted(SCORES_ADD('${highscore.score}', highscore.score, 'accuracy=${highscore.accuracy};date=${highscore.date}', GameJoltData.leaderboards.get('week-$weekName')), true, function(err) {
+ Logs.error('Could not post score to leaderboard: $err', RED, 'GameJolt');
+ }, function(resp) {
+ Logs.trace('Successfully posted score to leaderboard associated with week $weekName.', SUCCESS, LIGHTGRAY, 'GameJolt');
+ });
+
+ // week trophy and exception check
+ if (GameJoltData.definedTrophies.exists('week-$weekName')) {
+ var tropData:GJTrophyData = GameJoltData.definedTrophies.get('week-$weekName');
+ var hasException:Bool = false;
+ if (tropData.except != null) {
+ for (itms in tropData.except) {
+ var indic:String = itms.substr(0, 3);
+ var dat:Array = itms.substr(3).split(',');
+ for (d in dat) d.trim();
+ switch (indic) {
+ case "=D:": //difficulty exception
+ if (dat.contains(difficulty)) hasException = true;
+
+ case _:
+ // nothing lol
+ }
+ }
+ }
+ if (!hasException) GameJoltSecurity.unlockDefinedTrophy('week-$weekName');
+ }
+ }
+ }
return true;
}
return false;
diff --git a/source/hscript/Config.hx b/source/hscript/Config.hx
index 959e3938d6..95c4a3cf6e 100644
--- a/source/hscript/Config.hx
+++ b/source/hscript/Config.hx
@@ -38,7 +38,7 @@ class Config {
// Incase any of your files fail
// These are the module names
public static final DISALLOW_CUSTOM_CLASSES = [
-
+
];
public static final DISALLOW_ABSTRACT_AND_ENUM = [
@@ -47,6 +47,7 @@ class Config {
@:unreflective
public static final IMPORT_BLACKLIST:Array = [
-
+ "funkin.backend.system.gamejolt.GameJoltSecurity", // don't want people getting those gamejolt keys!
+ "funkin.backend.system.gamejolt.GameJoltData", // Global data items and really bad things to modify
];
}