343 lines
9.5 KiB
Plaintext
Executable File
343 lines
9.5 KiB
Plaintext
Executable File
import funkin.play.character.MultiSparrowCharacter;
|
|
import funkin.play.character.CharacterType;
|
|
import funkin.play.PlayState;
|
|
import funkin.play.GameOverSubState;
|
|
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
|
import flixel.util.FlxTimer;
|
|
import funkin.graphics.FunkinSprite;
|
|
import funkin.audio.FunkinSound;
|
|
import funkin.Paths;
|
|
import flixel.FlxSprite;
|
|
import flixel.FlxG;
|
|
import funkin.modding.base.ScriptedFunkinSprite;
|
|
import flixel.group.FlxTypedSpriteGroup;
|
|
import flixel.effects.FlxFlicker;
|
|
import funkin.play.PauseSubState;
|
|
import funkin.modding.module.ModuleHandler;
|
|
import flixel.tweens.FlxTween;
|
|
|
|
|
|
class PicoPlayerCharacter extends MultiSparrowCharacter {
|
|
function new() {
|
|
super('pico-playable');
|
|
}
|
|
|
|
function onCreate(event:ScriptEvent) {
|
|
super.onCreate(event);
|
|
|
|
// NOTE: this.x and this.y are not properly set here.
|
|
|
|
GameOverSubState.musicSuffix = '-pico';
|
|
GameOverSubState.blueBallSuffix = '-pico';
|
|
|
|
PauseSubState.musicSuffix = '-pico';
|
|
}
|
|
|
|
var deathSpriteRetry:FunkinSprite;
|
|
var deathSpriteNene:FunkinSprite;
|
|
|
|
var picoFade:FunkinSprite;
|
|
|
|
/**
|
|
* Initialize and cache sprites used for the death animation,
|
|
* for use later.
|
|
*/
|
|
function createDeathSprites() {
|
|
deathSpriteRetry = FunkinSprite.createSparrow(0, 0, "characters/Pico_Death_Retry");
|
|
deathSpriteRetry.animation.addByPrefix('idle', "Retry Text Loop0", 24, true);
|
|
deathSpriteRetry.animation.addByPrefix('confirm', "Retry Text Confirm0", 24, false);
|
|
|
|
deathSpriteRetry.visible = false;
|
|
|
|
//FlxG.debugger.track(deathSpriteRetry);
|
|
|
|
deathSpriteNene = FunkinSprite.createSparrow(0, 0, "characters/NeneKnifeToss");
|
|
var gf = PlayState.instance.currentStage.getGirlfriend();
|
|
deathSpriteNene.x = gf.originalPosition.x + 280;
|
|
deathSpriteNene.y = gf.originalPosition.y + 70;
|
|
deathSpriteNene.animation.addByPrefix('throw', "knife toss0", 24, false);
|
|
deathSpriteNene.visible = true;
|
|
deathSpriteNene.animation.finishCallback = function(name:String) {
|
|
deathSpriteNene.visible = false;
|
|
}
|
|
}
|
|
|
|
function onNoteHit(event:HitNoteScriptEvent)
|
|
{
|
|
if (event.eventCanceled) {
|
|
// onNoteHit event was cancelled by the gameplay module.
|
|
return;
|
|
}
|
|
|
|
if (event.note.noteData.getMustHitNote() && characterType == CharacterType.BF) {
|
|
// Override the hit note animation.
|
|
switch(event.note.kind) {
|
|
case "weekend-1-cockgun": // HE'S PULLING HIS COCK OUT
|
|
holdTimer = 0;
|
|
playCockGunAnim();
|
|
case "weekend-1-firegun":
|
|
holdTimer = 0;
|
|
playFireGunAnim();
|
|
default:
|
|
super.onNoteHit(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onNoteMiss(event:NoteScriptEvent)
|
|
{
|
|
// Override the miss note animation.
|
|
switch(event.note.kind) {
|
|
case "weekend-1-cockgun":
|
|
//playCockMissAnim();
|
|
case "weekend-1-firegun":
|
|
playCanExplodeAnim();
|
|
default:
|
|
super.onNoteMiss(event);
|
|
}
|
|
}
|
|
|
|
function playAnimation(name:String, restart:Bool, ignoreOther:Bool) {
|
|
if (name == "firstDeath") {
|
|
if (GameOverSubState.blueBallSuffix == '-pico-explode') {
|
|
// Explosion death animation.
|
|
doExplosionDeath();
|
|
} else {
|
|
// Standard death animation.
|
|
createDeathSprites();
|
|
|
|
GameOverSubState.instance.add(deathSpriteRetry);
|
|
GameOverSubState.instance.add(deathSpriteNene);
|
|
deathSpriteNene.animation.play("throw");
|
|
}
|
|
} else if (name == "deathConfirm") {
|
|
if (picoDeathExplosion != null) {
|
|
doExplosionConfirm();
|
|
} else {
|
|
deathSpriteRetry.animation.play('confirm');
|
|
// I think the glow makes the overall animation larger,
|
|
// but a plain FlxSprite doesn't have an animation offset option so we do it manually.
|
|
deathSpriteRetry.x -= 250;
|
|
deathSpriteRetry.y -= 200;
|
|
|
|
// Skip playing the animation.
|
|
return;
|
|
}
|
|
}
|
|
|
|
super.playAnimation(name, restart, ignoreOther);
|
|
}
|
|
|
|
var picoFlicker:FlxFlicker = null;
|
|
|
|
override function onAnimationFinished(name:String) {
|
|
super.onAnimationFinished(name);
|
|
|
|
if (name == 'shootMISS' && PlayState.instance.health > 0.0 && !PlayState.instance.isPlayerDying) {
|
|
// ERIC: You have to use super instead of this or it breaks.
|
|
// This is because typeof(this) is PolymodAbstractClass.
|
|
picoFlicker = FlxFlicker.flicker(super, 1, 1 / 30, true, true, function(_) {
|
|
picoFlicker = FlxFlicker.flicker(super, 0.5, 1 / 60, true, true, function(_) {
|
|
picoFlicker = null;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
public override function onPause(event:PauseScriptEvent) {
|
|
super.onPause(event);
|
|
|
|
if (picoFlicker != null) {
|
|
picoFlicker.pause();
|
|
this.visible = true;
|
|
}
|
|
}
|
|
|
|
public override function onResume(event:ScriptEvent) {
|
|
super.onResume(event);
|
|
|
|
if (picoFlicker != null) {
|
|
picoFlicker.resume();
|
|
}
|
|
}
|
|
|
|
public override function getDeathCameraOffsets():Array<Float> {
|
|
var result = super.getDeathCameraOffsets();
|
|
|
|
if (GameOverSubState.blueBallSuffix == '-pico-explode') {
|
|
return [result[0], result[1] + 100];
|
|
}
|
|
|
|
return [result[0], result[1]];
|
|
}
|
|
|
|
var picoDeathExplosion:FlxAtlasSprite;
|
|
|
|
function doExplosionDeath() {
|
|
|
|
if (picoFlicker != null) {
|
|
picoFlicker.stop(); // this sets visible to true, but we make it false a few lines down anyways
|
|
}
|
|
|
|
// Suffixed death sound will already play.
|
|
GameOverSubState.instance.resetCameraZoom();
|
|
|
|
// Move the camera up.
|
|
GameOverSubState.instance.cameraFollowPoint.y -= 100;
|
|
|
|
var picoDeathExplosionPath = Paths.animateAtlas("characters/picoExplosionDeath", "weekend1");
|
|
picoDeathExplosion = new FlxAtlasSprite(this.x - 640, this.y - 340, picoDeathExplosionPath);
|
|
PlayState.instance.subState.add(picoDeathExplosion);
|
|
picoDeathExplosion.zIndex = 1000;
|
|
picoDeathExplosion.onAnimationFinish.add(onExplosionFinishAnim);
|
|
picoDeathExplosion.visible = true;
|
|
this.visible = false;
|
|
|
|
new FlxTimer().start(3.0, afterPicoDeathExplosionIntro);
|
|
|
|
picoDeathExplosion.playAnimation('intro');
|
|
|
|
}
|
|
|
|
var singed:FunkinSound;
|
|
function afterPicoDeathExplosionIntro(timer:FlxTimer) {
|
|
// Start the (standard) death music, 3.5 seconds after the explosion starts,
|
|
// not when the explosion sound finishes or when the loop starts.
|
|
GameOverSubState.instance.startDeathMusic(1.0, false);
|
|
singed = FunkinSound.load(Paths.sound('singed_loop'), true, false, true);
|
|
// singed.fadeIn(0.5, 0.3, 1.0);
|
|
}
|
|
|
|
function doExplosionConfirm() {
|
|
// Suffixed confirm music will already play.
|
|
picoDeathExplosion.playAnimation('Confirm');
|
|
if (singed != null) {
|
|
singed.stop();
|
|
singed = null;
|
|
}
|
|
}
|
|
|
|
function onExplosionFinishAnim(animLabel:String) {
|
|
if (animLabel == 'intro') {
|
|
picoDeathExplosion.playAnimation('Loop Start', true, false, true);
|
|
} else if (animLabel == 'Confirm') {
|
|
// Do nothing, the animation will just play.
|
|
}
|
|
}
|
|
|
|
override function onGameOver(event:ScriptEvent):Void {
|
|
super.onGameOver(event);
|
|
}
|
|
|
|
override function onSongRetry(event:ScriptEvent):Void {
|
|
super.onSongRetry(event);
|
|
|
|
// Don't let these pile up.
|
|
clearCasings();
|
|
|
|
// Reset to standard death animation.
|
|
GameOverSubState.musicSuffix = '-pico';
|
|
GameOverSubState.blueBallSuffix = '-pico';
|
|
|
|
PauseSubState.musicSuffix = '-pico';
|
|
|
|
picoDeathExplosion = null;
|
|
this.visible = true;
|
|
}
|
|
|
|
function onAnimationFrame(name:String, frameNumber:Int, frameIndex:Int) {
|
|
super.onAnimationFrame(name, frameNumber, frameIndex);
|
|
|
|
if (name == "firstDeath" && frameNumber == 36 - 1) {
|
|
deathSpriteRetry.animation.play('idle');
|
|
deathSpriteRetry.visible = true;
|
|
GameOverSubState.instance.startDeathMusic(1.0, false);
|
|
// force the deathloop to play in here, since we are starting the music early it
|
|
// doesn't check this in gameover substate !
|
|
// also no animation suffix 🤔
|
|
GameOverSubState.instance.boyfriend.playAnimation('deathLoop');
|
|
|
|
deathSpriteRetry.x = this.x + 416;
|
|
deathSpriteRetry.y = this.y + 42;
|
|
}
|
|
|
|
if (name == "cock" && frameNumber == 3) {
|
|
createCasing();
|
|
}
|
|
}
|
|
|
|
var casingGroup:FlxTypedSpriteGroup;
|
|
|
|
function createCasing() {
|
|
if (casingGroup == null) {
|
|
casingGroup = new FlxTypedSpriteGroup();
|
|
casingGroup.x = this.x + 250;
|
|
casingGroup.y = this.y + 100;
|
|
casingGroup.zIndex = 1000;
|
|
addToStage(casingGroup);
|
|
}
|
|
|
|
var casing = ScriptedFunkinSprite.init('CasingSprite', 0, 0);
|
|
if (casing != null)
|
|
casingGroup.add(casing);
|
|
}
|
|
|
|
function clearCasings() {
|
|
// Clear the casing group.
|
|
if (casingGroup != null) {
|
|
casingGroup.clear();
|
|
casingGroup = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play the animation where Pico readies his gun to shoot the can.
|
|
*/
|
|
function playCockGunAnim() {
|
|
this.playAnimation('cock', true, true);
|
|
|
|
picoFade = new FlxSprite(0, 0);
|
|
picoFade.frames = this.frames;
|
|
picoFade.frame = this.frame;
|
|
picoFade.updateHitbox();
|
|
picoFade.x = this.x;
|
|
picoFade.y = this.y;
|
|
// picoFade.stamp(this, 0, 0);
|
|
picoFade.alpha = 0.3;
|
|
picoFade.zIndex = this.zIndex - 3;
|
|
addToStage(picoFade);
|
|
FlxTween.tween(picoFade.scale, {x: 1.3, y: 1.3}, 0.4);
|
|
FlxTween.tween(picoFade, {alpha: 0}, 0.4);
|
|
|
|
|
|
FunkinSound.playOnce(Paths.sound('Gun_Prep'), 1.0);
|
|
}
|
|
/**
|
|
* Play the animation where Pico shoots the can successfully.
|
|
*/
|
|
function playFireGunAnim(hip:Bool) {
|
|
this.playAnimation('shoot', true, true);
|
|
FunkinSound.playOnce(Paths.soundRandom('shot', 1, 4));
|
|
}
|
|
/**
|
|
* Play the animation where Pico is hit by the exploding can.
|
|
*/
|
|
function playCanExplodeAnim() {
|
|
|
|
this.playAnimation('shootMISS', true, true);
|
|
// Donk.
|
|
FunkinSound.playOnce(Paths.sound('Pico_Bonk'), 1.0);
|
|
}
|
|
|
|
function addToStage(sprite:FlxSprite) {
|
|
if (this.debug) {
|
|
// We are in the chart editor or something.
|
|
// TODO: Make this work properly.
|
|
} else if (PlayState.instance != null && PlayState.instance.currentStage != null) {
|
|
PlayState.instance.currentStage.add(sprite);
|
|
} else {
|
|
trace('Could not add Pico sprite to stage.');
|
|
}
|
|
}
|
|
}
|