new chart system implementation progress

This commit is contained in:
Yoshman29
2023-02-20 14:06:47 +01:00
parent 8db770bc28
commit beda6f28c2
14 changed files with 451 additions and 1397 deletions
+1 -1
View File
@@ -1 +1 @@
2286
2313
+60 -6
View File
@@ -6,17 +6,23 @@ import openfl.utils.Assets;
class Chart {
public static function parse(songName:String, difficulty:String = "normal"):ChartData {
var chartPath = Paths.chart(songName, difficulty);
var base:ChartData = {
strumLines: [],
noteTypes: [],
events: [],
meta: {
name: songName,
bpm: 0
bpm: 0,
beatsPerMesure: 4,
stepsPerBeat: 4
},
scrollSpeed: 2,
stage: "stage",
codenameChart: true
codenameChart: true,
fromMods: Paths.assetsTree.existsSpecific(chartPath, "TEXT", MODS)
};
var chartPath = Paths.chart(songName, difficulty);
if (!Assets.exists(chartPath)) {
Logs.trace('Chart for song ${songName} ($difficulty) at "$chartPath" was not found.', ERROR, RED);
return base;
@@ -39,6 +45,7 @@ class Chart {
data = field;
}
base.scrollSpeed = data.speed;
base.stage = data.stage;
base.meta.name = data.song;
base.meta.bpm = data.bpm;
@@ -46,13 +53,25 @@ class Chart {
base.strumLines.push({
characters: [data.player2],
opponent: true,
position: "dad",
notes: []
});
base.strumLines.push({
characters: [data.player1],
opponent: false,
position: "boyfriend",
notes: []
});
if (data.gf != "none") {
base.strumLines.push({
characters: [data.gf != null ? data.gf : "gf"],
opponent: true,
position: "girlfriend",
notes: [],
visible: false,
strumLinePos: 0.5
});
}
var camFocusedBF:Bool = false;
var beatsPerMesure:Float = data.beatsPerMesure.getDefault(4);
@@ -61,10 +80,12 @@ class Chart {
var curCrochet:Float = ((60 / curBPM) * 1000);
if (data.notes != null) for(section in data.notes) {
if (section == null) continue; // Yoshi Engine charts crash fix
if (section == null) {
curTime += curCrochet * beatsPerMesure;
continue; // Yoshi Engine charts crash fix
}
if (camFocusedBF != (camFocusedBF = section.mustHitSection)) {
// TODO: auto-add event for cam movement
base.events.push({
time: curTime,
type: CAM_MOVEMENT,
@@ -73,10 +94,22 @@ class Chart {
}
if (section.sectionNotes != null) for(note in section.sectionNotes) {
if (note[1] < 0) continue;
var daStrumTime:Float = note[0];
var daNoteData:Int = Std.int(note[1] % 8);
var daNoteType:Int = Std.int(note[1] / 8);
var gottaHitNote:Bool = daNoteData >= 4 ? !section.mustHitSection : section.mustHitSection;
if (note.length > 2) {
if (note[3] is Int)
daNoteType = addNoteType(base, data.noteTypes[Std.int(note[3])-1]);
else if (note[3] is String)
daNoteType = addNoteType(base, note[3]);
} else {
daNoteType = addNoteType(base, data.noteTypes[daNoteType-1]);
}
base.strumLines[gottaHitNote ? 1 : 0].notes.push({
time: daStrumTime,
@@ -88,7 +121,7 @@ class Chart {
if (section.changeBPM && section.bpm != curBPM) {
// TODO: BPM CHANGE EVENT
curCrochet = ((60 / section.bpm) * 1000);
curCrochet = ((60 / (curBPM = section.bpm)) * 1000);
}
curTime += curCrochet * beatsPerMesure;
@@ -96,6 +129,19 @@ class Chart {
}
return base;
}
private static function addNoteType(chart:ChartData, noteTypeName:String):Int {
switch(noteTypeName.trim()) {
case "Default Note" | null | "":
return 0;
default:
var index = chart.noteTypes.indexOf(noteTypeName);
if (index > -1)
return index+1;
chart.noteTypes.push(noteTypeName);
return chart.noteTypes.length;
}
}
}
typedef ChartData = {
@@ -104,11 +150,17 @@ typedef ChartData = {
public var meta:ChartMetaData;
public var codenameChart:Bool;
public var stage:String;
public var scrollSpeed:Float;
public var fromMods:Bool;
public var noteTypes:Array<String>;
}
typedef ChartMetaData = {
public var name:String;
public var bpm:Float;
public var ?beatsPerMesure:Float;
public var ?stepsPerBeat:Float;
public var ?needsVoices:Bool;
public var ?icon:String;
public var ?color:Dynamic;
public var ?difficulties:Array<String>;
@@ -120,7 +172,9 @@ typedef ChartStrumLine = {
var characters:Array<String>;
var opponent:Bool;
var notes:Array<ChartNote>;
var position:String;
var ?strumLinePos:Float; // 0.25 = default opponent pos, 0.75 = default boyfriend pos
var ?visible:Null<Bool>;
}
typedef ChartNote = {
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -57,7 +57,7 @@ class GameOverSubstate extends MusicBeatSubstate
lossSFX = FlxG.sound.play(Paths.sound(lossSFXName));
Conductor.changeBPM(100);
DiscordUtil.changePresence('Game Over', PlayState.SONG.song + " (" + PlayState.difficulty + ")");
DiscordUtil.changePresence('Game Over', PlayState.SONG.meta.name + " (" + PlayState.difficulty + ")");
}
override function update(elapsed:Float)
+22 -20
View File
@@ -1,5 +1,6 @@
package funkin.game;
import funkin.chart.Chart.ChartNote;
import openfl.utils.Assets;
import flixel.FlxG;
import flixel.FlxCamera;
@@ -19,17 +20,21 @@ class Note extends FlxSprite
public var strumTime:Float = 0;
public var mustPress(get, never):Bool;
public var strumLine(get, never):StrumLine;
private function get_strumLine() {
if (PlayState.instance != null)
return PlayState.instance.players[strumLineID];
return null;
public var strumLine(default, set):StrumLine;
private function set_strumLine(strLine:StrumLine) {
if (this.strumLine != null) {
if (this.strumLine.notes != null)
this.strumLine.notes.remove(this, true);
strLine.notes.add(this);
strLine.notes.sortNotes();
}
return strumLine = strLine;
}
private function get_mustPress():Bool {
if (PlayState.instance != null)
return PlayState.instance.players[strumLineID] != null && !PlayState.instance.players[strumLineID].cpu;
return strumLineID == 1;
// if (PlayState.instance != null)
// return PlayState.instance.players[strumLineID] != null && !PlayState.instance.players[strumLineID].cpu;
return false;
}
public var noteData:Int = 0;
public var canBeHit:Bool = false;
@@ -72,7 +77,6 @@ class Note extends FlxSprite
public var flipSustain:Bool = true;
public var noteTypeID:Int = 0;
public var strumLineID:Int = 0;
// TO APPLY THOSE ON A SINGLE NOTE
public var scrollSpeed:Null<Float> = null;
@@ -93,27 +97,25 @@ class Note extends FlxSprite
public var animSuffix:String = "";
public function new(strumTime:Float, noteData:Int, noteType:Int = 0, strumLineID:Int = 1, ?prevNote:Note, ?sustainNote:Bool = false, animSuffix:String = "")
public function new(strumLine:StrumLine, noteData:ChartNote, isSustainNote:Bool)
{
super();
if (prevNote == null)
prevNote = this;
this.prevNote = prevNote;
this.strumLineID = strumLineID;
this.noteTypeID = noteType;
isSustainNote = sustainNote;
// TODO: Sustain note
this.prevNote = strumLine.notes.members.last();
this.noteTypeID = noteData.type.getDefault(0);
this.isSustainNote = false;
this.strumLine = strumLine;
x += 50;
// MAKE SURE ITS DEFINITELY OFF SCREEN?
y -= 2000;
this.strumTime = strumTime;
this.strumTime = noteData.time.getDefault(0);
this.noteData = noteData;
this.noteData = noteData.id.getDefault(0);
var customType = Paths.image('game/notes/${this.noteType}');
var event = EventManager.get(NoteCreationEvent).recycle(this, strumID, this.noteType, noteTypeID, strumLineID, mustPress, Assets.exists(customType) ? 'game/notes/${this.noteType}' : 'game/notes/default', 0.7, animSuffix);
var event = EventManager.get(NoteCreationEvent).recycle(this, strumID, this.noteType, noteTypeID, PlayState.instance.players.indexOf(strumLine), mustPress, Assets.exists(customType) ? 'game/notes/${this.noteType}' : 'game/notes/default', 0.7, animSuffix);
if (PlayState.instance != null)
event = PlayState.instance.scripts.event("onNoteCreation", event);
+188 -326
View File
@@ -1,7 +1,7 @@
package funkin.game;
import funkin.chart.Chart;
import funkin.game.SplashHandler;
import funkin.editors.CharacterEditor;
import funkin.scripting.DummyScript;
import funkin.menus.StoryMenuState.WeekData;
import funkin.ui.FunkinText;
@@ -9,49 +9,26 @@ import flixel.group.FlxSpriteGroup;
import funkin.options.Options;
import funkin.scripting.Script;
import flixel.util.FlxDestroyUtil;
import funkin.system.Section.SwagSection;
import funkin.system.Song.SwagSong;
import funkin.scripting.ScriptPack;
import funkin.shaders.WiggleEffect.WiggleEffectType;
import flixel.FlxBasic;
import flixel.FlxCamera;
import flixel.FlxG;
import flixel.FlxGame;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.FlxSubState;
import flixel.addons.display.FlxGridOverlay;
import flixel.addons.effects.FlxTrail;
import flixel.addons.effects.FlxTrailArea;
import flixel.addons.effects.chainable.FlxEffectSprite;
import flixel.addons.effects.chainable.FlxWaveEffect;
import flixel.addons.transition.FlxTransitionableState;
import flixel.graphics.atlas.FlxAtlas;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxSound;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.ui.FlxBar;
import flixel.util.FlxCollision;
import flixel.util.FlxColor;
import flixel.util.FlxSort;
import flixel.util.FlxStringUtil;
import flixel.util.FlxTimer;
import haxe.Json;
import haxe.io.Path;
import lime.utils.Assets;
import openfl.display.BlendMode;
import openfl.display.StageQuality;
import openfl.filters.ShaderFilter;
import funkin.system.Conductor;
import funkin.system.Song;
import funkin.editors.ChartingState;
import funkin.cutscenes.*;
import funkin.menus.*;
@@ -60,6 +37,7 @@ import funkin.scripting.events.*;
using StringTools;
@:access(flixel.text.FlxText.FlxTextFormatRange)
@:access(funkin.game.StrumLine)
class PlayState extends MusicBeatState
{
/**
@@ -68,9 +46,9 @@ class PlayState extends MusicBeatState
public static var instance:PlayState = null;
/**
* SONG METADATA
* SONG DATA (Chart, Metadata)
*/
public static var SONG:SwagSong;
public static var SONG:ChartData;
/**
* Whenever the song is being played in Story Mode.
*/
@@ -127,6 +105,10 @@ class PlayState extends MusicBeatState
* Current Stage.
*/
public var stage:Stage;
/**
* Current Stage.
*/
public var validScore:Bool = true;
/**
* Whenever the player can die.
*/
@@ -164,20 +146,15 @@ class PlayState extends MusicBeatState
/**
* Dad character
*/
public var dad(default, set):Character;
public var dad(get, set):Character;
/**
* Girlfriend character
*/
public var gf:Character;
public var gf(get, set):Character;
/**
* Boyfriend character
*/
public var boyfriend(default, set):Character;
/**
* Group of all of the notes. Using `forEach` on this group will only loop through the first notes.
*/
public var notes:NoteGroup;
public var boyfriend(get, set):Character;
/**
* Strum line position
@@ -197,19 +174,14 @@ class PlayState extends MusicBeatState
* Previous cam follow.
*/
private static var smoothTransitionData:PlayStateTransitionData;
/**
* All of the strum line notes.
*/
public var strumLineNotes:FlxTypedGroup<Strum>;
/**
* Player strums.
*/
public var playerStrums:StrumLine;
public var playerStrums(get, null):StrumLine;
/**
* CPU strums.
*/
public var cpuStrums:StrumLine;
public var cpuStrums(get, null):StrumLine;
/**
* Shortcut to `playerStrums`.
*/
@@ -393,7 +365,14 @@ class PlayState extends MusicBeatState
* Timer for the start countdown
*/
public var startTimer:FlxTimer;
/**
* Remaining events
*/
public var events:Array<ChartEvent> = [];
/**
* Current camera target. -1 means no automatic camera targetting.
*/
public var curCameraTarget:Int = 0;
/**
* Length of the intro countdown.
*/
@@ -420,10 +399,6 @@ class PlayState extends MusicBeatState
*/
public var canPause:Bool = true;
/**
* Current section (can be `null` when using YoshiCrafter Engine charts).
*/
public var curSection(get, null):SwagSection;
/**
* Format for the accuracy rating.
*/
@@ -524,17 +499,17 @@ class PlayState extends MusicBeatState
persistentDraw = true;
if (SONG == null)
SONG = Song.loadFromJson('tutorial', 'normal');
SONG = Chart.parse('tutorial', 'normal');
scrollSpeed = SONG.speed;
scrollSpeed = SONG.scrollSpeed;
Conductor.setupSong(SONG);
detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay";
// Checks if cutscene files exists
var cutscenePath = Paths.script('songs/${SONG.song.toLowerCase()}/cutscene');
var endCutscenePath = Paths.script('songs/${SONG.song.toLowerCase()}/cutscene-end');
var cutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene');
var endCutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene-end');
if (Assets.exists(cutscenePath)) cutscene = cutscenePath;
if (Assets.exists(endCutscenePath)) endCutscene = endCutscenePath;
@@ -544,29 +519,7 @@ class PlayState extends MusicBeatState
// CHARACTER INITIALISATION
#if REGION
// Updating Discord Rich Presence.
dad = new Character(100, 100, SONG.player2);
if (dad != null && dad.isGF) {
dad.setPosition(400, 130);
gf = dad;
dad.scrollFactor.set(0.95, 0.95);
} else {
var gfVersion = SONG.gf;
if (gfVersion == null) gfVersion = "gf";
if (gfVersion != "none") {
gf = new Character(400, 130, gfVersion);
gf.scrollFactor.set(0.95, 0.95);
}
}
var dadMidpoint = dad.getGraphicMidpoint();
comboGroup = new FlxSpriteGroup(FlxG.width * 0.55, (FlxG.height * 0.5) - 60);
boyfriend = new Character(770, 100, SONG.player1, true);
#end
// SCRIPTS & STAGE INITIALISATION
@@ -574,16 +527,17 @@ class PlayState extends MusicBeatState
if (SONG.stage == null || SONG.stage.trim() == "") SONG.stage = "stage";
add(stage = new Stage(SONG.stage));
var camPos:FlxPoint = new FlxPoint(dadMidpoint.x, dadMidpoint.y);
dadMidpoint.put();
// var camPos:FlxPoint = new FlxPoint(dadMidpoint.x, dadMidpoint.y);
// dadMidpoint.put();
var camPos:FlxPoint = new FlxPoint(0, 0);
switch(SONG.song) {
switch(SONG.meta.name) {
// case "":
// ADD YOUR HARDCODED SCRIPTS HERE!
default:
for(content in [
Paths.getFolderContent('songs/${SONG.song.toLowerCase()}/scripts', true, fromMods ? MODS : SOURCE),
Paths.getFolderContent('songs/${SONG.meta.name.toLowerCase()}/scripts', true, fromMods ? MODS : SOURCE),
Paths.getFolderContent('data/charts/', true, fromMods ? MODS : SOURCE)]) {
for(file in content) {
var ext = Path.extension(file).toLowerCase();
@@ -593,18 +547,7 @@ class PlayState extends MusicBeatState
}
}
scripts.load();
scripts.call("create");
if (gf != null) {
gf.danceOnBeat = false;
add(gf);
}
add(comboGroup);
if (dad != null) add(dad);
if (boyfriend != null) add(boyfriend);
#end
// PRECACHING
@@ -617,17 +560,48 @@ class PlayState extends MusicBeatState
#if REGION
strumLine = new FlxObject(0, 50, FlxG.width, 10);
strumLine.scrollFactor.set();
strumLineNotes = new FlxTypedGroup<Strum>();
add(strumLineNotes);
players.push(cpuStrums = new StrumLine([dad], 0.25, !coopMode && !opponentMode, true, coopMode ? controlsP2 : controls));
players.push(playerStrums = new StrumLine([boyfriend], 0.75, !coopMode && opponentMode, false, coopMode ? controlsP1 : controls));
generateSong(SONG);
trace(SONG.noteTypes);
for(noteType in SONG.noteTypes) {
var scriptPath = Paths.script('data/notes/${noteType}');
if (Assets.exists(scriptPath) && !scripts.contains(scriptPath)) {
var script = Script.create(scriptPath);
if (!(script is DummyScript)) {
scripts.add(script);
script.load();
}
}
}
for(strumLine in SONG.strumLines) {
if (strumLine == null) continue;
var chars = [];
var charPosName:String = strumLine.position == null ? (strumLine.opponent ? "dad" : "boyfriend") : strumLine.position;
if (strumLine.characters != null) for(k=>charName in strumLine.characters) {
var char = new Character(0, 0, charName, !strumLine.opponent);
stage.applyCharStuff(char, charPosName);
char.x += k * 50;
chars.push(char);
}
var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.opponent ? 0.25 : 0.75) : strumLine.strumLinePos;
var strLine = new StrumLine(chars, strOffset, !coopMode && (strumLine.opponent != opponentMode), strumLine.opponent, coopMode ? (strumLine.opponent ? controlsP2 : controlsP1) : controls);
strLine.cameras = [camHUD];
players.push(strLine);
if (strumLine.visible != false)
add(strLine);
strLine.generate(strumLine);
}
splashHandler = new SplashHandler();
add(splashHandler);
generateSong(SONG);
scripts.load();
scripts.call("create");
#end
// CAMERA & HUD INITIALISATION
@@ -686,7 +660,7 @@ class PlayState extends MusicBeatState
missesTxt.alignment = CENTER;
accuracyTxt.alignment = LEFT;
for(e in [strumLineNotes, notes, healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt])
for(e in [healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt])
e.cameras = [camHUD];
#end
@@ -709,7 +683,7 @@ class PlayState extends MusicBeatState
* This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts.
*/
public dynamic function updateDiscordPresence()
DiscordUtil.changeSongPresence(detailsText, (paused ? "Paused - " : "") + SONG.song + " (" + difficulty + ")", inst, getIconRPC());
DiscordUtil.changeSongPresence(detailsText, (paused ? "Paused - " : "") + SONG.meta.name + " (" + difficulty + ")", inst, getIconRPC());
/**
* Starts a cutscene.
@@ -721,13 +695,13 @@ class PlayState extends MusicBeatState
if (callback == null)
callback = startCountdown;
if (cutsceneScriptPath == null)
cutsceneScriptPath = Paths.script('songs/${SONG.song.toLowerCase()}/${prefix}cutscene');
cutsceneScriptPath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/${prefix}cutscene');
if (playCutscenes) {
inCutscene = true;
var videoCutscene = Paths.video('${PlayState.SONG.song.toLowerCase()}-${prefix}cutscene');
var videoCutsceneAlt = Paths.file('songs/${PlayState.SONG.song.toLowerCase()}/${prefix}cutscene.mp4');
var dialogue = Paths.file('songs/${PlayState.SONG.song.toLowerCase()}/${prefix}dialogue.xml');
var videoCutscene = Paths.video('${PlayState.SONG.meta.name.toLowerCase()}-${prefix}cutscene');
var videoCutsceneAlt = Paths.file('songs/${PlayState.SONG.meta.name.toLowerCase()}/${prefix}cutscene.mp4');
var dialogue = Paths.file('songs/${PlayState.SONG.meta.name.toLowerCase()}/${prefix}dialogue.xml');
persistentUpdate = true;
if (cutsceneScriptPath != null) {
openSubState(new ScriptedCutscene(cutsceneScriptPath, function() {
@@ -870,131 +844,35 @@ class PlayState extends MusicBeatState
instance = null;
}
public function generateNotes(songData:SwagSong) {
// TODO: redo with proper chart parser & codename chart format
if (songData == null) return;
if (songData.noteTypes == null) songData.noteTypes = [];
var noteData:Array<SwagSection> = songData.notes;
var stepCrochet = Conductor.stepCrochet;
for (section in noteData)
{
if (section == null || section.sectionNotes == null) continue;
if (section.changeBPM && section.bpm > 0)
stepCrochet = ((60 / section.bpm) * 250);
for (songNotes in section.sectionNotes)
{
if (songNotes == null) continue;
var daStrumTime:Float = songNotes[0];
var daNoteData:Int = Std.int(songNotes[1] % 8);
if (daNoteData < 0) continue;
var daNoteType:Int = Std.int(songNotes[1] / 8);
var gottaHitNote:Bool = daNoteData >= 4 ? !section.mustHitSection : section.mustHitSection;
if (songNotes.length > 2) {
if (songNotes[3] is Int)
daNoteType = addNoteType(songData.noteTypes[Std.int(songNotes[3])-1], songNotes[3] == 0);
else if (songNotes[3] is String)
daNoteType = addNoteType(songNotes[3]);
} else {
daNoteType = addNoteType(songData.noteTypes[daNoteType-1], daNoteType == 0);
}
var swagNote:Note;
if (notes.length > 0)
swagNote = notes.members[Std.int(notes.members.length - 1)];
else
swagNote = null;
swagNote.nextNote = (swagNote = new Note(daStrumTime, daNoteData % 4, daNoteType, gottaHitNote ? 1 : 0, swagNote, false, section.altAnim ? "-alt" : ""));
swagNote.sustainLength = songNotes[2];
swagNote.scrollFactor.set(0, 0);
swagNote.stepLength = stepCrochet;
notes.add(swagNote);
// calculate sustain length and fix
var susLength:Float = swagNote.sustainLength / stepCrochet;
if (susLength > 0.75) susLength++;
// create sustains
for (susNote in 0...Math.floor(susLength))
{
swagNote = new Note(daStrumTime + (stepCrochet * susNote), daNoteData % 4, daNoteType, gottaHitNote ? 1 : 0, swagNote, true, section.altAnim ? "-alt" : "");
swagNote.scrollFactor.set();
swagNote.stepLength = stepCrochet;
notes.add(swagNote);
}
// preload splash
if (Options.splashesEnabled)
splashHandler.getSplashGroup(swagNote.splash);
}
}
notes.sortNotes();
}
@:dox(hide) private function generateSong(?songData:SwagSong):Void
@:dox(hide) private function generateSong(?songData:ChartData):Void
{
if (songData == null) songData = SONG;
if (songData.maxHealth != null && songData.maxHealth > 0)
maxHealth = songData.maxHealth;
events = songData.events != null ? [for(e in songData.events) e] : [];
events.sort(function(p1, p2) {
return FlxSort.byValues(FlxSort.ASCENDING, p1.time, p2.time);
});
camZoomingInterval = cast songData.beatsPerMesure.getDefault(4);
camZoomingInterval = cast songData.meta.beatsPerMesure.getDefault(4);
Conductor.changeBPM(songData.bpm);
Conductor.changeBPM(songData.meta.bpm);
curSong = songData.song.toLowerCase();
curSong = songData.meta.name.toLowerCase();
inst = FlxG.sound.load(Paths.inst(SONG.song, difficulty));
inst = FlxG.sound.load(Paths.inst(SONG.meta.name, difficulty));
vocals = FlxG.sound.list.recycle(FlxSound);
@:privateAccess {
vocals.reset();
vocals.exists = true;
}
if (SONG.needsVoices)
vocals.loadEmbedded(Paths.voices(SONG.song, difficulty));
if (SONG.meta.needsVoices != false) // null or true
vocals.loadEmbedded(Paths.voices(SONG.meta.name, difficulty));
FlxG.sound.list.add(vocals);
inst.persist = vocals.persist = false;
notes = new NoteGroup();
add(notes);
generateNotes(songData);
generatedMusic = true;
}
public function addNoteType(name:String, addAsDefaultIfUnknown:Bool = false):Int {
if (name == null) {
if (addAsDefaultIfUnknown)
return 0;
else
name = "Unknown";
}
for(k=>e in noteTypesArray)
if (e == name) return k;
noteTypesArray.push(name);
// loads script
var scriptPath = Paths.script('data/notes/${name}');
if (Assets.exists(scriptPath) && !scripts.contains(scriptPath)) {
var script = Script.create(scriptPath);
if (!(script is DummyScript)) {
scripts.add(script);
script.load();
}
}
return noteTypesArray.length-1;
}
@:dox(hide) function sortByShit(Obj1:Note, Obj2:Note):Int {
return FlxSort.byValues(FlxSort.ASCENDING, Obj1.strumTime, Obj2.strumTime);
@@ -1057,7 +935,7 @@ class PlayState extends MusicBeatState
* Returns the Discord RPC icon.
*/
public inline function getIconRPC():String
return (dad != null ? (dad.icon != null ? dad.icon : dad.curCharacter) : SONG.player2);
return (dad != null ? (dad.icon != null ? dad.icon : dad.curCharacter) : null);
var __songPlaying:Bool = false;
var __wasAutoPause:Bool = false;
@@ -1105,11 +983,6 @@ class PlayState extends MusicBeatState
scripts.call("onVocalsResync");
}
@:dox(hide)
public function get_curSection() {
return PlayState.SONG.notes[curMeasure];
}
/**
* Pauses the game.
*/
@@ -1166,19 +1039,11 @@ class PlayState extends MusicBeatState
pauseGame();
if (canAccessDebugMenus) {
if (FlxG.keys.justPressed.SEVEN)
{
FlxG.switchState(new ChartingState());
}
if (FlxG.keys.justPressed.F5) {
Logs.trace('Reloading scripts...', WARNING, YELLOW);
scripts.reload();
Logs.trace('Song scripts successfully reloaded.', WARNING, GREEN);
}
if (FlxG.keys.justPressed.EIGHT)
FlxG.switchState(new CharacterEditor(SONG.player2));
if (FlxG.keys.justPressed.NINE)
FlxG.switchState(new CharacterEditor(SONG.player1));
}
iconP1.scale.set(lerp(iconP1.scale.x, 1, 0.33), lerp(iconP1.scale.y, 1, 0.33));
@@ -1213,28 +1078,31 @@ class PlayState extends MusicBeatState
}
}
if (generatedMusic && curSection != null)
while(events.length > 0 && events[0].time <= Conductor.songPosition)
executeEvent(events.shift());
if (generatedMusic && players[curCameraTarget] != null)
{
var dadPos = dad != null ? dad.getCameraPosition() : FlxPoint.get(0,0);
var bfPos = boyfriend != null ? boyfriend.getCameraPosition() : FlxPoint.get(0,0);
var section = curSection;
// from dad to bf
var ratio:Float = 0;
if (section.camTarget != null)
ratio = section.camTarget;
else
ratio = section.mustHitSection ? 1 : 0;
var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(FlxPoint.get(FlxMath.lerp(dadPos.x, bfPos.x, ratio), FlxMath.lerp(dadPos.y, bfPos.y, ratio)), bfPos, dadPos, ratio, ratio >= 0.5));
if (!event.cancelled) {
camFollow.setPosition(
event.position.x, event.position.y
);
var strumLine = players[curCameraTarget];
var pos = FlxPoint.get();
var r = 0;
for(c in strumLine.characters) {
if (c == null) continue;
var cpos = c.getCameraPosition();
pos.x += cpos.x;
pos.y += cpos.y;
r++;
cpos.put();
}
for(e in [event.position, event.bfCamPos, event.dadCamPos])
e.put();
if (r > 0) {
pos.x /= r;
pos.y /= r;
var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLine, r));
if (!event.cancelled)
camFollow.setPosition(pos.x, pos.y);
}
pos.put();
}
if (camZooming)
@@ -1253,10 +1121,6 @@ class PlayState extends MusicBeatState
gameOver(dad);
if (generatedMusic) {
__updateNote_event = EventManager.get(NoteUpdateEvent);
notes.forEachAlive(updateNote);
}
if (!inCutscene)
keyShit();
@@ -1269,49 +1133,28 @@ class PlayState extends MusicBeatState
scripts.call("postUpdate", [elapsed]);
}
var __updateNote_strum:Strum = null;
var __updateNote_event:NoteUpdateEvent = null;
@:dox(hide)
function updateNote(daNote:Note)
{
for(e in players[daNote.strumLineID].members) {
if (e.ID == daNote.noteData % 4) {
__updateNote_strum = e;
break; //ing bad
}
public function executeEvent(event:ChartEvent) {
if (event == null) return;
if (event.params == null) return;
switch(event.type) {
case CUSTOM:
var d = [for(d in event.params) d];
scripts.call(d.shift(), d);
case CAM_MOVEMENT:
if (event.params.length > 0 && event.params[0] is Int)
curCameraTarget = event.params[0];
case BPM_CHANGE:
// todo!!
case ALT_ANIM_TOGGLE:
// todo!!!
default:
}
PlayState.instance.scripts.event("onNoteUpdate", __updateNote_event.recycle(daNote, FlxG.elapsed, __updateNote_strum));
if (__updateNote_event.cancelled) return;
if (__updateNote_event.__updateHitWindow) {
daNote.canBeHit = (daNote.strumTime > Conductor.songPosition - (hitWindow * daNote.latePressWindow)
&& daNote.strumTime < Conductor.songPosition + (hitWindow * daNote.earlyPressWindow));
if (daNote.strumTime < Conductor.songPosition - hitWindow && !daNote.wasGoodHit)
daNote.tooLate = true;
}
if (__updateNote_event.__autoCPUHit && (daNote.strumLine == null || daNote.strumLine.cpu) && !daNote.wasGoodHit && daNote.strumTime < Conductor.songPosition) goodNoteHit(daNote);
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (daNote.stepLength) < Conductor.songPosition) {
deleteNote(daNote);
return;
}
if (daNote.tooLate && !(daNote.strumLine != null && daNote.strumLine.cpu)) {
noteMiss(daNote);
return;
}
if (__updateNote_event.strum == null) return;
if (__updateNote_event.__reposNote) __updateNote_event.strum.updateNotePosition(daNote);
__updateNote_event.strum.updateSustain(daNote);
PlayState.instance.scripts.event("onNotePostUpdate", __updateNote_event);
}
@:dox(hide)
public var __updateNote_event:NoteUpdateEvent = null;
/**
* Forces a game over.
* @param character Character which died. Default to `boyfriend`.
@@ -1353,10 +1196,10 @@ class PlayState extends MusicBeatState
inst.pause();
vocals.pause();
if (SONG.validScore)
if (validScore)
{
#if !switch
Highscore.saveScore(SONG.song, {
Highscore.saveScore(SONG.meta.name, {
score: songScore,
misses: misses,
accuracy: accuracy
@@ -1380,7 +1223,7 @@ class PlayState extends MusicBeatState
{
FlxG.switchState(new StoryMenuState());
if (SONG.validScore)
if (validScore)
{
// TODO: more week info saving
Highscore.saveWeekScore(storyWeek.name, {
@@ -1425,7 +1268,13 @@ class PlayState extends MusicBeatState
var __justReleased:Array<Bool> = [];
private function keyShit():Void
{
if (generatedMusic) {
__updateNote_event = EventManager.get(NoteUpdateEvent);
}
for(id=>p in players) {
p.updateNotes();
if (p.cpu) continue;
__funcsToExec.clear();
@@ -1447,8 +1296,8 @@ class PlayState extends MusicBeatState
if (__pressed.contains(true)) {
__funcsToExec.push(function(note:Note) {
if (note.strumLineID == id && __pressed[note.strumID] && note.isSustainNote && note.canBeHit && !note.wasGoodHit) {
goodNoteHit(note);
if (__pressed[note.strumID] && note.isSustainNote && note.canBeHit && !note.wasGoodHit) {
goodNoteHit(p, note);
}
});
}
@@ -1456,25 +1305,25 @@ class PlayState extends MusicBeatState
var notePerStrum = [for(_ in 0...4) null];
if (__justPressed.contains(true)) {
__funcsToExec.push(function(note:Note) {
if (__justPressed[note.strumID] && !note.isSustainNote && note.strumLineID == id && !note.wasGoodHit && note.canBeHit) {
if (__justPressed[note.strumID] && !note.isSustainNote && !note.wasGoodHit && note.canBeHit) {
if (notePerStrum[note.strumID] == null) notePerStrum[note.strumID] = note;
else if (Math.abs(notePerStrum[note.strumID].strumTime - note.strumTime) <= 2) deleteNote(note);
else if (Math.abs(notePerStrum[note.strumID].strumTime - note.strumTime) <= 2) p.deleteNote(note);
else if (note.strumTime < notePerStrum[note.strumID].strumTime) notePerStrum[note.strumID] = note;
}
});
}
if (__funcsToExec.length > 0) {
notes.forEachAlive(function(note:Note) {
p.notes.forEachAlive(function(note:Note) {
for(e in __funcsToExec) e(note);
});
}
if (!p.ghostTapping) for(k=>p in __justPressed) if (p && notePerStrum[k] == null) {
if (!p.ghostTapping) for(k=>pr in __justPressed) if (pr && notePerStrum[k] == null) {
// FUCK YOU
noteMiss(null, k, id);
noteMiss(p, null, k, id);
}
for(e in notePerStrum) if (e != null) goodNoteHit(e);
for(e in notePerStrum) if (e != null) goodNoteHit(p, e);
p.forEach(function(str:Strum) {
str.updatePlayerInput(__pressed[str.ID], __justPressed[str.ID], __justReleased[str.ID]);
@@ -1489,18 +1338,17 @@ class PlayState extends MusicBeatState
* @param direction Specify a custom direction in case note is null.
* @param player Specify a custom player in case note is null.
*/
public function noteMiss(note:Note, ?direction:Int, ?player:Int):Void
public function noteMiss(strumLine:StrumLine, note:Note, ?direction:Int, ?player:Int):Void
{
var playerID:Null<Int> = note == null ? player : note.strumLineID;
var playerID:Null<Int> = note == null ? player : players.indexOf(strumLine);
var directionID:Null<Int> = note == null ? direction : note.strumID;
if (playerID == null || directionID == null) return;
var event:NoteHitEvent = scripts.event("onPlayerMiss", EventManager.get(NoteHitEvent).recycle(true, false, false, note, players[player].characters, true, note != null ? note.noteType : null, "", "", "", directionID, -10, 0, -0.04, "shit"));
var event:NoteHitEvent = scripts.event("onPlayerMiss", EventManager.get(NoteHitEvent).recycle(true, false, false, note, players[playerID].characters, true, note != null ? note.noteType : null, "", "", "", directionID, -10, 0, -0.04, "shit"));
if (event.cancelled) return;
if (event.note != null && event.note.strumLine != null) event.note.strumLine.addHealth(event.healthGain);
if (event.note != null && strumLine != null) strumLine.addHealth(event.healthGain);
if (gf != null && combo > 5 && gf.hasAnimation('sad'))
gf.playAnim('sad', event.forceAnim, MISS);
combo = 0;
@@ -1525,21 +1373,22 @@ class PlayState extends MusicBeatState
char.stunned = true;
char.playSingAnim(directionID, "miss", MISS);
}
// boyfriend.stunned = true;
deleteNote(note);
if (strumLine != null && note != null) {
strumLine.deleteNote(note);
}
}
@:dox(hide)
public function getNoteType(id:Int):String {
return noteTypesArray[id];
return SONG.noteTypes[id-1];
}
/**
* Hits a note
* @param note Note to hit.
*/
public function goodNoteHit(note:Note):Void
public function goodNoteHit(strumLine:StrumLine, note:Note):Void
{
if(note == null || note.wasGoodHit) return;
@@ -1573,10 +1422,10 @@ class PlayState extends MusicBeatState
}
var event:NoteHitEvent;
if (note.strumLine != null && !note.strumLine.cpu)
event = scripts.event("onPlayerHit", EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, note, note.strumLine.characters, true, note.noteType, note.animSuffix, "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, note.noteData > 0 ? 0.023 : 0.004, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick"));
if (strumLine != null && !strumLine.cpu)
event = scripts.event("onPlayerHit", EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, note, strumLine.characters, true, note.noteType, note.animSuffix, "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, note.noteData > 0 ? 0.023 : 0.004, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick"));
else
event = scripts.event("onDadHit", EventManager.get(NoteHitEvent).recycle(false, false, false, note, note.strumLine.characters, false, note.noteType, note.animSuffix, "game/score/", "", note.strumID, 0, null, 0, daRating, false));
event = scripts.event("onDadHit", EventManager.get(NoteHitEvent).recycle(false, false, false, note, strumLine.characters, false, note.noteType, note.animSuffix, "game/score/", "", note.strumID, 0, null, 0, daRating, false));
scripts.event("onNoteHit", event);
if (!event.cancelled) {
@@ -1664,7 +1513,7 @@ class PlayState extends MusicBeatState
ratingNum += 1;
}
if (event.note.strumLine != null) event.note.strumLine.addHealth(event.healthGain);
if (strumLine != null) strumLine.addHealth(event.healthGain);
if (!event.animCancelled)
for(char in event.characters)
@@ -1686,24 +1535,13 @@ class PlayState extends MusicBeatState
}
}
if (event.deleteNote && !note.isSustainNote) deleteNote(note);
}
/**
* Deletes a note.
* @param note Note to delete
*/
public function deleteNote(note:Note) {
if (note == null) return;
var event:SimpleNoteEvent = scripts.event("onNoteDelete", EventManager.get(SimpleNoteEvent).recycle(note));
if (!event.cancelled) {
scripts.call("onNoteDelete", [note]);
note.kill();
notes.remove(note, true);
note.destroy();
}
if (event.deleteNote && !note.isSustainNote) strumLine.deleteNote(note);
}
public inline function deleteNote(note:Note)
if (note.strumLine != null)
note.strumLine.deleteNote(note);
@:dox(hide)
override function stepHit(curStep:Int)
{
@@ -1752,16 +1590,40 @@ class PlayState extends MusicBeatState
private inline function set_cpu(s:StrumLine):StrumLine
return cpuStrums = s;
private function get_boyfriend():Character {
if (players != null && players[1] != null)
return players[1].characters[0];
return null;
}
private function set_boyfriend(bf:Character):Character {
if (players != null && players[1] != null)
players[1].characters = [bf];
return this.boyfriend = bf;
return bf;
}
private function get_dad():Character {
if (players != null && players[0] != null)
return players[0].characters[0];
return null;
}
private function set_dad(dad:Character):Character {
if (players != null && players[0] != null)
players[0].characters = [dad];
return this.dad = dad;
return dad;
}
private function get_gf():Character {
if (players != null && players[2] != null)
return players[2].characters[0];
return null;
}
private function set_gf(gf:Character):Character {
if (players != null && players[2] != null)
players[2].characters = [gf];
return gf;
}
private inline function get_cpuStrums():StrumLine
return players[0];
private inline function get_playerStrums():StrumLine
return players[1];
}
class ComboRating {
+68 -13
View File
@@ -19,6 +19,8 @@ class Stage extends FlxBasic implements IBeatReceiver {
public var stagePath:String;
public var stageSprites:Map<String, FlxSprite> = [];
public var stageScript:Script;
public var state:FlxState;
public var characterPoses:Map<String, StageCharPos> = [];
public function getSprite(name:String) {
return stageSprites[name];
@@ -29,6 +31,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
if (state == null) state = PlayState.instance;
if (state == null) state = FlxG.state;
this.state = state;
stagePath = Paths.xml('stages/$stage');
try {
@@ -89,15 +92,18 @@ class Stage extends FlxBasic implements IBeatReceiver {
state.add(spr);
spr;
case "boyfriend" | "bf":
if (PlayState.instance == null || PlayState.instance.boyfriend == null) continue;
doCharNodeShit(PlayState.instance.boyfriend, node);
PlayState.instance.add(PlayState.instance.boyfriend);
PlayState.instance.boyfriend;
addCharPos("boyfriend", node);
null;
case "girlfriend" | "gf":
if (PlayState.instance == null || PlayState.instance.gf == null) continue;
doCharNodeShit(PlayState.instance.gf, node);
PlayState.instance.add(PlayState.instance.gf);
PlayState.instance.gf;
addCharPos("girlfriend", node);
null;
case "dad" | "opponent":
addCharPos("dad", node);
null;
case "character":
if (!node.has.name) continue;
addCharPos(node.att.name, node);
null;
case "ratings" | "combo":
if (PlayState.instance == null) continue;
PlayState.instance.comboGroup.setPosition(
@@ -106,11 +112,6 @@ class Stage extends FlxBasic implements IBeatReceiver {
);
PlayState.instance.add(PlayState.instance.comboGroup);
PlayState.instance.comboGroup;
case "dad" | "opponent":
if (PlayState.instance == null || PlayState.instance.dad == null || PlayState.instance.dad.isGF) continue;
doCharNodeShit(PlayState.instance.dad, node);
PlayState.instance.add(PlayState.instance.dad);
PlayState.instance.dad;
default: null;
}
if (sprite != null) {
@@ -120,6 +121,24 @@ class Stage extends FlxBasic implements IBeatReceiver {
}
}
if (characterPoses["girlfriend"] == null)
addCharPos("girlfriend", null, {
x: 400,
y: 130
});
if (characterPoses["dad"] == null)
addCharPos("dad", null, {
x: 100,
y: 100
});
if (characterPoses["boyfriend"] == null)
addCharPos("boyfriend", null, {
x: 770,
y: 100
});
if (PlayState.instance == null) return;
stageScript = Script.create(Paths.script('data/stages/$stage'));
for(k=>e in stageSprites) {
@@ -128,7 +147,32 @@ class Stage extends FlxBasic implements IBeatReceiver {
PlayState.instance.scripts.add(stageScript);
}
public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo) {
var dummyBasic:FlxBasic = new FlxBasic();
dummyBasic.active = false;
state.add(dummyBasic);
characterPoses[name] = {
node: node,
nonXMLInfo: nonXMLInfo,
dummy: dummyBasic
};
}
public function applyCharStuff(char:Character, posName:String) {
if (characterPoses[posName] == null)
state.add(char);
else {
var charPos = characterPoses[posName];
if (charPos.node != null) {
doCharNodeShit(char, charPos.node);
} else if (charPos.nonXMLInfo != null) {
char.setPosition(charPos.nonXMLInfo.x, charPos.nonXMLInfo.y);
}
state.insert(state.members.indexOf(charPos.dummy), char);
}
}
private static function doCharNodeShit(char:Character, node:Access) {
if (node.has.x) {
var x:Null<Float> = Std.parseFloat(node.att.x);
@@ -157,4 +201,15 @@ class Stage extends FlxBasic implements IBeatReceiver {
public function stepHit(curStep:Int) {}
public function measureHit(curMeasure:Int) {}
}
typedef StageCharPos = {
var node:Access;
var dummy:FlxBasic;
var ?nonXMLInfo:StageCharPosInfo;
}
typedef StageCharPosInfo = {
var x:Float;
var y:Float;
}
+85 -1
View File
@@ -1,5 +1,8 @@
package funkin.game;
import funkin.scripting.events.SimpleNoteEvent;
import funkin.system.Conductor;
import funkin.chart.Chart.ChartStrumLine;
import funkin.system.Controls;
import funkin.scripting.events.StrumCreationEvent;
import flixel.group.FlxGroup.FlxTypedGroup;
@@ -28,6 +31,10 @@ class StrumLine extends FlxTypedGroup<Strum> {
* Whenever Ghost Tapping is enabled.
*/
@:isVar public var ghostTapping(get, set):Null<Bool> = null;
/**
* Group of all of the notes in this strumline. Using `forEach` on this group will only loop through the first notes for performance reasons.
*/
public var notes:NoteGroup;
private function get_ghostTapping() {
if (this.ghostTapping != null) return this.ghostTapping;
@@ -47,6 +54,70 @@ class StrumLine extends FlxTypedGroup<Strum> {
this.cpu = cpu;
this.opponentSide = opponentSide;
this.controls = controls;
this.notes = new NoteGroup();
}
public function generate(strumLine:ChartStrumLine) {
if (strumLine.notes != null) for(note in strumLine.notes) {
notes.add(new Note(this, note, false));
}
notes.sortNotes();
}
public override function update(elapsed:Float) {
super.update(elapsed);
notes.update(elapsed);
}
public override function draw() {
super.draw();
notes.cameras = cameras;
notes.draw();
}
public function updateNotes() {
notes.forEach(updateNote);
}
var __updateNote_strum:Strum;
public function updateNote(daNote:Note) {
for(e in members) {
if (e.ID == daNote.noteData % 4) {
__updateNote_strum = e;
break; //ing bad
}
}
PlayState.instance.scripts.event("onNoteUpdate", PlayState.instance.__updateNote_event.recycle(daNote, FlxG.elapsed, __updateNote_strum));
if (PlayState.instance.__updateNote_event.cancelled) return;
if (PlayState.instance.__updateNote_event.__updateHitWindow) {
daNote.canBeHit = (daNote.strumTime > Conductor.songPosition - (PlayState.instance.hitWindow * daNote.latePressWindow)
&& daNote.strumTime < Conductor.songPosition + (PlayState.instance.hitWindow * daNote.earlyPressWindow));
if (daNote.strumTime < Conductor.songPosition - PlayState.instance.hitWindow && !daNote.wasGoodHit)
daNote.tooLate = true;
}
if (PlayState.instance.__updateNote_event.__autoCPUHit && cpu && !daNote.wasGoodHit && daNote.strumTime < Conductor.songPosition) PlayState.instance.goodNoteHit(this, daNote);
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (daNote.stepLength) < Conductor.songPosition) {
deleteNote(daNote);
return;
}
if (daNote.tooLate && !cpu) {
PlayState.instance.noteMiss(this, daNote);
return;
}
if (PlayState.instance.__updateNote_event.strum == null) return;
if (PlayState.instance.__updateNote_event.__reposNote) PlayState.instance.__updateNote_event.strum.updateNotePosition(daNote);
PlayState.instance.__updateNote_event.strum.updateSustain(daNote);
PlayState.instance.scripts.event("onNotePostUpdate", PlayState.instance.__updateNote_event);
}
public inline function addHealth(health:Float)
@@ -103,12 +174,25 @@ class StrumLine extends FlxTypedGroup<Strum> {
}
add(babyArrow);
PlayState.instance.strumLineNotes.add(babyArrow);
babyArrow.playAnim('static');
}
}
/**
* Deletes a note from this strumline.
* @param note Note to delete
*/
public function deleteNote(note:Note) {
if (note == null) return;
var event:SimpleNoteEvent = PlayState.instance.scripts.event("onNoteDelete", EventManager.get(SimpleNoteEvent).recycle(note));
if (!event.cancelled) {
note.kill();
notes.remove(note, true);
note.destroy();
}
}
/**
* SETTERS & GETTERS
*/
+9 -1
View File
@@ -108,6 +108,12 @@ class FreeplayState extends MusicBeatState
*/
public var iconArray:Array<HealthIcon> = [];
/**
* FlxInterpolateColor object for smooth transition between Freeplay colors.
*/
public var interpColor:FlxInterpolateColor;
override function create()
{
songList = FreeplaySonglist.get();
@@ -165,6 +171,7 @@ class FreeplayState extends MusicBeatState
changeDiff(0, true);
changeCoopMode(0, true);
interpColor = new FlxInterpolateColor(bg.color);
super.create();
}
@@ -213,7 +220,8 @@ class FreeplayState extends MusicBeatState
scoreText.x = coopText.x = scoreBG.x + 4;
diffText.x = Std.int(scoreBG.x + ((scoreBG.width - diffText.width) / 2));
bg.color = CoolUtil.lerpColor(bg.color, songs[curSelected].color, 0.0625);
interpColor.fpsLerpTo(songs[curSelected].color, 0.0625);
bg.color = interpColor.color;
var dontPlaySongThisFrame = false;
+1 -1
View File
@@ -65,7 +65,7 @@ class PauseSubState extends MusicBeatSubstate
bg.scrollFactor.set();
add(bg);
var levelInfo:FlxText = new FlxText(20, 15, 0, PlayState.SONG.song, 32);
var levelInfo:FlxText = new FlxText(20, 15, 0, PlayState.SONG.meta.name, 32);
var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, PlayState.difficulty.toUpperCase(), 32);
var multiplayerText:FlxText = new FlxText(20, 15 + 32 + 32, 0, PlayState.opponentMode ? 'OPPONENT MODE' : (PlayState.coopMode ? 'CO-OP MODE' : ''), 32);
+5 -17
View File
@@ -1,5 +1,6 @@
package funkin.scripting.events;
import funkin.game.StrumLine;
import flixel.math.FlxPoint;
class CamMoveEvent extends CancellableEvent {
@@ -9,25 +10,12 @@ class CamMoveEvent extends CancellableEvent {
public var position:FlxPoint;
/**
* Boyfriend's camera position.
* Currently focused strumline.
*/
public var bfCamPos:FlxPoint;
/**
* Dad's camera position.
*/
public var dadCamPos:FlxPoint;
public var strumLine:StrumLine;
/**
* Ratio for lerping between Dad and BF.
* 0 = Camera focuses dad
* 0.5 = Duet Camera
* 1 = Camera focuses BF
* Number of focused characters
*/
public var ratio:Float = 0;
/**
* Whenever the camera focuses BF more than dad.
*/
public var focusBF:Bool = false;
public var focusedCharacters:Int;
}
+4 -3
View File
@@ -1,5 +1,6 @@
package funkin.system;
import funkin.chart.Chart;
import flixel.math.FlxMath;
import openfl.Lib;
import flixel.FlxState;
@@ -121,10 +122,10 @@ class Conductor
changeBPM(0);
}
public static function setupSong(SONG:SwagSong) {
public static function setupSong(SONG:ChartData) {
reset();
mapBPMChanges(SONG);
changeBPM(SONG.bpm, cast SONG.beatsPerMesure.getDefault(4), cast SONG.stepsPerBeat.getDefault(4));
// mapBPMChanges(SONG); // TODO!!!
changeBPM(SONG.meta.bpm, cast SONG.meta.beatsPerMesure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4));
}
/**
* Maps BPM changes from a song.
+3 -1
View File
@@ -24,6 +24,7 @@ import flixel.util.FlxAxes;
import flixel.graphics.frames.FlxFrame;
import flixel.graphics.frames.FlxFramesCollection;
import flash.geom.ColorTransform;
import funkin.chart.Chart;
using StringTools;
@@ -294,7 +295,8 @@ class CoolUtil
public static function __loadSong(name:String, difficulty:String) {
PlayState.difficulty = difficulty;
PlayState.SONG = Song.loadFromJson(name, difficulty);
PlayState.SONG = Chart.parse(name, difficulty);
PlayState.fromMods = PlayState.SONG.fromMods;
}
public static function setSpriteSize(sprite:FlxSprite, width:Float, height:Float) {
sprite.scale.set(width / sprite.frameWidth, height / sprite.frameHeight);
@@ -3,6 +3,10 @@ package funkin.utils;
import flixel.FlxG;
import flixel.util.FlxColor;
/**
* Additional class for FlxColor lerping.
* Gets rid of precision issues with `FlxColor.interpolate`
*/
class FlxInterpolateColor {
public var red:Float = 0;
public var green:Float = 0;