import funkin.play.character.SparrowCharacter; import funkin.play.PlayState; import flixel.FlxG; import flixel.FlxSprite; import flixel.group.FlxTypedSpriteGroup; import funkin.graphics.FunkinSprite; import funkin.Paths; import funkin.modding.base.ScriptedFlxAtlasSprite; import funkin.modding.base.ScriptedFlxSprite; import funkin.modding.base.ScriptedFlxSpriteGroup; import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.audio.visualize.ABotVis; class NeneCharacter extends SparrowCharacter { function new() { super('nene'); } var pupilState:Int = 0; var PUPIL_STATE_NORMAL = 0; var PUPIL_STATE_LEFT = 1; var abot:FlxAtlasSprite; var abotViz:ABotVis; var stereoBG:FlxSprite; var eyeWhites:FlxSprite; var pupil:FlxAtlasSprite; function onCreate(event:ScriptEvent) { super.onCreate(event); stereoBG = new FlxSprite(0, 0, Paths.image('characters/abot/stereoBG')); eyeWhites = new FunkinSprite().makeSolidColor(160, 60); pupil = new FlxAtlasSprite(0, 0, Paths.animateAtlas("characters/abot/systemEyes", "shared")); pupil.x = this.x; pupil.y = this.y; pupil.zIndex = this.zIndex - 5; abot = ScriptedFlxAtlasSprite.init('ABotAtlasSprite', 0, 0); abot.x = this.x; abot.y = this.y; abot.zIndex = this.zIndex - 1; abotViz = new ABotVis(FlxG.sound.music); abotViz.x = this.x; abotViz.y = this.y; abotViz.zIndex = abot.zIndex + 1; FlxG.debugger.track(abotViz); } /** * At this amount of life, Nene will raise her knife. */ var VULTURE_THRESHOLD = 0.25 * 2; /** * Nene is in her default state. 'danceLeft' or 'danceRight' may be playing right now, * or maybe her 'combo' or 'drop' animations are active. * * Transitions: * If player health <= VULTURE_THRESHOLD, transition to STATE_PRE_RAISE. */ var STATE_DEFAULT = 0; /** * Nene has recognized the player is at low health, * but has to wait for the appropriate point in the animation to move on. * * Transitions: * If player health > VULTURE_THRESHOLD, transition back to STATE_DEFAULT without changing animation. * If current animation is combo or drop, transition when animation completes. * If current animation is danceLeft, wait until frame 14 to transition to STATE_RAISE. * If current animation is danceRight, wait until danceLeft starts. */ var STATE_PRE_RAISE = 1; /** * Nene is raising her knife. * When moving to this state, immediately play the 'raiseKnife' animation. * * Transitions: * Once 'raiseKnife' animation completes, transition to STATE_READY. */ var STATE_RAISE = 2; /** * Nene is holding her knife ready to strike. * During this state, hold the animation on the first frame, and play it at random intervals. * This makes the blink look less periodic. * * Transitions: * If the player runs out of health, move to the GameOverSubState. No transition needed. * If player health > VULTURE_THRESHOLD, transition to STATE_LOWER. */ var STATE_READY = 3; /** * Nene is raising her knife. * When moving to this state, immediately play the 'lowerKnife' animation. * * Transitions: * Once 'lowerKnife' animation completes, transition to STATE_DEFAULT. */ var STATE_LOWER = 4; /** * Nene's animations are tracked in a simple state machine. * Given the current state and an incoming event, the state changes. */ var currentState:Int = STATE_DEFAULT; /** * Nene blinks every X beats, with X being randomly generated each time. * This keeps the animation from looking too periodic. */ var MIN_BLINK_DELAY:Int = 3; var MAX_BLINK_DELAY:Int = 7; var blinkCountdown:Int = MIN_BLINK_DELAY; function dance(forceRestart:Bool) { //abot.playAnimation("", forceRestart); // Then, perform the appropriate animation for the current state. switch(currentState) { case STATE_DEFAULT: if (hasDanced) { playAnimation('danceRight', forceRestart); } else { playAnimation('danceLeft', forceRestart); } hasDanced = !hasDanced; case STATE_PRE_RAISE: playAnimation('danceLeft', false); hasDanced = false; case STATE_READY: if (blinkCountdown == 0) { playAnimation('idleKnife', false); blinkCountdown = FlxG.random.int(MIN_BLINK_DELAY, MAX_BLINK_DELAY); } else { blinkCountdown--; } default: // In other states, don't interrupt the existing animation. } } var refershedLol:Bool = false; /** * Called when the chart hits a song event. */ public override function onSongEvent(scriptEvent:SongEventScriptEvent) { super.onSongEvent(scriptEvent); if (scriptEvent.eventData.eventKind == "FocusCamera") { var eventProps = scriptEvent.eventData.value; switch (Std.parseInt(eventProps.char)) { case 0: movePupilsRight(); case 1: movePupilsLeft(); default: } } } function movePupilsLeft():Void { if (pupilState == PUPIL_STATE_LEFT) return; pupil.playAnimation(''); pupil.anim.curFrame = 0; // pupilState = PUPIL_STATE_LEFT; } function movePupilsRight():Void { if (pupilState == PUPIL_STATE_NORMAL) return; pupil.playAnimation(''); pupil.anim.curFrame = 17; // pupilState = PUPIL_STATE_NORMAL; } function moveByNoteKind(kind:String) { // Force ABot to look where the action is happening. switch(event.note.kind) { case "weekend-1-lightcan": movePupilsLeft(); case "weekend-1-kickcan": // movePupilsLeft(); case "weekend-1-kneecan": // movePupilsLeft(); case "weekend-1-cockgun": movePupilsRight(); case "weekend-1-firegun": // movePupilsRight(); default: // Nothing } } function onNoteHit(event:HitNoteScriptEvent) { super.onNoteHit(event); moveByNoteKind(event.note.kind); } function onNoteMiss(event:NoteScriptEvent) { super.onNoteMiss(event); moveByNoteKind(event.note.kind); } function onUpdate(event:UpdateScriptEvent) { super.onUpdate(event); // Set the visibility of ABot to match Nene's. abot.visible = this.visible; pupil.visible = this.visible; eyeWhites.visible = this.visible; stereoBG.visible = this.visible; if (pupil.anim.isPlaying) { switch (pupilState) { case PUPIL_STATE_NORMAL: if (pupil.anim.curFrame >= 17) { pupilState = PUPIL_STATE_LEFT; pupil.anim.pause(); } case PUPIL_STATE_LEFT: if (pupil.anim.curFrame >= 31) { pupilState = PUPIL_STATE_NORMAL; pupil.anim.pause(); } } } // refreshes just for the zIndex shit! if (!refershedLol) { abot.x = this.x - 100; abot.y = this.y + 316; // 764 - 740 abot.zIndex = this.zIndex - 10; PlayState.instance.currentStage.add(abot); abotViz.x = this.x + 100; abotViz.y = this.y + 400; abotViz.zIndex = abot.zIndex - 1; PlayState.instance.currentStage.add(abotViz); eyeWhites.x = abot.x + 40; eyeWhites.y = abot.y + 250; eyeWhites.zIndex = abot.zIndex - 10; PlayState.instance.currentStage.add(eyeWhites); pupil.x = this.x - 607; pupil.y = this.y - 176; pupil.zIndex = eyeWhites.zIndex + 5; PlayState.instance.currentStage.add(pupil); stereoBG.x = abot.x + 150; stereoBG.y = abot.y + 30; stereoBG.zIndex = abot.zIndex - 8; PlayState.instance.currentStage.add(stereoBG); PlayState.instance.currentStage.refresh(); refershedLol = true; } if (shouldTransitionState()) { transitionState(); } } public function onScriptEvent(event:ScriptEvent):Void { if (event.type == "SONG_START") { abotViz.snd = FlxG.sound.music; abotViz.initAnalyzer(); } } var animationFinished:Bool = false; function onAnimationFinished(name:String) { switch(currentState) { case STATE_RAISE: if (name == "raiseKnife") { animationFinished = true; transitionState(); } case STATE_LOWER: if (name == "lowerKnife") { animationFinished = true; transitionState(); } default: // Ignore. } } function onAnimationFrame(name:String, frameNumber:Int, frameIndex:Int) { super.onAnimationFrame(name, frameNumber, frameIndex); switch(currentState) { case STATE_PRE_RAISE: if (name == "danceLeft" && frameNumber == 14) { animationFinished = true; transitionState(); } default: // Ignore. } } function shouldTransitionState():Bool { return PlayState.instance.currentStage.getBoyfriend().characterId != "pico-blazin"; } function transitionState() { switch (currentState) { case STATE_DEFAULT: if (PlayState.instance.health <= VULTURE_THRESHOLD) { // trace('NENE: Health is low, transitioning to STATE_PRE_RAISE'); currentState = STATE_PRE_RAISE; } else { currentState = STATE_DEFAULT; } case STATE_PRE_RAISE: if (PlayState.instance.health > VULTURE_THRESHOLD) { // trace('NENE: Health went back up, transitioning to STATE_DEFAULT'); currentState = STATE_DEFAULT; } else if (animationFinished) { // trace('NENE: Animation finished, transitioning to STATE_RAISE'); currentState = STATE_RAISE; playAnimation('raiseKnife'); animationFinished = false; } case STATE_RAISE: if (animationFinished) { // trace('NENE: Animation finished, transitioning to STATE_READY'); currentState = STATE_READY; animationFinished = false; } case STATE_READY: if (PlayState.instance.health > VULTURE_THRESHOLD) { // trace('NENE: Health went back up, transitioning to STATE_LOWER'); currentState = STATE_LOWER; playAnimation('lowerKnife'); } case STATE_LOWER: if (animationFinished) { // trace('NENE: Animation finished, transitioning to STATE_DEFAULT'); currentState = STATE_DEFAULT; animationFinished = false; } default: // trace('UKNOWN STATE ' + currentState); currentState = STATE_DEFAULT; } } }