diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index c2d4c2680..bacde05e8 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -25,6 +25,7 @@ import funkin.freeplayStuff.BGScrollingText; import funkin.freeplayStuff.DJBoyfriend; import funkin.freeplayStuff.FreeplayScore; import funkin.freeplayStuff.SongMenuItem; +import funkin.play.HealthIcon; import lime.app.Future; import lime.utils.Assets; import funkin.shaderslmfao.AngleMask; @@ -295,7 +296,7 @@ class FreeplayState extends MusicBeatSubstate // grpSongs.add(songText); var icon:HealthIcon = new HealthIcon(songs[i].songCharacter); - icon.sprTracker = songText; + // icon.sprTracker = songText; // using a FlxGroup is too much fuss! iconArray.push(icon); diff --git a/source/funkin/HealthIcon.hx b/source/funkin/HealthIcon.hx deleted file mode 100644 index 617af2b74..000000000 --- a/source/funkin/HealthIcon.hx +++ /dev/null @@ -1,88 +0,0 @@ -package funkin; - -import flixel.FlxSprite; -import openfl.utils.Assets; -import funkin.play.PlayState; - -using StringTools; - -class HealthIcon extends FlxSprite -{ - /** - * Used for FreeplayState! If you use it elsewhere, prob gonna annoying - */ - public var sprTracker:FlxSprite; - - public var char:String = ''; - - var isPlayer:Bool = false; - - public function new(char:String = 'bf', isPlayer:Bool = false) - { - super(); - - this.isPlayer = isPlayer; - - antialiasing = true; - changeIcon(char); - scrollFactor.set(); - } - - public var isOldIcon:Bool = false; - - public function swapOldIcon():Void - { - isOldIcon = !isOldIcon; - - if (isOldIcon) - changeIcon('bf-old'); - else - changeIcon(PlayState.currentSong.player1); - } - - var pixelArrayFunny:Array = CoolUtil.coolTextFile(Paths.file('images/icons/pixelIcons.txt')); - - public function changeIcon(newChar:String):Void - { - if (newChar != 'bf-pixel' && newChar != 'bf-old') - newChar = newChar.split('-')[0].trim(); - - if (!Assets.exists(Paths.image('icons/icon-' + newChar))) - { - FlxG.log.warn('No icon with data: $newChar : using default placeholder face instead!'); - newChar = "face"; - } - - if (newChar != char) - { - if (animation.getByName(newChar) == null) - { - var imgSize:Int = 150; - - if (newChar.endsWith('pixel') || pixelArrayFunny.contains(newChar)) - imgSize = 32; - - loadGraphic(Paths.image('icons/icon-' + newChar), true, imgSize, imgSize); - - animation.add(newChar, [0, 1], 0, false, isPlayer); - } - animation.play(newChar); - char = newChar; - - if (newChar.endsWith('pixel') || pixelArrayFunny.contains(newChar)) - antialiasing = false; - else - antialiasing = true; - setGraphicSize(150); - updateHitbox(); - } - } - - override function update(elapsed:Float) - { - super.update(elapsed); - - if (sprTracker != null) - setPosition(sprTracker.x + sprTracker.width + 10, sprTracker.y - 30); - } -} diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index f27e21b15..ae63b7801 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -46,9 +46,6 @@ class MusicBeatState extends FlxUIState { super.create(); - if (transIn != null) - trace('reg ' + transIn.region); - createWatermarkText(); } diff --git a/source/funkin/SongLoad.hx b/source/funkin/SongLoad.hx index 770795602..f786d96f5 100644 --- a/source/funkin/SongLoad.hx +++ b/source/funkin/SongLoad.hx @@ -189,8 +189,11 @@ class SongLoad noteStuff[sectionIndex].sectionNotes[noteIndex].strumTime = arrayDipshit[0]; noteStuff[sectionIndex].sectionNotes[noteIndex].noteData = arrayDipshit[1]; noteStuff[sectionIndex].sectionNotes[noteIndex].sustainLength = arrayDipshit[2]; - noteStuff[sectionIndex].sectionNotes[noteIndex].altNote = arrayDipshit[3]; - if (arrayDipshit.length >= 5) + if (arrayDipshit.length > 3) + { + noteStuff[sectionIndex].sectionNotes[noteIndex].altNote = arrayDipshit[3]; + } + if (arrayDipshit.length > 4) { noteStuff[sectionIndex].sectionNotes[noteIndex].noteKind = arrayDipshit[4]; } diff --git a/source/funkin/StoryMenuState.hx b/source/funkin/StoryMenuState.hx index 5cfdd38c1..9cd57ec37 100644 --- a/source/funkin/StoryMenuState.hx +++ b/source/funkin/StoryMenuState.hx @@ -116,8 +116,6 @@ class StoryMenuState extends MusicBeatState grpLocks = new FlxTypedGroup(); add(grpLocks); - trace("Line 70"); - #if discord_rpc // Updating Discord Rich Presence DiscordClient.changePresence("In the Menus", null); @@ -147,8 +145,6 @@ class StoryMenuState extends MusicBeatState } } - trace("Line 96"); - for (char in 0...3) { var weekCharacterThing:MenuCharacter = new MenuCharacter((FlxG.width * 0.25) * (1 + char) - 150, weekCharacters[curWeek][char]); @@ -180,8 +176,6 @@ class StoryMenuState extends MusicBeatState difficultySelectors = new FlxGroup(); add(difficultySelectors); - trace("Line 124"); - 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"); @@ -206,8 +200,6 @@ class StoryMenuState extends MusicBeatState rightArrow.animation.play('idle'); difficultySelectors.add(rightArrow); - trace("Line 150"); - add(yellowBG); add(grpWeekCharacters); @@ -222,8 +214,6 @@ class StoryMenuState extends MusicBeatState updateText(); - trace("Line 165"); - super.create(); } diff --git a/source/funkin/TitleState.hx b/source/funkin/TitleState.hx index f54bfbd3c..76749b47b 100644 --- a/source/funkin/TitleState.hx +++ b/source/funkin/TitleState.hx @@ -189,8 +189,6 @@ class TitleState extends MusicBeatState gfDance.antialiasing = true; add(gfDance); - trace('MACRO TEST: ${gfDance.zIndex}'); - // alphaShader.shader.funnyShit.input = gfDance.pixels; // old shit logoBl.shader = alphaShader.shader; diff --git a/source/funkin/api/newgrounds/NGUnsafe.hx b/source/funkin/api/newgrounds/NGUnsafe.hx new file mode 100644 index 000000000..2995988a9 --- /dev/null +++ b/source/funkin/api/newgrounds/NGUnsafe.hx @@ -0,0 +1,78 @@ +package funkin.api.newgrounds; + +import flixel.util.FlxSignal; +import flixel.util.FlxTimer; +import lime.app.Application; +import openfl.display.Stage; +#if newgrounds +import io.newgrounds.NG; +import io.newgrounds.NGLite; +import io.newgrounds.components.ScoreBoardComponent.Period; +import io.newgrounds.objects.Error; +import io.newgrounds.objects.Medal; +import io.newgrounds.objects.Score; +import io.newgrounds.objects.ScoreBoard; +import io.newgrounds.objects.events.Response; +import io.newgrounds.objects.events.Result.GetCurrentVersionResult; +import io.newgrounds.objects.events.Result.GetVersionResult; +#end + +using StringTools; + +/** + * Contains any script functions which should be BLOCKED from use by modded scripts. + */ +class NGUnsafe +{ + static public function logEvent(event:String) + { + #if newgrounds + NG.core.calls.event.logEvent(event).send(); + trace('should have logged: ' + event); + #else + #if debug + trace('event:$event - not logged, missing NG.io lib'); + #end + #end + } + + static public function unlockMedal(id:Int) + { + #if newgrounds + if (isLoggedIn) + { + var medal = NG.core.medals.get(id); + if (!medal.unlocked) + medal.sendUnlock(); + } + #else + #if debug + trace('medal:$id - not unlocked, missing NG.io lib'); + #end + #end + } + + static public function postScore(score:Int = 0, song:String) + { + #if newgrounds + if (isLoggedIn) + { + for (id in NG.core.scoreBoards.keys()) + { + var board = NG.core.scoreBoards.get(id); + + if (song == board.name) + { + board.postScore(score, "Uhh meow?"); + } + + // trace('loaded scoreboard id:$id, name:${board.name}'); + } + } + #else + #if debug + trace('Song:$song, Score:$score - not posted, missing NG.io lib'); + #end + #end + } +} diff --git a/source/funkin/api/newgrounds/NGUtil.hx b/source/funkin/api/newgrounds/NGUtil.hx new file mode 100644 index 000000000..8ec06b27f --- /dev/null +++ b/source/funkin/api/newgrounds/NGUtil.hx @@ -0,0 +1,260 @@ +package funkin.api.newgrounds; + +import flixel.util.FlxSignal; +import flixel.util.FlxTimer; +import lime.app.Application; +import openfl.display.Stage; +#if newgrounds +import io.newgrounds.NG; +import io.newgrounds.NGLite; +import io.newgrounds.components.ScoreBoardComponent.Period; +import io.newgrounds.objects.Error; +import io.newgrounds.objects.Medal; +import io.newgrounds.objects.Score; +import io.newgrounds.objects.ScoreBoard; +import io.newgrounds.objects.events.Response; +import io.newgrounds.objects.events.Result.GetCurrentVersionResult; +import io.newgrounds.objects.events.Result.GetVersionResult; +#end + +using StringTools; + +/** + * Contains any script functions which should be ALLOWD for use by modded scripts. + */ +class NGUtil +{ + #if newgrounds + /** + * True, if the saved sessionId was used in the initial login, and failed to connect. + * Used in MainMenuState to show a popup to establish a new connection + */ + public static var savedSessionFailed(default, null):Bool = false; + + public static var scoreboardsLoaded:Bool = false; + public static var isLoggedIn(get, never):Bool; + + inline static function get_isLoggedIn() + { + return NG.core != null && NG.core.loggedIn; + } + + public static var scoreboardArray:Array = []; + + public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal(); + public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal(); + + public static var GAME_VER:String = ""; + + static public function checkVersion(callback:String->Void) + { + trace('checking NG.io version'); + GAME_VER = "v" + Application.current.meta.get('version'); + + NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response) + { + GAME_VER = response.result.data.currentVersion; + trace('CURRENT NG VERSION: ' + GAME_VER); + callback(GAME_VER); + }).send(); + } + + static public function init() + { + var api = APIStuff.API; + if (api == null || api.length == 0) + { + trace("Missing Newgrounds API key, aborting connection"); + return; + } + trace("connecting to newgrounds"); + + #if NG_FORCE_EXPIRED_SESSION + var sessionId:String = "fake_session_id"; + function onSessionFail(error:Error) + { + trace("Forcing an expired saved session. " + "To disable, comment out NG_FORCE_EXPIRED_SESSION in Project.xml"); + savedSessionFailed = true; + } + #else + var sessionId:String = NGLite.getSessionId(); + if (sessionId != null) + trace("found web session id"); + + #if (debug) + if (sessionId == null && APIStuff.SESSION != null) + { + trace("using debug session id"); + sessionId = APIStuff.SESSION; + } + #end + + var onSessionFail:Error->Void = null; + if (sessionId == null && FlxG.save.data.sessionId != null) + { + trace("using stored session id"); + sessionId = FlxG.save.data.sessionId; + onSessionFail = function(error) savedSessionFailed = true; + } + #end + + NG.create(api, sessionId, #if NG_DEBUG true #else false #end, onSessionFail); + + #if NG_VERBOSE + NG.core.verbose = true; + #end + // Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet + NG.core.initEncryption(APIStuff.EncKey); // Found in you NG project view + + if (NG.core.attemptingLogin) + { + /* a session_id was found in the loadervars, this means the user is playing on newgrounds.com + * and we should login shortly. lets wait for that to happen + */ + trace("attempting login"); + NG.core.onLogin.add(onNGLogin); + } + // GK: taking out auto login, adding a login button to the main menu + // else + // { + // /* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to. + // * Note: This will cause a new browser window to pop up where they can log in to newgrounds + // */ + // NG.core.requestLogin(onNGLogin); + // } + } + + /** + * Attempts to log in to newgrounds by requesting a new session ID, only call if no session ID was found automatically + * @param popupLauncher The function to call to open the login url, must be inside + * a user input event or the popup blocker will block it. + * @param onComplete A callback with the result of the connection. + */ + static public function login(?popupLauncher:(Void->Void)->Void, onComplete:ConnectionResult->Void) + { + trace("Logging in manually"); + var onPending:Void->Void = null; + if (popupLauncher != null) + { + onPending = function() popupLauncher(NG.core.openPassportUrl); + } + + var onSuccess:Void->Void = onNGLogin; + var onFail:Error->Void = null; + var onCancel:Void->Void = null; + if (onComplete != null) + { + onSuccess = function() + { + onNGLogin(); + onComplete(Success); + } + onFail = function(e) onComplete(Fail(e.message)); + onCancel = function() onComplete(Cancelled); + } + + NG.core.requestLogin(onSuccess, onPending, onFail, onCancel); + } + + inline static public function cancelLogin():Void + { + NG.core.cancelLoginRequest(); + } + + static function onNGLogin():Void + { + trace('logged in! user:${NG.core.user.name}'); + FlxG.save.data.sessionId = NG.core.sessionId; + FlxG.save.flush(); + // Load medals then call onNGMedalFetch() + NG.core.requestMedals(onNGMedalFetch); + + // Load Scoreboards hten call onNGBoardsFetch() + NG.core.requestScoreBoards(onNGBoardsFetch); + + ngDataLoaded.dispatch(); + } + + static public function logout() + { + NG.core.logOut(); + + FlxG.save.data.sessionId = null; + FlxG.save.flush(); + } + + // --- MEDALS + static function onNGMedalFetch():Void + { + /* + // Reading medal info + for (id in NG.core.medals.keys()) + { + var medal = NG.core.medals.get(id); + trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}'); + } + + // Unlocking medals + var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer + if (!unlockingMedal.unlocked) + unlockingMedal.sendUnlock(); + */ + } + + // --- SCOREBOARDS + static function onNGBoardsFetch():Void + { + /* + // Reading medal info + for (id in NG.core.scoreBoards.keys()) + { + var board = NG.core.scoreBoards.get(id); + trace('loaded scoreboard id:$id, name:${board.name}'); + } + */ + // var board = NG.core.scoreBoards.get(8004);// ID found in NG project view + + // Posting a score thats OVER 9000! + // board.postScore(FlxG.random.int(0, 1000)); + + // --- To view the scores you first need to select the range of scores you want to see --- + + // add an update listener so we know when we get the new scores + // board.onUpdate.add(onNGScoresFetch); + trace("shoulda got score by NOW!"); + // board.requestScores(20);// get the best 10 scores ever logged + // more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores + } + + static function onNGScoresFetch():Void + { + scoreboardsLoaded = true; + + ngScoresLoaded.dispatch(); + /* + for (score in NG.core.scoreBoards.get(8737).scores) + { + trace('score loaded user:${score.user.name}, score:${score.formatted_value}'); + + } + */ + + // var board = NG.core.scoreBoards.get(8004);// ID found in NG project view + // board.postScore(HighScore.score); + + // NGUtil.scoreboardArray = NG.core.scoreBoards.get(8004).scores; + } + #end +} + +enum ConnectionResult +{ + /** Log in successful */ + Success; + + /** Could not login */ + Fail(msg:String); + + /** User cancelled the login */ + Cancelled; +} diff --git a/source/funkin/api/newgrounds/NgPrompt.hx b/source/funkin/api/newgrounds/NgPrompt.hx new file mode 100644 index 000000000..5f08bf443 --- /dev/null +++ b/source/funkin/api/newgrounds/NgPrompt.hx @@ -0,0 +1,104 @@ +package funkin.api.newgrounds; + +#if newgrounds +import funkin.NGio; +import funkin.ui.Prompt; + +class NgPrompt extends Prompt +{ + public function new(text:String, style:ButtonStyle = Yes_No) + { + super(text, style); + } + + static public function showLogin() + { + return showLoginPrompt(true); + } + + static public function showSavedSessionFailed() + { + return showLoginPrompt(false); + } + + static function showLoginPrompt(fromUi:Bool) + { + var prompt = new NgPrompt("Talking to server...", None); + prompt.openCallback = NGUtil.login.bind(function popupLauncher(openPassportUrl) + { + var choiceMsg = fromUi ? #if web "Log in to Newgrounds?" #else null #end // User-input needed to allow popups + : "Your session has expired.\n Please login again."; + + if (choiceMsg != null) + { + prompt.setText(choiceMsg); + prompt.setButtons(Yes_No); + #if web + prompt.buttons.getItem("yes").fireInstantly = true; + #end + prompt.onYes = function() + { + prompt.setText("Connecting..." #if web + "\n(check your popup blocker)" #end); + prompt.setButtons(None); + openPassportUrl(); + }; + prompt.onNo = function() + { + prompt.close(); + prompt = null; + NGio.cancelLogin(); + }; + } + else + { + prompt.setText("Connecting..."); + openPassportUrl(); + } + }, function onLoginComplete(result:ConnectionResult) + { + switch (result) + { + case Success: + { + prompt.setText("Login Successful"); + prompt.setButtons(Ok); + prompt.onYes = prompt.close; + } + case Fail(msg): + { + trace("Login Error:" + msg); + prompt.setText("Login failed"); + prompt.setButtons(Ok); + prompt.onYes = prompt.close; + } + case Cancelled: + { + if (prompt != null) + { + prompt.setText("Login cancelled by user"); + prompt.setButtons(Ok); + prompt.onYes = prompt.close; + } + else + trace("Login cancelled via prompt"); + } + } + }); + + return prompt; + } + + static public function showLogout() + { + var user = io.newgrounds.NG.core.user.name; + var prompt = new NgPrompt('Log out of $user?', Yes_No); + prompt.onYes = function() + { + NGio.logout(); + prompt.close(); + }; + prompt.onNo = prompt.close; + return prompt; + } +} +#end diff --git a/source/funkin/api/newgrounds/README.md b/source/funkin/api/newgrounds/README.md new file mode 100644 index 000000000..f61e1b0fd --- /dev/null +++ b/source/funkin/api/newgrounds/README.md @@ -0,0 +1,9 @@ +# funkin.api.newgrounds + +This package contains two main classes: +- `NGUtil` contains utility functions for interacting with the Newgrounds API. + - This includes any functions which scripts should be able to use, + such as retrieving achievement status. +- `NGUnsafe` contains sensitive utility functions for interacting with the Newgrounds API. + - This includes any functions which scripts should not be able to use, + such as writing high scores or posting achievements. \ No newline at end of file diff --git a/source/funkin/charting/ChartingState.hx b/source/funkin/charting/ChartingState.hx index ea3af489d..7c5e8bd27 100644 --- a/source/funkin/charting/ChartingState.hx +++ b/source/funkin/charting/ChartingState.hx @@ -8,6 +8,7 @@ import funkin.audiovis.ABotVis; import funkin.audiovis.PolygonSpectogram; import funkin.audiovis.SpectogramSprite; import flixel.FlxSprite; +import funkin.play.HealthIcon; import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; import flixel.addons.ui.FlxInputText; @@ -705,7 +706,7 @@ class ChartingState extends MusicBeatState { if (FlxG.mouse.overlaps(leftIcon)) { - if (leftIcon.char == _song.player1) + if (leftIcon.characterId == _song.player1) { p1Muted = !p1Muted; leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; @@ -727,7 +728,7 @@ class ChartingState extends MusicBeatState // sloppy copypaste lol deal with it! if (FlxG.mouse.overlaps(rightIcon)) { - if (rightIcon.char == _song.player1) + if (rightIcon.characterId == _song.player1) { p1Muted = !p1Muted; rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; @@ -1129,16 +1130,16 @@ class ChartingState extends MusicBeatState { if (check_mustHitSection.checked) { - leftIcon.changeIcon(_song.player1); - rightIcon.changeIcon(_song.player2); + leftIcon.characterId = (_song.player1); + rightIcon.characterId = (_song.player2); leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; rightIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0; } else { - leftIcon.changeIcon(_song.player2); - rightIcon.changeIcon(_song.player1); + leftIcon.characterId = (_song.player2); + rightIcon.characterId = (_song.player1); leftIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0; rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index 1f6d149cb..882dc86fe 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -136,6 +136,8 @@ class PolymodHandler // Ensure script files have merge support. output.addType("hscript", TextFileFormat.PLAINTEXT); output.addType("hxs", TextFileFormat.PLAINTEXT); + output.addType("hxc", TextFileFormat.PLAINTEXT); + output.addType("hx", TextFileFormat.PLAINTEXT); // You can specify the format of a specific file, with file extension. // output.addFile("data/introText.txt", TextFileFormat.LINES) @@ -146,8 +148,8 @@ class PolymodHandler { return { assetLibraryPaths: [ - "songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2", - "week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8", + "songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2", + "week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8", ] } } diff --git a/source/funkin/play/HealthIcon.hx b/source/funkin/play/HealthIcon.hx new file mode 100644 index 000000000..4bc4f4386 --- /dev/null +++ b/source/funkin/play/HealthIcon.hx @@ -0,0 +1,346 @@ +package funkin.play; + +import funkin.play.character.CharacterData.CharacterDataParser; +import flixel.FlxSprite; +import openfl.utils.Assets; + +/** + * This is a rework of the health icon with the following changes: + * - The health icon now owns its own state logic. It queries health and updates the sprite itself, + * rather than relying on PlayState to command it. + * - The health icon now supports animations. + * - The health icon will now search for a SparrowV2 (XML) spritesheet, and use that for rendering if it can. + * - If it can't find a spritesheet, it will the old format; a two-frame 300x150 image. + * - If the spritesheet is found, the health icon will attempt to load and use the following animations as appropriate: + * - `idle`, `winning`, `losing`, `toWinning`, `fromWinning`, `toLosing`, `fromLosing` + * - The health icon is now easier to control via scripts. + * - Set `autoUpdate` to false to prevent the health icon from changing its own animations. + * - Once `autoUpdate` is false, you can manually call `playAnimation()` to play a specific animation. + * - i.e. `PlayState.instance.iconP1.playAnimation("losing")` + * - Scripts can also utilize all functionality that a normal FlxSprite would have access to, such as adding supplimental animations. + * - i.e. `PlayState.instance.iconP1.animation.addByPrefix("jumpscare", "jumpscare", 24, false);` + * @author MasterEric + */ +class HealthIcon extends FlxSprite +{ + /** + * The character this icon is representing. + * Setting this variable will automatically update the graphic. + */ + public var characterId(default, set):String; + + /** + * Whether this health icon should automatically update its state based on the character's health. + * You can set this to false if you want to manually forc + */ + public var autoUpdate:Bool = true; + + /** + * The player the health icon is attached to. + */ + var playerId:Int = 0; + + /** + * Whether the sprite is pixel art or not. + * Calculated when loading an icon. + */ + var isPixel:Bool = false; + + /** + * At this amount of health, play the Winning animation instead of the idle. + */ + static final WINNING_THRESHOLD = 0.8 * 2; + + /** + * At this amount of health, play the Losing animation instead of the idle. + */ + static final LOSING_THRESHOLD = 0.2 * 2; + + /** + * The maximum health of the player. + */ + static final MAXIMUM_HEALTH = 2; + + /** + * The size of a non-pixel icon when using the legacy format. + * Remember, modern icons can be any size. + */ + static final LEGACY_ICON_SIZE = 150; + + /** + * The size of a pixel icon when using the legacy format. + * Remember, modern icons can be any size. + */ + static final LEGACY_PIXEL_SIZE = 32; + + public function new(char:String = 'bf', playerId:Int = 0) + { + super(0, 0); + this.playerId = playerId; + this.scrollFactor.set(); + + this.characterId = char; + + this.antialiasing = !isPixel; + + this.flipX = playerId == 0; + } + + function set_characterId(value:String):String + { + if (value == characterId) + return value; + + characterId = value; + loadCharacter(characterId); + return value; + } + + /** + * Easter egg; press 9 in the PlayState to use the old player icon. + */ + public function toggleOldIcon():Void + { + if (characterId == 'bf-old') + { + characterId = PlayState.currentSong.player1; + } + else + { + characterId = 'bf-old'; + } + } + + /** + * Called by Flixel every frame. Includes logic to manage the currently playing animation. + */ + override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (PlayState.instance == null) + return; + + // Auto-update the state of the icon based on the player's health. + if (autoUpdate) + { + switch (playerId) + { + case 0: // Boyfriend + updateHealthIcon(PlayState.instance.health); + case 1: // Dad + updateHealthIcon(MAXIMUM_HEALTH - PlayState.instance.health); + } + } + } + + function updateHealthIcon(health:Float) + { + // We want to efficiently handle animation playback + + // Here, we use the current animation name to track the current state + // of a simple state machine. Neat! + + switch (getCurrentAnimation()) + { + case IDLE: + if (health < LOSING_THRESHOLD) + playAnimation(TO_LOSING, LOSING); + else if (health > WINNING_THRESHOLD) + playAnimation(TO_WINNING, WINNING); + else + playAnimation(IDLE); + case WINNING: + if (health < WINNING_THRESHOLD) + playAnimation(FROM_WINNING, IDLE); + else + playAnimation(WINNING, IDLE); + case LOSING: + if (health > LOSING_THRESHOLD) + playAnimation(FROM_LOSING, IDLE); + else + playAnimation(LOSING, IDLE); + case TO_LOSING: + if (isAnimationFinished()) + playAnimation(LOSING, IDLE); + case TO_WINNING: + if (isAnimationFinished()) + playAnimation(WINNING, IDLE); + case FROM_LOSING | FROM_WINNING: + if (isAnimationFinished()) + playAnimation(IDLE); + } + } + + /** + * Load + * @param charId + */ + function loadAnimationNew(charId:String):Void + { + this.animation.addByPrefix(IDLE, IDLE, 24, true); + this.animation.addByPrefix(WINNING, WINNING, 24, true); + this.animation.addByPrefix(LOSING, LOSING, 24, true); + this.animation.addByPrefix(TO_WINNING, TO_WINNING, 24, true); + this.animation.addByPrefix(TO_LOSING, TO_LOSING, 24, true); + this.animation.addByPrefix(FROM_WINNING, FROM_WINNING, 24, true); + this.animation.addByPrefix(FROM_LOSING, FROM_LOSING, 24, true); + } + + /** + * Load health icon animations using the legacy format. + * Simply assumes two icons, one on + * @param charId + */ + function loadAnimationOld(charId:String):Void + { + this.animation.add(IDLE, [0], 0, false, this.playerId == 0); + this.animation.add(LOSING, [1], 0, false, this.playerId == 0); + } + + function correctCharacterId(charId:String):String + { + if (!Assets.exists(Paths.image('icons/icon-' + charId))) + { + FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!'); + return "face"; + } + + return charId; + } + + function isNewSpritesheet(charId:String):Bool + { + return Assets.exists(Paths.xml('icons/icon-' + characterId)); + } + + function fetchIsPixel(charId:String):Bool + { + var charData = CharacterDataParser.fetchCharacterData(charId); + if (charData == null) + { + FlxG.log.warn('No character data found for character: $charId'); + return false; + } + return charData.isPixel; + } + + function loadCharacter(charId:String):Void + { + if (correctCharacterId(charId) != charId) + { + characterId = correctCharacterId(charId); + return; + } + + isPixel = fetchIsPixel(charId); + + if (isNewSpritesheet(charId)) + { + frames = Paths.getSparrowAtlas('icons/icon-$charId'); + + loadAnimationNew(charId); + } + else + { + loadGraphic(Paths.image('icons/icon-$charId'), true, isPixel ? LEGACY_PIXEL_SIZE : LEGACY_ICON_SIZE, + isPixel ? LEGACY_PIXEL_SIZE : LEGACY_ICON_SIZE); + + loadAnimationOld(charId); + } + } + + /** + * @return Name of the current animation being played by this health icon. + */ + public function getCurrentAnimation():String + { + if (this.animation == null || this.animation.curAnim == null) + return ""; + return this.animation.curAnim.name; + } + + /** + * @return Whether this sprite posesses the given animation. + * Only true if the animation was successfully loaded from the XML. + */ + public function hasAnimation(id:String):Bool + { + if (this.animation == null) + return false; + + return this.animation.getByName(id) != null; + } + + /** + * @return Whether the current animation is in the finished state. + */ + public function isAnimationFinished():Bool + { + return this.animation.finished; + } + + /** + * Plays the animation with the given name. + * @param name The name of the animation to play. + * @param fallback The fallback animation to play if the given animation is not found. + * @param restart Whether to forcibly restart the animation if it is already playing. + */ + public function playAnimation(name:String, fallback:String = null, restart = false):Void + { + // Attempt to play the animation + if (hasAnimation(name)) + this.animation.play(name, restart, false, 0); + + // Play the fallback animation if the requested animation was not found + if (fallback != null && hasAnimation(fallback)) + this.animation.play(fallback, restart, false, 0); + + // If we don't have an animation, we're done. + } +} + +enum abstract HealthIconState(String) to String from String +{ + /** + * Indicates the health icon is in the default animation. + * Plays as long as health is between 20% and 80%. + */ + var IDLE = "idle"; + + /** + * Indicates the health icon is playing the Winning animation. + * Plays as long as health is above 80%. + */ + var WINNING = "winning"; + + /** + * Indicates the health icon is playing the Losing animation. + * Plays as long as health is below 20%. + */ + var LOSING = "losing"; + + /** + * Indicates that the health icon is transitioning between `idle` and `winning`. + * The next animation will play once the current animation finishes. + */ + var TO_WINNING = "toWinning"; + + /** + * Indicates that the health icon is transitioning between `idle` and `losing`. + * The next animation will play once the current animation finishes. + */ + var TO_LOSING = "toLosing"; + + /** + * Indicates that the health icon is transitioning between `winning` and `idle`. + * The next animation will play once the current animation finishes. + */ + var FROM_WINNING = "fromWinning"; + + /** + * Indicates that the health icon is transitioning between `losing` and `idle`. + * The next animation will play once the current animation finishes. + */ + var FROM_LOSING = "fromLosing"; +} diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index f369441be..fb205b6c4 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,7 +1,6 @@ package funkin.play; import funkin.play.character.BaseCharacter; -import flixel.addons.effects.FlxTrail; import flixel.addons.transition.FlxTransitionableState; import flixel.FlxCamera; import flixel.FlxObject; @@ -29,6 +28,7 @@ import funkin.modding.module.ModuleHandler; import funkin.Note; import funkin.play.character.CharacterData; import funkin.play.stage.Stage; +import funkin.play.HealthIcon; import funkin.play.stage.StageData; import funkin.play.Strumline.StrumlineArrow; import funkin.play.Strumline.StrumlineStyle; @@ -143,7 +143,7 @@ class PlayState extends MusicBeatState implements IHook * NOTE: This must be an FlxObject, not an FlxPoint, because it needs to be added to the scene. * Once it's added to the scene, the camera can be configured to follow it. */ - public var cameraFollowPoint:FlxObject = new FlxObject(0, 0, 1, 1); + public var cameraFollowPoint:FlxSprite = new FlxSprite(0, 0); /** * PRIVATE INSTANCE VARIABLES @@ -275,6 +275,10 @@ class PlayState extends MusicBeatState implements IHook instance = this; + // TEMP: For testing + cameraFollowPoint.makeGraphic(8, 8, 0xFFFF00FF); + cameraFollowPoint.zIndex = 1000000; + // Reduce physics accuracy (who cares!!!) to improve animation quality. FlxG.fixedTimestep = false; @@ -370,11 +374,11 @@ class PlayState extends MusicBeatState implements IHook scoreText.scrollFactor.set(); add(scoreText); - iconP1 = new HealthIcon(currentSong.player1, true); + iconP1 = new HealthIcon(currentSong.player1, 0); iconP1.y = healthBar.y - (iconP1.height / 2); add(iconP1); - iconP2 = new HealthIcon(currentSong.player2, false); + iconP2 = new HealthIcon(currentSong.player2, 1); iconP2.y = healthBar.y - (iconP2.height / 2); add(iconP2); @@ -536,51 +540,17 @@ class PlayState extends MusicBeatState implements IHook if (dad != null) { dad.characterType = CharacterType.DAD; - cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x, dad.getGraphicMidpoint().y); + cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y); } switch (currentSong.player2) { case 'gf': - var gfPoint:FlxPoint = currentStage.getGirlfriendPosition(); - dad.setPosition(gfPoint.x, gfPoint.y); - - // girlfriend.visible = false; - if (isStoryMode) { cameraFollowPoint.x += 600; tweenCamIn(); } - case "spooky": - dad.y += 200; - case "monster": - dad.y += 100; - case 'monster-christmas': - dad.y += 130; - case 'dad': - cameraFollowPoint.x += 400; - case 'pico': - cameraFollowPoint.x += 600; - dad.y += 300; - case 'parents-christmas': - dad.x -= 500; - case 'senpai' | 'senpai-angry': - dad.x += 150; - dad.y += 360; - cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y); - case 'spirit': - dad.x -= 150; - dad.y += 100; - cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y); - case 'tankman': - dad.y += 180; - } - - if (currentSong.player1 == "pico") - { - dad.x -= 100; - dad.y -= 100; } // @@ -596,11 +566,6 @@ class PlayState extends MusicBeatState implements IHook // REPOSITIONING PER STAGE switch (currentStageId) { - case 'schoolEvil': - var evilTrail = new FlxTrail(dad, null, 4, 24, 0.3, 0.069); - // Go behind Spirit. - evilTrail.zIndex = 190; - add(evilTrail); case "tank": girlfriend.y += 10; girlfriend.x -= 30; @@ -1081,7 +1046,7 @@ class PlayState extends MusicBeatState implements IHook FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState()); if (FlxG.keys.justPressed.NINE) - iconP1.swapOldIcon(); + iconP1.toggleOldIcon(); iconP1.setGraphicSize(Std.int(CoolUtil.coolLerp(iconP1.width, 150, 0.15))); iconP2.setGraphicSize(Std.int(CoolUtil.coolLerp(iconP2.width, 150, 0.15))); @@ -1097,16 +1062,6 @@ class PlayState extends MusicBeatState implements IHook if (health > 2) health = 2; - if (healthBar.percent < 20) - iconP1.animation.curAnim.curFrame = 1; - else - iconP1.animation.curAnim.curFrame = 0; - - if (healthBar.percent > 80) - iconP2.animation.curAnim.curFrame = 1; - else - iconP2.animation.curAnim.curFrame = 0; - #if debug if (FlxG.keys.justPressed.ONE) endSong(); @@ -1121,7 +1076,7 @@ class PlayState extends MusicBeatState implements IHook { cameraRightSide = SongLoad.getSong()[Std.int(curStep / 16)].mustHitSection; - cameraMovement(); + controlCamera(); } if (camZooming) @@ -1195,7 +1150,7 @@ class PlayState extends MusicBeatState implements IHook inactiveNotes.shift(); } - if (generatedMusic) + if (generatedMusic && playerStrumline != null) { activeNotes.forEachAlive(function(daNote:Note) { @@ -1522,57 +1477,35 @@ class PlayState extends MusicBeatState implements IHook comboPopUps.displayCombo(combo); } - function cameraMovement() + function controlCamera() { if (currentStage == null) return; - if (cameraFollowPoint.x != currentStage.getDad().getMidpoint().x + 150 && !cameraRightSide) + var isFocusedOnDad = cameraFollowPoint.x == currentStage.getDad().cameraFocusPoint.x; + var isFocusedOnBF = cameraFollowPoint.x == currentStage.getBoyfriend().cameraFocusPoint.x; + + if (cameraRightSide && !isFocusedOnBF) { - cameraFollowPoint.setPosition(currentStage.getDad().getMidpoint().x + 150, currentStage.getDad().getMidpoint().y - 100); - // camFollow.setPosition(lucky.getMidpoint().x - 120, lucky.getMidpoint().y + 210); - - switch (currentStage.getDad().characterId) - { - case 'mom': - cameraFollowPoint.y = currentStage.getDad().getMidpoint().y; - case 'senpai' | 'senpai-angry': - cameraFollowPoint.y = currentStage.getDad().getMidpoint().y - 430; - cameraFollowPoint.x = currentStage.getDad().getMidpoint().x - 100; - } - - if (currentStage.getDad().characterId == 'mom') - vocals.volume = 1; - - if (currentSong.song.toLowerCase() == 'tutorial') - tweenCamIn(); - } - - if (cameraRightSide && cameraFollowPoint.x != currentStage.getBoyfriend().getMidpoint().x - 100) - { - cameraFollowPoint.setPosition(currentStage.getBoyfriend().getMidpoint().x - 100, currentStage.getBoyfriend().getMidpoint().y - 100); - - switch (currentStageId) - { - case 'limo': - cameraFollowPoint.x = currentStage.getBoyfriend().getMidpoint().x - 300; - case 'mall': - cameraFollowPoint.y = currentStage.getBoyfriend().getMidpoint().y - 200; - case 'school' | 'schoolEvil': - cameraFollowPoint.x = currentStage.getBoyfriend().getMidpoint().x - 200; - cameraFollowPoint.y = currentStage.getBoyfriend().getMidpoint().y - 200; - } + // Focus the camera on the player. + cameraFollowPoint.setPosition(currentStage.getBoyfriend().cameraFocusPoint.x, currentStage.getBoyfriend().cameraFocusPoint.y); + // TODO: Un-hardcode this. if (currentSong.song.toLowerCase() == 'tutorial') FlxTween.tween(FlxG.camera, {zoom: 1 * FlxCamera.defaultZoom}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut}); } - } + else if (!cameraRightSide && !isFocusedOnDad) + { + // Focus the camera on the opponent. + cameraFollowPoint.setPosition(currentStage.getDad().cameraFocusPoint.x, currentStage.getDad().cameraFocusPoint.y); - public var test:(PlayState) -> Void = function(instance:PlayState) - { - trace('test'); - trace(instance.currentStageId); - }; + // TODO: Un-hardcode this stuff. + if (currentStage.getDad().characterId == 'mom') + vocals.volume = 1; + if (currentSong.song.toLowerCase() == 'tutorial') + tweenCamIn(); + } + } public function keyShit(test:Bool):Void { @@ -1684,6 +1617,8 @@ class PlayState extends MusicBeatState implements IHook for (keyId => isPressed in pressArray) { + if (playerStrumline == null) + continue; var arrow:StrumlineArrow = PlayState.instance.playerStrumline.getArrow(keyId); if (isPressed && arrow.animation.curAnim.name != 'confirm') @@ -2072,7 +2007,6 @@ class PlayState extends MusicBeatState implements IHook public function refresh() { sort(SortUtil.byZIndex, FlxSort.ASCENDING); - trace('Stage sorted by z-index'); } /** diff --git a/source/funkin/play/VanillaCutscenes.hx b/source/funkin/play/VanillaCutscenes.hx index a67ff8773..9cbcd8736 100644 --- a/source/funkin/play/VanillaCutscenes.hx +++ b/source/funkin/play/VanillaCutscenes.hx @@ -2,14 +2,13 @@ package funkin.play; import flixel.util.FlxTimer; import flixel.tweens.FlxTween; -import flixel.tweens.FlxTween; import flixel.tweens.FlxEase; import flixel.util.FlxColor; import flixel.FlxSprite; /** * Static methods for playing cutscenes in the PlayState. - * TODO: Softcode this shit!!!!!1! + * TODO: Un-hardcode this shit!!!!!1! */ class VanillaCutscenes { @@ -64,7 +63,7 @@ class VanillaCutscenes @:privateAccess PlayState.instance.startCountdown(); @:privateAccess - PlayState.instance.cameraMovement(); + PlayState.instance.controlCamera(); } public static function playHorrorStartCutscene() diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 81ac7aaaf..b619bbe27 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -1,5 +1,6 @@ package funkin.play.character; +import funkin.play.character.CharacterData.CharacterDataParser; import flixel.math.FlxPoint; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent.UpdateScriptEvent; @@ -40,7 +41,8 @@ class BaseCharacter extends Bopper final singTimeCrochet:Float; /** - * The x and y position to subtract from the stage's X value to get the character's proper rendering position. + * Character position in stage file is at the bottom center of the character. + * Position from stage file - character origin is at the top left corner of the character. */ public var characterOrigin(get, null):FlxPoint; @@ -48,15 +50,64 @@ class BaseCharacter extends Bopper { var xPos = (width / 2); // Horizontal center var yPos = (height); // Vertical bottom - trace('Origin: ${characterId} (${xPos}, ${yPos})'); return new FlxPoint(xPos, yPos); } + /** + * Returns the point the camera should focus on. + * Should be approximately centered on the character, and should not move based on the current animation. + * + * Set the position of this rather than reassigning it, so that anything referencing it will not be affected. + */ + public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0); + + override function set_animOffset(value:Array) + { + if (animOffset == null) + animOffset = [0, 0]; + if (animOffset == value) + return value; + + var xDiff = animOffset[0] - value[0]; + var yDiff = animOffset[1] - value[1]; + + // Call the super function so that camera focus point is not affected. + super.set_x(this.x + xDiff); + super.set_y(this.y + yDiff); + + return animOffset = value; + } + + /** + * If the x position changes, other than via changing the animation offset, + * then we need to update the camera focus point. + */ override function set_x(value:Float):Float { + if (value == this.x) + return value; + + var xDiff = value - this.x; + this.cameraFocusPoint.x += xDiff; + return super.set_x(value); } + /** + * If the y position changes, other than via changing the animation offset, + * then we need to update the camera focus point. + */ + override function set_y(value:Float):Float + { + if (value == this.y) + return value; + + var yDiff = value - this.y; + this.cameraFocusPoint.y += yDiff; + + return super.set_y(value); + } + public function new(id:String) { super(); @@ -74,6 +125,26 @@ class BaseCharacter extends Bopper } } + /** + * Set the sprite scale to the appropriate value. + * @param scale + */ + function setScale(scale:Null):Void + { + if (scale == null) + scale = 1.0; + + this.scale.x = scale; + this.scale.y = scale; + this.updateHitbox(); + } + + override function onCreate(event:ScriptEvent):Void + { + this.cameraFocusPoint = new FlxPoint(this.x + this.width / 2 + _data.cameraOffset[0], this.y + this.height / 2 + _data.cameraOffset[0]); + super.onCreate(event); + } + public override function onUpdate(event:UpdateScriptEvent):Void { super.onUpdate(event); @@ -112,7 +183,7 @@ class BaseCharacter extends Bopper FlxG.watch.addQuick('singTimeMs-${characterId}', singTimeMs); if (holdTimer > singTimeMs && shouldStopSinging) { - trace('holdTimer reached ${holdTimer}sec (> ${singTimeMs}), stopping sing animation'); + // trace('holdTimer reached ${holdTimer}sec (> ${singTimeMs}), stopping sing animation'); holdTimer = 0; dance(true); } @@ -229,8 +300,6 @@ class BaseCharacter extends Bopper { super.onNoteHit(event); - trace('HIT NOTE: ${event.note.data.dir} : ${event.note.isSustainNote}'); - holdTimer = 0; if (event.note.mustPress && characterType == BF) diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 87be5f83e..0a5dae1c6 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -135,7 +135,11 @@ class CharacterDataParser trace(' Failed to instantiate scripted character: ${charCls}'); continue; } - characterScriptedClass.set(character.characterId, charCls); + else + { + trace(' Successfully instantiated scripted character: ${charCls}'); + characterScriptedClass.set(character.characterId, charCls); + } } trace(' Successfully loaded ${Lambda.count(characterCache)} stages.'); @@ -290,6 +294,7 @@ class CharacterDataParser static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.SPARROW; static final DEFAULT_SCALE:Float = 1; static final DEFAULT_SCROLL:Array = [0, 0]; + static final DEFAULT_CAMERAOFFSET:Array = [0, 0]; static final DEFAULT_STARTINGANIM:String = "idle"; /** @@ -303,7 +308,7 @@ class CharacterDataParser { if (input == null) { - trace('[CHARDATA] ERROR: Could not parse character data for "${id}".'); + // trace('[CHARDATA] ERROR: Could not parse character data for "${id}".'); return null; } @@ -346,6 +351,11 @@ class CharacterDataParser input.scale = DEFAULT_SCALE; } + if (input.cameraOffset == null) + { + input.cameraOffset = DEFAULT_CAMERAOFFSET; + } + if (input.isPixel == null) { input.isPixel = DEFAULT_ISPIXEL; @@ -451,11 +461,18 @@ typedef CharacterData = var assetPath:String; /** - * Either the scale of the graphic as a float, or the [w, h] scale as an array of two floats. + * The scale of the graphic as a float. * Pro tip: On pixel-art levels, save the sprites small and set this value to 6 or so to save memory. * @default 1 */ - var scale:OneOfTwo>; + var scale:Null; + + /** + * The amount to offset the camera by while focusing on this character. + * Default value focuses on the character directly. + * @default [0, 0] + */ + var cameraOffset:Array; /** * Setting this to true disables anti-aliasing for the character. diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx index b49088e50..20bfeb474 100644 --- a/source/funkin/play/character/MultiSparrowCharacter.hx +++ b/source/funkin/play/character/MultiSparrowCharacter.hx @@ -41,16 +41,17 @@ class MultiSparrowCharacter extends BaseCharacter { trace('Creating MULTI SPARROW CHARACTER: ' + this.characterId); - buildSprite(); - - playAnimation(_data.startingAnimation); + buildSprites(); + super.onCreate(event); } - function buildSprite() + function buildSprites() { buildSpritesheets(); buildAnimations(); + playAnimation(_data.startingAnimation); + if (_data.isPixel) { this.antialiasing = false; @@ -59,12 +60,6 @@ class MultiSparrowCharacter extends BaseCharacter { this.antialiasing = true; } - - if (_data.scale != null) - { - this.setGraphicSize(Std.int(this.width * this.scale.x)); - this.updateHitbox(); - } } function buildSpritesheets() @@ -115,26 +110,28 @@ class MultiSparrowCharacter extends BaseCharacter } if (assetPath == null) { - trace('Asset path is null, falling back to default. This is normal!'); + // trace('Asset path is null, falling back to default. This is normal!'); loadFramesByAssetPath(_data.assetPath); return; } if (this.activeMember == assetPath) { - trace('Already using this asset path: ${assetPath}'); + // trace('Already using this asset path: ${assetPath}'); return; } if (members.exists(assetPath)) { + // Switch to a new set of sprites. trace('Loading frames from asset path: ${assetPath}'); this.frames = members.get(assetPath); this.activeMember = assetPath; + this.setScale(_data.scale); } else { - trace('Multi-Sparrow could not find asset path: ${assetPath}'); + trace('[WARN] MultiSparrow character ${characterId} could not find asset path: ${assetPath}'); } } @@ -149,20 +146,18 @@ class MultiSparrowCharacter extends BaseCharacter } else { - trace('Multi-Sparrow could not find animation: ${animName}'); + trace('[WARN] MultiSparrow character ${characterId} could not find animation: ${animName}'); } } function buildAnimations() { - trace('[SPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}'); + trace('[MULTISPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}'); // We need to swap to the proper frame collection before adding the animations, I think? for (anim in _data.animations) { - trace('Using frames: ${anim.name}'); loadFramesByAnimName(anim.name); - trace('Adding animation'); FlxAnimationUtil.addAtlasAnimation(this, anim); if (anim.offsets == null) @@ -176,7 +171,7 @@ class MultiSparrowCharacter extends BaseCharacter } var animNames = this.animation.getNameList(); - trace('[SPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}'); + trace('[MULTISPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}'); } public override function playAnimation(name:String, restart:Bool = false):Void diff --git a/source/funkin/play/character/PackerCharacter.hx b/source/funkin/play/character/PackerCharacter.hx index bf3032a9a..b7282423e 100644 --- a/source/funkin/play/character/PackerCharacter.hx +++ b/source/funkin/play/character/PackerCharacter.hx @@ -24,6 +24,8 @@ class PackerCharacter extends BaseCharacter loadAnimations(); playAnimation(_data.startingAnimation); + + super.onCreate(event); } function loadSpritesheet() @@ -48,11 +50,7 @@ class PackerCharacter extends BaseCharacter this.antialiasing = true; } - if (_data.scale != null) - { - this.setGraphicSize(Std.int(this.width * this.scale.x)); - this.updateHitbox(); - } + this.setScale(_data.scale); } function loadAnimations() diff --git a/source/funkin/play/character/SparrowCharacter.hx b/source/funkin/play/character/SparrowCharacter.hx index 5835eed4c..e8191940c 100644 --- a/source/funkin/play/character/SparrowCharacter.hx +++ b/source/funkin/play/character/SparrowCharacter.hx @@ -26,6 +26,8 @@ class SparrowCharacter extends BaseCharacter loadAnimations(); playAnimation(_data.startingAnimation); + + super.onCreate(event); } function loadSpritesheet() @@ -50,11 +52,7 @@ class SparrowCharacter extends BaseCharacter this.antialiasing = true; } - if (_data.scale != null) - { - this.setGraphicSize(Std.int(this.width * this.scale.x)); - this.updateHitbox(); - } + this.setScale(_data.scale); } function loadAnimations() diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index efaa6f74f..5609ec48c 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -32,7 +32,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass /** * Offset the character's sprite by this much when playing each animation. */ - public var animationOffsets:Map> = new Map>(); + public var animationOffsets:Map> = new Map>(); /** * Add a suffix to the `idle` animation (or `danceLeft` and `danceRight` animations) @@ -47,23 +47,22 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass return value; } - /** - * Set this value to define an additional vertical offset to this sprite's position. - */ - public var yOffset:Float = 0; + private var animOffset(default, set):Array = [0, 0]; - override function set_y(value:Float):Float + function set_animOffset(value:Array) { - this.y = this.yOffset + value; - return this.y; - } + if (animOffset == null) + animOffset = [0, 0]; + if (animOffset == value) + return value; - function set_yOffset(value:Float):Float - { - var diff = value - this.yOffset; - this.yOffset = value; - this.y += diff; - return value; + var xDiff = animOffset[0] - value[0]; + var yDiff = animOffset[1] - value[1]; + + this.x += xDiff; + this.y += yDiff; + + return animOffset = value; } /** @@ -193,11 +192,11 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass var offsets = animationOffsets.get(name); if (offsets != null) { - this.offset.set(offsets[0], offsets[1]); + this.animOffset = offsets; } else { - this.offset.set(0, 0); + this.animOffset = [0, 0]; } } diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 007933595..e04ca0092 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -211,7 +211,6 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte public function refresh() { sort(SortUtil.byZIndex, FlxSort.ASCENDING); - trace('Stage sorted by z-index'); } /** @@ -242,8 +241,6 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte if (character == null) return; - var debugMarker:FlxSprite = new FlxSprite(0, 0); - // Apply position and z-index. switch (charType) { @@ -251,40 +248,24 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte this.characters.set("bf", character); character.zIndex = _data.characters.bf.zIndex; // Subtracting the origin ensures characters are positioned relative to their feet. - trace('Data: ' + _data.characters.bf.position[0] + ', ' + _data.characters.bf.position[1]); character.x = _data.characters.bf.position[0] - character.characterOrigin.x; character.y = _data.characters.bf.position[1] - character.characterOrigin.y; - trace('Character position: ' + character.x + ', ' + character.y); - debugMarker.x = _data.characters.bf.position[0]; - debugMarker.y = _data.characters.bf.position[1]; - trace('Debug marker position: ' + debugMarker.x + ', ' + debugMarker.y); case GF: this.characters.set("gf", character); character.zIndex = _data.characters.gf.zIndex; // Subtracting the origin ensures characters are positioned relative to their feet. character.x = _data.characters.gf.position[0] - character.characterOrigin.x; character.y = _data.characters.gf.position[1] - character.characterOrigin.y; - debugMarker.x = _data.characters.gf.position[0]; - debugMarker.y = _data.characters.gf.position[1]; case DAD: this.characters.set("dad", character); character.zIndex = _data.characters.dad.zIndex; // Subtracting the origin ensures characters are positioned relative to their feet. character.x = _data.characters.dad.position[0] - character.characterOrigin.x; character.y = _data.characters.dad.position[1] - character.characterOrigin.y; - debugMarker.x = _data.characters.dad.position[0]; - debugMarker.y = _data.characters.dad.position[1]; default: this.characters.set(character.characterId, character); } - #if debug - // Add a DEBUG marker for the character's origin. - debugMarker.makeGraphic(10, 10, 0xFFFF00FF); - debugMarker.zIndex = 10000; - this.add(debugMarker); - #end - // Add the character to the scene. this.add(character); } diff --git a/source/funkin/play/stage/StageData.hx b/source/funkin/play/stage/StageData.hx index 475f4ad9c..ae625c627 100644 --- a/source/funkin/play/stage/StageData.hx +++ b/source/funkin/play/stage/StageData.hx @@ -223,10 +223,9 @@ class StageDataParser input.cameraZoom = DEFAULT_CAMERAZOOM; } - if (input.props == null || input.props.length == 0) + if (input.props == null) { - trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing props'); - return null; + input.props = []; } for (inputProp in input.props) diff --git a/source/funkin/shaderslmfao/BuildingShaders.hx b/source/funkin/shaderslmfao/BuildingShaders.hx index 673429b61..62b9b0154 100644 --- a/source/funkin/shaderslmfao/BuildingShaders.hx +++ b/source/funkin/shaderslmfao/BuildingShaders.hx @@ -4,79 +4,28 @@ import flixel.system.FlxAssets.FlxShader; class BuildingShaders { - public var shader(default, null):BuildingShaderLegacy; + public var shader(default, null):BuildingShader; + public var daAlpha:Float = 1; public function new():Void { - shader = new BuildingShaderLegacy(); - shader.buildingAlpha = 0; + shader = new BuildingShader(); + shader.alphaShit.value = [0]; } public function update(elapsed:Float):Void { - shader.buildingAlpha += elapsed; + shader.alphaShit.value[0] += elapsed; } public function reset() { - shader.buildingAlpha = 0; + shader.alphaShit.value[0] = 0; } } -class BuildingShader extends FlxRuntimeShader +class BuildingShader extends FlxShader { - public var buildingAlpha(get, set):Float; - - function get_buildingAlpha():Float - { - return getFloat('alphaShit'); - } - - function set_buildingAlpha(value:Float):Float - { - // Every time buildingAlpha is changed, update the property of the shader. - setFloat('alphaShit', value); - return value; - } - - static final FRAGMENT_SHADER = " - #pragma header - - uniform float alphaShit; - - void main() - { - vec4 color = flixel_texture2D(bitmap, openfl_TextureCoordv); - - if (color.a > 0.0) - color -= alphaShit; - - gl_FragColor = color; - } - "; - - public function new() - { - super(FRAGMENT_SHADER, null, true); - } -} - -class BuildingShaderLegacy extends FlxShader -{ - public var buildingAlpha(get, set):Float; - - function get_buildingAlpha():Float - { - return alphaShit.value[0]; - } - - function set_buildingAlpha(value:Float):Float - { - // Every time buildingAlpha is changed, update the property of the shader. - alphaShit.value = [value]; - return value; - } - @:glFragmentSource(' #pragma header diff --git a/source/funkin/ui/PreferencesMenu.hx b/source/funkin/ui/PreferencesMenu.hx index 6fcf7b57a..d6faf7b27 100644 --- a/source/funkin/ui/PreferencesMenu.hx +++ b/source/funkin/ui/PreferencesMenu.hx @@ -157,16 +157,17 @@ class PreferencesMenu extends Page }); } - private static function preferenceCheck(prefString:String, prefValue:Dynamic):Void + private static function preferenceCheck(prefString:String, defaultValue:Dynamic):Void { if (preferences.get(prefString) == null) { - preferences.set(prefString, prefValue); - trace('set preference!'); + // Set the value to default. + preferences.set(prefString, defaultValue); + trace('Set preference to default: ${prefString} = ${defaultValue}'); } else { - trace('found preference: ' + preferences.get(prefString)); + trace('Found preference: ${prefString} = ${preferences.get(prefString)}'); } } } diff --git a/source/funkin/ui/animDebugShit/DebugBoundingState.hx b/source/funkin/ui/animDebugShit/DebugBoundingState.hx index 10ecd1054..19012a500 100644 --- a/source/funkin/ui/animDebugShit/DebugBoundingState.hx +++ b/source/funkin/ui/animDebugShit/DebugBoundingState.hx @@ -1,5 +1,7 @@ package funkin.ui.animDebugShit; +import funkin.play.character.CharacterData.CharacterDataParser; +import funkin.play.character.SparrowCharacter; import flixel.addons.display.FlxGridOverlay; import flixel.addons.ui.FlxInputText; import flixel.addons.ui.FlxUIDropDownMenu; @@ -392,7 +394,7 @@ class DebugBoundingState extends FlxState if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.UP || FlxG.keys.justPressed.DOWN) { var animName = animDropDownMenu.selectedLabel; - var coolValues:Array = swagChar.animationOffsets.get(animName); + var coolValues:Array = swagChar.animationOffsets.get(animName); var multiplier:Int = 5; @@ -443,7 +445,7 @@ class DebugBoundingState extends FlxState swagChar.destroy(); } - swagChar = new BaseCharacter(char); + swagChar = CharacterDataParser.fetchCharacter(char); swagChar.x = 100; swagChar.y = 100; // swagChar.debugMode = true;