410 lines
10 KiB
Plaintext
Executable File
410 lines
10 KiB
Plaintext
Executable File
import flixel.FlxG;
|
|
import flixel.FlxSprite;
|
|
import flixel.math.FlxBasePoint;
|
|
import flixel.tweens.FlxEase;
|
|
import flixel.tweens.FlxTween;
|
|
import flixel.util.FlxTimer;
|
|
import funkin.audio.FunkinSound;
|
|
import funkin.Conductor;
|
|
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
|
import funkin.graphics.FunkinSprite;
|
|
import funkin.modding.base.ScriptedFlxAtlasSprite;
|
|
import funkin.Paths;
|
|
import funkin.play.cutscene.CutsceneType;
|
|
import funkin.play.cutscene.VideoCutscene;
|
|
import funkin.play.GameOverSubState;
|
|
import funkin.play.PlayState;
|
|
import funkin.play.PlayStatePlaylist;
|
|
import funkin.play.song.Song;
|
|
import funkin.play.stage.StageProp;
|
|
import funkin.save.Save;
|
|
|
|
// We have to use FlxBasePoint in scripts because FlxPoint is inlined and not available in scripts
|
|
|
|
class TwoHOTSong extends Song
|
|
{
|
|
var hasPlayedCutscene:Bool = false;
|
|
//var tempConductor:Conductor;
|
|
var cutsceneMusic:FunkinSound;
|
|
|
|
function new()
|
|
{
|
|
super('2hot');
|
|
|
|
hasPlayedCutscene = false;
|
|
}
|
|
|
|
/**
|
|
* Health lost when hit by can.
|
|
*/
|
|
var HEALTH_LOSS = 0.25 * 2;
|
|
|
|
public function listDifficulties(variationId:String, variationIds:Array<String>, showLocked:Bool):Array<String> {
|
|
if (showLocked || Save.instance.hasBeatenLevel('weekend1')) {
|
|
return super.listDifficulties(variationId, variationIds);
|
|
}
|
|
|
|
// Hide all difficulties if the player has not beaten the week.
|
|
return [];
|
|
}
|
|
|
|
public override function onCountdownStart(event:CountdownScriptEvent):Void {
|
|
super.onCountdownStart(event);
|
|
}
|
|
|
|
function onSongRetry(event:ScriptEvent)
|
|
{
|
|
super.onSongRetry(event);
|
|
|
|
removeCans();
|
|
gunCocked = false;
|
|
}
|
|
|
|
public override function onSongEnd(event:CountdownScriptEvent):Void {
|
|
super.onSongEnd(event);
|
|
if (!PlayStatePlaylist.isStoryMode) hasPlayedCutscene = true;
|
|
|
|
if (!hasPlayedCutscene) {
|
|
hasPlayedCutscene = true;
|
|
|
|
event.cancel();
|
|
|
|
// start the video cutscene and hide it so the other stuff can happen after
|
|
startCutscene();
|
|
} else {
|
|
// Make sure the cutscene can play again next time!
|
|
hasPlayedCutscene = false;
|
|
// DO NOT CANCEL THE EVENT!
|
|
}
|
|
}
|
|
|
|
// the Other stuff
|
|
function endCutscene(){
|
|
VideoCutscene.onVideoStarted.removeAll();
|
|
VideoCutscene.hideVideo();
|
|
|
|
new FlxTimer().start(1, function(tmr)
|
|
{
|
|
PlayState.instance.tweenCameraToPosition(1539, 833.5, 2, FlxEase.quadInOut);
|
|
PlayState.instance.tweenCameraZoom(0.69, 2, true, FlxEase.quadInOut);
|
|
});
|
|
|
|
// Since no music plays at this part I'm too lazy to hook Nene up to a conductor,
|
|
// so we just dance on a timer here.
|
|
new FlxTimer().start(0.5, function(tmr)
|
|
{
|
|
PlayState.instance.currentStage.getGirlfriend().dance(true);
|
|
}, 10);
|
|
|
|
new FlxTimer().start(2, function(tmr)
|
|
{
|
|
PlayState.instance.currentStage.getBoyfriend().playAnimation('intro1', true, true);
|
|
});
|
|
new FlxTimer().start(2.5, function(tmr)
|
|
{
|
|
PlayState.instance.currentStage.getDad().playAnimation('pissed', true, true);
|
|
});
|
|
|
|
new FlxTimer().start(6, function(tmr)
|
|
{
|
|
// video would play around here
|
|
//PlayState.instance.endSong(true);
|
|
|
|
trace('Pausing ending to play a video cutscene (`2hot`)');
|
|
|
|
// Add a black background behind the cutscene to fix a transition bug!
|
|
trace('Adding black background behind cutscene over UI');
|
|
var bgSprite = new FunkinSprite(-100, -100);
|
|
bgSprite.makeSolidColor(2000, 2500, 0xFF000000);
|
|
bgSprite.cameras = [PlayState.instance.camHUD]; // Show over the HUD.
|
|
bgSprite.zIndex = 1000000;
|
|
PlayState.instance.add(bgSprite);
|
|
PlayState.instance.refresh();
|
|
|
|
VideoCutscene.showVideo();
|
|
});
|
|
|
|
}
|
|
|
|
function startCutscene(){
|
|
VideoCutscene.onVideoStarted.add(endCutscene);
|
|
PlayState.instance.camHUD.visible = false;
|
|
|
|
PlayState.instance.isInCutscene = true;
|
|
hasPlayedCutscene = true;
|
|
|
|
PlayState.instance.currentStage.getBoyfriend().danceEvery = 0;
|
|
PlayState.instance.currentStage.getDad().danceEvery = 0;
|
|
|
|
startVideo();
|
|
}
|
|
|
|
function startVideo() {
|
|
VideoCutscene.play(Paths.videos('2hotCutscene'), CutsceneType.ENDING);
|
|
}
|
|
|
|
function removeCans()
|
|
{
|
|
for (can in spawnedCans)
|
|
{
|
|
can.kill();
|
|
}
|
|
spawnedCans = [];
|
|
}
|
|
|
|
function onStateChangeEnd(event:StateChangeScriptEvent)
|
|
{
|
|
super.onStateChangeEnd(event);
|
|
|
|
if ((Std.isOfType(event.targetState, PlayState)))
|
|
{
|
|
return;
|
|
}
|
|
hardClear();
|
|
}
|
|
|
|
function hardClear()
|
|
{
|
|
removeCans();
|
|
gunCocked = false;
|
|
}
|
|
|
|
var gunCocked:Bool = false;
|
|
var spawnedCans:Array<ScriptedFlxAtlasSprite> = [];
|
|
|
|
function onNoteHit(event:HitNoteScriptEvent)
|
|
{
|
|
super.onNoteHit(event);
|
|
if (PlayState.instance.currentStage == null) return;
|
|
|
|
switch (event.note.kind)
|
|
{
|
|
case "weekend-1-lightcan":
|
|
// Do nothing, but place this such that the animation plays at the right time.
|
|
case "weekend-1-kickcan":
|
|
// This creates the can and starts the animation.
|
|
// We define the behavior of the can in a separate scripted class,
|
|
// which allows the can to track and manage its own properties.
|
|
var newCan:ScriptedFlxAtlasSprite = ScriptedFlxAtlasSprite.init('SpraycanAtlasSprite', 0, 0);
|
|
|
|
var spraycanPile = PlayState.instance.currentStage.getNamedProp('spraycanPile');
|
|
|
|
newCan.x = spraycanPile.x - 430;
|
|
newCan.y = spraycanPile.y - 840;
|
|
newCan.zIndex = spraycanPile.zIndex - 1;
|
|
|
|
newCan.scriptCall('playCanStart');
|
|
|
|
PlayState.instance.currentStage.add(newCan);
|
|
PlayState.instance.currentStage.refresh(); // Apply z-index.
|
|
spawnedCans.push(newCan);
|
|
case "weekend-1-kneecan":
|
|
// Do nothing, but place this such that the animation plays at the right time.
|
|
case "weekend-1-cockgun": // lol
|
|
gunCocked = true;
|
|
new FlxTimer().start(1.0, function()
|
|
{
|
|
gunCocked = false;
|
|
});
|
|
case "weekend-1-firegun":
|
|
if (gunCocked)
|
|
{
|
|
trace('Firing gun!');
|
|
shootNextCan();
|
|
}
|
|
else
|
|
{
|
|
trace('Cannot fire gun!');
|
|
// The player cannot hit this note.
|
|
event.cancelEvent();
|
|
}
|
|
}
|
|
}
|
|
|
|
public var STATE_ARCING:Int = 2; // In the air.
|
|
public var STATE_SHOT:Int = 3; // Hit by the player.
|
|
public var STATE_IMPACTED:Int = 4; // Impacted the player.
|
|
|
|
function getNextCanWithState(desiredState:Int)
|
|
{
|
|
for (index in 0...spawnedCans.length)
|
|
{
|
|
var can = spawnedCans[index];
|
|
var canState = can.scriptGet('currentState');
|
|
|
|
if (canState == desiredState)
|
|
{
|
|
// Return the can we found.
|
|
return can;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function onUpdate(event:UpdateScriptEvent) {
|
|
super.onUpdate(event);
|
|
}
|
|
|
|
function darkenStageProps()
|
|
{
|
|
// Darken the background, then fade it back.
|
|
for (stageProp in PlayState.instance.currentStage.members)
|
|
{
|
|
// Determine if the stage prop is something that should be excluded from darkening.
|
|
if (Std.isOfType(stageProp, StageProp)) {
|
|
if (stageProp.name == "bf" || stageProp.name == "dad" || stageProp.name == "gf") // This refers to the player.
|
|
{
|
|
// Exclude.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Select cans.
|
|
if (spawnedCans.contains(stageProp)) {
|
|
// Exclude.
|
|
continue;
|
|
}
|
|
|
|
// Hacky way of selecting PicoPlayable.picoFade.
|
|
if (stageProp.zIndex == (PlayState.instance.currentStage.getBoyfriend().zIndex - 3)) {
|
|
// Exclude.
|
|
continue;
|
|
}
|
|
|
|
// If not excluded, darken.
|
|
stageProp.color = 0xFF111111;
|
|
new FlxTimer().start(1/24, (tmr) ->
|
|
{
|
|
stageProp.color = 0xFF222222;
|
|
FlxTween.color(stageProp, 1.4, 0xFF222222, 0xFFFFFFFF);
|
|
});
|
|
}
|
|
}
|
|
|
|
function blackenStageProps()
|
|
{
|
|
// Blacken the background (also Darnell and Nene) entirely, then restore it once the gameOverSubState is up.
|
|
for (stageProp in PlayState.instance.currentStage.members)
|
|
{
|
|
// Determine if the stage prop is something that should be excluded from blackening.
|
|
if (Std.isOfType(stageProp, StageProp)) {
|
|
if (stageProp.name == "bf") // This refers to the player.
|
|
{
|
|
// Exclude.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Select cans.
|
|
if (spawnedCans.contains(stageProp)) {
|
|
// Exclude.
|
|
continue;
|
|
}
|
|
|
|
// If not excluded, blacken.
|
|
stageProp.color = 0xFF000000;
|
|
new FlxTimer().start(1.0, (tmr) ->
|
|
{
|
|
stageProp.color = 0xFFFFFFFF;
|
|
});
|
|
}
|
|
}
|
|
|
|
function shootNextCan()
|
|
{
|
|
var can = getNextCanWithState(STATE_ARCING);
|
|
|
|
if (can != null)
|
|
{
|
|
can.scriptSet('currentState', STATE_SHOT);
|
|
can.scriptCall('playCanShot');
|
|
|
|
new FlxTimer().start(1/24, function(tmr)
|
|
{
|
|
darkenStageProps();
|
|
});
|
|
|
|
}
|
|
}
|
|
|
|
function missNextCan()
|
|
{
|
|
var can = getNextCanWithState(STATE_ARCING);
|
|
|
|
if (can != null)
|
|
{
|
|
can.scriptSet('currentState', STATE_IMPACTED);
|
|
}
|
|
}
|
|
|
|
function spawnImpactParticle()
|
|
{
|
|
var impactParticle = FunkinSprite.createSparrow(0, 0, 'CanImpactParticle');
|
|
impactParticle.animation.addByPrefix('idle', 'CanImpactParticle0', 24, false);
|
|
impactParticle.animation.play('idle');
|
|
impactParticle.x = PlayState.instance.currentStage.getBoyfriend().x + 400;
|
|
impactParticle.y = PlayState.instance.currentStage.getBoyfriend().y - 200;
|
|
PlayState.instance.currentStage.add(impactParticle);
|
|
|
|
impactParticle.animation.finishCallback = function()
|
|
{
|
|
impactParticle.kill();
|
|
};
|
|
}
|
|
|
|
function onNoteMiss(event:NoteScriptEvent)
|
|
{
|
|
super.onNoteMiss(event.note);
|
|
|
|
trace('Missed note on 2hot stage...' + event.note.noteData);
|
|
|
|
switch (event.note.kind)
|
|
{
|
|
case "weekend-1-cockgun":
|
|
event.healthChange = 0.0; // We cause health loss later.
|
|
case "weekend-1-firegun":
|
|
gunCocked = false;
|
|
event.healthChange = 0.0; // We cause health loss elsewhere.
|
|
missNextCan();
|
|
takeCanDamage();
|
|
case "weekend-1-firegun-hip":
|
|
gunCocked = false;
|
|
event.healthChange = 0.0; // We cause health loss elsewhere.
|
|
missNextCan();
|
|
takeCanDamage();
|
|
case "weekend-1-firegun-far":
|
|
gunCocked = false;
|
|
event.healthChange = 0.0; // We cause health loss elsewhere.
|
|
missNextCan();
|
|
takeCanDamage();
|
|
}
|
|
}
|
|
|
|
function takeCanDamage():Void {
|
|
trace('Taking damage from can exploding!');
|
|
PlayState.instance.health -= HEALTH_LOSS;
|
|
// TODO: This is jank as hell! Add some better way to prevent onNoteMiss's normal health loss.
|
|
// PlayState.instance.health += 0.0775;
|
|
|
|
if (PlayState.instance.health <= 0) {
|
|
trace('Died to the can! Use special death animation.');
|
|
|
|
// Reset to standard death animation.
|
|
GameOverSubState.musicSuffix = '-pico-explode';
|
|
GameOverSubState.blueBallSuffix = '-pico-explode';
|
|
|
|
blackenStageProps();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replay the cutscene after leaving the song.
|
|
*/
|
|
function onCreate(event:ScriptEvent):Void
|
|
{
|
|
super.onCreate(event);
|
|
|
|
hasPlayedCutscene = false;
|
|
}
|
|
}
|