diff --git a/Project.xml b/Project.xml index b52d423..35a958f 100644 --- a/Project.xml +++ b/Project.xml @@ -2,7 +2,7 @@ - + @@ -21,10 +21,10 @@ - + - + @@ -33,7 +33,7 @@ - + @@ -56,12 +56,12 @@ - - - - + + + + - +
@@ -73,6 +73,7 @@ +
@@ -102,12 +103,14 @@ - - -
+ +
+ + +
@@ -124,7 +127,7 @@ - + @@ -134,7 +137,8 @@ - + + @@ -145,16 +149,18 @@ - + + + +
+
- - @@ -179,16 +185,11 @@
-
- - - - -
- + + @@ -234,6 +235,9 @@ + + + @@ -245,10 +249,10 @@ - + - + diff --git a/art/flashFiles/FNF_main_menu_assets.fla b/art/flashFiles/FNF_main_menu_assets.fla index 682e257..d8eae59 100644 Binary files a/art/flashFiles/FNF_main_menu_assets.fla and b/art/flashFiles/FNF_main_menu_assets.fla differ diff --git a/art/unused stuff/FunkinText.hx b/art/unused stuff/FunkinText.hx new file mode 100644 index 0000000..f1e5478 --- /dev/null +++ b/art/unused stuff/FunkinText.hx @@ -0,0 +1,192 @@ +package game.objects; + +import flixel.text.FlxText; +import flixel.FlxCamera; +import flixel.math.FlxPoint; +import flixel.util.FlxColor; + +/** + * Enhanced FlxText with fixes for character clipping and rendering issues + */ +class FunkinText extends FlxText +{ + /** + * Enable pixel-perfect alignment to prevent sub-pixel positioning artifacts + */ + public var pixelPerfect:Bool = true; + + /** + * Additional font metrics correction for fonts with unusual character bounds + */ + public var fontMetricsCorrection:Float = 0; + + /** + * Horizontal gutter to prevent character clipping on sides + */ + static inline var HORIZONTAL_GUTTER:Int = 4; + + /** + * Vertical gutter to prevent character clipping on top/bottom + */ + static inline var VERTICAL_GUTTER:Int = 8; + + /** + * Creates a new FunkinText object with enhanced rendering + * + * @param X The x position of the text + * @param Y The y position of the text + * @param FieldWidth The width of the text object + * @param Text The actual text to display initially + * @param Size The font size for this text object + * @param EmbeddedFont Whether this text field uses embedded fonts + */ + public function new(X:Float = 0, Y:Float = 0, FieldWidth:Float = 0, ?Text:String, Size:Int = 8, EmbeddedFont:Bool = true) + { + super(X, Y, FieldWidth, Text, Size, EmbeddedFont); + + // Apply additional horizontal padding by default + if (FieldWidth > 0) + { + this.fieldWidth = FieldWidth + HORIZONTAL_GUTTER; + } + } + + /** + * Regenerate the text graphic with applied fixes + */ + override function regenGraphic():Void + { + // Apply font metrics correction before regeneration + if (fontMetricsCorrection != 0 && _autoHeight && textField != null) + { + textField.height = textField.textHeight + fontMetricsCorrection; + } + + super.regenGraphic(); + } + + /** + * Override drawSimple to apply pixel-perfect positioning + */ + override function drawSimple(camera:FlxCamera):Void + { + getScreenPosition(_point, camera).subtract(offset).subtract(_graphicOffset); + + if (pixelPerfect && isPixelPerfectRender(camera)) + { + _point.x = Math.floor(_point.x); + _point.y = Math.floor(_point.y); + } + + _point.copyTo(_flashPoint); + camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing); + } + + /** + * Override drawComplex to apply pixel-perfect positioning + */ + override function drawComplex(camera:FlxCamera):Void + { + _frame.prepareMatrix(_matrix, ANGLE_0, checkFlipX(), checkFlipY()); + _matrix.translate(-origin.x, -origin.y); + _matrix.scale(scale.x, scale.y); + + if (bakedRotationAngle <= 0) + { + updateTrig(); + + if (angle != 0) + _matrix.rotateWithTrig(_cosAngle, _sinAngle); + } + + getScreenPosition(_point, camera).subtract(offset).subtract(_graphicOffset); + _point.add(origin.x, origin.y); + _matrix.translate(_point.x, _point.y); + + if (pixelPerfect && isPixelPerfectRender(camera)) + { + _matrix.tx = Math.floor(_matrix.tx); + _matrix.ty = Math.floor(_matrix.ty); + } + + camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader); + } + + /** + * Apply preset configurations for different font types + * + * @param preset The font preset to apply + * @return This FunkinText instance (for chaining) + */ + public function setFontPreset(preset:FontPreset):FunkinText + { + switch(preset) + { + case PIXEL: + // Optimal for pixel fonts - disable antialiasing, enable pixel perfection + pixelPerfect = true; + fontMetricsCorrection = 2; + + case SMOOTH: + // Optimal for smooth vector fonts - enable antialiasing, disable pixel perfection + pixelPerfect = false; + fontMetricsCorrection = 0; + + case CLEAR: + // Balanced preset - enable both antialiasing and pixel perfection + pixelPerfect = true; + fontMetricsCorrection = 1; + } + _regen = true; + return this; + } + + /** + * Set custom gutters for precise control over text padding + * + * @param horizontal Horizontal gutter size + * @param vertical Vertical gutter size + * @return This FunkinText instance (for chaining) + */ + public function setCustomGutters(horizontal:Int, vertical:Int):FunkinText + { + // Note: This would require overriding more internal methods to take effect + // For now, it serves as a reminder for future enhancements + return this; + } + + /** + * Enable or disable pixel-perfect rendering + * + * @param value Whether to enable pixel-perfect rendering + * @return This FunkinText instance (for chaining) + */ + public function setPixelPerfect(value:Bool):FunkinText + { + pixelPerfect = value; + return this; + } + + /** + * Set font metrics correction value + * + * @param correction Additional height correction for problematic fonts + * @return This FunkinText instance (for chaining) + */ + public function setFontMetricsCorrection(correction:Float):FunkinText + { + fontMetricsCorrection = correction; + _regen = true; + return this; + } +} + +/** + * Font presets for different rendering requirements + */ +enum FontPreset +{ + PIXEL; // For pixel fonts - crisp rendering without antialiasing + SMOOTH; // For smooth vector fonts - best quality with antialiasing + CLEAR; // Balanced preset - good for most use cases +} \ No newline at end of file diff --git a/art/unused stuff/chart editor things.hx b/art/unused stuff/chart editor things.hx new file mode 100644 index 0000000..21522fd --- /dev/null +++ b/art/unused stuff/chart editor things.hx @@ -0,0 +1,341 @@ +class ContextMenu extends MusicBeatSubstate +{ + var bg:FlxSprite; + var menuBg:FlxSprite; + var buttons:Array = []; + + public function new(x:Float, y:Float, note:Note, deleteCallback:Note->Void, copyCallback:Note->Void, pasteCallback:Void->Void) + { + super(); + + closeCallback = () -> close(); + + bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.TRANSPARENT); + bg.scrollFactor.set(); + bg.alpha = 0.0001; + bg.setPosition(0, 0); + add(bg); + + menuBg = new FlxSprite(x, y).makeGraphic(0, 0, FlxColor.BLACK); + menuBg.alpha = 0.8; + menuBg.scrollFactor.set(); + add(menuBg); + + var buttonY = y + 5; + createButton("Delete", x + 5, buttonY, function() { + deleteCallback(note); + closeMenu(); + }); + + buttonY += 30; + createButton("Copy", x + 5, buttonY, function() { + copyCallback(note); + closeMenu(); + }); + + buttonY += 30; + createButton("Paste", x + 5, buttonY, function() { + pasteCallback(); + closeMenu(); + }); + + //note properties removed for event notes due to critical error + if (note.noteData > -1) { + buttonY += 30; + createButton("Properties", x + 5, buttonY, () -> openNoteProperties(note)); + } + + var buttonCount = note.noteData > -1 ? 4 : 3; + menuBg.makeGraphic(90, buttonCount * 30, FlxColor.BLACK); + + cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + } + + function createButton(label:String, x:Float, y:Float, onClick:Void->Void) + { + var button = new PsychUIButton(x, y, label, onClick); + button.scrollFactor.set(); + add(button); + buttons.push(button); + return button; + } + + function closeMenu():Void + { + if (closeCallback != null) closeCallback(); + } + + function openNoteProperties(note:Note):Void + { + var parent:ChartEditorState = cast FlxG.state.subState._parentState; + @:privateAccess { + openSubState(new NotePropertiesSubstate(note, function(updatedNote:Note) { + parent.saveToUndo(); + parent.updateNoteData(note, updatedNote); + parent.updateGrid(); + closeSubState(); + }, parent.eventStuff)); + } + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (FlxG.mouse.justPressed) { + var mousePoint = FlxG.mouse.getViewPosition(camera); + + if (!menuBg.getScreenBounds(null, camera).containsPoint(mousePoint)) + closeMenu(); + } + + if (FlxG.keys.justPressed.ESCAPE) + closeMenu(); + } +} + +class NotePropertiesSubstate extends MusicBeatSubstate +{ + var note:Note; + var onSaveCallback:Note->Void; + var onCloseCallback:Void->Void; + var eventStuff:Array; + + var descText:FlxText; + var strumTimeStepper:PsychUINumericStepper; + var noteDataStepper:PsychUINumericStepper; + var sustainStepper:PsychUINumericStepper; + var typeInput:PsychUIInputText; + var value1Input:PsychUIInputText; + var value2Input:PsychUIInputText; + var eventDropdown:PsychUIDropDownMenu; + + public function new(note:Note, onSaveCallback:Note->Void, eventStuff:Array) + { + super(); + this.note = note; + this.onSaveCallback = onSaveCallback; + this.eventStuff = eventStuff; + + var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + bg.alpha = 0.6; + bg.scrollFactor.set(); + add(bg); + + var panel = new FlxSprite(FlxG.width / 2 - 150, FlxG.height / 2 - 150).makeGraphic(300, 300, FlxColor.GRAY); + panel.scrollFactor.set(); + add(panel); + + var title = new FlxText(panel.x, panel.y + 10, 300, "Note Properties", 16); + title.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, CENTER); + title.scrollFactor.set(); + add(title); + + var yOffset:Int = 50; + + if (note.noteData > -1) + { + var timeLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Strum Time:"); + timeLabel.scrollFactor.set(); + add(timeLabel); + + strumTimeStepper = new PsychUINumericStepper(panel.x + 120, panel.y + yOffset, 10, note.strumTime, 0, 999999, 0); + strumTimeStepper.scrollFactor.set(); + add(strumTimeStepper); + yOffset += 30; + + var dataLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Note Data:"); + dataLabel.scrollFactor.set(); + add(dataLabel); + + noteDataStepper = new PsychUINumericStepper(panel.x + 120, panel.y + yOffset, 1, note.noteData, 0, 7, 0); + noteDataStepper.scrollFactor.set(); + add(noteDataStepper); + yOffset += 30; + + var sustainLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Sustain:"); + sustainLabel.scrollFactor.set(); + add(sustainLabel); + + sustainStepper = new PsychUINumericStepper(panel.x + 120, panel.y + yOffset, 10, note.sustainLength, 0, 9999, 0); + sustainStepper.scrollFactor.set(); + add(sustainStepper); + yOffset += 30; + + var typeLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Note Type:"); + typeLabel.scrollFactor.set(); + add(typeLabel); + + typeInput = new PsychUIInputText(panel.x + 120, panel.y + yOffset, 150, note.noteType != null ? note.noteType : ""); + typeInput.scrollFactor.set(); + add(typeInput); + } + else + { + var eventLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Event Type:"); + eventLabel.scrollFactor.set(); + add(eventLabel); + + var eventList = []; + for (i in 0...eventStuff.length) { + eventList.push({label: eventStuff[i][0], id: Std.string(i)}); + } + + var eventNames:Array = [for (event in eventStuff) event[0]]; + eventDropdown = new PsychUIDropDownMenu(panel.x + 120, panel.y + yOffset, + eventNames, + function(id:Int, value:String) { + if (id >= 0 && id < eventStuff.length) { + var eventName = eventStuff[id][0]; + var eventDesc = eventStuff[id][1]; + descText.text = eventDesc; + + if (value1Input != null && value1Input.text == "") { + var defaultValues = getDefaultEventValues(eventName); + value1Input.text = defaultValues[0]; + value2Input.text = defaultValues[1]; + } + } + } + ); + eventDropdown.selectedIndex = Std.parseInt(note.eventName); + eventDropdown.scrollFactor.set(); + + yOffset += 30; + + var val1Label = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Value 1:"); + val1Label.scrollFactor.set(); + add(val1Label); + + value1Input = new PsychUIInputText(panel.x + 120, panel.y + yOffset, 150, note.eventVal1 != null ? note.eventVal1 : ""); + value1Input.scrollFactor.set(); + add(value1Input); + + yOffset += 30; + + var val2Label = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Value 2:"); + val2Label.scrollFactor.set(); + add(val2Label); + + value2Input = new PsychUIInputText(panel.x + 120, panel.y + yOffset, 150, note.eventVal2 != null ? note.eventVal2 : ""); + value2Input.scrollFactor.set(); + add(value2Input); + + var currentEventIndex = -1; + for (i in 0...eventStuff.length) + { + if (eventStuff[i][0] == note.eventName) + { + currentEventIndex = i; + break; + } + } + + descText = new FlxText(panel.x + 20, panel.y + yOffset + 30, 260, "", 12); + descText.wordWrap = true; + descText.setFormat(Paths.font("vcr.ttf"), 12, FlxColor.WHITE); + descText.scrollFactor.set(); + add(descText); + + if (currentEventIndex != -1) + { + eventDropdown.selectedIndex = currentEventIndex; + descText.text = eventStuff[currentEventIndex][1]; + } + else + { + eventDropdown.selectedLabel = note.eventName; + descText.text = "Custom Event"; + } + } + + yOffset += 40; + + if (note.noteData == -1) yOffset += 80; + + var saveButton = new PsychUIButton(panel.x + 50, panel.y + yOffset, "Save", () -> { + saveChanges(); + close(); + }); + add(saveButton); + + var cancelButton = new PsychUIButton(panel.x + 150, panel.y + yOffset, "Cancel", () -> close()); + add(cancelButton); + + add(eventDropdown); + + cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + } + + function saveChanges() + { + var updatedNote = new Note(0, 0); + updatedNote.noteData = note.noteData; + + if (note.noteData > -1) //Nomal note + { + var newData = Std.int(noteDataStepper.value); + if (note.noteData > 3) newData += 4; + + updatedNote.strumTime = strumTimeStepper.value; + updatedNote.noteData = newData; + updatedNote.sustainLength = sustainStepper.value; + updatedNote.noteType = typeInput.text; + } + else //Event + { + updatedNote.strumTime = note.strumTime; + updatedNote.eventName = eventDropdown.selectedLabel; + updatedNote.eventVal1 = value1Input.text; + updatedNote.eventVal2 = value2Input.text; + + if (value1Input != null) updatedNote.eventVal1 = value1Input.text; + if (value2Input != null) updatedNote.eventVal2 = value2Input.text; + } + + onSaveCallback(updatedNote); + } + + function getDefaultEventValues(eventName:String):Array + { + switch(eventName) + { + case 'Dadbattle Spotlight': + return ['1', '0']; + case 'Hey!': + return ['BF', '0.6']; + case 'Set GF Speed': + return ['1', '']; + case 'Add Camera Zoom': + return ['0.015', '0.03']; + case 'Play Animation': + return ['idle', 'BF']; + case 'Camera Follow Pos': + return ['', '']; + case 'Alt Idle Animation': + return ['BF', '-alt']; + case 'Screen Shake': + return ['0, 0.05', '0, 0.05']; + case 'Change Character': + return ['BF', 'bf-car']; + case 'Change Scroll Speed': + return ['1', '1']; + case 'Lyrics': + return ['Hello! --FF0000', '2']; + case 'Set Property': + return ['health', '0.5']; + default: + return ['', '']; + } + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (FlxG.keys.justPressed.ESCAPE) + { + close(); + } + } +} \ No newline at end of file diff --git a/assets/preload/data/characterList.txt b/assets/preload/data/characterList.txt index c4177d9..669dc48 100644 --- a/assets/preload/data/characterList.txt +++ b/assets/preload/data/characterList.txt @@ -1,26 +1,5 @@ bf +bf-pixel-opponent dad gf -spooky -pico -mom -mom-car -bf-car -gf-car -parents-christmas -monster-christmas -bf-christmas -gf-christmas -monster -bf-pixel -gf-pixel -senpai -senpai-angry -spirit -bf-pixel-opponent -gf-tankmen -tankman -pico-speaker -bf-holding-gf -pico-player -tankman-player \ No newline at end of file +gf-playable \ No newline at end of file diff --git a/assets/preload/data/stageList.txt b/assets/preload/data/stageList.txt index 9e41c09..03550f1 100644 --- a/assets/preload/data/stageList.txt +++ b/assets/preload/data/stageList.txt @@ -1,9 +1 @@ -stage -spooky -philly -limo -mall -mallEvil -school -schoolEvil -tank \ No newline at end of file +stage \ No newline at end of file diff --git a/assets/preload/images/credits/gct.png b/assets/preload/images/credits/gct.png new file mode 100644 index 0000000..647cd89 Binary files /dev/null and b/assets/preload/images/credits/gct.png differ diff --git a/assets/preload/images/credits/sea3.png b/assets/preload/images/credits/sea3.png new file mode 100644 index 0000000..6239ff3 Binary files /dev/null and b/assets/preload/images/credits/sea3.png differ diff --git a/assets/preload/images/credits/slushi.png b/assets/preload/images/credits/slushi.png new file mode 100644 index 0000000..46333f2 Binary files /dev/null and b/assets/preload/images/credits/slushi.png differ diff --git a/assets/preload/images/mainmenu/menu_awards.png b/assets/preload/images/mainmenu/menu_awards.png index 901ed1a..656f2f1 100644 Binary files a/assets/preload/images/mainmenu/menu_awards.png and b/assets/preload/images/mainmenu/menu_awards.png differ diff --git a/assets/preload/images/mainmenu/menu_awards.xml b/assets/preload/images/mainmenu/menu_awards.xml index 2d34192..d67d0cd 100644 --- a/assets/preload/images/mainmenu/menu_awards.xml +++ b/assets/preload/images/mainmenu/menu_awards.xml @@ -1,17 +1,17 @@  - + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/assets/preload/images/mainmenu/menu_credits.png b/assets/preload/images/mainmenu/menu_credits.png index 01601cb..4332c5c 100644 Binary files a/assets/preload/images/mainmenu/menu_credits.png and b/assets/preload/images/mainmenu/menu_credits.png differ diff --git a/assets/preload/images/mainmenu/menu_credits.xml b/assets/preload/images/mainmenu/menu_credits.xml index a981da6..7eeaf4a 100644 --- a/assets/preload/images/mainmenu/menu_credits.xml +++ b/assets/preload/images/mainmenu/menu_credits.xml @@ -1,16 +1,17 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/assets/preload/images/mainmenu/menu_donate.png b/assets/preload/images/mainmenu/menu_donate.png deleted file mode 100644 index 961328d..0000000 Binary files a/assets/preload/images/mainmenu/menu_donate.png and /dev/null differ diff --git a/assets/preload/images/mainmenu/menu_donate.xml b/assets/preload/images/mainmenu/menu_donate.xml deleted file mode 100644 index 9cd243c..0000000 --- a/assets/preload/images/mainmenu/menu_donate.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/assets/preload/images/mainmenu/menu_freeplay.png b/assets/preload/images/mainmenu/menu_freeplay.png index db046a6..7026509 100644 Binary files a/assets/preload/images/mainmenu/menu_freeplay.png and b/assets/preload/images/mainmenu/menu_freeplay.png differ diff --git a/assets/preload/images/mainmenu/menu_freeplay.xml b/assets/preload/images/mainmenu/menu_freeplay.xml index 668c127..a19689f 100644 --- a/assets/preload/images/mainmenu/menu_freeplay.xml +++ b/assets/preload/images/mainmenu/menu_freeplay.xml @@ -1,15 +1,17 @@  - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/assets/preload/images/mainmenu/menu_mods.png b/assets/preload/images/mainmenu/menu_mods.png index 327680d..6832772 100644 Binary files a/assets/preload/images/mainmenu/menu_mods.png and b/assets/preload/images/mainmenu/menu_mods.png differ diff --git a/assets/preload/images/mainmenu/menu_mods.xml b/assets/preload/images/mainmenu/menu_mods.xml index 992fea7..db070a3 100644 --- a/assets/preload/images/mainmenu/menu_mods.xml +++ b/assets/preload/images/mainmenu/menu_mods.xml @@ -1,17 +1,17 @@  - + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/assets/preload/images/mainmenu/menu_options.png b/assets/preload/images/mainmenu/menu_options.png index 4293cc9..a46ed94 100644 Binary files a/assets/preload/images/mainmenu/menu_options.png and b/assets/preload/images/mainmenu/menu_options.png differ diff --git a/assets/preload/images/mainmenu/menu_options.xml b/assets/preload/images/mainmenu/menu_options.xml index 3b3a9f9..8076ed4 100644 --- a/assets/preload/images/mainmenu/menu_options.xml +++ b/assets/preload/images/mainmenu/menu_options.xml @@ -1,15 +1,17 @@  - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/assets/preload/images/mainmenu/menu_story_mode.png b/assets/preload/images/mainmenu/menu_story_mode.png index 68525e7..8b1efda 100644 Binary files a/assets/preload/images/mainmenu/menu_story_mode.png and b/assets/preload/images/mainmenu/menu_story_mode.png differ diff --git a/assets/preload/images/mainmenu/menu_story_mode.xml b/assets/preload/images/mainmenu/menu_story_mode.xml index 0305496..9920b6f 100644 --- a/assets/preload/images/mainmenu/menu_story_mode.xml +++ b/assets/preload/images/mainmenu/menu_story_mode.xml @@ -1,15 +1,17 @@  - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/assets/preload/images/ye.png b/assets/preload/images/ye.png new file mode 100644 index 0000000..aaf5049 Binary files /dev/null and b/assets/preload/images/ye.png differ diff --git a/assets/preload/weeks/test.json b/assets/preload/weeks/test.json new file mode 100644 index 0000000..c4ea3ec --- /dev/null +++ b/assets/preload/weeks/test.json @@ -0,0 +1,20 @@ +{ + "songs": [ + ["Test", "gf", [165, 0, 77]] + ], + + "weekCharacters": [ + "", + "bf", + "gf" + ], + "weekBackground": "stage", + + "storyName": "", + "weekBefore": "tutorial", + "weekName": "Tutorial", + "startUnlocked": true, + + "hideStoryMode": false, + "hideFreeplay": false +} diff --git a/assets/preload/weeks/weekList.txt b/assets/preload/weeks/weekList.txt index 724e580..30d74d2 100644 --- a/assets/preload/weeks/weekList.txt +++ b/assets/preload/weeks/weekList.txt @@ -1,8 +1 @@ -tutorial -week1 -week2 -week3 -week4 -week5 -week6 -week7 \ No newline at end of file +test \ No newline at end of file diff --git a/assets/shared/images/noteSplashes.png b/assets/shared/images/noteSplashes.png index 36ee067..1e26091 100644 Binary files a/assets/shared/images/noteSplashes.png and b/assets/shared/images/noteSplashes.png differ diff --git a/assets/shared/images/noteSplashes.xml b/assets/shared/images/noteSplashes.xml index b53b009..b90f10b 100644 --- a/assets/shared/images/noteSplashes.xml +++ b/assets/shared/images/noteSplashes.xml @@ -1,37 +1,37 @@  - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/shared/images/weeb/weebSchool.png b/assets/shared/images/weeb/weebSchool.png deleted file mode 100644 index f4aa7b5..0000000 Binary files a/assets/shared/images/weeb/weebSchool.png and /dev/null differ diff --git a/assets/shared/images/weeb/weebSky.png b/assets/shared/images/weeb/weebSky.png deleted file mode 100644 index bac9df5..0000000 Binary files a/assets/shared/images/weeb/weebSky.png and /dev/null differ diff --git a/assets/shared/images/weeb/weebStreet.png b/assets/shared/images/weeb/weebStreet.png deleted file mode 100644 index 69df5fd..0000000 Binary files a/assets/shared/images/weeb/weebStreet.png and /dev/null differ diff --git a/assets/shared/images/weeb/weebTrees.png b/assets/shared/images/weeb/weebTrees.png deleted file mode 100644 index a37f1af..0000000 Binary files a/assets/shared/images/weeb/weebTrees.png and /dev/null differ diff --git a/assets/shared/images/weeb/weebTrees.txt b/assets/shared/images/weeb/weebTrees.txt deleted file mode 100644 index 7846c00..0000000 --- a/assets/shared/images/weeb/weebTrees.txt +++ /dev/null @@ -1,20 +0,0 @@ -trees_0 = 0 0 512 512 -trees_1 = 513 0 512 512 -trees_2 = 1539 1026 512 512 -trees_3 = 2052 513 512 512 -trees_4 = 2565 0 512 512 -trees_5 = 3078 0 512 512 -trees_6 = 2565 513 512 512 -trees_7 = 2052 1026 512 512 -trees_8 = 2565 1026 512 512 -trees_9 = 3078 513 512 512 -trees_10 = 1026 0 512 512 -trees_11 = 0 513 512 512 -trees_12 = 513 513 512 512 -trees_13 = 0 1026 512 512 -trees_14 = 513 1026 512 512 -trees_15 = 1026 513 512 512 -trees_16 = 1026 1026 512 512 -trees_17 = 1539 0 512 512 -trees_18 = 1539 513 512 512 -trees_19 = 2052 0 512 512 \ No newline at end of file diff --git a/assets/preload/characters/bf-car.json b/base_game/characters/bf-car.json similarity index 100% rename from assets/preload/characters/bf-car.json rename to base_game/characters/bf-car.json diff --git a/assets/preload/characters/bf-christmas.json b/base_game/characters/bf-christmas.json similarity index 100% rename from assets/preload/characters/bf-christmas.json rename to base_game/characters/bf-christmas.json diff --git a/assets/preload/characters/bf-dead.json b/base_game/characters/bf-dead.json similarity index 100% rename from assets/preload/characters/bf-dead.json rename to base_game/characters/bf-dead.json diff --git a/assets/preload/characters/bf-holding-gf-dead.json b/base_game/characters/bf-holding-gf-dead.json similarity index 100% rename from assets/preload/characters/bf-holding-gf-dead.json rename to base_game/characters/bf-holding-gf-dead.json diff --git a/assets/preload/characters/bf-holding-gf.json b/base_game/characters/bf-holding-gf.json similarity index 100% rename from assets/preload/characters/bf-holding-gf.json rename to base_game/characters/bf-holding-gf.json diff --git a/assets/preload/characters/bf-pixel-dead.json b/base_game/characters/bf-pixel-dead.json similarity index 100% rename from assets/preload/characters/bf-pixel-dead.json rename to base_game/characters/bf-pixel-dead.json diff --git a/base_game/characters/bf-pixel-opponent.json b/base_game/characters/bf-pixel-opponent.json new file mode 100644 index 0000000..d0b72bb --- /dev/null +++ b/base_game/characters/bf-pixel-opponent.json @@ -0,0 +1,122 @@ +{ + "animations": [ + { + "loop": false, + "offsets": [ + 2, + 0 + ], + "fps": 24, + "anim": "idle", + "indices": [], + "name": "BF IDLE" + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "anim": "singUPmiss", + "fps": 24, + "name": "BF UP MISS", + "indices": [] + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "anim": "singUP", + "fps": 24, + "name": "BF UP NOTE", + "indices": [] + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "anim": "singDOWN", + "fps": 24, + "name": "BF DOWN NOTE", + "indices": [] + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "anim": "singDOWNmiss", + "fps": 24, + "name": "BF DOWN MISS", + "indices": [] + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "anim": "singLEFT", + "fps": 24, + "name": "BF RIGHT NOTE", + "indices": [] + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "anim": "singRIGHTmiss", + "fps": 24, + "name": "BF LEFT MISS", + "indices": [] + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "anim": "singLEFTmiss", + "fps": 24, + "name": "BF RIGHT MISS", + "indices": [] + }, + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "fps": 24, + "anim": "singRIGHT", + "indices": [], + "name": "BF LEFT NOTE" + } + ], + "no_antialiasing": true, + "image": "characters/bf/bfPixel", + "position": [ + 80, + 480 + ], + "healthicon": "bf-pixel", + "flip_x": true, + "healthbar_colors": [ + 123, + 214, + 246 + ], + "camera_position": [ + 50, + -160 + ], + "sing_duration": 4, + "scale": 6.8 +} \ No newline at end of file diff --git a/assets/preload/characters/bf-pixel.json b/base_game/characters/bf-pixel.json similarity index 100% rename from assets/preload/characters/bf-pixel.json rename to base_game/characters/bf-pixel.json diff --git a/base_game/characters/bf.json b/base_game/characters/bf.json new file mode 100644 index 0000000..5b6d74f --- /dev/null +++ b/base_game/characters/bf.json @@ -0,0 +1,188 @@ +{ + "animations": [ + { + "offsets": [ + -5, + 0 + ], + "loop": false, + "fps": 24, + "anim": "idle", + "indices": [], + "name": "BF idle dance" + }, + { + "offsets": [ + 5, + -6 + ], + "loop": false, + "fps": 24, + "anim": "singLEFT", + "indices": [], + "name": "BF NOTE LEFT0" + }, + { + "offsets": [ + -20, + -51 + ], + "loop": false, + "fps": 24, + "anim": "singDOWN", + "indices": [], + "name": "BF NOTE DOWN0" + }, + { + "offsets": [ + -46, + 27 + ], + "loop": false, + "fps": 24, + "anim": "singUP", + "indices": [], + "name": "BF NOTE UP0" + }, + { + "offsets": [ + -48, + -7 + ], + "loop": false, + "fps": 24, + "anim": "singRIGHT", + "indices": [], + "name": "BF NOTE RIGHT0" + }, + { + "offsets": [ + 7, + 19 + ], + "loop": false, + "fps": 24, + "anim": "singLEFTmiss", + "indices": [], + "name": "BF NOTE LEFT MISS" + }, + { + "offsets": [ + -15, + -19 + ], + "loop": false, + "fps": 24, + "anim": "singDOWNmiss", + "indices": [], + "name": "BF NOTE DOWN MISS" + }, + { + "offsets": [ + -46, + 27 + ], + "loop": false, + "fps": 24, + "anim": "singUPmiss", + "indices": [], + "name": "BF NOTE UP MISS" + }, + { + "offsets": [ + -44, + 22 + ], + "loop": false, + "fps": 24, + "anim": "singRIGHTmiss", + "indices": [], + "name": "BF NOTE RIGHT MISS" + }, + { + "offsets": [ + -3, + 5 + ], + "loop": false, + "fps": 24, + "anim": "hey", + "indices": [], + "name": "BF HEY" + }, + { + "offsets": [ + 14, + 18 + ], + "loop": false, + "fps": 24, + "anim": "hurt", + "indices": [], + "name": "BF hit" + }, + { + "offsets": [ + -4, + 0 + ], + "loop": true, + "fps": 24, + "anim": "scared", + "indices": [], + "name": "BF idle shaking" + }, + { + "offsets": [ + -10, + -16 + ], + "loop": false, + "fps": 24, + "anim": "dodge", + "indices": [], + "name": "boyfriend dodge" + }, + { + "offsets": [ + 294, + 267 + ], + "loop": false, + "fps": 24, + "anim": "attack", + "indices": [], + "name": "boyfriend attack" + }, + { + "offsets": [ + -40, + -40 + ], + "loop": false, + "fps": 24, + "anim": "pre-attack", + "indices": [], + "name": "bf pre attack" + } + ], + "no_antialiasing": false, + "image": "characters/bf/BOYFRIEND", + "position": [ + 0, + 350 + ], + "healthicon": "bf", + "flip_x": true, + "healthbar_colors": [ + 49, + 176, + 209 + ], + "camera_position": [ + 0, + 0 + ], + "sing_duration": 4, + "scale": 1 +} \ No newline at end of file diff --git a/base_game/characters/dad.json b/base_game/characters/dad.json new file mode 100644 index 0000000..a757143 --- /dev/null +++ b/base_game/characters/dad.json @@ -0,0 +1,142 @@ +{ + "animations": [ + { + "offsets": [ + -9, + 10 + ], + "loop": false, + "anim": "singLEFT", + "fps": 24, + "name": "Dad Sing Note LEFT", + "indices": [] + }, + { + "offsets": [ + 0, + -30 + ], + "loop": false, + "anim": "singDOWN", + "fps": 24, + "name": "Dad Sing Note DOWN", + "indices": [] + }, + { + "offsets": [ + -6, + 50 + ], + "loop": false, + "anim": "singUP", + "fps": 24, + "name": "Dad Sing Note UP", + "indices": [] + }, + { + "offsets": [ + 0, + 27 + ], + "loop": false, + "anim": "singRIGHT", + "fps": 24, + "name": "Dad Sing Note RIGHT", + "indices": [] + }, + { + "offsets": [ + 0, + 0 + ], + "loop": false, + "anim": "idle", + "fps": 24, + "name": "Dad idle dance", + "indices": [] + }, + { + "offsets": [ + 0, + 0 + ], + "loop": true, + "fps": 24, + "anim": "idle-loop", + "indices": [ + 10, + 11, + 12, + 12 + ], + "name": "Dad idle dance" + }, + { + "offsets": [ + -9, + 10 + ], + "loop": true, + "fps": 24, + "anim": "singLEFT-loop", + "indices": [ + 12, + 13, + 14, + 15 + ], + "name": "Dad Sing Note LEFT" + }, + { + "offsets": [ + 0, + 27 + ], + "loop": true, + "fps": 24, + "anim": "singRIGHT-loop", + "indices": [ + 15, + 16, + 17, + 18 + ], + "name": "Dad Sing Note RIGHT" + }, + { + "offsets": [ + -6, + 50 + ], + "loop": true, + "fps": 24, + "anim": "singUP-loop", + "indices": [ + 56, + 57, + 58, + 59 + ], + "name": "Dad Sing Note UP" + } + ], + "no_antialiasing": false, + "image": "characters/daddy/DADDY_DEAREST", + "position": [ + 0, + 0 + ], + "healthicon": "dad", + "flip_x": false, + "healthbar_colors": [ + 175, + 102, + 206 + ], + "camera_position": [ + 0, + 0 + ], + "sing_duration": 6.1, + "scale": 1 +} \ No newline at end of file diff --git a/assets/preload/characters/gf-car.json b/base_game/characters/gf-car.json similarity index 100% rename from assets/preload/characters/gf-car.json rename to base_game/characters/gf-car.json diff --git a/assets/preload/characters/gf-christmas.json b/base_game/characters/gf-christmas.json similarity index 100% rename from assets/preload/characters/gf-christmas.json rename to base_game/characters/gf-christmas.json diff --git a/assets/preload/characters/gf-pixel.json b/base_game/characters/gf-pixel.json similarity index 100% rename from assets/preload/characters/gf-pixel.json rename to base_game/characters/gf-pixel.json diff --git a/base_game/characters/gf-playable.json b/base_game/characters/gf-playable.json new file mode 100644 index 0000000..8767d59 --- /dev/null +++ b/base_game/characters/gf-playable.json @@ -0,0 +1,210 @@ +{ + "animations": [ + { + "offsets": [ + 3, + 0 + ], + "loop": false, + "anim": "cheer", + "fps": 24, + "name": "GF Cheer", + "indices": [] + }, + { + "offsets": [ + 0, + -19 + ], + "loop": false, + "anim": "singLEFT", + "fps": 24, + "name": "GF left note", + "indices": [] + }, + { + "offsets": [ + 0, + -20 + ], + "loop": false, + "anim": "singDOWN", + "fps": 24, + "name": "GF Down Note", + "indices": [] + }, + { + "offsets": [ + 0, + 4 + ], + "loop": false, + "anim": "singUP", + "fps": 24, + "name": "GF Up Note", + "indices": [] + }, + { + "offsets": [ + 0, + -20 + ], + "loop": false, + "anim": "singRIGHT", + "fps": 24, + "name": "GF Right Note", + "indices": [] + }, + { + "offsets": [ + -2, + -21 + ], + "loop": false, + "anim": "sad", + "fps": 24, + "name": "gf sad", + "indices": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12 + ] + }, + { + "offsets": [ + 0, + -9 + ], + "loop": false, + "anim": "danceLeft", + "fps": 24, + "name": "GF Dancing Beat", + "indices": [ + 30, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ] + }, + { + "offsets": [ + 0, + -9 + ], + "loop": false, + "anim": "danceRight", + "fps": 24, + "name": "GF Dancing Beat", + "indices": [ + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29 + ] + }, + { + "offsets": [ + 45, + -8 + ], + "loop": true, + "anim": "hairBlow", + "fps": 24, + "name": "GF Dancing Beat Hair blowing", + "indices": [ + 0, + 1, + 2, + 3 + ] + }, + { + "offsets": [ + 0, + -9 + ], + "loop": false, + "anim": "hairFall", + "fps": 24, + "name": "GF Dancing Beat Hair Landing", + "indices": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ] + }, + { + "offsets": [ + -2, + -17 + ], + "loop": true, + "anim": "scared", + "fps": 24, + "name": "GF FEAR", + "indices": [] + } + ], + "vocals_file": "", + "no_antialiasing": false, + "image": "characters/gf/GF_assets_Playable", + "position": [ + 0, + 0 + ], + "healthicon": "gf", + "flip_x": false, + "healthbar_colors": [ + 165, + 0, + 77 + ], + "camera_position": [ + 0, + 0 + ], + "sing_duration": 4, + "scale": 1 +} \ No newline at end of file diff --git a/assets/preload/characters/gf-tankmen.json b/base_game/characters/gf-tankmen.json similarity index 100% rename from assets/preload/characters/gf-tankmen.json rename to base_game/characters/gf-tankmen.json diff --git a/base_game/characters/gf.json b/base_game/characters/gf.json new file mode 100644 index 0000000..d3d630c --- /dev/null +++ b/base_game/characters/gf.json @@ -0,0 +1,209 @@ +{ + "animations": [ + { + "loop": false, + "offsets": [ + 3, + 0 + ], + "fps": 24, + "anim": "cheer", + "indices": [], + "name": "GF Cheer" + }, + { + "loop": false, + "offsets": [ + 0, + -19 + ], + "fps": 24, + "anim": "singLEFT", + "indices": [], + "name": "GF left note" + }, + { + "loop": false, + "offsets": [ + 0, + -20 + ], + "fps": 24, + "anim": "singDOWN", + "indices": [], + "name": "GF Down Note" + }, + { + "loop": false, + "offsets": [ + 0, + 4 + ], + "fps": 24, + "anim": "singUP", + "indices": [], + "name": "GF Up Note" + }, + { + "loop": false, + "offsets": [ + 0, + -20 + ], + "fps": 24, + "anim": "singRIGHT", + "indices": [], + "name": "GF Right Note" + }, + { + "loop": false, + "offsets": [ + -2, + -21 + ], + "fps": 24, + "anim": "sad", + "indices": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12 + ], + "name": "gf sad" + }, + { + "loop": false, + "offsets": [ + 0, + -9 + ], + "fps": 24, + "anim": "danceLeft", + "indices": [ + 30, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "name": "GF Dancing Beat" + }, + { + "loop": false, + "offsets": [ + 0, + -9 + ], + "fps": 24, + "anim": "danceRight", + "indices": [ + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29 + ], + "name": "GF Dancing Beat" + }, + { + "loop": true, + "offsets": [ + 45, + -8 + ], + "fps": 24, + "anim": "hairBlow", + "indices": [ + 0, + 1, + 2, + 3 + ], + "name": "GF Dancing Beat Hair blowing" + }, + { + "loop": false, + "offsets": [ + 0, + -9 + ], + "fps": 24, + "anim": "hairFall", + "indices": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ], + "name": "GF Dancing Beat Hair Landing" + }, + { + "loop": true, + "offsets": [ + -2, + -17 + ], + "fps": 24, + "anim": "scared", + "indices": [], + "name": "GF FEAR" + } + ], + "no_antialiasing": false, + "image": "characters/gf/GF_assets", + "position": [ + 0, + 0 + ], + "healthicon": "gf", + "flip_x": false, + "healthbar_colors": [ + 165, + 0, + 77 + ], + "camera_position": [ + 0, + 0 + ], + "sing_duration": 4, + "scale": 1 +} \ No newline at end of file diff --git a/assets/preload/characters/mom-car.json b/base_game/characters/mom-car.json similarity index 100% rename from assets/preload/characters/mom-car.json rename to base_game/characters/mom-car.json diff --git a/assets/preload/characters/mom.json b/base_game/characters/mom.json similarity index 100% rename from assets/preload/characters/mom.json rename to base_game/characters/mom.json diff --git a/assets/preload/characters/monster-christmas.json b/base_game/characters/monster-christmas.json similarity index 100% rename from assets/preload/characters/monster-christmas.json rename to base_game/characters/monster-christmas.json diff --git a/assets/preload/characters/monster.json b/base_game/characters/monster.json similarity index 100% rename from assets/preload/characters/monster.json rename to base_game/characters/monster.json diff --git a/assets/preload/characters/parents-christmas.json b/base_game/characters/parents-christmas.json similarity index 100% rename from assets/preload/characters/parents-christmas.json rename to base_game/characters/parents-christmas.json diff --git a/assets/preload/characters/pico-player.json b/base_game/characters/pico-player.json similarity index 100% rename from assets/preload/characters/pico-player.json rename to base_game/characters/pico-player.json diff --git a/assets/preload/characters/pico-speaker.json b/base_game/characters/pico-speaker.json similarity index 100% rename from assets/preload/characters/pico-speaker.json rename to base_game/characters/pico-speaker.json diff --git a/assets/preload/characters/pico.json b/base_game/characters/pico.json similarity index 100% rename from assets/preload/characters/pico.json rename to base_game/characters/pico.json diff --git a/assets/preload/characters/senpai-angry.json b/base_game/characters/senpai-angry.json similarity index 100% rename from assets/preload/characters/senpai-angry.json rename to base_game/characters/senpai-angry.json diff --git a/assets/preload/characters/senpai.json b/base_game/characters/senpai.json similarity index 100% rename from assets/preload/characters/senpai.json rename to base_game/characters/senpai.json diff --git a/assets/preload/characters/spirit.json b/base_game/characters/spirit.json similarity index 100% rename from assets/preload/characters/spirit.json rename to base_game/characters/spirit.json diff --git a/assets/preload/characters/spooky.json b/base_game/characters/spooky.json similarity index 100% rename from assets/preload/characters/spooky.json rename to base_game/characters/spooky.json diff --git a/assets/preload/characters/tankman-player.json b/base_game/characters/tankman-player.json similarity index 100% rename from assets/preload/characters/tankman-player.json rename to base_game/characters/tankman-player.json diff --git a/assets/preload/characters/tankman.json b/base_game/characters/tankman.json similarity index 100% rename from assets/preload/characters/tankman.json rename to base_game/characters/tankman.json diff --git a/assets/preload/data/blammed/blammed-easy.json b/base_game/data/blammed/blammed-easy.json similarity index 100% rename from assets/preload/data/blammed/blammed-easy.json rename to base_game/data/blammed/blammed-easy.json diff --git a/assets/preload/data/blammed/blammed-hard.json b/base_game/data/blammed/blammed-hard.json similarity index 100% rename from assets/preload/data/blammed/blammed-hard.json rename to base_game/data/blammed/blammed-hard.json diff --git a/assets/preload/data/blammed/blammed.json b/base_game/data/blammed/blammed.json similarity index 100% rename from assets/preload/data/blammed/blammed.json rename to base_game/data/blammed/blammed.json diff --git a/assets/preload/data/blammed/events.json b/base_game/data/blammed/events.json similarity index 100% rename from assets/preload/data/blammed/events.json rename to base_game/data/blammed/events.json diff --git a/assets/preload/data/bopeebo/bopeebo-boobs.json b/base_game/data/bopeebo/bopeebo-boobs.json similarity index 100% rename from assets/preload/data/bopeebo/bopeebo-boobs.json rename to base_game/data/bopeebo/bopeebo-boobs.json diff --git a/assets/preload/data/bopeebo/bopeebo-easy.json b/base_game/data/bopeebo/bopeebo-easy.json similarity index 100% rename from assets/preload/data/bopeebo/bopeebo-easy.json rename to base_game/data/bopeebo/bopeebo-easy.json diff --git a/assets/preload/data/bopeebo/bopeebo-hard.json b/base_game/data/bopeebo/bopeebo-hard.json similarity index 100% rename from assets/preload/data/bopeebo/bopeebo-hard.json rename to base_game/data/bopeebo/bopeebo-hard.json diff --git a/assets/preload/data/bopeebo/bopeebo.json b/base_game/data/bopeebo/bopeebo.json similarity index 100% rename from assets/preload/data/bopeebo/bopeebo.json rename to base_game/data/bopeebo/bopeebo.json diff --git a/assets/preload/data/bopeebo/events.json b/base_game/data/bopeebo/events.json similarity index 100% rename from assets/preload/data/bopeebo/events.json rename to base_game/data/bopeebo/events.json diff --git a/base_game/data/characterList.txt b/base_game/data/characterList.txt new file mode 100644 index 0000000..c4177d9 --- /dev/null +++ b/base_game/data/characterList.txt @@ -0,0 +1,26 @@ +bf +dad +gf +spooky +pico +mom +mom-car +bf-car +gf-car +parents-christmas +monster-christmas +bf-christmas +gf-christmas +monster +bf-pixel +gf-pixel +senpai +senpai-angry +spirit +bf-pixel-opponent +gf-tankmen +tankman +pico-speaker +bf-holding-gf +pico-player +tankman-player \ No newline at end of file diff --git a/assets/preload/data/cocoa/cocoa-easy.json b/base_game/data/cocoa/cocoa-easy.json similarity index 100% rename from assets/preload/data/cocoa/cocoa-easy.json rename to base_game/data/cocoa/cocoa-easy.json diff --git a/assets/preload/data/cocoa/cocoa-hard.json b/base_game/data/cocoa/cocoa-hard.json similarity index 100% rename from assets/preload/data/cocoa/cocoa-hard.json rename to base_game/data/cocoa/cocoa-hard.json diff --git a/assets/preload/data/cocoa/cocoa.json b/base_game/data/cocoa/cocoa.json similarity index 100% rename from assets/preload/data/cocoa/cocoa.json rename to base_game/data/cocoa/cocoa.json diff --git a/assets/preload/data/cocoa/events.json b/base_game/data/cocoa/events.json similarity index 100% rename from assets/preload/data/cocoa/events.json rename to base_game/data/cocoa/events.json diff --git a/assets/preload/data/dad-battle/dad-battle-easy.json b/base_game/data/dad-battle/dad-battle-easy.json similarity index 100% rename from assets/preload/data/dad-battle/dad-battle-easy.json rename to base_game/data/dad-battle/dad-battle-easy.json diff --git a/assets/preload/data/dad-battle/dad-battle-hard.json b/base_game/data/dad-battle/dad-battle-hard.json similarity index 100% rename from assets/preload/data/dad-battle/dad-battle-hard.json rename to base_game/data/dad-battle/dad-battle-hard.json diff --git a/assets/preload/data/dad-battle/dad-battle.json b/base_game/data/dad-battle/dad-battle.json similarity index 100% rename from assets/preload/data/dad-battle/dad-battle.json rename to base_game/data/dad-battle/dad-battle.json diff --git a/assets/preload/data/dad-battle/events.json b/base_game/data/dad-battle/events.json similarity index 100% rename from assets/preload/data/dad-battle/events.json rename to base_game/data/dad-battle/events.json diff --git a/base_game/data/data-goes-here.txt b/base_game/data/data-goes-here.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/preload/data/eggnog/eggnog-easy.json b/base_game/data/eggnog/eggnog-easy.json similarity index 100% rename from assets/preload/data/eggnog/eggnog-easy.json rename to base_game/data/eggnog/eggnog-easy.json diff --git a/assets/preload/data/eggnog/eggnog-hard.json b/base_game/data/eggnog/eggnog-hard.json similarity index 100% rename from assets/preload/data/eggnog/eggnog-hard.json rename to base_game/data/eggnog/eggnog-hard.json diff --git a/assets/preload/data/eggnog/eggnog.json b/base_game/data/eggnog/eggnog.json similarity index 100% rename from assets/preload/data/eggnog/eggnog.json rename to base_game/data/eggnog/eggnog.json diff --git a/assets/preload/data/eggnog/events.json b/base_game/data/eggnog/events.json similarity index 100% rename from assets/preload/data/eggnog/events.json rename to base_game/data/eggnog/events.json diff --git a/base_game/data/freeplayColors.txt b/base_game/data/freeplayColors.txt new file mode 100644 index 0000000..554ec09 --- /dev/null +++ b/base_game/data/freeplayColors.txt @@ -0,0 +1,7 @@ +0xFF9271FD +0xFF9271FD +0xFF223344 +0xFF941653 +0xFFFC96D7 +0xFFA0D1FF +0xFFFF78BF \ No newline at end of file diff --git a/assets/preload/data/fresh/events.json b/base_game/data/fresh/events.json similarity index 100% rename from assets/preload/data/fresh/events.json rename to base_game/data/fresh/events.json diff --git a/assets/preload/data/fresh/fresh-easy.json b/base_game/data/fresh/fresh-easy.json similarity index 100% rename from assets/preload/data/fresh/fresh-easy.json rename to base_game/data/fresh/fresh-easy.json diff --git a/assets/preload/data/fresh/fresh-hard.json b/base_game/data/fresh/fresh-hard.json similarity index 100% rename from assets/preload/data/fresh/fresh-hard.json rename to base_game/data/fresh/fresh-hard.json diff --git a/assets/preload/data/fresh/fresh.json b/base_game/data/fresh/fresh.json similarity index 100% rename from assets/preload/data/fresh/fresh.json rename to base_game/data/fresh/fresh.json diff --git a/assets/preload/data/guns/guns-easy.json b/base_game/data/guns/guns-easy.json similarity index 100% rename from assets/preload/data/guns/guns-easy.json rename to base_game/data/guns/guns-easy.json diff --git a/assets/preload/data/guns/guns-hard.json b/base_game/data/guns/guns-hard.json similarity index 100% rename from assets/preload/data/guns/guns-hard.json rename to base_game/data/guns/guns-hard.json diff --git a/assets/preload/data/guns/guns.json b/base_game/data/guns/guns.json similarity index 100% rename from assets/preload/data/guns/guns.json rename to base_game/data/guns/guns.json diff --git a/assets/preload/data/high/events.json b/base_game/data/high/events.json similarity index 100% rename from assets/preload/data/high/events.json rename to base_game/data/high/events.json diff --git a/assets/preload/data/high/high-easy.json b/base_game/data/high/high-easy.json similarity index 100% rename from assets/preload/data/high/high-easy.json rename to base_game/data/high/high-easy.json diff --git a/assets/preload/data/high/high-hard.json b/base_game/data/high/high-hard.json similarity index 100% rename from assets/preload/data/high/high-hard.json rename to base_game/data/high/high-hard.json diff --git a/assets/preload/data/high/high.json b/base_game/data/high/high.json similarity index 100% rename from assets/preload/data/high/high.json rename to base_game/data/high/high.json diff --git a/base_game/data/introText.txt b/base_game/data/introText.txt new file mode 100644 index 0000000..82f29aa --- /dev/null +++ b/base_game/data/introText.txt @@ -0,0 +1,68 @@ +shoutouts to tom fulp--lmao +Ludum dare--extraordinaire +cyberzone--coming soon +love to thriftman--swag +ultimate rhythm gaming--probably +dope ass game--playstation magazine +in loving memory of--henryeyes +dancin--forever +funkin--forever +ritz dx--rest in peace lol +rate five--pls no blam +rhythm gaming--ultimate +game of the year--forever +you already know--we really out here +rise and grind--love to luis +like parappa--but cooler +album of the year--chuckie finster +free gitaroo man--with love to wandaboy +better than geometry dash--fight me robtop +kiddbrute for president--vote now +play dead estate--on newgrounds +this is a god damn prototype--we workin on it okay +women are real--this is official +too over exposed--newgrounds cant handle us +Hatsune Miku--biggest inspiration +too many people--my head hurts +newgrounds--forever +refined taste in music--if i say so myself +his name isnt keith--dumb eggy lol +his name isnt evan--silly tiktok +stream chuckie finster--on spotify +never forget to--pray to god +dont play rust--we only funkin +good bye--my penis +dababy--biggest inspiration +fashionably late--but here it is +yooooooooooo--yooooooooo +pico funny--pico funny +updates each friday--on time every time +shoutouts to mason--for da homies +bonk--get in the discord call +this isn't a copy of ke--*vine boom* +we do a little trolling--h +sexiest engine--in da block +he's named evilsk8er--not evilsker +actual functional--input system +its nerf--or nothing +penis--balls even +fnf is not for kids--go outside +little herobrine--im cumming in your mom +what you know about--rolling down in the deep +oh god i'm white--and i'm on twitter +i gotta--bucket of chicken +dream stans--literally scare me +weeg mod--check it out +what da dog--doin +go mango go--go mango go +go pico yeah yeah--*twerks cutely* +forever engine--my beloved +wash your hands--stay safe bro +wear a mask--even if vaccinated +you dont understand--i hate society as a whole +psych engine port--fatherless behavior +i made a psych port--free clout yay +wanna work on my fnf--fnf mod +gazozoz--sickest bomb recoveries +atvdriver--the rats are coming +pessy--pure perfection diff --git a/assets/preload/data/main-view.xml b/base_game/data/main-view.xml similarity index 100% rename from assets/preload/data/main-view.xml rename to base_game/data/main-view.xml diff --git a/assets/preload/data/milf/events.json b/base_game/data/milf/events.json similarity index 100% rename from assets/preload/data/milf/events.json rename to base_game/data/milf/events.json diff --git a/assets/preload/data/milf/milf-easy.json b/base_game/data/milf/milf-easy.json similarity index 100% rename from assets/preload/data/milf/milf-easy.json rename to base_game/data/milf/milf-easy.json diff --git a/assets/preload/data/milf/milf-hard.json b/base_game/data/milf/milf-hard.json similarity index 100% rename from assets/preload/data/milf/milf-hard.json rename to base_game/data/milf/milf-hard.json diff --git a/assets/preload/data/milf/milf.json b/base_game/data/milf/milf.json similarity index 100% rename from assets/preload/data/milf/milf.json rename to base_game/data/milf/milf.json diff --git a/assets/preload/data/monster/monster-easy.json b/base_game/data/monster/monster-easy.json similarity index 100% rename from assets/preload/data/monster/monster-easy.json rename to base_game/data/monster/monster-easy.json diff --git a/assets/preload/data/monster/monster-hard.json b/base_game/data/monster/monster-hard.json similarity index 100% rename from assets/preload/data/monster/monster-hard.json rename to base_game/data/monster/monster-hard.json diff --git a/assets/preload/data/monster/monster.json b/base_game/data/monster/monster.json similarity index 100% rename from assets/preload/data/monster/monster.json rename to base_game/data/monster/monster.json diff --git a/assets/preload/data/philly-nice/events.json b/base_game/data/philly-nice/events.json similarity index 100% rename from assets/preload/data/philly-nice/events.json rename to base_game/data/philly-nice/events.json diff --git a/assets/preload/data/philly-nice/philly-nice-easy.json b/base_game/data/philly-nice/philly-nice-easy.json similarity index 100% rename from assets/preload/data/philly-nice/philly-nice-easy.json rename to base_game/data/philly-nice/philly-nice-easy.json diff --git a/assets/preload/data/philly-nice/philly-nice-hard.json b/base_game/data/philly-nice/philly-nice-hard.json similarity index 100% rename from assets/preload/data/philly-nice/philly-nice-hard.json rename to base_game/data/philly-nice/philly-nice-hard.json diff --git a/assets/preload/data/philly-nice/philly-nice.json b/base_game/data/philly-nice/philly-nice.json similarity index 100% rename from assets/preload/data/philly-nice/philly-nice.json rename to base_game/data/philly-nice/philly-nice.json diff --git a/assets/preload/data/pico/pico-easy.json b/base_game/data/pico/pico-easy.json similarity index 100% rename from assets/preload/data/pico/pico-easy.json rename to base_game/data/pico/pico-easy.json diff --git a/assets/preload/data/pico/pico-hard.json b/base_game/data/pico/pico-hard.json similarity index 100% rename from assets/preload/data/pico/pico-hard.json rename to base_game/data/pico/pico-hard.json diff --git a/assets/preload/data/pico/pico.json b/base_game/data/pico/pico.json similarity index 100% rename from assets/preload/data/pico/pico.json rename to base_game/data/pico/pico.json diff --git a/assets/preload/data/ridge/ridge.json b/base_game/data/ridge/ridge.json similarity index 100% rename from assets/preload/data/ridge/ridge.json rename to base_game/data/ridge/ridge.json diff --git a/assets/preload/data/roses/events.json b/base_game/data/roses/events.json similarity index 100% rename from assets/preload/data/roses/events.json rename to base_game/data/roses/events.json diff --git a/assets/preload/data/roses/roses-easy.json b/base_game/data/roses/roses-easy.json similarity index 100% rename from assets/preload/data/roses/roses-easy.json rename to base_game/data/roses/roses-easy.json diff --git a/assets/preload/data/roses/roses-hard.json b/base_game/data/roses/roses-hard.json similarity index 100% rename from assets/preload/data/roses/roses-hard.json rename to base_game/data/roses/roses-hard.json diff --git a/assets/preload/data/roses/roses.json b/base_game/data/roses/roses.json similarity index 100% rename from assets/preload/data/roses/roses.json rename to base_game/data/roses/roses.json diff --git a/assets/preload/data/roses/rosesDialogue.txt b/base_game/data/roses/rosesDialogue.txt similarity index 100% rename from assets/preload/data/roses/rosesDialogue.txt rename to base_game/data/roses/rosesDialogue.txt diff --git a/assets/preload/data/satin-panties/events.json b/base_game/data/satin-panties/events.json similarity index 100% rename from assets/preload/data/satin-panties/events.json rename to base_game/data/satin-panties/events.json diff --git a/assets/preload/data/satin-panties/satin-panties-easy.json b/base_game/data/satin-panties/satin-panties-easy.json similarity index 100% rename from assets/preload/data/satin-panties/satin-panties-easy.json rename to base_game/data/satin-panties/satin-panties-easy.json diff --git a/assets/preload/data/satin-panties/satin-panties-hard.json b/base_game/data/satin-panties/satin-panties-hard.json similarity index 100% rename from assets/preload/data/satin-panties/satin-panties-hard.json rename to base_game/data/satin-panties/satin-panties-hard.json diff --git a/assets/preload/data/satin-panties/satin-panties.json b/base_game/data/satin-panties/satin-panties.json similarity index 100% rename from assets/preload/data/satin-panties/satin-panties.json rename to base_game/data/satin-panties/satin-panties.json diff --git a/assets/preload/data/senpai/senpai-easy.json b/base_game/data/senpai/senpai-easy.json similarity index 100% rename from assets/preload/data/senpai/senpai-easy.json rename to base_game/data/senpai/senpai-easy.json diff --git a/assets/preload/data/senpai/senpai-hard.json b/base_game/data/senpai/senpai-hard.json similarity index 100% rename from assets/preload/data/senpai/senpai-hard.json rename to base_game/data/senpai/senpai-hard.json diff --git a/assets/preload/data/senpai/senpai.json b/base_game/data/senpai/senpai.json similarity index 100% rename from assets/preload/data/senpai/senpai.json rename to base_game/data/senpai/senpai.json diff --git a/assets/preload/data/senpai/senpaiDialogue.txt b/base_game/data/senpai/senpaiDialogue.txt similarity index 100% rename from assets/preload/data/senpai/senpaiDialogue.txt rename to base_game/data/senpai/senpaiDialogue.txt diff --git a/assets/preload/data/smash/smash.json b/base_game/data/smash/smash.json similarity index 100% rename from assets/preload/data/smash/smash.json rename to base_game/data/smash/smash.json diff --git a/assets/preload/data/south/south-easy.json b/base_game/data/south/south-easy.json similarity index 100% rename from assets/preload/data/south/south-easy.json rename to base_game/data/south/south-easy.json diff --git a/assets/preload/data/south/south-hard.json b/base_game/data/south/south-hard.json similarity index 100% rename from assets/preload/data/south/south-hard.json rename to base_game/data/south/south-hard.json diff --git a/assets/preload/data/south/south.json b/base_game/data/south/south.json similarity index 100% rename from assets/preload/data/south/south.json rename to base_game/data/south/south.json diff --git a/base_game/data/specialThanks.txt b/base_game/data/specialThanks.txt new file mode 100644 index 0000000..bbef17b --- /dev/null +++ b/base_game/data/specialThanks.txt @@ -0,0 +1,23 @@ +wanda +fizzd +kiddbrute +HenryEYES +Clone Hero +Tom Fulp +StuffedWombat +mmatt_ugh +Squidly +Luis +GeoKureli +Will Blanton +SrPelo +Austin East +Krystin, Kaye-lyn, Cassidy, Mack, Levi, and Jasmine. +Laurel +bbpanzu +Etika +Foamymuffin (insert travis scott lyrics here) +SiIvaGunner +Masaya Matsuura + +BIT BOY - MIKE WELSH \ No newline at end of file diff --git a/assets/preload/data/spookeez/spookeez-easy.json b/base_game/data/spookeez/spookeez-easy.json similarity index 100% rename from assets/preload/data/spookeez/spookeez-easy.json rename to base_game/data/spookeez/spookeez-easy.json diff --git a/assets/preload/data/spookeez/spookeez-hard.json b/base_game/data/spookeez/spookeez-hard.json similarity index 100% rename from assets/preload/data/spookeez/spookeez-hard.json rename to base_game/data/spookeez/spookeez-hard.json diff --git a/assets/preload/data/spookeez/spookeez.json b/base_game/data/spookeez/spookeez.json similarity index 100% rename from assets/preload/data/spookeez/spookeez.json rename to base_game/data/spookeez/spookeez.json diff --git a/base_game/data/stageList.txt b/base_game/data/stageList.txt new file mode 100644 index 0000000..9e41c09 --- /dev/null +++ b/base_game/data/stageList.txt @@ -0,0 +1,9 @@ +stage +spooky +philly +limo +mall +mallEvil +school +schoolEvil +tank \ No newline at end of file diff --git a/assets/preload/data/stress/events.json b/base_game/data/stress/events.json similarity index 100% rename from assets/preload/data/stress/events.json rename to base_game/data/stress/events.json diff --git a/assets/preload/data/stress/picospeaker.json b/base_game/data/stress/picospeaker.json similarity index 100% rename from assets/preload/data/stress/picospeaker.json rename to base_game/data/stress/picospeaker.json diff --git a/assets/preload/data/stress/stress-easy.json b/base_game/data/stress/stress-easy.json similarity index 100% rename from assets/preload/data/stress/stress-easy.json rename to base_game/data/stress/stress-easy.json diff --git a/assets/preload/data/stress/stress-hard.json b/base_game/data/stress/stress-hard.json similarity index 100% rename from assets/preload/data/stress/stress-hard.json rename to base_game/data/stress/stress-hard.json diff --git a/assets/preload/data/stress/stress.json b/base_game/data/stress/stress.json similarity index 100% rename from assets/preload/data/stress/stress.json rename to base_game/data/stress/stress.json diff --git a/assets/preload/data/thorns/events.json b/base_game/data/thorns/events.json similarity index 100% rename from assets/preload/data/thorns/events.json rename to base_game/data/thorns/events.json diff --git a/assets/preload/data/thorns/thorns-easy.json b/base_game/data/thorns/thorns-easy.json similarity index 100% rename from assets/preload/data/thorns/thorns-easy.json rename to base_game/data/thorns/thorns-easy.json diff --git a/assets/preload/data/thorns/thorns-hard.json b/base_game/data/thorns/thorns-hard.json similarity index 100% rename from assets/preload/data/thorns/thorns-hard.json rename to base_game/data/thorns/thorns-hard.json diff --git a/assets/preload/data/thorns/thorns.json b/base_game/data/thorns/thorns.json similarity index 100% rename from assets/preload/data/thorns/thorns.json rename to base_game/data/thorns/thorns.json diff --git a/assets/preload/data/thorns/thornsDialogue.txt b/base_game/data/thorns/thornsDialogue.txt similarity index 100% rename from assets/preload/data/thorns/thornsDialogue.txt rename to base_game/data/thorns/thornsDialogue.txt diff --git a/assets/preload/data/tutorial/events.json b/base_game/data/tutorial/events.json similarity index 100% rename from assets/preload/data/tutorial/events.json rename to base_game/data/tutorial/events.json diff --git a/assets/preload/data/tutorial/tutorial-easy.json b/base_game/data/tutorial/tutorial-easy.json similarity index 100% rename from assets/preload/data/tutorial/tutorial-easy.json rename to base_game/data/tutorial/tutorial-easy.json diff --git a/assets/preload/data/tutorial/tutorial-hard.json b/base_game/data/tutorial/tutorial-hard.json similarity index 100% rename from assets/preload/data/tutorial/tutorial-hard.json rename to base_game/data/tutorial/tutorial-hard.json diff --git a/assets/preload/data/tutorial/tutorial.json b/base_game/data/tutorial/tutorial.json similarity index 100% rename from assets/preload/data/tutorial/tutorial.json rename to base_game/data/tutorial/tutorial.json diff --git a/assets/preload/data/ugh/events.json b/base_game/data/ugh/events.json similarity index 100% rename from assets/preload/data/ugh/events.json rename to base_game/data/ugh/events.json diff --git a/assets/preload/data/ugh/ugh-easy.json b/base_game/data/ugh/ugh-easy.json similarity index 100% rename from assets/preload/data/ugh/ugh-easy.json rename to base_game/data/ugh/ugh-easy.json diff --git a/assets/preload/data/ugh/ugh-hard.json b/base_game/data/ugh/ugh-hard.json similarity index 100% rename from assets/preload/data/ugh/ugh-hard.json rename to base_game/data/ugh/ugh-hard.json diff --git a/assets/preload/data/ugh/ugh.json b/base_game/data/ugh/ugh.json similarity index 100% rename from assets/preload/data/ugh/ugh.json rename to base_game/data/ugh/ugh.json diff --git a/assets/preload/data/winter-horrorland/events.json b/base_game/data/winter-horrorland/events.json similarity index 100% rename from assets/preload/data/winter-horrorland/events.json rename to base_game/data/winter-horrorland/events.json diff --git a/assets/preload/data/winter-horrorland/winter-horrorland-easy.json b/base_game/data/winter-horrorland/winter-horrorland-easy.json similarity index 100% rename from assets/preload/data/winter-horrorland/winter-horrorland-easy.json rename to base_game/data/winter-horrorland/winter-horrorland-easy.json diff --git a/assets/preload/data/winter-horrorland/winter-horrorland-hard.json b/base_game/data/winter-horrorland/winter-horrorland-hard.json similarity index 100% rename from assets/preload/data/winter-horrorland/winter-horrorland-hard.json rename to base_game/data/winter-horrorland/winter-horrorland-hard.json diff --git a/assets/preload/data/winter-horrorland/winter-horrorland.json b/base_game/data/winter-horrorland/winter-horrorland.json similarity index 100% rename from assets/preload/data/winter-horrorland/winter-horrorland.json rename to base_game/data/winter-horrorland/winter-horrorland.json diff --git a/assets/preload/images/bgs/christmas/bgEscalator.png b/base_game/images/bgs/christmas/bgEscalator.png similarity index 100% rename from assets/preload/images/bgs/christmas/bgEscalator.png rename to base_game/images/bgs/christmas/bgEscalator.png diff --git a/assets/preload/images/bgs/christmas/bgWalls.png b/base_game/images/bgs/christmas/bgWalls.png similarity index 100% rename from assets/preload/images/bgs/christmas/bgWalls.png rename to base_game/images/bgs/christmas/bgWalls.png diff --git a/assets/preload/images/bgs/christmas/bottomBop.png b/base_game/images/bgs/christmas/bottomBop.png similarity index 100% rename from assets/preload/images/bgs/christmas/bottomBop.png rename to base_game/images/bgs/christmas/bottomBop.png diff --git a/assets/preload/images/bgs/christmas/bottomBop.xml b/base_game/images/bgs/christmas/bottomBop.xml similarity index 100% rename from assets/preload/images/bgs/christmas/bottomBop.xml rename to base_game/images/bgs/christmas/bottomBop.xml diff --git a/assets/preload/images/bgs/christmas/christmasTree.png b/base_game/images/bgs/christmas/christmasTree.png similarity index 100% rename from assets/preload/images/bgs/christmas/christmasTree.png rename to base_game/images/bgs/christmas/christmasTree.png diff --git a/assets/preload/images/bgs/christmas/evilBG.png b/base_game/images/bgs/christmas/evilBG.png similarity index 100% rename from assets/preload/images/bgs/christmas/evilBG.png rename to base_game/images/bgs/christmas/evilBG.png diff --git a/assets/preload/images/bgs/christmas/evilSnow.png b/base_game/images/bgs/christmas/evilSnow.png similarity index 100% rename from assets/preload/images/bgs/christmas/evilSnow.png rename to base_game/images/bgs/christmas/evilSnow.png diff --git a/assets/preload/images/bgs/christmas/evilTree.png b/base_game/images/bgs/christmas/evilTree.png similarity index 100% rename from assets/preload/images/bgs/christmas/evilTree.png rename to base_game/images/bgs/christmas/evilTree.png diff --git a/assets/preload/images/bgs/christmas/fgSnow.png b/base_game/images/bgs/christmas/fgSnow.png similarity index 100% rename from assets/preload/images/bgs/christmas/fgSnow.png rename to base_game/images/bgs/christmas/fgSnow.png diff --git a/assets/preload/images/bgs/christmas/santa.png b/base_game/images/bgs/christmas/santa.png similarity index 100% rename from assets/preload/images/bgs/christmas/santa.png rename to base_game/images/bgs/christmas/santa.png diff --git a/assets/preload/images/bgs/christmas/santa.xml b/base_game/images/bgs/christmas/santa.xml similarity index 100% rename from assets/preload/images/bgs/christmas/santa.xml rename to base_game/images/bgs/christmas/santa.xml diff --git a/assets/preload/images/bgs/christmas/upperBop.png b/base_game/images/bgs/christmas/upperBop.png similarity index 100% rename from assets/preload/images/bgs/christmas/upperBop.png rename to base_game/images/bgs/christmas/upperBop.png diff --git a/assets/preload/images/bgs/christmas/upperBop.xml b/base_game/images/bgs/christmas/upperBop.xml similarity index 100% rename from assets/preload/images/bgs/christmas/upperBop.xml rename to base_game/images/bgs/christmas/upperBop.xml diff --git a/assets/preload/images/bgs/limo/bgLimo.png b/base_game/images/bgs/limo/bgLimo.png similarity index 100% rename from assets/preload/images/bgs/limo/bgLimo.png rename to base_game/images/bgs/limo/bgLimo.png diff --git a/assets/preload/images/bgs/limo/bgLimo.xml b/base_game/images/bgs/limo/bgLimo.xml similarity index 100% rename from assets/preload/images/bgs/limo/bgLimo.xml rename to base_game/images/bgs/limo/bgLimo.xml diff --git a/assets/preload/images/bgs/limo/dumb.png b/base_game/images/bgs/limo/dumb.png similarity index 100% rename from assets/preload/images/bgs/limo/dumb.png rename to base_game/images/bgs/limo/dumb.png diff --git a/assets/preload/images/bgs/limo/fastCarLol.png b/base_game/images/bgs/limo/fastCarLol.png similarity index 100% rename from assets/preload/images/bgs/limo/fastCarLol.png rename to base_game/images/bgs/limo/fastCarLol.png diff --git a/assets/preload/images/bgs/limo/limoDancer.png b/base_game/images/bgs/limo/limoDancer.png similarity index 100% rename from assets/preload/images/bgs/limo/limoDancer.png rename to base_game/images/bgs/limo/limoDancer.png diff --git a/assets/preload/images/bgs/limo/limoDancer.xml b/base_game/images/bgs/limo/limoDancer.xml similarity index 100% rename from assets/preload/images/bgs/limo/limoDancer.xml rename to base_game/images/bgs/limo/limoDancer.xml diff --git a/assets/preload/images/bgs/limo/limoDrive.png b/base_game/images/bgs/limo/limoDrive.png similarity index 100% rename from assets/preload/images/bgs/limo/limoDrive.png rename to base_game/images/bgs/limo/limoDrive.png diff --git a/assets/preload/images/bgs/limo/limoDrive.xml b/base_game/images/bgs/limo/limoDrive.xml similarity index 100% rename from assets/preload/images/bgs/limo/limoDrive.xml rename to base_game/images/bgs/limo/limoDrive.xml diff --git a/assets/preload/images/bgs/limo/limoSunset.png b/base_game/images/bgs/limo/limoSunset.png similarity index 100% rename from assets/preload/images/bgs/limo/limoSunset.png rename to base_game/images/bgs/limo/limoSunset.png diff --git a/assets/preload/images/bgs/philly/behindTrain.png b/base_game/images/bgs/philly/behindTrain.png similarity index 100% rename from assets/preload/images/bgs/philly/behindTrain.png rename to base_game/images/bgs/philly/behindTrain.png diff --git a/assets/preload/images/bgs/philly/city.png b/base_game/images/bgs/philly/city.png similarity index 100% rename from assets/preload/images/bgs/philly/city.png rename to base_game/images/bgs/philly/city.png diff --git a/assets/preload/images/bgs/philly/gradient.png b/base_game/images/bgs/philly/gradient.png similarity index 100% rename from assets/preload/images/bgs/philly/gradient.png rename to base_game/images/bgs/philly/gradient.png diff --git a/assets/preload/images/bgs/philly/particle.png b/base_game/images/bgs/philly/particle.png similarity index 100% rename from assets/preload/images/bgs/philly/particle.png rename to base_game/images/bgs/philly/particle.png diff --git a/assets/preload/images/bgs/philly/sky.png b/base_game/images/bgs/philly/sky.png similarity index 100% rename from assets/preload/images/bgs/philly/sky.png rename to base_game/images/bgs/philly/sky.png diff --git a/assets/preload/images/bgs/philly/street.png b/base_game/images/bgs/philly/street.png similarity index 100% rename from assets/preload/images/bgs/philly/street.png rename to base_game/images/bgs/philly/street.png diff --git a/assets/preload/images/bgs/philly/train.png b/base_game/images/bgs/philly/train.png similarity index 100% rename from assets/preload/images/bgs/philly/train.png rename to base_game/images/bgs/philly/train.png diff --git a/assets/preload/images/bgs/philly/window.png b/base_game/images/bgs/philly/window.png similarity index 100% rename from assets/preload/images/bgs/philly/window.png rename to base_game/images/bgs/philly/window.png diff --git a/assets/preload/images/bgs/spooky/halloween_bg.png b/base_game/images/bgs/spooky/halloween_bg.png similarity index 100% rename from assets/preload/images/bgs/spooky/halloween_bg.png rename to base_game/images/bgs/spooky/halloween_bg.png diff --git a/assets/preload/images/bgs/spooky/halloween_bg.xml b/base_game/images/bgs/spooky/halloween_bg.xml similarity index 100% rename from assets/preload/images/bgs/spooky/halloween_bg.xml rename to base_game/images/bgs/spooky/halloween_bg.xml diff --git a/assets/preload/images/bgs/spooky/halloween_bg_low.png b/base_game/images/bgs/spooky/halloween_bg_low.png similarity index 100% rename from assets/preload/images/bgs/spooky/halloween_bg_low.png rename to base_game/images/bgs/spooky/halloween_bg_low.png diff --git a/assets/preload/images/bgs/tankman/tank0.png b/base_game/images/bgs/tankman/tank0.png similarity index 100% rename from assets/preload/images/bgs/tankman/tank0.png rename to base_game/images/bgs/tankman/tank0.png diff --git a/assets/preload/images/bgs/tankman/tank0.xml b/base_game/images/bgs/tankman/tank0.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tank0.xml rename to base_game/images/bgs/tankman/tank0.xml diff --git a/assets/preload/images/bgs/tankman/tank1.png b/base_game/images/bgs/tankman/tank1.png similarity index 100% rename from assets/preload/images/bgs/tankman/tank1.png rename to base_game/images/bgs/tankman/tank1.png diff --git a/assets/preload/images/bgs/tankman/tank1.xml b/base_game/images/bgs/tankman/tank1.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tank1.xml rename to base_game/images/bgs/tankman/tank1.xml diff --git a/assets/preload/images/bgs/tankman/tank2.png b/base_game/images/bgs/tankman/tank2.png similarity index 100% rename from assets/preload/images/bgs/tankman/tank2.png rename to base_game/images/bgs/tankman/tank2.png diff --git a/assets/preload/images/bgs/tankman/tank2.xml b/base_game/images/bgs/tankman/tank2.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tank2.xml rename to base_game/images/bgs/tankman/tank2.xml diff --git a/assets/preload/images/bgs/tankman/tank3.png b/base_game/images/bgs/tankman/tank3.png similarity index 100% rename from assets/preload/images/bgs/tankman/tank3.png rename to base_game/images/bgs/tankman/tank3.png diff --git a/assets/preload/images/bgs/tankman/tank3.xml b/base_game/images/bgs/tankman/tank3.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tank3.xml rename to base_game/images/bgs/tankman/tank3.xml diff --git a/assets/preload/images/bgs/tankman/tank4.png b/base_game/images/bgs/tankman/tank4.png similarity index 100% rename from assets/preload/images/bgs/tankman/tank4.png rename to base_game/images/bgs/tankman/tank4.png diff --git a/assets/preload/images/bgs/tankman/tank4.xml b/base_game/images/bgs/tankman/tank4.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tank4.xml rename to base_game/images/bgs/tankman/tank4.xml diff --git a/assets/preload/images/bgs/tankman/tank5.png b/base_game/images/bgs/tankman/tank5.png similarity index 100% rename from assets/preload/images/bgs/tankman/tank5.png rename to base_game/images/bgs/tankman/tank5.png diff --git a/assets/preload/images/bgs/tankman/tank5.xml b/base_game/images/bgs/tankman/tank5.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tank5.xml rename to base_game/images/bgs/tankman/tank5.xml diff --git a/assets/preload/images/bgs/tankman/tankBuildings.png b/base_game/images/bgs/tankman/tankBuildings.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankBuildings.png rename to base_game/images/bgs/tankman/tankBuildings.png diff --git a/assets/preload/images/bgs/tankman/tankClouds.png b/base_game/images/bgs/tankman/tankClouds.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankClouds.png rename to base_game/images/bgs/tankman/tankClouds.png diff --git a/assets/preload/images/bgs/tankman/tankGround.png b/base_game/images/bgs/tankman/tankGround.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankGround.png rename to base_game/images/bgs/tankman/tankGround.png diff --git a/assets/preload/images/bgs/tankman/tankMountains.png b/base_game/images/bgs/tankman/tankMountains.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankMountains.png rename to base_game/images/bgs/tankman/tankMountains.png diff --git a/assets/preload/images/bgs/tankman/tankRolling.png b/base_game/images/bgs/tankman/tankRolling.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankRolling.png rename to base_game/images/bgs/tankman/tankRolling.png diff --git a/assets/preload/images/bgs/tankman/tankRolling.xml b/base_game/images/bgs/tankman/tankRolling.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tankRolling.xml rename to base_game/images/bgs/tankman/tankRolling.xml diff --git a/assets/preload/images/bgs/tankman/tankRuins.png b/base_game/images/bgs/tankman/tankRuins.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankRuins.png rename to base_game/images/bgs/tankman/tankRuins.png diff --git a/assets/preload/images/bgs/tankman/tankSky.png b/base_game/images/bgs/tankman/tankSky.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankSky.png rename to base_game/images/bgs/tankman/tankSky.png diff --git a/assets/preload/images/bgs/tankman/tankWatchtower.png b/base_game/images/bgs/tankman/tankWatchtower.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankWatchtower.png rename to base_game/images/bgs/tankman/tankWatchtower.png diff --git a/assets/preload/images/bgs/tankman/tankWatchtower.xml b/base_game/images/bgs/tankman/tankWatchtower.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tankWatchtower.xml rename to base_game/images/bgs/tankman/tankWatchtower.xml diff --git a/assets/preload/images/bgs/tankman/tankmanKilled1.png b/base_game/images/bgs/tankman/tankmanKilled1.png similarity index 100% rename from assets/preload/images/bgs/tankman/tankmanKilled1.png rename to base_game/images/bgs/tankman/tankmanKilled1.png diff --git a/assets/preload/images/bgs/tankman/tankmanKilled1.xml b/base_game/images/bgs/tankman/tankmanKilled1.xml similarity index 100% rename from assets/preload/images/bgs/tankman/tankmanKilled1.xml rename to base_game/images/bgs/tankman/tankmanKilled1.xml diff --git a/base_game/images/campaign_menu_UI_assets.png b/base_game/images/campaign_menu_UI_assets.png new file mode 100644 index 0000000..80ac9d4 Binary files /dev/null and b/base_game/images/campaign_menu_UI_assets.png differ diff --git a/base_game/images/campaign_menu_UI_assets.xml b/base_game/images/campaign_menu_UI_assets.xml new file mode 100644 index 0000000..7f33b64 --- /dev/null +++ b/base_game/images/campaign_menu_UI_assets.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/base_game/images/checkboxanim.png b/base_game/images/checkboxanim.png new file mode 100644 index 0000000..2554523 Binary files /dev/null and b/base_game/images/checkboxanim.png differ diff --git a/base_game/images/checkboxanim.xml b/base_game/images/checkboxanim.xml new file mode 100644 index 0000000..67055f6 --- /dev/null +++ b/base_game/images/checkboxanim.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/preload/images/cutscenes/guns.png b/base_game/images/cutscenes/guns.png similarity index 100% rename from assets/preload/images/cutscenes/guns.png rename to base_game/images/cutscenes/guns.png diff --git a/assets/preload/images/cutscenes/guns.xml b/base_game/images/cutscenes/guns.xml similarity index 100% rename from assets/preload/images/cutscenes/guns.xml rename to base_game/images/cutscenes/guns.xml diff --git a/assets/preload/images/cutscenes/picoAppears/Animation.json b/base_game/images/cutscenes/picoAppears/Animation.json similarity index 100% rename from assets/preload/images/cutscenes/picoAppears/Animation.json rename to base_game/images/cutscenes/picoAppears/Animation.json diff --git a/assets/preload/images/cutscenes/picoAppears/spritemap1.json b/base_game/images/cutscenes/picoAppears/spritemap1.json similarity index 100% rename from assets/preload/images/cutscenes/picoAppears/spritemap1.json rename to base_game/images/cutscenes/picoAppears/spritemap1.json diff --git a/assets/preload/images/cutscenes/picoAppears/spritemap1.png b/base_game/images/cutscenes/picoAppears/spritemap1.png similarity index 100% rename from assets/preload/images/cutscenes/picoAppears/spritemap1.png rename to base_game/images/cutscenes/picoAppears/spritemap1.png diff --git a/assets/preload/images/cutscenes/stress.png b/base_game/images/cutscenes/stress.png similarity index 100% rename from assets/preload/images/cutscenes/stress.png rename to base_game/images/cutscenes/stress.png diff --git a/assets/preload/images/cutscenes/stress.xml b/base_game/images/cutscenes/stress.xml similarity index 100% rename from assets/preload/images/cutscenes/stress.xml rename to base_game/images/cutscenes/stress.xml diff --git a/assets/preload/images/cutscenes/stress2.png b/base_game/images/cutscenes/stress2.png similarity index 100% rename from assets/preload/images/cutscenes/stress2.png rename to base_game/images/cutscenes/stress2.png diff --git a/assets/preload/images/cutscenes/stress2.xml b/base_game/images/cutscenes/stress2.xml similarity index 100% rename from assets/preload/images/cutscenes/stress2.xml rename to base_game/images/cutscenes/stress2.xml diff --git a/assets/preload/images/cutscenes/stressGF.png b/base_game/images/cutscenes/stressGF.png similarity index 100% rename from assets/preload/images/cutscenes/stressGF.png rename to base_game/images/cutscenes/stressGF.png diff --git a/assets/preload/images/cutscenes/stressGF.xml b/base_game/images/cutscenes/stressGF.xml similarity index 100% rename from assets/preload/images/cutscenes/stressGF.xml rename to base_game/images/cutscenes/stressGF.xml diff --git a/assets/preload/images/cutscenes/stressPico/Animation.json b/base_game/images/cutscenes/stressPico/Animation.json similarity index 100% rename from assets/preload/images/cutscenes/stressPico/Animation.json rename to base_game/images/cutscenes/stressPico/Animation.json diff --git a/assets/preload/images/cutscenes/stressPico/spritemap.json b/base_game/images/cutscenes/stressPico/spritemap.json similarity index 100% rename from assets/preload/images/cutscenes/stressPico/spritemap.json rename to base_game/images/cutscenes/stressPico/spritemap.json diff --git a/assets/preload/images/cutscenes/stressPico/spritemap.png b/base_game/images/cutscenes/stressPico/spritemap.png similarity index 100% rename from assets/preload/images/cutscenes/stressPico/spritemap.png rename to base_game/images/cutscenes/stressPico/spritemap.png diff --git a/assets/preload/images/cutscenes/tankman/Animation.json b/base_game/images/cutscenes/tankman/Animation.json similarity index 100% rename from assets/preload/images/cutscenes/tankman/Animation.json rename to base_game/images/cutscenes/tankman/Animation.json diff --git a/assets/preload/images/cutscenes/tankman/spritemap1.json b/base_game/images/cutscenes/tankman/spritemap1.json similarity index 100% rename from assets/preload/images/cutscenes/tankman/spritemap1.json rename to base_game/images/cutscenes/tankman/spritemap1.json diff --git a/assets/preload/images/cutscenes/tankman/spritemap1.png b/base_game/images/cutscenes/tankman/spritemap1.png similarity index 100% rename from assets/preload/images/cutscenes/tankman/spritemap1.png rename to base_game/images/cutscenes/tankman/spritemap1.png diff --git a/assets/preload/images/cutscenes/ugh.png b/base_game/images/cutscenes/ugh.png similarity index 100% rename from assets/preload/images/cutscenes/ugh.png rename to base_game/images/cutscenes/ugh.png diff --git a/assets/preload/images/cutscenes/ugh.xml b/base_game/images/cutscenes/ugh.xml similarity index 100% rename from assets/preload/images/cutscenes/ugh.xml rename to base_game/images/cutscenes/ugh.xml diff --git a/base_game/images/gfDanceTitle.json b/base_game/images/gfDanceTitle.json new file mode 100644 index 0000000..b3a304a --- /dev/null +++ b/base_game/images/gfDanceTitle.json @@ -0,0 +1,10 @@ +{ + "titlex":-150, + "titley":-100, + "startx":100, + "starty":576, + "gfx":512, + "gfy":40, + "backgroundSprite":"", + "bpm":102 +} \ No newline at end of file diff --git a/base_game/images/gfDanceTitle.png b/base_game/images/gfDanceTitle.png new file mode 100644 index 0000000..537d462 Binary files /dev/null and b/base_game/images/gfDanceTitle.png differ diff --git a/base_game/images/gfDanceTitle.xml b/base_game/images/gfDanceTitle.xml new file mode 100644 index 0000000..0166d1c --- /dev/null +++ b/base_game/images/gfDanceTitle.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/preload/images/gore/coldHeartKiller.png b/base_game/images/gore/coldHeartKiller.png similarity index 100% rename from assets/preload/images/gore/coldHeartKiller.png rename to base_game/images/gore/coldHeartKiller.png diff --git a/assets/preload/images/gore/metalPole.png b/base_game/images/gore/metalPole.png similarity index 100% rename from assets/preload/images/gore/metalPole.png rename to base_game/images/gore/metalPole.png diff --git a/assets/preload/images/gore/noooooo.png b/base_game/images/gore/noooooo.png similarity index 100% rename from assets/preload/images/gore/noooooo.png rename to base_game/images/gore/noooooo.png diff --git a/assets/preload/images/gore/noooooo.xml b/base_game/images/gore/noooooo.xml similarity index 100% rename from assets/preload/images/gore/noooooo.xml rename to base_game/images/gore/noooooo.xml diff --git a/assets/preload/images/gore/stupidBlood.png b/base_game/images/gore/stupidBlood.png similarity index 100% rename from assets/preload/images/gore/stupidBlood.png rename to base_game/images/gore/stupidBlood.png diff --git a/assets/preload/images/gore/stupidBlood.xml b/base_game/images/gore/stupidBlood.xml similarity index 100% rename from assets/preload/images/gore/stupidBlood.xml rename to base_game/images/gore/stupidBlood.xml diff --git a/base_game/images/icons/icon-bf-old.png b/base_game/images/icons/icon-bf-old.png new file mode 100644 index 0000000..50455c2 Binary files /dev/null and b/base_game/images/icons/icon-bf-old.png differ diff --git a/base_game/images/icons/icon-bf-pixel.png b/base_game/images/icons/icon-bf-pixel.png new file mode 100644 index 0000000..5d05a47 Binary files /dev/null and b/base_game/images/icons/icon-bf-pixel.png differ diff --git a/base_game/images/icons/icon-bf.png b/base_game/images/icons/icon-bf.png new file mode 100644 index 0000000..5c749eb Binary files /dev/null and b/base_game/images/icons/icon-bf.png differ diff --git a/base_game/images/icons/icon-dad.png b/base_game/images/icons/icon-dad.png new file mode 100644 index 0000000..13d3b3d Binary files /dev/null and b/base_game/images/icons/icon-dad.png differ diff --git a/assets/preload/images/icons/icon-darnell.png b/base_game/images/icons/icon-darnell.png similarity index 100% rename from assets/preload/images/icons/icon-darnell.png rename to base_game/images/icons/icon-darnell.png diff --git a/base_game/images/icons/icon-face.png b/base_game/images/icons/icon-face.png new file mode 100644 index 0000000..cea4eff Binary files /dev/null and b/base_game/images/icons/icon-face.png differ diff --git a/base_game/images/icons/icon-gf.png b/base_game/images/icons/icon-gf.png new file mode 100644 index 0000000..a191241 Binary files /dev/null and b/base_game/images/icons/icon-gf.png differ diff --git a/assets/preload/images/icons/icon-mom.png b/base_game/images/icons/icon-mom.png similarity index 100% rename from assets/preload/images/icons/icon-mom.png rename to base_game/images/icons/icon-mom.png diff --git a/assets/preload/images/icons/icon-monster.png b/base_game/images/icons/icon-monster.png similarity index 100% rename from assets/preload/images/icons/icon-monster.png rename to base_game/images/icons/icon-monster.png diff --git a/assets/preload/images/icons/icon-parents.png b/base_game/images/icons/icon-parents.png similarity index 100% rename from assets/preload/images/icons/icon-parents.png rename to base_game/images/icons/icon-parents.png diff --git a/assets/preload/images/icons/icon-pico-pixel.png b/base_game/images/icons/icon-pico-pixel.png similarity index 100% rename from assets/preload/images/icons/icon-pico-pixel.png rename to base_game/images/icons/icon-pico-pixel.png diff --git a/assets/preload/images/icons/icon-pico.png b/base_game/images/icons/icon-pico.png similarity index 100% rename from assets/preload/images/icons/icon-pico.png rename to base_game/images/icons/icon-pico.png diff --git a/assets/preload/images/icons/icon-senpai-angry-pixel.png b/base_game/images/icons/icon-senpai-angry-pixel.png similarity index 100% rename from assets/preload/images/icons/icon-senpai-angry-pixel.png rename to base_game/images/icons/icon-senpai-angry-pixel.png diff --git a/assets/preload/images/icons/icon-senpai-pixel.png b/base_game/images/icons/icon-senpai-pixel.png similarity index 100% rename from assets/preload/images/icons/icon-senpai-pixel.png rename to base_game/images/icons/icon-senpai-pixel.png diff --git a/assets/preload/images/icons/icon-spirit-pixel.png b/base_game/images/icons/icon-spirit-pixel.png similarity index 100% rename from assets/preload/images/icons/icon-spirit-pixel.png rename to base_game/images/icons/icon-spirit-pixel.png diff --git a/assets/preload/images/icons/icon-spooky.png b/base_game/images/icons/icon-spooky.png similarity index 100% rename from assets/preload/images/icons/icon-spooky.png rename to base_game/images/icons/icon-spooky.png diff --git a/assets/preload/images/icons/icon-tankman-bloody.png b/base_game/images/icons/icon-tankman-bloody.png similarity index 100% rename from assets/preload/images/icons/icon-tankman-bloody.png rename to base_game/images/icons/icon-tankman-bloody.png diff --git a/assets/preload/images/icons/icon-tankman.png b/base_game/images/icons/icon-tankman.png similarity index 100% rename from assets/preload/images/icons/icon-tankman.png rename to base_game/images/icons/icon-tankman.png diff --git a/base_game/images/logoBumpin.png b/base_game/images/logoBumpin.png new file mode 100644 index 0000000..bd7e178 Binary files /dev/null and b/base_game/images/logoBumpin.png differ diff --git a/base_game/images/logoBumpin.xml b/base_game/images/logoBumpin.xml new file mode 100644 index 0000000..2a6009a --- /dev/null +++ b/base_game/images/logoBumpin.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/preload/images/menubackgrounds/menu_christmas.png b/base_game/images/menubackgrounds/menu_christmas.png similarity index 100% rename from assets/preload/images/menubackgrounds/menu_christmas.png rename to base_game/images/menubackgrounds/menu_christmas.png diff --git a/assets/preload/images/menubackgrounds/menu_halloween.png b/base_game/images/menubackgrounds/menu_halloween.png similarity index 100% rename from assets/preload/images/menubackgrounds/menu_halloween.png rename to base_game/images/menubackgrounds/menu_halloween.png diff --git a/assets/preload/images/menubackgrounds/menu_limo.png b/base_game/images/menubackgrounds/menu_limo.png similarity index 100% rename from assets/preload/images/menubackgrounds/menu_limo.png rename to base_game/images/menubackgrounds/menu_limo.png diff --git a/assets/preload/images/menubackgrounds/menu_philly.png b/base_game/images/menubackgrounds/menu_philly.png similarity index 100% rename from assets/preload/images/menubackgrounds/menu_philly.png rename to base_game/images/menubackgrounds/menu_philly.png diff --git a/assets/preload/images/menubackgrounds/menu_school.png b/base_game/images/menubackgrounds/menu_school.png similarity index 100% rename from assets/preload/images/menubackgrounds/menu_school.png rename to base_game/images/menubackgrounds/menu_school.png diff --git a/assets/preload/images/menubackgrounds/menu_stage.png b/base_game/images/menubackgrounds/menu_stage.png similarity index 100% rename from assets/preload/images/menubackgrounds/menu_stage.png rename to base_game/images/menubackgrounds/menu_stage.png diff --git a/assets/preload/images/menubackgrounds/menu_tank.png b/base_game/images/menubackgrounds/menu_tank.png similarity index 100% rename from assets/preload/images/menubackgrounds/menu_tank.png rename to base_game/images/menubackgrounds/menu_tank.png diff --git a/base_game/images/menucharacters/Menu_BF.png b/base_game/images/menucharacters/Menu_BF.png new file mode 100644 index 0000000..b0004ea Binary files /dev/null and b/base_game/images/menucharacters/Menu_BF.png differ diff --git a/base_game/images/menucharacters/Menu_BF.xml b/base_game/images/menucharacters/Menu_BF.xml new file mode 100644 index 0000000..a1f2332 --- /dev/null +++ b/base_game/images/menucharacters/Menu_BF.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/images/menucharacters/Menu_Dad.png b/base_game/images/menucharacters/Menu_Dad.png new file mode 100644 index 0000000..0dc5160 Binary files /dev/null and b/base_game/images/menucharacters/Menu_Dad.png differ diff --git a/base_game/images/menucharacters/Menu_Dad.xml b/base_game/images/menucharacters/Menu_Dad.xml new file mode 100644 index 0000000..4dfee5c --- /dev/null +++ b/base_game/images/menucharacters/Menu_Dad.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/base_game/images/menucharacters/Menu_GF.png b/base_game/images/menucharacters/Menu_GF.png new file mode 100644 index 0000000..7db2c6b Binary files /dev/null and b/base_game/images/menucharacters/Menu_GF.png differ diff --git a/base_game/images/menucharacters/Menu_GF.xml b/base_game/images/menucharacters/Menu_GF.xml new file mode 100644 index 0000000..682ca2d --- /dev/null +++ b/base_game/images/menucharacters/Menu_GF.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/preload/images/menucharacters/Menu_Mom.png b/base_game/images/menucharacters/Menu_Mom.png similarity index 100% rename from assets/preload/images/menucharacters/Menu_Mom.png rename to base_game/images/menucharacters/Menu_Mom.png diff --git a/assets/preload/images/menucharacters/Menu_Mom.xml b/base_game/images/menucharacters/Menu_Mom.xml similarity index 100% rename from assets/preload/images/menucharacters/Menu_Mom.xml rename to base_game/images/menucharacters/Menu_Mom.xml diff --git a/assets/preload/images/menucharacters/Menu_Parents.png b/base_game/images/menucharacters/Menu_Parents.png similarity index 100% rename from assets/preload/images/menucharacters/Menu_Parents.png rename to base_game/images/menucharacters/Menu_Parents.png diff --git a/assets/preload/images/menucharacters/Menu_Parents.xml b/base_game/images/menucharacters/Menu_Parents.xml similarity index 100% rename from assets/preload/images/menucharacters/Menu_Parents.xml rename to base_game/images/menucharacters/Menu_Parents.xml diff --git a/assets/preload/images/menucharacters/Menu_Pico.png b/base_game/images/menucharacters/Menu_Pico.png similarity index 100% rename from assets/preload/images/menucharacters/Menu_Pico.png rename to base_game/images/menucharacters/Menu_Pico.png diff --git a/assets/preload/images/menucharacters/Menu_Pico.xml b/base_game/images/menucharacters/Menu_Pico.xml similarity index 100% rename from assets/preload/images/menucharacters/Menu_Pico.xml rename to base_game/images/menucharacters/Menu_Pico.xml diff --git a/assets/preload/images/menucharacters/Menu_Senpai.png b/base_game/images/menucharacters/Menu_Senpai.png similarity index 100% rename from assets/preload/images/menucharacters/Menu_Senpai.png rename to base_game/images/menucharacters/Menu_Senpai.png diff --git a/assets/preload/images/menucharacters/Menu_Senpai.xml b/base_game/images/menucharacters/Menu_Senpai.xml similarity index 100% rename from assets/preload/images/menucharacters/Menu_Senpai.xml rename to base_game/images/menucharacters/Menu_Senpai.xml diff --git a/assets/preload/images/menucharacters/Menu_Spooky_Kids.png b/base_game/images/menucharacters/Menu_Spooky_Kids.png similarity index 100% rename from assets/preload/images/menucharacters/Menu_Spooky_Kids.png rename to base_game/images/menucharacters/Menu_Spooky_Kids.png diff --git a/assets/preload/images/menucharacters/Menu_Spooky_Kids.xml b/base_game/images/menucharacters/Menu_Spooky_Kids.xml similarity index 100% rename from assets/preload/images/menucharacters/Menu_Spooky_Kids.xml rename to base_game/images/menucharacters/Menu_Spooky_Kids.xml diff --git a/assets/preload/images/menucharacters/Menu_Tankman.png b/base_game/images/menucharacters/Menu_Tankman.png similarity index 100% rename from assets/preload/images/menucharacters/Menu_Tankman.png rename to base_game/images/menucharacters/Menu_Tankman.png diff --git a/assets/preload/images/menucharacters/Menu_Tankman.xml b/base_game/images/menucharacters/Menu_Tankman.xml similarity index 100% rename from assets/preload/images/menucharacters/Menu_Tankman.xml rename to base_game/images/menucharacters/Menu_Tankman.xml diff --git a/base_game/images/menucharacters/bf.json b/base_game/images/menucharacters/bf.json new file mode 100644 index 0000000..9fb1049 --- /dev/null +++ b/base_game/images/menucharacters/bf.json @@ -0,0 +1,11 @@ +{ + + "image": "Menu_BF", + "position": [ + 15, + -40 + ], + "scale": 1, + "idle_anim": "M BF Idle", + "confirm_anim": "M bf HEY" +} \ No newline at end of file diff --git a/base_game/images/menucharacters/dad.json b/base_game/images/menucharacters/dad.json new file mode 100644 index 0000000..7306970 --- /dev/null +++ b/base_game/images/menucharacters/dad.json @@ -0,0 +1,11 @@ +{ + + "image": "Menu_Dad", + "position": [ + 0, + 0 + ], + "scale": 1, + "idle_anim": "M Dad Idle", + "confirm_anim": "M Dad Idle" +} \ No newline at end of file diff --git a/base_game/images/menucharacters/gf.json b/base_game/images/menucharacters/gf.json new file mode 100644 index 0000000..bdd62ce --- /dev/null +++ b/base_game/images/menucharacters/gf.json @@ -0,0 +1,11 @@ +{ + + "image": "Menu_GF", + "position": [ + 0, + -25 + ], + "scale": 1, + "idle_anim": "M GF Idle", + "confirm_anim": "M GF Idle" +} \ No newline at end of file diff --git a/assets/preload/images/menucharacters/mom.json b/base_game/images/menucharacters/mom.json similarity index 100% rename from assets/preload/images/menucharacters/mom.json rename to base_game/images/menucharacters/mom.json diff --git a/assets/preload/images/menucharacters/parents-christmas.json b/base_game/images/menucharacters/parents-christmas.json similarity index 100% rename from assets/preload/images/menucharacters/parents-christmas.json rename to base_game/images/menucharacters/parents-christmas.json diff --git a/assets/preload/images/menucharacters/pico.json b/base_game/images/menucharacters/pico.json similarity index 100% rename from assets/preload/images/menucharacters/pico.json rename to base_game/images/menucharacters/pico.json diff --git a/assets/preload/images/menucharacters/senpai.json b/base_game/images/menucharacters/senpai.json similarity index 100% rename from assets/preload/images/menucharacters/senpai.json rename to base_game/images/menucharacters/senpai.json diff --git a/assets/preload/images/menucharacters/spooky.json b/base_game/images/menucharacters/spooky.json similarity index 100% rename from assets/preload/images/menucharacters/spooky.json rename to base_game/images/menucharacters/spooky.json diff --git a/assets/preload/images/menucharacters/tankman.json b/base_game/images/menucharacters/tankman.json similarity index 100% rename from assets/preload/images/menucharacters/tankman.json rename to base_game/images/menucharacters/tankman.json diff --git a/base_game/images/menudifficulties/easy.png b/base_game/images/menudifficulties/easy.png new file mode 100644 index 0000000..7d14f92 Binary files /dev/null and b/base_game/images/menudifficulties/easy.png differ diff --git a/base_game/images/menudifficulties/hard.png b/base_game/images/menudifficulties/hard.png new file mode 100644 index 0000000..df133fe Binary files /dev/null and b/base_game/images/menudifficulties/hard.png differ diff --git a/base_game/images/menudifficulties/normal.png b/base_game/images/menudifficulties/normal.png new file mode 100644 index 0000000..14aec74 Binary files /dev/null and b/base_game/images/menudifficulties/normal.png differ diff --git a/assets/preload/images/smokeLeft.png b/base_game/images/smokeLeft.png similarity index 100% rename from assets/preload/images/smokeLeft.png rename to base_game/images/smokeLeft.png diff --git a/assets/preload/images/smokeLeft.xml b/base_game/images/smokeLeft.xml similarity index 100% rename from assets/preload/images/smokeLeft.xml rename to base_game/images/smokeLeft.xml diff --git a/assets/preload/images/smokeRight.png b/base_game/images/smokeRight.png similarity index 100% rename from assets/preload/images/smokeRight.png rename to base_game/images/smokeRight.png diff --git a/assets/preload/images/smokeRight.xml b/base_game/images/smokeRight.xml similarity index 100% rename from assets/preload/images/smokeRight.xml rename to base_game/images/smokeRight.xml diff --git a/assets/preload/images/storymenu/tutorial.png b/base_game/images/storymenu/tutorial.png similarity index 100% rename from assets/preload/images/storymenu/tutorial.png rename to base_game/images/storymenu/tutorial.png diff --git a/assets/preload/images/storymenu/week1.png b/base_game/images/storymenu/week1.png similarity index 100% rename from assets/preload/images/storymenu/week1.png rename to base_game/images/storymenu/week1.png diff --git a/assets/preload/images/storymenu/week2.png b/base_game/images/storymenu/week2.png similarity index 100% rename from assets/preload/images/storymenu/week2.png rename to base_game/images/storymenu/week2.png diff --git a/assets/preload/images/storymenu/week3.png b/base_game/images/storymenu/week3.png similarity index 100% rename from assets/preload/images/storymenu/week3.png rename to base_game/images/storymenu/week3.png diff --git a/assets/preload/images/storymenu/week4.png b/base_game/images/storymenu/week4.png similarity index 100% rename from assets/preload/images/storymenu/week4.png rename to base_game/images/storymenu/week4.png diff --git a/assets/preload/images/storymenu/week5.png b/base_game/images/storymenu/week5.png similarity index 100% rename from assets/preload/images/storymenu/week5.png rename to base_game/images/storymenu/week5.png diff --git a/assets/preload/images/storymenu/week6.png b/base_game/images/storymenu/week6.png similarity index 100% rename from assets/preload/images/storymenu/week6.png rename to base_game/images/storymenu/week6.png diff --git a/assets/preload/images/storymenu/week7.png b/base_game/images/storymenu/week7.png similarity index 100% rename from assets/preload/images/storymenu/week7.png rename to base_game/images/storymenu/week7.png diff --git a/base_game/music/freakyMenu.mp3 b/base_game/music/freakyMenu.mp3 new file mode 100644 index 0000000..8132bfe Binary files /dev/null and b/base_game/music/freakyMenu.mp3 differ diff --git a/base_game/music/freakyMenu.ogg b/base_game/music/freakyMenu.ogg new file mode 100644 index 0000000..372111d Binary files /dev/null and b/base_game/music/freakyMenu.ogg differ diff --git a/base_game/music/offsetSong.mp3 b/base_game/music/offsetSong.mp3 new file mode 100644 index 0000000..d064405 Binary files /dev/null and b/base_game/music/offsetSong.mp3 differ diff --git a/base_game/music/offsetSong.ogg b/base_game/music/offsetSong.ogg new file mode 100644 index 0000000..a25e564 Binary files /dev/null and b/base_game/music/offsetSong.ogg differ diff --git a/base_game/shared/images/characters/bf/BOYFRIEND.png b/base_game/shared/images/characters/bf/BOYFRIEND.png new file mode 100644 index 0000000..1909fd7 Binary files /dev/null and b/base_game/shared/images/characters/bf/BOYFRIEND.png differ diff --git a/base_game/shared/images/characters/bf/BOYFRIEND.xml b/base_game/shared/images/characters/bf/BOYFRIEND.xml new file mode 100644 index 0000000..086637c --- /dev/null +++ b/base_game/shared/images/characters/bf/BOYFRIEND.xml @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/shared/images/characters/bf/bfAndGF.png b/base_game/shared/images/characters/bf/bfAndGF.png similarity index 100% rename from assets/shared/images/characters/bf/bfAndGF.png rename to base_game/shared/images/characters/bf/bfAndGF.png diff --git a/assets/shared/images/characters/bf/bfAndGF.xml b/base_game/shared/images/characters/bf/bfAndGF.xml similarity index 100% rename from assets/shared/images/characters/bf/bfAndGF.xml rename to base_game/shared/images/characters/bf/bfAndGF.xml diff --git a/assets/shared/images/characters/bf/bfCar.png b/base_game/shared/images/characters/bf/bfCar.png similarity index 100% rename from assets/shared/images/characters/bf/bfCar.png rename to base_game/shared/images/characters/bf/bfCar.png diff --git a/assets/shared/images/characters/bf/bfCar.xml b/base_game/shared/images/characters/bf/bfCar.xml similarity index 100% rename from assets/shared/images/characters/bf/bfCar.xml rename to base_game/shared/images/characters/bf/bfCar.xml diff --git a/assets/shared/images/characters/bf/bfChristmas.png b/base_game/shared/images/characters/bf/bfChristmas.png similarity index 100% rename from assets/shared/images/characters/bf/bfChristmas.png rename to base_game/shared/images/characters/bf/bfChristmas.png diff --git a/assets/shared/images/characters/bf/bfChristmas.xml b/base_game/shared/images/characters/bf/bfChristmas.xml similarity index 100% rename from assets/shared/images/characters/bf/bfChristmas.xml rename to base_game/shared/images/characters/bf/bfChristmas.xml diff --git a/base_game/shared/images/characters/bf/bfPixel.png b/base_game/shared/images/characters/bf/bfPixel.png new file mode 100644 index 0000000..59f5cb4 Binary files /dev/null and b/base_game/shared/images/characters/bf/bfPixel.png differ diff --git a/base_game/shared/images/characters/bf/bfPixel.xml b/base_game/shared/images/characters/bf/bfPixel.xml new file mode 100644 index 0000000..a3622d8 --- /dev/null +++ b/base_game/shared/images/characters/bf/bfPixel.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/shared/images/characters/bf/death/BOYFRIEND_DEAD.png b/base_game/shared/images/characters/bf/death/BOYFRIEND_DEAD.png new file mode 100644 index 0000000..3712fd7 Binary files /dev/null and b/base_game/shared/images/characters/bf/death/BOYFRIEND_DEAD.png differ diff --git a/base_game/shared/images/characters/bf/death/BOYFRIEND_DEAD.xml b/base_game/shared/images/characters/bf/death/BOYFRIEND_DEAD.xml new file mode 100644 index 0000000..1bff107 --- /dev/null +++ b/base_game/shared/images/characters/bf/death/BOYFRIEND_DEAD.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/shared/images/characters/bf/death/bfHoldingGF-DEAD.png b/base_game/shared/images/characters/bf/death/bfHoldingGF-DEAD.png similarity index 100% rename from assets/shared/images/characters/bf/death/bfHoldingGF-DEAD.png rename to base_game/shared/images/characters/bf/death/bfHoldingGF-DEAD.png diff --git a/assets/shared/images/characters/bf/death/bfHoldingGF-DEAD.xml b/base_game/shared/images/characters/bf/death/bfHoldingGF-DEAD.xml similarity index 100% rename from assets/shared/images/characters/bf/death/bfHoldingGF-DEAD.xml rename to base_game/shared/images/characters/bf/death/bfHoldingGF-DEAD.xml diff --git a/base_game/shared/images/characters/bf/death/bfPixelsDEAD.png b/base_game/shared/images/characters/bf/death/bfPixelsDEAD.png new file mode 100644 index 0000000..4f157af Binary files /dev/null and b/base_game/shared/images/characters/bf/death/bfPixelsDEAD.png differ diff --git a/base_game/shared/images/characters/bf/death/bfPixelsDEAD.xml b/base_game/shared/images/characters/bf/death/bfPixelsDEAD.xml new file mode 100644 index 0000000..ed29301 --- /dev/null +++ b/base_game/shared/images/characters/bf/death/bfPixelsDEAD.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/shared/images/characters/daddy/DADDY_DEAREST.png b/base_game/shared/images/characters/daddy/DADDY_DEAREST.png new file mode 100644 index 0000000..b07762a Binary files /dev/null and b/base_game/shared/images/characters/daddy/DADDY_DEAREST.png differ diff --git a/base_game/shared/images/characters/daddy/DADDY_DEAREST.xml b/base_game/shared/images/characters/daddy/DADDY_DEAREST.xml new file mode 100644 index 0000000..d055370 --- /dev/null +++ b/base_game/shared/images/characters/daddy/DADDY_DEAREST.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/shared/images/characters/gf/GF_assets.png b/base_game/shared/images/characters/gf/GF_assets.png new file mode 100644 index 0000000..87c82b3 Binary files /dev/null and b/base_game/shared/images/characters/gf/GF_assets.png differ diff --git a/base_game/shared/images/characters/gf/GF_assets.xml b/base_game/shared/images/characters/gf/GF_assets.xml new file mode 100644 index 0000000..d155baf --- /dev/null +++ b/base_game/shared/images/characters/gf/GF_assets.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/shared/images/characters/gf/GF_assets_Playable.png b/base_game/shared/images/characters/gf/GF_assets_Playable.png new file mode 100644 index 0000000..bad72e0 Binary files /dev/null and b/base_game/shared/images/characters/gf/GF_assets_Playable.png differ diff --git a/base_game/shared/images/characters/gf/GF_assets_Playable.xml b/base_game/shared/images/characters/gf/GF_assets_Playable.xml new file mode 100644 index 0000000..f48e618 --- /dev/null +++ b/base_game/shared/images/characters/gf/GF_assets_Playable.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/shared/images/characters/gf/gfCar.png b/base_game/shared/images/characters/gf/gfCar.png similarity index 100% rename from assets/shared/images/characters/gf/gfCar.png rename to base_game/shared/images/characters/gf/gfCar.png diff --git a/assets/shared/images/characters/gf/gfCar.xml b/base_game/shared/images/characters/gf/gfCar.xml similarity index 100% rename from assets/shared/images/characters/gf/gfCar.xml rename to base_game/shared/images/characters/gf/gfCar.xml diff --git a/assets/shared/images/characters/gf/gfChristmas.png b/base_game/shared/images/characters/gf/gfChristmas.png similarity index 100% rename from assets/shared/images/characters/gf/gfChristmas.png rename to base_game/shared/images/characters/gf/gfChristmas.png diff --git a/assets/shared/images/characters/gf/gfChristmas.xml b/base_game/shared/images/characters/gf/gfChristmas.xml similarity index 100% rename from assets/shared/images/characters/gf/gfChristmas.xml rename to base_game/shared/images/characters/gf/gfChristmas.xml diff --git a/assets/shared/images/characters/gf/gfPixel.png b/base_game/shared/images/characters/gf/gfPixel.png similarity index 100% rename from assets/shared/images/characters/gf/gfPixel.png rename to base_game/shared/images/characters/gf/gfPixel.png diff --git a/assets/shared/images/characters/gf/gfPixel.xml b/base_game/shared/images/characters/gf/gfPixel.xml similarity index 100% rename from assets/shared/images/characters/gf/gfPixel.xml rename to base_game/shared/images/characters/gf/gfPixel.xml diff --git a/assets/shared/images/characters/gf/gfTankmen.png b/base_game/shared/images/characters/gf/gfTankmen.png similarity index 100% rename from assets/shared/images/characters/gf/gfTankmen.png rename to base_game/shared/images/characters/gf/gfTankmen.png diff --git a/assets/shared/images/characters/gf/gfTankmen.xml b/base_game/shared/images/characters/gf/gfTankmen.xml similarity index 100% rename from assets/shared/images/characters/gf/gfTankmen.xml rename to base_game/shared/images/characters/gf/gfTankmen.xml diff --git a/assets/shared/images/characters/lemon/Monster_Assets.png b/base_game/shared/images/characters/lemon/Monster_Assets.png similarity index 100% rename from assets/shared/images/characters/lemon/Monster_Assets.png rename to base_game/shared/images/characters/lemon/Monster_Assets.png diff --git a/assets/shared/images/characters/lemon/Monster_Assets.xml b/base_game/shared/images/characters/lemon/Monster_Assets.xml similarity index 100% rename from assets/shared/images/characters/lemon/Monster_Assets.xml rename to base_game/shared/images/characters/lemon/Monster_Assets.xml diff --git a/assets/shared/images/characters/lemon/monsterChristmas.png b/base_game/shared/images/characters/lemon/monsterChristmas.png similarity index 100% rename from assets/shared/images/characters/lemon/monsterChristmas.png rename to base_game/shared/images/characters/lemon/monsterChristmas.png diff --git a/assets/shared/images/characters/lemon/monsterChristmas.xml b/base_game/shared/images/characters/lemon/monsterChristmas.xml similarity index 100% rename from assets/shared/images/characters/lemon/monsterChristmas.xml rename to base_game/shared/images/characters/lemon/monsterChristmas.xml diff --git a/assets/shared/images/characters/mom_dad_christmas_assets.png b/base_game/shared/images/characters/mom_dad_christmas_assets.png similarity index 100% rename from assets/shared/images/characters/mom_dad_christmas_assets.png rename to base_game/shared/images/characters/mom_dad_christmas_assets.png diff --git a/assets/shared/images/characters/mom_dad_christmas_assets.xml b/base_game/shared/images/characters/mom_dad_christmas_assets.xml similarity index 100% rename from assets/shared/images/characters/mom_dad_christmas_assets.xml rename to base_game/shared/images/characters/mom_dad_christmas_assets.xml diff --git a/assets/shared/images/characters/mommy/momCar.png b/base_game/shared/images/characters/mommy/momCar.png similarity index 100% rename from assets/shared/images/characters/mommy/momCar.png rename to base_game/shared/images/characters/mommy/momCar.png diff --git a/assets/shared/images/characters/mommy/momCar.xml b/base_game/shared/images/characters/mommy/momCar.xml similarity index 100% rename from assets/shared/images/characters/mommy/momCar.xml rename to base_game/shared/images/characters/mommy/momCar.xml diff --git a/assets/shared/images/characters/pico/Pico_FNF_assetss.png b/base_game/shared/images/characters/pico/Pico_FNF_assetss.png similarity index 100% rename from assets/shared/images/characters/pico/Pico_FNF_assetss.png rename to base_game/shared/images/characters/pico/Pico_FNF_assetss.png diff --git a/assets/shared/images/characters/pico/Pico_FNF_assetss.xml b/base_game/shared/images/characters/pico/Pico_FNF_assetss.xml similarity index 100% rename from assets/shared/images/characters/pico/Pico_FNF_assetss.xml rename to base_game/shared/images/characters/pico/Pico_FNF_assetss.xml diff --git a/assets/shared/images/characters/pico/picoSpeaker.png b/base_game/shared/images/characters/pico/picoSpeaker.png similarity index 100% rename from assets/shared/images/characters/pico/picoSpeaker.png rename to base_game/shared/images/characters/pico/picoSpeaker.png diff --git a/assets/shared/images/characters/pico/picoSpeaker.xml b/base_game/shared/images/characters/pico/picoSpeaker.xml similarity index 100% rename from assets/shared/images/characters/pico/picoSpeaker.xml rename to base_game/shared/images/characters/pico/picoSpeaker.xml diff --git a/assets/shared/images/characters/s&p/spooky_kids_assets.png b/base_game/shared/images/characters/s&p/spooky_kids_assets.png similarity index 100% rename from assets/shared/images/characters/s&p/spooky_kids_assets.png rename to base_game/shared/images/characters/s&p/spooky_kids_assets.png diff --git a/assets/shared/images/characters/s&p/spooky_kids_assets.xml b/base_game/shared/images/characters/s&p/spooky_kids_assets.xml similarity index 100% rename from assets/shared/images/characters/s&p/spooky_kids_assets.xml rename to base_game/shared/images/characters/s&p/spooky_kids_assets.xml diff --git a/assets/shared/images/characters/senpai/senpai.png b/base_game/shared/images/characters/senpai/senpai.png similarity index 100% rename from assets/shared/images/characters/senpai/senpai.png rename to base_game/shared/images/characters/senpai/senpai.png diff --git a/assets/shared/images/characters/senpai/senpai.xml b/base_game/shared/images/characters/senpai/senpai.xml similarity index 100% rename from assets/shared/images/characters/senpai/senpai.xml rename to base_game/shared/images/characters/senpai/senpai.xml diff --git a/assets/shared/images/characters/senpai/spirit.png b/base_game/shared/images/characters/senpai/spirit.png similarity index 100% rename from assets/shared/images/characters/senpai/spirit.png rename to base_game/shared/images/characters/senpai/spirit.png diff --git a/assets/shared/images/characters/senpai/spirit.txt b/base_game/shared/images/characters/senpai/spirit.txt similarity index 100% rename from assets/shared/images/characters/senpai/spirit.txt rename to base_game/shared/images/characters/senpai/spirit.txt diff --git a/assets/shared/images/characters/tankman/tankmanCaptain.png b/base_game/shared/images/characters/tankman/tankmanCaptain.png similarity index 100% rename from assets/shared/images/characters/tankman/tankmanCaptain.png rename to base_game/shared/images/characters/tankman/tankmanCaptain.png diff --git a/assets/shared/images/characters/tankman/tankmanCaptain.xml b/base_game/shared/images/characters/tankman/tankmanCaptain.xml similarity index 100% rename from assets/shared/images/characters/tankman/tankmanCaptain.xml rename to base_game/shared/images/characters/tankman/tankmanCaptain.xml diff --git a/base_game/shared/images/dialogue/BF_Dialogue.png b/base_game/shared/images/dialogue/BF_Dialogue.png new file mode 100644 index 0000000..ba452f1 Binary files /dev/null and b/base_game/shared/images/dialogue/BF_Dialogue.png differ diff --git a/base_game/shared/images/dialogue/BF_Dialogue.xml b/base_game/shared/images/dialogue/BF_Dialogue.xml new file mode 100644 index 0000000..8ce0762 --- /dev/null +++ b/base_game/shared/images/dialogue/BF_Dialogue.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/shared/images/dialogue/GF_Dialogue.png b/base_game/shared/images/dialogue/GF_Dialogue.png new file mode 100644 index 0000000..423ca5a Binary files /dev/null and b/base_game/shared/images/dialogue/GF_Dialogue.png differ diff --git a/base_game/shared/images/dialogue/GF_Dialogue.xml b/base_game/shared/images/dialogue/GF_Dialogue.xml new file mode 100644 index 0000000..77c7fb2 --- /dev/null +++ b/base_game/shared/images/dialogue/GF_Dialogue.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/shared/images/pauseAlt/bfLol.png b/base_game/shared/images/pauseAlt/bfLol.png new file mode 100644 index 0000000..8a01ee8 Binary files /dev/null and b/base_game/shared/images/pauseAlt/bfLol.png differ diff --git a/base_game/shared/images/pauseAlt/bfLol.xml b/base_game/shared/images/pauseAlt/bfLol.xml new file mode 100644 index 0000000..14a7f19 --- /dev/null +++ b/base_game/shared/images/pauseAlt/bfLol.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base_game/shared/images/pauseAlt/pauseBG.png b/base_game/shared/images/pauseAlt/pauseBG.png new file mode 100644 index 0000000..ba11b9f Binary files /dev/null and b/base_game/shared/images/pauseAlt/pauseBG.png differ diff --git a/base_game/shared/images/pauseAlt/pauseUI.png b/base_game/shared/images/pauseAlt/pauseUI.png new file mode 100644 index 0000000..3dfa7de Binary files /dev/null and b/base_game/shared/images/pauseAlt/pauseUI.png differ diff --git a/base_game/shared/images/pauseAlt/pauseUI.xml b/base_game/shared/images/pauseAlt/pauseUI.xml new file mode 100644 index 0000000..a71de7c --- /dev/null +++ b/base_game/shared/images/pauseAlt/pauseUI.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/base_game/shared/images/pixelUI/HURTNOTE_assets.png b/base_game/shared/images/pixelUI/HURTNOTE_assets.png new file mode 100644 index 0000000..753b4bd Binary files /dev/null and b/base_game/shared/images/pixelUI/HURTNOTE_assets.png differ diff --git a/base_game/shared/images/pixelUI/HURTNOTE_assetsENDS.png b/base_game/shared/images/pixelUI/HURTNOTE_assetsENDS.png new file mode 100644 index 0000000..bfd4b63 Binary files /dev/null and b/base_game/shared/images/pixelUI/HURTNOTE_assetsENDS.png differ diff --git a/base_game/shared/images/pixelUI/NOTE_assets.png b/base_game/shared/images/pixelUI/NOTE_assets.png new file mode 100644 index 0000000..8a3f2e3 Binary files /dev/null and b/base_game/shared/images/pixelUI/NOTE_assets.png differ diff --git a/base_game/shared/images/pixelUI/NOTE_assetsENDS.png b/base_game/shared/images/pixelUI/NOTE_assetsENDS.png new file mode 100644 index 0000000..783b8c2 Binary files /dev/null and b/base_game/shared/images/pixelUI/NOTE_assetsENDS.png differ diff --git a/base_game/shared/images/pixelUI/bad-pixel.png b/base_game/shared/images/pixelUI/bad-pixel.png new file mode 100644 index 0000000..1d77539 Binary files /dev/null and b/base_game/shared/images/pixelUI/bad-pixel.png differ diff --git a/base_game/shared/images/pixelUI/combo-pixel.png b/base_game/shared/images/pixelUI/combo-pixel.png new file mode 100644 index 0000000..6348766 Binary files /dev/null and b/base_game/shared/images/pixelUI/combo-pixel.png differ diff --git a/base_game/shared/images/pixelUI/date-pixel.png b/base_game/shared/images/pixelUI/date-pixel.png new file mode 100644 index 0000000..fa2bf67 Binary files /dev/null and b/base_game/shared/images/pixelUI/date-pixel.png differ diff --git a/base_game/shared/images/pixelUI/good-pixel.png b/base_game/shared/images/pixelUI/good-pixel.png new file mode 100644 index 0000000..085ba6d Binary files /dev/null and b/base_game/shared/images/pixelUI/good-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num0-pixel.png b/base_game/shared/images/pixelUI/num0-pixel.png new file mode 100644 index 0000000..131cb99 Binary files /dev/null and b/base_game/shared/images/pixelUI/num0-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num1-pixel.png b/base_game/shared/images/pixelUI/num1-pixel.png new file mode 100644 index 0000000..ba3aebb Binary files /dev/null and b/base_game/shared/images/pixelUI/num1-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num2-pixel.png b/base_game/shared/images/pixelUI/num2-pixel.png new file mode 100644 index 0000000..a503e87 Binary files /dev/null and b/base_game/shared/images/pixelUI/num2-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num3-pixel.png b/base_game/shared/images/pixelUI/num3-pixel.png new file mode 100644 index 0000000..8e1e2fb Binary files /dev/null and b/base_game/shared/images/pixelUI/num3-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num4-pixel.png b/base_game/shared/images/pixelUI/num4-pixel.png new file mode 100644 index 0000000..c2c16f0 Binary files /dev/null and b/base_game/shared/images/pixelUI/num4-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num5-pixel.png b/base_game/shared/images/pixelUI/num5-pixel.png new file mode 100644 index 0000000..3e75f1f Binary files /dev/null and b/base_game/shared/images/pixelUI/num5-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num6-pixel.png b/base_game/shared/images/pixelUI/num6-pixel.png new file mode 100644 index 0000000..47bd2ec Binary files /dev/null and b/base_game/shared/images/pixelUI/num6-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num7-pixel.png b/base_game/shared/images/pixelUI/num7-pixel.png new file mode 100644 index 0000000..b50afad Binary files /dev/null and b/base_game/shared/images/pixelUI/num7-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num8-pixel.png b/base_game/shared/images/pixelUI/num8-pixel.png new file mode 100644 index 0000000..4786d8f Binary files /dev/null and b/base_game/shared/images/pixelUI/num8-pixel.png differ diff --git a/base_game/shared/images/pixelUI/num9-pixel.png b/base_game/shared/images/pixelUI/num9-pixel.png new file mode 100644 index 0000000..355c4af Binary files /dev/null and b/base_game/shared/images/pixelUI/num9-pixel.png differ diff --git a/base_game/shared/images/pixelUI/ready-pixel.png b/base_game/shared/images/pixelUI/ready-pixel.png new file mode 100644 index 0000000..ffb21d6 Binary files /dev/null and b/base_game/shared/images/pixelUI/ready-pixel.png differ diff --git a/base_game/shared/images/pixelUI/set-pixel.png b/base_game/shared/images/pixelUI/set-pixel.png new file mode 100644 index 0000000..80660d3 Binary files /dev/null and b/base_game/shared/images/pixelUI/set-pixel.png differ diff --git a/base_game/shared/images/pixelUI/shit-pixel.png b/base_game/shared/images/pixelUI/shit-pixel.png new file mode 100644 index 0000000..729e303 Binary files /dev/null and b/base_game/shared/images/pixelUI/shit-pixel.png differ diff --git a/base_game/shared/images/pixelUI/sick-pixel.png b/base_game/shared/images/pixelUI/sick-pixel.png new file mode 100644 index 0000000..762ab10 Binary files /dev/null and b/base_game/shared/images/pixelUI/sick-pixel.png differ diff --git a/assets/shared/images/smoke.png b/base_game/shared/images/smoke.png similarity index 100% rename from assets/shared/images/smoke.png rename to base_game/shared/images/smoke.png diff --git a/assets/shared/images/spotlight.png b/base_game/shared/images/spotlight.png similarity index 100% rename from assets/shared/images/spotlight.png rename to base_game/shared/images/spotlight.png diff --git a/base_game/shared/images/stage_light.png b/base_game/shared/images/stage_light.png new file mode 100644 index 0000000..0f9a88b Binary files /dev/null and b/base_game/shared/images/stage_light.png differ diff --git a/base_game/shared/images/stageback.png b/base_game/shared/images/stageback.png new file mode 100644 index 0000000..e1a2483 Binary files /dev/null and b/base_game/shared/images/stageback.png differ diff --git a/base_game/shared/images/stagecurtains.png b/base_game/shared/images/stagecurtains.png new file mode 100644 index 0000000..4bff0dd Binary files /dev/null and b/base_game/shared/images/stagecurtains.png differ diff --git a/base_game/shared/images/stagefront.png b/base_game/shared/images/stagefront.png new file mode 100644 index 0000000..96730b5 Binary files /dev/null and b/base_game/shared/images/stagefront.png differ diff --git a/base_game/shared/images/timeBar.png b/base_game/shared/images/timeBar.png new file mode 100644 index 0000000..fa561c6 Binary files /dev/null and b/base_game/shared/images/timeBar.png differ diff --git a/assets/week6_sfx/music/Lunchbox.mp3 b/base_game/shared/music/Lunchbox.mp3 similarity index 100% rename from assets/week6_sfx/music/Lunchbox.mp3 rename to base_game/shared/music/Lunchbox.mp3 diff --git a/assets/week6_sfx/music/Lunchbox.ogg b/base_game/shared/music/Lunchbox.ogg similarity index 100% rename from assets/week6_sfx/music/Lunchbox.ogg rename to base_game/shared/music/Lunchbox.ogg diff --git a/assets/week6_sfx/music/LunchboxScary.mp3 b/base_game/shared/music/LunchboxScary.mp3 similarity index 100% rename from assets/week6_sfx/music/LunchboxScary.mp3 rename to base_game/shared/music/LunchboxScary.mp3 diff --git a/assets/week6_sfx/music/LunchboxScary.ogg b/base_game/shared/music/LunchboxScary.ogg similarity index 100% rename from assets/week6_sfx/music/LunchboxScary.ogg rename to base_game/shared/music/LunchboxScary.ogg diff --git a/assets/week6_sfx/music/gameOver-pixel.mp3 b/base_game/shared/music/gameOver-pixel.mp3 similarity index 100% rename from assets/week6_sfx/music/gameOver-pixel.mp3 rename to base_game/shared/music/gameOver-pixel.mp3 diff --git a/assets/week6_sfx/music/gameOver-pixel.ogg b/base_game/shared/music/gameOver-pixel.ogg similarity index 100% rename from assets/week6_sfx/music/gameOver-pixel.ogg rename to base_game/shared/music/gameOver-pixel.ogg diff --git a/assets/week6_sfx/music/gameOverEnd-pixel.mp3 b/base_game/shared/music/gameOverEnd-pixel.mp3 similarity index 100% rename from assets/week6_sfx/music/gameOverEnd-pixel.mp3 rename to base_game/shared/music/gameOverEnd-pixel.mp3 diff --git a/assets/week6_sfx/music/gameOverEnd-pixel.ogg b/base_game/shared/music/gameOverEnd-pixel.ogg similarity index 100% rename from assets/week6_sfx/music/gameOverEnd-pixel.ogg rename to base_game/shared/music/gameOverEnd-pixel.ogg diff --git a/assets/shared/sounds/ANGRY.mp3 b/base_game/shared/sounds/ANGRY.mp3 similarity index 100% rename from assets/shared/sounds/ANGRY.mp3 rename to base_game/shared/sounds/ANGRY.mp3 diff --git a/assets/shared/sounds/ANGRY.ogg b/base_game/shared/sounds/ANGRY.ogg similarity index 100% rename from assets/shared/sounds/ANGRY.ogg rename to base_game/shared/sounds/ANGRY.ogg diff --git a/assets/shared/sounds/ANGRY_TEXT_BOX.mp3 b/base_game/shared/sounds/ANGRY_TEXT_BOX.mp3 similarity index 100% rename from assets/shared/sounds/ANGRY_TEXT_BOX.mp3 rename to base_game/shared/sounds/ANGRY_TEXT_BOX.mp3 diff --git a/assets/shared/sounds/ANGRY_TEXT_BOX.ogg b/base_game/shared/sounds/ANGRY_TEXT_BOX.ogg similarity index 100% rename from assets/shared/sounds/ANGRY_TEXT_BOX.ogg rename to base_game/shared/sounds/ANGRY_TEXT_BOX.ogg diff --git a/base_game/shared/sounds/GF_1.mp3 b/base_game/shared/sounds/GF_1.mp3 new file mode 100644 index 0000000..50e4a30 Binary files /dev/null and b/base_game/shared/sounds/GF_1.mp3 differ diff --git a/base_game/shared/sounds/GF_1.ogg b/base_game/shared/sounds/GF_1.ogg new file mode 100644 index 0000000..b7521ab Binary files /dev/null and b/base_game/shared/sounds/GF_1.ogg differ diff --git a/base_game/shared/sounds/GF_2.mp3 b/base_game/shared/sounds/GF_2.mp3 new file mode 100644 index 0000000..d3e11e4 Binary files /dev/null and b/base_game/shared/sounds/GF_2.mp3 differ diff --git a/base_game/shared/sounds/GF_2.ogg b/base_game/shared/sounds/GF_2.ogg new file mode 100644 index 0000000..32bfea1 Binary files /dev/null and b/base_game/shared/sounds/GF_2.ogg differ diff --git a/base_game/shared/sounds/GF_3.mp3 b/base_game/shared/sounds/GF_3.mp3 new file mode 100644 index 0000000..cdbcf4d Binary files /dev/null and b/base_game/shared/sounds/GF_3.mp3 differ diff --git a/base_game/shared/sounds/GF_3.ogg b/base_game/shared/sounds/GF_3.ogg new file mode 100644 index 0000000..c855a40 Binary files /dev/null and b/base_game/shared/sounds/GF_3.ogg differ diff --git a/base_game/shared/sounds/GF_4.mp3 b/base_game/shared/sounds/GF_4.mp3 new file mode 100644 index 0000000..63c0c07 Binary files /dev/null and b/base_game/shared/sounds/GF_4.mp3 differ diff --git a/base_game/shared/sounds/GF_4.ogg b/base_game/shared/sounds/GF_4.ogg new file mode 100644 index 0000000..89c48e0 Binary files /dev/null and b/base_game/shared/sounds/GF_4.ogg differ diff --git a/base_game/shared/sounds/Metronome_Tick.mp3 b/base_game/shared/sounds/Metronome_Tick.mp3 new file mode 100644 index 0000000..ee413c5 Binary files /dev/null and b/base_game/shared/sounds/Metronome_Tick.mp3 differ diff --git a/base_game/shared/sounds/Metronome_Tick.ogg b/base_game/shared/sounds/Metronome_Tick.ogg new file mode 100644 index 0000000..be29a7d Binary files /dev/null and b/base_game/shared/sounds/Metronome_Tick.ogg differ diff --git a/assets/week6_sfx/sounds/Senpai_Dies.mp3 b/base_game/shared/sounds/Senpai_Dies.mp3 similarity index 100% rename from assets/week6_sfx/sounds/Senpai_Dies.mp3 rename to base_game/shared/sounds/Senpai_Dies.mp3 diff --git a/assets/week6_sfx/sounds/Senpai_Dies.ogg b/base_game/shared/sounds/Senpai_Dies.ogg similarity index 100% rename from assets/week6_sfx/sounds/Senpai_Dies.ogg rename to base_game/shared/sounds/Senpai_Dies.ogg diff --git a/assets/shared/sounds/badnoise1.mp3 b/base_game/shared/sounds/badnoise1.mp3 similarity index 100% rename from assets/shared/sounds/badnoise1.mp3 rename to base_game/shared/sounds/badnoise1.mp3 diff --git a/assets/shared/sounds/badnoise1.ogg b/base_game/shared/sounds/badnoise1.ogg similarity index 100% rename from assets/shared/sounds/badnoise1.ogg rename to base_game/shared/sounds/badnoise1.ogg diff --git a/assets/shared/sounds/badnoise2.mp3 b/base_game/shared/sounds/badnoise2.mp3 similarity index 100% rename from assets/shared/sounds/badnoise2.mp3 rename to base_game/shared/sounds/badnoise2.mp3 diff --git a/assets/shared/sounds/badnoise2.ogg b/base_game/shared/sounds/badnoise2.ogg similarity index 100% rename from assets/shared/sounds/badnoise2.ogg rename to base_game/shared/sounds/badnoise2.ogg diff --git a/assets/shared/sounds/badnoise3.mp3 b/base_game/shared/sounds/badnoise3.mp3 similarity index 100% rename from assets/shared/sounds/badnoise3.mp3 rename to base_game/shared/sounds/badnoise3.mp3 diff --git a/assets/shared/sounds/badnoise3.ogg b/base_game/shared/sounds/badnoise3.ogg similarity index 100% rename from assets/shared/sounds/badnoise3.ogg rename to base_game/shared/sounds/badnoise3.ogg diff --git a/base_game/shared/sounds/clickText.mp3 b/base_game/shared/sounds/clickText.mp3 new file mode 100644 index 0000000..05ad896 Binary files /dev/null and b/base_game/shared/sounds/clickText.mp3 differ diff --git a/base_game/shared/sounds/clickText.ogg b/base_game/shared/sounds/clickText.ogg new file mode 100644 index 0000000..b448d7c Binary files /dev/null and b/base_game/shared/sounds/clickText.ogg differ diff --git a/base_game/shared/sounds/dialogue.mp3 b/base_game/shared/sounds/dialogue.mp3 new file mode 100644 index 0000000..1ace7ba Binary files /dev/null and b/base_game/shared/sounds/dialogue.mp3 differ diff --git a/base_game/shared/sounds/dialogue.ogg b/base_game/shared/sounds/dialogue.ogg new file mode 100644 index 0000000..1dac75b Binary files /dev/null and b/base_game/shared/sounds/dialogue.ogg differ diff --git a/base_game/shared/sounds/dialogueClose.mp3 b/base_game/shared/sounds/dialogueClose.mp3 new file mode 100644 index 0000000..5882f08 Binary files /dev/null and b/base_game/shared/sounds/dialogueClose.mp3 differ diff --git a/base_game/shared/sounds/dialogueClose.ogg b/base_game/shared/sounds/dialogueClose.ogg new file mode 100644 index 0000000..c871e48 Binary files /dev/null and b/base_game/shared/sounds/dialogueClose.ogg differ diff --git a/assets/week6_sfx/sounds/fnf_loss_sfx-pixel.mp3 b/base_game/shared/sounds/fnf_loss_sfx-pixel.mp3 similarity index 100% rename from assets/week6_sfx/sounds/fnf_loss_sfx-pixel.mp3 rename to base_game/shared/sounds/fnf_loss_sfx-pixel.mp3 diff --git a/assets/week6_sfx/sounds/fnf_loss_sfx-pixel.ogg b/base_game/shared/sounds/fnf_loss_sfx-pixel.ogg similarity index 100% rename from assets/week6_sfx/sounds/fnf_loss_sfx-pixel.ogg rename to base_game/shared/sounds/fnf_loss_sfx-pixel.ogg diff --git a/base_game/shared/sounds/fnf_loss_sfx.mp3 b/base_game/shared/sounds/fnf_loss_sfx.mp3 new file mode 100644 index 0000000..8d95560 Binary files /dev/null and b/base_game/shared/sounds/fnf_loss_sfx.mp3 differ diff --git a/base_game/shared/sounds/fnf_loss_sfx.ogg b/base_game/shared/sounds/fnf_loss_sfx.ogg new file mode 100644 index 0000000..1d70db2 Binary files /dev/null and b/base_game/shared/sounds/fnf_loss_sfx.ogg differ diff --git a/base_game/shared/sounds/hitsound.mp3 b/base_game/shared/sounds/hitsound.mp3 new file mode 100644 index 0000000..fe275df Binary files /dev/null and b/base_game/shared/sounds/hitsound.mp3 differ diff --git a/base_game/shared/sounds/hitsound.ogg b/base_game/shared/sounds/hitsound.ogg new file mode 100644 index 0000000..67b1f99 Binary files /dev/null and b/base_game/shared/sounds/hitsound.ogg differ diff --git a/base_game/shared/sounds/intro1.mp3 b/base_game/shared/sounds/intro1.mp3 new file mode 100644 index 0000000..93e77a6 Binary files /dev/null and b/base_game/shared/sounds/intro1.mp3 differ diff --git a/base_game/shared/sounds/intro1.ogg b/base_game/shared/sounds/intro1.ogg new file mode 100644 index 0000000..a42cb17 Binary files /dev/null and b/base_game/shared/sounds/intro1.ogg differ diff --git a/base_game/shared/sounds/intro2.mp3 b/base_game/shared/sounds/intro2.mp3 new file mode 100644 index 0000000..7b956c7 Binary files /dev/null and b/base_game/shared/sounds/intro2.mp3 differ diff --git a/base_game/shared/sounds/intro2.ogg b/base_game/shared/sounds/intro2.ogg new file mode 100644 index 0000000..990701b Binary files /dev/null and b/base_game/shared/sounds/intro2.ogg differ diff --git a/base_game/shared/sounds/intro3.mp3 b/base_game/shared/sounds/intro3.mp3 new file mode 100644 index 0000000..51d656d Binary files /dev/null and b/base_game/shared/sounds/intro3.mp3 differ diff --git a/base_game/shared/sounds/intro3.ogg b/base_game/shared/sounds/intro3.ogg new file mode 100644 index 0000000..dc66050 Binary files /dev/null and b/base_game/shared/sounds/intro3.ogg differ diff --git a/base_game/shared/sounds/introGo.mp3 b/base_game/shared/sounds/introGo.mp3 new file mode 100644 index 0000000..fcb349e Binary files /dev/null and b/base_game/shared/sounds/introGo.mp3 differ diff --git a/base_game/shared/sounds/introGo.ogg b/base_game/shared/sounds/introGo.ogg new file mode 100644 index 0000000..b1f0086 Binary files /dev/null and b/base_game/shared/sounds/introGo.ogg differ diff --git a/base_game/shared/sounds/missnote1.mp3 b/base_game/shared/sounds/missnote1.mp3 new file mode 100644 index 0000000..a3fe025 Binary files /dev/null and b/base_game/shared/sounds/missnote1.mp3 differ diff --git a/base_game/shared/sounds/missnote1.ogg b/base_game/shared/sounds/missnote1.ogg new file mode 100644 index 0000000..b8bfc8e Binary files /dev/null and b/base_game/shared/sounds/missnote1.ogg differ diff --git a/base_game/shared/sounds/missnote2.mp3 b/base_game/shared/sounds/missnote2.mp3 new file mode 100644 index 0000000..e0384ca Binary files /dev/null and b/base_game/shared/sounds/missnote2.mp3 differ diff --git a/base_game/shared/sounds/missnote2.ogg b/base_game/shared/sounds/missnote2.ogg new file mode 100644 index 0000000..c38c484 Binary files /dev/null and b/base_game/shared/sounds/missnote2.ogg differ diff --git a/base_game/shared/sounds/missnote3.mp3 b/base_game/shared/sounds/missnote3.mp3 new file mode 100644 index 0000000..fe119c9 Binary files /dev/null and b/base_game/shared/sounds/missnote3.mp3 differ diff --git a/base_game/shared/sounds/missnote3.ogg b/base_game/shared/sounds/missnote3.ogg new file mode 100644 index 0000000..59506db Binary files /dev/null and b/base_game/shared/sounds/missnote3.ogg differ diff --git a/assets/week6_sfx/sounds/pixelText.mp3 b/base_game/shared/sounds/pixelText.mp3 similarity index 100% rename from assets/week6_sfx/sounds/pixelText.mp3 rename to base_game/shared/sounds/pixelText.mp3 diff --git a/assets/week6_sfx/sounds/pixelText.ogg b/base_game/shared/sounds/pixelText.ogg similarity index 100% rename from assets/week6_sfx/sounds/pixelText.ogg rename to base_game/shared/sounds/pixelText.ogg diff --git a/base_game/shared/sounds/soundTest.mp3 b/base_game/shared/sounds/soundTest.mp3 new file mode 100644 index 0000000..f625b1a Binary files /dev/null and b/base_game/shared/sounds/soundTest.mp3 differ diff --git a/base_game/shared/sounds/soundTest.ogg b/base_game/shared/sounds/soundTest.ogg new file mode 100644 index 0000000..4eea479 Binary files /dev/null and b/base_game/shared/sounds/soundTest.ogg differ diff --git a/base_game/shared/sounds/sounds-go-here.txt b/base_game/shared/sounds/sounds-go-here.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/songs/blammed/Inst.mp3 b/base_game/songs/blammed/Inst.mp3 similarity index 100% rename from assets/songs/blammed/Inst.mp3 rename to base_game/songs/blammed/Inst.mp3 diff --git a/assets/songs/blammed/Inst.ogg b/base_game/songs/blammed/Inst.ogg similarity index 100% rename from assets/songs/blammed/Inst.ogg rename to base_game/songs/blammed/Inst.ogg diff --git a/assets/songs/blammed/Voices.mp3 b/base_game/songs/blammed/Voices.mp3 similarity index 100% rename from assets/songs/blammed/Voices.mp3 rename to base_game/songs/blammed/Voices.mp3 diff --git a/assets/songs/blammed/Voices.ogg b/base_game/songs/blammed/Voices.ogg similarity index 100% rename from assets/songs/blammed/Voices.ogg rename to base_game/songs/blammed/Voices.ogg diff --git a/assets/songs/bopeebo/Inst.mp3 b/base_game/songs/bopeebo/Inst.mp3 similarity index 100% rename from assets/songs/bopeebo/Inst.mp3 rename to base_game/songs/bopeebo/Inst.mp3 diff --git a/assets/songs/bopeebo/Inst.ogg b/base_game/songs/bopeebo/Inst.ogg similarity index 100% rename from assets/songs/bopeebo/Inst.ogg rename to base_game/songs/bopeebo/Inst.ogg diff --git a/assets/songs/bopeebo/Voices.mp3 b/base_game/songs/bopeebo/Voices.mp3 similarity index 100% rename from assets/songs/bopeebo/Voices.mp3 rename to base_game/songs/bopeebo/Voices.mp3 diff --git a/assets/songs/bopeebo/Voices.ogg b/base_game/songs/bopeebo/Voices.ogg similarity index 100% rename from assets/songs/bopeebo/Voices.ogg rename to base_game/songs/bopeebo/Voices.ogg diff --git a/assets/songs/cocoa/Inst.mp3 b/base_game/songs/cocoa/Inst.mp3 similarity index 100% rename from assets/songs/cocoa/Inst.mp3 rename to base_game/songs/cocoa/Inst.mp3 diff --git a/assets/songs/cocoa/Inst.ogg b/base_game/songs/cocoa/Inst.ogg similarity index 100% rename from assets/songs/cocoa/Inst.ogg rename to base_game/songs/cocoa/Inst.ogg diff --git a/assets/songs/cocoa/Voices.mp3 b/base_game/songs/cocoa/Voices.mp3 similarity index 100% rename from assets/songs/cocoa/Voices.mp3 rename to base_game/songs/cocoa/Voices.mp3 diff --git a/assets/songs/cocoa/Voices.ogg b/base_game/songs/cocoa/Voices.ogg similarity index 100% rename from assets/songs/cocoa/Voices.ogg rename to base_game/songs/cocoa/Voices.ogg diff --git a/assets/songs/dad-battle/Inst.mp3 b/base_game/songs/dad-battle/Inst.mp3 similarity index 100% rename from assets/songs/dad-battle/Inst.mp3 rename to base_game/songs/dad-battle/Inst.mp3 diff --git a/assets/songs/dad-battle/Inst.ogg b/base_game/songs/dad-battle/Inst.ogg similarity index 100% rename from assets/songs/dad-battle/Inst.ogg rename to base_game/songs/dad-battle/Inst.ogg diff --git a/assets/songs/dad-battle/Voices.mp3 b/base_game/songs/dad-battle/Voices.mp3 similarity index 100% rename from assets/songs/dad-battle/Voices.mp3 rename to base_game/songs/dad-battle/Voices.mp3 diff --git a/assets/songs/dad-battle/Voices.ogg b/base_game/songs/dad-battle/Voices.ogg similarity index 100% rename from assets/songs/dad-battle/Voices.ogg rename to base_game/songs/dad-battle/Voices.ogg diff --git a/assets/songs/eggnog/Inst.mp3 b/base_game/songs/eggnog/Inst.mp3 similarity index 100% rename from assets/songs/eggnog/Inst.mp3 rename to base_game/songs/eggnog/Inst.mp3 diff --git a/assets/songs/eggnog/Inst.ogg b/base_game/songs/eggnog/Inst.ogg similarity index 100% rename from assets/songs/eggnog/Inst.ogg rename to base_game/songs/eggnog/Inst.ogg diff --git a/assets/songs/eggnog/Voices.mp3 b/base_game/songs/eggnog/Voices.mp3 similarity index 100% rename from assets/songs/eggnog/Voices.mp3 rename to base_game/songs/eggnog/Voices.mp3 diff --git a/assets/songs/eggnog/Voices.ogg b/base_game/songs/eggnog/Voices.ogg similarity index 100% rename from assets/songs/eggnog/Voices.ogg rename to base_game/songs/eggnog/Voices.ogg diff --git a/assets/songs/fresh/Inst.mp3 b/base_game/songs/fresh/Inst.mp3 similarity index 100% rename from assets/songs/fresh/Inst.mp3 rename to base_game/songs/fresh/Inst.mp3 diff --git a/assets/songs/fresh/Inst.ogg b/base_game/songs/fresh/Inst.ogg similarity index 100% rename from assets/songs/fresh/Inst.ogg rename to base_game/songs/fresh/Inst.ogg diff --git a/assets/songs/fresh/Voices.mp3 b/base_game/songs/fresh/Voices.mp3 similarity index 100% rename from assets/songs/fresh/Voices.mp3 rename to base_game/songs/fresh/Voices.mp3 diff --git a/assets/songs/fresh/Voices.ogg b/base_game/songs/fresh/Voices.ogg similarity index 100% rename from assets/songs/fresh/Voices.ogg rename to base_game/songs/fresh/Voices.ogg diff --git a/assets/songs/guns/Inst.mp3 b/base_game/songs/guns/Inst.mp3 similarity index 100% rename from assets/songs/guns/Inst.mp3 rename to base_game/songs/guns/Inst.mp3 diff --git a/assets/songs/guns/Inst.ogg b/base_game/songs/guns/Inst.ogg similarity index 100% rename from assets/songs/guns/Inst.ogg rename to base_game/songs/guns/Inst.ogg diff --git a/assets/songs/guns/Voices.mp3 b/base_game/songs/guns/Voices.mp3 similarity index 100% rename from assets/songs/guns/Voices.mp3 rename to base_game/songs/guns/Voices.mp3 diff --git a/assets/songs/guns/Voices.ogg b/base_game/songs/guns/Voices.ogg similarity index 100% rename from assets/songs/guns/Voices.ogg rename to base_game/songs/guns/Voices.ogg diff --git a/assets/songs/high/Inst.mp3 b/base_game/songs/high/Inst.mp3 similarity index 100% rename from assets/songs/high/Inst.mp3 rename to base_game/songs/high/Inst.mp3 diff --git a/assets/songs/high/Inst.ogg b/base_game/songs/high/Inst.ogg similarity index 100% rename from assets/songs/high/Inst.ogg rename to base_game/songs/high/Inst.ogg diff --git a/assets/songs/high/Voices.mp3 b/base_game/songs/high/Voices.mp3 similarity index 100% rename from assets/songs/high/Voices.mp3 rename to base_game/songs/high/Voices.mp3 diff --git a/assets/songs/high/Voices.ogg b/base_game/songs/high/Voices.ogg similarity index 100% rename from assets/songs/high/Voices.ogg rename to base_game/songs/high/Voices.ogg diff --git a/assets/songs/milf/Inst.mp3 b/base_game/songs/milf/Inst.mp3 similarity index 100% rename from assets/songs/milf/Inst.mp3 rename to base_game/songs/milf/Inst.mp3 diff --git a/assets/songs/milf/Inst.ogg b/base_game/songs/milf/Inst.ogg similarity index 100% rename from assets/songs/milf/Inst.ogg rename to base_game/songs/milf/Inst.ogg diff --git a/assets/songs/milf/Voices.mp3 b/base_game/songs/milf/Voices.mp3 similarity index 100% rename from assets/songs/milf/Voices.mp3 rename to base_game/songs/milf/Voices.mp3 diff --git a/assets/songs/milf/Voices.ogg b/base_game/songs/milf/Voices.ogg similarity index 100% rename from assets/songs/milf/Voices.ogg rename to base_game/songs/milf/Voices.ogg diff --git a/assets/songs/monster/Inst.mp3 b/base_game/songs/monster/Inst.mp3 similarity index 100% rename from assets/songs/monster/Inst.mp3 rename to base_game/songs/monster/Inst.mp3 diff --git a/assets/songs/monster/Inst.ogg b/base_game/songs/monster/Inst.ogg similarity index 100% rename from assets/songs/monster/Inst.ogg rename to base_game/songs/monster/Inst.ogg diff --git a/assets/songs/monster/Voices.mp3 b/base_game/songs/monster/Voices.mp3 similarity index 100% rename from assets/songs/monster/Voices.mp3 rename to base_game/songs/monster/Voices.mp3 diff --git a/assets/songs/monster/Voices.ogg b/base_game/songs/monster/Voices.ogg similarity index 100% rename from assets/songs/monster/Voices.ogg rename to base_game/songs/monster/Voices.ogg diff --git a/assets/songs/philly-nice/Inst.mp3 b/base_game/songs/philly-nice/Inst.mp3 similarity index 100% rename from assets/songs/philly-nice/Inst.mp3 rename to base_game/songs/philly-nice/Inst.mp3 diff --git a/assets/songs/philly-nice/Inst.ogg b/base_game/songs/philly-nice/Inst.ogg similarity index 100% rename from assets/songs/philly-nice/Inst.ogg rename to base_game/songs/philly-nice/Inst.ogg diff --git a/assets/songs/philly-nice/Voices.mp3 b/base_game/songs/philly-nice/Voices.mp3 similarity index 100% rename from assets/songs/philly-nice/Voices.mp3 rename to base_game/songs/philly-nice/Voices.mp3 diff --git a/assets/songs/philly-nice/Voices.ogg b/base_game/songs/philly-nice/Voices.ogg similarity index 100% rename from assets/songs/philly-nice/Voices.ogg rename to base_game/songs/philly-nice/Voices.ogg diff --git a/assets/songs/pico/Inst.mp3 b/base_game/songs/pico/Inst.mp3 similarity index 100% rename from assets/songs/pico/Inst.mp3 rename to base_game/songs/pico/Inst.mp3 diff --git a/assets/songs/pico/Inst.ogg b/base_game/songs/pico/Inst.ogg similarity index 100% rename from assets/songs/pico/Inst.ogg rename to base_game/songs/pico/Inst.ogg diff --git a/assets/songs/pico/Voices.mp3 b/base_game/songs/pico/Voices.mp3 similarity index 100% rename from assets/songs/pico/Voices.mp3 rename to base_game/songs/pico/Voices.mp3 diff --git a/assets/songs/pico/Voices.ogg b/base_game/songs/pico/Voices.ogg similarity index 100% rename from assets/songs/pico/Voices.ogg rename to base_game/songs/pico/Voices.ogg diff --git a/assets/songs/roses/Inst.mp3 b/base_game/songs/roses/Inst.mp3 similarity index 100% rename from assets/songs/roses/Inst.mp3 rename to base_game/songs/roses/Inst.mp3 diff --git a/assets/songs/roses/Inst.ogg b/base_game/songs/roses/Inst.ogg similarity index 100% rename from assets/songs/roses/Inst.ogg rename to base_game/songs/roses/Inst.ogg diff --git a/assets/songs/roses/Voices.mp3 b/base_game/songs/roses/Voices.mp3 similarity index 100% rename from assets/songs/roses/Voices.mp3 rename to base_game/songs/roses/Voices.mp3 diff --git a/assets/songs/roses/Voices.ogg b/base_game/songs/roses/Voices.ogg similarity index 100% rename from assets/songs/roses/Voices.ogg rename to base_game/songs/roses/Voices.ogg diff --git a/assets/songs/satin-panties/Inst.mp3 b/base_game/songs/satin-panties/Inst.mp3 similarity index 100% rename from assets/songs/satin-panties/Inst.mp3 rename to base_game/songs/satin-panties/Inst.mp3 diff --git a/assets/songs/satin-panties/Inst.ogg b/base_game/songs/satin-panties/Inst.ogg similarity index 100% rename from assets/songs/satin-panties/Inst.ogg rename to base_game/songs/satin-panties/Inst.ogg diff --git a/assets/songs/satin-panties/Voices.mp3 b/base_game/songs/satin-panties/Voices.mp3 similarity index 100% rename from assets/songs/satin-panties/Voices.mp3 rename to base_game/songs/satin-panties/Voices.mp3 diff --git a/assets/songs/satin-panties/Voices.ogg b/base_game/songs/satin-panties/Voices.ogg similarity index 100% rename from assets/songs/satin-panties/Voices.ogg rename to base_game/songs/satin-panties/Voices.ogg diff --git a/assets/songs/senpai/Inst.mp3 b/base_game/songs/senpai/Inst.mp3 similarity index 100% rename from assets/songs/senpai/Inst.mp3 rename to base_game/songs/senpai/Inst.mp3 diff --git a/assets/songs/senpai/Inst.ogg b/base_game/songs/senpai/Inst.ogg similarity index 100% rename from assets/songs/senpai/Inst.ogg rename to base_game/songs/senpai/Inst.ogg diff --git a/assets/songs/senpai/Voices.mp3 b/base_game/songs/senpai/Voices.mp3 similarity index 100% rename from assets/songs/senpai/Voices.mp3 rename to base_game/songs/senpai/Voices.mp3 diff --git a/assets/songs/senpai/Voices.ogg b/base_game/songs/senpai/Voices.ogg similarity index 100% rename from assets/songs/senpai/Voices.ogg rename to base_game/songs/senpai/Voices.ogg diff --git a/assets/songs/south/Inst.mp3 b/base_game/songs/south/Inst.mp3 similarity index 100% rename from assets/songs/south/Inst.mp3 rename to base_game/songs/south/Inst.mp3 diff --git a/assets/songs/south/Inst.ogg b/base_game/songs/south/Inst.ogg similarity index 100% rename from assets/songs/south/Inst.ogg rename to base_game/songs/south/Inst.ogg diff --git a/assets/songs/south/Voices.mp3 b/base_game/songs/south/Voices.mp3 similarity index 100% rename from assets/songs/south/Voices.mp3 rename to base_game/songs/south/Voices.mp3 diff --git a/assets/songs/south/Voices.ogg b/base_game/songs/south/Voices.ogg similarity index 100% rename from assets/songs/south/Voices.ogg rename to base_game/songs/south/Voices.ogg diff --git a/assets/songs/spookeez/Inst.mp3 b/base_game/songs/spookeez/Inst.mp3 similarity index 100% rename from assets/songs/spookeez/Inst.mp3 rename to base_game/songs/spookeez/Inst.mp3 diff --git a/assets/songs/spookeez/Inst.ogg b/base_game/songs/spookeez/Inst.ogg similarity index 100% rename from assets/songs/spookeez/Inst.ogg rename to base_game/songs/spookeez/Inst.ogg diff --git a/assets/songs/spookeez/Voices.mp3 b/base_game/songs/spookeez/Voices.mp3 similarity index 100% rename from assets/songs/spookeez/Voices.mp3 rename to base_game/songs/spookeez/Voices.mp3 diff --git a/assets/songs/spookeez/Voices.ogg b/base_game/songs/spookeez/Voices.ogg similarity index 100% rename from assets/songs/spookeez/Voices.ogg rename to base_game/songs/spookeez/Voices.ogg diff --git a/assets/songs/stress/Inst.mp3 b/base_game/songs/stress/Inst.mp3 similarity index 100% rename from assets/songs/stress/Inst.mp3 rename to base_game/songs/stress/Inst.mp3 diff --git a/assets/songs/stress/Inst.ogg b/base_game/songs/stress/Inst.ogg similarity index 100% rename from assets/songs/stress/Inst.ogg rename to base_game/songs/stress/Inst.ogg diff --git a/assets/songs/stress/Voices.mp3 b/base_game/songs/stress/Voices.mp3 similarity index 100% rename from assets/songs/stress/Voices.mp3 rename to base_game/songs/stress/Voices.mp3 diff --git a/assets/songs/stress/Voices.ogg b/base_game/songs/stress/Voices.ogg similarity index 100% rename from assets/songs/stress/Voices.ogg rename to base_game/songs/stress/Voices.ogg diff --git a/assets/songs/thorns/Inst.mp3 b/base_game/songs/thorns/Inst.mp3 similarity index 100% rename from assets/songs/thorns/Inst.mp3 rename to base_game/songs/thorns/Inst.mp3 diff --git a/assets/songs/thorns/Inst.ogg b/base_game/songs/thorns/Inst.ogg similarity index 100% rename from assets/songs/thorns/Inst.ogg rename to base_game/songs/thorns/Inst.ogg diff --git a/assets/songs/thorns/Voices.mp3 b/base_game/songs/thorns/Voices.mp3 similarity index 100% rename from assets/songs/thorns/Voices.mp3 rename to base_game/songs/thorns/Voices.mp3 diff --git a/assets/songs/thorns/Voices.ogg b/base_game/songs/thorns/Voices.ogg similarity index 100% rename from assets/songs/thorns/Voices.ogg rename to base_game/songs/thorns/Voices.ogg diff --git a/assets/songs/tutorial/Inst.mp3 b/base_game/songs/tutorial/Inst.mp3 similarity index 100% rename from assets/songs/tutorial/Inst.mp3 rename to base_game/songs/tutorial/Inst.mp3 diff --git a/assets/songs/tutorial/Inst.ogg b/base_game/songs/tutorial/Inst.ogg similarity index 100% rename from assets/songs/tutorial/Inst.ogg rename to base_game/songs/tutorial/Inst.ogg diff --git a/assets/songs/ugh/Inst.mp3 b/base_game/songs/ugh/Inst.mp3 similarity index 100% rename from assets/songs/ugh/Inst.mp3 rename to base_game/songs/ugh/Inst.mp3 diff --git a/assets/songs/ugh/Inst.ogg b/base_game/songs/ugh/Inst.ogg similarity index 100% rename from assets/songs/ugh/Inst.ogg rename to base_game/songs/ugh/Inst.ogg diff --git a/assets/songs/ugh/Voices.mp3 b/base_game/songs/ugh/Voices.mp3 similarity index 100% rename from assets/songs/ugh/Voices.mp3 rename to base_game/songs/ugh/Voices.mp3 diff --git a/assets/songs/ugh/Voices.ogg b/base_game/songs/ugh/Voices.ogg similarity index 100% rename from assets/songs/ugh/Voices.ogg rename to base_game/songs/ugh/Voices.ogg diff --git a/assets/songs/winter-horrorland/Inst.mp3 b/base_game/songs/winter-horrorland/Inst.mp3 similarity index 100% rename from assets/songs/winter-horrorland/Inst.mp3 rename to base_game/songs/winter-horrorland/Inst.mp3 diff --git a/assets/songs/winter-horrorland/Inst.ogg b/base_game/songs/winter-horrorland/Inst.ogg similarity index 100% rename from assets/songs/winter-horrorland/Inst.ogg rename to base_game/songs/winter-horrorland/Inst.ogg diff --git a/assets/songs/winter-horrorland/Voices.mp3 b/base_game/songs/winter-horrorland/Voices.mp3 similarity index 100% rename from assets/songs/winter-horrorland/Voices.mp3 rename to base_game/songs/winter-horrorland/Voices.mp3 diff --git a/assets/songs/winter-horrorland/Voices.ogg b/base_game/songs/winter-horrorland/Voices.ogg similarity index 100% rename from assets/songs/winter-horrorland/Voices.ogg rename to base_game/songs/winter-horrorland/Voices.ogg diff --git a/assets/preload/stages/limo.json b/base_game/stages/limo.json similarity index 100% rename from assets/preload/stages/limo.json rename to base_game/stages/limo.json diff --git a/assets/preload/stages/mall.json b/base_game/stages/mall.json similarity index 100% rename from assets/preload/stages/mall.json rename to base_game/stages/mall.json diff --git a/assets/preload/stages/mallEvil.json b/base_game/stages/mallEvil.json similarity index 100% rename from assets/preload/stages/mallEvil.json rename to base_game/stages/mallEvil.json diff --git a/assets/preload/stages/philly.json b/base_game/stages/philly.json similarity index 100% rename from assets/preload/stages/philly.json rename to base_game/stages/philly.json diff --git a/assets/preload/stages/school.json b/base_game/stages/school.json similarity index 100% rename from assets/preload/stages/school.json rename to base_game/stages/school.json diff --git a/assets/preload/stages/schoolEvil.json b/base_game/stages/schoolEvil.json similarity index 100% rename from assets/preload/stages/schoolEvil.json rename to base_game/stages/schoolEvil.json diff --git a/assets/preload/stages/spooky.json b/base_game/stages/spooky.json similarity index 100% rename from assets/preload/stages/spooky.json rename to base_game/stages/spooky.json diff --git a/base_game/stages/stage.json b/base_game/stages/stage.json new file mode 100644 index 0000000..5728299 --- /dev/null +++ b/base_game/stages/stage.json @@ -0,0 +1,15 @@ +{ + "directory": "", + "defaultZoom": 0.9, + "isPixelStage": false, + + "boyfriend": [770, 100], + "girlfriend": [400, 130], + "opponent": [100, 100], + "hide_girlfriend": false, + + "camera_boyfriend": [0, 0], + "camera_opponent": [0, 0], + "camera_girlfriend": [0, 0], + "camera_speed": 1 +} \ No newline at end of file diff --git a/assets/preload/stages/tank.json b/base_game/stages/tank.json similarity index 100% rename from assets/preload/stages/tank.json rename to base_game/stages/tank.json diff --git a/base_game/videos/put-your-videos-here.txt b/base_game/videos/put-your-videos-here.txt new file mode 100644 index 0000000..01e28a4 --- /dev/null +++ b/base_game/videos/put-your-videos-here.txt @@ -0,0 +1 @@ +They have to be in .mp4 and 1280x720 resolution!!! \ No newline at end of file diff --git a/assets/preload/weeks/tutorial.json b/base_game/weeks/tutorial.json similarity index 100% rename from assets/preload/weeks/tutorial.json rename to base_game/weeks/tutorial.json diff --git a/assets/preload/weeks/week1.json b/base_game/weeks/week1.json similarity index 100% rename from assets/preload/weeks/week1.json rename to base_game/weeks/week1.json diff --git a/assets/preload/weeks/week2.json b/base_game/weeks/week2.json similarity index 100% rename from assets/preload/weeks/week2.json rename to base_game/weeks/week2.json diff --git a/assets/preload/weeks/week3.json b/base_game/weeks/week3.json similarity index 100% rename from assets/preload/weeks/week3.json rename to base_game/weeks/week3.json diff --git a/assets/preload/weeks/week4.json b/base_game/weeks/week4.json similarity index 100% rename from assets/preload/weeks/week4.json rename to base_game/weeks/week4.json diff --git a/assets/preload/weeks/week5.json b/base_game/weeks/week5.json similarity index 100% rename from assets/preload/weeks/week5.json rename to base_game/weeks/week5.json diff --git a/assets/preload/weeks/week6.json b/base_game/weeks/week6.json similarity index 100% rename from assets/preload/weeks/week6.json rename to base_game/weeks/week6.json diff --git a/assets/preload/weeks/week7.json b/base_game/weeks/week7.json similarity index 100% rename from assets/preload/weeks/week7.json rename to base_game/weeks/week7.json diff --git a/base_game/weeks/weekList.txt b/base_game/weeks/weekList.txt new file mode 100644 index 0000000..724e580 --- /dev/null +++ b/base_game/weeks/weekList.txt @@ -0,0 +1,8 @@ +tutorial +week1 +week2 +week3 +week4 +week5 +week6 +week7 \ No newline at end of file diff --git a/setup/source-config-unix.sh b/setup/source-config-unix.sh new file mode 100644 index 0000000..794b82a --- /dev/null +++ b/setup/source-config-unix.sh @@ -0,0 +1,37 @@ +#!/bin/bash +clear +echo +echo "Installing necessary libraries. Please wait..." +echo + +haxelib setup ~/haxelib + +haxelib install tjson --quiet +haxelib install hxjsonast --quiet +haxelib install flxgif --quiet +haxelib set flixel 6.1.1 +haxelib git lime https://github.com/GreenColdTea/lime-9.0.0 +haxelib git openfl https://github.com/GreenColdTea/openfl.git +haxelib install format --quiet +haxelib install hxp --quiet +haxelib install flixel-waveform --quiet --skip-dependencies +haxelib run lime setup flixel +haxelib set flixel-tools 1.5.1 +haxelib set flixel-addons 3.3.2 +haxelib set hxdiscord_rpc 1.3.0 +haxelib set hxopus 2.0.0 +haxelib git away3d https://github.com/openfl/away3d.git +haxelib git hxcpp https://github.com/FunkinCrew/hxcpp +haxelib git hxvlc https://github.com/MAJigsaw77/hxvlc.git --quiet --skip-dependencies +haxelib git flxsoundfilters https://github.com/TheZoroForce240/FlxSoundFilters.git +haxelib git rulescript https://github.com/Kriptel/RuleScript.git dev --skip-dependencies +haxelib git hscript https://github.com/HaxeFoundation/hscript.git 92ffe9c519bbccf783df0b3400698c5b3cc645ef +haxelib git sl-windows-api https://github.com/GreenColdTea/windows-api-improved.git +haxelib git flixel-animate https://github.com/MaybeMaru/flixel-animate.git 220463c8089444d6c9957b68919fe88e6cba495f +haxelib git hxluajit https://github.com/MAJigsaw77/hxluajit.git +haxelib git hxluajit-wrapper https://github.com/MAJigsaw77/hxluajit-wrapper.git --skip-dependencies +haxelib list + +echo +read -n 1 -s -r -p "Done! Press any key to close the app!" +echo diff --git a/setup/source-config-windows.bat b/setup/source-config-windows.bat index 356d500..f4eba23 100644 --- a/setup/source-config-windows.bat +++ b/setup/source-config-windows.bat @@ -8,23 +8,27 @@ haxelib setup C:\haxelib haxelib install tjson --quiet haxelib install hxjsonast --quiet haxelib install flxgif --quiet -haxelib set flixel 6.1.0 +haxelib set flixel 6.1.1 haxelib git lime https://github.com/GreenColdTea/lime-9.0.0 -haxelib install format -haxelib install hxp -haxelib set openfl 9.4.1 -haxelib install hxvlc --quiet --skip-dependencies +haxelib git openfl https://github.com/GreenColdTea/openfl.git +haxelib install format --quiet +haxelib install hxp --quiet +haxelib install flixel-waveform --quiet --skip-dependencies haxelib run lime setup flixel haxelib set flixel-tools 1.5.1 haxelib set flixel-addons 3.3.2 haxelib set hxdiscord_rpc 1.3.0 +haxelib set hxopus 2.0.0 +haxelib git away3d https://github.com/openfl/away3d.git haxelib git hxcpp https://github.com/FunkinCrew/hxcpp +haxelib git hxvlc https://github.com/MAJigsaw77/hxvlc.git --quiet --skip-dependencies haxelib git flxsoundfilters https://github.com/TheZoroForce240/FlxSoundFilters.git -haxelib git rulescript https://github.com/Kriptel/RuleScript.git dev -haxelib git hscript https://github.com/HaxeFoundation/hscript.git +haxelib git rulescript https://github.com/Kriptel/RuleScript.git dev --skip-dependencies +haxelib git hscript https://github.com/HaxeFoundation/hscript.git 92ffe9c519bbccf783df0b3400698c5b3cc645ef haxelib git sl-windows-api https://github.com/GreenColdTea/windows-api-improved.git -haxelib git flixel-animate https://github.com/MaybeMaru/flixel-animate.git 40ea31d4b598a01411c8c96f52702e090478ba1f -haxelib git linc_luajit https://github.com/superpowers04/linc_luajit.git +haxelib git flixel-animate https://github.com/MaybeMaru/flixel-animate.git 220463c8089444d6c9957b68919fe88e6cba495f +haxelib git hxluajit https://github.com/MAJigsaw77/hxluajit.git +haxelib git hxluajit-wrapper https://github.com/MAJigsaw77/hxluajit-wrapper.git --skip-dependencies haxelib list echo. echo Done! Press any key to close the app! diff --git a/source/Init.hx b/source/Init.hx index 33c0e20..9a024a5 100644 --- a/source/Init.hx +++ b/source/Init.hx @@ -4,27 +4,31 @@ import flixel.FlxG; import flixel.FlxState; import flixel.input.keyboard.FlxKey; +import openfl.Lib; +import openfl.display.Sprite; +import openfl.display.StageScaleMode; + import game.backend.PlayerSettings; import game.backend.WeekData; import game.backend.utils.CoolUtil; import game.states.StoryMenuState; +import game.backend.plugins.*; + class Init extends FlxState { public static var muteKeys:Array = [FlxKey.ZERO]; public static var volumeDownKeys:Array = [FlxKey.NUMPADMINUS, FlxKey.MINUS]; public static var volumeUpKeys:Array = [FlxKey.NUMPADPLUS, FlxKey.PLUS]; + public static var fpsVar:FPSCounterPlugin; + override function create() { game.backend.PlayerSettings.init(); FlxG.save.bind('ccengine', CoolUtil.getSavePath()); - #if GLOBAL_SCRIPTS - if(!game.scripting.HScriptGlobal.globalScriptActive) game.scripting.HScriptGlobal.addGlobalScript(); - #end - ClientPrefs.init(); game.backend.Highscore.load(); @@ -38,13 +42,21 @@ class Init extends FlxState StoryMenuState.weekCompleted = FlxG.save.data.weekCompleted; } - #if (LUA_ALLOWED && MODS_ALLOWED) - Paths.pushGlobalMods(); + #if MODS_ALLOWED + game.backend.system.Mods.pushGlobalMods(); WeekData.loadTheFirstEnabledMod(); #end + fpsVar = new FPSCounterPlugin(10, 3, 0xFFFFFF); + Lib.current.addChild(fpsVar); + Lib.current.stage.align = "tl"; + Lib.current.stage.scaleMode = StageScaleMode.NO_SCALE; + + if(fpsVar != null) fpsVar.visible = ClientPrefs.showFPS; + FlxG.fixedTimestep = false; FlxG.game.focusLostFramerate = #if mobile 30 #else 60 #end; + FlxG.cameras.useBufferLocking = true; FlxG.keys.preventDefaultKeys = [TAB]; #if html5 @@ -52,13 +64,34 @@ class Init extends FlxState FlxG.mouse.visible = false; #end - #if FEATURE_DEBUG_TRACY + #if (FEATURE_DEBUG_TRACY && !macro) openfl.Lib.current.stage.addEventListener(openfl.events.Event.EXIT_FRAME, (e:openfl.events.Event) -> cpp.vm.tracy.TracyProfiler.frameMark()); cpp.vm.tracy.TracyProfiler.setThreadName("main"); #end + pluginsLessGo(); + + #if desktop + FlxG.mouse.visible = false; + FlxG.mouse.useSystemCursor = true; + #end + + #if !html5 + FlxG.scaleMode = new flixel.FlxScaleMode(); + #end + + #if GLOBAL_SCRIPTS + if(!game.scripting.HScriptGlobal.globalScriptActive) game.scripting.HScriptGlobal.addGlobalScript(); + #end + FlxG.switchState(() -> new game.states.TitleState()); } + + private function pluginsLessGo() + { + HotReloadPlugin.init(); + DebugConsolePlugin.init(); + } } \ No newline at end of file diff --git a/source/Main.hx b/source/Main.hx index a55ad9e..09feacd 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -11,7 +11,6 @@ import flixel.FlxCamera; import flixel.input.keyboard.FlxKey; import openfl.Assets; import openfl.Lib; -import openfl.display.FPS; import openfl.display.Sprite; import openfl.events.Event; import openfl.display.StageScaleMode; @@ -24,17 +23,16 @@ import openfl.errors.Error; import game.backend.CrashHandler; #end -#if LUA_ALLOWED -import game.scripting.LuaCallbackHandler; -#end +import game.objects.FunkinSoundTray; -import game.backend.plugins.HotReloadPlugin; -import game.backend.plugins.CMDEnablingPlugin; +#if MODS_ALLOWED +import game.backend.system.Mods; +#end using StringTools; // NATIVE API STUFF, YOU CAN IGNORE THIS AND SCROLL // -#if (linux && !debug) +#if (linux && cpp && !debug) @:cppInclude('./_external/gamemode_client.h') @:cppFileCode('#define GAMEMODE_AUTO') #end @@ -51,8 +49,6 @@ class Main extends Sprite startFullscreen: false // if the game should start at fullscreen mode }; - public static var fpsVar:FPS; - //for colorblind mode public static var colorblindMode:Int = -1; public static var colorblindIntensity:Float = 1.0; @@ -73,6 +69,10 @@ class Main extends Sprite CrashHandler.init(); #end + #if MODS_ALLOWED + Application.current.onExit.add((_) -> Mods.clearTempFiles()); + #end + if (stage != null) { init(); @@ -123,44 +123,25 @@ class Main extends Sprite #end lime.Native.fixScaling(); - lime.Native.disableWinReport(); - - #if LUA_ALLOWED llua.Lua.set_callbacks_function(cpp.Callable.fromStaticFunction(LuaCallbackHandler.call)); #end - - #if VIDEOS_ALLOWED - hxvlc.util.Handle.init(#if (hxvlc >= "1.8.0") ['--no-lua'] #end); - #end - - addChild(new FlxGame(game.width, game.height, Init, #if (flixel < "5.0.0") game.zoom, #end game.framerate, game.framerate, game.skipSplash, game.startFullscreen)); + lime.Native.registerAsGame(); - pluginsLessGo(); - - #if desktop - FlxG.mouse.visible = false; - FlxG.mouse.useSystemCursor = true; + #if sl_windows_api + WindowsAPI.disableWindowsReport(); + //WindowsAPI.disableWindowsGhosting(); #end - #if !html5 - FlxG.scaleMode = new flixel.FlxScaleMode(); - #end + var push:FlxGame = new FlxGame(game.width, game.height, Init, #if (flixel < "5.0.0") game.zoom, #end game.framerate, game.framerate, game.skipSplash, game.startFullscreen); + @:privateAccess + push._customSoundTray = FunkinSoundTray; - #if !mobile - fpsVar = new FPS(10, 3, 0xFFFFFF); - addChild(fpsVar); - Lib.current.stage.align = "tl"; - Lib.current.stage.scaleMode = StageScaleMode.NO_SCALE; - if(fpsVar != null) { - fpsVar.visible = ClientPrefs.showFPS; - } - #end + addChild(push); FlxG.signals.gameResized.add((w, h) -> { - if (fpsVar != null) - fpsVar.positionFPS(10, 3, Math.min(w / FlxG.width, h / FlxG.height)); + Init.fpsVar?.positionFPS(10, 3, Math.min(w / FlxG.width, h / FlxG.height)); resetSpriteCache(this); - if (FlxG.cameras != null && FlxG.cameras.list != null) { + if (FlxG.cameras?.list != null) { for (cam in FlxG.cameras.list) { if (cam != null) resetSpriteCache(cam.flashSprite); @@ -179,6 +160,7 @@ class Main extends Sprite #end } + @:noCompletion private static function resetSpriteCache(sprite:Sprite):Void { @:privateAccess { if (sprite != null) @@ -300,10 +282,4 @@ class Main extends Sprite ClientPrefs.colorBlindIntensity = intensity; ClientPrefs.saveSettings(); } - - private function pluginsLessGo() - { - HotReloadPlugin.init(); - CMDEnablingPlugin.init(); - } } \ No newline at end of file diff --git a/source/RuleScriptAbstracts.txt b/source/RuleScriptAbstracts.txt index 7ef843a..6c5b0c6 100644 --- a/source/RuleScriptAbstracts.txt +++ b/source/RuleScriptAbstracts.txt @@ -13,3 +13,22 @@ flixel.util.typeLimit.OneOfThree flixel.util.typeLimit.OneOfFour flixel.util.typeLimit.NextState openfl.display.BlendMode +haxe.CallStack +haxe.EnumFlags +haxe.Int32 +haxe.Int64 +haxe.Rest +haxe.display.Protocol.HaxeRequestMethod +haxe.display.Protocol.HaxeNotificationMethod +haxe.display.Protocol.HaxeResponseErrorSeverity +haxe.ds.HashMap +haxe.ds.Vector +haxe.http.HttpStatus +haxe.io.Float32Array +haxe.io.Float64Array +haxe.io.Int32Array +haxe.io.Mime +haxe.io.Scheme +haxe.io.UInt8Array +haxe.io.UInt16Array +haxe.io.UInt32Array \ No newline at end of file diff --git a/source/api/Discord.hx b/source/api/Discord.hx index 302305b..aa630dc 100644 --- a/source/api/Discord.hx +++ b/source/api/Discord.hx @@ -7,8 +7,9 @@ import hxdiscord_rpc.Discord; import hxdiscord_rpc.Types; #if LUA_ALLOWED -import llua.Lua; -import llua.State; +import hxluajit.Lua; +import hxluajit.Types; +import hxluajit.wrapper.LuaUtils; #end class DiscordClient { @@ -124,8 +125,8 @@ class DiscordClient } #if LUA_ALLOWED - public static function addLuaCallbacks(lua:State) { - Lua_helper.add_callback(lua, "changePresence", function(details:String, state:Null, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float) { + public static function addLuaCallbacks(lua:cpp.RawPointer) { + LuaUtils.addFunction(lua, "changePresence", function(details:String, state:Null, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float) { #if DISCORD_ALLOWED changePresence(details, state, smallImageKey, hasStartTimestamp, endTimestamp); #else diff --git a/source/flixel/system/ui/FlxSoundTray.hx b/source/flixel/system/ui/FlxSoundTray.hx deleted file mode 100644 index 657ba6e..0000000 --- a/source/flixel/system/ui/FlxSoundTray.hx +++ /dev/null @@ -1,255 +0,0 @@ -package flixel.system.ui; - -#if FLX_SOUND_SYSTEM -import flixel.FlxG; -import flixel.system.FlxAssets; -import flixel.util.FlxColor; -import openfl.Lib; -import openfl.display.Bitmap; -import openfl.display.BitmapData; -import openfl.display.Sprite; -import openfl.display.Shape; -import openfl.text.TextField; -import openfl.text.TextFormat; -import openfl.text.TextFormatAlign; -import openfl.text.AntiAliasType; -#if flash -import openfl.text.GridFitType; -#end - -/** - * The flixel sound tray, the little volume meter that pops down sometimes. - * Accessed via `FlxG.game.soundTray` or `FlxG.sound.soundTray`. - */ -#if (flixel > "6.0.0") -@:allow(flixel.system.frontEnds.SoundFrontEnd) -#end -class FlxSoundTray extends Sprite -{ - /** - * Because reading any data from DisplayObject is insanely expensive in hxcpp, keep track of whether we need to update it or not. - */ - public var active:Bool; - - /** - * Helps us auto-hide the sound tray after a volume change. - */ - var _timer:Float; - - /** - * Volume indicator ring - */ - var _volumeRing:Shape; - - /** - * How wide the sound tray background is. - */ - var _width:Int = 70; - - var _defaultScale:Float = 2.0; - - /**The sound used when increasing the volume.**/ - public var volumeUpSound:String = "assets/sounds/volup"; - - /**The sound used when decreasing the volume.**/ - public var volumeDownSound:String = 'assets/sounds/voldown'; - - /**Whether or not changing the volume should make noise.**/ - public var silent:Bool = false; - - /** - * Ring params - */ - final RING_RADIUS:Float = 15; - final RING_THICKNESS:Float = 8; - final RING_CENTER_X:Float = 35; // horizontally - final RING_CENTER_Y:Float = 20; // vertically - - /** - * Sets up the "sound tray", the little volume meter that pops down sometimes. - */ - @:keep - public function new() - { - super(); - - FlxG.sound.cache('$volumeUpSound.ogg'); - FlxG.sound.cache('$volumeDownSound.ogg'); - - visible = false; - scaleX = _defaultScale; - scaleY = _defaultScale; - //bg - var tmp:Bitmap = new Bitmap(new BitmapData(_width, 52, true, 0x7F000000)); - screenCenter(); - addChild(tmp); - - var text:TextField = new TextField(); - text.width = tmp.width; - text.height = tmp.height; - text.multiline = true; - text.wordWrap = true; - text.selectable = false; - text.antiAliasType = AntiAliasType.ADVANCED; - - #if flash - text.embedFonts = true; - text.gridFitType = GridFitType.PIXEL; - #else - text.sharpness = 400; - #end - //ass text - var dtf:TextFormat = new TextFormat(FlxAssets.FONT_DEFAULT, 10, 0xffffff); - dtf.align = TextFormatAlign.CENTER; - text.defaultTextFormat = dtf; - addChild(text); - text.text = "VOLUME"; - text.y = 38; - - _volumeRing = new Shape(); - addChild(_volumeRing); - - y = -height; - visible = false; - } - - /** - * Draws a ring segment - */ - function drawRing(shape:Shape, centerX:Float, centerY:Float, radius:Float, thickness:Float, - startAngle:Float, endAngle:Float, color:FlxColor, alpha:Float = 1.0):Void - { - var g = shape.graphics; - g.clear(); - - if (endAngle <= startAngle) - return; - - // convert degrees to radians - var startRad:Float = (startAngle - 90) * Math.PI / 180; - var endRad:Float = (endAngle - 90) * Math.PI / 180; - - // draw ring using curveTo for sexy smoothness - g.beginFill(color, alpha); - var innerRadius = radius - thickness/2; - var outerRadius = radius + thickness/2; - - // move to starting point on inner circle - g.moveTo( - centerX + Math.cos(startRad) * innerRadius, - centerY + Math.sin(startRad) * innerRadius - ); - - // draw arc along outer circle - for (i in 0...32) - { - var t = i / 31; - var angle = startRad + t * (endRad - startRad); - g.lineTo( - centerX + Math.cos(angle) * outerRadius, - centerY + Math.sin(angle) * outerRadius - ); - } - - // same as above but inner circle - for (i in 0...32) - { - var t = (31 - i) / 31; - var angle = startRad + t * (endRad - startRad); - g.lineTo( - centerX + Math.cos(angle) * innerRadius, - centerY + Math.sin(angle) * innerRadius - ); - } - - g.endFill(); - } - - /** - * This function updates the soundtray object. - */ - public function update(MS:Float):Void - { - if (_timer > 0) - { - _timer -= (MS / 1000); - } - else if (y > -height) - { - // increased slide up speed cuz was very slow (from 1,5 to 0.5) - y -= (MS / 1000) * height * 1.5; - - if (y <= -height) - { - visible = false; - active = false; - - #if FLX_SAVE - if (FlxG.save.isBound) - { - FlxG.save.data.mute = FlxG.sound.muted; - FlxG.save.data.volume = FlxG.sound.volume; - FlxG.save.flush(); - } - #end - } - } - } - - /** - * Makes the little volume tray slide out. - * - * @param up Whether the volume is increasing. - */ - public function show(up:Bool = false):Void - { - if (!silent) - { - var sound = FlxAssets.getSoundAddExtension(up ? volumeUpSound : volumeDownSound); - if (sound != null) - FlxG.sound.play(sound); - } - - // reduced display time (from 1.0 to 0.7) - _timer = 0.7; - y = 0; - visible = true; - active = true; - var globalVolume:Int = Math.round(FlxG.sound.volume * 10); - - if (FlxG.sound.muted) - { - globalVolume = 0; - } - - // draw volume ring (0-360 degrees) - var volumeAngle:Float = 360 * (globalVolume / 10); - drawRing(_volumeRing, RING_CENTER_X, RING_CENTER_Y, RING_RADIUS, RING_THICKNESS, - 0, volumeAngle, FlxColor.WHITE, 1.0); - } - - public function screenCenter():Void - { - scaleX = _defaultScale; - scaleY = _defaultScale; - - x = (0.5 * (Lib.current.stage.stageWidth - _width * _defaultScale) - FlxG.game.x); - } - - #if (flixel > "6.0.0") - public function showAnim(volume:Float, ?sound:FlxSoundAsset, duration:Float = 1.0, label:String = "VOLUME") {} - - function showIncrement() - { - show(true); - } - - function showDecrement() - { - show(false); - } - - function updateSize() {} - #end -} -#end \ No newline at end of file diff --git a/source/flixel/text/FlxText.hx b/source/flixel/text/FlxText.hx deleted file mode 100644 index 02df352..0000000 --- a/source/flixel/text/FlxText.hx +++ /dev/null @@ -1,1439 +0,0 @@ -package flixel.text; - -import flixel.FlxG; -import flixel.FlxSprite; -import flixel.graphics.FlxGraphic; -import flixel.graphics.atlas.FlxAtlas; -import flixel.graphics.atlas.FlxNode; -import flixel.graphics.frames.FlxFramesCollection; -import flixel.math.FlxMath; -import flixel.math.FlxPoint; -import flixel.math.FlxRect; -import flixel.system.FlxAssets; -import flixel.util.FlxColor; -import flixel.util.FlxDestroyUtil; -import flixel.util.helpers.FlxRange; -import openfl.Assets; -import openfl.display.BitmapData; -import openfl.geom.ColorTransform; -import openfl.text.TextField; -import openfl.text.TextFieldAutoSize; -import openfl.text.TextFormat; -import openfl.text.TextFormatAlign; - -using flixel.util.FlxStringUtil; -#if flash -import openfl.geom.Rectangle; -#end - -// TODO: think about filters and text - -/** - * Extends FlxSprite to support rendering text. Can tint, fade, rotate and scale just like a sprite. Doesn't really animate - * though. Also does nice pixel-perfect centering on pixel fonts as long as they are only one-liners. - * - * ## Autosizing - * - * By default `FlxText` is autosized to fit it's text. - * To set a fixed size, use the `fieldWidth`, `fieldHeight` and `autoSize` fields. - */ -class FlxText extends FlxSprite -{ - /** - * 4px gutter at the bottom when the field has automatic height - */ - static inline var VERTICAL_GUTTER:Int = 4; - - /** - * The text being displayed. - */ - public var text(default, set):String = ""; - - /** - * The size of the text being displayed in pixels. - */ - public var size(get, set):Int; - - /** - * A number representing the amount of space that is uniformly distributed - * between all characters. The value specifies the number of pixels that are - * added to the advance after each character. - */ - public var letterSpacing(get, set):Float; - - /** - * The font used for this text (assuming that it's using embedded font). - */ - public var font(get, set):String; - - /** - * Whether this text field uses an embedded font (by default) or not. - * Read-only - use `systemFont` to specify a system font to use, which then automatically sets this to `false`. - */ - public var embedded(get, never):Bool; - - /** - * The system font for this text (not embedded). Setting this sets `embedded` to `false`. - * Passing an invalid font name (like `""` or `null`) causes a default font to be used. - */ - public var systemFont(get, set):String; - - /** - * Whether to use bold text or not (`false` by default). - */ - public var bold(get, set):Bool; - - /** - * Whether to use italic text or not (`false` by default). Only works on Flash. - */ - public var italic(get, set):Bool; - - /** - * Whether to use underlined text or not (`false` by default). - */ - public var underline(get, set):Bool; - - /** - * Whether to use word wrapping and multiline or not (`true` by default). - */ - public var wordWrap(get, set):Bool; - - /** - * The alignment of the font. Note: `autoSize` must be set to - * `false` or `alignment` won't show any visual differences. - */ - public var alignment(get, set):FlxTextAlign; - - /** - * The border style to use - */ - public var borderStyle(default, set):FlxTextBorderStyle = NONE; - - /** - * The color of the border in `0xAARRGGBB` format - */ - public var borderColor(default, set):FlxColor = FlxColor.TRANSPARENT; - - /** - * The size of the border, in pixels. - */ - public var borderSize(default, set):Float = 1; - - /** - * How many iterations do use when drawing the border. `0`: only 1 iteration, `1`: one iteration for every pixel in `borderSize` - * A value of `1` will have the best quality for large border sizes, but might reduce performance when changing text. - * NOTE: If the `borderSize` is `1`, `borderQuality` of `0` or `1` will have the exact same effect (and performance). - */ - public var borderQuality(default, set):Float = 1; - - /** - * Reference to a `TextField` object used internally for rendering - - * be sure to know what you're doing if messing with its properties! - */ - public var textField(default, null):TextField; - - /** - * The width of the `TextField` object used for bitmap generation for this `FlxText` object. - * Use it when you want to change the visible width of text. Enables `autoSize` if `<= 0`. - * - * **NOTE:** auto width always implies auto height - */ - public var fieldWidth(get, set):Float; - - /** - * The height of `TextField` object used for bitmap generation for this `FlxText` object. - * Use it when you want to change the visible height of the text. Enables "auto height" if `<= 0`. - * - * **NOTE:** Fixed height has no effect if `autoSize = true`. - * @since 5.4.0 - */ - public var fieldHeight(get, set):Float; - - /** - * Whether the `fieldWidth` and `fieldHeight` should be determined automatically. - * Requires `wordWrap` to be `false`. - */ - public var autoSize(get, set):Bool; - - var _autoHeight:Bool = true; - - /** - * Internal handler for deprecated `shadowOffset` field - */ - var _shadowOffset:FlxPoint = FlxPoint.get(1, 1); - - /** - * Offset that is applied to the shadow border style, if active. - * `x` and `y` are multiplied by `borderSize`. Default is `(1, 1)`, or lower-right corner. - */ - @:deprecated("shadowOffset is deprecated, use setBorderStyle(SHADOW_XY(offsetX, offsetY)), instead") // 5.9.0 - public var shadowOffset(get, never):FlxPoint; - - /** - * Used to offset the graphic to account for the border - */ - var _graphicOffset:FlxPoint = FlxPoint.get(0, 0); - - var _defaultFormat:TextFormat; - var _formatAdjusted:TextFormat; - var _formatRanges:Array = []; - var _font:String; - - /** - * Helper boolean which tells whether to update graphic of this text object or not. - */ - var _regen:Bool = true; - - /** - * Helper vars to draw border styles with transparency. - */ - var _borderPixels:BitmapData; - - var _borderColorTransform:ColorTransform; - - var _hasBorderAlpha = false; - - #if flash - /** - * Helper to draw line by line used at `drawTextFieldTo()`. - */ - var _textFieldRect:Rectangle = new Rectangle(); - #end - - /** - * Creates a new `FlxText` object at the specified position. - * - * @param X The x position of the text. - * @param Y The y position of the text. - * @param FieldWidth The `width` of the text object. Enables `autoSize` if `<= 0`. - * (`height` is determined automatically). - * @param Text The actual text you would like to display initially. - * @param Size The font size for this text object. - * @param EmbeddedFont Whether this text field uses embedded fonts or not. - */ - public function new(X:Float = 0, Y:Float = 0, FieldWidth:Float = 0, ?Text:String, Size:Int = 8, EmbeddedFont:Bool = true) - { - super(X, Y); - - if (Text == null || Text == "") - { - // empty texts have a textHeight of 0, need to - // prevent initializing with "" before the first calcFrame() call - text = ""; - Text = " "; - } - else - { - text = Text; - } - - textField = new TextField(); - textField.selectable = false; - textField.multiline = true; - textField.wordWrap = true; - _defaultFormat = new TextFormat(null, Size, 0xffffff); - letterSpacing = 0; - font = FlxAssets.FONT_DEFAULT; - _formatAdjusted = new TextFormat(); - textField.defaultTextFormat = _defaultFormat; - textField.text = Text; - fieldWidth = FieldWidth; - textField.embedFonts = EmbeddedFont; - textField.sharpness = 100; - textField.height = (Text.length <= 0) ? 1 : 10; - - allowCollisions = NONE; - moves = false; - - set_antialiasing(antialiasing); - - drawFrame(); - } - - /** - * Clean up memory. - */ - override public function destroy():Void - { - textField = null; - _font = null; - _defaultFormat = null; - _formatAdjusted = null; - _shadowOffset = FlxDestroyUtil.put(_shadowOffset); - _graphicOffset = FlxDestroyUtil.put(_graphicOffset); - super.destroy(); - } - - override public function drawFrame(Force:Bool = false):Void - { - _regen = _regen || Force; - super.drawFrame(_regen); - } - - /** - * Stamps text onto specified atlas object and loads graphic from this atlas. - * WARNING: Changing text after stamping it on the atlas will break the atlas, so do it only for - * static texts and only after making all the text customizing (like `size`, `alignment`, `color`, etc.) - * - * @param atlas atlas to stamp graphic to. - * @return whether the graphic was stamped on the atlas successfully - */ - public function stampOnAtlas(atlas:FlxAtlas):Bool - { - regenGraphic(); - - var node:FlxNode = atlas.addNode(graphic.bitmap, graphic.key); - var result:Bool = (node != null); - - if (node != null) - { - frames = node.getImageFrame(); - } - - return result; - } - - /** - * Applies formats to text between marker characters, then removes those markers. - * NOTE: this will clear all `FlxTextFormat`s and return to the default format. - * - * Usage: - * - * ```haxe - * text.applyMarkup( - * "show $green text$ between dollar-signs", - * [new FlxTextFormatMarkerPair(greenFormat, "$")] - * ); - * ``` - * - * Even works for complex nested formats like this: - * - * ```haxe - * var yellow = new FlxTextFormatMarkerPair(yellowFormat, "@"); - * var green = new FlxTextFormatMarkerPair(greenFormat, ""); - * text.applyMarkup("Hey @Buddy@, what is going @on?@", [yellow, green]); - * ``` - * - * @param input The text you want to format - * @param rules `FlxTextFormat`s to selectively apply, paired with marker strings - */ - public function applyMarkup(input:UnicodeString, rules:Array):FlxText - { - if (rules == null || rules.length == 0) - return this; // there's no point in running the big loop - - clearFormats(); // start with default formatting - - var rangeStarts:Array = []; - var rangeEnds:Array = []; - var rulesToApply:Array = []; - - var i:Int = 0; - for (rule in rules) - { - if (rule.marker == null || rule.format == null) - continue; - - var start:Bool = false; - var markerLength:Int = rule.marker.length; - - if (!input.contains(rule.marker)) - continue; // marker not present - - // inspect each character - for (charIndex in 0...input.length) - { - if ((input.substr(charIndex, markerLength):UnicodeString) != rule.marker) - continue; // it's not one of the markers - - if (start) - { - start = false; - rangeEnds.push(charIndex); // end a format block - } - else // we're outside of a format block - { - start = true; // start a format block - rangeStarts.push(charIndex); - rulesToApply.push(rule); - } - } - - if (start) - { - // we ended with an unclosed block, mark it as infinite - rangeEnds.push(-1); - } - - i++; - } - - // Remove all of the markers in the string - for (rule in rules) - input = input.remove(rule.marker); - - // Adjust all the ranges to reflect the removed markers - for (i in 0...rangeStarts.length) - { - // Consider each range start - var delIndex:Int = rangeStarts[i]; - var markerLength:Int = rulesToApply[i].marker.length; - - // Any start or end index that is HIGHER than this must be subtracted by one markerLength - for (j in 0...rangeStarts.length) - { - if (rangeStarts[j] > delIndex) - { - rangeStarts[j] -= markerLength; - } - if (rangeEnds[j] > delIndex) - { - rangeEnds[j] -= markerLength; - } - } - - // Consider each range end - delIndex = rangeEnds[i]; - - // Any start or end index that is HIGHER than this must be subtracted by one markerLength - for (j in 0...rangeStarts.length) - { - if (rangeStarts[j] > delIndex) - { - rangeStarts[j] -= markerLength; - } - if (rangeEnds[j] > delIndex) - { - rangeEnds[j] -= markerLength; - } - } - } - - // Apply the new text - text = input; - - // Apply each format selectively to the given range - for (i in 0...rangeStarts.length) - addFormat(rulesToApply[i].format, rangeStarts[i], rangeEnds[i]); - - return this; - } - - /** - * Adds another format to this `FlxText` - * - * @param Format The format to be added. - * @param Start The start index of the string where the format will be applied. - * @param End The end index of the string where the format will be applied. - */ - public function addFormat(Format:FlxTextFormat, Start:Int = -1, End:Int = -1):FlxText - { - _formatRanges.push(new FlxTextFormatRange(Format, Start, End)); - // sort the array using the start value of the format so we can skip formats that can't be applied to the textField - _formatRanges.sort(function(left, right) - { - return left.range.start < right.range.start ? -1 : 1; - }); - _regen = true; - - return this; - } - - /** - * Removes a specific `FlxTextFormat` from this text. - * If a range is specified, this only removes the format when it touches that range. - */ - public function removeFormat(Format:FlxTextFormat, ?Start:Int, ?End:Int):FlxText - { - var i = _formatRanges.length; - while (i-- > 0) - { - var formatRange = _formatRanges[i]; - if (formatRange.format != Format) - continue; - - if (Start != null && End != null) - { - var range = formatRange.range; - if (Start >= range.end || End <= range.start) - continue; - - if (Start > range.start && End < range.end) - { - addFormat(formatRange.format, End + 1, range.end); - range.end = Start; - continue; - } - - if (Start <= range.start && End < range.end) - { - range.start = End; - continue; - } - - if (Start > range.start && End >= range.end) - { - range.end = Start; - continue; - } - } - - _formatRanges.remove(formatRange); - } - - _regen = true; - - return this; - } - - /** - * Clears all the formats applied. - */ - public function clearFormats():FlxText - { - _formatRanges = []; - updateDefaultFormat(); - - return this; - } - - /** - * You can use this if you have a lot of text parameters to set instead of the individual properties. - * - * @param Font The name of the font face for the text display. - * @param Size The size of the font (in pixels essentially). - * @param Color The color of the text in `0xRRGGBB` format. - * @param Alignment The desired alignment - * @param BorderStyle Which border style to use - * @param BorderColor Color for the border, `0xAARRGGBB` format - * @param EmbeddedFont Whether this text field uses embedded fonts or not - * @return This `FlxText` instance (nice for chaining stuff together, if you're into that). - */ - public function setFormat(?Font:String, Size:Int = 8, Color:FlxColor = FlxColor.WHITE, ?Alignment:FlxTextAlign, ?BorderStyle:FlxTextBorderStyle, - BorderColor:FlxColor = FlxColor.TRANSPARENT, EmbeddedFont:Bool = true):FlxText - { - BorderStyle = (BorderStyle == null) ? NONE : BorderStyle; - - if (EmbeddedFont) - { - font = Font; - } - else if (Font != null) - { - systemFont = Font; - } - - size = Size; - color = Color; - if (Alignment != null) - alignment = Alignment; - setBorderStyle(BorderStyle, BorderColor); - - updateDefaultFormat(); - - return this; - } - - /** - * Set border's style (shadow, outline, etc), color, and size all in one go! - * - * @param Style outline style - * @param Color outline color in `0xAARRGGBB` format - * @param Size outline size in pixels - * @param Quality outline quality - # of iterations to use when drawing. `0`: just 1, `1`: equal number to `Size` - */ - public inline function setBorderStyle(Style:FlxTextBorderStyle, Color:FlxColor = 0, Size:Float = 1, Quality:Float = 1):FlxText - { - borderStyle = Style; - borderColor = Color; - borderSize = Size; - borderQuality = Quality; - - return this; - } - - override function updateHitbox() - { - regenGraphic(); - super.updateHitbox(); - } - - override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect - { - regenGraphic(); - return super.getScreenBounds(newRect, camera); - } - - function set_fieldWidth(value:Float):Float - { - if (textField == null) - return value; - - if (value <= 0) - { - wordWrap = false; - autoSize = true; - // auto width always implies auto height - _autoHeight = true; - } - else - { - autoSize = false; - wordWrap = true; - textField.width = value; - } - - _regen = true; - return value; - } - - function get_fieldWidth():Float - { - return (textField != null) ? textField.width : 0; - } - - function get_fieldHeight():Float - { - return (textField != null) ? textField.height : 0; - } - - function set_fieldHeight(value:Float):Float - { - if (textField == null) - return value; - - if (value <= 0) - { - _autoHeight = true; - } - else - { - _autoHeight = false; - textField.height = value; - } - _regen = true; - return value; - } - - function set_autoSize(value:Bool):Bool - { - if (textField != null) - { - textField.autoSize = value ? TextFieldAutoSize.LEFT : TextFieldAutoSize.NONE; - _regen = true; - } - - return value; - } - - function get_autoSize():Bool - { - return (textField != null) ? (textField.autoSize != TextFieldAutoSize.NONE) : false; - } - - function set_text(Text:String):String - { - text = Text; - if (textField != null) - { - var ot:String = textField.text; - textField.text = Text; - _regen = (textField.text != ot) || _regen; - } - return Text; - } - - inline function get_size():Int - { - return Std.int(_defaultFormat.size); - } - - function set_size(Size:Int):Int - { - _defaultFormat.size = Size; - updateDefaultFormat(); - return Size; - } - - inline function get_letterSpacing():Float - { - return _defaultFormat.letterSpacing; - } - - function set_letterSpacing(LetterSpacing:Float):Float - { - _defaultFormat.letterSpacing = LetterSpacing; - updateDefaultFormat(); - return LetterSpacing; - } - - override function setColorTransform(redMultiplier = 1.0, greenMultiplier = 1.0, blueMultiplier = 1.0, alphaMultiplier = 1.0, redOffset = 0.0, greenOffset = 0.0, blueOffset = 0.0, alphaOffset = 0.0) - { - super.setColorTransform(1, 1, 1, 1, redOffset, greenOffset, blueOffset, alphaOffset); - _defaultFormat.color = FlxColor.fromRGBFloat(redMultiplier, greenMultiplier, blueMultiplier, 0); - updateDefaultFormat(); - } - - override function set_color(value:FlxColor):Int - { - if (_defaultFormat.color == value.rgb) - { - return value; - } - _defaultFormat.color = value.rgb; - color = value; - updateDefaultFormat(); - return value; - } - - inline function get_font():String - { - return _font; - } - - function set_font(Font:String):String - { - textField.embedFonts = true; - - if (Font != null) - { - var newFontName:String = Font; - if (FlxG.assets.exists(Font, FONT)) - { - newFontName = FlxG.assets.getFontUnsafe(Font).fontName; - } - - _defaultFormat.font = newFontName; - } - else - { - _defaultFormat.font = FlxAssets.FONT_DEFAULT; - } - - updateDefaultFormat(); - return _font = _defaultFormat.font; - } - - inline function get_embedded():Bool - { - return textField.embedFonts; - } - - inline function get_systemFont():String - { - return _defaultFormat.font; - } - - function set_systemFont(Font:String):String - { - textField.embedFonts = false; - _defaultFormat.font = Font; - updateDefaultFormat(); - return Font; - } - - inline function get_bold():Bool - { - return _defaultFormat.bold; - } - - function set_bold(value:Bool):Bool - { - if (_defaultFormat.bold != value) - { - _defaultFormat.bold = value; - updateDefaultFormat(); - } - return value; - } - - inline function get_italic():Bool - { - return _defaultFormat.italic; - } - - function set_italic(value:Bool):Bool - { - if (_defaultFormat.italic != value) - { - _defaultFormat.italic = value; - updateDefaultFormat(); - } - return value; - } - - inline function get_underline():Bool - { - return _defaultFormat.underline; - } - - function set_underline(value:Bool):Bool - { - if (_defaultFormat.underline != value) - { - _defaultFormat.underline = value; - updateDefaultFormat(); - } - return value; - } - - inline function get_wordWrap():Bool - { - return textField.wordWrap; - } - - function set_wordWrap(value:Bool):Bool - { - if (textField.wordWrap != value) - { - textField.wordWrap = value; - _regen = true; - } - return value; - } - - inline function get_alignment():FlxTextAlign - { - return FlxTextAlign.fromOpenFL(_defaultFormat.align); - } - - function set_alignment(Alignment:FlxTextAlign):FlxTextAlign - { - _defaultFormat.align = FlxTextAlign.toOpenFL(Alignment); - updateDefaultFormat(); - return Alignment; - } - - function set_borderStyle(style:FlxTextBorderStyle):FlxTextBorderStyle - { - if (style != borderStyle) - _regen = true; - - return borderStyle = style; - } - - function set_borderColor(Color:FlxColor):FlxColor - { - if (borderColor != Color && borderStyle != NONE) - _regen = true; - _hasBorderAlpha = Color.alphaFloat < 1; - return borderColor = Color; - } - - function set_borderSize(Value:Float):Float - { - if (Value != borderSize && borderStyle != NONE) - _regen = true; - - return borderSize = Value; - } - - function set_borderQuality(Value:Float):Float - { - Value = FlxMath.bound(Value, 0, 1); - if (Value != borderQuality && borderStyle != NONE) - _regen = true; - - return borderQuality = Value; - } - - override function set_graphic(Value:FlxGraphic):FlxGraphic - { - var oldGraphic:FlxGraphic = graphic; - var graph:FlxGraphic = super.set_graphic(Value); - FlxG.bitmap.removeIfNoUse(oldGraphic); - return graph; - } - - override function set_antialiasing(value:Bool):Bool - { - if (value) - { - textField.antiAliasType = NORMAL; - textField.sharpness = 100; - } - else - { - textField.antiAliasType = ADVANCED; - textField.sharpness = 400; - } - - _regen = true; - - return antialiasing = value; - } - - override function get_width():Float - { - regenGraphic(); - return super.get_width(); - } - - override function get_height():Float - { - regenGraphic(); - return super.get_height(); - } - - inline function get_shadowOffset() - { - return _shadowOffset; - } - - override function updateColorTransform():Void - { - colorTransform.alphaMultiplier = alpha; - - dirty = true; - } - - function regenGraphic():Void - { - if (textField == null || !_regen) - return; - - final oldWidth:Int = graphic != null ? graphic.width : 0; - final oldHeight:Int = graphic != null ? graphic.height : VERTICAL_GUTTER; - - final newWidthFloat:Float = textField.width; - final newHeightFloat:Float = _autoHeight ? textField.textHeight + VERTICAL_GUTTER : textField.height; - - var borderWidth:Float = 0; - var borderHeight:Float = 0; - switch(borderStyle) - { - case SHADOW if (_shadowOffset.x != 1 || _shadowOffset.y != 1): - borderWidth += Math.abs(_shadowOffset.x); - borderHeight += Math.abs(_shadowOffset.y); - - case SHADOW: // With the default shadowOffset value - borderWidth += Math.abs(borderSize); - borderHeight += Math.abs(borderSize); - - case SHADOW_XY(offsetX, offsetY): - borderWidth += Math.abs(offsetX); - borderHeight += Math.abs(offsetY); - - case OUTLINE_FAST | OUTLINE: - borderWidth += Math.abs(borderSize) * 2; - borderHeight += Math.abs(borderSize) * 2; - - case NONE: - } - - final newWidth:Int = Math.ceil(newWidthFloat + borderWidth); - final newHeight:Int = Math.ceil(newHeightFloat + borderHeight); - - // prevent text height from shrinking on flash if text == "" - if (textField.textHeight != 0 && (oldWidth != newWidth || oldHeight != newHeight)) - { - // Need to generate a new buffer to store the text graphic - final key:String = FlxG.bitmap.getUniqueKey("text"); - makeGraphic(newWidth, newHeight, FlxColor.TRANSPARENT, false, key); - width = Math.ceil(newWidthFloat); - height = Math.ceil(newHeightFloat); - - #if FLX_TRACK_GRAPHICS - graphic.trackingInfo = 'text($ID, $text)'; - #end - - if (_hasBorderAlpha) - _borderPixels = graphic.bitmap.clone(); - - if (_autoHeight) - textField.height = newHeight; - - _flashRect.x = 0; - _flashRect.y = 0; - _flashRect.width = newWidth; - _flashRect.height = newHeight; - } - else // Else just clear the old buffer before redrawing the text - { - graphic.bitmap.fillRect(_flashRect, FlxColor.TRANSPARENT); - if (_hasBorderAlpha) - { - if (_borderPixels == null) - _borderPixels = new BitmapData(frameWidth, frameHeight, true); - else - _borderPixels.fillRect(_flashRect, FlxColor.TRANSPARENT); - } - } - - if (textField != null && textField.text != null) - { - // Now that we've cleared a buffer, we need to actually render the text to it - copyTextFormat(_defaultFormat, _formatAdjusted); - - _matrix.identity(); - - applyBorderStyle(); - applyBorderTransparency(); - applyFormats(_formatAdjusted, false); - - drawTextFieldTo(graphic.bitmap); - } - - _regen = false; - resetFrame(); - } - - /** - * Internal function to draw textField to a BitmapData, if flash it calculates every line x to avoid blurry lines. - */ - function drawTextFieldTo(graphic:BitmapData):Void - { - #if flash - if (alignment == FlxTextAlign.CENTER && isTextBlurry()) - { - var h:Int = 0; - var tx:Float = _matrix.tx; - for (i in 0...textField.numLines) - { - var lineMetrics = textField.getLineMetrics(i); - - // Workaround for blurry lines caused by non-integer x positions on flash - var diff:Float = lineMetrics.x - Std.int(lineMetrics.x); - if (diff != 0) - { - _matrix.tx = tx + diff; - } - _textFieldRect.setTo(0, h, textField.width, lineMetrics.height + lineMetrics.descent); - - graphic.draw(textField, _matrix, null, null, _textFieldRect, false); - - _matrix.tx = tx; - h += Std.int(lineMetrics.height); - } - - return; - } - #elseif !web - // Fix to render desktop and mobile text in the same visual location as web - _matrix.translate(-1, -1); // left and up - graphic.draw(textField, _matrix); - _matrix.translate(1, 1); // return to center - return; - #end - - graphic.draw(textField, _matrix); - } - - #if flash - /** - * Helper function for `drawTextFieldTo()`, this checks if thw workaround is needed to prevent blurry lines. - */ - function isTextBlurry():Bool - { - for (i in 0...textField.numLines) - { - var lineMetricsX = textField.getLineMetrics(i).x; - if (lineMetricsX - Std.int(lineMetricsX) != 0) - { - return true; - } - } - return false; - } - #end - - override public function draw():Void - { - regenGraphic(); - super.draw(); - } - - override function drawSimple(camera:FlxCamera):Void - { - // same as super but checks _graphicOffset - getScreenPosition(_point, camera).subtract(offset).subtract(_graphicOffset); - if (isPixelPerfectRender(camera)) - _point.floor(); - - _point.copyTo(_flashPoint); - camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing); - } - - override function drawComplex(camera:FlxCamera):Void - { - _frame.prepareMatrix(_matrix, ANGLE_0, checkFlipX(), checkFlipY()); - _matrix.translate(-origin.x, -origin.y); - _matrix.scale(scale.x, scale.y); - - if (bakedRotationAngle <= 0) - { - updateTrig(); - - if (angle != 0) - _matrix.rotateWithTrig(_cosAngle, _sinAngle); - } - - // same as super but checks _graphicOffset - getScreenPosition(_point, camera).subtract(offset).subtract(_graphicOffset); - _point.add(origin.x, origin.y); - _matrix.translate(_point.x, _point.y); - - if (isPixelPerfectRender(camera)) - { - _matrix.tx = Math.floor(_matrix.tx); - _matrix.ty = Math.floor(_matrix.ty); - } - - camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader); - } - - /** - * Internal function to update the current animation frame. - * - * @param RunOnCpp Whether the frame should also be recalculated if we're on a non-flash target - */ - override function calcFrame(RunOnCpp:Bool = false):Void - { - if (textField == null) - return; - - if (FlxG.renderTile && !RunOnCpp) - return; - - regenGraphic(); - super.calcFrame(RunOnCpp); - } - - function applyBorderStyle():Void - { - // offset entire image to fit the border - switch(borderStyle) - { - case SHADOW if (_shadowOffset.x != 1 || _shadowOffset.y != 1): - _graphicOffset.x = _shadowOffset.x > 0 ? _shadowOffset.x : 0; - _graphicOffset.y = _shadowOffset.y > 0 ? _shadowOffset.y : 0; - - case SHADOW: // With the default shadowOffset value - if (borderSize < 0) - _graphicOffset.set(-borderSize, -borderSize); - - case SHADOW_XY(offsetX, offsetY): - _graphicOffset.x = offsetX < 0 ? -offsetX : 0; - _graphicOffset.y = offsetY < 0 ? -offsetY : 0; - - case OUTLINE_FAST | OUTLINE if (borderSize < 0): - _graphicOffset.set(-borderSize, -borderSize); - - case NONE | OUTLINE_FAST | OUTLINE: - _graphicOffset.set(0, 0); - } - _matrix.translate(_graphicOffset.x, _graphicOffset.y); - - switch (borderStyle) - { - case SHADOW if (_shadowOffset.x != 1 || _shadowOffset.y != 1): - // Render a shadow beneath the text using the shadowOffset property - applyFormats(_formatAdjusted, true); - - var iterations = borderQuality < 1 ? 1 : Std.int(Math.abs(borderSize) * borderQuality); - final delta = borderSize / iterations; - for (i in 0...iterations) - { - copyTextWithOffset(delta, delta); - } - - _matrix.translate(-_shadowOffset.x * borderSize, -_shadowOffset.y * borderSize); - - case SHADOW: // With the default shadowOffset value - // Render a shadow beneath the text - applyFormats(_formatAdjusted, true); - - final originX = _matrix.tx; - final originY = _matrix.ty; - - final iterations = borderQuality < 1 ? 1 : Std.int(Math.abs(borderSize) * borderQuality); - var i = iterations + 1; - while (i-- > 1) - { - copyTextWithOffset(borderSize / iterations * i, borderSize / iterations * i); - // reset to origin - _matrix.tx = originX; - _matrix.ty = originY; - } - - case SHADOW_XY(shadowX, shadowY): - // Render a shadow beneath the text with the specified offset - applyFormats(_formatAdjusted, true); - - final originX = _matrix.tx; - final originY = _matrix.ty; - - // Size is max of both, so (4, 4) has 4 iterations, just like SHADOW - final size = Math.max(shadowX, shadowY); - final iterations = borderQuality < 1 ? 1 : Std.int(size * borderQuality); - var i = iterations + 1; - while (i-- > 1) - { - copyTextWithOffset(shadowX / iterations * i, shadowY / iterations * i); - // reset to origin - _matrix.tx = originX; - _matrix.ty = originY; - } - - case OUTLINE: - // Render an outline around the text - // (do 8 offset draw calls) - applyFormats(_formatAdjusted, true); - - final iterations = FlxMath.maxInt(1, Std.int(borderSize * borderQuality)); - var i = iterations + 1; - while (i-- > 1) - { - final curDelta = borderSize / iterations * i; - copyTextWithOffset(-curDelta, -curDelta); // upper-left - copyTextWithOffset(curDelta, 0); // upper-middle - copyTextWithOffset(curDelta, 0); // upper-right - copyTextWithOffset(0, curDelta); // middle-right - copyTextWithOffset(0, curDelta); // lower-right - copyTextWithOffset(-curDelta, 0); // lower-middle - copyTextWithOffset(-curDelta, 0); // lower-left - copyTextWithOffset(0, -curDelta); // lower-left - - _matrix.translate(curDelta, 0); // return to center - } - - case OUTLINE_FAST: - // Render an outline around the text - // (do 4 diagonal offset draw calls) - // (this method might not work with certain narrow fonts) - applyFormats(_formatAdjusted, true); - - final iterations = FlxMath.maxInt(1, Std.int(borderSize * borderQuality)); - var i = iterations + 1; - while (i-- > 1) - { - final curDelta = borderSize / iterations * i; - copyTextWithOffset(-curDelta, -curDelta); // upper-left - copyTextWithOffset(curDelta * 2, 0); // upper-right - copyTextWithOffset(0, curDelta * 2); // lower-right - copyTextWithOffset(-curDelta * 2, 0); // lower-left - - _matrix.translate(curDelta, -curDelta); // return to center - } - - case NONE: - } - } - - inline function applyBorderTransparency() - { - if (!_hasBorderAlpha) - return; - - if (_borderColorTransform == null) - _borderColorTransform = new ColorTransform(); - - _borderColorTransform.alphaMultiplier = borderColor.alphaFloat; - _borderPixels.colorTransform(_borderPixels.rect, _borderColorTransform); - graphic.bitmap.draw(_borderPixels); - } - - /** - * Helper function for `applyBorderStyle()` - */ - inline function copyTextWithOffset(x:Float, y:Float) - { - var graphic:BitmapData = _hasBorderAlpha ? _borderPixels : graphic.bitmap; - _matrix.translate(x, y); - drawTextFieldTo(graphic); - } - - function applyFormats(FormatAdjusted:TextFormat, UseBorderColor:Bool = false):Void - { - // Apply the default format - copyTextFormat(_defaultFormat, FormatAdjusted, false); - FormatAdjusted.color = UseBorderColor ? borderColor.rgb : _defaultFormat.color; - textField.setTextFormat(FormatAdjusted); - - // Apply other formats - for (formatRange in _formatRanges) - { - if (textField.text.length - 1 < formatRange.range.start) - { - // we can break safely because the array is ordered by the format start value - break; - } - else - { - var textFormat:TextFormat = formatRange.format.format; - copyTextFormat(textFormat, FormatAdjusted, false); - FormatAdjusted.color = UseBorderColor ? formatRange.format.borderColor.rgb : textFormat.color; - } - - textField.setTextFormat(FormatAdjusted, formatRange.range.start, Std.int(Math.min(formatRange.range.end, textField.text.length))); - } - } - - function copyTextFormat(from:TextFormat, to:TextFormat, withAlign:Bool = true):Void - { - to.font = from.font; - to.bold = from.bold; - to.italic = from.italic; - to.underline = from.underline; - to.size = from.size; - to.color = from.color; - to.leading = from.leading; - if (withAlign) - to.align = from.align; - } - - /** - * A helper function for updating the TextField that we use for rendering. - * - * @return A writable copy of `TextField.defaultTextFormat`. - */ - function dtfCopy():TextFormat - { - var dtf:TextFormat = textField.defaultTextFormat; - return new TextFormat(dtf.font, dtf.size, dtf.color, dtf.bold, dtf.italic, dtf.underline, dtf.url, dtf.target, dtf.align); - } - - inline function updateDefaultFormat():Void - { - textField.defaultTextFormat = _defaultFormat; - textField.setTextFormat(_defaultFormat); - _regen = true; - } - - override function set_frames(Frames:FlxFramesCollection):FlxFramesCollection - { - super.set_frames(Frames); - _regen = false; - return Frames; - } -} - -@:allow(flixel.text.FlxText.applyFormats) -class FlxTextFormat -{ - /** - * The leading (vertical space between lines) of the text. - * @since 4.10.0 - */ - public var leading(default, set):Int; - - /** - * The border color if the text has a shadow or a border - */ - var borderColor:FlxColor; - - var format(default, null):TextFormat; - - /** - * @param fontColor Font color, in `0xRRGGBB` format. Inherits from the default format by default. - * @param bold Whether the text should be bold (must be supported by the font). `false` by default. - * @param italic Whether the text should be in italics (must be supported by the font). Only works on Flash. `false` by default. - * @param borderColor Border color, in `0xAARRGGBB` format. By default, no border (`null` / transparent). - * @param underline Whether the text should be underlined. `false` by default. - */ - public function new(?fontColor:FlxColor, ?bold:Bool, ?italic:Bool, ?borderColor:FlxColor, ?underline:Bool) - { - format = new TextFormat(null, null, fontColor, bold, italic, underline); - this.borderColor = borderColor == null ? FlxColor.TRANSPARENT : borderColor; - } - - function set_leading(value:Int):Int - { - format.leading = value; - return value; - } -} - -private class FlxTextFormatRange -{ - public var range(default, null):FlxRange; - public var format(default, null):FlxTextFormat; - - public function new(format:FlxTextFormat, start:Int, end:Int) - { - range = new FlxRange(start, end); - this.format = format; - } -} - -class FlxTextFormatMarkerPair -{ - public var format:FlxTextFormat; - public var marker:UnicodeString; - - public function new(format:FlxTextFormat, marker:UnicodeString) - { - this.format = format; - this.marker = marker; - } -} - -enum FlxTextBorderStyle -{ - NONE; - - /** - * A simple shadow to the lower-right - */ - SHADOW; - - /** - * A shadow that allows custom placement - * **Note:** Ignores borderSize - */ - SHADOW_XY(offsetX:Float, offsetY:Float); - - /** - * Outline on all 8 sides - */ - OUTLINE; - - /** - * Outline, optimized using only 4 draw calls - * **Note:** Might not work for narrow and/or 1-pixel fonts - */ - OUTLINE_FAST; -} - -enum abstract FlxTextAlign(String) from String -{ - var LEFT = "left"; - - /** - * Warning: on Flash, this can have a negative impact on performance - * of multiline texts that are frequently regenerated (especially with - * `borderStyle == OUTLINE`) due to a workaround for blurry rendering. - */ - var CENTER = "center"; - - var RIGHT = "right"; - var JUSTIFY = "justify"; - - public static function fromOpenFL(align:TextFormatAlign):FlxTextAlign - { - return switch (align) - { - // This `null` check is needed for HashLink, otherwise it will cast - // a `null` alignment to 0 which results in returning `CENTER` - // instead of the default `LEFT`. - case null: LEFT; - case TextFormatAlign.LEFT: LEFT; - case TextFormatAlign.CENTER: CENTER; - case TextFormatAlign.RIGHT: RIGHT; - case TextFormatAlign.JUSTIFY: JUSTIFY; - default: LEFT; - } - } - - public static function toOpenFL(align:FlxTextAlign):TextFormatAlign - { - return switch (align) - { - case FlxTextAlign.LEFT: TextFormatAlign.LEFT; - case FlxTextAlign.CENTER: TextFormatAlign.CENTER; - case FlxTextAlign.RIGHT: TextFormatAlign.RIGHT; - case FlxTextAlign.JUSTIFY: TextFormatAlign.JUSTIFY; - default: TextFormatAlign.LEFT; - } - } -} diff --git a/source/game/Paths.hx b/source/game/Paths.hx index 2fa5c6f..21b75c0 100644 --- a/source/game/Paths.hx +++ b/source/game/Paths.hx @@ -1,848 +1,813 @@ package game; -import flixel.system.FlxAssets; -import flixel.math.FlxPoint; +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxFrame.FlxFrameAngle; -import openfl.geom.Rectangle; +import flixel.math.FlxPoint; import flixel.math.FlxRect; -import haxe.xml.Access; +import flixel.system.FlxAssets; + +import openfl.display.BitmapData; +import openfl.geom.Rectangle; +import openfl.media.Sound; import openfl.system.System; -import flixel.FlxG; -import flixel.graphics.frames.FlxAtlasFrames; import openfl.utils.AssetType; import openfl.utils.Assets as OpenFlAssets; -import lime.utils.Assets; -import flixel.FlxSprite; -#if sys -import sys.io.File; -import sys.FileSystem; -#end -import flixel.graphics.FlxGraphic; -import openfl.display.BitmapData; + import haxe.Json; +import haxe.xml.Access; + +import lime.utils.Assets; #if flixel_animate import animate.FlxAnimateFrames.SpritemapInput; import animate.FlxAnimateFrames.FilterQuality; #end -import openfl.media.Sound; +#if sys +import sys.io.File; +import sys.FileSystem; +#end using StringTools; @:access(openfl.display.BitmapData) class Paths { - inline public static var SOUND_EXT = #if web "mp3" #else "ogg" #end; - inline public static var VIDEO_EXT = "mp4"; - inline public static var WAV_EXT = "wav"; - - - #if MODS_ALLOWED - public static var ignoreModFolders:Array = [ - 'characters', - 'custom_events', - 'custom_notetypes', - 'data', - 'songs', - 'music', - #if NDLL_ALLOWED 'ndlls', #end - 'sounds', - 'shaders', - 'videos', - 'images', - 'stages', - 'weeks', - 'fonts', - 'scripts', - ]; - #end - - public static function excludeAsset(key:String) { - if (!dumpExclusions.contains(key)) - dumpExclusions.push(key); - } - - public static var dumpExclusions:Array = - [ - 'assets/music/freakyMenu.$SOUND_EXT', - 'assets/shared/music/breakfast.$SOUND_EXT', - 'assets/shared/music/tea-time.$SOUND_EXT', - ]; - public static var localTrackedAssets:Array = []; - /// haya I love you for the base cache dump I took to the max - public static function clearUnusedMemory() { - // clear non local assets in the tracked assets list - - for (key in currentTrackedAssets.keys()) - { - // if it is not currently contained within the used local assets - if (!localTrackedAssets.contains(key) && !dumpExclusions.contains(key)) - { - destroyGraphic(currentTrackedAssets.get(key)); // get rid of the graphic - currentTrackedAssets.remove(key); // and remove the key from local cache map - } - } - // run the garbage collector for good measure lmfao - MemoryUtil.forceGC((FlxG.state is PlayState) ? false : true); - } - - // define the locally tracked assets - - @:access(flixel.system.frontEnds.BitmapFrontEnd._cache) - public static function clearStoredMemory(?cleanUnused:Bool = false) { - // clear anything not in the tracked assets list - for (key in FlxG.bitmap._cache.keys()) - { - if (!currentTrackedAssets.exists(key)) - destroyGraphic(FlxG.bitmap.get(key)); - } - - // clear all sounds that are cached - for (key => asset in currentTrackedSounds) - { - if (!localTrackedAssets.contains(key) && !dumpExclusions.contains(key) && asset != null) - { - Assets.cache.clear(key); - currentTrackedSounds.remove(key); - } - } - - FlxG.bitmap.clearUnused(); - MemoryUtil.compact(); - - // flags everything to be cleared out next unused memory clear - localTrackedAssets.resize(0); - openfl.Assets.cache.clear("songs"); - } - - public static function freeGraphicsFromMemory() - { - var protectedGfx:Array = []; - function checkForGraphics(spr:Dynamic) - { - try - { - var grp:Array = Reflect.getProperty(spr, 'members'); - if(grp != null) - { - //trace('is actually a group'); - for (member in grp) - { - checkForGraphics(member); - } - return; - } - } - - try - { - var gfx:FlxGraphic = Reflect.getProperty(spr, 'graphic'); - if(gfx != null) protectedGfx.push(gfx); - } - //catch(haxe.Exception) {} - } - - for (member in FlxG.state.members) checkForGraphics(member); - - if(FlxG.state.subState != null) - for (member in FlxG.state.subState.members) - checkForGraphics(member); - - for (key in currentTrackedAssets.keys()) - { - // if it is not currently contained within the used local assets - if (!dumpExclusions.contains(key)) - { - var graphic:FlxGraphic = currentTrackedAssets.get(key); - if(!protectedGfx.contains(graphic)) - { - destroyGraphic(graphic); // get rid of the graphic - currentTrackedAssets.remove(key); // and remove the key from local cache map - //trace('deleted $key'); - } - } - } - } - - inline static function destroyGraphic(graphic:FlxGraphic) - { - // free some gpu memory - graphic?.bitmap?.__texture?.dispose(); - FlxG.bitmap.remove(graphic); - } - - static public var currentModDirectory:String = ''; - static public var currentLevel:String; - static public function setCurrentLevel(name:String) - { - currentLevel = name.toLowerCase(); - } - - public static function getPath(file:String, ?type:AssetType = TEXT, ?library:Null = null, ?modsAllowed:Bool = false):String - { - #if MODS_ALLOWED - if(modsAllowed) - { - var modded:String = modFolders(file); - if(FileSystem.exists(modded)) return modded; - } - #end - - if (library != null) - return getLibraryPath(file, library); - - if (currentLevel != null) - { - var levelPath:String = ''; - if(currentLevel != 'shared') { - levelPath = getLibraryPathForce(file, 'week_assets', currentLevel); - if (OpenFlAssets.exists(levelPath, type)) - return levelPath; - } - - levelPath = getLibraryPathForce(file, "shared"); - if (OpenFlAssets.exists(levelPath, type)) - return levelPath; - } - - return getPreloadPath(file); - } - - static public function getLibraryPath(file:String, library = "preload") - { - return if (library == "preload" || library == "default") getPreloadPath(file); else getLibraryPathForce(file, library); - } - - inline static function getLibraryPathForce(file:String, library:String, ?level:String) - { - level ??= library; - var returnPath = '$library:assets/$level/$file'; - return returnPath; - } - - inline public static function getPreloadPath(file:String = '') - { - return 'assets/$file'; - } - - #if SCRIPTABLE_STATES - static public function getStateScripts(statePath:String):Array { - var foldersToCheck:Array = [ - Paths.getPreloadPath('scripts/states/$statePath/'), - #if MODS_ALLOWED - Paths.mods('scripts/states/$statePath/'), - #end - ]; - - #if MODS_ALLOWED - if (Paths.currentModDirectory != null && Paths.currentModDirectory.length > 0) { - foldersToCheck.unshift(Paths.mods('${Paths.currentModDirectory}/scripts/states/$statePath/')); - } - - for (mod in Paths.getGlobalMods()) { - foldersToCheck.unshift(Paths.mods('$mod/scripts/states/$statePath/')); - } - #end - - foldersToCheck.push(Paths.getPreloadPath('scripts/states/$statePath.hx')); - #if MODS_ALLOWED - foldersToCheck.push(Paths.mods('scripts/states/$statePath.hx')); - foldersToCheck.push(Paths.mods('${Paths.currentModDirectory}/scripts/states/$statePath.hx')); - for (mod in Paths.getGlobalMods()) { - foldersToCheck.push(Paths.mods('$mod/scripts/states/$statePath.hx')); - } - #end - - return foldersToCheck; - } - - - //same as above but for substates - static public function getSubstateScripts(statePath:String):Array { - var foldersToCheck:Array = [ - Paths.getPreloadPath('scripts/substates/$statePath/'), - #if MODS_ALLOWED - Paths.mods('scripts/substates/$statePath/'), - #end - ]; - - #if MODS_ALLOWED - if (currentModDirectory != null && currentModDirectory.length > 0) { - foldersToCheck.unshift(Paths.mods('${currentModDirectory}/scripts/substates/$statePath/')); - } - - for (mod in getGlobalMods()) { - foldersToCheck.unshift(Paths.mods('$mod/scripts/substates/$statePath/')); - } - #end - - foldersToCheck.push(Paths.getPreloadPath('scripts/substates/$statePath.hx')); - #if MODS_ALLOWED - foldersToCheck.push(Paths.mods('scripts/substates/$statePath.hx')); - foldersToCheck.push(Paths.mods('${currentModDirectory}/scripts/substates/$statePath.hx')); - for (mod in getGlobalMods()) { - foldersToCheck.push(Paths.mods('$mod/scripts/substates/$statePath.hx')); - } - #end - - return foldersToCheck; - } - #end - - inline static public function file(file:String, type:AssetType = TEXT, ?library:String) - { - return getPath(file, type, library); - } - - inline static public function txt(key:String, ?library:String) - { - return getPath('data/$key.txt', TEXT, library); - } - - inline static public function xml(key:String, ?library:String) - { - return getPath('data/$key.xml', TEXT, library); - } - - inline static public function json(key:String, ?library:String) - { - return getPath('data/$key.json', TEXT, library); - } - - inline static public function shaderFragment(key:String, ?library:String) - { - return getPath('shaders/$key.frag', TEXT, library); - } - inline static public function shaderVertex(key:String, ?library:String) - { - return getPath('shaders/$key.vert', TEXT, library); - } - inline static public function lua(key:String, ?library:String) - { - return getPath('$key.lua', TEXT, library); - } - - static public function video(key:String) - { - #if MODS_ALLOWED - var file:String = modsVideo(key); - if(FileSystem.exists(file)) { - return file; - } - #end - return 'assets/videos/$key.$VIDEO_EXT'; - } - - inline static public function sound(key:String, ?library:String):Sound - { - var sound:Sound = returnSound('sounds', key, library); - return sound; - } - - inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String) - return sound(key + FlxG.random.int(min, max), library); - - inline static public function music(key:String, ?library:String):Sound - { - var file:Sound = returnSound('music', key, library); - return file; - } - - inline static public function voices(song:String, postfix:String = null):Sound - { - var songKey:String = '${formatToSongPath(song)}/voices'; - if (postfix != null) songKey += '-' + postfix; - - var snd = returnSound(null, songKey, 'songs', false); - if (snd == null) { - songKey = '${formatToSongPath(song)}/Voices'; - if (postfix != null) songKey += '-' + postfix; - snd = returnSound(null, songKey, 'songs', false); - } - - return snd; - } - - inline static public function inst(song:String):Any { - var songKey:String = '${formatToSongPath(song)}/inst'; - - var snd = returnSound(null, songKey, 'songs', false); - if (snd == null) { - songKey = '${formatToSongPath(song)}/Inst'; - snd = returnSound(null, songKey, 'songs', false); - } - - return snd; - } - - #if NDLL_ALLOWED - inline static public function ndll(key:String) { - #if MODS_ALLOWED - var file:String = modsNdll(key + "-" + game.backend.utils.NdllUtil.os + ".ndll"); - if(FileSystem.exists(file)) { - return file; - } - #end - return 'assets/$key'; - } - #end - - public static function listDirectory(path:String):Array - { - var result:Array = []; - - #if MODS_ALLOWED - var modsList:Array = [currentModDirectory]; - modsList = modsList.concat(getGlobalMods()); - - for (mod in modsList) - { - if (mod == null || mod.length == 0) continue; - - var modPath = mods('$mod/$path'); - if (FileSystem.exists(modPath) && FileSystem.isDirectory(modPath)) - { - for (file in FileSystem.readDirectory(modPath)) - { - var fullPath = haxe.io.Path.join([modPath, file]); - if (!FileSystem.isDirectory(fullPath)) - result.push(fullPath); - } - } - } - #end - - var assetsPath = getPreloadPath(path); - if (OpenFlAssets.exists(assetsPath)) - { - // some shits for web targets(for the future mb) - #if web - var prefix = assetsPath + "/"; - for (asset in OpenFlAssets.list(ALL)) - { - if (asset.startsWith(prefix) && asset != prefix) - result.push(asset); - } - #else - if (FileSystem.exists(assetsPath) && FileSystem.isDirectory(assetsPath)) - { - for (file in FileSystem.readDirectory(assetsPath)) - { - var fullPath = haxe.io.Path.join([assetsPath, file]); - if (!FileSystem.isDirectory(fullPath)) - result.push(fullPath); - } - } - #end - } - - return result; - } - - // completely rewritten asset loading? fuck! - public static var currentTrackedAssets:Map = []; - static public function image(key:String, ?library:String = null, ?allowGPU:Bool = true):FlxGraphic - { - var bitmap:BitmapData = null; - if (currentTrackedAssets.exists(key)) - { - localTrackedAssets.push(key); - return currentTrackedAssets.get(key); - } - return cacheBitmap(key, library, bitmap, allowGPU); - - trace('oh no its returning null NOOOO ($file)'); - return null; - } - - static public function cacheBitmap(key:String, ?library:String = null, ?bitmap:BitmapData = null, ?allowGPU:Bool = true) - { - if (bitmap == null) - { - var file:String = getPath('images/$key.png', IMAGE, library, true); - #if MODS_ALLOWED - if (FileSystem.exists(file)) - bitmap = BitmapData.fromFile(file); - else #end if (OpenFlAssets.exists(file, IMAGE)) - bitmap = OpenFlAssets.getBitmapData(file); - - if (bitmap == null) - { - trace('Bitmap not found: $file | key: $key'); - return null; - } - } - - if (allowGPU && (ClientPrefs.cacheOnGPU || ClientPrefs.adaptiveCache) && bitmap.image != null) - { - bitmap.lock(); - if (bitmap.__texture == null) - { - bitmap.image.premultiplied = true; - bitmap.getTexture(FlxG.stage.context3D); - } - bitmap.getSurface(); - bitmap.disposeImage(); - bitmap.image.data = null; - bitmap.image = null; - bitmap.readable = true; - } - - var graph:FlxGraphic = FlxGraphic.fromBitmapData(bitmap, false, key); - graph.persist = true; - graph.destroyOnNoUse = false; - - currentTrackedAssets.set(key, graph); - localTrackedAssets.push(key); - return graph; - } - - inline static public function getTextFromFile(key:String, ?ignoreMods:Bool = false):String - { - var path:String = getPath(key, TEXT, !ignoreMods); - #if MODS_ALLOWED - return (FileSystem.exists(path)) ? File.getContent(path) : null; - #else - return (OpenFlAssets.exists(path, TEXT)) ? Assets.getText(path) : null; - #end - } - - inline static public function font(key:String) - { - #if MODS_ALLOWED - var file:String = modsFont(key); - if(FileSystem.exists(file)) { - return file; - } - #end - return 'assets/fonts/$key'; - } - - public static function fileExists(key:String, type:AssetType, ?ignoreMods:Bool = false, ?library:String = null) - { - #if MODS_ALLOWED - if(FileSystem.exists(getPath(key, type, library, !ignoreMods))) { - #else - if(OpenFlAssets.exists(getPath(key, type, library, !ignoreMods))) { - #end - return true; - } - return false; - } - - static public function getAtlas(key:String, ?library:String = null, ?allowGPU:Bool = true):FlxAtlasFrames - { - var useMod = false; - var imageLoaded:FlxGraphic = image(key, library, allowGPU); - - var myXml:Dynamic = getPath('images/$key.xml', TEXT, library, true); - if(OpenFlAssets.exists(myXml) #if MODS_ALLOWED || (FileSystem.exists(myXml) && (useMod = true)) #end ) - { - #if MODS_ALLOWED - return FlxAtlasFrames.fromSparrow(imageLoaded, (useMod ? File.getContent(myXml) : myXml)); - #else - return FlxAtlasFrames.fromSparrow(imageLoaded, myXml); - #end - } - else - { - var myJson:Dynamic = getPath('images/$key.json', TEXT, library, true); - if(OpenFlAssets.exists(myJson) #if MODS_ALLOWED || (FileSystem.exists(myJson) && (useMod = true)) #end ) - { - #if MODS_ALLOWED - return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, (useMod ? File.getContent(myJson) : myJson)); - #else - return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, myJson); - #end - } - } - return getPackerAtlas(key, library); - } - - inline static public function getSparrowAtlas(key:String, ?library:String = null, ?allowGPU:Bool = false):FlxAtlasFrames - { - if (ClientPrefs.cacheOnGPU) { - allowGPU = true; - } - var imageLoaded:FlxGraphic = image(key, library, allowGPU); - #if MODS_ALLOWED - var xmlExists:Bool = false; - - var xml:String = modsXml(key); - if(FileSystem.exists(xml)) xmlExists = true; - - return FlxAtlasFrames.fromSparrow(imageLoaded, (xmlExists ? File.getContent(xml) : getPath('images/$key.xml', library))); - #else - return FlxAtlasFrames.fromSparrow(imageLoaded, getPath('images/$key.xml', library)); - #end - } - - inline static public function getPackerAtlas(key:String, ?library:String = null, ?allowGPU:Bool = false):FlxAtlasFrames - { - if (ClientPrefs.cacheOnGPU) { - allowGPU = true; - } else { - allowGPU = false; - } - var imageLoaded:FlxGraphic = image(key, library, allowGPU); - #if MODS_ALLOWED - var txtExists:Bool = false; - - var txt:String = modsTxt(key); - if(FileSystem.exists(txt)) txtExists = true; - - return FlxAtlasFrames.fromSpriteSheetPacker(imageLoaded, (txtExists ? File.getContent(txt) : getPath('images/$key.txt', library))); - #else - return FlxAtlasFrames.fromSpriteSheetPacker(imageLoaded, getPath('images/$key.txt', library)); - #end - } - - inline static public function getAsepriteAtlas(key:String, ?library:String = null, ?allowGPU:Bool = true):FlxAtlasFrames - { - var imageLoaded:FlxGraphic = image(key, library, allowGPU); - #if MODS_ALLOWED - var jsonExists:Bool = false; - - var json:String = modsImagesJson(key); - if(FileSystem.exists(json)) jsonExists = true; - - return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, (jsonExists ? File.getContent(json) : getPath('images/$key.json', library))); - #else - return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, getPath('images/$key.json', library)); - #end - } - - #if flixel_animate - inline static public function getAnimateAtlas(key:String, ?library:String = null):FlxAnimateFrames - { - return FlxAnimateFrames.fromAnimate(getPath('images/$key', TEXT, library, true)); - } - #end - - inline static public function formatToSongPath(path:String) { - var invalidChars = ~/[~&\\;:<>#]/; - var hideChars = ~/[.,'"%?!]/; - - var path = invalidChars.split(path.replace(' ', '-')).join("-"); - return hideChars.split(path).join("").toLowerCase(); - } - - public static function getAbsolutePath(assetPath:String):String { - #if sys - var cleanPath = assetPath; - if (cleanPath.indexOf("assets/") == 0) { - cleanPath = cleanPath.substring(7); - } - return Sys.getCwd() + "assets/" + cleanPath; - #else - return assetPath; - #end - } - - public static var currentTrackedSounds:Map = []; - public static function returnSound(path:Null, key:String, ?library:String, ?beepOnNull:Bool = true) { - #if MODS_ALLOWED - var modLibPath:String = ''; - if (library != null) modLibPath = '$library/'; - if (path != null) modLibPath += '$path'; - - var file:String = modsSounds(modLibPath, key, WAV_EXT); - if(FileSystem.exists(file)) { - if(!currentTrackedSounds.exists(file)) { - currentTrackedSounds.set(file, CoolUtil.loadHighBitrateWav(key, file)); - } - localTrackedAssets.push(file); - return currentTrackedSounds.get(file); - } - - file = modsSounds(modLibPath, key); - if(FileSystem.exists(file)) { - if(!currentTrackedSounds.exists(file)) { - currentTrackedSounds.set(file, Sound.fromFile(file)); - } - localTrackedAssets.push(file); - return currentTrackedSounds.get(file); - } - #end - - // checking wav files for existing - var wavPath:String = getPath((path != null ? '$path/' : '') + '$key.$WAV_EXT', SOUND, library); - if(OpenFlAssets.exists(wavPath, SOUND)) { - if(!currentTrackedSounds.exists(wavPath)) { - //for web only mp3 lel - #if (sys && !web) - var absolutePath = getAbsolutePath(wavPath); - if (FileSystem.exists(absolutePath)) - currentTrackedSounds.set(wavPath, CoolUtil.loadHighBitrateWav(key, absolutePath)); - else - currentTrackedSounds.set(wavPath, OpenFlAssets.getSound(wavPath)); - #else - currentTrackedSounds.set(wavPath, OpenFlAssets.getSound(wavPath)); - #end - } - localTrackedAssets.push(wavPath); - return currentTrackedSounds.get(wavPath); - } - - var standardPath:String = getPath((path != null ? '$path/' : '') + '$key.$SOUND_EXT', SOUND, library); - if(OpenFlAssets.exists(standardPath, SOUND)) { - if(!currentTrackedSounds.exists(standardPath)) { - currentTrackedSounds.set(standardPath, OpenFlAssets.getSound(standardPath)); - } - localTrackedAssets.push(standardPath); - return currentTrackedSounds.get(standardPath); - } - - if(beepOnNull) { - trace('SOUND NOT FOUND: $key, PATH: $path'); - return FlxAssets.getSoundAddExtension('flixel/sounds/beep'); - } - return null; - } - - #if MODS_ALLOWED - inline static public function mods(key:String = '') { - return 'contents/$key'; - } - - #if SCRIPTABLE_STATES - inline static public function modsStates(key:String, state:String) - return modFolders('scripts/states/$state/$key.hx'); - #end - - inline static public function modsFont(key:String) { - return modFolders('fonts/$key'); - } - - inline static public function modsJson(key:String) { - return modFolders('data/$key.json'); - } - - inline static public function modsVideo(key:String) { - return modFolders('videos/$key.$VIDEO_EXT'); - } - - #if NDLL_ALLOWED - public static function modsNdll(key:String) { - if(currentModDirectory != null && currentModDirectory.length > 0) { - var fileToCheck:String = "contents/" + currentModDirectory + '/' + key; - if(FileSystem.exists(fileToCheck)) { - return fileToCheck; - } - } - - for(mod in getGlobalMods()) { - var fileToCheck:String = "contents/" + mod + '/' + key; - if(FileSystem.exists(fileToCheck)) - return fileToCheck; - - } - return 'contents/' + key; - } - #end - - inline static public function modsSounds(path:String, key:String, ?ext:String = null) { - ext ??= SOUND_EXT; - return modFolders('$path/$key.$ext'); - } - - inline static public function modsImages(key:String) { - return modFolders('images/$key.png'); - } - - inline static public function modsImagesJson(key:String) { - return modFolders('images/$key.json'); - } - - inline static public function modsXml(key:String) { - return modFolders('images/$key.xml'); - } - - inline static public function modsTxt(key:String) { - return modFolders('images/$key.txt'); - } - - inline static public function modsShaderFragment(key:String, ?library:String) - { - return modFolders('shaders/$key.frag'); - } - inline static public function modsShaderVertex(key:String, ?library:String) - { - return modFolders('shaders/$key.vert'); - } - - /* Goes unused for now - inline static public function modsAchievements(key:String) { - return modFolders('achievements/' + key + '.json'); - }*/ - - static public function modFolders(key:String) { - if(currentModDirectory != null && currentModDirectory.length > 0) { - var fileToCheck:String = mods(currentModDirectory + '/' + key); - if(FileSystem.exists(fileToCheck)) { - return fileToCheck; - } - } - - for(mod in getGlobalMods()){ - var fileToCheck:String = mods(mod + '/' + key); - if(FileSystem.exists(fileToCheck)) - return fileToCheck; - - } - return 'contents/' + key; - } - - public static var globalMods:Array = []; - - static public function getGlobalMods() - return globalMods; - - static public function pushGlobalMods() // prob a better way to do this but idc - { - globalMods = []; - var path:String = 'modsList.txt'; - if(FileSystem.exists(path)) - { - var list:Array = CoolUtil.coolTextFile(path); - for (i in list) - { - var dat = i.split("|"); - if (dat[1] == "1") - { - var folder = dat[0]; - var path = Paths.mods(folder + '/pack.json'); - if(FileSystem.exists(path)) { - try{ - var rawJson:String = File.getContent(path); - if(rawJson != null && rawJson.length > 0) { - var stuff:Dynamic = Json.parse(rawJson); - var global:Bool = Reflect.getProperty(stuff, "runsGlobally"); - if(global)globalMods.push(dat[0]); - } - } catch(e:Dynamic){ - trace(e); - } - } - } - } - } - return globalMods; - } - - static public function getModDirectories():Array { - var list:Array = []; - var modsFolder:String = mods(); - if(FileSystem.exists(modsFolder)) { - for (folder in FileSystem.readDirectory(modsFolder)) { - var path = haxe.io.Path.join([modsFolder, folder]); - if (sys.FileSystem.isDirectory(path) && !ignoreModFolders.contains(folder) && !list.contains(folder)) { - list.push(folder); - } - } - } - return list; - } - #end -} + inline public static final SOUND_EXT = #if web "mp3" #else "ogg" #end; + inline public static final VIDEO_EXT = "mp4"; + + @:unreflective + inline public static final OPUS_EXT = "opus"; + + @:unreflective + inline public static final WAV_EXT = "wav"; + + public static function excludeAsset(key:String) { + if (!dumpExclusions.contains(key)) + dumpExclusions.push(key); + } + + public static var dumpExclusions:Array = + [ + 'assets/music/freakyMenu.$SOUND_EXT', + ]; + + public static var localTrackedAssets:Array = []; + public static function clearUnusedMemory(cleanMajor:Bool = true) { + if (FlxG.state is PlayState) cleanMajor = false; // dont do major cleans ingame + + for (key in currentTrackedAssets.keys()) + { + if (!localTrackedAssets.contains(key) && !dumpExclusions.contains(key)) + { + destroyGraphic(currentTrackedAssets.get(key)); + currentTrackedAssets.remove(key); + } + } + MemoryUtil.forceGC(cleanMajor); + } + + @:access(flixel.system.frontEnds.BitmapFrontEnd._cache) + public static function clearStoredMemory() { + for (key in FlxG.bitmap._cache.keys()) + { + if (!currentTrackedAssets.exists(key)) + destroyGraphic(FlxG.bitmap.get(key)); + } + + for (key => asset in currentTrackedSounds) + { + if (!localTrackedAssets.contains(key) && !dumpExclusions.contains(key) && asset != null) + { + Assets.cache.clear(key); + currentTrackedSounds.remove(key); + } + } + + FlxG.bitmap.clearUnused(); + MemoryUtil.compact(); + + localTrackedAssets.resize(0); + openfl.Assets.cache.clear("songs"); + } + + public static function freeGraphicsFromMemory() + { + var protectedGfx:Array = []; + function checkForGraphics(spr:Dynamic) + { + try + { + var grp:Array = Reflect.getProperty(spr, 'members'); + if(grp != null) + { + for (member in grp) + checkForGraphics(member); + return; + } + } + + try + { + var gfx:FlxGraphic = Reflect.getProperty(spr, 'graphic'); + if(gfx != null) protectedGfx.push(gfx); + } + } + + for (member in FlxG.state.members) checkForGraphics(member); + + if(FlxG.state.subState != null) + for (member in FlxG.state.subState.members) + checkForGraphics(member); + + for (key in currentTrackedAssets.keys()) + { + if (!dumpExclusions.contains(key)) + { + var graphic:FlxGraphic = currentTrackedAssets.get(key); + if(!protectedGfx.contains(graphic)) + { + destroyGraphic(graphic); + currentTrackedAssets.remove(key); + } + } + } + } + + inline static function destroyGraphic(graphic:FlxGraphic) + { + graphic?.bitmap?.__texture?.dispose(); + FlxG.bitmap?.remove(graphic); + } + + public static var currentLevel:String; + public static function setCurrentLevel(name:String) + { + currentLevel = name.toLowerCase(); + } + + public static function getPath(file:String, ?type:AssetType = TEXT, ?library:Null = null, ?modsAllowed:Bool = false):String + { + #if MODS_ALLOWED + if(modsAllowed) + { + var modded:String = Mods.modFolders(file); + if(FileSystem.exists(modded)) return modded; + } + #end + + if (library != null) + return getLibraryPath(file, library); + + if (currentLevel != null) + { + var levelPath:String = ''; + if(currentLevel != 'shared') { + levelPath = getLibraryPathForce(file, 'week_assets', currentLevel); + if (OpenFlAssets.exists(levelPath, type)) + return levelPath; + } + + levelPath = getLibraryPathForce(file, "shared"); + if (OpenFlAssets.exists(levelPath, type)) + return levelPath; + } + + return getPreloadPath(file); + } + + static public function getLibraryPath(file:String, library = "preload") + { + return if (library == "preload" || library == "default") getPreloadPath(file); else getLibraryPathForce(file, library); + } + + inline static function getLibraryPathForce(file:String, library:String, ?level:String) + { + level ??= library; + var returnPath = '$library:assets/$level/$file'; + return returnPath; + } + + inline public static function getPreloadPath(file:String = '') + { + return 'assets/$file'; + } + + #if SCRIPTABLE_STATES + static public function getStateScripts(statePath:String):Array { + var foldersToCheck:Array = [ + Paths.getPreloadPath('scripts/states/$statePath/'), + #if MODS_ALLOWED + Mods.getModPath('scripts/states/$statePath/'), + #end + ]; + + #if MODS_ALLOWED + if (Mods.currentModDirectory != null && Mods.currentModDirectory.length > 0) { + foldersToCheck.insert(0, Mods.getModPath('${Mods.currentModDirectory}/scripts/states/$statePath/')); + } + + for (mod in Mods.getGlobalMods()) { + foldersToCheck.insert(0, Mods.getModPath('$mod/scripts/states/$statePath/')); + } + #end + + foldersToCheck.push(Paths.getPreloadPath('scripts/states/$statePath.hx')); + #if MODS_ALLOWED + foldersToCheck.push(Mods.getModPath('scripts/states/$statePath.hx')); + foldersToCheck.push(Mods.getModPath('${Mods.currentModDirectory}/scripts/states/$statePath.hx')); + for (mod in Mods.getGlobalMods()) { + foldersToCheck.push(Mods.getModPath('$mod/scripts/states/$statePath.hx')); + } + #end + + return foldersToCheck; + } + + static public function getSubstateScripts(statePath:String):Array { + var foldersToCheck:Array = [ + Paths.getPreloadPath('scripts/substates/$statePath/'), + #if MODS_ALLOWED + Mods.getModPath('scripts/substates/$statePath/'), + #end + ]; + + #if MODS_ALLOWED + if (Mods.currentModDirectory != null && Mods.currentModDirectory.length > 0) { + foldersToCheck.insert(0, Mods.getModPath('${Mods.currentModDirectory}/scripts/substates/$statePath/')); + } + + for (mod in Mods.getGlobalMods()) { + foldersToCheck.insert(0, Mods.getModPath('$mod/scripts/substates/$statePath/')); + } + #end + + foldersToCheck.push(Paths.getPreloadPath('scripts/substates/$statePath.hx')); + #if MODS_ALLOWED + foldersToCheck.push(Mods.getModPath('scripts/substates/$statePath.hx')); + foldersToCheck.push(Mods.getModPath('${Mods.currentModDirectory}/scripts/substates/$statePath.hx')); + for (mod in Mods.getGlobalMods()) { + foldersToCheck.push(Mods.getModPath('$mod/scripts/substates/$statePath.hx')); + } + #end + + return foldersToCheck; + } + #end + + inline static public function file(file:String, type:AssetType = TEXT, ?library:String) + { + return getPath(file, type, library); + } + + inline static public function txt(key:String, ?library:String) + { + return getPath('data/$key.txt', TEXT, library); + } + + inline static public function xml(key:String, ?library:String) + { + return getPath('data/$key.xml', TEXT, library); + } + + inline static public function json(key:String, ?library:String) + { + return getPath('data/$key.json', TEXT, library); + } + + inline static public function shaderFragment(key:String, ?library:String) + { + return getPath('shaders/$key.frag', TEXT, library); + } + inline static public function shaderVertex(key:String, ?library:String) + { + return getPath('shaders/$key.vert', TEXT, library); + } + inline static public function lua(key:String, ?library:String) + { + return getPath('$key.lua', TEXT, library); + } + + static public function video(key:String) + { + #if MODS_ALLOWED + var file:String = Mods.modsVideo(key); + if(file.startsWith('zip://')) { + var parts = file.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var tempPath = Mods.extractFileFromZipMod(mod, filePath, 'videos'); + if(tempPath != null) return tempPath; + } + if(FileSystem.exists(file)) { + return file; + } + #end + return 'assets/videos/$key.$VIDEO_EXT'; + } + + inline static public function sound(key:String, ?library:String):Sound + { + var sound:Sound = returnSound('sounds', key, library); + return sound; + } + + inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String) + return sound(key + FlxG.random.int(min, max), library); + + inline static public function music(key:String, ?library:String):Sound + { + var file:Sound = returnSound('music', key, library); + return file; + } + + inline static public function voices(song:String, postfix:String = null):Sound + { + var songKey:String = '${formatToSongPath(song)}/voices'; + if (postfix != null) songKey += '-' + postfix; + + var snd = returnSound(null, songKey, 'songs', false); + if (snd == null) { + songKey = '${formatToSongPath(song)}/Voices'; + if (postfix != null) songKey += '-' + postfix; + snd = returnSound(null, songKey, 'songs', false); + } + + return snd; + } + + inline static public function inst(song:String):Any { + var songKey:String = '${formatToSongPath(song)}/inst'; + + var snd = returnSound(null, songKey, 'songs', false); + if (snd == null) { + songKey = '${formatToSongPath(song)}/Inst'; + snd = returnSound(null, songKey, 'songs', false); + } + + return snd; + } + + #if NDLL_ALLOWED + inline static public function ndll(key:String) { + #if MODS_ALLOWED + var file:String = Mods.modsNdll(key + "-" + game.backend.utils.NdllUtil.os + ".ndll"); + if(FileSystem.exists(file)) { + return file; + } + #end + return 'assets/$key'; + } + #end + + public static function listDirectory(path:String):Array + { + var result:Array = []; + + #if MODS_ALLOWED + var modsList:Array = [Mods.currentModDirectory]; + modsList = modsList.concat(Mods.getGlobalMods()); + + for (mod in modsList) + { + if (mod == null || mod.length == 0) continue; + + var modPath = Mods.getModPath('$mod/$path'); + if (FileSystem.exists(modPath) && FileSystem.isDirectory(modPath)) + { + for (file in FileSystem.readDirectory(modPath)) + { + var fullPath = haxe.io.Path.join([modPath, file]); + if (!FileSystem.isDirectory(fullPath)) + result.push(fullPath); + } + } + } + #end + + var assetsPath = getPreloadPath(path); + if (OpenFlAssets.exists(assetsPath)) + { + #if web + var prefix = assetsPath + "/"; + for (asset in OpenFlAssets.list(ALL)) + { + if (asset.startsWith(prefix) && asset != prefix) + result.push(asset); + } + #else + if (FileSystem.exists(assetsPath) && FileSystem.isDirectory(assetsPath)) + { + for (file in FileSystem.readDirectory(assetsPath)) + { + var fullPath = haxe.io.Path.join([assetsPath, file]); + if (!FileSystem.isDirectory(fullPath)) + result.push(fullPath); + } + } + #end + } + + return result; + } + + public static var currentTrackedAssets:Map = []; + static public function image(key:String, ?library:String = null, ?allowGPU:Bool = true, ?imgFormat:String = "png"):FlxGraphic + { + var bitmap:BitmapData = null; + if (currentTrackedAssets.exists(key)) + { + localTrackedAssets.push(key); + return currentTrackedAssets.get(key); + } + return cacheBitmap(key, library, bitmap, allowGPU, imgFormat); + + trace('oh no its returning null NOOOO ($file)'); + return null; + } + + static public function cacheBitmap(key:String, ?library:String = null, ?bitmap:BitmapData = null, ?allowGPU:Bool = true, ?imgFormat:String) + { + if (bitmap == null) + { + var file:String = getPath('images/$key.$imgFormat', IMAGE, library, true); + + #if MODS_ALLOWED + if (file.startsWith('zip://')) { + var parts = file.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var content = Mods.getFileFromMod(mod, filePath); + if (content != null) bitmap = BitmapData.fromBytes(content); + } + else #end #if sys if (FileSystem.exists(file)) + bitmap = BitmapData.fromFile(file); + else #end if (OpenFlAssets.exists(file, IMAGE)) + bitmap = OpenFlAssets.getBitmapData(file); + + if (bitmap == null) + { + trace('Bitmap not found: $file | key: $key'); + return null; + } + } + + if (allowGPU && (ClientPrefs.cacheOnGPU || ClientPrefs.adaptiveCache) && bitmap.image != null) + { + bitmap.lock(); + if (bitmap.__texture == null) + { + bitmap.image.premultiplied = true; + bitmap.getTexture(FlxG.stage.context3D); + } + bitmap.getSurface(); + bitmap.disposeImage(); + bitmap.image.data = null; + bitmap.image = null; + bitmap.readable = true; + } + + var graph:FlxGraphic = FlxGraphic.fromBitmapData(bitmap, false, key); + graph.persist = true; + graph.destroyOnNoUse = false; + + currentTrackedAssets.set(key, graph); + localTrackedAssets.push(key); + return graph; + } + + inline static public function getTextFromFile(key:String, ?ignoreMods:Bool = false):String + { + var path:String = getPath(key, TEXT, !ignoreMods); + #if MODS_ALLOWED + if (path.startsWith('zip://')) { + var parts = path.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var content = Mods.getFileFromMod(mod, filePath); + return (content != null) ? content.toString() : null; + } + return (FileSystem.exists(path)) ? File.getContent(path) : null; + #else + return (OpenFlAssets.exists(path, TEXT)) ? Assets.getText(path) : null; + #end + } + + static public function font(key:String) + { + #if MODS_ALLOWED + var file:String = Mods.modsFont(key); + if(file.startsWith('zip://')) { + var parts = file.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var tempPath = Mods.extractFileFromZipMod(mod, filePath, 'fonts'); + if(tempPath != null) return tempPath; + } + if(FileSystem.exists(file)) { + return file; + } + #end + return 'assets/fonts/$key'; + } + + public static function fileExists(key:String, type:AssetType, ?ignoreMods:Bool = false, ?library:String = null) + { + var path:String = getPath(key, type, library, !ignoreMods); + + #if MODS_ALLOWED + if (path.startsWith('zip://')) { + return true; + } + if(FileSystem.exists(path)) { + #else + if(OpenFlAssets.exists(path, type)) { + #end + return true; + } + return false; + } + + static public function getAtlas(key:String, ?library:String = null, ?allowGPU:Bool = true):FlxAtlasFrames + { + var useMod = false; + var imageLoaded:FlxGraphic = image(key, library, allowGPU); + + var myXml:Dynamic = getPath('images/$key.xml', TEXT, library, true); + #if MODS_ALLOWED + if(myXml.startsWith('zip://')) { + var parts = myXml.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var content = Mods.getFileFromMod(mod, filePath); + if (content != null) { + return FlxAtlasFrames.fromSparrow(imageLoaded, content.toString()); + } + } + else #end if(OpenFlAssets.exists(myXml) #if MODS_ALLOWED || (FileSystem.exists(myXml) && (useMod = true)) #end ) + { + #if MODS_ALLOWED + return FlxAtlasFrames.fromSparrow(imageLoaded, (useMod ? File.getContent(myXml) : myXml)); + #else + return FlxAtlasFrames.fromSparrow(imageLoaded, myXml); + #end + } + else + { + var myJson:Dynamic = getPath('images/$key.json', TEXT, library, true); + #if MODS_ALLOWED + if(myJson.startsWith('zip://')) { + var parts = myJson.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var content = Mods.getFileFromMod(mod, filePath); + if (content != null) { + return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, content.toString()); + } + } + else #end if(OpenFlAssets.exists(myJson) #if MODS_ALLOWED || (FileSystem.exists(myJson) && (useMod = true)) #end ) + { + #if MODS_ALLOWED + return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, (useMod ? File.getContent(myJson) : myJson)); + #else + return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, myJson); + #end + } + } + return getPackerAtlas(key, library); + } + + static public function getSparrowAtlas(key:String, ?library:String = null, ?allowGPU:Bool = false):FlxAtlasFrames + { + if (ClientPrefs.cacheOnGPU) { + allowGPU = true; + } + var imageLoaded:FlxGraphic = image(key, library, allowGPU); + #if MODS_ALLOWED + var xmlExists:Bool = false; + + var xml:String = Mods.modsXml(key); + if(xml.startsWith('zip://')) { + var parts = xml.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var content = Mods.getFileFromMod(mod, filePath); + if (content != null) return FlxAtlasFrames.fromSparrow(imageLoaded, content.toString()); + } + else if(FileSystem.exists(xml)) xmlExists = true; + + return FlxAtlasFrames.fromSparrow(imageLoaded, (xmlExists ? File.getContent(xml) : getPath('images/$key.xml', library))); + #else + return FlxAtlasFrames.fromSparrow(imageLoaded, getPath('images/$key.xml', library)); + #end + } + + static public function getPackerAtlas(key:String, ?library:String = null, ?allowGPU:Bool = false):FlxAtlasFrames + { + if (ClientPrefs.cacheOnGPU) { + allowGPU = true; + } else { + allowGPU = false; + } + var imageLoaded:FlxGraphic = image(key, library, allowGPU); + #if MODS_ALLOWED + var txtExists:Bool = false; + + var txt:String = Mods.modsTxt(key); + if(txt.startsWith('zip://')) { + var parts = txt.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var content = Mods.getFileFromMod(mod, filePath); + if (content != null) return FlxAtlasFrames.fromSpriteSheetPacker(imageLoaded, content.toString()); + } + else if(FileSystem.exists(txt)) txtExists = true; + + return FlxAtlasFrames.fromSpriteSheetPacker(imageLoaded, (txtExists ? File.getContent(txt) : getPath('images/$key.txt', library))); + #else + return FlxAtlasFrames.fromSpriteSheetPacker(imageLoaded, getPath('images/$key.txt', library)); + #end + } + + static public function getAsepriteAtlas(key:String, ?library:String = null, ?allowGPU:Bool = true):FlxAtlasFrames + { + var imageLoaded:FlxGraphic = image(key, library, allowGPU); + #if MODS_ALLOWED + var jsonExists:Bool = false; + + var json:String = Mods.modsImagesJson(key); + if(json.startsWith('zip://')) { + var parts = json.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var content = Mods.getFileFromMod(mod, filePath); + if (content != null) return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, content.toString()); + } + else if(FileSystem.exists(json)) jsonExists = true; + + return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, (jsonExists ? File.getContent(json) : getPath('images/$key.json', library))); + #else + return FlxAtlasFrames.fromTexturePackerJson(imageLoaded, getPath('images/$key.json', library)); + #end + } + + #if flixel_animate + inline static public function getAnimateAtlas(key:String, ?library:String = null):FlxAnimateFrames + { + return FlxAnimateFrames.fromAnimate(getPath('images/$key', TEXT, library, true)); + } + #end + + inline static public function formatToSongPath(path:String) { + var invalidChars = ~/[~&\\;:<>#]/; + var hideChars = ~/[.,'"%?!]/; + + var path = invalidChars.split(path.replace(' ', '-')).join("-"); + return hideChars.split(path).join("").toLowerCase(); + } + + public static function getAbsolutePath(assetPath:String):String { + #if sys + var cleanPath = assetPath; + if (cleanPath.indexOf("assets/") == 0) { + cleanPath = cleanPath.substring(7); + } + return Sys.getCwd() + getPreloadPath(cleanPath); + #else + return assetPath; + #end + } + + public static var currentTrackedSounds:Map = []; + public static function returnSound(path:Null, key:String, ?library:String, ?beepOnNull:Bool = true) { + #if MODS_ALLOWED + var modLibPath:String = ''; + if (library != null) modLibPath = '$library/'; + if (path != null) modLibPath += '$path'; + + var file:String = Mods.modsSounds(modLibPath, key, WAV_EXT); + if(file.startsWith('zip://')) { + var parts = file.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var tempPath = Mods.extractFileFromZipMod(mod, filePath, 'sounds'); + if(tempPath != null) { + if(!currentTrackedSounds.exists(file)) { + #if (sys && !web) + if (FileSystem.exists(tempPath)) + currentTrackedSounds.set(file, CoolUtil.loadHighBitrateWav(key, tempPath)); + else + currentTrackedSounds.set(file, Sound.fromFile(tempPath)); + #else + currentTrackedSounds.set(file, Sound.fromFile(tempPath)); + #end + } + localTrackedAssets.push(file); + return currentTrackedSounds.get(file); + } + } + else if(FileSystem.exists(file)) { + if(!currentTrackedSounds.exists(file)) { + currentTrackedSounds.set(file, CoolUtil.loadHighBitrateWav(key, file)); + } + localTrackedAssets.push(file); + return currentTrackedSounds.get(file); + } + + file = Mods.modsSounds(modLibPath, key); + if(file.startsWith('zip://')) { + var parts = file.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var tempPath = Mods.extractFileFromZipMod(mod, filePath, 'sounds'); + if(tempPath != null) { + if(!currentTrackedSounds.exists(file)) { + currentTrackedSounds.set(file, Sound.fromFile(tempPath)); + } + localTrackedAssets.push(file); + return currentTrackedSounds.get(file); + } + } + else if(FileSystem.exists(file)) { + if(!currentTrackedSounds.exists(file)) { + currentTrackedSounds.set(file, Sound.fromFile(file)); + } + localTrackedAssets.push(file); + return currentTrackedSounds.get(file); + } + + #if (hxopus && sys) + file = Mods.modsSounds(modLibPath, key, OPUS_EXT); + if(file.startsWith('zip://')) { + var parts = file.substr(6).split('/'); + var mod = parts[0]; + var filePath = parts.slice(1).join('/'); + var tempPath = Mods.extractFileFromZipMod(mod, filePath, 'sounds'); + if(tempPath != null) { + if(!currentTrackedSounds.exists(file)) { + var bytes = File.getBytes(tempPath); + currentTrackedSounds.set(file, hxopus.Opus.toOpenFL(bytes)); + } + localTrackedAssets.push(file); + return currentTrackedSounds.get(file); + } + } + else if(FileSystem.exists(file)) { + if(!currentTrackedSounds.exists(file)) { + var bytes = File.getBytes(file); + currentTrackedSounds.set(file, hxopus.Opus.toOpenFL(bytes)); + } + localTrackedAssets.push(file); + return currentTrackedSounds.get(file); + } + #end + #end + + var wavPath:String = getPath((path != null ? '$path/' : '') + '$key.$WAV_EXT', SOUND, library); + if(OpenFlAssets.exists(wavPath, SOUND)) { + if(!currentTrackedSounds.exists(wavPath)) { + #if (sys && !web) + var absolutePath = getAbsolutePath(wavPath); + if (FileSystem.exists(absolutePath)) + currentTrackedSounds.set(wavPath, CoolUtil.loadHighBitrateWav(key, absolutePath)); + else + currentTrackedSounds.set(wavPath, OpenFlAssets.getSound(wavPath)); + #else + currentTrackedSounds.set(wavPath, OpenFlAssets.getSound(wavPath)); + #end + } + localTrackedAssets.push(wavPath); + return currentTrackedSounds.get(wavPath); + } + + var standardPath:String = getPath((path != null ? '$path/' : '') + '$key.$SOUND_EXT', SOUND, library); + if(OpenFlAssets.exists(standardPath, SOUND)) { + if(!currentTrackedSounds.exists(standardPath)) { + currentTrackedSounds.set(standardPath, OpenFlAssets.getSound(standardPath)); + } + localTrackedAssets.push(standardPath); + return currentTrackedSounds.get(standardPath); + } + + #if hxopus + var opusPath:String = getPath((path != null ? '$path/' : '') + '$key.$OPUS_EXT', SOUND, library); + if(OpenFlAssets.exists(opusPath, SOUND)) { + if(!currentTrackedSounds.exists(opusPath)) { + var bytes = OpenFlAssets.getBytes(opusPath); + currentTrackedSounds.set(opusPath, hxopus.Opus.toOpenFL(bytes)); + } + localTrackedAssets.push(opusPath); + return currentTrackedSounds.get(opusPath); + } + #end + + if(beepOnNull) { + trace('SOUND NOT FOUND: $key, PATH: $path'); + return FlxAssets.getSoundAddExtension('flixel/sounds/beep'); + } + return null; + } +} \ No newline at end of file diff --git a/source/game/PlayState.hx b/source/game/PlayState.hx index d885ebc..d3dea1e 100644 --- a/source/game/PlayState.hx +++ b/source/game/PlayState.hx @@ -97,18 +97,28 @@ import hxvlc.flixel.FlxVideo as MP4Handler; #end #if MODCHART_ALLOWED -import modchart.Manager as ModManager; +import game.modchart.*; +import game.modchart.ModManager; +#end + +#if target.threaded +import sys.thread.Thread; #end using StringTools; class PlayState extends MusicBeatState { - public static var STRUM_X = 42; - public static var STRUM_X_MIDDLESCROLL = -278; + public static final STRUM_X = 42; + public static final STRUM_X_MIDDLESCROLL = -278; var noteRows:Array>> = [[],[],[]]; + private var shutdownThread:Bool = false; + private var gameFroze:Bool = false; + private var requiresSyncing:Bool = false; + private var lastCorrectSongPos:Float = -1.0; + public static var ratingStuff:Array = [ ['You Suck!', 0.2], //From 0% to 19% ['Shit', 0.4], //From 20% to 39% @@ -130,21 +140,7 @@ class PlayState extends MusicBeatState public var dadMap:Map = new Map(); public var gfMap:Map = new Map(); public var variables:Map = new Map(); - public var modchartTweens:Map = new Map(); - public var modchartSprites:Map = new Map(); - #if flixel_animate - public var modchartAnimateSprites:Map = new Map(); - #end - public var modchartBackdrops:Map = new Map(); - public var modchartTimers:Map = new Map(); - public var modchartSounds:Map = new Map(); - public var modchartTexts:Map = new Map(); - public var modchartSaves:Map = new Map(); - #else - public var boyfriendMap:Map = new Map(); - public var dadMap:Map = new Map(); - public var gfMap:Map = new Map(); - public var variables:Map = new Map(); + public var cameraShaders:Map = new Map(); public var modchartTweens:Map = new Map(); public var modchartSprites:Map = new Map(); #if flixel_animate @@ -155,6 +151,23 @@ class PlayState extends MusicBeatState public var modchartSounds:Map = new Map(); public var modchartTexts:Map = new Map(); public var modchartSaves:Map = new Map(); + #else + public var boyfriendMap:Map = new Map(); + public var dadMap:Map = new Map(); + public var gfMap:Map = new Map(); + public var variables:Map = new Map(); + public var cameraShaders:Map = new Map(); + public var modchartTweens:Map = new Map(); + public var modchartSprites:Map = new Map(); + #if flixel_animate + public var modchartAnimateSprites:Map = new Map(); + public var modchartBackdrops:Map = new Map(); + public var modchartTimers:Map = new Map(); + public var modchartSounds:Map = new Map(); + public var modchartTexts:Map = new Map(); + public var modchartSaves:Map = new Map(); + #end + #end #if MODCHART_ALLOWED @@ -261,6 +274,8 @@ class PlayState extends MusicBeatState public var camHUD:FlxCamera; public var camGame:FlxCamera; public var camOther:FlxCamera; + public var camPause:FlxCamera; + public var camSubState:FlxCamera; public var cameraSpeed:Float = 1; var dialogue:Array = ['blah blah blah', 'coolswag']; @@ -350,7 +365,6 @@ class PlayState extends MusicBeatState override public function create() { //trace('Playback Rate: ' + playbackRate); - Paths.clearStoredMemory(); // for lua instance = this; @@ -419,11 +433,18 @@ class PlayState extends MusicBeatState camGame = initFNFCamera(); camHUD = new FlxCamera(); camOther = new FlxCamera(); + camPause = new FlxCamera(); + camSubState = new FlxCamera(); + camHUD.bgColor.alpha = 0; camOther.bgColor.alpha = 0; + camPause.bgColor.alpha = 0; + camSubState.bgColor.alpha = 0; FlxG.cameras.add(camHUD, false); FlxG.cameras.add(camOther, false); + FlxG.cameras.add(camPause, false); + FlxG.cameras.add(camSubState, false); grpNoteSplashes = new FlxTypedGroup(); grpHoldCovers = new FlxTypedGroup(); @@ -545,12 +566,12 @@ class PlayState extends MusicBeatState var foldersToCheck:Array = [Paths.getPreloadPath('scripts/')]; #if MODS_ALLOWED - foldersToCheck.insert(0, Paths.mods('scripts/')); - if(Paths.currentModDirectory != null && Paths.currentModDirectory.length > 0) - foldersToCheck.insert(0, Paths.mods(Paths.currentModDirectory + '/scripts/')); + foldersToCheck.insert(0, Mods.getModPath('scripts/')); + if(Mods.currentModDirectory != null && Mods.currentModDirectory.length > 0) + foldersToCheck.insert(0, Mods.getModPath(Mods.currentModDirectory + '/scripts/')); - for(mod in Paths.getGlobalMods()) - foldersToCheck.insert(0, Paths.mods(mod + '/scripts/')); + for(mod in Mods.getGlobalMods()) + foldersToCheck.insert(0, Mods.getModPath(mod + '/scripts/')); #end for (folder in foldersToCheck) @@ -724,7 +745,7 @@ class PlayState extends MusicBeatState generateSong(SONG.song); #if MODCHART_ALLOWED - modManager = new ModManager(); + modManager = new ModManager(this); #end // After all characters being loaded, it makes then invisible 0.01s later so that the player won't freeze when you change characters @@ -852,7 +873,7 @@ class PlayState extends MusicBeatState for (event in eventPushedMap.keys()) { #if (LUA_ALLOWED && MODS_ALLOWED) - var luaToLoad:String = Paths.modFolders('custom_events/' + event + '.lua'); + var luaToLoad:String = Mods.modFolders('custom_events/' + event + '.lua'); if(FileSystem.exists(luaToLoad)) { luaArray.push(new FunkinLua(luaToLoad)); @@ -874,7 +895,7 @@ class PlayState extends MusicBeatState #end #if (HSCRIPT_ALLOWED && MODS_ALLOWED) - var hxToLoad:String = Paths.modFolders('custom_events/' + event + '.hx'); + var hxToLoad:String = Mods.modFolders('custom_events/' + event + '.hx'); if(FileSystem.exists(hxToLoad)) { hscriptArray.push(new FunkinHScript(hxToLoad)); @@ -905,12 +926,12 @@ class PlayState extends MusicBeatState var foldersToCheck:Array = [Paths.getPreloadPath('data/' + Paths.formatToSongPath(SONG.song) + '/')]; #if MODS_ALLOWED - foldersToCheck.insert(0, Paths.mods('data/' + Paths.formatToSongPath(SONG.song) + '/')); - if(Paths.currentModDirectory != null && Paths.currentModDirectory.length > 0) - foldersToCheck.insert(0, Paths.mods(Paths.currentModDirectory + '/data/' + Paths.formatToSongPath(SONG.song) + '/')); + foldersToCheck.insert(0, Mods.getModPath('data/' + Paths.formatToSongPath(SONG.song) + '/')); + if(Mods.currentModDirectory != null && Mods.currentModDirectory.length > 0) + foldersToCheck.insert(0, Mods.getModPath(Mods.currentModDirectory + '/data/' + Paths.formatToSongPath(SONG.song) + '/')); - for(mod in Paths.getGlobalMods()) - foldersToCheck.insert(0, Paths.mods(mod + '/data/' + Paths.formatToSongPath(SONG.song) + '/' ));// using push instead of insert because these should run after everything else + for(mod in Mods.getGlobalMods()) + foldersToCheck.insert(0, Mods.getModPath(mod + '/data/' + Paths.formatToSongPath(SONG.song) + '/' ));// using push instead of insert because these should run after everything else #end for (folder in foldersToCheck) @@ -1033,9 +1054,8 @@ class PlayState extends MusicBeatState Paths.music(key); } } + super.create(); - - Paths.clearUnusedMemory(); } #if (!flash && sys) @@ -1044,7 +1064,7 @@ class PlayState extends MusicBeatState { if(!ClientPrefs.shaders) return new FlxRuntimeShader(); - #if (!flash && MODS_ALLOWED && sys) + #if (!flash && sys) if(!runtimeShaders.exists(name) && !initLuaShader(name)) { FlxG.log.warn('Shader $name is missing!'); @@ -1069,14 +1089,14 @@ class PlayState extends MusicBeatState return true; } - var foldersToCheck:Array = [Paths.getPreloadPath('shaders/') #if MODS_ALLOWED , Paths.mods('shaders/')#end]; + var foldersToCheck:Array = [Paths.getPreloadPath('shaders/') #if MODS_ALLOWED , Mods.getModPath('shaders/')#end]; #if MODS_ALLOWED - if(Paths.currentModDirectory != null && Paths.currentModDirectory.length > 0) - foldersToCheck.insert(0, Paths.mods(Paths.currentModDirectory + '/shaders/')); + if(Mods.currentModDirectory != null && Mods.currentModDirectory.length > 0) + foldersToCheck.insert(0, Mods.getModPath(Mods.currentModDirectory + '/shaders/')); - for(mod in Paths.getGlobalMods()) - foldersToCheck.insert(0, Paths.mods(mod + '/shaders/')); + for(mod in Mods.getGlobalMods()) + foldersToCheck.insert(0, Mods.getModPath(mod + '/shaders/')); #end for (folder in foldersToCheck) @@ -1219,8 +1239,8 @@ class PlayState extends MusicBeatState var doPush:Bool = false; var luaFile:String = 'characters/' + name + '.lua'; #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(luaFile))) { - luaFile = Paths.modFolders(luaFile); + if(FileSystem.exists(Mods.modFolders(luaFile))) { + luaFile = Mods.modFolders(luaFile); doPush = true; } else { luaFile = Paths.getPreloadPath(luaFile); @@ -1249,7 +1269,7 @@ class PlayState extends MusicBeatState var doPush:Bool = false; var hxFile:String = 'characters/' + name + '.hx'; #if MODS_ALLOWED - var replacePath:String = Paths.modFolders(hxFile); + var replacePath:String = Mods.modFolders(hxFile); if(FileSystem.exists(replacePath)) { hxFile = replacePath; @@ -1442,6 +1462,17 @@ class PlayState extends MusicBeatState //if(ClientPrefs.middleScroll) opponentStrums.members[i].visible = false; } + //modchart calling func + #if MODCHART_ALLOWED + modManager.receptors = [playerStrums.members, opponentStrums.members]; + + callOnScripts('onModchartCall', []); + modManager.registerDefaultModifiers(); + + callOnScripts('onModchartCallPost', []); + Modcharts.loadModchart(modManager, SONG.song); + #end + startedCountdown = true; Conductor.songPosition = -Conductor.crochet * 5; setOnScripts('startedCountdown', true); @@ -1460,12 +1491,6 @@ class PlayState extends MusicBeatState } moveCameraSection(); - //modchart calling func - #if MODCHART_ALLOWED - callOnScripts('onModchartCall', [modManager]); - addModManager(modManager); - #end - startTimer = new FlxTimer().start(Conductor.crochet / 1000 / playbackRate, function(tmr:FlxTimer) { characterBopper(tmr.loopsLeft); @@ -1570,15 +1595,6 @@ class PlayState extends MusicBeatState return true; } - #if MODCHART_ALLOWED - public function addModManager(obj:ModManager) - { - if (obj.playfields == null || obj.playfields.length == 0) return; - - add(obj); - } - #end - public function addBehindGF(obj:FlxObject) { insert(members.indexOf(gfGroup), obj); @@ -1648,7 +1664,7 @@ class PlayState extends MusicBeatState public function setSongTime(time:Float) { - if(time < 0) time = 0; + if(time <= 0) time = 0; FlxG.sound.music.pause(); vocals.pause(); @@ -1735,6 +1751,8 @@ class PlayState extends MusicBeatState #end setOnScripts('songLength', songLength); callOnScripts('onSongStart'); + + runSongSyncThread(); } var debugNum:Int = 0; @@ -1798,16 +1816,13 @@ class PlayState extends MusicBeatState noteData = songData.notes; var playerCounter:Int = 0; - var daBeats:Int = 0; // Not exactly representative of 'daBeats' lol, just how much it has looped + var oldNote:Note = null; + var daBpm:Float = Conductor.bpm; var songName:String = Paths.formatToSongPath(SONG.song); var file:String = Paths.json(songName + '/events'); - #if MODS_ALLOWED - if (FileSystem.exists(Paths.modsJson(songName + '/events')) || FileSystem.exists(file)) { - #else - if (OpenFlAssets.exists(file)) { - #end + try { var eventsData:Array = Song.loadFromJson('events', songName).events; for (event in eventsData) //Event Notes { @@ -1825,31 +1840,35 @@ class PlayState extends MusicBeatState eventPushed(subEvent); } } - } + } + catch (e) {} for (section in noteData) { - for (songNotes in section.sectionNotes) + if (section.changeBPM && daBpm != section.bpm) + daBpm = section.bpm; + + for (i in 0...section.sectionNotes.length) { + final songNotes:Array = section.sectionNotes[i]; + var daStrumTime:Float = songNotes[0]; var daNoteData:Int = Std.int(songNotes[1] % 4); + var holdLength:Float = songNotes[2]; var gottaHitNote:Bool = section.mustHitSection; + if (Math.isNaN(holdLength)) + holdLength = 0.0; + if (songNotes[1] > 3) { gottaHitNote = !section.mustHitSection; } - var oldNote:Note; - if (unspawnNotes.length > 0) - oldNote = unspawnNotes[Std.int(unspawnNotes.length - 1)]; - else - oldNote = null; - var swagNote:Note = new Note(daStrumTime, daNoteData, oldNote); swagNote.mustPress = gottaHitNote; - swagNote.sustainLength = songNotes[2]; + swagNote.sustainLength = holdLength; swagNote.gfNote = (section.gfSection && (songNotes[1]<4)); swagNote.row = Conductor.secsToRow(daStrumTime); swagNote.noteType = songNotes[3]; @@ -1861,31 +1880,45 @@ class PlayState extends MusicBeatState noteRows[idx][swagNote.row].push(swagNote); swagNote.scrollFactor.set(); - - var susLength:Float = swagNote.sustainLength; - - susLength = susLength / Conductor.stepCrochet; + unspawnNotes.push(swagNote); - var floorSus:Int = Math.floor(susLength); + var curStepCrochet:Float = 60 / daBpm * 1000 / 4.0; + final floorSus:Int = Math.floor(swagNote.sustainLength / curStepCrochet); if(floorSus > 0) { for (susNote in 0...floorSus+1) { oldNote = unspawnNotes[Std.int(unspawnNotes.length - 1)]; - var sustainNote:Note = new Note(daStrumTime + (Conductor.stepCrochet * susNote) + (Conductor.stepCrochet / FlxMath.roundDecimal(songSpeed, 2)), daNoteData, oldNote, true); - sustainNote.mustPress = gottaHitNote; - sustainNote.gfNote = (section.gfSection && (songNotes[1]<4)); + var sustainNote:Note = new Note(daStrumTime + (curStepCrochet * susNote), daNoteData, oldNote, true); + sustainNote.mustPress = swagNote.mustPress; + sustainNote.gfNote = swagNote.gfNote; sustainNote.noteType = swagNote.noteType; sustainNote.scrollFactor.set(); - swagNote.tail.push(sustainNote); sustainNote.parent = swagNote; unspawnNotes.push(sustainNote); + swagNote.tail.push(sustainNote); - if (sustainNote.mustPress) + sustainNote.correctionOffset = swagNote.height / 2; + if(!PlayState.isPixelStage) { - sustainNote.x += FlxG.width / 2; // general offset + if(oldNote.isSustainNote) + { + oldNote.scale.y *= Note.SUSTAIN_SIZE / oldNote.frameHeight; + oldNote.scale.y /= playbackRate; + oldNote.resizeByRatio(curStepCrochet / Conductor.stepCrochet); + } + + if(ClientPrefs.downScroll) + sustainNote.correctionOffset = 0; } + else if(oldNote.isSustainNote) + { + oldNote.scale.y /= playbackRate; + oldNote.resizeByRatio(curStepCrochet / Conductor.stepCrochet); + } + + if (sustainNote.mustPress) sustainNote.x += FlxG.width / 2; // general offset else if(ClientPrefs.middleScroll) { sustainNote.x += 310; @@ -1913,6 +1946,8 @@ class PlayState extends MusicBeatState if(!noteTypeMap.exists(swagNote.noteType)) { noteTypeMap.set(swagNote.noteType, true); } + + oldNote = swagNote; } daBeats += 1; } @@ -2077,6 +2112,7 @@ class PlayState extends MusicBeatState paused = false; callOnScripts('onResume'); + runSongSyncThread(); #if DISCORD_ALLOWED if (startTimer != null && startTimer.finished) @@ -2129,25 +2165,23 @@ class PlayState extends MusicBeatState { if(finishTimer != null) return; - vocals.pause(); - opponentVocals.pause(); + //trace('resynced vocals at ' + Math.floor(Conductor.songPosition)); FlxG.sound.music.play(); - #if FLX_PITCH FlxG.sound.music.pitch = playbackRate; - #end - Conductor.songPosition = FlxG.sound.music.time; - if (Conductor.songPosition <= vocals.length) - { - vocals.time = Conductor.songPosition; - #if FLX_PITCH vocals.pitch = playbackRate; #end - } - if (Conductor.songPosition <= opponentVocals.length) + #if FLX_PITCH FlxG.sound.music.pitch = playbackRate; #end + Conductor.songPosition = FlxG.sound.music.time + Conductor.offset; + + var checkVocals = [vocals, opponentVocals]; + for (voc in checkVocals) { - opponentVocals.time = Conductor.songPosition; - #if FLX_PITCH opponentVocals.pitch = playbackRate; #end + if (FlxG.sound.music.time < vocals.length) + { + voc.time = FlxG.sound.music.time; + #if FLX_PITCH voc.pitch = playbackRate; #end + voc.play(); + } + else voc.pause(); } - vocals.play(); - opponentVocals.play(); } public var paused:Bool = false; @@ -2214,13 +2248,8 @@ class PlayState extends MusicBeatState //for health bar smoothing displayHealth = FlxMath.lerp(displayHealth, health, 0.1 * playbackRate); - var mult:Float = FlxMath.lerp(1, iconP1.scale.x, Math.exp(-elapsed * 9 * playbackRate)); - iconP1.scale.set(mult, mult); - iconP1.updateHitbox(); - - var mult:Float = FlxMath.lerp(1, iconP2.scale.x, Math.exp(-elapsed * 9 * playbackRate)); - iconP2.scale.set(mult, mult); - iconP2.updateHitbox(); + iconP1.updateIconScale(elapsed); + iconP2.updateIconScale(elapsed); var iconOffset:Int = 26; @@ -2307,6 +2336,11 @@ class PlayState extends MusicBeatState } doDeathCheck(); + #if MODCHART_ALLOWED + modManager.updateTimeline(curDecStep); + modManager.update(elapsed); + #end + if (unspawnNotes[0] != null) { var time:Float = spawnTime; @@ -2330,6 +2364,24 @@ class PlayState extends MusicBeatState } } + #if MODCHART_ALLOWED + opponentStrums.forEachAlive((strum:StrumNote) -> + { + var pos = modManager.getPos(0, 0, 0, curDecBeat, strum.noteData, 1, strum, [], strum.vec3Cache); + modManager.updateObject(curDecBeat, strum, pos, 1); + strum.x = pos.x; + strum.y = pos.y; + }); + + playerStrums.forEachAlive((strum:StrumNote) -> + { + var pos = modManager.getPos(0, 0, 0, curDecBeat, strum.noteData, 0, strum, [], strum.vec3Cache); + modManager.updateObject(curDecBeat, strum, pos, 0); + strum.x = pos.x; + strum.y = pos.y; + }); + #end + if (generatedMusic) { if(!inCutscene) @@ -2368,16 +2420,40 @@ class PlayState extends MusicBeatState strumAngle += daNote.offsetAngle; strumAlpha *= daNote.multAlpha; - if (strumScroll) //Downscroll - { - //daNote.y = (strumY + 0.45 * (Conductor.songPosition - daNote.strumTime) * songSpeed); - daNote.distance = (0.45 * (Conductor.songPosition - daNote.strumTime) * songSpeed * daNote.multSpeed); - } - else //Upscroll + #if MODCHART_ALLOWED + var pN:Int = daNote.mustPress ? 0 : 1; + var pos = modManager.getPos(daNote.strumTime, modManager.getVisPos(Conductor.songPosition, daNote.strumTime, songSpeed), + daNote.strumTime - Conductor.songPosition, curDecBeat, daNote.noteData, pN, daNote, [], daNote.vec3Cache); + + modManager.updateObject(curDecBeat, daNote, pos, pN); + + pos.x += daNote.offsetX; + pos.y += daNote.offsetY; + daNote.x = pos.x; + daNote.y = pos.y; + + if (daNote.isSustainNote) { - //daNote.y = (strumY - 0.45 * (Conductor.songPosition - daNote.strumTime) * songSpeed); - daNote.distance = (-0.45 * (Conductor.songPosition - daNote.strumTime) * songSpeed * daNote.multSpeed); + var futureSongPos = Conductor.songPosition + 75; + var diff = daNote.strumTime - futureSongPos; + var vDiff = modManager.getVisPos(futureSongPos, daNote.strumTime, songSpeed); + + var nextPos = modManager.getPos(daNote.strumTime, vDiff, diff, Conductor.getStep(futureSongPos) / 4, daNote.noteData, pN, daNote, [], + daNote.vec3Cache); + nextPos.x += daNote.offsetX; + nextPos.y += daNote.offsetY; + var diffX = (nextPos.x - pos.x); + var diffY = (nextPos.y - pos.y); + var rad = Math.atan2(diffY, diffX); + var deg = rad * (180 / Math.PI); + + daNote.mAngle = (deg != 0 ? deg + 90 : 0); } + #end + + daNote.distance = (0.45 * (Conductor.songPosition - daNote.strumTime) * songSpeed * daNote.multSpeed); + if (!strumScroll) //Downscroll + daNote.distance *= -1; var angleDir = strumDirection * Math.PI / 180; if (daNote.copyAngle) @@ -2391,21 +2467,15 @@ class PlayState extends MusicBeatState if(daNote.copyY) { - daNote.y = strumY + Math.sin(angleDir) * daNote.distance; + daNote.y = strumY + daNote.correctionOffset + Math.sin(angleDir) * daNote.distance; if(strumScroll && daNote.isSustainNote) { - if (daNote.animation.curAnim.name.endsWith('end')) { - daNote.y += 10.5 * (fakeCrochet / 400) * 1.5 * songSpeed + (46 * (songSpeed - 1)); - daNote.y -= 46 * (1 - (fakeCrochet / 600)) * songSpeed; - if(PlayState.isPixelStage) { - daNote.y += (7 + (6 - daNote.originalHeightForCalcs) * PlayState.daPixelZoom) * songSpeed; - } else { - daNote.y -= 19.25 + (songSpeed - 1); - } + if(isPixelStage) + { + daNote.y -= daPixelZoom * 9.5; } - daNote.y += (Note.swagWidth / 2) - (60.5 * (songSpeed - 1)); - daNote.y += 27.5 * ((SONG.bpm / 100) - 1) * (songSpeed - 1); + daNote.y -= (daNote.frameHeight * daNote.scale.y) - (Note.swagWidth / 2); } } @@ -2416,9 +2486,7 @@ class PlayState extends MusicBeatState if(!daNote.blockHit && daNote.mustPress && cpuControlled && daNote.canBeHit) { if(daNote.isSustainNote) { - if(daNote.canBeHit) { - goodNoteHit(daNote); - } + if(daNote.parent?.wasGoodHit) goodNoteHit(daNote); } else if(daNote.strumTime <= Conductor.songPosition || daNote.isSustainNote) { goodNoteHit(daNote); } @@ -2428,28 +2496,34 @@ class PlayState extends MusicBeatState if(strumGroup.members[daNote.noteData].sustainReduce && daNote.isSustainNote && (daNote.mustPress || !daNote.ignoreNote) && (!daNote.mustPress || (daNote.wasGoodHit || (daNote.prevNote.wasGoodHit && !daNote.canBeHit)))) { + #if !MODCHART_ALLOWED + var swagRect = daNote.clipRect; + swagRect ??= new FlxRect(0, 0, daNote.frameWidth, daNote.frameHeight); + #else + var swagRect = new FlxRect(0, 0, daNote.frameWidth, daNote.frameHeight); + #end if (strumScroll) { if(daNote.y - daNote.offset.y * daNote.scale.y + daNote.height >= center) { - var swagRect = new FlxRect(0, 0, daNote.frameWidth, daNote.frameHeight); swagRect.height = (center - daNote.y) / daNote.scale.y; swagRect.y = daNote.frameHeight - swagRect.height; - - daNote.clipRect = swagRect; } } else { if (daNote.y + daNote.offset.y * daNote.scale.y <= center) { - var swagRect = new FlxRect(0, 0, daNote.width / daNote.scale.x, daNote.height / daNote.scale.y); swagRect.y = (center - daNote.y) / daNote.scale.y; + #if !MODCHART_ALLOWED + swagRect.height = (daNote.height / daNote.scale.y) - swagRect.y; + #else swagRect.height -= swagRect.y; - - daNote.clipRect = swagRect; + #end } } + + daNote.clipRect = swagRect; } // Kill extremely late notes and cause misses @@ -3021,7 +3095,6 @@ class PlayState extends MusicBeatState if (storyPlaylist.length <= 0) { WeekData.loadTheFirstEnabledMod(); - FlxG.sound.playMusic(Paths.music('freakyMenu')); cancelMusicFadeTween(); canResync = false; @@ -3054,7 +3127,7 @@ class PlayState extends MusicBeatState prevCamFollow = camFollow; PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0] + difficulty, PlayState.storyPlaylist[0]); - FlxG.sound.music.stop(); + FlxG.sound.music?.stop(); LoadingState.loadAndSwitchState(() -> new PlayState()); } @@ -3066,7 +3139,6 @@ class PlayState extends MusicBeatState cancelMusicFadeTween(); canResync = false; FlxG.switchState(() -> new FreeplayState()); - FlxG.sound.playMusic(Paths.music('freakyMenu')); changedDifficulty = false; } transitioning = true; @@ -3672,7 +3744,7 @@ class PlayState extends MusicBeatState if (SONG.needsVoices) if(opponentVocals.length <= 0) vocals.volume = 1; - iconP2.scale.set(1.15, 1.15); + iconP2.flash(1.12, 1); var time:Float = 0.15; if(note.isSustainNote && !note.animation.curAnim.name.endsWith('end')) { @@ -3805,7 +3877,7 @@ class PlayState extends MusicBeatState note.wasGoodHit = true; vocals.volume = 1; - iconP1.scale.set(1.15, 1.15); + iconP1.flash(1.12, 1); var isSus:Bool = note.isSustainNote; //GET OUT OF MY HEAD, GET OUT OF MY HEAD, GET OUT OF MY HEAD var leData:Int = Math.round(Math.abs(note.noteData)); @@ -3921,7 +3993,7 @@ class PlayState extends MusicBeatState } var holdCover:NoteHoldCover = grpHoldCovers.recycle(NoteHoldCover); - holdCover.startCrochet = Conductor.stepCrochet; + holdCover.startCrochet = Conductor.stepCrochet / playbackRate; holdCover.frameRate = Math.floor(24 / 100 * SONG.bpm); holdCover.setupHoldCover(strum, note, skin, hueColor, satColor, brtColor); @@ -3933,7 +4005,7 @@ class PlayState extends MusicBeatState override function destroy() { for (lua in luaArray) { - lua.call('onDestroy', []); + lua.safeCall('onDestroy', []); lua.stop(); } luaArray = []; @@ -3956,6 +4028,9 @@ class PlayState extends MusicBeatState FlxG.animationTimeScale = 1; #if FLX_PITCH FlxG.sound.music.pitch = 1; #end instance = null; + shutdownThread = true; + FlxG.signals.preUpdate.remove(checkForResync); + super.destroy(); } @@ -4008,11 +4083,8 @@ class PlayState extends MusicBeatState notes.sort(FlxSort.byY, ClientPrefs.downScroll ? FlxSort.ASCENDING : FlxSort.DESCENDING); } - iconP1.scale.set(1.2, 1.2); - iconP2.scale.set(1.2, 1.2); - - iconP1.updateHitbox(); - iconP2.updateHitbox(); + iconP1.flash(1.2, 1); + iconP2.flash(1.2, 1); characterBopper(curBeat); @@ -4086,7 +4158,7 @@ class PlayState extends MusicBeatState } #if MODS_ALLOWED - var luaToLoad:String = Paths.modFolders(luaFile); + var luaToLoad:String = Mods.modFolders(luaFile); if(FileSystem.exists(luaToLoad)) { luaArray.push(new FunkinLua(luaToLoad)); @@ -4103,6 +4175,13 @@ class PlayState extends MusicBeatState } #elseif sys var luaToLoad:String = Paths.getPreloadPath(luaFile); + if(FileSystem.exists(luaToLoad)) + { + luaArray.push(new FunkinLua(luaToLoad)); + return true; + } + #else + var luaToLoad:String = Paths.getPreloadPath(luaFile); if(OpenFlAssets.exists(luaToLoad)) { luaArray.push(new FunkinLua(luaToLoad)); @@ -4122,7 +4201,7 @@ class PlayState extends MusicBeatState } #if MODS_ALLOWED - var hscriptToLoad:String = Paths.modFolders(hscriptFile); + var hscriptToLoad:String = Mods.modFolders(hscriptFile); if(FileSystem.exists(hscriptToLoad)) { hscriptArray.push(new FunkinHScript(hscriptToLoad)); @@ -4144,6 +4223,13 @@ class PlayState extends MusicBeatState hscriptArray.push(new FunkinHScript(hscriptToLoad)); return true; } + #else + var hscriptToLoad:String = Paths.getPreloadPath(hscriptFile); + if(OpenFlAssets.exists(hscriptToLoad)) + { + hscriptArray.push(new FunkinHScript(hscriptToLoad)); + return true; + } #end return false; } @@ -4205,7 +4291,7 @@ class PlayState extends MusicBeatState if(exclusions.contains(script.scriptName)) continue; - var myValue = script.call(event, args); + var myValue = script.safeCall(event, args); if(myValue == FunkinLua.Function_StopLua && !ignoreStops) break; @@ -4382,6 +4468,40 @@ class PlayState extends MusicBeatState } #end - var curLight:Int = -1; - var curLightEvent:Int = -1; + function checkForResync() + { + if (paused) return; + + if (requiresSyncing) + { + requiresSyncing = false; + setSongTime(lastCorrectSongPos); + } + + gameFroze = false; + } + + public function runSongSyncThread() + { + #if target.threaded + Thread.create(() -> { + while (!endingSong && !paused && !shutdownThread) + { + if (requiresSyncing) continue; + + if (gameFroze) + { + lastCorrectSongPos = Conductor.songPosition; + requiresSyncing = true; + continue; + } + gameFroze = true; + + Sys.sleep(0.5); + } + }); + #end + + if (!FlxG.signals.preUpdate.has(checkForResync)) FlxG.signals.preUpdate.add(checkForResync); + } } diff --git a/source/game/backend/ClientPrefs.hx b/source/game/backend/ClientPrefs.hx index 3c43ba6..a7b808a 100644 --- a/source/game/backend/ClientPrefs.hx +++ b/source/game/backend/ClientPrefs.hx @@ -3,6 +3,9 @@ package game.backend; import flixel.FlxG; import flixel.util.FlxSave; import flixel.input.keyboard.FlxKey; + +import lime.ui.WindowVSyncMode; + import game.states.backend.Achievements; import game.backend.Controls; @@ -28,6 +31,8 @@ class ClientPrefs { public static var hideHud:Bool = false; public static var noteOffset:Int = 0; public static var arrowHSV:Array> = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]; + public static var vsync:Bool = false; + public static var unlimitedFPS:Bool = false; public static var ghostTapping:Bool = true; public static var timeBarType:String = 'Time Left'; public static var scoreZoom:Bool = true; @@ -120,6 +125,8 @@ class ClientPrefs { controllerMode = false; hitsoundVolume = 0; pauseMusic = 'Tea Time'; + vsync = false; + unlimitedFPS = false; checkForUpdates = true; comboStacking = true; colorBlindMode = 'None'; @@ -172,6 +179,8 @@ class ClientPrefs { FlxG.save.data.controllerMode = controllerMode; FlxG.save.data.hitsoundVolume = hitsoundVolume; FlxG.save.data.pauseMusic = pauseMusic; + FlxG.save.data.vsync = vsync; + FlxG.save.data.unlimitedFPS = unlimitedFPS; FlxG.save.data.checkForUpdates = checkForUpdates; FlxG.save.data.comboStacking = comboStacking; FlxG.save.data.colorBlindMode = colorBlindMode; @@ -221,6 +230,8 @@ class ClientPrefs { if (FlxG.save.data.controllerMode != null) controllerMode = FlxG.save.data.controllerMode; if (FlxG.save.data.hitsoundVolume != null) hitsoundVolume = FlxG.save.data.hitsoundVolume; if (FlxG.save.data.pauseMusic != null) pauseMusic = FlxG.save.data.pauseMusic; + if (FlxG.save.data.vsync != null) vsync = FlxG.save.data.vsync; + if (FlxG.save.data.unlimitedFPS != null) unlimitedFPS = FlxG.save.data.unlimitedFPS; if (FlxG.save.data.checkForUpdates != null) checkForUpdates = FlxG.save.data.checkForUpdates; if (FlxG.save.data.comboStacking != null) comboStacking = FlxG.save.data.comboStacking; if (FlxG.save.data.colorBlindMode != null) colorBlindMode = FlxG.save.data.colorBlindMode; @@ -247,17 +258,36 @@ class ClientPrefs { } private static function applySettings() { - if (Main.fpsVar != null) { - Main.fpsVar.visible = showFPS; - } - - if (framerate > FlxG.drawFramerate) { - FlxG.updateFramerate = framerate; - FlxG.drawFramerate = framerate; - } else { - FlxG.drawFramerate = framerate; - FlxG.updateFramerate = framerate; + if (Init.fpsVar != null) { + Init.fpsVar.visible = showFPS; } + + FlxG.stage.quality = lowQuality ? LOW : BEST; + + var vsyncMode:WindowVSyncMode = vsync ? WindowVSyncMode.ON : WindowVSyncMode.OFF; + openfl.Lib.application.window.setVSyncMode(vsyncMode); + + #if !html5 + if (unlimitedFPS) + { + FlxG.drawFramerate = 0; + FlxG.updateFramerate = 0; + } + else + { + var targetFPS:Int = framerate; + if(targetFPS > FlxG.drawFramerate) + { + FlxG.updateFramerate = targetFPS; + FlxG.drawFramerate = targetFPS; + } + else + { + FlxG.drawFramerate = targetFPS; + FlxG.updateFramerate = targetFPS; + } + } + #end if (FlxG.save.data.volume != null) FlxG.sound.volume = FlxG.save.data.volume; if (FlxG.save.data.mute != null) FlxG.sound.muted = FlxG.save.data.mute; diff --git a/source/game/backend/Conductor.hx b/source/game/backend/Conductor.hx index 66ec28f..bd9d328 100644 --- a/source/game/backend/Conductor.hx +++ b/source/game/backend/Conductor.hx @@ -25,10 +25,10 @@ class Conductor public static var lastSongPos:Float; public static var offset:Float = 0; - public static var ROWS_PER_BEAT = 48; // from Stepmania - public static var BEATS_PER_MEASURE = 4; // TODO: time sigs - public static var ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE; // from Stepmania - public static var MAX_NOTE_ROW = 1 << 30; // from Stepmania + public static final ROWS_PER_BEAT = 48; // from Stepmania + public static final BEATS_PER_MEASURE = 4; // TODO: time sigs + public static final ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE; // from Stepmania + public static final MAX_NOTE_ROW = 1 << 30; // from Stepmania public inline static function beatToRow(beat:Float):Int return Math.round(beat * ROWS_PER_BEAT); diff --git a/source/game/backend/CrashHandler.hx b/source/game/backend/CrashHandler.hx index 4cf014d..891b246 100644 --- a/source/game/backend/CrashHandler.hx +++ b/source/game/backend/CrashHandler.hx @@ -24,6 +24,7 @@ enum AudioStatus { class CrashHandler { + @:unreflective static final LOGS_DIR = "logs/"; public static function init():Void @@ -202,29 +203,7 @@ class CrashHandler logContent.add('\n${content}\n'); logContent.add('==================== SYSTEM INFORMATION ==================\n\n'); - var osInfo = "Unknown"; - #if windows - try { - var windowsCurrentVersionPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; - var buildStr = WindowsRegistry.getKey(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "CurrentBuildNumber"); - var buildNumber:Int = 0; - if (buildStr != null) { - var parsed = Std.parseInt(buildStr); - if (parsed != null) buildNumber = parsed; - } - var edition = WindowsRegistry.getKey(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "ProductName"); - edition ??= "Windows"; - - if (buildNumber >= 22000) { - edition = edition.replace("Windows 10", "Windows 11"); - } - osInfo = edition; - } catch (e:Dynamic) { - osInfo = '${System.platformLabel} ${System.platformVersion}'; - } - #else - osInfo = '${System.platformLabel} ${System.platformVersion}'; - #end + var osInfo = '${System.platformLabel} ${System.platformVersion}'; var arch = "Unknown"; try { @@ -303,48 +282,20 @@ class CrashHandler private static function getGpuInfo():String { try { - #if windows - try { - return WindowsRegistry.getKey(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\0000", "DriverDesc"); - } catch(e:Dynamic) {} - - //fallback to WMIC - var process = new Process("wmic", ["path", "win32_VideoController", "get", "name"]); - var result = process.stdout.readAll().toString(); - process.close(); - - var lines = result.split("\n"); - for (line in lines) { - if (line.trim() != "" && line.indexOf("Name") == -1) { - return line.trim(); - } - } - #elseif linux - var process = new Process("lspci", []); - var result = process.stdout.readAll().toString(); - process.close(); - - var lines = result.split("\n"); - for (line in lines) { - if (line.indexOf("VGA") != -1 || line.indexOf("3D") != -1) { - var parts = line.split(":"); - if (parts.length > 1) return parts[parts.length-1].trim(); - } - } - #elseif mac - var process = new Process("system_profiler", ["SPDisplaysDataType"]); - var result = process.stdout.readAll().toString(); - process.close(); - - var lines = result.split("\n"); - for (line in lines) { - if (line.indexOf("Chipset Model") != -1) { - var parts = line.split(":"); - if (parts.length > 1) return parts[1].trim(); + var gpuName:String = "N/A"; + @:privateAccess { + if (FlxG.stage?.context3D?.gl != null) { + var renderer = FlxG.stage.context3D.gl.getParameter(FlxG.stage.context3D.gl.RENDERER); + if (renderer != null) { + gpuName = Std.string(renderer).split("/")[0].trim(); + if (gpuName != "N/A" && gpuName != "") { + return gpuName; + } + } } } - #end } catch (e:Dynamic) {} + return "Unknown GPU"; } @@ -352,42 +303,88 @@ class CrashHandler { try { #if sl_windows_api - var totalMemBytes:Float = winapi.WindowsAPI.obtainRAM(); - if (!Math.isNaN(totalMemBytes)) { - var gb = Math.round(totalMemBytes / 1024 * 100) / 100; - return '${gb} GB'; + var ramInfo:String = winapi.WindowsAPI.obtainRAMInfo(true); + + if (ramInfo.indexOf("MB") != -1) { + var parts = ramInfo.split("MB"); + var ramValueStr = parts[0].trim(); + var ramValue = Std.parseInt(ramValueStr); + + if (ramValue != null) { + var gb = Math.round(ramValue / 1024 * 100) / 100; + var hasTypeInfo = false; + var ramType = ""; + + if (parts.length > 1 && parts[1] != null) { + var typePart = parts[1].trim(); + if (typePart.length > 2 && typePart.charAt(0) == '(' && typePart.charAt(typePart.length - 1) == ')') { + ramType = typePart.substring(1, typePart.length - 1); + if (ramType.length > 0) { + hasTypeInfo = true; + } + } + } + + if (hasTypeInfo) { + return '${gb} GB (${ramType})'; + } else { + return '${gb} GB'; + } + } } + return "Unknown RAM"; #else - //fallback to system commands + // Fallback var total = 0.0; if (Sys.systemName() == "Windows") { - var process = new Process("wmic", ["computersystem", "get", "totalphysicalmemory"]); - var result = process.stdout.readAll().toString(); - process.close(); - - var lines = result.split("\n"); - for (line in lines) { - if (line.trim() != "" && line.indexOf("TotalPhysicalMemory") == -1) { - total = Std.parseFloat(line.trim()); - break; + try { + var process = new Process("powershell", ["Get-CimInstance -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum | Select-Object Sum"]); + var result = process.stdout.readAll().toString(); + process.close(); + + var lines = result.split("\n"); + for (line in lines) { + if (line.indexOf("Sum") != -1) { + var sumStr = line.split(":")[1].trim(); + total = Std.parseFloat(sumStr); + break; + } } + } catch (e:Dynamic) { + try { + var process = new Process("wmic", ["computersystem", "get", "totalphysicalmemory"]); + var result = process.stdout.readAll().toString(); + process.close(); + + var lines = result.split("\n"); + for (line in lines) { + if (line.trim() != "" && line.indexOf("TotalPhysicalMemory") == -1) { + total = Std.parseFloat(line.trim()); + break; + } + } + } catch (e2:Dynamic) {} } } else if (Sys.systemName() == "Linux") { - var process = new Process("grep", ["MemTotal", "/proc/meminfo"]); - var result = process.stdout.readAll().toString(); - process.close(); - - var tokens = result.split(" ").filter(function(token) return token.trim() != ""); - if (tokens.length > 1) { - total = Std.parseFloat(tokens[1]) * 1024; - } + try { + var process = new Process("grep", ["MemTotal", "/proc/meminfo"]); + var result = process.stdout.readAll().toString(); + process.close(); + + var tokens = result.split(" ").filter(function(token) return token.trim() != ""); + if (tokens.length > 1) { + total = Std.parseFloat(tokens[1]) * 1024; + } + } catch (e:Dynamic) {} } else if (Sys.systemName() == "Mac") { - var process = new Process("sysctl", ["-n", "hw.memsize"]); - total = Std.parseFloat(process.stdout.readAll().toString().trim()); - process.close(); + try { + var process = new Process("sysctl", ["-n", "hw.memsize"]); + total = Std.parseFloat(process.stdout.readAll().toString().trim()); + process.close(); + } catch (e:Dynamic) {} } if (!Math.isNaN(total) && total > 0) { @@ -395,7 +392,9 @@ class CrashHandler return '${gb} GB'; } #end - } catch (e:Dynamic) {} + } catch (e:Dynamic) { + trace("Error getting RAM info: " + e); + } return "Unknown RAM"; } #end diff --git a/source/game/backend/CutsceneHandler.hx b/source/game/backend/CutsceneHandler.hx index 784fd2e..47e7cdd 100644 --- a/source/game/backend/CutsceneHandler.hx +++ b/source/game/backend/CutsceneHandler.hx @@ -13,6 +13,8 @@ typedef CutsceneEvent = { class CutsceneHandler extends FlxBasic { private var controls(get, never):Controls; + + private var _canSkip:Bool = false; public var timedEvents:Array = []; public var skipCallback:Void->Void = null; @@ -22,7 +24,7 @@ class CutsceneHandler extends FlxBasic public var music:String = null; final _timeToSkip:Float = 1; - var _canSkip:Bool = false; + public var holdingTime:Float = 0; public var skipSprite:FlxPieDial; public var finishCallback:Void->Void = null; diff --git a/source/game/backend/ErrorShader.hx b/source/game/backend/ErrorShader.hx deleted file mode 100644 index 535508e..0000000 --- a/source/game/backend/ErrorShader.hx +++ /dev/null @@ -1,75 +0,0 @@ -package game.backend; - -import game.shaders.flixel.FlxShader; -import flixel.FlxG; -import flixel.addons.display.FlxRuntimeShader; - -import lime.graphics.opengl.GLProgram; - -#if sl_windows_api -import winapi.WindowsAPI.MessageBoxIcon; -import winapi.WindowsAPI.MessageBoxType; -import winapi.WindowsAPI; -#end - -using StringTools; - -class ErrorShader extends FlxShader implements IErrorHandler { - public var shaderName:String = ''; - public dynamic function onError(error:Dynamic):Void {} - - public function new(?shaderName:String) { - this.shaderName = shaderName; - super(); - } - - override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram { - try { - return super.__createGLProgram(vertexSource, fragmentSource); - } catch (error) { - ErrorShader.crashSave(this.shaderName, error, onError); - return null; - } - } - - public static function crashSave(shaderName:String, error:Dynamic, onError:Dynamic) { - shaderName = (shaderName == null ? 'unnamed' : shaderName); - - CoolUtil.showPopUp( - 'There has been an error compiling this shader!', - 'Error on shader "${shaderName}"!' - ); - final dateNow = Date.now().toString().replace(" ", "_").replace(":", "'"); - if (!sys.FileSystem.exists('./crash/')) sys.FileSystem.createDirectory('./crash/'); - - final crashLogPath = './crash/shader_${shaderName}_${dateNow}.txt'; - sys.io.File.saveContent(crashLogPath, error); - trace('Shader Crashlog saved at "$crashLogPath"'); - - onError(error); - } -} - -class ErrorRuntimeShader extends FlxRuntimeShader implements IErrorHandler { - public var shaderName:String = ''; - public dynamic function onError(error:Dynamic):Void {} - - public function new(?shaderName:String, ?fragmentSource:String, ?vertexSource:String) { - this.shaderName = shaderName; - super(fragmentSource, vertexSource); - } - - override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram { - try { - return super.__createGLProgram(vertexSource, fragmentSource); - } catch (error) { - ErrorShader.crashSave(this.shaderName, error, onError); - return null; - } - } -} - -interface IErrorHandler { - public var shaderName:String; - public dynamic function onError(error:Dynamic):Void; -} \ No newline at end of file diff --git a/source/game/backend/FunkinShader.hx b/source/game/backend/FunkinShader.hx deleted file mode 100644 index 28b27b3..0000000 --- a/source/game/backend/FunkinShader.hx +++ /dev/null @@ -1,88 +0,0 @@ -package game.backend; - -import game.shaders.flixel.FlxShader; -import flixel.addons.display.FlxRuntimeShader; -import lime.graphics.opengl.GLProgram; -import lime.app.Application; - -using StringTools; -class FunkinShader extends FlxShader implements IErrorHandler -{ - public var shaderName:String = ''; - public dynamic function onError(error:Dynamic):Void {} - public function new(?shaderName:String) - { - this.shaderName = shaderName; - super(); - } - - override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram - { - try - { - final res = super.__createGLProgram(vertexSource, fragmentSource); - return res; - } - catch (error) - { - FunkinShader.crashSave(this.shaderName, error, onError); - return null; - } - } - - public static function crashSave(shaderName:String, error:Dynamic, onError:Dynamic) // prevent the app from dying immediately - { - if(shaderName == null) shaderName = 'unnamed'; - var alertTitle:String = 'Error on Shader: "$shaderName"'; - - trace(error); - - #if !debug - // Save a crash log on Release builds - var errMsg:String = ""; - var dateNow:String = Date.now().toString().replace(" ", "_").replace(":", "'"); - - if (!sys.FileSystem.exists('./crash/')) - sys.FileSystem.createDirectory('./crash/'); - - var crashLogPath:String = './crash/shader_${shaderName}_${dateNow}.txt'; - sys.io.File.saveContent(crashLogPath, error); - CoolUtil.showPopUp('Error log saved at: $crashLogPath', alertTitle, MSG_INFORMATION); - #else - CoolUtil.showPopUp('Error logs aren\'t created on debug builds, check the trace log instead.', alertTitle); - #end - - onError(error); - } -} - -class FunkinRuntimeShader extends FlxRuntimeShader implements IErrorHandler -{ - public var shaderName:String = ''; - public dynamic function onError(error:Dynamic):Void {} - public function new(?shaderName:String, ?fragmentSource:String, ?vertexSource:String) - { - this.shaderName = shaderName; - super(fragmentSource, vertexSource); - } - - override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram - { - try - { - final res = super.__createGLProgram(vertexSource, fragmentSource); - return res; - } - catch (error) - { - FunkinShader.crashSave(this.shaderName, error, onError); - return null; - } - } -} - -interface IErrorHandler -{ - public var shaderName:String; - public dynamic function onError(error:Dynamic):Void; -} \ No newline at end of file diff --git a/source/game/backend/Song.hx b/source/game/backend/Song.hx index 612063e..b506eb2 100644 --- a/source/game/backend/Song.hx +++ b/source/game/backend/Song.hx @@ -135,7 +135,7 @@ class Song var formattedSong:String = Paths.formatToSongPath(jsonInput); #if MODS_ALLOWED - var moddyFile:String = Paths.modsJson(formattedFolder + '/' + formattedSong); + var moddyFile:String = Mods.modsJson(formattedFolder + '/' + formattedSong); if(FileSystem.exists(moddyFile)) { rawJson = File.getContent(moddyFile).trim(); } diff --git a/source/game/backend/StageData.hx b/source/game/backend/StageData.hx index ecc03cf..37bdae7 100644 --- a/source/game/backend/StageData.hx +++ b/source/game/backend/StageData.hx @@ -13,19 +13,21 @@ import game.backend.Song; using StringTools; typedef StageFile = { - var directory:String; - var defaultZoom:Float; - var isPixelStage:Bool; + var directory:String; + var defaultZoom:Float; + var isPixelStage:Bool; - var boyfriend:Array; - var girlfriend:Array; - var opponent:Array; - var hide_girlfriend:Bool; + var boyfriend:Array; + var girlfriend:Array; + var opponent:Array; + var hide_girlfriend:Bool; - var camera_boyfriend:Array; - var camera_opponent:Array; - var camera_girlfriend:Array; - var camera_speed:Null; + var camera_boyfriend:Array; + var camera_opponent:Array; + var camera_girlfriend:Array; + var camera_speed:Null; + + @:optional var loadingImages:Array; } class StageData { @@ -73,7 +75,7 @@ class StageData { var path:String = Paths.getPreloadPath('stages/' + stage + '.json'); #if MODS_ALLOWED - var modPath:String = Paths.modFolders('stages/' + stage + '.json'); + var modPath:String = Mods.modFolders('stages/' + stage + '.json'); if(FileSystem.exists(modPath)) { rawJson = File.getContent(modPath); } else if(FileSystem.exists(path)) { diff --git a/source/game/backend/WeekData.hx b/source/game/backend/WeekData.hx index 9becece..f3bb6b7 100644 --- a/source/game/backend/WeekData.hx +++ b/source/game/backend/WeekData.hx @@ -91,8 +91,8 @@ class WeekData { weeksLoaded.clear(); #if MODS_ALLOWED var disabledMods:Array = []; - var modsListPath:String = 'modsList.txt'; - var directories:Array = [Paths.mods(), Paths.getPreloadPath()]; + var modsListPath:String = Paths.txt('modsList'); + var directories:Array = [Mods.getModPath(), Paths.getPreloadPath()]; var originalLength:Int = directories.length; if(FileSystem.exists(modsListPath)) { @@ -106,9 +106,9 @@ class WeekData { } else // Sort mod loading order based on modsList.txt file { - var path = haxe.io.Path.join([Paths.mods(), splitName[0]]); + var path = haxe.io.Path.join([Mods.getModPath(), splitName[0]]); //trace('trying to push: ' + splitName[0]); - if (sys.FileSystem.isDirectory(path) && !Paths.ignoreModFolders.contains(splitName[0]) && !disabledMods.contains(splitName[0]) && !directories.contains(path + '/')) + if (sys.FileSystem.isDirectory(path) && !Mods.ignoreModFolders.contains(splitName[0]) && !disabledMods.contains(splitName[0]) && !directories.contains(path + '/')) { directories.push(path + '/'); //trace('pushed Directory: ' + splitName[0]); @@ -117,10 +117,10 @@ class WeekData { } } - var modsDirectories:Array = Paths.getModDirectories(); + var modsDirectories:Array = Mods.getModDirectories(); for (folder in modsDirectories) { - var pathThing:String = haxe.io.Path.join([Paths.mods(), folder]) + '/'; + var pathThing:String = haxe.io.Path.join([Mods.getModPath(), folder]) + '/'; if (!disabledMods.contains(folder) && !directories.contains(pathThing)) { directories.push(pathThing); @@ -143,7 +143,7 @@ class WeekData { #if MODS_ALLOWED if(j >= originalLength) { - weekFile.folder = directories[j].substring(Paths.mods().length, directories[j].length-1); + weekFile.folder = directories[j].substring(Mods.getModPath().length, directories[j].length-1); } #end @@ -194,7 +194,7 @@ class WeekData { if(i >= originalLength) { #if MODS_ALLOWED - weekFile.folder = directory.substring(Paths.mods().length, directory.length-1); + weekFile.folder = directory.substring(Mods.getModPath().length, directory.length-1); #end } if((PlayState.isStoryMode && !weekFile.hideStoryMode) || (!PlayState.isStoryMode && !weekFile.hideFreeplay)) @@ -237,20 +237,22 @@ class WeekData { } public static function setDirectoryFromWeek(?data:WeekData = null) { - Paths.currentModDirectory = ''; + #if MODS_ALLOWED + Mods.currentModDirectory = ''; if(data != null && data.folder != null && data.folder.length > 0) { - Paths.currentModDirectory = data.folder; + Mods.currentModDirectory = data.folder; } + #end } public static function loadTheFirstEnabledMod() { - Paths.currentModDirectory = ''; - #if MODS_ALLOWED - if (FileSystem.exists("modsList.txt")) + Mods.currentModDirectory = ''; + + if (FileSystem.exists(Paths.txt("modsList"))) { - var list:Array = CoolUtil.listFromString(File.getContent("modsList.txt")); + var list:Array = CoolUtil.listFromString(File.getContent(Paths.txt("modsList"))); var foundTheTop = false; for (i in list) { @@ -258,7 +260,7 @@ class WeekData { if (dat[1] == "1" && !foundTheTop) { foundTheTop = true; - Paths.currentModDirectory = dat[0]; + Mods.currentModDirectory = dat[0]; } } } diff --git a/source/game/backend/plugins/CMDEnablingPlugin.hx b/source/game/backend/plugins/CMDEnablingPlugin.hx deleted file mode 100644 index 4561271..0000000 --- a/source/game/backend/plugins/CMDEnablingPlugin.hx +++ /dev/null @@ -1,84 +0,0 @@ -package game.backend.plugins; - -import flixel.FlxG; -import flixel.FlxBasic; -import flixel.util.FlxSignal; -import openfl.text.TextField; -import openfl.text.TextFormat; -import openfl.display.Sprite; - -/** - * Plugin that allows easy command line access w/o compiling - * - * press F12 to open system command line - */ -class CMDEnablingPlugin extends FlxBasic -{ - static var instance:Null = null; - - var console:Sprite; - var consoleText:TextField; - var consoleVisible:Bool = false; - var consoleWidth:Int = 400; - - public static function init() - { - if (instance == null) FlxG.plugins.addPlugin(instance = new CMDEnablingPlugin()); - } - - public function new() - { - super(); - this.visible = false; - createConsole(); - hijackTrace(); - } - - @:noCompletion - function createConsole():Void - { - console = new Sprite(); - console.graphics.beginFill(0x000000, 0.7); - console.graphics.drawRect(0, 0, FlxG.width, 200); - console.graphics.endFill(); - - console.x = FlxG.width - consoleWidth; - - consoleText = new TextField(); - consoleText.x = 5; - consoleText.width = consoleWidth - 10; - consoleText.height = 200; - consoleText.multiline = true; - consoleText.wordWrap = true; - consoleText.defaultTextFormat = new TextFormat("Courier New", 13, 0xFFFFFF); - consoleText.text = "Debug Console - Press F12 to hide/show\n\n"; - - console.addChild(consoleText); - console.visible = false; - FlxG.stage.addChild(console); - } - - @:noCompletion - private function hijackTrace():Void - { - var originalTrace = haxe.Log.trace; - - haxe.Log.trace = (v:Dynamic, ?infos:haxe.PosInfos) -> { - var message = '${infos.fileName}:${infos.lineNumber} - $v\n'; - consoleText.text += message; - consoleText.scrollV = consoleText.maxScrollV; - originalTrace(v, infos); - }; - } - - override function update(elapsed:Float) - { - super.update(elapsed); - - if (FlxG.keys.justPressed.F12) - { - consoleVisible = !consoleVisible; - console.visible = consoleVisible; - } - } -} \ No newline at end of file diff --git a/source/game/backend/plugins/DebugConsolePlugin.hx b/source/game/backend/plugins/DebugConsolePlugin.hx new file mode 100644 index 0000000..1151c61 --- /dev/null +++ b/source/game/backend/plugins/DebugConsolePlugin.hx @@ -0,0 +1,968 @@ +package game.backend.plugins; + +import flixel.FlxG; +import flixel.FlxBasic; +import flixel.FlxState; +import flixel.FlxSprite; +import flixel.group.FlxGroup; +import flixel.tweens.FlxTween; +import flixel.tweens.FlxEase; + +import openfl.text.TextField; +import openfl.text.TextFormat; +import openfl.display.Sprite; +import openfl.events.KeyboardEvent; +import openfl.events.TextEvent; +import openfl.events.MouseEvent; +import openfl.ui.Keyboard; +import openfl.desktop.Clipboard; +import openfl.desktop.ClipboardFormats; + +import haxe.Json; + +import game.scripting.FunkinHScript; + +using StringTools; + +class DebugConsolePlugin extends FlxBasic +{ + static var instance:Null = null; + + var console:Sprite; + var consoleText:TextField; + var consoleVisible:Bool = false; + var consoleWidth:Int = 700; + var consoleHeight:Int = 400; + + var commandHistory:Array = []; + var historyIndex:Int = 0; + var currentInput:String = ""; + var prompt:String = "> "; + + var cursorVisible:Bool = true; + var cursorTimer:Float = 0; + var cursorBlinkRate:Float = 0.5; // seconds + + var cursorPosition:Int = 0; + + var wasMouseVisible:Bool = true; + + var hscript:FunkinHScript; + var hscriptMode:Bool = false; + + var lastDisplayText:String = ""; + + var preventTextSelection:Bool = false; + + var isDragging:Bool = false; + var dragOffsetX:Float = 0; + var dragOffsetY:Float = 0; + + var titleBar:Sprite; + + var autoScrollToBottom:Bool = true; + var userScrolledManually:Bool = false; + + public static function init() + { + if (instance == null) FlxG.plugins.addPlugin(instance = new DebugConsolePlugin()); + } + + public function new() + { + super(); + this.visible = false; + initializeHScript(); + createConsole(); + hijackTrace(); + loadConsolePosition(); + } + + function initializeHScript():Void + { + hscript = new FunkinHScript("", FlxG.state, true); + hscript.scriptName = "Debug Console[HS]"; + + hscript.set("console", this); + hscript.set("print", (v:Dynamic) -> addOutput(Std.string(v), 0xFFFFFF)); + hscript.set("clearConsole", () -> { + consoleText.text = "Debug Console (F12 to toggle)\nDrag title bar to move | Shift+Enter for new line\n" + prompt; + lastDisplayText = consoleText.text; + userScrolledManually = false; + autoScrollToBottom = true; + }); + } + + function createConsole():Void + { + console = new Sprite(); + + console.graphics.beginFill(0x0D0D0D, 0.95); + console.graphics.drawRoundRect(0, 0, consoleWidth, consoleHeight, 10, 10); + console.graphics.endFill(); + + console.graphics.lineStyle(2, 0x444444, 0.8); + console.graphics.drawRoundRect(0, 0, consoleWidth, consoleHeight, 10, 10); + + titleBar = new Sprite(); + titleBar.graphics.beginFill(0x333333, 0.8); + titleBar.graphics.drawRoundRect(0, 0, consoleWidth, 25, 10, 10); + titleBar.graphics.endFill(); + + var titleText = new TextField(); + titleText.x = 10; + titleText.y = 5; + titleText.width = consoleWidth - 20; + titleText.height = 20; + titleText.defaultTextFormat = new TextFormat("Consolas", 12, 0xE0E0E0); + titleText.text = "Debug Console (Drag to move)"; + titleText.selectable = false; + titleText.mouseEnabled = false; + titleBar.addChild(titleText); + + console.addChild(titleBar); + + console.x = (FlxG.stage.stageWidth - consoleWidth) / 2; + console.y = 20; + + consoleText = new TextField(); + consoleText.x = 12; + consoleText.y = 30; + consoleText.width = consoleWidth - 24; + consoleText.height = consoleHeight - 40; + consoleText.multiline = true; + consoleText.wordWrap = true; + consoleText.defaultTextFormat = new TextFormat("Consolas", 14, 0xE0E0E0); + consoleText.background = false; + consoleText.border = false; + + consoleText.type = openfl.text.TextFieldType.DYNAMIC; + consoleText.selectable = true; + consoleText.mouseEnabled = true; + consoleText.tabEnabled = false; + + consoleText.text = "Debug Console (F12 to toggle)\nDrag title bar to move | Shift+Enter for new line\n" + prompt; + lastDisplayText = consoleText.text; + + console.addChild(consoleText); + console.visible = false; + + console.tabEnabled = true; + console.focusRect = false; + + FlxG.stage.addChild(console); + + FlxG.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); + FlxG.stage.addEventListener(TextEvent.TEXT_INPUT, onTextInput); + + consoleText.addEventListener(MouseEvent.MOUSE_DOWN, onTextMouseDown); + consoleText.addEventListener(MouseEvent.MOUSE_UP, onTextMouseUp); + + console.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + + titleBar.addEventListener(MouseEvent.MOUSE_DOWN, onTitleBarMouseDown); + + FlxG.stage.addEventListener(MouseEvent.MOUSE_UP, onStageMouseUp); + FlxG.stage.addEventListener(MouseEvent.MOUSE_MOVE, onStageMouseMove); + } + + private function onTitleBarMouseDown(event:MouseEvent):Void + { + isDragging = true; + + dragOffsetX = event.stageX - console.x; + dragOffsetY = event.stageY - console.y; + event.stopPropagation(); + } + + private function onStageMouseUp(event:MouseEvent):Void + { + if (isDragging) { + isDragging = false; + saveConsolePosition(); + } + } + + private function onStageMouseMove(event:MouseEvent):Void + { + if (isDragging && consoleVisible) { + console.x = event.stageX - dragOffsetX; + console.y = event.stageY - dragOffsetY; + + var stageWidth = FlxG.stage.stageWidth; + var stageHeight = FlxG.stage.stageHeight; + + if (console.x < 0) console.x = 0; + if (console.y < 0) console.y = 0; + if (console.x + consoleWidth > stageWidth) console.x = stageWidth - consoleWidth; + if (console.y + consoleHeight > stageHeight) console.y = stageHeight - consoleHeight; + } + } + + private function saveConsolePosition():Void + { + if (FlxG.save.data != null) { + FlxG.save.data.consoleX = console.x; + FlxG.save.data.consoleY = console.y; + FlxG.save.flush(); + } + } + + private function loadConsolePosition():Void + { + if (FlxG.save.data != null) { + if (FlxG.save.data.consoleX != null) console.x = FlxG.save.data.consoleX; + if (FlxG.save.data.consoleY != null) console.y = FlxG.save.data.consoleY; + + var stageWidth = FlxG.stage.stageWidth; + var stageHeight = FlxG.stage.stageHeight; + + if (console.x < 0) console.x = 0; + if (console.y < 0) console.y = 0; + if (console.x + consoleWidth > stageWidth) console.x = stageWidth - consoleWidth; + if (console.y + consoleHeight > stageHeight) console.y = stageHeight - consoleHeight; + } + } + + private function onTextMouseDown(event:MouseEvent):Void + { + var lines = consoleText.text.split("\n"); + var clickY = event.localY; + var lineHeight = consoleText.textHeight / lines.length; + var clickedLine = Math.floor(clickY / lineHeight); + + if (clickedLine >= lines.length - 1) { + preventTextSelection = true; + event.stopPropagation(); + + FlxG.stage.focus = console; + } else { + preventTextSelection = false; + } + } + + private function onTextMouseUp(event:MouseEvent) + { + if (preventTextSelection) { + consoleText.setSelection(consoleText.length, consoleText.length); + event.stopPropagation(); + } + } + + private function onMouseWheel(event:MouseEvent):Void + { + if (!consoleVisible) return; + + var scrollAmount = event.delta > 0 ? -1 : 1; + consoleText.scrollV += scrollAmount * 3; + + if (consoleText.scrollV < 1) consoleText.scrollV = 1; + if (consoleText.scrollV > consoleText.maxScrollV) consoleText.scrollV = consoleText.maxScrollV; + + userScrolledManually = true; + autoScrollToBottom = (consoleText.scrollV == consoleText.maxScrollV); + + event.stopPropagation(); + } + + private function hijackTrace():Void + { + var originalTrace = haxe.Log.trace; + + haxe.Log.trace = (v:Dynamic, ?infos:haxe.PosInfos) -> { + var message = 'TRACE: $v'; + addOutput(message, 0x8888FF); + originalTrace(v, infos); + }; + } + + private function onKeyDown(event:KeyboardEvent):Void + { + if (!consoleVisible) return; + + switch(event.keyCode) + { + case Keyboard.ENTER: + if (event.shiftKey) { + insertTextAtCursor("\n"); + event.preventDefault(); + } else { + if (StringTools.trim(currentInput) == "") { + event.preventDefault(); + return; + } + executeCommand(); + resetCursor(); + } + + case Keyboard.BACKSPACE: + if (currentInput.length > 0 && cursorPosition > 0) { + currentInput = currentInput.substring(0, cursorPosition - 1) + currentInput.substring(cursorPosition); + cursorPosition--; + updateDisplay(); + resetCursor(); + } + event.preventDefault(); + + case Keyboard.LEFT: + if (cursorPosition > 0) { + cursorPosition--; + updateDisplay(); + resetCursor(); + } + event.preventDefault(); + + case Keyboard.RIGHT: + if (cursorPosition < currentInput.length) { + cursorPosition++; + updateDisplay(); + resetCursor(); + } + event.preventDefault(); + + case Keyboard.UP: + navigateHistory(-1); + event.preventDefault(); + resetCursor(); + + case Keyboard.DOWN: + navigateHistory(1); + event.preventDefault(); + resetCursor(); + + case Keyboard.HOME: + cursorPosition = 0; + updateDisplay(); + resetCursor(); + event.preventDefault(); + + case Keyboard.END: + cursorPosition = currentInput.length; + updateDisplay(); + resetCursor(); + event.preventDefault(); + + case Keyboard.TAB: + autoComplete(); + event.preventDefault(); + resetCursor(); + + case Keyboard.ESCAPE: + hideConsole(); + event.preventDefault(); + + case Keyboard.F10: + hscriptMode = !hscriptMode; + addOutput("HScript mode: " + (hscriptMode ? "ON" : "OFF"), hscriptMode ? 0x88FF88 : 0xFF8888); + event.preventDefault(); + resetCursor(); + + case Keyboard.V: + if (event.ctrlKey || event.commandKey) { + pasteFromClipboard(); + event.preventDefault(); + } + + default: + if (FlxG.stage.focus == console) { + event.preventDefault(); + } + } + } + + private function insertTextAtCursor(text:String):Void + { + currentInput = currentInput.substring(0, cursorPosition) + text + currentInput.substring(cursorPosition); + cursorPosition += text.length; + updateDisplay(); + resetCursor(); + } + + private function pasteFromClipboard():Void + { + try { + #if (sys || desktop) + var clipboardText = Clipboard.generalClipboard.getData(ClipboardFormats.TEXT_FORMAT); + if (clipboardText != null && Std.isOfType(clipboardText, String)) { + var text:String = cast clipboardText; + insertTextAtCursor(text); + } + #else + addOutput("Clipboard access not available on this platform", 0xFFFF88); + #end + } catch (e:Dynamic) { + addOutput('Clipboard error: $e', 0xFF8888); + } + } + + private function onTextInput(event:TextEvent):Void + { + if (!consoleVisible) return; + + var char = event.text; + if (char == null || char.length == 0) return; + + insertTextAtCursor(char); + event.preventDefault(); + } + + private function resetCursor():Void + { + cursorVisible = true; + cursorTimer = 0; + } + + private function navigateHistory(direction:Int):Void + { + if (commandHistory.length == 0) return; + + historyIndex += direction; + historyIndex = Std.int(Math.max(0, Math.min(commandHistory.length, historyIndex))); + + if (historyIndex == commandHistory.length) + { + currentInput = ""; + cursorPosition = 0; + } + else + { + currentInput = commandHistory[historyIndex]; + cursorPosition = currentInput.length; + } + + updateDisplay(); + } + + private function autoComplete():Void + { + if (currentInput == "") return; + + var suggestions = getAutoCompleteSuggestions(currentInput); + + if (suggestions.length == 1) + { + currentInput = suggestions[0]; + cursorPosition = currentInput.length; + updateDisplay(); + } + else if (suggestions.length > 1) + { + addOutput("Possible completions: " + suggestions.join(", "), 0xFFFF88); + } + } + + function getAutoCompleteSuggestions(input:String):Array + { + var suggestions:Array = []; + + var commands = ["help", "clear", "objects", "fields", "call", "set", "new", "cursor", "hscript"]; + for (cmd in commands) + { + if (cmd.toLowerCase().startsWith(input.toLowerCase())) + { + suggestions.push(cmd); + } + } + + return suggestions; + } + + private function updateDisplay():Void + { + var wasAtBottom = consoleText.scrollV == consoleText.maxScrollV; + + var lines = lastDisplayText.split("\n"); + + var lastPromptIndex = -1; + for (i in 0...lines.length) { + if (lines[i].startsWith(prompt) || lines[i].startsWith("[HS] " + prompt)) { + lastPromptIndex = i; + } + } + + var outputLines = lastPromptIndex >= 0 ? lines.slice(0, lastPromptIndex) : lines; + + var modePrefix = hscriptMode ? "[HS] " : ""; + var inputBeforeCursor = modePrefix + prompt + currentInput.substring(0, cursorPosition); + var inputAfterCursor = currentInput.substring(cursorPosition); + var inputLine = inputBeforeCursor + (cursorVisible ? "|" : "") + inputAfterCursor; + + var newText = outputLines.join("\n") + "\n" + inputLine; + consoleText.text = newText; + lastDisplayText = newText; + + if (autoScrollToBottom && wasAtBottom) { + consoleText.scrollV = consoleText.maxScrollV; + } + } + + private function addOutput(message:String, color:Int = 0xE0E0E0):Void + { + var wasAtBottom = consoleText.scrollV == consoleText.maxScrollV; + + var lines = lastDisplayText.split("\n"); + + var lastPromptIndex = -1; + for (i in 0...lines.length) { + if (lines[i].startsWith(prompt) || lines[i].startsWith("[HS] " + prompt)) { + lastPromptIndex = i; + } + } + + var outputLines = lastPromptIndex >= 0 ? lines.slice(0, lastPromptIndex) : lines; + + var modePrefix = hscriptMode ? "[HS] " : ""; + var inputBeforeCursor = modePrefix + prompt + currentInput.substring(0, cursorPosition); + var inputAfterCursor = currentInput.substring(cursorPosition); + var inputLine = inputBeforeCursor + (cursorVisible ? "|" : "") + inputAfterCursor; + + var formattedText = outputLines.join("\n") + "\n" + message + "\n" + inputLine; + consoleText.text = formattedText; + lastDisplayText = formattedText; + + if (autoScrollToBottom && wasAtBottom) { + consoleText.scrollV = consoleText.maxScrollV; + } + } + + private function executeCommand():Void + { + var command:String = StringTools.trim(currentInput); + + if (command == "") { + return; + } + + commandHistory.push(command); + historyIndex = commandHistory.length; + + currentInput = ""; + cursorPosition = 0; + + addOutput((hscriptMode ? "[HS] " : "") + prompt + command, 0x88FF88); + + userScrolledManually = false; + autoScrollToBottom = true; + + if (hscriptMode) + { + executeHScript(command); + } + else + { + processCommand(command); + } + + updateDisplay(); + } + + private function executeHScript(code:String):Void + { + try + { + var result = hscript.executeString(code); + if (result != null) + { + addOutput("Result: " + Std.string(result), 0x88FFFF); + } + } + catch (e:Dynamic) + { + addOutput('HScript Error: $e', 0xFF8888); + } + } + + private function processCommand(command:String):Void + { + var args:Array = command.split(" ").filter(arg -> arg != ""); + var cmd:String = args[0].toLowerCase(); + + switch(cmd) + { + case "help": + showHelp(); + + case "clear", "cls": + consoleText.text = "Debug Console (F12 to toggle)\nDrag title bar to move | Shift+Enter for new line\n" + prompt; + lastDisplayText = consoleText.text; + userScrolledManually = false; + autoScrollToBottom = true; + + case "objects", "obj": + listAvailableObjects(); + + case "fields", "props": + if (args.length > 1) + { + inspectObject(args[1]); + } + else + { + addOutput("Usage: fields ", 0xFF8888); + } + + case "call", "method": + if (args.length > 2) + { + callMethod(args[1], args[2], args.slice(3)); + } + else + { + addOutput("Usage: call [args...]", 0xFF8888); + } + + case "set": + if (args.length > 3) + { + setProperty(args[1], args[2], args.slice(3).join(" ")); + } + else + { + addOutput("Usage: set ", 0xFF8888); + } + + case "new", "create": + if (args.length > 1) + { + createInstance(args[1], args.slice(2)); + } + else + { + addOutput("Usage: new [args...]", 0xFF8888); + } + + case "cursor": + if (args.length > 1) + { + if (args[1] == "show") + { + FlxG.mouse.visible = true; + addOutput("Cursor shown", 0x88FF88); + } + else if (args[1] == "hide") + { + FlxG.mouse.visible = false; + addOutput("Cursor hidden", 0x88FF88); + } + else + { + addOutput("Usage: cursor [show|hide]", 0xFF8888); + } + } + else + { + FlxG.mouse.visible = !FlxG.mouse.visible; + addOutput("Cursor " + (FlxG.mouse.visible ? "shown" : "hidden"), 0x88FF88); + } + + case "hscript": + hscriptMode = !hscriptMode; + addOutput("HScript mode: " + (hscriptMode ? "ON" : "OFF"), hscriptMode ? 0x88FF88 : 0xFF8888); + + default: + addOutput("Unknown command: '" + cmd + "'. Type 'help' for available commands.", 0xFF8888); + } + } + + private function showHelp():Void + { + var helpSections:Map> = [ + "Available commands:" => [ + "help - show this message", + "clear - clear console", + "objects - list available runtime objects", + "fields - show object fields/properties", + "set - set property value", + "call [args] - call method", + "new [args] - create new instance", + "cursor [show|hide] - show/hide mouse cursor", + "hscript - toggle HScript mode" + ], + "Navigation:" => [ + "Arrow keys - move cursor", + "Home/End - move to start/end of line", + "Shift+Enter - new line (in HScript mode)", + "Drag title bar - move console" + ], + "Examples:" => [ + "FlxG.camera.zoom", + "FlxG.fullscreen = true", + "state.members.length", + "new flixel.FlxSprite", + "cursor show" + ], + "HScript Examples:" => [ + "> FlxG.camera.zoom = 1.5", + "> for (i in 0...10) trace(i)", + "> var x = 10; x * 2" + ] + ]; + + for (sectionTitle => sectionContent in helpSections) { + addOutput(sectionTitle, 0x88FFFF); + for (line in sectionContent) { + addOutput(" " + line, 0x88FFFF); + } + } + } + + function listAvailableObjects():Void + { + var variables = []; + try + { + var ruleField = Reflect.field(hscript, "rule"); + if (ruleField != null) + { + var variablesField = Reflect.field(ruleField, "variables"); + if (variablesField != null) + { + var map:Map = cast variablesField; + for (key in map.keys()) + { + variables.push(key); + } + } + } + } + catch (e:Dynamic) {} + + addOutput("Available objects:", 0xFFFF88); + for (name in variables) + { + try + { + var value = hscript.get(name); + var typeName = Type.getClassName(Type.getClass(value)) ?? "Dynamic"; + addOutput(' $name - $typeName', 0xFFFF88); + } + catch (e:Dynamic) + { + addOutput(' $name - ', 0xFFFF88); + } + } + } + + private function inspectObject(objectName:String):Void + { + try + { + var obj = hscript.get(objectName); + if (obj == null) + { + addOutput('Object "$objectName" not found', 0xFF8888); + return; + } + + addOutput('Fields and properties of $objectName:', 0x88FF88); + + var fields = Type.getInstanceFields(Type.getClass(obj)); + for (field in fields) + { + if (!field.startsWith("_")) + { + try + { + var value = Reflect.field(obj, field); + var valueStr = value == null ? "null" : Std.string(value); + if (valueStr.length > 50) valueStr = valueStr.substr(0, 47) + "..."; + addOutput(' $field = $valueStr', 0x88FF88); + } + catch (e:Dynamic) + { + addOutput(' $field = ', 0xFF8888); + } + } + } + } + catch (e:Dynamic) + { + addOutput('Error inspecting object: $e', 0xFF8888); + } + } + + private function createInstance(className:String, args:Array):Void + { + try + { + var clazz:Class = Type.resolveClass(className); + if (clazz == null) + { + addOutput('Class "$className" not found', 0xFF8888); + return; + } + + var parsedArgs:Array = []; + for (arg in args) + { + parsedArgs.push(parseArgument(arg)); + } + + var instance:Dynamic = Type.createInstance(clazz, parsedArgs); + + var instanceName = 'instance_${Std.int(Math.random() * 10000)}'; + hscript.set(instanceName, instance); + + addOutput('Created instance: $instanceName of $className', 0x88FF88); + } + catch (e:Dynamic) + { + addOutput('Error creating instance: $e', 0xFF8888); + } + } + + private function parseArgument(arg:String):Dynamic + { + var intVal = Std.parseInt(arg); + if (intVal != null) return intVal; + + var floatVal = Std.parseFloat(arg); + if (!Math.isNaN(floatVal) && floatVal != 0) return floatVal; + + if (arg == "true") return true; + if (arg == "false") return false; + if (arg == "null") return null; + + return arg; + } + + private function setProperty(objectName:String, propertyPath:String, valueExpr:String):Void + { + try + { + var obj = hscript.get(objectName); + if (obj == null) + { + addOutput('Object "$objectName" not found', 0xFF8888); + return; + } + + var value:Dynamic = parseArgument(valueExpr); + + var parts = propertyPath.split("."); + var targetObj = obj; + + for (i in 0...parts.length - 1) + { + targetObj = Reflect.field(targetObj, parts[i]); + if (targetObj == null) + { + addOutput('Property "${parts.slice(0, i + 1).join(".")}" not found', 0xFF8888); + return; + } + } + + var finalProp = parts[parts.length - 1]; + Reflect.setField(targetObj, finalProp, value); + addOutput('Set $objectName.$propertyPath = $value', 0x88FF88); + } + catch (e:Dynamic) + { + addOutput('Error: $e', 0xFF8888); + } + } + + private function callMethod(objectName:String, methodPath:String, args:Array):Void + { + try + { + var obj = hscript.get(objectName); + if (obj == null) + { + addOutput('Object "$objectName" not found', 0xFF8888); + return; + } + + var parsedArgs:Array = []; + for (arg in args) + { + parsedArgs.push(parseArgument(arg)); + } + + var result = Reflect.callMethod(obj, Reflect.field(obj, methodPath), parsedArgs); + addOutput('Result: $result', 0x88FF88); + } + catch (e:Dynamic) + { + addOutput('Error calling $objectName.$methodPath: $e', 0xFF8888); + } + } + + private function showConsole():Void + { + consoleVisible = true; + console.visible = true; + + wasMouseVisible = FlxG.mouse.visible; + FlxG.mouse.visible = true; + + FlxG.stage.focus = console; + currentInput = ""; + cursorPosition = 0; + + userScrolledManually = false; + autoScrollToBottom = true; + + resetCursor(); + updateDisplay(); + } + + private function hideConsole():Void + { + consoleVisible = false; + console.visible = false; + + FlxG.mouse.visible = wasMouseVisible; + + FlxG.stage.focus = FlxG.stage; + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (FlxG.keys.justPressed.F12) + { + if (consoleVisible) + { + hideConsole(); + } + else + { + showConsole(); + } + } + + if (consoleVisible) + { + cursorTimer += elapsed; + if (cursorTimer >= cursorBlinkRate) + { + cursorTimer = 0; + cursorVisible = !cursorVisible; + updateDisplay(); + } + + FlxG.keys.reset(); + } + } + + override public function destroy():Void + { + FlxG.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); + FlxG.stage.removeEventListener(TextEvent.TEXT_INPUT, onTextInput); + consoleText.removeEventListener(MouseEvent.MOUSE_DOWN, onTextMouseDown); + consoleText.removeEventListener(MouseEvent.MOUSE_UP, onTextMouseUp); + console.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + + titleBar.removeEventListener(MouseEvent.MOUSE_DOWN, onTitleBarMouseDown); + + FlxG.stage.removeEventListener(MouseEvent.MOUSE_UP, onStageMouseUp); + FlxG.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onStageMouseMove); + + console?.parent?.removeChild(console); + hscript?.stop(); + + super.destroy(); + } +} \ No newline at end of file diff --git a/source/game/backend/plugins/FPSCounterPlugin.hx b/source/game/backend/plugins/FPSCounterPlugin.hx new file mode 100644 index 0000000..fddacb8 --- /dev/null +++ b/source/game/backend/plugins/FPSCounterPlugin.hx @@ -0,0 +1,820 @@ +package game.backend.plugins; + +import haxe.Timer; + +import openfl.Lib; +import openfl.events.Event; +import openfl.events.KeyboardEvent; +import openfl.ui.Keyboard; +import openfl.text.TextField; +import openfl.text.TextFormat; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.geom.Matrix; +import openfl.geom.Rectangle; +import openfl.system.System; +import openfl.utils.Assets; + +import flixel.FlxG; + +import game.backend.utils.MemoryUtil; + +#if gl_stats +import openfl.display._internal.stats.Context3DStats; +import openfl.display._internal.stats.DrawCallContext; +#end + +#if sys +import sys.FileSystem; +import sys.io.File; +#end + +@:allow(Init) +class FPSCounterPlugin extends Bitmap +{ + public var currentFPS(default, null):Int; + public var currentMemory(get, never):Float; + public var showDebugInfo:Bool = false; + + public var strokeSize:Int = 1; + public var strokeColor:Int = 0xFF000000; + public var fillColor:Int = 0xFFFFFFFF; + public var fontSize:Int = 11; + public var fontCustom = "_sans"; + + // Warning system + public var performanceWarnings(default, null):Array = []; + public var warningLevel(default, null):Int = 0; // 0 = normal, 1 = warning, 2 = dangerous, 3 = critical + private var lastWarningUpdate:Float = 0; + private var warningUpdateInterval:Float = 2.0; + + // Warning thresholds + private var warningThresholds = { + fpsLow: 0.8, // 80% of target FPS + fpsVeryLow: 0.6, // 60% of target FPS + fpsCritical: 0.4, // 40% of target FPS + memoryHigh: 2e9, // 2 GB + memoryVeryHigh: 3e9, // 3 GB + memoryCritical: 4e9, // 4 GB + frameTimeHigh: 25.0, // 25ms (40 FPS) + frameTimeVeryHigh: 40.0, // 40ms (25 FPS) + systemMemoryLow: 0.21e9 // 200 MB free RAM + }; + + private var cacheCount:Int = 0; + private var currentTime:Float = 0; + private var times:Array = []; + private var lastFrameTime:Float = 0; + + #if (openfl >= "9.4.0") + private var peakMemory:Float = 0; + #else + private var peakMemory:UInt = 0; + #end + + private var graphWidth:Int = 135; + private var graphHeight:Int = 40; + private var graphHistory:Array = []; + private var maxGraphPoints:Int = 135; + private var frameTimes:Array = []; + private var maxFrameTimeHistory:Int = 20; + + @:unreflective + private final dataTexts = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + private var minFPS:Int = 9999; + private var maxFPS:Int = 0; + private var avgFPS:Float = 0; + private var fpsSamples:Int = 0; + private var totalFPS:Float = 0; + private var lastStatReset:Float = 0; + + private var logEnabled:Bool = false; + private var logFile:String = "fps_log.txt"; + private var logTimer:Float = 0; + private var logInterval:Float = 1.0; + + private var counterVisible:Bool = true; + private var keyCooldown:Float = 0; + private var keyCooldownTime:Float = 0.2; + + private var cachedBMD:BitmapData = null; + private var lastUpdateTime:Float = 0; + private var updateInterval:Float = 0.033; + private var memoryReadings:Array = []; + private var maxMemoryReadings:Int = 10; + private var smoothMemory:Float = 0; + private var availableSystemMemory:Float = 0; + private var lastMemoryUpdate:Float = 0; + private var memoryUpdateInterval:Float = 1.0; + + private var graphDirty:Bool = true; + private var lastGraphUpdate:Float = 0; + private var graphUpdateInterval:Float = 0.033; + + private var lastOutput:String = ""; + private var lastFPS:Int = -1; + private var lastMem:Float = -1; + + // Performance analysis statistics + private var lowFPSFrames:Int = 0; + private var totalFrames:Int = 0; + private var performanceScore:Float = 0; + private var stabilityIssues:Int = 0; + + public function new(x:Float = 10, y:Float = 10, ?fillColor:Int = 0xFFFFFFFF) + { + super(); + this.x = x; + this.y = y; + this.fillColor = fillColor; + lastFrameTime = Timer.stamp(); + lastStatReset = lastFrameTime; + lastUpdateTime = lastFrameTime; + lastMemoryUpdate = lastFrameTime; + lastWarningUpdate = lastFrameTime; + + for (i in 0...maxMemoryReadings) { + memoryReadings.push(0); + } + + if (logEnabled) { + initLogFile(); + } + + Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPress); + addEventListener(Event.ENTER_FRAME, onEnterFrame); + } + + private function onKeyPress(event:KeyboardEvent):Void + { + if (keyCooldown > 0) return; + + switch (event.keyCode) { + case Keyboard.F2: + resetStats(); + keyCooldown = keyCooldownTime; + case Keyboard.F3: + toggleDebugInfo(); + keyCooldown = keyCooldownTime; + } + } + + inline function get_currentMemory():Float + { + return smoothMemory; + } + + private function onEnterFrame(event:Event):Void + { + var currentTimeStamp = Timer.stamp(); + var deltaTime = currentTimeStamp - lastFrameTime; + + if (keyCooldown > 0) { + keyCooldown -= deltaTime; + } + + var currentTime = currentTimeStamp * 1000; + times.push(currentTime); + + while (times[0] < currentTime - 1000) + times.shift(); + + var currentCount = times.length < FlxG.updateFramerate ? times.length : FlxG.updateFramerate; + currentFPS = ClientPrefs.unlimitedFPS ? times.length : Math.round(currentCount); + + if (currentTimeStamp - lastUpdateTime >= updateInterval) { + updateMemoryStats(currentTimeStamp); + updateStatistics(); + updateFrameTiming(deltaTime); + + if (currentTimeStamp - lastWarningUpdate >= warningUpdateInterval) { + updatePerformanceWarnings(); + lastWarningUpdate = currentTimeStamp; + } + + if (counterVisible) { + var output = buildOutputString(); + if (output != lastOutput || currentFPS != lastFPS || smoothMemory != lastMem) { + updateText(output); + lastOutput = output; + lastFPS = currentFPS; + lastMem = smoothMemory; + } + } + + lastUpdateTime = currentTimeStamp; + } + + if (currentTimeStamp - lastGraphUpdate >= graphUpdateInterval) { + updateGraphs(); + lastGraphUpdate = currentTimeStamp; + } + + if (logEnabled) { + updateLogging(currentTimeStamp); + } + + cacheCount = currentCount; + lastFrameTime = currentTimeStamp; + } + + private function updatePerformanceWarnings():Void + { + performanceWarnings = []; + warningLevel = 0; + + if (!ClientPrefs.unlimitedFPS) { + var targetFPS = ClientPrefs.vsync ? getDisplayRefreshRate() : ClientPrefs.framerate; + + if (currentFPS < targetFPS * warningThresholds.fpsCritical) { + performanceWarnings.push("CRITICAL: Very low FPS!"); + warningLevel = 3; + } else if (currentFPS < targetFPS * warningThresholds.fpsVeryLow) { + performanceWarnings.push("WARNING: Low FPS"); + if (2 > warningLevel) warningLevel = 2; + } else if (currentFPS < targetFPS * warningThresholds.fpsLow) { + performanceWarnings.push("Notice: FPS below normal"); + if (1 > warningLevel) warningLevel = 1; + } + + if (frameTimes.length > 0) { + var frameStats = getFrameTimingStats(); + if (frameStats.avg > warningThresholds.frameTimeVeryHigh) { + performanceWarnings.push("WARNING: High frame time"); + if (2 > warningLevel) warningLevel = 2; + } else if (frameStats.avg > warningThresholds.frameTimeHigh) { + performanceWarnings.push("Notice: Elevated frame time"); + if (1 > warningLevel) warningLevel = 1; + } + } + + if (graphHistory.length > 10) { + var stability = calculateFPSStability(); + if (stability < 0.7) { + performanceWarnings.push("Notice: Unstable FPS"); + if (1 > warningLevel) warningLevel = 1; + } + } + } + + if (smoothMemory > warningThresholds.memoryCritical) { + performanceWarnings.push("CRITICAL: Critical memory usage!"); + warningLevel = 3; + } else if (smoothMemory > warningThresholds.memoryVeryHigh) { + performanceWarnings.push("WARNING: High memory usage"); + if (2 > warningLevel) warningLevel = 2; + } else if (smoothMemory > warningThresholds.memoryHigh) { + performanceWarnings.push("Notice: Elevated memory usage"); + if (1 > warningLevel) warningLevel = 1; + } + + if (availableSystemMemory > 0 && availableSystemMemory < warningThresholds.systemMemoryLow) { + performanceWarnings.push("WARNING: Low free system memory"); + if (2 > warningLevel) warningLevel = 2; + } + } + + private function calculateFPSStability():Float + { + if (graphHistory.length < 10) return 1.0; + + var targetFPS = ClientPrefs.vsync ? getDisplayRefreshRate() : ClientPrefs.framerate; + var averageFPS = 0.0; + var variance = 0.0; + + for (fps in graphHistory) + averageFPS += fps; + + averageFPS /= graphHistory.length; + + for (fps in graphHistory) { + variance += (fps - averageFPS) * (fps - averageFPS); + } + variance /= graphHistory.length; + + var stability = 1.0 - (variance / (targetFPS * targetFPS)); + return Math.max(0, Math.min(1.0, stability)); + } + + private function updateMemoryStats(currentTime:Float):Void + { + var currentMem = MemoryUtil.getAccurateRamUsage(); + + if (currentMem < 0) { + #if (openfl >= "9.4.0") + currentMem = System.totalMemoryNumber; + #else + currentMem = System.totalMemory; + #end + } + + memoryReadings.push(currentMem); + if (memoryReadings.length > maxMemoryReadings) { + memoryReadings.shift(); + } + + var total:Float = 0; + for (reading in memoryReadings) { + total += reading; + } + smoothMemory = total / memoryReadings.length; + + if (currentTime - lastMemoryUpdate >= memoryUpdateInterval) { + availableSystemMemory = MemoryUtil.getAvailableSystemMemory(); + lastMemoryUpdate = currentTime; + } + + if (currentMem > peakMemory) { + peakMemory = currentMem; + } + } + + private function updateStatistics():Void + { + if (currentFPS < minFPS && currentFPS > 0) minFPS = currentFPS; + if (currentFPS > maxFPS) maxFPS = currentFPS; + + fpsSamples++; + totalFPS += currentFPS; + avgFPS = totalFPS / fpsSamples; + + if (!ClientPrefs.unlimitedFPS) { + var targetFPS = ClientPrefs.vsync ? getDisplayRefreshRate() : ClientPrefs.framerate; + if (currentFPS < targetFPS * 0.8) { + lowFPSFrames++; + } + } + totalFrames++; + + var fpsScore:Float; + if (ClientPrefs.unlimitedFPS) { + fpsScore = Math.min(currentFPS / 120.0 * 60.0, 60.0); + } else { + var targetFPS = ClientPrefs.vsync ? getDisplayRefreshRate() : ClientPrefs.framerate; + fpsScore = (currentFPS / targetFPS) * 60; + fpsScore = Math.min(fpsScore, 60); + } + + var memoryRatio = Math.min(smoothMemory / 2e9, 1.0); + var memoryScore = 40 * (1 - memoryRatio); + var stabilityFactor = 0; + + if (!ClientPrefs.unlimitedFPS && stabilityIssues > 10) { + stabilityFactor = -20; + } else if (!ClientPrefs.unlimitedFPS && stabilityIssues > 5) { + stabilityFactor = -10; + } else if (!ClientPrefs.unlimitedFPS && stabilityIssues > 2) { + stabilityFactor = -5; + } + + performanceScore = fpsScore + memoryScore + stabilityFactor; + performanceScore = Math.max(0, Math.min(100, performanceScore)); + + var currentTime = Timer.stamp(); + if (currentTime - lastStatReset > 30) { + resetStatistics(); + } + } + + private function resetStatistics():Void + { + minFPS = 9999; + maxFPS = 0; + avgFPS = 0; + fpsSamples = 0; + totalFPS = 0; + lowFPSFrames = 0; + totalFrames = 0; + performanceScore = 100; + stabilityIssues = 0; + lastStatReset = Timer.stamp(); + } + + private function updateGraphs():Void + { + graphHistory.push(currentFPS); + if (graphHistory.length > maxGraphPoints) { + graphHistory.shift(); + } + graphDirty = true; + } + + private function updateFrameTiming(deltaTime:Float):Void + { + var frameTime = deltaTime * 1000; + frameTimes.push(frameTime); + if (frameTimes.length > maxFrameTimeHistory) { + frameTimes.shift(); + } + + if (frameTimes.length >= 3) { + var lastFrame = frameTimes[frameTimes.length - 1]; + var prevFrame = frameTimes[frameTimes.length - 2]; + if (lastFrame > prevFrame * 2.0 && lastFrame > 33.0) { + stabilityIssues++; + } + } + } + + private function updateLogging(currentTime:Float):Void + { + if (currentTime - logTimer > logInterval) { + logData(); + logTimer = currentTime; + } + } + + private function buildOutputString():String + { + var fpsText:String; + if (ClientPrefs.unlimitedFPS) { + fpsText = 'FPS: $currentFPS (Unlimited)'; + } else { + var targetFPS = ClientPrefs.vsync ? getDisplayRefreshRate() : ClientPrefs.framerate; + fpsText = 'FPS: $currentFPS / $targetFPS'; + } + + var output = fpsText; + + var memoryText = "RAM: " + getSizeLabel(smoothMemory); + if (availableSystemMemory > 0) { + memoryText += " / Sys: " + getSizeLabel(availableSystemMemory); + } + output += "\n" + memoryText; + + output += '\nRAM Peak: ${getSizeLabel(peakMemory)}'; + + var performanceGrade = getPerformanceGrade(); + output += '\nPerformance: ${Math.round(performanceScore)}% (${performanceGrade})'; + + if (performanceWarnings.length > 0) { + output += "\n\n--- WARNINGS ---"; + for (warning in performanceWarnings) { + output += "\n!" + warning; + } + } + + if (showDebugInfo) { + output += "\n\n--- DETAILS ---"; + output += "\nMin/Max/Avg: " + minFPS + "/" + maxFPS + "/" + Math.round(avgFPS); + output += "\nVSync: " + (ClientPrefs.vsync ? "ON" : "OFF"); + output += "\nUnlimited FPS: " + (ClientPrefs.unlimitedFPS ? "ON" : "OFF"); + + output += "\nGC RAM: " + flixel.util.FlxStringUtil.formatBytes(#if (openfl >= "9.4.0") System.totalMemoryNumber #else currentMem = System.totalMemory #end); + + if (frameTimes.length > 0) { + var frameStats = getFrameTimingStats(); + output += "\nFrame: " + frameStats.avg + "ms (min: " + frameStats.min + "ms, max: " + frameStats.max + "ms)"; + } + + if (!ClientPrefs.unlimitedFPS) { + output += "\nStability: " + Math.round(calculateFPSStability() * 100) + "%"; + } + output += "\nProblem frames: " + stabilityIssues; + } + + switch (warningLevel) { + case 3: fillColor = 0xFFFF0000; // Red - critical + case 2: fillColor = 0xFFFFFF00; // Yellow - dangerous + case 1: fillColor = 0xFFFFA500; // Orange - warning + default: fillColor = 0xFFFFFFFF; // White - normal + } + + return output; + } + + private function getPerformanceGrade():String + { + if (performanceScore >= 90) return "Excellent"; + if (performanceScore >= 75) return "Good"; + if (performanceScore >= 60) return "Normal"; + if (performanceScore >= 40) return "Poor"; + return "Very poor"; + } + + private function getFrameTimingStats():{avg:Float, min:Float, max:Float} + { + var avg:Float = 0; + var min:Float = 1000; + var max:Float = 0; + + for (time in frameTimes) { + avg += time; + if (time < min) min = time; + if (time > max) max = time; + } + avg /= frameTimes.length; + + return { + avg: Math.round(avg * 100) / 100, + min: Math.round(min * 100) / 100, + max: Math.round(max * 100) / 100 + }; + } + + private static function getDisplayRefreshRate():Int + { + var window = Lib.application.window; + if (window?.display?.currentMode != null) + { + return window.display.currentMode.refreshRate; + } + return 60; + } + + private function getSizeLabel(num:Float):String + { + var size:Float = num; + var data = 0; + while (size > 1024 && data < dataTexts.length - 1) + { + data++; + size /= 1024; + } + + size = Math.round(size * 100) / 100; + if (data <= 2) + size = Math.round(size); + + return size + " " + dataTexts[data]; + } + + private function updateText(content:String):Void + { + var tf = new TextField(); + tf.defaultTextFormat = new TextFormat(fontCustom, fontSize, fillColor); + tf.text = content; + tf.autoSize = LEFT; + tf.multiline = true; + tf.selectable = false; + + tf.antiAliasType = ADVANCED; + tf.sharpness = 0; + tf.gridFitType = PIXEL; + + var textWidth = tf.width + strokeSize * 2 + 4; + var textHeight = tf.height + strokeSize * 2 + 2; + + var totalWidth = textWidth; + var totalHeight = textHeight; + + if (cachedBMD == null || cachedBMD.width != Math.ceil(totalWidth) || cachedBMD.height != Math.ceil(totalHeight)) { + cachedBMD?.dispose(); + cachedBMD = new BitmapData(Math.ceil(totalWidth), Math.ceil(totalHeight), true, 0x00000000); + } else { + cachedBMD.fillRect(cachedBMD.rect, 0x00000000); + } + + for (dx in -strokeSize...strokeSize + 1) { + for (dy in -strokeSize...strokeSize + 1) { + if (dx != 0 || dy != 0) { + tf.textColor = strokeColor; + cachedBMD.draw(tf, new Matrix(1, 0, 0, 1, strokeSize + dx, strokeSize + dy)); + } + } + } + + tf.textColor = fillColor; + cachedBMD.draw(tf, new Matrix(1, 0, 0, 1, strokeSize, strokeSize)); + + if (showDebugInfo && graphDirty && graphHistory.length > 1) { + var debugWidth = Math.max(textWidth, graphWidth + strokeSize * 2); + var debugHeight = textHeight + graphHeight + 5; + + if (cachedBMD.width != debugWidth || cachedBMD.height != debugHeight) { + var newBMD = new BitmapData(Math.ceil(debugWidth), Math.ceil(debugHeight), true, 0x00000000); + newBMD.copyPixels(cachedBMD, cachedBMD.rect, new openfl.geom.Point(0, 0)); + cachedBMD?.dispose(); + cachedBMD = newBMD; + } + + drawGraph(cachedBMD, strokeSize, Std.int(textHeight + 5)); + graphDirty = false; + } + + this.bitmapData = cachedBMD; + } + + private function drawGraph(bmd:BitmapData, x:Int, y:Int):Void + { + if (graphHistory.length < 2) return; + + var graphX = x; + var graphY = y; + + bmd.fillRect(new Rectangle(graphX, graphY, graphWidth, graphHeight), 0x88000000); + + for (i in 0...5) { + var lineY = graphY + Std.int(graphHeight * i / 4); + bmd.fillRect(new Rectangle(graphX, lineY, graphWidth, 1), 0x33FFFFFF); + } + + for (i in 0...6) { + var lineX = graphX + Std.int(graphWidth * i / 5); + bmd.fillRect(new Rectangle(lineX, graphY, 1, graphHeight), 0x33FFFFFF); + } + + var maxValue = Math.max(ClientPrefs.framerate, Math.max(currentFPS, getArrayMax(graphHistory))); + if (maxValue < 1) maxValue = 1; + + if (!ClientPrefs.unlimitedFPS) { + var targetY = graphY + graphHeight - Std.int((ClientPrefs.framerate / maxValue) * graphHeight); + bmd.fillRect(new Rectangle(graphX, targetY - 1, graphWidth, 3), 0xAA00FF00); + + var targetLabel = '${ClientPrefs.framerate}'; + var labelX = graphX + graphWidth - 15; + var labelY = targetY - 8; + bmd.fillRect(new Rectangle(labelX - 2, labelY - 1, 16, 10), 0xAA000000); + bmd.fillRect(new Rectangle(labelX - 1, labelY, 14, 8), 0xAA00FF00); + } + + var points:Array<{x:Int, y:Int}> = []; + var segmentCount = Std.int(Math.min(graphHistory.length, graphWidth)); + + for (i in 0...segmentCount) { + var historyIndex = graphHistory.length - segmentCount + i; + if (historyIndex < 0) continue; + + var xPos = graphX + Std.int((i / segmentCount) * graphWidth); + var yPos = graphY + graphHeight - Std.int((graphHistory[historyIndex] / maxValue) * graphHeight); + + yPos = Std.int(Math.max(graphY + 1, Math.min(graphY + graphHeight - 2, yPos))); + xPos = Std.int(Math.max(graphX + 1, Math.min(graphX + graphWidth - 2, xPos))); + + points.push({x: xPos, y: yPos}); + } + + if (points.length >= 2) { + for (i in 1...points.length) { + var prev = points[i - 1]; + var curr = points[i]; + + drawSmoothLine(bmd, prev.x, prev.y, curr.x, curr.y, fillColor, 2); + drawSmoothLine(bmd, prev.x, prev.y + 1, curr.x, curr.y + 1, 0x66FFFFFF, 1); + } + + var pointStep = Math.floor(points.length / 8); + if (pointStep == 0) pointStep = 1; + + for (i in 0...points.length) { + if (i % pointStep == 0) { + var point = points[i]; + bmd.fillRect(new Rectangle(point.x - 2, point.y - 2, 5, 5), 0xAA000000); + bmd.fillRect(new Rectangle(point.x - 1, point.y - 1, 3, 3), fillColor); + } + } + + var lastPoint = points[points.length - 1]; + bmd.fillRect(new Rectangle(lastPoint.x - 3, lastPoint.y - 3, 7, 7), 0xAA000000); + bmd.fillRect(new Rectangle(lastPoint.x - 2, lastPoint.y - 2, 5, 5), 0xFFFFFF00); + + var lastFPS = graphHistory[graphHistory.length - 1]; + var fpsText = '${Math.round(lastFPS)}'; + var textX = lastPoint.x - 8; + var textY = lastPoint.y - 12; + bmd.fillRect(new Rectangle(textX - 1, textY - 1, 18, 10), 0xAA000000); + bmd.fillRect(new Rectangle(textX, textY, 16, 8), 0xAAFFFFFF); + } + + bmd.fillRect(new Rectangle(graphX, graphY, graphWidth, 1), 0x99FFFFFF); + bmd.fillRect(new Rectangle(graphX, graphY + graphHeight - 1, graphWidth, 1), 0x99FFFFFF); + bmd.fillRect(new Rectangle(graphX, graphY, 1, graphHeight), 0x99FFFFFF); + bmd.fillRect(new Rectangle(graphX + graphWidth - 1, graphY, 1, graphHeight), 0x99FFFFFF); + } + + private function drawSmoothLine(bmd:BitmapData, x1:Int, y1:Int, x2:Int, y2:Int, color:Int, thickness:Int = 1):Void + { + var dx = Math.abs(x2 - x1); + var dy = Math.abs(y2 - y1); + var sx = (x1 < x2) ? 1 : -1; + var sy = (y1 < y2) ? 1 : -1; + var err = dx - dy; + + var x = x1; + var y = y1; + + var maxPoints = Math.max(dx, dy); + var pointsDrawn = 0; + + while (pointsDrawn < maxPoints && pointsDrawn < 100) { + for (tx in -thickness...thickness + 1) { + for (ty in -thickness...thickness + 1) { + var dist = Math.sqrt(tx * tx + ty * ty); + if (dist <= thickness) { + var px = x + tx; + var py = y + ty; + if (px >= 0 && px < bmd.width && py >= 0 && py < bmd.height) { + bmd.setPixel32(px, py, color); + } + } + } + } + + if (x == x2 && y == y2) break; + + var e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x += sx; + } + if (e2 < dx) { + err += dx; + y += sy; + } + + pointsDrawn++; + } + } + + private function getArrayMax(arr:Array):Float + { + var max = arr[0]; + for (i in 1...arr.length) { + if (arr[i] > max) max = arr[i]; + } + return max; + } + + private function initLogFile():Void + { + #if sys + try { + var header = "Timestamp,FPS,Memory,PeakMemory,AvailableMemory,VSync,TargetFPS,PerformanceScore,Warnings\n"; + File.saveContent(logFile, header); + } catch (e:Dynamic) { + trace("Failed to create log file: " + e); + } + #end + } + + private function logData():Void + { + #if sys + try { + var timestamp = Date.now().toString(); + var warnings = performanceWarnings.join("; "); + var logLine = timestamp + "," + currentFPS + "," + smoothMemory + "," + peakMemory + "," + + availableSystemMemory + "," + (ClientPrefs.vsync ? "ON" : "OFF") + "," + + (ClientPrefs.vsync ? getDisplayRefreshRate() : ClientPrefs.framerate) + "," + + performanceScore + "," + warnings + "\n"; + + File.saveContent(logFile, File.getContent(logFile) + logLine); + } catch (e:Dynamic) { + trace("Failed to write log: " + e); + } + #end + } + + public inline function positionFPS(X:Float, Y:Float, ?scale:Float = 1) + { + scaleX = scaleY = #if android (scale > 1 ? scale : 1) #else (scale < 1 ? scale : 1) #end; + x = FlxG.game.x + X; + y = FlxG.game.y + Y; + } + + public function toggleDebugInfo():Void + { + showDebugInfo = !showDebugInfo; + graphDirty = true; + } + + public function resetStats():Void + { + resetStatistics(); + graphHistory = []; + frameTimes = []; + memoryReadings = []; + for (i in 0...maxMemoryReadings) { + memoryReadings.push(0); + } + peakMemory = 0; + performanceWarnings = []; + warningLevel = 0; + graphDirty = true; + lastOutput = ""; + } + + public function setLogging(enabled:Bool):Void + { + logEnabled = enabled; + if (logEnabled) { + initLogFile(); + } + } + + public function destroy():Void + { + removeEventListener(Event.ENTER_FRAME, onEnterFrame); + Lib.current.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyPress); + + if (cachedBMD != null) { + cachedBMD.dispose(); + cachedBMD = null; + } + + if (this.bitmapData != null && this.bitmapData != cachedBMD) { + this.bitmapData.dispose(); + } + } +} \ No newline at end of file diff --git a/source/game/backend/system/Mods.hx b/source/game/backend/system/Mods.hx new file mode 100644 index 0000000..f60e016 --- /dev/null +++ b/source/game/backend/system/Mods.hx @@ -0,0 +1,817 @@ +package game.backend.system; + +#if sys +import sys.FileSystem; +import sys.io.File; +#end + +import haxe.io.Bytes; +import haxe.zip.Reader; +import haxe.zip.Entry; +import haxe.Json; + +import game.Paths; + +class Mods +{ + @:unreflective + inline public static final MODS_FOLDER = "contents"; + + public static var debugMode:Bool = #if DEBUG_MODS true #else false #end; + + public static var ignoreModFolders:Array = [ + 'characters', + 'custom_events', + 'custom_notetypes', + 'data', + 'fonts', + 'images', + 'music', + 'ndlls', + 'scripts', + 'songs', + 'sounds', + 'source', + 'shaders', + 'stages', + 'videos', + 'weeks', + ]; + + public static var currentModDirectory:String = ''; + public static var globalMods:Array = []; + + public static var zipModsCache:Map> = new Map(); + public static var tempExtractedFolders:Array = []; + + #if MODS_ALLOWED + inline public static function getModPath(key:String = ''):String + { + return '$MODS_FOLDER/$key'; + } + + public static function normalizePath(path:String):String { + if (path == null) return path; + + while (path.indexOf("//") != -1) { + path = path.replace("//", "/"); + } + if (path.startsWith("/")) { + path = path.substr(1); + } + return path; + } + + public static function modExists(mod:String):Bool { + var modPath = getModPath(mod); + return FileSystem.exists(modPath) || FileSystem.exists('$modPath.zip'); + } + + public static function isZipMod(mod:String):Bool + { + var modPath = getModPath(mod); + return FileSystem.exists('$modPath.zip'); + } + + public static function getModFileContent(path:String):Null + { + path = normalizePath(path); + + if (currentModDirectory != null && currentModDirectory.length > 0) { + var content = getFileFromMod(currentModDirectory, path); + if (content != null) return content; + } + + for (mod in getGlobalMods()) { + var content = getFileFromMod(mod, path); + if (content != null) return content; + } + + return null; + } + + public static function getFileFromMod(mod:String, path:String):Null + { + path = normalizePath(path); + var modPath = getModPath(mod); + + if (isZipMod(mod)) { + return getFileFromZipMod(mod, path); + } + + var filePath = normalizePath('$modPath/$path'); + if (FileSystem.exists(filePath) && !FileSystem.isDirectory(filePath)) { + return File.getBytes(filePath); + } + + return null; + } + + public static function getFileFromZipMod(mod:String, path:String):Null + { + path = normalizePath(path); + var zipPath = '${getModPath(mod)}.zip'; + + if (!zipModsCache.exists(mod)) { + if (!loadZipMod(mod)) { + return null; + } + } + + var modCache = zipModsCache.get(mod); + if (modCache == null) return null; + + if (modCache.exists(path)) { + return modCache.get(path); + } + + var pathVariants = getPathVariants(mod, path); + + for (variant in pathVariants) { + variant = normalizePath(variant); + if (modCache.exists(variant)) { + if (debugMode) trace('Found file with variant: $variant (original: $path)'); + return modCache.get(variant); + } + } + + var fileName = path.split('/').pop(); + if (fileName != null && fileName.length > 0) { + for (key in modCache.keys()) { + if (key.endsWith('/' + fileName) || key == fileName) { + if (debugMode) trace('Found file by name: $key (looking for: $path)'); + return modCache.get(key); + } + } + } + + if (debugMode) { + trace('File not found in ZIP: $path'); + trace('Tried variants: $pathVariants'); + trace('Available files in mod $mod:'); + + var allKeys = [for (key in modCache.keys()) key]; + var totalFileCount = allKeys.length; + + var relevantFiles = []; + for (key in allKeys) { + if (key.toLowerCase().indexOf(path.toLowerCase()) != -1 || + path.toLowerCase().indexOf(key.toLowerCase()) != -1 || + key.indexOf(mod) != -1 || + (fileName != null && key.endsWith('/' + fileName))) { + relevantFiles.push(key); + } + } + + if (relevantFiles.length > 0) { + for (key in relevantFiles) { + trace(' $key'); + } + } else { + var maxFiles = Std.int(Math.min(20, totalFileCount)); + for (i in 0...maxFiles) { + trace(' ${allKeys[i]}'); + } + if (totalFileCount > 20) { + trace(' ... and ${totalFileCount - 20} more files'); + } + } + } + + return null; + } + + private static function getPathVariants(mod:String, path:String):Array { + var variants = [ + path, + '$mod/$path', + path.toLowerCase(), + path.toUpperCase(), + path.startsWith('$mod/') ? path.substring(mod.length + 1) : path, + path.replace('$mod/', ''), + path.replace(' ', '_'), + path.replace('_', ' ') + ]; + + /*if (path.startsWith("songs/")) { + var songPath = path.substring(6); + variants.push('$mod/songs/$songPath'); + variants.push('songs/$mod/$songPath'); + variants.push(songPath); + variants.push('data/$songPath'); + variants.push('$mod/data/$songPath'); + } + + if (path.startsWith("data/")) { + var dataPath = path.substring(5); + variants.push('$mod/data/$dataPath'); + variants.push('data/$mod/$dataPath'); + variants.push(dataPath); + + if (dataPath.endsWith('.json')) { + variants.push('songs/${dataPath.replace(".json", "")}/$dataPath'); + variants.push('$mod/songs/${dataPath.replace(".json", "")}/$dataPath'); + } + } + + if (path.startsWith("images/")) { + var imagePath = path.substring(7); + variants.push('$mod/images/$imagePath'); + variants.push('images/$mod/$imagePath'); + variants.push(imagePath); + } + + if (path.startsWith("music/")) { + var musicPath = path.substring(6); + variants.push('$mod/music/$musicPath'); + variants.push('music/$mod/$musicPath'); + variants.push('sounds/$musicPath'); + variants.push('$mod/sounds/$musicPath'); + variants.push(musicPath); + } + + if (path.startsWith("sounds/")) { + var soundPath = path.substring(7); + variants.push('$mod/sounds/$soundPath'); + variants.push('sounds/$mod/$soundPath'); + variants.push('music/$soundPath'); + variants.push('$mod/music/$soundPath'); + variants.push(soundPath); + }*/ + + var uniqueVariants = new Map(); + var result = []; + for (v in variants) { + if (v != null && !uniqueVariants.exists(v)) { + uniqueVariants.set(v, true); + result.push(v); + } + } + return result; + } + + public static function loadZipMod(mod:String):Bool + { + var zipPath = '${getModPath(mod)}.zip'; + + if (!FileSystem.exists(zipPath)) { + if (debugMode) trace('ZIP mod not found: $zipPath'); + return false; + } + + try { + var bytes = File.getBytes(zipPath); + var input = new haxe.io.BytesInput(bytes); + var entries:List = Reader.readZip(input); + var fileMap:Map = new Map(); + + if (debugMode) trace('Loading ZIP mod: $mod'); + var fileCount:Int = 0; + + var hasModFolder = false; + var rootFiles = new Map(); + + for (entry in entries) { + var fileName = entry.fileName; + if (!fileName.endsWith("/")) { + var data = Reader.unzip(entry); + var normalizedFileName = normalizePath(fileName); + fileMap.set(normalizedFileName, data); + fileCount++; + + if (normalizedFileName.startsWith('$mod/')) { + hasModFolder = true; + } + + var firstSlash = normalizedFileName.indexOf("/"); + if (firstSlash == -1) { + rootFiles.set(normalizedFileName, true); + } else { + var rootFolder = normalizedFileName.substring(0, firstSlash); + rootFiles.set(rootFolder, true); + } + } + } + + zipModsCache.set(mod, fileMap); + if (debugMode) { + trace('ZIP mod $mod loaded successfully with $fileCount files'); + trace('ZIP structure analysis:'); + trace(' - Has mod folder structure: $hasModFolder'); + var rootKeys = [for (k in rootFiles.keys()) k]; + trace(' - Root folders/files: [${rootKeys.join(", ")}]'); + } + return true; + } catch (e:Dynamic) { + if (debugMode) trace('Error loading ZIP mod $mod: $e'); + return false; + } + } + + public static function modFileExists(path:String):Bool + { + return getModFileContent(path) != null; + } + + #if SCRIPTABLE_STATES + public static function modsStates(key:String, state:String) + return modFolders('scripts/states/$state/$key.hx'); + #end + + public static function modsFont(key:String) { + return modFolders('fonts/$key'); + } + + public static function modsJson(key:String) { + return modFolders('data/$key.json'); + } + + public static function modsVideo(key:String) { + return modFolders('videos/$key.${Paths.VIDEO_EXT}'); + } + + #if NDLL_ALLOWED + public static function modsNdll(key:String) { + if (currentModDirectory != null && currentModDirectory.length > 0) { + var fileToCheck:String = getModPath(currentModDirectory + '/ndlls/$key'); + if (FileSystem.exists(fileToCheck)) { + return fileToCheck; + } + + if (isZipMod(currentModDirectory)) { + var tempPath = extractFileFromZipMod(currentModDirectory, 'ndlls/$key', 'ndlls'); + if (tempPath != null) return tempPath; + } + } + + for (mod in getGlobalMods()) { + var fileToCheck:String = getModPath(mod + '/ndlls/$key'); + if (FileSystem.exists(fileToCheck)) + return fileToCheck; + + if (isZipMod(mod)) { + var tempPath = extractFileFromZipMod(mod, 'ndlls/$key', 'ndlls'); + if (tempPath != null) return tempPath; + } + } + return '$MODS_FOLDER/ndlls/' + key; + } + #end + + public static function modsSounds(path:String, key:String, ?ext:String = null) { + if (ext == null) ext = Paths.SOUND_EXT; + var fullPath = normalizePath('$path/$key.$ext'); + return modFolders(fullPath); + } + + public static function modsImages(key:String, ?imgFormat:String = "png") { + return modFolders('images/$key.$imgFormat'); + } + + public static function modsImagesJson(key:String) { + return modFolders('images/$key.json'); + } + + public static function modsXml(key:String) { + return modFolders('images/$key.xml'); + } + + public static function modsTxt(key:String) { + return modFolders('images/$key.txt'); + } + + public static function modsShaderFragment(key:String, ?library:String) + { + return modFolders('shaders/$key.frag'); + } + + public static function modsShaderVertex(key:String, ?library:String) + { + return modFolders('shaders/$key.vert'); + } + + static public function modFolders(key:String) { + key = normalizePath(key); + + if (currentModDirectory != null && currentModDirectory.length > 0) { + var fileToCheck:String = getModPath(currentModDirectory + '/' + key); + + if (FileSystem.exists(fileToCheck)) { + return fileToCheck; + } + + if (isZipMod(currentModDirectory) && modFileExists(key)) { + return 'zip://$currentModDirectory/$key'; + } + } + + for (mod in getGlobalMods()) { + var fileToCheck:String = getModPath(mod + '/' + key); + if (FileSystem.exists(fileToCheck)) + return fileToCheck; + + if (isZipMod(mod) && getFileFromMod(mod, key) != null) { + return 'zip://$mod/$key'; + } + } + return '$MODS_FOLDER/' + key; + } + + static public function extractFileFromZipMod(mod:String, filePath:String, category:String):String { + filePath = normalizePath(filePath); + var content = getFileFromZipMod(mod, filePath); + if (content == null) return null; + + var tempDir = 'temp/$mod/$category'; + if (!FileSystem.exists(tempDir)) { + FileSystem.createDirectory(tempDir); + } + + var fileName = filePath.split('/').pop(); + var tempPath = '$tempDir/$fileName'; + + File.saveBytes(tempPath, content); + tempExtractedFolders.push(tempDir); + + return tempPath; + } + + public static function extractZipMod(mod:String):Bool { + if (!isZipMod(mod)) { + if (debugMode) trace('Mod $mod is not a ZIP mod or does not exist'); + return false; + } + + var zipPath = '${getModPath(mod)}.zip'; + var extractPath = getModPath(mod); + + if (FileSystem.exists(extractPath)) { + if (debugMode) trace('Extraction path already exists: $extractPath'); + return false; + } + + try { + FileSystem.createDirectory(extractPath); + + var bytes = File.getBytes(zipPath); + var input = new haxe.io.BytesInput(bytes); + var entries:List = Reader.readZip(input); + var extractedFiles = 0; + + if (debugMode) trace('Extracting ZIP mod: $mod to $extractPath'); + + var hasRootFolder = true; + var rootFolderName = null; + + for (entry in entries) { + var fileName = entry.fileName; + var parts = fileName.split('/'); + + if (rootFolderName == null && parts.length > 0 && parts[0] != '') { + rootFolderName = parts[0]; + } + + if (parts.length == 1 && !fileName.endsWith('/')) { + hasRootFolder = false; + break; + } + } + + if (hasRootFolder && rootFolderName != null && rootFolderName == mod) { + if (debugMode) trace('ZIP has root folder matching mod name, stripping it: $rootFolderName'); + } else { + hasRootFolder = false; + if (debugMode) trace('ZIP does not have matching root folder, extracting as-is'); + } + + for (entry in entries) { + var fileName = entry.fileName; + + if (fileName.endsWith("/")) continue; + + var targetFileName = fileName; + + if (hasRootFolder && rootFolderName != null) { + if (fileName.startsWith(rootFolderName + '/')) { + targetFileName = fileName.substring(rootFolderName.length + 1); + } + } + + var fullPath = extractPath + "/" + targetFileName; + + var dirPath = haxe.io.Path.directory(fullPath); + if (!FileSystem.exists(dirPath)) + FileSystem.createDirectory(dirPath); + + var data = Reader.unzip(entry); + File.saveBytes(fullPath, data); + extractedFiles++; + + if (debugMode && extractedFiles % 10 == 0) { + trace(' Extracted $extractedFiles files...'); + } + } + + zipModsCache.remove(mod); + + trace('Successfully extracted mod $mod: $extractedFiles files'); + return true; + + } catch (e:Dynamic) { + trace('Error extracting ZIP mod $mod: $e'); + + try { + if (FileSystem.exists(extractPath)) { + deleteDirectory(extractPath); + } + } catch (cleanupError:Dynamic) { + trace('Error cleaning up after failed extraction: $cleanupError'); + } + + return false; + } + } + + public static function getZipModInfo(mod:String):{size:Int, fileCount:Int} { + if (!isZipMod(mod)) { + return {size: 0, fileCount: 0}; + } + + var zipPath = '${getModPath(mod)}.zip'; + + try { + var bytes = File.getBytes(zipPath); + var input = new haxe.io.BytesInput(bytes); + var entries:List = Reader.readZip(input); + + var fileCount = 0; + for (entry in entries) { + if (!entry.fileName.endsWith("/")) { + fileCount++; + } + } + + return { + size: bytes.length, + fileCount: fileCount + }; + } catch (e:Dynamic) { + if (debugMode) trace('Error getting ZIP mod info for $mod: $e'); + return {size: 0, fileCount: 0}; + } + } + + public static function clearTempFiles() { + for (tempDir in tempExtractedFolders) { + if (FileSystem.exists(tempDir)) { + deleteDirectory(tempDir); + } + } + tempExtractedFolders = []; + zipModsCache.clear(); + } + + public static function deleteDirectory(path:String) { + if (FileSystem.exists(path)) { + for (entry in FileSystem.readDirectory(path)) { + var entryPath = path + "/" + entry; + if (FileSystem.isDirectory(entryPath)) { + deleteDirectory(entryPath); + } else { + FileSystem.deleteFile(entryPath); + } + } + FileSystem.deleteDirectory(path); + } + } + + public static function deleteZipMod(mod:String):Bool { + var zipPath = '${getModPath(mod)}.zip'; + if (FileSystem.exists(zipPath)) { + try { + FileSystem.deleteFile(zipPath); + if (debugMode) trace('Deleted ZIP file: $zipPath'); + return true; + } catch (e:Dynamic) { + trace('Error deleting ZIP file $zipPath: $e'); + return false; + } + } + return false; + } + + static public function getGlobalMods() + return globalMods; + + static public function pushGlobalMods() { + globalMods = []; + var path:String = Paths.txt('modsList'); + if (FileSystem.exists(path)) { + var list:Array = CoolUtil.coolTextFile(path); + for (i in list) { + var dat = i.split("|"); + if (dat[1] == "1") { + var folder = dat[0]; + var jsonPath = Mods.getModPath(folder + '/pack.json'); + var zipJsonPath = '${Mods.getModPath(folder)}.zip'; + + var packJsonContent:Null = null; + + if (FileSystem.exists(jsonPath)) { + packJsonContent = File.getBytes(jsonPath); + } else if (FileSystem.exists(zipJsonPath)) { + packJsonContent = getFileFromZipMod(folder, 'pack.json'); + } + + if (packJsonContent != null) { + try { + var rawJson:String = packJsonContent.toString(); + if (rawJson != null && rawJson.length > 0) { + var stuff:Dynamic = Json.parse(rawJson); + var global:Bool = Reflect.getProperty(stuff, "runsGlobally"); + if (global) globalMods.push(dat[0]); + } + } catch (e:Dynamic) { + trace(e); + } + } + } + } + } + return globalMods; + } + + static public function getModDirectories():Array { + var list:Array = []; + var modsFolder:String = getModPath(); + if (FileSystem.exists(modsFolder)) { + for (folder in FileSystem.readDirectory(modsFolder)) { + var path = haxe.io.Path.join([modsFolder, folder]); + + if (FileSystem.isDirectory(path) && !ignoreModFolders.contains(folder) && !list.contains(folder)) { + list.push(folder); + } else if (folder.endsWith('.zip')) { + var modName = folder.substr(0, folder.length - 4); + if (!ignoreModFolders.contains(modName) && !list.contains(modName)) { + list.push(modName); + } + } + } + } + return list; + } + + public static function debugZipMod(mod:String):Void { + if (!isZipMod(mod)) { + trace('$mod is not a ZIP mod'); + return; + } + + if (!zipModsCache.exists(mod)) { + loadZipMod(mod); + } + + var modCache = zipModsCache.get(mod); + if (modCache == null) { + trace('Failed to load ZIP mod: $mod'); + return; + } + + trace('=== DEBUG ZIP MOD: $mod ==='); + + var allKeys = [for (key in modCache.keys()) key]; + var totalFileCount = allKeys.length; + + for (key in allKeys) { + trace(' $key (${modCache.get(key).length} bytes)'); + } + + trace('Total files: $totalFileCount'); + trace('=== END DEBUG ==='); + } + + public static function analyzeZipStructure(mod:String):Void { + if (!isZipMod(mod)) { + trace('$mod is not a ZIP mod'); + return; + } + + if (!zipModsCache.exists(mod)) { + loadZipMod(mod); + } + + var modCache = zipModsCache.get(mod); + if (modCache == null) { + trace('Failed to load ZIP mod: $mod'); + return; + } + + var allKeys = [for (key in modCache.keys()) key]; + + trace('=== ZIP STRUCTURE ANALYSIS: $mod ==='); + + var fileTypes = new Map(); + var folders = new Map(); + + for (key in allKeys) { + var parts = key.split('/'); + var fileName = parts.pop(); + var extension = fileName.split('.').pop().toLowerCase(); + + if (!fileTypes.exists(extension)) fileTypes.set(extension, 0); + fileTypes.set(extension, fileTypes.get(extension) + 1); + + for (i in 0...parts.length) { + var folderPath = parts.slice(0, i + 1).join('/'); + if (!folders.exists(folderPath)) { + folders.set(folderPath, 0); + } + folders.set(folderPath, folders.get(folderPath) + 1); + } + } + + trace('File types:'); + for (ext in fileTypes.keys()) { + trace(' .$ext: ${fileTypes.get(ext)} files'); + } + + trace('Folder structure:'); + var folderList = [for (f in folders.keys()) f]; + folderList.sort(function(a, b) return a.length - b.length); + for (folder in folderList) { + trace(' $folder/ (${folders.get(folder)} files)'); + } + + var hasModFolder = false; + for (key in allKeys) { + if (key.startsWith('$mod/')) { + hasModFolder = true; + break; + } + } + trace('Has mod folder structure: $hasModFolder'); + + trace('=== END ANALYSIS ==='); + } + #end +} + +class ModMetadata +{ + public var folder:String; + public var name:String; + public var description:String; + public var color:FlxColor; + public var restart:Bool; + public var alphabet:Alphabet; + public var icon:AttachedSprite; + + public function new(folder:String) + { + this.folder = folder; + this.name = folder; + this.description = "No description provided."; + this.color = ModsMenuState.defaultColor; + this.restart = false; + + var jsonBytes:Bytes = Mods.getFileFromMod(folder, 'pack.json'); + if(jsonBytes != null) { + try { + var rawJson:String = jsonBytes.toString(); + if(rawJson != null && rawJson.length > 0) { + var stuff:Dynamic = Json.parse(rawJson); + + var colors:Array = Reflect.getProperty(stuff, "color"); + var description:String = Reflect.getProperty(stuff, "description"); + var name:String = Reflect.getProperty(stuff, "name"); + var restart:Bool = Reflect.getProperty(stuff, "restart"); + + if(name != null && name.length > 0) + { + this.name = name; + } + if(description != null && description.length > 0) + { + this.description = description; + } + if(name == 'Name') + { + this.name = folder; + } + if(description == 'Description') + { + this.description = "No description provided."; + } + if(colors != null && colors.length > 2) + { + this.color = FlxColor.fromRGB(colors[0], colors[1], colors[2]); + } + + this.restart = restart; + } + } catch(e:Dynamic) { + trace('Error parsing pack.json for mod $folder: $e'); + } + } + } +} \ No newline at end of file diff --git a/source/game/backend/utils/CoolUtil.hx b/source/game/backend/utils/CoolUtil.hx index 97c1a2b..91cd2b0 100644 --- a/source/game/backend/utils/CoolUtil.hx +++ b/source/game/backend/utils/CoolUtil.hx @@ -36,38 +36,23 @@ class CoolUtil public static var difficulties:Array = []; - inline static public function getBuildTarget() { + public static function getBuildTarget():String { #if windows - return 'windows'; + return "Windows"; #elseif linux - return 'linux'; + return "Linux"; #elseif mac - return 'mac'; - #elseif html5 - return 'browser'; + return "Mac"; #elseif android - return 'android'; + return "Android"; #elseif ios - return 'ios'; + return "iOS"; + #elseif html5 + return "HTML5"; #else - return 'unknown'; + return "Unknown"; #end } - - #if HSCRIPT_ALLOWED - /** - * Gets the hscript preprocessors for haxe scripts and runHaxeCode - */ - public static dynamic function getHScriptPreprocessors() { - var preprocessors:Map = game.backend.utils.MacroUtil.defines; - preprocessors.set("CC_ENGINE", true); - preprocessors.set("CC_ENGINE_VER", Application.current.meta.get('version')); - preprocessors.set("BUILD_TARGET", getBuildTarget()); - preprocessors.set("INITIAL_STATE", Type.getClassName(Type.getClass(FlxG.state))); - - return preprocessors; - } - #end public static function getDifficultyFilePath(num:Null = null) { @@ -163,7 +148,7 @@ class CoolUtil if(FlxG.save.data.modSettings == null) FlxG.save.data.modSettings = new Map(); var settings:Map = FlxG.save.data.modSettings.get(modName); - var path:String = Paths.mods('$modName/data/settings.json'); + var path:String = Mods.getModPath('$modName/data/settings.json'); if(FileSystem.exists(path)) { if(settings == null || !settings.exists(saveTag)) @@ -194,7 +179,7 @@ class CoolUtil } FlxG.save.data.modSettings.set(modName, settings); } catch(e:Dynamic) { - var errorTitle = 'Mod name: ' + Paths.currentModDirectory; + var errorTitle = 'Mod name: ' + Mods.currentModDirectory; var errorMsg = 'An error occurred: $e'; showPopUp(errorMsg, errorTitle); @@ -272,6 +257,13 @@ class CoolUtil #end } + inline public static function setWindowDarkMode(title:String, enable:Bool) { + #if windows + title ??= lime.app.Application.current.window.title; + lime.Native.setWindowDarkMode(title, enable); + #end + } + //uhhhh does this even work at all? i'm starting to doubt inline public static function precacheSound(sound:String, ?library:String = null):Void { Paths.sound(sound, library); @@ -293,13 +285,6 @@ class CoolUtil #end } - inline public static function setDarkMode(title:String, enable:Bool) { - #if windows - title ??= lime.app.Application.current.window.title; - lime.Native.setDarkMode(title, enable); - #end - } - inline public static function showPopUp(message:String, title:String #if sl_windows_api, ?icon:MessageBoxIcon, ?type:MessageBoxType #end, showScrollableMSG:Bool = false):Void { #if android @@ -312,7 +297,7 @@ class CoolUtil else WindowsAPI.showMessageBox(message, title, icon, type); #else - lime.app.Application.current.window.alert(message, title); + FlxG.stage.window.alert(message, title); #end } diff --git a/source/game/backend/utils/MacroUtil.hx b/source/game/backend/utils/MacroUtil.hx index 76f355d..14cff5d 100644 --- a/source/game/backend/utils/MacroUtil.hx +++ b/source/game/backend/utils/MacroUtil.hx @@ -17,10 +17,34 @@ class MacroUtil { #if display return macro $v{[]}; #else - return macro $v{Context.getDefines()}; + var definesMap = Context.getDefines(); + var processedDefines = new Map(); + + for (key => value in definesMap) { + processedDefines.set(key, parseDefineValueMacro(value)); + } + + return macro $v{processedDefines}; #end } + #if macro + private static function parseDefineValueMacro(value:String):Dynamic { + if (value == "1") return true; + if (value == "0") return false; + if (value == "true") return true; + if (value == "false") return false; + + var floatVal = Std.parseFloat(value); + if (!Math.isNaN(floatVal)) return floatVal; + + var intVal = Std.parseInt(value); + if (intVal != null) return intVal; + + return value; + } + #end + /** * Macro for automatically adding scripts to resources * Finds all .hx files in specified folders and adds them to resources @@ -31,9 +55,9 @@ class MacroUtil { var basePaths = ["scripts/"]; #if MODS_ALLOWED - if (FileSystem.exists("mods/")) { - for (mod in FileSystem.readDirectory("mods/")) { - var modPath = Path.join(["mods", mod, "scripts"]); + if (FileSystem.exists("contents/")) { + for (mod in FileSystem.readDirectory("contents/")) { + var modPath = Path.join(["contents", mod, "scripts"]); if (FileSystem.exists(modPath) && FileSystem.isDirectory(modPath)) { basePaths.push(modPath + "/"); } diff --git a/source/game/backend/utils/MemoryUtil.hx b/source/game/backend/utils/MemoryUtil.hx index dcab6f6..e318345 100644 --- a/source/game/backend/utils/MemoryUtil.hx +++ b/source/game/backend/utils/MemoryUtil.hx @@ -89,7 +89,194 @@ class MemoryUtil { return stats; } + + public static function getAccurateRamUsage():Float { + #if windows + return WindowsMemoryAPI.getProcessMemoryUsage(); + #elseif (linux || android) + return getLinuxMemoryUsage(); + #elseif mac + return getMacMemoryUsage(); + #elseif ios + return getIOSMemoryUsage(); + #else + #if (openfl || flash) + return openfl.system.System.totalMemoryNumber; + #else + return -1; + #end + #end + } + + // Linux implementation + // Same for android cuz it's kinda linux too, but idk if there needs using JNI + #if (linux || android) + private static function getLinuxMemoryUsage():Float { + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + FILE* file = fopen("/proc/self/statm", "r"); + if (file) { + long pages = 0; + if (fscanf(file, "%*s%ld", &pages) == 1) { + long page_size = sysconf(_SC_PAGESIZE); + result = (double)(pages * page_size); + } + fclose(file); + } + '); + return result; + #else + return -1; + #end + } + #end + + // macOS implementation + #if mac + private static function getMacMemoryUsage():Float { + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + task_basic_info_data_t taskInfo; + mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&taskInfo, &infoCount) == KERN_SUCCESS) { + result = (double)taskInfo.resident_size; + } + '); + return result; + #else + return -1; + #end + } + #end + + // iOS implementation + #if ios + private static function getIOSMemoryUsage():Float { + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + task_vm_info_data_t vmInfo; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + if (task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vmInfo, &count) == KERN_SUCCESS) { + result = (double)vmInfo.phys_footprint; + } + '); + return result; + #else + return -1; + #end + } + #end + + public static function getPeakRamUsage():Float { + #if windows + return WindowsMemoryAPI.getPeakMemoryUsage(); + #else + return getAccurateRamUsage(); + #end + } + + public static function getAvailableSystemMemory():Float { + #if windows + return WindowsMemoryAPI.getAvailableSystemMemory(); + #elseif linux + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + struct sysinfo info; + if (sysinfo(&info) == 0) { + result = (double)(info.freeram * info.mem_unit); + } + '); + return result; + #else + return -1; + #end + #elseif mac + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + vm_size_t page_size; + vm_statistics64_data_t vm_stats; + mach_msg_type_number_t host_size = sizeof(vm_stats) / sizeof(natural_t); + + if (host_page_size(mach_host_self(), &page_size) == KERN_SUCCESS && + host_statistics64(mach_host_self(), HOST_VM_INFO, (host_info64_t)&vm_stats, &host_size) == KERN_SUCCESS) { + result = (double)((int64_t)vm_stats.free_count * (int64_t)page_size); + } + '); + return result; + #else + return -1; + #end + #else + return -1; + #end + } +} + +//С++ MY BELOVED <3 +#if windows +@:buildXml(" + + + + +") +@:headerCode(' +#include +#include +') +class WindowsMemoryAPI { + public static function getProcessMemoryUsage(moreAccurate:Bool = true):Float { + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + PROCESS_MEMORY_COUNTERS_EX pmc; + if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) { + result = moreAccurate ? (double)pmc.WorkingSetSize : (double)pmc.PrivateUsage; + } + '); + return result; + #else + return -1; + #end + } + + public static function getPeakMemoryUsage():Float { + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + PROCESS_MEMORY_COUNTERS_EX pmc; + if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) { + result = (double)pmc.PeakPagefileUsage; + } + '); + return result; + #else + return -1; + #end + } + + public static function getAvailableSystemMemory():Float { + #if cpp + var result:Float = -1.0; + untyped __cpp__(' + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + if (GlobalMemoryStatusEx(&statex)) { + result = (double)statex.ullAvailPhys; + } + '); + return result; + #else + return -1; + #end + } } +#end typedef MemoryStats = { var usage:Int; diff --git a/source/game/backend/utils/WindowsRegistry.hx b/source/game/backend/utils/WindowsRegistry.hx index d4d6e72..6ad8ba1 100644 --- a/source/game/backend/utils/WindowsRegistry.hx +++ b/source/game/backend/utils/WindowsRegistry.hx @@ -49,72 +49,4 @@ class WindowsRegistry { { return null; } - - #if windows - @:functionCode(' - HKEY hKey; - LONG result; - - std::wstring subkey = std::wstring(key.wchar_str()); - std::wstring valname = std::wstring(string.wchar_str()); - std::wstring data = std::wstring(value.wchar_str()); - - result = RegCreateKeyExW((HKEY)reinterpret_cast(static_cast(hive)), subkey.c_str(), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL); - if (result != ERROR_SUCCESS) return false; - - result = RegSetValueExW(hKey, valname.c_str(), 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(wchar_t))); - RegCloseKey(hKey); - - return result == ERROR_SUCCESS; - ') - #end - public static function set(hive:RegistryHive, key:String, string:String, value:String):Bool - { - return false; - } - - #if windows - @:functionCode(' - HKEY hKey; - LONG result; - - std::wstring subkey = std::wstring(key.wchar_str()); - std::wstring valname = std::wstring(string.wchar_str()); - - result = RegOpenKeyExW((HKEY)reinterpret_cast(static_cast(hive)), subkey.c_str(), 0, KEY_READ, &hKey); - if (result != ERROR_SUCCESS) return false; - - DWORD dataType = 0; - result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, NULL); - - RegCloseKey(hKey); - - return result == ERROR_SUCCESS; - ') - #end - public static function exists(hive:RegistryHive, key:String, string:String):Bool - { - return false; - } - - #if windows - @:functionCode(' - HKEY hKey; - LONG result; - - std::wstring subkey = std::wstring(key.wchar_str()); - std::wstring valname = std::wstring(string.wchar_str()); - - result = RegOpenKeyExW((HKEY)reinterpret_cast(static_cast(hive)), subkey.c_str(), 0, KEY_SET_VALUE, &hKey); - if (result != ERROR_SUCCESS) return false; - - result = RegDeleteValueW(hKey, valname.c_str()); - RegCloseKey(hKey); - - return result == ERROR_SUCCESS; - ') - #end - public static function delete(hive:RegistryHive, key:String, string:String):Bool { - return false; - } } \ No newline at end of file diff --git a/source/game/import.hx b/source/game/import.hx index be87615..7f0f317 100644 --- a/source/game/import.hx +++ b/source/game/import.hx @@ -2,6 +2,7 @@ import game.*; import game.backend.*; import game.backend.utils.*; +import game.backend.system.*; import game.states.*; import game.states.options.*; import game.substates.*; @@ -18,6 +19,8 @@ import game.stages.backend.BaseStage; import game.states.backend.MusicBeatState; import game.substates.backend.MusicBeatSubstate; +import game.backend.system.Mods; + import flixel.animation.PsychAnimationController; #if flxsoundfilters diff --git a/source/game/modchart/EventTimeline.hx b/source/game/modchart/EventTimeline.hx new file mode 100644 index 0000000..ce2efcb --- /dev/null +++ b/source/game/modchart/EventTimeline.hx @@ -0,0 +1,166 @@ +package game.modchart; + +import game.modchart.events.ModEvent; +import game.modchart.events.BaseEvent; + +/** + * Manages and executes mod events in chronological order + * Handles both modifier-specific events and general timeline events + */ +class EventTimeline { + // Storage for events organized by modifier name + public var modEvents:Map> = []; + + // Storage for general timeline events + public var events:Array = []; + + public function new() {} + + /** + * Initializes storage for a new modifier's events + */ + public function addMod(modName:String) { + modEvents.set(modName, []); + } + + /** + * Adds an event to the timeline and maintains execution order + */ + public function addEvent(event:BaseEvent) { + if (Std.isOfType(event, ModEvent)) { + addModEvent(cast event); + } else if (!events.contains(event)) { + addGeneralEvent(event); + } + } + + /** + * Handles adding modifier-specific events + */ + private function addModEvent(modEvent:ModEvent) { + var modName = modEvent.modName; + + // Ensure modifier has event storage + if (!modEvents.exists(modName)) { + addMod(modName); + } + + var eventList = modEvents.get(modName); + if (!eventList.contains(modEvent)) { + eventList.push(modEvent); + sortModEventsByStep(eventList); + } + } + + /** + * Handles adding general timeline events + */ + private function addGeneralEvent(event:BaseEvent) { + events.push(event); + sortBaseEventsByStep(events); + } + + /** + * Sorts modifier events by their execution step for proper timeline order + */ + private function sortModEventsByStep(eventArray:Array) { + eventArray.sort((a, b) -> Std.int(a.executionStep - b.executionStep)); + } + + /** + * Sorts base events by their execution step for proper timeline order + */ + private function sortBaseEventsByStep(eventArray:Array) { + eventArray.sort((a, b) -> Std.int(a.executionStep - b.executionStep)); + } + + /** + * Updates and executes all events that are due at the current step + */ + public function update(currentStep:Float) { + executeModEvents(currentStep); + executeGeneralEvents(currentStep); + } + + /** + * Executes all modifier-specific events + */ + private function executeModEvents(currentStep:Float) { + for (modName in modEvents.keys()) { + executeModEventList(modEvents.get(modName), currentStep); + } + } + + /** + * Executes events for a specific modifier + */ + private function executeModEventList(eventList:Array, currentStep:Float) { + var completedEvents:Array = []; + + for (event in eventList) { + if (shouldSkipEvent(event)) continue; + + if (currentStep >= event.executionStep) { + event.run(currentStep); + } else { + // Events are sorted, so we can stop checking once we find one that's not ready + break; + } + + if (event.finished) { + completedEvents.push(event); + } + } + + cleanupCompletedModEvents(eventList, completedEvents); + } + + /** + * Executes all general timeline events + */ + private function executeGeneralEvents(currentStep:Float) { + var completedEvents:Array = []; + + for (event in events) { + if (shouldSkipEvent(event)) continue; + + if (currentStep >= event.executionStep) { + event.run(currentStep); + } else { + // Events are sorted, so we can stop checking once we find one that's not ready + break; + } + + if (event.finished) { + completedEvents.push(event); + } + } + + cleanupCompletedBaseEvents(events, completedEvents); + } + + /** + * Checks if an event should be skipped during execution + */ + private inline function shouldSkipEvent(event:BaseEvent):Bool { + return event.ignoreExecution || event.finished; + } + + /** + * Removes completed modifier events from the event list + */ + private function cleanupCompletedModEvents(eventList:Array, completedEvents:Array) { + for (completedEvent in completedEvents) { + eventList.remove(completedEvent); + } + } + + /** + * Removes completed base events from the event list + */ + private function cleanupCompletedBaseEvents(eventList:Array, completedEvents:Array) { + for (completedEvent in completedEvents) { + eventList.remove(completedEvent); + } + } +} \ No newline at end of file diff --git a/source/game/modchart/ModManager.hx b/source/game/modchart/ModManager.hx new file mode 100644 index 0000000..f5543db --- /dev/null +++ b/source/game/modchart/ModManager.hx @@ -0,0 +1,391 @@ +package game.modchart; + +import flixel.tweens.FlxEase; +import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.FlxSprite; +import flixel.FlxG; + +import math.Vector3; + +import game.modchart.Modifier.ModifierType; +import game.modchart.modifiers.*; +import game.modchart.events.*; + +/** + * Handles chart modifiers on gameplay elements + * Combines concepts from Schmovin', Andromeda, and custom modifier systems + */ +class ModManager { + private var state:PlayState; + + // Modifier storage + public var notemodRegister:Map = []; + public var miscmodRegister:Map = []; + public var register:Map = []; + public var modArray:Array = []; + + // Active modifiers per player + public var activeMods:Array> = [[], []]; + + public var receptors:Array> = []; + public var timeline:EventTimeline = new EventTimeline(); + + @:deprecated("Use notemodRegister and miscmodRegister instead") + public var registerByType:Map> = [ + NOTE_MOD => [], + MISC_MOD => [] + ]; + + public function new(state:PlayState) { + this.state = state; + } + + /** + * Registers all default modifiers used in the game + */ + public function registerDefaultModifiers() { + var defaultModifiers:Array = [ + FlipModifier, ReverseModifier, InvertModifier, + DrunkModifier, BeatModifier, AlphaModifier, + ScaleModifier, ConfusionModifier, OpponentModifier, + TransformModifier, InfinitePathModifier, PerspectiveModifier + ]; + + for (modClass in defaultModifiers) { + quickRegister(Type.createInstance(modClass, [this])); + } + + // Special case modifiers with custom parameters + quickRegister(new RotateModifier(this)); + quickRegister(new RotateModifier(this, 'center', new Vector3( + (FlxG.width / 2) - (Note.swagWidth / 2), + (FlxG.height / 2) - Note.swagWidth / 2 + ))); + quickRegister(new LocalRotateModifier(this, 'local')); + + // Note spawn time conf + quickRegister(new SubModifier("noteSpawnTime", this)); + setValue("noteSpawnTime", 1250); + } + + /** + * Quick registration helper for modifiers + */ + public inline function quickRegister(mod:Modifier) { + registerMod(mod.getName(), mod); + } + + /** + * Registers a modifier and its submodifiers + */ + public function registerMod(modName:String, mod:Modifier, registerSubmods:Bool = true) { + register.set(modName, mod); + + // Categorize modifier by type + switch (mod.getModType()) { + case NOTE_MOD: + notemodRegister.set(modName, mod); + case MISC_MOD: + miscmodRegister.set(modName, mod); + } + + timeline.addMod(modName); + modArray.push(mod); + + // Register submodifiers recursively + if (registerSubmods) { + for (submodName in mod.submods.keys()) { + var submod = mod.submods.get(submodName); + quickRegister(submod); + } + } + + // Initialize modifier value + setValue(modName, 0); + + // Sort modifiers by execution order + modArray.sort((a, b) -> Std.int(a.getOrder() - b.getOrder())); + } + + public function get(modName:String):Modifier { + return register.get(modName); + } + + inline public function getPercent(modName:String, player:Int):Float { + return register.get(modName).getPercent(player); + } + + inline public function getValue(modName:String, player:Int):Float { + return register.get(modName).getValue(player); + } + + inline public function setPercent(modName:String, percent:Float, player:Int = -1) { + setValue(modName, percent / 100, player); + } + + /** + * Sets modifier value and manages active modifier list + */ + public function setValue(modName:String, value:Float, player:Int = -1) { + if (player == -1) { + // Apply to all players if no specific player specified + for (playerNum in 0...2) { + setValue(modName, value, playerNum); + } + } else { + var modifier = register.get(modName); + var parentMod = modifier.parent ?? modifier; + var parentName = parentMod.getName(); + + activeMods[player] ??= []; + modifier.setValue(value, player); + + updateActiveModifiersList(modName, parentName, value, player, modifier, parentMod); + } + } + + /** + * Updates the active modifiers list based on current modifier states + */ + private function updateActiveModifiersList( + modName:String, + parentName:String, + value:Float, + player:Int, + modifier:Modifier, + parentMod:Modifier + ) { + var shouldExecute = parentMod.shouldExecute(player, value); + + if (!activeMods[player].contains(parentName) && shouldExecute) { + // Add modifier and its parent to active list + if (modifier.getName() != parentName) { + activeMods[player].push(modifier.getName()); + } + activeMods[player].push(parentName); + } else if (!shouldExecute) { + // Remove modifier from active list + removeModifierFromActiveList(modName, parentName, player, modifier, parentMod); + } + + // Maintain execution order + activeMods[player].sort((a, b) -> Std.int(register.get(a).getOrder() - register.get(b).getOrder())); + } + + /** + * Removes modifier from active list after checking dependencies + */ + private function removeModifierFromActiveList( + modName:String, + parentName:String, + player:Int, + modifier:Modifier, + parentMod:Modifier + ) + { + // Remove the specific modifier + if (modifier != parentMod) { + activeMods[player].remove(modifier.getName()); + } + + // Check if parent modifier should remain active + if (parentMod != null) { + var shouldKeepParent = parentMod.shouldExecute(player, parentMod.getValue(player)) || + hasActiveSubmodifiers(parentMod, player); + + if (!shouldKeepParent) { + activeMods[player].remove(parentMod.getName()); + } + } else { + activeMods[player].remove(modifier.getName()); + } + } + + /** + * Checks if any submodifiers of a parent are still active + */ + private function hasActiveSubmodifiers(parentMod:Modifier, player:Int):Bool { + for (submod in parentMod.submods) { + if (submod.shouldExecute(player, submod.getValue(player))) { + return true; + } + } + return false; + } + + public function update(elapsed:Float) { + for (mod in modArray) { + if (mod.active && mod.doesUpdate()) { + mod.update(elapsed); + } + } + } + + public function updateTimeline(curStep:Float) { + timeline.update(curStep); + } + + /** + * Updates visual properties of game objects based on active modifiers + */ + public function updateObject(beat:Float, obj:FlxSprite, position:Vector3, player:Int) { + for (modName in activeMods[player]) { + var modifier = notemodRegister.get(modName); + + if (modifier == null || !obj.active) continue; + + if (Std.isOfType(obj, Note)) { + var note:Note = cast obj; + modifier.updateNote(beat, note, position, player); + } else if (Std.isOfType(obj, StrumNote)) { + var strum:StrumNote = cast obj; + modifier.updateReceptor(beat, strum, position, player); + } + } + + // Update object properties after modifier applications + if (Std.isOfType(obj, Note)) { + obj.updateHitbox(); + } + + obj.centerOrigin(); + obj.centerOffsets(); + + // Apply type-specific offsets for notes + if (Std.isOfType(obj, Note)) { + var note:Note = cast obj; + note.offset.x += note.typeOffsetX; + note.offset.y += note.typeOffsetY; + } + } + + /** + * Gets the base X position for a note based on direction and player + */ + public function getBaseX(direction:Int, player:Int):Float { + var baseX:Float = PlayState.STRUM_X_MIDDLESCROLL + Note.swagWidth * direction; + + if (ClientPrefs.middleScroll) { + if (player == 1) { // opponent strums + baseX += 360; + if (direction > 1) { + baseX += FlxG.width / 2 + 25; + } + } else { // player strums + var totalWidth = Note.swagWidth * 4; + var screenCenter = FlxG.width / 2; + baseX = screenCenter - totalWidth / 2 + Note.swagWidth * direction; + } + } else { + baseX = (FlxG.width / 2) - Note.swagWidth - 54 + Note.swagWidth * direction; + + switch (player) { + case 0: // player strums + baseX += FlxG.width / 2 - Note.swagWidth * 2 - 100; + case 1: // opponent strums + baseX -= FlxG.width / 2 - Note.swagWidth * 2 - 100; + } + baseX -= 56; + } + + return baseX; + } + + /** + * Calculates visual position based on song timing + */ + inline public function getVisPos(songPos:Float = 0, strumTime:Float = 0, songSpeed:Float = 1):Float { + return -(0.45 * (songPos - strumTime) * songSpeed); + } + + /** + * Gets the final position of an object after applying all modifier transformations + */ + public function getPos( + time:Float, + diff:Float, + tDiff:Float, + beat:Float, + data:Int, + player:Int, + obj:FlxSprite, + ?exclusions:Array, + ?position:Vector3 + ):Vector3 { + exclusions = exclusions ?? []; + position = position ?? new Vector3(); + + if (!obj.active) return position; + + // Start with base position + position.x = getBaseX(data, player); + position.y = 50 + diff; + position.z = 0; + + // Apply all active modifiers + for (modName in activeMods[player]) { + if (exclusions.contains(modName)) continue; + + var modifier = notemodRegister.get(modName); + if (modifier == null || !obj.active) continue; + + position = modifier.getPos(time, diff, tDiff, beat, position, data, player, obj); + } + + return position; + } + + public function queueEaseP(step:Float, endStep:Float, modName:String, percent:Float, style:String = 'linear', player:Int = -1, ?startPercent:Float) { + queueEase(step, endStep, modName, percent / 100, style, player, startPercent != null ? startPercent / 100 : null); + } + + public function queueSetP(step:Float, modName:String, percent:Float, player:Int = -1) { + queueSet(step, modName, percent / 100, player); + } + + public function queueEase(step:Float, endStep:Float, modName:String, target:Float, style:String = 'linear', player:Int = -1, ?startValue:Float) { + if (player == -1) { + queueEase(step, endStep, modName, target, style, 0, startValue); + queueEase(step, endStep, modName, target, style, 1, startValue); + } else { + var easeFunction = getEaseFunction(style); + timeline.addEvent(new EaseEvent(step, endStep, modName, target, easeFunction, player, this)); + } + } + + public function queueSet(step:Float, modName:String, target:Float, player:Int = -1) { + if (player == -1) { + queueSet(step, modName, target, 0); + queueSet(step, modName, target, 1); + } else { + timeline.addEvent(new SetEvent(step, modName, target, player, this)); + } + } + + public function queueFunc(step:Float, endStep:Float, callback:(CallbackEvent, Float) -> Void) { + timeline.addEvent(new StepCallbackEvent(step, endStep, callback, this)); + } + + public function queueFuncOnce(step:Float, callback:(CallbackEvent, Float) -> Void) { + timeline.addEvent(new CallbackEvent(step, callback, this)); + } + + /** + * Gets easing function by name, defaults to linear + */ + private function getEaseFunction(style:String):Float->Float + { + var easeFunc:Float->Float = FlxEase.linear; + + try { + var customEase = Reflect.getProperty(FlxEase, style); + if (customEase != null) { + easeFunc = customEase; + } + } catch (e:Dynamic) { + trace('Ease function "$style" not found, using linear'); + } + + return easeFunc; + } +} \ No newline at end of file diff --git a/source/game/modchart/Modcharts.hx b/source/game/modchart/Modcharts.hx new file mode 100644 index 0000000..13df0cb --- /dev/null +++ b/source/game/modchart/Modcharts.hx @@ -0,0 +1,29 @@ +package game.modchart; + +import flixel.math.FlxAngle; + +import game.modchart.*; +import game.modchart.events.CallbackEvent; + +class Modcharts { + static function numericForInterval(start, end, interval, func){ + var index = start; + while(index < end){ + func(index); + index += interval; + } + } + + static var songs = ["fresh"]; + public static function isModcharted(songName:String){ + if (songs.contains(songName.toLowerCase())) + return true; + + // add other conditionals if needed + + //return true; // turns modchart system on for all songs, only use for like.. debugging + return false; + } + + public static function loadModchart(modManager:ModManager, songName:String) {} +} \ No newline at end of file diff --git a/source/game/modchart/Modifier.hx b/source/game/modchart/Modifier.hx new file mode 100644 index 0000000..5e4e201 --- /dev/null +++ b/source/game/modchart/Modifier.hx @@ -0,0 +1,245 @@ +package game.modchart; + +import flixel.FlxSprite; +import math.Vector3; + +// Based on Schmovin' and Andromeda's modifier systems + +/** + * Types of modifiers that can be applied + */ +enum ModifierType { + NOTE_MOD; // Affects note positioning and movement + MISC_MOD; // Affects other gameplay elements +} + +/** + * Execution order constants for modifier prioritization + */ +enum abstract ModifierOrder(Int) to Int { + var FIRST = -1000; // Highest priority - executes first + var PRE_REVERSE = -3; // Before reverse operations + var REVERSE = -2; // Reverse operations + var POST_REVERSE = -1; // After reverse operations + var DEFAULT = 0; // Standard execution order + var LAST = 1000; // Lowest priority - executes last +} + +/** + * Base class for all gameplay modifiers + * Handles visual transformations and effects on notes and receptors + */ +class Modifier { + public var modMgr:ModManager; // Reference to the modifier manager + public var percents:Array = [0, 0]; // Modifier intensity per player [player1, player2] + public var submods:Map = []; // Child modifiers + public var parent:Modifier; // Parent modifier for submodifiers + public var active:Bool = false; // Performance optimization flag + + public function new(modMgr:ModManager, ?parent:Modifier) { + this.modMgr = modMgr; + this.parent = parent; + + // Initialize all declared submodifiers + for (submodName in getSubmods()) { + submods.set(submodName, new SubModifier(submodName, modMgr, this)); + } + } + + /** + * Returns the type of modifier (note-affecting or miscellaneous) + * Override in subclasses for note modifiers + */ + public function getModType():ModifierType { + return MISC_MOD; + } + + /** + * Returns the execution order priority + * Override in subclasses to control modifier execution order + */ + public function getOrder():Int { + return DEFAULT; + } + + /** + * Returns whether this modifier should update each frame + * Override for modifiers that need continuous updates + */ + public function doesUpdate():Bool { + return getModType() == MISC_MOD; + } + + /** + * Returns whether this modifier should ignore position calculations + * Override to true for non-position-affecting modifiers + */ + public function ignorePos():Bool { + return false; + } + + /** + * Returns whether this modifier should skip receptor updates + * Override to false for modifiers that affect receptors + */ + public function ignoreUpdateReceptor():Bool { + return true; + } + + /** + * Returns whether this modifier should skip note updates + * Override to false for modifiers that affect notes + */ + public function ignoreUpdateNote():Bool { + return true; + } + + /** + * Determines if modifier should execute based on current value + * Override for modifiers that need to run even at 0% intensity + */ + public function shouldExecute(player:Int, value:Float):Bool { + return value != 0; + } + + /** + * Returns the unique name identifier for this modifier + * MUST be overridden in subclasses + */ + public function getName():String { + throw new haxe.exceptions.NotImplementedException("Modifier name must be implemented in subclass"); + return ''; + } + + /** + * Gets the raw value (0-1) for the specified player + */ + public function getValue(player:Int):Float { + return percents[player]; + } + + /** + * Gets the percentage value (0-100) for the specified player + */ + public function getPercent(player:Int):Float { + return getValue(player) * 100; + } + + /** + * Sets the raw value (0-1) for the specified player(s) + */ + public function setValue(value:Float, player:Int = -1) { + if (player == -1) { + // Apply to all players + for (idx in 0...percents.length) { + percents[idx] = value; + } + } else { + percents[player] = value; + } + } + + /** + * Sets the percentage value (0-100) for the specified player(s) + */ + public function setPercent(percent:Float, player:Int = -1) { + setValue(percent / 100, player); + } + + /** + * Returns list of submodifier names this modifier contains + * Override in subclasses that have child modifiers + */ + public function getSubmods():Array { + return []; + } + + /** + * Gets percentage value of a submodifier + */ + public function getSubmodPercent(modName:String, player:Int):Float { + return submods.exists(modName) ? submods.get(modName).getPercent(player) : 0; + } + + /** + * Gets raw value of a submodifier + */ + public function getSubmodValue(modName:String, player:Int):Float { + return submods.exists(modName) ? submods.get(modName).getValue(player) : 0; + } + + /** + * Sets percentage value of a submodifier + */ + public function setSubmodPercent(modName:String, endPercent:Float, player:Int) { + if (submods.exists(modName)) { + submods.get(modName).setPercent(endPercent, player); + } + } + + /** + * Sets raw value of a submodifier + */ + public function setSubmodValue(modName:String, endValue:Float, player:Int) { + if (submods.exists(modName)) { + submods.get(modName).setValue(endValue, player); + } + } + + /** + * Updates receptor visual properties + * Override in subclasses that affect receptors + * + * @param beat Current beat with decimal precision + * @param receptor The strum note receptor to update + * @param pos Current position vector + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + */ + public function updateReceptor(beat:Float, receptor:StrumNote, pos:Vector3, player:Int) { + // Base implementation - override in subclasses + } + + /** + * Updates note visual properties + * Override in subclasses that affect notes + * + * @param beat Current beat with decimal precision + * @param note The note to update + * @param pos Current position vector + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + */ + public function updateNote(beat:Float, note:Note, pos:Vector3, player:Int) { + // Base implementation - override in subclasses + } + + /** + * Calculates and returns modified position for game objects + * Override in subclasses that affect object positioning + * + * @param time Note/receptor strum time + * @param diff Visual difference (strumTime - currentTime with scroll speed math) + * @param tDiff Time difference + * @param beat Current beat with decimal precision + * @param pos Current position vector + * @param data Column/direction/note data + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector + */ + public function getPos( + time:Float, + diff:Float, + tDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + return pos; // Base implementation returns original position + } + + public function update(elapsed:Float) { + // Base implementation - override in subclasses + } +} \ No newline at end of file diff --git a/source/game/modchart/NoteModifier.hx b/source/game/modchart/NoteModifier.hx new file mode 100644 index 0000000..7946c16 --- /dev/null +++ b/source/game/modchart/NoteModifier.hx @@ -0,0 +1,8 @@ +package game.modchart; + +import game.modchart.Modifier.ModifierType; + +class NoteModifier extends Modifier { + override function getModType() + return NOTE_MOD; // tells the mod manager to call this modifier when updating receptors/notes +} \ No newline at end of file diff --git a/source/game/modchart/SubModifier.hx b/source/game/modchart/SubModifier.hx new file mode 100644 index 0000000..b492d87 --- /dev/null +++ b/source/game/modchart/SubModifier.hx @@ -0,0 +1,16 @@ +package game.modchart; + +class SubModifier extends Modifier { + var name:String = 'unspecified'; + + override function getName() return name; + + override function getOrder() return Modifier.ModifierOrder.LAST; + + override function doesUpdate() return false; + + public function new(name:String, modMgr:ModManager, ?parent:Modifier) { + super(modMgr, parent); + this.name = name; + } +} \ No newline at end of file diff --git a/source/game/modchart/events/BaseEvent.hx b/source/game/modchart/events/BaseEvent.hx new file mode 100644 index 0000000..9176859 --- /dev/null +++ b/source/game/modchart/events/BaseEvent.hx @@ -0,0 +1,15 @@ +package game.modchart.events; + +class BaseEvent { + public var manager:ModManager; + public var executionStep:Float = 0; + public var ignoreExecution:Bool = false; + public var finished:Bool = false; + public function new(step:Float, manager:ModManager) + { + this.manager = manager; + this.executionStep = step; + } + + public function run(curStep:Float){} +} \ No newline at end of file diff --git a/source/game/modchart/events/CallbackEvent.hx b/source/game/modchart/events/CallbackEvent.hx new file mode 100644 index 0000000..243b686 --- /dev/null +++ b/source/game/modchart/events/CallbackEvent.hx @@ -0,0 +1,15 @@ +package game.modchart.events; + +class CallbackEvent extends BaseEvent { + public var callback:(CallbackEvent, Float)->Void; + public function new(step:Float, callback:(CallbackEvent, Float)->Void, modMgr:ModManager) + { + super(step, modMgr); + this.callback = callback; + } + + override function run(curStep:Float){ + callback(this, curStep); + finished = true; + } +} \ No newline at end of file diff --git a/source/game/modchart/events/EaseEvent.hx b/source/game/modchart/events/EaseEvent.hx new file mode 100644 index 0000000..75f7947 --- /dev/null +++ b/source/game/modchart/events/EaseEvent.hx @@ -0,0 +1,101 @@ +package game.modchart.events; + +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; + +/** + * Handles smooth transitions of modifier values over time + * Applies easing functions to gradually change modifier values between steps + */ +class EaseEvent extends ModEvent { + public var endStep:Float = 0; // Step when the ease animation completes + public var startVal:Null; // Starting value (null = use current value) + public var easeFunc:EaseFunction; // Easing function to use for transition + public var length:Float = 0; // Total duration in steps + + /** + * Creates a new ease event for smooth modifier value transitions + * + * @param step Starting step for the ease animation + * @param endStep Ending step for the ease animation + * @param modName Name of the modifier to animate + * @param target Target value to ease towards + * @param easeFunc Easing function to use for the transition + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param modMgr Reference to the modifier manager + * @param startVal Optional starting value (uses current value if null) + */ + public function new( + step:Float, + endStep:Float, + modName:String, + target:Float, + easeFunc:EaseFunction, + player:Int = 0, + modMgr:ModManager, + ?startVal:Float + ) { + super(step, modName, target, player, modMgr); + + this.endStep = endStep; + this.easeFunc = easeFunc; + this.startVal = startVal; + + if (mod == null) + trace('Warning: Modifier "$modName" is null!'); + + length = endStep - step; + } + + /** + * Custom easing function that mimics FlxTween behavior + * + * @param e Easing function reference + * @param t Current time (progress through animation) + * @param b Beginning value (start value) + * @param c Change in value (target - start) + * @param d Total duration + * @return Current eased value + */ + private function ease(e:EaseFunction, t:Float, b:Float, c:Float, d:Float):Float { + var normalizedTime = t / d; + return c * e(normalizedTime) + b; + } + + /** + * Executes the ease event based on current step progress + * + * @param curStep Current step in the timeline + */ + override function run(curStep:Float) { + if (curStep <= endStep) { + // Animation in progress - calculate current eased value + executeEaseStep(curStep); + } else { + // Animation complete - set final value + completeEaseAnimation(); + } + } + + /** + * Calculates and applies the current eased value during animation + */ + private function executeEaseStep(curStep:Float) { + if (startVal == null) + startVal = mod.getValue(player); + + var timePassed = curStep - executionStep; + var valueChange = endVal - startVal; + + var currentValue = ease(easeFunc, timePassed, startVal, valueChange, length); + manager.setValue(modName, currentValue, player); + } + + /** + * Finalizes the ease animation by setting the target value + */ + private function completeEaseAnimation() { + finished = true; + manager.setValue(modName, endVal, player); + } +} \ No newline at end of file diff --git a/source/game/modchart/events/ModEvent.hx b/source/game/modchart/events/ModEvent.hx new file mode 100644 index 0000000..ef102af --- /dev/null +++ b/source/game/modchart/events/ModEvent.hx @@ -0,0 +1,19 @@ +package game.modchart.events; + +class ModEvent extends BaseEvent { + public var modName:String = ''; + public var endVal:Float = 0; + public var player:Int = -1; + + private var mod:Modifier; + + public function new(step:Float, modName:String, target:Float, player:Int = -1, modMgr:ModManager) + { + super(step, modMgr); + this.modName = modName; + this.player = player; + endVal = target; + + this.mod = modMgr.get(modName); + } +} \ No newline at end of file diff --git a/source/game/modchart/events/SetEvent.hx b/source/game/modchart/events/SetEvent.hx new file mode 100644 index 0000000..71ace0f --- /dev/null +++ b/source/game/modchart/events/SetEvent.hx @@ -0,0 +1,10 @@ +package game.modchart.events; + +class SetEvent extends ModEvent { + override function run(curStep:Float) + { + //mod.setValue(endVal, player); + manager.setValue(modName, endVal, player); + finished = true; + } +} \ No newline at end of file diff --git a/source/game/modchart/events/StepCallbackEvent.hx b/source/game/modchart/events/StepCallbackEvent.hx new file mode 100644 index 0000000..fb930a6 --- /dev/null +++ b/source/game/modchart/events/StepCallbackEvent.hx @@ -0,0 +1,18 @@ +package game.modchart.events; + +class StepCallbackEvent extends CallbackEvent +{ + public var endStep:Float = 0; + public function new(step:Float, endStep:Float, callback:(CallbackEvent, Float)->Void, modMgr:ModManager) + { + super(step, callback, modMgr); + this.endStep = endStep; + } + + override function run(curStep:Float){ + if(curStep <= endStep) + callback(this, curStep); + else + finished = true; + } +} \ No newline at end of file diff --git a/source/game/modchart/import.hx b/source/game/modchart/import.hx new file mode 100644 index 0000000..2e7885d --- /dev/null +++ b/source/game/modchart/import.hx @@ -0,0 +1,4 @@ +import game.objects.Note; +import game.objects.StrumNote; + +import flixel.math.FlxMath; \ No newline at end of file diff --git a/source/game/modchart/modifiers/AlphaModifier.hx b/source/game/modchart/modifiers/AlphaModifier.hx new file mode 100644 index 0000000..900dc72 --- /dev/null +++ b/source/game/modchart/modifiers/AlphaModifier.hx @@ -0,0 +1,236 @@ +package game.modchart.modifiers; + +import flixel.FlxG; +import flixel.math.FlxMath; +import flixel.math.FlxPoint; +import math.*; +import game.modchart.*; + +/** + * Handles note and receptor visibility effects including: + * - Stealth (partial/full invisibility) + * - Hidden (fade out from bottom) + * - Sudden (fade out from top) + * - Blink (pulsing visibility) + * - RandomVanish (distance-based fading) + * - Dark (receptor dimming) + */ +class AlphaModifier extends NoteModifier { + private static final FADE_DISTANCE_Y:Float = 120; // Distance for fade effects + + override function getName():String { + return 'stealth'; + } + + override function ignorePos():Bool { + return true; // This modifier doesn't affect position, only visibility + } + + override function shouldExecute(player:Int, val:Float):Bool { + return true; // Always execute to handle submodifiers + } + + override function ignoreUpdateReceptor():Bool { + return false; // This modifier affects receptors + } + + override function ignoreUpdateNote():Bool { + return false; // This modifier affects notes + } + + override function getSubmods():Array { + var subMods:Array = [ + "noteAlpha", "alpha", "hidden", "hiddenOffset", "sudden", + "suddenOffset", "blink", "randomVanish", "dark", + "useStealthGlow", "stealthPastReceptors" + ]; + + // Add per-noteData variants for specific column effects + for (i in 0...4) { + subMods.push('noteAlpha$i'); + subMods.push('alpha$i'); + subMods.push('dark$i'); + } + + return subMods; + } + + /** + * Calculates combined hidden+sudden effect intensity + */ + private function getHiddenSudden(player:Int = -1):Float { + return getSubmodValue("hidden", player) * getSubmodValue("sudden", player); + } + + /** + * Calculates the end boundary for hidden (bottom fade) effect + */ + private function getHiddenEnd(player:Int = -1):Float { + var centerY = FlxG.height / 2; + var hiddenScale = MathUtil.scale(getHiddenSudden(player), 0, 1, -1, -1.25); + var hiddenOffset = (FlxG.height / 2) * getSubmodValue("hiddenOffset", player); + + return centerY + FADE_DISTANCE_Y * hiddenScale + hiddenOffset; + } + + /** + * Calculates the start boundary for hidden (bottom fade) effect + */ + private function getHiddenStart(player:Int = -1):Float { + var centerY = FlxG.height / 2; + var hiddenScale = MathUtil.scale(getHiddenSudden(player), 0, 1, 0, -0.25); + var hiddenOffset = (FlxG.height / 2) * getSubmodValue("hiddenOffset", player); + + return centerY + FADE_DISTANCE_Y * hiddenScale + hiddenOffset; + } + + /** + * Calculates the end boundary for sudden (top fade) effect + */ + private function getSuddenEnd(player:Int = -1):Float { + var centerY = FlxG.height / 2; + var suddenScale = MathUtil.scale(getHiddenSudden(player), 0, 1, 1, 1.25); + var suddenOffset = (FlxG.height / 2) * getSubmodValue("suddenOffset", player); + + return centerY + FADE_DISTANCE_Y * suddenScale + suddenOffset; + } + + /** + * Calculates the start boundary for sudden (top fade) effect + */ + private function getSuddenStart(player:Int = -1):Float { + var centerY = FlxG.height / 2; + var suddenScale = MathUtil.scale(getHiddenSudden(player), 0, 1, 0, 0.25); + var suddenOffset = (FlxG.height / 2) * getSubmodValue("suddenOffset", player); + + return centerY + FADE_DISTANCE_Y * suddenScale + suddenOffset; + } + + // ==================== VISIBILITY CALCULATIONS ==================== + + /** + * Calculates overall visibility based on all alpha effects + */ + private function getVisibility(yPos:Float, player:Int, note:Note):Float { + var alpha:Float = 0; + var distFromCenter = yPos; + + // Skip stealth effects for notes past receptors if configured + if (yPos < 0 && getSubmodValue("stealthPastReceptors", player) == 0) { + return 1.0; + } + + var currentTime = Conductor.songPosition / 1000; + + // Apply hidden (bottom fade) effect + if (getSubmodValue("hidden", player) != 0) { + var hiddenAdjust = MathUtil.clamp( + MathUtil.scale(yPos, getHiddenStart(player), getHiddenEnd(player), 0, -1), + -1, 0 + ); + alpha += getSubmodValue("hidden", player) * hiddenAdjust; + } + + // Apply sudden (top fade) effect + if (getSubmodValue("sudden", player) != 0) { + var suddenAdjust = MathUtil.clamp( + MathUtil.scale(yPos, getSuddenStart(player), getSuddenEnd(player), 0, -1), + -1, 0 + ); + alpha += getSubmodValue("sudden", player) * suddenAdjust; + } + + // Apply base stealth effect + if (getValue(player) != 0) { + alpha -= getValue(player); + } + + // Apply blinking effect + if (getSubmodValue("blink", player) != 0) { + var blinkFactor = MathUtil.quantize(FlxMath.fastSin(currentTime * 10), 0.3333); + alpha += MathUtil.scale(blinkFactor, 0, 1, -1, 0); + } + + // Apply random vanish (distance-based fading) effect + if (getSubmodValue("randomVanish", player) != 0) { + var realFadeDist:Float = 240; + var vanishAdjust = MathUtil.scale( + Math.abs(distFromCenter), + realFadeDist, + 2 * realFadeDist, + -1, 0 + ) * getSubmodValue("randomVanish", player); + alpha += vanishAdjust; + } + + return MathUtil.clamp(alpha + 1, 0, 1); + } + + /** + * Calculates glow intensity based on visibility + */ + private function getGlow(visibility:Float):Float { + var glow = MathUtil.scale(visibility, 1, 0.5, 0, 1.3); + return MathUtil.clamp(glow, 0, 1); + } + + /** + * Calculates alpha transparency based on visibility + */ + private function getAlpha(visibility:Float):Float { + var alpha = MathUtil.scale(visibility, 0.5, 0, 1, 0); + return MathUtil.clamp(alpha, 0, 1); + } + + // ==================== NOTE VISIBILITY UPDATES ==================== + + override function updateNote(beat:Float, note:Note, pos:Vector3, player:Int) { + var actualPlayer = note.mustPress ? 0 : 1; + + // Get current note position (excluding reverse modifier for accurate Y calculation) + @:privateAccess + var currentPos = modMgr.getPos( + note.strumTime, + modMgr.getVisPos(Conductor.songPosition, note.strumTime, PlayState.instance.songSpeed), + note.strumTime - Conductor.songPosition, + PlayState.instance.curBeat, + note.noteData, + actualPlayer, + note, + ["reverse"] // Exclude reverse modifier from position calculation + ); + + // Calculate base alpha modifiers + var alphaMod = (1 - getSubmodValue("alpha", actualPlayer)) + * (1 - getSubmodValue('alpha${note.noteData}', actualPlayer)) + * (1 - getSubmodValue("noteAlpha", actualPlayer)) + * (1 - getSubmodValue('noteAlpha${note.noteData}', actualPlayer)); + + var visibility = getVisibility(currentPos.y, actualPlayer, note); + + // Apply stealth glow effect or direct alpha + if (getSubmodValue("dontUseStealthGlow", actualPlayer) == 0) { + note.colorSwap.daAlpha = getAlpha(visibility); + note.colorSwap.flash = getGlow(visibility); + } else { + note.colorSwap.daAlpha = visibility; + } + + // Apply additional alpha modifications + note.colorSwap.daAlpha *= alphaMod; + } + + override function updateReceptor(beat:Float, receptor:StrumNote, pos:Vector3, player:Int) { + var alpha = (1 - getSubmodValue("alpha", player)) + * (1 - getSubmodValue('alpha${receptor.noteData}', player)); + + // Apply dark (dimming) effect + if (getSubmodValue("dark", player) != 0 || getSubmodValue('dark${receptor.noteData}', player) != 0) { + alpha = alpha * (1 - getSubmodValue("dark", player)) + * (1 - getSubmodValue('dark${receptor.noteData}', player)); + } + + @:privateAccess + receptor.colorSwap.daAlpha = alpha; + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/BeatModifier.hx b/source/game/modchart/modifiers/BeatModifier.hx new file mode 100644 index 0000000..7e812d1 --- /dev/null +++ b/source/game/modchart/modifiers/BeatModifier.hx @@ -0,0 +1,138 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import flixel.math.FlxMath; +import flixel.math.FlxPoint; + +import math.*; +import game.modchart.*; + +/** + * Creates a rhythmic wobbling effect on notes based on the beat + * Notes move horizontally in sync with the music beat for visual emphasis + */ +class BeatModifier extends NoteModifier { + override function getName():String { + return 'beat'; + } + + /** + * Calculates and applies beat-based horizontal movement to note positions + * Creates a wobble effect that accelerates and decelerates with each beat + * + * @param time Note strum time + * @param visualDiff Visual position difference (strumTime - currentTime with scroll speed) + * @param timeDiff Time difference (strumTime - currentTime) + * @param beat Current beat with decimal precision + * @param pos Current position vector to modify + * @param data Note direction/column + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector with beat wobble applied + */ + override function getPos( + time:Float, + visualDiff:Float, + timeDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + // Early return if modifier is disabled for this player + if (getValue(player) == 0) { + return pos; + } + + var wobbleAmount = calculateBeatWobble(visualDiff); + pos.x += getValue(player) * wobbleAmount; + + return pos; + } + + /** + * Calculates the horizontal wobble amount based on current beat progression + * Uses smooth acceleration and deceleration for natural movement + */ + private function calculateBeatWobble(visualDiff:Float):Float { + // Animation timing constants + final ACCELERATION_TIME:Float = 0.3; // Time spent accelerating (in beats) + final TOTAL_CYCLE_TIME:Float = 0.7; // Total beat cycle time (in beats) + + @:privateAccess + var currentBeat:Float = PlayState.instance.curBeat + ACCELERATION_TIME; + + // Don't apply effect for negative beats (before song starts) + if (currentBeat < 0) { + return 0; + } + + var beatProgress = getNormalizedBeatProgress(currentBeat, TOTAL_CYCLE_TIME); + + // Return 0 if we're outside the active wobble portion of the cycle + if (beatProgress >= TOTAL_CYCLE_TIME) { + return 0; + } + + var wobbleIntensity = calculateWobbleIntensity(beatProgress, ACCELERATION_TIME, TOTAL_CYCLE_TIME); + var directionalShift = applyWobbleDirection(wobbleIntensity, visualDiff); + + return directionalShift; + } + + /** + * Normalizes the beat value to a 0-1 range within the cycle + * Handles beat wrapping and fractional beat calculations + */ + private function getNormalizedBeatProgress(currentBeat:Float, cycleTime:Float):Float { + var isEvenBeat = (currentBeat % 2) != 0; + var fractionalBeat = currentBeat - Math.floor(currentBeat); + + // Ensure we have a continuous 0-1 progression + fractionalBeat += 1; + fractionalBeat -= Math.floor(fractionalBeat); + + return fractionalBeat; + } + + /** + * Calculates the intensity of wobble using easing functions + * Applies smooth acceleration and deceleration + */ + private function calculateWobbleIntensity(beatProgress:Float, accelTime:Float, totalTime:Float):Float { + var intensity:Float = 0; + + if (beatProgress < accelTime) { + // Acceleration phase - ease in + intensity = MathUtil.scale(beatProgress, 0, accelTime, 0, 1); + intensity *= intensity; // Quadratic ease in + } else { + // Deceleration phase - ease out + intensity = MathUtil.scale(beatProgress, accelTime, totalTime, 1, 0); + intensity = 1 - (1 - intensity) * (1 - intensity); // Quadratic ease out + } + + return intensity; + } + + /** + * Applies directional and positional factors to the wobble intensity + * Creates the final horizontal shift value + */ + private function applyWobbleDirection(wobbleIntensity:Float, visualDiff:Float):Float { + @:privateAccess + var isEvenBeat = (PlayState.instance.curBeat % 2) != 0; + + // Alternate direction on even/odd beats + if (isEvenBeat) { + wobbleIntensity *= -1; + } + + // Apply sine wave based on visual position for wave-like effect + var baseShift = 40 * wobbleIntensity; + var waveEffect = FlxMath.fastSin((visualDiff / 30) + Math.PI / 2); + + return baseShift * waveEffect; + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/ConfusionModifier.hx b/source/game/modchart/modifiers/ConfusionModifier.hx new file mode 100644 index 0000000..5f98f29 --- /dev/null +++ b/source/game/modchart/modifiers/ConfusionModifier.hx @@ -0,0 +1,102 @@ +package game.modchart.modifiers; + +import flixel.math.FlxPoint; +import flixel.math.FlxMath; + +import game.modchart.*; +import math.*; + +/** + * Applies rotation effects to notes and receptors + * Creates visual disorientation by rotating gameplay elements at various angles + */ +class ConfusionModifier extends NoteModifier { + override function getName():String { + return 'confusion'; + } + + /** + * Always execute this modifier to handle submodifiers even when base value is 0 + */ + override function shouldExecute(player:Int, val:Float):Bool { + return true; + } + + /** + * Returns all submodifiers supported by this modifier + * Includes per-noteData variants for column-specific effects + */ + override function getSubmods():Array { + var subMods:Array = [ + "noteAngle", // Global note angle offset + "receptorAngle" // Global receptor angle offset + ]; + + // Add per-column submodifiers for fine-tuned control + for (i in 0...4) { + subMods.push('note${i}Angle'); // Note angle for specific column + subMods.push('receptor${i}Angle'); // Receptor angle for specific column + subMods.push('confusion${i}'); // Confusion intensity for specific column + } + + return subMods; + } + + /** + * Updates note rotation based on confusion values + * Regular notes use dynamic angles, sustain notes preserve their original angle + * + * @param beat Current beat with decimal precision + * @param note The note to update + * @param pos Current position vector + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + */ + override function updateNote(beat:Float, note:Note, pos:Vector3, player:Int) { + if (!note.isSustainNote) { + // Calculate total rotation angle for regular notes + var totalAngle = calculateNoteAngle(note, player); + note.angle = totalAngle; + } else { + // Sustain notes keep their original angle to maintain visual consistency + note.angle = note.mAngle; + } + } + + /** + * Calculates the total rotation angle for a note + * Combines global, per-column, and note-specific angle modifiers + */ + private function calculateNoteAngle(note:Note, player:Int):Float { + var baseConfusion = getValue(player); // Global confusion intensity + var columnConfusion = getSubmodValue('confusion${note.noteData}', player); // Column-specific confusion + var noteAngle = getSubmodValue('note${note.noteData}Angle', player); // Note-specific angle offset + + return baseConfusion + columnConfusion + noteAngle; + } + + /** + * Updates receptor rotation based on confusion values + * Receptors rotate independently of notes for visual variety + * + * @param beat Current beat with decimal precision + * @param receptor The strum note receptor to update + * @param pos Current position vector + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + */ + override function updateReceptor(beat:Float, receptor:StrumNote, pos:Vector3, player:Int) { + var totalAngle = calculateReceptorAngle(receptor, player); + receptor.angle = totalAngle; + } + + /** + * Calculates the total rotation angle for a receptor + * Combines global, per-column, and receptor-specific angle modifiers + */ + private function calculateReceptorAngle(receptor:StrumNote, player:Int):Float { + var baseConfusion = getValue(player); // Global confusion intensity + var columnConfusion = getSubmodValue('confusion${receptor.noteData}', player); // Column-specific confusion + var receptorAngle = getSubmodValue('receptor${receptor.noteData}Angle', player); // Receptor-specific angle offset + + return baseConfusion + columnConfusion + receptorAngle; + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/DrunkModifier.hx b/source/game/modchart/modifiers/DrunkModifier.hx new file mode 100644 index 0000000..86577e5 --- /dev/null +++ b/source/game/modchart/modifiers/DrunkModifier.hx @@ -0,0 +1,137 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import ui.*; +import game.modchart.*; +import flixel.math.FlxPoint; +import flixel.math.FlxMath; +import flixel.FlxG; +import math.*; + +/** + * Applies various wobble and wave effects to note positions + * Simulates visual distortion effects like drunkenness, tipsiness, and bumpy movement + */ +class DrunkModifier extends NoteModifier { + override function getName():String { + return 'drunk'; + } + + /** + * Returns all submodifiers for controlling different aspects of drunk effects + */ + override function getSubmods():Array { + return [ + "tipsy", "tipsySpeed", "tipsyOffset", // Tipsy effect - vertical wobble + "bumpy", "bumpyOffset", "bumpyPeriod", // Tipsy effect - vertical wobble + "drunkSpeed", "drunkOffset", "drunkPeriod", // Drunk effect - horizontal wobble + "tipZ", "tipZSpeed", "tipZOffset", // Drunk effect - horizontal wobble + "drunkZ", "drunkZSpeed", "drunkZOffset", "drunkZPeriod" // DrunkZ effect - Z-axis drunk wobble + ]; + } + + /** + * Applies various drunk effects to note positions + * Combines multiple wave-based distortions for complex visual effects + */ + override function getPos( + time:Float, + visualDiff:Float, + timeDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + applyTipsyEffect(pos, data, player); + applyDrunkEffect(pos, visualDiff, data, player); + applyTipZEffect(pos, data, player); + applyBumpyEffect(pos, visualDiff, player); + + return pos; + } + + /** + * Applies tipsy effect - vertical wobble using cosine waves + * Creates up-and-down bouncing motion + */ + private function applyTipsyEffect(pos:Vector3, noteData:Int, player:Int):Void { + var tipsyIntensity = getSubmodValue("tipsy", player); + + if (tipsyIntensity != 0) { + var speed = getSubmodValue("tipsySpeed", player); + var offset = getSubmodValue("tipsyOffset", player); + var currentTime = Conductor.songPosition / 1000; + + // Calculate vertical offset using cosine wave + var waveAngle = currentTime * ((speed * 1.2) + 1.2) + noteData * ((offset * 1.8) + 1.8); + var verticalOffset = tipsyIntensity * FlxMath.fastCos(waveAngle) * Note.swagWidth * 0.4; + + pos.y += verticalOffset; + } + } + + /** + * Applies drunk effect - horizontal wobble using cosine waves + * Creates side-to-side swaying motion + */ + private function applyDrunkEffect(pos:Vector3, visualDiff:Float, noteData:Int, player:Int):Void { + var drunkIntensity = getValue(player); + + if (drunkIntensity != 0) { + var speed = getSubmodValue("drunkSpeed", player); + var period = getSubmodValue("drunkPeriod", player); + var offset = getSubmodValue("drunkOffset", player); + var currentTime = Conductor.songPosition / 1000; + + // Calculate horizontal offset using complex wave function + var waveAngle = currentTime * (1 + speed) + + noteData * ((offset * 0.2) + 0.2) + + visualDiff * ((period * 10) + 10) / FlxG.height; + + var horizontalOffset = drunkIntensity * FlxMath.fastCos(waveAngle) * Note.swagWidth * 0.5; + + pos.x += horizontalOffset; + } + } + + /** + * Applies tipZ effect - depth wobble using cosine waves + * Creates forward-and-back movement in the Z-axis + */ + private function applyTipZEffect(pos:Vector3, noteData:Int, player:Int):Void { + var tipZIntensity = getSubmodValue("tipZ", player); + + if (tipZIntensity != 0) { + var speed = getSubmodValue("tipZSpeed", player); + var offset = getSubmodValue("tipZOffset", player); + var currentTime = Conductor.songPosition / 1000; + + // Calculate Z-axis offset using cosine wave + var waveAngle = currentTime * ((speed * 1.2) + 1.2) + noteData * ((offset * 1.8) + 3.2); + var depthOffset = tipZIntensity * FlxMath.fastCos(waveAngle) * 0.15; + + pos.z += depthOffset; + } + } + + /** + * Applies bumpy effect - depth wobble using sine waves + * Creates bouncing motion based on visual position + */ + private function applyBumpyEffect(pos:Vector3, visualDiff:Float, player:Int):Void { + var bumpyIntensity = getSubmodValue("bumpy", player); + + if (bumpyIntensity != 0) { + var period = getSubmodValue("bumpyPeriod", player); + var offset = getSubmodValue("bumpyOffset", player); + + // Calculate Z-axis offset using sine wave based on visual position + var waveAngle = (visualDiff + (100.0 * offset)) / ((period * 16.0) + 16.0); + var depthOffset = (bumpyIntensity * 40 * FlxMath.fastSin(waveAngle)) / 250; + + pos.z += depthOffset; + } + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/FlipModifier.hx b/source/game/modchart/modifiers/FlipModifier.hx new file mode 100644 index 0000000..ba47847 --- /dev/null +++ b/source/game/modchart/modifiers/FlipModifier.hx @@ -0,0 +1,69 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import math.Vector3; + +/** + * Horizontally flips note positions across the center axis + * Reverses the left-to-right order of notes while maintaining spacing + */ +class FlipModifier extends NoteModifier { + override function getName():String { + return 'flip'; + } + + /** + * Applies horizontal flip transformation to note positions + * Mirrors note positions across the center axis of the playfield + * + * @param time Note strum time + * @param diff Visual position difference + * @param tDiff Time difference (strumTime - currentTime) + * @param beat Current beat with decimal precision + * @param pos Current position vector to modify + * @param data Note direction/column (0-3) + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector with flip applied + */ + override function getPos( + time:Float, + diff:Float, + tDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + // Early return if flip is disabled for this player + if (getValue(player) == 0) { + return pos; + } + + var flipOffset = calculateFlipOffset(data, player); + pos.x += flipOffset; + + return pos; + } + + /** + * Calculates the horizontal offset needed to flip a note position + * Moves notes from left side to right side and vice versa + */ + private function calculateFlipOffset(noteData:Int, player:Int):Float { + var receptors = modMgr.receptors[player]; + var receptorCount = receptors.length; + + // Calculate the mirror distance from center + // For 4 columns: + // - data=0 (leftmost) moves 1.5 spaces right + // - data=1 moves 0.5 spaces right + // - data=2 moves 0.5 spaces left + // - data=3 (rightmost) moves 1.5 spaces left + var mirrorDistanceMultiplier = (receptorCount / 2) - 0.5 - noteData; + var flipDistance = Note.swagWidth * mirrorDistanceMultiplier * getValue(player); + + return flipDistance; + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/InfinitePathModifier.hx b/source/game/modchart/modifiers/InfinitePathModifier.hx new file mode 100644 index 0000000..77dd335 --- /dev/null +++ b/source/game/modchart/modifiers/InfinitePathModifier.hx @@ -0,0 +1,73 @@ +package game.modchart.modifiers; + +import flixel.math.FlxMath; +import flixel.FlxG; + +import math.Vector3; + +/** + * Creates an infinite looping path for notes to follow + * Uses a mathematical curve (lemniscate/figure-8 pattern) for continuous note movement + */ +class InfinitePathModifier extends PathModifier { + override function getName():String { + return 'infinite'; + } + + /** + * Returns the movement speed for notes following the infinite path + * Controls how quickly notes move along the predefined curve + */ + override function getMoveSpeed():Float { + return 1850; + } + + /** + * Generates an infinite looping path (lemniscate/figure-8 pattern) + * Creates a continuous closed curve for notes to follow indefinitely + * + * @return 2D array of Vector3 points defining the path for each note column + */ + override function getPath():Array> { + var infinitePath:Array> = [[], [], [], []]; + var angleStep:Int = 15; // Degrees between each path point + + // Generate path points from 0 to 360 degrees + var currentAngle:Int = 0; + while (currentAngle < 360) { + var pathPoint = calculatePathPoint(currentAngle); + + // All note columns share the same path shape + for (noteData in 0...infinitePath.length) { + infinitePath[noteData].push(pathPoint); + } + + currentAngle += angleStep; + } + + return infinitePath; + } + + /** + * Calculates a single point on the infinite path using parametric equations + * Creates a lemniscate (figure-8) pattern centered on screen + * + * @param angle Current angle in degrees for parametric calculation + * @return Vector3 position on the infinite path + */ + private function calculatePathPoint(angle:Float):Vector3 { + var radians = angle * Math.PI / 180; + + // Parametric equations for a lemniscate (figure-8 curve) + // x = a * sin(t) + // y = a * sin(t) * cos(t) + var scaleFactor:Float = 600; + var screenCenterX = FlxG.width / 2; + var screenCenterY = FlxG.height / 2; + + var x = screenCenterX + FlxMath.fastSin(radians) * scaleFactor; + var y = screenCenterY + (FlxMath.fastSin(radians) * FlxMath.fastCos(radians)) * scaleFactor; + + return new Vector3(x, y, 0); + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/InvertModifier.hx b/source/game/modchart/modifiers/InvertModifier.hx new file mode 100644 index 0000000..43d0a4c --- /dev/null +++ b/source/game/modchart/modifiers/InvertModifier.hx @@ -0,0 +1,64 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import math.Vector3; + +/** + * Swaps the positions of left and right note columns + * Creates a mirror effect by exchanging note positions across the center axis + */ +class InvertModifier extends NoteModifier { + override function getName():String { + return 'invert'; + } + + /** + * Applies horizontal inversion transformation to note positions + * Swaps left and right columns while maintaining middle columns + * + * @param time Note strum time + * @param diff Visual position difference + * @param tDiff Time difference (strumTime - currentTime) + * @param beat Current beat with decimal precision + * @param pos Current position vector to modify + * @param data Note direction/column (0-3) + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector with inversion applied + */ + override function getPos( + time:Float, + diff:Float, + tDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + // Early return if inversion is disabled for this player + if (getValue(player) == 0) { + return pos; + } + + var invertOffset = calculateInvertOffset(data, player); + pos.x += invertOffset; + + return pos; + } + + /** + * Calculates the horizontal offset needed to invert note positions + * Swaps columns based on even/odd indexing: + * - Even columns (0, 2) move right by one note width + * - Odd columns (1, 3) move left by one note width + * This effectively swaps left and right sides + */ + private function calculateInvertOffset(noteData:Int, player:Int):Float { + var isEvenColumn = (noteData % 2 == 0); + var direction = isEvenColumn ? 1 : -1; + var invertDistance = Note.swagWidth * direction * getValue(player); + + return invertDistance; + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/LocalRotateModifier.hx b/source/game/modchart/modifiers/LocalRotateModifier.hx new file mode 100644 index 0000000..d6e19f0 --- /dev/null +++ b/source/game/modchart/modifiers/LocalRotateModifier.hx @@ -0,0 +1,158 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import flixel.math.FlxPoint; +import flixel.math.FlxMath; +import flixel.FlxG; + +import game.modchart.*; +import math.Vector3; + +/** + * Applies 3D rotation transformations around a local origin point + * Rotates notes in 3D space (X, Y, Z axes) relative to a specified center point + * Based on Schmovin' rotation system with X-Y-Z Euler angle order + */ +class LocalRotateModifier extends NoteModifier { + private var prefix:String; // Namespace prefix for modifier submodifiers + + /** + * Creates a new LocalRotateModifier with optional prefix for namespacing + * + * @param modMgr Reference to the modifier manager + * @param prefix Prefix for modifier names (e.g., 'local' for 'localrotateX') + * @param parent Parent modifier if this is a submodifier + */ + public function new(modMgr:ModManager, ?prefix:String = '', ?parent:Modifier) { + this.prefix = prefix; + super(modMgr, parent); + } + + override function getName():String { + return '${prefix}rotateX'; + } + + /** + * Sets execution order to after reverse operations for proper transformation order + */ + override function getOrder():Int { + return Modifier.ModifierOrder.POST_REVERSE; + } + + /** + * Returns the rotation submodifiers for Y and Z axes + */ + override function getSubmods():Array { + return [ + '${prefix}rotateY', // Y-axis rotation + '${prefix}rotateZ' // Z-axis rotation + ]; + } + + /** + * Linear interpolation helper function + */ + private inline function lerp(start:Float, end:Float, ratio:Float):Float { + return start + (end - start) * ratio; + } + + /** + * Applies 3D rotation to a vector using X-Y-Z Euler angle order + * Based on Schmovin' rotation implementation + * + * @param vec The vector to rotate + * @param xAngle Rotation angle around X-axis (radians) + * @param yAngle Rotation angle around Y-axis (radians) + * @param zAngle Rotation angle around Z-axis (radians) + * @return The rotated vector + */ + private function rotateVector3D(vec:Vector3, xAngle:Float, yAngle:Float, zAngle:Float):Vector3 { + // First rotation: around Z-axis + var rotatedZ = MathUtil.rotate(vec.x, vec.y, zAngle); + var afterZ = new Vector3(rotatedZ.x, rotatedZ.y, vec.z); + + // Second rotation: around X-axis + var rotatedX = MathUtil.rotate(afterZ.z, afterZ.y, xAngle); + var afterX = new Vector3(afterZ.x, rotatedX.y, rotatedX.x); + + // Third rotation: around Y-axis + var rotatedY = MathUtil.rotate(afterX.x, afterX.z, yAngle); + var afterY = new Vector3(rotatedY.x, afterX.y, rotatedY.y); + + // Clean up temporary objects + rotatedZ.putWeak(); + rotatedX.putWeak(); + rotatedY.putWeak(); + + return afterY; + } + + /** + * Applies 3D rotation transformation to note positions + * Rotates notes around a local origin point (player's side center) + * + * @param time Note strum time + * @param visualDiff Visual position difference + * @param timeDiff Time difference (strumTime - currentTime) + * @param beat Current beat with decimal precision + * @param pos Current position vector to modify + * @param data Note direction/column (0-3) + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector with 3D rotation applied + */ + override function getPos( + time:Float, + visualDiff:Float, + timeDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + var rotationOrigin = calculateRotationOrigin(player); + + // Calculate offset from rotation origin + var positionOffset = pos.subtract(rotationOrigin); + + // Apply scale to Z-axis for depth effect + var scale = FlxG.height; + positionOffset.z *= scale; + + // Apply 3D rotation + var rotatedOffset = rotateVector3D( + positionOffset, + getValue(player), // X-axis rotation + getSubmodValue('${prefix}rotateY', player), // Y-axis rotation + getSubmodValue('${prefix}rotateZ', player) // Z-axis rotation + ); + + // Restore Z-axis scale and apply final position + rotatedOffset.z /= scale; + return rotationOrigin.add(rotatedOffset); + } + + /** + * Calculates the rotation origin point (center of rotation) + * Based on player side and note layout + */ + private function calculateRotationOrigin(player:Int):Vector3 { + // Calculate center X position for the player's side + var centerX:Float = (FlxG.width / 2) - Note.swagWidth - 54 + Note.swagWidth * 1.5; + + switch (player) { + case 0: // Player 1 (BF) + centerX += FlxG.width / 2 - Note.swagWidth * 2 - 100; + case 1: // Player 2 (Dad) + centerX -= FlxG.width / 2 - Note.swagWidth * 2 - 100; + } + + centerX -= 56; // Additional offset + + // Center Y position + var centerY = FlxG.height / 2 - Note.swagWidth / 2; + + return new Vector3(centerX, centerY); + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/OpponentModifier.hx b/source/game/modchart/modifiers/OpponentModifier.hx new file mode 100644 index 0000000..5cdfc15 --- /dev/null +++ b/source/game/modchart/modifiers/OpponentModifier.hx @@ -0,0 +1,78 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import flixel.math.FlxPoint; +import flixel.math.FlxMath; + +import game.modchart.*; +import math.*; + +/** + * Swaps note positions between player and opponent sides + * Moves notes horizontally between the player's side and the opponent's side + * Creates a gameplay mechanic where players must hit notes from the opposite side + */ +class OpponentModifier extends NoteModifier { + override function getName():String { + return 'opponentSwap'; + } + + /** + * Applies opponent swap transformation to note positions + * Moves notes between player and opponent sides based on modifier intensity + * + * @param time Note strum time + * @param diff Visual position difference + * @param tDiff Time difference (strumTime - currentTime) + * @param beat Current beat with decimal precision + * @param pos Current position vector to modify + * @param data Note direction/column (0-3) + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector with opponent swap applied + */ + override function getPos( + time:Float, + diff:Float, + tDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + // Early return if opponent swap is disabled for this player + if (getValue(player) == 0) { + return pos; + } + + var swapOffset = calculateOpponentSwapOffset(data, player); + pos.x += swapOffset; + + return pos; + } + + /** + * Calculates the horizontal offset needed to swap note positions between sides + * Determines the distance between player and opponent sides and applies swap + */ + private function calculateOpponentSwapOffset(noteData:Int, player:Int):Float { + var opponentPlayer = getOpponentPlayerIndex(player); + + var opponentBaseX = modMgr.getBaseX(noteData, opponentPlayer); + var playerBaseX = modMgr.getBaseX(noteData, player); + + var horizontalDistance = opponentBaseX - playerBaseX; + var swapOffset = horizontalDistance * getValue(player); + + return swapOffset; + } + + /** + * Returns the opponent player index for the given player + * Swaps 0 between 1 (Player 1 between Player 2) + */ + private function getOpponentPlayerIndex(player:Int):Int { + return Std.int(MathUtil.scale(player, 0, 1, 1, 0)); + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/PathModifier.hx b/source/game/modchart/modifiers/PathModifier.hx new file mode 100644 index 0000000..7a3b97c --- /dev/null +++ b/source/game/modchart/modifiers/PathModifier.hx @@ -0,0 +1,183 @@ +package game.modchart.modifiers; + +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.math.FlxPoint; +import flixel.math.FlxMath; + +import game.modchart.*; +import math.Vector3; + +/** + * Path information structure for tracking points along a path + */ +typedef PathInfo = { + var position:Vector3; // 3D position of the path point + var dist:Float; // Distance to next point (for interpolation) + var start:Float; // Cumulative distance from path start to this point + var end:Float; // Cumulative distance from path start to next point +} + +/** + * Base class for modifiers that move notes along predefined paths + * Handles path interpolation and movement calculations for custom note trajectories + */ +class PathModifier extends NoteModifier { + private var moveSpeed:Float; // Speed of note movement along path + private var pathData:Array> = []; // Processed path information + private var totalDists:Array = []; // Total distance of each path + + override function getName():String { + return 'basePath'; + } + + /** + * Returns the movement speed for notes following the path + * Override in subclasses to customize path speed + */ + public function getMoveSpeed():Float { + return 5000; + } + + /** + * Returns the path definition as arrays of Vector3 points + * Must be overridden in subclasses to provide actual path data + */ + public function getPath():Array> { + return []; + } + + public function new(modMgr:ModManager, ?parent:Modifier) { + super(modMgr, parent); + + initPathData(); + } + + /** + * Initializes and processes path data for efficient interpolation + * Calculates distances and prepares path information structures + */ + private function initPathData():Void { + moveSpeed = getMoveSpeed(); + var rawPath:Array> = getPath(); + + // Process each direction/path + for (direction in 0...rawPath.length) { + initDirectionPath(direction, rawPath[direction]); + } + + // Debug output for path distances + for (direction in 0...totalDists.length) { + trace('Path direction $direction total distance: ${totalDists[direction]}'); + } + } + + /** + * Initializes path data for a specific direction/column + */ + private function initDirectionPath(direction:Int, pathPoints:Array):Void { + totalDists[direction] = 0; + pathData[direction] = []; + + for (pointIndex in 0...pathPoints.length) { + var pointPosition = pathPoints[pointIndex]; + + // Create path info structure for this point + var pathInfo:PathInfo = { + position: pointPosition.add(new Vector3(-Note.swagWidth / 2, -Note.swagWidth / 2)), + start: totalDists[direction], + end: 0, + dist: 0 + }; + + pathData[direction].push(pathInfo); + + // Calculate distances between points (skip first point) + if (pointIndex > 0) { + updatePathDistances(direction, pointIndex); + } + } + } + + /** + * Updates distance calculations between path points + */ + private function updatePathDistances(direction:Int, currentIndex:Int):Void { + var currentPoint = pathData[direction][currentIndex]; + var previousPoint = pathData[direction][currentIndex - 1]; + + // Calculate distance between consecutive points + var segmentDistance = Math.abs(Vector3.distance(previousPoint.position, currentPoint.position)); + totalDists[direction] += segmentDistance; + + // Update path info with new distance data + previousPoint.end = totalDists[direction]; + previousPoint.dist = previousPoint.start - totalDists[direction]; // Negative distance for interpolation + } + + /** + * Applies path-based movement to note positions + * Interpolates note position along predefined path based on timing + */ + override function getPos( + time:Float, + visualDiff:Float, + timeDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + // Early return if path modifier is disabled + if (getValue(player) == 0) { + return pos; + } + + var pathProgress = calculatePathProgress(timeDiff, data); + var interpolatedPosition = interpolateAlongPath(data, pathProgress, pos, player); + + return interpolatedPosition; + } + + /** + * Calculates progress along the path based on time difference + */ + private function calculatePathProgress(timeDiff:Float, noteData:Int):Float { + var visualTimeDiff = -timeDiff; // Convert to positive progress value + var progress = (visualTimeDiff / -moveSpeed) * totalDists[noteData]; + return progress; + } + + /** + * Interpolates position along the path based on progress + */ + private function interpolateAlongPath(noteData:Int, progress:Float, originalPos:Vector3, player:Int):Vector3 { + var path = pathData[noteData]; + var outputPos = originalPos.clone(); + + // Handle progress before path start + if (progress <= 0) { + return originalPos.lerp(path[0].position, getValue(player)); + } + + // Find the current path segment and interpolate + for (pointIndex in 0...path.length - 1) { + var currentPoint = path[pointIndex]; + var nextPoint = path[pointIndex + 1]; + + if (progress > currentPoint.start && progress < currentPoint.end) { + var interpolationAlpha = (currentPoint.start - progress) / currentPoint.dist; + var pathPosition = currentPoint.position.lerp(nextPoint.position, interpolationAlpha); + outputPos = originalPos.lerp(pathPosition, getValue(player)); + break; + } + } + + return outputPos; + } + + override function getSubmods():Array { + return []; + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/PerspectiveModifier.hx b/source/game/modchart/modifiers/PerspectiveModifier.hx new file mode 100644 index 0000000..2728fca --- /dev/null +++ b/source/game/modchart/modifiers/PerspectiveModifier.hx @@ -0,0 +1,93 @@ +package game.modchart.modifiers; + +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.math.FlxPoint; +import flixel.math.FlxMath; + +import game.modchart.*; +import math.Vector3; + +using StringTools; +// NOTE: THIS SHOULDNT HAVE ITS PERCENTAGE MODIFIED +// THIS IS JUST HERE TO ALLOW OTHER MODIFIERS TO HAVE PERSPECTIVE + +// did my research +// i now know what a frustrum is lmao +// stuff ill forget after tonight + +// its the next day and yea i forgot already LOL +// something somethng clipping idk + +// either way +// perspective projection woo + +final class PerspectiveModifier extends NoteModifier { + override function getName() return 'perspectiveDONTUSE'; + + override function getOrder() return Modifier.ModifierOrder.LAST + 100; + + override function shouldExecute(player:Int, val:Float) return true; + + var fov = Math.PI/2; + var near = 0; + var far = 2; + + function FastTan(rad:Float) // thanks schmoovin + { + return FlxMath.fastSin(rad) / FlxMath.fastCos(rad); + } + + + public function getVector(curZ:Float,pos:Vector3):Vector3{ + var halfOffset = new Vector3(FlxG.width/2, FlxG.height/2); + pos = pos.subtract(halfOffset); + var oX = pos.x; + var oY = pos.y; + + + // should I be using a matrix? + // .. nah im sure itll be fine just doing this manually + // instead of doing a proper perspective projection matrix + + //var aspect = FlxG.width/FlxG.height; + var aspect = 1; + + var shit = curZ-1; + if(shit > 0)shit=0; // thanks schmovin!! + + var ta = FastTan(fov/2); + var x = oX * aspect/ta; + var y = oY/ta; + var a = (near+far)/(near-far); + var b = 2*near*far/(near-far); + var z = (a*shit+b); + //trace(shit, curZ, z, x/z, y/z); + var returnedVector = new Vector3(x/z,y/z,z).add(halfOffset); + + return returnedVector; + } + + /*override function getReceptorPos(receptor:Receptor, pos:Vector3, data:Int, player:Int){ // maybe replace FlxPoint with a Vector3? + // HI 4MBR0S3 IM SORRY :(( I GENUINELY FUCKIN FORGOT TO CREDIT PLEASEDONTHATEMEILOVEYOURSTUFF:( + var vec = getVector(receptor.z,pos); + pos.x=vec.x; + pos.y=vec.y; + + return pos; + }*/ + override function getPos(time:Float, visualDiff:Float, timeDiff:Float, beat:Float, pos:Vector3, data:Int, player:Int, obj:FlxSprite) + return getVector(pos.z,pos); + + + override function updateReceptor(beat:Float, receptor:StrumNote, pos:Vector3, player:Int){ + receptor.scale.scale(1/pos.z); + } + + + override function updateNote(beat:Float, note:Note, pos:Vector3, player:Int){ + note.scale.scale(1/pos.z); + } + + +} diff --git a/source/game/modchart/modifiers/ReverseModifier.hx b/source/game/modchart/modifiers/ReverseModifier.hx new file mode 100644 index 0000000..7a49405 --- /dev/null +++ b/source/game/modchart/modifiers/ReverseModifier.hx @@ -0,0 +1,144 @@ +package game.modchart.modifiers; + +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.math.FlxMath; +import flixel.math.FlxPoint; +import flixel.math.FlxRect; + +import math.*; +import game.modchart.*; +import game.modchart.Modifier.ModifierOrder; + +using StringTools; + +class ReverseModifier extends NoteModifier { + override function getOrder() return REVERSE; + override function getName() return 'reverse'; + + public function getReverseValue(dir:Int, player:Int, ?scrolling=false){ + var suffix = scrolling ? 'Scroll' : ''; + + var receptors = modMgr.receptors[player]; + var kNum = receptors.length; + var val:Float = 0; + if(dir >= kNum/2) + val += getSubmodValue("split" + suffix, player); + + if((dir % 2) == 1) + val += getSubmodValue("alternate" + suffix, player); + + var first = kNum / 4; + var last = kNum - 1 - first; + + if(dir >= first && dir <= last) + val += getSubmodValue("cross" + suffix, player); + + + if(suffix == '') + val += getValue(player) + getSubmodValue("reverse" + Std.string(dir), player); + else + val += getSubmodValue("reverse" + suffix, player); + + + if(getSubmodValue("unboundedReverse", player) == 0){ + val %= 2; + if(val > 1)val = 2 - val; + } + + if(ClientPrefs.downScroll) + val = 1 - val; + + return val; + } + + public function getScrollReversePerc(dir:Int, player:Int) + return getReverseValue(dir,player) * 100; + + override function shouldExecute(player:Int,val:Float) + return true; + + override function ignoreUpdateNote() + return false; + + override function updateNote(beat:Float, daNote:Note, pos:Vector3, player:Int) + { + if (daNote.isSustainNote) + { + var y = pos.y; + var revPerc = getReverseValue(daNote.noteData, player); + var strumLine = modMgr.receptors[player][daNote.noteData]; + + var shitGotHit = (strumLine.sustainReduce + && daNote.isSustainNote + && (daNote.mustPress || !daNote.ignoreNote) + && (!daNote.mustPress || (daNote.wasGoodHit || (daNote.prevNote.wasGoodHit && !daNote.canBeHit)))); + + if (shitGotHit) + { + var center:Float = strumLine.y + Note.swagWidth / 2; + + var swagRect = new FlxRect(0, 0, daNote.frameWidth, daNote.frameHeight); + if (revPerc >= 0.5) // Downscroll behavior' + { + if (y - daNote.offset.y * daNote.scale.y + daNote.height >= center) + { + swagRect.height = (center - y) / daNote.scale.y; + swagRect.y = daNote.frameHeight - swagRect.height; + } + } + else // Upscroll behavior + { + if (y + daNote.offset.y * daNote.scale.y <= center) + { + swagRect.y = (center - y) / daNote.scale.y; + swagRect.height -= swagRect.y; + } + } + + daNote.clipRect = swagRect; + } + } + } + + override function getPos(time:Float, visualDiff:Float, timeDiff:Float, beat:Float, pos:Vector3, data:Int, player:Int, obj:FlxSprite) + { + var perc = getReverseValue(data, player); + var shift = MathUtil.scale(perc, 0, 1, 50, FlxG.height - 150); + var mult = MathUtil.scale(perc, 0, 1, 1, -1); + shift = MathUtil.scale(getSubmodValue("centered", player), 0, 1, shift, (FlxG.height/2) - 56); + + pos.y = shift + (visualDiff * mult); + + // Sus note positioning adjustments + // Using in-game logic + if(obj is Note) + { + var note:Note = cast obj; + if (note.isSustainNote) + { + if (perc >= 0.5) // Downscroll behavior + { + if (PlayState.isPixelStage) + { + pos.y -= PlayState.daPixelZoom * 9.5; + } + pos.y -= (note.frameHeight * note.scale.y) - (Note.swagWidth / 2); + } + } + } + + return pos; + } + + override function getSubmods(){ + var subMods:Array = ["cross", "split", "alternate", "reverseScroll", "crossScroll", "splitScroll", "alternateScroll", "centered", "unboundedReverse"]; + + var receptors = modMgr.receptors[0]; + for (i in 0...4) + { + subMods.push('reverse${i}'); + } + return subMods; + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/RotateModifier.hx b/source/game/modchart/modifiers/RotateModifier.hx new file mode 100644 index 0000000..19cb67d --- /dev/null +++ b/source/game/modchart/modifiers/RotateModifier.hx @@ -0,0 +1,149 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import flixel.math.FlxPoint; +import flixel.math.FlxMath; +import flixel.FlxG; + +import game.modchart.*; +import math.Vector3; + +/** + * Applies 3D rotation transformations around a specified origin point + * Rotates notes in 3D space (X, Y, Z axes) around a customizable center point + * Based on Schmovin' rotation system with X-Y-Z Euler angle order + * This version supports custom rotation origins and prefix namespacing + */ +class RotateModifier extends NoteModifier { + private var daOrigin:Vector3; // Custom rotation origin point + private var prefix:String; // Namespace prefix for modifier submodifiers + + /** + * Creates a new RotateModifier with optional prefix and custom origin + * + * @param modMgr Reference to the modifier manager + * @param prefix Prefix for modifier names (e.g., 'center' for 'centerrotateX') + * @param origin Custom rotation origin point (uses note position if null) + * @param parent Parent modifier if this is a submodifier + */ + public function new(modMgr:ModManager, ?prefix:String = '', ?origin:Vector3, ?parent:Modifier) { + this.prefix = prefix; + this.daOrigin = origin; + super(modMgr, parent); + } + + override function getName():String { + return '${prefix}rotateX'; + } + + /** + * Sets execution order to very late for proper transformation order + * Ensures rotation is applied after most other transformations + */ + override function getOrder():Int { + return Modifier.ModifierOrder.LAST + 2; + } + + /** + * Returns the rotation submodifiers for Y and Z axes + */ + override function getSubmods():Array { + return [ + '${prefix}rotateY', // Y-axis rotation + '${prefix}rotateZ' // Z-axis rotation + ]; + } + + /** + * Applies 3D rotation to a vector using Z-X-Y Euler angle order + * Based on Schmovin' rotation implementation + * Rotation order: Z -> X -> Y + * + * @param vec The vector to rotate + * @param xAngle Rotation angle around X-axis (radians) + * @param yAngle Rotation angle around Y-axis (radians) + * @param zAngle Rotation angle around Z-axis (radians) + * @return The rotated vector + */ + private function rotateVector3D(vec:Vector3, xAngle:Float, yAngle:Float, zAngle:Float):Vector3 { + // First rotation: around Z-axis (affects X and Y) + var rotatedZ = MathUtil.rotate(vec.x, vec.y, zAngle); + var afterZ = new Vector3(rotatedZ.x, rotatedZ.y, vec.z); + + // Second rotation: around X-axis (affects Y and Z) + var rotatedX = MathUtil.rotate(afterZ.z, afterZ.y, xAngle); + var afterX = new Vector3(afterZ.x, rotatedX.y, rotatedX.x); + + // Third rotation: around Y-axis (affects X and Z) + var rotatedY = MathUtil.rotate(afterX.x, afterX.z, yAngle); + var afterY = new Vector3(rotatedY.x, afterX.y, rotatedY.y); + + // Clean up temporary objects to prevent memory leaks + rotatedZ.putWeak(); + rotatedX.putWeak(); + rotatedY.putWeak(); + + return afterY; + } + + /** + * Applies 3D rotation transformation to note positions + * Rotates notes around specified origin point with configurable axes + * + * @param time Note strum time + * @param visualDiff Visual position difference + * @param timeDiff Time difference (strumTime - currentTime) + * @param beat Current beat with decimal precision + * @param pos Current position vector to modify + * @param data Note direction/column (0-3) + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector with 3D rotation applied + */ + override function getPos( + time:Float, + visualDiff:Float, + timeDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + var rotationOrigin = getRotationOrigin(data, player); + + // Convert to local coordinates relative to rotation origin + var localOffset = pos.subtract(rotationOrigin); + + // Apply scale to Z-axis for consistent depth effect + var depthScale = FlxG.height; + localOffset.z *= depthScale; + + // Apply 3D rotation to local coordinates + var rotatedOffset = rotateVector3D( + localOffset, + getValue(player), // X-axis rotation + getSubmodValue('${prefix}rotateY', player), // Y-axis rotation + getSubmodValue('${prefix}rotateZ', player) // Z-axis rotation + ); + + // Restore Z-axis scale and convert back to world coordinates + rotatedOffset.z /= depthScale; + return rotationOrigin.add(rotatedOffset); + } + + /** + * Calculates the rotation origin point + * Uses custom origin if provided, otherwise calculates based on note position + */ + private function getRotationOrigin(noteData:Int, player:Int):Vector3 { + if (daOrigin != null) { + return daOrigin; + } else { + // Default to note's base position centered vertically + var baseX = modMgr.getBaseX(noteData, player); + var centerY = FlxG.height / 2 - Note.swagWidth / 2; + return new Vector3(baseX, centerY); + } + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/ScaleModifier.hx b/source/game/modchart/modifiers/ScaleModifier.hx new file mode 100644 index 0000000..fc02ee4 --- /dev/null +++ b/source/game/modchart/modifiers/ScaleModifier.hx @@ -0,0 +1,169 @@ +package game.modchart.modifiers; + +import flixel.math.FlxPoint; + +import game.modchart.Modifier.ModifierOrder; +import math.Vector3; + +/** + * Applies various scaling transformations to notes and receptors + * Handles miniaturization, stretching, squishing, and per-column scaling effects + */ +class ScaleModifier extends NoteModifier { + override function getName():String { + return 'mini'; + } + + /** + * Sets execution order to before reverse operations for proper transformation order + */ + override function getOrder():Int { + return PRE_REVERSE; + } + + /** + * Always execute this modifier to handle submodifiers even when base value is 0 + */ + override function shouldExecute(player:Int, val:Float):Bool { + return true; + } + + override function ignorePos():Bool { + return true; // This modifier doesn't affect position, only scale + } + + override function ignoreUpdateReceptor():Bool { + return false; // This modifier affects receptors + } + + override function ignoreUpdateNote():Bool { + return false; // This modifier affects notes + } + + /** + * Returns all submodifiers supported by this modifier + * Includes per-noteData variants for column-specific effects + */ + override function getSubmods():Array { + var subMods:Array = [ + "squish", // Global squish effect + "stretch", // Global stretch effect + "miniX", // Global X-axis miniaturization + "miniY" // Global Y-axis miniaturization + ]; + + // Add per-column submodifiers for fine-tuned control + for (i in 0...4) { + subMods.push('mini${i}X'); // X-axis mini for specific column + subMods.push('mini${i}Y'); // Y-axis mini for specific column + subMods.push('squish${i}'); // Squish effect for specific column + subMods.push('stretch${i}'); // Stretch effect for specific column + } + + return subMods; + } + + /** + * Calculates the final scale by applying all scaling effects + * + * @param sprite The game object (note or receptor) being scaled + * @param baseScale The original scale of the object + * @param noteData Note direction/column (0-3) + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @return Modified scale point with all effects applied + */ + private function getScale(sprite:Dynamic, baseScale:FlxPoint, noteData:Int, player:Int):FlxPoint { + var finalScale = baseScale.clone(); + var originalYScale = finalScale.y; // Preserve for sustain notes + + applyMiniEffects(finalScale, noteData, player); + applyStretchAndSquishEffects(finalScale, noteData, player); + + // Sustain notes maintain their original Y scale for visual consistency + if (Std.isOfType(sprite, Note) && cast(sprite, Note).isSustainNote) { + finalScale.y = originalYScale; + } + + return finalScale; + } + + /** + * Applies miniaturization effects to the scale + * Reduces size on X and Y axes with per-column control + */ + private function applyMiniEffects(scale:FlxPoint, noteData:Int, player:Int):Void { + // Apply base mini effect (affects both axes) + var baseMini = getValue(player); + scale.x *= 1 - baseMini; + scale.y *= 1 - baseMini; + + // Apply axis-specific mini effects + var miniX = getSubmodValue("miniX", player) + getSubmodValue('mini${noteData}X', player); + var miniY = getSubmodValue("miniY", player) + getSubmodValue('mini${noteData}Y', player); + + scale.x *= 1 - miniX; + scale.y *= 1 - miniY; + } + + /** + * Applies stretch and squish distortion effects to the scale + * Creates non-uniform scaling for visual distortion effects + */ + private function applyStretchAndSquishEffects(scale:FlxPoint, noteData:Int, player:Int):Void { + var stretch = getSubmodValue("stretch", player) + getSubmodValue('stretch${noteData}', player); + var squish = getSubmodValue("squish", player) + getSubmodValue('squish${noteData}', player); + + // Calculate stretch factors (vertical stretch, horizontal compression) + var stretchFactorX = FlxMath.lerp(1, 0.5, stretch); // Horizontal compression + var stretchFactorY = FlxMath.lerp(1, 2, stretch); // Vertical stretch + + // Calculate squish factors (horizontal stretch, vertical compression) + var squishFactorX = FlxMath.lerp(1, 2, squish); // Horizontal stretch + var squishFactorY = FlxMath.lerp(1, 0.5, squish); // Vertical compression + + // Current implementation uses angle 0, so trigonometric functions simplify + // These would be more complex with rotation, but currently: + // sin(0) = 0, cos(0) = 1 + var angle = 0; + var sinAngle = Math.sin(angle * Math.PI / 180); // = 0 + var cosAngle = Math.cos(angle * Math.PI / 180); // = 1 + + // Apply stretch effects (simplified due to angle 0) + scale.x *= (sinAngle * stretchFactorY) + (cosAngle * stretchFactorX); // = stretchFactorX + scale.y *= (cosAngle * stretchFactorY) + (sinAngle * stretchFactorX); // = stretchFactorY + + // Apply squish effects (simplified due to angle 0) + scale.x *= (sinAngle * squishFactorY) + (cosAngle * squishFactorX); // = squishFactorX + scale.y *= (cosAngle * squishFactorY) + (sinAngle * squishFactorX); // = squishFactorY + } + + /** + * Updates note scale based on all scaling effects + * Sustain notes preserve their original Y scale for visual consistency + */ + override function updateNote(beat:Float, note:Note, pos:Vector3, player:Int) { + var baseScale = FlxPoint.weak(note.defScale.x, note.defScale.y); + var finalScale = getScale(note, baseScale, note.noteData, player); + + // Sustain notes always maintain their original Y scale + if (note.isSustainNote) { + finalScale.y = note.defScale.y; + } + + note.scale.copyFrom(finalScale); + finalScale.putWeak(); + baseScale.putWeak(); + } + + /** + * Updates receptor scale based on all scaling effects + */ + override function updateReceptor(beat:Float, receptor:StrumNote, pos:Vector3, player:Int) { + var baseScale = FlxPoint.weak(receptor.defScale.x, receptor.defScale.y); + var finalScale = getScale(receptor, baseScale, receptor.noteData, player); + + receptor.scale.copyFrom(finalScale); + finalScale.putWeak(); + baseScale.putWeak(); + } +} \ No newline at end of file diff --git a/source/game/modchart/modifiers/TransformModifier.hx b/source/game/modchart/modifiers/TransformModifier.hx new file mode 100644 index 0000000..fdf28cb --- /dev/null +++ b/source/game/modchart/modifiers/TransformModifier.hx @@ -0,0 +1,115 @@ +package game.modchart.modifiers; + +import flixel.FlxSprite; +import flixel.math.FlxPoint; +import flixel.math.FlxMath; +import flixel.FlxG; + +import game.modchart.*; +import math.Vector3; +import math.*; + +/** + * Applies positional translations to notes and receptors + * Moves objects along X, Y, and Z axes with global and per-column control + * Supports additive transformations for complex movement combinations + */ +class TransformModifier extends NoteModifier { + override function getName():String { + return 'transformX'; + } + + /** + * Sets execution order to very late for proper transformation order + * Ensures translations are applied after most other transformations + */ + override function getOrder():Int { + return Modifier.ModifierOrder.LAST; + } + + /** + * Returns all transformation submodifiers + * Includes global and per-column transformations for all three axes + * "-a" suffix indicates additive transformations that stack with base values + */ + override function getSubmods():Array { + var subMods:Array = [ + // Global axis transformations + "transformY", // Global Y-axis translation + "transformZ", // Global Z-axis translation (depth) + "transformX-a", // Global X-axis additive translation + "transformY-a", // Global Y-axis additive translation + "transformZ-a" // Global Z-axis additive translation + ]; + + // Add per-column transformations for fine-tuned control + for (column in 0...4) { + subMods.push('transform${column}X'); // Column-specific X translation + subMods.push('transform${column}Y'); // Column-specific Y translation + subMods.push('transform${column}Z'); // Column-specific Z translation + subMods.push('transform${column}X-a'); // Column-specific X additive translation + subMods.push('transform${column}Y-a'); // Column-specific Y additive translation + subMods.push('transform${column}Z-a'); // Column-specific Z additive translation + } + + return subMods; + } + + /** + * Applies translation transformations to note positions + * Combines global and per-column translations across all three axes + * + * @param time Note strum time + * @param visualDiff Visual position difference + * @param timeDiff Time difference (strumTime - currentTime) + * @param beat Current beat with decimal precision + * @param pos Current position vector to modify + * @param data Note direction/column (0-3) + * @param player Player index (0 = BF, 1 = Dad, -1 = Both) + * @param obj The game object (note or receptor) + * @return Modified position vector with translations applied + */ + override function getPos( + time:Float, + visualDiff:Float, + timeDiff:Float, + beat:Float, + pos:Vector3, + data:Int, + player:Int, + obj:FlxSprite + ):Vector3 { + applyGlobalTransformations(pos, player); + applyColumnTransformations(pos, data, player); + + return pos; + } + + /** + * Applies global transformations (affecting all columns) + */ + private function applyGlobalTransformations(pos:Vector3, player:Int):Void { + // X-axis: base value + additive transformation + pos.x += getValue(player) + getSubmodValue("transformX-a", player); + + // Y-axis: transformation + additive transformation + pos.y += getSubmodValue("transformY", player) + getSubmodValue("transformY-a", player); + + // Z-axis: transformation + additive transformation (depth) + pos.z += getSubmodValue("transformZ", player) + getSubmodValue("transformZ-a", player); + } + + /** + * Applies column-specific transformations + */ + private function applyColumnTransformations(pos:Vector3, column:Int, player:Int):Void { + // X-axis: column transformation + column additive transformation + pos.x += getSubmodValue('transform${column}X', player) + getSubmodValue('transform${column}X-a', player); + + // Y-axis: column transformation + column additive transformation + pos.y += getSubmodValue('transform${column}Y', player) + getSubmodValue('transform${column}Y-a', player); + + // Z-axis: column transformation + column additive transformation (depth) + pos.z += getSubmodValue('transform${column}Z', player) + getSubmodValue('transform${column}Z-a', player); + } +} \ No newline at end of file diff --git a/source/game/objects/Character.hx b/source/game/objects/Character.hx index 9880548..82b9f3d 100644 --- a/source/game/objects/Character.hx +++ b/source/game/objects/Character.hx @@ -6,7 +6,15 @@ import flixel.addons.effects.FlxTrail; import flixel.animation.FlxBaseAnimation; import flixel.graphics.frames.FlxAtlasFrames; import flixel.tweens.FlxTween; +import flixel.math.FlxPoint; import flixel.util.FlxSort; +import flixel.util.FlxColor; + +import flixel.addons.effects.FlxSkewedSprite; + +#if flixel_animate +import animate.FlxAnimate; +#end #if MODS_ALLOWED import sys.io.File; @@ -26,6 +34,7 @@ using StringTools; typedef CharacterFile = { var animations:Array; + @:optional var shadow:ShadowData; var image:String; var scale:Float; var sing_duration:Float; @@ -40,16 +49,30 @@ typedef CharacterFile = var healthbar_colors:Array; } -typedef AnimArray = +typedef ShadowData = { - var anim:String; - var name:String; - var fps:Int; - var loop:Bool; - var indices:Array; - var offsets:Array; + var visible:Bool; + var color:Array; + var offset:Array; + var skew:Array; + var alpha:Float; + var scale:Array; + var scrollFactor:Array; + var flip_x:Bool; + var flip_y:Bool; } +typedef AnimArray = { + var anim:String; + var name:String; + var fps:Int; + var loop:Bool; + var indices:Array; + var offsets:Array; + var shadow_offsets:Array; +} + + class Character extends FlxSprite { public var animOffsets:Map>; @@ -87,7 +110,6 @@ class Character extends FlxSprite public var hasMissAnimations:Bool = false; - // Used on Character Editor public var imageFile:String = ''; public var jsonScale:Float = 1; public var noAntialiasing:Bool = false; @@ -95,7 +117,24 @@ class Character extends FlxSprite public var vocalsFile:String = ''; public var healthColorArray:Array = [255, 0, 0]; - public static var DEFAULT_CHARACTER:String = 'bf'; // In case a character is missing, it will use BF on its place + public var shadowOffsets:Map> = new Map(); + + public var shadowVisible:Bool = false; + public var shadowColor:FlxColor = 0xFF000000; + public var shadowOffset:FlxPoint = FlxPoint.get(0, 0); + public var shadowSkew:FlxPoint = FlxPoint.get(0, 0); + public var shadowAlpha:Float = 0.6; + public var shadowScale:FlxPoint = FlxPoint.get(1, 1); + public var shadowScrollFactor:FlxPoint = FlxPoint.get(1, 1); + public var shadowSprite:FlxSkewedSprite; + public var shadowFlipX:Bool = false; + public var shadowFlipY:Bool = true; + public var shadowAntialiasing:Bool = true; + #if flixel_animate + public var shadowAtlas:FlxAnimate; + #end + + public static final DEFAULT_CHARACTER:String = 'bf'; // In case a character is missing, it will use BF on its place public function new(x:Float, y:Float, ?character:String = 'bf', ?isPlayer:Bool = false, ?isChibiChar:Bool = false) { @@ -123,7 +162,7 @@ class Character extends FlxSprite var characterPath:String = 'characters/' + curCharacter + '.json'; #if MODS_ALLOWED - var path:String = Paths.modFolders(characterPath); + var path:String = Mods.modFolders(characterPath); if (!FileSystem.exists(path)) { path = Paths.getPreloadPath(characterPath); @@ -135,8 +174,7 @@ class Character extends FlxSprite if (!Assets.exists(path)) #end { - path = Paths.getPreloadPath('characters/' + DEFAULT_CHARACTER + - '.json'); // If a character couldn't be found, change him to BF just to prevent a crash + path = Paths.getPreloadPath('characters/' + DEFAULT_CHARACTER + '.json'); // If a character couldn't be found, change him to BF just to prevent a crash } #if MODS_ALLOWED @@ -151,7 +189,7 @@ class Character extends FlxSprite // packer // texture #if MODS_ALLOWED - var modTxtToFind:String = Paths.modsTxt(json.image); + var modTxtToFind:String = Mods.modsTxt(json.image); var txtToFind:String = Paths.getPath('images/' + json.image + '.txt', TEXT); if (FileSystem.exists(modTxtToFind) || FileSystem.exists(txtToFind) || Assets.exists(txtToFind)) @@ -218,16 +256,29 @@ class Character extends FlxSprite flipX = !!json.flip_x; //bruhhh if (json.no_antialiasing) { - antialiasing = false; + antialiasing = shadowAntialiasing = false; noAntialiasing = true; } if (json.healthbar_colors != null && json.healthbar_colors.length > 2) healthColorArray = json.healthbar_colors; - antialiasing = !noAntialiasing; + if (json.shadow != null) + { + shadowVisible = json.shadow.visible; + shadowColor = FlxColor.fromRGB(json.shadow.color[0], json.shadow.color[1], json.shadow.color[2]); + shadowOffset.set(json.shadow.offset[0], json.shadow.offset[1]); + shadowSkew.set(json.shadow.skew[0], json.shadow.skew[1]); + shadowAlpha = json.shadow.alpha; + shadowScale.set(json.shadow.scale[0], json.shadow.scale[1]); + shadowScrollFactor.set(json.shadow.scrollFactor[0], json.shadow.scrollFactor[1]); + shadowFlipX = json.shadow.flip_x; + shadowFlipY = json.shadow.flip_y; + } + + antialiasing = shadowAntialiasing = !noAntialiasing; if (!ClientPrefs.globalAntialiasing) - antialiasing = false; + antialiasing = shadowAntialiasing = false; animationsArray = json.animations; if (animationsArray != null && animationsArray.length > 0) @@ -284,6 +335,78 @@ class Character extends FlxSprite } } + #if flixel_animate + if (isAnimateAtlas) + { + shadowAtlas = new FlxAnimate(); + shadowAtlas.visible = shadowVisible; + shadowAtlas.alpha = shadowAlpha; + shadowAtlas.color = shadowColor; + shadowAtlas.skew.x = shadowSkew.x; + shadowAtlas.skew.y = shadowSkew.y; + shadowAtlas.scale.x = scale.x * shadowScale.x; + shadowAtlas.scale.y = scale.y * shadowScale.y; + shadowAtlas.scrollFactor.x = shadowScrollFactor.x; + shadowAtlas.scrollFactor.y = shadowScrollFactor.y; + shadowAtlas.flipX = shadowFlipX; + shadowAtlas.flipY = shadowFlipY; + shadowAtlas.antialiasing = shadowAntialiasing; + + shadowAtlas.frames = Paths.getAnimateAtlas(json.image); + + for (anim in animationsArray) + { + var animAnim:String = '' + anim.anim; + var animName:String = '' + anim.name; + var animFps:Int = anim.fps; + var animLoop:Bool = !!anim.loop; + var animIndices:Array = anim.indices; + + if (animIndices != null && animIndices.length > 0) + shadowAtlas.anim.addBySymbolIndices(animAnim, animName, animIndices, animFps, animLoop); + else + shadowAtlas.anim.addBySymbol(animAnim, animName, animFps, animLoop); + } + } + else + #end + { + shadowSprite = new FlxSkewedSprite(); + shadowSprite.visible = shadowVisible; + shadowSprite.alpha = shadowAlpha; + shadowSprite.color = shadowColor; + shadowSprite.skew.x = shadowSkew.x; + shadowSprite.skew.y = shadowSkew.y; + shadowSprite.scale.x = scale.x * shadowScale.x; + shadowSprite.scale.y = scale.y * shadowScale.y; + shadowSprite.scrollFactor.x = shadowScrollFactor.x; + shadowSprite.scrollFactor.y = shadowScrollFactor.y; + shadowSprite.flipX = shadowFlipX; + shadowSprite.flipY = shadowFlipY; + shadowSprite.antialiasing = shadowAntialiasing; + + if(Paths.fileExists('images/' + json.image + '.txt', TEXT)) + shadowSprite.frames = Paths.getPackerAtlas(json.image); + else if(Paths.fileExists('images/' + json.image + '.json', TEXT)) + shadowSprite.frames = Paths.getAsepriteAtlas(json.image); + else + shadowSprite.frames = Paths.getSparrowAtlas(json.image); + + for (anim in animationsArray) + { + var animAnim:String = '' + anim.anim; + var animName:String = '' + anim.name; + var animFps:Int = anim.fps; + var animLoop:Bool = !!anim.loop; + var animIndices:Array = anim.indices; + + if (animIndices != null && animIndices.length > 0) + shadowSprite.animation.addByIndices(animAnim, animName, animIndices, "", animFps, animLoop); + else + shadowSprite.animation.addByPrefix(animAnim, animName, animFps, animLoop); + } + } + #if flixel_animate if (isAnimateAtlas) copyAtlasValues(); @@ -295,7 +418,7 @@ class Character extends FlxSprite recalculateDanceIdle(); dance(); - if (isPlayer)flipX = !flipX; + if (isPlayer) flipX = !flipX; switch(curCharacter) { @@ -383,6 +506,23 @@ class Character extends FlxSprite for (ghost in animGhosts) ghost.update(elapsed); + // update shadow + if (shadowVisible) + { + updateShadow(); + + #if flixel_animate + if (isAnimateAtlas) + { + shadowAtlas?.update(elapsed); + } + else + #end + { + shadowSprite?.update(elapsed); + } + } + super.update(elapsed); } @@ -429,6 +569,28 @@ class Character extends FlxSprite else offset.set(0, 0); + var shOffset = shadowOffsets.get(AnimName); + if(shadowOffsets.exists(AnimName)) + shadowOffset.set(shOffset[0], shOffset[1]); + else + shadowOffset.set(0, 0); + + // play shadow anim + if (shadowVisible) + { + #if flixel_animate + if (isAnimateAtlas) + { + shadowAtlas?.anim?.play(AnimName, Force, Reversed, Frame); + shadowAtlas?.update(0); + } + else + #end + { + shadowSprite?.animation?.play(AnimName, Force, Reversed, Frame); + } + } + if (curCharacter.startsWith('gf')) { if (AnimName == 'singLEFT') @@ -582,11 +744,90 @@ class Character extends FlxSprite public var atlas:FlxAnimate; #end + public function updateShadow():Void + { + #if flixel_animate + if (isAnimateAtlas && shadowAtlas != null) + { + if (!isAnimationNull() && shadowAtlas.anim.curAnim != null) + { + var animName = getAnimationName(); + if (shadowAtlas.anim.getByName(animName) != null) + { + shadowAtlas.anim.curAnim.curFrame = atlas.anim.curAnim.curFrame; + } + } + + shadowAtlas.setPosition(x + shadowOffset.x, y + shadowOffset.y); + shadowAtlas.flipX = flipX; + shadowAtlas.flipY = flipY; + shadowAtlas.offset.set(shadowOffset.x, shadowOffset.y); + shadowAtlas.skew.x = shadowSkew.x; + shadowAtlas.skew.y = shadowSkew.y; + shadowAtlas.scale.x = scale.x * shadowScale.x; + shadowAtlas.scale.y = scale.y * shadowScale.y; + shadowAtlas.scrollFactor.x = scrollFactor.x * shadowScrollFactor.x; + shadowAtlas.scrollFactor.y = scrollFactor.y * shadowScrollFactor.y; + shadowAtlas.flipX = shadowFlipX; + shadowAtlas.flipY = shadowFlipY; + shadowAtlas.antialiasing = shadowAntialiasing; + + shadowAtlas.visible = shadowVisible; + shadowAtlas.alpha = shadowAlpha; + shadowAtlas.color = shadowColor; + } + else if (shadowSprite != null) + #end + { + if (!isAnimationNull() && shadowSprite.animation.curAnim != null) + { + var animName = getAnimationName(); + if (shadowSprite.animation.getByName(animName) != null) + { + shadowSprite.animation.curAnim.curFrame = animation.curAnim.curFrame; + } + } + + shadowSprite.setPosition(x + shadowOffset.x, y + shadowOffset.y); + shadowSprite.flipX = flipX; + shadowSprite.flipY = flipY; + shadowSprite.offset.set(shadowOffset.x, shadowOffset.y); + shadowSprite.skew.x = shadowSkew.x; + shadowSprite.skew.y = shadowSkew.y; + shadowSprite.scale.x = scale.x * shadowScale.x; + shadowSprite.scale.y = scale.y * shadowScale.y; + shadowSprite.flipX = shadowFlipX; + shadowSprite.flipY = shadowFlipY; + shadowSprite.antialiasing = shadowAntialiasing; + + shadowSprite.visible = shadowVisible; + shadowSprite.alpha = shadowAlpha; + shadowSprite.color = shadowColor; + } + } + public override function draw() { var lastAlpha:Float = alpha; var lastColor:FlxColor = color; + // draw shadow first + if (shadowVisible) + { + //updateShadow(); + + #if flixel_animate + if (isAnimateAtlas) + { + shadowAtlas?.draw(); + } + else + #end + { + shadowSprite?.draw(); + } + } + #if flixel_animate if (isAnimateAtlas) { @@ -632,13 +873,53 @@ class Character extends FlxSprite atlas.color = color; } } + #end public override function destroy() { + // destroy shadow + #if flixel_animate + if (shadowAtlas != null) + { + shadowAtlas.destroy(); + shadowAtlas = null; + } + #end + + if (shadowSprite != null) + { + shadowSprite.destroy(); + shadowSprite = null; + } + + shadowOffset.put(); + shadowSkew.put(); + shadowScale.put(); + shadowScrollFactor.put(); + + // Destroy ghosts + for (ghost in animGhosts) + { + ghost?.destroy(); + } + animGhosts = []; + + for (tween in ghostTweens) + { + if (tween != null) + { + tween.cancel(); + } + } + ghostTweens = []; + + #if flixel_animate destroyAtlas(); + #end super.destroy(); } + #if flixel_animate inline public function destroyAtlas() { if (atlas != null) diff --git a/source/game/objects/DialogueBoxPsych.hx b/source/game/objects/DialogueBoxPsych.hx index b95f0c9..6a87cb5 100644 --- a/source/game/objects/DialogueBoxPsych.hx +++ b/source/game/objects/DialogueBoxPsych.hx @@ -92,7 +92,7 @@ class DialogueCharacter extends FlxSprite var rawJson = null; #if MODS_ALLOWED - var path:String = Paths.modFolders(characterPath); + var path:String = Mods.modFolders(characterPath); if (!FileSystem.exists(path)) { path = Paths.getPreloadPath(characterPath); } diff --git a/source/game/objects/FunkinSoundTray.hx b/source/game/objects/FunkinSoundTray.hx new file mode 100644 index 0000000..f02d9de --- /dev/null +++ b/source/game/objects/FunkinSoundTray.hx @@ -0,0 +1,417 @@ +package game.objects; + +import flixel.FlxG; +import flixel.system.FlxAssets; +import flixel.util.FlxColor; +import flixel.system.ui.FlxSoundTray; + +import openfl.Lib; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.display.Shape; +import openfl.text.TextField; +import openfl.text.TextFormat; +import openfl.text.TextFormatAlign; +import openfl.text.AntiAliasType; +#if flash +import openfl.text.GridFitType; +#end + +class FunkinSoundTray extends FlxSoundTray +{ + /** + * Volume indicator ring + */ + var _volumeRing:Shape; + + /** + * How wide the sound tray background is. + */ + var _width:Int = 80; + + // Constants for better maintainability + static final DISPLAY_TIME:Float = 0.7; + static final SLIDE_SPEED:Float = 1.5; + static final BG_ALPHA:Int = 0x7F000000; + static final TRAY_HEIGHT:Int = 52; + static final TEXT_Y_POS:Int = 38; + static final FONT_SIZE:Int = 10; + + /** + * Ring params + */ + final RING_RADIUS:Float = 15; + final RING_THICKNESS:Float = 8; + final RING_CENTER_X:Float = 40; + final RING_CENTER_Y:Float = 20; + + // Customizable colors + public var trayColor:FlxColor = 0x7F000000; + public var textColor:FlxColor = FlxColor.WHITE; + public var ringColor:FlxColor = FlxColor.WHITE; + public var outlineColor:FlxColor = FlxColor.RED; + + public var outlineThickness:Float = 1.5; + + public function new() + { + super(); + + // Remove default flixel sound tray elements + removeChildren(); + + /**The sound used when increasing the volume.**/ + volumeUpSound = "assets/sounds/volup"; + + /**The sound used when decreasing the volume.**/ + volumeDownSound = 'assets/sounds/voldown'; + + // Cache sounds if not silent + if (!silent) + { + FlxG.sound.cache('$volumeUpSound.ogg'); + FlxG.sound.cache('$volumeDownSound.ogg'); + } + + visible = false; + scaleX = _defaultScale; + scaleY = _defaultScale; + + // Create background + var bg:Bitmap = new Bitmap(new BitmapData(_width, TRAY_HEIGHT, true, trayColor)); + screenCenter(); + addChild(bg); + + // Create volume text + var text:TextField = createVolumeText(); + addChild(text); + + // Create volume ring + _volumeRing = new Shape(); + addChild(_volumeRing); + + y = -height; + visible = false; + } + + /** + * Creates and configures the volume text field + */ + private function createVolumeText():TextField + { + var text:TextField = new TextField(); + text.width = _width; + text.height = TRAY_HEIGHT; + text.multiline = true; + text.wordWrap = true; + text.selectable = false; + text.antiAliasType = AntiAliasType.ADVANCED; + + #if flash + text.embedFonts = true; + text.gridFitType = GridFitType.PIXEL; + #else + text.sharpness = 400; + #end + + var textFormat:TextFormat = new TextFormat(FlxAssets.FONT_DEFAULT, FONT_SIZE, textColor); + textFormat.align = TextFormatAlign.CENTER; + text.defaultTextFormat = textFormat; + text.text = "VOLUME"; + text.y = TEXT_Y_POS; + + return text; + } + + /** + * Draws a ring segment for volume indicator with outline + */ + function drawRingWithOutline(shape:Shape, centerX:Float, centerY:Float, radius:Float, thickness:Float, + startAngle:Float, endAngle:Float, fillColor:FlxColor, outlineColor:FlxColor, + outlineThickness:Float = 1.5, alpha:Float = 1.0):Void + { + var g = shape.graphics; + g.clear(); + + if (endAngle <= startAngle) + return; + + final SEGMENTS:Int = 32; + + // Convert degrees to radians + var startRad:Float = (startAngle - 90) * Math.PI / 180; + var endRad:Float = (endAngle - 90) * Math.PI / 180; + + var innerRadius = radius - thickness / 2; + var outerRadius = radius + thickness / 2; + + // Draw outline (slightly larger ring behind the main ring) + if (outlineThickness > 0) + { + g.lineStyle(outlineThickness, outlineColor, alpha); + g.beginFill(outlineColor, alpha); + + var outlineInnerRadius = innerRadius - outlineThickness / 2; + var outlineOuterRadius = outerRadius + outlineThickness / 2; + + // Move to starting point on inner outline circle + g.moveTo( + centerX + Math.cos(startRad) * outlineInnerRadius, + centerY + Math.sin(startRad) * outlineInnerRadius + ); + + // Draw arc along outer outline circle + for (i in 0...SEGMENTS) + { + var t = i / (SEGMENTS - 1); + var angle = startRad + t * (endRad - startRad); + g.lineTo( + centerX + Math.cos(angle) * outlineOuterRadius, + centerY + Math.sin(angle) * outlineOuterRadius + ); + } + + // Draw arc along inner outline circle (reverse direction) + for (i in 0...SEGMENTS) + { + var t = (SEGMENTS - 1 - i) / (SEGMENTS - 1); + var angle = startRad + t * (endRad - startRad); + g.lineTo( + centerX + Math.cos(angle) * outlineInnerRadius, + centerY + Math.sin(angle) * outlineInnerRadius + ); + } + + g.endFill(); + } + + // Draw main filled ring + g.lineStyle(); // Reset line style for fill + g.beginFill(fillColor, alpha); + + // Move to starting point on inner circle + g.moveTo( + centerX + Math.cos(startRad) * innerRadius, + centerY + Math.sin(startRad) * innerRadius + ); + + // Draw arc along outer circle + for (i in 0...SEGMENTS) + { + var t = i / (SEGMENTS - 1); + var angle = startRad + t * (endRad - startRad); + g.lineTo( + centerX + Math.cos(angle) * outerRadius, + centerY + Math.sin(angle) * outerRadius + ); + } + + // Draw arc along inner circle (reverse direction) + for (i in 0...SEGMENTS) + { + var t = (SEGMENTS - 1 - i) / (SEGMENTS - 1); + var angle = startRad + t * (endRad - startRad); + g.lineTo( + centerX + Math.cos(angle) * innerRadius, + centerY + Math.sin(angle) * innerRadius + ); + } + + g.endFill(); + } + + /** + * Alt method: draw segments with individual outlines + * Creates a more defined separation between segments + */ + function drawSegmentedRing(shape:Shape, centerX:Float, centerY:Float, radius:Float, thickness:Float, + startAngle:Float, endAngle:Float, fillColor:FlxColor, outlineColor:FlxColor, + segmentCount:Int = 10, outlineThickness:Float = 1.0):Void + { + var g = shape.graphics; + g.clear(); + + if (endAngle <= startAngle) + return; + + var segmentAngle:Float = (endAngle - startAngle) / segmentCount; + var innerRadius = radius - thickness / 2; + var outerRadius = radius + thickness / 2; + + for (i in 0...segmentCount) + { + var segmentStart:Float = startAngle + i * segmentAngle; + var segmentEnd:Float = segmentStart + segmentAngle; + + // Skip drawing if this segment would extend beyond the total end angle + if (segmentStart >= endAngle) break; + if (segmentEnd > endAngle) segmentEnd = endAngle; + + // Convert to radians + var segStartRad:Float = (segmentStart - 90) * Math.PI / 180; + var segEndRad:Float = (segmentEnd - 90) * Math.PI / 180; + + // Draw segment outline + g.lineStyle(outlineThickness, outlineColor, 1.0); + g.beginFill(fillColor, 1.0); + + // Create segment path + g.moveTo( + centerX + Math.cos(segStartRad) * innerRadius, + centerY + Math.sin(segStartRad) * innerRadius + ); + + // Outer arc + g.lineTo( + centerX + Math.cos(segStartRad) * outerRadius, + centerY + Math.sin(segStartRad) * outerRadius + ); + + var midSegments:Int = 8; + for (j in 1...midSegments) + { + var t:Float = j / (midSegments - 1); + var angle:Float = segStartRad + t * (segEndRad - segStartRad); + g.lineTo( + centerX + Math.cos(angle) * outerRadius, + centerY + Math.sin(angle) * outerRadius + ); + } + + g.lineTo( + centerX + Math.cos(segEndRad) * innerRadius, + centerY + Math.sin(segEndRad) * innerRadius + ); + + // Inner arc (back to start) + for (j in 0...midSegments) + { + var t:Float = (midSegments - 1 - j) / (midSegments - 1); + var angle:Float = segStartRad + t * (segEndRad - segStartRad); + g.lineTo( + centerX + Math.cos(angle) * innerRadius, + centerY + Math.sin(angle) * innerRadius + ); + } + + g.endFill(); + } + } + + override function update(MS:Float):Void + { + if (_timer > 0) + { + _timer -= (MS / 1000); + } + else if (y > -height) + { + var deltaY:Float = (MS / 1000) * height * SLIDE_SPEED; + y = Math.max(-height, y - deltaY); + + if (y <= -height) + { + visible = false; + active = false; + saveSoundSettings(); + } + } + } + + /** + * Saves sound settings to save data + */ + private function saveSoundSettings():Void + { + #if FLX_SAVE + if (FlxG.save.isBound) + { + FlxG.save.data.mute = FlxG.sound.muted; + FlxG.save.data.volume = FlxG.sound.volume; + FlxG.save.flush(); + } + #end + } + + /** + * Makes the little volume tray slide out. + * + * @param up Whether the volume is increasing. + */ + override function show(up:Bool = false):Void + { + if (!silent) + { + try + { + var sound = FlxAssets.getSoundAddExtension(up ? volumeUpSound : volumeDownSound); + if (sound != null) + FlxG.sound.play(sound); + } + catch (e:Dynamic) + { + trace("Sound not found: " + (up ? volumeUpSound : volumeDownSound)); + } + } + + _timer = DISPLAY_TIME; + y = 0; + visible = true; + active = true; + + var globalVolume:Int = Math.round(FlxG.sound.volume * 10); + if (FlxG.sound.muted) + { + globalVolume = 0; + } + + // Draw volume ring with outline + var volumeAngle:Float = 360 * (globalVolume / 10); + + // Choose which drawing method to use: + if (globalVolume > 0) { + // Ver 1: Continuous ring with outline + drawRingWithOutline(_volumeRing, RING_CENTER_X, RING_CENTER_Y, RING_RADIUS, RING_THICKNESS, + 0, volumeAngle, ringColor, outlineColor, outlineThickness, 1.0); + + // Ver 2: Segmented ring (uncomment to use instead) + // drawSegmentedRing(_volumeRing, RING_CENTER_X, RING_CENTER_Y, RING_RADIUS, RING_THICKNESS, + // 0, volumeAngle, ringColor, outlineColor, globalVolume, 1.0); + } else { + // Clear when volume is 0 + _volumeRing.graphics.clear(); + } + } + + /** + * Centers the sound tray on screen + */ + override function screenCenter():Void + { + scaleX = _defaultScale; + scaleY = _defaultScale; + + var stageWidth:Float = Lib.current.stage.stageWidth; + x = (stageWidth - _width * _defaultScale) * 0.5 - FlxG.game.x; + } + + // Compatibility methods for Flixel 6.0.0+ + #if (flixel > "6.0.0") + override function showAnim(volume:Float, ?sound:FlxSoundAsset, duration:Float = 1.0, label:String = "VOLUME") {} + + override function showIncrement():Void + { + show(true); + } + + override function showDecrement():Void + { + show(false); + } + + override function updateSize():Void + { + screenCenter(); + } + #end +} \ No newline at end of file diff --git a/source/game/objects/HealthIcon.hx b/source/game/objects/HealthIcon.hx index fd42863..05e3532 100644 --- a/source/game/objects/HealthIcon.hx +++ b/source/game/objects/HealthIcon.hx @@ -1,6 +1,7 @@ package game.objects; import flixel.FlxSprite; +import flixel.math.FlxMath; class HealthIcon extends FlxSprite { @@ -8,6 +9,10 @@ class HealthIcon extends FlxSprite private var isPlayer:Bool = false; private var char:String = ''; + public var targetScale:Float = 1; + public var lerpSpeed:Float = 7; + public var autoUpdateScale:Bool = true; + public function new(char:String = 'face', isPlayer:Bool = false, ?allowGPU:Bool = false) { super(); @@ -22,6 +27,44 @@ class HealthIcon extends FlxSprite if (sprTracker != null) setPosition(sprTracker.x + sprTracker.width + 12, sprTracker.y - 30); + + if (autoUpdateScale) + updateIconScale(elapsed); + } + + /** + * Updates icon scale using lerp for smooth animation + * @param elapsed Time elapsed since last frame + */ + public function updateIconScale(elapsed:Float):Void + { + if (scale.x != targetScale || scale.y != targetScale) + { + var lerpVal:Float = FlxMath.lerp(scale.x, targetScale, 1 - Math.exp(-elapsed * lerpSpeed)); + scale.set(lerpVal, lerpVal); + updateHitbox(); + } + } + + /** + * Sets target scale for lerp animation + * @param newScale New target scale + */ + public function setTargetScale(newScale:Float):Void + { + targetScale = newScale; + } + + /** + * Temporarily scales up the icon (e.g., when hitting a note) + * @param flashScale Scale for flash effect (default 1.15) + * @param resetScale Scale to return to after flash (default 1) + */ + public function flash(flashScale:Float = 1.15, resetScale:Float = 1):Void + { + scale.set(flashScale, flashScale); + targetScale = resetScale; + updateHitbox(); } private var iconOffsets:Array = [0, 0]; diff --git a/source/game/objects/MeshRender.hx b/source/game/objects/MeshRender.hx new file mode 100644 index 0000000..92e0e01 --- /dev/null +++ b/source/game/objects/MeshRender.hx @@ -0,0 +1,105 @@ +package game.objects; + +import flixel.FlxStrip; +import flixel.util.FlxColor; + +/** + * Yoinked from AustinEast, thanks hopefully u dont mind me using some of ur good code + * instead of my dumbass ugly code bro + */ +@:nullSafety +class MeshRender extends FlxStrip +{ + public var vertex_count(default, null):Int = 0; + public var index_count(default, null):Int = 0; + + public function new(x, y, ?col:FlxColor = FlxColor.WHITE) + { + super(x, y); + makeGraphic(1, 1, col); + } + + /** + * Add a vertex. + */ + public inline function build_vertex(x:Float, y:Float, u:Float = 0, v:Float = 0):Int + { + final index = vertex_count; + final pos = index << 1; + + vertices[pos] = x; + vertices[pos + 1] = y; + + uvtData[pos] = u; + uvtData[pos + 1] = v; + + vertex_count++; + return index; + } + + /** + * Build a triangle from three vertex indexes. + * @param a + * @param b + * @param c + */ + public function add_tri(a:Int, b:Int, c:Int):Void + { + indices[index_count] = a; + indices[index_count + 1] = b; + indices[index_count + 2] = c; + + index_count += 3; + } + + public function build_tri(ax:Float, ay:Float, bx:Float, by:Float, cx:Float, cy:Float, au:Float = 0, av:Float = 0, bu:Float = 0, bv:Float = 0, cu:Float = 0, cv:Float = 0):Void + { + add_tri(build_vertex(ax, ay, au, av), build_vertex(bx, by, bu, bv), build_vertex(cx, cy, cu, cv)); + } + + /** + * @param a top left vertex + * @param b top right vertex + * @param c bottom right vertex + * @param d bottom left vertex + */ + public function add_quad(a:Int, b:Int, c:Int, d:Int):Void + { + add_tri(a, b, c); + add_tri(a, c, d); + } + + /** + * Build a quad from four points. + * + * top right - a + * top left - b + * bottom right - c + * bottom left - d + */ + public function build_quad(ax:Float, ay:Float, bx:Float, by:Float, cx:Float, cy:Float, dx:Float, dy:Float, au:Float = 0, av:Float = 0, bu:Float = 0, bv:Float = 0, cu:Float = 0, cv:Float = 0, + du:Float = 0, dv:Float = 0):Void + { + // top left + var b = build_vertex(bx, by, bu, bv); + // top right + var a = build_vertex(ax, ay, au, av); + // bottom left + var c = build_vertex(cx, cy, cu, cv); + // bottom right + var d = build_vertex(dx, dy, du, dv); + + add_tri(a, b, c); + add_tri(a, c, d); + } + + public function clear() + { + vertices.length = 0; + indices.length = 0; + uvtData.length = 0; + colors.length = 0; + vertex_count = 0; + index_count = 0; + } +} diff --git a/source/game/objects/Note.hx b/source/game/objects/Note.hx index 247df6a..0366c10 100644 --- a/source/game/objects/Note.hx +++ b/source/game/objects/Note.hx @@ -1,11 +1,16 @@ package game.objects; +import math.Vector3; + import flixel.FlxG; import flixel.FlxSprite; import flixel.graphics.frames.FlxAtlasFrames; import flixel.math.FlxMath; +import flixel.math.FlxRect; import flixel.util.FlxColor; + import openfl.display.BitmapData; + import game.states.editors.ChartEditorState; using StringTools; @@ -19,6 +24,11 @@ typedef EventNote = { class Note extends FlxSprite { + public static final SUSTAIN_SIZE:Int = 44; + + public var vec3Cache:Vector3 = new Vector3(1, 1, 0); // for vector3 operations in modchart code + public var defScale:FlxPoint = FlxPoint.get(1, 1); // for modcharts to keep the scaling + public var extraData:Map = []; public var rawData:Dynamic; @@ -47,6 +57,11 @@ class Note extends FlxSprite public var isSustainNote:Bool = false; public var noteType(default, set):String = null; + public var mAngle:Float = 0; + public var bAngle:Float = 0; + public var typeOffsetX:Float = 0; // used to offset notes, mainly for note types. use in place of offset.x and offset.y when offsetting notetypes + public var typeOffsetY:Float = 0; + public var eventName:String = ''; public var eventLength:Int = 0; public var eventVal1:String = ''; @@ -111,6 +126,7 @@ class Note extends FlxSprite if(isSustainNote && !animation.curAnim.name.endsWith('end')) { scale.y *= ratio; + defScale.y = scale.y; updateHitbox(); } } @@ -206,13 +222,19 @@ class Note extends FlxSprite /*alpha = 0.6; multAlpha = 0.6;*/ hitsoundDisabled = true; + + #if MODCHART_ALLOWED + flipX = ClientPrefs.downScroll; + #else flipY = ClientPrefs.downScroll; + #end offsetX += width / 2; copyAngle = false; animation.play(colArray[noteData % 4] + 'holdend'); + defScale.copyFrom(scale); updateHitbox(); offsetX -= width / 2; @@ -224,18 +246,19 @@ class Note extends FlxSprite { prevNote.animation.play(colArray[prevNote.noteData % 4] + 'hold'); - prevNote.scale.y *= Conductor.stepCrochet / 191.84; + prevNote.scale.y *= Conductor.stepCrochet / 102 * 1.05; if(PlayState.instance != null) { prevNote.scale.y *= PlayState.instance.songSpeed; } if(PlayState.isPixelStage) { - prevNote.scale.y *= 2.44; + prevNote.scale.y *= 1.22; prevNote.scale.y *= (6 / height); //Auto adjust note size } + + prevNote.defScale?.copyFrom(prevNote.scale); prevNote.updateHitbox(); - // prevNote.setGraphicSize(); } if(PlayState.isPixelStage) { @@ -245,16 +268,19 @@ class Note extends FlxSprite } else if(!isSustainNote) { earlyHitMult = 1; } + + defScale?.copyFrom(scale); x += offsetX; } + var _lastNoteOffX:Float = 0; var lastNoteOffsetXForPixelAutoAdjusting:Float = 0; - var lastNoteScaleToo:Float = 1; public var originalHeightForCalcs:Float = 6; - function reloadNote(?prefix:String = '', ?texture:String = '', ?suffix:String = '') { + public var correctionOffset:Float = 0; //dont mess with this + public function reloadNote(prefix:String = '', texture:String = '', postfix:String = '') { prefix ??= ''; texture ??= ''; - suffix ??= ''; + postfix ??= ''; var skin:String = texture; if(texture.length < 1) { @@ -265,61 +291,52 @@ class Note extends FlxSprite } var animName:String = null; - if(animation.curAnim != null) { + if(animation?.curAnim != null) { animName = animation.curAnim.name; } var arraySkin:Array = skin.split('/'); - arraySkin[arraySkin.length-1] = prefix + arraySkin[arraySkin.length-1] + suffix; + arraySkin[arraySkin.length-1] = prefix + arraySkin[arraySkin.length-1] + postfix; var lastScaleY:Float = scale.y; - var blahblah:String = arraySkin.join('/'); + var skinName:String = arraySkin.join('/'); if(PlayState.isPixelStage) { if(isSustainNote) { - loadGraphic(Paths.image('pixelUI/' + blahblah + 'ENDS')); - width = width / 4; - height = height / 2; - originalHeightForCalcs = height; - loadGraphic(Paths.image('pixelUI/' + blahblah + 'ENDS'), true, Math.floor(width), Math.floor(height)); + var graphic = Paths.image('pixelUI/' + skinName + 'ENDS'); + loadGraphic(graphic, true, Math.floor(graphic.width / 4), Math.floor(graphic.height / 2)); } else { - loadGraphic(Paths.image('pixelUI/' + blahblah)); - width = width / 4; - height = height / 5; - loadGraphic(Paths.image('pixelUI/' + blahblah), true, Math.floor(width), Math.floor(height)); + var graphic = Paths.image('pixelUI/' + skinName); + loadGraphic(graphic, true, Math.floor(graphic.width / 4), Math.floor(graphic.height / 5)); } setGraphicSize(Std.int(width * PlayState.daPixelZoom)); loadPixelNoteAnims(); antialiasing = false; if(isSustainNote) { - offsetX += lastNoteOffsetXForPixelAutoAdjusting; - lastNoteOffsetXForPixelAutoAdjusting = (width - 7) * (PlayState.daPixelZoom / 2); - offsetX -= lastNoteOffsetXForPixelAutoAdjusting; - - /*if(animName != null && !animName.endsWith('end')) - { - lastScaleY /= lastNoteScaleToo; - lastNoteScaleToo = (6 / height); - lastScaleY *= lastNoteScaleToo; - }*/ + offsetX += _lastNoteOffX; + _lastNoteOffX = (width - 7) * (PlayState.daPixelZoom / 2); + offsetX -= _lastNoteOffX; } } else { - frames = Paths.getSparrowAtlas(blahblah); + frames = Paths.getSparrowAtlas(skinName); loadNoteAnims(); antialiasing = ClientPrefs.globalAntialiasing; + if(!isSustainNote) + { + centerOffsets(); + centerOrigin(); + } } + if(isSustainNote) { scale.y = lastScaleY; } + + defScale?.copyFrom(scale); updateHitbox(); if(animName != null) animation.play(animName, true); - - if(inEditor) { - setGraphicSize(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE); - updateHitbox(); - } } function loadNoteAnims() { @@ -378,4 +395,25 @@ class Note extends FlxSprite alpha = 0.3; } } + + @:noCompletion + override function set_clipRect(rect:FlxRect):FlxRect + { + clipRect = rect; + + if (frames != null) + frame = frames.frames[animation.frameIndex]; + + return rect; + } + + override function destroy() + { + texture = ''; + vec3Cache = null; + defScale?.put(); + clipRect = flixel.util.FlxDestroyUtil.put(clipRect); + + super.destroy(); + } } \ No newline at end of file diff --git a/source/game/objects/StrumNote.hx b/source/game/objects/StrumNote.hx index af68240..fb2781a 100644 --- a/source/game/objects/StrumNote.hx +++ b/source/game/objects/StrumNote.hx @@ -4,18 +4,23 @@ import flixel.FlxG; import flixel.FlxSprite; import flixel.graphics.frames.FlxAtlasFrames; +import math.Vector3; + using StringTools; class StrumNote extends FlxSprite { - private var colorSwap:ColorSwap; + public var vec3Cache:Vector3 = new Vector3(1, 1); // for vector3 operations in modchart code + public var defScale:FlxPoint = FlxPoint.get(); // for modcharts to keep the scaling + + public var noteData:Int = 0; public var resetAnim:Float = 0; - private var noteData:Int = 0; public var direction:Float = 90; //plan on doing scroll directions soon -bb public var downScroll:Bool = false; //plan on doing scroll directions soon -bb public var sustainReduce:Bool = true; private var player:Int; + private var colorSwap:ColorSwap; public var texture(default, set):String = null; private function set_texture(value:String):String { @@ -113,6 +118,8 @@ class StrumNote extends FlxSprite animation.addByPrefix('confirm', 'right confirm', 24, false); } } + + defScale?.copyFrom(scale); updateHitbox(); if(lastAnim != null) @@ -167,4 +174,12 @@ class StrumNote extends FlxSprite } } } + + override function destroy() + { + defScale?.put(); + vec3Cache = null; + + super.destroy(); + } } diff --git a/source/game/scripting/FunkinHScript.hx b/source/game/scripting/FunkinHScript.hx index faa5fa5..0bf49f5 100644 --- a/source/game/scripting/FunkinHScript.hx +++ b/source/game/scripting/FunkinHScript.hx @@ -1,23 +1,105 @@ package game.scripting; -import rulescript.parsers.HxParser; +import game.scripting.HScriptParser as HxParser; +import game.scripting.RuleScriptInterpEx as Interp; #if sys import sys.io.File; #end using StringTools; -class FunkinHScript extends FunkinRScript +class FunkinHScript extends FunkinRuleScript { public function new(path:String, parentInstance:Dynamic = null, skipCreate:Bool = false) { super(path, parentInstance, skipCreate); - scriptType = "HScript"; set("FunkinHScript", FunkinHScript); - rule.parser = new HxParser(); - rule.getParser(HxParser).allowAll(); + var hxParser = new HxParser(); + rule.parser = hxParser; + + hxParser.allowAll(); + + var preprocessors = getHScriptPreprocessors(); + /*trace('HScript Preprocessors for $path:'); + for (key => value in preprocessors) { + trace(' $key = $value (type: ${Type.typeof(value)})'); + }*/ + + hxParser.setPreprocessorValues(preprocessors); + hxParser.setParserParameters({ + strictMode: true, + requireSemicolons: false, + reportWarnings: true + }); var scriptToRun:String = loadScriptContent(path); execute(scriptToRun, skipCreate); } + + public function executeString(code:String):Dynamic { + try { + rule.execute(code); + return null; + } catch (e:Dynamic) { + if (rule.errorHandler != null) + rule.errorHandler(e); + else + trace('Error in executeString: ${e.message}'); + return null; + } + } + + public static dynamic function getHScriptPreprocessors() { + var preprocessors:Map = new Map(); + + // I hate my life 💔 + preprocessors.set("mobile", #if mobile true #else false #end); + preprocessors.set("desktop", #if desktop true #else false #end); + preprocessors.set("web", #if web true #else false #end); + preprocessors.set("debug", #if debug true #else false #end); + preprocessors.set("release", #if !debug true #else false #end); + + preprocessors.set("cpp", #if cpp true #else false #end); + preprocessors.set("hl", #if hl true #else false #end); + preprocessors.set("neko", #if neko true #else false #end); + preprocessors.set("js", #if js true #else false #end); + preprocessors.set("lua", #if lua true #else false #end); + preprocessors.set("php", #if php true #else false #end); + preprocessors.set("java", #if java true #else false #end); + preprocessors.set("cs", #if cs true #else false #end); + preprocessors.set("python", #if python true #else false #end); + + preprocessors.set("windows", #if windows true #else false #end); + preprocessors.set("mac", #if mac true #else false #end); + preprocessors.set("linux", #if linux true #else false #end); + preprocessors.set("html5", #if html5 true #else false #end); + preprocessors.set("switch", #if switch true #else false #end); + preprocessors.set("android", #if android true #else false #end); + preprocessors.set("ios", #if ios true #else false #end); + + preprocessors.set("ENGINE_VER", Application.current.meta.get('version')); + + preprocessors.set("sys", #if sys true #else false #end); + preprocessors.set("target.threaded", #if target.threaded true #else false #end); + preprocessors.set("target.static", #if target.static true #else false #end); + + preprocessors.set("haxe4", #if (haxe_ver >= 4.0) true #else false #end); + preprocessors.set("haxe3", #if (haxe_ver >= 3.0) true #else false #end); + + preprocessors.set("MODS_ALLOWED", #if MODS_ALLOWED true #else false #end); + preprocessors.set("LUA_ALLOWED", #if LUA_ALLOWED true #else false #end); + preprocessors.set("MODCHART_ALLOWED", #if MODCHART_ALLOWED true #else false #end); + preprocessors.set("NDLL_ALLOWED", #if NDLL_ALLOWED true #else false #end); + preprocessors.set("ACHIEVEMENTS_ALLOWED", #if ACHIEVEMENTS_ALLOWED true #else false #end); + preprocessors.set("VIDEOS_ALLOWED", #if VIDEOS_ALLOWED true #else false #end); + + var staticDefines = game.backend.utils.MacroUtil.defines.copy(); + for (key => value in staticDefines) { + if (!preprocessors.exists(key)) { + preprocessors.set(key, value); + } + } + + return preprocessors; + } } \ No newline at end of file diff --git a/source/game/scripting/FunkinLua.hx b/source/game/scripting/FunkinLua.hx index 420a170..f444611 100644 --- a/source/game/scripting/FunkinLua.hx +++ b/source/game/scripting/FunkinLua.hx @@ -1,10 +1,12 @@ package game.scripting; #if LUA_ALLOWED -import llua.Lua; -import llua.LuaL; -import llua.State; -import llua.Convert; +import hxluajit.Lua; +import hxluajit.LuaL; +import hxluajit.Types; +import hxluajit.wrapper.LuaConverter; +import hxluajit.wrapper.LuaUtils; +import hxluajit.wrapper.LuaError; #end import flixel.FlxG; @@ -22,7 +24,6 @@ import flixel.util.FlxTimer; import flixel.FlxSprite; import flixel.FlxCamera; import flixel.util.FlxColor; -import flixel.FlxBasic; import flixel.FlxObject; import flixel.FlxSprite; import flixel.math.FlxMath; @@ -31,14 +32,8 @@ import flixel.addons.transition.FlxTransitionableState; import flixel.addons.display.FlxBackdrop; import openfl.Lib; -import openfl.display.BlendMode; import openfl.display.BitmapData; import openfl.filters.BitmapFilter; -import openfl.utils.Assets; - -#if (!flash && sys) -import flixel.addons.display.FlxRuntimeShader; -#end #if sys import sys.FileSystem; @@ -51,3594 +46,1117 @@ import game.PlayState; import game.backend.Controls; import game.objects.Character; -import game.objects.DialogueBoxPsych; import game.objects.StrumNote; import game.shaders.flixel.FlxShader; #if HSCRIPT_ALLOWED -import rulescript.parsers.HxParser; -import game.scripting.FunkinRScript.RuleScriptInterpEx as Interp; +import game.scripting.HScriptParser as HxParser; +import game.scripting.RuleScriptInterpEx as Interp; #end #if DISCORD_ALLOWED import api.Discord; #end -#if flixel_animate -import animate.internal.Timeline; -import animate.FlxAnimateJson.TimelineJson; -#end +import game.scripting.lua.*; using StringTools; class FunkinLua { - public static var Function_Stop:Dynamic = 1; - public static var Function_Continue:Dynamic = 0; - public static var Function_StopLua:Dynamic = 2; - - //public var errorHandler:String->Void; - #if LUA_ALLOWED - public var lua:State = null; - #end - public var camTarget:FlxCamera; - public var scriptName:String = ''; - public var closed:Bool = false; - - public var modFolder:String = null; - - #if HSCRIPT_ALLOWED - public static var hscript:HScript = null; - #end - - public static var useCustomFunctions:Bool = false; - public static var customFunctions:Map = new Map(); - - public var callbacks:Map = new Map(); - - public function new(script:String, ?isString:Bool = false) { - #if LUA_ALLOWED - lua = LuaL.newstate(); - LuaL.openlibs(lua); - Lua.init_callbacks(lua); - - //trace('Lua version: ' + Lua.version()); - //trace("LuaJIT version: " + Lua.versionJIT()); - - try{ - #if HSCRIPT_ALLOWED - var result:Dynamic = null; - if(!isString) result = LuaL.dofile(lua, script); - else result = LuaL.dostring(lua, script); - - var resultStr:String = Lua.tostring(lua, result); - if(resultStr != null && result != 0) { - trace(isString ? 'Error on lua code! $resultStr' : 'Error on lua script! $resultStr'); - #if windows - if(!isString) CoolUtil.showPopUp(resultStr, 'Error on lua script!', MSG_INFORMATION); - #else - luaTrace((isString ? 'Error loading lua code: $resultStr' : ('Error loading lua script: "$script"\n' + resultStr)), true, false, FlxColor.RED); - #end - lua = null; - return; - } - #else - var result:Dynamic = null; - if(!isString) result = LuaL.dofile(lua, script); - else result = LuaL.dostring(lua, script); - - var resultStr:String = Lua.tostring(lua, result); - if(resultStr != null && result != 0) { - trace('Error on lua script! ' + resultStr); - #if !mac - CoolUtil.showPopUp(resultStr, 'Error on lua script!', MSG_INFORMATION); - #else - luaTrace('Error loading lua script: "$script"\n' + resultStr, true, false, FlxColor.RED); - #end - lua = null; - return; - } - #end - } catch(e:Dynamic) { - trace(e); - return; - } - scriptName = script; - var myFolder:Array = this.scriptName.trim().split('/'); - #if MODS_ALLOWED - if(myFolder[0] + '/' == Paths.mods() && (Paths.currentModDirectory == myFolder[1] || Paths.getGlobalMods().contains(myFolder[1]))) //is inside mods folder - this.modFolder = myFolder[1]; - #end - - #if HSCRIPT_ALLOWED - initHaxeModule(); - #end - - #if HSCRIPT_ALLOWED trace((isString ? 'lua string loaded succesfully' : 'lua file loaded succesfully: $script')); - #else trace('lua file loaded succesfully: $script'); #end - - // Lua shit - set('Function_StopLua', Function_StopLua); - set('Function_Stop', Function_Stop); - set('Function_Continue', Function_Continue); - set('luaDebugMode', false); - set('luaDeprecatedWarnings', true); - set('inChartEditor', false); - - // Song/Week shit - set('curBpm', Conductor.bpm); - set('bpm', PlayState.SONG.bpm); - set('scrollSpeed', PlayState.SONG.speed); - set('crochet', Conductor.crochet); - set('stepCrochet', Conductor.stepCrochet); - set('songLength', FlxG.sound.music.length); - set('songName', PlayState.SONG.song); - set('songPath', Paths.formatToSongPath(PlayState.SONG.song)); - set('startedCountdown', false); - set('curStage', PlayState.SONG.stage); - - set('isStoryMode', PlayState.isStoryMode); - set('difficulty', PlayState.storyDifficulty); - - var difficultyName:String = CoolUtil.difficulties[PlayState.storyDifficulty]; - set('difficultyName', difficultyName); - set('difficultyPath', Paths.formatToSongPath(difficultyName)); - set('weekRaw', PlayState.storyWeek); - set('week', WeekData.weeksList[PlayState.storyWeek]); - set('seenCutscene', PlayState.seenCutscene); - - // Camera poo - set('cameraX', 0); - set('cameraY', 0); - - // Screen stuff - set('screenWidth', FlxG.width); - set('screenHeight', FlxG.height); - - // PlayState cringe ass nae nae bullcrap - set('curBeat', 0); - set('curStep', 0); - set('curDecBeat', 0); - set('curDecStep', 0); - - set('score', 0); - set('misses', 0); - set('hits', 0); - - set('rating', 0); - set('ratingName', ''); - set('ratingFC', ''); - set('version', Application.current.meta.get('version')); - - set('inGameOver', false); - set('mustHitSection', false); - set('altAnim', false); - set('gfSection', false); - - // Gameplay settings - set('healthGainMult', PlayState.instance.healthGain); - set('healthLossMult', PlayState.instance.healthLoss); - set('playbackRate', PlayState.instance.playbackRate); - set('instakillOnMiss', PlayState.instance.instakillOnMiss); - set('botPlay', PlayState.instance.cpuControlled); - set('practice', PlayState.instance.practiceMode); - - for (i in 0...4) { - set('defaultPlayerStrumX' + i, 0); - set('defaultPlayerStrumY' + i, 0); - set('defaultOpponentStrumX' + i, 0); - set('defaultOpponentStrumY' + i, 0); - } - - // Default character positions woooo - set('defaultBoyfriendX', PlayState.instance.BF_X); - set('defaultBoyfriendY', PlayState.instance.BF_Y); - set('defaultOpponentX', PlayState.instance.DAD_X); - set('defaultOpponentY', PlayState.instance.DAD_Y); - set('defaultGirlfriendX', PlayState.instance.GF_X); - set('defaultGirlfriendY', PlayState.instance.GF_Y); - - // Character shit - set('boyfriendName', PlayState.SONG.player1); - set('dadName', PlayState.SONG.player2); - set('gfName', PlayState.SONG.gfVersion); - - // Some settings, no jokes - set('downscroll', ClientPrefs.downScroll); - set('middlescroll', ClientPrefs.middleScroll); - set('framerate', ClientPrefs.framerate); - set('ghostTapping', ClientPrefs.ghostTapping); - set('hideHud', ClientPrefs.hideHud); - set('timeBarType', ClientPrefs.timeBarType); - set('scoreZoom', ClientPrefs.scoreZoom); - set('cameraZoomOnBeat', ClientPrefs.camZooms); - set('flashingLights', ClientPrefs.flashing); - set('noteOffset', ClientPrefs.noteOffset); - set('healthBarAlpha', ClientPrefs.healthBarAlpha); - set('noResetButton', ClientPrefs.noReset); - set('lowQuality', ClientPrefs.lowQuality); - set('shadersEnabled', ClientPrefs.shaders); - set('scriptName', scriptName); - set('currentModDirectory', Paths.currentModDirectory); - - set('buildTarget', CoolUtil.getBuildTarget()); - - // custom substate - Lua_helper.add_callback(lua, "openCustomSubstate", function(name:String, pauseGame:Bool = false) { - if(pauseGame) - { - PlayState.instance.persistentUpdate = false; - PlayState.instance.persistentDraw = true; - PlayState.instance.paused = true; - if(FlxG.sound.music != null) { - FlxG.sound.music.pause(); - PlayState.instance.vocals.pause(); - } - } - PlayState.instance.openSubState(new CustomSubstate(name)); - }); - - Lua_helper.add_callback(lua, "closeCustomSubstate", function() { - if(CustomSubstate.instance != null) - { - PlayState.instance.closeSubState(); - CustomSubstate.instance = null; - return true; - } - return false; - }); - - // shader shit - Lua_helper.add_callback(lua, "initLuaShader", function(name:String, glslVersion:Int = 120) { - if(!ClientPrefs.shaders) return false; - - #if (!flash && MODS_ALLOWED && sys) - return initLuaShader(name, glslVersion); - #else - luaTrace("initLuaShader: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - return false; - }); - - Lua_helper.add_callback(lua, "setSpriteShader", function(obj:String, shader:String) { - if(!ClientPrefs.shaders) return false; - - #if (!flash && MODS_ALLOWED && sys) - if(!PlayState.instance.runtimeShaders.exists(shader) && !initLuaShader(shader)) - { - luaTrace('setSpriteShader: Shader $shader is missing!', false, false, FlxColor.RED); - return false; - } - - var killMe:Array = obj.split('.'); - var leObj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - leObj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(leObj != null) { - var arr:Array = PlayState.instance.runtimeShaders.get(shader); - leObj.shader = new FlxRuntimeShader(arr[0], arr[1]); - return true; - } - #else - luaTrace("setSpriteShader: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - return false; - }); - Lua_helper.add_callback(lua, "removeSpriteShader", function(obj:String) { - var killMe:Array = obj.split('.'); - var leObj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - leObj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(leObj != null) { - leObj.shader = null; - return true; - } - return false; - }); - - - Lua_helper.add_callback(lua, "getShaderBool", function(obj:String, prop:String) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if (shader == null) - { - Lua.pushnil(lua); - return null; - } - return shader.getBool(prop); - #else - luaTrace("getShaderBool: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - #end - }); - Lua_helper.add_callback(lua, "getShaderBoolArray", function(obj:String, prop:String) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if (shader == null) - { - Lua.pushnil(lua); - return null; - } - return shader.getBoolArray(prop); - #else - luaTrace("getShaderBoolArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - #end - }); - Lua_helper.add_callback(lua, "getShaderInt", function(obj:String, prop:String) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if (shader == null) - { - Lua.pushnil(lua); - return null; - } - return shader.getInt(prop); - #else - luaTrace("getShaderInt: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - #end - }); - Lua_helper.add_callback(lua, "getShaderIntArray", function(obj:String, prop:String) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if (shader == null) - { - Lua.pushnil(lua); - return null; - } - return shader.getIntArray(prop); - #else - luaTrace("getShaderIntArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - #end - }); - Lua_helper.add_callback(lua, "getShaderFloat", function(obj:String, prop:String) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if (shader == null) - { - Lua.pushnil(lua); - return null; - } - return shader.getFloat(prop); - #else - luaTrace("getShaderFloat: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - #end - }); - Lua_helper.add_callback(lua, "getShaderFloatArray", function(obj:String, prop:String) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if (shader == null) - { - Lua.pushnil(lua); - return null; - } - return shader.getFloatArray(prop); - #else - luaTrace("getShaderFloatArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - #end - }); - - - Lua_helper.add_callback(lua, "setShaderBool", function(obj:String, prop:String, value:Bool) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if(shader == null) return; - - shader.setBool(prop, value); - #else - luaTrace("setShaderBool: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - }); - Lua_helper.add_callback(lua, "setShaderBoolArray", function(obj:String, prop:String, values:Dynamic) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if(shader == null) return; - - shader.setBoolArray(prop, values); - #else - luaTrace("setShaderBoolArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - }); - Lua_helper.add_callback(lua, "setShaderInt", function(obj:String, prop:String, value:Int) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if(shader == null) return; - - shader.setInt(prop, value); - #else - luaTrace("setShaderInt: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - }); - Lua_helper.add_callback(lua, "setShaderIntArray", function(obj:String, prop:String, values:Dynamic) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if(shader == null) return; - - shader.setIntArray(prop, values); - #else - luaTrace("setShaderIntArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - }); - Lua_helper.add_callback(lua, "setShaderFloat", function(obj:String, prop:String, value:Float) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if(shader == null) return; - - shader.setFloat(prop, value); - #else - luaTrace("setShaderFloat: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - }); - Lua_helper.add_callback(lua, "setShaderFloatArray", function(obj:String, prop:String, values:Dynamic) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if(shader == null) return; - - shader.setFloatArray(prop, values); - #else - luaTrace("setShaderFloatArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - }); - - Lua_helper.add_callback(lua, "setShaderSampler2D", function(obj:String, prop:String, bitmapdataPath:String) { - #if (!flash && MODS_ALLOWED && sys) - var shader:FlxRuntimeShader = getShader(obj); - if(shader == null) return; - - // trace('bitmapdatapath: $bitmapdataPath'); - var value = Paths.image(bitmapdataPath); - if(value != null && value.bitmap != null) - { - // trace('Found bitmapdata. Width: ${value.bitmap.width} Height: ${value.bitmap.height}'); - shader.setSampler2D(prop, value.bitmap); - } - #else - luaTrace("setShaderSampler2D: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); - #end - }); - - - // - Lua_helper.add_callback(lua, "getRunningScripts", function(){ - var runningScripts:Array = []; - for (idx in 0...PlayState.instance.luaArray.length) - runningScripts.push(PlayState.instance.luaArray[idx].scriptName); - - - return runningScripts; - }); - - Lua_helper.add_callback(lua, "callOnLuas", function(?funcName:String, ?args:Array, ignoreStops=false, ignoreSelf=true, ?exclusions:Array){ - if(funcName==null){ - #if (linc_luajit >= "0.0.6") - LuaL.error(lua, "bad argument #1 to 'callOnLuas' (string expected, got nil)"); - #end - return; - } - if(args==null)args = []; - - if(exclusions==null)exclusions=[]; - - Lua.getglobal(lua, 'scriptName'); - var daScriptName = Lua.tostring(lua, -1); - Lua.pop(lua, 1); - if(ignoreSelf && !exclusions.contains(daScriptName))exclusions.push(daScriptName); - PlayState.instance.callOnLuas(funcName, args, ignoreStops, exclusions); - }); - - Lua_helper.add_callback(lua, "callScript", function(?luaFile:String, ?funcName:String, ?args:Array){ - if(luaFile==null){ - #if (linc_luajit >= "0.0.6") - LuaL.error(lua, "bad argument #1 to 'callScript' (string expected, got nil)"); - #end - return; - } - if(funcName==null){ - #if (linc_luajit >= "0.0.6") - LuaL.error(lua, "bad argument #2 to 'callScript' (string expected, got nil)"); - #end - return; - } - if(args==null){ - args = []; - } - var cervix = luaFile + ".lua"; - if(luaFile.endsWith(".lua"))cervix=luaFile; - var doPush = false; - #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(cervix))) - { - cervix = Paths.modFolders(cervix); - doPush = true; - } - else if(FileSystem.exists(cervix)) - { - doPush = true; - } - else { - cervix = Paths.getPreloadPath(cervix); - if(FileSystem.exists(cervix)) { - doPush = true; - } - } - #else - cervix = Paths.getPreloadPath(cervix); - if(Assets.exists(cervix)) { - doPush = true; - } - #end - if(doPush) - { - for (luaInstance in PlayState.instance.luaArray) - { - if(luaInstance.scriptName == cervix) - { - luaInstance.call(funcName, args); - - return; - } - - } - } - Lua.pushnil(lua); - - }); - - #if MODS_ALLOWED - Lua_helper.add_callback(lua, "getModSetting", function(saveTag:String, ?modName:String = null) { - if(modName == null) - { - if(this.modFolder == null) - { - luaTrace('getModSetting: Argument #2 is null and script is not inside a packed Mod folder!', false, false, FlxColor.RED); - return null; - } - modName = this.modFolder; - } - return CoolUtil.getModSetting(saveTag, modName); //Function was moved to CoolUtil - }); - #end - - Lua_helper.add_callback(lua, "getGlobalFromScript", function(?luaFile:String, ?global:String){ // returns the global from a script - if(luaFile==null){ - #if (linc_luajit >= "0.0.6") - LuaL.error(lua, "bad argument #1 to 'getGlobalFromScript' (string expected, got nil)"); - #end - return; - } - if(global==null){ - #if (linc_luajit >= "0.0.6") - LuaL.error(lua, "bad argument #2 to 'getGlobalFromScript' (string expected, got nil)"); - #end - return; - } - var cervix = luaFile + ".lua"; - if(luaFile.endsWith(".lua"))cervix=luaFile; - var doPush = false; - #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(cervix))) - { - cervix = Paths.modFolders(cervix); - doPush = true; - } - else if(FileSystem.exists(cervix)) - { - doPush = true; - } - else { - cervix = Paths.getPreloadPath(cervix); - if(FileSystem.exists(cervix)) { - doPush = true; - } - } - #else - cervix = Paths.getPreloadPath(cervix); - if(Assets.exists(cervix)) { - doPush = true; - } - #end - if(doPush) - { - for (luaInstance in PlayState.instance.luaArray) - { - if(luaInstance.scriptName == cervix) - { - Lua.getglobal(luaInstance.lua, global); - if(Lua.isnumber(luaInstance.lua,-1)){ - Lua.pushnumber(lua, Lua.tonumber(luaInstance.lua, -1)); - }else if(Lua.isstring(luaInstance.lua,-1)){ - Lua.pushstring(lua, Lua.tostring(luaInstance.lua, -1)); - }else if(Lua.isboolean(luaInstance.lua,-1)){ - Lua.pushboolean(lua, Lua.toboolean(luaInstance.lua, -1)); - }else{ - Lua.pushnil(lua); - } - // TODO: table - - Lua.pop(luaInstance.lua,1); // remove the global - - return; - } - - } - } - Lua.pushnil(lua); - }); - Lua_helper.add_callback(lua, "setGlobalFromScript", function(luaFile:String, global:String, val:Dynamic){ // returns the global from a script - var cervix = luaFile + ".lua"; - if(luaFile.endsWith(".lua"))cervix=luaFile; - var doPush = false; - #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(cervix))) - { - cervix = Paths.modFolders(cervix); - doPush = true; - } - else if(FileSystem.exists(cervix)) - { - doPush = true; - } - else { - cervix = Paths.getPreloadPath(cervix); - if(FileSystem.exists(cervix)) { - doPush = true; - } - } - #else - cervix = Paths.getPreloadPath(cervix); - if(Assets.exists(cervix)) { - doPush = true; - } - #end - if(doPush) - { - for (luaInstance in PlayState.instance.luaArray) - { - if(luaInstance.scriptName == cervix) - { - luaInstance.set(global, val); - } - - } - } - Lua.pushnil(lua); - }); - /*Lua_helper.add_callback(lua, "getGlobals", function(luaFile:String){ // returns a copy of the specified file's globals - var cervix = luaFile + ".lua"; - if(luaFile.endsWith(".lua"))cervix=luaFile; - var doPush = false; - #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(cervix))) - { - cervix = Paths.modFolders(cervix); - doPush = true; - } - else if(FileSystem.exists(cervix)) - { - doPush = true; - } - else { - cervix = Paths.getPreloadPath(cervix); - if(FileSystem.exists(cervix)) { - doPush = true; - } - } - #else - cervix = Paths.getPreloadPath(cervix); - if(Assets.exists(cervix)) { - doPush = true; - } - #end - if(doPush) - { - for (luaInstance in PlayState.instance.luaArray) - { - if(luaInstance.scriptName == cervix) - { - Lua.newtable(lua); - var tableIdx = Lua.gettop(lua); - - Lua.pushvalue(luaInstance.lua, Lua.LUA_GLOBALSINDEX); - Lua.pushnil(luaInstance.lua); - while(Lua.next(luaInstance.lua, -2) != 0) { - // key = -2 - // value = -1 - - var pop:Int = 0; - - // Manual conversion - // first we convert the key - if(Lua.isnumber(luaInstance.lua,-2)){ - Lua.pushnumber(lua, Lua.tonumber(luaInstance.lua, -2)); - pop++; - }else if(Lua.isstring(luaInstance.lua,-2)){ - Lua.pushstring(lua, Lua.tostring(luaInstance.lua, -2)); - pop++; - }else if(Lua.isboolean(luaInstance.lua,-2)){ - Lua.pushboolean(lua, Lua.toboolean(luaInstance.lua, -2)); - pop++; - } - // TODO: table - - - // then the value - if(Lua.isnumber(luaInstance.lua,-1)){ - Lua.pushnumber(lua, Lua.tonumber(luaInstance.lua, -1)); - pop++; - }else if(Lua.isstring(luaInstance.lua,-1)){ - Lua.pushstring(lua, Lua.tostring(luaInstance.lua, -1)); - pop++; - }else if(Lua.isboolean(luaInstance.lua,-1)){ - Lua.pushboolean(lua, Lua.toboolean(luaInstance.lua, -1)); - pop++; - } - // TODO: table - - if(pop==2)Lua.rawset(lua, tableIdx); // then set it - Lua.pop(luaInstance.lua, 1); // for the loop - } - Lua.pop(luaInstance.lua,1); // end the loop entirely - Lua.pushvalue(lua, tableIdx); // push the table onto the stack so it gets returned - - return; - } - - } - } - Lua.pushnil(lua); - });*/ - Lua_helper.add_callback(lua, "isRunning", function(luaFile:String){ - var cervix = luaFile + ".lua"; - if(luaFile.endsWith(".lua"))cervix=luaFile; - var doPush = false; - #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(cervix))) - { - cervix = Paths.modFolders(cervix); - doPush = true; - } - else if(FileSystem.exists(cervix)) - { - doPush = true; - } - else { - cervix = Paths.getPreloadPath(cervix); - if(FileSystem.exists(cervix)) { - doPush = true; - } - } - #else - cervix = Paths.getPreloadPath(cervix); - if(Assets.exists(cervix)) { - doPush = true; - } - #end - - if(doPush) - { - for (luaInstance in PlayState.instance.luaArray) - { - if(luaInstance.scriptName == cervix) - return true; - - } - } - return false; - }); - - - Lua_helper.add_callback(lua, "addLuaScript", function(luaFile:String, ?ignoreAlreadyRunning:Bool = false) { //would be dope asf. - var cervix = luaFile + ".lua"; - if(luaFile.endsWith(".lua"))cervix=luaFile; - var doPush = false; - #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(cervix))) - { - cervix = Paths.modFolders(cervix); - doPush = true; - } - else if(FileSystem.exists(cervix)) - { - doPush = true; - } - else { - cervix = Paths.getPreloadPath(cervix); - if(FileSystem.exists(cervix)) { - doPush = true; - } - } - #else - cervix = Paths.getPreloadPath(cervix); - if(Assets.exists(cervix)) { - doPush = true; - } - #end - - if(doPush) - { - if(!ignoreAlreadyRunning) - { - for (luaInstance in PlayState.instance.luaArray) - { - if(luaInstance.scriptName == cervix) - { - luaTrace('addLuaScript: The script "' + cervix + '" is already running!'); - return; - } - } - } - PlayState.instance.luaArray.push(new FunkinLua(cervix)); - return; - } - luaTrace("addLuaScript: Script doesn't exist!", false, false, FlxColor.RED); - }); - Lua_helper.add_callback(lua, "removeLuaScript", function(luaFile:String, ?ignoreAlreadyRunning:Bool = false) { //would be dope asf. - var cervix = luaFile + ".lua"; - if(luaFile.endsWith(".lua"))cervix=luaFile; - var doPush = false; - #if MODS_ALLOWED - if(FileSystem.exists(Paths.modFolders(cervix))) - { - cervix = Paths.modFolders(cervix); - doPush = true; - } - else if(FileSystem.exists(cervix)) - { - doPush = true; - } - else { - cervix = Paths.getPreloadPath(cervix); - if(FileSystem.exists(cervix)) { - doPush = true; - } - } - #else - cervix = Paths.getPreloadPath(cervix); - if(Assets.exists(cervix)) { - doPush = true; - } - #end - - if(doPush) - { - if(!ignoreAlreadyRunning) - { - for (luaInstance in PlayState.instance.luaArray) - { - if(luaInstance.scriptName == cervix) - { - //luaTrace('The script "' + cervix + '" is already running!'); - - PlayState.instance.luaArray.remove(luaInstance); - return; - } - } - } - return; - } - luaTrace("removeLuaScript: Script doesn't exist!", false, false, FlxColor.RED); - }); - - Lua_helper.add_callback(lua, "runHaxeCode", function(codeToRun:String) { - var retVal:Dynamic = null; - - #if HSCRIPT_ALLOWED - initHaxeModule(); - try { - retVal = hscript.execute(codeToRun); - } - catch (e:Dynamic) { - luaTrace(scriptName + ":" + lastCalledFunction + " - " + e, false, false, FlxColor.RED); - } - #else - luaTrace("runHaxeCode: HScript isn't supported on this platform!", false, false, FlxColor.RED); - #end - - if(retVal != null && !isOfTypes(retVal, [Bool, Int, Float, String, Array])) retVal = null; - if(retVal == null) Lua.pushnil(lua); - return retVal; - }); - - Lua_helper.add_callback(lua, "addHaxeLibrary", function(libName:String, ?libPackage:String = '') { - #if HSCRIPT_ALLOWED - initHaxeModule(); - try { - var str:String = ''; - if(libPackage.length > 0) - str = libPackage + '.'; - - hscript.variables.set(libName, Type.resolveClass(str + libName)); - } - catch (e:Dynamic) { - luaTrace(scriptName + ":" + lastCalledFunction + " - " + e, false, false, FlxColor.RED); - } - #end - }); - - Lua_helper.add_callback(lua, "loadSong", function(?name:String = null, ?difficultyNum:Int = -1) { - if(name == null || name.length < 1) - name = PlayState.SONG.song; - if (difficultyNum == -1) - difficultyNum = PlayState.storyDifficulty; - - var poop = Highscore.formatSong(name, difficultyNum); - PlayState.SONG = Song.loadFromJson(poop, name); - PlayState.storyDifficulty = difficultyNum; - PlayState.instance.persistentUpdate = false; - LoadingState.loadAndSwitchState(() -> new PlayState()); - - FlxG.sound.music.pause(); - FlxG.sound.music.volume = 0; - if(PlayState.instance.vocals != null) - { - PlayState.instance.vocals.pause(); - PlayState.instance.vocals.volume = 0; - } - }); - - Lua_helper.add_callback(lua, "loadGraphic", function(variable:String, image:String, ?gridX:Int = 0, ?gridY:Int = 0) { - var killMe:Array = variable.split('.'); - var spr:FlxSprite = getObjectDirectly(killMe[0]); - var animated = gridX != 0 || gridY != 0; - - if(killMe.length > 1) { - spr = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(spr != null && image != null && image.length > 0) - { - spr.loadGraphic(Paths.image(image), animated, gridX, gridY); - } - }); - Lua_helper.add_callback(lua, "loadFrames", function(variable:String, image:String, spriteType:String = "sparrow") { - var killMe:Array = variable.split('.'); - var spr:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - spr = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(spr != null && image != null && image.length > 0) - { - loadFrames(spr, image, spriteType); - } - }); - - Lua_helper.add_callback(lua, "getProperty", function(variable:String) { - var result:Dynamic = null; - var killMe:Array = variable.split('.'); - if(killMe.length > 1) - result = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - else - result = getVarInArray(getInstance(), variable); - - if(result == null) Lua.pushnil(lua); - return result; - }); - Lua_helper.add_callback(lua, "setProperty", function(variable:String, value:Dynamic) { - var killMe:Array = variable.split('.'); - if(killMe.length > 1) { - setVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1], value); - return true; - } - setVarInArray(getInstance(), variable, value); - return true; - }); - Lua_helper.add_callback(lua, "getPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic) { - var shitMyPants:Array = obj.split('.'); - var realObject:Dynamic = Reflect.getProperty(getInstance(), obj); - if(shitMyPants.length>1) - realObject = getPropertyLoopThingWhatever(shitMyPants, true, false); - - - if(Std.isOfType(realObject, FlxTypedGroup)) - { - var result:Dynamic = getGroupStuff(realObject.members[index], variable); - if(result == null) Lua.pushnil(lua); - return result; - } - - - var leArray:Dynamic = realObject[index]; - if(leArray != null) { - var result:Dynamic = null; - if(Type.typeof(variable) == ValueType.TInt) - result = leArray[variable]; - else - result = getGroupStuff(leArray, variable); - - if(result == null) Lua.pushnil(lua); - return result; - } - luaTrace("getPropertyFromGroup: Object #" + index + " from group: " + obj + " doesn't exist!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - }); - Lua_helper.add_callback(lua, "setPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic, value:Dynamic) { - var shitMyPants:Array = obj.split('.'); - var realObject:Dynamic = Reflect.getProperty(getInstance(), obj); - if(shitMyPants.length>1) - realObject = getPropertyLoopThingWhatever(shitMyPants, true, false); - - if(Std.isOfType(realObject, FlxTypedGroup)) { - setGroupStuff(realObject.members[index], variable, value); - return; - } - - var leArray:Dynamic = realObject[index]; - if(leArray != null) { - if(Type.typeof(variable) == ValueType.TInt) { - leArray[variable] = value; - return; - } - setGroupStuff(leArray, variable, value); - } - }); - Lua_helper.add_callback(lua, "removeFromGroup", function(obj:String, index:Int, dontDestroy:Bool = false) { - if(Std.isOfType(Reflect.getProperty(getInstance(), obj), FlxTypedGroup)) { - var sex = Reflect.getProperty(getInstance(), obj).members[index]; - if(!dontDestroy) - sex.kill(); - Reflect.getProperty(getInstance(), obj).remove(sex, true); - if(!dontDestroy) - sex.destroy(); - return; - } - Reflect.getProperty(getInstance(), obj).remove(Reflect.getProperty(getInstance(), obj)[index]); - }); - - Lua_helper.add_callback(lua, "getPropertyFromClass", function(classVar:String, variable:String) { - @:privateAccess - var killMe:Array = variable.split('.'); - if(killMe.length > 1) { - var coverMeInPiss:Dynamic = getVarInArray(Type.resolveClass(classVar), killMe[0]); - for (i in 1...killMe.length-1) { - coverMeInPiss = getVarInArray(coverMeInPiss, killMe[i]); - } - return getVarInArray(coverMeInPiss, killMe[killMe.length-1]); - } - return getVarInArray(Type.resolveClass(classVar), variable); - }); - Lua_helper.add_callback(lua, "setPropertyFromClass", function(classVar:String, variable:String, value:Dynamic) { - @:privateAccess - var killMe:Array = variable.split('.'); - if(killMe.length > 1) { - var coverMeInPiss:Dynamic = getVarInArray(Type.resolveClass(classVar), killMe[0]); - for (i in 1...killMe.length-1) { - coverMeInPiss = getVarInArray(coverMeInPiss, killMe[i]); - } - setVarInArray(coverMeInPiss, killMe[killMe.length-1], value); - return true; - } - setVarInArray(Type.resolveClass(classVar), variable, value); - return true; - }); - - //shitass stuff for epic coders like me B) *image of obama giving himself a medal* - Lua_helper.add_callback(lua, "getObjectOrder", function(obj:String) { - var killMe:Array = obj.split('.'); - var leObj:FlxBasic = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - leObj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(leObj != null) - { - return getInstance().members.indexOf(leObj); - } - luaTrace("getObjectOrder: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); - return -1; - }); - Lua_helper.add_callback(lua, "setObjectOrder", function(obj:String, position:Int) { - var killMe:Array = obj.split('.'); - var leObj:FlxBasic = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - leObj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(leObj != null) { - getInstance().remove(leObj, true); - getInstance().insert(position, leObj); - return; - } - luaTrace("setObjectOrder: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); - }); - - // gay ass tweens - Lua_helper.add_callback(lua, "doTweenX", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { - var penisExam:Dynamic = tweenShit(tag, vars); - if(penisExam != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {x: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } else { - luaTrace('doTweenX: Couldnt find object: ' + vars, false, false, FlxColor.RED); - } - }); - Lua_helper.add_callback(lua, "doTweenY", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { - var penisExam:Dynamic = tweenShit(tag, vars); - if(penisExam != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {y: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } else { - luaTrace('doTweenY: Couldnt find object: ' + vars, false, false, FlxColor.RED); - } - }); - Lua_helper.add_callback(lua, "doTweenAngle", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { - var penisExam:Dynamic = tweenShit(tag, vars); - if(penisExam != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {angle: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } else { - luaTrace('doTweenAngle: Couldnt find object: ' + vars, false, false, FlxColor.RED); - } - }); - Lua_helper.add_callback(lua, "doTweenAlpha", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { - var penisExam:Dynamic = tweenShit(tag, vars); - if(penisExam != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {alpha: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } else { - luaTrace('doTweenAlpha: Couldnt find object: ' + vars, false, false, FlxColor.RED); - } - }); - Lua_helper.add_callback(lua, "doTweenZoom", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { - var penisExam:Dynamic = tweenShit(tag, vars); - if(penisExam != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {zoom: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } else { - luaTrace('doTweenZoom: Couldnt find object: ' + vars, false, false, FlxColor.RED); - } - }); - Lua_helper.add_callback(lua, "doTweenColor", function(tag:String, vars:String, targetColor:String, duration:Float, ease:String) { - var penisExam:Dynamic = tweenShit(tag, vars); - if(penisExam != null) { - var color:Int = Std.parseInt(targetColor); - if(!targetColor.startsWith('0x')) color = Std.parseInt('0xff' + targetColor); - - var curColor:FlxColor = penisExam.color; - curColor.alphaFloat = penisExam.alpha; - PlayState.instance.modchartTweens.set(tag, FlxTween.color(penisExam, duration, curColor, color, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.modchartTweens.remove(tag); - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - } - })); - } else { - luaTrace('doTweenColor: Couldnt find object: ' + vars, false, false, FlxColor.RED); - } - }); - - //Tween shit, but for strums - Lua_helper.add_callback(lua, "noteTweenX", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { - cancelTween(tag); - if(note < 0) note = 0; - var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; - - if(testicle != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {x: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } - }); - Lua_helper.add_callback(lua, "noteTweenY", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { - cancelTween(tag); - if(note < 0) note = 0; - var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; - - if(testicle != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {y: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } - }); - Lua_helper.add_callback(lua, "noteTweenAngle", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { - cancelTween(tag); - if(note < 0) note = 0; - var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; - - if(testicle != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {angle: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } - }); - Lua_helper.add_callback(lua, "noteTweenDirection", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { - cancelTween(tag); - if(note < 0) note = 0; - var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; - - if(testicle != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {direction: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } - }); - Lua_helper.add_callback(lua, "mouseClicked", function(button:String) { - var boobs = FlxG.mouse.justPressed; - switch(button){ - case 'middle': - boobs = FlxG.mouse.justPressedMiddle; - case 'right': - boobs = FlxG.mouse.justPressedRight; - } - - - return boobs; - }); - Lua_helper.add_callback(lua, "mousePressed", function(button:String) { - var boobs = FlxG.mouse.pressed; - switch(button){ - case 'middle': - boobs = FlxG.mouse.pressedMiddle; - case 'right': - boobs = FlxG.mouse.pressedRight; - } - return boobs; - }); - Lua_helper.add_callback(lua, "mouseReleased", function(button:String) { - var boobs = FlxG.mouse.justReleased; - switch(button){ - case 'middle': - boobs = FlxG.mouse.justReleasedMiddle; - case 'right': - boobs = FlxG.mouse.justReleasedRight; - } - return boobs; - }); - Lua_helper.add_callback(lua, "noteTweenAngle", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { - cancelTween(tag); - if(note < 0) note = 0; - var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; - - if(testicle != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {angle: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } - }); - Lua_helper.add_callback(lua, "noteTweenAlpha", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { - cancelTween(tag); - if(note < 0) note = 0; - var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; - - if(testicle != null) { - PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {alpha: value}, duration, {ease: getFlxEaseByString(ease), - onComplete: function(twn:FlxTween) { - PlayState.instance.callOnLuas('onTweenCompleted', [tag]); - PlayState.instance.modchartTweens.remove(tag); - } - })); - } - }); - - Lua_helper.add_callback(lua, "cancelTween", function(tag:String) { - cancelTween(tag); - }); - - Lua_helper.add_callback(lua, "runTimer", function(tag:String, time:Float = 1, loops:Int = 1) { - cancelTimer(tag); - PlayState.instance.modchartTimers.set(tag, new FlxTimer().start(time, function(tmr:FlxTimer) { - if(tmr.finished) { - PlayState.instance.modchartTimers.remove(tag); - } - PlayState.instance.callOnLuas('onTimerCompleted', [tag, tmr.loops, tmr.loopsLeft]); - //trace('Timer Completed: ' + tag); - }, loops)); - }); - Lua_helper.add_callback(lua, "cancelTimer", function(tag:String) { - cancelTimer(tag); - }); - - /*Lua_helper.add_callback(lua, "getPropertyAdvanced", function(varsStr:String) { - var variables:Array = varsStr.replace(' ', '').split(','); - var leClass:Class = Type.resolveClass(variables[0]); - if(variables.length > 2) { - var curProp:Dynamic = Reflect.getProperty(leClass, variables[1]); - if(variables.length > 3) { - for (i in 2...variables.length-1) { - curProp = Reflect.getProperty(curProp, variables[i]); - } - } - return Reflect.getProperty(curProp, variables[variables.length-1]); - } else if(variables.length == 2) { - return Reflect.getProperty(leClass, variables[variables.length-1]); - } - return null; - }); - Lua_helper.add_callback(lua, "setPropertyAdvanced", function(varsStr:String, value:Dynamic) { - var variables:Array = varsStr.replace(' ', '').split(','); - var leClass:Class = Type.resolveClass(variables[0]); - if(variables.length > 2) { - var curProp:Dynamic = Reflect.getProperty(leClass, variables[1]); - if(variables.length > 3) { - for (i in 2...variables.length-1) { - curProp = Reflect.getProperty(curProp, variables[i]); - } - } - return Reflect.setProperty(curProp, variables[variables.length-1], value); - } else if(variables.length == 2) { - return Reflect.setProperty(leClass, variables[variables.length-1], value); - } - });*/ - - //stupid bietch ass functions - Lua_helper.add_callback(lua, "addScore", function(value:Int = 0) { - PlayState.instance.songScore += value; - PlayState.instance.RecalculateRating(); - }); - Lua_helper.add_callback(lua, "addMisses", function(value:Int = 0) { - PlayState.instance.songMisses += value; - PlayState.instance.RecalculateRating(); - }); - Lua_helper.add_callback(lua, "addHits", function(value:Int = 0) { - PlayState.instance.songHits += value; - PlayState.instance.RecalculateRating(); - }); - Lua_helper.add_callback(lua, "setScore", function(value:Int = 0) { - PlayState.instance.songScore = value; - PlayState.instance.RecalculateRating(); - }); - Lua_helper.add_callback(lua, "setMisses", function(value:Int = 0) { - PlayState.instance.songMisses = value; - PlayState.instance.RecalculateRating(); - }); - Lua_helper.add_callback(lua, "setHits", function(value:Int = 0) { - PlayState.instance.songHits = value; - PlayState.instance.RecalculateRating(); - }); - Lua_helper.add_callback(lua, "getScore", function() { - return PlayState.instance.songScore; - }); - Lua_helper.add_callback(lua, "getMisses", function() { - return PlayState.instance.songMisses; - }); - Lua_helper.add_callback(lua, "getHits", function() { - return PlayState.instance.songHits; - }); - - Lua_helper.add_callback(lua, "setHealth", function(value:Float = 0) { - PlayState.instance.health = value; - }); - Lua_helper.add_callback(lua, "addHealth", function(value:Float = 0) { - PlayState.instance.health += value; - }); - Lua_helper.add_callback(lua, "getHealth", function() { - return PlayState.instance.health; - }); - - Lua_helper.add_callback(lua, "getColorFromHex", function(color:String) { - if(!color.startsWith('0x')) color = '0xff' + color; - return Std.parseInt(color); - }); - - Lua_helper.add_callback(lua, "keyboardJustPressed", function(name:String) - { - return Reflect.getProperty(FlxG.keys.justPressed, name); - }); - Lua_helper.add_callback(lua, "keyboardPressed", function(name:String) - { - return Reflect.getProperty(FlxG.keys.pressed, name); - }); - Lua_helper.add_callback(lua, "keyboardReleased", function(name:String) - { - return Reflect.getProperty(FlxG.keys.justReleased, name); - }); - - Lua_helper.add_callback(lua, "anyGamepadJustPressed", function(name:String) - { - return FlxG.gamepads.anyJustPressed(name); - }); - Lua_helper.add_callback(lua, "anyGamepadPressed", function(name:String) - { - return FlxG.gamepads.anyPressed(name); - }); - Lua_helper.add_callback(lua, "anyGamepadReleased", function(name:String) - { - return FlxG.gamepads.anyJustReleased(name); - }); - - Lua_helper.add_callback(lua, "gamepadAnalogX", function(id:Int, ?leftStick:Bool = true) - { - var controller = FlxG.gamepads.getByID(id); - if (controller == null) - { - return 0.0; - } - return controller.getXAxis(leftStick ? LEFT_ANALOG_STICK : RIGHT_ANALOG_STICK); - }); - Lua_helper.add_callback(lua, "gamepadAnalogY", function(id:Int, ?leftStick:Bool = true) - { - var controller = FlxG.gamepads.getByID(id); - if (controller == null) - { - return 0.0; - } - return controller.getYAxis(leftStick ? LEFT_ANALOG_STICK : RIGHT_ANALOG_STICK); - }); - Lua_helper.add_callback(lua, "gamepadJustPressed", function(id:Int, name:String) - { - var controller = FlxG.gamepads.getByID(id); - if (controller == null) - { - return false; - } - return Reflect.getProperty(controller.justPressed, name) == true; - }); - Lua_helper.add_callback(lua, "gamepadPressed", function(id:Int, name:String) - { - var controller = FlxG.gamepads.getByID(id); - if (controller == null) - { - return false; - } - return Reflect.getProperty(controller.pressed, name) == true; - }); - Lua_helper.add_callback(lua, "gamepadReleased", function(id:Int, name:String) - { - var controller = FlxG.gamepads.getByID(id); - if (controller == null) - { - return false; - } - return Reflect.getProperty(controller.justReleased, name) == true; - }); - - Lua_helper.add_callback(lua, "keyJustPressed", function(name:String) { - var key:Bool = false; - switch(name) { - case 'left': key = PlayState.instance.getControl('NOTE_LEFT_P'); - case 'down': key = PlayState.instance.getControl('NOTE_DOWN_P'); - case 'up': key = PlayState.instance.getControl('NOTE_UP_P'); - case 'right': key = PlayState.instance.getControl('NOTE_RIGHT_P'); - case 'accept': key = PlayState.instance.getControl('ACCEPT'); - case 'back': key = PlayState.instance.getControl('BACK'); - case 'pause': key = PlayState.instance.getControl('PAUSE'); - case 'reset': key = PlayState.instance.getControl('RESET'); - case 'space': key = FlxG.keys.justPressed.SPACE;//an extra key for convinience - } - return key; - }); - Lua_helper.add_callback(lua, "keyPressed", function(name:String) { - var key:Bool = false; - switch(name) { - case 'left': key = PlayState.instance.getControl('NOTE_LEFT'); - case 'down': key = PlayState.instance.getControl('NOTE_DOWN'); - case 'up': key = PlayState.instance.getControl('NOTE_UP'); - case 'right': key = PlayState.instance.getControl('NOTE_RIGHT'); - case 'space': key = FlxG.keys.pressed.SPACE;//an extra key for convinience - } - return key; - }); - Lua_helper.add_callback(lua, "keyReleased", function(name:String) { - var key:Bool = false; - switch(name) { - case 'left': key = PlayState.instance.getControl('NOTE_LEFT_R'); - case 'down': key = PlayState.instance.getControl('NOTE_DOWN_R'); - case 'up': key = PlayState.instance.getControl('NOTE_UP_R'); - case 'right': key = PlayState.instance.getControl('NOTE_RIGHT_R'); - case 'space': key = FlxG.keys.justReleased.SPACE;//an extra key for convinience - } - return key; - }); - Lua_helper.add_callback(lua, "addCharacterToList", function(name:String, type:String) { - var charType:Int = 0; - switch(type.toLowerCase()) { - case 'dad': charType = 1; - case 'gf' | 'girlfriend': charType = 2; - } - PlayState.instance.addCharacterToList(name, charType); - }); - Lua_helper.add_callback(lua, "precacheImage", function(name:String, ?allowGPU:Bool = true) { - Paths.image(name, allowGPU); - }); - Lua_helper.add_callback(lua, "precacheSound", function(name:String) { - CoolUtil.precacheSound(name); - }); - Lua_helper.add_callback(lua, "precacheMusic", function(name:String) { - CoolUtil.precacheMusic(name); - }); - Lua_helper.add_callback(lua, "triggerEvent", function(name:String, arg1:Dynamic, arg2:Dynamic) { - var value1:String = arg1; - var value2:String = arg2; - PlayState.instance.triggerEventNote(name, value1, value2); - //trace('Triggered event: ' + name + ', ' + value1 + ', ' + value2); - return true; - }); - - Lua_helper.add_callback(lua, "startCountdown", function() { - PlayState.instance.startCountdown(); - return true; - }); - Lua_helper.add_callback(lua, "endSong", function() { - PlayState.instance.KillNotes(); - PlayState.instance.endSong(); - return true; - }); - Lua_helper.add_callback(lua, "restartSong", function(?skipTransition:Bool = false) { - PlayState.instance.persistentUpdate = false; - PauseSubState.restartSong(skipTransition); - return true; - }); - Lua_helper.add_callback(lua, "exitSong", function(?skipTransition:Bool = false) { - if(skipTransition) - { - FlxTransitionableState.skipNextTransIn = true; - FlxTransitionableState.skipNextTransOut = true; - } - - PlayState.cancelMusicFadeTween(); - - if(PlayState.isStoryMode) - FlxG.switchState(() -> new StoryMenuState()); - else - FlxG.switchState(() -> new FreeplayState()); - - FlxG.sound.playMusic(Paths.music('freakyMenu')); - PlayState.changedDifficulty = false; - PlayState.chartingMode = false; - PlayState.instance.transitioning = true; - WeekData.loadTheFirstEnabledMod(); - return true; - }); - Lua_helper.add_callback(lua, "getSongPosition", function() { - return Conductor.songPosition; - }); - - Lua_helper.add_callback(lua, "getCharacterX", function(type:String) { - switch(type.toLowerCase()) { - case 'dad' | 'opponent': - return PlayState.instance.dadGroup.x; - case 'gf' | 'girlfriend': - return PlayState.instance.gfGroup.x; - default: - return PlayState.instance.boyfriendGroup.x; - } - }); - Lua_helper.add_callback(lua, "setCharacterX", function(type:String, value:Float) { - switch(type.toLowerCase()) { - case 'dad' | 'opponent': - PlayState.instance.dadGroup.x = value; - case 'gf' | 'girlfriend': - PlayState.instance.gfGroup.x = value; - default: - PlayState.instance.boyfriendGroup.x = value; - } - }); - Lua_helper.add_callback(lua, "getCharacterY", function(type:String) { - switch(type.toLowerCase()) { - case 'dad' | 'opponent': - return PlayState.instance.dadGroup.y; - case 'gf' | 'girlfriend': - return PlayState.instance.gfGroup.y; - default: - return PlayState.instance.boyfriendGroup.y; - } - }); - Lua_helper.add_callback(lua, "setCharacterY", function(type:String, value:Float) { - switch(type.toLowerCase()) { - case 'dad' | 'opponent': - PlayState.instance.dadGroup.y = value; - case 'gf' | 'girlfriend': - PlayState.instance.gfGroup.y = value; - default: - PlayState.instance.boyfriendGroup.y = value; - } - }); - Lua_helper.add_callback(lua, "cameraSetTarget", function(target:String) { - var isDad:Bool = false; - if(target == 'dad') { - isDad = true; - } - PlayState.instance.moveCamera(isDad); - return isDad; - }); - Lua_helper.add_callback(lua, "cameraShake", function(camera:String, intensity:Float, duration:Float) { - cameraFromString(camera).shake(intensity, duration); - }); - - Lua_helper.add_callback(lua, "cameraFlash", function(camera:String, color:String, duration:Float,forced:Bool) { - var colorNum:Int = Std.parseInt(color); - if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); - cameraFromString(camera).flash(colorNum, duration,null,forced); - }); - Lua_helper.add_callback(lua, "cameraFade", function(camera:String, color:String, duration:Float,forced:Bool) { - var colorNum:Int = Std.parseInt(color); - if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); - cameraFromString(camera).fade(colorNum, duration,false,null,forced); - }); - Lua_helper.add_callback(lua, "setRatingPercent", function(value:Float) { - PlayState.instance.ratingPercent = value; - }); - Lua_helper.add_callback(lua, "setRatingName", function(value:String) { - PlayState.instance.ratingName = value; - }); - Lua_helper.add_callback(lua, "setRatingFC", function(value:String) { - PlayState.instance.ratingFC = value; - }); - Lua_helper.add_callback(lua, "getMouseX", function(camera:String) { - var cam:FlxCamera = cameraFromString(camera); - return FlxG.mouse.getViewPosition(cam).x; - }); - Lua_helper.add_callback(lua, "getMouseY", function(camera:String) { - var cam:FlxCamera = cameraFromString(camera); - return FlxG.mouse.getViewPosition(cam).y; - }); - - Lua_helper.add_callback(lua, "getMidpointX", function(variable:String) { - var killMe:Array = variable.split('.'); - var obj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - obj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - if(obj != null) return obj.getMidpoint().x; - - return 0; - }); - Lua_helper.add_callback(lua, "getMidpointY", function(variable:String) { - var killMe:Array = variable.split('.'); - var obj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - obj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - if(obj != null) return obj.getMidpoint().y; - - return 0; - }); - Lua_helper.add_callback(lua, "getGraphicMidpointX", function(variable:String) { - var killMe:Array = variable.split('.'); - var obj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - obj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - if(obj != null) return obj.getGraphicMidpoint().x; - - return 0; - }); - Lua_helper.add_callback(lua, "getGraphicMidpointY", function(variable:String) { - var killMe:Array = variable.split('.'); - var obj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - obj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - if(obj != null) return obj.getGraphicMidpoint().y; - - return 0; - }); - Lua_helper.add_callback(lua, "getScreenPositionX", function(variable:String) { - var killMe:Array = variable.split('.'); - var obj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - obj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - if(obj != null) return obj.getScreenPosition().x; - - return 0; - }); - Lua_helper.add_callback(lua, "getScreenPositionY", function(variable:String) { - var killMe:Array = variable.split('.'); - var obj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - obj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - if(obj != null) return obj.getScreenPosition().y; - - return 0; - }); - Lua_helper.add_callback(lua, "characterDance", function(character:String) { - switch(character.toLowerCase()) { - case 'dad': PlayState.instance.dad.dance(); - case 'gf' | 'girlfriend': if(PlayState.instance.gf != null) PlayState.instance.gf.dance(); - default: PlayState.instance.boyfriend.dance(); - } - }); - - Lua_helper.add_callback(lua, "makeLuaSprite", function(tag:String, image:String, x:Float, y:Float) { - tag = tag.replace('.', ''); - resetSpriteTag(tag); - var leSprite:ModchartSprite = new ModchartSprite(x, y); - if(image != null && image.length > 0) - { - leSprite.loadGraphic(Paths.image(image)); - } - leSprite.antialiasing = ClientPrefs.globalAntialiasing; - PlayState.instance.modchartSprites.set(tag, leSprite); - leSprite.active = true; - }); - - Lua_helper.add_callback(lua, "makeLuaBackdrop", function(tag:String, image:String, x:Float, y:Float, repeatX:Bool = false, repeatY:Bool = false) { - tag = tag.replace('.', ''); - resetSpriteTag(tag); - - var backdrop = new ModchartBackdrop(Paths.image(image), 1, 1, repeatX, repeatY); - backdrop.setPosition(x, y); - backdrop.antialiasing = ClientPrefs.globalAntialiasing; - - PlayState.instance.modchartBackdrops.set(tag, backdrop); - backdrop.active = true; - }); - - Lua_helper.add_callback(lua, "makeAnimatedLuaSprite", function(tag:String, image:String, x:Float, y:Float, ?spriteType:String = "sparrow") { - tag = tag.replace('.', ''); - resetSpriteTag(tag); - var leSprite:ModchartSprite = new ModchartSprite(x, y); - - loadFrames(leSprite, image, spriteType); - leSprite.antialiasing = ClientPrefs.globalAntialiasing; - PlayState.instance.modchartSprites.set(tag, leSprite); - }); - - Lua_helper.add_callback(lua, "makeGraphic", function(obj:String, width:Int, height:Int, color:String) { - var colorNum:Int = Std.parseInt(color); - if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); - - var spr:FlxSprite = PlayState.instance.getLuaObject(obj,false); - if(spr!=null) { - PlayState.instance.getLuaObject(obj,false).makeGraphic(width, height, colorNum); - return; - } - - var object:FlxSprite = Reflect.getProperty(getInstance(), obj); - if(object != null) { - object.makeGraphic(width, height, colorNum); - } - }); - Lua_helper.add_callback(lua, "addAnimationByPrefix", function(obj:String, name:String, prefix:String, framerate:Int = 24, loop:Bool = true) { - if(PlayState.instance.getLuaObject(obj,false)!=null) { - var cock:FlxSprite = PlayState.instance.getLuaObject(obj,false); - cock.animation.addByPrefix(name, prefix, framerate, loop); - if(cock.animation.curAnim == null) { - cock.animation.play(name, true); - } - return; - } - - var cock:FlxSprite = Reflect.getProperty(getInstance(), obj); - if(cock != null) { - cock.animation.addByPrefix(name, prefix, framerate, loop); - if(cock.animation.curAnim == null) { - cock.animation.play(name, true); - } - } - }); - - Lua_helper.add_callback(lua, "addAnimation", function(obj:String, name:String, frames:Array, framerate:Int = 24, loop:Bool = true) { - if(PlayState.instance.getLuaObject(obj,false)!=null) { - var cock:FlxSprite = PlayState.instance.getLuaObject(obj,false); - cock.animation.add(name, frames, framerate, loop); - if(cock.animation.curAnim == null) { - cock.animation.play(name, true); - } - return; - } - - var cock:FlxSprite = Reflect.getProperty(getInstance(), obj); - if(cock != null) { - cock.animation.add(name, frames, framerate, loop); - if(cock.animation.curAnim == null) { - cock.animation.play(name, true); - } - } - }); - - Lua_helper.add_callback(lua, "addAnimationByIndices", function(obj:String, name:String, prefix:String, indices:String, framerate:Int = 24) { - return addAnimByIndices(obj, name, prefix, indices, framerate, false); - }); - Lua_helper.add_callback(lua, "addAnimationByIndicesLoop", function(obj:String, name:String, prefix:String, indices:String, framerate:Int = 24) { - return addAnimByIndices(obj, name, prefix, indices, framerate, true); - }); - - - Lua_helper.add_callback(lua, "playAnim", function(obj:String, name:String, forced:Bool = false, ?reverse:Bool = false, ?startFrame:Int = 0) - { - var obj:Dynamic = getObjectDirectly(obj, false); - if(obj.playAnim != null) - { - obj.playAnim(name, forced, reverse, startFrame); - return true; - } - else - { - if(obj.anim != null) obj.anim.play(name, forced, reverse, startFrame); //FlxAnimate - else obj.animation.play(name, forced, reverse, startFrame); - return true; - } - return false; - }); - Lua_helper.add_callback(lua, "addOffset", function(obj:String, anim:String, x:Float, y:Float) { - if(PlayState.instance.modchartSprites.exists(obj)) { - PlayState.instance.modchartSprites.get(obj).animOffsets.set(anim, [x, y]); - return true; - } - - var char:Character = Reflect.getProperty(getInstance(), obj); - if(char != null) { - char.addOffset(anim, x, y); - return true; - } - return false; - }); - - //FlxAnimate Funcs - #if flixel_animate - Lua_helper.add_callback(lua, "makeFlxAnimateSprite", function(tag:String, atlasFolder:String, ?x:Float = 0, ?y:Float = 0) { - tag = tag.replace('.', ''); - var lastSprite = PlayState.instance.variables.get(tag); - if(lastSprite != null) - { - lastSprite.kill(); - PlayState.instance.remove(lastSprite); - lastSprite.destroy(); - } - - var mySprite:ModchartAnimateSprite = new ModchartAnimateSprite(x, y); - mySprite.frames = Paths.getAnimateAtlas(atlasFolder); - PlayState.instance.variables.set(tag, mySprite); - mySprite.active = true; - }); - - Lua_helper.add_callback(lua, "addAnimationBySymbol", function(tag:String, name:String, symbol:String, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) - { - var obj:Dynamic = PlayState.instance.variables.get(tag); - if(cast (obj, FlxAnimate) == null) return false; - - obj.anim.addBySymbol(name, symbol, framerate, loop, flipX, flipY); - if(obj.anim.lastPlayedAnim == null) - { - if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite - else obj.animation.play(name, true); - } - return true; - }); - - Lua_helper.add_callback(lua, "addAnimationBySymbolIndices", function(tag:String, name:String, symbol:String, ?indices:Any = null, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) - { - var obj:Dynamic = PlayState.instance.variables.get(tag); - if(cast (obj, FlxAnimate) == null) return false; - - if(indices == null) - indices = [0]; - else if(Std.isOfType(indices, String)) - { - var strIndices:Array = cast (indices, String).trim().split(','); - var myIndices:Array = []; - for (i in 0...strIndices.length) { - myIndices.push(Std.parseInt(strIndices[i])); - } - indices = myIndices; - } - - obj.anim.addBySymbolIndices(name, symbol, indices, framerate, loop, flipX, flipY); - if(obj.anim.lastPlayedAnim == null) - { - if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite - else obj.animation.play(name, true); - } - return true; - }); - - Lua_helper.add_callback(lua, "addAnimationByFrameLabel", function(tag:String, name:String, label:String, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) - { - var obj:Dynamic = PlayState.instance.variables.get(tag); - if(cast (obj, FlxAnimate) == null) return false; - - obj.anim.addByFrameLabel(name, label, framerate, loop, flipX, flipY); - if(obj.anim.lastPlayedAnim == null) - { - if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite - else obj.animation.play(name, true); - } - return true; - }); - - Lua_helper.add_callback(lua, "addAnimationByFrameLabelIndices", function(tag:String, name:String, label:String, ?indices:Any = null, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) - { - var obj:Dynamic = PlayState.instance.variables.get(tag); - if(cast (obj, FlxAnimate) == null) return false; - - if(indices == null) - indices = [0]; - else if(Std.isOfType(indices, String)) - { - var strIndices:Array = cast (indices, String).trim().split(','); - var myIndices:Array = []; - for (i in 0...strIndices.length) { - myIndices.push(Std.parseInt(strIndices[i])); - } - indices = myIndices; - } - - obj.anim.addByFrameLabelIndices(name, label, indices, framerate, loop, flipX, flipY); - if(obj.anim.lastPlayedAnim == null) - { - if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite - else obj.animation.play(name, true); - } - return true; - }); - - Lua_helper.add_callback(lua, "addByTimeline", function(tag:String, name:String, timelinePath:String, ?framerate:Float = 24, ?loop:Bool = true, ?flipX:Bool = false, ?flipY:Bool = false) { - var obj:Dynamic = PlayState.instance.variables.get(tag); - if(cast (obj, FlxAnimate) == null) return false; - - var timeline:Timeline = loadTimelineFromJson(timelinePath); - if(timeline == null) { - luaTrace('addByTimeline: Timeline not found: $timelinePath', false, false, FlxColor.RED); - return false; - } - - obj.anim.addByTimeline(name, timeline, framerate, loop, flipX, flipY); - return true; - }); - - Lua_helper.add_callback(lua, "addByTimelineIndices", function(tag:String, name:String, timelinePath:String, indices:Any = null, ?framerate:Float = 24, ?loop:Bool = true, ?flipX:Bool = false, ?flipY:Bool = false) { - var obj:Dynamic = PlayState.instance.variables.get(tag); - if(cast (obj, FlxAnimate) == null) return false; - - var timeline:Timeline = loadTimelineFromJson(timelinePath); - if(timeline == null) { - luaTrace('addByTimelineIndices: Timeline not found: $timelinePath', false, false, FlxColor.RED); - return false; - } - - var idxArray:Array = []; - if(Std.isOfType(indices, String)) { - var strIndices = cast(indices, String).split(','); - for(i in strIndices) idxArray.push(Std.parseInt(i.trim())); - } else if(Std.isOfType(indices, Array)) { - idxArray = cast indices; - } - - obj.anim.addByTimelineIndices(name, timeline, idxArray, framerate, loop, flipX, flipY); - return true; - }); - #end - - Lua_helper.add_callback(lua, "setScrollFactor", function(obj:String, scrollX:Float, scrollY:Float) { - if(PlayState.instance.getLuaObject(obj,false)!=null) { - PlayState.instance.getLuaObject(obj,false).scrollFactor.set(scrollX, scrollY); - return; - } - - var object:FlxObject = Reflect.getProperty(getInstance(), obj); - if(object != null) { - object.scrollFactor.set(scrollX, scrollY); - } - }); - Lua_helper.add_callback(lua, "addLuaSprite", function(tag:String, front:Bool = false) { - if(PlayState.instance.modchartSprites.exists(tag)) { - var shit:ModchartSprite = PlayState.instance.modchartSprites.get(tag); - if(!shit.wasAdded) { - if(front) - { - getInstance().add(shit); - } - else - { - if(PlayState.instance.isDead) - { - GameOverSubstate.instance.insert(GameOverSubstate.instance.members.indexOf(GameOverSubstate.instance.boyfriend), shit); - } - else - { - var position:Int = PlayState.instance.members.indexOf(PlayState.instance.gfGroup); - if(PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup) < position) { - position = PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup); - } else if(PlayState.instance.members.indexOf(PlayState.instance.dadGroup) < position) { - position = PlayState.instance.members.indexOf(PlayState.instance.dadGroup); - } - PlayState.instance.insert(position, shit); - } - } - shit.wasAdded = true; - //trace('added a thing: ' + tag); - } - } - }); - - Lua_helper.add_callback(lua, "addLuaBackdrop", function(tag:String, front:Bool = false) { - if (!PlayState.instance.modchartBackdrops.exists(tag)) return; - - final backdrop = PlayState.instance.modchartBackdrops.get(tag); - if (backdrop == null) return; - - if (!Reflect.hasField(backdrop, "wasAdded")) { - Reflect.setProperty(backdrop, "wasAdded", false); - } - - if (!Reflect.field(backdrop, "wasAdded")) { - if (front) { - getInstance().add(backdrop); - } else { - if (PlayState.instance.isDead) { - GameOverSubstate.instance.insert( - GameOverSubstate.instance.members.indexOf(GameOverSubstate.instance.boyfriend), - backdrop - ); - } else { - var position:Int = PlayState.instance.members.indexOf(PlayState.instance.gfGroup); - if (PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup) < position) - position = PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup); - if (PlayState.instance.members.indexOf(PlayState.instance.dadGroup) < position) - position = PlayState.instance.members.indexOf(PlayState.instance.dadGroup); - PlayState.instance.insert(position, backdrop); - } - } - Reflect.setProperty(backdrop, "wasAdded", true); - } - }); - - Lua_helper.add_callback(lua, "setGraphicSize", function(obj:String, x:Int, y:Int = 0, updateHitbox:Bool = true) { - if(PlayState.instance.getLuaObject(obj)!=null) { - var shit:FlxSprite = PlayState.instance.getLuaObject(obj); - shit.setGraphicSize(x, y); - if(updateHitbox) shit.updateHitbox(); - return; - } - - var killMe:Array = obj.split('.'); - var poop:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - poop = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(poop != null) { - poop.setGraphicSize(x, y); - if(updateHitbox) poop.updateHitbox(); - return; - } - luaTrace('setGraphicSize: Couldnt find object: ' + obj, false, false, FlxColor.RED); - }); - Lua_helper.add_callback(lua, "scaleObject", function(obj:String, x:Float, y:Float, updateHitbox:Bool = true) { - if(PlayState.instance.getLuaObject(obj)!=null) { - var shit:FlxSprite = PlayState.instance.getLuaObject(obj); - shit.scale.set(x, y); - if(updateHitbox) shit.updateHitbox(); - return; - } - - var killMe:Array = obj.split('.'); - var poop:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - poop = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(poop != null) { - poop.scale.set(x, y); - if(updateHitbox) poop.updateHitbox(); - return; - } - luaTrace('scaleObject: Couldnt find object: ' + obj, false, false, FlxColor.RED); - }); - Lua_helper.add_callback(lua, "updateHitbox", function(obj:String) { - if(PlayState.instance.getLuaObject(obj)!=null) { - var shit:FlxSprite = PlayState.instance.getLuaObject(obj); - shit.updateHitbox(); - return; - } - - var poop:FlxSprite = Reflect.getProperty(getInstance(), obj); - if(poop != null) { - poop.updateHitbox(); - return; - } - luaTrace('updateHitbox: Couldnt find object: ' + obj, false, false, FlxColor.RED); - }); - Lua_helper.add_callback(lua, "updateHitboxFromGroup", function(group:String, index:Int) { - if(Std.isOfType(Reflect.getProperty(getInstance(), group), FlxTypedGroup)) { - Reflect.getProperty(getInstance(), group).members[index].updateHitbox(); - return; - } - Reflect.getProperty(getInstance(), group)[index].updateHitbox(); - }); - - Lua_helper.add_callback(lua, "removeLuaSprite", function(tag:String, destroy:Bool = true) { - if(!PlayState.instance.modchartSprites.exists(tag)) { - return; - } - - var pee:ModchartSprite = PlayState.instance.modchartSprites.get(tag); - if(destroy) { - pee.kill(); - } - - if(pee.wasAdded) { - getInstance().remove(pee, true); - pee.wasAdded = false; - } - - if(destroy) { - pee.destroy(); - PlayState.instance.modchartSprites.remove(tag); - } - }); - - Lua_helper.add_callback(lua, "luaSpriteExists", function(tag:String) { - return PlayState.instance.modchartSprites.exists(tag); - }); - Lua_helper.add_callback(lua, "luaTextExists", function(tag:String) { - return PlayState.instance.modchartTexts.exists(tag); - }); - Lua_helper.add_callback(lua, "luaSoundExists", function(tag:String) { - return PlayState.instance.modchartSounds.exists(tag); - }); - - Lua_helper.add_callback(lua, "setHealthBarColors", function(leftHex:String, rightHex:String) { - var left:FlxColor = Std.parseInt(leftHex); - if(!leftHex.startsWith('0x')) left = Std.parseInt('0xff' + leftHex); - var right:FlxColor = Std.parseInt(rightHex); - if(!rightHex.startsWith('0x')) right = Std.parseInt('0xff' + rightHex); - - PlayState.instance.healthBar.createFilledBar(left, right); - PlayState.instance.healthBar.updateBar(); - }); - Lua_helper.add_callback(lua, "setTimeBarColors", function(leftHex:String, rightHex:String) { - var left:FlxColor = Std.parseInt(leftHex); - if(!leftHex.startsWith('0x')) left = Std.parseInt('0xff' + leftHex); - var right:FlxColor = Std.parseInt(rightHex); - if(!rightHex.startsWith('0x')) right = Std.parseInt('0xff' + rightHex); - - PlayState.instance.timeBar.createFilledBar(right, left); - PlayState.instance.timeBar.updateBar(); - }); - - Lua_helper.add_callback(lua, "setObjectCamera", function(obj:String, camera:String = '') { - /*if(PlayState.instance.modchartSprites.exists(obj)) { - PlayState.instance.modchartSprites.get(obj).cameras = [cameraFromString(camera)]; - return true; - } - else if(PlayState.instance.modchartTexts.exists(obj)) { - PlayState.instance.modchartTexts.get(obj).cameras = [cameraFromString(camera)]; - return true; - }*/ - var real = PlayState.instance.getLuaObject(obj); - if(real!=null){ - real.cameras = [cameraFromString(camera)]; - return true; - } - - var killMe:Array = obj.split('.'); - var object:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - object = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(object != null) { - object.cameras = [cameraFromString(camera)]; - return true; - } - luaTrace("setObjectCamera: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setBlendMode", function(obj:String, blend:String = '') { - var real = PlayState.instance.getLuaObject(obj); - if(real != null) { - real.blend = blendModeFromString(blend); - return true; - } - - var killMe:Array = obj.split('.'); - var spr:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - spr = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(spr != null) { - spr.blend = blendModeFromString(blend); - return true; - } - luaTrace("setBlendMode: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "screenCenter", function(obj:String, pos:String = 'xy') { - var spr:FlxSprite = PlayState.instance.getLuaObject(obj); - - if(spr==null){ - var killMe:Array = obj.split('.'); - spr = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - spr = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - } - - if(spr != null) - { - switch(pos.trim().toLowerCase()) - { - case 'x': - spr.screenCenter(X); - return; - case 'y': - spr.screenCenter(Y); - return; - default: - spr.screenCenter(XY); - return; - } - } - luaTrace("screenCenter: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); - }); - Lua_helper.add_callback(lua, "objectsOverlap", function(obj1:String, obj2:String) { - var namesArray:Array = [obj1, obj2]; - var objectsArray:Array = []; - for (i in 0...namesArray.length) - { - var real = PlayState.instance.getLuaObject(namesArray[i]); - if(real!=null) { - objectsArray.push(real); - } else { - objectsArray.push(Reflect.getProperty(getInstance(), namesArray[i])); - } - } - - if(!objectsArray.contains(null) && FlxG.overlap(objectsArray[0], objectsArray[1])) - { - return true; - } - return false; - }); - Lua_helper.add_callback(lua, "getPixelColor", function(obj:String, x:Int, y:Int) { - var killMe:Array = obj.split('.'); - var spr:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - spr = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(spr != null) - { - if(spr.framePixels != null) spr.framePixels.getPixel32(x, y); - return spr.pixels.getPixel32(x, y); - } - return 0; - }); - Lua_helper.add_callback(lua, "getRandomInt", function(min:Int, max:Int = FlxMath.MAX_VALUE_INT, exclude:String = '') { - var excludeArray:Array = exclude.split(','); - var toExclude:Array = []; - for (i in 0...excludeArray.length) - { - toExclude.push(Std.parseInt(excludeArray[i].trim())); - } - return FlxG.random.int(min, max, toExclude); - }); - Lua_helper.add_callback(lua, "getRandomFloat", function(min:Float, max:Float = 1, exclude:String = '') { - var excludeArray:Array = exclude.split(','); - var toExclude:Array = []; - for (i in 0...excludeArray.length) - { - toExclude.push(Std.parseFloat(excludeArray[i].trim())); - } - return FlxG.random.float(min, max, toExclude); - }); - Lua_helper.add_callback(lua, "getRandomBool", function(chance:Float = 50) { - return FlxG.random.bool(chance); - }); - Lua_helper.add_callback(lua, "startDialogue", function(dialogueFile:String, music:String = null) { - var path:String; - #if MODS_ALLOWED - path = Paths.modsJson(Paths.formatToSongPath(PlayState.SONG.song) + '/' + dialogueFile); - if(!FileSystem.exists(path)) - #end - path = Paths.json(Paths.formatToSongPath(PlayState.SONG.song) + '/' + dialogueFile); - - luaTrace('startDialogue: Trying to load dialogue: ' + path); - - #if MODS_ALLOWED - if(FileSystem.exists(path)) - #else - if(Assets.exists(path)) - #end - { - var shit:DialogueFile = DialogueBoxPsych.parseDialogue(path); - if(shit.dialogue.length > 0) { - PlayState.instance.startDialogue(shit, music); - luaTrace('startDialogue: Successfully loaded dialogue', false, false, FlxColor.GREEN); - return true; - } else { - luaTrace('startDialogue: Your dialogue file is badly formatted!', false, false, FlxColor.RED); - } - } else { - luaTrace('startDialogue: Dialogue file not found', false, false, FlxColor.RED); - if(PlayState.instance.endingSong) { - PlayState.instance.endSong(); - } else { - PlayState.instance.startCountdown(); - } - } - return false; - }); - Lua_helper.add_callback(lua, "playVideo", function(videoFile:String, ?isNotMidPartSong:Bool = false) { - #if VIDEOS_ALLOWED - if(FileSystem.exists(Paths.video(videoFile))) { - PlayState.instance.playVideo(videoFile, isNotMidPartSong); - return true; - } else { - luaTrace('playVideo: Video file not found: ' + videoFile, false, false, FlxColor.RED); - } - return false; - - #else - return true; - #end - }); - - Lua_helper.add_callback(lua, "playMusic", function(sound:String, volume:Float = 1, loop:Bool = false) { - FlxG.sound.playMusic(Paths.music(sound), volume, loop); - }); - Lua_helper.add_callback(lua, "playSound", function(sound:String, volume:Float = 1, ?tag:String = null) { - if(tag != null && tag.length > 0) { - tag = tag.replace('.', ''); - if(PlayState.instance.modchartSounds.exists(tag)) { - PlayState.instance.modchartSounds.get(tag).stop(); - } - PlayState.instance.modchartSounds.set(tag, FlxG.sound.play(Paths.sound(sound), volume, false, function() { - PlayState.instance.modchartSounds.remove(tag); - PlayState.instance.callOnLuas('onSoundFinished', [tag]); - })); - return; - } - FlxG.sound.play(Paths.sound(sound), volume); - }); - Lua_helper.add_callback(lua, "stopSound", function(tag:String) { - if(tag != null && tag.length > 1 && PlayState.instance.modchartSounds.exists(tag)) { - PlayState.instance.modchartSounds.get(tag).stop(); - PlayState.instance.modchartSounds.remove(tag); - } - }); - Lua_helper.add_callback(lua, "pauseSound", function(tag:String) { - if(tag != null && tag.length > 1 && PlayState.instance.modchartSounds.exists(tag)) { - PlayState.instance.modchartSounds.get(tag).pause(); - } - }); - Lua_helper.add_callback(lua, "resumeSound", function(tag:String) { - if(tag != null && tag.length > 1 && PlayState.instance.modchartSounds.exists(tag)) { - PlayState.instance.modchartSounds.get(tag).play(); - } - }); - Lua_helper.add_callback(lua, "soundFadeIn", function(tag:String, duration:Float, fromValue:Float = 0, toValue:Float = 1) { - if(tag == null || tag.length < 1) { - FlxG.sound.music.fadeIn(duration, fromValue, toValue); - } else if(PlayState.instance.modchartSounds.exists(tag)) { - PlayState.instance.modchartSounds.get(tag).fadeIn(duration, fromValue, toValue); - } - - }); - Lua_helper.add_callback(lua, "soundFadeOut", function(tag:String, duration:Float, toValue:Float = 0) { - if(tag == null || tag.length < 1) { - FlxG.sound.music.fadeOut(duration, toValue); - } else if(PlayState.instance.modchartSounds.exists(tag)) { - PlayState.instance.modchartSounds.get(tag).fadeOut(duration, toValue); - } - }); - Lua_helper.add_callback(lua, "soundFadeCancel", function(tag:String) { - if(tag == null || tag.length < 1) { - if(FlxG.sound.music.fadeTween != null) { - FlxG.sound.music.fadeTween.cancel(); - } - } else if(PlayState.instance.modchartSounds.exists(tag)) { - var theSound:FlxSound = PlayState.instance.modchartSounds.get(tag); - if(theSound.fadeTween != null) { - theSound.fadeTween.cancel(); - PlayState.instance.modchartSounds.remove(tag); - } - } - }); - Lua_helper.add_callback(lua, "getSoundVolume", function(tag:String) { - if(tag == null || tag.length < 1) { - if(FlxG.sound.music != null) { - return FlxG.sound.music.volume; - } - } else if(PlayState.instance.modchartSounds.exists(tag)) { - return PlayState.instance.modchartSounds.get(tag).volume; - } - return 0; - }); - Lua_helper.add_callback(lua, "setSoundVolume", function(tag:String, value:Float) { - if(tag == null || tag.length < 1) { - if(FlxG.sound.music != null) { - FlxG.sound.music.volume = value; - } - } else if(PlayState.instance.modchartSounds.exists(tag)) { - PlayState.instance.modchartSounds.get(tag).volume = value; - } - }); - Lua_helper.add_callback(lua, "getSoundTime", function(tag:String) { - if(tag != null && tag.length > 0 && PlayState.instance.modchartSounds.exists(tag)) { - return PlayState.instance.modchartSounds.get(tag).time; - } - return 0; - }); - Lua_helper.add_callback(lua, "setSoundTime", function(tag:String, value:Float) { - if(tag != null && tag.length > 0 && PlayState.instance.modchartSounds.exists(tag)) { - var theSound:FlxSound = PlayState.instance.modchartSounds.get(tag); - if(theSound != null) { - var wasResumed:Bool = theSound.playing; - theSound.pause(); - theSound.time = value; - if(wasResumed) theSound.play(); - } - } - }); - - Lua_helper.add_callback(lua, "debugPrint", function(text1:Dynamic = '', text2:Dynamic = '', text3:Dynamic = '', text4:Dynamic = '', text5:Dynamic = '') { - text1 ??= ''; - text2 ??= ''; - text3 ??= ''; - text4 ??= ''; - text5 ??= ''; - luaTrace('' + text1 + text2 + text3 + text4 + text5, true, false); - }); - - Lua_helper.add_callback(lua, "close", function() { - closed = true; - return closed; - }); - - Lua_helper.add_callback(lua, "changePresence", function(details:String, state:Null, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float) { - #if DISCORD_ALLOWED - DiscordClient.changePresence(details, state, smallImageKey, hasStartTimestamp, endTimestamp); - #else - luaTrace('changePresence: Discord presence is not allowed in this platform.', false, false, FlxColor.RED); - #end - return true; - }); - - - // LUA TEXTS - Lua_helper.add_callback(lua, "makeLuaText", function(tag:String, text:String, width:Int, x:Float, y:Float) { - tag = tag.replace('.', ''); - resetTextTag(tag); - var leText:ModchartText = new ModchartText(x, y, text, width); - PlayState.instance.modchartTexts.set(tag, leText); - }); - - Lua_helper.add_callback(lua, "setTextString", function(tag:String, text:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - obj.text = text; - return true; - } - luaTrace("setTextString: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setTextSize", function(tag:String, size:Int) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - obj.size = size; - return true; - } - luaTrace("setTextSize: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setTextWidth", function(tag:String, width:Float) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - obj.fieldWidth = width; - return true; - } - luaTrace("setTextWidth: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setTextBorder", function(tag:String, size:Int, color:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - var colorNum:Int = Std.parseInt(color); - if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); - - obj.borderSize = size; - obj.borderColor = colorNum; - return true; - } - luaTrace("setTextBorder: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setTextColor", function(tag:String, color:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - var colorNum:Int = Std.parseInt(color); - if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); - - obj.color = colorNum; - return true; - } - luaTrace("setTextColor: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setTextFont", function(tag:String, newFont:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - obj.font = Paths.font(newFont); - return true; - } - luaTrace("setTextFont: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setTextItalic", function(tag:String, italic:Bool) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - obj.italic = italic; - return true; - } - luaTrace("setTextItalic: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - Lua_helper.add_callback(lua, "setTextAlignment", function(tag:String, alignment:String = 'left') { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - obj.alignment = LEFT; - switch(alignment.trim().toLowerCase()) - { - case 'right': - obj.alignment = RIGHT; - case 'center': - obj.alignment = CENTER; - } - return true; - } - luaTrace("setTextAlignment: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return false; - }); - - Lua_helper.add_callback(lua, "getTextString", function(tag:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null && obj.text != null) - { - return obj.text; - } - luaTrace("getTextString: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - }); - Lua_helper.add_callback(lua, "getTextSize", function(tag:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - return obj.size; - } - luaTrace("getTextSize: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return -1; - }); - Lua_helper.add_callback(lua, "getTextFont", function(tag:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - return obj.font; - } - luaTrace("getTextFont: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - Lua.pushnil(lua); - return null; - }); - Lua_helper.add_callback(lua, "getTextWidth", function(tag:String) { - var obj:FlxText = getTextObject(tag); - if(obj != null) - { - return obj.fieldWidth; - } - luaTrace("getTextWidth: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); - return 0; - }); - - Lua_helper.add_callback(lua, "addLuaText", function(tag:String) { - if(PlayState.instance.modchartTexts.exists(tag)) { - var shit:ModchartText = PlayState.instance.modchartTexts.get(tag); - if(!shit.wasAdded) { - getInstance().add(shit); - shit.wasAdded = true; - //trace('added a thing: ' + tag); - } - } - }); - Lua_helper.add_callback(lua, "removeLuaText", function(tag:String, destroy:Bool = true) { - if(!PlayState.instance.modchartTexts.exists(tag)) { - return; - } - - var pee:ModchartText = PlayState.instance.modchartTexts.get(tag); - if(destroy) { - pee.kill(); - } - - if(pee.wasAdded) { - getInstance().remove(pee, true); - pee.wasAdded = false; - } - - if(destroy) { - pee.destroy(); - PlayState.instance.modchartTexts.remove(tag); - } - }); - - Lua_helper.add_callback(lua, "initSaveData", function(name:String, ?folder:String = 'psychenginemods') { - if(!PlayState.instance.modchartSaves.exists(name)) - { - var save:FlxSave = new FlxSave(); - save.bind(name, CoolUtil.getSavePath() + "/" + folder); - PlayState.instance.modchartSaves.set(name, save); - return; - } - luaTrace('initSaveData: Save file already initialized: ' + name); - }); - Lua_helper.add_callback(lua, "flushSaveData", function(name:String) { - if(PlayState.instance.modchartSaves.exists(name)) - { - PlayState.instance.modchartSaves.get(name).flush(); - return; - } - luaTrace('flushSaveData: Save file not initialized: ' + name, false, false, FlxColor.RED); - }); - Lua_helper.add_callback(lua, "getDataFromSave", function(name:String, field:String, ?defaultValue:Dynamic = null) { - if(PlayState.instance.modchartSaves.exists(name)) - { - var retVal:Dynamic = Reflect.field(PlayState.instance.modchartSaves.get(name).data, field); - return retVal; - } - luaTrace('getDataFromSave: Save file not initialized: ' + name, false, false, FlxColor.RED); - return defaultValue; - }); - Lua_helper.add_callback(lua, "setDataFromSave", function(name:String, field:String, value:Dynamic) { - if(PlayState.instance.modchartSaves.exists(name)) - { - Reflect.setField(PlayState.instance.modchartSaves.get(name).data, field, value); - return; - } - luaTrace('setDataFromSave: Save file not initialized: ' + name, false, false, FlxColor.RED); - }); - - Lua_helper.add_callback(lua, "checkFileExists", function(filename:String, ?absolute:Bool = false) { - #if MODS_ALLOWED - if(absolute) - { - return FileSystem.exists(filename); - } - - var path:String = Paths.modFolders(filename); - if(FileSystem.exists(path)) - { - return true; - } - return FileSystem.exists(Paths.getPath('assets/$filename', TEXT)); - #else - if(absolute) - { - return Assets.exists(filename); - } - return Assets.exists(Paths.getPath('assets/$filename', TEXT)); - #end - }); - Lua_helper.add_callback(lua, "saveFile", function(path:String, content:String, ?absolute:Bool = false) - { - try { - if(!absolute) - #if MODS_ALLOWED - File.saveContent(Paths.mods(path), content); - #else - File.saveContent(Paths.getPreloadPath(path), content); - #end - else - File.saveContent(path, content); - - return true; - } catch (e:Dynamic) { - luaTrace("saveFile: Error trying to save " + path + ": " + e, false, false, FlxColor.RED); - } - return false; - }); - Lua_helper.add_callback(lua, "deleteFile", function(path:String, ?ignoreModFolders:Bool = false) - { - try { - #if MODS_ALLOWED - if(!ignoreModFolders) - { - var lePath:String = Paths.modFolders(path); - if(FileSystem.exists(lePath)) - { - FileSystem.deleteFile(lePath); - return true; - } - } - #end - - var lePath:String = Paths.getPath(path, TEXT); - if(Assets.exists(lePath)) - { - FileSystem.deleteFile(lePath); - return true; - } - } catch (e:Dynamic) { - luaTrace("deleteFile: Error trying to delete " + path + ": " + e, false, false, FlxColor.RED); - } - return false; - }); - Lua_helper.add_callback(lua, "getTextFromFile", function(path:String, ?ignoreModFolders:Bool = false) { - return Paths.getTextFromFile(path, ignoreModFolders); - }); - - // DEPRECATED, DONT MESS WITH THESE SHITS, ITS JUST THERE FOR BACKWARD COMPATIBILITY - Lua_helper.add_callback(lua, "objectPlayAnimation", function(obj:String, name:String, forced:Bool = false, ?startFrame:Int = 0) { - luaTrace("objectPlayAnimation is deprecated! Use playAnim instead", false, true); - if(PlayState.instance.getLuaObject(obj,false) != null) { - PlayState.instance.getLuaObject(obj,false).animation.play(name, forced, false, startFrame); - return true; - } - - var spr:FlxSprite = Reflect.getProperty(getInstance(), obj); - if(spr != null) { - spr.animation.play(name, forced, false, startFrame); - return true; - } - return false; - }); - Lua_helper.add_callback(lua, "characterPlayAnim", function(character:String, anim:String, ?forced:Bool = false) { - luaTrace("characterPlayAnim is deprecated! Use playAnim instead", false, true); - switch(character.toLowerCase()) { - case 'dad': - if(PlayState.instance.dad.animOffsets.exists(anim)) - PlayState.instance.dad.playAnim(anim, forced); - case 'gf' | 'girlfriend': - if(PlayState.instance.gf != null && PlayState.instance.gf.animOffsets.exists(anim)) - PlayState.instance.gf.playAnim(anim, forced); - default: - if(PlayState.instance.boyfriend.animOffsets.exists(anim)) - PlayState.instance.boyfriend.playAnim(anim, forced); - } - }); - Lua_helper.add_callback(lua, "luaSpriteMakeGraphic", function(tag:String, width:Int, height:Int, color:String) { - luaTrace("luaSpriteMakeGraphic is deprecated! Use makeGraphic instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - var colorNum:Int = Std.parseInt(color); - if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); - - PlayState.instance.modchartSprites.get(tag).makeGraphic(width, height, colorNum); - } - }); - Lua_helper.add_callback(lua, "luaSpriteAddAnimationByPrefix", function(tag:String, name:String, prefix:String, framerate:Int = 24, loop:Bool = true) { - luaTrace("luaSpriteAddAnimationByPrefix is deprecated! Use addAnimationByPrefix instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - var cock:ModchartSprite = PlayState.instance.modchartSprites.get(tag); - cock.animation.addByPrefix(name, prefix, framerate, loop); - if(cock.animation.curAnim == null) { - cock.animation.play(name, true); - } - } - }); - Lua_helper.add_callback(lua, "luaSpriteAddAnimationByIndices", function(tag:String, name:String, prefix:String, indices:String, framerate:Int = 24) { - luaTrace("luaSpriteAddAnimationByIndices is deprecated! Use addAnimationByIndices instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - var strIndices:Array = indices.trim().split(','); - var die:Array = []; - for (i in 0...strIndices.length) { - die.push(Std.parseInt(strIndices[i])); - } - var pussy:ModchartSprite = PlayState.instance.modchartSprites.get(tag); - pussy.animation.addByIndices(name, prefix, die, '', framerate, false); - if(pussy.animation.curAnim == null) { - pussy.animation.play(name, true); - } - } - }); - Lua_helper.add_callback(lua, "luaSpritePlayAnimation", function(tag:String, name:String, forced:Bool = false) { - luaTrace("luaSpritePlayAnimation is deprecated! Use playAnim instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - PlayState.instance.modchartSprites.get(tag).animation.play(name, forced); - } - }); - Lua_helper.add_callback(lua, "setLuaSpriteCamera", function(tag:String, camera:String = '') { - luaTrace("setLuaSpriteCamera is deprecated! Use setObjectCamera instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - PlayState.instance.modchartSprites.get(tag).cameras = [cameraFromString(camera)]; - return true; - } - luaTrace("Lua sprite with tag: " + tag + " doesn't exist!"); - return false; - }); - Lua_helper.add_callback(lua, "setLuaSpriteScrollFactor", function(tag:String, scrollX:Float, scrollY:Float) { - luaTrace("setLuaSpriteScrollFactor is deprecated! Use setScrollFactor instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - PlayState.instance.modchartSprites.get(tag).scrollFactor.set(scrollX, scrollY); - return true; - } - return false; - }); - Lua_helper.add_callback(lua, "scaleLuaSprite", function(tag:String, x:Float, y:Float) { - luaTrace("scaleLuaSprite is deprecated! Use scaleObject instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - var shit:ModchartSprite = PlayState.instance.modchartSprites.get(tag); - shit.scale.set(x, y); - shit.updateHitbox(); - return true; - } - return false; - }); - Lua_helper.add_callback(lua, "getPropertyLuaSprite", function(tag:String, variable:String) { - luaTrace("getPropertyLuaSprite is deprecated! Use getProperty instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - var killMe:Array = variable.split('.'); - if(killMe.length > 1) { - var coverMeInPiss:Dynamic = Reflect.getProperty(PlayState.instance.modchartSprites.get(tag), killMe[0]); - for (i in 1...killMe.length-1) { - coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); - } - return Reflect.getProperty(coverMeInPiss, killMe[killMe.length-1]); - } - return Reflect.getProperty(PlayState.instance.modchartSprites.get(tag), variable); - } - return null; - }); - Lua_helper.add_callback(lua, "setPropertyLuaSprite", function(tag:String, variable:String, value:Dynamic) { - luaTrace("setPropertyLuaSprite is deprecated! Use setProperty instead", false, true); - if(PlayState.instance.modchartSprites.exists(tag)) { - var killMe:Array = variable.split('.'); - if(killMe.length > 1) { - var coverMeInPiss:Dynamic = Reflect.getProperty(PlayState.instance.modchartSprites.get(tag), killMe[0]); - for (i in 1...killMe.length-1) { - coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); - } - Reflect.setProperty(coverMeInPiss, killMe[killMe.length-1], value); - return true; - } - Reflect.setProperty(PlayState.instance.modchartSprites.get(tag), variable, value); - return true; - } - luaTrace("setPropertyLuaSprite: Lua sprite with tag: " + tag + " doesn't exist!"); - return false; - }); - Lua_helper.add_callback(lua, "musicFadeIn", function(duration:Float, fromValue:Float = 0, toValue:Float = 1) { - FlxG.sound.music.fadeIn(duration, fromValue, toValue); - luaTrace('musicFadeIn is deprecated! Use soundFadeIn instead.', false, true); - - }); - Lua_helper.add_callback(lua, "musicFadeOut", function(duration:Float, toValue:Float = 0) { - FlxG.sound.music.fadeOut(duration, toValue); - luaTrace('musicFadeOut is deprecated! Use soundFadeOut instead.', false, true); - }); - - // Other stuff - Lua_helper.add_callback(lua, "stringStartsWith", function(str:String, start:String) { - return str.startsWith(start); - }); - Lua_helper.add_callback(lua, "stringEndsWith", function(str:String, end:String) { - return str.endsWith(end); - }); - Lua_helper.add_callback(lua, "stringSplit", function(str:String, split:String) { - return str.split(split); - }); - Lua_helper.add_callback(lua, "stringTrim", function(str:String) { - return str.trim(); - }); - - Lua_helper.add_callback(lua, "directoryFileList", function(folder:String) { - var list:Array = []; - #if sys - if(FileSystem.exists(folder)) { - for (folder in FileSystem.readDirectory(folder)) { - if (!list.contains(folder)) { - list.push(folder); - } - } - } - #end - return list; - }); - - #if HSCRIPT_ALLOWED - Lua_helper.add_callback(lua, "setOnHScript", PlayState.instance.setOnHScript); - - Lua_helper.add_callback(lua, "callOnHScript", function(funcName:String, ?args:Array = null, ?ignoreStops=false, ?ignoreSelf:Bool = true, ?excludeScripts:Array = null, ?excludeValues:Array = null) { - excludeScripts ??= []; - if(ignoreSelf && !excludeScripts.contains(scriptName)) excludeScripts.push(scriptName); - return PlayState.instance.callOnHScript(funcName, args, ignoreStops, excludeScripts, excludeValues); - }); - #end - - if(useCustomFunctions && customFunctions != null) { - for(tag => func in customFunctions) Lua_helper.add_callback(lua, Std.string(tag), func); - } - - call('onCreate', []); - #end - } - - public static function isOfTypes(value:Any, types:Array) - { - for (type in types) - { - if(Std.isOfType(value, type)) return true; - } - return false; - } - - public function addLocalCallback(name:String, func:Dynamic) - { - callbacks.set(name, func); - #if LUA_ALLOWED - Lua_helper.add_callback(lua, name, func); //just so that it gets called - #end - } - - #if HSCRIPT_ALLOWED - public function initHaxeModule() - { - if(hscript == null) - { - trace('initializing haxe interp for: $scriptName'); - hscript = new HScript(); //TO DO: Fix issue with 2 scripts not being able to use the same variable names - } - } - #end - - public static function setVarInArray(instance:Dynamic, variable:String, value:Dynamic):Bool - { - if (variable == null || variable.length == 0) return false; - - var shit:Array = variable.split('['); - if (shit.length > 1) - { - var blah:Dynamic = null; - var propName:String = shit[0]; - - if (PlayState.instance != null && PlayState.instance.variables.exists(propName)) - { - blah = PlayState.instance.variables.get(propName); - } - else if (instance != null && Reflect.hasField(instance, propName)) - { - blah = Reflect.getProperty(instance, propName); - } - - if (blah == null) { - trace('WARNING: Property not found: ${shit[0]}'); - return false; - } - - for (i in 1...shit.length) - { - var arrayPart:String = shit[i]; - if (arrayPart.endsWith(']')) arrayPart = arrayPart.substr(0, arrayPart.length - 1); - - if (arrayPart.length == 0) continue; - - var key:Dynamic = try Std.parseInt(arrayPart) catch(e:Dynamic) arrayPart; - - if (i == shit.length - 1) - { - if (Std.isOfType(blah, Array) || Reflect.hasField(blah, 'set') || Reflect.isObject(blah)) - { - blah[key] = value; - return true; - } - trace('WARNING: Cannot set index on non-array: ${shit[0]}'); - return false; - } - else - { - if (blah != null && (Reflect.isObject(blah) || Std.isOfType(blah, Array))) - { - blah = blah[key]; - } - else - { - trace('WARNING: Cannot access index on non-container: ${shit.slice(0, i).join('[')}'); - return false; - } - } - } - return false; - } - - try - { - if (PlayState.instance != null && PlayState.instance.variables.exists(variable)) - { - PlayState.instance.variables.set(variable, value); - return true; - } - - if (instance != null) - { - Reflect.setProperty(instance, variable, value); - return true; - } - - trace('WARNING: Instance is null for property: $variable'); - return false; - } - catch(e:Dynamic) - { - trace('ERROR: Failed to set property $variable: ${e.message}'); - return false; - } - } - public static function getVarInArray(instance:Dynamic, variable:String):Any - { - var shit:Array = variable.split('['); - if(shit.length > 1) - { - var blah:Dynamic = null; - if(PlayState.instance.variables.exists(shit[0])) - { - var retVal:Dynamic = PlayState.instance.variables.get(shit[0]); - if(retVal != null) - blah = retVal; - } - else - blah = Reflect.getProperty(instance, shit[0]); - - for (i in 1...shit.length) - { - var leNumStr:Dynamic = shit[i].substr(0, shit[i].length - 1); - //fucking hl fix - var leNum:Dynamic = try Std.parseInt(leNumStr) catch (_) null; - leNum ??= leNumStr; - - blah = blah[leNum]; - } - return blah; - } - - if(PlayState.instance.variables.exists(variable)) - { - var retVal:Dynamic = PlayState.instance.variables.get(variable); - if(retVal != null) - return retVal; - } - - return Reflect.getProperty(instance, variable); - } - - #if flixel_animate - private function loadTimelineFromJson(path:String):Timeline { - #if MODS_ALLOWED - var rawJson:String = Paths.getTextFromFile(path); - #else - var rawJson:String = Assets.getText(Paths.json(path)); - #end - - if(rawJson != null && rawJson.length > 0) { - try { - var json:TimelineJson = haxe.Json.parse(rawJson); - // This ass pissed me off tbh - var dummyGraphic = FlxGraphic.fromRectangle(1, 1, FlxColor.TRANSPARENT); - var dummyParent = new FlxAnimateFrames(dummyGraphic); - return new Timeline(json, dummyParent, path); - } catch(e:Dynamic) { - trace('Error parsing timeline JSON: $e'); - } - } - return null; - } - #end - - inline static function getTextObject(name:String):FlxText - { - return PlayState.instance.modchartTexts.exists(name) ? PlayState.instance.modchartTexts.get(name) : Reflect.getProperty(PlayState.instance, name); - } - - #if (!flash && sys) - public function getShader(obj:String):FlxRuntimeShader - { - var killMe:Array = obj.split('.'); - var leObj:FlxSprite = getObjectDirectly(killMe[0]); - if(killMe.length > 1) { - leObj = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); - } - - if(leObj != null) { - var shader:Dynamic = leObj.shader; - var shader:FlxRuntimeShader = shader; - return shader; - } - return null; - } - #end - - function initLuaShader(name:String, ?glslVersion:Int = 120) - { - if(!ClientPrefs.shaders) return false; - - #if (!flash && sys) - if(PlayState.instance.runtimeShaders.exists(name)) - { - luaTrace('Shader $name was already initialized!'); - return true; - } - - var foldersToCheck:Array = [Paths.getPreloadPath('shaders/') #if MODS_ALLOWED , Paths.mods('shaders/') #end]; - - #if MODS_ALLOWED - if(Paths.currentModDirectory != null && Paths.currentModDirectory.length > 0) - foldersToCheck.insert(0, Paths.mods(Paths.currentModDirectory + '/shaders/')); - - for(mod in Paths.getGlobalMods()) - foldersToCheck.insert(0, Paths.mods(mod + '/shaders/')); - #end - - for (folder in foldersToCheck) - { - if(FileSystem.exists(folder)) - { - var frag:String = folder + name + '.frag'; - var vert:String = folder + name + '.vert'; - var found:Bool = false; - if(FileSystem.exists(frag)) - { - frag = File.getContent(frag); - found = true; - } - else frag = null; - - if(FileSystem.exists(vert)) - { - vert = File.getContent(vert); - found = true; - } - else vert = null; - - if(found) - { - PlayState.instance.runtimeShaders.set(name, [frag, vert]); - //trace('Found shader $name!'); - return true; - } - } - } - luaTrace('Missing shader $name .frag AND .vert files!', false, false, FlxColor.RED); - #else - luaTrace('This platform doesn\'t support Runtime Shaders!', false, false, FlxColor.RED); - #end - return false; - } - - function getGroupStuff(leArray:Dynamic, variable:String) { - var killMe:Array = variable.split('.'); - if(killMe.length > 1) { - var coverMeInPiss:Dynamic = Reflect.getProperty(leArray, killMe[0]); - for (i in 1...killMe.length-1) { - coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); - } - switch(Type.typeof(coverMeInPiss)){ - case ValueType.TClass(haxe.ds.StringMap) | ValueType.TClass(haxe.ds.ObjectMap) | ValueType.TClass(haxe.ds.IntMap) | ValueType.TClass(haxe.ds.EnumValueMap): - return coverMeInPiss.get(killMe[killMe.length-1]); - default: - return Reflect.getProperty(coverMeInPiss, killMe[killMe.length-1]); - }; - } - switch(Type.typeof(leArray)){ - case ValueType.TClass(haxe.ds.StringMap) | ValueType.TClass(haxe.ds.ObjectMap) | ValueType.TClass(haxe.ds.IntMap) | ValueType.TClass(haxe.ds.EnumValueMap): - return leArray.get(variable); - default: - return Reflect.getProperty(leArray, variable); - }; - } - - function loadFrames(spr:FlxSprite, image:String, spriteType:String) - { - switch(spriteType.toLowerCase().trim()) - { - /*case "texture" | "textureatlas" | "tex": - spr.frames = AtlasFrameMaker.construct(image); - - case "texture_noaa" | "textureatlas_noaa" | "tex_noaa": - spr.frames = AtlasFrameMaker.construct(image, null, true);*/ - - case 'aseprite', 'ase', 'json', 'jsoni8': - spr.frames = Paths.getAsepriteAtlas(image); - - case "packer" | "packeratlas" | "pac": - spr.frames = Paths.getPackerAtlas(image); - - default: - spr.frames = Paths.getSparrowAtlas(image); - } - } - - function setGroupStuff(leArray:Dynamic, variable:String, value:Dynamic) { - var killMe:Array = variable.split('.'); - if(killMe.length > 1) { - var coverMeInPiss:Dynamic = Reflect.getProperty(leArray, killMe[0]); - for (i in 1...killMe.length-1) { - coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); - } - Reflect.setProperty(coverMeInPiss, killMe[killMe.length-1], value); - return; - } - Reflect.setProperty(leArray, variable, value); - } - - function resetTextTag(tag:String) { - if(!PlayState.instance.modchartTexts.exists(tag)) { - return; - } - - var pee:ModchartText = PlayState.instance.modchartTexts.get(tag); - pee.kill(); - if(pee.wasAdded) { - PlayState.instance.remove(pee, true); - } - pee.destroy(); - PlayState.instance.modchartTexts.remove(tag); - } - - function resetSpriteTag(tag:String) { - if(!PlayState.instance.modchartSprites.exists(tag)) { - return; - } - - var pee:ModchartSprite = PlayState.instance.modchartSprites.get(tag); - pee.kill(); - if(pee.wasAdded) { - PlayState.instance.remove(pee, true); - } - pee.destroy(); - PlayState.instance.modchartSprites.remove(tag); - } - - function cancelTween(tag:String) { - if(PlayState.instance.modchartTweens.exists(tag)) { - PlayState.instance.modchartTweens.get(tag).cancel(); - PlayState.instance.modchartTweens.get(tag).destroy(); - PlayState.instance.modchartTweens.remove(tag); - } - } - - function tweenShit(tag:String, vars:String) { - cancelTween(tag); - var variables:Array = vars.split('.'); - var sexyProp:Dynamic = getObjectDirectly(variables[0]); - if(variables.length > 1) { - sexyProp = getVarInArray(getPropertyLoopThingWhatever(variables), variables[variables.length-1]); - } - return sexyProp; - } - - function cancelTimer(tag:String) { - if(PlayState.instance.modchartTimers.exists(tag)) { - var theTimer:FlxTimer = PlayState.instance.modchartTimers.get(tag); - theTimer.cancel(); - theTimer.destroy(); - PlayState.instance.modchartTimers.remove(tag); - } - } - - //Better optimized than using some getProperty shit or idk - function getFlxEaseByString(?ease:String = '') { - switch(ease.toLowerCase().trim()) { - case 'backin': return FlxEase.backIn; - case 'backinout': return FlxEase.backInOut; - case 'backout': return FlxEase.backOut; - case 'bouncein': return FlxEase.bounceIn; - case 'bounceinout': return FlxEase.bounceInOut; - case 'bounceout': return FlxEase.bounceOut; - case 'circin': return FlxEase.circIn; - case 'circinout': return FlxEase.circInOut; - case 'circout': return FlxEase.circOut; - case 'cubein': return FlxEase.cubeIn; - case 'cubeinout': return FlxEase.cubeInOut; - case 'cubeout': return FlxEase.cubeOut; - case 'elasticin': return FlxEase.elasticIn; - case 'elasticinout': return FlxEase.elasticInOut; - case 'elasticout': return FlxEase.elasticOut; - case 'expoin': return FlxEase.expoIn; - case 'expoinout': return FlxEase.expoInOut; - case 'expoout': return FlxEase.expoOut; - case 'quadin': return FlxEase.quadIn; - case 'quadinout': return FlxEase.quadInOut; - case 'quadout': return FlxEase.quadOut; - case 'quartin': return FlxEase.quartIn; - case 'quartinout': return FlxEase.quartInOut; - case 'quartout': return FlxEase.quartOut; - case 'quintin': return FlxEase.quintIn; - case 'quintinout': return FlxEase.quintInOut; - case 'quintout': return FlxEase.quintOut; - case 'sinein': return FlxEase.sineIn; - case 'sineinout': return FlxEase.sineInOut; - case 'sineout': return FlxEase.sineOut; - case 'smoothstepin': return FlxEase.smoothStepIn; - case 'smoothstepinout': return FlxEase.smoothStepInOut; - case 'smoothstepout': return FlxEase.smoothStepInOut; - case 'smootherstepin': return FlxEase.smootherStepIn; - case 'smootherstepinout': return FlxEase.smootherStepInOut; - case 'smootherstepout': return FlxEase.smootherStepOut; - } - return FlxEase.linear; - } - - function blendModeFromString(blend:String):BlendMode { - switch(blend.toLowerCase().trim()) { - case 'add': return ADD; - case 'alpha': return ALPHA; - case 'darken': return DARKEN; - case 'difference': return DIFFERENCE; - case 'erase': return ERASE; - case 'hardlight': return HARDLIGHT; - case 'invert': return INVERT; - case 'layer': return LAYER; - case 'lighten': return LIGHTEN; - case 'multiply': return MULTIPLY; - case 'overlay': return OVERLAY; - case 'screen': return SCREEN; - case 'shader': return SHADER; - case 'subtract': return SUBTRACT; - } - return NORMAL; - } - - function cameraFromString(cam:String):FlxCamera { - switch(cam.toLowerCase()) { - case 'camhud' | 'hud': return PlayState.instance.camHUD; - case 'camother' | 'other': return PlayState.instance.camOther; - } - return PlayState.instance.camGame; - } - - public function luaTrace(text:String, ignoreCheck:Bool = false, deprecated:Bool = false, color:FlxColor = FlxColor.WHITE) { + @:unreflective + public static final Function_Stop:Dynamic = 1; + + @:unreflective + public static final Function_Continue:Dynamic = 0; + + @:unreflective + public static final Function_StopLua:Dynamic = 2; + + #if LUA_ALLOWED + public var lua:cpp.RawPointer = null; + #end + public var scriptName:String = ''; + public var closed:Bool = false; + public var modFolder:String = null; + + #if HSCRIPT_ALLOWED + public static var hscript:FunkinHScript = null; + #end + + public static var useCustomFunctions:Bool = false; + public static var customFunctions:Map = new Map(); + public var callbacks:Map = new Map(); + + public static var lastCalledScript:FunkinLua = null; + public var lastCalledFunction:String = ''; + + public function new(script:String, ?isString:Bool = false) { + #if LUA_ALLOWED + lua = LuaL.newstate(); + LuaL.openlibs(lua); + + LuaError.errorHandler = function(error:String) { + trace('Lua Error: $error'); + #if windows + CoolUtil.showPopUp(error, 'Lua Error'); + #end + }; + + try { + var result:Int; + if (!isString) { + result = LuaL.dofile(lua, script); + } else { + result = LuaL.dostring(lua, script); + } + + if (result != Lua.OK) { + var errorMsg:String = Lua.tostring(lua, -1); + trace('Error loading lua script: $errorMsg'); + #if windows + if (!isString) CoolUtil.showPopUp(errorMsg, 'Error on lua script!'); + #end + lua = null; + return; + } + } catch (e:Dynamic) { + trace('Exception loading lua: $e'); + return; + } + + scriptName = script; + var myFolder:Array = this.scriptName.trim().split('/'); + #if MODS_ALLOWED + if (myFolder[0] + '/' == Mods.getModPath() && (Mods.currentModDirectory == myFolder[1] || Mods.getGlobalMods().contains(myFolder[1]))) { + this.modFolder = myFolder[1]; + } + #end + + #if HSCRIPT_ALLOWED + initHaxeModule(); + #end + + trace((isString ? 'lua string loaded succesfully' : 'lua file loaded succesfully: $script')); + + set('Function_StopLua', Function_StopLua); + set('Function_Stop', Function_Stop); + set('Function_Continue', Function_Continue); + + set('luaDebugMode', false); + set('luaDeprecatedWarnings', true); + set('luaPropertyDebugTraces', false); + set('inChartEditor', false); + + set('curBpm', Conductor.bpm); + set('bpm', PlayState.SONG.bpm); + set('scrollSpeed', PlayState.SONG.speed); + set('crochet', Conductor.crochet); + set('stepCrochet', Conductor.stepCrochet); + set('songLength', FlxG.sound.music.length); + set('songName', PlayState.SONG.song); + set('songPath', Paths.formatToSongPath(PlayState.SONG.song)); + set('startedCountdown', false); + set('curStage', PlayState.SONG.stage); + + set('isStoryMode', PlayState.isStoryMode); + set('difficulty', PlayState.storyDifficulty); + + var difficultyName:String = CoolUtil.difficulties[PlayState.storyDifficulty]; + set('difficultyName', difficultyName); + set('difficultyPath', Paths.formatToSongPath(difficultyName)); + set('weekRaw', PlayState.storyWeek); + set('week', WeekData.weeksList[PlayState.storyWeek]); + set('seenCutscene', PlayState.seenCutscene); + + set('cameraX', 0); + set('cameraY', 0); + + set('screenWidth', FlxG.width); + set('screenHeight', FlxG.height); + + // PlayState + set('curBeat', 0); + set('curStep', 0); + set('curDecBeat', 0); + set('curDecStep', 0); + + set('score', 0); + set('misses', 0); + set('hits', 0); + + set('rating', 0); + set('ratingName', ''); + set('ratingFC', ''); + set('version', Application.current.meta.get('version')); + + set('inGameOver', false); + set('mustHitSection', false); + set('altAnim', false); + set('gfSection', false); + + set('healthGainMult', PlayState.instance.healthGain); + set('healthLossMult', PlayState.instance.healthLoss); + set('playbackRate', PlayState.instance.playbackRate); + set('instakillOnMiss', PlayState.instance.instakillOnMiss); + set('botPlay', PlayState.instance.cpuControlled); + set('practice', PlayState.instance.practiceMode); + + for (i in 0...4) { + set('defaultPlayerStrumX' + i, 0); + set('defaultPlayerStrumY' + i, 0); + set('defaultOpponentStrumX' + i, 0); + set('defaultOpponentStrumY' + i, 0); + } + + set('defaultBoyfriendX', PlayState.instance.BF_X); + set('defaultBoyfriendY', PlayState.instance.BF_Y); + set('defaultOpponentX', PlayState.instance.DAD_X); + set('defaultOpponentY', PlayState.instance.DAD_Y); + set('defaultGirlfriendX', PlayState.instance.GF_X); + set('defaultGirlfriendY', PlayState.instance.GF_Y); + + set('boyfriendName', PlayState.SONG.player1); + set('dadName', PlayState.SONG.player2); + set('gfName', PlayState.SONG.gfVersion); + + set('downscroll', ClientPrefs.downScroll); + set('middlescroll', ClientPrefs.middleScroll); + set('framerate', ClientPrefs.framerate); + set('ghostTapping', ClientPrefs.ghostTapping); + set('hideHud', ClientPrefs.hideHud); + set('timeBarType', ClientPrefs.timeBarType); + set('scoreZoom', ClientPrefs.scoreZoom); + set('cameraZoomOnBeat', ClientPrefs.camZooms); + set('flashingLights', ClientPrefs.flashing); + set('noteOffset', ClientPrefs.noteOffset); + set('healthBarAlpha', ClientPrefs.healthBarAlpha); + set('noResetButton', ClientPrefs.noReset); + set('lowQuality', ClientPrefs.lowQuality); + set('shadersEnabled', ClientPrefs.shaders); + set('scriptName', scriptName); + set('currentModDirectory', Mods.currentModDirectory); + + set('buildTarget', CoolUtil.getBuildTarget()); + + registerCustomFunctions(); + + if (functionExists('onCreate')) call('onCreate', []); + #end + } + + private function registerCustomFunctions():Void { + #if LUA_ALLOWED + LuaUtils.addFunction(lua, "openCustomSubstate", function(name:String, pauseGame:Bool = false):Void { + if (pauseGame) { + PlayState.instance.persistentUpdate = false; + PlayState.instance.persistentDraw = true; + PlayState.instance.paused = true; + if (FlxG.sound.music != null) { + FlxG.sound.music.pause(); + PlayState.instance.vocals.pause(); + } + } + PlayState.instance.openSubState(new CustomSubstate(name)); + }); + + LuaUtils.addFunction(lua, "closeCustomSubstate", function():Bool { + if (CustomSubstate.instance != null) { + PlayState.instance.closeSubState(); + CustomSubstate.instance = null; + return true; + } + return false; + }); + + LuaUtils.addFunction(lua, "getRunningScripts", function():Array { + var runningScripts:Array = []; + for (idx in 0...PlayState.instance.luaArray.length) { + runningScripts.push(PlayState.instance.luaArray[idx].scriptName); + } + return runningScripts; + }); + + LuaUtils.addFunction(lua, "callOnLuas", function(funcName:String, ?args:Array, ignoreStops:Bool = false, ignoreSelf:Bool = true, ?exclusions:Array):Void { + if (args == null) args = []; + if (exclusions == null) exclusions = []; + + var daScriptName:String = scriptName; + if (ignoreSelf && !exclusions.contains(daScriptName)) { + exclusions.push(daScriptName); + } + PlayState.instance.callOnLuas(funcName, args, ignoreStops, exclusions); + }); + + LuaUtils.addFunction(lua, "callScript", function(luaFile:String, funcName:String, ?args:Array):Dynamic { + if (args == null) args = []; + + var cervix:String = luaFile + ".lua"; + if (luaFile.endsWith(".lua")) cervix = luaFile; + + var doPush:Bool = false; + #if MODS_ALLOWED + if (FileSystem.exists(Mods.modFolders(cervix))) { + cervix = Mods.modFolders(cervix); + doPush = true; + } else if (FileSystem.exists(cervix)) { + doPush = true; + } else { + cervix = Paths.getPreloadPath(cervix); + if (FileSystem.exists(cervix)) { + doPush = true; + } + } + #else + cervix = Paths.getPreloadPath(cervix); + if (Assets.exists(cervix)) { + doPush = true; + } + #end + + if (doPush) { + for (luaInstance in PlayState.instance.luaArray) { + if (luaInstance.scriptName == cervix) { + return luaInstance.call(funcName, args); + } + } + } + return null; + }); + + #if MODS_ALLOWED + LuaUtils.addFunction(lua, "getModSetting", function(saveTag:String, ?modName:String = null):Dynamic { + if (modName == null) { + if (this.modFolder == null) { + luaTrace('getModSetting: Argument #2 is null and script is not inside a packed Mod folder!', false, false, FlxColor.RED); + return null; + } + modName = this.modFolder; + } + return CoolUtil.getModSetting(saveTag, modName); + }); + #end + + LuaUtils.addFunction(lua, "getGlobalFromScript", function(luaFile:String, global:String):Dynamic { + var cervix:String = luaFile + ".lua"; + if (luaFile.endsWith(".lua")) cervix = luaFile; + + var doPush:Bool = false; + #if MODS_ALLOWED + if (FileSystem.exists(Mods.modFolders(cervix))) { + cervix = Mods.modFolders(cervix); + doPush = true; + } else if (FileSystem.exists(cervix)) { + doPush = true; + } else { + cervix = Paths.getPreloadPath(cervix); + if (FileSystem.exists(cervix)) { + doPush = true; + } + } + #else + cervix = Paths.getPreloadPath(cervix); + if (Assets.exists(cervix)) { + doPush = true; + } + #end + + if (doPush) { + for (luaInstance in PlayState.instance.luaArray) { + if (luaInstance.scriptName == cervix) { + return LuaUtils.getVariable(luaInstance.lua, global); + } + } + } + return null; + }); + + LuaUtils.addFunction(lua, "setGlobalFromScript", function(luaFile:String, global:String, val:Dynamic):Void { + var cervix:String = luaFile + ".lua"; + if (luaFile.endsWith(".lua")) cervix = luaFile; + + var doPush:Bool = false; + #if MODS_ALLOWED + if (FileSystem.exists(Mods.modFolders(cervix))) { + cervix = Mods.modFolders(cervix); + doPush = true; + } else if (FileSystem.exists(cervix)) { + doPush = true; + } else { + cervix = Paths.getPreloadPath(cervix); + if (FileSystem.exists(cervix)) { + doPush = true; + } + } + #else + cervix = Paths.getPreloadPath(cervix); + if (Assets.exists(cervix)) { + doPush = true; + } + #end + + if (doPush) { + for (luaInstance in PlayState.instance.luaArray) { + if (luaInstance.scriptName == cervix) { + luaInstance.set(global, val); + } + } + } + }); + + LuaUtils.addFunction(lua, "isRunning", function(luaFile:String):Bool { + var cervix:String = luaFile + ".lua"; + if (luaFile.endsWith(".lua")) cervix = luaFile; + + var doPush:Bool = false; + #if MODS_ALLOWED + if (FileSystem.exists(Mods.modFolders(cervix))) { + cervix = Mods.modFolders(cervix); + doPush = true; + } else if (FileSystem.exists(cervix)) { + doPush = true; + } else { + cervix = Paths.getPreloadPath(cervix); + if (FileSystem.exists(cervix)) { + doPush = true; + } + } + #else + cervix = Paths.getPreloadPath(cervix); + if (Assets.exists(cervix)) { + doPush = true; + } + #end + + if (doPush) { + for (luaInstance in PlayState.instance.luaArray) { + if (luaInstance.scriptName == cervix) { + return true; + } + } + } + return false; + }); + + LuaUtils.addFunction(lua, "addLuaScript", function(luaFile:String, ?ignoreAlreadyRunning:Bool = false):Void { + var cervix:String = luaFile + ".lua"; + if (luaFile.endsWith(".lua")) cervix = luaFile; + + var doPush:Bool = false; + #if MODS_ALLOWED + if (FileSystem.exists(Mods.modFolders(cervix))) { + cervix = Mods.modFolders(cervix); + doPush = true; + } else if (FileSystem.exists(cervix)) { + doPush = true; + } else { + cervix = Paths.getPreloadPath(cervix); + if (FileSystem.exists(cervix)) { + doPush = true; + } + } + #else + cervix = Paths.getPreloadPath(cervix); + if (Assets.exists(cervix)) { + doPush = true; + } + #end + + if (doPush) { + if (!ignoreAlreadyRunning) { + for (luaInstance in PlayState.instance.luaArray) { + if (luaInstance.scriptName == cervix) { + luaTrace('addLuaScript: The script "$cervix" is already running!'); + return; + } + } + } + PlayState.instance.luaArray.push(new FunkinLua(cervix)); + return; + } + luaTrace("addLuaScript: Script doesn't exist!", false, false, FlxColor.RED); + }); + + LuaUtils.addFunction(lua, "removeLuaScript", function(luaFile:String, ?ignoreAlreadyRunning:Bool = false):Void { + var cervix:String = luaFile + ".lua"; + if (luaFile.endsWith(".lua")) cervix = luaFile; + + var doPush:Bool = false; + #if MODS_ALLOWED + if (FileSystem.exists(Mods.modFolders(cervix))) { + cervix = Mods.modFolders(cervix); + doPush = true; + } else if (FileSystem.exists(cervix)) { + doPush = true; + } else { + cervix = Paths.getPreloadPath(cervix); + if (FileSystem.exists(cervix)) { + doPush = true; + } + } + #else + cervix = Paths.getPreloadPath(cervix); + if (Assets.exists(cervix)) { + doPush = true; + } + #end + + if (doPush) { + if (!ignoreAlreadyRunning) { + for (luaInstance in PlayState.instance.luaArray) { + if (luaInstance.scriptName == cervix) { + PlayState.instance.luaArray.remove(luaInstance); + return; + } + } + } + return; + } + luaTrace("removeLuaScript: Script doesn't exist!", false, false, FlxColor.RED); + }); + + #if HSCRIPT_ALLOWED + LuaUtils.addFunction(lua, "runHaxeCode", function(codeToRun:String):Dynamic { + var retVal:Dynamic = null; + initHaxeModule(); + try { + if (hscript != null) { + retVal = hscript.executeString(codeToRun); + } + } catch (e:Dynamic) { + luaTrace(scriptName + ":" + lastCalledFunction + " - " + e, false, false, FlxColor.RED); + } + + if (retVal != null && !isOfTypes(retVal, [Bool, Int, Float, String, Array])) { + retVal = null; + } + return retVal; + }); + + LuaUtils.addFunction(lua, "addHaxeLibrary", function(libName:String, ?libPackage:String = ''):Void { + initHaxeModule(); + try { + var str:String = ''; + if (libPackage.length > 0) { + str = libPackage + '.'; + } + hscript.set(libName, Type.resolveClass(str + libName)); + } catch (e:Dynamic) { + luaTrace(scriptName + ":" + lastCalledFunction + " - " + e, false, false, FlxColor.RED); + } + }); + #end + + LuaUtils.addFunction(lua, "getProperty", function(variable:String):Dynamic { + var result:Dynamic = null; + var killMe:Array = variable.split('.'); + if (killMe.length > 1) { + result = getVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } else { + result = getVarInArray(getInstance(), variable); + } + return result; + }); + + LuaUtils.addFunction(lua, "setProperty", function(variable:String, value:Dynamic):Bool { + var killMe:Array = variable.split('.'); + if (killMe.length > 1) { + setVarInArray(getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1], value); + } else { + setVarInArray(getInstance(), variable, value); + } + return true; + }); + + LuaUtils.addFunction(lua, "getPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic):Dynamic { + var shitMyPants:Array = obj.split('.'); + var realObject:Dynamic = Reflect.getProperty(getInstance(), obj); + if (shitMyPants.length > 1) { + realObject = getPropertyLoopThingWhatever(shitMyPants, true, false); + } + + if (Std.isOfType(realObject, FlxTypedGroup)) { + var result:Dynamic = getGroupStuff(realObject.members[index], variable); + return result; + } + + var leArray:Dynamic = realObject[index]; + if (leArray != null) { + var result:Dynamic = null; + if (Type.typeof(variable) == ValueType.TInt) { + result = leArray[variable]; + } else { + result = getGroupStuff(leArray, variable); + } + return result; + } + luaTrace("getPropertyFromGroup: Object #" + index + " from group: " + obj + " doesn't exist!", false, false, FlxColor.RED); + return null; + }); + + LuaUtils.addFunction(lua, "setPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic, value:Dynamic):Void { + var shitMyPants:Array = obj.split('.'); + var realObject:Dynamic = Reflect.getProperty(getInstance(), obj); + if (shitMyPants.length > 1) { + realObject = getPropertyLoopThingWhatever(shitMyPants, true, false); + } + + if (Std.isOfType(realObject, FlxTypedGroup)) { + setGroupStuff(realObject.members[index], variable, value); + return; + } + + var leArray:Dynamic = realObject[index]; + if (leArray != null) { + if (Type.typeof(variable) == ValueType.TInt) { + leArray[variable] = value; + return; + } + setGroupStuff(leArray, variable, value); + } + }); + + LuaUtils.addFunction(lua, "removeFromGroup", function(obj:String, index:Int, dontDestroy:Bool = false):Void { + if (Std.isOfType(Reflect.getProperty(getInstance(), obj), FlxTypedGroup)) { + var sex = Reflect.getProperty(getInstance(), obj).members[index]; + if (!dontDestroy) { + sex.kill(); + } + Reflect.getProperty(getInstance(), obj).remove(sex, true); + if (!dontDestroy) { + sex.destroy(); + } + return; + } + Reflect.getProperty(getInstance(), obj).remove(Reflect.getProperty(getInstance(), obj)[index]); + }); + + LuaUtils.addFunction(lua, "getPropertyFromClass", function(classVar:String, variable:String):Dynamic { + var killMe:Array = variable.split('.'); + if (killMe.length > 1) { + var coverMeInPiss:Dynamic = getVarInArray(Type.resolveClass(classVar), killMe[0]); + for (i in 1...killMe.length-1) { + coverMeInPiss = getVarInArray(coverMeInPiss, killMe[i]); + } + return getVarInArray(coverMeInPiss, killMe[killMe.length-1]); + } + return getVarInArray(Type.resolveClass(classVar), variable); + }); + + LuaUtils.addFunction(lua, "setPropertyFromClass", function(classVar:String, variable:String, value:Dynamic):Bool { + var killMe:Array = variable.split('.'); + if (killMe.length > 1) { + var coverMeInPiss:Dynamic = getVarInArray(Type.resolveClass(classVar), killMe[0]); + for (i in 1...killMe.length-1) { + coverMeInPiss = getVarInArray(coverMeInPiss, killMe[i]); + } + setVarInArray(coverMeInPiss, killMe[killMe.length-1], value); + return true; + } + setVarInArray(Type.resolveClass(classVar), variable, value); + return true; + }); + + final luaFuncs:Array = [ + LuaColor, + LuaControls, + LuaFileManager, + LuaObject, + LuaPlayState, + LuaSave, + LuaSound, + LuaSprites, + LuaText, + LuaTimer, + LuaTween, + LuaRandom, + LuaShader, + LuaDeprecated + ]; + + for (luaFunc in luaFuncs) { + if (luaFunc != null && Reflect.isFunction(luaFunc.init)) { + luaFunc.init(this); + } + } + + LuaUtils.addFunction(lua, "debugPrint", function(text1:Dynamic = '', text2:Dynamic = '', text3:Dynamic = '', text4:Dynamic = '', text5:Dynamic = ''):Void { + text1 = (text1 == null) ? '' : text1; + text2 = (text2 == null) ? '' : text2; + text3 = (text3 == null) ? '' : text3; + text4 = (text4 == null) ? '' : text4; + text5 = (text5 == null) ? '' : text5; + luaTrace('' + text1 + text2 + text3 + text4 + text5, true, false); + }); + + LuaUtils.addFunction(lua, "close", function():Bool { + closed = true; + return closed; + }); + + LuaUtils.addFunction(lua, "changePresence", function(details:String, state:Null, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float):Bool { + #if DISCORD_ALLOWED + DiscordClient.changePresence(details, state, smallImageKey, hasStartTimestamp, endTimestamp); + #else + luaTrace('changePresence: Discord presence is not allowed in this platform.', false, false, FlxColor.RED); + #end + return true; + }); + + // String functions + LuaUtils.addFunction(lua, "stringStartsWith", function(str:String, start:String):Bool { + return str.startsWith(start); + }); + + LuaUtils.addFunction(lua, "stringEndsWith", function(str:String, end:String):Bool { + return str.endsWith(end); + }); + + LuaUtils.addFunction(lua, "stringSplit", function(str:String, split:String):Array { + return str.split(split); + }); + + LuaUtils.addFunction(lua, "stringTrim", function(str:String):String { + return str.trim(); + }); + + LuaUtils.addFunction(lua, "directoryFileList", function(folder:String):Array { + var list:Array = []; + #if sys + if (FileSystem.exists(folder)) { + for (file in FileSystem.readDirectory(folder)) { + if (!list.contains(file)) { + list.push(file); + } + } + } + #end + return list; + }); + + #if HSCRIPT_ALLOWED + LuaUtils.addFunction(lua, "setOnHScript", PlayState.instance.setOnHScript); + + LuaUtils.addFunction(lua, "callOnHScript", function(funcName:String, ?args:Array = null, ?ignoreStops:Bool = false, ?ignoreSelf:Bool = true, ?excludeScripts:Array = null, ?excludeValues:Array = null):Dynamic { + excludeScripts = (excludeScripts == null) ? [] : excludeScripts; + if (ignoreSelf && !excludeScripts.contains(scriptName)) { + excludeScripts.push(scriptName); + } + return PlayState.instance.callOnHScript(funcName, args, ignoreStops, excludeScripts, excludeValues); + }); + #end + + if (useCustomFunctions && customFunctions != null) { + for (tag => func in customFunctions) { + LuaUtils.addFunction(lua, Std.string(tag), func); + } + } + #end + } + + public static function isOfTypes(value:Any, types:Array):Bool { + for (type in types) { + if (Std.isOfType(value, type)) return true; + } + return false; + } + + public function addLocalCallback(name:String, func:Dynamic):Void { + callbacks.set(name, func); + #if LUA_ALLOWED + LuaUtils.addFunction(lua, name, func); + #end + } + + #if HSCRIPT_ALLOWED + public function initHaxeModule():Void { + if (hscript == null) { + trace('initializing haxe interp for: $scriptName'); + hscript = new FunkinHScript("", PlayState.instance, true); + hscript.scriptName = scriptName; + } + } + #end + + public static function setVarInArray(instance:Dynamic, variable:String, value:Dynamic):Bool { + if (variable == null || variable.length == 0) { + #if LUA_ALLOWED + if (getBool('luaDebugMode')) { + trace('ERROR: setVarInArray - variable is null or empty (instance: $instance)'); + } + #end + return false; + } + + var shit:Array = variable.split('['); + if (shit.length > 1) { + var blah:Dynamic = null; + var propName:String = shit[0]; + var instanceInfo:String = getObjectInfo(instance); + + if (PlayState.instance != null && PlayState.instance.variables.exists(propName)) { + blah = PlayState.instance.variables.get(propName); + } else if (instance != null && Reflect.hasField(instance, propName)) { + blah = Reflect.getProperty(instance, propName); + } + + if (blah == null) { + #if LUA_ALLOWED + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('WARNING: Property not found: "${shit[0]}" (object: $instanceInfo, full path: $variable)'); + #end + return false; + } + + for (i in 1...shit.length) { + var arrayPart:String = shit[i]; + if (arrayPart.endsWith(']')) arrayPart = arrayPart.substr(0, arrayPart.length - 1); + + if (arrayPart.length == 0) continue; + + var key:Dynamic = try Std.parseInt(arrayPart) catch(e:Dynamic) arrayPart; + + if (i == shit.length - 1) { + if (Std.isOfType(blah, Array) || Reflect.hasField(blah, 'set') || Reflect.isObject(blah)) { + try { + Reflect.setProperty(blah, key, value); + return true; + } catch (e:Dynamic) { + var blahInfo:String = getObjectInfo(blah); + #if LUA_ALLOWED + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('ERROR: Failed to set property "$variable" on object: $blahInfo - ${e.message}'); + #end + return false; + } + } + var blahInfo:String = getObjectInfo(blah); + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('WARNING: Cannot set index on non-array: "${shit[0]}" (object: $blahInfo, type: ${Type.getClassName(Type.getClass(blah))})'); + return false; + } else { + if (blah != null && (Reflect.isObject(blah) || Std.isOfType(blah, Array))) { + try { + blah = Reflect.getProperty(blah, key); + } catch (e:Dynamic) { + var blahInfo:String = getObjectInfo(blah); + #if LUA_ALLOWED + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('ERROR: Failed to access index "$key" on object: $blahInfo while setting "$variable" - ${e.message}'); + #end + return false; + } + } else { + var blahInfo:String = getObjectInfo(blah); + #if LUA_ALLOWED + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('WARNING: Cannot access index on non-container: ${shit.slice(0, i).join('[')} (object: $blahInfo, type: ${Type.getClassName(Type.getClass(blah))})'); + #end + return false; + } + } + } + return false; + } + + try { + var instanceInfo:String = getObjectInfo(instance); + + if (PlayState.instance != null && PlayState.instance.variables.exists(variable)) { + PlayState.instance.variables.set(variable, value); + return true; + } + + if (instance != null) { + Reflect.setProperty(instance, variable, value); + return true; + } + + #if LUA_ALLOWED + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('WARNING: Instance is null for property: "$variable" (called from Lua script, object info: $instanceInfo)'); + #end + return false; + } catch (e:Dynamic) { + var instanceInfo:String = getObjectInfo(instance); + #if LUA_ALLOWED + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('ERROR: Failed to set property "$variable" on object: $instanceInfo - ${e.message}'); + #end + return false; + } + } + + public static function getVarInArray(instance:Dynamic, variable:String):Any { + var shit:Array = variable.split('['); + if (shit.length > 1) { + var blah:Dynamic = null; + if (PlayState.instance.variables.exists(shit[0])) { + var retVal:Dynamic = PlayState.instance.variables.get(shit[0]); + if (retVal != null) { + blah = retVal; + } + } else { + blah = Reflect.getProperty(instance, shit[0]); + } + + for (i in 1...shit.length) { + var leNumStr:Dynamic = shit[i].substr(0, shit[i].length - 1); + var leNum:Dynamic = try Std.parseInt(leNumStr) catch (_) null; + leNum = (leNum == null) ? leNumStr : leNum; + + blah = Reflect.getProperty(blah, leNum); + } + return blah; + } + + if (PlayState.instance.variables.exists(variable)) { + var retVal:Dynamic = PlayState.instance.variables.get(variable); + if (retVal != null) { + return retVal; + } + } + + return Reflect.getProperty(instance, variable); + } + + private static function getObjectInfo(obj:Dynamic):String { + if (obj == null) return "null"; + + var className:String = Type.getClassName(Type.getClass(obj)); + var result:String = 'type: $className'; + + if (Std.isOfType(obj, FlxSprite)) { + var sprite:FlxSprite = cast obj; + result += ', x: ${sprite.x}, y: ${sprite.y}, visible: ${sprite.visible}'; + } else if (Std.isOfType(obj, Character)) { + var char:Character = cast obj; + result += ', curCharacter: ${char.curCharacter}, x: ${char.x}, y: ${char.y}'; + } else if (Std.isOfType(obj, FlxText)) { + var text:FlxText = cast obj; + result += ', text: "${text.text}", x: ${text.x}, y: ${text.y}'; + } else if (Std.isOfType(obj, FlxCamera)) { + var cam:FlxCamera = cast obj; + result += ', x: ${cam.scroll.x}, y: ${cam.scroll.y}, zoom: ${cam.zoom}'; + } else if (Std.isOfType(obj, String)) { + result += ', value: "$obj"'; + } else if (Std.isOfType(obj, Int) || Std.isOfType(obj, Float)) { + result += ', value: $obj'; + } else if (Std.isOfType(obj, Bool)) { + result += ', value: $obj'; + } + + return result; + } + + function getGroupStuff(leArray:Dynamic, variable:String):Dynamic { + var killMe:Array = variable.split('.'); + if (killMe.length > 1) { + var coverMeInPiss:Dynamic = Reflect.getProperty(leArray, killMe[0]); + for (i in 1...killMe.length-1) { + coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); + } + return Reflect.getProperty(coverMeInPiss, killMe[killMe.length-1]); + } + return Reflect.getProperty(leArray, variable); + } + + function setGroupStuff(leArray:Dynamic, variable:String, value:Dynamic):Void { + var killMe:Array = variable.split('.'); + if (killMe.length > 1) { + var coverMeInPiss:Dynamic = Reflect.getProperty(leArray, killMe[0]); + for (i in 1...killMe.length-1) { + coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); + } + Reflect.setProperty(coverMeInPiss, killMe[killMe.length-1], value); + return; + } + Reflect.setProperty(leArray, variable, value); + } + + public static function cameraFromString(cam:String):FlxCamera { + switch (cam.toLowerCase()) { + case 'camhud' | 'hud': return PlayState.instance.camHUD; + case 'camother' | 'other': return PlayState.instance.camOther; + } + return PlayState.instance.camGame; + } + + public static function luaTrace(text:String, ignoreCheck:Bool = false, deprecated:Bool = false, color:FlxColor = FlxColor.WHITE):Void { + #if LUA_ALLOWED + if (ignoreCheck || getBool('luaDebugMode')) { + if (deprecated && !getBool('luaDeprecatedWarnings')) { + return; + } + PlayState.instance.addTextToDebug(text, color); + trace(text); + } + #end + } + + function getErrorMessage(status:Int):String { + #if LUA_ALLOWED + var v:String = Lua.tostring(lua, -1); + Lua.pop(lua, 1); + + if (v != null) v = v.trim(); + if (v == null || v == "") { + if (status == 2) return "Runtime Error"; // LUA_ERRRUN = 2 + else if (status == 4) return "Memory Allocation Error"; // LUA_ERRMEM = 4 + else if (status == 5) return "Critical Error"; // LUA_ERRERR = 5 + return "Unknown Error"; + } + + return v; + #end + return null; + } + + function typeToString(type:Int):String { + #if LUA_ALLOWED + if (type == 1) return "boolean"; // LUA_TBOOLEAN = 1 + else if (type == 3) return "number"; // LUA_TNUMBER = 3 + else if (type == 4) return "string"; // LUA_TSTRING = 4 + else if (type == 5) return "table"; // LUA_TTABLE = 5 + else if (type == 6) return "function"; // LUA_TFUNCTION = 6 + + if (type <= 0) return "nil"; // LUA_TNIL = 0 + #end + return "unknown"; + } + + public function call(func:String, args:Array):Dynamic { + #if LUA_ALLOWED + if (closed || lua == null) return Function_Continue; + + lastCalledFunction = func; + lastCalledScript = this; + + try { + Lua.getglobal(lua, func); + var type:Int = Lua.type(lua, -1); + + if (type != 6) { // 6 = LUA_TFUNCTION + Lua.pop(lua, 1); + + if (func != 'onCreate' && func != 'onUpdate' && func != 'onStepHit' && + func != 'onBeatHit' && func != 'eventEarlyTrigger') { + trace('Lua function "$func" not found'); + } + return Function_Continue; + } + + for (arg in args) { + LuaConverter.toLua(lua, arg); + } + + var status:Int = Lua.pcall(lua, args.length, 1, 0); + + if (status != Lua.OK) { + var error:String = Lua.tostring(lua, -1); + if (error != 'attempt to call a nil value' && !error.contains('onCreate') && !error.contains('eventEarlyTrigger')) { + luaTrace('ERROR ($func): $error', false, false, FlxColor.RED); + } + Lua.pop(lua, 1); + return Function_Continue; + } + + var result:Dynamic = LuaConverter.fromLua(lua, -1); + Lua.pop(lua, 1); + + if (result == null) result = Function_Continue; + return result; + } catch (e:Dynamic) { + trace('Error calling Lua function $func: $e'); + } + #end + return Function_Continue; + } + + public static function getPropertyLoopThingWhatever(killMe:Array, ?checkForTextsToo:Bool = true, ?getProperty:Bool = true):Dynamic { + if (killMe.length == 0) { + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('ERROR: getPropertyLoopThingWhatever - empty path array'); + return null; + } + + var coverMeInPiss:Dynamic = getObjectDirectly(killMe[0], checkForTextsToo); + var end:Int = killMe.length; + if (getProperty) end = killMe.length - 1; + + for (i in 1...end) { + if (coverMeInPiss == null) { + #if LUA_ALLOWED if (getBool('luaDebugPropertyTraces')) #end trace('WARNING: getPropertyLoopThingWhatever - null object at path: ${killMe.slice(0, i).join(".")} (full path: ${killMe.join(".")})'); + return null; + } + coverMeInPiss = getVarInArray(coverMeInPiss, killMe[i]); + } + return coverMeInPiss; + } + + public static function getObjectDirectly(objectName:String, ?checkForTextsToo:Bool = true):Dynamic { + var coverMeInPiss:Dynamic = PlayState.instance.getLuaObject(objectName, checkForTextsToo); + if (coverMeInPiss == null) { + coverMeInPiss = getVarInArray(getInstance(), objectName); + } + return coverMeInPiss; + } + + public function set(variable:String, data:Dynamic):Void { + #if LUA_ALLOWED + if (lua == null) return; + LuaUtils.setVariable(lua, variable, data); + #end + } + + #if LUA_ALLOWED + static function getBool(variable:String):Bool { + if (lastCalledScript == null) return false; + + var lua:cpp.RawPointer = lastCalledScript.lua; + if (lua == null) return false; + + var result:Dynamic = LuaUtils.getVariable(lua, variable); + return (result == true || result == 'true'); + } + #end + + public function stop():Void { + #if LUA_ALLOWED + if (lua == null) return; + + LuaUtils.cleanupStateFunctions(lua); + Lua.close(lua); + lua = null; + #end + } + + public static inline function getInstance():Dynamic { + return PlayState.instance.isDead ? GameOverSubstate.instance : PlayState.instance; + } + + public function safeCall(func:String, args:Array):Dynamic { #if LUA_ALLOWED - if(ignoreCheck || getBool('luaDebugMode')) { - if(deprecated && !getBool('luaDeprecatedWarnings')) { - return; - } - PlayState.instance.addTextToDebug(text, color); - trace(text); - } - #end - } - - function getErrorMessage(status:Int):String { - #if LUA_ALLOWED - var v:String = Lua.tostring(lua, -1); - Lua.pop(lua, 1); - - if (v != null) v = v.trim(); - if (v == null || v == "") { - switch(status) { - case Lua.LUA_ERRRUN: return "Runtime Error"; - case Lua.LUA_ERRMEM: return "Memory Allocation Error"; - case Lua.LUA_ERRERR: return "Critical Error"; - } - return "Unknown Error"; - } - - return v; - #end - return null; - } - - var lastCalledFunction:String = ''; - public static var lastCalledScript:FunkinLua = null; - public function call(func:String, args:Array):Dynamic { - #if LUA_ALLOWED - if(closed) return Function_Continue; - - lastCalledFunction = func; - lastCalledScript = this; - try { - if(lua == null) return Function_Continue; - - Lua.getglobal(lua, func); - var type:Int = Lua.type(lua, -1); - - if (type != Lua.LUA_TFUNCTION) { - if (type > Lua.LUA_TNIL) - luaTrace("ERROR (" + func + "): attempt to call a " + typeToString(type) + " value", false, false, FlxColor.RED); - - Lua.pop(lua, 1); - return Function_Continue; - } - - for (arg in args) Convert.toLua(lua, arg); - var status:Int = Lua.pcall(lua, args.length, 1, 0); - - // Checks if it's not successful, then show a error. - if (status != Lua.LUA_OK) { - var error:String = getErrorMessage(status); - luaTrace("ERROR (" + func + "): " + error, false, false, FlxColor.RED); - return Function_Continue; - } - - // If successful, pass and then return the result. - var result:Dynamic = cast Convert.fromLua(lua, -1); - result ??= Function_Continue; - - Lua.pop(lua, 1); - return result; - } - catch (e:Dynamic) { - trace(e); + if (functionExists(func)) { + return call(func, args); } #end return Function_Continue; } - static function addAnimByIndices(obj:String, name:String, prefix:String, indices:String, framerate:Int = 24, loop:Bool = false) - { - var strIndices:Array = indices.trim().split(','); - var die:Array = []; - for (i in 0...strIndices.length) { - die.push(Std.parseInt(strIndices[i])); - } - - if(PlayState.instance.getLuaObject(obj, false)!=null) { - var pussy:FlxSprite = PlayState.instance.getLuaObject(obj, false); - pussy.animation.addByIndices(name, prefix, die, '', framerate, loop); - if(pussy.animation.curAnim == null) { - pussy.animation.play(name, true); - } - return true; - } - - var pussy:FlxSprite = Reflect.getProperty(getInstance(), obj); - if(pussy != null) { - pussy.animation.addByIndices(name, prefix, die, '', framerate, loop); - if(pussy.animation.curAnim == null) { - pussy.animation.play(name, true); - } - return true; - } - return false; - } - - public static function getPropertyLoopThingWhatever(killMe:Array, ?checkForTextsToo:Bool = true, ?getProperty:Bool=true):Dynamic - { - var coverMeInPiss:Dynamic = getObjectDirectly(killMe[0], checkForTextsToo); - var end = killMe.length; - if(getProperty)end=killMe.length-1; - - for (i in 1...end) { - coverMeInPiss = getVarInArray(coverMeInPiss, killMe[i]); - } - return coverMeInPiss; - } - - public static function getObjectDirectly(objectName:String, ?checkForTextsToo:Bool = true):Dynamic - { - var coverMeInPiss:Dynamic = PlayState.instance.getLuaObject(objectName, checkForTextsToo); - if(coverMeInPiss==null) - coverMeInPiss = getVarInArray(getInstance(), objectName); - - return coverMeInPiss; - } - - function typeToString(type:Int):String { - #if LUA_ALLOWED - switch(type) { - case Lua.LUA_TBOOLEAN: return "boolean"; - case Lua.LUA_TNUMBER: return "number"; - case Lua.LUA_TSTRING: return "string"; - case Lua.LUA_TTABLE: return "table"; - case Lua.LUA_TFUNCTION: return "function"; - } - if (type <= Lua.LUA_TNIL) return "nil"; - #end - return "unknown"; - } - - public function set(variable:String, data:Dynamic) { - #if LUA_ALLOWED - if(lua == null) { - return; - } - - Convert.toLua(lua, data); - Lua.setglobal(lua, variable); - #end - } - - #if LUA_ALLOWED - public function getBool(variable:String) { - if(lastCalledScript == null) return false; - - var lua:State = lastCalledScript.lua; - if(lua == null) return false; - - var result:String = null; - Lua.getglobal(lua, variable); - result = Convert.fromLua(lua, -1); - Lua.pop(lua, 1); - - if(result == null) { - return false; - } - return (result == 'true'); - } - #end - - public function stop() { - #if LUA_ALLOWED - if(lua == null) { - return; - } - - Lua.close(lua); - lua = null; - #end - } - - public static inline function getInstance() - { - return PlayState.instance.isDead ? GameOverSubstate.instance : PlayState.instance; - } -} - -class ModchartSprite extends FlxSprite -{ - public var wasAdded:Bool = false; - public var animOffsets:Map> = new Map>(); - //public var isInFront:Bool = false; - - public function new(?x:Float = 0, ?y:Float = 0) - { - super(x, y); - antialiasing = ClientPrefs.globalAntialiasing; - } + public function functionExists(func:String):Bool { + #if LUA_ALLOWED + if (lua == null) return false; + + Lua.getglobal(lua, func); + var exists:Bool = Lua.type(lua, -1) == 6; // 6 = LUA_TFUNCTION + Lua.pop(lua, 1); + return exists; + #else + return false; + #end + } } -class ModchartBackdrop extends FlxBackdrop { - public var wasAdded:Bool = false; - public var animOffsets:Map> = new Map>(); - - public function new(graphic:Dynamic, ?x:Float = 0, ?y:Float = 0, repeatX:Bool = true, repeatY:Bool = true) { - super(x, y); - - if (graphic != null) { - loadGraphic(graphic, repeatX, repeatY); - } +class ModchartSprite extends FlxSprite { + public var wasAdded:Bool = false; + public var animOffsets:Map> = new Map>(); - antialiasing = ClientPrefs.globalAntialiasing; - } + public function new(?x:Float = 0, ?y:Float = 0) { + super(x, y); + antialiasing = ClientPrefs.globalAntialiasing; + } } #if flixel_animate @@ -3666,238 +1184,131 @@ class ModchartAnimateSprite extends FlxAnimate } #end -class ModchartText extends FlxText -{ - public var wasAdded:Bool = false; - public function new(x:Float, y:Float, text:String, width:Float) - { - super(x, y, width, text, 16); - setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - cameras = [PlayState.instance.camHUD]; - scrollFactor.set(); - borderSize = 2; - } -} +class ModchartBackdrop extends FlxBackdrop { + public var wasAdded:Bool = false; + public var animOffsets:Map> = new Map>(); -class DebugLuaText extends FlxText -{ - private var disableTime:Float = 6; - public var readyToRemove:Bool = false; - public var parentGroup:FlxTypedGroup; + public function new(graphic:Dynamic, ?x:Float = 0, ?y:Float = 0, repeatX:Bool = true, repeatY:Bool = true) { + super(x, y); - public function new(text:String, parentGroup:FlxTypedGroup, color:FlxColor) { - this.parentGroup = parentGroup; - super(10, 10, FlxG.width - 20, text, 16); - - setFormat(Paths.font("vcr.ttf"), 20, color, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - scrollFactor.set(); - borderSize = 1; - - wordWrap = true; - - updateHitbox(); - } + if (graphic != null) { + loadGraphic(graphic, repeatX, repeatY); + } - override function update(elapsed:Float) { - super.update(elapsed); - disableTime -= elapsed; - - if(disableTime < 0) disableTime = 0; - if(disableTime < 1) alpha = disableTime; - - if(disableTime <= 0 && alpha <= 0) { - readyToRemove = true; - } - } + antialiasing = ClientPrefs.globalAntialiasing; + } } -class DebugTextGroup extends FlxTypedGroup -{ - private var totalHeight:Float = 0; - - public function new() { - super(); - } - - public function addText(text:DebugLuaText) { - insert(0, text); - recalculatePositions(); - } - - public function removeText(text:DebugLuaText) { - remove(text); - recalculatePositions(); - } - - override function update(elapsed:Float) { - super.update(elapsed); - - var i:Int = 0; - while (i < length) { - var text = members[i]; - if (text != null && text.readyToRemove) { - removeText(text); - text.destroy(); - } else { - i++; - } - } - } - - public function recalculatePositions() { - totalHeight = 10; - - forEachAlive(function(text:DebugLuaText) { - text.y = totalHeight; - totalHeight += text.height + 5; - }); - } +class ModchartText extends FlxText { + public var wasAdded:Bool = false; + + public function new(x:Float, y:Float, text:String, width:Float) { + super(x, y, width, text, 16); + setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + cameras = [PlayState.instance.camHUD]; + scrollFactor.set(); + borderSize = 2; + } } -class CustomSubstate extends MusicBeatSubstate -{ - public static var name:String = 'unnamed'; - public static var instance:CustomSubstate; - - override function create() - { - instance = this; - - PlayState.instance.callOnLuas('onCustomSubstateCreate', [name]); - super.create(); - PlayState.instance.callOnLuas('onCustomSubstateCreatePost', [name]); - } - - public function new(name:String) - { - CustomSubstate.name = name; - super(); - cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; - } - - override function update(elapsed:Float) - { - PlayState.instance.callOnLuas('onCustomSubstateUpdate', [name, elapsed]); - super.update(elapsed); - PlayState.instance.callOnLuas('onCustomSubstateUpdatePost', [name, elapsed]); - } - - override function destroy() - { - PlayState.instance.callOnLuas('onCustomSubstateDestroy', [name]); - super.destroy(); - } +class DebugLuaText extends FlxText { + private var disableTime:Float = 6; + public var readyToRemove:Bool = false; + public var parentGroup:FlxTypedGroup; + + public function new(text:String, parentGroup:FlxTypedGroup, color:FlxColor) { + this.parentGroup = parentGroup; + super(10, 10, FlxG.width - 20, text, 16); + + setFormat(Paths.font("vcr.ttf"), 20, color, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + scrollFactor.set(); + borderSize = 1; + + wordWrap = true; + + updateHitbox(); + } + + override function update(elapsed:Float) { + super.update(elapsed); + disableTime -= elapsed; + + if (disableTime < 0) disableTime = 0; + if (disableTime < 1) alpha = disableTime; + + if (disableTime <= 0 && alpha <= 0) { + readyToRemove = true; + } + } } -#if HSCRIPT_ALLOWED -class HScript -{ - public static var parser:HxParser; - public var interp:Interp; - public var parentLua:FunkinLua; - - public var variables(get, never):Map; - public function get_variables() return interp.variables; - - public var defaultClasses:Map = [ - 'FlxG' => flixel.FlxG, - 'FlxCamera' => flixel.FlxCamera, - 'FlxObject' => flixel.FlxObject, - 'FlxSprite' => flixel.FlxSprite, - 'FlxTimer' => flixel.util.FlxTimer, - 'FlxTween' => flixel.tweens.FlxTween, - 'FlxEase' => flixel.tweens.FlxEase, - "FlxMath" => flixel.math.FlxMath, - "FlxGroup" => flixel.group.FlxGroup, - "FlxTypedGroup" => flixel.group.FlxGroup.FlxTypedGroup, - "FlxSpriteGroup" => flixel.group.FlxSpriteGroup, - "FlxBackdrop" => flixel.addons.display.FlxBackdrop, - "FlxTiledSprite" => flixel.addons.display.FlxTiledSprite, - - 'PlayState' => PlayState, - 'game' => PlayState.instance, - - 'Paths' => Paths, - 'Conductor' => game.backend.Conductor, - 'ClientPrefs' => game.backend.ClientPrefs, - "BGSprite" => BGSprite, - - #if VIDEOS_ALLOWED - "FunkinVideoSprite" => game.objects.FunkinVideoSprite, - #end - - #if flixel_animate "FlxAnimate" => FlxAnimate, #end - - 'Character' => Character, - 'Alphabet' => game.objects.Alphabet, - 'CustomSubstate' => CustomSubstate, - #if (!flash && sys) 'FlxRuntimeShader' => flixel.addons.display.FlxRuntimeShader, #end - 'ShaderFilter' => openfl.filters.ShaderFilter, - 'StringTools' => StringTools, - 'Lambda' => Lambda - ]; - - public var defaultFunctions:Map = [ - 'setVar' => function(name:String, value:Dynamic) { - PlayState.instance.variables.set(name, value); - }, - 'getVar' => function(name:String) { - var result:Dynamic = null; - if(PlayState.instance.variables.exists(name)) result = PlayState.instance.variables.get(name); - return result; - }, - 'removeVar' => function(name:String) { - if(PlayState.instance.variables.exists(name)) - { - PlayState.instance.variables.remove(name); - return true; - } - return false; - }, - 'add' => function(basic:flixel.FlxBasic, ?frontOfChars:Bool = false) { - if (frontOfChars) { - PlayState.instance.add(basic); - return; - } - - var position:Int = PlayState.instance.members.indexOf(PlayState.instance.gfGroup); - if(PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup) < position) position = PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup); - else if(PlayState.instance.members.indexOf(PlayState.instance.dadGroup) < position) position = PlayState.instance.members.indexOf(PlayState.instance.dadGroup); - PlayState.instance.insert(position, basic); - }, - 'insert' => PlayState.instance.insert, - 'remove' => PlayState.instance.remove, - 'addBehindGF' => PlayState.instance.addBehindGF, - 'addBehindDad' => PlayState.instance.addBehindDad, - 'addBehindBF' => PlayState.instance.addBehindBF, - 'buildTarget' => CoolUtil.getBuildTarget() - ]; - - public function new(?parent:FunkinLua) { - initParser(); - interp = new Interp(); - - for(key => value in defaultClasses) interp.variables.set(key, value); - for(key => func in defaultFunctions) interp.variables.set(key, func); - - if(FlxG.state is PlayState) interp.variables.set('game', PlayState.instance); - if(parent != null) { - this.parentLua = parent; - interp.variables.set('this', this); - interp.variables.set('parentLua', parent); - interp.variables.set('scriptName', parent.scriptName); - } - } - - function initParser() { - parser = new rulescript.parsers.HxParser(); - parser.preprocesorValues = CoolUtil.getHScriptPreprocessors(); - } - - public function execute(codeToRun:String):Dynamic { - HScript.parser.parser.line = 1; - HScript.parser.parser.allowTypes = true; - return interp.execute(HScript.parser.parser.parseString(codeToRun, 'HScript', 0)); - } +class DebugTextGroup extends FlxTypedGroup { + private var totalHeight:Float = 0; + + public function new() { + super(); + } + + public function addText(text:DebugLuaText) { + insert(0, text); + recalculatePositions(); + } + + public function removeText(text:DebugLuaText) { + remove(text); + recalculatePositions(); + } + + override function update(elapsed:Float) { + super.update(elapsed); + + var i:Int = 0; + while (i < length) { + var text = members[i]; + if (text != null && text.readyToRemove) { + removeText(text); + text.destroy(); + } else { + i++; + } + } + } + + public function recalculatePositions() { + totalHeight = 10; + + forEachAlive(function(text:DebugLuaText) { + text.y = totalHeight; + totalHeight += text.height + 5; + }); + } } -#end + +class CustomSubstate extends MusicBeatSubstate { + public static var name:String = 'unnamed'; + public static var instance:CustomSubstate; + + override function create() { + instance = this; + + PlayState.instance?.callOnLuas('onCustomSubstateCreate', [name]); + super.create(); + PlayState.instance?.callOnLuas('onCustomSubstateCreatePost', [name]); + } + + public function new(name:String) { + CustomSubstate.name = name; + super(); + } + + override function update(elapsed:Float) { + PlayState.instance?.callOnLuas('onCustomSubstateUpdate', [name, elapsed]); + super.update(elapsed); + PlayState.instance?.callOnLuas('onCustomSubstateUpdatePost', [name, elapsed]); + } + + override function destroy() { + PlayState.instance?.callOnLuas('onCustomSubstateDestroy', [name]); + super.destroy(); + } +} \ No newline at end of file diff --git a/source/game/scripting/FunkinRScript.hx b/source/game/scripting/FunkinRScript.hx deleted file mode 100644 index 97b3f30..0000000 --- a/source/game/scripting/FunkinRScript.hx +++ /dev/null @@ -1,386 +0,0 @@ -package game.scripting; - -#if sys -import sys.io.File; -#end - -import haxe.ds.StringMap; -import haxe.io.Path; - -import rulescript.*; -import rulescript.parsers.*; -import rulescript.RuleScript; - -import rulescript.interps.RuleScriptInterp; - -import game.scripting.HScriptClassManager.ScriptClassRef; - -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.group.FlxGroup; - -using StringTools; -using Lambda; - -class FunkinRScript { - static final PRESET_VARS:Map = [ - // Flixel Classes - "FlxG" => flixel.FlxG, - "FlxSprite" => flixel.FlxSprite, - "FlxSpriteUtil" => flixel.util.FlxSpriteUtil, - "FlxTimer" => flixel.util.FlxTimer, - "FlxTween" => flixel.tweens.FlxTween, - "FlxEase" => flixel.tweens.FlxEase, - "FlxText" => flixel.text.FlxText, - - #if VIDEOS_ALLOWED - "FunkinVideoSprite" => game.objects.FunkinVideoSprite, - #end - - #if flxsoundfilters - "FlxFilteredSound" => FlxFilteredSound, - #end - - #if flxgif - "FlxGifSprite" => FlxGifSprite, - "FlxGifBackdrop" => FlxGifBackdrop, - #end - - "Paths" => game.Paths, - "Character" => game.objects.Character, - "CoolUtil" => game.backend.utils.CoolUtil, - "MusicBeatState" => MusicBeatState, - "Conductor" => game.backend.Conductor, - "ClientPrefs" => game.backend.ClientPrefs, - "PlayState" => game.PlayState, - "BGSprite" => game.objects.BGSprite, - "FunkinRScript" => FunkinRScript, - "FunkinLua" => FunkinLua, - - 'StringMap' => haxe.ds.StringMap, - 'IntMap' => haxe.ds.IntMap, - 'ObjectMap' => haxe.ds.ObjectMap, - ]; - - static final ABSTRACT_IMPORTS:Array = [ - "flixel.util.FlxColor", - "flixel.input.keyboard.FlxKey", - "haxe.ds.Map", - #if flxgif - "flxgif.FlxGifAsset", - #end - "openfl.display.BlendMode" - ]; - - public var scriptType:String = "N/A"; //yeah - public var scriptName:String; - public var active(default, null):Bool = true; - - private var rule:RuleScript; - private var parentInstance:Dynamic; - private var callbacks:Map> = new Map(); - private var importedPackages:Map = new Map(); - - public static function fromFile(file:String, ?instance:Dynamic, skipCreate:Bool = false):Null { - return switch Path.extension(file).toLowerCase() { - case "hx": new FunkinRScript(file, instance, skipCreate); - case _: null; - } - } - - public function new(path:String, parentInstance:Dynamic = null, skipCreate:Bool = false) { - this.parentInstance = parentInstance; - scriptName = path; - - rule = new RuleScript(new RuleScriptInterpEx(this)); - rule.scriptName = path; - rule.errorHandler = onError; - - try { - var content = loadScriptContent(path); - execute(content, skipCreate); - } catch (e:haxe.Exception) { - trace('Failed to load script $path: ${e.message}'); - active = false; - } - } - - private function loadScriptContent(path:String):String { - #if sys - return File.getContent(path); - #else - var resourceName = path.replace("/", "_").replace(".", "_").replace(":", "_"); - var content = haxe.Resource.getString(resourceName); - if (content == null) { - throw 'HScript not found in resources: $path (resource name: $resourceName)'; - } - return content; - #end - } - - function execute(code:String, skipCreate:Bool) { - presetVariables(); - rule.tryExecute(code); - if (!skipCreate) call("onCreate"); - } - - function presetVariables() { - for (key => value in PRESET_VARS) - set(key, value); - - for (get in ABSTRACT_IMPORTS) - rulescript.types.Abstracts.resolveAbstract(get); - - if (parentInstance != null) - set("parent", parentInstance); - - if (FlxG.state is PlayState) - set("game", PlayState.instance); - - set("add", addObject); - set("remove", removeObject); - set("insert", insertObject); - set("getObject", getObject); - set("getAll", getAllObjects); - } - - public function addObject(object:Dynamic, ?group:String):Bool { - if (parentInstance == null) return false; - - try { - if (group != null && Reflect.hasField(parentInstance, group)) { - var targetGroup = Reflect.field(parentInstance, group); - if (Std.isOfType(targetGroup, FlxTypedGroup) || Std.isOfType(targetGroup, FlxGroup)) { - targetGroup.add(object); - return true; - } - } - - if (Reflect.hasField(parentInstance, "add")) { - Reflect.callMethod(parentInstance, Reflect.field(parentInstance, "add"), [object]); - return true; - } - } catch (e:Dynamic) { - trace('Error adding object: ${e.message}'); - } - return false; - } - - public function removeObject(object:Dynamic, ?group:String):Bool { - if (parentInstance == null) return false; - - try { - if (group != null && Reflect.hasField(parentInstance, group)) { - var targetGroup = Reflect.field(parentInstance, group); - if (Std.isOfType(targetGroup, FlxTypedGroup) || Std.isOfType(targetGroup, FlxGroup)) { - targetGroup.remove(object); - return true; - } - } - - if (Reflect.hasField(parentInstance, "remove")) { - Reflect.callMethod(parentInstance, Reflect.field(parentInstance, "remove"), [object]); - return true; - } - } catch (e:Dynamic) { - trace('Error removing object: ${e.message}'); - } - return false; - } - - public function insertObject(position:Int, object:Dynamic, ?group:String):Bool { - if (parentInstance == null) return false; - - try { - if (group != null && Reflect.hasField(parentInstance, group)) { - var targetGroup = Reflect.field(parentInstance, group); - if (Std.isOfType(targetGroup, FlxTypedGroup)) { - targetGroup.insert(position, object); - return true; - } - } - } catch (e:Dynamic) { - trace('Error inserting object: ${e.message}'); - } - return false; - } - - public function getObject(index:Int, group:String):Dynamic { - if (parentInstance == null) return null; - - try { - if (Reflect.hasField(parentInstance, group)) { - var targetGroup = Reflect.field(parentInstance, group); - if (Std.isOfType(targetGroup, FlxTypedGroup)) { - return targetGroup.members[index]; - } - } - } catch (e:Dynamic) { - trace('Error getting object: ${e.message}'); - } - return null; - } - - public function getAllObjects(group:String):Array { - if (parentInstance == null) return []; - - try { - if (Reflect.hasField(parentInstance, group)) { - var targetGroup = Reflect.field(parentInstance, group); - if (Std.isOfType(targetGroup, FlxTypedGroup)) { - return targetGroup.members; - } - } - } catch (e:Dynamic) { - trace('Error getting objects: ${e.message}'); - } - return []; - } - - public function resolveType(typeName:String):Dynamic { - var cl = Type.resolveClass(typeName); - if (cl != null) return cl; - - for (pkg in importedPackages.keys()) { - cl = Type.resolveClass(pkg + "." + typeName); - if (cl != null) return cl; - } - - return null; - } - - public function call(event:String, ?args:Array):Dynamic { - if (!active) return null; - - if (callbacks.exists(event)) { - for (cb in callbacks.get(event)) { - try { - Reflect.callMethod(null, cb, args != null ? args : []); - } catch (e:Dynamic) { - @:privateAccess - onError(haxe.Exception.caught(e)); - } - } - } - - if (!exists(event)) return null; - - try { - return Reflect.callMethod(null, get(event), args != null ? args : []); - } catch (e:Dynamic) { - @:privateAccess - onError(haxe.Exception.caught(e)); - return null; - } - } - - public function exists(variable:String):Bool { - return active && rule.variables.exists(variable); - } - - public function get(variable:String):Dynamic { - return exists(variable) ? rule.variables.get(variable) : null; - } - - public function set(variable:String, value:Dynamic):Void { - if (active) rule.variables.set(variable, value); - } - - public function addCallback(event:String, callback:Dynamic):Void { - if (!callbacks.exists(event)) - callbacks.set(event, []); - callbacks.get(event).push(callback); - } - - public function removeCallback(event:String, callback:Dynamic):Bool { - return if (callbacks.exists(event)) { - var arr = callbacks.get(event); - var result = arr.remove(callback); - if (arr.length == 0) callbacks.remove(event); - result; - } else false; - } - - function onError(e:haxe.Exception):Void { - final text = 'Error in $scriptName: ${e.details()}'; - trace(text); - CoolUtil.hxTrace(text, FlxColor.RED); - } - - public function stop():Void { - if (!active) return; - - active = false; - rule.variables.clear(); - callbacks.clear(); - importedPackages.clear(); - rule = null; - parentInstance = null; - } -} - -class RuleScriptInterpEx extends RuleScriptInterp { - public static var resolveScriptState:ResolveScriptState; - public var ref:ScriptClassRef; - public var funkScript:FunkinRScript; - - public function new(?funkScript:FunkinRScript) { - this.funkScript = funkScript; - super(); - } - - override function resolveType(path:String):Dynamic { - var resolved = funkScript.resolveType(path); - if (resolved != null) { - resolveScriptState = {owner: this, mode: "resolve"}; - return resolved; - } - - resolveScriptState = {owner: this, mode: "resolve"}; - return super.resolveType(path); - } - - override function cnew(cl:String, args:Array):Dynamic { - resolveScriptState = {owner: this, mode: "cnew", args: args}; - return super.cnew(cl, args); - } - - override function get(o:Dynamic, f:String):Dynamic { - if (o == this) { - if (this.ref != null && this.ref.staticFields.exists(f)) - return this.ref.staticFields.get(f); - } - - if (Std.isOfType(o, ScriptClassRef)) { - var cls:ScriptClassRef = cast o; - if (cls.staticFields.exists(f)) - return cls.staticFields.get(f); - } - - return super.get(o, f); - } - - override function set(o:Dynamic, f:String, v:Dynamic):Dynamic { - if (o == this) { - if (this.ref != null && this.ref.staticFields.exists(f)) { - this.ref.staticFields.set(f, v); - return v; - } - } - - if (Std.isOfType(o, ScriptClassRef)) { - var cls:ScriptClassRef = cast o; - if (cls.staticFields.exists(f)) { - cls.staticFields.set(f, v); - return v; - } - } - - return super.set(o, f, v); - } -} - -typedef ResolveScriptState = { - var owner:RuleScriptInterpEx; - var mode:String; // resolve or cnew - var ?args:Array; -} \ No newline at end of file diff --git a/source/game/scripting/FunkinRuleScript.hx b/source/game/scripting/FunkinRuleScript.hx new file mode 100644 index 0000000..8ec501b --- /dev/null +++ b/source/game/scripting/FunkinRuleScript.hx @@ -0,0 +1,497 @@ +package game.scripting; + +#if sys +import sys.io.File; +#end + +import haxe.ds.StringMap; +import haxe.io.Path; + +import rulescript.*; +import rulescript.parsers.*; +import rulescript.RuleScript; +import rulescript.scriptedClass.RuleScriptedClassUtil.*; +import rulescript.scriptedClass.RuleScriptedClassUtil; +import rulescript.scriptedClass.RuleScriptedClass.*; +import rulescript.scriptedClass.RuleScriptedClass; +import rulescript.types.ScriptedTypeUtil; +import rulescript.types.ScriptedAbstract; +import rulescript.interps.RuleScriptInterp; +import rulescript.types.ScriptedModule; +import rulescript.types.Abstracts; + +import hscript.Expr; + +import game.scripting.HScriptParser as HxParser; + +import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.group.FlxGroup; + +using StringTools; +using Lambda; + +class FunkinRuleScript { + + @:unreflective + static final PRESET_VARS:haxe.ds.Map = [ + // Flixel Classes + "FlxG" => flixel.FlxG, + "FlxSprite" => flixel.FlxSprite, + "FlxSpriteUtil" => flixel.util.FlxSpriteUtil, + "FlxTimer" => flixel.util.FlxTimer, + "FlxTween" => flixel.tweens.FlxTween, + "FlxEase" => flixel.tweens.FlxEase, + "FlxText" => flixel.text.FlxText, + "FlxSound" => flixel.sound.FlxSound, + "FlxTextBorderStyle" => flixel.text.FlxText.FlxTextBorderStyle, + "FlxCamera" => flixel.FlxCamera, + "FlxTextFormat" => flixel.text.FlxText.FlxTextFormat, + "FlxTextFormatMarkerPair" => flixel.text.FlxText.FlxTextFormatMarkerPair, + + "BaseScaleMode" => flixel.system.scaleModes.BaseScaleMode, + "FillScaleMode" => flixel.system.scaleModes.FillScaleMode, + "FixedScaleAdjustSizeScaleMode" => flixel.system.scaleModes.FixedScaleAdjustSizeScaleMode, + "FixedScaleMode" => flixel.system.scaleModes.FixedScaleMode, + "PixelPerfectScaleMode" => flixel.system.scaleModes.PixelPerfectScaleMode, + "RatioScaleMode" => flixel.system.scaleModes.RatioScaleMode, + "RelativeScaleMode" => flixel.system.scaleModes.RelativeScaleMode, + "StageSizeScaleMode" => flixel.system.scaleModes.StageSizeScaleMode, + + #if VIDEOS_ALLOWED + "FunkinVideoSprite" => game.objects.FunkinVideoSprite, + #end + + #if flxsoundfilters + "FlxFilteredSound" => FlxFilteredSound, + #end + + #if flxgif + "FlxGifSprite" => FlxGifSprite, + "FlxGifBackdrop" => FlxGifBackdrop, + #end + + #if MODCHART_ALLOWED + "ModManager" => game.modchart.ModManager, + #end + + "FlxShader" => game.shaders.flixel.FlxShader, + + "Paths" => game.Paths, + "Character" => game.objects.Character, + "PlayerSettings" => game.backend.PlayerSettings, + "CoolUtil" => game.backend.utils.CoolUtil, + "MusicBeatState" => MusicBeatState, + "MusicBeatSubstate" => MusicBeatSubstate, + "Conductor" => game.backend.Conductor, + "ClientPrefs" => game.backend.ClientPrefs, + "PlayState" => game.PlayState, + "BGSprite" => game.objects.BGSprite, + "FunkinRuleScript" => FunkinRuleScript, + + #if LUA_ALLOWED + "FunkinLua" => FunkinLua, + #end + + #if MODS_ALLOWED + "Mods" => game.backend.system.Mods, + #end + + #if SCRIPTABLE_STATES + "TitleState" => game.states.TitleState, + "MainMenuState" => game.states.MainMenuState, + "OptionsMenu" => game.states.options.OptionsState, + "CreditsState" => game.states.CreditsState, + "StoryMenuState" => game.states.StoryMenuState, + "FreeplayState" => game.states.FreeplayState, + "LoadingState" => game.states.LoadingState, + "HScriptState" => game.scripting.HScriptState, + "HScriptSubstate" => game.scripting.HScriptSubstate, + #end + + 'StringMap' => haxe.ds.StringMap, + 'IntMap' => haxe.ds.IntMap, + 'ObjectMap' => haxe.ds.ObjectMap + ]; + + @:unreflective + static final ABSTRACT_IMPORTS:Array = [ + "flixel.util.FlxColor", + "flixel.input.keyboard.FlxKey", + "flixel.tweens.FlxTween.FlxTweenType", + "flixel.text.FlxText.FlxTextAlign", + #if flxgif + "flxgif.FlxGifAsset", + #end + "openfl.display.BlendMode" + ]; + + public var scriptName:String; + public var active(default, null):Bool = true; + + private var rule:RuleScript; + private var parentInstance:Dynamic; + private var callbacks:haxe.ds.Map> = new haxe.ds.Map(); + private var importedPackages:haxe.ds.Map = new haxe.ds.Map(); + + public function new(path:String, parentInstance:Dynamic = null, skipCreate:Bool = false) { + this.parentInstance = parentInstance; + scriptName = path; + + initScriptedClasses(); + + rule = new RuleScript(new RuleScriptInterpEx(this)); + rule.scriptName = path; + rule.errorHandler = onError; + + try { + var content = loadScriptContent(path); + execute(content, skipCreate); + } catch (e:haxe.Exception) { + trace('Failed to load script $path: ${e.message}'); + active = false; + } + } + + private function initScriptedClasses() { + ScriptedTypeUtil.resolveModule = function(name:String):Array + { + var path = parseTypePath(name); + if (path.name == null || path.name.length == 0) { + trace('Invalid module path: $name'); + return null; + } + + var filePath = 'source/${path.name.replace('.', '/')}.hx'; + + if (!Paths.fileExists(filePath, TEXT)) { + //trace('Module not found: $filePath'); + return null; + } + + var content = Paths.getTextFromFile(filePath); + if (content == null) { + trace('Failed to load module content: $filePath'); + return null; + } + + var parser = new HxParser(); + parser.allowAll(); + parser.mode = MODULE; + + try { + return parser.parseModule(content); + } catch (e:Dynamic) { + trace('Failed to parse module $filePath: $e'); + return null; + } + } + + RuleScriptedClassUtil.buildBridge = function(typePath:String, superInstance:Dynamic):RuleScript + { + var type:ScriptedClassType = ScriptedTypeUtil.resolveScript(typePath); + if (type == null) { + trace('Failed to resolve script type: $typePath'); + return null; + } + + var script = new RuleScript(new RuleScriptInterpEx()); + script.scriptName = typePath; + + script.getParser(HxParser).allowAll(); + script.superInstance = superInstance; + script.getInterp(RuleScriptInterpEx).skipNextRestore = true; + + if (type.isExpr) { + script.execute(cast type); + return script; + } else { + var cl:ScriptedClass = cast type; + RuleScriptedClassUtil.buildScriptedClass(cl, script); + } + + return script; + }; + + ScriptedTypeUtil.resolveScript = function(name:String):Dynamic + { + var path = parseTypePath(name); + if (path.name == null || path.name.length == 0) { + trace('Invalid script path: $name'); + return null; + } + + final module:Array = ScriptedTypeUtil.resolveModule(path.name); + if (module == null) { + //trace('Module not found for script: $name'); + return null; + } + + try { + @:privateAccess + var scriptedModule = new ScriptedModule(path.name, module, ScriptedTypeUtil._currentContext); + return scriptedModule.types[path.typeName]; + } catch (e:Dynamic) { + trace('Failed to create scripted module for $name: $e'); + return null; + } + }; + } + + private function parseTypePath(name:String):{name:String, typeName:String} { + if (name == null || name.length == 0) { + return {name: "", typeName: ""}; + } + + var path:Array = name.split('.'); + if (path.length == 0) { + return {name: "", typeName: ""}; + } + + var typeName = path.pop(); + var moduleName = path.join('.'); + + //If no package, use the type name as module name + if (moduleName.length == 0) { + moduleName = typeName; + } + + return { + name: moduleName, + typeName: typeName + }; + } + + private function loadScriptContent(path:String):String { + if (path == null || path.length == 0) return "// Empty script lol"; + + #if sys + if (FileSystem.exists(path)) { + return File.getContent(path); + } else { + throw 'Script file not found: $path'; + } + #else + var resourceName = path.replace("/", "_").replace(".", "_").replace(":", "_"); + var content = haxe.Resource.getString(resourceName); + if (content == null) { + throw 'HScript not found in resources: $path (resource name: $resourceName)'; + } + return content; + #end + } + + function execute(code:String, skipCreate:Bool) { + presetVariables(); + rule.tryExecute(code); + if (!skipCreate) call("onCreate"); + } + + function presetVariables() { + for (key => value in PRESET_VARS) + set(key, value); + + for (abstractType in ABSTRACT_IMPORTS) { + var abstractInstance = Abstracts.resolveAbstract(abstractType); + var typeName = abstractType.split('.').pop(); + set(typeName, abstractInstance); + } + + if (parentInstance != null) + set("parent", parentInstance); + + var isPlayState = FlxG.state is PlayState; + if (isPlayState) { + set("game", PlayState.instance); + + set("add", function(basic:flixel.FlxBasic, ?frontOfChars:Bool = false) { + if (frontOfChars) { + PlayState.instance.add(basic); + return; + } + + var position:Int = PlayState.instance.members.indexOf(PlayState.instance.gfGroup); + if(PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup) < position) + position = PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup); + else if(PlayState.instance.members.indexOf(PlayState.instance.dadGroup) < position) + position = PlayState.instance.members.indexOf(PlayState.instance.dadGroup); + + PlayState.instance.insert(position, basic); + }); + + set("insert", PlayState.instance.insert); + set("remove", PlayState.instance.remove); + set("addBehindGF", PlayState.instance.addBehindGF); + set("addBehindDad", PlayState.instance.addBehindDad); + set("addBehindBF", PlayState.instance.addBehindBF); + + set("setVar", (name:String, value:Dynamic) -> { + PlayState.instance.variables.set(name, value); + return value; + }); + set("getVar", (name:String) -> { + var result:Dynamic = null; + if(PlayState.instance.variables.exists(name)) + result = PlayState.instance.variables.get(name); + + return result; + }); + set("removeVar", (name:String) -> { + if(PlayState.instance.variables.exists(name)) { + PlayState.instance.variables.remove(name); + return true; + } + + return false; + }); + + } else { + var scriptObject = FlxG.state.subState ?? FlxG.state; + set("game", scriptObject); + + set("add", scriptObject.add); + set("insert", scriptObject.insert); + set("remove", scriptObject.remove); + + set("setVar", (name:String, value:Dynamic) -> { + rule.variables.set(name, value); + return value; + }); + set("getVar", (name:String) -> { + var result:Dynamic = rule.variables.get(name); + return result; + }); + set("removeVar", (name:String) -> { + if(rule.variables.exists(name)) { + rule.variables.remove(name); + return true; + } + return false; + }); + } + + set("controls", game.backend.PlayerSettings.player1.controls); + + set("getObject", getObject); + set("getAll", getAllObjects); + } + + public function getObject(index:Int, group:String):Dynamic { + if (parentInstance == null) return null; + + try { + if (Reflect.hasField(parentInstance, group)) { + var targetGroup = Reflect.field(parentInstance, group); + if (Std.isOfType(targetGroup, FlxTypedGroup)) { + return targetGroup.members[index]; + } + } + } catch (e:Dynamic) { + trace('Error getting object: ${e.message}'); + } + return null; + } + + public function getAllObjects(group:String):Array { + if (parentInstance == null) return []; + + try { + if (Reflect.hasField(parentInstance, group)) { + var targetGroup = Reflect.field(parentInstance, group); + if (Std.isOfType(targetGroup, FlxTypedGroup)) { + return targetGroup.members; + } + } + } catch (e:Dynamic) { + trace('Error getting objects: ${e.message}'); + } + return []; + } + + public function resolveType(typeName:String):Dynamic { + var cl = Type.resolveClass(typeName); + if (cl != null) return cl; + + for (pkg in importedPackages.keys()) { + cl = Type.resolveClass(pkg + "." + typeName); + if (cl != null) return cl; + } + + // Try to resolve as scripted class + try { + var scriptedType = ScriptedTypeUtil.resolveScript(typeName); + if (scriptedType != null) return scriptedType; + } catch (e:Dynamic) { + trace('Failed to resolve scripted type $typeName: $e'); + } + + return null; + } + + public function call(event:String, ?args:Array):Dynamic { + if (!active) return null; + + if (callbacks.exists(event)) { + for (cb in callbacks.get(event)) { + try { + Reflect.callMethod(null, cb, args ?? []); + } catch (e:Dynamic) { + @:privateAccess + onError(haxe.Exception.caught(e)); + } + } + } + + if (!exists(event)) return null; + + try { + return Reflect.callMethod(null, get(event), args ?? []); + } catch (e:Dynamic) { + @:privateAccess + onError(haxe.Exception.caught(e)); + return null; + } + } + + public function exists(variable:String):Bool { + return active && rule.variables.exists(variable); + } + + public function get(variable:String):Dynamic { + return exists(variable) ? rule.variables.get(variable) : null; + } + + public function set(variable:String, value:Dynamic):Void { + if (active) rule.variables.set(variable, value); + } + + public function addCallback(event:String, callback:Dynamic):Void { + if (!callbacks.exists(event)) + callbacks.set(event, []); + callbacks.get(event).push(callback); + } + + public function removeCallback(event:String, callback:Dynamic):Bool { + return if (callbacks.exists(event)) { + var arr = callbacks.get(event); + var result = arr.remove(callback); + if (arr.length == 0) callbacks.remove(event); + result; + } else false; + } + + function onError(e:haxe.Exception):Void { + final text = 'Error in $scriptName: ${e.details()}'; + CoolUtil.hxTrace(text, FlxColor.RED); + } + + public function stop():Void { + if (!active) return; + + active = false; + rule?.variables?.clear(); + callbacks?.clear(); + importedPackages?.clear(); + rule = null; + parentInstance = null; + } +} \ No newline at end of file diff --git a/source/game/scripting/HScriptClassManager.hx b/source/game/scripting/HScriptClassManager.hx deleted file mode 100644 index 764c5bc..0000000 --- a/source/game/scripting/HScriptClassManager.hx +++ /dev/null @@ -1,300 +0,0 @@ -package game.scripting; - -import flixel.FlxG; - -import rulescript.*; -import rulescript.parsers.HxParser; -import rulescript.scriptedClass.*; -import rulescript.types.ScriptedTypeUtil; - -import hscript.Expr; -import hscript.Parser; - -#if sys -import sys.io.File; -#end - -import game.scripting.FunkinRScript.RuleScriptInterpEx; - -import haxe.ds.StringMap; - -using StringTools; - -class HScriptClassManager { - public static final SCRIPTABLE_CLASSES:Map> = { - var map = new Map>(); - map.set(Type.getClassName(TempClass), ScriptedTempClass); - map; - }; - - public static var classes:Map = new Map(); - - public static function init():Void { - ScriptedTypeUtil.resolveScript = __resolveScript; - RuleScriptedClassUtil.buildBridge = __buildRuleScript; - reloadScriptedClasses(); - } - - public static function reloadScriptedClasses():Void { - classes.clear(); - - var sourceFiles = new Map(); - - #if sys - for (file in CoolUtil.readRecursive("source")) { - if (file.endsWith(".hx")) { - try { - sourceFiles.set(file, File.getContent(Paths.getPath(file))); - } catch (e:haxe.Exception) { - trace('Failed to read file: $file - ${e.message}'); - } - } - } - #else - var resourceNames = haxe.Resource.listNames(); - for (name in resourceNames) { - if (name.startsWith("source_") && name.endsWith("_hx")) { - try { - var originalPath = name.substring(7, name.length - 3).replace("_", "/") + ".hx"; - var content = haxe.Resource.getString(name); - sourceFiles.set(originalPath, content); - } catch (e:haxe.Exception) { - trace('Failed to read resource: $name - ${e.message}'); - } - } - } - #end - - for (file => content in sourceFiles) { - processScriptFile(file, content); - } - } - - private static function processScriptFile(file:String, content:String):Void { - try { - var parser = new HxParser(); - parser.allowAll(); - parser.mode = MODULE; - - var expr = parser.parse(content); - var ogParser = new Parser(); - var ogExpr = ogParser.parseModule(content); - - var parentCls:Class = ScriptedTempClass; - var baseCls:Null> = null; - var imports = new Map(); - - for (e in ogExpr) { - processDeclaration(e, imports, file, expr, parentCls, baseCls); - } - } catch (e:haxe.Exception) { - trace('Failed to process script file: $file - ${e.message}'); - } - } - - private static function processDeclaration( - e:hscript.Expr.ModuleDecl, - imports:Map, - file:String, - expr:Expr, - parentCls:Class, - baseCls:Null> - ):Void { - switch (e) { - case DImport(path, everything): - var name = path[path.length - 1]; - imports.set(name, path.join(".")); - - case DClass(c): - if (c.extend != null) { - processClassExtend(c.extend, imports, parentCls, baseCls); - } - - var ref = createScriptClassRef(file, parentCls, baseCls, expr, c); - if (ref != null) { - processStaticFields(ref, c, expr); - classes.set(ref.path, ref); - } - - default: - // nothing... - } - } - - private static function processClassExtend( - extend:hscript.Expr.CType, - imports:Map, - parentCls:Class, - baseCls:Null> - ):Void { - switch (extend) { - case CTPath(path, params): - var p = path.join("."); - if (imports.exists(p)) p = imports.get(p); - - baseCls = Type.resolveClass(p); - if (baseCls != null) { - var className = Type.getClassName(baseCls); - if (className != null && SCRIPTABLE_CLASSES.exists(className)) { - parentCls = SCRIPTABLE_CLASSES.get(className); - } else { - trace('[WARN] Class $p is not scriptable or not found!'); - } - } - default: - } - } - - private static function createScriptClassRef( - file:String, - parentCls:Class, - baseCls:Null>, - expr:Expr, - c:hscript.Expr.ClassDecl - ):Null { - var path = file.split("/source/")[1].replace(".hx", "").replace("/", "."); - - return { - path: path, - scriptedClass: parentCls, - extend: baseCls, - expr: expr, - staticFields: new Map() - }; - } - - private static function processStaticFields(ref:ScriptClassRef, c:hscript.Expr.ClassDecl, expr:Expr):Void { - var staticFieldNames = new Array(); - - for (field in c.fields) { - if (field.access.contains(AStatic)) { - staticFieldNames.push(field.name); - } - } - - if (staticFieldNames.length > 0) { - executeStaticFields(ref, staticFieldNames); - removeStaticFieldsFromExpr(expr, staticFieldNames); - } - } - - private static function executeStaticFields(ref:ScriptClassRef, staticFieldNames:Array):Void { - try { - var rulescript = new RuleScript(new RuleScriptInterpEx(), new HxParser()); - cast(rulescript.interp, RuleScriptInterpEx).ref = ref; - rulescript.execute(ref.expr); - - for (key => data in rulescript.variables) { - if (staticFieldNames.contains(key)) { - ref.staticFields.set(key, data); - } - } - } catch (e:haxe.Exception) { - trace('Failed to execute static fields for ${ref.path}: ${e.message}'); - } - } - - private static function removeStaticFieldsFromExpr(expr:Expr, staticFieldNames:Array):Void { - switch (expr.e) { - case EBlock(fields): - var i = fields.length; - while (i-- > 0) { - var field = fields[i]; - switch (field.e) { - case EFunction(_, _, name, _) | EVar(name, _, _): - if (staticFieldNames.contains(name)) { - fields.splice(i, 1); - } - default: - } - } - default: - //doing nothing - } - } - - static function __buildRuleScript(typeName:String, superInstance:Dynamic):RuleScript { - if (!classes.exists(typeName)) - throw 'Script class $typeName not found'; - - var ref = classes.get(typeName); - var interp = new FunkinRScript.RuleScriptInterpEx(); - interp.ref = ref; - - var rulescript = new RuleScript(interp, new HxParser()); - rulescript.superInstance = superInstance; - - try { - rulescript.execute(ref.expr); - } catch (e:haxe.Exception) { - trace('Failed to build rule script for $typeName: ${e.message}'); - } - - return rulescript; - } - - static function __resolveScript(path:String):Dynamic { - var state = RuleScriptInterpEx.resolveScriptState; - if (state == null) return null; - - var clsName = path.split(".").pop(); - - return switch (state.mode) { - case "resolve": classes.get(path); - case "cnew": - if (classes.exists(path)) { - createInstance(path, state.args); - } else if (state.owner.variables.exists(clsName)) { - var ref = state.owner.variables.get(clsName); - if (ref != null && ref.expr != null) { - createInstance(ref.path, state.args); - } else { - null; - } - } else { - null; - } - default: null; - } - } - - public static function createInstance(path:String, ?args:Array):Dynamic { - if (!classes.exists(path)) return null; - - var ref = classes.get(path); - if (args == null) args = []; - - try { - return Type.createInstance(ref.scriptedClass, [path, args]); - } catch (e:haxe.Exception) { - trace('Failed to create instance of $path: ${e.message}'); - return null; - } - } - - public static function listScriptClassesExtends(cls:Class):Array { - var result = new Array(); - - for (key => scriptClass in classes) { - if (scriptClass.extend == cls) { - result.push(key); - } - } - - return result; - } -} - -@:structInit class ScriptClassRef { - public var path:String; - public var extend:Null>; - public var scriptedClass:Class; - public var expr:Expr; - public var staticFields:Map; -} - -class TempClass { - public function new() {} -} - -class ScriptedTempClass implements rulescript.scriptedClass.RuleScriptedClass extends TempClass {} \ No newline at end of file diff --git a/source/game/scripting/HScriptGlobal.hx b/source/game/scripting/HScriptGlobal.hx index 318367e..0fe49ca 100644 --- a/source/game/scripting/HScriptGlobal.hx +++ b/source/game/scripting/HScriptGlobal.hx @@ -17,12 +17,12 @@ class HScriptGlobal { var scriptPath:String = null; #if sys - var foldersToCheck:Array = [Paths.getPreloadPath('scripts/') #if MODS_ALLOWED , Paths.mods('scripts/') #end]; + var foldersToCheck:Array = [Paths.getPreloadPath('scripts/') #if MODS_ALLOWED , Mods.getModPath('scripts/') #end]; #if MODS_ALLOWED - if(Paths.currentModDirectory != null && Paths.currentModDirectory.length > 0) - foldersToCheck.insert(0, Paths.mods('${Paths.currentModDirectory}/scripts/')); - for(mod in Paths.getGlobalMods()) - foldersToCheck.insert(0, Paths.mods('$mod/scripts/states/')); + if(Mods.currentModDirectory != null && Mods.currentModDirectory.length > 0) + foldersToCheck.insert(0, Mods.getModPath('${Mods.currentModDirectory}/scripts/')); + for(mod in Mods.getGlobalMods()) + foldersToCheck.insert(0, Mods.getModPath('$mod/scripts/states/')); #end for (folder in foldersToCheck) { @@ -78,9 +78,11 @@ class HScriptGlobal { if (executeMethod != null) { Reflect.callMethod(script, executeMethod, [content, false]); } else { - var parser = new rulescript.parsers.HxParser(); + var parser = new HScriptParser(); parser.allowAll(); - var ruleScript = new rulescript.RuleScript(new rulescript.interps.RuleScriptInterp(), parser); + parser.preprocesorValues = FunkinHScript.getHScriptPreprocessors(); + + var ruleScript = new rulescript.RuleScript(new RuleScriptInterpEx(), parser); ruleScript.execute(content); for (key in ruleScript.variables.keys()) { diff --git a/source/game/scripting/HScriptParser.hx b/source/game/scripting/HScriptParser.hx new file mode 100644 index 0000000..1257acd --- /dev/null +++ b/source/game/scripting/HScriptParser.hx @@ -0,0 +1,784 @@ +package game.scripting; + +import hscript.Expr; +import hscript.Parser.Token; + +import rulescript.parsers.HxParser; + +using StringTools; +using rulescript.Tools; + +class HScriptParser extends HxParser +{ + public var strictMode:Bool = true; + public var requireSemicolons:Bool = true; + public var reportWarnings:Bool = true; + + private var errors:Array; + private var warnings:Array; + private var currentLine:Int = 1; + private var currentColumn:Int = 1; + + public var preprocessorValues:Map; + private var preprocStack:Array<{active:Bool, elseFound:Bool}>; + private var currentActive:Bool = true; + + public function setPreprocessorValues(values:Map):Void { + this.preprocessorValues = values; + } + + public function setDefines(values:Map):Void { + this.preprocessorValues = values; + } + + public function new(?strictMode:Bool = true) + { + super(); + this.strictMode = strictMode; + this.errors = []; + this.warnings = []; + this.preprocessorValues = new Map(); + this.preprocStack = []; + } + + override public function parse(code:String):Expr + { + errors = []; + warnings = []; + currentLine = 1; + currentColumn = 1; + preprocStack = []; + currentActive = true; + + try { + var processedCode = preprocessCode(code); + + if (strictMode && !validateBasicSyntax(processedCode)) { + throw new haxe.Exception("Basic syntax validation failed"); + } + + var result = super.parse(processedCode); + + result = transformFieldAssignments(result); + + if (reportWarnings && warnings.length > 0) { + trace('Parser warnings:'); + for (warning in warnings) { + trace(' $warning'); + } + } + + if (errors.length > 0) { + throw new haxe.Exception("Parser errors detected: " + errors.join("; ")); + } + + return result; + } catch (e:haxe.Exception) { + var enhancedError = 'Error at line $currentLine, column $currentColumn: ${e.message}'; + if (errors.length > 0) { + enhancedError += "\nAdditional errors: " + errors.join("; "); + } + throw new haxe.Exception(enhancedError); + } + } + + private function transformFieldAssignments(expr:Expr):Expr { + #if hscriptPos + return switch(expr.e) { + case EBinop("=", e1, e2): + switch(e1.e) { + case EField(obj, field): + var transformedObj = transformFieldAssignments(obj); + var transformedValue = transformFieldAssignments(e2); + { + e: ECall( + { + e: EField( + { + e: EIdent("Reflect"), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }, + "setProperty" + ), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }, + [ + transformedObj, + { + e: EConst(CString(field)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }, + transformedValue + ] + ), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + default: + { + e: EBinop("=", transformFieldAssignments(e1), transformFieldAssignments(e2)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + } + + case EBinop(op, e1, e2): + { + e: EBinop(op, transformFieldAssignments(e1), transformFieldAssignments(e2)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EUnop(op, postFix, e): + { + e: EUnop(op, postFix, transformFieldAssignments(e)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EField(e, field): + { + e: EField(transformFieldAssignments(e), field), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case ECall(e, params): + { + e: ECall(transformFieldAssignments(e), [for (p in params) transformFieldAssignments(p)]), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EArray(e, index): + { + e: EArray(transformFieldAssignments(e), transformFieldAssignments(index)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EBlock(exprs): + { + e: EBlock([for (e in exprs) transformFieldAssignments(e)]), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EIf(cond, e1, e2): + { + e: EIf(transformFieldAssignments(cond), transformFieldAssignments(e1), + e2 == null ? null : transformFieldAssignments(e2)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EWhile(cond, e): + { + e: EWhile(transformFieldAssignments(cond), transformFieldAssignments(e)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EFor(v, it, e): + { + e: EFor(v, transformFieldAssignments(it), transformFieldAssignments(e)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case ESwitch(e, cases, def): + { + e: ESwitch(transformFieldAssignments(e), + [for (c in cases) { + values: [for (v in c.values) transformFieldAssignments(v)], + expr: transformFieldAssignments(c.expr) + }], + def == null ? null : transformFieldAssignments(def)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case ETry(e, v, t, ecatch): + { + e: ETry(transformFieldAssignments(e), v, t, transformFieldAssignments(ecatch)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EFunction(args, e, name, ret): + { + e: EFunction(args, transformFieldAssignments(e), name, ret), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EReturn(e): + { + e: EReturn(e == null ? null : transformFieldAssignments(e)), + pmin: expr.pmin, + pmax: expr.pmax, + origin: expr.origin, + line: expr.line + }; + + case EBreak | EContinue | ETernary(_, _, _): + expr; + + default: + expr; + } + #else + return switch(expr) { + case EBinop("=", e1, e2): + switch(e1) { + case EField(obj, field): + ECall( + EField(EIdent("Reflect"), "setProperty"), + [transformFieldAssignments(obj), EConst(CString(field)), transformFieldAssignments(e2)] + ); + default: + EBinop("=", transformFieldAssignments(e1), transformFieldAssignments(e2)); + } + + case EBinop(op, e1, e2): + EBinop(op, transformFieldAssignments(e1), transformFieldAssignments(e2)); + + case EUnop(op, postFix, e): + EUnop(op, postFix, transformFieldAssignments(e)); + + case EField(e, field): + EField(transformFieldAssignments(e), field); + + case ECall(e, params): + ECall(transformFieldAssignments(e), [for (p in params) transformFieldAssignments(p)]); + + case EArray(e, index): + EArray(transformFieldAssignments(e), transformFieldAssignments(index)); + + case EBlock(exprs): + EBlock([for (e in exprs) transformFieldAssignments(e)]); + + case EIf(cond, e1, e2): + EIf(transformFieldAssignments(cond), transformFieldAssignments(e1), + e2 == null ? null : transformFieldAssignments(e2)); + + case EWhile(cond, e): + EWhile(transformFieldAssignments(cond), transformFieldAssignments(e)); + + case EFor(v, it, e): + EFor(v, transformFieldAssignments(it), transformFieldAssignments(e)); + + case ESwitch(e, cases, def): + ESwitch(transformFieldAssignments(e), + [for (c in cases) { + values: [for (v in c.values) transformFieldAssignments(v)], + expr: transformFieldAssignments(c.expr) + }], + def == null ? null : transformFieldAssignments(def)); + + case ETry(e, v, t, ecatch): + ETry(transformFieldAssignments(e), v, t, transformFieldAssignments(ecatch)); + + case EFunction(args, e, name, ret): + EFunction(args, transformFieldAssignments(e), name, ret); + + case EReturn(e): + EReturn(e == null ? null : transformFieldAssignments(e)); + + case EBreak | EContinue | ETernary(_, _, _): + expr; + + default: + expr; + } + #end + } + + private function preprocessCode(code:String):String + { + var lines = code.split("\n"); + var output = []; + preprocStack = []; + currentActive = true; + + for (i in 0...lines.length) { + var line = lines[i]; + var trimmed = line.trim(); + currentLine = i + 1; + + if (trimmed.startsWith("#")) { + if (trimmed.startsWith("#if ")) { + var condition = trimmed.substr(4).trim(); + var conditionMet = evaluatePreprocessorCondition(condition); + + preprocStack.push({ + active: currentActive, + elseFound: false + }); + + currentActive = currentActive && conditionMet; + continue; + } + else if (trimmed.startsWith("#elseif ")) { + if (preprocStack.length == 0) { + addError("Unexpected #elseif without #if", i + 1, 1); + continue; + } + + var lastCondition = preprocStack[preprocStack.length - 1]; + if (lastCondition.elseFound) { + addError("#elseif after #else", i + 1, 1); + currentActive = false; + } else { + var condition = trimmed.substr(8).trim(); + var conditionMet = evaluatePreprocessorCondition(condition); + + if (currentActive) { + currentActive = false; + } else { + currentActive = lastCondition.active && conditionMet; + } + preprocStack[preprocStack.length - 1].elseFound = true; + } + continue; + } + else if (trimmed == "#else") { + if (preprocStack.length == 0) { + addError("Unexpected #else without #if", i + 1, 1); + continue; + } + + var lastCondition = preprocStack[preprocStack.length - 1]; + if (lastCondition.elseFound) { + addError("#else after #else", i + 1, 1); + currentActive = false; + } else { + currentActive = lastCondition.active && !currentActive; + preprocStack[preprocStack.length - 1].elseFound = true; + } + continue; + } + else if (trimmed == "#end") { + if (preprocStack.length == 0) { + addError("Unexpected #end without #if", i + 1, 1); + continue; + } + + var lastCondition = preprocStack.pop(); + currentActive = lastCondition.active; + continue; + } + else if (trimmed.startsWith("#error ")) { + var message = trimmed.substr(7).trim(); + if (currentActive) { + addError("Preprocessor error: " + message, i + 1, 1); + } + continue; + } + else if (trimmed.startsWith("#warning ")) { + var message = trimmed.substr(9).trim(); + if (currentActive) { + addWarning("Preprocessor warning: " + message, i + 1, 1); + } + continue; + } + else if (trimmed.startsWith("#set ")) { + var setExpr = trimmed.substr(5).trim(); + if (currentActive) { + processSetDirective(setExpr, i + 1); + } + continue; + } + } + + if (currentActive) { + output.push(line); + } + } + + if (preprocStack.length > 0) { + addError("Unclosed #if directive", lines.length, 1); + } + + return output.join("\n"); + } + + private function evaluatePreprocessorCondition(condition:String):Bool + { + condition = condition.trim(); + + while (condition.startsWith("(") && condition.endsWith(")")) { + condition = condition.substring(1, condition.length - 1).trim(); + } + + if (condition == "true") return true; + if (condition == "false") return false; + + if (condition.startsWith("!")) { + var subCondition = condition.substr(1).trim(); + return !evaluatePreprocessorCondition(subCondition); + } + + var andIndex = condition.indexOf(" && "); + if (andIndex != -1) { + var left = condition.substring(0, andIndex).trim(); + var right = condition.substring(andIndex + 4).trim(); + return evaluatePreprocessorCondition(left) && evaluatePreprocessorCondition(right); + } + + var orIndex = condition.indexOf(" || "); + if (orIndex != -1) { + var left = condition.substring(0, orIndex).trim(); + var right = condition.substring(orIndex + 4).trim(); + return evaluatePreprocessorCondition(left) || evaluatePreprocessorCondition(right); + } + + var eqIndex = condition.indexOf(" == "); + if (eqIndex != -1) { + var left = condition.substring(0, eqIndex).trim(); + var right = condition.substring(eqIndex + 4).trim(); + return evaluatePreprocessorValue(left) == evaluatePreprocessorValue(right); + } + + var neqIndex = condition.indexOf(" != "); + if (neqIndex != -1) { + var left = condition.substring(0, neqIndex).trim(); + var right = condition.substring(neqIndex + 4).trim(); + return evaluatePreprocessorValue(left) != evaluatePreprocessorValue(right); + } + + return evaluatePreprocessorValue(condition) == true; + } + + private function evaluatePreprocessorValue(value:String):Dynamic + { + value = value.trim(); + + if (value == "true") return true; + if (value == "false") return false; + + var intVal = Std.parseInt(value); + if (intVal != null) return intVal != 0; + + var floatVal = Std.parseFloat(value); + if (!Math.isNaN(floatVal)) return floatVal != 0; + + if (preprocessorValues.exists(value)) { + var val = preprocessorValues.get(value); + + if (Std.isOfType(val, Bool)) return val; + if (Std.isOfType(val, Int) || Std.isOfType(val, Float)) return val != 0; + if (Std.isOfType(val, String)) { + var str:String = val; + return str != "" && str != "0" && str.toLowerCase() != "false"; + } + return val != null; + } + + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + var strVal = value.substring(1, value.length - 1); + return strVal != "" && strVal != "0" && strVal.toLowerCase() != "false"; + } + + addWarning("Unknown preprocessor identifier: " + value, currentLine, currentColumn); + return false; + } + + private function processSetDirective(expr:String, line:Int):Void + { + var parts = expr.split("="); + if (parts.length != 2) { + addError("Invalid #set directive syntax. Use: #set flag = value", line, 1); + return; + } + + var flag = parts[0].trim(); + var valueStr = parts[1].trim(); + + var value:Dynamic = null; + + if (valueStr == "true") { + value = true; + } else if (valueStr == "false") { + value = false; + } else { + var num = Std.parseFloat(valueStr); + if (!Math.isNaN(num)) { + value = num; + } else { + if ((valueStr.startsWith('"') && valueStr.endsWith('"')) || + (valueStr.startsWith("'") && valueStr.endsWith("'"))) { + value = valueStr.substring(1, valueStr.length - 1); + } else { + addError('Invalid value for #set: $valueStr', line, 1); + return; + } + } + } + + preprocessorValues.set(flag, value); + } + + private function validateBasicSyntax(code:String):Bool + { + var lines = code.split("\n"); + var inComment = false; + var inString = false; + var stringChar:Int = 0; + var braceStack:Array = []; + var bracketStack:Array = []; + var parenStack:Array = []; + + for (i in 0...lines.length) { + var line = lines[i]; + var trimmed = line.trim(); + currentLine = i + 1; + + if (trimmed == "") { + continue; + } + + if (trimmed.startsWith("//")) { + continue; + } + + if (inComment) { + if (trimmed.indexOf("*/") != -1) { + inComment = false; + line = line.substring(line.indexOf("*/") + 2); + } else { + continue; + } + } + + var j = 0; + while (j < line.length) { + var char = line.charAt(j); + var nextChar = j + 1 < line.length ? line.charAt(j + 1) : ""; + + if (!inString && !inComment) { + if (char == "/" && nextChar == "*") { + inComment = true; + j += 2; + continue; + } + + if (char == '"' || char == "'") { + inString = true; + stringChar = char.charCodeAt(0); + j++; + continue; + } + + switch (char) { + case "{": braceStack.push("{"); + case "}": + if (braceStack.length == 0 || braceStack.pop() != "{") { + addError('Unmatched closing brace: }', i + 1, j + 1); + } + case "[": bracketStack.push("["); + case "]": + if (bracketStack.length == 0 || bracketStack.pop() != "[") { + addError('Unmatched closing bracket: ]', i + 1, j + 1); + } + case "(": parenStack.push("("); + case ")": + if (parenStack.length == 0 || parenStack.pop() != "(") { + addError('Unmatched closing parenthesis: )', i + 1, j + 1); + } + } + + if (requireSemicolons && strictMode) { + checkSemicolonRequirement(line, i + 1, j); + } + } else if (inString) { + if (char == "\\") { + j++; + } else if (char.charCodeAt(0) == stringChar) { + inString = false; + } + } + + j++; + } + + if (inString) { + addWarning('Unclosed string literal', i + 1, line.length); + } + + if (inComment && i == lines.length - 1) { + addWarning('Unclosed multi-line comment', i + 1, line.length); + } + } + + if (braceStack.length > 0) { + for (brace in braceStack) { + addError('Unclosed brace: $brace', lines.length, 1); + } + } + + if (bracketStack.length > 0) { + for (bracket in bracketStack) { + addError('Unclosed bracket: $bracket', lines.length, 1); + } + } + + if (parenStack.length > 0) { + for (paren in parenStack) { + addError('Unclosed parenthesis: $paren', lines.length, 1); + } + } + + return errors.length == 0; + } + + private function checkSemicolonRequirement(line:String, lineNum:Int, pos:Int):Void + { + var trimmed = line.trim(); + if (trimmed == "" || trimmed.startsWith("//")) return; + + var noSemicolonEndings = [ + "{", "}", + "function", "class", "enum", "typedef", "interface", + "if", "for", "while", "switch", "do", + "try", "catch", "finally" + ]; + + for (ending in noSemicolonEndings) { + if (trimmed.endsWith(ending) || trimmed.endsWith(ending + " ")) { + return; + } + } + + if (!trimmed.endsWith(";") && !trimmed.endsWith("{") && !trimmed.endsWith("}")) { + addWarning('Missing semicolon', lineNum, trimmed.length); + } + } + + private function addError(message:String, line:Int, column:Int):Void + { + errors.push('Line $line, column $column: $message'); + } + + private function addWarning(message:String, line:Int, column:Int):Void + { + if (reportWarnings) { + warnings.push('Line $line, column $column: $message'); + } + } + + private function tokenString(tk:Token):String + { + return switch (tk) { + case TEof: ""; + case TConst(c): constString(c); + case TId(s): s; + case TOp(s): s; + case TPOpen: "("; + case TPClose: ")"; + case TBrOpen: "{"; + case TBrClose: "}"; + case TDot: "."; + case TQuestionDot: "?."; + case TComma: ","; + case TSemicolon: ";"; + case TBkOpen: "["; + case TBkClose: "]"; + case TQuestion: "?"; + case TDoubleDot: ":"; + case TMeta(id): "@" + id; + case TPrepro(id): "#" + id; + case TApostr: "'"; + } + } + + private function constString(c:Const):String + { + return switch (c) { + case CInt(v): Std.string(v); + case CFloat(f): Std.string(f); + case CString(s): '"$s"'; + } + } + + public function getErrors():Array + { + return errors.copy(); + } + + public function getWarnings():Array + { + return warnings.copy(); + } + + public function hasErrors():Bool + { + return errors.length > 0; + } + + public function hasWarnings():Bool + { + return warnings.length > 0; + } + + public function clearErrors():Void + { + errors = []; + warnings = []; + } + + public function setStrictMode(enabled:Bool):Void + { + strictMode = enabled; + requireSemicolons = enabled; + } + + public function setParserParameters(params:{ + ?strictMode:Bool, + ?requireSemicolons:Bool, + ?reportWarnings:Bool + }):Void + { + if (params.strictMode != null) strictMode = params.strictMode; + if (params.requireSemicolons != null) requireSemicolons = params.requireSemicolons; + if (params.reportWarnings != null) reportWarnings = params.reportWarnings; + } +} \ No newline at end of file diff --git a/source/game/scripting/HScriptState.hx b/source/game/scripting/HScriptState.hx index 6c313ac..2bf0463 100644 --- a/source/game/scripting/HScriptState.hx +++ b/source/game/scripting/HScriptState.hx @@ -16,7 +16,7 @@ class HScriptState extends MusicBeatState this.originalClassName = className; var parts = className.split("."); - this.stateName = parts[parts.length - 1]; + stateName = parts[parts.length - 1]; super(); } @@ -61,7 +61,7 @@ class HScriptState extends MusicBeatState for (path in scriptFiles) { try { menuScriptArray.push(new FunkinHScript(path, this)); - if (path.contains('contents/')) + if (path.contains('${Mods.MODS_FOLDER}/')) trace('Loaded mod state script: $path'); else trace('Loaded base game state script: $path'); diff --git a/source/game/scripting/HScriptSubstate.hx b/source/game/scripting/HScriptSubstate.hx index c4945fa..ca53853 100644 --- a/source/game/scripting/HScriptSubstate.hx +++ b/source/game/scripting/HScriptSubstate.hx @@ -41,7 +41,7 @@ class HScriptSubstate extends MusicBeatSubstate for (path in scriptFiles) { menuScriptArray.push(new FunkinHScript(path, this)); - if (path.contains('contents/')) + if (path.contains('${Mods.MODS_FOLDER}/')) trace('Loaded mod substate script: $path'); else trace('Loaded base game substate script: $path'); diff --git a/source/game/scripting/LuaCallbackHandler.hx b/source/game/scripting/LuaCallbackHandler.hx deleted file mode 100644 index 683cfbe..0000000 --- a/source/game/scripting/LuaCallbackHandler.hx +++ /dev/null @@ -1,67 +0,0 @@ -#if LUA_ALLOWED -package game.scripting; - -import llua.Lua; -import llua.LuaL; -import llua.State; -import llua.Convert; - -class LuaCallbackHandler -{ - public static inline function call(l:State, fname:String):Int - { - try - { - //trace('calling $fname'); - var cbf:Dynamic = Lua_helper.callbacks.get(fname); - - //Local functions have the lowest priority - //This is to prevent a "for" loop being called in every single operation, - //so that it only loops on reserved/special functions - if(cbf == null) - { - //trace('checking last script'); - var last:FunkinLua = FunkinLua.lastCalledScript; - if(last == null || last.lua != l) - { - //trace('looping thru scripts'); - for (script in PlayState.instance.luaArray) - if(script != FunkinLua.lastCalledScript && script != null && script.lua == l) - { - //trace('found script'); - cbf = script.callbacks.get(fname); - break; - } - } - else cbf = last.callbacks.get(fname); - } - - if(cbf == null) return 0; - - var nparams:Int = Lua.gettop(l); - var args:Array = []; - - for (i in 0...nparams) { - args[i] = Convert.fromLua(l, i + 1); - } - - var ret:Dynamic = null; - /* return the number of results */ - - ret = Reflect.callMethod(null,cbf,args); - - if(ret != null){ - Convert.toLua(l, ret); - return 1; - } - } - catch(e:Dynamic) - { - if(Lua_helper.sendErrorsToLua) {LuaL.error(l, 'CALLBACK ERROR! ${if(e.message != null) e.message else e}');return 0;} - trace(e); - throw(e); - } - return 0; - } -} -#end \ No newline at end of file diff --git a/source/game/scripting/RuleScriptInterpEx.hx b/source/game/scripting/RuleScriptInterpEx.hx new file mode 100644 index 0000000..2cbe120 --- /dev/null +++ b/source/game/scripting/RuleScriptInterpEx.hx @@ -0,0 +1,164 @@ +package game.scripting; + +import rulescript.interps.RuleScriptInterp; +import rulescript.interps.bytecode.Command; +import rulescript.RuleScript.IInterp; +import rulescript.scriptedClass.RuleScriptedClass; +import rulescript.types.Property; +import rulescript.types.ScriptedTypeUtil; +import hscript.Expr; + +class RuleScriptInterpEx extends RuleScriptInterp +{ + public static var resolveScriptState:ResolveScriptState; + public var ref:ScriptClassRef; + public var script:FunkinRuleScript; + + public function new(?script:FunkinRuleScript) { + this.script = script; + super(); + } + + override function resolveType(path:String):Dynamic { + var resolved = script.resolveType(path); + if (resolved != null) { + resolveScriptState = {owner: this, mode: "resolve"}; + return resolved; + } + + resolveScriptState = {owner: this, mode: "resolve"}; + return super.resolveType(path); + } + + override function cnew(cl:String, args:Array):Dynamic { + resolveScriptState = {owner: this, mode: "cnew", args: args}; + return super.cnew(cl, args); + } + + override function get(o:Dynamic, f:String):Dynamic { + if (o == this) { + if (this.ref != null && this.ref.staticFields.exists(f)) + return this.ref.staticFields.get(f); + } + + if (Std.isOfType(o, ScriptClassRef)) { + var cls:ScriptClassRef = cast o; + if (cls.staticFields.exists(f)) + return cls.staticFields.get(f); + } + + return super.get(o, f); + } + + override function set(o:Dynamic, f:String, v:Dynamic):Dynamic { + if (o == this) { + if (this.ref != null && this.ref.staticFields.exists(f)) { + this.ref.staticFields.set(f, v); + return v; + } + } + + if (Std.isOfType(o, ScriptClassRef)) { + var cls:ScriptClassRef = cast o; + if (cls.staticFields.exists(f)) { + cls.staticFields.set(f, v); + return v; + } + } + + return super.set(o, f, v); + } + + override function assign(e1:Expr, e2:Expr):Dynamic + { + var v = expr(e2); + #if hscriptPos + switch(e1.e) { + #else + switch(e1) { + #end + case EIdent(id): + var l = locals.get(id); + if (l == null) + setVar(id, v); + else + { + if (l.r != null && Type.getClassName(Type.getClass(l.r)) == "rulescript.types.Property") + cast(l.r, rulescript.types.Property).value = v; + else + l.r = v; + } + case EField(e, f): + var obj = expr(e); + obj ??= {}; + + try { + Reflect.setProperty(obj, f, v); + } catch (e:Dynamic) { + Reflect.setField(obj, f, v); + } + return v; + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + setMapValue(arr, index, v); + } else { + arr[index] = v; + } + case ETypeVarPath(path): + if (path.length < 2) { + throw new haxe.Exception("Invalid ETypeVarPath for assignment: " + path.join(".")); + } + + var field = path[path.length - 1]; + var objPath = path.slice(0, -1); + + var obj:Dynamic = null; + + var first = objPath[0]; + if (locals.exists(first) || variables.exists(first)) { + obj = resolve(first); + for (i in 1...objPath.length) { + obj = get(obj, objPath[i]); + } + } else { + var typePath = objPath.join("."); + obj = resolveType(typePath); + } + + if (obj == null) { + throw new haxe.Exception('Cannot resolve object for assignment: ${objPath.join(".")}'); + } + + try { + Reflect.setProperty(obj, field, v); + } catch (e:Dynamic) { + Reflect.setField(obj, field, v); + } + return v; + default: + #if hscriptPos + var exprStr = Std.string(e1.e); + #else + var exprStr = Std.string(e1); + #end + throw new haxe.Exception('Invalid assignment target: $exprStr'); + } + return v; + } +} + +@:structInit class ScriptClassRef { + public var path:String; + public var extend:Null>; + public var scriptedClass:Class; + public var expr:Expr; + public var staticFields:haxe.ds.Map; +} + +typedef ResolveScriptState = { + var owner:RuleScriptInterpEx; + var mode:String; // resolve or cnew + var ?args:Array; +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaColor.hx b/source/game/scripting/lua/LuaColor.hx new file mode 100644 index 0000000..b94fba1 --- /dev/null +++ b/source/game/scripting/lua/LuaColor.hx @@ -0,0 +1,48 @@ +package game.scripting.lua; + +class LuaColor +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "getColorFromHex", function(color:String) { + if(!color.startsWith('0x')) color = '0xff' + color; + return Std.parseInt(color); + }); + + LuaUtils.addFunction(lua, "setHealthBarColors", function(leftHex:String, rightHex:String) { + var left:FlxColor = Std.parseInt(leftHex); + if(!leftHex.startsWith('0x')) left = Std.parseInt('0xff' + leftHex); + var right:FlxColor = Std.parseInt(rightHex); + if(!rightHex.startsWith('0x')) right = Std.parseInt('0xff' + rightHex); + + PlayState.instance.healthBar.createFilledBar(left, right); + PlayState.instance.healthBar.updateBar(); + }); + LuaUtils.addFunction(lua, "setTimeBarColors", function(leftHex:String, rightHex:String) { + var left:FlxColor = Std.parseInt(leftHex); + if(!leftHex.startsWith('0x')) left = Std.parseInt('0xff' + leftHex); + var right:FlxColor = Std.parseInt(rightHex); + if(!rightHex.startsWith('0x')) right = Std.parseInt('0xff' + rightHex); + + PlayState.instance.timeBar.createFilledBar(right, left); + PlayState.instance.timeBar.updateBar(); + }); + + LuaUtils.addFunction(lua, "getPixelColor", function(obj:String, x:Int, y:Int) { + var killMe:Array = obj.split('.'); + var spr:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + spr = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(spr != null) + { + if(spr.framePixels != null) spr.framePixels.getPixel32(x, y); + return spr.pixels.getPixel32(x, y); + } + return 0; + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaControls.hx b/source/game/scripting/lua/LuaControls.hx new file mode 100644 index 0000000..d75de0e --- /dev/null +++ b/source/game/scripting/lua/LuaControls.hx @@ -0,0 +1,153 @@ +package game.scripting.lua; + +class LuaControls +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "mouseClicked", function(button:String) { + var boobs = FlxG.mouse.justPressed; + switch(button){ + case 'middle': + boobs = FlxG.mouse.justPressedMiddle; + case 'right': + boobs = FlxG.mouse.justPressedRight; + } + + + return boobs; + }); + + LuaUtils.addFunction(lua, "mousePressed", function(button:String) { + var boobs = FlxG.mouse.pressed; + switch(button){ + case 'middle': + boobs = FlxG.mouse.pressedMiddle; + case 'right': + boobs = FlxG.mouse.pressedRight; + } + return boobs; + }); + LuaUtils.addFunction(lua, "mouseReleased", function(button:String) { + var boobs = FlxG.mouse.justReleased; + switch(button){ + case 'middle': + boobs = FlxG.mouse.justReleasedMiddle; + case 'right': + boobs = FlxG.mouse.justReleasedRight; + } + return boobs; + }); + + LuaUtils.addFunction(lua, "keyboardJustPressed", function(name:String) + { + return Reflect.getProperty(FlxG.keys.justPressed, name); + }); + LuaUtils.addFunction(lua, "keyboardPressed", function(name:String) + { + return Reflect.getProperty(FlxG.keys.pressed, name); + }); + LuaUtils.addFunction(lua, "keyboardReleased", function(name:String) + { + return Reflect.getProperty(FlxG.keys.justReleased, name); + }); + + LuaUtils.addFunction(lua, "anyGamepadJustPressed", function(name:String) + { + return FlxG.gamepads.anyJustPressed(name); + }); + LuaUtils.addFunction(lua, "anyGamepadPressed", function(name:String) + { + return FlxG.gamepads.anyPressed(name); + }); + LuaUtils.addFunction(lua, "anyGamepadReleased", function(name:String) + { + return FlxG.gamepads.anyJustReleased(name); + }); + + LuaUtils.addFunction(lua, "gamepadAnalogX", function(id:Int, ?leftStick:Bool = true) + { + var controller = FlxG.gamepads.getByID(id); + if (controller == null) + { + return 0.0; + } + return controller.getXAxis(leftStick ? LEFT_ANALOG_STICK : RIGHT_ANALOG_STICK); + }); + LuaUtils.addFunction(lua, "gamepadAnalogY", function(id:Int, ?leftStick:Bool = true) + { + var controller = FlxG.gamepads.getByID(id); + if (controller == null) + { + return 0.0; + } + return controller.getYAxis(leftStick ? LEFT_ANALOG_STICK : RIGHT_ANALOG_STICK); + }); + LuaUtils.addFunction(lua, "gamepadJustPressed", function(id:Int, name:String) + { + var controller = FlxG.gamepads.getByID(id); + if (controller == null) + { + return false; + } + return Reflect.getProperty(controller.justPressed, name) == true; + }); + LuaUtils.addFunction(lua, "gamepadPressed", function(id:Int, name:String) + { + var controller = FlxG.gamepads.getByID(id); + if (controller == null) + { + return false; + } + return Reflect.getProperty(controller.pressed, name) == true; + }); + LuaUtils.addFunction(lua, "gamepadReleased", function(id:Int, name:String) + { + var controller = FlxG.gamepads.getByID(id); + if (controller == null) + { + return false; + } + return Reflect.getProperty(controller.justReleased, name) == true; + }); + + LuaUtils.addFunction(lua, "keyJustPressed", function(name:String) { + var key:Bool = false; + switch(name) { + case 'left': key = PlayState.instance.getControl('NOTE_LEFT_P'); + case 'down': key = PlayState.instance.getControl('NOTE_DOWN_P'); + case 'up': key = PlayState.instance.getControl('NOTE_UP_P'); + case 'right': key = PlayState.instance.getControl('NOTE_RIGHT_P'); + case 'accept': key = PlayState.instance.getControl('ACCEPT'); + case 'back': key = PlayState.instance.getControl('BACK'); + case 'pause': key = PlayState.instance.getControl('PAUSE'); + case 'reset': key = PlayState.instance.getControl('RESET'); + case 'space': key = FlxG.keys.justPressed.SPACE;//an extra key for convinience + } + return key; + }); + LuaUtils.addFunction(lua, "keyPressed", function(name:String) { + var key:Bool = false; + switch(name) { + case 'left': key = PlayState.instance.getControl('NOTE_LEFT'); + case 'down': key = PlayState.instance.getControl('NOTE_DOWN'); + case 'up': key = PlayState.instance.getControl('NOTE_UP'); + case 'right': key = PlayState.instance.getControl('NOTE_RIGHT'); + case 'space': key = FlxG.keys.pressed.SPACE;//an extra key for convinience + } + return key; + }); + LuaUtils.addFunction(lua, "keyReleased", function(name:String) { + var key:Bool = false; + switch(name) { + case 'left': key = PlayState.instance.getControl('NOTE_LEFT_R'); + case 'down': key = PlayState.instance.getControl('NOTE_DOWN_R'); + case 'up': key = PlayState.instance.getControl('NOTE_UP_R'); + case 'right': key = PlayState.instance.getControl('NOTE_RIGHT_R'); + case 'space': key = FlxG.keys.justReleased.SPACE;//an extra key for convinience + } + return key; + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaDeprecated.hx b/source/game/scripting/lua/LuaDeprecated.hx new file mode 100644 index 0000000..6708e4f --- /dev/null +++ b/source/game/scripting/lua/LuaDeprecated.hx @@ -0,0 +1,148 @@ +package game.scripting.lua; + +class LuaDeprecated +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + // DEPRECATED, DONT MESS WITH THESE SHITS, ITS JUST THERE FOR BACKWARD COMPATIBILITY + LuaUtils.addFunction(lua, "objectPlayAnimation", function(obj:String, name:String, forced:Bool = false, ?startFrame:Int = 0) { + FunkinLua.luaTrace("objectPlayAnimation is deprecated! Use playAnim instead", false, true); + if(PlayState.instance.getLuaObject(obj,false) != null) { + PlayState.instance.getLuaObject(obj,false).animation.play(name, forced, false, startFrame); + return true; + } + + var spr:FlxSprite = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(spr != null) { + spr.animation.play(name, forced, false, startFrame); + return true; + } + return false; + }); + LuaUtils.addFunction(lua, "characterPlayAnim", function(character:String, anim:String, ?forced:Bool = false) { + FunkinLua.luaTrace("characterPlayAnim is deprecated! Use playAnim instead", false, true); + switch(character.toLowerCase()) { + case 'dad': + if(PlayState.instance.dad.animOffsets.exists(anim)) + PlayState.instance.dad.playAnim(anim, forced); + case 'gf' | 'girlfriend': + if(PlayState.instance.gf != null && PlayState.instance.gf.animOffsets.exists(anim)) + PlayState.instance.gf.playAnim(anim, forced); + default: + if(PlayState.instance.boyfriend.animOffsets.exists(anim)) + PlayState.instance.boyfriend.playAnim(anim, forced); + } + }); + LuaUtils.addFunction(lua, "luaSpriteMakeGraphic", function(tag:String, width:Int, height:Int, color:String) { + FunkinLua.luaTrace("luaSpriteMakeGraphic is deprecated! Use makeGraphic instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + var colorNum:Int = Std.parseInt(color); + if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); + + PlayState.instance.modchartSprites.get(tag).makeGraphic(width, height, colorNum); + } + }); + LuaUtils.addFunction(lua, "luaSpriteAddAnimationByPrefix", function(tag:String, name:String, prefix:String, framerate:Int = 24, loop:Bool = true) { + FunkinLua.luaTrace("luaSpriteAddAnimationByPrefix is deprecated! Use addAnimationByPrefix instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + var cock:ModchartSprite = PlayState.instance.modchartSprites.get(tag); + cock.animation.addByPrefix(name, prefix, framerate, loop); + if(cock.animation.curAnim == null) { + cock.animation.play(name, true); + } + } + }); + LuaUtils.addFunction(lua, "luaSpriteAddAnimationByIndices", function(tag:String, name:String, prefix:String, indices:String, framerate:Int = 24) { + FunkinLua.luaTrace("luaSpriteAddAnimationByIndices is deprecated! Use addAnimationByIndices instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + var strIndices:Array = indices.trim().split(','); + var die:Array = []; + for (i in 0...strIndices.length) { + die.push(Std.parseInt(strIndices[i])); + } + var pussy:ModchartSprite = PlayState.instance.modchartSprites.get(tag); + pussy.animation.addByIndices(name, prefix, die, '', framerate, false); + if(pussy.animation.curAnim == null) { + pussy.animation.play(name, true); + } + } + }); + LuaUtils.addFunction(lua, "luaSpritePlayAnimation", function(tag:String, name:String, forced:Bool = false) { + FunkinLua.luaTrace("luaSpritePlayAnimation is deprecated! Use playAnim instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + PlayState.instance.modchartSprites.get(tag).animation.play(name, forced); + } + }); + LuaUtils.addFunction(lua, "setLuaSpriteCamera", function(tag:String, camera:String = '') { + FunkinLua.luaTrace("setLuaSpriteCamera is deprecated! Use setObjectCamera instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + PlayState.instance.modchartSprites.get(tag).cameras = [FunkinLua.cameraFromString(camera)]; + return true; + } + FunkinLua.luaTrace("Lua sprite with tag: " + tag + " doesn't exist!"); + return false; + }); + LuaUtils.addFunction(lua, "setLuaSpriteScrollFactor", function(tag:String, scrollX:Float, scrollY:Float) { + FunkinLua.luaTrace("setLuaSpriteScrollFactor is deprecated! Use setScrollFactor instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + PlayState.instance.modchartSprites.get(tag).scrollFactor.set(scrollX, scrollY); + return true; + } + return false; + }); + LuaUtils.addFunction(lua, "scaleLuaSprite", function(tag:String, x:Float, y:Float) { + FunkinLua.luaTrace("scaleLuaSprite is deprecated! Use scaleObject instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + var shit:ModchartSprite = PlayState.instance.modchartSprites.get(tag); + shit.scale.set(x, y); + shit.updateHitbox(); + return true; + } + return false; + }); + LuaUtils.addFunction(lua, "getPropertyLuaSprite", function(tag:String, variable:String) { + FunkinLua.luaTrace("getPropertyLuaSprite is deprecated! Use getProperty instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + var killMe:Array = variable.split('.'); + if(killMe.length > 1) { + var coverMeInPiss:Dynamic = Reflect.getProperty(PlayState.instance.modchartSprites.get(tag), killMe[0]); + for (i in 1...killMe.length-1) { + coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); + } + return Reflect.getProperty(coverMeInPiss, killMe[killMe.length-1]); + } + return Reflect.getProperty(PlayState.instance.modchartSprites.get(tag), variable); + } + return null; + }); + LuaUtils.addFunction(lua, "setPropertyLuaSprite", function(tag:String, variable:String, value:Dynamic) { + FunkinLua.luaTrace("setPropertyLuaSprite is deprecated! Use setProperty instead", false, true); + if(PlayState.instance.modchartSprites.exists(tag)) { + var killMe:Array = variable.split('.'); + if(killMe.length > 1) { + var coverMeInPiss:Dynamic = Reflect.getProperty(PlayState.instance.modchartSprites.get(tag), killMe[0]); + for (i in 1...killMe.length-1) { + coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); + } + Reflect.setProperty(coverMeInPiss, killMe[killMe.length-1], value); + return true; + } + Reflect.setProperty(PlayState.instance.modchartSprites.get(tag), variable, value); + return true; + } + FunkinLua.luaTrace("setPropertyLuaSprite: Lua sprite with tag: " + tag + " doesn't exist!"); + return false; + }); + LuaUtils.addFunction(lua, "musicFadeIn", function(duration:Float, fromValue:Float = 0, toValue:Float = 1) { + FlxG.sound.music.fadeIn(duration, fromValue, toValue); + FunkinLua.luaTrace('musicFadeIn is deprecated! Use soundFadeIn instead.', false, true); + + }); + LuaUtils.addFunction(lua, "musicFadeOut", function(duration:Float, toValue:Float = 0) { + FlxG.sound.music.fadeOut(duration, toValue); + FunkinLua.luaTrace('musicFadeOut is deprecated! Use soundFadeOut instead.', false, true); + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaFileManager.hx b/source/game/scripting/lua/LuaFileManager.hx new file mode 100644 index 0000000..6505825 --- /dev/null +++ b/source/game/scripting/lua/LuaFileManager.hx @@ -0,0 +1,80 @@ +package game.scripting.lua; + +import openfl.utils.Assets; + +class LuaFileManager +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "checkFileExists", function(filename:String, ?absolute:Bool = false) { + #if MODS_ALLOWED + if(absolute) + { + return FileSystem.exists(filename); + } + + var path:String = Mods.modFolders(filename); + if(FileSystem.exists(path)) + { + return true; + } + return FileSystem.exists(Paths.getPath('assets/$filename', TEXT)); + #else + if(absolute) + { + return Assets.exists(filename); + } + return Assets.exists(Paths.getPath('assets/$filename', TEXT)); + #end + }); + LuaUtils.addFunction(lua, "saveFile", function(path:String, content:String, ?absolute:Bool = false) + { + try { + if(!absolute) + #if MODS_ALLOWED + File.saveContent(Mods.getModPath(path), content); + #else + File.saveContent(Paths.getPreloadPath(path), content); + #end + else + File.saveContent(path, content); + + return true; + } catch (e:Dynamic) { + FunkinLua.luaTrace("saveFile: Error trying to save " + path + ": " + e, false, false, FlxColor.RED); + } + return false; + }); + LuaUtils.addFunction(lua, "deleteFile", function(path:String, ?ignoreModFolders:Bool = false) + { + try { + #if MODS_ALLOWED + if(!ignoreModFolders) + { + var lePath:String = Mods.modFolders(path); + if(FileSystem.exists(lePath)) + { + FileSystem.deleteFile(lePath); + return true; + } + } + #end + + var lePath:String = Paths.getPath(path, TEXT); + if(Assets.exists(lePath)) + { + FileSystem.deleteFile(lePath); + return true; + } + } catch (e:Dynamic) { + FunkinLua.luaTrace("deleteFile: Error trying to delete " + path + ": " + e, false, false, FlxColor.RED); + } + return false; + }); + LuaUtils.addFunction(lua, "getTextFromFile", function(path:String, ?ignoreModFolders:Bool = false) { + return Paths.getTextFromFile(path, ignoreModFolders); + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaObject.hx b/source/game/scripting/lua/LuaObject.hx new file mode 100644 index 0000000..f4ec647 --- /dev/null +++ b/source/game/scripting/lua/LuaObject.hx @@ -0,0 +1,225 @@ +package game.scripting.lua; + +import flixel.FlxBasic; +import flixel.FlxObject; +import openfl.display.BlendMode; + +class LuaObject +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "setScrollFactor", function(obj:String, scrollX:Float, scrollY:Float) { + if(PlayState.instance.getLuaObject(obj,false)!=null) { + PlayState.instance.getLuaObject(obj,false).scrollFactor.set(scrollX, scrollY); + return; + } + + var object:FlxObject = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(object != null) { + object.scrollFactor.set(scrollX, scrollY); + } + }); + LuaUtils.addFunction(lua, "setGraphicSize", function(obj:String, x:Int, y:Int = 0, updateHitbox:Bool = true) { + if(PlayState.instance.getLuaObject(obj)!=null) { + var shit:FlxSprite = PlayState.instance.getLuaObject(obj); + shit.setGraphicSize(x, y); + if(updateHitbox) shit.updateHitbox(); + return; + } + + var killMe:Array = obj.split('.'); + var poop:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + poop = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(poop != null) { + poop.setGraphicSize(x, y); + if(updateHitbox) poop.updateHitbox(); + return; + } + FunkinLua.luaTrace('setGraphicSize: Couldnt find object: ' + obj, false, false, FlxColor.RED); + }); + LuaUtils.addFunction(lua, "scaleObject", function(obj:String, x:Float, y:Float, updateHitbox:Bool = true) { + if(PlayState.instance.getLuaObject(obj)!=null) { + var shit:FlxSprite = PlayState.instance.getLuaObject(obj); + shit.scale.set(x, y); + if(updateHitbox) shit.updateHitbox(); + return; + } + + var killMe:Array = obj.split('.'); + var poop:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + poop = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(poop != null) { + poop.scale.set(x, y); + if(updateHitbox) poop.updateHitbox(); + return; + } + FunkinLua.luaTrace('scaleObject: Couldnt find object: ' + obj, false, false, FlxColor.RED); + }); + LuaUtils.addFunction(lua, "updateHitbox", function(obj:String) { + if(PlayState.instance.getLuaObject(obj)!=null) { + var shit:FlxSprite = PlayState.instance.getLuaObject(obj); + shit.updateHitbox(); + return; + } + + var poop:FlxSprite = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(poop != null) { + poop.updateHitbox(); + return; + } + FunkinLua.luaTrace('updateHitbox: Couldnt find object: ' + obj, false, false, FlxColor.RED); + }); + LuaUtils.addFunction(lua, "updateHitboxFromGroup", function(group:String, index:Int) { + if(Std.isOfType(Reflect.getProperty(FunkinLua.getInstance(), group), FlxTypedGroup)) { + Reflect.getProperty(FunkinLua.getInstance(), group).members[index].updateHitbox(); + return; + } + Reflect.getProperty(FunkinLua.getInstance(), group)[index].updateHitbox(); + }); + + LuaUtils.addFunction(lua, "setObjectCamera", function(obj:String, camera:String = '') { + var real = PlayState.instance.getLuaObject(obj); + if(real!=null){ + real.cameras = [FunkinLua.cameraFromString(camera)]; + return true; + } + + var killMe:Array = obj.split('.'); + var object:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + object = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(object != null) { + object.cameras = [FunkinLua.cameraFromString(camera)]; + return true; + } + FunkinLua.luaTrace("setObjectCamera: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setBlendMode", function(obj:String, blend:String = '') { + var real = PlayState.instance.getLuaObject(obj); + if(real != null) { + real.blend = blendModeFromString(blend); + return true; + } + + var killMe:Array = obj.split('.'); + var spr:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + spr = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(spr != null) { + spr.blend = blendModeFromString(blend); + return true; + } + FunkinLua.luaTrace("setBlendMode: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "screenCenter", function(obj:String, pos:String = 'xy') { + var spr:FlxSprite = PlayState.instance.getLuaObject(obj); + + if(spr==null){ + var killMe:Array = obj.split('.'); + spr = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + spr = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + } + + if(spr != null) + { + switch(pos.trim().toLowerCase()) + { + case 'x': + spr.screenCenter(X); + return; + case 'y': + spr.screenCenter(Y); + return; + default: + spr.screenCenter(XY); + return; + } + } + FunkinLua.luaTrace("screenCenter: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); + }); + LuaUtils.addFunction(lua, "objectsOverlap", function(obj1:String, obj2:String) { + var namesArray:Array = [obj1, obj2]; + var objectsArray:Array = []; + for (i in 0...namesArray.length) + { + var real = PlayState.instance.getLuaObject(namesArray[i]); + if(real!=null) { + objectsArray.push(real); + } else { + objectsArray.push(Reflect.getProperty(FunkinLua.getInstance(), namesArray[i])); + } + } + + if(!objectsArray.contains(null) && FlxG.overlap(objectsArray[0], objectsArray[1])) + { + return true; + } + return false; + }); + + LuaUtils.addFunction(lua, "getObjectOrder", function(obj:String) { + var killMe:Array = obj.split('.'); + var leObj:FlxBasic = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + leObj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(leObj != null) + { + return FunkinLua.getInstance().members.indexOf(leObj); + } + FunkinLua.luaTrace("getObjectOrder: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); + return -1; + }); + LuaUtils.addFunction(lua, "setObjectOrder", function(obj:String, position:Int) { + var killMe:Array = obj.split('.'); + var leObj:FlxBasic = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + leObj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(leObj != null) { + FunkinLua.getInstance().remove(leObj, true); + FunkinLua.getInstance().insert(position, leObj); + return; + } + FunkinLua.luaTrace("setObjectOrder: Object " + obj + " doesn't exist!", false, false, FlxColor.RED); + }); + } + + static function blendModeFromString(blend:String):BlendMode { + switch(blend.toLowerCase().trim()) { + case 'add': return ADD; + case 'alpha': return ALPHA; + case 'darken': return DARKEN; + case 'difference': return DIFFERENCE; + case 'erase': return ERASE; + case 'hardlight': return HARDLIGHT; + case 'invert': return INVERT; + case 'layer': return LAYER; + case 'lighten': return LIGHTEN; + case 'multiply': return MULTIPLY; + case 'overlay': return OVERLAY; + case 'screen': return SCREEN; + case 'shader': return SHADER; + case 'subtract': return SUBTRACT; + } + return NORMAL; + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaPlayState.hx b/source/game/scripting/lua/LuaPlayState.hx new file mode 100644 index 0000000..959b445 --- /dev/null +++ b/source/game/scripting/lua/LuaPlayState.hx @@ -0,0 +1,339 @@ +package game.scripting.lua; + +import game.objects.DialogueBoxPsych; +import game.objects.DialogueBoxPsych.DialogueFile; + +class LuaPlayState +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "addScore", function(value:Int = 0) { + PlayState.instance.songScore += value; + PlayState.instance.RecalculateRating(); + }); + LuaUtils.addFunction(lua, "addMisses", function(value:Int = 0) { + PlayState.instance.songMisses += value; + PlayState.instance.RecalculateRating(); + }); + LuaUtils.addFunction(lua, "addHits", function(value:Int = 0) { + PlayState.instance.songHits += value; + PlayState.instance.RecalculateRating(); + }); + LuaUtils.addFunction(lua, "setScore", function(value:Int = 0) { + PlayState.instance.songScore = value; + PlayState.instance.RecalculateRating(); + }); + LuaUtils.addFunction(lua, "setMisses", function(value:Int = 0) { + PlayState.instance.songMisses = value; + PlayState.instance.RecalculateRating(); + }); + LuaUtils.addFunction(lua, "setHits", function(value:Int = 0) { + PlayState.instance.songHits = value; + PlayState.instance.RecalculateRating(); + }); + LuaUtils.addFunction(lua, "getScore", function() { + return PlayState.instance.songScore; + }); + LuaUtils.addFunction(lua, "getMisses", function() { + return PlayState.instance.songMisses; + }); + LuaUtils.addFunction(lua, "getHits", function() { + return PlayState.instance.songHits; + }); + + LuaUtils.addFunction(lua, "setHealth", function(value:Float = 0) { + PlayState.instance.health = value; + }); + LuaUtils.addFunction(lua, "addHealth", function(value:Float = 0) { + PlayState.instance.health += value; + }); + LuaUtils.addFunction(lua, "getHealth", function() { + return PlayState.instance.health; + }); + + LuaUtils.addFunction(lua, "addCharacterToList", function(name:String, type:String) { + var charType:Int = 0; + switch(type.toLowerCase()) { + case 'dad': charType = 1; + case 'gf' | 'girlfriend': charType = 2; + } + PlayState.instance.addCharacterToList(name, charType); + }); + LuaUtils.addFunction(lua, "precacheImage", function(name:String, ?allowGPU:Bool = true) { + Paths.image(name, allowGPU); + }); + LuaUtils.addFunction(lua, "precacheSound", function(name:String) { + CoolUtil.precacheSound(name); + }); + LuaUtils.addFunction(lua, "precacheMusic", function(name:String) { + CoolUtil.precacheMusic(name); + }); + LuaUtils.addFunction(lua, "triggerEvent", function(name:String, arg1:Dynamic, arg2:Dynamic) { + var value1:String = arg1; + var value2:String = arg2; + PlayState.instance.triggerEventNote(name, value1, value2); + //trace('Triggered event: ' + name + ', ' + value1 + ', ' + value2); + return true; + }); + + LuaUtils.addFunction(lua, "startCountdown", function() { + PlayState.instance.startCountdown(); + return true; + }); + LuaUtils.addFunction(lua, "endSong", function() { + PlayState.instance.KillNotes(); + PlayState.instance.endSong(); + return true; + }); + LuaUtils.addFunction(lua, "restartSong", function(?skipTransition:Bool = false) { + PlayState.instance.persistentUpdate = false; + PauseSubState.restartSong(skipTransition); + return true; + }); + LuaUtils.addFunction(lua, "exitSong", function(?skipTransition:Bool = false) { + if(skipTransition) + { + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; + } + + PlayState.cancelMusicFadeTween(); + + if(PlayState.isStoryMode) + FlxG.switchState(() -> new StoryMenuState()); + else + FlxG.switchState(() -> new FreeplayState()); + + FlxG.sound.playMusic(Paths.music('freakyMenu')); + PlayState.changedDifficulty = false; + PlayState.chartingMode = false; + PlayState.instance.transitioning = true; + WeekData.loadTheFirstEnabledMod(); + return true; + }); + LuaUtils.addFunction(lua, "getSongPosition", function() { + return Conductor.songPosition; + }); + + LuaUtils.addFunction(lua, "getCharacterX", function(type:String) { + switch(type.toLowerCase()) { + case 'dad' | 'opponent': + return PlayState.instance.dadGroup.x; + case 'gf' | 'girlfriend': + return PlayState.instance.gfGroup.x; + default: + return PlayState.instance.boyfriendGroup.x; + } + }); + LuaUtils.addFunction(lua, "setCharacterX", function(type:String, value:Float) { + switch(type.toLowerCase()) { + case 'dad' | 'opponent': + PlayState.instance.dadGroup.x = value; + case 'gf' | 'girlfriend': + PlayState.instance.gfGroup.x = value; + default: + PlayState.instance.boyfriendGroup.x = value; + } + }); + LuaUtils.addFunction(lua, "getCharacterY", function(type:String) { + switch(type.toLowerCase()) { + case 'dad' | 'opponent': + return PlayState.instance.dadGroup.y; + case 'gf' | 'girlfriend': + return PlayState.instance.gfGroup.y; + default: + return PlayState.instance.boyfriendGroup.y; + } + }); + LuaUtils.addFunction(lua, "setCharacterY", function(type:String, value:Float) { + switch(type.toLowerCase()) { + case 'dad' | 'opponent': + PlayState.instance.dadGroup.y = value; + case 'gf' | 'girlfriend': + PlayState.instance.gfGroup.y = value; + default: + PlayState.instance.boyfriendGroup.y = value; + } + }); + LuaUtils.addFunction(lua, "cameraSetTarget", function(target:String) { + var isDad:Bool = false; + if(target == 'dad') { + isDad = true; + } + PlayState.instance.moveCamera(isDad); + return isDad; + }); + LuaUtils.addFunction(lua, "cameraShake", function(camera:String, intensity:Float, duration:Float) { + FunkinLua.cameraFromString(camera).shake(intensity, duration); + }); + LuaUtils.addFunction(lua, "cameraFlash", function(camera:String, color:String, duration:Float,forced:Bool) { + if (!ClientPrefs.flashing) return; + + var colorNum:Int = Std.parseInt(color); + if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); + FunkinLua.cameraFromString(camera).flash(colorNum, duration,null,forced); + }); + LuaUtils.addFunction(lua, "cameraFade", function(camera:String, color:String, duration:Float,forced:Bool) { + var colorNum:Int = Std.parseInt(color); + if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); + FunkinLua.cameraFromString(camera).fade(colorNum, duration,false,null,forced); + }); + LuaUtils.addFunction(lua, "setRatingPercent", function(value:Float) { + PlayState.instance.ratingPercent = value; + }); + LuaUtils.addFunction(lua, "setRatingName", function(value:String) { + PlayState.instance.ratingName = value; + }); + LuaUtils.addFunction(lua, "setRatingFC", function(value:String) { + PlayState.instance.ratingFC = value; + }); + LuaUtils.addFunction(lua, "getMouseX", function(camera:String) { + var cam:FlxCamera = FunkinLua.cameraFromString(camera); + return FlxG.mouse.getViewPosition(cam).x; + }); + LuaUtils.addFunction(lua, "getMouseY", function(camera:String) { + var cam:FlxCamera = FunkinLua.cameraFromString(camera); + return FlxG.mouse.getViewPosition(cam).y; + }); + + LuaUtils.addFunction(lua, "getMidpointX", function(variable:String) { + var killMe:Array = variable.split('.'); + var obj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + obj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + if(obj != null) return obj.getMidpoint().x; + + return 0; + }); + LuaUtils.addFunction(lua, "getMidpointY", function(variable:String) { + var killMe:Array = variable.split('.'); + var obj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + obj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + if(obj != null) return obj.getMidpoint().y; + + return 0; + }); + LuaUtils.addFunction(lua, "getGraphicMidpointX", function(variable:String) { + var killMe:Array = variable.split('.'); + var obj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + obj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + if(obj != null) return obj.getGraphicMidpoint().x; + + return 0; + }); + LuaUtils.addFunction(lua, "getGraphicMidpointY", function(variable:String) { + var killMe:Array = variable.split('.'); + var obj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + obj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + if(obj != null) return obj.getGraphicMidpoint().y; + + return 0; + }); + LuaUtils.addFunction(lua, "getScreenPositionX", function(variable:String) { + var killMe:Array = variable.split('.'); + var obj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + obj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + if(obj != null) return obj.getScreenPosition().x; + + return 0; + }); + LuaUtils.addFunction(lua, "getScreenPositionY", function(variable:String) { + var killMe:Array = variable.split('.'); + var obj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + obj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + if(obj != null) return obj.getScreenPosition().y; + + return 0; + }); + LuaUtils.addFunction(lua, "characterDance", function(character:String) { + switch(character.toLowerCase()) { + case 'dad': PlayState.instance?.dad?.dance(); + case 'gf' | 'girlfriend': PlayState.instance?.gf?.dance(); + default: PlayState.instance.boyfriend.dance(); + } + }); + + LuaUtils.addFunction(lua, "loadSong", function(?name:String = null, ?difficultyNum:Int = -1) { + if(name == null || name.length < 1) + name = PlayState.SONG.song; + if (difficultyNum == -1) + difficultyNum = PlayState.storyDifficulty; + + var poop = Highscore.formatSong(name, difficultyNum); + PlayState.SONG = Song.loadFromJson(poop, name); + PlayState.storyDifficulty = difficultyNum; + PlayState.instance.persistentUpdate = false; + LoadingState.loadAndSwitchState(() -> new PlayState()); + + FlxG.sound.music.pause(); + FlxG.sound.music.volume = 0; + if(PlayState.instance.vocals != null) + { + PlayState.instance.vocals.pause(); + PlayState.instance.vocals.volume = 0; + } + }); + + LuaUtils.addFunction(lua, "startDialogue", function(dialogueFile:String, music:String = null) { + var path:String; + #if MODS_ALLOWED + path = Mods.modsJson(Paths.formatToSongPath(PlayState.SONG.song) + '/' + dialogueFile); + if(!FileSystem.exists(path)) + #end + path = Paths.json(Paths.formatToSongPath(PlayState.SONG.song) + '/' + dialogueFile); + + FunkinLua.luaTrace('startDialogue: Trying to load dialogue: ' + path); + + #if MODS_ALLOWED + if(FileSystem.exists(path)) + #else + if(Assets.exists(path)) + #end + { + var shit:DialogueFile = DialogueBoxPsych.parseDialogue(path); + if(shit.dialogue.length > 0) { + PlayState.instance.startDialogue(shit, music); + FunkinLua.luaTrace('startDialogue: Successfully loaded dialogue', false, false, FlxColor.GREEN); + return true; + } else { + FunkinLua.luaTrace('startDialogue: Your dialogue file is badly formatted!', false, false, FlxColor.RED); + } + } else { + FunkinLua.luaTrace('startDialogue: Dialogue file not found', false, false, FlxColor.RED); + if(PlayState.instance.endingSong) { + PlayState.instance.endSong(); + } else { + PlayState.instance.startCountdown(); + } + } + return false; + }); + LuaUtils.addFunction(lua, "playVideo", function(videoFile:String, ?isNotMidPartSong:Bool = false) { + #if VIDEOS_ALLOWED + if(FileSystem.exists(Paths.video(videoFile))) { + PlayState.instance.playVideo(videoFile, isNotMidPartSong); + return true; + } else { + FunkinLua.luaTrace('playVideo: Video file not found: ' + videoFile, false, false, FlxColor.RED); + } + return false; + + #else + return true; + #end + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaRandom.hx b/source/game/scripting/lua/LuaRandom.hx new file mode 100644 index 0000000..0f08cc3 --- /dev/null +++ b/source/game/scripting/lua/LuaRandom.hx @@ -0,0 +1,31 @@ +package game.scripting.lua; + +class LuaRandom +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "getRandomInt", function(min:Int, max:Int = FlxMath.MAX_VALUE_INT, exclude:String = '') { + var excludeArray:Array = exclude.split(','); + var toExclude:Array = []; + for (i in 0...excludeArray.length) + { + toExclude.push(Std.parseInt(excludeArray[i].trim())); + } + return FlxG.random.int(min, max, toExclude); + }); + LuaUtils.addFunction(lua, "getRandomFloat", function(min:Float, max:Float = 1, exclude:String = '') { + var excludeArray:Array = exclude.split(','); + var toExclude:Array = []; + for (i in 0...excludeArray.length) + { + toExclude.push(Std.parseFloat(excludeArray[i].trim())); + } + return FlxG.random.float(min, max, toExclude); + }); + LuaUtils.addFunction(lua, "getRandomBool", function(chance:Float = 50) { + return FlxG.random.bool(chance); + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaSave.hx b/source/game/scripting/lua/LuaSave.hx new file mode 100644 index 0000000..c223ad6 --- /dev/null +++ b/source/game/scripting/lua/LuaSave.hx @@ -0,0 +1,48 @@ +package game.scripting.lua; + +import flixel.util.FlxSave; + +class LuaSave +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "initSaveData", function(name:String, ?folder:String = 'psychenginemods') { + if(!PlayState.instance.modchartSaves.exists(name)) + { + var save:FlxSave = new FlxSave(); + save.bind(name, CoolUtil.getSavePath() + "/" + folder); + PlayState.instance.modchartSaves.set(name, save); + return; + } + FunkinLua.luaTrace('initSaveData: Save file already initialized: ' + name); + }); + LuaUtils.addFunction(lua, "flushSaveData", function(name:String) { + if(PlayState.instance.modchartSaves.exists(name)) + { + PlayState.instance.modchartSaves.get(name).flush(); + return; + } + FunkinLua.luaTrace('flushSaveData: Save file not initialized: ' + name, false, false, FlxColor.RED); + }); + + LuaUtils.addFunction(lua, "getDataFromSave", function(name:String, field:String, ?defaultValue:Dynamic = null) { + if(PlayState.instance.modchartSaves.exists(name)) + { + var retVal:Dynamic = Reflect.field(PlayState.instance.modchartSaves.get(name).data, field); + return retVal; + } + FunkinLua.luaTrace('getDataFromSave: Save file not initialized: ' + name, false, false, FlxColor.RED); + return defaultValue; + }); + LuaUtils.addFunction(lua, "setDataFromSave", function(name:String, field:String, value:Dynamic) { + if(PlayState.instance.modchartSaves.exists(name)) + { + Reflect.setField(PlayState.instance.modchartSaves.get(name).data, field, value); + return; + } + FunkinLua.luaTrace('setDataFromSave: Save file not initialized: ' + name, false, false, FlxColor.RED); + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaShader.hx b/source/game/scripting/lua/LuaShader.hx new file mode 100644 index 0000000..606e2f3 --- /dev/null +++ b/source/game/scripting/lua/LuaShader.hx @@ -0,0 +1,345 @@ +package game.scripting.lua; + +#if (!flash && sys) +import flixel.addons.display.FlxRuntimeShader; +import openfl.filters.ShaderFilter; +#end + +class LuaShader +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "initLuaShader", function(name:String, glslVersion:Int = 120) { + if(!ClientPrefs.shaders) return false; + + #if (!flash && sys) + return initLuaShader(name, glslVersion); + #else + FunkinLua.luaTrace("initLuaShader: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + return false; + }); + + LuaUtils.addFunction(lua, "setSpriteShader", function(obj:String, shader:String) { + if(!ClientPrefs.shaders) return false; + + #if (!flash && sys) + if(!PlayState.instance.runtimeShaders.exists(shader) && !initLuaShader(shader)) + { + FunkinLua.luaTrace('setSpriteShader: Shader $shader is missing!', false, false, FlxColor.RED); + return false; + } + + var killMe:Array = obj.split('.'); + var leObj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + leObj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(leObj != null) { + var arr:Array = PlayState.instance.runtimeShaders.get(shader); + leObj.shader = new FlxRuntimeShader(arr[0], arr[1]); + return true; + } + #else + FunkinLua.luaTrace("setSpriteShader: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + return false; + }); + LuaUtils.addFunction(lua, "removeSpriteShader", function(obj:String) { + var killMe:Array = obj.split('.'); + var leObj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + leObj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(leObj != null) { + leObj.shader = null; + return true; + } + return false; + }); + + LuaUtils.addFunction(lua, "setShaderCamera", function(camera:String, shader:String) { + if(!ClientPrefs.shaders) { + FunkinLua.luaTrace('setShaderCamera: Shaders are disabled!', false, false, FlxColor.RED); + return; + } + + #if (!flash && sys) + var cam:FlxCamera = FunkinLua.cameraFromString(camera); + if(cam == null) { + FunkinLua.luaTrace('setShaderCamera: Camera not found: $camera', false, false, FlxColor.RED); + return; + } + + if(shader == null || shader.trim() == '') { + cam.filters = []; + if(PlayState.instance.cameraShaders.exists(camera)) + PlayState.instance.cameraShaders.remove(camera); + return; + } + + if(!PlayState.instance.runtimeShaders.exists(shader)) { + FunkinLua.luaTrace('setShaderCamera: Shader not found: $shader', false, false, FlxColor.RED); + return; + } + + var arr:Array = PlayState.instance.runtimeShaders.get(shader); + var runtimeShader:FlxRuntimeShader = new FlxRuntimeShader(arr[0], arr[1]); + cam.filters = [new ShaderFilter(runtimeShader)]; + PlayState.instance.cameraShaders.set(camera, runtimeShader); + #else + FunkinLua.luaTrace("setShaderCamera: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + + + LuaUtils.addFunction(lua, "getShaderBool", function(obj:String, prop:String) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if (shader == null) + { + Lua.pushnil(lua); + return null; + } + return shader.getBool(prop); + #else + FunkinLua.luaTrace("getShaderBool: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + #end + }); + LuaUtils.addFunction(lua, "getShaderBoolArray", function(obj:String, prop:String) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if (shader == null) + { + Lua.pushnil(lua); + return null; + } + return shader.getBoolArray(prop); + #else + FunkinLua.luaTrace("getShaderBoolArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + #end + }); + LuaUtils.addFunction(lua, "getShaderInt", function(obj:String, prop:String) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if (shader == null) + { + Lua.pushnil(lua); + return null; + } + return shader.getInt(prop); + #else + FunkinLua.luaTrace("getShaderInt: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + #end + }); + LuaUtils.addFunction(lua, "getShaderIntArray", function(obj:String, prop:String) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if (shader == null) + { + Lua.pushnil(lua); + return null; + } + return shader.getIntArray(prop); + #else + FunkinLua.luaTrace("getShaderIntArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + #end + }); + LuaUtils.addFunction(lua, "getShaderFloat", function(obj:String, prop:String) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if (shader == null) + { + Lua.pushnil(lua); + return null; + } + return shader.getFloat(prop); + #else + FunkinLua.luaTrace("getShaderFloat: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + #end + }); + LuaUtils.addFunction(lua, "getShaderFloatArray", function(obj:String, prop:String) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if (shader == null) + { + Lua.pushnil(lua); + return null; + } + return shader.getFloatArray(prop); + #else + FunkinLua.luaTrace("getShaderFloatArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + #end + }); + + + LuaUtils.addFunction(lua, "setShaderBool", function(obj:String, prop:String, value:Bool) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if(shader == null) return; + + shader.setBool(prop, value); + #else + FunkinLua.luaTrace("setShaderBool: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + LuaUtils.addFunction(lua, "setShaderBoolArray", function(obj:String, prop:String, values:Dynamic) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if(shader == null) return; + + shader.setBoolArray(prop, values); + #else + FunkinLua.luaTrace("setShaderBoolArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + LuaUtils.addFunction(lua, "setShaderInt", function(obj:String, prop:String, value:Int) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if(shader == null) return; + + shader.setInt(prop, value); + #else + FunkinLua.luaTrace("setShaderInt: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + LuaUtils.addFunction(lua, "setShaderIntArray", function(obj:String, prop:String, values:Dynamic) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if(shader == null) return; + + shader.setIntArray(prop, values); + #else + FunkinLua.luaTrace("setShaderIntArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + LuaUtils.addFunction(lua, "setShaderFloat", function(obj:String, prop:String, value:Float) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if(shader == null) return; + + shader.setFloat(prop, value); + #else + FunkinLua.luaTrace("setShaderFloat: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + LuaUtils.addFunction(lua, "setShaderFloatArray", function(obj:String, prop:String, values:Dynamic) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if(shader == null) return; + + shader.setFloatArray(prop, values); + #else + FunkinLua.luaTrace("setShaderFloatArray: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + + LuaUtils.addFunction(lua, "setShaderSampler2D", function(obj:String, prop:String, bitmapdataPath:String) { + #if (!flash && sys) + var shader:FlxRuntimeShader = getShader(obj); + if(shader == null) return; + + // trace('bitmapdatapath: $bitmapdataPath'); + var value = Paths.image(bitmapdataPath); + if(value != null && value.bitmap != null) + { + // trace('Found bitmapdata. Width: ${value.bitmap.width} Height: ${value.bitmap.height}'); + shader.setSampler2D(prop, value.bitmap); + } + #else + FunkinLua.luaTrace("setShaderSampler2D: Platform unsupported for Runtime Shaders!", false, false, FlxColor.RED); + #end + }); + } + + static function initLuaShader(name:String, ?glslVersion:Int = 120) + { + if(!ClientPrefs.shaders) return false; + + #if (!flash && sys) + if(PlayState.instance.runtimeShaders.exists(name)) + { + FunkinLua.luaTrace('Shader $name was already initialized!'); + return true; + } + + var foldersToCheck:Array = [Paths.getPreloadPath('shaders/') #if MODS_ALLOWED , Mods.getModPath('shaders/') #end]; + + #if MODS_ALLOWED + if(Mods.currentModDirectory != null && Mods.currentModDirectory.length > 0) + foldersToCheck.insert(0, Mods.getModPath(Mods.currentModDirectory + '/shaders/')); + + for(mod in Mods.getGlobalMods()) + foldersToCheck.insert(0, Mods.getModPath(mod + '/shaders/')); + #end + + for (folder in foldersToCheck) + { + if(FileSystem.exists(folder)) + { + var frag:String = folder + name + '.frag'; + var vert:String = folder + name + '.vert'; + var found:Bool = false; + if(FileSystem.exists(frag)) + { + frag = File.getContent(frag); + found = true; + } + else frag = null; + + if(FileSystem.exists(vert)) + { + vert = File.getContent(vert); + found = true; + } + else vert = null; + + if(found) + { + PlayState.instance.runtimeShaders.set(name, [frag, vert]); + //trace('Found shader $name!'); + return true; + } + } + } + FunkinLua.luaTrace('Missing shader $name .frag AND .vert files!', false, false, FlxColor.RED); + #else + FunkinLua.luaTrace('This platform doesn\'t support Runtime Shaders!', false, false, FlxColor.RED); + #end + return false; + } + + #if (!flash && sys) + static function getShader(obj:String):FlxRuntimeShader + { + var killMe:Array = obj.split('.'); + var leObj:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + leObj = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(leObj != null) { + var shader:Dynamic = leObj.shader; + var shader:FlxRuntimeShader = shader; + return shader; + } + return null; + } + #end +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaSound.hx b/source/game/scripting/lua/LuaSound.hx new file mode 100644 index 0000000..60dd534 --- /dev/null +++ b/source/game/scripting/lua/LuaSound.hx @@ -0,0 +1,111 @@ +package game.scripting.lua; + +class LuaSound +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "playMusic", function(sound:String, volume:Float = 1, loop:Bool = false) { + FlxG.sound.playMusic(Paths.music(sound), volume, loop); + }); + LuaUtils.addFunction(lua, "playSound", function(sound:String, volume:Float = 1, ?tag:String = null) { + if(tag != null && tag.length > 0) { + tag = tag.replace('.', ''); + if(PlayState.instance.modchartSounds.exists(tag)) { + PlayState.instance.modchartSounds.get(tag).stop(); + } + PlayState.instance.modchartSounds.set(tag, FlxG.sound.play(Paths.sound(sound), volume, false, function() { + PlayState.instance.modchartSounds.remove(tag); + PlayState.instance.callOnLuas('onSoundFinished', [tag]); + })); + return; + } + FlxG.sound.play(Paths.sound(sound), volume); + }); + LuaUtils.addFunction(lua, "stopSound", function(tag:String) { + if(tag != null && tag.length > 1 && PlayState.instance.modchartSounds.exists(tag)) { + PlayState.instance.modchartSounds.get(tag).stop(); + PlayState.instance.modchartSounds.remove(tag); + } + }); + LuaUtils.addFunction(lua, "pauseSound", function(tag:String) { + if(tag != null && tag.length > 1 && PlayState.instance.modchartSounds.exists(tag)) { + PlayState.instance.modchartSounds.get(tag).pause(); + } + }); + LuaUtils.addFunction(lua, "resumeSound", function(tag:String) { + if(tag != null && tag.length > 1 && PlayState.instance.modchartSounds.exists(tag)) { + PlayState.instance.modchartSounds.get(tag).play(); + } + }); + LuaUtils.addFunction(lua, "soundFadeIn", function(tag:String, duration:Float, fromValue:Float = 0, toValue:Float = 1) { + if(tag == null || tag.length < 1) { + FlxG.sound.music.fadeIn(duration, fromValue, toValue); + } else if(PlayState.instance.modchartSounds.exists(tag)) { + PlayState.instance.modchartSounds.get(tag).fadeIn(duration, fromValue, toValue); + } + + }); + LuaUtils.addFunction(lua, "soundFadeOut", function(tag:String, duration:Float, toValue:Float = 0) { + if(tag == null || tag.length < 1) { + FlxG.sound.music.fadeOut(duration, toValue); + } else if(PlayState.instance.modchartSounds.exists(tag)) { + PlayState.instance.modchartSounds.get(tag).fadeOut(duration, toValue); + } + }); + LuaUtils.addFunction(lua, "soundFadeCancel", function(tag:String) { + if(tag == null || tag.length < 1) { + if(FlxG.sound.music.fadeTween != null) { + FlxG.sound.music.fadeTween.cancel(); + } + } else if(PlayState.instance.modchartSounds.exists(tag)) { + var theSound:FlxSound = PlayState.instance.modchartSounds.get(tag); + if(theSound.fadeTween != null) { + theSound.fadeTween.cancel(); + PlayState.instance.modchartSounds.remove(tag); + } + } + }); + LuaUtils.addFunction(lua, "getSoundVolume", function(tag:String) { + if(tag == null || tag.length < 1) { + if(FlxG.sound.music != null) { + return FlxG.sound.music.volume; + } + } else if(PlayState.instance.modchartSounds.exists(tag)) { + return PlayState.instance.modchartSounds.get(tag).volume; + } + return 0; + }); + LuaUtils.addFunction(lua, "setSoundVolume", function(tag:String, value:Float) { + if(tag == null || tag.length < 1) { + if(FlxG.sound.music != null) { + FlxG.sound.music.volume = value; + } + } else if(PlayState.instance.modchartSounds.exists(tag)) { + PlayState.instance.modchartSounds.get(tag).volume = value; + } + }); + LuaUtils.addFunction(lua, "getSoundTime", function(tag:String) { + if(tag != null && tag.length > 0 && PlayState.instance.modchartSounds.exists(tag)) { + return PlayState.instance.modchartSounds.get(tag).time; + } + return 0; + }); + LuaUtils.addFunction(lua, "setSoundTime", function(tag:String, value:Float) { + if(tag != null && tag.length > 0 && PlayState.instance.modchartSounds.exists(tag)) { + var theSound:FlxSound = PlayState.instance.modchartSounds.get(tag); + if(theSound != null) { + var wasResumed:Bool = theSound.playing; + theSound.pause(); + theSound.time = value; + if(wasResumed) theSound.play(); + } + } + }); + + LuaUtils.addFunction(lua, "luaSoundExists", function(tag:String) { + return PlayState.instance.modchartSounds.exists(tag); + }); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaSprites.hx b/source/game/scripting/lua/LuaSprites.hx new file mode 100644 index 0000000..7fe3eb8 --- /dev/null +++ b/source/game/scripting/lua/LuaSprites.hx @@ -0,0 +1,480 @@ +package game.scripting.lua; + +#if flixel_animate +import animate.internal.Timeline; +import animate.FlxAnimateJson.TimelineJson; +#end + +import flixel.graphics.FlxGraphic; + +import game.objects.Character; + +class LuaSprites +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "makeLuaSprite", function(tag:String, image:String, x:Float, y:Float) { + tag = tag.replace('.', ''); + resetSpriteTag(tag); + var leSprite:ModchartSprite = new ModchartSprite(x, y); + if(image != null && image.length > 0) + { + leSprite.loadGraphic(Paths.image(image)); + } + leSprite.antialiasing = ClientPrefs.globalAntialiasing; + PlayState.instance.modchartSprites.set(tag, leSprite); + leSprite.active = true; + }); + + LuaUtils.addFunction(lua, "makeLuaBackdrop", function(tag:String, image:String, x:Float, y:Float, repeatX:Bool = false, repeatY:Bool = false) { + tag = tag.replace('.', ''); + resetSpriteTag(tag); + + var backdrop = new ModchartBackdrop(Paths.image(image), 1, 1, repeatX, repeatY); + backdrop.setPosition(x, y); + backdrop.antialiasing = ClientPrefs.globalAntialiasing; + + PlayState.instance.modchartBackdrops.set(tag, backdrop); + backdrop.active = true; + }); + + LuaUtils.addFunction(lua, "makeAnimatedLuaSprite", function(tag:String, image:String, x:Float, y:Float, ?spriteType:String = "sparrow") { + tag = tag.replace('.', ''); + resetSpriteTag(tag); + var leSprite:ModchartSprite = new ModchartSprite(x, y); + + loadFrames(leSprite, image, spriteType); + leSprite.antialiasing = ClientPrefs.globalAntialiasing; + PlayState.instance.modchartSprites.set(tag, leSprite); + }); + + LuaUtils.addFunction(lua, "makeGraphic", function(obj:String, width:Int, height:Int, color:String) { + var colorNum:Int = Std.parseInt(color); + if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); + + var spr:FlxSprite = PlayState.instance.getLuaObject(obj,false); + if(spr!=null) { + PlayState.instance.getLuaObject(obj,false).makeGraphic(width, height, colorNum); + return; + } + + var object:FlxSprite = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(object != null) { + object.makeGraphic(width, height, colorNum); + } + }); + LuaUtils.addFunction(lua, "addAnimationByPrefix", function(obj:String, name:String, prefix:String, framerate:Int = 24, loop:Bool = true) { + if(PlayState.instance.getLuaObject(obj,false)!=null) { + var cock:FlxSprite = PlayState.instance.getLuaObject(obj,false); + cock.animation.addByPrefix(name, prefix, framerate, loop); + if(cock.animation.curAnim == null) { + cock.animation.play(name, true); + } + return; + } + + var cock:FlxSprite = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(cock != null) { + cock.animation.addByPrefix(name, prefix, framerate, loop); + if(cock.animation.curAnim == null) { + cock.animation.play(name, true); + } + } + }); + + LuaUtils.addFunction(lua, "addAnimation", function(obj:String, name:String, frames:Array, framerate:Int = 24, loop:Bool = true) { + if(PlayState.instance.getLuaObject(obj,false)!=null) { + var cock:FlxSprite = PlayState.instance.getLuaObject(obj,false); + cock.animation.add(name, frames, framerate, loop); + if(cock.animation.curAnim == null) { + cock.animation.play(name, true); + } + return; + } + + var cock:FlxSprite = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(cock != null) { + cock.animation.add(name, frames, framerate, loop); + if(cock.animation.curAnim == null) { + cock.animation.play(name, true); + } + } + }); + + LuaUtils.addFunction(lua, "addAnimationByIndices", function(obj:String, name:String, prefix:String, indices:String, framerate:Int = 24) { + return addAnimByIndices(obj, name, prefix, indices, framerate, false); + }); + LuaUtils.addFunction(lua, "addAnimationByIndicesLoop", function(obj:String, name:String, prefix:String, indices:String, framerate:Int = 24) { + return addAnimByIndices(obj, name, prefix, indices, framerate, true); + }); + + + LuaUtils.addFunction(lua, "playAnim", function(obj:String, name:String, forced:Bool = false, ?reverse:Bool = false, ?startFrame:Int = 0) + { + var obj:Dynamic = FunkinLua.getObjectDirectly(obj, false); + if(obj.playAnim != null) + { + obj.playAnim(name, forced, reverse, startFrame); + return true; + } + else + { + if(obj.anim != null) obj.anim.play(name, forced, reverse, startFrame); //FlxAnimate + else obj.animation.play(name, forced, reverse, startFrame); + return true; + } + return false; + }); + LuaUtils.addFunction(lua, "addOffset", function(obj:String, anim:String, x:Float, y:Float) { + if(PlayState.instance.modchartSprites.exists(obj)) { + PlayState.instance.modchartSprites.get(obj).animOffsets.set(anim, [x, y]); + return true; + } + + var char:Character = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(char != null) { + char.addOffset(anim, x, y); + return true; + } + return false; + }); + + //FlxAnimate Funcs + #if flixel_animate + LuaUtils.addFunction(lua, "makeFlxAnimateSprite", function(tag:String, atlasFolder:String, ?x:Float = 0, ?y:Float = 0) { + tag = tag.replace('.', ''); + var lastSprite = PlayState.instance.variables.get(tag); + if(lastSprite != null) + { + lastSprite.kill(); + PlayState.instance.remove(lastSprite); + lastSprite.destroy(); + } + + var mySprite:ModchartAnimateSprite = new ModchartAnimateSprite(x, y); + mySprite.frames = Paths.getAnimateAtlas(atlasFolder); + PlayState.instance.variables.set(tag, mySprite); + mySprite.active = true; + }); + + LuaUtils.addFunction(lua, "addAnimationBySymbol", function(tag:String, name:String, symbol:String, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) + { + var obj:Dynamic = PlayState.instance.variables.get(tag); + if(cast (obj, FlxAnimate) == null) return false; + + obj.anim.addBySymbol(name, symbol, framerate, loop, flipX, flipY); + if(obj.anim.lastPlayedAnim == null) + { + if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite + else obj.animation.play(name, true); + } + return true; + }); + + LuaUtils.addFunction(lua, "addAnimationBySymbolIndices", function(tag:String, name:String, symbol:String, ?indices:Any = null, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) + { + var obj:Dynamic = PlayState.instance.variables.get(tag); + if(cast (obj, FlxAnimate) == null) return false; + + if(indices == null) + indices = [0]; + else if(Std.isOfType(indices, String)) + { + var strIndices:Array = cast (indices, String).trim().split(','); + var myIndices:Array = []; + for (i in 0...strIndices.length) { + myIndices.push(Std.parseInt(strIndices[i])); + } + indices = myIndices; + } + + obj.anim.addBySymbolIndices(name, symbol, indices, framerate, loop, flipX, flipY); + if(obj.anim.lastPlayedAnim == null) + { + if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite + else obj.animation.play(name, true); + } + return true; + }); + + LuaUtils.addFunction(lua, "addAnimationByFrameLabel", function(tag:String, name:String, label:String, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) + { + var obj:Dynamic = PlayState.instance.variables.get(tag); + if(cast (obj, FlxAnimate) == null) return false; + + obj.anim.addByFrameLabel(name, label, framerate, loop, flipX, flipY); + if(obj.anim.lastPlayedAnim == null) + { + if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite + else obj.animation.play(name, true); + } + return true; + }); + + LuaUtils.addFunction(lua, "addAnimationByFrameLabelIndices", function(tag:String, name:String, label:String, ?indices:Any = null, ?framerate:Float = 24, ?loop:Bool = false, ?flipX:Bool = false, ?flipY:Bool = false) + { + var obj:Dynamic = PlayState.instance.variables.get(tag); + if(cast (obj, FlxAnimate) == null) return false; + + if(indices == null) + indices = [0]; + else if(Std.isOfType(indices, String)) + { + var strIndices:Array = cast (indices, String).trim().split(','); + var myIndices:Array = []; + for (i in 0...strIndices.length) { + myIndices.push(Std.parseInt(strIndices[i])); + } + indices = myIndices; + } + + obj.anim.addByFrameLabelIndices(name, label, indices, framerate, loop, flipX, flipY); + if(obj.anim.lastPlayedAnim == null) + { + if(obj.playAnim != null) obj.playAnim(name, true); //is ModchartAnimateSprite + else obj.animation.play(name, true); + } + return true; + }); + + LuaUtils.addFunction(lua, "addByTimeline", function(tag:String, name:String, timelinePath:String, ?framerate:Float = 24, ?loop:Bool = true, ?flipX:Bool = false, ?flipY:Bool = false) { + var obj:Dynamic = PlayState.instance.variables.get(tag); + if(cast (obj, FlxAnimate) == null) return false; + + var timeline:Timeline = loadTimelineFromJson(timelinePath); + if(timeline == null) { + FunkinLua.luaTrace('addByTimeline: Timeline not found: $timelinePath', false, false, FlxColor.RED); + return false; + } + + obj.anim.addByTimeline(name, timeline, framerate, loop, flipX, flipY); + return true; + }); + + LuaUtils.addFunction(lua, "addByTimelineIndices", function(tag:String, name:String, timelinePath:String, indices:Any = null, ?framerate:Float = 24, ?loop:Bool = true, ?flipX:Bool = false, ?flipY:Bool = false) { + var obj:Dynamic = PlayState.instance.variables.get(tag); + if(cast (obj, FlxAnimate) == null) return false; + + var timeline:Timeline = loadTimelineFromJson(timelinePath); + if(timeline == null) { + FunkinLua.luaTrace('addByTimelineIndices: Timeline not found: $timelinePath', false, false, FlxColor.RED); + return false; + } + + var idxArray:Array = []; + if(Std.isOfType(indices, String)) { + var strIndices = cast(indices, String).split(','); + for(i in strIndices) idxArray.push(Std.parseInt(i.trim())); + } else if(Std.isOfType(indices, Array)) { + idxArray = cast indices; + } + + obj.anim.addByTimelineIndices(name, timeline, idxArray, framerate, loop, flipX, flipY); + return true; + }); + #end + + LuaUtils.addFunction(lua, "addLuaSprite", function(tag:String, front:Bool = false) { + if(PlayState.instance.modchartSprites.exists(tag)) { + var shit:ModchartSprite = PlayState.instance.modchartSprites.get(tag); + if(!shit.wasAdded) { + if(front) + { + FunkinLua.getInstance().add(shit); + } + else + { + if(PlayState.instance.isDead) + { + GameOverSubstate.instance.insert(GameOverSubstate.instance.members.indexOf(GameOverSubstate.instance.boyfriend), shit); + } + else + { + var position:Int = PlayState.instance.members.indexOf(PlayState.instance.gfGroup); + if(PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup) < position) { + position = PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup); + } else if(PlayState.instance.members.indexOf(PlayState.instance.dadGroup) < position) { + position = PlayState.instance.members.indexOf(PlayState.instance.dadGroup); + } + PlayState.instance.insert(position, shit); + } + } + shit.wasAdded = true; + //trace('added a thing: ' + tag); + } + } + }); + + LuaUtils.addFunction(lua, "addLuaBackdrop", function(tag:String, front:Bool = false) { + if (!PlayState.instance.modchartBackdrops.exists(tag)) return; + + final backdrop = PlayState.instance.modchartBackdrops.get(tag); + if (backdrop == null) return; + + if (!Reflect.hasField(backdrop, "wasAdded")) { + Reflect.setProperty(backdrop, "wasAdded", false); + } + + if (!Reflect.field(backdrop, "wasAdded")) { + if (front) { + FunkinLua.getInstance().add(backdrop); + } else { + if (PlayState.instance.isDead) { + GameOverSubstate.instance.insert( + GameOverSubstate.instance.members.indexOf(GameOverSubstate.instance.boyfriend), + backdrop + ); + } else { + var position:Int = PlayState.instance.members.indexOf(PlayState.instance.gfGroup); + if (PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup) < position) + position = PlayState.instance.members.indexOf(PlayState.instance.boyfriendGroup); + if (PlayState.instance.members.indexOf(PlayState.instance.dadGroup) < position) + position = PlayState.instance.members.indexOf(PlayState.instance.dadGroup); + PlayState.instance.insert(position, backdrop); + } + } + Reflect.setProperty(backdrop, "wasAdded", true); + } + }); + + LuaUtils.addFunction(lua, "removeLuaSprite", function(tag:String, destroy:Bool = true) { + if(!PlayState.instance.modchartSprites.exists(tag)) { + return; + } + + var pee:ModchartSprite = PlayState.instance.modchartSprites.get(tag); + if(destroy) { + pee.kill(); + } + + if(pee.wasAdded) { + FunkinLua.getInstance().remove(pee, true); + pee.wasAdded = false; + } + + if(destroy) { + pee.destroy(); + PlayState.instance.modchartSprites.remove(tag); + } + }); + + LuaUtils.addFunction(lua, "luaSpriteExists", function(tag:String) { + return PlayState.instance.modchartSprites.exists(tag); + }); + + LuaUtils.addFunction(lua, "loadGraphic", function(variable:String, image:String, ?gridX:Int = 0, ?gridY:Int = 0) { + var killMe:Array = variable.split('.'); + var spr:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + var animated = gridX != 0 || gridY != 0; + + if(killMe.length > 1) { + spr = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(spr != null && image != null && image.length > 0) + { + spr.loadGraphic(Paths.image(image), animated, gridX, gridY); + } + }); + LuaUtils.addFunction(lua, "loadFrames", function(variable:String, image:String, spriteType:String = "sparrow") { + var killMe:Array = variable.split('.'); + var spr:FlxSprite = FunkinLua.getObjectDirectly(killMe[0]); + if(killMe.length > 1) { + spr = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(killMe), killMe[killMe.length-1]); + } + + if(spr != null && image != null && image.length > 0) + { + loadFrames(spr, image, spriteType); + } + }); + } + + static function resetSpriteTag(tag:String) { + if(!PlayState.instance.modchartSprites.exists(tag)) { + return; + } + + var pee:ModchartSprite = PlayState.instance.modchartSprites.get(tag); + pee.kill(); + if(pee.wasAdded) { + PlayState.instance.remove(pee, true); + } + pee.destroy(); + PlayState.instance.modchartSprites.remove(tag); + } + + static function loadFrames(spr:FlxSprite, image:String, spriteType:String) + { + switch(spriteType.toLowerCase().trim()) + { + /*case "texture" | "textureatlas" | "tex": + spr.frames = AtlasFrameMaker.construct(image); + + case "texture_noaa" | "textureatlas_noaa" | "tex_noaa": + spr.frames = AtlasFrameMaker.construct(image, null, true);*/ + + case 'aseprite', 'ase', 'json', 'jsoni8': + spr.frames = Paths.getAsepriteAtlas(image); + + case "packer" | "packeratlas" | "pac": + spr.frames = Paths.getPackerAtlas(image); + + default: + spr.frames = Paths.getSparrowAtlas(image); + } + } + + static function addAnimByIndices(obj:String, name:String, prefix:String, indices:String, framerate:Int = 24, loop:Bool = false) + { + var strIndices:Array = indices.trim().split(','); + var die:Array = []; + for (i in 0...strIndices.length) { + die.push(Std.parseInt(strIndices[i])); + } + + if(PlayState.instance.getLuaObject(obj, false)!=null) { + var pussy:FlxSprite = PlayState.instance.getLuaObject(obj, false); + pussy.animation.addByIndices(name, prefix, die, '', framerate, loop); + if(pussy.animation.curAnim == null) { + pussy.animation.play(name, true); + } + return true; + } + + var pussy:FlxSprite = Reflect.getProperty(FunkinLua.getInstance(), obj); + if(pussy != null) { + pussy.animation.addByIndices(name, prefix, die, '', framerate, loop); + if(pussy.animation.curAnim == null) { + pussy.animation.play(name, true); + } + return true; + } + return false; + } + + #if flixel_animate + static function loadTimelineFromJson(path:String):Timeline { + #if MODS_ALLOWED + var rawJson:String = Paths.getTextFromFile(path); + #else + var rawJson:String = Assets.getText(Paths.json(path)); + #end + + if(rawJson != null && rawJson.length > 0) { + try { + var json:TimelineJson = haxe.Json.parse(rawJson); + // This ass pissed me off tbh + var dummyGraphic = FlxGraphic.fromRectangle(1, 1, FlxColor.TRANSPARENT); + var dummyParent = new FlxAnimateFrames(dummyGraphic); + return new Timeline(json, dummyParent, path); + } catch(e:Dynamic) { + trace('Error parsing timeline JSON: $e'); + } + } + return null; + } + #end +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaText.hx b/source/game/scripting/lua/LuaText.hx new file mode 100644 index 0000000..c0cdee7 --- /dev/null +++ b/source/game/scripting/lua/LuaText.hx @@ -0,0 +1,204 @@ +package game.scripting.lua; + +class LuaText +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "makeLuaText", function(tag:String, text:String, width:Int, x:Float, y:Float) { + tag = tag.replace('.', ''); + resetTextTag(tag); + var leText:ModchartText = new ModchartText(x, y, text, width); + PlayState.instance.modchartTexts.set(tag, leText); + }); + + LuaUtils.addFunction(lua, "setTextString", function(tag:String, text:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + obj.text = text; + return true; + } + FunkinLua.luaTrace("setTextString: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setTextSize", function(tag:String, size:Int) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + obj.size = size; + return true; + } + FunkinLua.luaTrace("setTextSize: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setTextWidth", function(tag:String, width:Float) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + obj.fieldWidth = width; + return true; + } + FunkinLua.luaTrace("setTextWidth: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setTextBorder", function(tag:String, size:Int, color:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + var colorNum:Int = Std.parseInt(color); + if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); + + obj.borderSize = size; + obj.borderColor = colorNum; + return true; + } + FunkinLua.luaTrace("setTextBorder: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setTextColor", function(tag:String, color:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + var colorNum:Int = Std.parseInt(color); + if(!color.startsWith('0x')) colorNum = Std.parseInt('0xff' + color); + + obj.color = colorNum; + return true; + } + FunkinLua.luaTrace("setTextColor: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setTextFont", function(tag:String, newFont:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + obj.font = Paths.font(newFont); + return true; + } + FunkinLua.luaTrace("setTextFont: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setTextItalic", function(tag:String, italic:Bool) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + obj.italic = italic; + return true; + } + FunkinLua.luaTrace("setTextItalic: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + LuaUtils.addFunction(lua, "setTextAlignment", function(tag:String, alignment:String = 'left') { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + obj.alignment = LEFT; + switch(alignment.trim().toLowerCase()) + { + case 'right': + obj.alignment = RIGHT; + case 'center': + obj.alignment = CENTER; + } + return true; + } + FunkinLua.luaTrace("setTextAlignment: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return false; + }); + + LuaUtils.addFunction(lua, "getTextString", function(tag:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null && obj.text != null) + { + return obj.text; + } + FunkinLua.luaTrace("getTextString: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + }); + LuaUtils.addFunction(lua, "getTextSize", function(tag:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + return obj.size; + } + FunkinLua.luaTrace("getTextSize: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return -1; + }); + LuaUtils.addFunction(lua, "getTextFont", function(tag:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + return obj.font; + } + FunkinLua.luaTrace("getTextFont: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + Lua.pushnil(lua); + return null; + }); + LuaUtils.addFunction(lua, "getTextWidth", function(tag:String) { + var obj:FlxText = getTextObject(tag); + if(obj != null) + { + return obj.fieldWidth; + } + FunkinLua.luaTrace("getTextWidth: Object " + tag + " doesn't exist!", false, false, FlxColor.RED); + return 0; + }); + + LuaUtils.addFunction(lua, "addLuaText", function(tag:String) { + if(PlayState.instance.modchartTexts.exists(tag)) { + var shit:ModchartText = PlayState.instance.modchartTexts.get(tag); + if(!shit.wasAdded) { + FunkinLua.getInstance().add(shit); + shit.wasAdded = true; + //trace('added a thing: ' + tag); + } + } + }); + LuaUtils.addFunction(lua, "removeLuaText", function(tag:String, destroy:Bool = true) { + if(!PlayState.instance.modchartTexts.exists(tag)) { + return; + } + + var pee:ModchartText = PlayState.instance.modchartTexts.get(tag); + if(destroy) { + pee.kill(); + } + + if(pee.wasAdded) { + FunkinLua.getInstance().remove(pee, true); + pee.wasAdded = false; + } + + if(destroy) { + pee.destroy(); + PlayState.instance.modchartTexts.remove(tag); + } + }); + + LuaUtils.addFunction(lua, "luaTextExists", function(tag:String) { + return PlayState.instance.modchartTexts.exists(tag); + }); + } + + static function resetTextTag(tag:String) { + if(!PlayState.instance.modchartTexts.exists(tag)) { + return; + } + + var pee:ModchartText = PlayState.instance.modchartTexts.get(tag); + pee.kill(); + if(pee.wasAdded) { + PlayState.instance.remove(pee, true); + } + pee.destroy(); + PlayState.instance.modchartTexts.remove(tag); + } + + inline static function getTextObject(name:String):FlxText + { + return PlayState.instance.modchartTexts.exists(name) ? PlayState.instance.modchartTexts.get(name) : Reflect.getProperty(PlayState.instance, name); + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaTimer.hx b/source/game/scripting/lua/LuaTimer.hx new file mode 100644 index 0000000..7423869 --- /dev/null +++ b/source/game/scripting/lua/LuaTimer.hx @@ -0,0 +1,32 @@ +package game.scripting.lua; + +class LuaTimer +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "runTimer", function(tag:String, time:Float = 1, loops:Int = 1) { + cancelTimer(tag); + PlayState.instance.modchartTimers.set(tag, new FlxTimer().start(time, function(tmr:FlxTimer) { + if(tmr.finished) { + PlayState.instance.modchartTimers.remove(tag); + } + PlayState.instance.callOnLuas('onTimerCompleted', [tag, tmr.loops, tmr.loopsLeft]); + //trace('Timer Completed: ' + tag); + }, loops)); + }); + LuaUtils.addFunction(lua, "cancelTimer", function(tag:String) { + cancelTimer(tag); + }); + } + + static function cancelTimer(tag:String) { + if(PlayState.instance.modchartTimers.exists(tag)) { + var theTimer:FlxTimer = PlayState.instance.modchartTimers.get(tag); + theTimer.cancel(); + theTimer.destroy(); + PlayState.instance.modchartTimers.remove(tag); + } + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/LuaTween.hx b/source/game/scripting/lua/LuaTween.hx new file mode 100644 index 0000000..a42ca1e --- /dev/null +++ b/source/game/scripting/lua/LuaTween.hx @@ -0,0 +1,246 @@ +package game.scripting.lua; + +import game.objects.StrumNote; + +class LuaTween +{ + public static function init(script:FunkinLua) + { + var lua:cpp.RawPointer = script.lua; + + LuaUtils.addFunction(lua, "doTweenX", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { + var penisExam:Dynamic = tweenShit(tag, vars); + if(penisExam != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {x: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } else { + FunkinLua.luaTrace('doTweenX: Couldnt find object: ' + vars, false, false, FlxColor.RED); + } + }); + LuaUtils.addFunction(lua, "doTweenY", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { + var penisExam:Dynamic = tweenShit(tag, vars); + if(penisExam != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {y: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } else { + FunkinLua.luaTrace('doTweenY: Couldnt find object: ' + vars, false, false, FlxColor.RED); + } + }); + LuaUtils.addFunction(lua, "doTweenAngle", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { + var penisExam:Dynamic = tweenShit(tag, vars); + if(penisExam != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {angle: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } else { + FunkinLua.luaTrace('doTweenAngle: Couldnt find object: ' + vars, false, false, FlxColor.RED); + } + }); + LuaUtils.addFunction(lua, "doTweenAlpha", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { + var penisExam:Dynamic = tweenShit(tag, vars); + if(penisExam != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {alpha: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } else { + FunkinLua.luaTrace('doTweenAlpha: Couldnt find object: ' + vars, false, false, FlxColor.RED); + } + }); + LuaUtils.addFunction(lua, "doTweenZoom", function(tag:String, vars:String, value:Dynamic, duration:Float, ease:String) { + var penisExam:Dynamic = tweenShit(tag, vars); + if(penisExam != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(penisExam, {zoom: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } else { + FunkinLua.luaTrace('doTweenZoom: Couldnt find object: ' + vars, false, false, FlxColor.RED); + } + }); + LuaUtils.addFunction(lua, "doTweenColor", function(tag:String, vars:String, targetColor:String, duration:Float, ease:String) { + var penisExam:Dynamic = tweenShit(tag, vars); + if(penisExam != null) { + var color:Int = Std.parseInt(targetColor); + if(!targetColor.startsWith('0x')) color = Std.parseInt('0xff' + targetColor); + + var curColor:FlxColor = penisExam.color; + curColor.alphaFloat = penisExam.alpha; + PlayState.instance.modchartTweens.set(tag, FlxTween.color(penisExam, duration, curColor, color, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.modchartTweens.remove(tag); + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + } + })); + } else { + FunkinLua.luaTrace('doTweenColor: Couldnt find object: ' + vars, false, false, FlxColor.RED); + } + }); + + //Tween shit, but for strums + LuaUtils.addFunction(lua, "noteTweenX", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { + cancelTween(tag); + if(note < 0) note = 0; + var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; + + if(testicle != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {x: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } + }); + LuaUtils.addFunction(lua, "noteTweenY", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { + cancelTween(tag); + if(note < 0) note = 0; + var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; + + if(testicle != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {y: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } + }); + LuaUtils.addFunction(lua, "noteTweenAngle", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { + cancelTween(tag); + if(note < 0) note = 0; + var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; + + if(testicle != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {angle: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } + }); + LuaUtils.addFunction(lua, "noteTweenDirection", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { + cancelTween(tag); + if(note < 0) note = 0; + var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; + + if(testicle != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {direction: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } + }); + LuaUtils.addFunction(lua, "noteTweenAngle", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { + cancelTween(tag); + if(note < 0) note = 0; + var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; + + if(testicle != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {angle: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } + }); + LuaUtils.addFunction(lua, "noteTweenAlpha", function(tag:String, note:Int, value:Dynamic, duration:Float, ease:String) { + cancelTween(tag); + if(note < 0) note = 0; + var testicle:StrumNote = PlayState.instance.strumLineNotes.members[note % PlayState.instance.strumLineNotes.length]; + + if(testicle != null) { + PlayState.instance.modchartTweens.set(tag, FlxTween.tween(testicle, {alpha: value}, duration, {ease: getFlxEaseByString(ease), + onComplete: function(twn:FlxTween) { + PlayState.instance.callOnLuas('onTweenCompleted', [tag]); + PlayState.instance.modchartTweens.remove(tag); + } + })); + } + }); + + LuaUtils.addFunction(lua, "cancelTween", function(tag:String) { + cancelTween(tag); + }); + } + + static function cancelTween(tag:String) { + if(PlayState.instance.modchartTweens.exists(tag)) { + PlayState.instance.modchartTweens.get(tag).cancel(); + PlayState.instance.modchartTweens.get(tag).destroy(); + PlayState.instance.modchartTweens.remove(tag); + } + } + + static function tweenShit(tag:String, vars:String) { + cancelTween(tag); + var variables:Array = vars.split('.'); + var sexyProp:Dynamic = FunkinLua.getObjectDirectly(variables[0]); + if(variables.length > 1) { + sexyProp = FunkinLua.getVarInArray(FunkinLua.getPropertyLoopThingWhatever(variables), variables[variables.length-1]); + } + return sexyProp; + } + + //Better optimized than using some getProperty shit or idk + static function getFlxEaseByString(?ease:String = '') { + switch(ease.toLowerCase().trim()) { + case 'backin': return FlxEase.backIn; + case 'backinout': return FlxEase.backInOut; + case 'backout': return FlxEase.backOut; + case 'bouncein': return FlxEase.bounceIn; + case 'bounceinout': return FlxEase.bounceInOut; + case 'bounceout': return FlxEase.bounceOut; + case 'circin': return FlxEase.circIn; + case 'circinout': return FlxEase.circInOut; + case 'circout': return FlxEase.circOut; + case 'cubein': return FlxEase.cubeIn; + case 'cubeinout': return FlxEase.cubeInOut; + case 'cubeout': return FlxEase.cubeOut; + case 'elasticin': return FlxEase.elasticIn; + case 'elasticinout': return FlxEase.elasticInOut; + case 'elasticout': return FlxEase.elasticOut; + case 'expoin': return FlxEase.expoIn; + case 'expoinout': return FlxEase.expoInOut; + case 'expoout': return FlxEase.expoOut; + case 'quadin': return FlxEase.quadIn; + case 'quadinout': return FlxEase.quadInOut; + case 'quadout': return FlxEase.quadOut; + case 'quartin': return FlxEase.quartIn; + case 'quartinout': return FlxEase.quartInOut; + case 'quartout': return FlxEase.quartOut; + case 'quintin': return FlxEase.quintIn; + case 'quintinout': return FlxEase.quintInOut; + case 'quintout': return FlxEase.quintOut; + case 'sinein': return FlxEase.sineIn; + case 'sineinout': return FlxEase.sineInOut; + case 'sineout': return FlxEase.sineOut; + case 'smoothstepin': return FlxEase.smoothStepIn; + case 'smoothstepinout': return FlxEase.smoothStepInOut; + case 'smoothstepout': return FlxEase.smoothStepInOut; + case 'smootherstepin': return FlxEase.smootherStepIn; + case 'smootherstepinout': return FlxEase.smootherStepInOut; + case 'smootherstepout': return FlxEase.smootherStepOut; + } + return FlxEase.linear; + } +} \ No newline at end of file diff --git a/source/game/scripting/lua/import.hx b/source/game/scripting/lua/import.hx new file mode 100644 index 0000000..907d7d7 --- /dev/null +++ b/source/game/scripting/lua/import.hx @@ -0,0 +1,15 @@ +#if LUA_ALLOWED +import hxluajit.Lua; +import hxluajit.LuaL; +import hxluajit.Types; +import hxluajit.wrapper.LuaConverter; +import hxluajit.wrapper.LuaUtils; +import hxluajit.wrapper.LuaError; + +#if flixel_animate +import game.scripting.FunkinLua.ModchartAnimateSprite; +#end +import game.scripting.FunkinLua.ModchartSprite; +import game.scripting.FunkinLua.ModchartText; +import game.scripting.FunkinLua.ModchartBackdrop; +#end \ No newline at end of file diff --git a/source/game/shaders/ColorSwap.hx b/source/game/shaders/ColorSwap.hx index 7df585b..932285e 100644 --- a/source/game/shaders/ColorSwap.hx +++ b/source/game/shaders/ColorSwap.hx @@ -8,7 +8,23 @@ class ColorSwap { public var hue(default, set):Float = 0; public var saturation(default, set):Float = 0; public var brightness(default, set):Float = 0; + public var daAlpha(default, set):Float = 1; + public var flash(default, set):Float = 0; + private function set_daAlpha(value:Float) + { + daAlpha = value; + shader.daAlpha.value[0] = daAlpha; + return daAlpha; + } + + private function set_flash(value:Float) + { + flash = value; + shader.flash.value[0] = flash; + return flash; + } + private function set_hue(value:Float) { hue = value; shader.uTime.value[0] = hue; @@ -30,6 +46,8 @@ class ColorSwap { public function new() { shader.uTime.value = [0, 0, 0]; + shader.daAlpha.value = [1]; + shader.flash.value = [0]; shader.awesomeOutline.value = [false]; } } @@ -84,6 +102,8 @@ class ColorSwapShader extends FlxShader { } uniform vec3 uTime; + uniform float daAlpha; + uniform float flash; uniform bool awesomeOutline; const float offset = 1.0 / 128.0; @@ -152,6 +172,10 @@ class ColorSwapShader extends FlxShader { color = vec4(1.0, 1.0, 1.0, 1.0); } } + if(flash != 0.0){ + color = mix(color,vec4(1.0,1.0,1.0,1.0),flash) * color.a; + } + color *= daAlpha; gl_FragColor = color; /* diff --git a/source/game/states/CreditsState.hx b/source/game/states/CreditsState.hx index 9ac9709..4362162 100644 --- a/source/game/states/CreditsState.hx +++ b/source/game/states/CreditsState.hx @@ -3,39 +3,79 @@ package game.states; #if DISCORD_ALLOWED import api.Discord.DiscordClient; #end -import openfl.text.TextField; import flixel.FlxG; import flixel.FlxSprite; -import flixel.addons.display.FlxGridOverlay; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.math.FlxMath; import flixel.text.FlxText; import flixel.util.FlxColor; import flixel.tweens.FlxTween; import flixel.tweens.FlxEase; + #if MODS_ALLOWED import sys.FileSystem; import sys.io.File; #end -import lime.utils.Assets; + +import openfl.utils.Assets as OpenFlAssets; using StringTools; class CreditsState extends MusicBeatState { - var curSelected:Int = -1; - - private var grpOptions:FlxTypedGroup; - private var iconArray:Array = []; - private var creditsStuff:Array> = []; + static final CREDITS_DATA:Array> = [ + ['CC Engine'], + ["JustX", "gct", "Creator of this fork", "", "FF0000"], + ["localisteer", "natella", "Beta-Tester, Bug Reporter and Big Guy", "https://x.com/nathanalogie", "7CA5E9"], + ["Sea3Plays", "sea3", "Active user and Bug reporter", "https://www.youtube.com/channel/UCtORhyWNUT18Trb4QrZMI0Q", "FF7F00"], + [''], + ['Special Thanks To'], + ["MAJigsaw77", "majigsaw", "GLSL Es 300 and GLSL 330 support\n.MP4 Video Loader Library (hxvlc) and FlxGif", "https://x.com/MAJigsaw77", "5F5F5F"], + ["superpowers04", "superpowers04", "LUA JIT Fork", "https://x.com/superpowers04", "B957ED"], + ["MaybeMaru", "cheems", "Creator of Flixel-Animate", "https://x.com/maybemaru_", "dDF3DD"], + ["Slushi", "slushi", "Creator of Slushi Windows API", "https://github.com/Slushi-Github", "FFCBCF"], + ["Kriptel", "kriptel", "Creator of Rulescript", "https://x.com/kriptelpro", "8D4785"], + ["Nkreep", "nanokrip", "That guy who doesn't like Haxe", "https://x.com/narutokreep", "77F3FF"], + [''], + ['PE Team'], + ["Shadow Mario", "shadowmario", "Main Programmer and Head of Psych Engine", "https://ko-fi.com/shadowmario", "444444"], + ["Riveren", "riveren", "Main Artist/Animator of Psych Engine", "https://x.com/riverennn", "14967B"], + [''], + ['Former PE Members'], + ['bb-panzu', 'bb', 'Ex-Programmer of Psych Engine', 'https://x.com/bbsub3', '3E813A'], + [''], + ['PE Contributors'], + ['iFlicky', 'flicky', 'Composer of Psync and Tea Time\nMade the Dialogue Sounds', 'https://x.com/flicky_i', '9E29CF'], + ['KadeDev', 'kade', 'Fixed some cool stuff on Chart Editor\nand other PRs', 'https://x.com/kade0912', '64A250'], + ['SqirraRNG', 'sqirra', 'Base code for\nChart Editor\'s Waveform', 'https://x.com/gedehari', 'E1843A'], + ['Keoiki', 'keoiki', 'Note Splash Animations', 'https://x.com/Keoiki_', 'D2D2D2'], + [''], + ["Funkin' Crew"], + ['ninjamuffin99', 'ninjamuffin99', "Main Programmer of Friday Night Funkin'", 'https://x.com/ninja_muffin99', 'CF2D2D'], + ['EliteMasterEric', 'mastereric', "Programmer of Friday Night Funkin'", 'https://x.com/EliteMasterEric', 'FFBD40'], + ['PhantomArcade', 'phantomarcade', "Animator & Director of Friday Night Funkin'", 'https://x.com/PhantomArcade3K', 'FADC45'], + ['evilsk8r', 'evilsk8r', "Artist of Friday Night Funkin'", 'https://x.com/evilsk8r', '5ABD4B'], + ['kawaisprite', 'kawaisprite', "Composer of Friday Night Funkin'", 'https://x.com/kawaisprite', '378FC7'] + ]; + + var creditsOptions:FlxTypedGroup; + var iconArray:Array = []; + var creditsList:Array> = []; + #if MODS_ALLOWED + var processedMods:Array = []; + #end - var bg:FlxSprite; - var descText:FlxText; + var background:FlxSprite; + var descriptionText:FlxText; + var descriptionBox:AttachedSprite; var intendedColor:Int; var colorTween:FlxTween; - var descBox:AttachedSprite; + var selectionTween:FlxTween; - var offsetThing:Float = -75; + var curSelected:Int = -1; + var isQuitting:Bool = false; + var holdTime:Float = 0; + final textOffset:Float = -75; override function create() { @@ -43,312 +83,356 @@ class CreditsState extends MusicBeatState Paths.clearUnusedMemory(); #if DISCORD_ALLOWED - // Updating Discord Rich Presence DiscordClient.changePresence("In the Menus", null); #end persistentUpdate = true; - - if (!isSoftcodedState()) - { - bg = new FlxSprite().loadGraphic(Paths.image('menuDesat')); - add(bg); - bg.screenCenter(); + initializeCredits(); + + if (!isSoftcodedState()) { + if(FlxG.sound.music == null) FlxG.sound.playMusic(Paths.music('freakyMenu')); - grpOptions = new FlxTypedGroup(); - add(grpOptions); + createInterface(); + } + + super.create(); + } + + function initializeCredits() + { + #if MODS_ALLOWED + loadModCredits(); + #end + + for (item in CREDITS_DATA) creditsList.push(item); + } - #if MODS_ALLOWED - var path:String = 'modsList.txt'; - if(FileSystem.exists(path)) + #if MODS_ALLOWED + function loadModCredits() + { + processedMods = []; + + // load from mods list file + var modsListPath = Paths.txt('modsList'); + if (FileSystem.exists(modsListPath)) + { + var modEntries = CoolUtil.coolTextFile(modsListPath); + for (entry in modEntries) { - var leMods:Array = CoolUtil.coolTextFile(path); - for (i in 0...leMods.length) + if (modEntries.length > 1 && entry.length > 0) { - if(leMods.length > 1 && leMods[0].length > 0) { - var modSplit:Array = leMods[i].split('|'); - if(!Paths.ignoreModFolders.contains(modSplit[0].toLowerCase()) && !modsAdded.contains(modSplit[0])) + var modData = entry.split('|'); + if (!Mods.ignoreModFolders.contains(modData[0].toLowerCase()) && !processedMods.contains(modData[0])) { - if(modSplit[1] == '1') - pushModCreditsToList(modSplit[0]); - else - modsAdded.push(modSplit[0]); - } + if (modData[1] == '1') + addModCredits(modData[0]); + else + processedMods.push(modData[0]); } } } + } + + // load from mod directories + var modFolders = Mods.getModDirectories(); + modFolders.push(''); + for (folder in modFolders) + { + addModCredits(folder); + } + } + + function addModCredits(folder:String) + { + if (processedMods.contains(folder)) return; + + var creditsFile = folder != null && folder.trim().length > 0 + ? Mods.getModPath(folder + '/data/credits.txt') + : Mods.getModPath('data/credits.txt'); - var arrayOfFolders:Array = Paths.getModDirectories(); - arrayOfFolders.push(''); - for (folder in arrayOfFolders) + if (FileSystem.exists(creditsFile)) + { + var fileContent = File.getContent(creditsFile).split('\n'); + for (line in fileContent) { - pushModCreditsToList(folder); - } - #end - - var pisspoop:Array> = [ //Name - Icon name - Description - Link - BG Color - ['Special Thanks To'], - ["MAJigsaw77", "majigsaw", "GLSL Es 300 and GLSL 330 support\n.MP4 Video Loader Library (hxvlc) and FlxGif", "https://x.com/MAJigsaw77", "5F5F5F"], - ["superpowers04", "superpowers04", "LUA JIT Fork", "https://x.com/superpowers04", "B957ED"], - ["MaybeMaru", "cheems", "Creator of Flixel-Animate", "https://x.com/maybemaru_", "dDF3DD"], - ["Slushi", "slushi", "Creator of Slushi Windows API", "https://https://github.com/Slushi-Github", "FFCBCF"], - ["Kriptel", "kriptel", "Creator of Rulescript", "https://x.com/kriptelpro", "8D4785"], - ["localisteer", "natella", "Beta-Tester, Bug Reporter and Big Guy", "https://x.com/nathanalogie", "7CA5E9"], - ["Nkreep", "nanokrip", "That guy who doesn't like Haxe", "https://x.com/narutokreep", "77F3FF"], - [''], - ['PE Team'], - ["Shadow Mario", "shadowmario", "Main Programmer and Head of Psych Engine", "https://ko-fi.com/shadowmario", "444444"], - ["Riveren", "riveren", "Main Artist/Animator of Psych Engine", "https://x.com/riverennn", "14967B"], - [''], - ['Former PE Members'], - ['bb-panzu', 'bb', 'Ex-Programmer of Psych Engine', 'https://x.com/bbsub3', '3E813A'], - [''], - ['PE Contributors'], - ['iFlicky', 'flicky', 'Composer of Psync and Tea Time\nMade the Dialogue Sounds', 'https://x.com/flicky_i', '9E29CF'], - ['KadeDev', 'kade', 'Fixed some cool stuff on Chart Editor\nand other PRs', 'https://x.com/kade0912', '64A250'], - ['SqirraRNG', 'sqirra', 'Base code for\nChart Editor\'s Waveform', 'https://x.com/gedehari', 'E1843A'], - ['Keoiki', 'keoiki', 'Note Splash Animations', 'https://x.com/Keoiki_', 'D2D2D2'], - [''], - ["Funkin' Crew"], - ['ninjamuffin99', 'ninjamuffin99', "Main Programmer of Friday Night Funkin'", 'https://x.com/ninja_muffin99', 'CF2D2D'], - ['EliteMasterEric', 'mastereric', "Programmer of Friday Night Funkin'", 'https://x.com/EliteMasterEric', 'FFBD40'], - ['PhantomArcade', 'phantomarcade', "Animator & Director of Friday Night Funkin'", 'https://x.com/PhantomArcade3K', 'FADC45'], - ['evilsk8r', 'evilsk8r', "Artist of Friday Night Funkin'", 'https://x.com/evilsk8r', '5ABD4B'], - ['kawaisprite', 'kawaisprite', "Composer of Friday Night Funkin'", 'https://x.com/kawaisprite', '378FC7'] - ]; - - for(i in pisspoop){ - creditsStuff.push(i); + var creditEntry = line.replace('\\n', '\n').split("::"); + if (creditEntry.length >= 5) + creditEntry.push(folder); + creditsList.push(creditEntry); } - - for (i in 0...creditsStuff.length) - { - var isSelectable:Bool = !unselectableCheck(i); - var optionText:Alphabet = new Alphabet(FlxG.width / 2, 300, creditsStuff[i][0], !isSelectable); - optionText.isMenuItem = true; - optionText.targetY = i; - optionText.changeX = false; - optionText.snapToPosition(); - grpOptions.add(optionText); - - if(isSelectable) { - if(creditsStuff[i][5] != null) - { - Paths.currentModDirectory = creditsStuff[i][5]; - } + creditsList.push(['']); + } + processedMods.push(folder); + } + #end - var icon:AttachedSprite = new AttachedSprite('credits/' + creditsStuff[i][1]); - icon.xAdd = optionText.width + 10; - icon.sprTracker = optionText; - - // using a FlxGroup is too much fuss! - iconArray.push(icon); - add(icon); - Paths.currentModDirectory = ''; + function createInterface() + { + createBackground(); + createCreditsList(); + createDescriptionPanel(); + setupInitialSelection(); + } - if(curSelected == -1) curSelected = i; - } - else optionText.alignment = CENTERED; - } + function createBackground() + { + background = new FlxSprite().loadGraphic(Paths.image('menuDesat')); + background.screenCenter(); + add(background); + } + + function createCreditsList() + { + creditsOptions = new FlxTypedGroup(); + add(creditsOptions); + + for (i in 0...creditsList.length) + { + var isSelectable = !isUnselectable(i); + var option = new Alphabet(FlxG.width / 2, 300, creditsList[i][0], !isSelectable); + option.isMenuItem = true; + option.targetY = i; + option.changeX = false; + option.snapToPosition(); + + if (!isSelectable) + option.alignment = CENTERED; - descBox = new AttachedSprite(); - descBox.makeGraphic(1, 1, FlxColor.BLACK); - descBox.xAdd = -10; - descBox.yAdd = -10; - descBox.alphaMult = 0.6; - descBox.alpha = 0.6; - add(descBox); - - descText = new FlxText(50, FlxG.height + offsetThing - 25, 1180, "", 32); - descText.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, CENTER/*, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK*/); - descText.scrollFactor.set(); - //descText.borderSize = 2.4; - descBox.sprTracker = descText; - add(descText); - - bg.color = getCurrentBGColor(); - intendedColor = bg.color; - changeSelection(); + creditsOptions.add(option); + + if (isSelectable) + { + createIconForEntry(creditsList[i], option); + if (curSelected == -1) + curSelected = i; + } } - else + } + + function createIconForEntry(entry:Array, text:Alphabet) + { + if (entry[5] != null) + Mods.currentModDirectory = entry[5]; + + var icon = new AttachedSprite('credits/' + entry[1]); + + if(#if sys !FileSystem #else !OpenFlAssets #end .exists(Paths.getPath('images/credits/${entry[1]}.png', IMAGE, null, true))) + icon = new AttachedSprite('credits/missing_icon'); + + icon.xAdd = text.width + 10; + icon.sprTracker = text; + + iconArray.push(icon); + add(icon); + Mods.currentModDirectory = ''; + } + + function createDescriptionPanel() + { + descriptionBox = new AttachedSprite(); + descriptionBox.makeGraphic(1, 1, FlxColor.BLACK); + descriptionBox.xAdd = -10; + descriptionBox.yAdd = -10; + descriptionBox.alphaMult = 0.6; + descriptionBox.alpha = 0.6; + add(descriptionBox); + + descriptionText = new FlxText(50, FlxG.height + textOffset - 25, 1180, "", 32); + descriptionText.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, CENTER); + descriptionText.scrollFactor.set(); + descriptionBox.sprTracker = descriptionText; + add(descriptionText); + } + + function setupInitialSelection() + { + background.color = getCurrentBackgroundColor(); + intendedColor = background.color; + changeSelection(); + } + + override function update(elapsed:Float) + { + if (FlxG.sound.music?.volume < 0.7) + FlxG.sound.music.volume += 0.5 * FlxG.elapsed; + + if (!isQuitting) { - bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - add(bg); + if (!isSoftcodedState()) + { + handleInput(elapsed); + updateMenuItems(elapsed); + } + else if (controls.BACK) + { + exitState(); + } } - super.create(); + super.update(elapsed); } - var quitting:Bool = false; - var holdTime:Float = 0; - override function update(elapsed:Float) + function handleInput(elapsed:Float) { - if (FlxG.sound.music.volume < 0.7) + if (creditsList.length > 1) { - FlxG.sound.music.volume += 0.5 * FlxG.elapsed; + var shiftMultiplier = FlxG.keys.pressed.SHIFT ? 3 : 1; + handleSelectionInput(shiftMultiplier, elapsed); } - if (!isSoftcodedState() && !quitting) + if (controls.ACCEPT) { - if(creditsStuff.length > 1) - { - var shiftMult:Int = 1; - if(FlxG.keys.pressed.SHIFT) shiftMult = 3; + openSelectedLink(); + } - var upP = controls.UI_UP_P; - var downP = controls.UI_DOWN_P; + if (controls.BACK) + { + exitState(); + } + } - if (upP) - { - changeSelection(-shiftMult); - holdTime = 0; - } - if (downP) - { - changeSelection(shiftMult); - holdTime = 0; - } + function handleSelectionInput(shiftMultiplier:Int, elapsed:Float) + { + var upPressed = controls.UI_UP_P; + var downPressed = controls.UI_DOWN_P; - if(controls.UI_DOWN || controls.UI_UP) - { - var checkLastHold:Int = Math.floor((holdTime - 0.5) * 10); - holdTime += elapsed; - var checkNewHold:Int = Math.floor((holdTime - 0.5) * 10); + if (upPressed) + { + changeSelection(-shiftMultiplier); + holdTime = 0; + } + if (downPressed) + { + changeSelection(shiftMultiplier); + holdTime = 0; + } - if(holdTime > 0.5 && checkNewHold - checkLastHold > 0) - { - changeSelection((checkNewHold - checkLastHold) * (controls.UI_UP ? -shiftMult : shiftMult)); - } - } - } + if (controls.UI_DOWN || controls.UI_UP) + { + var lastHoldCheck = Math.floor((holdTime - 0.5) * 10); + holdTime += elapsed; + var newHoldCheck = Math.floor((holdTime - 0.5) * 10); - if(controls.ACCEPT && (creditsStuff[curSelected][3] == null || creditsStuff[curSelected][3].length > 4)) { - CoolUtil.browserLoad(creditsStuff[curSelected][3]); - } - if (controls.BACK) + if (holdTime > 0.5 && newHoldCheck - lastHoldCheck > 0) { - if(colorTween != null) { - colorTween.cancel(); - } - FlxG.sound.play(Paths.sound('cancelMenu')); - FlxG.switchState(() -> new MainMenuState()); - quitting = true; + changeSelection((newHoldCheck - lastHoldCheck) * (controls.UI_UP ? -shiftMultiplier : shiftMultiplier)); } - - for (item in grpOptions.members) + } + } + + function updateMenuItems(elapsed:Float) + { + for (item in creditsOptions.members) + { + if (!item.bold) { - if(!item.bold) + var lerpValue = MathUtil.boundTo(elapsed * 12, 0, 1); + if (item.targetY == 0) { - var lerpVal:Float = MathUtil.boundTo(elapsed * 12, 0, 1); - if(item.targetY == 0) - { - var lastX:Float = item.x; - item.screenCenter(X); - item.x = FlxMath.lerp(lastX, item.x - 70, lerpVal); - } - else - { - item.x = FlxMath.lerp(item.x, 200 + -40 * Math.abs(item.targetY), lerpVal); - } + var lastX = item.x; + item.screenCenter(X); + item.x = FlxMath.lerp(lastX, item.x - 70, lerpValue); + } + else + { + item.x = FlxMath.lerp(item.x, 200 + -40 * Math.abs(item.targetY), lerpValue); } } } - else if (controls.BACK && !quitting) + } + + function openSelectedLink() + { + var selectedEntry = creditsList[curSelected]; + if (selectedEntry[3] != null && selectedEntry[3].length > 4) { - FlxG.sound.play(Paths.sound('cancelMenu')); - FlxG.switchState(() -> new MainMenuState()); - quitting = true; + CoolUtil.browserLoad(selectedEntry[3]); } + } - super.update(elapsed); + function exitState() + { + isQuitting = true; + colorTween?.cancel(); + FlxG.sound.play(Paths.sound('cancelMenu')); + FlxG.switchState(() -> new MainMenuState()); } - var moveTween:FlxTween = null; function changeSelection(change:Int = 0) { if (isSoftcodedState()) return; - + FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); + do { - curSelected += change; - if (curSelected < 0) - curSelected = creditsStuff.length - 1; - if (curSelected >= creditsStuff.length) - curSelected = 0; - } while(unselectableCheck(curSelected)); - - var newColor:Int = getCurrentBGColor(); - if(newColor != intendedColor) { + curSelected = FlxMath.wrap(curSelected + change, 0, creditsList.length - 1); + } while (isUnselectable(curSelected)); + + updateBackgroundColor(); + updateSelectionDisplay(); + updateDescription(); + } + + function updateBackgroundColor() + { + var newColor = getCurrentBackgroundColor(); + if (newColor != intendedColor) + { colorTween?.cancel(); intendedColor = newColor; - colorTween = FlxTween.color(bg, 1, bg.color, intendedColor, { + colorTween = FlxTween.color(background, 1, background.color, intendedColor, { onComplete: _ -> colorTween = null }); } + } - var bullShit:Int = 0; - - for (item in grpOptions.members) + function updateSelectionDisplay() + { + var index = 0; + for (item in creditsOptions.members) { - item.targetY = bullShit - curSelected; - bullShit++; + item.targetY = index - curSelected; + index++; - if(!unselectableCheck(bullShit-1)) { - item.alpha = 0.6; - if (item.targetY == 0) { - item.alpha = 1; - } + if (!isUnselectable(index - 1)) + { + item.alpha = item.targetY == 0 ? 1 : 0.6; } } - - descText.text = creditsStuff[curSelected][2]; - descText.y = FlxG.height - descText.height + offsetThing - 60; - - if(moveTween != null) moveTween.cancel(); - moveTween = FlxTween.tween(descText, {y : descText.y + 75}, 0.25, {ease: FlxEase.sineOut}); - - descBox.setGraphicSize(Std.int(descText.width + 20), Std.int(descText.height + 25)); - descBox.updateHitbox(); } - #if MODS_ALLOWED - private var modsAdded:Array = []; - function pushModCreditsToList(folder:String) + function updateDescription() { - if(modsAdded.contains(folder)) return; - - var creditsFile:String = null; - if(folder != null && folder.trim().length > 0) creditsFile = Paths.mods(folder + '/data/credits.txt'); - else creditsFile = Paths.mods('data/credits.txt'); - - if (FileSystem.exists(creditsFile)) - { - var firstarray:Array = File.getContent(creditsFile).split('\n'); - for(i in firstarray) - { - var arr:Array = i.replace('\\n', '\n').split("::"); - if(arr.length >= 5) arr.push(folder); - creditsStuff.push(arr); - } - creditsStuff.push(['']); - } - modsAdded.push(folder); + descriptionText.text = creditsList[curSelected][2]; + descriptionText.y = FlxG.height - descriptionText.height + textOffset - 60; + + selectionTween?.cancel(); + selectionTween = FlxTween.tween(descriptionText, {y: descriptionText.y + 75}, 0.25, { + ease: FlxEase.sineOut + }); + + descriptionBox.setGraphicSize( + Std.int(descriptionText.width + 20), + Std.int(descriptionText.height + 25) + ); + descriptionBox.updateHitbox(); } - #end - function getCurrentBGColor() { + function getCurrentBackgroundColor():Int + { if (isSoftcodedState()) return FlxColor.BLACK; - - var bgColor:String = creditsStuff[curSelected][4]; - if(!bgColor.startsWith('0x')) { - bgColor = '0xFF' + bgColor; + + var colorString = creditsList[curSelected][4]; + if (!colorString.startsWith('0x')) + { + colorString = '0xFF' + colorString; } - return Std.parseInt(bgColor); + return Std.parseInt(colorString); } - private function unselectableCheck(num:Int):Bool { + function isUnselectable(index:Int):Bool + { if (isSoftcodedState()) return true; - - return creditsStuff[num].length <= 1; + return creditsList[index].length <= 1; } } \ No newline at end of file diff --git a/source/game/states/FreeplayState.hx b/source/game/states/FreeplayState.hx index 9505056..4e5f240 100644 --- a/source/game/states/FreeplayState.hx +++ b/source/game/states/FreeplayState.hx @@ -3,10 +3,8 @@ package game.states; #if DISCORD_ALLOWED import api.Discord.DiscordClient; #end -import openfl.text.TextField; import flixel.FlxG; import flixel.FlxSprite; -import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.math.FlxMath; @@ -14,13 +12,6 @@ import flixel.text.FlxText; import flixel.util.FlxColor; import flixel.tweens.FlxTween; import lime.utils.Assets; -import flixel.sound.FlxSound; -import openfl.utils.Assets as OpenFlAssets; -import game.backend.WeekData; - -#if MODS_ALLOWED -import sys.FileSystem; -#end import game.objects.HealthIcon; import game.states.editors.ChartEditorState; @@ -29,30 +20,34 @@ using StringTools; class FreeplayState extends MusicBeatState { - var songs:Array = []; + static final DEFAULT_COLOR:FlxColor = 0xFF9271FD; - var selector:FlxText; - private static var curSelected:Int = 0; - var curDifficulty:Int = -1; - private static var lastDifficultyName:String = ''; + var songs:Array = []; + var grpSongs:FlxTypedGroup; + var iconArray:Array = []; - var scoreBG:FlxSprite; + var background:FlxSprite; + var scoreBackground:FlxSprite; var scoreText:FlxText; - var diffText:FlxText; + var difficultyText:FlxText; + var helpText:FlxText; + + var curDifficulty:Int = 0; + + static var curSelected:Int = 0; + static var lastDifficultyName:String = ''; + var lerpScore:Int = 0; var lerpRating:Float = 0; var intendedScore:Int = 0; var intendedRating:Float = 0; - - private var grpSongs:FlxTypedGroup; - private var curPlaying:Bool = false; - - private var iconArray:Array = []; - - var bg:FlxSprite; var intendedColor:Int; var colorTween:FlxTween; + var instPlaying:Int = -1; + var holdTime:Float = 0; + public static var vocals:FlxSound = null; + override function create() { Paths.clearStoredMemory(); @@ -63,134 +58,150 @@ class FreeplayState extends MusicBeatState WeekData.reloadWeekFiles(false); #if DISCORD_ALLOWED - // Updating Discord Rich Presence DiscordClient.changePresence("In the Menus", null); #end + loadAvailableSongs(); + if (!isSoftcodedState()) { - for (i in 0...WeekData.weeksList.length) { - if(weekIsLocked(WeekData.weeksList[i])) continue; + if(FlxG.sound.music == null) FlxG.sound.playMusic(Paths.music('freakyMenu')); + + createMenuInterface(); + setupNavigation(); + } - var leWeek:WeekData = WeekData.weeksLoaded.get(WeekData.weeksList[i]); - var leSongs:Array = []; - var leChars:Array = []; + super.create(); + } - for (j in 0...leWeek.songs.length) - { - leSongs.push(leWeek.songs[j][0]); - leChars.push(leWeek.songs[j][1]); - } + function loadAvailableSongs() + { + for (i in 0...WeekData.weeksList.length) + { + if (weekIsLocked(WeekData.weeksList[i])) continue; - WeekData.setDirectoryFromWeek(leWeek); - for (song in leWeek.songs) - { - var colors:Array = song[2]; - if(colors == null || colors.length < 3) - { - colors = [146, 113, 253]; - } - addSong(song[0], i, song[1], FlxColor.fromRGB(colors[0], colors[1], colors[2])); - } + var week:WeekData = WeekData.weeksLoaded.get(WeekData.weeksList[i]); + WeekData.setDirectoryFromWeek(week); + + for (song in week.songs) + { + var colors:Array = song[2]; + if (colors == null || colors.length < 3) + colors = [146, 113, 253]; + + addSong(song[0], i, song[1], FlxColor.fromRGB(colors[0], colors[1], colors[2])); } - WeekData.loadTheFirstEnabledMod(); + } + WeekData.loadTheFirstEnabledMod(); + } - bg = new FlxSprite().loadGraphic(Paths.image('menuDesat')); - bg.antialiasing = ClientPrefs.globalAntialiasing; - add(bg); - bg.screenCenter(); + function createMenuInterface() + { + createBackground(); + createSongList(); + createScoreDisplay(); + createHelpText(); + } - grpSongs = new FlxTypedGroup(); - add(grpSongs); + function createBackground() + { + background = new FlxSprite().loadGraphic(Paths.image('menuDesat')); + background.antialiasing = ClientPrefs.globalAntialiasing; + background.screenCenter(); + add(background); - for (i in 0...songs.length) - { - var songText:Alphabet = new Alphabet(90, 320, songs[i].songName, true); - songText.isMenuItem = true; - songText.targetY = i - curSelected; - grpSongs.add(songText); - - var maxWidth = 980; - if (songText.width > maxWidth) songText.scaleX = maxWidth / songText.width; - songText.snapToPosition(); - - Paths.currentModDirectory = songs[i].folder; - var icon:HealthIcon = new HealthIcon(songs[i].songCharacter); - icon.sprTracker = songText; - - // using a FlxGroup is too much fuss! - iconArray.push(icon); - add(icon); - } - WeekData.setDirectoryFromWeek(); + if (songs.length > 0) + { + background.color = songs[curSelected].color; + intendedColor = background.color; + } + } - scoreText = new FlxText(FlxG.width * 0.7, 5, 0, "", 32); - scoreText.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, RIGHT); + function createSongList() + { + grpSongs = new FlxTypedGroup(); + add(grpSongs); - scoreBG = new FlxSprite(scoreText.x - 6, 0).makeGraphic(1, 66, 0xFF000000); - scoreBG.alpha = 0.6; - add(scoreBG); + for (i in 0...songs.length) + { + var songText = new Alphabet(90, 320, songs[i].songName, true); + songText.isMenuItem = true; + songText.targetY = i - curSelected; + + var maxWidth = 980; + if (songText.width > maxWidth) + songText.scaleX = maxWidth / songText.width; + + songText.snapToPosition(); + grpSongs.add(songText); + + Mods.currentModDirectory = songs[i].folder; + var icon = new HealthIcon(songs[i].songCharacter); + icon.sprTracker = songText; + iconArray.push(icon); + add(icon); + } + WeekData.setDirectoryFromWeek(); + } - diffText = new FlxText(scoreText.x, scoreText.y + 36, 0, "", 24); - diffText.font = scoreText.font; - add(diffText); + function createScoreDisplay() + { + scoreText = new FlxText(FlxG.width * 0.7, 5, 0, "", 32); + scoreText.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, RIGHT); - add(scoreText); + scoreBackground = new FlxSprite(scoreText.x - 6, 0).makeGraphic(1, 66, 0xFF000000); + scoreBackground.alpha = 0.6; + add(scoreBackground); - if(curSelected >= songs.length) curSelected = 0; - bg.color = songs[curSelected].color; - intendedColor = bg.color; + difficultyText = new FlxText(scoreText.x, scoreText.y + 36, 0, "", 24); + difficultyText.font = scoreText.font; + add(difficultyText); - if(lastDifficultyName == '') lastDifficultyName = CoolUtil.defaultDifficulty; - curDifficulty = Math.round(Math.max(0, CoolUtil.defaultDifficulties.indexOf(lastDifficultyName))); - - changeSelection(); - changeDiff(); - - var textBG:FlxSprite = new FlxSprite(0, FlxG.height - 26).makeGraphic(FlxG.width, 26, 0xFF000000); - textBG.alpha = 0.6; - add(textBG); - - #if PRELOAD_ALL - var leText:String = "Press SPACE to listen to the Song / Press CTRL to open the Gameplay Changers Menu / Press RESET to Reset your Score and Accuracy."; - var size:Int = 16; - #else - var leText:String = "Press CTRL to open the Gameplay Changers Menu / Press RESET to Reset your Score and Accuracy."; - var size:Int = 18; - #end - var text:FlxText = new FlxText(textBG.x, textBG.y + 4, FlxG.width, leText, size); - text.setFormat(Paths.font("vcr.ttf"), size, FlxColor.WHITE, RIGHT); - text.scrollFactor.set(); - add(text); - } - else - { - // Для softcoded состояний просто загружаем песни - for (i in 0...WeekData.weeksList.length) { - if(weekIsLocked(WeekData.weeksList[i])) continue; + add(scoreText); - var leWeek:WeekData = WeekData.weeksLoaded.get(WeekData.weeksList[i]); - WeekData.setDirectoryFromWeek(leWeek); - for (song in leWeek.songs) - { - var colors:Array = song[2]; - if(colors == null || colors.length < 3) - { - colors = [146, 113, 253]; - } - addSong(song[0], i, song[1], FlxColor.fromRGB(colors[0], colors[1], colors[2])); - } - } - WeekData.loadTheFirstEnabledMod(); - } + if (lastDifficultyName == '') + lastDifficultyName = CoolUtil.defaultDifficulty; + + curDifficulty = Math.round(Math.max(0, CoolUtil.defaultDifficulties.indexOf(lastDifficultyName))); + + updateSelection(); + updateDifficulty(); + } - super.create(); + function createHelpText() + { + var textBackground = new FlxSprite(0, FlxG.height - 26).makeGraphic(FlxG.width, 26, 0xFF000000); + textBackground.alpha = 0.6; + add(textBackground); + + #if PRELOAD_ALL + var helpString = "Press SPACE to listen to the Song / Press CTRL to open the Gameplay Changers Menu / Press RESET to Reset your Score and Accuracy."; + var textSize = 16; + #else + var helpString = "Press CTRL to open the Gameplay Changers Menu / Press RESET to Reset your Score and Accuracy."; + var textSize = 18; + #end + + helpText = new FlxText(textBackground.x, textBackground.y + 4, FlxG.width, helpString, textSize); + helpText.setFormat(Paths.font("vcr.ttf"), textSize, FlxColor.WHITE, RIGHT); + helpText.scrollFactor.set(); + add(helpText); } - override function closeSubState() { - if (!isSoftcodedState()) { - changeSelection(0, false); + function setupNavigation() + { + if (songs.length > 0) + { + curSelected = Std.int(Math.min(curSelected, songs.length - 1)); + updateSelection(); } + } + + override function closeSubState() + { + if (!isSoftcodedState()) + updateSelection(0, false); + persistentUpdate = true; super.closeSubState(); } @@ -200,302 +211,328 @@ class FreeplayState extends MusicBeatState songs.push(new SongMetadata(songName, weekNum, songCharacter, color)); } - function weekIsLocked(name:String):Bool { - var leWeek:WeekData = WeekData.weeksLoaded.get(name); - return (!leWeek.startUnlocked && leWeek.weekBefore.length > 0 && (!StoryMenuState.weekCompleted.exists(leWeek.weekBefore) || !StoryMenuState.weekCompleted.get(leWeek.weekBefore))); + function weekIsLocked(name:String):Bool + { + var week:WeekData = WeekData.weeksLoaded.get(name); + return (!week.startUnlocked && week.weekBefore.length > 0 && + (!StoryMenuState.weekCompleted.exists(week.weekBefore) || !StoryMenuState.weekCompleted.get(week.weekBefore))); } - var instPlaying:Int = -1; - var holdTime:Float = 0; - public static var vocals:FlxSound = null; override function update(elapsed:Float) { - if (FlxG.sound.music.volume < 0.7) FlxG.sound.music.volume += 0.5 * FlxG.elapsed; + if (FlxG.sound.music?.volume < 0.7) + FlxG.sound.music.volume += 0.5 * FlxG.elapsed; if (!isSoftcodedState()) { - lerpScore = Math.floor(FlxMath.lerp(lerpScore, intendedScore, MathUtil.boundTo(elapsed * 24, 0, 1))); - lerpRating = FlxMath.lerp(lerpRating, intendedRating, MathUtil.boundTo(elapsed * 12, 0, 1)); - - if (Math.abs(lerpScore - intendedScore) <= 10) - lerpScore = intendedScore; - if (Math.abs(lerpRating - intendedRating) <= 0.01) - lerpRating = intendedRating; - - var ratingSplit:Array = Std.string(Highscore.floorDecimal(lerpRating * 100, 2)).split('.'); - if(ratingSplit.length < 2) { //No decimals, add an empty space - ratingSplit.push(''); - } - - while(ratingSplit[1].length < 2) { //Less than 2 decimals in it, add decimals then - ratingSplit[1] += '0'; - } - - scoreText.text = 'PERSONAL BEST: ' + lerpScore + ' (' + ratingSplit.join('.') + '%)'; - positionHighscore(); + updateScoreDisplay(elapsed); + handleInput(elapsed); + } - var upP = controls.UI_UP_P; - var downP = controls.UI_DOWN_P; - var accepted = controls.ACCEPT; - var space = FlxG.keys.justPressed.SPACE; - var ctrl = FlxG.keys.justPressed.CONTROL; + super.update(elapsed); + } - var shiftMult:Int = FlxG.keys.pressed.SHIFT ? 3 : 1; + function updateScoreDisplay(elapsed:Float) + { + lerpScore = Math.floor(FlxMath.lerp(lerpScore, intendedScore, MathUtil.boundTo(elapsed * 24, 0, 1))); + lerpRating = FlxMath.lerp(lerpRating, intendedRating, MathUtil.boundTo(elapsed * 12, 0, 1)); - if(songs.length > 1) - { - if (upP) - { - changeSelection(-shiftMult); - holdTime = 0; - } - if (downP) - { - changeSelection(shiftMult); - holdTime = 0; - } + if (Math.abs(lerpScore - intendedScore) <= 10) + lerpScore = intendedScore; + if (Math.abs(lerpRating - intendedRating) <= 0.01) + lerpRating = intendedRating; - if(controls.UI_DOWN || controls.UI_UP) - { - var checkLastHold:Int = Math.floor((holdTime - 0.5) * 10); - holdTime += elapsed; - var checkNewHold:Int = Math.floor((holdTime - 0.5) * 10); - - if(holdTime > 0.5 && checkNewHold - checkLastHold > 0) - { - changeSelection((checkNewHold - checkLastHold) * (controls.UI_UP ? -shiftMult : shiftMult)); - changeDiff(); - } - } + var ratingSplit = Std.string(Highscore.floorDecimal(lerpRating * 100, 2)).split('.'); + if (ratingSplit.length < 2) + ratingSplit.push(''); + + while (ratingSplit[1].length < 2) + ratingSplit[1] += '0'; - if(FlxG.mouse.wheel != 0) - { - FlxG.sound.play(Paths.sound('scrollMenu'), 0.2); - changeSelection(-shiftMult * FlxG.mouse.wheel, false); - changeDiff(); - } - } + scoreText.text = 'PERSONAL BEST: ' + lerpScore + ' (' + ratingSplit.join('.') + '%)'; + positionScoreDisplay(); + } - if (controls.UI_LEFT_P) - changeDiff(-1); - else if (controls.UI_RIGHT_P) - changeDiff(1); - else if (upP || downP) changeDiff(); + function handleInput(elapsed:Float) + { + if (songs.length == 0 || FlxG.state.subState != null) return; + + var upPressed = controls.UI_UP_P; + var downPressed = controls.UI_DOWN_P; + var leftPressed = controls.UI_LEFT_P; + var rightPressed = controls.UI_RIGHT_P; + var accepted = controls.ACCEPT; + var spacePressed = FlxG.keys.justPressed.SPACE; + var controlPressed = FlxG.keys.justPressed.CONTROL; + var resetPressed = controls.RESET; + var backPressed = controls.BACK; + + var shiftMultiplier = FlxG.keys.pressed.SHIFT ? 3 : 1; + + handleSelectionInput(upPressed, downPressed, shiftMultiplier, elapsed); + handleDifficultyInput(leftPressed, rightPressed, upPressed, downPressed); + handleSpecialInput(spacePressed, controlPressed, resetPressed, backPressed, accepted, shiftMultiplier); + } - if (controls.BACK) + function handleSelectionInput(upPressed:Bool, downPressed:Bool, shiftMultiplier:Int, elapsed:Float) + { + if (songs.length > 1) + { + if (upPressed) { - persistentUpdate = false; - colorTween?.cancel(); - FlxG.sound.play(Paths.sound('cancelMenu')); - FlxG.switchState(() -> new MainMenuState()); + changeSelection(-shiftMultiplier); + holdTime = 0; } - - if(ctrl) + if (downPressed) { - persistentUpdate = false; - openSubState(new GameplayChangersSubstate()); + changeSelection(shiftMultiplier); + holdTime = 0; } - else if(space) + + if (controls.UI_DOWN || controls.UI_UP) { - if(instPlaying != curSelected) + var checkLastHold = Math.floor((holdTime - 0.5) * 10); + holdTime += elapsed; + var checkNewHold = Math.floor((holdTime - 0.5) * 10); + + if (holdTime > 0.5 && checkNewHold - checkLastHold > 0) { - #if PRELOAD_ALL - destroyFreeplayVocals(); - FlxG.sound.music.volume = 0; - Paths.currentModDirectory = songs[curSelected].folder; - var poop:String = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), curDifficulty); - PlayState.SONG = Song.loadFromJson(poop, songs[curSelected].songName.toLowerCase()); - vocals = PlayState.SONG.needsVoices ? new FlxSound().loadEmbedded(Paths.voices(PlayState.SONG.song)) : new FlxSound(); - - FlxG.sound?.list.add(vocals); - FlxG.sound?.playMusic(Paths.inst(PlayState.SONG.song), 0.7); - vocals?.play(); - vocals.persist = true; - vocals.looped = true; - vocals.volume = 0.7; - instPlaying = curSelected; - #end + changeSelection((checkNewHold - checkLastHold) * (controls.UI_UP ? -shiftMultiplier : shiftMultiplier)); + updateDifficulty(); } } - else if (accepted) - { - persistentUpdate = false; - - final songLowercase:String = Paths.formatToSongPath(songs[curSelected].songName); - final poop:String = Highscore.formatSong(songLowercase, curDifficulty); - - trace(poop); - - PlayState.SONG = Song.loadFromJson(poop, songLowercase); - PlayState.isStoryMode = false; - PlayState.storyDifficulty = curDifficulty; - - trace('CURRENT WEEK: ' + WeekData.getWeekFileName()); - colorTween?.cancel(); - - final shiftPressed = FlxG.keys.pressed.SHIFT; - LoadingState.loadAndSwitchState(() -> { - return shiftPressed - ? new ChartEditorState() - : new PlayState(); - }); - - FlxG.sound.music.volume = 0; - - destroyFreeplayVocals(); - } - else if(controls.RESET) + if (FlxG.mouse.wheel != 0) { - persistentUpdate = false; - openSubState(new ResetScoreSubState(songs[curSelected].songName, curDifficulty, songs[curSelected].songCharacter)); - FlxG.sound.play(Paths.sound('scrollMenu')); + FlxG.sound.play(Paths.sound('scrollMenu'), 0.2); + changeSelection(-shiftMultiplier * FlxG.mouse.wheel, false); + updateDifficulty(); } } - - super.update(elapsed); } - public static function destroyFreeplayVocals() { - vocals?.stop(); - vocals?.destroy(); + function handleDifficultyInput(leftPressed:Bool, rightPressed:Bool, upPressed:Bool, downPressed:Bool) + { + if (CoolUtil.difficulties.length < 1) return; + + if (leftPressed) + changeDifficulty(-1); + else if (rightPressed) + changeDifficulty(1); + else if (upPressed || downPressed) + updateDifficulty(); + } - vocals = null; + function handleSpecialInput(spacePressed:Bool, controlPressed:Bool, resetPressed:Bool, backPressed:Bool, accepted:Bool, shiftMultiplier:Int) + { + if (backPressed) + { + persistentUpdate = false; + colorTween?.cancel(); + FlxG.sound.play(Paths.sound('cancelMenu')); + FlxG.switchState(() -> new MainMenuState()); + } + else if (controlPressed) + { + persistentUpdate = false; + openSubState(new GameplayChangersSubstate()); + } + else if (spacePressed) + { + previewSong(); + } + else if (accepted) + { + startSong(FlxG.keys.pressed.SHIFT); + } + else if (resetPressed) + { + persistentUpdate = false; + openSubState(new ResetScoreSubState(songs[curSelected].songName, curDifficulty, songs[curSelected].songCharacter)); + FlxG.sound.play(Paths.sound('scrollMenu')); + } } - function changeDiff(change:Int = 0) + function previewSong() { - if (isSoftcodedState()) return; - - curDifficulty += change; + #if PRELOAD_ALL + if (instPlaying != curSelected) + { + FlxG.sound.music.volume = 0; + Mods.currentModDirectory = songs[curSelected].folder; + var formattedSong = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), curDifficulty); + PlayState.SONG = Song.loadFromJson(formattedSong, songs[curSelected].songName.toLowerCase()); - if (curDifficulty < 0) - curDifficulty = CoolUtil.difficulties.length-1; - if (curDifficulty >= CoolUtil.difficulties.length) - curDifficulty = 0; + FlxG.sound?.playMusic(Paths.inst(PlayState.SONG.song), 0.7); + instPlaying = curSelected; + } + #end + } - lastDifficultyName = CoolUtil.difficulties[curDifficulty]; + function startSong(shiftPressed:Bool) + { + persistentUpdate = false; - #if !switch - intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); - intendedRating = Highscore.getRating(songs[curSelected].songName, curDifficulty); - #end + var songPath = Paths.formatToSongPath(songs[curSelected].songName); + var formattedSong = Highscore.formatSong(songPath, curDifficulty); + PlayState.SONG = Song.loadFromJson(formattedSong, songPath); + PlayState.isStoryMode = false; PlayState.storyDifficulty = curDifficulty; - diffText.text = '< ' + CoolUtil.difficultyString() + ' >'; - positionHighscore(); + + colorTween?.cancel(); + + LoadingState.loadAndSwitchState(() -> { + return shiftPressed ? new ChartEditorState() : new PlayState(); + }); + + FlxG.sound.music.volume = 0; } function changeSelection(change:Int = 0, playSound:Bool = true) { - if (isSoftcodedState()) return; - - if(playSound) FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); + if (playSound) + FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); - curSelected += change; + curSelected = FlxMath.wrap(curSelected + change, 0, songs.length - 1); + updateSelection(); + } - if (curSelected < 0) - curSelected = songs.length - 1; - if (curSelected >= songs.length) - curSelected = 0; - - var newColor:Int = songs[curSelected].color; - if(newColor != intendedColor) { + function updateSelection(change:Int = 0, updateDiff:Bool = true) + { + if (change != 0) + curSelected = FlxMath.wrap(curSelected + change, 0, songs.length - 1); + + if (songs.length > 0) + { + updateBackgroundColor(); + updateSongDisplay(); + updateWeekData(); + + if (updateDiff) + updateDifficulty(); + } + } + + function updateBackgroundColor() + { + var newColor = songs[curSelected].color; + if (newColor != intendedColor) + { colorTween?.cancel(); intendedColor = newColor; - colorTween = FlxTween.color(bg, 1, bg.color, intendedColor, { + colorTween = FlxTween.color(background, 1, background.color, intendedColor, { onComplete: (_) -> colorTween = null }); } + } + function updateSongDisplay() + { #if !switch intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); intendedRating = Highscore.getRating(songs[curSelected].songName, curDifficulty); #end - var bullShit:Int = 0; - for (i in 0...iconArray.length) - { iconArray[i].alpha = 0.6; - } - - iconArray[curSelected].alpha = 1; + + if (iconArray.length > curSelected) + iconArray[curSelected].alpha = 1; + var index = 0; for (item in grpSongs.members) { - item.targetY = bullShit - curSelected; - bullShit++; - - item.alpha = 0.6; - // item.setGraphicSize(Std.int(item.width * 0.8)); - - if (item.targetY == 0) - { - item.alpha = 1; - // item.setGraphicSize(Std.int(item.width)); - } + item.targetY = index - curSelected; + item.alpha = item.targetY == 0 ? 1 : 0.6; + index++; } - - Paths.currentModDirectory = songs[curSelected].folder; + } + + function updateWeekData() + { + Mods.currentModDirectory = songs[curSelected].folder; PlayState.storyWeek = songs[curSelected].week; CoolUtil.difficulties = CoolUtil.defaultDifficulties.copy(); - var diffStr:String = WeekData.getCurrentWeek().difficulties; - if(diffStr != null) diffStr = diffStr.trim(); //Fuck you HTML5 - - if(diffStr != null && diffStr.length > 0) + var diffStr = WeekData.getCurrentWeek().difficulties; + + if (diffStr != null && diffStr.trim().length > 0) { - var diffs:Array = diffStr.split(','); - var i:Int = diffs.length - 1; + var diffs = diffStr.split(','); + var i = diffs.length - 1; while (i > 0) { - if(diffs[i] != null) + if (diffs[i] != null) { diffs[i] = diffs[i].trim(); - if(diffs[i].length < 1) diffs.remove(diffs[i]); + if (diffs[i].length < 1) + diffs.remove(diffs[i]); } --i; } - if(diffs.length > 0 && diffs[0].length > 0) - { + if (diffs.length > 0 && diffs[0].length > 0) CoolUtil.difficulties = diffs; - } } - if(CoolUtil.difficulties.contains(CoolUtil.defaultDifficulty)) + if (CoolUtil.difficulties.contains(CoolUtil.defaultDifficulty)) curDifficulty = Math.round(Math.max(0, CoolUtil.defaultDifficulties.indexOf(CoolUtil.defaultDifficulty))); else curDifficulty = 0; - var newPos:Int = CoolUtil.difficulties.indexOf(lastDifficultyName); - //trace('Pos of ' + lastDifficultyName + ' is ' + newPos); - if(newPos > -1) - { + var newPos = CoolUtil.difficulties.indexOf(lastDifficultyName); + if (newPos > -1) curDifficulty = newPos; + } + + function changeDifficulty(change:Int = 0) + { + if (CoolUtil.difficulties.length > 1) + { + curDifficulty = FlxMath.wrap(curDifficulty + change, 0, CoolUtil.difficulties.length - 1); + updateDifficulty(); } } - private function positionHighscore() { - if (isSoftcodedState()) return; - - scoreText.x = FlxG.width - scoreText.width - 6; + function updateDifficulty() + { + if (CoolUtil.difficulties.length > 0) + { + curDifficulty = FlxMath.wrap(curDifficulty, 0, CoolUtil.difficulties.length - 1); + lastDifficultyName = CoolUtil.difficulties[curDifficulty]; + } + else + { + lastDifficultyName = CoolUtil.defaultDifficulty; + } + + #if !switch + intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); + intendedRating = Highscore.getRating(songs[curSelected].songName, curDifficulty); + #end - scoreBG.scale.x = FlxG.width - scoreText.x + 6; - scoreBG.x = FlxG.width - (scoreBG.scale.x / 2); - diffText.x = Std.int(scoreBG.x + (scoreBG.width / 2)); - diffText.x -= diffText.width / 2; + PlayState.storyDifficulty = curDifficulty; + difficultyText.text = CoolUtil.difficulties.length <= 1 ? CoolUtil.difficultyString() : '< ' + CoolUtil.difficultyString() + ' >'; + positionScoreDisplay(); + } + + function positionScoreDisplay() + { + scoreText.x = FlxG.width - scoreText.width - 6; + scoreBackground.scale.x = FlxG.width - scoreText.x + 6; + scoreBackground.x = FlxG.width - (scoreBackground.scale.x / 2); + difficultyText.x = Std.int(scoreBackground.x + (scoreBackground.width / 2) - (difficultyText.width / 2)); } } class SongMetadata { - public var songName:String = ""; - public var week:Int = 0; - public var songCharacter:String = ""; - public var color:Int = -7179779; - public var folder:String = ""; + public var songName:String; + public var week:Int; + public var songCharacter:String; + public var color:Int; + public var folder:String; public function new(song:String, week:Int, songCharacter:String, color:Int) { @@ -503,7 +540,8 @@ class SongMetadata this.week = week; this.songCharacter = songCharacter; this.color = color; - this.folder = Paths.currentModDirectory; - if(this.folder == null) this.folder = ''; + this.folder = Mods.currentModDirectory; + + this.folder ??= ''; } } \ No newline at end of file diff --git a/source/game/states/LoadingState.hx b/source/game/states/LoadingState.hx index 0c98e1b..6b3319d 100644 --- a/source/game/states/LoadingState.hx +++ b/source/game/states/LoadingState.hx @@ -1,7 +1,5 @@ package game.states; -import lime.app.Promise; -import lime.app.Future; import flixel.FlxG; import flixel.FlxSprite; import flixel.FlxState; @@ -14,12 +12,35 @@ import flixel.math.FlxMath; import openfl.utils.Assets; import openfl.media.Sound; +import lime.app.Promise; +import lime.app.Future; +import lime.system.ThreadPool; +import lime.system.WorkOutput; +import lime.system.WorkOutput.ThreadMode; + import lime.utils.Assets as LimeAssets; import lime.utils.AssetLibrary; import lime.utils.AssetManifest; +import haxe.Exception; import haxe.io.Path; +import game.backend.StageData.StageFile; + +enum LoadTaskType { + FILE_IO; + IMAGE_PROCESSING; + AUDIO_PROCESSING; + JSON_PROCESSING; + MAIN_THREAD; +} + +typedef LoadTask = { + type:LoadTaskType, + execute:Void->Void, + ?description:String +} + class LoadingState extends MusicBeatState { static final MIN_TIME = 1.0; @@ -30,22 +51,69 @@ class LoadingState extends MusicBeatState var callbacks:MultiCallback; var targetShit:Float = 0; - #if sys - static var loadMutex = new sys.thread.Mutex(); - #end - - var loadQueue:ArrayVoid> = []; + static var fileIOPool:ThreadPool; + static var imageProcessingPool:ThreadPool; + static var audioProcessingPool:ThreadPool; + static var jsonProcessingPool:ThreadPool; + + var loadQueue:Array = []; var queueIndex:Int = 0; - var tasksPerFrame:Int = 3; + var tasksPerFrame:Int = 2; var loadingStarted:Bool = false; var startTimer:FlxTimer; + public final maxFileIOThreads:Int = 2; + public final maxImageThreads:Int = 2; + public final maxAudioThreads:Int = 1; + public final maxJSONThreads:Int = 2; + public function new(target:NextState, stopMusic:Bool, directory:String) { super(); this.target = target; this.stopMusic = stopMusic; this.directory = directory; + + initializeMultiChannelPools(); + } + + function initializeMultiChannelPools() + { + if (fileIOPool == null) { + fileIOPool = new ThreadPool(0, maxFileIOThreads); + fileIOPool.onComplete.add(onFileIOComplete); + fileIOPool.onError.add(onFileIOError); + #if (haxe_ver >= 4.1) + fileIOPool.onUncaughtError.add(onFileIOUncaughtError); + #end + } + + if (imageProcessingPool == null) { + imageProcessingPool = new ThreadPool(0, maxImageThreads); + imageProcessingPool.onComplete.add(onImageProcessingComplete); + imageProcessingPool.onError.add(onImageProcessingError); + #if (haxe_ver >= 4.1) + imageProcessingPool.onUncaughtError.add(onImageProcessingUncaughtError); + #end + } + + if (audioProcessingPool == null) { + audioProcessingPool = new ThreadPool(0, maxAudioThreads); + audioProcessingPool.onComplete.add(onAudioProcessingComplete); + audioProcessingPool.onError.add(onAudioProcessingError); + #if (haxe_ver >= 4.1) + audioProcessingPool.onUncaughtError.add(onAudioProcessingUncaughtError); + #end + } + + if (jsonProcessingPool == null) { + jsonProcessingPool = new ThreadPool(0, maxJSONThreads); + jsonProcessingPool.onComplete.add(onJSONProcessingComplete); + jsonProcessingPool.onError.add(onJSONProcessingError); + #if (haxe_ver >= 4.1) + jsonProcessingPool.onUncaughtError.add(onJSONProcessingUncaughtError); + #end + } } var funkay:FlxSprite; @@ -55,10 +123,14 @@ class LoadingState extends MusicBeatState override function create() { + Paths.clearStoredMemory(); + Paths.clearUnusedMemory(false); + var bg:FlxSprite = new FlxSprite(0, 0).makeGraphic(FlxG.width, FlxG.height, 0xffcaff4d); add(bg); + funkay = new FlxSprite(0, 0).loadGraphic(Paths.getPath('images/funkay.png', IMAGE)); - funkay.setGraphicSize(0, FlxG.height); + funkay.setGraphicSize(FlxG.width, FlxG.height); funkay.updateHitbox(); funkay.antialiasing = ClientPrefs.globalAntialiasing; add(funkay); @@ -81,14 +153,11 @@ class LoadingState extends MusicBeatState percentText.borderSize = 2; add(percentText); - @:privateAccess + @:privateAccess startTimer = new FlxTimer().start(CustomFadeTransition.lastDuration, (_) -> { loadingStarted = true; startLoading(); }); - - var fadeTime = 0.5; - FlxG.camera.fade(FlxG.camera.bgColor, fadeTime, true); } function startLoading() @@ -126,24 +195,68 @@ class LoadingState extends MusicBeatState { if (char != null) { - loadQueue.push(() -> { - var callback = callbacks.add("character:" + char); - loadCharacterJson(char, () -> callback()); + loadQueue.push({ + type: MAIN_THREAD, // JSON processing in main thread + execute: () -> { + var callback = callbacks.add("character:" + char); + loadCharacter(char, () -> callback()); + }, + description: 'Load character: $char' }); } } - } - - if (PlayState.SONG != null) - { - loadQueue.push(() -> checkLoadSong(getSongPath())); + + loadQueue.push({ + type: MAIN_THREAD, // Audio loading in main thread + execute: () -> checkLoadSong(getSongPath()), + description: 'Load song audio' + }); + if (PlayState.SONG.needsVoices) - loadQueue.push(() -> checkLoadSong(getVocalPath())); + { + var vocalPaths = getVocalPaths(); + for (vocalPath in vocalPaths) + { + loadQueue.push({ + type: MAIN_THREAD, + execute: () -> checkLoadSong(vocalPath), + description: 'Load vocal audio: $vocalPath' + }); + } + } + + var stage = PlayState.SONG.stage; + stage ??= StageData.vanillaSongStage(PlayState.SONG.song); + + var stageFile:StageFile = StageData.getStageFile(stage); + if (stageFile != null && stageFile.loadingImages != null) + { + for (image in stageFile.loadingImages) + { + loadQueue.push({ + type: MAIN_THREAD, // Image loading in main thread + execute: () -> { + var callback = callbacks.add("stageImage:" + image); + loadStageImage(image, () -> callback()); + }, + description: 'Load stage image: $image' + }); + } + } } - loadQueue.push(() -> checkLibrary("shared")); + loadQueue.push({ + type: MAIN_THREAD, // Library operations in main thread + execute: () -> checkLibrary("shared"), + description: 'Check shared library' + }); + if(directory != null && directory.length > 0 && directory != 'shared') { - loadQueue.push(() -> checkLibrary(directory)); + loadQueue.push({ + type: MAIN_THREAD, // Library operations in main thread + execute: () -> checkLibrary(directory), + description: 'Check directory library: $directory' + }); } } @@ -156,20 +269,12 @@ class LoadingState extends MusicBeatState var count = 0; while (queueIndex < loadQueue.length && count < tasksPerFrame) { - loadQueue[queueIndex](); + var task = loadQueue[queueIndex]; + executeTask(task); queueIndex++; count++; } } - - funkay.setGraphicSize(Std.int(0.88 * FlxG.width + 0.9 * (funkay.width - 0.88 * FlxG.width))); - funkay.updateHitbox(); - - if(controls.ACCEPT) - { - funkay.setGraphicSize(Std.int(funkay.width + 60)); - funkay.updateHitbox(); - } if(callbacks != null) { var progress:Float = 1 - (callbacks.numRemaining / callbacks.length); @@ -180,99 +285,116 @@ class LoadingState extends MusicBeatState percentText.text = '$percent%'; } } + + function executeTask(task:LoadTask) + { + trace('Executing task: ${task.description}'); + + try { + task.execute(); + } catch (e:Dynamic) { + trace('Error executing task ${task.description}: $e'); + } + } + + // for future stuff + function onFileIOComplete(state:Dynamic) {} + function onImageProcessingComplete(state:Dynamic) {} + function onAudioProcessingComplete(state:Dynamic) {} + function onJSONProcessingComplete(state:Dynamic) {} + function handleTaskError(state:Dynamic) {} + function onFileIOError(state:Dynamic) {} + function onImageProcessingError(state:Dynamic) {} + function onAudioProcessingError(state:Dynamic) {} + function onJSONProcessingError(state:Dynamic) {} + #if (haxe_ver >= 4.1) + function onFileIOUncaughtError(exception:Exception) {} + function onImageProcessingUncaughtError(exception:Exception) {} + function onAudioProcessingUncaughtError(exception:Exception) {} + function onJSONProcessingUncaughtError(exception:Exception) {} + #end - function loadCharacterJson(character:String, onComplete:Void->Void) + function loadCharacter(character:String, onComplete:Void->Void) { var characterPath:String = 'characters/' + character + '.json'; var path:String = Paths.getPath(characterPath, TEXT, null, true); + var rawJson:String = null; #if MODS_ALLOWED - if (sys.FileSystem.exists(path)) - { + if (sys.FileSystem.exists(path)) { + rawJson = sys.io.File.getContent(path); + } else #end if (Assets.exists(path)) { + rawJson = Assets.getText(path); + } + + if (rawJson != null) { try { - var rawJson = sys.io.File.getContent(path); var json:Dynamic = haxe.Json.parse(rawJson); - - if (json.image != null) { + if (json != null && json.image != null) { loadCharacterImage(json.image, onComplete); - } else { - loadCharacterImage('characters/' + character, onComplete); + return; } } catch (e:Dynamic) { - trace('Error loading character JSON: $character, error: $e'); - loadCharacterImage('characters/' + character, onComplete); + trace('Error parsing character JSON for $character: $e'); } } - else - #end - if (Assets.exists(path)) - { - Assets.loadText(path).onComplete(function(rawJson:String) { - try { - var json:Dynamic = haxe.Json.parse(rawJson); - - if (json.image != null) { - loadCharacterImage(json.image, onComplete); - } else { - loadCharacterImage('characters/' + character, onComplete); - } - } catch (e:Dynamic) { - trace('Error parsing character JSON: $character, error: $e'); - loadCharacterImage('characters/' + character, onComplete); - } - }).onError(function(e) { - trace('Error loading character JSON: $character, error: $e'); - loadCharacterImage('characters/' + character, onComplete); - }); - } - else - { - loadCharacterImage('characters/' + character, onComplete); - } + + // fallback to default image path + loadCharacterImage('characters/' + character, onComplete); } - + function loadCharacterImage(image:String, onComplete:Void->Void) { var callback = callbacks.add("characterImage:" + image); - #if flixel_animate - var animatePath:String = Paths.getPath('images/$image/Animation.json', TEXT, null, true); - - if (#if MODS_ALLOWED sys.FileSystem.exists(animatePath) || #end Assets.exists(animatePath)) - { - Paths.getAnimateAtlas(image); - callback(); - onComplete(); - return; - } - #end + var formats = checkImageFormats(image); - var xmlPath = Paths.getPath('images/$image.xml', TEXT, null, true); - if (Assets.exists(xmlPath) #if MODS_ALLOWED || sys.FileSystem.exists(xmlPath) #end) - { + if (formats.animate) + Paths.getAnimateAtlas(image); + else if (formats.xml) Paths.getSparrowAtlas(image, null, true); - callback(); - onComplete(); - return; - } - - var jsonPath = Paths.getPath('images/$image.json', TEXT, null, true); - if (Assets.exists(jsonPath) #if MODS_ALLOWED || sys.FileSystem.exists(jsonPath) #end) - { + else if (formats.json) Paths.getAsepriteAtlas(image, null, true); - callback(); - onComplete(); - return; - } + else if (formats.txt) + Paths.getPackerAtlas(image, null, true); + else if (formats.png) + Paths.image(image); + else + trace('WARNING: Character image not found: $image'); - var txtPath = Paths.getPath('images/$image.txt', TEXT, null, true); - if (Assets.exists(txtPath) #if MODS_ALLOWED || sys.FileSystem.exists(txtPath) #end) - { + callback(); + onComplete(); + } + + function checkImageFormats(image:String):Dynamic + { + return { + animate: #if flixel_animate Assets.exists(Paths.getPath('images/$image/Animation.json', TEXT, null, true)) #else false #end, + xml: Assets.exists(Paths.getPath('images/$image.xml', TEXT, null, true)), + json: Assets.exists(Paths.getPath('images/$image.json', TEXT, null, true)), + txt: Assets.exists(Paths.getPath('images/$image.txt', TEXT, null, true)), + png: Assets.exists(Paths.getPath('images/$image.png', IMAGE, null, true)) + }; + } + + function loadStageImage(image:String, onComplete:Void->Void) + { + var callback = callbacks.add("stageImage:" + image); + + var formats = checkImageFormats(image); + + if (formats.animate) + Paths.getAnimateAtlas(image); + else if (formats.xml) + Paths.getSparrowAtlas(image, null, true); + else if (formats.json) + Paths.getAsepriteAtlas(image, null, true); + else if (formats.txt) Paths.getPackerAtlas(image, null, true); - callback(); - onComplete(); - return; - } + else if (formats.png) + Paths.image(image); + else + trace('WARNING: Stage image not found: $image'); callback(); onComplete(); @@ -280,27 +402,25 @@ class LoadingState extends MusicBeatState function checkLoadSong(path:String) { - if (path.startsWith('contents/')) { + if (path == null) { + var callback = callbacks.add("null_song_path"); + callback(); + return; + } + + #if MODS_ALLOWED + if (path.startsWith('${Mods.MODS_FOLDER}/')) { var callback = callbacks.add("modSong:" + path); - #if MODS_ALLOWED - if (sys.FileSystem.exists(path)) { - try { - var sound = Sound.fromFile(path); - Paths.currentTrackedSounds.set(path, sound); - callback(); - } catch (e:Dynamic) { - trace('Error loading mod sound: $path, error: $e'); - callback(); - } - } else { - trace('Mod sound not found: $path'); + try { + var sound = Sound.fromFile(path); + Paths.currentTrackedSounds.set(path, sound); + callback(); + } catch (e:Dynamic) { + trace('Error loading mod sound: $path, error: $e'); callback(); } - #else - callback(); - #end - } else { + } else #end { if (!Assets.cache.hasSound(path)) { var callback = callbacks.add("song:" + path); @@ -318,9 +438,6 @@ class LoadingState extends MusicBeatState } function checkLibrary(library:String) { - #if sys - loadMutex.acquire(); - #end try { if (Assets.getLibrary(library) == null) { @@ -328,9 +445,6 @@ class LoadingState extends MusicBeatState if (!LimeAssets.libraryPaths.exists(library)) { trace('Library $library not found, but continuing anyway'); - #if sys - loadMutex.release(); - #end var callback = callbacks.add("missing_library:" + library); callback(); return; @@ -349,20 +463,15 @@ class LoadingState extends MusicBeatState } } catch (e) { trace('Exception in checkLibrary: $e'); - #if sys - loadMutex.release(); - #end var callback = callbacks.add("error_library:" + library); callback(); } - #if sys - loadMutex.release(); - #end } function onLoad() { - trace('Loading complete!'); + trace('Loading complete! Loaded ${callbacks.getFired().length} items'); + if (stopMusic) FlxG.sound?.music?.stop(); @@ -371,56 +480,85 @@ class LoadingState extends MusicBeatState static function getSongPath():String { - #if MODS_ALLOWED - var modPath = modsSongs('${Paths.formatToSongPath(PlayState.SONG.song)}/Inst'); - if (sys.FileSystem.exists(modPath)) { - return modPath; - } - #end return instPath(PlayState.SONG.song); } - static function getVocalPath():String + static function getVocalPaths():Array { - #if MODS_ALLOWED - var modPath = modsSongs('${Paths.formatToSongPath(PlayState.SONG.song)}/Voices'); - if (sys.FileSystem.exists(modPath)) { - return modPath; - } - #end - return voicesPath(PlayState.SONG.song); + var paths:Array = []; + + var vocalsPath:String = voicesPath(PlayState.SONG.song); + if (vocalsPath != null) paths.push(vocalsPath); + + var playerVocalsFile:String = PlayState.instance?.boyfriend?.vocalsFile ?? 'Player'; + var playerVocals:String = voicesPath(PlayState.SONG.song, playerVocalsFile); + if (playerVocals != null) paths.push(playerVocals); + + var opponentVocalsFile:String = PlayState.instance?.dad?.vocalsFile ?? 'Opponent'; + var opponentVocals:String = voicesPath(PlayState.SONG.song, opponentVocalsFile); + if (opponentVocals != null) paths.push(opponentVocals); + + return paths; } - #if MODS_ALLOWED - static function modsSongs(key:String) + static function instPath(song:String):String { - return Paths.mods('songs/$key.${Paths.SOUND_EXT}'); + var songKey:String = '${Paths.formatToSongPath(song)}/inst'; + + var path = getSoundFilePath(null, songKey, 'songs'); + if (path == null) { + songKey = '${Paths.formatToSongPath(song)}/Inst'; + path = getSoundFilePath(null, songKey, 'songs'); + } + + return path; } - #end - static public function instPath(song:String):String + static function voicesPath(song:String, postfix:String = null):String { - var songKey:String = '${Paths.formatToSongPath(song)}/Inst'; - #if MODS_ALLOWED - var modPath = Paths.mods('songs/$songKey.${Paths.SOUND_EXT}'); - if (sys.FileSystem.exists(modPath)) { - return modPath; + var songKey:String = '${Paths.formatToSongPath(song)}/voices'; + if (postfix != null) songKey += '-' + postfix; + + var path = getSoundFilePath(null, songKey, 'songs'); + if (path == null) { + songKey = '${Paths.formatToSongPath(song)}/Voices'; + if (postfix != null) songKey += '-' + postfix; + path = getSoundFilePath(null, songKey, 'songs'); } - #end - return Paths.getPath('$songKey.${Paths.SOUND_EXT}', SOUND, 'songs', true); + return path; } - static public function voicesPath(song:String, postfix:String = null):String + @:noCompletion + private static function getSoundFilePath(path:Null, key:String, ?library:String):String { - var songKey:String = '${Paths.formatToSongPath(song)}/Voices'; - if (postfix != null) songKey += '-' + postfix; #if MODS_ALLOWED - var modPath = Paths.mods('songs/$songKey.${Paths.SOUND_EXT}'); - if (sys.FileSystem.exists(modPath)) { - return modPath; + var modLibPath:String = ''; + if (library != null) modLibPath = '$library/'; + if (path != null) modLibPath += '$path/'; + + var file:String = Mods.modsSounds(modLibPath, key, Paths.WAV_EXT); + if(FileSystem.exists(file)) { + return file; + } + + file = Mods.modsSounds(modLibPath, key); + if(FileSystem.exists(file)) { + return file; } #end - return Paths.getPath('$songKey.${Paths.SOUND_EXT}', SOUND, 'songs', true); + + var fullKey = (path != null ? '$path/' : '') + key; + var wavPath:String = Paths.getPath('$fullKey.${Paths.WAV_EXT}', SOUND, library); + if(Assets.exists(wavPath, SOUND)) { + return wavPath; + } + + var standardPath:String = Paths.getPath('$fullKey.${Paths.SOUND_EXT}', SOUND, library); + if(Assets.exists(standardPath, SOUND)) { + return standardPath; + } + + return null; } public static function loadAndSwitchState(targetFactory:Void->NextState, stopMusic = false) @@ -438,7 +576,7 @@ class LoadingState extends MusicBeatState var loaded:Bool = true; if (PlayState.SONG != null) { loaded = isSoundLoaded(getSongPath()) && - (!PlayState.SONG.needsVoices || isSoundLoaded(getVocalPath())) && + (!PlayState.SONG.needsVoices || areVocalsLoaded()) && isLibraryLoaded("shared") && isLibraryLoaded(directory) && #if MODS_ALLOWED isModsLoaded() #else true #end && @@ -471,6 +609,19 @@ class LoadingState extends MusicBeatState if (char != null && !isCharacterLoaded(char)) return false; } + + var stage = PlayState.SONG.stage; + stage ??= StageData.vanillaSongStage(PlayState.SONG.song); + + var stageFile:StageFile = StageData.getStageFile(stage); + if (stageFile != null && stageFile.loadingImages != null) + { + for (image in stageFile.loadingImages) + { + if (!isStageImageLoaded(image)) + return false; + } + } } return true; } @@ -495,23 +646,50 @@ class LoadingState extends MusicBeatState return false; } + + static function isStageImageLoaded(image:String):Bool + { + var pathsToCheck = [ + Paths.getPath('images/$image.png', IMAGE, null, true), + Paths.getPath('images/$image.xml', TEXT, null, true), + Paths.getPath('images/$image.json', TEXT, null, true), + Paths.getPath('images/$image.txt', TEXT, null, true) + ]; + + for (path in pathsToCheck) + { + #if MODS_ALLOWED + if (sys.FileSystem.exists(path)) return true; + #end + if (Assets.exists(path)) return true; + } + + return false; + } #if MODS_ALLOWED static function isModsLoaded():Bool { - return Paths.getGlobalMods().length > 0; + return Mods.getGlobalMods().length > 0; } #end + + static function areVocalsLoaded():Bool + { + var vocalPaths = getVocalPaths(); + for (path in vocalPaths) + { + if (!isSoundLoaded(path)) + return false; + } + return true; + } static function isSoundLoaded(path:String):Bool { - if (path.startsWith('contents/')) { - #if MODS_ALLOWED - return sys.FileSystem.exists(path); - #else - return false; - #end - } + #if MODS_ALLOWED + if (path.startsWith('${Mods.MODS_FOLDER}/')) return sys.FileSystem.exists(path); + #end return Assets.cache.hasSound(path); } @@ -527,7 +705,7 @@ class LoadingState extends MusicBeatState callbacks = null; percentText?.destroy(); - if (startTimer != null) startTimer.destroy(); + startTimer?.destroy(); } static function initSongsManifest() diff --git a/source/game/states/MainMenuState.hx b/source/game/states/MainMenuState.hx index 6e6e4b8..442995c 100644 --- a/source/game/states/MainMenuState.hx +++ b/source/game/states/MainMenuState.hx @@ -7,17 +7,14 @@ import flixel.FlxG; import flixel.FlxObject; import flixel.FlxSprite; import flixel.FlxCamera; -import flixel.addons.transition.TransitionData; import flixel.addons.transition.FlxTransitionableState; import flixel.effects.FlxFlicker; -import flixel.graphics.frames.FlxAtlasFrames; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.text.FlxText; -import flixel.math.FlxMath; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.util.FlxColor; -import flixel.input.keyboard.FlxKey; + import lime.app.Application; import game.states.backend.Achievements; @@ -27,13 +24,7 @@ using StringTools; class MainMenuState extends MusicBeatState { - public static var curSelected:Int = 0; - - var menuItems:FlxTypedGroup; - private var camGame:FlxCamera; - private var camAchievement:FlxCamera; - - var optionShit:Array = [ + static var MENU_OPTIONS = [ 'story_mode', 'freeplay', #if MODS_ALLOWED 'mods', #end @@ -42,10 +33,15 @@ class MainMenuState extends MusicBeatState 'options' ]; - var magenta:FlxSprite; + var menuItems:FlxTypedGroup; + var background:FlxSprite; + var overlay:FlxSprite; + var versionText:FlxText; var camFollow:FlxObject; - var camFollowPos:FlxObject; - var debugKeys:Array; + + public static var curSelected:Int = 0; + + var isTransitioning:Bool = false; override function create() { @@ -53,229 +49,258 @@ class MainMenuState extends MusicBeatState Paths.clearUnusedMemory(); #if MODS_ALLOWED - Paths.pushGlobalMods(); + Mods.pushGlobalMods(); #end WeekData.loadTheFirstEnabledMod(); #if DISCORD_ALLOWED DiscordClient.changePresence("In the Menus", null); #end - debugKeys = ClientPrefs.copyKey(ClientPrefs.keyBinds.get('debug_1')); - - camGame = initFNFCamera(); - camAchievement = new FlxCamera(); - camAchievement.bgColor.alpha = 0; - - FlxG.cameras.add(camAchievement, false); - - persistentUpdate = persistentDraw = true; + initCamera(); + if (!isSoftcodedState()) { - var yScroll:Float = Math.max(0.25 - (0.05 * (optionShit.length - 4)), 0.1); - var bg:FlxSprite = new FlxSprite(-80).loadGraphic(Paths.image('menuBG')); - bg.scrollFactor.set(0, yScroll); - bg.setGraphicSize(Std.int(bg.width * 1.175)); - bg.updateHitbox(); - bg.screenCenter(); - bg.antialiasing = ClientPrefs.globalAntialiasing; - add(bg); - - camFollow = new FlxObject(0, 0, 1, 1); - camFollowPos = new FlxObject(0, 0, 1, 1); - add(camFollow); - add(camFollowPos); - - magenta = new FlxSprite(-80).loadGraphic(Paths.image('menuDesat')); - magenta.scrollFactor.set(0, yScroll); - magenta.setGraphicSize(Std.int(magenta.width * 1.175)); - magenta.updateHitbox(); - magenta.screenCenter(); - magenta.visible = false; - magenta.antialiasing = ClientPrefs.globalAntialiasing; - magenta.color = 0xFFfd719b; - add(magenta); + if(FlxG.sound.music == null) FlxG.sound.playMusic(Paths.music('freakyMenu')); - menuItems = new FlxTypedGroup(); - add(menuItems); - - var scale:Float = 1; - for (i in 0...optionShit.length) - { - var offset:Float = 108 - (Math.max(optionShit.length, 4) - 4) * 80; - var menuItem:FlxSprite = new FlxSprite(0, (i * 140) + offset); - menuItem.scale.x = scale; - menuItem.scale.y = scale; - menuItem.frames = Paths.getSparrowAtlas('mainmenu/menu_' + optionShit[i]); - menuItem.animation.addByPrefix('idle', optionShit[i] + " basic", 24); - menuItem.animation.addByPrefix('selected', optionShit[i] + " white", 24); - menuItem.animation.play('idle'); - menuItem.ID = i; - menuItem.screenCenter(X); - menuItems.add(menuItem); - var scr:Float = (optionShit.length - 4) * 0.135; - if(optionShit.length < 6) scr = 0; - menuItem.scrollFactor.set(0, scr); - menuItem.antialiasing = ClientPrefs.globalAntialiasing; - menuItem.updateHitbox(); - } - - var versionShit:FlxText = new FlxText(12, FlxG.height - 44, 0, "Psych Engine v" + '0.6.3', 12); - versionShit.scrollFactor.set(); - versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - add(versionShit); - var versionShit:FlxText = new FlxText(12, FlxG.height - 24, 0, "CorruptCore Engine v" + Application.current.meta.get('version'), 12); - versionShit.scrollFactor.set(); - versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - add(versionShit); + createMenuStuff(); - changeItem(); + FlxG.camera.follow(camFollow, null, 0.17); #if ACHIEVEMENTS_ALLOWED - Achievements.loadAchievements(); - var leDate = Date.now(); - if (leDate.getDay() == 5 && leDate.getHours() >= 18) { - var achieveID:Int = Achievements.getAchievementIndex('friday_night_play'); - if(!Achievements.isAchievementUnlocked(Achievements.achievementsStuff[achieveID][2])) { - Achievements.achievementsMap.set(Achievements.achievementsStuff[achieveID][2], true); - giveAchievement(); - ClientPrefs.saveSettings(); - } - } + checkFridayNightAchievement(); #end - - FlxG.camera.follow(camFollow, null, 0.6); - } - else - { - if(FlxG.sound.music == null) { - FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); - } } super.create(); } - #if ACHIEVEMENTS_ALLOWED - function giveAchievement() { - add(new AchievementObject('friday_night_play', camAchievement)); - FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); - trace('Giving achievement "friday_night_play"'); + function initCamera() + { + persistentUpdate = persistentDraw = true; + initFNFCamera(); + add(camFollow = new FlxObject(0, 0, 1, 1)); } - #end - var selectedSomethin:Bool = false; + function createMenuStuff() + { + createBackground(); + createMenuItems(); + createVersionText(); + } - override function update(elapsed:Float) + function createBackground() { - if (FlxG.sound.music.volume < 0.8) + var yScroll = Math.max(0.25 - (0.05 * (MENU_OPTIONS.length - 4)), 0.1); + + background = new FlxSprite(-80).loadGraphic(Paths.image('menuBG')); + background.scrollFactor.set(0, yScroll); + background.setGraphicSize(Std.int(background.width * 1.175)); + background.updateHitbox(); + background.screenCenter(); + background.antialiasing = ClientPrefs.globalAntialiasing; + add(background); + + overlay = new FlxSprite(-80).loadGraphic(Paths.image('menuDesat')); + overlay.scrollFactor.set(0, yScroll); + overlay.setGraphicSize(Std.int(overlay.width * 1.175)); + overlay.updateHitbox(); + overlay.screenCenter(); + overlay.visible = false; + overlay.antialiasing = ClientPrefs.globalAntialiasing; + overlay.color = 0xFFfd719b; + add(overlay); + } + + function createMenuItems() + { + menuItems = new FlxTypedGroup(); + add(menuItems); + + var itemSpacing = 140; + var baseOffset = 108 - Math.max(MENU_OPTIONS.length - 4, 0) * 80; + + for (i in 0...MENU_OPTIONS.length) { - FlxG.sound.music.volume += 0.5 * elapsed; - if (FreeplayState.vocals != null) - FreeplayState.vocals.volume += 0.5 * elapsed; + var menuItem = createMenuItem(i, baseOffset + i * itemSpacing); + menuItems.add(menuItem); } - if (!isSoftcodedState() && !selectedSomethin) - { - if (controls.UI_UP_P) - changeItem(-1); + changeSelection(); + } - if (controls.UI_DOWN_P) - changeItem(1); + function createMenuItem(id:Int, yPos:Float):FlxSprite + { + var menuItem = new FlxSprite(0, yPos); + menuItem.frames = Paths.getSparrowAtlas('mainmenu/menu_' + MENU_OPTIONS[id]); + menuItem.antialiasing = ClientPrefs.globalAntialiasing; + menuItem.animation.addByPrefix('idle', MENU_OPTIONS[id] + " basic", 24); + menuItem.animation.addByPrefix('selected', MENU_OPTIONS[id] + " white", 24); + menuItem.animation.play('idle'); + menuItem.centerOffsets(); + menuItem.screenCenter(X); + menuItem.updateHitbox(); + menuItem.ID = id; + + var scrollFactor = (MENU_OPTIONS.length - 4) * 0.135; + if (MENU_OPTIONS.length < 6) scrollFactor = 0; + menuItem.scrollFactor.set(0, scrollFactor); + + return menuItem; + } - if (controls.BACK) - { - selectedSomethin = true; - FlxG.sound.play(Paths.sound('cancelMenu')); - FlxG.switchState(() -> new TitleState()); - } + function createVersionText() + { + versionText = new FlxText(12, FlxG.height - 36, 0, getVersionString(), 12); + versionText.scrollFactor.set(); + versionText.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + add(versionText); + } - if (controls.ACCEPT) - { - FlxG.sound.play(Paths.sound('confirmMenu')); - if (optionShit[curSelected] == 'donate') - { - CoolUtil.browserLoad('https://ninja-muffin24.itch.io/funkin'); - } - else - { - selectedSomethin = true; - - if (ClientPrefs.flashing) - FlxFlicker.flicker(magenta, 1.1, 0.15, false); - - FlxFlicker.flicker(menuItems.members[curSelected], 1, 0.06, false, false, function(flick:FlxFlicker) - { - switch (optionShit[curSelected]) - { - case 'story_mode': - FlxG.switchState(() -> new StoryMenuState()); - case 'freeplay': - FlxG.switchState(() -> new FreeplayState()); - - #if MODS_ALLOWED - case 'mods': - FlxG.switchState(() -> new ModsMenuState()); - #end - - #if ACHIEVEMENTS_ALLOWED - case 'awards': - FlxG.switchState(() -> new AchievementsMenuState()); - #end - - case 'credits': - FlxG.switchState(() -> new CreditsState()); - case 'options': - LoadingState.loadAndSwitchState(() -> new OptionsState()); - OptionsState.onPlayState = false; - } - }); - - for (i in 0...menuItems.members.length) - { - if (i == curSelected) - continue; - FlxTween.tween(menuItems.members[i], {alpha: 0}, 0.4, { - ease: FlxEase.quadOut, - onComplete: function(twn:FlxTween) - { - menuItems.members[i].kill(); - } - }); - } - } + function getVersionString():String + { + var baseVersion = "Based on Psych Engine v0.6.3"; + var customVersion = "CorruptCore Engine v" + Application.current.meta.get('version'); + return '$baseVersion\n$customVersion'; + } + + #if ACHIEVEMENTS_ALLOWED + function checkFridayNightAchievement() + { + var currentDate = Date.now(); + if (currentDate.getDay() == 5 && currentDate.getHours() >= 18) { + var achieveID = Achievements.getAchievementIndex('friday_night_play'); + if (!Achievements.isAchievementUnlocked(Achievements.achievementsStuff[achieveID][2])) { + Achievements.achievementsMap.set(Achievements.achievementsStuff[achieveID][2], true); + unlockAchievement(); } - #if desktop - else if (FlxG.keys.anyJustPressed(debugKeys)) + } + } + + function unlockAchievement() + { + add(new AchievementObject('friday_night_play', new FlxCamera())); + FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); + } + #end + + override function update(elapsed:Float) + { + if (FlxG.sound.music?.volume < 0.8) + FlxG.sound.music.volume += 0.5 * elapsed; + + if (!isTransitioning) + { + if (!isSoftcodedState()) { - selectedSomethin = true; - FlxG.switchState(() -> new MasterEditorMenu()); + handleInput(); } - #end } super.update(elapsed); } - function changeItem(huh:Int = 0) + function handleInput() + { + if (controls.UI_UP_P) + changeSelection(-1); + else if (controls.UI_DOWN_P) + changeSelection(1); + else if (controls.BACK) + exitToTitle(); + else if (controls.ACCEPT) + selectMenuItem(); + #if desktop + else if (FlxG.keys.anyJustPressed(ClientPrefs.copyKey(ClientPrefs.keyBinds.get('debug_1')))) + openEditors(); + #end + } + + function changeSelection(change:Int = 0) { FlxG.sound.play(Paths.sound('scrollMenu')); + menuItems.members[curSelected].animation.play('idle'); menuItems.members[curSelected].updateHitbox(); - menuItems.members[curSelected].screenCenter(X); + + curSelected = FlxMath.wrap(curSelected + change, 0, MENU_OPTIONS.length - 1); + + var selectedItem = menuItems.members[curSelected]; + selectedItem.animation.play('selected'); + selectedItem.centerOffsets(); + + camFollow.setPosition( + selectedItem.getGraphicMidpoint().x, + selectedItem.getGraphicMidpoint().y - (MENU_OPTIONS.length > 4 ? MENU_OPTIONS.length * 8 : 0) + ); + } - curSelected += huh; + function exitToTitle() + { + isTransitioning = true; + FlxG.sound.play(Paths.sound('cancelMenu')); + FlxG.switchState(() -> new TitleState()); + } - if (curSelected >= menuItems.length) - curSelected = 0; - if (curSelected < 0) - curSelected = menuItems.length - 1; + function selectMenuItem() + { + isTransitioning = true; + FlxG.sound.play(Paths.sound('confirmMenu')); - menuItems.members[curSelected].animation.play('selected'); - menuItems.members[curSelected].centerOffsets(); - menuItems.members[curSelected].screenCenter(X); + if (ClientPrefs.flashing) + FlxFlicker.flicker(overlay, 1.1, 0.15, false); - camFollow.setPosition(menuItems.members[curSelected].getGraphicMidpoint().x, - menuItems.members[curSelected].getGraphicMidpoint().y - (menuItems.length > 4 ? menuItems.length * 8 : 0)); + FlxFlicker.flicker(menuItems.members[curSelected], 1, 0.06, false, false, (_) -> + { + transitionToSelectedState(); + }); + + fadeOutUnselectedItems(); + } + + function fadeOutUnselectedItems() + { + for (item in menuItems.members) + { + if (item.ID != curSelected) + { + FlxTween.tween(item, {alpha: 0}, 0.4, { + ease: FlxEase.quadOut, + onComplete: (_) -> item.kill() + }); + } + } } + + function transitionToSelectedState() + { + switch (MENU_OPTIONS[curSelected]) + { + case 'story_mode': + FlxG.switchState(() -> new StoryMenuState()); + case 'freeplay': + FlxG.switchState(() -> new FreeplayState()); + #if MODS_ALLOWED + case 'mods': + FlxG.switchState(() -> new ModsMenuState()); + #end + #if ACHIEVEMENTS_ALLOWED + case 'awards': + FlxG.switchState(() -> new AchievementsMenuState()); + #end + case 'credits': + FlxG.switchState(() -> new CreditsState()); + case 'options': + LoadingState.loadAndSwitchState(() -> new OptionsState()); + OptionsState.onPlayState = false; + } + } + + #if desktop + function openEditors() + { + isTransitioning = true; + FlxG.switchState(() -> new MasterEditorMenu()); + } + #end } \ No newline at end of file diff --git a/source/game/states/ModsMenuState.hx b/source/game/states/ModsMenuState.hx index fd283d3..b5c4aaf 100644 --- a/source/game/states/ModsMenuState.hx +++ b/source/game/states/ModsMenuState.hx @@ -3,8 +3,11 @@ package game.states; #if DISCORD_ALLOWED import api.Discord.DiscordClient; #end -import openfl.text.TextField; + +import game.backend.system.Mods.ModMetadata; + import flixel.FlxG; +import flixel.FlxBasic; import flixel.FlxSprite; import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; @@ -12,750 +15,1002 @@ import flixel.addons.ui.FlxButtonPlus; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.math.FlxMath; import flixel.text.FlxText; +import flixel.ui.FlxButton; import flixel.util.FlxColor; +import flixel.sound.FlxSound; import flixel.tweens.FlxTween; + import lime.utils.Assets; -import flixel.sound.FlxSound; + +import openfl.display.BitmapData; +import openfl.geom.Rectangle; +import openfl.text.TextField; import openfl.utils.Assets as OpenFlAssets; -import sys.io.File; -import sys.FileSystem; + import haxe.Json; +import haxe.io.Bytes; +import haxe.io.BytesInput; import haxe.format.JsonParser; -import openfl.display.BitmapData; -import openfl.geom.Rectangle; -import flixel.ui.FlxButton; -import flixel.FlxBasic; -import sys.io.File; -/*import haxe.zip.Reader; +import haxe.zip.Reader; import haxe.zip.Entry; -import haxe.zip.Uncompress; -import haxe.zip.Writer;*/ + +#if sys +import sys.io.File; +import sys.FileSystem; +#end using StringTools; class ModsMenuState extends MusicBeatState { - var mods:Array = []; static var changedAThing = false; - var bg:FlxSprite; - var intendedColor:Int; - var colorTween:FlxTween; - - var noModsTxt:FlxText; - var selector:AttachedSprite; - var descriptionTxt:FlxText; - var needaReset = false; - private static var curSelected:Int = 0; - public static var defaultColor:FlxColor = 0xFF665AFF; - - var buttonDown:FlxButton; - var buttonTop:FlxButton; - var buttonDisableAll:FlxButton; - var buttonEnableAll:FlxButton; - var buttonUp:FlxButton; - var buttonToggle:FlxButton; - var buttonsArray:Array = []; - - var installButton:FlxButton; - var removeButton:FlxButton; - - var modsList:Array = []; - - var visibleWhenNoMods:Array = []; - var visibleWhenHasMods:Array = []; - - override function create() - { - Paths.clearStoredMemory(); - Paths.clearUnusedMemory(); - WeekData.setDirectoryFromWeek(); - - #if DISCORD_ALLOWED - // Updating Discord Rich Presence - DiscordClient.changePresence("In the Menus", null); - #end - - bg = new FlxSprite().loadGraphic(Paths.image('menuDesat')); - bg.antialiasing = ClientPrefs.globalAntialiasing; - add(bg); - bg.screenCenter(); - - noModsTxt = new FlxText(0, 0, FlxG.width, "NO MODS INSTALLED\nPRESS BACK TO EXIT AND INSTALL A MOD", 48); - if(FlxG.random.bool(0.1)) noModsTxt.text += '\nBITCH.'; //meanie - noModsTxt.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - noModsTxt.scrollFactor.set(); - noModsTxt.borderSize = 2; - add(noModsTxt); - noModsTxt.screenCenter(); - visibleWhenNoMods.push(noModsTxt); - - var path:String = 'modsList.txt'; - if(FileSystem.exists(path)) - { - var leMods:Array = CoolUtil.coolTextFile(path); - for (i in 0...leMods.length) - { - if(leMods.length > 1 && leMods[0].length > 0) { - var modSplit:Array = leMods[i].split('|'); - if(!Paths.ignoreModFolders.contains(modSplit[0].toLowerCase())) - { - addToModsList([modSplit[0], (modSplit[1] == '1')]); - //trace(modSplit[1]); - } - } - } - } - - // FIND MOD FOLDERS - var boolshit = true; - if (FileSystem.exists("modsList.txt")){ - for (folder in Paths.getModDirectories()) - { - if(!Paths.ignoreModFolders.contains(folder)) - { - addToModsList([folder, true]); //i like it false by default. -bb //Well, i like it True! -Shadow - } - } - } - saveTxt(); - - selector = new AttachedSprite(); - selector.xAdd = -205; - selector.yAdd = -68; - selector.alphaMult = 0.5; - makeSelectorGraphic(); - add(selector); - visibleWhenHasMods.push(selector); - - //attached buttons - var startX:Int = 1120; - - buttonToggle = new FlxButton(startX, 0, "ON", function() - { - if(mods[curSelected].restart) - { - needaReset = true; - } - modsList[curSelected][1] = !modsList[curSelected][1]; - updateButtonToggle(); - FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); - }); - buttonToggle.setGraphicSize(50, 50); - buttonToggle.updateHitbox(); - add(buttonToggle); - buttonsArray.push(buttonToggle); - visibleWhenHasMods.push(buttonToggle); - - buttonToggle.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.WHITE, CENTER); - setAllLabelsOffset(buttonToggle, -15, 10); - startX -= 70; - - buttonUp = new FlxButton(startX, 0, "/\\", function() - { - moveMod(-1); - FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); - }); - buttonUp.setGraphicSize(50, 50); - buttonUp.updateHitbox(); - add(buttonUp); - buttonsArray.push(buttonUp); - visibleWhenHasMods.push(buttonUp); - buttonUp.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); - setAllLabelsOffset(buttonUp, -15, 10); - startX -= 70; - - buttonDown = new FlxButton(startX, 0, "\\/", function() { - moveMod(1); - FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); - }); - buttonDown.setGraphicSize(50, 50); - buttonDown.updateHitbox(); - add(buttonDown); - buttonsArray.push(buttonDown); - visibleWhenHasMods.push(buttonDown); - buttonDown.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); - setAllLabelsOffset(buttonDown, -15, 10); - - startX -= 100; - buttonTop = new FlxButton(startX, 0, "TOP", function() { - var doRestart:Bool = (mods[0].restart || mods[curSelected].restart); - for (i in 0...curSelected) //so it shifts to the top instead of replacing the top one - { - moveMod(-1, true); - } - - if(doRestart) needaReset = true; - FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); - }); - buttonTop.setGraphicSize(80, 50); - buttonTop.updateHitbox(); - buttonTop.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); - setAllLabelsOffset(buttonTop, 0, 10); - add(buttonTop); - buttonsArray.push(buttonTop); - visibleWhenHasMods.push(buttonTop); - - - startX -= 190; - buttonDisableAll = new FlxButton(startX, 0, "DISABLE ALL", function() { - for (i in modsList) - { - i[1] = false; - } - for (mod in mods) - { - if (mod.restart) - { - needaReset = true; - break; - } - } - updateButtonToggle(); - FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); - }); - buttonDisableAll.setGraphicSize(170, 50); - buttonDisableAll.updateHitbox(); - buttonDisableAll.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); - buttonDisableAll.label.fieldWidth = 170; - setAllLabelsOffset(buttonDisableAll, 0, 10); - add(buttonDisableAll); - buttonsArray.push(buttonDisableAll); - visibleWhenHasMods.push(buttonDisableAll); - - startX -= 190; - buttonEnableAll = new FlxButton(startX, 0, "ENABLE ALL", function() { - for (i in modsList) - { - i[1] = true; - } - for (mod in mods) - { - if (mod.restart) - { - needaReset = true; - break; - } - } - updateButtonToggle(); - FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); - }); - buttonEnableAll.setGraphicSize(170, 50); - buttonEnableAll.updateHitbox(); - buttonEnableAll.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); - buttonEnableAll.label.fieldWidth = 170; - setAllLabelsOffset(buttonEnableAll, 0, 10); - add(buttonEnableAll); - buttonsArray.push(buttonEnableAll); - visibleWhenHasMods.push(buttonEnableAll); - - // more buttons - var startX:Int = 1100; - - - - - /* - installButton = new FlxButton(startX, 620, "Install Mod", function() - { - installMod(); - }); - installButton.setGraphicSize(150, 70); - installButton.updateHitbox(); - installButton.color = FlxColor.GREEN; - installButton.label.fieldWidth = 135; - installButton.label.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, CENTER); - setAllLabelsOffset(installButton, 2, 24); - add(installButton); - startX -= 180; - - removeButton = new FlxButton(startX, 620, "Delete Selected Mod", function() - { - var path = haxe.io.Path.join([Paths.mods(), modsList[curSelected][0]]); - if(FileSystem.exists(path) && FileSystem.isDirectory(path)) - { - trace('Trying to delete directory ' + path); - try - { - FileSystem.deleteFile(path); //FUCK YOU HAXE WHY DONT YOU WORK WAAAAAAAAAAAAH - - var icon = mods[curSelected].icon; - var alphabet = mods[curSelected].alphabet; - remove(icon); - remove(alphabet); - icon.destroy(); - alphabet.destroy(); - modsList.remove(modsList[curSelected]); - mods.remove(mods[curSelected]); - - if(curSelected >= mods.length) --curSelected; - changeSelection(); - } - catch(e) - { - trace('Error deleting directory: ' + e); - } - } - }); - removeButton.setGraphicSize(150, 70); - removeButton.updateHitbox(); - removeButton.color = FlxColor.RED; - removeButton.label.fieldWidth = 135; - removeButton.label.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, CENTER); - setAllLabelsOffset(removeButton, 2, 15); - add(removeButton); - visibleWhenHasMods.push(removeButton);*/ - - /////// - descriptionTxt = new FlxText(148, 0, FlxG.width - 216, "", 32); - descriptionTxt.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, LEFT); - descriptionTxt.scrollFactor.set(); - add(descriptionTxt); - visibleWhenHasMods.push(descriptionTxt); - - var i:Int = 0; - var len:Int = modsList.length; - while (i < modsList.length) - { - var values:Array = modsList[i]; - if(!FileSystem.exists(Paths.mods(values[0]))) - { - modsList.remove(modsList[i]); - continue; - } - - var newMod:ModMetadata = new ModMetadata(values[0]); - mods.push(newMod); - - newMod.alphabet = new Alphabet(0, 0, mods[i].name, true); - var scale:Float = Math.min(840 / newMod.alphabet.width, 1); - newMod.alphabet.scaleX = scale; - newMod.alphabet.scaleY = scale; - newMod.alphabet.y = i * 150; - newMod.alphabet.x = 310; - add(newMod.alphabet); - //Don't ever cache the icons, it's a waste of loaded memory - var loadedIcon:BitmapData = null; - var iconToUse:String = Paths.mods(values[0] + '/pack.png'); - if(FileSystem.exists(iconToUse)) - { - loadedIcon = BitmapData.fromFile(iconToUse); - } - - newMod.icon = new AttachedSprite(); - if(loadedIcon != null) - { - newMod.icon.loadGraphic(loadedIcon, true, 150, 150);//animated icon support - var totalFrames = Math.floor(loadedIcon.width / 150) * Math.floor(loadedIcon.height / 150); - newMod.icon.animation.add("icon", [for (i in 0...totalFrames) i],10); - newMod.icon.animation.play("icon"); - } - else - { - newMod.icon.loadGraphic(Paths.image('unknownMod')); - } - newMod.icon.sprTracker = newMod.alphabet; - newMod.icon.xAdd = -newMod.icon.width - 30; - newMod.icon.yAdd = -45; - add(newMod.icon); - i++; - } - - if(curSelected >= mods.length) curSelected = 0; - - if(mods.length < 1) - bg.color = defaultColor; - else - bg.color = mods[curSelected].color; - - intendedColor = bg.color; - changeSelection(); - updatePosition(); - FlxG.sound.play(Paths.sound('scrollMenu')); - - FlxG.mouse.visible = true; - - super.create(); - } - - /*function getIntArray(max:Int):Array{ - var arr:Array = []; - for (i in 0...max) { - arr.push(i); - } - return arr; - }*/ - function addToModsList(values:Array) - { - for (i in 0...modsList.length) - { - if(modsList[i][0] == values[0]) - { - //trace(modsList[i][0], values[0]); - return; - } - } - modsList.push(values); - } - - function updateButtonToggle() - { - if (modsList[curSelected][1]) - { - buttonToggle.label.text = 'ON'; - buttonToggle.color = FlxColor.GREEN; - } - else - { - buttonToggle.label.text = 'OFF'; - buttonToggle.color = FlxColor.RED; - } - } - - function moveMod(change:Int, skipResetCheck:Bool = false) - { - if(mods.length > 1) - { - var doRestart:Bool = (mods[0].restart); - - var newPos:Int = curSelected + change; - if(newPos < 0) - { - modsList.push(modsList.shift()); - mods.push(mods.shift()); - } - else if(newPos >= mods.length) - { - modsList.insert(0, modsList.pop()); - mods.insert(0, mods.pop()); - } - else - { - var lastArray:Array = modsList[curSelected]; - modsList[curSelected] = modsList[newPos]; - modsList[newPos] = lastArray; - - var lastMod:ModMetadata = mods[curSelected]; - mods[curSelected] = mods[newPos]; - mods[newPos] = lastMod; - } - changeSelection(change); - - if(!doRestart) doRestart = mods[curSelected].restart; - if(!skipResetCheck && doRestart) needaReset = true; - } - } - - function saveTxt() - { - var fileStr:String = ''; - for (values in modsList) - { - if(fileStr.length > 0) fileStr += '\n'; - fileStr += values[0] + '|' + (values[1] ? '1' : '0'); - } - - var path:String = 'modsList.txt'; - File.saveContent(path, fileStr); - Paths.pushGlobalMods(); - } - - var noModsSine:Float = 0; - var canExit:Bool = true; - override function update(elapsed:Float) - { - if(noModsTxt.visible) - { - noModsSine += 180 * elapsed; - noModsTxt.alpha = 1 - Math.sin((Math.PI * noModsSine) / 180); - } - - if(canExit && controls.BACK) - { - if(colorTween != null) colorTween?.cancel(); - - FlxG.sound.play(Paths.sound('cancelMenu')); - FlxG.mouse.visible = false; - - saveTxt(); - if(needaReset) - { - //FlxG.switchState(() -> new TitleState()); - TitleState.initialized = false; - TitleState.closedState = false; - FlxG.sound?.music?.fadeOut(0.3); - FreeplayState.vocals?.fadeOut(0.3); - FreeplayState.vocals = null; - FlxG.camera.fade(FlxColor.BLACK, 0.5, false, FlxG.resetGame, false); - } - else - { - FlxG.switchState(() -> new MainMenuState()); - } - } - - if(controls.UI_UP_P) - { - changeSelection(-1); - FlxG.sound.play(Paths.sound('scrollMenu')); - } - if(controls.UI_DOWN_P) - { - changeSelection(1); - FlxG.sound.play(Paths.sound('scrollMenu')); - } - updatePosition(elapsed); - super.update(elapsed); - } - - function setAllLabelsOffset(button:FlxButton, x:Float, y:Float) - { - for (point in button.labelOffsets) - { - point.set(x, y); - } - } - - function changeSelection(change:Int = 0) - { - var noMods:Bool = (mods.length < 1); - for (obj in visibleWhenHasMods) - { - obj.visible = !noMods; - } - for (obj in visibleWhenNoMods) - { - obj.visible = noMods; - } - if(noMods) return; - - curSelected += change; - if(curSelected < 0) - curSelected = mods.length - 1; - else if(curSelected >= mods.length) - curSelected = 0; - - var newColor:Int = mods[curSelected].color; - if(newColor != intendedColor) { - colorTween?.cancel(); - intendedColor = newColor; - colorTween = FlxTween.color(bg, 1, bg.color, intendedColor, { - onComplete: (_) -> colorTween = null - }); - } - - var i:Int = 0; - for (mod in mods) - { - mod.alphabet.alpha = 0.6; - if(i == curSelected) - { - mod.alphabet.alpha = 1; - selector.sprTracker = mod.alphabet; - descriptionTxt.text = mod.description; - if (mod.restart)//finna make it to where if nothing changed then it won't reset - descriptionTxt.text += " (This Mod will restart the game!)"; - - // correct layering - var stuffArray:Array = [/*removeButton, installButton,*/ selector, descriptionTxt, mod.alphabet, mod.icon]; - for (obj in stuffArray) - { - remove(obj); - insert(members.length, obj); - } - for (obj in buttonsArray) - { - remove(obj); - insert(members.length, obj); - } - } - i++; - } - updateButtonToggle(); - } - - function updatePosition(elapsed:Float = -1) - { - var i:Int = 0; - for (mod in mods) - { - var intendedPos:Float = (i - curSelected) * 225 + 200; - if(i > curSelected) intendedPos += 225; - mod.alphabet.y = (elapsed != -1) ? FlxMath.lerp(mod.alphabet.y, intendedPos, MathUtil.boundTo(elapsed * 12, 0, 1)) : intendedPos; - - if(i == curSelected) - { - descriptionTxt.y = mod.alphabet.y + 160; - for (button in buttonsArray) - { - button.y = mod.alphabet.y + 320; - } - } - i++; - } - } - - var cornerSize:Int = 11; - function makeSelectorGraphic() - { - selector.makeGraphic(1100, 450, FlxColor.BLACK); - selector.pixels.fillRect(new Rectangle(0, 190, selector.width, 5), 0x0); - - // Why did i do this? Because i'm a lmao stupid, of course - // also i wanted to understand better how fillRect works so i did this shit lol??? - selector.pixels.fillRect(new Rectangle(0, 0, cornerSize, cornerSize), 0x0); //top left - drawCircleCornerOnSelector(false, false); - selector.pixels.fillRect(new Rectangle(selector.width - cornerSize, 0, cornerSize, cornerSize), 0x0); //top right - drawCircleCornerOnSelector(true, false); - selector.pixels.fillRect(new Rectangle(0, selector.height - cornerSize, cornerSize, cornerSize), 0x0); //bottom left - drawCircleCornerOnSelector(false, true); - selector.pixels.fillRect(new Rectangle(selector.width - cornerSize, selector.height - cornerSize, cornerSize, cornerSize), 0x0); //bottom right - drawCircleCornerOnSelector(true, true); - } - - function drawCircleCornerOnSelector(flipX:Bool, flipY:Bool) - { - var antiX:Float = (selector.width - cornerSize); - var antiY:Float = flipY ? (selector.height - 1) : 0; - if(flipY) antiY -= 2; - selector.pixels.fillRect(new Rectangle((flipX ? antiX : 1), Std.int(Math.abs(antiY - 8)), 10, 3), FlxColor.BLACK); - if(flipY) antiY += 1; - selector.pixels.fillRect(new Rectangle((flipX ? antiX : 2), Std.int(Math.abs(antiY - 6)), 9, 2), FlxColor.BLACK); - if(flipY) antiY += 1; - selector.pixels.fillRect(new Rectangle((flipX ? antiX : 3), Std.int(Math.abs(antiY - 5)), 8, 1), FlxColor.BLACK); - selector.pixels.fillRect(new Rectangle((flipX ? antiX : 4), Std.int(Math.abs(antiY - 4)), 7, 1), FlxColor.BLACK); - selector.pixels.fillRect(new Rectangle((flipX ? antiX : 5), Std.int(Math.abs(antiY - 3)), 6, 1), FlxColor.BLACK); - selector.pixels.fillRect(new Rectangle((flipX ? antiX : 6), Std.int(Math.abs(antiY - 2)), 5, 1), FlxColor.BLACK); - selector.pixels.fillRect(new Rectangle((flipX ? antiX : 8), Std.int(Math.abs(antiY - 1)), 3, 1), FlxColor.BLACK); - } - - /*var _file:FileReference = null; - function installMod() { - var zipFilter:FileFilter = new FileFilter('ZIP', 'zip'); - _file = new FileReference(); - _file.addEventListener(Event.SELECT, onLoadComplete); - _file.addEventListener(Event.CANCEL, onLoadCancel); - _file.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); - _file.browse([zipFilter]); - canExit = false; - } - - function onLoadComplete(_):Void - { - _file.removeEventListener(Event.SELECT, onLoadComplete); - _file.removeEventListener(Event.CANCEL, onLoadCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); - - var fullPath:String = null; - @:privateAccess - if(_file.__path != null) fullPath = _file.__path; - - if(fullPath != null) - { - var rawZip:String = File.getContent(fullPath); - if(rawZip != null) - { - FlxG.resetState(); - var uncompressingFile:Bytes = new Uncompress().run(File.getBytes(rawZip)); - if (uncompressingFile.done) - { - trace('test'); - _file = null; - return; - } - } - } - _file = null; - canExit = true; - trace("File couldn't be loaded! Wtf?"); - } - - function onLoadCancel(_):Void - { - _file.removeEventListener(Event.SELECT, onLoadComplete); - _file.removeEventListener(Event.CANCEL, onLoadCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); - _file = null; - canExit = true; - trace("Cancelled file loading."); - } - - function onLoadError(_):Void - { - _file.removeEventListener(Event.SELECT, onLoadComplete); - _file.removeEventListener(Event.CANCEL, onLoadCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); - _file = null; - canExit = true; - trace("Problem loading file"); - }*/ + static var curSelected:Int = 0; + + var mods:Array = []; + var bg:FlxSprite; + var intendedColor:Int; + var colorTween:FlxTween; + + var noModsTxt:FlxText; + var selector:AttachedSprite; + var needaReset = false; + + public static final defaultColor:FlxColor = 0xFF665AFF; + + var buttonDown:FlxButton; + var buttonTop:FlxButton; + var buttonDisableAll:FlxButton; + var buttonEnableAll:FlxButton; + var buttonUp:FlxButton; + var buttonToggle:FlxButton; + var buttonExtract:FlxButton; + var buttonsArray:Array = []; + + var installButton:FlxButton; + var removeButton:FlxButton; + + var modsList:Array = []; + + var visibleWhenNoMods:Array = []; + var visibleWhenHasMods:Array = []; + + var extractInfoTxt:FlxText; + var isExtracting:Bool = false; + + var descriptionBg:FlxSprite; + var descriptionText:FlxText; + var descriptionScroll:Float = 0; + var descriptionMaxScroll:Float = 0; + + override function create() + { + Paths.clearStoredMemory(); + Paths.clearUnusedMemory(); + WeekData.setDirectoryFromWeek(); + + #if DISCORD_ALLOWED + DiscordClient.changePresence("In the Menus", null); + #end + + bg = new FlxSprite().loadGraphic(Paths.image('menuDesat')); + bg.antialiasing = ClientPrefs.globalAntialiasing; + add(bg); + bg.screenCenter(); + + noModsTxt = new FlxText(0, 0, FlxG.width, "NO MODS INSTALLED\nPRESS BACK TO EXIT AND INSTALL A MOD", 48); + if(FlxG.random.bool(0.01)) noModsTxt.text += '\nBITCH.'; + noModsTxt.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + noModsTxt.scrollFactor.set(); + noModsTxt.borderSize = 2; + add(noModsTxt); + noModsTxt.screenCenter(); + visibleWhenNoMods.push(noModsTxt); + + var path:String = Paths.txt('modsList'); + if(FileSystem.exists(path)) + { + var leMods:Array = CoolUtil.coolTextFile(path); + for (i in 0...leMods.length) + { + if(leMods.length > 1 && leMods[0].length > 0) { + var modSplit:Array = leMods[i].split('|'); + if(!Mods.ignoreModFolders.contains(modSplit[0].toLowerCase())) + { + addToModsList([modSplit[0], (modSplit[1] == '1')]); + } + } + } + } + + if (FileSystem.exists(path)){ + for (folder in Mods.getModDirectories()) + { + if(!Mods.ignoreModFolders.contains(folder)) + { + if (Mods.modExists(folder)) { + var alreadyInList = false; + for (mod in modsList) { + if (mod[0] == folder) { + alreadyInList = true; + break; + } + } + if (!alreadyInList) { + addToModsList([folder, true]); + } + } + } + } + } + saveTxt(); + + selector = new AttachedSprite(); + selector.xAdd = -205; + selector.yAdd = -68; + selector.alphaMult = 0.5; + makeSelectorGraphic(); + add(selector); + visibleWhenHasMods.push(selector); + + var startX:Int = 1120; + + buttonToggle = new FlxButton(startX, 0, "ON", function() + { + if(mods[curSelected].restart) + { + needaReset = true; + } + modsList[curSelected][1] = !modsList[curSelected][1]; + updateButtonToggle(); + FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); + }); + buttonToggle.setGraphicSize(50, 50); + buttonToggle.updateHitbox(); + add(buttonToggle); + buttonsArray.push(buttonToggle); + visibleWhenHasMods.push(buttonToggle); + + buttonToggle.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.WHITE, CENTER); + setAllLabelsOffset(buttonToggle, -15, 10); + startX -= 70; + + buttonUp = new FlxButton(startX, 0, "/\\", function() + { + moveMod(-1); + FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); + }); + buttonUp.setGraphicSize(50, 50); + buttonUp.updateHitbox(); + add(buttonUp); + buttonsArray.push(buttonUp); + visibleWhenHasMods.push(buttonUp); + buttonUp.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); + setAllLabelsOffset(buttonUp, -15, 10); + startX -= 70; + + buttonDown = new FlxButton(startX, 0, "\\/", function() { + moveMod(1); + FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); + }); + buttonDown.setGraphicSize(50, 50); + buttonDown.updateHitbox(); + add(buttonDown); + buttonsArray.push(buttonDown); + visibleWhenHasMods.push(buttonDown); + buttonDown.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); + setAllLabelsOffset(buttonDown, -15, 10); + + startX -= 100; + buttonTop = new FlxButton(startX, 0, "TOP", function() { + var doRestart:Bool = (mods[0].restart || mods[curSelected].restart); + for (i in 0...curSelected) + { + moveMod(-1, true); + } + + if(doRestart) needaReset = true; + FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); + }); + buttonTop.setGraphicSize(80, 50); + buttonTop.updateHitbox(); + buttonTop.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); + setAllLabelsOffset(buttonTop, 0, 10); + add(buttonTop); + buttonsArray.push(buttonTop); + visibleWhenHasMods.push(buttonTop); + + startX -= 190; + buttonDisableAll = new FlxButton(startX, 0, "DISABLE ALL", function() { + for (i in modsList) + { + i[1] = false; + } + for (mod in mods) + { + if (mod.restart) + { + needaReset = true; + break; + } + } + updateButtonToggle(); + FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); + }); + buttonDisableAll.setGraphicSize(170, 50); + buttonDisableAll.updateHitbox(); + buttonDisableAll.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); + buttonDisableAll.label.fieldWidth = 170; + setAllLabelsOffset(buttonDisableAll, 0, 10); + add(buttonDisableAll); + buttonsArray.push(buttonDisableAll); + visibleWhenHasMods.push(buttonDisableAll); + + startX -= 190; + buttonEnableAll = new FlxButton(startX, 0, "ENABLE ALL", function() { + for (i in modsList) + { + i[1] = true; + } + for (mod in mods) + { + if (mod.restart) + { + needaReset = true; + break; + } + } + updateButtonToggle(); + FlxG.sound.play(Paths.sound('scrollMenu'), 0.6); + }); + buttonEnableAll.setGraphicSize(170, 50); + buttonEnableAll.updateHitbox(); + buttonEnableAll.label.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.BLACK, CENTER); + buttonEnableAll.label.fieldWidth = 170; + setAllLabelsOffset(buttonEnableAll, 0, 10); + add(buttonEnableAll); + buttonsArray.push(buttonEnableAll); + visibleWhenHasMods.push(buttonEnableAll); + + startX -= 120; + buttonExtract = new FlxButton(startX, 0, "EXTRACT", function() { + if (!isExtracting && mods.length > 0 && Mods.isZipMod(modsList[curSelected][0])) { + extractSelectedMod(); + } else { + FlxG.sound.play(Paths.sound('cancelMenu')); + } + }); + buttonExtract.setGraphicSize(100, 50); + buttonExtract.updateHitbox(); + buttonExtract.label.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.BLACK, CENTER); + buttonExtract.label.fieldWidth = 100; + setAllLabelsOffset(buttonExtract, 0, 15); + add(buttonExtract); + buttonsArray.push(buttonExtract); + visibleWhenHasMods.push(buttonExtract); + + extractInfoTxt = new FlxText(148, 0, FlxG.width - 216, "", 16); + extractInfoTxt.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.YELLOW, LEFT); + extractInfoTxt.scrollFactor.set(); + add(extractInfoTxt); + visibleWhenHasMods.push(extractInfoTxt); + + descriptionBg = new FlxSprite(148, 0).makeGraphic(FlxG.width - 275, 120, FlxColor.BLACK); + descriptionBg.alpha = 0.6; + descriptionBg.scrollFactor.set(); + add(descriptionBg); + visibleWhenHasMods.push(descriptionBg); + + descriptionText = new FlxText(descriptionBg.x + 5, descriptionBg.y + 5, descriptionBg.width, "", 16); + descriptionText.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, LEFT); + descriptionText.scrollFactor.set(); + add(descriptionText); + visibleWhenHasMods.push(descriptionText); + + remove(descriptionText); + remove(descriptionBg); + add(descriptionBg); + add(descriptionText); + + var i:Int = 0; + var len:Int = modsList.length; + while (i < modsList.length) + { + var values:Array = modsList[i]; + if(!Mods.modExists(values[0])) + { + modsList.remove(modsList[i]); + continue; + } + + var newMod:ModMetadata = new ModMetadata(values[0]); + mods.push(newMod); + + newMod.alphabet = new Alphabet(0, 0, mods[i].name, true); + var scale:Float = Math.min(840 / newMod.alphabet.width, 1); + newMod.alphabet.scaleX = scale; + newMod.alphabet.scaleY = scale; + newMod.alphabet.y = i * 150; + newMod.alphabet.x = 310; + add(newMod.alphabet); + + var loadedIcon:BitmapData = null; + var iconBytes = Mods.getFileFromMod(values[0], 'pack.png'); + if(iconBytes != null) + { + try { + loadedIcon = BitmapData.fromBytes(iconBytes); + } catch(e:Dynamic) { + trace('Error loading icon for mod ${values[0]}: $e'); + } + } + + newMod.icon = new AttachedSprite(); + if(loadedIcon != null) + { + newMod.icon.loadGraphic(loadedIcon, true, 150, 150); + var totalFrames = Math.floor(loadedIcon.width / 150) * Math.floor(loadedIcon.height / 150); + newMod.icon.animation.add("icon", [for (i in 0...totalFrames) i],10); + newMod.icon.animation.play("icon"); + } + else + { + newMod.icon.loadGraphic(Paths.image('unknownMod')); + } + newMod.icon.sprTracker = newMod.alphabet; + newMod.icon.xAdd = -newMod.icon.width - 30; + newMod.icon.yAdd = -45; + add(newMod.icon); + i++; + } + + if(curSelected >= mods.length) curSelected = 0; + + if(mods.length < 1) + bg.color = defaultColor; + else + bg.color = mods[curSelected].color; + + intendedColor = bg.color; + changeSelection(); + updatePosition(); + FlxG.sound.play(Paths.sound('scrollMenu')); + + FlxG.mouse.visible = true; + + super.create(); + } + + function updateDescriptionText() + { + if (mods.length == 0 || curSelected >= mods.length) return; + + var mod = mods[curSelected]; + var description = mod.description; + if (mod.restart) + description += " (This Mod will restart the game!)"; + + descriptionText.text = description; + + descriptionText.autoSize = true; + + var textHeight = descriptionText.height; + descriptionText.autoSize = false; + descriptionMaxScroll = Math.max(0, textHeight - descriptionBg.height + 10); + descriptionScroll = 0; + descriptionText.y = descriptionBg.y + 5; + descriptionText.clipRect = null; + } + + function addToModsList(values:Array) + { + for (i in 0...modsList.length) + { + if(modsList[i][0] == values[0]) + { + return; + } + } + modsList.push(values); + } + + function updateButtonToggle() + { + if (modsList[curSelected][1]) + { + buttonToggle.label.text = 'ON'; + buttonToggle.color = FlxColor.GREEN; + } + else + { + buttonToggle.label.text = 'OFF'; + buttonToggle.color = FlxColor.RED; + } + } + + function moveMod(change:Int, skipResetCheck:Bool = false) + { + if(mods.length > 1) + { + var doRestart:Bool = (mods[0].restart); + + var newPos:Int = curSelected + change; + if(newPos < 0) + { + modsList.push(modsList.shift()); + mods.push(mods.shift()); + } + else if(newPos >= mods.length) + { + modsList.insert(0, modsList.pop()); + mods.insert(0, mods.pop()); + } + else + { + var lastArray:Array = modsList[curSelected]; + modsList[curSelected] = modsList[newPos]; + modsList[newPos] = lastArray; + + var lastMod:ModMetadata = mods[curSelected]; + mods[curSelected] = mods[newPos]; + mods[newPos] = lastMod; + } + changeSelection(change); + + if(!doRestart) doRestart = mods[curSelected].restart; + if(!skipResetCheck && doRestart) needaReset = true; + } + } + + function saveTxt() + { + var fileStr:String = ''; + for (values in modsList) + { + if(fileStr.length > 0) fileStr += '\n'; + fileStr += values[0] + '|' + (values[1] ? '1' : '0'); + } + + var path:String = 'modsList'; + File.saveContent(Paths.txt(path), fileStr); + Mods.pushGlobalMods(); + } + + function extractSelectedMod() { + if (mods.length == 0) return; + + var modName = modsList[curSelected][0]; + + if (!Mods.isZipMod(modName)) { + FlxG.sound.play(Paths.sound('cancelMenu')); + return; + } + + var info = Mods.getZipModInfo(modName); + var sizeMB = Math.round(info.size / (1024 * 1024) * 100) / 100; + + var confirmText = 'Extract "${mods[curSelected].name}"?\n'; + confirmText += 'Files: ${info.fileCount}, Size: ${sizeMB} MB\n'; + confirmText += 'This may take a while for large mods.\n'; + + var confirmSubState = new ModExtractConfirmSubstate(confirmText, function(confirmed:Bool) { + if (confirmed) { + isExtracting = true; + for (button in buttonsArray) { + button.visible = false; + } + + openSubState(new ModExtractProgressSubstate(modName, info.fileCount, function(success) { + if (success) { + FlxG.sound.play(Paths.sound('confirmMenu')); + extractInfoTxt.text = "Extraction complete! ZIP deleted."; + + FlxG.camera.flash(FlxColor.GREEN, 0.5, () -> { + FlxG.resetState(); + }); + } else { + FlxG.sound.play(Paths.sound('cancelMenu')); + extractInfoTxt.text = "Extraction failed or cancelled!"; + + new FlxTimer().start(2, (_) -> { + isExtracting = false; + for (button in buttonsArray) { + button.visible = true; + } + extractInfoTxt.text = ""; + changeSelection(); + }); + } + })); + } + }); + + openSubState(confirmSubState); + } + + var noModsSine:Float = 0; + var canExit:Bool = true; + override function update(elapsed:Float) + { + if(noModsTxt.visible) + { + noModsSine += 180 * elapsed; + noModsTxt.alpha = 1 - Math.sin((Math.PI * noModsSine) / 180); + } + + if (!isExtracting) + { + if (mods.length > 0 && descriptionMaxScroll > 0) { + var mouseWheel = FlxG.mouse.wheel; + if (mouseWheel != 0) { + descriptionScroll -= mouseWheel * 30; + descriptionScroll = FlxMath.bound(descriptionScroll, 0, descriptionMaxScroll); + descriptionText.y = descriptionBg.y + 5 - descriptionScroll; + } + + if (controls.UI_UP_P) { + descriptionScroll -= 40; + descriptionScroll = FlxMath.bound(descriptionScroll, 0, descriptionMaxScroll); + descriptionText.y = descriptionBg.y + 5 - descriptionScroll; + FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); + } + + if (controls.UI_DOWN_P) { + descriptionScroll += 40; + descriptionScroll = FlxMath.bound(descriptionScroll, 0, descriptionMaxScroll); + descriptionText.y = descriptionBg.y + 5 - descriptionScroll; + FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); + } + } + + if(canExit && controls.BACK) + { + colorTween?.cancel(); + + FlxG.sound.play(Paths.sound('cancelMenu')); + FlxG.mouse.visible = false; + + saveTxt(); + if(needaReset) + { + TitleState.initialized = false; + TitleState.closedState = false; + FlxG.sound?.music?.fadeOut(0.3); + FreeplayState.vocals?.fadeOut(0.3); + FreeplayState.vocals = null; + FlxG.camera.fade(FlxColor.BLACK, 0.5, false, FlxG.resetGame, false); + } + else + { + FlxG.switchState(() -> new MainMenuState()); + } + } + + if(controls.UI_UP_P) + { + changeSelection(-1); + FlxG.sound.play(Paths.sound('scrollMenu')); + } + + if(controls.UI_DOWN_P) + { + changeSelection(1); + FlxG.sound.play(Paths.sound('scrollMenu')); + } + } + + updatePosition(elapsed); + super.update(elapsed); + } + + function setAllLabelsOffset(button:FlxButton, x:Float, y:Float) + { + for (point in button.labelOffsets) + { + point.set(x, y); + } + } + + function changeSelection(change:Int = 0) + { + var noMods:Bool = (mods.length < 1); + for (obj in visibleWhenHasMods) + { + obj.visible = !noMods; + } + for (obj in visibleWhenNoMods) + { + obj.visible = noMods; + } + if(noMods) return; + + curSelected += change; + if(curSelected < 0) + curSelected = mods.length - 1; + else if(curSelected >= mods.length) + curSelected = 0; + + descriptionScroll = 0; + + if (buttonExtract != null) { + var isZipMod = Mods.isZipMod(modsList[curSelected][0]); + buttonExtract.visible = isZipMod && !isExtracting; + + if (isZipMod) { + var info = Mods.getZipModInfo(modsList[curSelected][0]); + var sizeMB = Math.round(info.size / (1024 * 1024) * 100) / 100; + extractInfoTxt.text = 'ZIP Mod: ${info.fileCount} files, ${sizeMB} MB'; + } else { + extractInfoTxt.text = ""; + } + } + + var newColor:Int = mods[curSelected].color; + if(newColor != intendedColor) { + colorTween?.cancel(); + intendedColor = newColor; + colorTween = FlxTween.color(bg, 1, bg.color, intendedColor, { + onComplete: (_) -> colorTween = null + }); + } + + var i:Int = 0; + for (mod in mods) + { + mod.alphabet.alpha = 0.6; + if(i == curSelected) + { + mod.alphabet.alpha = 1; + selector.sprTracker = mod.alphabet; + + updateDescriptionText(); + + var stuffArray:Array = [descriptionBg, descriptionText, selector, mod.alphabet, mod.icon]; + for (obj in stuffArray) + { + remove(obj); + insert(members.length, obj); + } + + for (obj in buttonsArray) + { + remove(obj); + insert(members.length, obj); + } + } + i++; + } + updateButtonToggle(); + } + + function updatePosition(elapsed:Float = -1) + { + var i:Int = 0; + for (mod in mods) + { + var intendedPos:Float = (i - curSelected) * 225 + 200; + if(i > curSelected) intendedPos += 225; + mod.alphabet.y = (elapsed != -1) ? FlxMath.lerp(mod.alphabet.y, intendedPos, MathUtil.boundTo(elapsed * 12, 0, 1)) : intendedPos; + + if(i == curSelected) + { + var descriptionY = mod.alphabet.y + 160; + descriptionBg.y = descriptionY; + descriptionText.y = descriptionY + 5 - descriptionScroll; + + extractInfoTxt.y = mod.alphabet.y + 290; + for (button in buttonsArray) + { + button.y = mod.alphabet.y + 310; + } + } + i++; + } + } + + var cornerSize:Int = 11; + function makeSelectorGraphic() + { + selector.makeGraphic(1100, 450, FlxColor.BLACK); + selector.pixels.fillRect(new Rectangle(0, 190, selector.width, 5), 0x0); + + selector.pixels.fillRect(new Rectangle(0, 0, cornerSize, cornerSize), 0x0); + drawCircleCornerOnSelector(false, false); + selector.pixels.fillRect(new Rectangle(selector.width - cornerSize, 0, cornerSize, cornerSize), 0x0); + drawCircleCornerOnSelector(true, false); + selector.pixels.fillRect(new Rectangle(0, selector.height - cornerSize, cornerSize, cornerSize), 0x0); + drawCircleCornerOnSelector(false, true); + selector.pixels.fillRect(new Rectangle(selector.width - cornerSize, selector.height - cornerSize, cornerSize, cornerSize), 0x0); + drawCircleCornerOnSelector(true, true); + } + + function drawCircleCornerOnSelector(flipX:Bool, flipY:Bool) + { + var antiX:Float = (selector.width - cornerSize); + var antiY:Float = flipY ? (selector.height - 1) : 0; + if(flipY) antiY -= 2; + selector.pixels.fillRect(new Rectangle((flipX ? antiX : 1), Std.int(Math.abs(antiY - 8)), 10, 3), FlxColor.BLACK); + if(flipY) antiY += 1; + selector.pixels.fillRect(new Rectangle((flipX ? antiX : 2), Std.int(Math.abs(antiY - 6)), 9, 2), FlxColor.BLACK); + if(flipY) antiY += 1; + selector.pixels.fillRect(new Rectangle((flipX ? antiX : 3), Std.int(Math.abs(antiY - 5)), 8, 1), FlxColor.BLACK); + selector.pixels.fillRect(new Rectangle((flipX ? antiX : 4), Std.int(Math.abs(antiY - 4)), 7, 1), FlxColor.BLACK); + selector.pixels.fillRect(new Rectangle((flipX ? antiX : 5), Std.int(Math.abs(antiY - 3)), 6, 1), FlxColor.BLACK); + selector.pixels.fillRect(new Rectangle((flipX ? antiX : 6), Std.int(Math.abs(antiY - 2)), 5, 1), FlxColor.BLACK); + selector.pixels.fillRect(new Rectangle((flipX ? antiX : 8), Std.int(Math.abs(antiY - 1)), 3, 1), FlxColor.BLACK); + } } -class ModMetadata +class ModExtractConfirmSubstate extends MusicBeatSubstate { - public var folder:String; - public var name:String; - public var description:String; - public var color:FlxColor; - public var restart:Bool;//trust me. this is very important - public var alphabet:Alphabet; - public var icon:AttachedSprite; - - public function new(folder:String) - { - this.folder = folder; - this.name = folder; - this.description = "No description provided."; - this.color = ModsMenuState.defaultColor; - this.restart = false; - - //Try loading json - var path = Paths.mods(folder + '/pack.json'); - if(FileSystem.exists(path)) { - var rawJson:String = File.getContent(path); - if(rawJson != null && rawJson.length > 0) { - var stuff:Dynamic = Json.parse(rawJson); - //using reflects cuz for some odd reason my haxe hates the stuff.var shit - var colors:Array = Reflect.getProperty(stuff, "color"); - var description:String = Reflect.getProperty(stuff, "description"); - var name:String = Reflect.getProperty(stuff, "name"); - var restart:Bool = Reflect.getProperty(stuff, "restart"); - - if(name != null && name.length > 0) - { - this.name = name; - } - if(description != null && description.length > 0) - { - this.description = description; - } - if(name == 'Name') - { - this.name = folder; - } - if(description == 'Description') - { - this.description = "No description provided."; - } - if(colors != null && colors.length > 2) - { - this.color = FlxColor.fromRGB(colors[0], colors[1], colors[2]); - } - - this.restart = restart; - /* - if(stuff.name != null && stuff.name.length > 0) - { - this.name = stuff.name; - } - if(stuff.description != null && stuff.description.length > 0) - { - this.description = stuff.description; - } - if(stuff.color != null && stuff.color.length > 2) - { - this.color = FlxColor.fromRGB(stuff.color[0], stuff.color[1], stuff.color[2]); - }*/ - } - } - } + var callback:Bool->Void; + var background:FlxSprite; + var text:FlxText; + var yesButton:FlxButton; + var noButton:FlxButton; + + public function new(message:String, callback:Bool->Void) + { + super(); + + this.callback = callback; + + background = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + background.alpha = 0.8; + add(background); + + text = new FlxText(50, FlxG.height / 2 - 150, FlxG.width - 100, message, 24); + text.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.WHITE, CENTER); + add(text); + + var warningText = new FlxText(50, FlxG.height / 2 + 20, FlxG.width - 100, + "Note: Original ZIP file will be deleted after successful extraction.", 16); + warningText.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.YELLOW, CENTER); + add(warningText); + + yesButton = new FlxButton(FlxG.width / 2 - 120, FlxG.height / 2 + 70, "YES", () -> { + close(); + callback(true); + }); + yesButton.setGraphicSize(100, 50); + add(yesButton); + + noButton = new FlxButton(FlxG.width / 2 + 20, FlxG.height / 2 + 70, "NO", () -> { + close(); + callback(false); + }); + noButton.setGraphicSize(100, 50); + add(noButton); + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (FlxG.keys.justPressed.ESCAPE || controls.BACK) { + close(); + callback(false); + } + } } + +class ModExtractProgressSubstate extends MusicBeatSubstate +{ + var mod:String; + var callback:Bool->Void; + var background:FlxSprite; + var progressText:FlxText; + var progressBar:FlxSprite; + var progressBarBg:FlxSprite; + var cancelButton:FlxButton; + + var totalFiles:Int = 0; + var extractedFiles:Int = 0; + var isExtracting:Bool = true; + var extractionSuccess:Bool = false; + + public function new(mod:String, totalFiles:Int, callback:Bool->Void) + { + super(); + this.mod = mod; + this.totalFiles = totalFiles; + this.callback = callback; + } + + override function create() + { + super.create(); + + background = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + background.alpha = 0.8; + add(background); + + var titleText = new FlxText(0, FlxG.height / 2 - 100, FlxG.width, "Extracting Mod...", 32); + titleText.setFormat(Paths.font("vcr.ttf"), 32, FlxColor.WHITE, CENTER); + titleText.screenCenter(X); + add(titleText); + + progressBarBg = new FlxSprite(FlxG.width / 2 - 150, FlxG.height / 2 - 25); + progressBarBg.makeGraphic(300, 30, FlxColor.GRAY); + add(progressBarBg); + + progressBar = new FlxSprite(FlxG.width / 2 - 148, FlxG.height / 2 - 23); + progressBar.makeGraphic(1, 26, FlxColor.GREEN); + add(progressBar); + + progressText = new FlxText(0, FlxG.height / 2 + 20, FlxG.width, "Preparing...", 24); + progressText.setFormat(Paths.font("vcr.ttf"), 24, FlxColor.WHITE, CENTER); + progressText.screenCenter(X); + add(progressText); + + cancelButton = new FlxButton(FlxG.width / 2 - 50, FlxG.height / 2 + 70, "CANCEL", () -> { + isExtracting = false; + close(); + callback(false); + }); + cancelButton.setGraphicSize(100, 40); + add(cancelButton); + + new FlxTimer().start(0.1, (_) -> startExtraction()); + } + + function startExtraction() { + if (!isExtracting) return; + + var frameCallback:openfl.events.Event->Void = null; + frameCallback = (_) -> { + FlxG.stage.removeEventListener(openfl.events.Event.ENTER_FRAME, frameCallback); + + try { + extractionSuccess = extractWithProgress(); + + if (extractionSuccess) { + Mods.deleteZipMod(mod); + + progressText.text = "Extraction complete!"; + progressBar.makeGraphic(296, 26, FlxColor.GREEN); + + FlxG.sound.play(Paths.sound('confirmMenu')); + new FlxTimer().start(1, (_) -> { + close(); + callback(true); + }); + } else { + progressText.text = "Extraction failed!"; + FlxG.sound.play(Paths.sound('cancelMenu')); + new FlxTimer().start(2, (_) -> { + close(); + callback(false); + }); + } + } catch (e:Dynamic) { + trace('Error during extraction: $e'); + progressText.text = "Extraction error!"; + FlxG.sound.play(Paths.sound('cancelMenu')); + new FlxTimer().start(2, (_) -> { + close(); + callback(false); + }); + } + }; + FlxG.stage.addEventListener(openfl.events.Event.ENTER_FRAME, frameCallback); + } + + function extractWithProgress():Bool { + if (!Mods.isZipMod(mod)) { + return false; + } + + var zipPath = '${Mods.getModPath(mod)}.zip'; + var extractPath = Mods.getModPath(mod); + + if (FileSystem.exists(extractPath)) return false; + + try { + FileSystem.createDirectory(extractPath); + + var bytes = File.getBytes(zipPath); + var input = new BytesInput(bytes); + var entriesList = Reader.readZip(input); + var entries:Array = []; + + var iter = entriesList.iterator(); + while (iter.hasNext()) { + entries.push(iter.next()); + } + + var fileCount = 0; + + for (entry in entries) { + var fileName:String = entry.fileName; + if (!StringTools.endsWith(fileName, "/")) { + fileCount++; + } + } + + totalFiles = fileCount; + extractedFiles = 0; + + var hasRootFolder = true; + var rootFolderName:String = null; + + for (entry in entries) { + var fileName:String = entry.fileName; + var parts = fileName.split('/'); + + if (rootFolderName == null && parts.length > 0 && parts[0] != '') { + rootFolderName = parts[0]; + } + + if (parts.length == 1 && !StringTools.endsWith(fileName, "/")) { + hasRootFolder = false; + break; + } + } + + var shouldStripRootFolder = (hasRootFolder && rootFolderName != null && rootFolderName == mod); + + for (entry in entries) { + if (!isExtracting) { + Mods.deleteDirectory(extractPath); + return false; + } + + var fileName:String = entry.fileName; + + if (StringTools.endsWith(fileName, "/")) { + continue; + } + + var targetFileName:String = fileName; + + if (shouldStripRootFolder && StringTools.startsWith(fileName, rootFolderName + '/')) { + targetFileName = fileName.substring(rootFolderName.length + 1); + } + + var fullPath:String = extractPath + "/" + targetFileName; + + var dirPath = haxe.io.Path.directory(fullPath); + if (!FileSystem.exists(dirPath)) { + FileSystem.createDirectory(dirPath); + } + + var data = Reader.unzip(entry); + File.saveBytes(fullPath, data); + extractedFiles++; + + var progress = extractedFiles / totalFiles; + var barWidth = Std.int(296 * progress); + progressBar.makeGraphic(barWidth, 26, FlxColor.GREEN); + + progressText.text = 'Extracting: $extractedFiles/$totalFiles (${Std.int(progress * 100)}%)'; + } + + Mods.zipModsCache.remove(mod); + return true; + + } catch (e:Dynamic) { + trace('Error extracting ZIP mod $mod: $e'); + + try { + if (FileSystem.exists(extractPath)) { + Mods.deleteDirectory(extractPath); + } + } catch (cleanupError:Dynamic) { + trace('Error cleaning up after failed extraction: $cleanupError'); + } + + return false; + } + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (FlxG.keys.justPressed.ESCAPE || controls.BACK) { + isExtracting = false; + close(); + callback(false); + } + } +} \ No newline at end of file diff --git a/source/game/states/StoryMenuState.hx b/source/game/states/StoryMenuState.hx index c68c187..1eec8b1 100644 --- a/source/game/states/StoryMenuState.hx +++ b/source/game/states/StoryMenuState.hx @@ -6,17 +6,14 @@ import api.Discord.DiscordClient; import flixel.FlxG; import flixel.FlxSprite; import flixel.FlxSubState; -import flixel.addons.transition.FlxTransitionableState; -import flixel.graphics.frames.FlxAtlasFrames; import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.graphics.frames.FlxAtlasFrames; import flixel.group.FlxGroup; import flixel.math.FlxMath; import flixel.text.FlxText; import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxTimer; -import flixel.graphics.FlxGraphic; -import lime.net.curl.CURLCode; import game.backend.WeekData; import game.states.objects.MenuItem; @@ -28,29 +25,34 @@ class StoryMenuState extends MusicBeatState { public static var weekCompleted:Map = new Map(); - var scoreText:FlxText; - - private static var lastDifficultyName:String = ''; - var curDifficulty:Int = 1; - - var txtWeekTitle:FlxText; - var bgSprite:FlxSprite; - - private static var curWeek:Int = 0; + static var lastDifficultyName:String = ''; + static var curWeek:Int = 0; - var txtTracklist:FlxText; - - var grpWeekText:FlxTypedGroup; - var grpWeekCharacters:FlxTypedGroup; + var scoreText:FlxText; + var weekTitleText:FlxText; + var tracklistText:FlxText; + var background:FlxSprite; + var yellowBg:FlxSprite; - var grpLocks:FlxTypedGroup; + var weekTextGroup:FlxTypedGroup; + var weekCharactersGroup:FlxTypedGroup; + var lockGroup:FlxTypedGroup; + var difficultyGroup:FlxGroup; - var difficultySelectors:FlxGroup; - var sprDifficulty:FlxSprite; + var difficultySpr:FlxSprite; var leftArrow:FlxSprite; var rightArrow:FlxSprite; + var curDifficulty:Int = 1; + var lerpScore:Int = 0; + var intendedScore:Int = 0; + + var isMovingBack:Bool = false; + var isWeekSelected:Bool = false; + var isSelectionLocked:Bool = false; + var loadedWeeks:Array = []; + var difficultyTween:FlxTween; override function create() { @@ -59,160 +61,203 @@ class StoryMenuState extends MusicBeatState PlayState.isStoryMode = true; WeekData.reloadWeekFiles(true); - if(curWeek >= WeekData.weeksList.length) curWeek = 0; + + if (curWeek >= WeekData.weeksList.length) curWeek = 0; + persistentUpdate = persistentDraw = true; + loadAvailableWeeks(); if (!isSoftcodedState()) { - scoreText = new FlxText(10, 10, 0, "SCORE: 49324858", 36); - scoreText.setFormat("VCR OSD Mono", 32); + if(FlxG.sound.music == null) FlxG.sound.playMusic(Paths.music('freakyMenu')); + + createMenuInterface(); + setupInitialDisplay(); + } + + #if DISCORD_ALLOWED + DiscordClient.changePresence("In the Menus", null); + #end + + super.create(); + } + + function loadAvailableWeeks() + { + for (i in 0...WeekData.weeksList.length) + { + var week:WeekData = WeekData.weeksLoaded.get(WeekData.weeksList[i]); + var locked:Bool = isWeekLocked(WeekData.weeksList[i]); + + if (!locked || !week.hiddenUntilUnlocked) + { + loadedWeeks.push(week); + } + } + } - txtWeekTitle = new FlxText(FlxG.width * 0.7, 10, 0, "", 32); - txtWeekTitle.setFormat("VCR OSD Mono", 32, FlxColor.WHITE, RIGHT); - txtWeekTitle.alpha = 0.7; + function createMenuInterface() + { + createWeekSelection(); + createTextElements(); + createVisualElements(); + createWeekCharacters(); + createDifficultySelector(); + } - var rankText:FlxText = new FlxText(0, 10); - rankText.text = 'RANK: GREAT'; - rankText.setFormat(Paths.font("vcr.ttf"), 32); - rankText.size = scoreText.size; - rankText.screenCenter(X); + function createTextElements() + { + scoreText = new FlxText(10, 10, 0, "WEEK SCORE: 0", 32); + scoreText.setFormat("VCR OSD Mono", 32); - var ui_tex = Paths.getSparrowAtlas('campaign_menu_UI_assets'); - var bgYellow:FlxSprite = new FlxSprite(0, 56).makeGraphic(FlxG.width, 386, 0xFFF9CF51); - bgSprite = new FlxSprite(0, 56); - bgSprite.antialiasing = ClientPrefs.globalAntialiasing; + weekTitleText = new FlxText(FlxG.width * 0.7, 10, 0, "", 32); + weekTitleText.setFormat("VCR OSD Mono", 32, FlxColor.WHITE, RIGHT); + weekTitleText.alpha = 0.7; - grpWeekText = new FlxTypedGroup(); - add(grpWeekText); + tracklistText = new FlxText(FlxG.width * 0.05, 0, 0, "", 32); + tracklistText.alignment = CENTER; + tracklistText.font = Paths.font("vcr.ttf"); + tracklistText.antialiasing = ClientPrefs.globalAntialiasing; + tracklistText.color = 0xFFe55777; - var blackBarThingie:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, 56, FlxColor.BLACK); - add(blackBarThingie); + add(scoreText); + add(weekTitleText); + } - grpWeekCharacters = new FlxTypedGroup(); + function createVisualElements() + { + yellowBg = new FlxSprite(0, 56).makeGraphic(FlxG.width, 386, 0xFFF9CF51); + add(yellowBg); - grpLocks = new FlxTypedGroup(); - add(grpLocks); + background = new FlxSprite(0, 56); + background.antialiasing = ClientPrefs.globalAntialiasing; + add(background); - #if DISCORD_ALLOWED - // Updating Discord Rich Presence - DiscordClient.changePresence("In the Menus", null); - #end + var blackBar = new FlxSprite().makeGraphic(FlxG.width, 56, FlxColor.BLACK); + add(blackBar); - var num:Int = 0; - for (i in 0...WeekData.weeksList.length) - { - var weekFile:WeekData = WeekData.weeksLoaded.get(WeekData.weeksList[i]); - var isLocked:Bool = weekIsLocked(WeekData.weeksList[i]); - if(!isLocked || !weekFile.hiddenUntilUnlocked) - { - loadedWeeks.push(weekFile); - WeekData.setDirectoryFromWeek(weekFile); - var weekThing:MenuItem = new MenuItem(0, bgSprite.y + 396, WeekData.weeksList[i]); - weekThing.y += ((weekThing.height + 20) * num); - weekThing.targetY = num; - grpWeekText.add(weekThing); - - weekThing.screenCenter(X); - weekThing.antialiasing = ClientPrefs.globalAntialiasing; - // weekThing.updateHitbox(); - - // Needs an offset thingie - if (isLocked) - { - var lock:FlxSprite = new FlxSprite(weekThing.width + 10 + weekThing.x); - lock.frames = ui_tex; - lock.animation.addByPrefix('lock', 'lock'); - lock.animation.play('lock'); - lock.ID = i; - lock.antialiasing = ClientPrefs.globalAntialiasing; - grpLocks.add(lock); - } - num++; - } - } + var tracksSprite = new FlxSprite(FlxG.width * 0.07, background.y + 425).loadGraphic(Paths.image('Menu_Tracks')); + tracksSprite.antialiasing = ClientPrefs.globalAntialiasing; + add(tracksSprite); - WeekData.setDirectoryFromWeek(loadedWeeks[0]); - var charArray:Array = loadedWeeks[0].weekCharacters; - for (char in 0...3) - { - var weekCharacterThing:MenuCharacter = new MenuCharacter((FlxG.width * 0.25) * (1 + char) - 150, charArray[char]); - weekCharacterThing.y += 70; - grpWeekCharacters.add(weekCharacterThing); - } + tracklistText.y = tracksSprite.y + 60; + add(tracklistText); + } - difficultySelectors = new FlxGroup(); - add(difficultySelectors); + function createWeekSelection() + { + weekTextGroup = new FlxTypedGroup(); + lockGroup = new FlxTypedGroup(); - leftArrow = new FlxSprite(grpWeekText.members[0].x + grpWeekText.members[0].width + 10, grpWeekText.members[0].y + 10); - leftArrow.frames = ui_tex; - leftArrow.animation.addByPrefix('idle', "arrow left"); - leftArrow.animation.addByPrefix('press', "arrow push left"); - leftArrow.animation.play('idle'); - leftArrow.antialiasing = ClientPrefs.globalAntialiasing; - difficultySelectors.add(leftArrow); + add(weekTextGroup); + add(lockGroup); - CoolUtil.difficulties = CoolUtil.defaultDifficulties.copy(); - if(lastDifficultyName == '') - { - lastDifficultyName = CoolUtil.defaultDifficulty; - } - curDifficulty = Math.round(Math.max(0, CoolUtil.defaultDifficulties.indexOf(lastDifficultyName))); - - sprDifficulty = new FlxSprite(0, leftArrow.y); - sprDifficulty.antialiasing = ClientPrefs.globalAntialiasing; - difficultySelectors.add(sprDifficulty); - - rightArrow = new FlxSprite(leftArrow.x + 376, leftArrow.y); - rightArrow.frames = ui_tex; - rightArrow.animation.addByPrefix('idle', 'arrow right'); - rightArrow.animation.addByPrefix('press', "arrow push right", 24, false); - rightArrow.animation.play('idle'); - rightArrow.antialiasing = ClientPrefs.globalAntialiasing; - difficultySelectors.add(rightArrow); - - add(bgYellow); - add(bgSprite); - add(grpWeekCharacters); - - var tracksSprite:FlxSprite = new FlxSprite(FlxG.width * 0.07, bgSprite.y + 425).loadGraphic(Paths.image('Menu_Tracks')); - tracksSprite.antialiasing = ClientPrefs.globalAntialiasing; - add(tracksSprite); - - txtTracklist = new FlxText(FlxG.width * 0.05, tracksSprite.y + 60, 0, "", 32); - txtTracklist.alignment = CENTER; - txtTracklist.font = rankText.font; - txtTracklist.color = 0xFFe55777; - add(txtTracklist); - // add(rankText); - add(scoreText); - add(txtWeekTitle); - - changeWeek(); - changeDifficulty(); + var uiTexture = Paths.getSparrowAtlas('campaign_menu_UI_assets'); + + for (i in 0...loadedWeeks.length) + { + var week = loadedWeeks[i]; + var locked = isWeekLocked(week.fileName); + + createWeekItem(week, i, locked, uiTexture); } - else + } + + function createWeekItem(week:WeekData, index:Int, locked:Bool, uiTexture:FlxAtlasFrames) + { + var weekItem = new MenuItem(0, 56 + 396, week.fileName); + weekItem.y += ((weekItem.height + 25) * index); + weekItem.targetY = index; + weekItem.screenCenter(X); + weekItem.antialiasing = ClientPrefs.globalAntialiasing; + weekTextGroup.add(weekItem); + + if (locked) { - var num:Int = 0; - for (i in 0...WeekData.weeksList.length) - { - var weekFile:WeekData = WeekData.weeksLoaded.get(WeekData.weeksList[i]); - var isLocked:Bool = weekIsLocked(WeekData.weeksList[i]); - if(!isLocked || !weekFile.hiddenUntilUnlocked) - { - loadedWeeks.push(weekFile); - num++; - } - } + createLock(weekItem, index, uiTexture); } + } - super.create(); + function createLock(weekItem:MenuItem, index:Int, uiTexture:FlxAtlasFrames) + { + var lock = new FlxSprite(weekItem.width + 10 + weekItem.x); + lock.frames = uiTexture; + lock.animation.addByPrefix('lock', 'lock'); + lock.animation.play('lock'); + lock.ID = index; + lock.antialiasing = ClientPrefs.globalAntialiasing; + lockGroup.add(lock); } - override function closeSubState() { - persistentUpdate = true; - if (!isSoftcodedState()) { - changeWeek(); + function createDifficultySelector() + { + difficultyGroup = new FlxGroup(); + add(difficultyGroup); + + var uiTexture = Paths.getSparrowAtlas('campaign_menu_UI_assets'); + var firstWeekItem = weekTextGroup.members[0]; + + leftArrow = new FlxSprite(firstWeekItem.x + firstWeekItem.width + 235, firstWeekItem.y + 10); + leftArrow.frames = uiTexture; + leftArrow.animation.addByPrefix('idle', "arrow left"); + leftArrow.animation.addByPrefix('press', "arrow push left"); + leftArrow.animation.play('idle'); + leftArrow.antialiasing = ClientPrefs.globalAntialiasing; + difficultyGroup.add(leftArrow); + + difficultySpr = new FlxSprite(0, leftArrow.y); + difficultySpr.antialiasing = ClientPrefs.globalAntialiasing; + difficultyGroup.add(difficultySpr); + + rightArrow = new FlxSprite(leftArrow.x + 376, leftArrow.y); + rightArrow.frames = uiTexture; + rightArrow.animation.addByPrefix('idle', 'arrow right'); + rightArrow.animation.addByPrefix('press', "arrow push right", 24, false); + rightArrow.animation.play('idle'); + rightArrow.antialiasing = ClientPrefs.globalAntialiasing; + difficultyGroup.add(rightArrow); + + initializeDifficulty(); + } + + function createWeekCharacters() + { + weekCharactersGroup = new FlxTypedGroup(); + add(weekCharactersGroup); + + for (i in 0...3) + { + var character = new MenuCharacter((FlxG.width * 0.25) * (1 + i) - 150, ''); + character.y += 70; + weekCharactersGroup.add(character); } + updateWeekCharacters(); + } + + function initializeDifficulty() + { + CoolUtil.difficulties = CoolUtil.defaultDifficulties.copy(); + + if (lastDifficultyName == '') lastDifficultyName = CoolUtil.defaultDifficulty; + + curDifficulty = Math.round(Math.max(0, CoolUtil.defaultDifficulties.indexOf(lastDifficultyName))); + updateDifficultyDisplay(); + } + + function setupInitialDisplay() + { + updateWeekCharacters(); + updateWeekDisplay(); + updateDifficulty(); + } + + override function closeSubState() + { + persistentUpdate = true; + + if (!isSoftcodedState()) + updateWeekDisplay(); + super.closeSubState(); } @@ -220,288 +265,313 @@ class StoryMenuState extends MusicBeatState { if (!isSoftcodedState()) { - lerpScore = Math.floor(FlxMath.lerp(lerpScore, intendedScore, MathUtil.boundTo(elapsed * 30, 0, 1))); - if(Math.abs(intendedScore - lerpScore) < 10) lerpScore = intendedScore; - - scoreText.text = "WEEK SCORE:" + lerpScore; + updateScoreDisplay(elapsed); + handleUserInput(); + updateLockPositions(); + } - if (!movedBack && !selectedWeek) - { - var upP = controls.UI_UP_P; - var downP = controls.UI_DOWN_P; - if (upP) - { - changeWeek(-1); - FlxG.sound.play(Paths.sound('scrollMenu')); - } - - if (downP) - { - changeWeek(1); - FlxG.sound.play(Paths.sound('scrollMenu')); - } - - if(FlxG.mouse.wheel != 0) - { - FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); - changeWeek(-FlxG.mouse.wheel); - changeDifficulty(); - } - - if (controls.UI_RIGHT) - rightArrow.animation.play('press') - else - rightArrow.animation.play('idle'); - - if (controls.UI_LEFT) - leftArrow.animation.play('press'); - else - leftArrow.animation.play('idle'); - - if (controls.UI_RIGHT_P) - changeDifficulty(1); - else if (controls.UI_LEFT_P) - changeDifficulty(-1); - else if (upP || downP) - changeDifficulty(); - - if(FlxG.keys.justPressed.CONTROL) - { - persistentUpdate = false; - openSubState(new GameplayChangersSubstate()); - } - else if(controls.RESET) - { - persistentUpdate = false; - openSubState(new ResetScoreSubState('', curDifficulty, '', curWeek)); - //FlxG.sound.play(Paths.sound('scrollMenu')); - } - else if (controls.ACCEPT) - { - selectWeek(); - } - } + super.update(elapsed); + } - if (controls.BACK && !movedBack && !selectedWeek) - { - FlxG.sound.play(Paths.sound('cancelMenu')); - movedBack = true; - FlxG.switchState(() -> new MainMenuState()); - } + function updateScoreDisplay(elapsed:Float) + { + lerpScore = Math.floor(FlxMath.lerp(lerpScore, intendedScore, MathUtil.boundTo(elapsed * 30, 0, 1))); + + if (Math.abs(intendedScore - lerpScore) < 10) + lerpScore = intendedScore; - grpLocks.forEach(function(lock:FlxSprite) - { - lock.y = grpWeekText.members[lock.ID].y; - lock.visible = (lock.y > FlxG.height / 2); - }); - } + scoreText.text = "WEEK SCORE:" + lerpScore; + } - super.update(elapsed); + function updateWeekCharacters() + { + var characters = loadedWeeks[curWeek].weekCharacters; + for (i in 0...weekCharactersGroup.length) + weekCharactersGroup.members[i].changeCharacter(i < characters.length ? characters[i] : ''); } - var movedBack:Bool = false; - var selectedWeek:Bool = false; - var stopspamming:Bool = false; + function handleUserInput() + { + if (!isMovingBack && !isWeekSelected && FlxG.state.subState == null) + { + handleWeekSelection(); + handleDifficultySelection(); + handleSpecialActions(); + handleNavigation(); + } + } - function selectWeek() + function handleWeekSelection() { - if (isSoftcodedState()) return; - - if (!weekIsLocked(loadedWeeks[curWeek].fileName)) + var upPressed = controls.UI_UP_P; + var downPressed = controls.UI_DOWN_P; + + if (upPressed) { - if (stopspamming == false) - { - FlxG.sound.play(Paths.sound('confirmMenu')); - - grpWeekText.members[curWeek].startFlashing(); - - for (char in grpWeekCharacters.members) - { - if (char.character != '' && char.hasConfirmAnimation) - { - char.animation.play('confirm'); - } - } - stopspamming = true; - } + changeWeek(-1); + } - // We can't use Dynamic Array .copy() because that crashes HTML5, here's a workaround. - var songArray:Array = []; - var leWeek:Array = loadedWeeks[curWeek].songs; - for (i in 0...leWeek.length) { - songArray.push(leWeek[i][0]); - } + if (downPressed) + { + changeWeek(1); + } - // Nevermind that's stupid lmao - PlayState.storyPlaylist = songArray; - PlayState.isStoryMode = true; - selectedWeek = true; + if (FlxG.mouse.wheel != 0) + { + FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); + changeWeek(-FlxG.mouse.wheel); + updateDifficulty(); + } + } - var diffic = CoolUtil.getDifficultyFilePath(curDifficulty) ?? ''; + function handleDifficultySelection() + { + updateArrowAnimations(); + + var leftPressed = controls.UI_LEFT_P; + var rightPressed = controls.UI_RIGHT_P; + var upPressed = controls.UI_UP_P; + var downPressed = controls.UI_DOWN_P; + + if (rightPressed) + changeDifficulty(1); + else if (leftPressed) + changeDifficulty(-1); + else if (upPressed || downPressed) + updateDifficulty(); + } - PlayState.storyDifficulty = curDifficulty; + function updateArrowAnimations() + { + rightArrow.animation.play(controls.UI_RIGHT ? 'press' : 'idle'); + leftArrow.animation.play(controls.UI_LEFT ? 'press' : 'idle'); + } - PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase() + diffic, PlayState.storyPlaylist[0].toLowerCase()); - PlayState.campaignScore = 0; - PlayState.campaignMisses = 0; - new FlxTimer().start(1, function(tmr:FlxTimer) - { - LoadingState.loadAndSwitchState(() -> new PlayState(), true); - FreeplayState.destroyFreeplayVocals(); - }); - } else { + function handleSpecialActions() + { + if (FlxG.keys.justPressed.CONTROL) + { + persistentUpdate = false; + openSubState(new GameplayChangersSubstate()); + } + else if (controls.RESET) + { + persistentUpdate = false; + openSubState(new ResetScoreSubState('', curDifficulty, '', curWeek)); + } + else if (controls.ACCEPT) + { + selectWeek(); + } + } + + function handleNavigation() + { + if (controls.BACK && !isMovingBack && !isWeekSelected) + { FlxG.sound.play(Paths.sound('cancelMenu')); + isMovingBack = true; + FlxG.switchState(() -> new MainMenuState()); } } - var tweenDifficulty:FlxTween; - function changeDifficulty(change:Int = 0):Void + function updateLockPositions() { - if (isSoftcodedState()) return; - - curDifficulty += change; + lockGroup.forEach(function(lock:FlxSprite) + { + lock.y = weekTextGroup.members[lock.ID].y; + lock.visible = (lock.y > FlxG.height / 2); + }); + } - if (curDifficulty < 0) - curDifficulty = CoolUtil.difficulties.length-1; - if (curDifficulty >= CoolUtil.difficulties.length) - curDifficulty = 0; + function selectWeek() + { + if (isWeekLocked(loadedWeeks[curWeek].fileName)) + { + FlxG.sound.play(Paths.sound('cancelMenu')); + return; + } - WeekData.setDirectoryFromWeek(loadedWeeks[curWeek]); + if (isSelectionLocked) return; - var diff:String = CoolUtil.difficulties[curDifficulty]; - var newImage:FlxGraphic = Paths.image('menudifficulties/' + Paths.formatToSongPath(diff)); - //trace(Paths.currentModDirectory + ', menudifficulties/' + Paths.formatToSongPath(diff)); + isSelectionLocked = true; + FlxG.sound.play(Paths.sound('confirmMenu')); - if(sprDifficulty.graphic != newImage) + weekTextGroup.members[curWeek].startFlashing(); + + for (character in weekCharactersGroup.members) { - sprDifficulty.loadGraphic(newImage); - sprDifficulty.x = leftArrow.x + 60; - sprDifficulty.x += (308 - sprDifficulty.width) / 3; - sprDifficulty.alpha = 0; - sprDifficulty.y = leftArrow.y - 15; - - if(tweenDifficulty != null) tweenDifficulty.cancel(); - tweenDifficulty = FlxTween.tween(sprDifficulty, {y: leftArrow.y + 15, alpha: 1}, 0.07, {onComplete: (_) -> tweenDifficulty = null}); + if (character.character != '' && character.hasConfirmAnimation) + character.animation.play('confirm'); } - lastDifficultyName = diff; - #if !switch - intendedScore = Highscore.getWeekScore(loadedWeeks[curWeek].fileName, curDifficulty); - #end + // prepare playlist + var songNames = loadedWeeks[curWeek].songs.map(song -> song[0]); + PlayState.storyPlaylist = songNames; + PlayState.isStoryMode = true; + isWeekSelected = true; + + // load first song + var difficultyPath = CoolUtil.getDifficultyFilePath(curDifficulty) ?? ''; + PlayState.storyDifficulty = curDifficulty; + PlayState.SONG = Song.loadFromJson( + PlayState.storyPlaylist[0].toLowerCase() + difficultyPath, + PlayState.storyPlaylist[0].toLowerCase() + ); + + // reset campaign stats + PlayState.campaignScore = 0; + PlayState.campaignMisses = 0; + + new FlxTimer().start(1, _ -> { + LoadingState.loadAndSwitchState(() -> new PlayState(), true); + }); } - var lerpScore:Int = 0; - var intendedScore:Int = 0; - - function changeWeek(change:Int = 0):Void + function changeDifficulty(change:Int = 0) { - if (isSoftcodedState()) return; - - curWeek += change; + curDifficulty = FlxMath.wrap(curDifficulty + change, 0, CoolUtil.difficulties.length - 1); + updateDifficulty(); + } - if (curWeek >= loadedWeeks.length) - curWeek = 0; - if (curWeek < 0) - curWeek = loadedWeeks.length - 1; + function updateDifficulty() + { + WeekData.setDirectoryFromWeek(loadedWeeks[curWeek]); - var leWeek:WeekData = loadedWeeks[curWeek]; - WeekData.setDirectoryFromWeek(leWeek); + var difficultyName = CoolUtil.difficulties[curDifficulty]; + lastDifficultyName = difficultyName; - var leName:String = leWeek.storyName; - txtWeekTitle.text = leName.toUpperCase(); - txtWeekTitle.x = FlxG.width - (txtWeekTitle.width + 10); + updateDifficultyDisplay(); + updateWeekScore(); + } - var bullShit:Int = 0; + function updateDifficultyDisplay() + { + var difficultyName = CoolUtil.difficulties[curDifficulty]; + var difficultyImg = Paths.image('menudifficulties/' + Paths.formatToSongPath(difficultyName)); - var unlocked:Bool = !weekIsLocked(leWeek.fileName); - for (item in grpWeekText.members) + if (difficultySpr.graphic != difficultyImg) { - item.targetY = bullShit - curWeek; - if (item.targetY == Std.int(0) && unlocked) - item.alpha = 1; - else - item.alpha = 0.6; - bullShit++; + difficultySpr.loadGraphic(difficultyImg); + difficultySpr.x = leftArrow.x + 60; + difficultySpr.x += (320 - difficultySpr.width) / 3; + difficultySpr.alpha = 0; + difficultySpr.y = leftArrow.y - 10; + + difficultyTween?.cancel(); + difficultyTween = FlxTween.tween(difficultySpr, + {y: leftArrow.y + 10, alpha: 1}, 0.07, + {onComplete: _ -> difficultyTween = null} + ); } + } - bgSprite.visible = true; - var assetName:String = leWeek.weekBackground; - if(assetName == null || assetName.length < 1) { - bgSprite.visible = false; - } else { - bgSprite.loadGraphic(Paths.image('menubackgrounds/menu_' + assetName)); - } - PlayState.storyWeek = curWeek; + function changeWeek(change:Int = 0) + { + if(loadedWeeks.length <= 1) return; - CoolUtil.difficulties = CoolUtil.defaultDifficulties.copy(); - var diffStr:String = WeekData.getCurrentWeek().difficulties; - if(diffStr != null) diffStr = diffStr.trim(); //Fuck you HTML5 - difficultySelectors.visible = unlocked; + FlxG.sound.play(Paths.sound('scrollMenu')); - if(diffStr != null && diffStr.length > 0) - { - var diffs:Array = diffStr.split(','); - var i:Int = diffs.length - 1; - while (i > 0) - { - if(diffs[i] != null) - { - diffs[i] = diffs[i].trim(); - if(diffs[i].length < 1) diffs.remove(diffs[i]); - } - --i; - } + curWeek = FlxMath.wrap(curWeek + change, 0, loadedWeeks.length - 1); + updateWeekDisplay(); + } - if(diffs.length > 0 && diffs[0].length > 0) - { - CoolUtil.difficulties = diffs; - } - } - - if(CoolUtil.difficulties.contains(CoolUtil.defaultDifficulty)) - curDifficulty = Math.round(Math.max(0, CoolUtil.defaultDifficulties.indexOf(CoolUtil.defaultDifficulty))); - else - curDifficulty = 0; + function updateWeekDisplay() + { + var week = loadedWeeks[curWeek]; + WeekData.setDirectoryFromWeek(week); + + updateWeekTitle(week); + updateWeekSelection(); + updateWeekBackground(week); + updateWeekDifficulties(week); + updateWeekCharacters(); + updateTracklist(week); + updateWeekScore(); + } - var newPos:Int = CoolUtil.difficulties.indexOf(lastDifficultyName); - //trace('Pos of ' + lastDifficultyName + ' is ' + newPos); - if(newPos > -1) curDifficulty = newPos; - updateText(); + function updateWeekTitle(week:WeekData) + { + weekTitleText.text = week.storyName.toUpperCase(); + weekTitleText.x = FlxG.width - (weekTitleText.width + 10); } - function weekIsLocked(name:String):Bool { - var leWeek:WeekData = WeekData.weeksLoaded.get(name); - return (!leWeek.startUnlocked && leWeek.weekBefore.length > 0 && (!weekCompleted.exists(leWeek.weekBefore) || !weekCompleted.get(leWeek.weekBefore))); + function updateWeekSelection() + { + var weekUnlocked = !isWeekLocked(loadedWeeks[curWeek].fileName); + + for (i in 0...weekTextGroup.members.length) + { + var item = weekTextGroup.members[i]; + item.targetY = i - curWeek; + item.alpha = (item.targetY == 0 && weekUnlocked) ? 1 : 0.6; + } } - function updateText() + function updateWeekBackground(week:WeekData) { - if (isSoftcodedState()) return; + var backgroundAsset = week.weekBackground; - var weekArray:Array = loadedWeeks[curWeek].weekCharacters; - for (i in 0...grpWeekCharacters.length) { - grpWeekCharacters.members[i].changeCharacter(weekArray[i]); + if (backgroundAsset == null || backgroundAsset.length < 1) + { + background.visible = false; } - - var leWeek:WeekData = loadedWeeks[curWeek]; - var stringThing:Array = []; - for (i in 0...leWeek.songs.length) { - stringThing.push(leWeek.songs[i][0]); + else + { + background.loadGraphic(Paths.image('menubackgrounds/menu_' + backgroundAsset)); + background.visible = true; } + } - txtTracklist.text = ''; - for (i in 0...stringThing.length) + function updateWeekDifficulties(week:WeekData) + { + var weekUnlocked = !isWeekLocked(week.fileName); + difficultyGroup.visible = weekUnlocked; + + CoolUtil.difficulties = CoolUtil.defaultDifficulties.copy(); + var difficultyString = week.difficulties; + + if (difficultyString != null && difficultyString.trim().length > 0) { - txtTracklist.text += stringThing[i] + '\n'; + var difficulties = difficultyString.split(',') + .map(diff -> diff.trim()) + .filter(diff -> diff.length > 0); + + if (difficulties.length > 0) + CoolUtil.difficulties = difficulties; } - txtTracklist.text = txtTracklist.text.toUpperCase(); + if (CoolUtil.difficulties.contains(CoolUtil.defaultDifficulty)) + curDifficulty = Math.round(Math.max(0, CoolUtil.defaultDifficulties.indexOf(CoolUtil.defaultDifficulty))); + else + curDifficulty = 0; + + var storedPosition = CoolUtil.difficulties.indexOf(lastDifficultyName); + if (storedPosition > -1) + curDifficulty = storedPosition; + + PlayState.storyWeek = curWeek; + } - txtTracklist.screenCenter(X); - txtTracklist.x -= FlxG.width * 0.35; + function updateTracklist(week:WeekData) + { + var trackNames = week.songs.map(song -> song[0]); + tracklistText.text = trackNames.join('\n').toUpperCase(); + tracklistText.screenCenter(X); + tracklistText.x -= FlxG.width * 0.35; + } + function updateWeekScore() + { #if !switch intendedScore = Highscore.getWeekScore(loadedWeeks[curWeek].fileName, curDifficulty); #end } + + function isWeekLocked(weekName:String):Bool + { + var week = WeekData.weeksLoaded.get(weekName); + return (!week.startUnlocked && week.weekBefore.length > 0 && + (!weekCompleted.exists(week.weekBefore) || !weekCompleted.get(week.weekBefore))); + } } \ No newline at end of file diff --git a/source/game/states/TitleState.hx b/source/game/states/TitleState.hx index f95bff5..c14901a 100644 --- a/source/game/states/TitleState.hx +++ b/source/game/states/TitleState.hx @@ -177,9 +177,6 @@ class TitleState extends MusicBeatState } else { - if(FlxG.sound.music == null) { - FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); - } skippedIntro = true; } } @@ -192,20 +189,9 @@ class TitleState extends MusicBeatState function startIntro() { - if (isSoftcodedState()) - { - if(FlxG.sound.music == null) { - FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); - } - return; - } + if (isSoftcodedState()) return; - if (!initialized) - { - if(FlxG.sound.music == null) { - FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); - } - } + if(!initialized && FlxG.sound.music == null) FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); Conductor.changeBPM(titleJSON.bpm); persistentUpdate = true; @@ -265,13 +251,13 @@ class TitleState extends MusicBeatState logoBl.shader = swagShader.shader; titleText = new FlxSprite(titleJSON.startx, titleJSON.starty); - #if (desktop && MODS_ALLOWED) - var path = "contents/" + Paths.currentModDirectory + "/images/titleEnter.png"; + #if MODS_ALLOWED + var path = Mods.MODS_FOLDER + "/" + Mods.currentModDirectory + "/images/titleEnter.png"; if (!FileSystem.exists(path)){ - path = "contents/images/titleEnter.png"; + path = '${Mods.MODS_FOLDER}/images/titleEnter.png'; } if (!FileSystem.exists(path)){ - path = "assets/images/titleEnter.png"; + path = Paths.getPath('images/titleEnter.png'); } titleText.frames = FlxAtlasFrames.fromSparrow(BitmapData.fromFile(path),File.getContent(StringTools.replace(path,".png",".xml"))); #else @@ -535,27 +521,13 @@ class TitleState extends MusicBeatState FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); FlxG.sound.music.fadeIn(4, 0, 0.7); case 2: - #if PSYCH_WATERMARKS - createCoolText(['Psych Engine by'], 15); - #else createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8er']); - #end case 4: - #if PSYCH_WATERMARKS - addMoreText('Shadow Mario', 15); - addMoreText('RiverOaken', 15); - addMoreText('shubs', 15); - #else addMoreText('present'); - #end case 5: deleteCoolText(); case 6: - #if PSYCH_WATERMARKS - createCoolText(['Not associated', 'with'], -40); - #else createCoolText(['In association', 'with'], -40); - #end case 8: addMoreText('newgrounds', -40); if (credGroup != null) ngSpr.visible = true; diff --git a/source/game/states/backend/Achievements.hx b/source/game/states/backend/Achievements.hx index 339db17..4bfabef 100644 --- a/source/game/states/backend/Achievements.hx +++ b/source/game/states/backend/Achievements.hx @@ -36,6 +36,10 @@ class Achievements { public static function unlockAchievement(name:String):Void { FlxG.log.add('Completed achievement "' + name +'"'); achievementsMap.set(name, true); + + FlxG.save.data.achievementsMap = achievementsMap; + FlxG.save.flush(); + FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); } @@ -56,13 +60,11 @@ class Achievements { } public static function loadAchievements():Void { - if(FlxG.save.data != null) { - if(FlxG.save.data.achievementsMap != null) { - achievementsMap = FlxG.save.data.achievementsMap; - } - if(henchmenDeath == 0 && FlxG.save.data.henchmenDeath != null) { - henchmenDeath = FlxG.save.data.henchmenDeath; - } + if(FlxG.save?.data?.achievementsMap != null) { + achievementsMap = FlxG.save.data.achievementsMap; + } + if(henchmenDeath == 0 && FlxG.save?.data?.henchmenDeath != null) { + henchmenDeath = FlxG.save.data.henchmenDeath; } } } diff --git a/source/game/states/backend/EditorLua.hx b/source/game/states/backend/EditorLua.hx index c15e2da..7e0eef3 100644 --- a/source/game/states/backend/EditorLua.hx +++ b/source/game/states/backend/EditorLua.hx @@ -1,10 +1,12 @@ package game.states.backend; #if LUA_ALLOWED -import llua.Lua; -import llua.LuaL; -import llua.State; -import llua.Convert; +import hxluajit.Lua; +import hxluajit.LuaL; +import hxluajit.Types; +import hxluajit.wrapper.LuaConverter; +import hxluajit.wrapper.LuaUtils; +import hxluajit.wrapper.LuaError; #end import flixel.FlxG; @@ -37,26 +39,28 @@ import api.Discord; using StringTools; class EditorLua { - public static var Function_Stop = 1; - public static var Function_Continue = 0; + @:unreflective + public static final Function_Stop = 1; + + @:unreflective + public static final Function_Continue = 0; #if LUA_ALLOWED - public var lua:State = null; + public var lua:cpp.RawPointer = null; #end public function new(script:String) { #if LUA_ALLOWED lua = LuaL.newstate(); LuaL.openlibs(lua); - Lua.init_callbacks(lua); //trace('Lua version: ' + Lua.version()); //trace("LuaJIT version: " + Lua.versionJIT()); - var result:Dynamic = LuaL.dofile(lua, script); + var result:Int = LuaL.dofile(lua, script); var resultStr:String = Lua.tostring(lua, result); if(resultStr != null && result != 0) { - CoolUtil.showPopUp(resultStr, 'Error on .LUA script!', MSG_INFORMATION); + CoolUtil.showPopUp(resultStr, 'Error on .LUA script!' #if sl_windows_api , MSG_INFORMATION #end); trace('Error on .LUA script! ' + resultStr); lua = null; return; @@ -90,7 +94,7 @@ class EditorLua { set('middlescroll', ClientPrefs.middleScroll); //stuff 4 noobz like you B) - Lua_helper.add_callback(lua, "getProperty", function(variable:String) { + LuaUtils.addFunction(lua, "getProperty", function(variable:String):Dynamic { var killMe:Array = variable.split('.'); if(killMe.length > 1) { var coverMeInPiss:Dynamic = Reflect.getProperty(EditorPlayState.instance, killMe[0]); @@ -102,7 +106,8 @@ class EditorLua { } return Reflect.getProperty(EditorPlayState.instance, variable); }); - Lua_helper.add_callback(lua, "setProperty", function(variable:String, value:Dynamic) { + + LuaUtils.addFunction(lua, "setProperty", function(variable:String, value:Dynamic):Void { var killMe:Array = variable.split('.'); if(killMe.length > 1) { var coverMeInPiss:Dynamic = Reflect.getProperty(EditorPlayState.instance, killMe[0]); @@ -110,11 +115,13 @@ class EditorLua { for (i in 1...killMe.length-1) { coverMeInPiss = Reflect.getProperty(coverMeInPiss, killMe[i]); } - return Reflect.setProperty(coverMeInPiss, killMe[killMe.length-1], value); + Reflect.setProperty(coverMeInPiss, killMe[killMe.length-1], value); + } else { + Reflect.setProperty(EditorPlayState.instance, variable, value); } - return Reflect.setProperty(EditorPlayState.instance, variable, value); }); - Lua_helper.add_callback(lua, "getPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic) { + + LuaUtils.addFunction(lua, "getPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic):Dynamic { if(Std.isOfType(Reflect.getProperty(EditorPlayState.instance, obj), FlxTypedGroup)) { return Reflect.getProperty(Reflect.getProperty(EditorPlayState.instance, obj).members[index], variable); } @@ -128,20 +135,24 @@ class EditorLua { } return null; }); - Lua_helper.add_callback(lua, "setPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic, value:Dynamic) { + + LuaUtils.addFunction(lua, "setPropertyFromGroup", function(obj:String, index:Int, variable:Dynamic, value:Dynamic):Void { if(Std.isOfType(Reflect.getProperty(EditorPlayState.instance, obj), FlxTypedGroup)) { - return Reflect.setProperty(Reflect.getProperty(EditorPlayState.instance, obj).members[index], variable, value); + Reflect.setProperty(Reflect.getProperty(EditorPlayState.instance, obj).members[index], variable, value); + return; } var leArray:Dynamic = Reflect.getProperty(EditorPlayState.instance, obj)[index]; if(leArray != null) { if(Type.typeof(variable) == ValueType.TInt) { - return leArray[variable] = value; + leArray[variable] = value; + } else { + Reflect.setProperty(leArray, variable, value); } - return Reflect.setProperty(leArray, variable, value); } }); - Lua_helper.add_callback(lua, "removeFromGroup", function(obj:String, index:Int, dontDestroy:Bool = false) { + + LuaUtils.addFunction(lua, "removeFromGroup", function(obj:String, index:Int, dontDestroy:Bool = false):Void { if(Std.isOfType(Reflect.getProperty(EditorPlayState.instance, obj), FlxTypedGroup)) { var sex = Reflect.getProperty(EditorPlayState.instance, obj).members[index]; if(!dontDestroy) @@ -154,33 +165,26 @@ class EditorLua { Reflect.getProperty(EditorPlayState.instance, obj).remove(Reflect.getProperty(EditorPlayState.instance, obj)[index]); }); - Lua_helper.add_callback(lua, "getColorFromHex", function(color:String) { + LuaUtils.addFunction(lua, "getColorFromHex", function(color:String):Int { if(!color.startsWith('0x')) color = '0xff' + color; return Std.parseInt(color); }); - Lua_helper.add_callback(lua, "setGraphicSize", function(obj:String, x:Int, y:Int = 0) { + LuaUtils.addFunction(lua, "setGraphicSize", function(obj:String, x:Int, y:Int = 0):Void { var poop:FlxSprite = Reflect.getProperty(EditorPlayState.instance, obj); - if(poop != null) { - poop.setGraphicSize(x, y); - poop.updateHitbox(); - return; - } + poop?.setGraphicSize(x, y); + poop?.updateHitbox(); }); - Lua_helper.add_callback(lua, "scaleObject", function(obj:String, x:Float, y:Float) { + + LuaUtils.addFunction(lua, "scaleObject", function(obj:String, x:Float, y:Float):Void { var poop:FlxSprite = Reflect.getProperty(EditorPlayState.instance, obj); - if(poop != null) { - poop.scale.set(x, y); - poop.updateHitbox(); - return; - } + poop?.scale.set(x, y); + poop?.updateHitbox(); }); - Lua_helper.add_callback(lua, "updateHitbox", function(obj:String) { + + LuaUtils.addFunction(lua, "updateHitbox", function(obj:String):Void { var poop:FlxSprite = Reflect.getProperty(EditorPlayState.instance, obj); - if(poop != null) { - poop.updateHitbox(); - return; - } + poop?.updateHitbox(); }); api.Discord.DiscordClient.addLuaCallbacks(lua); @@ -195,37 +199,51 @@ class EditorLua { return Function_Continue; } - Lua.getglobal(lua, event); + try { + Lua.getglobal(lua, event); + var type:Int = Lua.type(lua, -1); + + if (type != 6) { // 6 = LUA_TFUNCTION + Lua.pop(lua, 1); + + if (event != 'onCreate' && event != 'onUpdate') { + trace('Lua function "$event" not found'); + } + return Function_Continue; + } + + for (arg in args) { + LuaConverter.toLua(lua, arg); + } - for (arg in args) { - Convert.toLua(lua, arg); - } + var status:Int = Lua.pcall(lua, args.length, 1, 0); - var result:Null = Lua.pcall(lua, args.length, 1, 0); - if(result != null && resultIsAllowed(lua, result)) { - /*var resultStr:String = Lua.tostring(lua, result); - var error:String = Lua.tostring(lua, -1); - Lua.pop(lua, 1);*/ - if(Lua.type(lua, -1) == Lua.LUA_TSTRING) { + if (status != Lua.OK) { var error:String = Lua.tostring(lua, -1); - Lua.pop(lua, 1); - if(error == 'attempt to call a nil value') { //Makes it ignore warnings and not break stuff if you didn't put the functions on your lua file - return Function_Continue; + if (error != 'attempt to call a nil value' && !error.contains('onCreate')) { + trace('Lua error calling $event: $error'); } + Lua.pop(lua, 1); + return Function_Continue; } - var conv:Dynamic = Convert.fromLua(lua, result); - return conv; + var result:Dynamic = LuaConverter.fromLua(lua, -1); + Lua.pop(lua, 1); + + if (result == null) result = Function_Continue; + return result; + } catch(e:Dynamic) { + trace('Error calling Lua function $event: $e'); } #end return Function_Continue; } #if LUA_ALLOWED - function resultIsAllowed(leLua:State, leResult:Null) { //Makes it ignore warnings - switch(Lua.type(leLua, leResult)) { - case Lua.LUA_TNIL | Lua.LUA_TBOOLEAN | Lua.LUA_TNUMBER | Lua.LUA_TSTRING | Lua.LUA_TTABLE: - return true; + function resultIsAllowed(leLua:cpp.RawPointer, leResult:Null):Bool { + var type:Int = Lua.type(leLua, leResult); + if (type == 0 || type == 1 || type == 3 || type == 4 || type == 5) { + return true; } return false; } @@ -237,25 +255,14 @@ class EditorLua { return; } - Convert.toLua(lua, data); - Lua.setglobal(lua, variable); + LuaUtils.setVariable(lua, variable, data); #end } #if LUA_ALLOWED public function getBool(variable:String) { - var result:String = null; - Lua.getglobal(lua, variable); - result = Convert.fromLua(lua, -1); - Lua.pop(lua, 1); - - if(result == null) { - return false; - } - - // YES! FINALLY IT WORKS - //trace('variable: ' + variable + ', ' + result); - return (result == 'true'); + var result:Dynamic = LuaUtils.getVariable(lua, variable); + return (result == true || result == 'true'); } #end @@ -265,6 +272,7 @@ class EditorLua { return; } + LuaUtils.cleanupStateFunctions(lua); Lua.close(lua); lua = null; #end diff --git a/source/game/states/backend/MusicBeatState.hx b/source/game/states/backend/MusicBeatState.hx index 2a5e882..9c42c92 100644 --- a/source/game/states/backend/MusicBeatState.hx +++ b/source/game/states/backend/MusicBeatState.hx @@ -22,7 +22,7 @@ import sys.FileSystem; #end #if SCRIPTABLE_STATES import game.scripting.FunkinHScript; -import game.scripting.FunkinRScript; +import game.scripting.FunkinRuleScript; import game.scripting.HScriptGlobal; #end @@ -32,7 +32,7 @@ import openfl.utils.AssetType; class MusicBeatState extends FlxState { #if (HSCRIPT_ALLOWED && SCRIPTABLE_STATES) - public var menuScriptArray:Array = []; + public var menuScriptArray:Array = []; private var excludeStates:Array; #end @@ -76,7 +76,6 @@ class MusicBeatState extends FlxState // (WStaticInitOrder) Warning : maybe loop in static generation of MusicBeatState private static function initExcludeStates():Array { return [ - game.states.LoadingState, game.PlayState, game.scripting.HScriptState, MusicBeatState, @@ -168,7 +167,7 @@ class MusicBeatState extends FlxState for (path in scriptFiles) { menuScriptArray.push(new FunkinHScript(path, this)); - if (path.contains('contents/')) + if (path.contains('${Mods.MODS_FOLDER}/')) trace('Loaded mod state script: $path'); else trace('Loaded base game state script: $path'); @@ -178,6 +177,8 @@ class MusicBeatState extends FlxState super.create(); + quickSetOnMenuScripts('this', this); + quickCallMenuScript("onCreatePost", []); } @@ -368,6 +369,11 @@ class MusicBeatState extends FlxState { if(quickCallMenuScript("onOpenSubState", [subState]) != FunkinLua.Function_Stop) super.openSubState(subState); } + + override public function closeSubState() + { + if(quickCallMenuScript("onCloseSubState", []) != FunkinLua.Function_Stop) super.closeSubState(); + } override public function onResize(w:Int, h:Int) { super.onResize(w, h); diff --git a/source/game/states/editors/CharacterEditorState.hx b/source/game/states/editors/CharacterEditorState.hx index 75fedfb..f9d1f62 100644 --- a/source/game/states/editors/CharacterEditorState.hx +++ b/source/game/states/editors/CharacterEditorState.hx @@ -26,17 +26,30 @@ import haxe.Json; import lime.system.Clipboard; import game.objects.Character; +import game.objects.Character.ShadowData; import game.objects.HealthIcon; using StringTools; typedef HistoryStuff = { var animations:Array; + var shadow:Null<{ + var visible:Bool; + var color:Array; + var offset:Array; + var skew:Array; + var alpha:Float; + var scale:Array; + var scrollFactor:Array; + var flip_x:Bool; + var flip_y:Bool; + }>; + var shadow_offsets:Array<{anim:String, offsets:Array}>; var position:Array; var scale:Float; var cameraPosition:Array; var healthColor:Array; - var curAnim:Int; + var curAnim:Int; } @:bitmap("psych-ui/images/cursorCross.png") @@ -46,28 +59,23 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler { var char:Character; var ghostChar:Character; - var textAnim:FlxText; var bgLayer:FlxTypedGroup; var charLayer:FlxTypedGroup; + var shadowLayer:FlxTypedGroup; var dumbTexts:FlxTypedGroup; //var animList:Array = []; var curAnim:Int = 0; var daAnim:String = 'spooky'; var goToPlayState:Bool = true; - public function new(daAnim:String = 'spooky', goToPlayState:Bool = true) - { - super(); - this.daAnim = daAnim; - this.goToPlayState = goToPlayState; - } - var UI_box:PsychUIBox; var UI_characterbox:PsychUIBox; private var camEditor:FlxCamera; private var camHUD:FlxCamera; + var textAnim:FlxText; + var grid:FlxSprite; var gridVisible:Bool = false; @@ -84,9 +92,22 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler var cameraFollowPointer:FlxSprite; var healthBarBG:FlxSprite; + var draggingCamera:Bool = false; + var cameraSmoothness:Float = 0.2; + var cameraDragSensitivity:Float = 0.5; + var cameraScrollTarget:FlxPoint = FlxPoint.get(FlxG.camera.scroll.x, FlxG.camera.scroll.y); + var lastAutoSaveTime:Float = 0; + static inline final AUTO_SAVE_INTERVAL:Float = 60; // Auto save every 60 seconds + public function new(daAnim:String = 'spooky', goToPlayState:Bool = true) + { + super(); + this.daAnim = daAnim; + this.goToPlayState = goToPlayState; + } + override function create() { //FlxG.sound.playMusic(Paths.music('breakfast'), 0.5); @@ -96,7 +117,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler FlxG.cameras.add(camHUD, false); - grid = FlxGridOverlay.create(10, 10, FlxG.width * 4, FlxG.height * 4, true, 0x22FFFFFF, 0x55FFFFFF); + grid = FlxGridOverlay.create(15, 15, FlxG.width * 6, FlxG.height * 6, true, 0x22FFFFFF, 0x55FFFFFF); //42 БРАТУХА grid.screenCenter(); grid.visible = gridVisible; grid.cameras = [camEditor]; @@ -106,6 +127,9 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler add(grid); + shadowLayer = new FlxTypedGroup(); + add(shadowLayer); + charLayer = new FlxTypedGroup(); add(charLayer); @@ -186,11 +210,13 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler addSettingsUI(); addCharacterUI(); addAnimationsUI(); + addShadowsUI(); UI_box.selectedName = 'Settings'; UI_characterbox.selectedName = 'Character'; reloadCharacterOptions(); + reloadShadowCharOptions(); super.create(); } @@ -201,7 +227,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler UI_box.scrollFactor.set(); UI_box.cameras = [camHUD]; - UI_characterbox = new PsychUIBox(UI_box.x - 100, UI_box.y + UI_box.height + 10, 350, 280, ['Animations', 'Character']); + UI_characterbox = new PsychUIBox(UI_box.x - 100, UI_box.y + UI_box.height + 10, 350, 280, ['Shadows\n(Unfinished)', 'Animations', 'Character']); UI_characterbox.scrollFactor.set(); UI_characterbox.cameras = [camHUD]; add(UI_characterbox); @@ -285,84 +311,93 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } var TemplateCharacter:String = '{ - "animations": [ - { - "loop": false, - "offsets": [ - 0, - 0 - ], - "fps": 24, - "anim": "idle", - "indices": [], - "name": "Dad idle dance" - }, - { - "offsets": [ - 0, - 0 - ], - "indices": [], - "fps": 24, - "anim": "singLEFT", - "loop": false, - "name": "Dad Sing Note LEFT" - }, - { - "offsets": [ - 0, - 0 - ], - "indices": [], - "fps": 24, - "anim": "singDOWN", - "loop": false, - "name": "Dad Sing Note DOWN" - }, - { - "offsets": [ - 0, - 0 - ], - "indices": [], - "fps": 24, - "anim": "singUP", - "loop": false, - "name": "Dad Sing Note UP" - }, - { - "offsets": [ - 0, - 0 - ], - "indices": [], - "fps": 24, - "anim": "singRIGHT", - "loop": false, - "name": "Dad Sing Note RIGHT" - } - ], - "no_antialiasing": false, - "image": "characters/DADDY_DEAREST", - "position": [ - 0, - 0 - ], - "healthicon": "face", - "flip_x": false, - "healthbar_colors": [ - 161, - 161, - 161 - ], - "camera_position": [ - 0, - 0 - ], - "sing_duration": 6.1, - "vocals_file": null, - "scale": 1 - }'; + "animations": [ + { + "loop": false, + "offsets": [ + 0, + 0 + ], + "fps": 24, + "anim": "idle", + "indices": [], + "name": "Dad idle dance" + }, + { + "offsets": [ + 0, + 0 + ], + "indices": [], + "fps": 24, + "anim": "singLEFT", + "loop": false, + "name": "Dad Sing Note LEFT" + }, + { + "offsets": [ + 0, + 0 + ], + "indices": [], + "fps": 24, + "anim": "singDOWN", + "loop": false, + "name": "Dad Sing Note DOWN" + }, + { + "offsets": [ + 0, + 0 + ], + "indices": [], + "fps": 24, + "anim": "singUP", + "loop": false, + "name": "Dad Sing Note UP" + }, + { + "offsets": [ + 0, + 0 + ], + "indices": [], + "fps": 24, + "anim": "singRIGHT", + "loop": false, + "name": "Dad Sing Note RIGHT" + } + ], + "shadow": { + "visible": true, + "color": [0, 0, 0], + "offset": [0, 0], + "skew": [0, 0], + "alpha": 0.6, + "scale": [1, 1], + "scrollFactor": [1, 1] + }, + "no_antialiasing": false, + "image": "characters/daddy/DADDY_DEAREST", + "position": [ + 0, + 0 + ], + "healthicon": "face", + "flip_x": false, + "healthbar_colors": [ + 161, + 161, + 161 + ], + "camera_position": [ + 0, + 0 + ], + "sing_duration": 6.1, + "vocals_file": null, + "scale": 1 + }'; var ghostAnim:String = ''; var ghostAlpha:Float = 0.6; @@ -482,12 +517,32 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler character.healthIcon = parsedJson.healthicon; character.healthColorArray = parsedJson.healthbar_colors; character.setPosition(character.positionArray[0] + OFFSET_X + 100, character.positionArray[1]); + + if (parsedJson.shadow != null) { + var shadowData:ShadowData = parsedJson.shadow; + character.shadowVisible = shadowData.visible; + character.shadowColor = FlxColor.fromRGB( + shadowData.color[0], + shadowData.color[1], + shadowData.color[2] + ); + character.shadowOffset.set(shadowData.offset[0], shadowData.offset[1]); + character.shadowSkew.set(shadowData.skew[0], shadowData.skew[1]); + character.shadowAlpha = shadowData.alpha; + character.shadowScale.set(shadowData.scale[0], shadowData.scale[1]); + character.shadowScrollFactor.set( + shadowData.scrollFactor[0], + shadowData.scrollFactor[1] + ); + character.shadowFlipX = shadowData.flip_x; + character.shadowFlipY = shadowData.flip_y; + } } reloadCharacterImage(); reloadCharacterDropDown(); reloadCharacterOptions(); - resetHealthBarColor(); + reloadShadowCharOptions(); updatePointerPos(); genBoyOffsets(); saveHistoryStuff(); @@ -546,7 +601,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler healthIconInputText = new PsychUIInputText(15, imageInputText.y + 35, 75, leHealthIcon.getCharacter(), 8); - vocalsInputText = new PsychUIInputText(15, healthIconInputText.y + 35, 75, char.vocalsFile != null ? char.vocalsFile : '', 8); + vocalsInputText = new PsychUIInputText(15, healthIconInputText.y + 35, 75, char.vocalsFile ?? '', 8); singDurationStepper = new PsychUINumericStepper(15, healthIconInputText.y + 75, 0.1, 4, 0, 999, 1); @@ -574,11 +629,11 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler ghostChar.antialiasing = char.antialiasing; }; - positionXStepper = new PsychUINumericStepper(flipXCheckBox.x + 110, flipXCheckBox.y, 10, char.positionArray[0], -9000, 9000, 0); - positionYStepper = new PsychUINumericStepper(positionXStepper.x + 70, positionXStepper.y, 10, char.positionArray[1], -9000, 9000, 0); + positionXStepper = new PsychUINumericStepper(flipXCheckBox.x + 110, flipXCheckBox.y, 10, char.positionArray[0]); + positionYStepper = new PsychUINumericStepper(positionXStepper.x + 70, positionXStepper.y, 10, char.positionArray[1]); - positionCameraXStepper = new PsychUINumericStepper(positionXStepper.x, positionXStepper.y + 40, 10, char.cameraPosition[0], -9000, 9000, 0); - positionCameraYStepper = new PsychUINumericStepper(positionYStepper.x, positionYStepper.y + 40, 10, char.cameraPosition[1], -9000, 9000, 0); + positionCameraXStepper = new PsychUINumericStepper(positionXStepper.x, positionXStepper.y + 40, 10, char.cameraPosition[0]); + positionCameraYStepper = new PsychUINumericStepper(positionYStepper.x, positionYStepper.y + 40, 10, char.cameraPosition[1]); var saveCharacterButton:PsychUIButton = new PsychUIButton(reloadImage.x, noAntialiasingCheckBox.y + 40, "Save Character", () -> saveCharacter()); @@ -613,6 +668,8 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler tab_group.add(saveCharacterButton); } + var shadowAnimOffsetXStepper:PsychUINumericStepper; + var shadowAnimOffsetYStepper:PsychUINumericStepper; var animationDropDown:PsychUIDropDownMenu; var animationInputText:PsychUIInputText; var animationNameInputText:PsychUIInputText; @@ -628,6 +685,9 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler animationNameFramerate = new PsychUINumericStepper(animationInputText.x + 170, animationInputText.y, 1, 24, 0, 240, 0); animationLoopCheckBox = new PsychUICheckBox(animationNameInputText.x + 170, animationNameInputText.y - 1, "Should it Loop?", 100); + shadowAnimOffsetXStepper = new PsychUINumericStepper(animationInputText.x + 170, animationInputText.y - 55, 1, 0, -1000, 1000, 0); + shadowAnimOffsetYStepper = new PsychUINumericStepper(shadowAnimOffsetXStepper.x + 70, shadowAnimOffsetXStepper.y, 1, 0, -1000, 1000, 0); + animationDropDown = new PsychUIDropDownMenu(15, animationInputText.y - 55, null, (selectedAnimation:Int, pressed:String) -> { var anim:AnimArray = char.animationsArray[selectedAnimation]; animationInputText.text = anim.anim; @@ -641,6 +701,14 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler curAnim = selectedAnimation; char.playAnim(anim.anim, true); + + var shadowOffsets = char.shadowOffsets.get(anim.anim); + if (shadowOffsets == null) { + shadowOffsets = [0, 0]; + char.shadowOffsets.set(anim.anim, shadowOffsets); + } + shadowAnimOffsetXStepper.value = shadowOffsets[0]; + shadowAnimOffsetYStepper.value = shadowOffsets[1]; if (ghostChar.visible) ghostChar.playAnim(anim.anim, true); @@ -683,7 +751,8 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler fps: Math.round(animationNameFramerate.value), loop: animationLoopCheckBox.checked, indices: indices, - offsets: lastOffsets + offsets: lastOffsets, + shadow_offsets: [0, 0] }; if(char.isAnimateAtlas) { if(indices != null && indices.length > 0) { @@ -702,6 +771,8 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler if(!char.hasAnimation(newAnim.anim)) char.addOffset(newAnim.anim, 0, 0); char.animationsArray.push(newAnim); + char.shadowOffsets?.set(newAnim.anim, [0, 0]); + if(lastAnim == animationInputText.text) { var leAnim = !char.isAnimateAtlas ? char.animation.getByName(lastAnim) : char.atlas.anim.getByName(lastAnim); if(leAnim != null && leAnim.frames.length > 0) { @@ -744,6 +815,8 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler char.animationsArray.remove(anim); } + char.shadowOffsets?.remove(anim.anim); + if(resetAnim && char.animationsArray.length > 0) { char.playAnim(char.animationsArray[0].anim, true); curAnim = 0; @@ -772,6 +845,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler tab_group.add(new FlxText(animationNameFramerate.x, animationNameFramerate.y - 18, 0, 'Framerate:')); tab_group.add(new FlxText(animationNameInputText.x, animationNameInputText.y - 18, 0, 'Animation on .XML/.TXT file:')); tab_group.add(new FlxText(animationIndicesInputText.x, animationIndicesInputText.y - 18, 0, 'ADVANCED - Animation Indices:')); + tab_group.add(new FlxText(shadowAnimOffsetXStepper.x, shadowAnimOffsetXStepper.y - 18, 0, 'Shadow Anim X/Y:')); tab_group.add(animationInputText); tab_group.add(animationNameInputText); @@ -781,6 +855,95 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler tab_group.add(addUpdateButton); tab_group.add(removeButton); tab_group.add(animationDropDown); + tab_group.add(shadowAnimOffsetXStepper); + tab_group.add(shadowAnimOffsetYStepper); + } + + var shadowVisibleCheck:PsychUICheckBox; + var shadowColorStepperR:PsychUINumericStepper; + var shadowColorStepperG:PsychUINumericStepper; + var shadowColorStepperB:PsychUINumericStepper; + var shadowAlphaSlider:PsychUISlider; + var shadowOffsetXStepper:PsychUINumericStepper; + var shadowOffsetYStepper:PsychUINumericStepper; + var shadowSkewXStepper:PsychUINumericStepper; + var shadowSkewYStepper:PsychUINumericStepper; + var shadowScaleXStepper:PsychUINumericStepper; + var shadowScaleYStepper:PsychUINumericStepper; + var shadowScrollXStepper:PsychUINumericStepper; + var shadowScrollYStepper:PsychUINumericStepper; + var shadowFlipXCheckBox:PsychUICheckBox; + var shadowFlipYCheckBox:PsychUICheckBox; + function addShadowsUI() { + var tab_group = UI_characterbox.getTab('Shadows\n(Unfinished)').menu; + + shadowVisibleCheck = new PsychUICheckBox(10, 10, "Shadow Visible", 100); + shadowVisibleCheck.checked = char.shadowVisible; + shadowVisibleCheck.onClick = () -> { + char.shadowVisible = !char.shadowVisible; + char.updateShadow(); + }; + + shadowColorStepperR = new PsychUINumericStepper(70, 35, 20, char.shadowColor.red, 0, 255, 0); + shadowColorStepperG = new PsychUINumericStepper(shadowColorStepperR.x + 70, shadowColorStepperR.y, 20, char.shadowColor.green, 0, 255, 0); + shadowColorStepperB = new PsychUINumericStepper(shadowColorStepperG.x + 70, shadowColorStepperG.y, 20, char.shadowColor.blue, 0, 255, 0); + + shadowAlphaSlider = new PsychUISlider(75, 50, (v:Float) -> { + char.shadowAlpha = v; + if(char.shadowSprite != null) char.shadowSprite.alpha = char.shadowAlpha; + #if flixel_animate + if(char.shadowAtlas != null) char.shadowAtlas.alpha = char.shadowAlpha; + #end + }, char.shadowAlpha, 0, 1); + shadowAlphaSlider.label = 'Alpha:'; + shadowAlphaSlider.labelText.y += 2.5; + + shadowOffsetXStepper = new PsychUINumericStepper(shadowAlphaSlider.x + 35, 112.5, 1, char.shadowOffset.x, -9999, 9999); + shadowOffsetYStepper = new PsychUINumericStepper(shadowOffsetXStepper.x + 70, shadowOffsetXStepper.y, 1, char.shadowOffset.y, -9999, 9999); + + shadowSkewXStepper = new PsychUINumericStepper(shadowAlphaSlider.x + 35, 142.5, 1, char.shadowSkew.x, -180, 180, 0); + shadowSkewYStepper = new PsychUINumericStepper(shadowSkewXStepper.x + 70, shadowSkewXStepper.y, 1, char.shadowSkew.y, -180, 180, 0); + + shadowScaleXStepper = new PsychUINumericStepper(shadowAlphaSlider.x + 35, 172.5, 0.1, char.shadowScale.x, 0.05, 10, 2); + shadowScaleYStepper = new PsychUINumericStepper(shadowScaleXStepper.x + 70, shadowScaleXStepper.y, 0.1, char.shadowScale.y, 0.05, 10, 2); + + shadowScrollXStepper = new PsychUINumericStepper(shadowAlphaSlider.x + 35, 202.5, 0.1, char.shadowScrollFactor.x, 0, 2, 1); + shadowScrollYStepper = new PsychUINumericStepper(shadowScrollXStepper.x + 70, shadowScrollXStepper.y, 0.1, char.shadowScrollFactor.y, 0, 2, 1); + + shadowFlipXCheckBox = new PsychUICheckBox(shadowAlphaSlider.x - 2.5, 230, "Flip X", 100); + shadowFlipXCheckBox.checked = char.shadowFlipX; + shadowFlipXCheckBox.onClick = () -> { + char.shadowFlipX = !char.shadowFlipX; + char.updateShadow(); + }; + + shadowFlipYCheckBox = new PsychUICheckBox(shadowFlipXCheckBox.x + 155, shadowFlipXCheckBox.y, "Flip Y", 100); + shadowFlipYCheckBox.checked = char.shadowFlipY; + shadowFlipYCheckBox.onClick = () -> { + char.shadowFlipY = !char.shadowFlipY; + char.updateShadow(); + }; + + tab_group.add(shadowVisibleCheck); + tab_group.add(new FlxText(shadowColorStepperG.x - 15, shadowColorStepperG.y - 15, 0, 'Shadow Color R/G/B:')); + tab_group.add(shadowColorStepperR); + tab_group.add(shadowColorStepperG); + tab_group.add(shadowColorStepperB); + tab_group.add(shadowAlphaSlider); + tab_group.add(new FlxText(shadowOffsetXStepper.x + 15, 100, 0, 'Shadow Offset X/Y:')); + tab_group.add(shadowOffsetXStepper); + tab_group.add(shadowOffsetYStepper); + tab_group.add(new FlxText(shadowSkewXStepper.x + 15, 130, 0, 'Shadow Skew X/Y:')); + tab_group.add(shadowSkewXStepper); + tab_group.add(shadowSkewYStepper); + tab_group.add(new FlxText(shadowScaleXStepper.x + 15, 160, 0, 'Shadow Scale X/Y:')); + tab_group.add(shadowScaleXStepper); + tab_group.add(shadowScaleYStepper); + tab_group.add(new FlxText(shadowScrollXStepper.x + 15, 190, 0, 'Shadow Scroll X/Y:')); + tab_group.add(shadowScrollXStepper); + tab_group.add(shadowScrollYStepper); + tab_group.add(shadowFlipXCheckBox); + tab_group.add(shadowFlipYCheckBox); } public function UIEvent(id:String, sender:Dynamic) { @@ -868,6 +1031,96 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler healthBarBG.color = FlxColor.fromRGB(char.healthColorArray[0], char.healthColorArray[1], char.healthColorArray[2]); saveHistoryStuff(); } + //shadow thingies + else if(sender == shadowColorStepperR) { + char.shadowColor.red = Math.round(shadowColorStepperR.value); + char.updateShadow(); + saveHistoryStuff(); + } + else if(sender == shadowColorStepperG) { + char.shadowColor.green = Math.round(shadowColorStepperG.value); + char.updateShadow(); + saveHistoryStuff(); + } + else if(sender == shadowColorStepperB) { + char.shadowColor.blue = Math.round(shadowColorStepperB.value); + char.updateShadow(); + saveHistoryStuff(); + } + else if(sender == shadowOffsetXStepper) + { + char.shadowOffset.x = shadowOffsetXStepper.value; + char.updateShadow(); + saveHistoryStuff(); + } + else if(sender == shadowOffsetYStepper) + { + char.shadowOffset.y = shadowOffsetYStepper.value; + char.updateShadow(); + saveHistoryStuff(); + } + else if(sender == shadowScaleXStepper) + { + char.shadowScale.x = shadowScaleXStepper.value; + char.updateShadow(); + saveHistoryStuff(); + } + else if(sender == shadowScaleYStepper) + { + char.shadowScale.x = shadowScaleXStepper.value; + char.updateShadow(); + saveHistoryStuff(); + } + else if(sender == shadowSkewXStepper) { + char.shadowSkew.x = shadowSkewXStepper.value; + if(char.shadowSprite != null) char.shadowSprite.skew.x = char.shadowSkew.x; + #if flixel_animate + if(char.shadowAtlas != null) char.shadowAtlas.skew.x = char.shadowSkew.x; + #end + saveHistoryStuff(); + } + else if(sender == shadowSkewYStepper) { + char.shadowSkew.y = shadowSkewYStepper.value; + if(char.shadowSprite != null) char.shadowSprite.skew.y = char.shadowSkew.y; + #if flixel_animate + if(char.shadowAtlas != null) char.shadowAtlas.skew.y = char.shadowSkew.y; + #end + saveHistoryStuff(); + } + else if(sender == shadowAnimOffsetXStepper) + { + if(char.animationsArray[curAnim] != null) { + var animName = char.animationsArray[curAnim].anim; + var offsets = char.shadowOffsets.get(animName) ?? [0, 0]; + offsets[0] = Std.int(shadowAnimOffsetXStepper.value); + char.shadowOffsets.set(animName, offsets); + + if(animName == char.getAnimationName()) { + char.shadowSprite.offset.x = shadowAnimOffsetXStepper.value; + if(char.shadowSprite != null) char.shadowSprite.offset.x = char.shadowOffset.x + shadowAnimOffsetXStepper.value; + #if flixel_animate + if(char.shadowAtlas != null) char.shadowAtlas.offset.x = char.shadowOffset.x + shadowAnimOffsetXStepper.value; + #end + } + } + } + else if(sender == shadowAnimOffsetYStepper) + { + if(char.animationsArray[curAnim] != null) { + var animName = char.animationsArray[curAnim].anim; + var offsets = char.shadowOffsets.get(animName) ?? [0, 0]; + offsets[1] = Std.int(shadowAnimOffsetYStepper.value); + char.shadowOffsets.set(animName, offsets); + + if(animName == char.getAnimationName()) { + char.shadowOffset.y = shadowAnimOffsetYStepper.value; + if(char.shadowSprite != null) char.shadowSprite.offset.y = char.offset.y + shadowAnimOffsetYStepper.value; + #if flixel_animate + if(char.shadowAtlas != null) char.shadowAtlas.offset.y = char.offset.y + shadowAnimOffsetYStepper.value; + #end + } + } + } } } @@ -917,6 +1170,8 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler char.addOffset(animAnim, 0, 0); } } + + reloadShadowCharImage(); char.setPosition(char.positionArray[0] + OFFSET_X + 100, char.positionArray[1]); ghostChar.setPosition(char.x, char.y); @@ -929,6 +1184,65 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler reloadGhost(); } + function reloadShadowCharImage() { + if(char == null) return; + + var imagePath = 'images/' + char.imageFile; + + #if flixel_animate + if(char.isAnimateAtlas) { + if(char.shadowAtlas != null) { + char.shadowAtlas.frames = Paths.getAnimateAtlas(char.imageFile); + char.shadowAtlas.anim.destroyAnimations(); + + for (anim in char.animationsArray) { + if(anim.indices != null && anim.indices.length > 0) { + char.shadowAtlas.anim.addBySymbolIndices(anim.anim, anim.name, anim.indices, anim.fps, anim.loop); + } else { + char.shadowAtlas.anim.addBySymbol(anim.anim, anim.name, anim.fps, anim.loop); + } + } + } + } else { + #end + if(char.shadowSprite != null) { + if(Paths.fileExists(imagePath + '.txt', TEXT)) + char.shadowSprite.frames = Paths.getPackerAtlas(char.imageFile); + else if(Paths.fileExists(imagePath + '.json', TEXT)) + char.shadowSprite.frames = Paths.getAsepriteAtlas(char.imageFile); + else + char.shadowSprite.frames = Paths.getSparrowAtlas(char.imageFile); + + char.shadowSprite.animation.destroyAnimations(); + + for (anim in char.animationsArray) { + if(anim.indices != null && anim.indices.length > 0) { + char.shadowSprite.animation.addByIndices(anim.anim, anim.name, anim.indices, "", anim.fps, anim.loop); + } else { + char.shadowSprite.animation.addByPrefix(anim.anim, anim.name, anim.fps, anim.loop); + } + } + } + #if flixel_animate + } + #end + + if(!char.isAnimationNull()) { + var currentAnim = char.getAnimationName(); + #if flixel_animate + if(char.isAnimateAtlas && char.shadowAtlas != null) { + char.shadowAtlas.anim.play(currentAnim, true); + } else if(char.shadowSprite != null) { + #else + if(char.shadowSprite != null) { + #end + char.shadowSprite.animation.play(currentAnim, true); + } + } + + char.updateShadow(); + } + function genBoyOffsets():Void { var daLoop:Int = 0; @@ -945,9 +1259,10 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } dumbTexts.clear(); - for (anim => offsets in char.animOffsets) + for (anim in char.animationsArray) { - var text:FlxText = new FlxText(10, 20 + (18 * daLoop), 0, anim + ": " + offsets, 15); + var offsets = char.animOffsets.get(anim.anim); + var text:FlxText = new FlxText(10, 20 + (18 * daLoop), 0, anim.anim + ": " + offsets, 15); text.setFormat(null, 16, FlxColor.WHITE, CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); text.scrollFactor.set(); text.borderSize = 1; @@ -991,6 +1306,19 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler --i; } charLayer.clear(); + + var i:Int = shadowLayer.members.length-1; + while(i >= 0) { + var memb:FlxSprite = shadowLayer.members[i]; + if(memb != null) { + memb.kill(); + shadowLayer.remove(memb); + memb.destroy(); + } + --i; + } + shadowLayer.clear(); + ghostChar = new Character(0, 0, daAnim, !isDad); ghostChar.debugMode = true; ghostChar.alpha = 0.6; @@ -1003,6 +1331,13 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler char.debugMode = true; charLayer.add(ghostChar); + + #if flixel_animate + if (char.shadowAtlas != null) + shadowLayer.add(char.shadowAtlas); + else #end if (char.shadowSprite != null) + shadowLayer.add(char.shadowSprite); + charLayer.add(char); char.setPosition(char.positionArray[0] + OFFSET_X + 100, char.positionArray[1]); @@ -1025,6 +1360,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler saveHistoryStuff(); } reloadCharacterOptions(); + reloadShadowCharOptions(); reloadBGs(); updatePointerPos(); } @@ -1037,12 +1373,12 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler var offY:Float = 0; if(!char.isPlayer) { - offX = char.getMidpoint().x + 150 + char.cameraPosition[0]; + offX = char.getMidpoint().x + 100 + char.cameraPosition[0]; offY = char.getMidpoint().y - 100 + char.cameraPosition[1]; } else { - offX = char.getMidpoint().x - 100 - char.cameraPosition[0]; + offX = char.getMidpoint().x - 150 + char.cameraPosition[0]; offY = char.getMidpoint().y - 100 + char.cameraPosition[1]; } cameraFollowPointer.setPosition(offX, offY); @@ -1068,22 +1404,42 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler if(UI_characterbox != null) { imageInputText.text = char.imageFile; healthIconInputText.text = char.healthIcon; - vocalsInputText.text = char.vocalsFile != null ? char.vocalsFile : ''; + vocalsInputText.text = char.vocalsFile ?? ''; singDurationStepper.value = char.singDuration; + positionXStepper.value = char.positionArray[0]; + positionYStepper.value = char.positionArray[1]; + positionCameraXStepper.value = char.cameraPosition[0]; + positionCameraYStepper.value = char.cameraPosition[1]; scaleStepper.value = char.jsonScale; flipXCheckBox.checked = char.originalFlipX; noAntialiasingCheckBox.checked = char.noAntialiasing; resetHealthBarColor(); leHealthIcon.changeIcon(healthIconInputText.text, false); - positionXStepper.value = char.positionArray[0]; - positionYStepper.value = char.positionArray[1]; - positionCameraXStepper.value = char.cameraPosition[0]; - positionCameraYStepper.value = char.cameraPosition[1]; reloadAnimationDropDown(); updatePresence(); } } + inline function reloadShadowCharOptions() { + if(UI_characterbox != null) { + shadowVisibleCheck.checked = char.shadowVisible; + shadowColorStepperR.value = char.shadowColor.red; + shadowColorStepperG.value = char.shadowColor.green; + shadowColorStepperB.value = char.shadowColor.blue; + shadowAlphaSlider.value = char.shadowAlpha; + shadowOffsetXStepper.value = char.shadowOffset.x; + shadowOffsetYStepper.value = char.shadowOffset.y; + shadowSkewXStepper.value = char.shadowSkew.x; + shadowSkewYStepper.value = char.shadowSkew.y; + shadowScaleXStepper.value = char.shadowScale.x; + shadowScaleYStepper.value = char.shadowScale.y; + shadowScrollXStepper.value = char.shadowScrollFactor.x; + shadowScrollYStepper.value = char.shadowScrollFactor.y; + shadowFlipXCheckBox.checked = char.shadowFlipX; + shadowFlipYCheckBox.checked = char.shadowFlipY; + } + } + inline function reloadAnimationDropDown() { var animList:Array = []; for (i in 0...char.animationsArray.length) { @@ -1092,10 +1448,10 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler if(animList.length < 1) animList.push('NO ANIMATIONS'); //Prevents crash animationDropDown.list = animList; - //reloadGhost(); + genBoyOffsets(); } - inline function reloadGhost() { + function reloadGhost() { var wasVisible = ghostChar.visible; var alpha = ghostChar.alpha; @@ -1160,15 +1516,15 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler ghostChar.antialiasing = char.antialiasing; } - inline function reloadCharacterDropDown() { + function reloadCharacterDropDown() { var charsLoaded:Map = new Map(); #if sys characterList = []; - var directories:Array = [#if MODS_ALLOWED Paths.mods('characters/'), Paths.mods(Paths.currentModDirectory + '/characters/'), #end Paths.getPreloadPath('characters/')]; + var directories:Array = [#if MODS_ALLOWED Mods.getModPath('characters/'), Mods.getModPath(Mods.currentModDirectory + '/characters/'), #end Paths.getPreloadPath('characters/')]; #if MODS_ALLOWED - for(mod in Paths.getGlobalMods()) - directories.push(Paths.mods(mod + '/characters/')); + for(mod in Mods.getGlobalMods()) + directories.push(Mods.getModPath(mod + '/characters/')); #end for (i in 0...directories.length) { var directory:String = directories[i]; @@ -1209,6 +1565,16 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler healthBarBG.color = FlxColor.fromRGB(char.healthColorArray[0], char.healthColorArray[1], char.healthColorArray[2]); } + inline function resetShadowCharColor() { + shadowColorStepperR.value = char.shadowColor.red; + shadowColorStepperG.value = char.shadowColor.green; + shadowColorStepperB.value = char.shadowColor.blue; + if(char.shadowSprite != null) char.shadowSprite.color = char.shadowColor; + #if flixel_animate + if(char.shadowAtlas != null) char.shadowAtlas.color = char.shadowColor; + #end + } + function updatePresence() { #if DISCORD_ALLOWED // Updating Discord Rich Presence @@ -1236,8 +1602,6 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } if(char.animationsArray[curAnim] != null) { - textAnim.text = char.animationsArray[curAnim].anim; - var animName = char.animationsArray[curAnim].anim; var validAnim = false; @@ -1249,7 +1613,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } if(!validAnim) { - textAnim.text += ' (ERROR!)'; + textAnim.text = 'ERROR!'; } } else { textAnim.text = ''; @@ -1321,17 +1685,38 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler if(FlxG.keys.justPressed.R && !FlxG.keys.pressed.CONTROL) FlxG.camera.zoom = 1; else if (FlxG.keys.pressed.E && FlxG.camera.zoom < 3) { FlxG.camera.zoom += elapsed * FlxG.camera.zoom * shiftMult * ctrlMult; - if(FlxG.camera.zoom > 3) FlxG.camera.zoom = 3; + if(FlxG.camera.zoom >= 3) FlxG.camera.zoom = 3; } else if (FlxG.keys.pressed.Q && FlxG.camera.zoom > 0.1) { FlxG.camera.zoom -= elapsed * FlxG.camera.zoom * shiftMult * ctrlMult; - if(FlxG.camera.zoom < 0.1) FlxG.camera.zoom = 0.1; + if(FlxG.camera.zoom <= 0.1) FlxG.camera.zoom = 0.1; } - if (FlxG.keys.pressed.J) FlxG.camera.scroll.x -= elapsed * 500 * shiftMult * ctrlMult; - if (FlxG.keys.pressed.K) FlxG.camera.scroll.y += elapsed * 500 * shiftMult * ctrlMult; - if (FlxG.keys.pressed.L) FlxG.camera.scroll.x += elapsed * 500 * shiftMult * ctrlMult; - if (FlxG.keys.pressed.I) FlxG.camera.scroll.y -= elapsed * 500 * shiftMult * ctrlMult; + if (!(FlxG.mouse.overlaps(UI_box.bg) || FlxG.mouse.overlaps(UI_characterbox.bg) || FlxG.mouse.overlaps(UI_box) || FlxG.mouse.overlaps(UI_characterbox))) + { + if (FlxG.mouse.pressed && FlxG.mouse.justPressed && PsychUIInputText.focusOn == null) + { + draggingCamera = true; + cameraScrollTarget.set(FlxG.camera.scroll.x, FlxG.camera.scroll.y); + } + + if (FlxG.mouse.justReleased) + { + draggingCamera = false; + } + + if (draggingCamera && FlxG.mouse.pressed) + { + var deltaX = FlxG.mouse.deltaViewX * cameraDragSensitivity; + var deltaY = FlxG.mouse.deltaViewY * cameraDragSensitivity; + + cameraScrollTarget.x -= deltaX; + cameraScrollTarget.y -= deltaY; + + FlxG.camera.scroll.x += (cameraScrollTarget.x - FlxG.camera.scroll.x) * cameraSmoothness; + FlxG.camera.scroll.y += (cameraScrollTarget.y - FlxG.camera.scroll.y) * cameraSmoothness; + } + } if(char.animationsArray.length > 0) { if (FlxG.keys.justPressed.W) @@ -1431,7 +1816,6 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } var _file:FileReference; - function onSaveComplete(_):Void { _file.removeEventListener(Event.COMPLETE, onSaveComplete); @@ -1472,13 +1856,26 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler sys.FileSystem.createDirectory(backupDir); #end - var json = { + var shadowData:ShadowData = { + visible: char.shadowVisible, + color: [char.shadowColor.red, char.shadowColor.green, char.shadowColor.blue], + offset: [char.shadowOffset.x, char.shadowOffset.y], + skew: [char.shadowSkew.x, char.shadowSkew.y], + alpha: char.shadowAlpha, + scale: [char.shadowScale.x, char.shadowScale.y], + scrollFactor: [char.shadowScrollFactor.x, char.shadowScrollFactor.y], + flip_x: char.shadowFlipX, + flip_y: char.shadowFlipY + }; + + var json:CharacterFile = { "animations": char.animationsArray, + "shadow": shadowData, "image": char.imageFile, "scale": char.jsonScale, "sing_duration": char.singDuration, "healthicon": char.healthIcon, - "position": char.positionArray, + "position": char.positionArray, "camera_position": char.cameraPosition, "flip_x": char.originalFlipX, "no_antialiasing": char.noAntialiasing, @@ -1499,16 +1896,27 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler if(_file != null) return; try { - var json = { + var shadowData:ShadowData = { + visible: char.shadowVisible, + color: [char.shadowColor.red, char.shadowColor.green, char.shadowColor.blue], + offset: [char.shadowOffset.x, char.shadowOffset.y], + skew: [char.shadowSkew.x, char.shadowSkew.y], + alpha: char.shadowAlpha, + scale: [char.shadowScale.x, char.shadowScale.y], + scrollFactor: [char.shadowScrollFactor.x, char.shadowScrollFactor.y], + flip_x: char.shadowFlipX, + flip_y: char.shadowFlipY + }; + + var json:CharacterFile = { "animations": char.animationsArray, + "shadow": shadowData, "image": char.imageFile, "scale": char.jsonScale, "sing_duration": char.singDuration, "healthicon": char.healthIcon, - - "position": char.positionArray, + "position": char.positionArray, "camera_position": char.cameraPosition, - "flip_x": char.originalFlipX, "no_antialiasing": char.noAntialiasing, "vocals_file": char.vocalsFile, @@ -1541,6 +1949,11 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } function saveHistoryStuff() { + var shadowOffsetsArray:Array<{anim:String, offsets:Array}> = []; + for (key in char.shadowOffsets.keys()) { + shadowOffsetsArray.push({anim: key, offsets: char.shadowOffsets.get(key).copy()}); + } + var state:HistoryStuff = { animations: [for (anim in char.animationsArray) { anim: anim.anim, @@ -1548,8 +1961,21 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler fps: anim.fps, loop: anim.loop, indices: anim.indices.copy(), - offsets: anim.offsets.copy() + offsets: anim.offsets.copy(), + shadow_offsets: char.shadowOffsets.exists(anim.anim) ? char.shadowOffsets.get(anim.anim).copy() : [0, 0] }], + shadow: { + visible: char.shadowVisible, + color: [char.shadowColor.red, char.shadowColor.green, char.shadowColor.blue], + offset: [char.shadowOffset.x, char.shadowOffset.y], + skew: [char.shadowSkew.x, char.shadowSkew.y], + alpha: char.shadowAlpha, + scale: [char.shadowScale.x, char.shadowScale.y], + scrollFactor: [char.shadowScrollFactor.x, char.shadowScrollFactor.y], + flip_x: char.shadowFlipX, + flip_y: char.shadowFlipY + }, + shadow_offsets: shadowOffsetsArray, position: char.positionArray.copy(), scale: char.jsonScale, cameraPosition: char.cameraPosition.copy(), @@ -1570,6 +1996,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler restoreState(undos.pop()); reloadCharacterOptions(); + reloadShadowCharOptions(); genBoyOffsets(); } @@ -1580,10 +2007,16 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler restoreState(redos.pop()); reloadCharacterOptions(); + reloadShadowCharOptions(); genBoyOffsets(); } function getCurrentState():HistoryStuff { + var shadowOffsetsArray:Array<{anim:String, offsets:Array}> = []; + for (key in char.shadowOffsets.keys()) { + shadowOffsetsArray.push({anim: key, offsets: char.shadowOffsets.get(key).copy()}); + } + return { animations: [for (anim in char.animationsArray) { anim: anim.anim, @@ -1591,8 +2024,21 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler fps: anim.fps, loop: anim.loop, indices: anim.indices.copy(), - offsets: anim.offsets.copy() + offsets: anim.offsets.copy(), + shadow_offsets: char.shadowOffsets.exists(anim.anim) ? char.shadowOffsets.get(anim.anim).copy() : [0, 0] }], + shadow: { + visible: char.shadowVisible, + color: [char.shadowColor.red, char.shadowColor.green, char.shadowColor.blue], + offset: [char.shadowOffset.x, char.shadowOffset.y], + skew: [char.shadowSkew.x, char.shadowSkew.y], + alpha: char.shadowAlpha, + scale: [char.shadowScale.x, char.shadowScale.y], + scrollFactor: [char.shadowScrollFactor.x, char.shadowScrollFactor.y], + flip_x: char.shadowFlipX, + flip_y: char.shadowFlipY + }, + shadow_offsets: shadowOffsetsArray, position: char.positionArray.copy(), scale: char.jsonScale, cameraPosition: char.cameraPosition.copy(), @@ -1607,9 +2053,11 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler ghostChar.animationsArray = [for (anim in state.animations) anim]; for (anim in char.animationsArray) { - char.addOffset(anim.anim, anim.offsets[0], anim.offsets[1]); - ghostChar.addOffset(anim.anim, anim.offsets[0], anim.offsets[1]); - } + char.addOffset(anim.anim, anim.offsets[0], anim.offsets[1]); + ghostChar.addOffset(anim.anim, anim.offsets[0], anim.offsets[1]); + + char.shadowOffsets.set(anim.anim, anim.shadow_offsets.copy()); + } char.positionArray = state.position.copy(); char.cameraPosition = state.cameraPosition.copy(); @@ -1622,12 +2070,57 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } curAnim = state.curAnim; - if (curAnim < 0) curAnim = 0; + if (curAnim <= 0) curAnim = 0; if (curAnim >= char.animationsArray.length) curAnim = char.animationsArray.length - 1; + + if (state.shadow != null) { + var shadowData:ShadowData = state.shadow; + + char.shadowVisible = shadowData.visible; + char.shadowColor = FlxColor.fromRGB(shadowData.color[0], shadowData.color[1], shadowData.color[2]); + char.shadowOffset.set(shadowData.offset[0], shadowData.offset[1]); + char.shadowSkew.set(shadowData.skew[0], shadowData.skew[1]); + char.shadowAlpha = shadowData.alpha; + char.shadowScale.set(shadowData.scale[0], shadowData.scale[1]); + char.shadowScrollFactor.set(shadowData.scrollFactor[0], shadowData.scrollFactor[1]); + char.shadowFlipX = shadowData.flip_x; + char.shadowFlipY = shadowData.flip_y; + + #if flixel_animate + if (char.isAnimateAtlas && char.shadowAtlas != null) { + char.shadowAtlas.visible = char.shadowVisible; + char.shadowAtlas.color = char.shadowColor; + char.shadowAtlas.alpha = char.shadowAlpha; + char.shadowAtlas.skew.x = char.shadowSkew.x; + char.shadowAtlas.skew.y = char.shadowSkew.y; + char.shadowAtlas.scale.x = char.scale.x * char.shadowScale.x; + char.shadowAtlas.scale.y = char.scale.y * char.shadowScale.y; + char.shadowAtlas.scrollFactor.x = char.shadowScrollFactor.x; + char.shadowAtlas.scrollFactor.y = char.shadowScrollFactor.y; + char.shadowAtlas.flipX = char.shadowFlipX; + char.shadowAtlas.flipY = char.shadowFlipY; + } else if (char.shadowSprite != null) { + #else + if (char.shadowSprite != null) { + #end + char.shadowSprite.visible = char.shadowVisible; + char.shadowSprite.color = char.shadowColor; + char.shadowSprite.alpha = char.shadowAlpha; + char.shadowSprite.skew.x = char.shadowSkew.x; + char.shadowSprite.skew.y = char.shadowSkew.y; + char.shadowSprite.scale.x = char.scale.x * char.shadowScale.x; + char.shadowSprite.scale.y = char.scale.y * char.shadowScale.y; + char.shadowSprite.scrollFactor.x = char.shadowScrollFactor.x; + char.shadowSprite.scrollFactor.y = char.shadowScrollFactor.y; + char.shadowSprite.flipX = char.shadowFlipX; + char.shadowSprite.flipY = char.shadowFlipY; + } + } reloadAnimationDropDown(); reloadGhost(); reloadCharacterOptions(); + reloadShadowCharOptions(); reloadCharacterImage(); updatePointerPos(); genBoyOffsets(); @@ -1644,6 +2137,7 @@ class CharacterEditorState extends MusicBeatState implements PsychUIEventHandler } override function destroy() { + cameraScrollTarget.put(); super.destroy(); } } diff --git a/source/game/states/editors/ChartEditorState.hx b/source/game/states/editors/ChartEditorState.hx index e9527ee..4f0b983 100644 --- a/source/game/states/editors/ChartEditorState.hx +++ b/source/game/states/editors/ChartEditorState.hx @@ -62,19 +62,30 @@ import sys.FileSystem; import sys.io.File; #end + +enum abstract WaveformTarget(String) +{ + var INST = 'inst'; + var PLAYER = 'voc'; + var OPPONENT = 'opp'; +} + #if (flixel < "5.3.0") @:access(flixel.system.FlxSound._sound) #else @:access(flixel.sound.FlxSound._sound) #end @:access(openfl.media.Sound.__buffer) - class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.PsychUIEvent { public static final GRID_SIZE:Int = 40; + public static final GRID_COLUMNS_PER_PLAYER:Int = 4; + public static final GRID_PLAYERS:Int = 2; public final CAM_OFFSET:Int = 180; + private var tempBpm:Float = 0; + var autoBackupTimer:FlxTimer; var backupInterval:Float = 30; // half minute per seconds @@ -137,7 +148,10 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy ['Set Property', "Value 1: Variable name\nValue 2: New value"] ]; - private var _file:FileReference; + var noteTextMap:Map = new Map(); + var cachedSectionTimes:Array = []; + + var _file:FileReference; var postfix:String = ''; var mainBox:PsychUIBox; @@ -153,9 +167,10 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy * Usually rounded up?? */ public static var curSec:Int = 0; - public static var lastSection:Int = 0; private static var lastSong:String = ''; + var waveformTarget:WaveformTarget = INST; + var followPoint:FlxPoint; var strumLine:FlxSprite; var quant:AttachedSprite; @@ -193,8 +208,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy **/ var curSelectedNote:Array = null; - var tempBpm:Float = 0; - var playbackSpeed:Float = 1; var vocals:FlxSound = null; @@ -231,6 +244,20 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy 0xFFF9393F // red (right note) ]; + var gridColors:Dynamic = { + background: 0xFF0C1020, + mainLines: 0xFF1C2038, + secondaryLines: 0xFF141830, + beatLines: 0xFF2C4880, + sectionLines: 0xFF3C5888 + }; + + var waveformColors:Map = [ + INST => 0xFF4A88C8, + PLAYER => 0xFF4AC84A, + OPPONENT => 0xFFC84A4A + ]; + //select box things var selectBox:FlxSprite; var selectBoxOutline:FlxSprite; @@ -240,7 +267,9 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var clipboardNotes:Array = []; - var waveformSprite:FlxSprite; + var waveform:FlxSprite; + var currentWaveformSound:FlxSound = null; + var gridLayer:FlxTypedGroup; public static var quantization:Int = 16; @@ -276,8 +305,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy override function create() { - //yep, this shit causes lags - MemoryUtil.forceGC(false); + Paths.clearStoredMemory(); if (PlayState.SONG != null) _song = PlayState.SONG; @@ -311,14 +339,24 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy ignoreWarnings = chartEditorSave.data.ignoreWarnings; var bg:FlxSprite = new FlxSprite().loadGraphic(Paths.image('menuDesat')); bg.scrollFactor.set(); - bg.color = 0xFF222222; + bg.color = gridColors.background; add(bg); gridLayer = new FlxTypedGroup(); add(gridLayer); - waveformSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, 0x00FFFFFF); - add(waveformSprite); + gridBG = new FlxSprite(); + prevGridBG = new FlxSprite(); + nextGridBG = new FlxSprite(); + + gridLayer.add(gridBG); + gridLayer.add(prevGridBG); + gridLayer.add(nextGridBG); + + waveform = new FlxSprite(gridBG.x + GRID_SIZE, gridBG.y).makeGraphic(1, 1, 0xFF4A3C88); + waveform.scrollFactor.x = 0; + waveform.visible = false; + add(waveform); var eventIcon:FlxSprite = new FlxSprite(GRID_SIZE + 375).loadGraphic(Paths.image('eventArrow')); leftIcon = new HealthIcon('bf'); @@ -363,6 +401,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy updateJsonData(); currentSongName = Paths.formatToSongPath(_song.song); loadSong(); + _cacheSections(); reloadGridLayer(); Conductor.changeBPM(_song.bpm); Conductor.mapBPMChanges(_song); @@ -380,8 +419,8 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy add(quant); strumLineNotes = new FlxTypedGroup(); - for (i in 0...8){ - var note:StrumNote = new StrumNote(gridBG.x + GRID_SIZE * (i+1), strumLine.y, i % 4, 0); + for (i in 0...(GRID_COLUMNS_PER_PLAYER * GRID_PLAYERS)){ + var note:StrumNote = new StrumNote(gridBG.x + GRID_SIZE * (i+1), strumLine.y, i % GRID_COLUMNS_PER_PLAYER, 0); note.setGraphicSize(GRID_SIZE, GRID_SIZE); note.updateHitbox(); note.playAnim('static', true); @@ -433,6 +472,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy addNoteUI(); addEventsUI(); addChartingUI(); + updateHeads(); updateWaveform(); @@ -449,13 +489,13 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy if(lastSong != currentSongName) changeSection(); lastSong = currentSongName; + loadJSONEvents(); + zoomTxt = new FlxText(10, 10, 0, "Zoom: 1 / 1", 16); zoomTxt.y -= 500; zoomTxt.scrollFactor.set(); add(zoomTxt); - updateGrid(); - createSongSlider(); opponent.visible = false; @@ -484,17 +524,32 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy tmr.reset(backupInterval); }, 0); + Paths.clearUnusedMemory(); + super.create(); } + function loadJSONEvents() + { + var songName:String = Paths.formatToSongPath(_song.song); + var file:String = Paths.json(songName + '/events'); + try + { + var events:SwagSong = Song.loadFromJson('events', songName); + _song.events = events.events; + changeSection(curSec); + } catch (e) {} + } + var sliderBg:FlxSprite; var pressF1Text:FlxText; var positionSlider:PsychUISlider; var timeText:FlxText; - inline function createSongSlider() { + function createSongSlider() { sliderBg = new FlxSprite(0, FlxG.height - 60).makeGraphic(FlxG.width, 60, FlxColor.BLACK); sliderBg.alpha = 0.85; sliderBg.scrollFactor.set(); + sliderBg.updateHitbox(); sliderBg.cameras = [camUI]; add(sliderBg); @@ -529,6 +584,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy updateGrid(); } }, 0, 0, 1, FlxG.width - 20, FlxColor.fromRGB(194, 62, 201), FlxColor.WHITE); + positionSlider.scrollFactor.set(); positionSlider.rightColor = FlxColor.fromRGB(57, 23, 59); positionSlider.minText.visible = false; @@ -548,12 +604,13 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var currentTime = formatTime(FlxG.sound.music.time); var totalTime = formatTime(FlxG.sound.music.length); - timeText.text = currentTime + " / " + totalTime; + timeText.text = '$currentTime / $totalTime'; } else { timeText.text = "00:00:00 / 00:00:00"; } } + var stepperBPM:PsychUINumericStepper; var check_mute_inst:PsychUICheckBox = null; var check_mute_vocals:PsychUICheckBox = null; var check_mute_vocals_opponent:PsychUICheckBox = null; @@ -567,7 +624,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var holdCoverInputText:PsychUIInputText; var stageDropDown:PsychUIDropDownMenu; var playbackSlider:PsychUISlider; - inline function addSongUI():Void + function addSongUI():Void { var tab_group_song = mainBox.getTab('Song').menu; @@ -611,24 +668,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy backupManager.createManualBackup(_song) ); - var loadEventJson:PsychUIButton = new PsychUIButton(loadBackupButton.x, loadBackupButton.y + 30, 'Load Events', function() - { - - var songName:String = Paths.formatToSongPath(_song.song); - var file:String = Paths.json(songName + '/events'); - #if sys - if (#if MODS_ALLOWED FileSystem.exists(Paths.modsJson(songName + '/events')) || #end FileSystem.exists(SUtil.getPath() + file)) - #else - if (OpenFlAssets.exists(file)) - #end - { - clearEvents(); - var events:SwagSong = Song.loadFromJson('events', songName); - _song.events = events.events; - changeSection(curSec); - } - }); - var saveEvents:PsychUIButton = new PsychUIButton(saveButton.x, reloadSongJson.y, 'Save Events', function () { saveEvents(); @@ -666,27 +705,31 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy clear_events.normalStyle.bgColor = FlxColor.RED; clear_events.normalStyle.textColor = FlxColor.WHITE; - var stepperBPM:PsychUINumericStepper = new PsychUINumericStepper(10, 70, 1, 1, 1, 400, 3); + stepperBPM = new PsychUINumericStepper(10, 70, 1, 1, 1, 400, 3); stepperBPM.value = Conductor.bpm; stepperBPM.name = 'song_bpm'; stepperBPM.onValueChange = () -> { - _song.bpm = stepperBPM.value; + tempBpm = stepperBPM.value; + Conductor.mapBPMChanges(_song); + Conductor.changeBPM(stepperBPM.value); + updateWaveform(); updateGrid(); } var stepperSpeed:PsychUINumericStepper = new PsychUINumericStepper(10, stepperBPM.y + 35, 0.1, 1, 0.1, 10, 1); stepperSpeed.value = _song.speed; stepperSpeed.name = 'song_speed'; - var directories:Array = [#if MODS_ALLOWED Paths.mods('characters/'), Paths.mods(Paths.currentModDirectory + '/characters/'), #end SUtil.getPath() + Paths.getPreloadPath('characters/')]; + stepperSpeed.onValueChange = () -> _song.speed = stepperSpeed.value; + var directories:Array = [#if MODS_ALLOWED Mods.getModPath('characters/'), Mods.getModPath(Mods.currentModDirectory + '/characters/'), #end Paths.getPreloadPath('characters/')]; #if MODS_ALLOWED - for(mod in Paths.getGlobalMods()) - directories.push(Paths.mods(mod + '/characters/')); + for(mod in Mods.getGlobalMods()) + directories.push(Mods.getModPath(mod + '/characters/')); #else var directories:Array = [Paths.getPreloadPath('characters/')]; #end var tempMap:Map = new Map(); - var characters:Array = CoolUtil.coolTextFile(SUtil.getPath() + Paths.txt('characterList')); + var characters:Array = CoolUtil.coolTextFile( Paths.txt('characterList')); for (i in 0...characters.length) { tempMap.set(characters[i], true); } @@ -736,15 +779,15 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy player2DropDown.selectedLabel = _song.player2; #if MODS_ALLOWED - var directories:Array = [Paths.mods('stages/'), Paths.mods(Paths.currentModDirectory + '/stages/'), SUtil.getPath() + Paths.getPreloadPath('stages/')]; - for(mod in Paths.getGlobalMods()) - directories.push(Paths.mods(mod + '/stages/')); + var directories:Array = [Mods.getModPath('stages/'), Mods.getModPath(Mods.currentModDirectory + '/stages/'), Paths.getPreloadPath('stages/')]; + for(mod in Mods.getGlobalMods()) + directories.push(Mods.getModPath(mod + '/stages/')); #else var directories:Array = [Paths.getPreloadPath('stages/')]; #end tempMap.clear(); - var stageFile:Array = CoolUtil.coolTextFile(SUtil.getPath() + Paths.txt('stageList')); + var stageFile:Array = CoolUtil.coolTextFile( Paths.txt('stageList')); var stages:Array = []; for (i in 0...stageFile.length) { //Prevent duplicates var stageToCheck:String = stageFile[i]; @@ -813,7 +856,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy tab_group_song.add(reloadSongJson); tab_group_song.add(createBackupButton); tab_group_song.add(loadBackupButton); - tab_group_song.add(loadEventJson); tab_group_song.add(stepperBPM); tab_group_song.add(stepperSpeed); tab_group_song.add(reloadNotesButton); @@ -833,8 +875,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy tab_group_song.add(gfVersionDropDown); tab_group_song.add(player1DropDown); tab_group_song.add(stageDropDown); - - //FlxG.camera.follow(camPos, LOCKON, 999); } var stepperBeats:PsychUINumericStepper; @@ -847,39 +887,58 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var sectionToCopy:Int = 0; var notesCopied:Array; - inline function addSectionUI():Void + function addSectionUI():Void { var tab_group_section = mainBox.getTab('Section').menu; check_mustHitSection = new PsychUICheckBox(10, 15, "Must Hit Section", 100); check_mustHitSection.name = 'check_mustHit'; check_mustHitSection.checked = _song.notes[curSec].mustHitSection; + check_mustHitSection.onClick = () -> { + _song.notes[curSec].mustHitSection = check_mustHitSection.checked; + updateGrid(); + updateHeads(); + } check_gfSection = new PsychUICheckBox(10, check_mustHitSection.y + 22, "GF Section", 100); check_gfSection.name = 'check_gf'; check_gfSection.checked = _song.notes[curSec].gfSection; + check_gfSection.onClick = () -> { + _song.notes[curSec].gfSection = check_gfSection.checked; + updateGrid(); + updateHeads(); + } // _song.needsVoices = check_mustHit.checked; check_altAnim = new PsychUICheckBox(check_gfSection.x + 120, check_gfSection.y, "Alt Animation", 100); check_altAnim.checked = _song.notes[curSec].altAnim; + check_altAnim.name = 'check_altAnim'; + check_altAnim.onClick = () -> _song.notes[curSec].altAnim = check_altAnim.checked; stepperBeats = new PsychUINumericStepper(10, 100, 1, 4, 1, 6, 2); stepperBeats.value = getSectionBeats(); stepperBeats.name = 'section_beats'; - check_altAnim.name = 'check_altAnim'; + stepperBeats.onValueChange = () -> { + _song.notes[curSec].sectionBeats = stepperBeats.value; + reloadGridLayer(); + } check_changeBPM = new PsychUICheckBox(10, stepperBeats.y + 30, 'Change BPM', 100); check_changeBPM.checked = _song.notes[curSec].changeBPM; check_changeBPM.name = 'check_changeBPM'; check_changeBPM.onClick = () -> { + _song.notes[curSec].changeBPM = check_changeBPM.checked; + reloadGridLayer(); updateGrid(); updateNoteUI(); + FlxG.log.add('changed bpm'); } - stepperSectionBPM = new PsychUINumericStepper(10, check_changeBPM.y + 20, 1, Conductor.bpm, 0, 999, 1); - stepperSectionBPM.value = check_changeBPM.checked ? _song.notes[curSec].bpm : Conductor.bpm; + stepperSectionBPM = new PsychUINumericStepper(10, check_changeBPM.y + 20, 1, stepperBPM.value ?? Conductor.bpm, 0, 999, 1); + stepperSectionBPM.value = check_changeBPM.checked ? _song.notes[curSec].bpm : stepperBPM.value ?? Conductor.bpm; stepperSectionBPM.name = 'section_bpm'; stepperSectionBPM.onValueChange = () -> { + _song.notes[curSec].bpm = stepperSectionBPM.value; updateGrid(); updateNoteUI(); } @@ -918,7 +977,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { if(notesCopied == null || notesCopied.length < 1) return; - var addToTime:Float = Conductor.stepCrochet * (getSectionBeats() * 4 * (curSec - sectionToCopy)); + var addToTime:Float = Conductor.stepCrochet * (getSectionBeats() * GRID_COLUMNS_PER_PLAYER * (curSec - sectionToCopy)); //trace('Time to add: ' + addToTime); for (note in notesCopied) @@ -991,7 +1050,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy for (i in 0..._song.notes[curSec].sectionNotes.length) { var note:Array = _song.notes[curSec].sectionNotes[i]; - note[1] = (note[1] + 4) % 8; + note[1] = (note[1] + GRID_COLUMNS_PER_PLAYER) % GRID_COLUMNS_PER_PLAYER * 2; _song.notes[curSec].sectionNotes[i] = note; } updateGrid(); @@ -1007,7 +1066,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy for (note in _song.notes[daSec - value].sectionNotes) { - var strum = note[0] + Conductor.stepCrochet * (getSectionBeats(daSec) * 4 * value); + var strum = note[0] + Conductor.stepCrochet * (getSectionBeats(daSec) * GRID_COLUMNS_PER_PLAYER * value); var copiedNote:Array = [strum, note[1], note[2], note[3]]; @@ -1021,7 +1080,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var strumTime:Float = event[0]; if(endThing > event[0] && event[0] >= startThing) { - strumTime += Conductor.stepCrochet * (getSectionBeats(daSec) * 4 * value); + strumTime += Conductor.stepCrochet * (getSectionBeats(daSec) * GRID_COLUMNS_PER_PLAYER * value); var copiedEventArray:Array = []; for (i in 0...event[1].length) { @@ -1043,7 +1102,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy for (note in _song.notes[curSec].sectionNotes) { var boob = note[1]; - boob -= (boob > 3) ? 4 : -4; + boob -= (boob > (GRID_COLUMNS_PER_PLAYER - 1)) ? GRID_COLUMNS_PER_PLAYER : -GRID_COLUMNS_PER_PLAYER; var copiedNote:Array = [note[0], boob, note[2], note[3]]; duetNotes.push(copiedNote); @@ -1061,20 +1120,15 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var duetNotes:Array> = []; for (note in _song.notes[curSec].sectionNotes) { - var boob = note[1]%4; - boob = 3 - boob; - if (note[1] > 3) boob += 4; + var boob = note[1] % GRID_COLUMNS_PER_PLAYER; + boob = (GRID_COLUMNS_PER_PLAYER - 1) - boob; + if (note[1] > (GRID_COLUMNS_PER_PLAYER - 3)) boob += GRID_COLUMNS_PER_PLAYER; note[1] = boob; var copiedNote:Array = [note[0], boob, note[2], note[3]]; //duetNotes.push(copiedNote); } - for (i in duetNotes){ - //_song.notes[curSec].sectionNotes.push(i); - - } - updateGrid(); }); @@ -1102,13 +1156,32 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var noteTypeDropDown:PsychUIDropDownMenu; var currentType:Int = 0; - inline function addNoteUI():Void + function addNoteUI():Void { var tab_group_note = mainBox.getTab('Note').menu; - stepperSusLength = new PsychUINumericStepper(10, 25, Conductor.stepCrochet / 2, 0, 0, Conductor.stepCrochet * 64); + stepperSusLength = new PsychUINumericStepper(10, 25, Conductor.stepCrochet / 2, 0, 0, Conductor.stepCrochet * 16); stepperSusLength.value = 0; stepperSusLength.name = 'note_susLength'; + stepperSusLength.onValueChange = () -> { + if (selectedNotes.length > 0) + { + for (note in selectedNotes) + { + if (note.noteData > -1) // Only normal notes have sustain length + { + note.rawData[2] = stepperSusLength.value; + note.sustainLength = stepperSusLength.value; // Update the visual note property too + } + } + updateGrid(); + } + else if (curSelectedNote != null && curSelectedNote[2] != null) + { + curSelectedNote[2] = stepperSusLength.value; + updateGrid(); + } + } strumTimeInputText = new PsychUIInputText(10, 65, 180, "0"); tab_group_note.add(strumTimeInputText); @@ -1127,7 +1200,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy directories.push(Paths.getPreloadPath('custom_notetypes/')); #if MODS_ALLOWED - directories.push(Paths.modFolders('custom_notetypes/')); + directories.push(Mods.modFolders('custom_notetypes/')); #end #if sys @@ -1201,7 +1274,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var eventDropDown:PsychUIDropDownMenu; var descText:FlxText; var selectedEventText:FlxText; - inline function addEventsUI():Void + function addEventsUI():Void { var tab_group_event = mainBox.getTab('Events').menu; @@ -1211,7 +1284,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy directories.push(Paths.getPreloadPath('custom_events/')); #if MODS_ALLOWED - directories.push(Paths.modFolders('custom_events/')); + directories.push(Mods.modFolders('custom_events/')); #end #if sys @@ -1393,13 +1466,20 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var instVolume:PsychUINumericStepper; var voicesVolume:PsychUINumericStepper; var voicesOppVolume:PsychUINumericStepper; - inline function addChartingUI() { + function addChartingUI() { var tab_group_chart = mainBox.getTab('Charting').menu; chartEditorSave.data.chart_waveformInst ??= false; chartEditorSave.data.chart_waveformVoices ??= false; chartEditorSave.data.chart_waveformOppVoices ??= false; + if (chartEditorSave.data.chart_waveformInst) + waveformTarget = INST; + else if (chartEditorSave.data.chart_waveformVoices) + waveformTarget = PLAYER; + else if (chartEditorSave.data.chart_waveformOppVoices) + waveformTarget = OPPONENT; + waveformUseInstrumental = new PsychUICheckBox(10, 90, "Waveform (Instrumental)", 100); waveformUseInstrumental.checked = chartEditorSave.data.chart_waveformInst; waveformUseInstrumental.onClick = function() @@ -1409,6 +1489,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy chartEditorSave.data.chart_waveformVoices = false; chartEditorSave.data.chart_waveformOppVoices = false; chartEditorSave.data.chart_waveformInst = waveformUseInstrumental.checked; + waveformTarget = INST; updateWaveform(); }; @@ -1421,6 +1502,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy chartEditorSave.data.chart_waveformInst = false; chartEditorSave.data.chart_waveformOppVoices = false; chartEditorSave.data.chart_waveformVoices = waveformUseVoices.checked; + waveformTarget = PLAYER; updateWaveform(); }; @@ -1433,6 +1515,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy chartEditorSave.data.chart_waveformInst = false; chartEditorSave.data.chart_waveformVoices = false; chartEditorSave.data.chart_waveformOppVoices = waveformUseOppVoices.checked; + waveformTarget = OPPONENT; updateWaveform(); }; @@ -1516,14 +1599,17 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy instVolume = new PsychUINumericStepper(50, 270, 0.1, 1, 0, 1, 1); instVolume.value = FlxG.sound.music.volume; instVolume.name = 'inst_volume'; + instVolume.onValueChange = () -> FlxG.sound.music.volume = instVolume.value; voicesVolume = new PsychUINumericStepper(instVolume.x + 100, instVolume.y, 0.1, 1, 0, 1, 1); voicesVolume.value = vocals.volume; voicesVolume.name = 'voices_volume'; + voicesVolume.onValueChange = () -> vocals.volume = voicesVolume.value; voicesOppVolume = new PsychUINumericStepper(instVolume.x + 200, instVolume.y, 0.1, 1, 0, 1, 1); voicesOppVolume.value = vocals.volume; voicesOppVolume.name = 'voices_opp_volume'; + voicesOppVolume.onValueChange = () -> opponentVocals.volume = voicesOppVolume.value; #if FLX_PITCH playbackSlider = new PsychUISlider(145, 120, (v:Float) -> playbackSpeed = v, 1, 0.1, 5.0, 200); @@ -1556,9 +1642,9 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy tab_group_chart.add(playSoundDad); } - inline function loadSong():Void + function loadSong():Void { - FlxG.sound?.music?.stop(); + FlxG.sound?.music?.stop(); vocals?.stop(); vocals?.destroy(); @@ -1566,13 +1652,24 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy opponentVocals?.stop(); opponentVocals?.destroy(); + currentWaveformSound = null; + vocals = new FlxSound(); opponentVocals = new FlxSound(); + try { var playerVocals = Paths.voices(currentSongName, (characterData.vocalsP1 == null || characterData.vocalsP1.length < 1) ? 'Player' : characterData.vocalsP1); - vocals.loadEmbedded(playerVocals ?? Paths.voices(currentSongName)); + if (playerVocals != null) + vocals.loadEmbedded(playerVocals); + else + vocals.loadEmbedded(Paths.voices(currentSongName)); + } + catch (e:Dynamic) + { + trace("Could not load player vocals: " + e); } + vocals.autoDestroy = false; FlxG.sound.list.add(vocals); @@ -1582,10 +1679,16 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var oppVocals = Paths.voices(currentSongName, (characterData.vocalsP2 == null || characterData.vocalsP2.length < 1) ? 'Opponent' : characterData.vocalsP2); if(oppVocals != null) opponentVocals.loadEmbedded(oppVocals); } + catch (e:Dynamic) + { + trace("Could not load opponent vocals: " + e); + } + opponentVocals.autoDestroy = false; FlxG.sound.list.add(opponentVocals); generateSong(); + _cacheSections(); FlxG.sound.music.pause(); Conductor.songPosition = sectionStartTime(); FlxG.sound.music.time = Conductor.songPosition; @@ -1605,11 +1708,11 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var tmp:Dynamic = Reflect.field(_song, 'player' + Std.string(i)); var data:CharacterFile = loadHealthIconFromCharacter(cast tmp); Reflect.setField(characterData, 'iconP' + Std.string(i), !characterFailed ? data.healthicon : 'face'); - Reflect.setField(characterData, 'vocalsP' + Std.string(i), data.vocals_file != null ? data.vocals_file : ''); + Reflect.setField(characterData, 'vocalsP' + Std.string(i), data.vocals_file ?? ''); } } - inline function generateSong() { + function generateSong() { FlxG.sound.playMusic(Paths.inst(currentSongName), 0.6/*, false*/); if (instVolume != null) FlxG.sound.music.volume = instVolume.value; if (check_mute_inst != null && check_mute_inst.checked) FlxG.sound.music.volume = 0; @@ -1636,18 +1739,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy }; } - inline function generateUI():Void - { - while (bullshitUI.members.length > 0) - { - bullshitUI.remove(bullshitUI.members[0], true); - } - - // general freak - var title:FlxText = new FlxText(mainBox.x + 20, mainBox.y + 20, 0); - bullshitUI.add(title); - } - public function UIEvent(id:String, sender:Dynamic) { switch (id) @@ -1655,163 +1746,30 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy case PsychUIBox.DROP_EVENT: chartEditorSave.data.mainBoxPosition = [mainBox.x, mainBox.y]; chartEditorSave.data.infoBoxPosition = [infoBox.x, infoBox.y]; - - case PsychUICheckBox.CLICK_EVENT: - handleCheckBoxEvent(cast sender); - - case PsychUINumericStepper.CHANGE_EVENT if (Std.isOfType(sender, PsychUINumericStepper)): - handleNumericStepperEvent(cast sender); - - case PsychUIInputText.CHANGE_EVENT if (Std.isOfType(sender, PsychUIInputText)): - handleInputTextEvent(cast sender); - } - } - - private function handleCheckBoxEvent(check:PsychUICheckBox):Void - { - var label = check.label; - switch (label) - { - case 'Must Hit Section': - _song.notes[curSec].mustHitSection = check.checked; - updateGrid(); - updateHeads(); - - case 'GF Section': - _song.notes[curSec].gfSection = check.checked; - updateGrid(); - updateHeads(); - - case 'Change BPM': - _song.notes[curSec].changeBPM = check.checked; - reloadGridLayer(); - updateGrid(); - FlxG.log.add('changed bpm'); - - case "Alt Animation": - _song.notes[curSec].altAnim = check.checked; - } - } - - private function handleNumericStepperEvent(nums:PsychUINumericStepper):Void - { - if (nums == stepperSusLength) - { - handleNoteLengthChange(nums); - return; - } - - var wname = nums.name; - FlxG.log.add(wname); - - switch (wname) - { - case 'section_beats': - _song.notes[curSec].sectionBeats = nums.value; - reloadGridLayer(); - - case 'song_speed': - _song.speed = nums.value; - - case 'song_bpm': - tempBpm = nums.value; - Conductor.mapBPMChanges(_song); - Conductor.changeBPM(nums.value); - - case 'note_susLength': - if (curSelectedNote != null && curSelectedNote[2] != null) - { - curSelectedNote[2] = nums.value; - updateGrid(); - } - - case 'section_bpm': - _song.notes[curSec].bpm = nums.value; - updateGrid(); - - case 'inst_volume': - FlxG.sound.music.volume = nums.value; - - case 'voices_volume': - vocals.volume = nums.value; - - case 'voices_opp_volume': - opponentVocals.volume = nums.value; - if (check_mute_vocals_opponent.checked) opponentVocals.volume = 0; - } - } - - private function handleNoteLengthChange(nums:PsychUINumericStepper):Void - { - if (selectedNotes.length > 0) - { - for (note in selectedNotes) - { - if (note.noteData > -1) // Only normal notes have sustain length - { - note.rawData[2] = nums.value; - note.sustainLength = nums.value; // Update the visual note property too - } - } - updateGrid(); - } - else if (curSelectedNote != null && curSelectedNote[2] != null) - { - curSelectedNote[2] = nums.value; - updateGrid(); - } - } - - private function handleInputTextEvent(input:PsychUIInputText):Void - { - if (curSelectedNote == null) return; - - switch(input) - { - case value1InputText if(curSelectedNote[1][curEventSelected] != null): - curSelectedNote[1][curEventSelected][1] = input.text; - updateGrid(); - - case value2InputText if(curSelectedNote[1][curEventSelected] != null): - curSelectedNote[1][curEventSelected][2] = input.text; - updateGrid(); - - case strumTimeInputText: - var value:Float = Std.parseFloat(input.text); - if (Math.isNaN(value)) value = 0; - curSelectedNote[0] = value; - updateGrid(); } } - var updatedSection:Bool = false; - function sectionStartTime(add:Int = 0):Float { - var daBPM:Float = _song.bpm; - var daPos:Float = 0; - for (i in 0...curSec + add) - { - if(_song.notes[i] != null) - { - if (_song.notes[i].changeBPM) - { - daBPM = _song.notes[i].bpm; - } - daPos += getSectionBeats(i) * (1000 * 60 / daBPM); - } - } - return daPos; + if (cachedSectionTimes == null || cachedSectionTimes.length == 0) _cacheSections(); + + var index = curSec + add; + if (index < 0) index = 0; + if (index >= cachedSectionTimes.length) index = cachedSectionTimes.length - 1; + + if (cachedSectionTimes.length > index && index >= 0) + return cachedSectionTimes[index]; + return 0; } function isMouseOverUI():Bool { return FlxG.mouse.overlaps(mainBox) || FlxG.mouse.overlaps(infoBox) || - FlxG.mouse.overlaps(sliderBg) || FlxG.mouse.overlaps(positionSlider) || FlxG.mouse.overlaps(sliderBg); } + var iconJustFlashed:Bool = false; var lastConductorPos:Float; var colorSine:Float = 0; /** @@ -1822,10 +1780,25 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { super.update(elapsed); + // i figured out that some people have invisible mouse cursors for some reason + FlxG.mouse.visible = true; + curStep = recalculateSteps(); final conductorTime = Conductor.songPosition; final blockInput = PsychUIInputText.focusOn != null; final mouseOverUI = isMouseOverUI(); + + leftIcon.updateIconScale(elapsed); + rightIcon.updateIconScale(elapsed); + + var beatProgress = (Conductor.songPosition % Conductor.crochet) / Conductor.crochet; + if (beatProgress < 0.1 && !iconJustFlashed) { + leftIcon.flash(0.47, 0.35); + rightIcon.flash(0.47, 0.35); + iconJustFlashed = true; + } else if (beatProgress > 0.1) { + iconJustFlashed = false; + } updateMusicPlayback(); @@ -1839,6 +1812,8 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy ClientPrefs.toggleVolumeKeys(false); } + _song.bpm = tempBpm; + strumLineNotes.visible = quant.visible = vortex; updateNoteSelectionAndColors(elapsed); @@ -1861,8 +1836,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy if(curTime < 0) { FlxG.sound.music.pause(); curTime = 0; - } - else if(curTime > FlxG.sound.music.length) { + } else if(curTime > FlxG.sound.music.length) { FlxG.sound.music.pause(); curTime = 0; changeSection(); @@ -1870,7 +1844,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy Conductor.songPosition = curTime; if(!disableAutoScrolling.checked) { - if (Math.ceil(strumLine.y) >= gridBG.height) + if (Math.ceil(strumLine.y) > gridBG.height) { if (_song.notes[curSec + 1] == null) { @@ -1878,7 +1852,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } changeSection(curSec + 1, false); - } else if(strumLine.y < -10) { + } else if(strumLine.y < 0) { changeSection(curSec - 1, false); } } @@ -1892,14 +1866,30 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy strumLineUpdateY(); - for (i in 0...8) { + for (i in 0...GRID_COLUMNS_PER_PLAYER * GRID_PLAYERS) { strumLineNotes.members[i].y = strumLine.y; strumLineNotes.members[i].alpha = FlxG.sound.music.playing ? 1 : 0.35; } - if (quant != null && quant.exists) quant?.update(elapsed); - if (daNoteType != null && daNoteType.exists) daNoteType.update(elapsed); - if (daEventText != null && daEventText.exists) daEventText.update(elapsed); + if (quant?.exists) quant.update(elapsed); + + for (note => text in noteTextMap) { + if (note?.exists && text?.exists) { + if (note.noteData > -1) { + // default nutes + text.x = note.x - 32; + text.y = note.y + 6; + } else { + // event nutes + text.x = note.x - 410; + text.y = note.y; + if (note.eventLength > 1) text.y += 8; + } + + text.angle = note.angle; + text.alpha = note.alpha; + } + } } function handleKeyboardInput():Void @@ -1925,9 +1915,12 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } // Note sustains change - if(curSelectedNote != null && curSelectedNote[1] > -1) { - if (FlxG.keys.justPressed.E) changeNoteSustain(Conductor.stepCrochet); - if (FlxG.keys.justPressed.Q) changeNoteSustain(-Conductor.stepCrochet); + if(curSelectedNote != null) { + // needs for hl + if (Std.isOfType(curSelectedNote[1], Int) && curSelectedNote[1] > -1) { + if (FlxG.keys.justPressed.E) changeNoteSustain(Conductor.stepCrochet); + if (FlxG.keys.justPressed.Q) changeNoteSustain(-Conductor.stepCrochet); + } } // Hot keys @@ -1937,7 +1930,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy // Toggle zoom if(FlxG.keys.justPressed.Z #if mobile || _virtualpad.buttonZ.justPressed #end && curZoom > 0 && !FlxG.keys.pressed.CONTROL) { - curZoom--; + --curZoom; updateZoom(); } if(FlxG.keys.justPressed.X #if mobile || _virtualpad.buttonC.justPressed #end && curZoom < zoomList.length-1) { @@ -1959,7 +1952,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var shiftThing:Int = (FlxG.keys.pressed.SHIFT #if mobile || _virtualpad.buttonY.pressed #end) ? 4 : 1; if (FlxG.keys.justPressed.D #if mobile || _virtualpad.buttonRight.justPressed #end) changeSection(curSec + shiftThing); if (FlxG.keys.justPressed.A #if mobile || _virtualpad.buttonLeft.justPressed #end) - changeSection((curSec <= 0) ? _song.notes.length-1 : curSec - shiftThing); + changeSection((curSec <= 0) ? 0 : curSec - shiftThing); // Cursor control thingie if (FlxG.keys.pressed.W || FlxG.keys.pressed.S #if mobile || _virtualpad.buttonUp.pressed || _virtualpad.buttonDown.pressed #end) @@ -1983,7 +1976,8 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy quant.animation.play('q', true, false, curQuant); // Quntization - if(!vortex){ + if(!vortex) + { if (FlxG.keys.justPressed.UP || FlxG.keys.justPressed.DOWN) { handleQuantizedSeeking(); } @@ -2008,7 +2002,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy note.eventVal2 = curSelectedNote[1][0][2]; } } else { - note = new Note(curSelectedNote[0], noteData % 4); + note = new Note(curSelectedNote[0], noteData % GRID_COLUMNS_PER_PLAYER); note.sustainLength = curSelectedNote[2]; note.noteType = curSelectedNote[3]; } @@ -2036,8 +2030,8 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy if (FlxG.mouse.justReleased) handleMouseRelease(); - if (FlxG.mouse.justPressedRight) - handleRightClick(); + /*if (FlxG.mouse.justPressedRight) + handleRightClick();*/ } updateSelectionBox(); @@ -2162,7 +2156,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } } - function handleRightClick():Void + /*function handleRightClick():Void { var clickedNote:Note = null; curRenderedNotes.forEachAlive((note:Note) -> @@ -2179,13 +2173,17 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy pasteNote )); } - } + }*/ function handleMouseWheel():Void { FlxG.sound.music.pause(); if (!mouseQuant) - FlxG.sound.music.time -= (FlxG.mouse.wheel * Conductor.stepCrochet*0.8); + { + var newTime = FlxG.sound.music.time - (FlxG.mouse.wheel * Conductor.stepCrochet * 0.8); + if (newTime <= 0) newTime = 0; + FlxG.sound.music.time = newTime; + } else { var time:Float = FlxG.sound.music.time; @@ -2195,6 +2193,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy if (FlxG.mouse.wheel > 0) { var fuck:Float = MathUtil.quantize(beat, snap) - increase; + if (fuck <= 0) fuck = 0; FlxG.sound.music.time = Conductor.beatToSeconds(fuck); } else { var fuck:Float = MathUtil.quantize(beat, snap) + increase; @@ -2237,7 +2236,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { var noteDataToCheck:Int = note.noteData; if (noteDataToCheck > -1 && note.mustPress != _song.notes[curSec].mustHitSection) { - noteDataToCheck += 4; + noteDataToCheck += GRID_COLUMNS_PER_PLAYER; } if (selectedNotes.contains(note)) { @@ -2255,10 +2254,10 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { note.alpha = 0.4; if (note.strumTime > lastConductorPos && FlxG.sound.music.playing && note.noteData > -1) { - var data:Int = note.noteData % 4; + var data:Int = note.noteData % GRID_COLUMNS_PER_PLAYER; var noteDataToCheck:Int = note.noteData; if (noteDataToCheck > -1 && note.mustPress != _song.notes[curSec].mustHitSection) { - noteDataToCheck += 4; + noteDataToCheck += GRID_COLUMNS_PER_PLAYER; } strumLineNotes.members[noteDataToCheck].playAnim('confirm', true); @@ -2325,13 +2324,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } } - function updateWaveformIfNeeded():Void - { - if(waveformPrinted && (chartEditorSave.data.chart_waveformInst || chartEditorSave.data.chart_waveformVoices || chartEditorSave.data.chart_waveformOppVoices)) { - updateWaveform(); - } - } - /** * Playback update function */ @@ -2601,92 +2593,93 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } } - var lastSecBeats:Float = 0; - var lastSecBeatsNext:Float = 0; function reloadGridLayer() { - //gridLayer.clear(); - gridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 9, Std.int(GRID_SIZE * getSectionBeats() * 4 * zoomList[curZoom])); - gridBG?.screenCenter(X); //? due to hl - - #if hl - if (gridBG != null) - #end - waveformSprite.x = gridBG.x + GRID_SIZE / 2; + _cacheSections(); - if(chartEditorSave.data.chart_waveformInst || chartEditorSave.data.chart_waveformVoices || chartEditorSave.data.chart_waveformOppVoices) - updateWaveform(); + gridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 9, Std.int(GRID_SIZE * getSectionBeats() * GRID_COLUMNS_PER_PLAYER * zoomList[curZoom]), true, gridColors.mainLines, gridColors.secondaryLines); + gridBG.screenCenter(X); + waveform.x = gridBG.x + GRID_SIZE; + + updateWaveformIfNeeded(); updateGrid(); var foundPrevSec:Bool = false; var foundNextSec:Bool = false; - var leHeight:Int = Std.int(gridBG.height) * -1; if(curSec > 0 && sectionStartTime(-1) >= 0) { - prevGridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 9, Std.int(GRID_SIZE * getSectionBeats(curSec - 1) * 4 * zoomList[curZoom])); + prevGridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * (GRID_COLUMNS_PER_PLAYER * GRID_PLAYERS + 1), Std.int(GRID_SIZE * getSectionBeats(curSec - 1) * GRID_COLUMNS_PER_PLAYER * zoomList[curZoom]), true, gridColors.background, gridColors.mainLines); prevGridBG.screenCenter(X); - leHeight = Std.int(gridBG.y - prevGridBG.height); foundPrevSec = true; } - else prevGridBG = new FlxSprite().makeGraphic(1, 1, FlxColor.TRANSPARENT); + else + { + prevGridBG.makeGraphic(1, 1, FlxColor.TRANSPARENT); + } prevGridBG.y = gridBG.y - prevGridBG.height; - var leHeight2:Int = Std.int(gridBG.height); - nextGridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 9, Std.int(GRID_SIZE * getSectionBeats(curSec + 1) * 4 * zoomList[curZoom])); - nextGridBG.screenCenter(X); - if(sectionStartTime(1) <= FlxG.sound.music.length) + if(sectionStartTime(1) < FlxG.sound.music.length) { - leHeight2 = Std.int(gridBG.height + nextGridBG.height); + nextGridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * (GRID_COLUMNS_PER_PLAYER * GRID_PLAYERS + 1), Std.int(GRID_SIZE * getSectionBeats(curSec + 1) * GRID_COLUMNS_PER_PLAYER * zoomList[curZoom]), true, gridColors.background, gridColors.mainLines); + nextGridBG.screenCenter(X); foundNextSec = true; - nextGridBG.visible = true; } else { - nextGridBG.visible = false; - leHeight2 = Std.int(gridBG.height); + nextGridBG.makeGraphic(1, 1, FlxColor.TRANSPARENT); } nextGridBG.y = gridBG.height; - gridLayer.add(prevGridBG); - gridLayer.add(nextGridBG); - gridLayer.add(gridBG); + for (sprite in gridLayer) { + if (sprite != gridBG && sprite != prevGridBG && sprite != nextGridBG) { + sprite?.destroy(); + } + } + + gridLayer?.add(prevGridBG); + gridLayer?.add(nextGridBG); + gridLayer?.add(gridBG); if(foundPrevSec) { - var gridBlackPrev:FlxSprite = new FlxSprite(prevGridBG.x, prevGridBG.y).makeGraphic(Std.int(GRID_SIZE * 9), Std.int(prevGridBG.height), FlxColor.BLACK); + var gridBlackPrev:FlxSprite = new FlxSprite(prevGridBG.x, prevGridBG.y).makeGraphic(Std.int(GRID_SIZE * (GRID_COLUMNS_PER_PLAYER * GRID_PLAYERS + 1)), Std.int(prevGridBG.height), FlxColor.BLACK); gridBlackPrev.alpha = 0.4; - gridLayer.add(gridBlackPrev); + gridLayer?.add(gridBlackPrev); } if(foundNextSec) { - var gridBlackNext:FlxSprite = new FlxSprite(nextGridBG.x, gridBG.height).makeGraphic(Std.int(GRID_SIZE * 9), Std.int(nextGridBG.height), FlxColor.BLACK); + var gridBlackNext:FlxSprite = new FlxSprite(nextGridBG.x, gridBG.height).makeGraphic(Std.int(GRID_SIZE * (GRID_COLUMNS_PER_PLAYER * GRID_PLAYERS + 1)), Std.int(nextGridBG.height), FlxColor.BLACK); gridBlackNext.alpha = 0.4; - gridLayer.add(gridBlackNext); + gridLayer?.add(gridBlackNext); } var topY = prevGridBG.y; var totalHeight = (foundNextSec ? nextGridBG.y + nextGridBG.height : gridBG.y + gridBG.height) - topY; - var gridBlackLineLeft = new FlxSprite(gridBG.x + GRID_SIZE).makeGraphic(2, Std.int(totalHeight), FlxColor.BLACK); - gridBlackLineLeft.y = topY; - gridLayer.add(gridBlackLineLeft); + var gridLineLeft = new FlxSprite(gridBG.x + GRID_SIZE).makeGraphic(2, Std.int(totalHeight), gridColors.sectionLines); + gridLineLeft.y = topY + 1; + gridLayer.add(gridLineLeft); - var gridBlackLineRight = new FlxSprite(gridBG.x + gridBG.width - (GRID_SIZE * 4)).makeGraphic(2, Std.int(totalHeight), FlxColor.BLACK); - gridBlackLineRight.y = topY; - gridLayer.add(gridBlackLineRight); + var gridLineRight = new FlxSprite(gridBG.x + gridBG.width - (GRID_SIZE * GRID_COLUMNS_PER_PLAYER)).makeGraphic(2, Std.int(totalHeight), gridColors.sectionLines); + gridLineRight.y = topY + 1; + gridLayer.add(gridLineRight); - for (i in 1...Std.int(getSectionBeats())) { - var beatsep:FlxSprite = new FlxSprite(gridBG.x, (GRID_SIZE * (4 * zoomList[curZoom])) * i).makeGraphic(1, 1, 0x44FF0000); - beatsep.scale.x = gridBG.width; - beatsep.updateHitbox(); - if(vortex) gridLayer.add(beatsep); + function addBeatSeparators(grid:FlxSprite, beats:Float) { + for (i in 1...Std.int(beats)) { + var beatsep:FlxSprite = new FlxSprite(grid.x, grid.y + (GRID_SIZE * (GRID_COLUMNS_PER_PLAYER * zoomList[curZoom])) * i).makeGraphic(1, 1, gridColors.beatLines); + beatsep.scale.x = grid.width; + beatsep.updateHitbox(); + if(vortex) gridLayer.add(beatsep); + } } - lastSecBeats = getSectionBeats(); - if(sectionStartTime(1) > FlxG.sound.music.length) lastSecBeatsNext = 0; - else getSectionBeats(curSec + 1); + if(vortex) { + addBeatSeparators(gridBG, getSectionBeats()); + if(foundPrevSec) addBeatSeparators(prevGridBG, getSectionBeats(curSec - 1)); + if(foundNextSec) addBeatSeparators(nextGridBG, getSectionBeats(curSec + 1)); + } } function strumLineUpdateY() @@ -2694,288 +2687,62 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy strumLine.y = getYfromStrum((Conductor.songPosition - sectionStartTime()) / zoomList[curZoom] % (Conductor.stepCrochet * 16)) / (getSectionBeats() / 4); } - var waveformPrinted:Bool = true; - var wavData:Array>> = [[[0], [0]], [[0], [0]]]; - function updateWaveform() { - if(waveformPrinted) { - waveformSprite.makeGraphic(Std.int(gridBG.width), Std.int(gridBG.height), 0x00FFFFFF); - waveformSprite.pixels.fillRect(new Rectangle(0, 0, gridBG.width, gridBG.height), 0x00FFFFFF); + function changeNoteSustain(value:Float):Void + { + if (selectedNotes.length > 0) + { + for (note in selectedNotes) + { + if (note.noteData > -1 && note.rawData[2] != null) + { + note.rawData[2] += value; + note.rawData[2] = Math.max(note.rawData[2], 0); + } + } } - waveformPrinted = false; - - if(!chartEditorSave.data.chart_waveformInst && !chartEditorSave.data.chart_waveformVoices && !chartEditorSave.data.chart_waveformOppVoices) { - //trace('Epic fail on the waveform lol'); - return; + else if (curSelectedNote != null && curSelectedNote[2] != null) + { + curSelectedNote[2] += value; + curSelectedNote[2] = Math.max(curSelectedNote[2], 0); } - wavData[0][0] = []; - wavData[0][1] = []; - wavData[1][0] = []; - wavData[1][1] = []; - - var steps:Int = Math.round(getSectionBeats() * 4); - var st:Float = sectionStartTime(); - var et:Float = st + (Conductor.stepCrochet * steps); - - if (chartEditorSave.data.chart_waveformInst) { - var sound:FlxSound = FlxG.sound.music; - if (sound._sound != null && sound._sound.__buffer != null) { - var bytes:Bytes = sound._sound.__buffer.data.toBytes(); - - wavData = waveformData( - sound._sound.__buffer, - bytes, - st, - et, - 1, - wavData, - Std.int(gridBG.height) - ); - } - } - - if (chartEditorSave.data.chart_waveformVoices) { - var sound:FlxSound = vocals; - if (sound._sound != null && sound._sound.__buffer != null) { - var bytes:Bytes = sound._sound.__buffer.data.toBytes(); - - wavData = waveformData( - sound._sound.__buffer, - bytes, - st, - et, - 1, - wavData, - Std.int(gridBG.height) - ); - } - } - - if (chartEditorSave.data.chart_waveformOppVoices) { - var sound:FlxSound = opponentVocals; - if (sound._sound != null && sound._sound.__buffer != null) { - var bytes:Bytes = sound._sound.__buffer.data.toBytes(); - - wavData = waveformData( - sound._sound.__buffer, - bytes, - st, - et, - 1, - wavData, - Std.int(gridBG.height) - ); - } + updateGrid(); + updateNoteUI(); + } + + function recalculateSteps(add:Float = 0):Int + { + var lastChange:BPMChangeEvent = { + stepTime: 0, + songTime: 0, + bpm: 0 } - // Draws - var gSize:Int = Std.int(gridBG.width); - var hSize:Int = Std.int(gSize / 2); + for (i in 0...Conductor.bpmChangeMap.length) + { + if (FlxG.sound.music.time > Conductor.bpmChangeMap[i].songTime) + lastChange = Conductor.bpmChangeMap[i]; + } - var lmin:Float = 0; - var lmax:Float = 0; + curStep = lastChange.stepTime + Math.floor((FlxG.sound.music.time - lastChange.songTime + add) / Conductor.stepCrochet); + updateBeat(); - var rmin:Float = 0; - var rmax:Float = 0; + return curStep; + } - var size:Float = 1; + function resetSection(songBeginning:Bool = false):Void + { + updateGrid(); - var leftLength:Int = ( - wavData[0][0].length > wavData[0][1].length ? wavData[0][0].length : wavData[0][1].length - ); + FlxG.sound.music.pause(); + // Basically old freak from changeSection??? + FlxG.sound.music.time = sectionStartTime(); - var rightLength:Int = ( - wavData[1][0].length > wavData[1][1].length ? wavData[1][0].length : wavData[1][1].length - ); - - var length:Int = leftLength > rightLength ? leftLength : rightLength; - - var index:Int; - for (i in 0...length) { - index = i; - - lmin = FlxMath.bound(((index < wavData[0][0].length && index >= 0) ? wavData[0][0][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; - lmax = FlxMath.bound(((index < wavData[0][1].length && index >= 0) ? wavData[0][1][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; - - rmin = FlxMath.bound(((index < wavData[1][0].length && index >= 0) ? wavData[1][0][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; - rmax = FlxMath.bound(((index < wavData[1][1].length && index >= 0) ? wavData[1][1][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; - - waveformSprite.pixels.fillRect(new Rectangle(hSize - (lmin + rmin), i * size, (lmin + rmin) + (lmax + rmax), size), FlxColor.BLUE); - } - - waveformSprite.dirty = true; - waveformPrinted = true; - } - - function waveformData(buffer:AudioBuffer, bytes:Bytes, time:Float, endTime:Float, multiply:Float = 1, ?array:Array>>, ?steps:Float):Array>> - { - #if (lime_cffi && !macro) - if (buffer == null || buffer.data == null) return [[[0], [0]], [[0], [0]]]; - - var khz:Float = (buffer.sampleRate / 1000); - var channels:Int = buffer.channels; - - var index:Int = Std.int(time * khz); - - var samples:Float = ((endTime - time) * khz); - - steps ??= 1280; - - var samplesPerRow:Float = samples / steps; - var samplesPerRowI:Int = Std.int(samplesPerRow); - - var gotIndex:Int = 0; - - var lmin:Float = 0; - var lmax:Float = 0; - - var rmin:Float = 0; - var rmax:Float = 0; - - var rows:Float = 0; - - var simpleSample:Bool = true;//samples > 17200; - var v1:Bool = false; - - array ??= [[[0], [0]], [[0], [0]]]; - - while (index < (bytes.length - 1)) { - if (index >= 0) { - var byte:Int = bytes.getUInt16(index * channels * 2); - - if (byte > 65535 / 2) byte -= 65535; - - var sample:Float = (byte / 65535); - - if (sample > 0) { - if (sample > lmax) lmax = sample; - } else { - if (sample < lmin) lmin = sample; - } - - if (channels >= 2) { - byte = bytes.getUInt16((index * channels * 2) + 2); - - if (byte > 65535 / 2) byte -= 65535; - - sample = (byte / 65535); - - if (sample > 0) { - if (sample > rmax) rmax = sample; - } else { - if (sample < rmin) rmin = sample; - } - } - } - - v1 = samplesPerRowI > 0 ? (index % samplesPerRowI == 0) : false; - while (simpleSample ? v1 : rows >= samplesPerRow) { - v1 = false; - rows -= samplesPerRow; - - gotIndex++; - - var lRMin:Float = Math.abs(lmin) * multiply; - var lRMax:Float = lmax * multiply; - - var rRMin:Float = Math.abs(rmin) * multiply; - var rRMax:Float = rmax * multiply; - - if (gotIndex > array[0][0].length) array[0][0].push(lRMin); - else array[0][0][gotIndex - 1] = array[0][0][gotIndex - 1] + lRMin; - - if (gotIndex > array[0][1].length) array[0][1].push(lRMax); - else array[0][1][gotIndex - 1] = array[0][1][gotIndex - 1] + lRMax; - - if (channels >= 2) { - if (gotIndex > array[1][0].length) array[1][0].push(rRMin); - else array[1][0][gotIndex - 1] = array[1][0][gotIndex - 1] + rRMin; - - if (gotIndex > array[1][1].length) array[1][1].push(rRMax); - else array[1][1][gotIndex - 1] = array[1][1][gotIndex - 1] + rRMax; - - } else { - if (gotIndex > array[1][0].length) array[1][0].push(lRMin); - else array[1][0][gotIndex - 1] = array[1][0][gotIndex - 1] + lRMin; - - if (gotIndex > array[1][1].length) array[1][1].push(lRMax); - else array[1][1][gotIndex - 1] = array[1][1][gotIndex - 1] + lRMax; - } - - lmin = 0; - lmax = 0; - - rmin = 0; - rmax = 0; - } - - index++; - rows++; - if(gotIndex > steps) break; - } - - return array; - #else - return [[[0], [0]], [[0], [0]]]; - #end - } - - function changeNoteSustain(value:Float):Void - { - if (selectedNotes.length > 0) - { - for (note in selectedNotes) - { - if (note.noteData > -1 && note.rawData[2] != null) - { - note.rawData[2] += value; - note.rawData[2] = Math.max(note.rawData[2], 0); - } - } - } - else if (curSelectedNote != null && curSelectedNote[2] != null) - { - curSelectedNote[2] += value; - curSelectedNote[2] = Math.max(curSelectedNote[2], 0); - } - - updateGrid(); - updateNoteUI(); - } - - function recalculateSteps(add:Float = 0):Int - { - var lastChange:BPMChangeEvent = { - stepTime: 0, - songTime: 0, - bpm: 0 - } - - for (i in 0...Conductor.bpmChangeMap.length) - { - if (FlxG.sound.music.time > Conductor.bpmChangeMap[i].songTime) - lastChange = Conductor.bpmChangeMap[i]; - } - - curStep = lastChange.stepTime + Math.floor((FlxG.sound.music.time - lastChange.songTime + add) / Conductor.stepCrochet); - updateBeat(); - - return curStep; - } - - function resetSection(songBeginning:Bool = false):Void - { - updateGrid(); - - FlxG.sound.music.pause(); - // Basically old freak from changeSection??? - FlxG.sound.music.time = sectionStartTime(); - - if (songBeginning) - { - FlxG.sound.music.time = 0; - curSec = 0; - } + if (songBeginning) + { + FlxG.sound.music.time = 0; + curSec = 0; + } if(vocals != null) { vocals.pause(); @@ -2997,11 +2764,13 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy if (_song.notes[sec] != null) { curSec = sec; + _cacheSections(); + if (updateMusic) { FlxG.sound.music.pause(); - FlxG.sound.music.time = sectionStartTime(); + FlxG.sound.music.time = cachedSectionTimes[curSec]; if(vocals != null) { vocals.pause(); vocals.time = FlxG.sound.music.time; @@ -3013,30 +2782,17 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy updateCurStep(); } - var blah1:Float = getSectionBeats(); - var blah2:Float = getSectionBeats(curSec + 1); - if(sectionStartTime(1) > FlxG.sound.music.length) blah2 = 0; - - if(blah1 != lastSecBeats || blah2 != lastSecBeatsNext) - reloadGridLayer(); - else - updateGrid(); - + reloadGridLayer(); updateSectionUI(); + + updateWaveform(); } else { changeSection(); } - /*if (player.holdTimer >= Conductor.stepCrochet * 0.001 * player.singDuration) - player.dance(); - - if (opponent.holdTimer >= Conductor.stepCrochet * 0.001 * opponent.singDuration) - opponent.dance();*/ - Conductor.songPosition = FlxG.sound.music.time; - updateWaveform(); } function updateSectionUI():Void @@ -3082,7 +2838,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy characterFailed = false; var characterPath:String = 'characters/' + char + '.json'; #if MODS_ALLOWED - var path:String = Paths.modFolders(characterPath); + var path:String = Mods.modFolders(characterPath); if (!FileSystem.exists(path)) { path = Paths.getPreloadPath(characterPath); } @@ -3139,19 +2895,34 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } } - if (firstNote.noteData > -1) // nomal nutes + if (firstNote.noteData > -1) // normal notes { stepperSusLength.value = allSameSustain ? firstNote.rawData[2] : 0; currentType = allSameType ? noteTypeMap.get(firstNote.rawData[3]) : 0; noteTypeDropDown.selectedLabel = allSameType ? currentType + '. ' + firstNote.rawData[3] : '[Multiple]'; strumTimeInputText.text = ''; } - else // eventus + else // events { eventDropDown.selectedLabel = allSameEvent ? firstNote.eventName : '[Multiple]'; value1InputText.text = allSameEvent ? firstNote.eventVal1 : ''; value2InputText.text = allSameEvent ? firstNote.eventVal2 : ''; strumTimeInputText.text = ''; + + if (allSameEvent) { + var selectedEventIndex = -1; + for (i in 0...eventStuff.length) { + if (eventStuff[i][0] == firstNote.eventName) { + selectedEventIndex = i; + break; + } + } + if (selectedEventIndex != -1) { + descText.text = eventStuff[selectedEventIndex][1]; + } + } else { + descText.text = "Multiple Events Selected"; + } } } else if (curSelectedNote != null) @@ -3194,6 +2965,15 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy for (i in 0...group.length) { var obj = group.members[i]; if (obj != null) { + if (Std.isOfType(obj, FlxText)) { + var textObj:FlxText = cast obj; + for (note => text in noteTextMap) { + if (text == textObj) { + noteTextMap.remove(note); + break; + } + } + } obj.destroy(); group.remove(obj, true); } @@ -3201,18 +2981,24 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy group.clear(); } - var daNoteType:AttachedFlxText; - var daEventText:AttachedFlxText; function updateGrid():Void { - clearGroup(curRenderedNotes); - clearGroup(curRenderedSustains); - clearGroup(curRenderedNoteType); - clearGroup(nextRenderedNotes); - clearGroup(nextRenderedSustains); - clearGroup(prevRenderedNotes); - clearGroup(prevRenderedSustains); - + var clearingGrps:Array> = [ + curRenderedNotes, + curRenderedSustains, + curRenderedNoteType, + nextRenderedNotes, + nextRenderedSustains, + prevRenderedNotes, + prevRenderedSustains + ]; + + for (grp in clearingGrps) + clearGroup(grp); + + for (text in noteTextMap) text?.destroy(); + noteTextMap?.clear(); + if (_song.notes[curSec].changeBPM && _song.notes[curSec].bpm > 0) { Conductor.changeBPM(_song.notes[curSec].bpm); } else { @@ -3226,7 +3012,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } curRenderedNotes.forEachAlive((note:Note) -> note.color = selectedNotes.contains(note) ? FlxColor.BLUE : FlxColor.WHITE); - + // CURRENT SECTION var beats:Float = getSectionBeats(); for (i in _song.notes[curSec].sectionNotes) @@ -3237,50 +3023,46 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { curRenderedSustains.add(setupSusNote(note, beats)); } - + if(i[3] != null && note.noteType != null && note.noteType.length > 0) { var typeInt:Null = noteTypeMap.get(i[3]); - var theType:String = '' + typeInt ?? '?'; - - daNoteType = new AttachedFlxText(0, 0, 100, theType, 18); + var theType:String = typeInt != null ? '$typeInt' : '?'; + + var daNoteType = new FlxText(0, 0, 100, theType, 18); daNoteType.setFormat(Paths.font("pixel-latin.ttf"), 18, FlxColor.WHITE, CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); daNoteType.borderStyle = NONE; - daNoteType.xAdd = -32; - daNoteType.yAdd = 6; daNoteType.borderSize = 1; curRenderedNoteType.add(daNoteType); - daNoteType.sprTracker = note; + noteTextMap.set(note, daNoteType); } note.mustPress = _song.notes[curSec].mustHitSection; if(i[1] > 3) note.mustPress = !note.mustPress; } - + // CURRENT EVENTS var startThing:Float = sectionStartTime(); var endThing:Float = sectionStartTime(1); for (i in _song.events) { - if(endThing > i[0] && i[0] >= startThing) + if(i[0] >= startThing && i[0] < endThing) { var note:Note = setupNoteData(i, false); curRenderedNotes.add(note); - + var text:String = 'Event: ${note.eventName} (${Math.floor(note.strumTime)} ms)\nValue 1: ${note.eventVal1}\nValue 2: ${note.eventVal2}'; if(note.eventLength > 1) text = '${note.eventLength} Events:\n${note.eventName}'; - - daEventText = new AttachedFlxText(0, 0, 400, text, 8); + + var daEventText = new FlxText(0, 0, 400, text, 8); daEventText.setFormat(Paths.font("pixel-latin.ttf"), 8, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE_FAST, FlxColor.BLACK); daEventText.borderStyle = NONE; - daEventText.xAdd = -410; daEventText.borderSize = 1; - if(note.eventLength > 1) daEventText.yAdd += 8; curRenderedNoteType.add(daEventText); - daEventText.sprTracker = note; + noteTextMap.set(note, daEventText); } } - + // NEXT SECTION - var beats:Float = getSectionBeats(1); + var nextBeats:Float = getSectionBeats(1); if(curSec < _song.notes.length-1) { for (i in _song.notes[curSec+1].sectionNotes) { @@ -3289,13 +3071,13 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy nextRenderedNotes.add(note); if (note.sustainLength > 0) { - nextRenderedSustains.add(setupSusNote(note, beats)); + nextRenderedSustains.add(setupSusNote(note, nextBeats)); } } } // PREV SECTION - var beats:Float = getSectionBeats(-1); + var prevBeats:Float = getSectionBeats(-1); if(curSec > 0) { for (i in _song.notes[curSec-1].sectionNotes) { @@ -3304,17 +3086,17 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy prevRenderedNotes.add(note); if (note.sustainLength > 0) { - prevRenderedSustains.add(setupSusNote(note, beats)); + prevRenderedSustains.add(setupSusNote(note, prevBeats)); } } } - + // NEXT EVENTS - var startThing:Float = sectionStartTime(1); - var endThing:Float = sectionStartTime(2); + var nextStartThing:Float = sectionStartTime(1); + var nextEndThing:Float = sectionStartTime(2); for (i in _song.events) { - if(endThing > i[0] && i[0] >= startThing) + if(i[0] >= nextStartThing && i[0] < nextEndThing) { var note:Note = setupNoteData(i, true); note.alpha = 0.6; @@ -3323,69 +3105,85 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy } // PREV EVENTS - var beats:Float = getSectionBeats(-1); - if(curSec > 0) { - for (i in _song.events) + var prevStartThing:Float = sectionStartTime(-1); + var prevEndThing:Float = sectionStartTime(); + for (i in _song.events) + { + if(i[0] >= prevStartThing && i[0] < prevEndThing) { var note:Note = setupNoteData(i, false, true); note.alpha = 0.6; prevRenderedNotes.add(note); - if (note.sustainLength > 0) - { - prevRenderedSustains.add(setupSusNote(note, beats)); - } } } } function setupNoteData(i:Array, isNextSection:Bool, isPrevSection:Bool = false):Note { - var daNoteInfo = i[1]; var daStrumTime = i[0]; var daSus:Dynamic = i[2]; - var note:Note = new Note(daStrumTime, daNoteInfo % 4, null, null, true); - if(note.noteData < 0) daSus == null; - if(daSus != null) { //Common note - if(!Std.isOfType(i[3], String)) //Convert old note type to new note type format - { - i[3] = noteTypeIntMap.get(i[3]); - } - if(i.length > 3 && (i[3] == null || i[3].length < 1)) - { - i.remove(i[3]); - } - note.sustainLength = daSus; - note.noteType = i[3]; - } else { //Event note + var note:Note; + + // Another fix for hl target + if (Std.isOfType(i[1], Array)) + { + // Event note + var eventData:Array = i[1]; + note = new Note(daStrumTime, -1, null, null, true); note.loadGraphic(Paths.image('eventArrow')); - note.eventName = getEventName(i[1]); - note.eventLength = i[1].length; - if(i[1].length < 2) + note.eventName = getEventName(eventData); + note.eventLength = eventData.length; + if(eventData.length < 2) { - note.eventVal1 = i[1][0][1]; - note.eventVal2 = i[1][0][2]; + note.eventVal1 = eventData[0][1]; + note.eventVal2 = eventData[0][2]; } - note.eventName = getEventName(i[1]); note.noteData = -1; - daNoteInfo = -1; + + note.setGraphicSize(GRID_SIZE, GRID_SIZE); + note.updateHitbox(); + + // for event notes, position them in a special column + note.x = gridBG.x; // pos in first column } - - note.setGraphicSize(GRID_SIZE, GRID_SIZE); - note.updateHitbox(); - note.x = Math.floor(daNoteInfo * GRID_SIZE) + gridBG.x + GRID_SIZE; - if(isNextSection && _song.notes[curSec].mustHitSection != _song.notes[curSec+1].mustHitSection) { - if(daNoteInfo > 3) { - note.x -= GRID_SIZE * 4; - } else if(daSus != null) { - note.x += GRID_SIZE * 4; + else + { + // Regular note + var noteDataInt:Int = i[1]; + note = new Note(daStrumTime, noteDataInt % GRID_COLUMNS_PER_PLAYER, null, null, true); + if(daSus != null) { //Common note + if(!Std.isOfType(i[3], String)) //Convert old note type to new note type format + { + i[3] = noteTypeIntMap.get(i[3]); + } + if(i.length > 3 && (i[3] == null || i[3].length < 1)) + { + i.remove(i[3]); + } + note.sustainLength = daSus; + note.noteType = i[3]; } - } - if(isPrevSection && _song.notes[curSec].mustHitSection != _song.notes[curSec-1].mustHitSection) { - if(daNoteInfo > 3) { - note.x -= GRID_SIZE * 4; - } else if(daSus != null) { - note.x += GRID_SIZE * 4; + + note.setGraphicSize(GRID_SIZE, GRID_SIZE); + note.updateHitbox(); + + // for regular notes, use the actual note data for positioning + note.x = Math.floor(noteDataInt * GRID_SIZE) + gridBG.x + GRID_SIZE; + + if (isNextSection && _song.notes[curSec].mustHitSection != _song.notes[curSec+1].mustHitSection) { + if(noteDataInt >= GRID_COLUMNS_PER_PLAYER) { + note.x -= GRID_SIZE * GRID_COLUMNS_PER_PLAYER; + } else if(daSus != null) { + note.x += GRID_SIZE * GRID_COLUMNS_PER_PLAYER; + } + } + if(isPrevSection && _song.notes[curSec].mustHitSection != _song.notes[curSec-1].mustHitSection) { + if(noteDataInt >= GRID_COLUMNS_PER_PLAYER) { + note.x -= GRID_SIZE * GRID_COLUMNS_PER_PLAYER; + } else if(daSus != null) { + note.x += GRID_SIZE * GRID_COLUMNS_PER_PLAYER; + } } } @@ -3395,8 +3193,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy var beats:Float = getSectionBeats(curSec + num); note.y = getYfromStrumNotes(daStrumTime - sectionStartTime(), beats); note.rawData = i; - //if(isNextSection) note.y += gridBG.height; - //if(note.y < -150) note.y = -150; return note; } @@ -3476,12 +3272,10 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { var height:Int = Math.floor(FlxMath.remapToRange(note.sustainLength, 0, Conductor.stepCrochet * 16, 0, GRID_SIZE * 16 * zoomList[curZoom]) + (GRID_SIZE * zoomList[curZoom]) - GRID_SIZE / 2); var minHeight:Int = Std.int((GRID_SIZE * zoomList[curZoom] / 2) + GRID_SIZE / 2); - if(height < minHeight) height = minHeight; - if(height < 1) height = 1; + if(height <= minHeight) height = minHeight; + if(height <= 1) height = 1; - // Player/Opponent note sustains - var color:FlxColor; - color = sustainColors[note.noteData]; + var color:FlxColor = sustainColors[note.noteData]; var spr:FlxSprite = new FlxSprite(note.x + (GRID_SIZE * 0.5) - 4, note.y + GRID_SIZE / 2).makeGraphic(8, height, color); return spr; @@ -3501,6 +3295,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy }; _song.notes.push(sec); + _cacheSections(); } function selectNote(note:Note):Void @@ -3526,7 +3321,7 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy if(noteDataToCheck > -1) // Normal note { if(note.mustPress != _song.notes[curSec].mustHitSection) { - noteDataToCheck += 4; + noteDataToCheck += GRID_COLUMNS_PER_PLAYER; } for (i in _song.notes[curSec].sectionNotes) @@ -3624,11 +3419,11 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { curRenderedNotes.forEachAlive(function(note:Note) { - if (note.overlapsPoint(new FlxPoint(strumLineNotes.members[d].x + 1,strumLine.y+1)) && note.noteData == d%4) + if (note.overlapsPoint(new FlxPoint(strumLineNotes.members[d].x + 1,strumLine.y+1)) && note.noteData == d % GRID_COLUMNS_PER_PLAYER) { - //trace('tryin to delete note...'); - if(!delnote) deleteNote(note); - delnote = true; + //trace('tryin to delete note...'); + if(!delnote) deleteNote(note); + delnote = true; } }); } @@ -3637,6 +3432,8 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy addNote(cs, d, style); } } + + function clearSong():Void { for (daSection in 0..._song.notes.length) @@ -3702,21 +3499,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy updateNoteUI(); } - public static function checkForJSON(jsonInput:String, ?folder:String):String - { - var formattedFolder:String = Paths.formatToSongPath(folder); - var formattedSong:String = Paths.formatToSongPath(jsonInput); - - #if MODS_ALLOWED - var moddyFile:String = Paths.modsJson(formattedFolder + '/' + formattedSong); - if(FileSystem.exists(moddyFile)) { - return moddyFile; - } - #end - - return Paths.json(formattedFolder + '/' + formattedSong); - } - function saveToUndo() { if (undos.length >= maxUndoSteps) { var removed = undos.shift(); @@ -3750,7 +3532,11 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy { var leZoom:Float = zoomList[curZoom]; if(!doZoomCalc) leZoom = 1; - return FlxMath.remapToRange(yPos, gridBG.y, gridBG.y + gridBG.height * leZoom, 0, 16 * Conductor.stepCrochet); + + var beats:Float = getSectionBeats(); + var totalTime = beats * Conductor.stepCrochet * GRID_COLUMNS_PER_PLAYER; + + return FlxMath.remapToRange(yPos, gridBG.y, gridBG.y + gridBG.height * leZoom, 0, totalTime); } function getYfromStrum(strumTime:Float, doZoomCalc:Bool = true):Float @@ -3762,8 +3548,9 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy function getYfromStrumNotes(strumTime:Float, beats:Float):Float { - var value:Float = strumTime / (beats * 4 * Conductor.stepCrochet); - return GRID_SIZE * beats * 4 * zoomList[curZoom] * value + gridBG.y; + var totalTime = beats * Conductor.stepCrochet * GRID_COLUMNS_PER_PLAYER; + var value:Float = strumTime / totalTime; + return GRID_SIZE * beats * GRID_COLUMNS_PER_PLAYER * zoomList[curZoom] * value + gridBG.y; } function copyNote(note:Note):Void @@ -3897,15 +3684,15 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy note.strumTime, note.noteData, note.sustainLength, - note.noteType != null ? note.noteType : "" + note.noteType ?? "" ]; } else { return [ note.strumTime, [[ note.eventName, - note.eventVal1 != null ? note.eventVal1 : "", - note.eventVal2 != null ? note.eventVal2 : "" + note.eventVal1 ?? "", + note.eventVal2 ?? "" ]] ]; } @@ -3996,7 +3783,6 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy return FlxSort.byValues(FlxSort.ASCENDING, Obj1[0], Obj2[0]); } - function formatTime(ms:Float):String { var mm:Int = Std.int(ms / 60000); var ss:Int = Std.int((ms % 60000) / 1000); @@ -4065,44 +3851,253 @@ class ChartEditorState extends MusicBeatState implements PsychUIEventHandler.Psy FlxG.log.error("Problem saving Level data"); } - function getSectionBeats(?section:Null = null) + function getSectionBeats(?section:Null = null):Float { + if (_song == null || _song.notes == null) return 4; + section ??= curSec; - var val:Null = null; - if(_song.notes[section] != null) val = _song.notes[section].sectionBeats; - return val ?? 4; + if (section < 0 || section >= _song.notes.length) return 4; + + var sec = _song.notes[section]; + if (sec == null) return 4; + + return sec.sectionBeats ?? 4; } - override function destroy() { - autoBackupTimer?.cancel(); - autoBackupTimer?.destroy(); - - super.destroy(); + private function _cacheSections() + { + cachedSectionTimes = []; + if (_song == null || _song.notes == null || _song.notes.length == 0) { + cachedSectionTimes[0] = 0; + return; + } + + var time:Float = 0; + var bpm:Float = _song.bpm; + + for (i in 0..._song.notes.length) + { + var section = _song.notes[i]; + if(section == null) { + cachedSectionTimes[i] = time; + continue; + } + + if(section.changeBPM && section.bpm > 0) + bpm = section.bpm; + + cachedSectionTimes[i] = time; + time += (getSectionBeats(i) * (1000 * 60 / bpm)); + } + cachedSectionTimes[_song.notes.length] = time; } -} + var wavData:Array>> = [[[0], [0]], [[0], [0]]]; + function updateWaveform() { + #if (lime_cffi && !macro) + var shouldShowWaveform = chartEditorSave.data.chart_waveformInst || + chartEditorSave.data.chart_waveformVoices || + chartEditorSave.data.chart_waveformOppVoices; + + if(!shouldShowWaveform || curSec < 0 || curSec >= cachedSectionTimes.length) + { + waveform.visible = false; + return; + } -class AttachedFlxText extends FlxText -{ - public var sprTracker:FlxSprite; - public var xAdd:Float = 0; - public var yAdd:Float = 0; + waveform.visible = true; + waveform.y = gridBG.y; + var width:Int = Std.int(GRID_SIZE * GRID_COLUMNS_PER_PLAYER * GRID_PLAYERS); + var height:Int = Std.int(gridBG.height); + if(Std.int(waveform.height) != height && waveform?.pixels != null) + { + waveform.pixels.dispose(); + waveform.pixels.disposeImage(); + waveform.makeGraphic(width, height, FlxColor.TRANSPARENT); + } + waveform.pixels.fillRect(new Rectangle(0, 0, width, height), FlxColor.TRANSPARENT); - public function new(X:Float = 0, Y:Float = 0, FieldWidth:Float = 0, ?Text:String, Size:Int = 8, EmbeddedFont:Bool = true) { - super(X, Y, FieldWidth, Text, Size, EmbeddedFont); + wavData[0][0].resize(0); + wavData[0][1].resize(0); + wavData[1][0].resize(0); + wavData[1][1].resize(0); + + var sound:FlxSound = switch(waveformTarget) + { + case INST: + FlxG.sound.music; + case PLAYER: + vocals; + case OPPONENT: + opponentVocals; + case _: null; + } + + if (sound?._sound?.__buffer != null) + { + var bytes:Bytes = sound._sound.__buffer.data.toBytes(); + wavData = waveformData(sound._sound.__buffer, bytes, cachedSectionTimes[curSec] - Conductor.offset, cachedSectionTimes[curSec+1] - Conductor.offset, 1, wavData, height); + } + + var waveformColor:FlxColor = waveformColors.exists(waveformTarget) ? waveformColors.get(waveformTarget) : FlxColor.WHITE; + + // Draws + var gSize:Int = Std.int(GRID_SIZE * 8); + var hSize:Int = Std.int(gSize / 2); + var size:Float = 1; + + var leftLength:Int = (wavData[0][0].length > wavData[0][1].length ? wavData[0][0].length : wavData[0][1].length); + var rightLength:Int = (wavData[1][0].length > wavData[1][1].length ? wavData[1][0].length : wavData[1][1].length); + + var length:Int = leftLength > rightLength ? leftLength : rightLength; + + for (index in 0...length) + { + var lmin:Float = FlxMath.bound(((index < wavData[0][0].length && index >= 0) ? wavData[0][0][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; + var lmax:Float = FlxMath.bound(((index < wavData[0][1].length && index >= 0) ? wavData[0][1][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; + + var rmin:Float = FlxMath.bound(((index < wavData[1][0].length && index >= 0) ? wavData[1][0][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; + var rmax:Float = FlxMath.bound(((index < wavData[1][1].length && index >= 0) ? wavData[1][1][index] : 0) * (gSize / 1.12), -hSize, hSize) / 2; + + waveform.pixels.fillRect(new Rectangle(hSize - (lmin + rmin), index * size, (lmin + rmin) + (lmax + rmax), size), waveformColor); + } + #else + waveform.visible = false; + #end } - override public function update(elapsed:Float) + function waveformData(buffer:AudioBuffer, bytes:Bytes, time:Float, endTime:Float, multiply:Float = 1, ?array:Array>>, ?steps:Float):Array>> { - super.update(elapsed); + #if (lime_cffi && !macro) + if (buffer == null || buffer.data == null) return [[[0], [0]], [[0], [0]]]; + + var khz:Float = (buffer.sampleRate / 1000); + var channels:Int = buffer.channels; + + var index:Int = Std.int(time * khz); + + var samples:Float = ((endTime - time) * khz); + + if (steps == null) steps = 1280; + + var samplesPerRow:Float = samples / steps; + var samplesPerRowI:Int = Std.int(samplesPerRow); + + var gotIndex:Int = 0; + + var lmin:Float = 0; + var lmax:Float = 0; + + var rmin:Float = 0; + var rmax:Float = 0; + + var rows:Float = 0; + + var simpleSample:Bool = true; //samples > 17200; + var v1:Bool = false; + + array ??= [[[0], [0]], [[0], [0]]]; + + while (index < (bytes.length - 1)) { + if (index >= 0) { + var byte:Int = bytes.getUInt16(index * channels * 2); + + if (byte > 65535 / 2) byte -= 65535; + + var sample:Float = (byte / 65535); + + if (sample > 0) + if (sample > lmax) lmax = sample; + else + if (sample < lmin) lmin = sample; + + if (channels >= 2) { + byte = bytes.getUInt16((index * channels * 2) + 2); + + if (byte > 65535 / 2) byte -= 65535; + + sample = (byte / 65535); + + if (sample > 0) { + if (sample > rmax) rmax = sample; + } else { + if (sample < rmin) rmin = sample; + } + } + } + + v1 = samplesPerRowI > 0 ? (index % samplesPerRowI == 0) : false; + while (simpleSample ? v1 : rows >= samplesPerRow) { + v1 = false; + rows -= samplesPerRow; + + gotIndex++; + + var lRMin:Float = Math.abs(lmin) * multiply; + var lRMax:Float = lmax * multiply; + + var rRMin:Float = Math.abs(rmin) * multiply; + var rRMax:Float = rmax * multiply; + + if (gotIndex > array[0][0].length) array[0][0].push(lRMin); + else array[0][0][gotIndex - 1] = array[0][0][gotIndex - 1] + lRMin; + + if (gotIndex > array[0][1].length) array[0][1].push(lRMax); + else array[0][1][gotIndex - 1] = array[0][1][gotIndex - 1] + lRMax; + + if (channels >= 2) + { + if (gotIndex > array[1][0].length) array[1][0].push(rRMin); + else array[1][0][gotIndex - 1] = array[1][0][gotIndex - 1] + rRMin; + + if (gotIndex > array[1][1].length) array[1][1].push(rRMax); + else array[1][1][gotIndex - 1] = array[1][1][gotIndex - 1] + rRMax; + } + else + { + if (gotIndex > array[1][0].length) array[1][0].push(lRMin); + else array[1][0][gotIndex - 1] = array[1][0][gotIndex - 1] + lRMin; + + if (gotIndex > array[1][1].length) array[1][1].push(lRMax); + else array[1][1][gotIndex - 1] = array[1][1][gotIndex - 1] + lRMax; + } - if (sprTracker != null) { - setPosition(sprTracker.x + xAdd, sprTracker.y + yAdd); - angle = sprTracker.angle; - alpha = sprTracker.alpha; + lmin = 0; + lmax = 0; + + rmin = 0; + rmax = 0; + } + + index++; + rows++; + if(gotIndex > steps) break; } + + return array; + #else + return [[[0], [0]], [[0], [0]]]; + #end + } + + function updateWaveformIfNeeded():Void + { + var shouldShowWaveform = chartEditorSave.data.chart_waveformInst || + chartEditorSave.data.chart_waveformVoices || + chartEditorSave.data.chart_waveformOppVoices; + + if(shouldShowWaveform) updateWaveform(); + else waveform.visible = false; } + + override function destroy() { + autoBackupTimer?.cancel(); + autoBackupTimer?.destroy(); + + super.destroy(); + } + } class ChartingTipsSubstate extends MusicBeatSubstate @@ -4175,346 +4170,4 @@ class ChartingTipsSubstate extends MusicBeatSubstate close(); } } -} - -class ContextMenu extends MusicBeatSubstate -{ - var bg:FlxSprite; - var menuBg:FlxSprite; - var buttons:Array = []; - - public function new(x:Float, y:Float, note:Note, deleteCallback:Note->Void, copyCallback:Note->Void, pasteCallback:Void->Void) - { - super(); - - closeCallback = () -> close(); - - bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.TRANSPARENT); - bg.scrollFactor.set(); - bg.alpha = 0.0001; - bg.setPosition(0, 0); - add(bg); - - menuBg = new FlxSprite(x, y).makeGraphic(0, 0, FlxColor.BLACK); - menuBg.alpha = 0.8; - menuBg.scrollFactor.set(); - add(menuBg); - - var buttonY = y + 5; - createButton("Delete", x + 5, buttonY, function() { - deleteCallback(note); - closeMenu(); - }); - - buttonY += 30; - createButton("Copy", x + 5, buttonY, function() { - copyCallback(note); - closeMenu(); - }); - - buttonY += 30; - createButton("Paste", x + 5, buttonY, function() { - pasteCallback(); - closeMenu(); - }); - - //note properties removed for event notes due to critical error - if (note.noteData > -1) { - buttonY += 30; - createButton("Properties", x + 5, buttonY, () -> openNoteProperties(note)); - } - - var buttonCount = note.noteData > -1 ? 4 : 3; - menuBg.makeGraphic(90, buttonCount * 30, FlxColor.BLACK); - - cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; - } - - function createButton(label:String, x:Float, y:Float, onClick:Void->Void) - { - var button = new PsychUIButton(x, y, label, onClick); - button.scrollFactor.set(); - add(button); - buttons.push(button); - return button; - } - - function closeMenu():Void - { - if (closeCallback != null) closeCallback(); - } - - function openNoteProperties(note:Note):Void - { - var parent:ChartEditorState = cast FlxG.state.subState._parentState; - @:privateAccess { - openSubState(new NotePropertiesSubstate(note, function(updatedNote:Note) { - parent.saveToUndo(); - parent.updateNoteData(note, updatedNote); - parent.updateGrid(); - closeSubState(); - }, parent.eventStuff)); - } - } - - override function update(elapsed:Float) - { - super.update(elapsed); - - if (FlxG.mouse.justPressed) { - var mousePoint = FlxG.mouse.getViewPosition(camera); - - if (!menuBg.getScreenBounds(null, camera).containsPoint(mousePoint)) - closeMenu(); - } - - if (FlxG.keys.justPressed.ESCAPE) - closeMenu(); - } -} - -class NotePropertiesSubstate extends MusicBeatSubstate -{ - var note:Note; - var onSaveCallback:Note->Void; - var onCloseCallback:Void->Void; - var eventStuff:Array; - - var descText:FlxText; - var strumTimeStepper:PsychUINumericStepper; - var noteDataStepper:PsychUINumericStepper; - var sustainStepper:PsychUINumericStepper; - var typeInput:PsychUIInputText; - var value1Input:PsychUIInputText; - var value2Input:PsychUIInputText; - var eventDropdown:PsychUIDropDownMenu; - - public function new(note:Note, onSaveCallback:Note->Void, eventStuff:Array) - { - super(); - this.note = note; - this.onSaveCallback = onSaveCallback; - this.eventStuff = eventStuff; - - var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - bg.alpha = 0.6; - bg.scrollFactor.set(); - add(bg); - - var panel = new FlxSprite(FlxG.width / 2 - 150, FlxG.height / 2 - 150).makeGraphic(300, 300, FlxColor.GRAY); - panel.scrollFactor.set(); - add(panel); - - var title = new FlxText(panel.x, panel.y + 10, 300, "Note Properties", 16); - title.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, CENTER); - title.scrollFactor.set(); - add(title); - - var yOffset:Int = 50; - - if (note.noteData > -1) - { - var timeLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Strum Time:"); - timeLabel.scrollFactor.set(); - add(timeLabel); - - strumTimeStepper = new PsychUINumericStepper(panel.x + 120, panel.y + yOffset, 10, note.strumTime, 0, 999999, 0); - strumTimeStepper.scrollFactor.set(); - add(strumTimeStepper); - yOffset += 30; - - var dataLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Note Data:"); - dataLabel.scrollFactor.set(); - add(dataLabel); - - noteDataStepper = new PsychUINumericStepper(panel.x + 120, panel.y + yOffset, 1, note.noteData, 0, 7, 0); - noteDataStepper.scrollFactor.set(); - add(noteDataStepper); - yOffset += 30; - - var sustainLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Sustain:"); - sustainLabel.scrollFactor.set(); - add(sustainLabel); - - sustainStepper = new PsychUINumericStepper(panel.x + 120, panel.y + yOffset, 10, note.sustainLength, 0, 9999, 0); - sustainStepper.scrollFactor.set(); - add(sustainStepper); - yOffset += 30; - - var typeLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Note Type:"); - typeLabel.scrollFactor.set(); - add(typeLabel); - - typeInput = new PsychUIInputText(panel.x + 120, panel.y + yOffset, 150, note.noteType != null ? note.noteType : ""); - typeInput.scrollFactor.set(); - add(typeInput); - } - else - { - var eventLabel = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Event Type:"); - eventLabel.scrollFactor.set(); - add(eventLabel); - - var eventList = []; - for (i in 0...eventStuff.length) { - eventList.push({label: eventStuff[i][0], id: Std.string(i)}); - } - - var eventNames:Array = [for (event in eventStuff) event[0]]; - eventDropdown = new PsychUIDropDownMenu(panel.x + 120, panel.y + yOffset, - eventNames, - function(id:Int, value:String) { - if (id >= 0 && id < eventStuff.length) { - var eventName = eventStuff[id][0]; - var eventDesc = eventStuff[id][1]; - descText.text = eventDesc; - - if (value1Input != null && value1Input.text == "") { - var defaultValues = getDefaultEventValues(eventName); - value1Input.text = defaultValues[0]; - value2Input.text = defaultValues[1]; - } - } - } - ); - eventDropdown.selectedIndex = Std.parseInt(note.eventName); - eventDropdown.scrollFactor.set(); - - yOffset += 30; - - var val1Label = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Value 1:"); - val1Label.scrollFactor.set(); - add(val1Label); - - value1Input = new PsychUIInputText(panel.x + 120, panel.y + yOffset, 150, note.eventVal1 != null ? note.eventVal1 : ""); - value1Input.scrollFactor.set(); - add(value1Input); - - yOffset += 30; - - var val2Label = new FlxText(panel.x + 20, panel.y + yOffset, 100, "Value 2:"); - val2Label.scrollFactor.set(); - add(val2Label); - - value2Input = new PsychUIInputText(panel.x + 120, panel.y + yOffset, 150, note.eventVal2 != null ? note.eventVal2 : ""); - value2Input.scrollFactor.set(); - add(value2Input); - - var currentEventIndex = -1; - for (i in 0...eventStuff.length) - { - if (eventStuff[i][0] == note.eventName) - { - currentEventIndex = i; - break; - } - } - - descText = new FlxText(panel.x + 20, panel.y + yOffset + 30, 260, "", 12); - descText.wordWrap = true; - descText.setFormat(Paths.font("vcr.ttf"), 12, FlxColor.WHITE); - descText.scrollFactor.set(); - add(descText); - - if (currentEventIndex != -1) - { - eventDropdown.selectedIndex = currentEventIndex; - descText.text = eventStuff[currentEventIndex][1]; - } - else - { - eventDropdown.selectedLabel = note.eventName; - descText.text = "Custom Event"; - } - } - - yOffset += 40; - - if (note.noteData == -1) yOffset += 80; - - var saveButton = new PsychUIButton(panel.x + 50, panel.y + yOffset, "Save", () -> { - saveChanges(); - close(); - }); - add(saveButton); - - var cancelButton = new PsychUIButton(panel.x + 150, panel.y + yOffset, "Cancel", () -> close()); - add(cancelButton); - - add(eventDropdown); - - cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; - } - - function saveChanges() - { - var updatedNote = new Note(0, 0); - updatedNote.noteData = note.noteData; - - if (note.noteData > -1) //Nomal note - { - var newData = Std.int(noteDataStepper.value); - if (note.noteData > 3) newData += 4; - - updatedNote.strumTime = strumTimeStepper.value; - updatedNote.noteData = newData; - updatedNote.sustainLength = sustainStepper.value; - updatedNote.noteType = typeInput.text; - } - else //Event - { - updatedNote.strumTime = note.strumTime; - updatedNote.eventName = eventDropdown.selectedLabel; - updatedNote.eventVal1 = value1Input.text; - updatedNote.eventVal2 = value2Input.text; - - if (value1Input != null) updatedNote.eventVal1 = value1Input.text; - if (value2Input != null) updatedNote.eventVal2 = value2Input.text; - } - - onSaveCallback(updatedNote); - } - - function getDefaultEventValues(eventName:String):Array - { - switch(eventName) - { - case 'Dadbattle Spotlight': - return ['1', '0']; - case 'Hey!': - return ['BF', '0.6']; - case 'Set GF Speed': - return ['1', '']; - case 'Add Camera Zoom': - return ['0.015', '0.03']; - case 'Play Animation': - return ['idle', 'BF']; - case 'Camera Follow Pos': - return ['', '']; - case 'Alt Idle Animation': - return ['BF', '-alt']; - case 'Screen Shake': - return ['0, 0.05', '0, 0.05']; - case 'Change Character': - return ['BF', 'bf-car']; - case 'Change Scroll Speed': - return ['1', '1']; - case 'Lyrics': - return ['Hello! --FF0000', '2']; - case 'Set Property': - return ['health', '0.5']; - default: - return ['', '']; - } - } - - override function update(elapsed:Float) - { - super.update(elapsed); - - if (FlxG.keys.justPressed.ESCAPE) - { - close(); - } - } } \ No newline at end of file diff --git a/source/game/states/editors/EditorPlayState.hx b/source/game/states/editors/EditorPlayState.hx index e162ed4..284c6f7 100644 --- a/source/game/states/editors/EditorPlayState.hx +++ b/source/game/states/editors/EditorPlayState.hx @@ -78,6 +78,7 @@ class EditorPlayState extends MusicBeatState var bg:FlxSprite = new FlxSprite().loadGraphic(Paths.image('menuDesat')); bg.scrollFactor.set(); bg.color = FlxColor.fromHSB(FlxG.random.int(0, 359), FlxG.random.float(0, 0.8), FlxG.random.float(0.3, 1)); + bg.antialiasing = ClientPrefs.globalAntialiasing; add(bg); keysArray = [ @@ -122,7 +123,7 @@ class EditorPlayState extends MusicBeatState generateSong(PlayState.SONG.song); #if (LUA_ALLOWED && MODS_ALLOWED) for (notetype in noteTypeMap.keys()) { - var luaToLoad:String = Paths.modFolders('custom_notetypes/' + notetype + '.lua'); + var luaToLoad:String = Mods.modFolders('custom_notetypes/' + notetype + '.lua'); if(sys.FileSystem.exists(luaToLoad)) { var lua:game.states.backend.EditorLua = new game.states.backend.EditorLua(luaToLoad); new FlxTimer().start(0.1, function (tmr:FlxTimer) { diff --git a/source/game/states/editors/MasterEditorMenu.hx b/source/game/states/editors/MasterEditorMenu.hx index 0772444..0d52d66 100644 --- a/source/game/states/editors/MasterEditorMenu.hx +++ b/source/game/states/editors/MasterEditorMenu.hx @@ -72,12 +72,12 @@ class MasterEditorMenu extends MusicBeatState directoryTxt.scrollFactor.set(); add(directoryTxt); - for (folder in Paths.getModDirectories()) + for (folder in Mods.getModDirectories()) { directories.push(folder); } - var found:Int = directories.indexOf(Paths.currentModDirectory); + var found:Int = directories.indexOf(Mods.currentModDirectory); if(found > -1) curDirectory = found; changeDirectory(); #end @@ -132,7 +132,7 @@ class MasterEditorMenu extends MusicBeatState } FlxG.sound.music.volume = 0; #if PRELOAD_ALL - FreeplayState.destroyFreeplayVocals(); + //FreeplayState.destroyFreeplayVocals(); #end } @@ -183,8 +183,8 @@ class MasterEditorMenu extends MusicBeatState directoryTxt.text = '< No Mod Directory Loaded >'; else { - Paths.currentModDirectory = directories[curDirectory]; - directoryTxt.text = '< Loaded Mod Directory: ' + Paths.currentModDirectory + ' >'; + Mods.currentModDirectory = directories[curDirectory]; + directoryTxt.text = '< Loaded Mod Directory: ' + Mods.currentModDirectory + ' >'; } directoryTxt.text = directoryTxt.text.toUpperCase(); } diff --git a/source/game/states/editors/WeekEditorState.hx b/source/game/states/editors/WeekEditorState.hx index b139676..8cff1c0 100644 --- a/source/game/states/editors/WeekEditorState.hx +++ b/source/game/states/editors/WeekEditorState.hx @@ -315,7 +315,7 @@ class WeekEditorState extends MusicBeatState implements PsychUIEventHandler.Psyc var isMissing:Bool = true; if(assetName != null && assetName.length > 0) { - if( #if MODS_ALLOWED FileSystem.exists(Paths.modsImages('menubackgrounds/menu_' + assetName)) || #end + if( #if MODS_ALLOWED FileSystem.exists(Mods.modsImages('menubackgrounds/menu_' + assetName)) || #end Assets.exists(Paths.getPath('images/menubackgrounds/menu_' + assetName + '.png', IMAGE), IMAGE)) { bgSprite.loadGraphic(Paths.image('menubackgrounds/menu_' + assetName)); isMissing = false; @@ -334,7 +334,7 @@ class WeekEditorState extends MusicBeatState implements PsychUIEventHandler.Psyc var isMissing:Bool = true; if(assetName != null && assetName.length > 0) { - if( #if MODS_ALLOWED FileSystem.exists(Paths.modsImages('storymenu/' + assetName)) || #end + if( #if MODS_ALLOWED FileSystem.exists(Mods.modsImages('storymenu/' + assetName)) || #end Assets.exists(Paths.getPath('images/storymenu/' + assetName + '.png', IMAGE), IMAGE)) { weekThing.loadGraphic(Paths.image('storymenu/' + assetName)); isMissing = false; diff --git a/source/game/states/editors/meta/ChartBackupManager.hx b/source/game/states/editors/meta/ChartBackupManager.hx index 47ba13c..68fca17 100644 --- a/source/game/states/editors/meta/ChartBackupManager.hx +++ b/source/game/states/editors/meta/ChartBackupManager.hx @@ -30,7 +30,7 @@ class ChartBackupManager public static final VERSION:String = "1.0"; /** File extension used for backup files */ - public static final BACKUP_EXTENSION:String = "ccb"; + @:unreflective public static final BACKUP_EXTENSION:String = "ccb"; private var editor:ChartEditorState; @@ -147,7 +147,7 @@ class ChartBackupManager */ inline public function loadBackup():Void { #if desktop - var fileFilter = new FileFilter('Chart Backup Files', '*.$BACKUP_EXTENSION;*.json'); + var fileFilter = new FileFilter('Chart Backup Files', '*.$BACKUP_EXTENSION'); var fileRef = new FileReference(); fileRef.addEventListener(Event.SELECT, function onFileSelected(e:Event) { fileRef.removeEventListener(Event.SELECT, onFileSelected); diff --git a/source/game/states/editors/meta/FileDialogHandler.hx b/source/game/states/editors/meta/FileDialogHandler.hx new file mode 100644 index 0000000..9923dce --- /dev/null +++ b/source/game/states/editors/meta/FileDialogHandler.hx @@ -0,0 +1,245 @@ +package states.editors.content; + +import openfl.net.FileReference; +import openfl.events.Event; +import openfl.events.IOErrorEvent; +import flash.net.FileFilter; + +import haxe.Exception; +import sys.io.File; +import lime.ui.*; + +import flixel.FlxBasic; + +//Currently only supports OPEN and SAVE, might change that in the future, who knows +class FileDialogHandler extends FlxBasic +{ + var _fileRef:FileReferenceCustom; + var _dialogMode:FileDialogType = OPEN; + public function new() + { + _fileRef = new FileReferenceCustom(); + _fileRef.addEventListener(Event.CANCEL, onCancelFn); + _fileRef.addEventListener(IOErrorEvent.IO_ERROR, onErrorFn); + + super(); + } + + // callbacks + public var onComplete:Void->Void; + public var onCancel:Void->Void; + public var onError:Void->Void; + + var _currentEvent:openfl.events.Event->Void; + + public function save(?fileName:String = '', ?dataToSave:String = '', ?onComplete:Void->Void, ?onCancel:Void->Void, ?onError:Void->Void) + { + if(!completed) + { + throw new Exception('You must finish previous operation before starting a new one.'); + } + + this._dialogMode = SAVE; + _startUp(onComplete, onCancel, onError); + + removeEvents(); + _currentEvent = onSaveComplete; + _fileRef.addEventListener(#if desktop Event.SELECT #else Event.COMPLETE #end, _currentEvent); + _fileRef.save(dataToSave, fileName); + } + + public function open(?defaultName:String = null, ?title:String = null, ?filter:Array = null, ?onComplete:Void->Void, ?onCancel:Void->Void, ?onError:Void->Void) + { + if(!completed) + { + throw new Exception('You must finish previous operation before starting a new one.'); + } + + this._dialogMode = OPEN; + _startUp(onComplete, onCancel, onError); + if(filter == null) filter = [new FileFilter('JSON', 'json')]; + #if mac + filter = []; + #end + + removeEvents(); + _currentEvent = onLoadComplete; + _fileRef.addEventListener(#if desktop Event.SELECT #else Event.COMPLETE #end, _currentEvent); + _fileRef.browseEx(OPEN, defaultName, title, filter); + } + + public function openDirectory(?title:String = null, ?onComplete:Void->Void, ?onCancel:Void->Void, ?onError:Void->Void) + { + if(!completed) + { + throw new Exception('You must finish previous operation before starting a new one.'); + } + + this._dialogMode = OPEN_DIRECTORY; + _startUp(onComplete, onCancel, onError); + + removeEvents(); + _currentEvent = onLoadDirectoryComplete; + _fileRef.addEventListener(#if desktop Event.SELECT #else Event.COMPLETE #end, _currentEvent); + _fileRef.browseEx(OPEN_DIRECTORY, null, title); + } + + public var data:String; + public var path:String; + public var completed:Bool = true; + function onSaveComplete(_) + { + @:privateAccess + this.path = _fileRef._trackSavedPath; + this.completed = true; + trace('Saved file to: $path'); + + removeEvents(); + this.completed = true; + if(onComplete != null) onComplete(); + } + + function onLoadComplete(_) + { + @:privateAccess + this.path = _fileRef.__path; + this.data = File.getContent(this.path); + this.completed = true; + trace('Loaded file from: $path'); + + removeEvents(); + this.completed = true; + if(onComplete != null) + onComplete(); + } + + function onLoadDirectoryComplete(_) + { + @:privateAccess + this.path = _fileRef.__path; + this.completed = true; + trace('Loaded directory: $path'); + + removeEvents(); + this.completed = true; + if(onComplete != null) + onComplete(); + } + + function onCancelFn(_) + { + removeEvents(); + this.completed = true; + if(onCancel != null) onError(); + } + + function onErrorFn(_) + { + removeEvents(); + this.completed = true; + if(onError != null) onError(); + } + + function _startUp(onComplete:Void->Void, onCancel:Void->Void, onError:Void->Void) + { + this.onComplete = onComplete; + this.onCancel = onCancel; + this.onError = onError; + this.completed = false; + + this.data = null; + this.path = null; + } + + function removeEvents() + { + if(_currentEvent == null) return; + + _fileRef.removeEventListener(#if desktop Event.SELECT #else Event.COMPLETE #end, _currentEvent); + _currentEvent = null; + } + + override function destroy() + { + removeEvents(); + _fileRef = null; + _currentEvent = null; + onComplete = null; + onCancel = null; + onError = null; + data = null; + path = null; + completed = true; + super.destroy(); + } +} + +//Only way I could find to keep the path after saving a file +class FileReferenceCustom extends FileReference +{ + @:allow(backend.FileDialogHandler) + var _trackSavedPath:String; + override function saveFileDialog_onSelect(path:String):Void + { + _trackSavedPath = path; + super.saveFileDialog_onSelect(path); + } + + public function browseEx(browseType:FileDialogType = OPEN, ?defaultName:String, ?title:String = null, ?typeFilter:Array = null):Bool + { + __data = null; + __path = null; + + #if desktop + var filter = null; + + if (typeFilter != null) + { + var filters = []; + + for (type in typeFilter) + { + filters.push(StringTools.replace(StringTools.replace(type.extension, "*.", ""), ";", ",")); + } + + filter = filters.join(";"); + } + + var openFileDialog = new FileDialog(); + openFileDialog.onCancel.add(openFileDialog_onCancel); + openFileDialog.onSelect.add(openFileDialog_onSelect); + openFileDialog.browse(browseType, filter, defaultName, title); + return true; + #elseif (js && html5) + var filter = null; + if (typeFilter != null) + { + var filters = []; + for (type in typeFilter) + { + filters.push(StringTools.replace(StringTools.replace(type.extension, "*.", "."), ";", ",")); + } + filter = filters.join(","); + } + if (filter != null) + { + __inputControl.setAttribute("accept", filter); + } + __inputControl.onchange = function() + { + var file = __inputControl.files[0]; + modificationDate = Date.fromTime(file.lastModified); + creationDate = modificationDate; + size = file.size; + type = "." + Path.extension(file.name); + name = Path.withoutDirectory(file.name); + __path = file.name; + dispatchEvent(new Event(Event.SELECT)); + } + __inputControl.click(); + return true; + #end + + return false; + } +} \ No newline at end of file diff --git a/source/game/states/objects/MenuCharacter.hx b/source/game/states/objects/MenuCharacter.hx index ac5a87e..f93cd99 100644 --- a/source/game/states/objects/MenuCharacter.hx +++ b/source/game/states/objects/MenuCharacter.hx @@ -54,7 +54,7 @@ class MenuCharacter extends FlxSprite var rawJson = null; #if MODS_ALLOWED - var path:String = Paths.modFolders(characterPath); + var path:String = Mods.modFolders(characterPath); if (!FileSystem.exists(path)) { path = Paths.getPreloadPath(characterPath); } diff --git a/source/game/substates/GameOverSubstate.hx b/source/game/substates/GameOverSubstate.hx index 4660e3d..5e69f9f 100644 --- a/source/game/substates/GameOverSubstate.hx +++ b/source/game/substates/GameOverSubstate.hx @@ -41,6 +41,8 @@ class GameOverSubstate extends MusicBeatSubstate Conductor.songPosition = 0; + freezeParentState = false; + var beef = PlayState.instance.boyfriend.getScreenPosition(); boyfriend = new Character(beef.x, beef.y, characterName, true); boyfriend.x += boyfriend.positionArray[0] - PlayState.instance.boyfriend.positionArray[0]; diff --git a/source/game/substates/GameplayChangersSubstate.hx b/source/game/substates/GameplayChangersSubstate.hx index 6e6e479..60e483f 100644 --- a/source/game/substates/GameplayChangersSubstate.hx +++ b/source/game/substates/GameplayChangersSubstate.hx @@ -113,6 +113,8 @@ class GameplayChangersSubstate extends MusicBeatSubstate public function new() { super(); + + freezeParentState = false; var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); bg.alpha = 0.6; @@ -336,7 +338,9 @@ class GameplayChangersSubstate extends MusicBeatSubstate var val:Dynamic = option.getValue(); if(option.type == 'percent') val *= 100; var def:Dynamic = option.defaultValue; - option.text = text.replace('%v', val).replace('%d', def); + + // hashlink fix + option.text = text.replace('%v', Std.string(val)).replace('%d', Std.string(def)); } function clearHold() diff --git a/source/game/substates/PauseSubState.hx b/source/game/substates/PauseSubState.hx index 6a2fa3e..788b95e 100644 --- a/source/game/substates/PauseSubState.hx +++ b/source/game/substates/PauseSubState.hx @@ -138,7 +138,12 @@ class PauseSubState extends MusicBeatSubstate add(grpMenuShit); regenMenu(); - cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + + cameras = [PlayState.instance.camPause]; + + for (member in members) + if (Std.isOfType(member, FlxSprite) || Std.isOfType(member, FlxTypedGroup)) + member.cameras = [PlayState.instance.camPause]; super.create(); } @@ -151,6 +156,9 @@ class PauseSubState extends MusicBeatSubstate if (pauseMusic.volume < 0.5) pauseMusic.volume += 0.01 * elapsed; + if (!PlayState.instance?.camPause?.visible) + PlayState.instance.camPause.visible = true; + super.update(elapsed); updateSkipTextStuff(); @@ -325,6 +333,8 @@ class PauseSubState extends MusicBeatSubstate lvlDifftween?.cancel(); blueBallsTween?.cancel(); + if(PlayState.instance?.camPause != null) PlayState.instance.camPause.visible = false; + super.destroy(); } diff --git a/source/game/substates/ResetScoreSubState.hx b/source/game/substates/ResetScoreSubState.hx index 0a6ca7e..df20fc8 100644 --- a/source/game/substates/ResetScoreSubState.hx +++ b/source/game/substates/ResetScoreSubState.hx @@ -31,6 +31,8 @@ class ResetScoreSubState extends MusicBeatSubstate super(); + freezeParentState = false; + var name:String = song; if(week > -1) name = WeekData.weeksLoaded.get(WeekData.weeksList[week]).weekName; name += ' (${CoolUtil.difficulties[difficulty]})?'; diff --git a/source/game/substates/backend/MusicBeatSubstate.hx b/source/game/substates/backend/MusicBeatSubstate.hx index 50e639c..33a03b9 100644 --- a/source/game/substates/backend/MusicBeatSubstate.hx +++ b/source/game/substates/backend/MusicBeatSubstate.hx @@ -2,6 +2,7 @@ package game.substates.backend; import game.backend.Conductor.BPMChangeEvent; import game.scripting.FunkinLua; + import flixel.FlxG; import flixel.FlxSubState; import flixel.FlxBasic; @@ -13,6 +14,7 @@ import sys.FileSystem; import game.scripting.FunkinHScript; #end +import openfl.filters.BitmapFilter; import openfl.utils.Assets as OpenFlAssets; class MusicBeatSubstate extends FlxSubState @@ -22,6 +24,13 @@ class MusicBeatSubstate extends FlxSubState private var excludeSubStates:Array; #end + public var camSubState:FlxCamera; + + public var useCustomCamera:Bool = false; + public var freezeParentState:Bool = true; + + public var parentState:MusicBeatState; + // (WStaticInitOrder) Warning : maybe loop in static generation of MusicBeatSubstate private static function initExcludeSubStates():Array { return [game.scripting.HScriptSubstate]; @@ -35,6 +44,10 @@ class MusicBeatSubstate extends FlxSubState excludeSubStates = initExcludeSubStates(); #end + parentState = cast FlxG.state; + + initializeSubStateCamera(); + #if (HSCRIPT_ALLOWED && SCRIPTABLE_STATES) if (!excludeSubStates.contains(Type.getClass(this))) { @@ -66,7 +79,7 @@ class MusicBeatSubstate extends FlxSubState for (path in scriptFiles) { menuScriptArray.push(new FunkinHScript(path, this)); - if (path.contains('contents/')) + if (path.contains('${Mods.MODS_FOLDER}/')) trace('Loaded mod substate script: $path'); else trace('Loaded base game substate script: $path'); @@ -75,6 +88,54 @@ class MusicBeatSubstate extends FlxSubState #end } + private function initializeSubStateCamera():Void + { + if (Type.getClass(FlxG.state) == PlayState) + { + var playState:PlayState = cast FlxG.state; + if (playState.camSubState != null) + { + camSubState = playState.camSubState; + useCustomCamera = true; + + this.cameras = [camSubState]; + } + } + + if (camSubState == null) + { + camSubState = cameras != null && cameras.length > 0 ? cameras[0] : FlxG.camera; + } + } + + public function showSubStateCamera():Void + { + if (useCustomCamera && camSubState != null) + { + camSubState.visible = true; + camSubState.active = true; + } + } + + public function hideSubStateCamera():Void + { + if (useCustomCamera && camSubState != null) + { + camSubState.visible = false; + camSubState.active = false; + } + } + + public function setSubStateCameraEffects(?filters:Array):Void + { + if(camSubState != null) camSubState.filters = filters; + } + + public function resetSubStateCameraEffects():Void + { + if(camSubState != null) camSubState.filters = []; + } + private var lastBeat:Float = 0; private var lastStep:Float = 0; @@ -87,8 +148,16 @@ class MusicBeatSubstate extends FlxSubState override function create() { - super.create(); + if (!freezeParentState) + parentState.persistentUpdate = parentState.persistentDraw = true; + + showSubStateCamera(); + quickCallMenuScript("onCreate", []); + + super.create(); + + quickCallMenuScript("onCreatePost", []); } override function update(elapsed:Float) @@ -108,6 +177,20 @@ class MusicBeatSubstate extends FlxSubState quickCallMenuScript("onUpdatePost", [elapsed]); } + override function openSubState(SubState:FlxSubState) + { + showSubStateCamera(); + quickCallMenuScript("onOpenSubState", []); + super.openSubState(SubState); + } + + override function closeSubState() + { + hideSubStateCamera(); + quickCallMenuScript("onCloseSubState", []); + super.closeSubState(); + } + private function updateCurStep():Void { var lastChange = Conductor.getBPMFromSeconds(Conductor.songPosition); @@ -129,6 +212,8 @@ class MusicBeatSubstate extends FlxSubState override function destroy() { + hideSubStateCamera(); + #if (HSCRIPT_ALLOWED && SCRIPTABLE_STATES) for (sc in menuScriptArray) { diff --git a/source/game/substates/backend/Option.hx b/source/game/substates/backend/Option.hx index ffae9aa..936b36f 100644 --- a/source/game/substates/backend/Option.hx +++ b/source/game/substates/backend/Option.hx @@ -50,6 +50,9 @@ class Option public var description:String = ''; public var name:String = 'Unknown'; + public var visible:Bool = true; + public var active:Bool = true; + public function new(name:String, description:String = '', variable:String, type:String = 'bool', defaultValue:Dynamic = 'null variable value', ?options:Array = null) { this.name = name; @@ -104,6 +107,8 @@ class Option //nothing lol if(onChange != null) { onChange(); + + ClientPrefs.saveSettings(); } } diff --git a/source/game/substates/options/BaseOptionsMenu.hx b/source/game/substates/options/BaseOptionsMenu.hx index 0064c5e..4c817ba 100644 --- a/source/game/substates/options/BaseOptionsMenu.hx +++ b/source/game/substates/options/BaseOptionsMenu.hx @@ -51,12 +51,17 @@ class BaseOptionsMenu extends MusicBeatSubstate public var title:String; public var rpcTitle:String; + private var visibleOptions:Array