This commit is contained in:
Cameron Taylor 2021-10-24 08:20:24 -04:00
commit 38e0e79bc7
42 changed files with 2971 additions and 994 deletions

View file

@ -4,13 +4,27 @@ All notable changes will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [UNRELEASED]
### Added
- Cool waveform stuff for chart editor
- Noises in chart editor lol!
- 3 AWESOME PICO VS. DARNELL SONGS!!
- Character offset editor / spritesheet viewer
## Changed
- Lerp'd the healthbar
- Resetting from game over and "restart song" should be faster
- Health gain is different depending on how accurate you hit notes!
- slight less health gained on sustain notes
- The wave-y effect on Thorns for the school is now a shader, instead of a pre-baked animation!
## [0.2.8] - 2021-04-18 (note, this one is iffy cuz we slacked wit it lol!)
### Added
- TANKMAN! 3 NEW SONGS BY KAWAISPRITE (UGH, GUNS, STRESS)! Charting help by MtH!
- Monster added into week 2, FINALLY (Charting help by MtH and ChaoticGamer!)
- Can now change song difficulty mid-game.
- Shows some song info on pause screen.
- Cute little icons onto freeplay menu
- Offset files for easier modification of characters
### Changed
- ASSET LOADING OVERHAUL, WAY FASTER LOAD TIMES ON WEB!!! (THANKS TO GEOKURELI WOKE KING)
- Made difficulty selector on freeplay menu more apparent

View file

@ -60,6 +60,7 @@
<library name="week5" preload="true" />
<library name="week6" preload="true" />
<library name="week7" preload="true" />
<library name="week8" preload="true" />
</section>
<section if="NO_PRELOAD_ALL">
@ -73,6 +74,7 @@
<library name="week5" preload="false" />
<library name="week6" preload="false" />
<library name="week7" preload="false" />
<library name="week8" preload="false" />
</section>
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg" if="web"/>
@ -95,6 +97,8 @@
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3" unless="web"/>
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg" if="web"/>
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3" unless="web"/>
<assets path="assets/week8" library="week8" exclude="*.fla|*.ogg" if="web"/>
<assets path="assets/week8" library="week8" exclude="*.fla|*.mp3" unless="web"/>
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
@ -123,7 +127,8 @@
<haxelib name="flixel-ui" />
<!--haxelib name="newgrounds" unless="switch"/> -->
<haxelib name="faxe" if='switch'/>
<haxelib name="polymod"/>
<haxelib name="polymod" if="cpp"/>
<!-- <haxelib name="colyseus"/> -->
<!-- <haxelib name="colyseus-websocket" /> -->
<!-- <haxelib name="newgrounds"/> -->

View file

@ -88,3 +88,6 @@ As for Mac, 'lime test mac -debug' should work, if not the internet surely has a
### Additional guides
- [Command line basics](https://ninjamuffin99.newgrounds.com/news/post/1090480)
Commits are generally signed and verified, as of September 8th, 2021! Using GPG!

162
source/ABotVis.hx Normal file
View file

@ -0,0 +1,162 @@
import dsp.FFT;
import flixel.FlxSprite;
import flixel.addons.plugin.taskManager.FlxTask;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxMath;
import flixel.system.FlxSound;
import ui.PreferencesMenu.CheckboxThingie;
using Lambda;
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
{
public var vis:VisShit;
var volumes:Array<Float> = [];
public function new(snd:FlxSound)
{
super();
vis = new VisShit(snd);
// vis.snd = snd;
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas('aBotViz');
for (lol in 1...8)
{
// pushes initial value
volumes.push(0.0);
var viz:FlxSprite = new FlxSprite(50 * lol, 0);
viz.frames = visFrms;
add(viz);
var visStr = 'VIZ';
if (lol == 5)
visStr = 'viz'; // lol makes it lowercase, accomodates for art that I dont wanna rename!
viz.animation.addByPrefix('VIZ', visStr + lol, 0);
viz.animation.play('VIZ', false, false, -1);
}
}
override function update(elapsed:Float)
{
// updateViz();
updateFFT();
super.update(elapsed);
}
function updateFFT()
{
if (vis.snd != null)
{
vis.checkAndSetBuffer();
if (vis.setBuffer)
{
var remappedShit:Int = 0;
if (vis.snd.playing)
remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
else
remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, vis.numSamples));
var fftSamples:Array<Float> = [];
var swagBucks = remappedShit;
for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
{
var left = vis.audioData[swagBucks] / 32767;
var right = vis.audioData[swagBucks + 1] / 32767;
var balanced = (left + right) / 2;
swagBucks += 2;
fftSamples.push(balanced);
}
var freqShit = vis.funnyFFT(fftSamples);
for (i in 0...group.members.length)
{
var getSliceShit = function(s:Int)
{
var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, CoolUtil.coolBaseLog(10, freqShit[0].length));
return Math.round(Math.pow(10, powShit));
};
// var powShit:Float = getSliceShit(i);
var hzSliced:Int = getSliceShit(i);
var sliceLength:Int = Std.int(freqShit[0].length / group.members.length);
var volSlice = freqShit[0].slice(hzSliced, getSliceShit(i + 1));
var avgVel:Float = 0;
for (slice in volSlice)
{
avgVel += slice;
}
avgVel /= volSlice.length;
avgVel *= 10000000;
volumes[i] += avgVel - (FlxG.elapsed * (volumes[i] * 50));
var animFrame:Int = Std.int(volumes[i]);
animFrame = Math.floor(Math.min(5, animFrame));
animFrame = Math.floor(Math.max(0, animFrame));
animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
group.members[i].animation.curAnim.curFrame = animFrame;
if (FlxG.keys.justPressed.U)
{
trace(avgVel);
trace(group.members[i].animation.curAnim.curFrame);
}
}
// group.members[0].animation.curAnim.curFrame =
}
}
}
public function updateViz()
{
if (vis.snd != null)
{
var remappedShit:Int = 0;
vis.checkAndSetBuffer();
if (vis.setBuffer)
{
// var startingSample:Int = Std.int(FlxMath.remapToRange)
if (vis.snd.playing)
remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
for (i in 0...group.members.length)
{
var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, remappedShit, remappedShit + 500));
var left = vis.audioData[sampleApprox] / 32767;
var animFrame:Int = Std.int(FlxMath.remapToRange(left, -1, 1, 0, 6));
group.members[i].animation.curAnim.curFrame = animFrame;
}
}
}
}
}

View file

@ -217,26 +217,25 @@ class Character extends FlxSprite
quickAnimAdd('idle', "Pico Idle Dance");
quickAnimAdd('singUP', 'pico Up note0');
quickAnimAdd('singDOWN', 'Pico Down Note0');
if (isPlayer)
{
quickAnimAdd('singLEFT', 'Pico NOTE LEFT0');
quickAnimAdd('singRIGHT', 'Pico Note Right0');
quickAnimAdd('singRIGHTmiss', 'Pico Note Right Miss');
quickAnimAdd('singLEFTmiss', 'Pico NOTE LEFT miss');
}
else
{
// Need to be flipped! REDO THIS LATER!
quickAnimAdd('singLEFT', 'Pico Note Right0');
quickAnimAdd('singRIGHT', 'Pico NOTE LEFT0');
quickAnimAdd('singRIGHTmiss', 'Pico NOTE LEFT miss');
quickAnimAdd('singLEFTmiss', 'Pico Note Right Miss');
}
// isPlayer = true;
// Need to be flipped! REDO THIS LATER!
quickAnimAdd('singLEFT', 'Pico Note Right0');
quickAnimAdd('singRIGHT', 'Pico NOTE LEFT0');
quickAnimAdd('singRIGHTmiss', 'Pico NOTE LEFT miss');
quickAnimAdd('singLEFTmiss', 'Pico Note Right Miss');
quickAnimAdd('singUPmiss', 'pico Up note miss');
quickAnimAdd('singDOWNmiss', 'Pico Down Note MISS');
loadOffsetFile(curCharacter);
// right now it loads a seperate offset file for pico, would be cool if could generalize it!
var playerShit:String = "";
if (isPlayer)
playerShit += "Player";
loadOffsetFile(curCharacter + playerShit);
playAnim('idle');
@ -486,6 +485,41 @@ class Character extends FlxSprite
playAnim('idle');
flipX = true;
case 'darnell':
frames = Paths.getSparrowAtlas('characters/darnell');
quickAnimAdd('idle', 'Darnell Idle');
quickAnimAdd('singUP', "Darnell pose up");
quickAnimAdd('singDOWN', 'Darnell Pose Down');
quickAnimAdd('singRIGHT', 'darnell pose left');
quickAnimAdd('singLEFT', 'Darnell pose right'); // naming is reversed for left/right for darnell!
quickAnimAdd('laugh', 'darnell laugh');
// temp
loadOffsetFile(curCharacter);
playAnim('idle');
animation.finishCallback = function(animShit:String)
{
if (animShit.startsWith('sing'))
{
// loop the anim
// this way is a little verbose, but basically sets it to the same animation, but 8 frames before finish
playAnim(animShit, true, false, animation.getByName(animShit).frames.length - 8);
}
}
case 'nene':
// GIRLFRIEND CODE
tex = Paths.getSparrowAtlas('characters/Nene_assets');
frames = tex;
animation.addByIndices('danceLeft', 'nenebeforeyougetawoopin', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
animation.addByIndices('danceRight', 'nenebeforeyougetawoopin', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
loadOffsetFile(curCharacter);
playAnim('danceRight');
}
dance();
@ -627,7 +661,7 @@ class Character extends FlxSprite
{
switch (curCharacter)
{
case 'gf' | 'gf-christmas' | 'gf-car' | 'gf-pixel' | 'gf-tankmen':
case 'gf' | 'gf-christmas' | 'gf-car' | 'gf-pixel' | 'gf-tankmen' | "nene":
if (!animation.curAnim.name.startsWith('hair'))
{
danced = !danced;
@ -638,10 +672,6 @@ class Character extends FlxSprite
playAnim('danceLeft');
}
case 'pico-speaker':
// lol weed
// playAnim('shoot' + FlxG.random.int(1, 4), true);
case 'tankman':
if (!animation.curAnim.name.endsWith('DOWN-alt'))
playAnim('idle');

View file

@ -3,7 +3,9 @@ package;
import Conductor.BPMChangeEvent;
import Section.SwagSection;
import Song.SwagSong;
import dsp.FFT;
import flixel.FlxSprite;
import flixel.FlxStrip;
import flixel.addons.display.FlxGridOverlay;
import flixel.addons.ui.FlxInputText;
import flixel.addons.ui.FlxUI9SliceSprite;
@ -14,6 +16,7 @@ import flixel.addons.ui.FlxUIInputText;
import flixel.addons.ui.FlxUINumericStepper;
import flixel.addons.ui.FlxUITabMenu;
import flixel.addons.ui.FlxUITooltip.FlxUITooltipStyle;
import flixel.graphics.tile.FlxDrawTrianglesItem.DrawData;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxGroup;
import flixel.math.FlxMath;
@ -23,16 +26,22 @@ import flixel.text.FlxText;
import flixel.ui.FlxButton;
import flixel.ui.FlxSpriteButton;
import flixel.util.FlxColor;
import haxe.CallStack.StackItem;
import haxe.Json;
import lime.media.AudioBuffer;
import lime.utils.Assets;
import lime.utils.Int16Array;
import openfl.events.Event;
import openfl.events.IOErrorEvent;
import openfl.media.Sound;
import openfl.net.FileReference;
import openfl.utils.ByteArray;
using Lambda;
using StringTools;
using flixel.util.FlxSpriteUtil;
// add in "compiler save" that saves the JSON directly to the debug json using File.write() stuff on windows / sys
class ChartingState extends MusicBeatState
{
var _file:FileReference;
@ -75,15 +84,25 @@ class ChartingState extends MusicBeatState
var tempBpm:Float = 0;
var vocals:FlxSound;
var vocals:VoicesGroup;
var leftIcon:HealthIcon;
var rightIcon:HealthIcon;
var audioBuf:AudioBuffer = new AudioBuffer();
var playheadTest:FlxSprite;
var staticSpecGrp:FlxTypedGroup<SpectogramSprite>;
override function create()
{
curSection = lastSection;
// sys.io.File.saveContent('./bitShit.txt', "swag");
// trace(audioBuf.sampleRate);
gridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 8, GRID_SIZE * 16);
add(gridBG);
@ -119,7 +138,8 @@ class ChartingState extends MusicBeatState
player1: 'bf',
player2: 'dad',
speed: 1,
validScore: false
validScore: false,
voiceList: ["BF", "BF-pixel"]
};
}
@ -142,7 +162,7 @@ class ChartingState extends MusicBeatState
bpmTxt.scrollFactor.set();
add(bpmTxt);
strumLine = new FlxSprite(0, 50).makeGraphic(Std.int(FlxG.width / 2), 4);
strumLine = new FlxSprite(0, 50).makeGraphic(Std.int(GRID_SIZE * 8), 4);
add(strumLine);
dummyArrow = new FlxSprite().makeGraphic(GRID_SIZE, GRID_SIZE);
@ -157,8 +177,8 @@ class ChartingState extends MusicBeatState
UI_box = new FlxUITabMenu(null, tabs, true);
UI_box.resize(300, 400);
UI_box.x = FlxG.width / 2;
UI_box.y = 20;
UI_box.x = (FlxG.width / 4) * 3;
UI_box.y = 120;
add(UI_box);
addSongUI();
@ -203,6 +223,11 @@ class ChartingState extends MusicBeatState
saveLevel();
});
var saveCompiler:FlxButton = new FlxButton(110, 30, "Save compile", function()
{
saveLevel(true);
});
var reloadSong:FlxButton = new FlxButton(saveButton.x + saveButton.width + 10, saveButton.y, "Reload Audio", function()
{
loadSong(_song.song);
@ -246,6 +271,7 @@ class ChartingState extends MusicBeatState
tab_group_song.add(check_voices);
tab_group_song.add(check_mute_inst);
tab_group_song.add(saveButton);
tab_group_song.add(saveCompiler);
tab_group_song.add(reloadSong);
tab_group_song.add(reloadSongJson);
tab_group_song.add(loadAutosaveBtn);
@ -257,7 +283,7 @@ class ChartingState extends MusicBeatState
UI_box.addGroup(tab_group_song);
UI_box.scrollFactor.set();
FlxG.camera.follow(strumLine);
FlxG.camera.focusOn(gridBG.getGraphicMidpoint());
}
var stepperLength:FlxUINumericStepper;
@ -342,6 +368,8 @@ class ChartingState extends MusicBeatState
UI_box.addGroup(tab_group_note);
}
// var spec:SpectogramSprite;
function loadSong(daSong:String):Void
{
if (FlxG.sound.music != null)
@ -369,11 +397,53 @@ class ChartingState extends MusicBeatState
FlxG.sound.playMusic(Paths.inst(daSong), 0.6);
var musSpec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music, FlxColor.RED);
musSpec.x += 70;
musSpec.daHeight = FlxG.height / 2;
musSpec.scrollFactor.set();
musSpec.visType = FREQUENCIES;
add(musSpec);
// trace(audioBuf.data.length);
playheadTest = new FlxSprite(0, 0).makeGraphic(2, 255, FlxColor.RED);
playheadTest.scrollFactor.set();
add(playheadTest);
// WONT WORK FOR TUTORIAL OR TEST SONG!!! REDO LATER
vocals = new FlxSound().loadEmbedded(Paths.voices(daSong));
FlxG.sound.list.add(vocals);
vocals = new VoicesGroup(daSong, _song.voiceList);
// vocals = new FlxSound().loadEmbedded(Paths.voices(daSong));
// FlxG.sound.list.add(vocals);
staticSpecGrp = new FlxTypedGroup<SpectogramSprite>();
add(staticSpecGrp);
var aBoy:ABotVis = new ABotVis(FlxG.sound.music);
add(aBoy);
for (index => voc in vocals.members)
{
var vocalSpec:SpectogramSprite = new SpectogramSprite(voc, FlxG.random.color(0xFFAAAAAA, FlxColor.WHITE, 100));
vocalSpec.x = 70 - (50 * index);
vocalSpec.visType = FREQUENCIES;
vocalSpec.daHeight = musSpec.daHeight;
vocalSpec.y = vocalSpec.daHeight;
vocalSpec.scrollFactor.set();
add(vocalSpec);
var staticVocal:SpectogramSprite = new SpectogramSprite(voc, FlxG.random.color(0xFFAAAAAA, FlxColor.WHITE, 100));
if (index == 0)
staticVocal.x -= 150;
if (index == 1)
staticVocal.x = gridBG.width;
staticVocal.daHeight = GRID_SIZE * 16;
staticVocal.visType = STATIC;
staticSpecGrp.add(staticVocal);
}
FlxG.sound.music.pause();
vocals.pause();
FlxG.sound.music.onComplete = function()
@ -485,15 +555,29 @@ class ChartingState extends MusicBeatState
return daPos;
}
var p1Muted:Bool = false;
var p2Muted:Bool = false;
override function update(elapsed:Float)
{
// FlxG.camera.followLerp = CoolUtil.camLerpShit(0.05);
curStep = recalculateSteps();
Conductor.songPosition = FlxG.sound.music.time;
_song.song = typingShit.text;
playheadTest.x = FlxMath.remapToRange(Conductor.songPosition, 0, FlxG.sound.music.length, 0, FlxG.width);
strumLine.y = getYfromStrum((Conductor.songPosition - sectionStartTime()) % (Conductor.stepCrochet * _song.notes[curSection].lengthInSteps));
/* if (FlxG.sound.music.playing)
{
var normalizedShitIDK:Int = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, FlxG.sound.music.length, 0, audioBuf.data.length));
FlxG.watch.addQuick('WEIRD AUDIO SHIT LOL', audioBuf.data[normalizedShitIDK]);
// leftIcon.scale.x = FlxMath.remapToRange(audioBuf.data[normalizedShitIDK], 0, 255, 1, 2);
}*/
if (FlxG.keys.justPressed.X)
toggleAltAnimNote();
@ -514,35 +598,106 @@ class ChartingState extends MusicBeatState
FlxG.watch.addQuick('daBeat', curBeat);
FlxG.watch.addQuick('daStep', curStep);
if (FlxG.mouse.justPressed)
if (FlxG.mouse.pressedMiddle)
{
if (FlxG.mouse.overlaps(curRenderedNotes))
if (FlxG.sound.music.playing)
{
curRenderedNotes.forEach(function(note:Note)
FlxG.sound.music.pause();
vocals.pause();
}
FlxG.sound.music.time = getStrumTime(FlxG.mouse.y) + sectionStartTime();
vocals.time = FlxG.sound.music.time;
}
if (FlxG.mouse.pressed)
{
if (FlxG.keys.pressed.ALT)
{
if (FlxG.sound.music.playing)
{
if (FlxG.mouse.overlaps(note))
{
if (FlxG.keys.pressed.CONTROL)
{
selectNote(note);
}
else
{
trace('tryin to delete note...');
deleteNote(note);
}
}
});
FlxG.sound.music.pause();
vocals.pause();
}
FlxG.sound.music.time = getStrumTime(FlxG.mouse.y) + sectionStartTime();
vocals.time = FlxG.sound.music.time;
}
else
{
if (FlxG.mouse.x > gridBG.x
&& FlxG.mouse.x < gridBG.x + gridBG.width
&& FlxG.mouse.y > gridBG.y
&& FlxG.mouse.y < gridBG.y + (GRID_SIZE * _song.notes[curSection].lengthInSteps))
if (FlxG.mouse.justPressed)
{
FlxG.log.add('added note');
addNote();
if (FlxG.mouse.overlaps(leftIcon))
{
if (leftIcon.char == _song.player1)
{
p1Muted = !p1Muted;
leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
}
else
{
p2Muted = !p2Muted;
leftIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
}
vocals.members[0].volume = p1Muted ? 0 : 1;
// null check jus in case using old shit?
if (vocals.members[1] != null)
vocals.members[1].volume = p2Muted ? 0 : 1;
}
// sloppy copypaste lol deal with it!
if (FlxG.mouse.overlaps(rightIcon))
{
if (rightIcon.char == _song.player1)
{
p1Muted = !p1Muted;
rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
}
else
{
rightIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
p2Muted = !p2Muted;
}
vocals.members[0].volume = p1Muted ? 0 : 1;
// null check jus in case using old shit?
if (vocals.members[1] != null)
vocals.members[1].volume = p2Muted ? 0 : 1;
}
if (FlxG.mouse.overlaps(curRenderedNotes))
{
curRenderedNotes.forEach(function(note:Note)
{
if (FlxG.mouse.overlaps(note))
{
if (FlxG.keys.pressed.CONTROL)
{
selectNote(note);
}
else
{
trace('tryin to delete note...');
deleteNote(note);
}
}
});
}
else
{
if (FlxG.mouse.x > gridBG.x
&& FlxG.mouse.x < gridBG.x + gridBG.width
&& FlxG.mouse.y > gridBG.y
&& FlxG.mouse.y < gridBG.y + (GRID_SIZE * _song.notes[curSection].lengthInSteps))
{
FlxG.log.add('added note');
addNote();
}
}
}
}
}
@ -561,9 +716,15 @@ class ChartingState extends MusicBeatState
if (FlxG.keys.justPressed.ENTER)
{
autosaveSong();
lastSection = curSection;
PlayState.SONG = _song;
// JUST FOR DEBUG DARNELL STUFF, GENERALIZE THIS FOR BETTER LOADING ELSEWHERE TOO!
PlayState.storyWeek = 8;
FlxG.sound.music.stop();
vocals.stop();
LoadingState.loadAndSwitchState(new PlayState());
@ -624,7 +785,10 @@ class ChartingState extends MusicBeatState
FlxG.sound.music.pause();
vocals.pause();
FlxG.sound.music.time -= (FlxG.mouse.wheel * Conductor.stepCrochet * 0.4);
var ctrlMod:Float = FlxG.keys.pressed.CONTROL ? 0.1 : 1;
var shiftMod:Float = FlxG.keys.pressed.SHIFT ? 2 : 1;
FlxG.sound.music.time -= (FlxG.mouse.wheel * Conductor.stepCrochet * 0.4 * ctrlMod * shiftMod);
vocals.time = FlxG.sound.music.time;
}
@ -637,6 +801,9 @@ class ChartingState extends MusicBeatState
var daTime:Float = 700 * FlxG.elapsed;
if (FlxG.keys.pressed.CONTROL)
daTime *= 0.2;
if (FlxG.keys.pressed.W)
{
FlxG.sound.music.time -= daTime;
@ -683,9 +850,9 @@ class ChartingState extends MusicBeatState
if (FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.A)
changeSection(curSection - shiftThing);
bpmTxt.text = bpmTxt.text = Std.string(FlxMath.roundDecimal(Conductor.songPosition / 1000, 2))
bpmTxt.text = bpmTxt.text = Std.string(FlxMath.roundDecimal(Conductor.songPosition / 1000, 3))
+ " / "
+ Std.string(FlxMath.roundDecimal(FlxG.sound.music.length / 1000, 2))
+ Std.string(FlxMath.roundDecimal(FlxG.sound.music.length / 1000, 3))
+ "\nSection: "
+ curSection;
super.update(elapsed);
@ -830,11 +997,17 @@ class ChartingState extends MusicBeatState
{
leftIcon.changeIcon(_song.player1);
rightIcon.changeIcon(_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.animation.curAnim.curFrame = p2Muted ? 1 : 0;
rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
}
leftIcon.setGraphicSize(0, 45);
rightIcon.setGraphicSize(0, 45);
@ -848,6 +1021,16 @@ class ChartingState extends MusicBeatState
function updateGrid():Void
{
// null if checks jus cuz i put updateGrid() in some weird places!
if (staticSpecGrp != null)
{
staticSpecGrp.forEach(function(spec)
{
if (spec != null)
spec.generateSection(sectionStartTime(), (Conductor.stepCrochet * 32) / 1000);
});
}
while (curRenderedNotes.members.length > 0)
{
curRenderedNotes.remove(curRenderedNotes.members[0], true);
@ -952,6 +1135,15 @@ class ChartingState extends MusicBeatState
{
if (i[0] == note.strumTime && i[1] % 4 == note.noteData)
{
var placeIDK:Int = Std.int(((Math.floor(dummyArrow.y / GRID_SIZE) * GRID_SIZE)) / 40);
placeIDK = Std.int(Math.min(placeIDK, 15));
placeIDK = Std.int(Math.max(placeIDK, 1));
trace(placeIDK);
FlxG.sound.play(Paths.sound('funnyNoise/funnyNoise-0' + placeIDK));
FlxG.log.add('FOUND EVIL NUMBER');
_song.notes[curSection].sectionNotes.remove(i);
}
@ -984,6 +1176,33 @@ class ChartingState extends MusicBeatState
var noteSus = 0;
var noteAlt = false;
// FlxG.sound.play(Paths.sound('pianoStuff/piano-00' + FlxG.random.int(1, 9)), FlxG.random.float(0.01, 0.3));
switch (noteData)
{
case 0:
FlxG.sound.play(Paths.sound('pianoStuff/piano-015'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-013'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-009'), FlxG.random.float(0.1, 0.3));
case 1:
FlxG.sound.play(Paths.sound('pianoStuff/piano-015'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-012'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-009'), FlxG.random.float(0.1, 0.3));
case 2:
FlxG.sound.play(Paths.sound('pianoStuff/piano-015'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-011'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-009'), FlxG.random.float(0.1, 0.3));
case 3:
FlxG.sound.play(Paths.sound('pianoStuff/piano-014'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-011'), FlxG.random.float(0.1, 0.3));
FlxG.sound.play(Paths.sound('pianoStuff/piano-010'), FlxG.random.float(0.1, 0.3));
}
var bullshit:Int = Std.int((Math.floor(dummyArrow.y / GRID_SIZE) * GRID_SIZE) / 40);
FlxG.sound.play(Paths.sound('pianoStuff/piano-00' + Std.string((bullshit % 8) + 1)), FlxG.random.float(0.3, 0.6));
// trace('bullshit $bullshit'); // trace(Math.floor(dummyArrow.y / GRID_SIZE) * GRID_SIZE);
_song.notes[curSection].sectionNotes.push([noteStrum, noteData, noteSus, noteAlt]);
curSelectedNote = _song.notes[curSection].sectionNotes[_song.notes[curSection].sectionNotes.length - 1];
@ -1074,7 +1293,7 @@ class ChartingState extends MusicBeatState
FlxG.save.flush();
}
private function saveLevel()
private function saveLevel(?debugSavepath:Bool = false)
{
var json = {
"song": _song
@ -1082,11 +1301,19 @@ class ChartingState extends MusicBeatState
var data:String = Json.stringify(json);
#if hl
#if sys
// quick workaround, since it easier to load into hashlink, thus quicker/nicer to test?
// should get this auto-saved into a file or somethin
var filename = _song.song.toLowerCase();
sys.io.File.saveContent('./$filename.json', data);
if (debugSavepath)
{
// file path to assumingly your assets folder in your SOURCE CODE assets folder!!!
// update this later so the save button ONLY appears when you compile in debug mode!
sys.io.File.saveContent('../../../../assets/preload/data/$filename/$filename.json', data);
}
else
sys.io.File.saveContent('./$filename.json', data);
#else
if ((data != null) && (data.length > 0))
{

View file

@ -23,9 +23,6 @@ class Conductor
public static var lastSongPos:Float;
public static var offset:Float = 0;
public static var safeFrames:Int = 10;
public static var safeZoneOffset:Float = (safeFrames / 60) * 1000; // is calculated in create(), is safeFrames in milliseconds
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
public function new()

View file

@ -872,7 +872,7 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
public function new(swipeDir:Int = FlxObject.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
{
super(ANDROID, swipeDir, Trigger);
super(OTHER, swipeDir, Trigger);
activateLength = swipeLength;
}

View file

@ -1,14 +1,21 @@
package;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import haxe.Json;
import haxe.format.JsonParser;
import lime.math.Rectangle;
import lime.utils.Assets;
import openfl.filters.ShaderFilter;
import shaderslmfao.ScreenWipeShader;
using StringTools;
@ -21,6 +28,11 @@ class CoolUtil
return difficultyArray[PlayState.storyDifficulty];
}
public static function coolBaseLog(base:Float, fin:Float):Float
{
return Math.log(fin) / Math.log(base);
}
public static function coolTextFile(path:String):Array<String>
{
var daList:Array<String> = [];
@ -88,6 +100,38 @@ class CoolUtil
return lerp * (FlxG.elapsed / (1 / 60));
}
public static function coolSwitchState(state:FlxState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
{
var screenShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("shaderTransitionStuff/coolDots"));
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
screenWipeShit.funnyShit.input = screenShit.pixels;
FlxTween.tween(screenWipeShit, {daAlphaShit: 1}, time, {
ease: FlxEase.quadInOut,
onComplete: function(twn)
{
screenShit.destroy();
FlxG.switchState(new MainMenuState());
}
});
FlxG.camera.setFilters([new ShaderFilter(screenWipeShit)]);
}
/**
* Hashlink json encoding fix for some wacky bullshit
* https://github.com/HaxeFoundation/haxe/issues/6930#issuecomment-384570392
*/
public static function coolJSON(fileData:String)
{
var cont = fileData;
function is(n:Int, what:Int)
return cont.charCodeAt(n) == what;
return JsonParser.parse(cont.substr(if (is(0, 65279)) /// looks like a HL target, skipping only first character here:
1 else if (is(0, 239) && is(1, 187) && is(2, 191)) /// it seems to be Neko or PHP, start from position 3:
3 else /// all other targets, that prepare the UTF string correctly
0));
}
/*
* frame dependant lerp kinda lol
*/

View file

@ -14,6 +14,9 @@ import flixel.math.FlxPoint;
import flixel.text.FlxText;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import freeplayStuff.DJBoyfriend;
import freeplayStuff.SongMenuItem;
import lime.app.Future;
import lime.utils.Assets;
@ -44,10 +47,10 @@ class FreeplayState extends MusicBeatState
];
private var grpSongs:FlxTypedGroup<Alphabet>;
private var grpCapsules:FlxTypedGroup<SongMenuItem>;
private var curPlaying:Bool = false;
private var iconArray:Array<HealthIcon> = [];
var bg:FlxSprite;
var scoreBG:FlxSprite;
override function create()
@ -98,36 +101,51 @@ class FreeplayState extends MusicBeatState
if (StoryMenuState.weekUnlocked[7] || isDebug)
addWeek(['Ugh', 'Guns', 'Stress'], 7, ['tankman']);
addWeek(["Darnell", "lit-up", "2hot"], 8, ['darnell']);
addWeek(["bro"], 1, ['gf']);
// LOAD MUSIC
// LOAD CHARACTERS
bg = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
bg.setGraphicSize(Std.int(FlxG.width));
bg.updateHitbox();
trace(FlxG.width);
trace(FlxG.camera.zoom);
trace(FlxG.camera.initialZoom);
trace(FlxCamera.defaultZoom);
trace(FlxG.initialZoom);
add(bg);
grpSongs = new FlxTypedGroup<Alphabet>();
add(grpSongs);
grpCapsules = new FlxTypedGroup<SongMenuItem>();
add(grpCapsules);
for (i in 0...songs.length)
{
var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, (i * 150) + 160, songs[i].songName);
funnyMenu.targetPos.x = funnyMenu.x;
funnyMenu.ID = i;
new FlxTimer().start((0.06 * i) + 0.8, function(lerpTmr)
{
funnyMenu.doLerp = true;
});
grpCapsules.add(funnyMenu);
var songText:Alphabet = new Alphabet(0, (70 * i) + 30, songs[i].songName, true, false);
songText.x += 100;
songText.isMenuItem = true;
songText.targetY = i;
grpSongs.add(songText);
// grpSongs.add(songText);
var icon:HealthIcon = new HealthIcon(songs[i].songCharacter);
icon.sprTracker = songText;
// using a FlxGroup is too much fuss!
iconArray.push(icon);
add(icon);
// add(icon);
// songText.x += 40;
// DONT PUT X IN THE FIRST PARAMETER OF new ALPHABET() !!
@ -152,6 +170,9 @@ class FreeplayState extends MusicBeatState
changeSelection();
changeDiff();
var dj:DJBoyfriend = new DJBoyfriend(0, -100);
add(dj);
// FlxG.sound.playMusic(Paths.music('title'), 0);
// FlxG.sound.music.fadeIn(2, 0, 0.8);
// selector = new FlxText();
@ -226,7 +247,6 @@ class FreeplayState extends MusicBeatState
}
lerpScore = CoolUtil.coolLerp(lerpScore, intendedScore, 0.4);
bg.color = FlxColor.interpolate(bg.color, coolColors[songs[curSelected].week % coolColors.length], CoolUtil.camLerpShit(0.045));
scoreText.text = "PERSONAL BEST:" + Math.round(lerpScore);
@ -360,7 +380,20 @@ class FreeplayState extends MusicBeatState
if (accepted)
{
// if (Assets.exists())
var poop:String = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), curDifficulty);
// does not work properly, always just accidentally sets it to normal anyways!
/* if (!Assets.exists(Paths.json(songs[curSelected].songName + '/' + poop)))
{
// defaults to normal if HARD / EASY doesn't exist
// does not account if NORMAL doesn't exist!
FlxG.log.warn("CURRENT DIFFICULTY IS NOT CHARTED, DEFAULTING TO NORMAL!");
poop = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), 1);
curDifficulty = 1;
}*/
PlayState.SONG = Song.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
PlayState.isStoryMode = false;
PlayState.storyDifficulty = curDifficulty;
@ -441,20 +474,15 @@ class FreeplayState extends MusicBeatState
iconArray[curSelected].alpha = 1;
for (item in grpSongs.members)
for (index => capsule in grpCapsules.members)
{
item.targetY = bullShit - curSelected;
bullShit++;
capsule.selected = false;
item.alpha = 0.6;
// item.setGraphicSize(Std.int(item.width * 0.8));
if (item.targetY == 0)
{
item.alpha = 1;
// item.setGraphicSize(Std.int(item.width));
}
capsule.targetPos.y = ((index - curSelected) * 150) + 160;
capsule.targetPos.x = 270 + (60 * (Math.sin(index - curSelected)));
}
grpCapsules.members[curSelected].selected = true;
}
function positionHighscore()

View file

@ -1,81 +0,0 @@
package;
import flixel.FlxSprite;
import flixel.addons.transition.FlxTransitionableState;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.input.gamepad.FlxGamepad;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
class GameOverState extends FlxTransitionableState
{
var bfX:Float = 0;
var bfY:Float = 0;
public function new(x:Float, y:Float)
{
super();
bfX = x;
bfY = y;
}
override function create()
{
/* var loser:FlxSprite = new FlxSprite(100, 100);
var loseTex = FlxAtlasFrames.fromSparrow(AssetPaths.lose.png, AssetPaths.lose.xml);
loser.frames = loseTex;
loser.animation.addByPrefix('lose', 'lose', 24, false);
loser.animation.play('lose');
// add(loser); */
var bf:Boyfriend = new Boyfriend(bfX, bfY);
// bf.scrollFactor.set();
add(bf);
bf.playAnim('firstDeath');
FlxG.camera.follow(bf, LOCKON, 0.001);
/*
var restart:FlxSprite = new FlxSprite(500, 50).loadGraphic(AssetPaths.restart.png);
restart.setGraphicSize(Std.int(restart.width * 0.6));
restart.updateHitbox();
restart.alpha = 0;
restart.antialiasing = true;
// add(restart); */
FlxG.sound.music.fadeOut(2, FlxG.sound.music.volume * 0.6);
// FlxTween.tween(restart, {alpha: 1}, 1, {ease: FlxEase.quartInOut});
// FlxTween.tween(restart, {y: restart.y + 40}, 7, {ease: FlxEase.quartInOut, type: PINGPONG});
super.create();
}
private var fading:Bool = false;
override function update(elapsed:Float)
{
var pressed:Bool = FlxG.keys.justPressed.ANY;
var gamepad:FlxGamepad = FlxG.gamepads.lastActive;
if (gamepad != null)
{
if (gamepad.justPressed.ANY)
pressed = true;
}
pressed = false;
if (pressed && !fading)
{
fading = true;
FlxG.sound.music.fadeOut(0.5, 0, function(twn:FlxTween)
{
FlxG.sound.music.stop();
LoadingState.loadAndSwitchState(new PlayState());
});
}
super.update(elapsed);
}
}

View file

@ -3,6 +3,7 @@ package;
import flixel.FlxObject;
import flixel.FlxSubState;
import flixel.math.FlxPoint;
import flixel.system.FlxSound;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import haxe.display.Display.Package;
@ -16,8 +17,13 @@ class GameOverSubstate extends MusicBeatSubstate
var stageSuffix:String = "";
var randomGameover:Int = 1;
var gameOverMusic:FlxSound;
public function new(x:Float, y:Float)
{
gameOverMusic = new FlxSound();
FlxG.sound.list.add(gameOverMusic);
var daStage = PlayState.curStage;
var daBf:String = '';
switch (daStage)
@ -48,10 +54,18 @@ class GameOverSubstate extends MusicBeatSubstate
add(camFollow);
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + stageSuffix));
Conductor.changeBPM(100);
// Conductor.changeBPM(100);
switch (PlayState.SONG.player1)
{
case 'pico':
stageSuffix = 'Pico';
}
// FlxG.camera.followLerp = 1;
// FlxG.camera.focusOn(FlxPoint.get(FlxG.width / 2, FlxG.height / 2));
// commented out for now
FlxG.camera.scroll.set();
FlxG.camera.target = null;
@ -93,7 +107,8 @@ class GameOverSubstate extends MusicBeatSubstate
{
PlayState.deathCounter = 0;
PlayState.seenCutscene = false;
FlxG.sound.music.stop();
// FlxG.sound.music.stop();
gameOverMusic.stop();
if (PlayState.isStoryMode)
FlxG.switchState(new StoryMenuState());
@ -119,7 +134,10 @@ class GameOverSubstate extends MusicBeatSubstate
FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + randomGameover), 1, false, null, true, function()
{
if (!isEnding)
FlxG.sound.music.fadeIn(4, 0.2, 1);
{
gameOverMusic.fadeIn(4, 0.2, 1);
}
// FlxG.sound.music.fadeIn(4, 0.2, 1);
});
}
default:
@ -130,23 +148,21 @@ class GameOverSubstate extends MusicBeatSubstate
}
}
if (FlxG.sound.music.playing)
if (gameOverMusic.playing)
{
Conductor.songPosition = FlxG.sound.music.time;
Conductor.songPosition = gameOverMusic.time;
}
}
private function coolStartDeath(?vol:Float = 1):Void
{
if (!isEnding)
FlxG.sound.playMusic(Paths.music('gameOver' + stageSuffix), vol);
}
override function beatHit()
{
super.beatHit();
FlxG.log.add('beat');
{
gameOverMusic.loadEmbedded(Paths.music('gameOver' + stageSuffix));
gameOverMusic.volume = vol;
gameOverMusic.play();
}
// FlxG.sound.playMusic();
}
var isEnding:Bool = false;
@ -157,13 +173,17 @@ class GameOverSubstate extends MusicBeatSubstate
{
isEnding = true;
bf.playAnim('deathConfirm', true);
FlxG.sound.music.stop();
gameOverMusic.stop();
// FlxG.sound.music.stop();
FlxG.sound.play(Paths.music('gameOverEnd' + stageSuffix));
new FlxTimer().start(0.7, function(tmr:FlxTimer)
{
FlxG.camera.fade(FlxColor.BLACK, 2, false, function()
{
LoadingState.loadAndSwitchState(new PlayState());
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
PlayState.needsReset = true;
close();
// LoadingState.loadAndSwitchState(new PlayState());
});
});
}

View file

@ -1,6 +1,7 @@
package;
import flixel.FlxSprite;
import openfl.utils.Assets;
using StringTools;
@ -11,7 +12,8 @@ class HealthIcon extends FlxSprite
*/
public var sprTracker:FlxSprite;
var char:String = '';
public var char:String = '';
var isPlayer:Bool = false;
public function new(char:String = 'bf', isPlayer:Bool = false)
@ -44,6 +46,12 @@ class HealthIcon extends FlxSprite
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)

130
source/InitState.hx Normal file
View file

@ -0,0 +1,130 @@
package;
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.TransitionData;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.util.FlxColor;
import openfl.display.BitmapData;
import ui.PreferencesMenu;
import ui.stageBuildShit.StageBuilderState;
using StringTools;
#if colyseus
import io.colyseus.Client;
import io.colyseus.Room;
#end
#if discord_rpc
import Discord.DiscordClient;
#end
#if desktop
import sys.FileSystem;
import sys.io.File;
import sys.thread.Thread;
#end
class InitState extends FlxTransitionableState
{
override public function create():Void
{
#if android
FlxG.android.preventDefaultKeys = [FlxAndroidKey.BACK];
#end
#if newgrounds
NGio.init();
#end
#if discord_rpc
DiscordClient.initialize();
Application.current.onExit.add(function(exitCode)
{
DiscordClient.shutdown();
});
#end
// ==== flixel shit ==== //
// This big obnoxious white button is for MOBILE, so that you can press it
// easily with your finger when debug bullshit pops up during testing lol!
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function()
{
FlxG.debugger.visible = false;
});
FlxG.sound.muteKeys = [ZERO];
FlxG.game.focusLostFramerate = 60;
// FlxG.stage.window.borderless = true;
// FlxG.stage.window.mouseLock = true;
var diamond:FlxGraphic = FlxGraphic.fromClass(GraphicTransTileDiamond);
diamond.persist = true;
diamond.destroyOnNoUse = false;
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), {asset: diamond, width: 32, height: 32},
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), {asset: diamond, width: 32, height: 32},
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
// ===== save shit ===== //
FlxG.save.bind('funkin', 'ninjamuffin99');
// https://github.com/HaxeFlixel/flixel/pull/2396
// IF/WHEN MY PR GOES THRU AND IT GETS INTO MAIN FLIXEL, DELETE THIS CHUNKOF CODE, AND THEN UNCOMMENT THE LINE BELOW
// FlxG.sound.loadSavedPrefs();
if (FlxG.save.data.volume != null)
FlxG.sound.volume = FlxG.save.data.volume;
if (FlxG.save.data.mute != null)
FlxG.sound.muted = FlxG.save.data.mute;
// FlxG.save.close();
// FlxG.sound.loadSavedPrefs();
PreferencesMenu.initPrefs();
PlayerSettings.init();
Highscore.load();
if (FlxG.save.data.weekUnlocked != null)
{
// FIX LATER!!!
// WEEK UNLOCK PROGRESSION!!
// StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked;
if (StoryMenuState.weekUnlocked.length < 4)
StoryMenuState.weekUnlocked.insert(0, true);
// QUICK PATCH OOPS!
if (!StoryMenuState.weekUnlocked[0])
StoryMenuState.weekUnlocked[0] = true;
}
if (FlxG.save.data.seenVideo != null)
VideoState.seenVideo = FlxG.save.data.seenVideo;
// ===== fuck outta here ===== //
// FlxTransitionableState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
#if FREEPLAY
FlxG.switchState(new FreeplayState());
#elseif ANIMATE
FlxG.switchState(new animate.AnimTestStage());
#elseif CHARTING
FlxG.switchState(new ChartingState());
#elseif STAGEBUILD
FlxG.switchState(new StageBuilderState());
#elseif ANIMDEBUG
FlxG.switchState(new ui.animDebugShit.DebugBoundingState());
#elseif NETTEST
FlxG.switchState(new netTest.NetTest());
#else
FlxG.sound.cache(Paths.music('freakyMenu'));
FlxG.switchState(new TitleState());
#end
}
}

View file

@ -57,7 +57,18 @@ class LoadingState extends MusicBeatState
var introComplete = callbacks.add("introComplete");
checkLoadSong(getSongPath());
if (PlayState.SONG.needsVoices)
checkLoadSong(getVocalPath());
{
var files = PlayState.SONG.voiceList;
if (files == null)
files = [""]; // loads with no file name assumption, to load "Voices.ogg" or whatev normally
for (sndFile in files)
{
checkLoadSong(getVocalPath(sndFile));
}
}
checkLibrary("shared");
if (PlayState.storyWeek > 0)
checkLibrary("week" + PlayState.storyWeek);
@ -164,9 +175,9 @@ class LoadingState extends MusicBeatState
return Paths.inst(PlayState.SONG.song);
}
static function getVocalPath()
static function getVocalPath(?suffix:String)
{
return Paths.voices(PlayState.SONG.song);
return Paths.voices(PlayState.SONG.song, suffix);
}
inline static public function loadAndSwitchState(target:FlxState, stopMusic = false)

View file

@ -19,7 +19,7 @@ class Main extends Sprite
{
var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom).
var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
var initialState:Class<FlxState> = TitleState; // The FlxState the game starts with.
var initialState:Class<FlxState> = InitState; // The FlxState the game starts with.
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
#if web
var framerate:Int = 60; // How many frames per second the game should run at.
@ -48,7 +48,7 @@ class Main extends Sprite
frameworkParams: {
assetLibraryPaths: [
"songs" => "songs", "shared" => "shared", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2", "week3" => "week3",
"week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "tanky" => "tanky", "tankBG" => "tankBG"
"week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8"
]
},
framework: OPENFL,

View file

@ -16,6 +16,8 @@ import flixel.ui.FlxButton;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import lime.app.Application;
import openfl.filters.ShaderFilter;
import shaderslmfao.ScreenWipeShader;
import ui.AtlasMenuList;
import ui.MenuList;
import ui.OptionsState;

View file

@ -34,7 +34,6 @@ class Note extends FlxSprite
public var isSustainNote:Bool = false;
public var colorSwap:ColorSwap;
public var noteScore:Float = 1;
public static var swagWidth:Float = 160 * 0.7;
public static var PURP_NOTE:Int = 0;
@ -42,6 +41,15 @@ class Note extends FlxSprite
public static var BLUE_NOTE:Int = 1;
public static var RED_NOTE:Int = 3;
// SCORING STUFF
public static var safeFrames:Int = 10;
public static var HIT_WINDOW:Float = (safeFrames / 60) * 1000; // 166.67 ms hit window
// anything above bad threshold is shit
public static var BAD_THRESHOLD:Float = 0.8; // 125ms , 8 frames
public static var GOOD_THRESHOLD:Float = 0.55; // 91.67ms , 5.5 frames
public static var SICK_THRESHOLD:Float = 0.2; // 33.33ms , 2 frames
// anything below sick threshold is sick
public static var arrowColors:Array<Float> = [1, 1, 1, 1];
public function new(strumTime:Float, noteData:Int, ?prevNote:Note, ?sustainNote:Bool = false)
@ -145,7 +153,6 @@ class Note extends FlxSprite
if (isSustainNote && prevNote != null)
{
noteScore * 0.2;
alpha = 0.6;
if (PreferencesMenu.getPref('downscroll'))
@ -224,9 +231,9 @@ class Note extends FlxSprite
}
else
{
if (strumTime > Conductor.songPosition - Conductor.safeZoneOffset)
{ // The * 0.5 is so that it's easier to hit them too late, instead of too early
if (strumTime < Conductor.songPosition + (Conductor.safeZoneOffset * 0.5))
if (strumTime > Conductor.songPosition - HIT_WINDOW)
{ // * 0.2 if sustain note, so u have to keep holding it closer to all the way thru!
if (strumTime < Conductor.songPosition + (HIT_WINDOW * (isSustainNote ? 0.2 : 1)))
canBeHit = true;
}
else

View file

@ -31,17 +31,12 @@ class NoteSplash extends FlxSprite
alpha = 0.6;
animation.play('note' + noteData + '-' + FlxG.random.int(0, 1), true);
animation.curAnim.frameRate += FlxG.random.int(-2, 2);
animation.curAnim.frameRate = 24 + FlxG.random.int(-2, 2);
animation.finishCallback = function(name){
kill();
};
updateHitbox();
offset.set(width * 0.3, height * 0.3);
}
override function update(elapsed:Float)
{
if (animation.curAnim.finished)
kill();
super.update(elapsed);
}
}

View file

@ -25,12 +25,12 @@ class Paths
var levelPath = getLibraryPathForce(file, currentLevel);
if (OpenFlAssets.exists(levelPath, type))
return levelPath;
levelPath = getLibraryPathForce(file, "shared");
if (OpenFlAssets.exists(levelPath, type))
return levelPath;
}
var levelPath = getLibraryPathForce(file, "shared");
if (OpenFlAssets.exists(levelPath, type))
return levelPath;
return getPreloadPath(file);
}
@ -84,9 +84,12 @@ class Paths
return getPath('music/$key.$SOUND_EXT', MUSIC, library);
}
inline static public function voices(song:String)
inline static public function voices(song:String, ?suffix:String)
{
return 'songs:assets/songs/${song.toLowerCase()}/Voices.$SOUND_EXT';
if (suffix == null)
suffix = ""; // no suffix, for a sorta backwards compatibility with older-ish voice files
return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.$SOUND_EXT';
}
inline static public function inst(song:String)

View file

@ -174,7 +174,10 @@ class PauseSubState extends MusicBeatSubstate
menuItems = pauseOG;
regenMenu();
case "Restart Song":
FlxG.resetState();
PlayState.needsReset = true;
close();
// FlxG.resetState();
case "Exit to menu":
PlayState.seenCutscene = false;
PlayState.deathCounter = 0;

View file

@ -46,17 +46,22 @@ class PlayState extends MusicBeatState
public static var storyDifficulty:Int = 1;
public static var deathCounter:Int = 0;
public static var practiceMode:Bool = false;
public static var needsReset:Bool = false;
var halloweenLevel:Bool = false;
private var vocals:FlxSound;
private var vocals:VoicesGroup;
private var vocalsFinished:Bool = false;
private var dad:Character;
private var gf:Character;
private var boyfriend:Boyfriend;
/**
* Notes that should be ON SCREEN and have UPDATES running on them!
*/
private var notes:FlxTypedGroup<Note>;
private var unspawnNotes:Array<Note> = [];
private var strumLine:FlxSprite;
@ -73,6 +78,7 @@ class PlayState extends MusicBeatState
private var gfSpeed:Int = 1;
private var health:Float = 1;
private var healthDisplay:Float = 1;
private var combo:Int = 0;
private var healthBarBG:FlxSprite;
@ -86,7 +92,7 @@ class PlayState extends MusicBeatState
private var camHUD:FlxCamera;
private var camGame:FlxCamera;
var dialogue:Array<String> = ['blah blah blah', 'coolswag'];
var dialogue:Array<String>;
public static var seenCutscene:Bool = false;
@ -120,6 +126,9 @@ class PlayState extends MusicBeatState
var songScore:Int = 0;
var scoreTxt:FlxText;
// Dunno why its called doof lol, it's just the dialogue box
var doof:DialogueBox;
var grpNoteSplashes:FlxTypedGroup<NoteSplash>;
public static var campaignScore:Int = 0;
@ -176,23 +185,6 @@ class PlayState extends MusicBeatState
switch (SONG.song.toLowerCase())
{
case 'tutorial':
dialogue = ["Hey you're pretty cute.", 'Use the arrow keys to keep up \nwith me singing.'];
case 'bopeebo':
dialogue = [
'HEY!',
"You think you can just sing\nwith my daughter like that?",
"If you want to date her...",
"You're going to have to go \nthrough ME first!"
];
case 'fresh':
dialogue = ["Not too shabby boy.", ""];
case 'dadbattle':
dialogue = [
"gah you think you're hot stuff?",
"If you can beat me here...",
"Only then I will even CONSIDER letting you\ndate my daughter!"
];
case 'senpai':
dialogue = CoolUtil.coolTextFile(Paths.txt('senpai/senpaiDialogue'));
case 'roses':
@ -435,7 +427,28 @@ class PlayState extends MusicBeatState
bgGirls.updateHitbox();
add(bgGirls);
case 'thorns':
loadStage('schoolEvil');
// loadStage('schoolEvil');
curStage = 'schoolEvil';
var schoolBG:FlxSprite = new FlxSprite(-200, 0).loadGraphic(Paths.image('weeb/evilSchoolBG'));
wiggleShit.waveAmplitude = 0.017;
wiggleShit.waveSpeed = 2;
wiggleShit.waveFrequency = 4;
schoolBG.shader = wiggleShit.shader;
schoolBG.setGraphicSize(Std.int(schoolBG.width * 6));
schoolBG.updateHitbox();
// schoolBG.scale.set(6, 6);
add(schoolBG);
schoolBG.scrollFactor.set(0.7, 1);
var schoolFront:FlxSprite = new FlxSprite(-250, schoolBG.y + 20).loadGraphic(Paths.image('weeb/evilSchoolFG'));
schoolFront.shader = wiggleShit.shader;
schoolFront.setGraphicSize(Std.int(schoolFront.width * 6));
schoolFront.updateHitbox();
add(schoolFront);
case 'guns' | 'stress' | 'ugh':
loadStage('tank');
@ -485,10 +498,14 @@ class PlayState extends MusicBeatState
var fgTank3:BGSprite = new BGSprite('tank3', 1300, 1200, 3.5, 2.5, ['fg']);
foregroundSprites.add(fgTank3);
case "darnell":
loadStage('phillyStreets');
default:
loadStage('stage');
}
// all dis is shitty, redo later for stage shit
var gfVersion:String = 'gf';
switch (curStage)
@ -503,6 +520,11 @@ class PlayState extends MusicBeatState
gfVersion = 'gf-tankmen';
}
if (SONG.player1 == "pico")
{
gfVersion = "nene";
}
if (SONG.song.toLowerCase() == 'stress')
gfVersion = 'pico-speaker';
@ -619,6 +641,8 @@ class PlayState extends MusicBeatState
gf.x -= 170;
gf.y -= 75;
}
case 'stage' | 'phillyStreets':
dad.y = 870 - dad.height;
}
add(gf);
@ -638,11 +662,13 @@ class PlayState extends MusicBeatState
add(foregroundSprites);
var doof:DialogueBox = new DialogueBox(false, dialogue);
// doof.x += 70;
// doof.y = FlxG.height * 0.5;
doof.scrollFactor.set();
doof.finishThing = startCountdown;
if (dialogue != null)
{
doof = new DialogueBox(false, dialogue);
doof.scrollFactor.set();
doof.finishThing = startCountdown;
doof.cameras = [camHUD];
}
Conductor.songPosition = -5000;
@ -652,7 +678,6 @@ class PlayState extends MusicBeatState
strumLine.y = FlxG.height - 150; // 150 just random ass number lol
strumLine.scrollFactor.set();
strumLineNotes = new FlxTypedGroup<FlxSprite>();
add(strumLineNotes);
@ -684,10 +709,7 @@ class PlayState extends MusicBeatState
add(camFollow);
FlxG.camera.follow(camFollow, LOCKON, 0.04);
// FlxG.camera.setScrollBounds(0, FlxG.width, 0, FlxG.height);
FlxG.camera.zoom = defaultCamZoom;
FlxG.camera.focusOn(camFollow.getPosition());
resetCamFollow();
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
@ -702,7 +724,7 @@ class PlayState extends MusicBeatState
healthBarBG.y = FlxG.height * 0.1;
healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this,
'health', 0, 2);
'healthDisplay', 0, 2);
healthBar.scrollFactor.set();
healthBar.createFilledBar(0xFFFF0000, 0xFF66FF33);
// healthBar
@ -729,7 +751,6 @@ class PlayState extends MusicBeatState
iconP1.cameras = [camHUD];
iconP2.cameras = [camHUD];
scoreTxt.cameras = [camHUD];
doof.cameras = [camHUD];
// if (SONG.song == 'South')
// FlxG.camera.alpha = 0.7;
@ -773,7 +794,7 @@ class PlayState extends MusicBeatState
});
});
case 'senpai' | 'roses' | 'thorns':
schoolIntro(doof);
schoolIntro(doof); // doof is assumed to be non-null, lol!
case 'ugh':
ughIntro();
case 'stress':
@ -1382,7 +1403,14 @@ class PlayState extends MusicBeatState
generateStaticArrows(1);
talking = false;
restartCountdownTimer();
}
function restartCountdownTimer():Void
{
startedCountdown = true;
Conductor.songPosition = 0;
Conductor.songPosition -= Conductor.crochet * 5;
@ -1469,7 +1497,13 @@ class PlayState extends MusicBeatState
previousFrameTime = FlxG.game.ticks;
if (!paused)
FlxG.sound.playMusic(Paths.inst(SONG.song), 1, false);
{
if (FlxG.sound.music != null)
FlxG.sound.music.play(true);
else
FlxG.sound.playMusic(Paths.inst(SONG.song), 1, false);
}
FlxG.sound.music.onComplete = endSong;
vocals.play();
@ -1486,29 +1520,44 @@ class PlayState extends MusicBeatState
{
// FlxG.log.add(ChartParser.parse());
var songData = SONG;
Conductor.changeBPM(songData.bpm);
Conductor.changeBPM(SONG.bpm);
curSong = songData.song;
curSong = SONG.song;
if (SONG.needsVoices)
vocals = new FlxSound().loadEmbedded(Paths.voices(SONG.song));
vocals = new VoicesGroup(SONG.song, SONG.voiceList);
else
vocals = new FlxSound();
vocals = new VoicesGroup(SONG.song, null, false);
vocals.onComplete = function()
vocals.members[0].onComplete = function()
{
vocalsFinished = true;
};
FlxG.sound.list.add(vocals);
notes = new FlxTypedGroup<Note>();
add(notes);
regenNoteData();
generatedMusic = true;
}
function regenNoteData():Void
{
// make unspawn notes shit def empty
unspawnNotes = [];
notes.forEach(function(nt)
{
nt.kill();
notes.remove(nt, true);
nt.destroy();
});
var noteData:Array<SwagSection>;
// NEW SHIT
noteData = songData.notes;
noteData = SONG.notes;
for (section in noteData)
{
@ -1560,8 +1609,6 @@ class PlayState extends MusicBeatState
}
unspawnNotes.sort(sortByShit);
generatedMusic = true;
}
// Now you are probably wondering why I made 2 of these very similar functions
@ -1765,7 +1812,7 @@ class PlayState extends MusicBeatState
if (vocalsFinished)
return;
vocals.time = Conductor.songPosition;
vocals.time = FlxG.sound.music.time;
vocals.play();
}
@ -1775,16 +1822,28 @@ class PlayState extends MusicBeatState
override public function update(elapsed:Float)
{
// makes the lerp non-dependant on the framerate
// FlxG.camera.followLerp = CoolUtil.camLerpShit(0.04);
healthDisplay = FlxMath.lerp(healthDisplay, health, 0.15);
#if !debug
perfectMode = false;
#else
if (FlxG.keys.justPressed.H)
camHUD.visible = !camHUD.visible;
if (FlxG.keys.justPressed.K)
if (needsReset)
{
resetCamFollow();
paused = false;
persistentUpdate = true;
persistentDraw = true;
startingSong = true;
FlxG.sound.music.pause();
vocals.pause();
FlxG.sound.music.time = 0;
regenNoteData(); // loads the note data from start
health = 1;
restartCountdownTimer();
needsReset = false;
// FlxScreenGrab.grab(null, true, true);
/*
@ -1796,6 +1855,22 @@ class PlayState extends MusicBeatState
*/
// sys.io.File.saveContent('./swag.png', png.readUTFBytes(png.length));
}
#if !debug
perfectMode = false;
#else
if (FlxG.keys.justPressed.H)
camHUD.visible = !camHUD.visible;
if (FlxG.keys.justPressed.K)
{
@:privateAccess
var funnyData:Array<Int> = cast FlxG.sound.music._channel.__source.buffer.data;
funnyData.reverse();
@:privateAccess
FlxG.sound.music._channel.__source.buffer.data = cast funnyData;
}
#end
// do this BEFORE super.update() so songPosition is accurate
@ -1810,6 +1885,9 @@ class PlayState extends MusicBeatState
}
else
{
if (Paths.SOUND_EXT == 'mp3')
Conductor.offset = -13; // DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM!
Conductor.songPosition = FlxG.sound.music.time + Conductor.offset; // 20 is THE MILLISECONDS??
// Conductor.songPosition += FlxG.elapsed * 1000;
@ -1853,6 +1931,8 @@ class PlayState extends MusicBeatState
super.update(elapsed);
wiggleShit.update(elapsed);
scoreTxt.text = "Score:" + songScore;
var androidPause:Bool = false;
@ -1868,6 +1948,7 @@ class PlayState extends MusicBeatState
paused = true;
// 1 / 1000 chance for Gitaroo Man easter egg
// can this please move to dying it's kinda fucked up that pausing has a 1/1000 chance ur forced to restart
if (FlxG.random.bool(0.1))
{
// gitaroo man easter egg
@ -1896,12 +1977,12 @@ class PlayState extends MusicBeatState
#end
}
if (FlxG.keys.justPressed.EIGHT)
FlxG.switchState(new ui.animDebugShit.DebugBoundingState());
if (FlxG.keys.justPressed.NINE)
iconP1.swapOldIcon();
// FlxG.watch.addQuick('VOL', vocals.amplitudeLeft);
// FlxG.watch.addQuick('VOLRight', vocals.amplitudeRight);
iconP1.setGraphicSize(Std.int(CoolUtil.coolLerp(iconP1.width, 150, 0.15)));
iconP2.setGraphicSize(Std.int(CoolUtil.coolLerp(iconP2.width, 150, 0.15)));
@ -1910,8 +1991,8 @@ class PlayState extends MusicBeatState
var iconOffset:Int = 26;
iconP1.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 100, 0) * 0.01) - iconOffset);
iconP2.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 100, 0) * 0.01)) - (iconP2.width - iconOffset);
iconP1.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.value, 0, 2, 100, 0) * 0.01) - iconOffset);
iconP2.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.value, 0, 2, 100, 0) * 0.01)) - (iconP2.width - iconOffset);
if (health > 2)
health = 2;
@ -1968,24 +2049,9 @@ class PlayState extends MusicBeatState
gfSpeed = 2;
case 112:
gfSpeed = 1;
case 163:
// FlxG.sound.music.stop();
// FlxG.switchState(new TitleState());
}
}
if (curSong == 'Bopeebo')
{
switch (curBeat)
{
case 128, 129, 130:
vocals.volume = 0;
// FlxG.sound.music.stop();
// FlxG.switchState(new PlayState());
}
}
// better streaming of shit
if (!inCutscene && !_exiting)
{
// RESET = Quick Game Over Screen
@ -2011,8 +2077,8 @@ class PlayState extends MusicBeatState
persistentDraw = false;
paused = true;
vocals.stop();
FlxG.sound.music.stop();
vocals.pause();
FlxG.sound.music.pause();
// unloadAssets();
@ -2020,8 +2086,6 @@ class PlayState extends MusicBeatState
openSubState(new GameOverSubstate(boyfriend.getScreenPosition().x, boyfriend.getScreenPosition().y));
// FlxG.switchState(new GameOverState(boyfriend.getScreenPosition().x, boyfriend.getScreenPosition().y));
#if discord_rpc
// Game Over doesn't get his own variable because it's only used here
DiscordClient.changePresence("Game Over - " + detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
@ -2034,7 +2098,6 @@ class PlayState extends MusicBeatState
var dunceNote:Note = unspawnNotes[0];
notes.add(dunceNote);
var index:Int = unspawnNotes.indexOf(dunceNote);
unspawnNotes.shift();
}
@ -2101,16 +2164,19 @@ class PlayState extends MusicBeatState
if (daNote.altNote)
altAnim = '-alt';
switch (Math.abs(daNote.noteData))
if (!daNote.isSustainNote)
{
case 0:
dad.playAnim('singLEFT' + altAnim, true);
case 1:
dad.playAnim('singDOWN' + altAnim, true);
case 2:
dad.playAnim('singUP' + altAnim, true);
case 3:
dad.playAnim('singRIGHT' + altAnim, true);
switch (Math.abs(daNote.noteData))
{
case 0:
dad.playAnim('singLEFT' + altAnim, true);
case 1:
dad.playAnim('singDOWN' + altAnim, true);
case 2:
dad.playAnim('singUP' + altAnim, true);
case 3:
dad.playAnim('singRIGHT' + altAnim, true);
}
}
dad.holdTimer = 0;
@ -2149,7 +2215,7 @@ class PlayState extends MusicBeatState
{
if (daNote.tooLate)
{
health -= 0.0475;
health -= 0.0775;
vocals.volume = 0;
killCombo();
}
@ -2215,6 +2281,7 @@ class PlayState extends MusicBeatState
daPos += 4 * (1000 * 60 / daBPM);
}
Conductor.songPosition = FlxG.sound.music.time = daPos;
Conductor.songPosition += Conductor.offset;
updateCurStep();
resyncVocals();
}
@ -2331,25 +2398,39 @@ class PlayState extends MusicBeatState
var isSick:Bool = true;
if (noteDiff > Conductor.safeZoneOffset * 0.9)
var healthMulti:Float = 1;
if (daNote.noteData >= 0)
healthMulti *= 0.033;
else
healthMulti *= 0.002;
if (noteDiff > Note.HIT_WINDOW * Note.BAD_THRESHOLD)
{
healthMulti *= 0; // no health on shit note
daRating = 'shit';
score = 50;
isSick = false; // shitty copypaste on this literally just because im lazy and tired lol!
}
else if (noteDiff > Conductor.safeZoneOffset * 0.75)
else if (noteDiff > Note.HIT_WINDOW * Note.GOOD_THRESHOLD)
{
healthMulti *= 0.2;
daRating = 'bad';
score = 100;
isSick = false;
}
else if (noteDiff > Conductor.safeZoneOffset * 0.2)
else if (noteDiff > Note.HIT_WINDOW * Note.SICK_THRESHOLD)
{
healthMulti *= 0.78;
daRating = 'good';
score = 200;
isSick = false;
}
health += healthMulti;
if (isSick)
{
var noteSplash:NoteSplash = grpNoteSplashes.recycle(NoteSplash);
@ -2744,7 +2825,7 @@ class PlayState extends MusicBeatState
function noteMiss(direction:Int = 1):Void
{
// whole function used to be encased in if (!boyfriend.stunned)
health -= 0.04;
health -= 0.07;
killCombo();
if (!practiceMode)
@ -2804,11 +2885,6 @@ class PlayState extends MusicBeatState
popUpScore(note.strumTime, note);
}
if (note.noteData >= 0)
health += 0.023;
else
health += 0.004;
switch (note.noteData)
{
case 0:
@ -2841,6 +2917,14 @@ class PlayState extends MusicBeatState
}
}
function resetCamFollow():Void
{
FlxG.camera.follow(camFollow, LOCKON, 0.04);
// FlxG.camera.setScrollBounds(0, FlxG.width, 0, FlxG.height);
FlxG.camera.zoom = defaultCamZoom;
FlxG.camera.focusOn(camFollow.getPosition());
}
var fastCarCanDrive:Bool = true;
function resetFastCar():Void
@ -2955,11 +3039,6 @@ class PlayState extends MusicBeatState
{
resyncVocals();
}
if (dad.curCharacter == 'spooky' && curStep % 4 == 2)
{
// dad.dance();
}
}
var lightningStrikeBeat:Int = 0;
@ -2985,7 +3064,6 @@ class PlayState extends MusicBeatState
// Conductor.changeBPM(SONG.bpm);
}
// FlxG.log.add('change bpm' + SONG.notes[Std.int(curStep / 16)].changeBPM);
wiggleShit.update(Conductor.crochet);
// HARDCODING FOR MILF ZOOMS!
@ -3017,7 +3095,7 @@ class PlayState extends MusicBeatState
{
var animShit:ComboCounter = new ComboCounter(-100, 300, combo);
animShit.scrollFactor.set(0.6, 0.6);
add(animShit);
// add(animShit);
var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation

View file

@ -13,6 +13,7 @@ typedef SwagSong =
var notes:Array<SwagSection>;
var bpm:Float;
var needsVoices:Bool;
var voiceList:Array<String>;
var speed:Float;
var player1:String;

291
source/SpectogramSprite.hx Normal file
View file

@ -0,0 +1,291 @@
package;
import dsp.FFT;
import flixel.FlxSprite;
import flixel.group.FlxGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxVector;
import flixel.system.FlxSound;
import flixel.util.FlxColor;
import lime.utils.Int16Array;
using Lambda;
using flixel.util.FlxSpriteUtil;
class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
{
var sampleRate:Int;
var lengthOfShit:Int = 500;
public var visType:VISTYPE = UPDATED;
public var col:Int = FlxColor.WHITE;
public var daHeight:Float = FlxG.height;
public var vis:VisShit;
public function new(daSound:FlxSound, ?col:FlxColor = FlxColor.WHITE, ?height:Float = 720)
{
super();
vis = new VisShit(daSound);
this.col = col;
this.daHeight = height;
regenLineShit();
// makeGraphic(200, 200, FlxColor.BLACK);
}
public function regenLineShit():Void
{
for (i in 0...lengthOfShit)
{
var lineShit:FlxSprite = new FlxSprite(100, i / lengthOfShit * daHeight).makeGraphic(1, 1, col);
lineShit.active = false;
add(lineShit);
}
}
var setBuffer:Bool = false;
public var audioData:Int16Array;
var numSamples:Int = 0;
override function update(elapsed:Float)
{
switch (visType)
{
case UPDATED:
updateVisulizer();
case FREQUENCIES:
updateFFT();
default:
}
// if visType is static, call updateVisulizer() manually whenever you want to update it!
super.update(elapsed);
}
/**
* @param start is the start in milliseconds?
*/
public function generateSection(start:Float = 0, seconds:Float = 1):Void
{
checkAndSetBuffer();
// vis.checkAndSetBuffer();
if (setBuffer)
{
var samplesToGen:Int = Std.int(sampleRate * seconds);
var startingSample:Int = Std.int(FlxMath.remapToRange(start, 0, vis.snd.length, 0, numSamples));
var prevLine:FlxPoint = new FlxPoint();
for (i in 0...group.members.length)
{
var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, startingSample, startingSample + samplesToGen));
var left = audioData[sampleApprox] / 32767;
var right = audioData[sampleApprox + 1] / 32767;
var swagheight:Int = 200;
var balanced = (left + right) / 2;
group.members[i].x = prevLine.x;
group.members[i].y = prevLine.y;
prevLine.x = (balanced * swagheight / 2 + swagheight / 2) + x;
prevLine.y = (i / group.members.length * daHeight) + y;
var line = FlxVector.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y);
group.members[i].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
group.members[i].angle = line.degrees;
}
}
}
public function checkAndSetBuffer()
{
vis.checkAndSetBuffer();
if (vis.setBuffer)
{
audioData = vis.audioData;
sampleRate = vis.sampleRate;
setBuffer = vis.setBuffer;
numSamples = Std.int(audioData.length / 2);
}
}
var doAnim:Bool = false;
var frameCounter:Int = 0;
public function updateFFT()
{
if (vis.snd != null)
{
var remappedShit:Int = 0;
checkAndSetBuffer();
if (!doAnim)
{
frameCounter++;
if (frameCounter >= 0)
{
frameCounter = 0;
doAnim = true;
}
}
if (setBuffer && doAnim)
{
doAnim = false;
if (vis.snd.playing)
remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples));
else
remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples));
var i = remappedShit;
var prevLine:FlxPoint = new FlxPoint();
var swagheight:Int = 200;
var fftSamples:Array<Float> = [];
// var array:Array<Float> = cast audioData.subarray(remappedShit, remappedShit + lengthOfShit);
if (FlxG.keys.justPressed.M)
{
trace('POOP LOL');
var funnyAud = audioData.subarray(remappedShit, remappedShit + lengthOfShit);
for (poop in funnyAud)
{
// trace("actual audio: " + poop);
trace("win: " + poop);
}
// trace(audioData.subarray(remappedShit, remappedShit + lengthOfShit).buffer);
}
for (sample in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
{
var left = audioData[i] / 32767;
var right = audioData[i + 1] / 32767;
var balanced = (left + right) / 2;
i += 2;
// var remappedSample:Float = FlxMath.remapToRange(sample, remappedShit, remappedShit + lengthOfShit, 0, lengthOfShit - 1);
fftSamples.push(balanced);
}
var freqShit = vis.funnyFFT(fftSamples);
for (i in 0...group.members.length)
{
// needs to be exponential growth / scaling
// still need to optmize the FFT to run better, gets only samples needed?
// not every frequency is built the same!
// 20hz to 40z is a LOT of subtle low ends, but somethin like 20,000hz to 20,020hz, the difference is NOT the same!
var powedShit:Float = FlxMath.remapToRange(i, 0, group.members.length, 0, 4);
// a value between 10hz and 100Khz
var hzPicker:Float = Math.pow(10, powedShit);
// var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, startingSample, startingSample + samplesToGen));
var remappedFreq:Int = Std.int(FlxMath.remapToRange(hzPicker, 0, 10000, 0, freqShit[0].length - 1));
group.members[i].x = prevLine.x;
group.members[i].y = prevLine.y;
var freqPower:Float = 0;
for (pow in 0...freqShit.length)
freqPower += freqShit[pow][remappedFreq];
freqPower /= freqShit.length;
var freqIDK:Float = FlxMath.remapToRange(freqPower, 0, 0.000005, 0, 50);
prevLine.x = (freqIDK * swagheight / 2 + swagheight / 2) + x;
prevLine.y = (i / group.members.length * daHeight) + y;
var line = FlxVector.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y);
// dont draw a line until i figure out a nicer way to view da spikes and shit idk lol!
// group.members[i].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
// group.members[i].angle = line.degrees;
}
}
}
}
public function updateVisulizer():Void
{
if (vis.snd != null)
{
var remappedShit:Int = 0;
checkAndSetBuffer();
if (setBuffer)
{
if (vis.snd.playing)
remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples));
else
remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples));
var i = remappedShit;
var prevLine:FlxPoint = new FlxPoint();
var swagheight:Int = 200;
for (sample in remappedShit...remappedShit + lengthOfShit)
{
var left = audioData[i] / 32767;
var right = audioData[i + 1] / 32767;
var balanced = (left + right) / 2;
i += 2;
var remappedSample:Float = FlxMath.remapToRange(sample, remappedShit, remappedShit + lengthOfShit, 0, lengthOfShit - 1);
group.members[Std.int(remappedSample)].x = prevLine.x;
group.members[Std.int(remappedSample)].y = prevLine.y;
// group.members[0].y = prevLine.y;
// FlxSpriteUtil.drawLine(this, prevLine.x, prevLine.y, width * remappedSample, left * height / 2 + height / 2);
prevLine.x = (balanced * swagheight / 2 + swagheight / 2) + x;
prevLine.y = (Std.int(remappedSample) / lengthOfShit * daHeight) + y;
var line = FlxVector.get(prevLine.x - group.members[Std.int(remappedSample)].x, prevLine.y - group.members[Std.int(remappedSample)].y);
group.members[Std.int(remappedSample)].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
group.members[Std.int(remappedSample)].angle = line.degrees;
}
}
}
}
}
enum VISTYPE
{
STATIC;
UPDATED;
FREQUENCIES;
}

View file

@ -1,12 +1,8 @@
package;
import flixel.FlxGame;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.TransitionData;
import flixel.graphics.FlxGraphic;
import flixel.FlxState;
import flixel.group.FlxGroup;
import flixel.input.android.FlxAndroidKey;
import flixel.input.android.FlxAndroidKeys;
@ -14,8 +10,6 @@ import flixel.input.gamepad.FlxGamepad;
import flixel.input.gamepad.id.SwitchJoyconLeftID;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxGraphicAsset;
import flixel.system.FlxAssets;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
@ -26,32 +20,21 @@ import lime.graphics.Image;
import lime.media.AudioContext;
import lime.ui.Window;
import openfl.Assets;
import openfl.display.BitmapData;
import openfl.display.Sprite;
import openfl.events.AsyncErrorEvent;
import openfl.events.AsyncErrorEvent;
import openfl.events.Event;
import openfl.events.MouseEvent;
import openfl.events.NetStatusEvent;
import openfl.media.Video;
import openfl.net.NetConnection;
import openfl.net.NetStream;
import shaderslmfao.BuildingShaders.BuildingShader;
import shaderslmfao.BuildingShaders;
import shaderslmfao.ColorSwap;
import shaderslmfao.TitleOutline;
import ui.PreferencesMenu;
import ui.stageBuildShit.StageBuilderState;
using StringTools;
#if colyseus
import io.colyseus.Client;
import io.colyseus.Room;
#end
#if discord_rpc
import Discord.DiscordClient;
#end
#if desktop
import sys.FileSystem;
import sys.io.File;
@ -62,20 +45,15 @@ class TitleState extends MusicBeatState
{
public static var initialized:Bool = false;
var startedIntro:Bool;
var blackScreen:FlxSprite;
var credGroup:FlxGroup;
var credTextShit:Alphabet;
var textGroup:FlxGroup;
var ngSpr:FlxSprite;
var curWacky:Array<String> = [];
var wackyImage:FlxSprite;
var lastBeat:Int = 0;
var swagShader:ColorSwap;
var alphaShader:BuildingShaders;
var thingie:FlxSprite;
var video:Video;
var netStream:NetStream;
@ -83,24 +61,9 @@ class TitleState extends MusicBeatState
override public function create():Void
{
#if android
FlxG.android.preventDefaultKeys = [FlxAndroidKey.BACK];
#end
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function()
{
FlxG.debugger.visible = false;
});
startedIntro = false;
FlxG.game.focusLostFramerate = 60;
swagShader = new ColorSwap();
alphaShader = new BuildingShaders();
FlxG.sound.muteKeys = [ZERO];
curWacky = FlxG.random.getObject(getIntroTextShit());
FlxG.sound.cache(Paths.music('freakyMenu'));
@ -108,57 +71,6 @@ class TitleState extends MusicBeatState
super.create();
FlxG.save.bind('funkin', 'ninjamuffin99');
// https://github.com/HaxeFlixel/flixel/pull/2396
// IF/WHEN MY PR GOES THRU AND IT GETS INTO MAIN FLIXEL, DELETE THIS CHUNKOF CODE, AND THEN UNCOMMENT THE LINE BELOW
// FlxG.sound.loadSavedPrefs();
if (FlxG.save.data.volume != null)
{
FlxG.sound.volume = FlxG.save.data.volume;
}
if (FlxG.save.data.mute != null)
{
FlxG.sound.muted = FlxG.save.data.mute;
}
// FlxG.save.close();
// FlxG.sound.loadSavedPrefs();
PreferencesMenu.initPrefs();
PlayerSettings.init();
Highscore.load();
#if newgrounds
NGio.init();
#end
if (FlxG.save.data.weekUnlocked != null)
{
// FIX LATER!!!
// WEEK UNLOCK PROGRESSION!!
// StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked;
if (StoryMenuState.weekUnlocked.length < 4)
StoryMenuState.weekUnlocked.insert(0, true);
// QUICK PATCH OOPS!
if (!StoryMenuState.weekUnlocked[0])
StoryMenuState.weekUnlocked[0] = true;
}
if (FlxG.save.data.seenVideo != null)
{
VideoState.seenVideo = FlxG.save.data.seenVideo;
}
#if FREEPLAY
FlxG.switchState(new FreeplayState());
#elseif ANIMATE
FlxG.switchState(new animate.AnimTestStage());
#elseif CHARTING
FlxG.switchState(new ChartingState());
/*
#elseif web
@ -190,30 +102,10 @@ class TitleState extends MusicBeatState
*/
// netConnection.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown);
#elseif STAGEBUILD
FlxG.switchState(new StageBuilderState());
#elseif ANIMDEBUG
FlxG.switchState(new ui.animDebugShit.DebugBoundingState());
#elseif NETTEST
FlxG.switchState(new netTest.NetTest());
#else
new FlxTimer().start(1, function(tmr:FlxTimer)
{
startIntro();
});
#end
#if discord_rpc
DiscordClient.initialize();
Application.current.onExit.add(function(exitCode)
{
DiscordClient.shutdown();
});
#end
// FlxG.stage.window.borderless = true;
// FlxG.stage.window.mouseLock = true;
}
private function client_onMetaData(metaData:Dynamic)
@ -261,18 +153,6 @@ class TitleState extends MusicBeatState
function startIntro()
{
if (!initialized)
{
var diamond:FlxGraphic = FlxGraphic.fromClass(GraphicTransTileDiamond);
diamond.persist = true;
diamond.destroyOnNoUse = false;
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), {asset: diamond, width: 32, height: 32},
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1),
{asset: diamond, width: 32, height: 32}, new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
}
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
{
FlxG.sound.playMusic(Paths.music('freakyMenu'), 0);
@ -283,10 +163,6 @@ class TitleState extends MusicBeatState
persistentUpdate = true;
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
// bg.antialiasing = true;
// bg.setGraphicSize(Std.int(bg.width * 0.6));
// bg.updateHitbox();
add(bg);
logoBl = new FlxSprite(-150, -100);
@ -334,34 +210,18 @@ class TitleState extends MusicBeatState
// titleText.screenCenter(X);
add(titleText);
var logo:FlxSprite = new FlxSprite().loadGraphic(Paths.image('logo'));
logo.screenCenter();
logo.antialiasing = true;
// add(logo);
// FlxTween.tween(logoBl, {y: logoBl.y + 50}, 0.6, {ease: FlxEase.quadInOut, type: PINGPONG});
// FlxTween.tween(logo, {y: logoBl.y + 50}, 0.6, {ease: FlxEase.quadInOut, type: PINGPONG, startDelay: 0.1});
var animShit:ComboCounter = new ComboCounter(200, 200, 1423);
// add(animShit);
credGroup = new FlxGroup();
add(credGroup);
textGroup = new FlxGroup();
blackScreen = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
blackScreen = bg.clone();
credGroup.add(blackScreen);
// var atlasBullShit:FlxSprite = new FlxSprite();
// atlasBullShit.frames = CoolUtil.fromAnimate(Paths.image('money'), Paths.file('images/money.json'));
// credGroup.add(atlasBullShit);
credTextShit = new Alphabet(0, 0, "ninjamuffin99\nPhantomArcade\nkawaisprite\nevilsk8er", true);
credTextShit.screenCenter();
// credTextShit.alignment = CENTER;
credTextShit.visible = false;
ngSpr = new FlxSprite(0, FlxG.height * 0.52);
if (FlxG.random.bool(1))
@ -388,8 +248,6 @@ class TitleState extends MusicBeatState
ngSpr.screenCenter(X);
ngSpr.antialiasing = true;
FlxTween.tween(credTextShit, {y: credTextShit.y + 20}, 2.9, {ease: FlxEase.quadInOut, type: PINGPONG});
FlxG.mouse.visible = false;
if (initialized)
@ -399,9 +257,6 @@ class TitleState extends MusicBeatState
if (FlxG.sound.music != null)
FlxG.sound.music.onComplete = function() FlxG.switchState(new VideoState());
startedIntro = true;
// credGroup.add(credTextShit);
}
function getIntroTextShit():Array<Array<String>>
@ -421,9 +276,6 @@ class TitleState extends MusicBeatState
var transitioning:Bool = false;
var fnfShit:String = "Friday Night Funkin'";
var thingOffset:Int = 0;
override function update(elapsed:Float)
{
/* if (FlxG.onMobile)
@ -501,9 +353,10 @@ class TitleState extends MusicBeatState
if (FlxG.sound.music != null)
Conductor.songPosition = FlxG.sound.music.time;
// FlxG.watch.addQuick('amp', FlxG.sound.music.amplitude);
if (FlxG.keys.justPressed.F)
FlxG.fullscreen = !FlxG.fullscreen;
// do controls.PAUSE | controls.ACCEPT instead?
var pressedEnter:Bool = FlxG.keys.justPressed.ENTER;
if (FlxG.onMobile)
@ -542,13 +395,8 @@ class TitleState extends MusicBeatState
FlxG.camera.flash(FlxColor.WHITE, 1);
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
transitioning = true;
// FlxG.sound.music.stop();
// These assets are very unlikely to be used for the rest of gameplay, so it unloads them from cache/memory
// Saves about 50mb of RAM or so???
Assets.cache.clear(Paths.image('gfDanceTitle'));
Assets.cache.clear(Paths.image('logoBumpin'));
Assets.cache.clear(Paths.image('titleEnter'));
var targetState:FlxState = new MainMenuState();
#if newgrounds
if (!OutdatedSubState.leftState)
@ -561,19 +409,26 @@ class TitleState extends MusicBeatState
if (version.trim() != onlineVersion)
{
trace('OLD VERSION!');
// FlxG.switchState(new OutdatedSubState());
// targetState = new OutdatedSubState();
}
else
{
// FlxG.switchState(new MainMenuState());
// targetState = new MainMenuState();
}
// REDO FOR ITCH/FINAL SHIT
FlxG.switchState(new MainMenuState());
});
}
#else
FlxG.switchState(new MainMenuState());
#end
new FlxTimer().start(2, function(tmr:FlxTimer)
{
// These assets are very unlikely to be used for the rest of gameplay, so it unloads them from cache/memory
// Saves about 50mb of RAM or so???
Assets.cache.clear(Paths.image('gfDanceTitle'));
Assets.cache.clear(Paths.image('logoBumpin'));
Assets.cache.clear(Paths.image('titleEnter'));
// ngSpr??
FlxG.switchState(targetState);
});
// FlxG.sound.play(Paths.music('titleShoot'), 0.7);
}
if (pressedEnter && !skippedIntro && initialized)
@ -591,8 +446,6 @@ class TitleState extends MusicBeatState
#end
*/
// if (FlxG.keys.justPressed.SPACE)
// swagShader.hasOutline = !swagShader.hasOutline;
if (controls.UI_LEFT)
swagShader.update(-elapsed * 0.1);
if (controls.UI_RIGHT)
@ -640,6 +493,10 @@ class TitleState extends MusicBeatState
cheatActive = true;
FlxG.sound.playMusic(Paths.music('tutorialTitle'), 1);
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
add(spec);
Conductor.changeBPM(190);
FlxG.camera.flash(FlxColor.WHITE, 1);
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
@ -678,11 +535,56 @@ class TitleState extends MusicBeatState
}
var isRainbow:Bool = false;
var skippedIntro:Bool = false;
override function beatHit()
{
super.beatHit();
if (!skippedIntro)
{
FlxG.log.add(curBeat);
// if the user is draggin the window some beats will
// be missed so this is just to compensate
if (curBeat > lastBeat)
{
for (i in lastBeat...curBeat)
{
switch (i + 1)
{
case 1:
createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8er']);
case 3:
addMoreText('present');
case 4:
deleteCoolText();
case 5:
createCoolText(['In association', 'with']);
case 7:
addMoreText('newgrounds');
ngSpr.visible = true;
case 8:
deleteCoolText();
ngSpr.visible = false;
case 9:
createCoolText([curWacky[0]]);
case 11:
addMoreText(curWacky[1]);
case 12:
deleteCoolText();
case 13:
addMoreText('Friday');
case 14:
addMoreText('Night');
case 15:
addMoreText('Funkin');
case 16:
skipIntro();
}
}
}
lastBeat = curBeat;
}
if (skippedIntro)
{
if (cheatActive && curBeat % 2 == 0)
@ -697,73 +599,8 @@ class TitleState extends MusicBeatState
else
gfDance.animation.play('danceLeft');
}
else
{
FlxG.log.add(curBeat);
// if the user is draggin the window some beats will
// be missed so this is just to compensate
if (curBeat > lastBeat)
{
for (i in lastBeat...curBeat)
{
switch (i + 1)
{
case 1:
createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8er']);
// credTextShit.visible = true;
case 3:
addMoreText('present');
// credTextShit.text += '\npresent...';
// credTextShit.addText();
case 4:
deleteCoolText();
// credTextShit.visible = false;
// credTextShit.text = 'In association \nwith';
// credTextShit.screenCenter();
case 5:
createCoolText(['In association', 'with']);
case 7:
addMoreText('newgrounds');
ngSpr.visible = true;
// credTextShit.text += '\nNewgrounds';
case 8:
deleteCoolText();
ngSpr.visible = false;
// credTextShit.visible = false;
// credTextShit.text = 'Shoutouts Tom Fulp';
// credTextShit.screenCenter();
case 9:
createCoolText([curWacky[0]]);
// credTextShit.visible = true;
case 11:
addMoreText(curWacky[1]);
// credTextShit.text += '\nlmao';
case 12:
deleteCoolText();
// credTextShit.visible = false;
// credTextShit.text = "Friday";
// credTextShit.screenCenter();
case 13:
addMoreText('Friday');
// credTextShit.visible = true;
case 14:
addMoreText('Night');
// credTextShit.text += '\nNight';
case 15:
addMoreText('Funkin'); // credTextShit.text += '\nFunkin';
case 16:
skipIntro();
}
}
}
lastBeat = curBeat;
}
}
var skippedIntro:Bool = false;
function skipIntro():Void
{
if (!skippedIntro)

131
source/VisShit.hx Normal file
View file

@ -0,0 +1,131 @@
package;
import dsp.FFT;
import flixel.math.FlxMath;
import flixel.system.FlxSound;
import lime.utils.Int16Array;
using Lambda;
class VisShit
{
public var snd:FlxSound;
public var setBuffer:Bool = false;
public var audioData:Int16Array;
public var sampleRate:Int = 44100; // default, ez?
public var numSamples:Int = 0;
public function new(snd:FlxSound)
{
this.snd = snd;
}
public function funnyFFT(samples:Array<Float>, ?skipped:Int = 1):Array<Array<Float>>
{
// nab multiple samples at once in while / for loops?
var fs:Float = 44100 / skipped; // sample rate shit?
final fftN = 1024;
final halfN = Std.int(fftN / 2);
final overlap = 0.5;
final hop = Std.int(fftN * (1 - overlap));
// window function to compensate for overlapping
final a0 = 0.5; // => Hann(ing) window
final window = (n:Int) -> a0 - (1 - a0) * Math.cos(2 * Math.PI * n / fftN);
// NOTE TO SELF FOR WHEN I WAKE UP
// helpers, note that spectrum indexes suppose non-negative frequencies
final binSize = fs / fftN;
final indexToFreq = function(k:Int)
{
var powShit:Float = FlxMath.remapToRange(k, 0, halfN, 0, CoolUtil.coolBaseLog(10, halfN)); // 4.3 is almost the log of 20Khz or so. Close enuf lol
return 1.0 * (Math.pow(10, powShit)); // we need the `1.0` to avoid overflows
};
// "melodic" band-pass filter
final minFreq = 20.70;
final maxFreq = 4000.01;
final melodicBandPass = function(k:Int, s:Float)
{
// final freq = indexToFreq(k);
// final filter = freq > minFreq - binSize && freq < maxFreq + binSize ? 1 : 0;
return s;
};
var freqOutput:Array<Array<Float>> = [];
var c = 0; // index where each chunk begins
var indexOfArray:Int = 0;
while (c < samples.length)
{
// take a chunk (zero-padded if needed) and apply the window
final chunk = [
for (n in 0...fftN)
(c + n < samples.length ? samples[c + n] : 0.0) * window(n)
];
// compute positive spectrum with sampling correction and BP filter
final freqs = FFT.rfft(chunk).map(z -> z.scale(1 / fftN).magnitude).mapi(melodicBandPass);
freqOutput.push([]);
// if (FlxG.keys.justPressed.M)
// trace(FFT.rfft(chunk).map(z -> z.scale(1 / fs).magnitude));
// find spectral peaks and their instantaneous frequencies
for (k => s in freqs)
{
final time = c / fs;
final freq = indexToFreq(k);
final power = s * s;
if (FlxG.keys.justPressed.I)
{
trace(k);
haxe.Log.trace('${time};${freq};${power}', null);
}
if (freq < maxFreq)
freqOutput[indexOfArray].push(power);
//
}
// haxe.Log.trace("", null);
indexOfArray++;
// move to next (overlapping) chunk
c += hop;
}
if (FlxG.keys.justPressed.C)
trace(freqOutput.length);
return freqOutput;
}
public function checkAndSetBuffer()
{
if (snd != null && snd.playing)
{
if (!setBuffer)
{
// Math.pow3
@:privateAccess
var buf = snd._channel.__source.buffer;
// @:privateAccess
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
sampleRate = buf.sampleRate;
trace('got audio buffer shit');
trace(sampleRate);
trace(buf.bitsPerSample);
setBuffer = true;
numSamples = Std.int(audioData.length / 2);
}
}
}
}

83
source/VoicesGroup.hx Normal file
View file

@ -0,0 +1,83 @@
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.system.FlxSound;
// different than FlxSoundGroup cuz this can control all the sounds time and shit
// when needed
class VoicesGroup extends FlxTypedGroup<FlxSound>
{
public var time(default, set):Float = 0;
public var volume(default, set):Float = 1;
// make it a group that you add to?
public function new(song:String, ?files:Array<String>, ?needsVoices:Bool = true)
{
super();
if (!needsVoices)
{
// simply adds an empty sound? fills it in moreso for easier backwards compatibility
add(new FlxSound());
// FlxG.sound.list.add(snd);
return;
}
if (files == null)
files = [""]; // loads with no file name assumption, to load "Voices.ogg" or whatev normally
for (sndFile in files)
{
var snd:FlxSound = new FlxSound().loadEmbedded(Paths.voices(song, '$sndFile'));
FlxG.sound.list.add(snd); // adds it to sound group for proper volumes
add(snd); // adds it to main group for other shit
}
}
// prob a better / cleaner way to do all these forEach stuff?
public function pause()
{
forEachAlive(function(snd)
{
snd.pause();
});
}
public function play()
{
forEachAlive(function(snd)
{
snd.play();
});
}
public function stop()
{
forEachAlive(function(snd)
{
snd.stop();
});
}
function set_time(time:Float):Float
{
forEachAlive(function(snd)
{
// account for different offsets per sound?
snd.time = time;
});
return time;
}
// in PlayState, adjust the code so that it only mutes the player1 vocal tracks?
function set_volume(volume:Float):Float
{
forEachAlive(function(snd)
{
snd.volume = volume;
});
return volume;
}
}

View file

@ -96,8 +96,25 @@ class WiggleShader extends FlxShader
if (effectType == EFFECT_TYPE_DREAMY)
{
float offsetX = sin(pt.y * uFrequency + uTime * uSpeed) * uWaveAmplitude;
pt.x += offsetX; // * (pt.y - 1.0); // <- Uncomment to stop bottom part of the screen from moving
float w = 1 / openfl_TextureSize.y;
float h = 1 / openfl_TextureSize.x;
// look mom, I know how to write shaders now
pt.x = floor(pt.x / h) * h;
float offsetX = sin(pt.x * uFrequency + uTime * uSpeed) * uWaveAmplitude;
pt.y += floor(offsetX / w) * w; // * (pt.y - 1.0); // <- Uncomment to stop bottom part of the screen from moving
pt.y = floor(pt.y / w) * w;
float offsetY = sin(pt.y * (uFrequency / 2.0) + uTime * (uSpeed / 2.0)) * (uWaveAmplitude / 2.0);
pt.x += floor(offsetY / h) * h; // * (pt.y - 1.0); // <- Uncomment to stop bottom part of the screen from moving
}
else if (effectType == EFFECT_TYPE_WAVY)
{

View file

@ -1,7 +1,7 @@
package animate;
import animate.FlxSymbol.Parsed;
import animate.FlxSymbol.Timeline;
// import animate.FlxSymbol.Parsed;
// import animate.FlxSymbol.Timeline;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
@ -13,7 +13,7 @@ import lime.utils.Assets;
class AnimateTimeline extends FlxTypedGroup<FlxSprite>
{
var coolParsed:Parsed;
// var coolParsed:Parsed;
var playhead:FlxSprite;
public var curFrame(default, set):Int;
@ -31,45 +31,47 @@ class AnimateTimeline extends FlxTypedGroup<FlxSprite>
{
super();
hudCamShit = new FlxCamera();
hudCamShit.bgColor = FlxColor.TRANSPARENT;
FlxG.cameras.add(hudCamShit, false);
/* hudCamShit = new FlxCamera();
hudCamShit.bgColor = FlxColor.TRANSPARENT;
FlxG.cameras.add(hudCamShit, false);
playhead = new FlxSprite(0, -12).makeGraphic(2, 10, FlxColor.MAGENTA);
add(playhead);
playhead = new FlxSprite(0, -12).makeGraphic(2, 10, FlxColor.MAGENTA);
add(playhead);
hudCamShit.follow(playhead);
hudCamShit.setScrollBounds(0, null, -14, null);
hudCamShit.follow(playhead);
hudCamShit.setScrollBounds(0, null, -14, null);
curFrame = 0;
curFrame = 0;
coolParsed = cast Json.parse(Assets.getText(parsed));
coolParsed = cast Json.parse(Assets.getText(parsed));
var layerNum:Int = 0;
for (layer in coolParsed.AN.TL.L)
{
var frameNum:Int = 0;
var layerNum:Int = 0;
for (layer in coolParsed.AN.TL.L)
{
var frameNum:Int = 0;
for (frame in layer.FR)
{
var coolFrame:TimelineFrame = new TimelineFrame((frame.I * 12) + 12 * 5, layerNum * 12, frame.DU, frame);
add(coolFrame);
frameNum++;
}
for (frame in layer.FR)
{
var coolFrame:TimelineFrame = new TimelineFrame((frame.I * 12) + 12 * 5, layerNum * 12, frame.DU, frame);
add(coolFrame);
frameNum++;
}
var layerName:FlxText = new FlxText(0, layerNum * 12, 0, layer.LN, 10);
layerName.color = FlxColor.PURPLE;
layerName.scrollFactor.x = 0;
var layerName:FlxText = new FlxText(0, layerNum * 12, 0, layer.LN, 10);
layerName.color = FlxColor.PURPLE;
layerName.scrollFactor.x = 0;
var layerBG:FlxSprite = new FlxSprite(0, layerNum * 12).makeGraphic(12 * 4, 12);
layerBG.scrollFactor.x = 0;
var layerBG:FlxSprite = new FlxSprite(0, layerNum * 12).makeGraphic(12 * 4, 12);
layerBG.scrollFactor.x = 0;
add(layerBG);
add(layerName);
add(layerBG);
add(layerName);
layerNum++;
}
layerNum++;
}
this.cameras = [hudCamShit];
this.cameras = [hudCamShit];
*/
}
}

View file

@ -2,69 +2,182 @@ package animate;
// import animateAtlasPlayer.assets.AssetManager;
// import animateAtlasPlayer.core.Animation;
import animate.FlxSymbol.Parsed;
import animate.ParseAnimate.AnimJson;
import animate.ParseAnimate.Sprite;
import animate.ParseAnimate.Spritemap;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
import flixel.group.FlxGroup;
import flixel.math.FlxMatrix;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.system.FlxAssets.FlxGraphicAsset;
import haxe.Json;
import haxe.Utf8;
import haxe.format.JsonParser;
import lime.text.UTF8String;
import openfl.Assets;
import openfl.display.BitmapData;
import openfl.geom.Matrix;
import openfl.geom.Rectangle;
class FlxAnimate extends FlxSymbol
{
// var myAnim:Animation;
// var animBitmap:BitmapData;
var loadedQueue:Bool = false;
var jsonAnim:AnimJson;
var swagFrames:Array<BitmapData> = [];
var sprGrp:FlxTypedGroup<FlxSymbol>;
public function new(x:Float, y:Float)
{
var folder:String = 'tightBarsLol';
coolParse = cast Json.parse(Assets.getText(Paths.file('images/' + folder + '/Animation.json')));
super(x, y);
// reverses the layers, for proper rendering!
coolParse.AN.TL.L.reverse();
super(x, y, coolParse);
sprGrp = new FlxTypedGroup<FlxSymbol>();
var tests:Array<String> = ['tightBarsLol', 'tightestBars'];
var folder:String = tests[1];
frames = FlxAnimate.fromAnimate(Paths.file('images/' + folder + "/spritemap1.png"), Paths.file('images/$folder/spritemap1.json'));
jsonAnim = cast CoolUtil.coolJSON(Assets.getText(Paths.file('images/$folder/Animation.json')));
ParseAnimate.generateSymbolmap(jsonAnim.SD.S);
ParseAnimate.resetFrameList();
ParseAnimate.parseTimeline(jsonAnim.AN.TL, 0, 0);
generateSpriteShit();
/* var folder:String = 'tightestBars';
coolParse = cast Json.parse(Assets.getText(Paths.file('images/' + folder + '/Animation.json')));
// reverses the layers, for proper rendering!
coolParse.AN.TL.L.reverse();
super(x, y, coolParse);
frames = FlxAnimate.fromAnimate(Paths.file('images/' + folder + '/spritemap1.png'), Paths.file('images/' + folder + '/spritemap1.json'));
*/
frames = FlxAnimate.fromAnimate(Paths.file('images/' + folder + '/spritemap1.png'), Paths.file('images/' + folder + '/spritemap1.json'));
// frames
}
override function draw()
{
// having this commented out fixes some wacky scaling bullshit?
// or fixes drawing it twice?
// super.draw();
if (FlxG.keys.justPressed.ONE)
{
trace("-------------------------------------");
trace('CUR FRAME: ' + daFrame);
trace('--------------');
}
// renderFrame(coolParse.AN.TL, coolParse, true);
renderFrame(coolParse.AN.TL, coolParse, true);
actualFrameRender();
}
if (FlxG.keys.justPressed.E)
/**
* Puts all the needed sprites into a FlxTypedGroup, and properly recycles them?
**/
function generateSpriteShit()
{
sprGrp.kill(); // kills group, maybe dont need to do this one so broadly? ehh whatev
for (frameSorted in ParseAnimate.frameList)
{
for (shit in FlxSymbol.nestedShit.keys())
for (i in frameSorted)
{
for (spr in FlxSymbol.nestedShit.get(shit))
{
trace(shit);
spr.draw();
}
}
// instead of making them every frame, regenerate when needed?
var spr:FlxSymbol = sprGrp.recycle(FlxSymbol); // redo this to recycle from a list later
spr.frames = frames;
spr.frame = spr.frames.getByName(i.frameName); // this one is fine
spr.updateHitbox();
FlxSymbol.nestedShit.clear();
// move this? wont work here!
if (FlxG.keys.justPressed.I)
{
trace(i.frameName);
trace(i.depthString);
// trace("random lol: " + i.randomLol);
}
// cuz its in group, gets a lil fuckie when animated, need to go thru and properly reset each thing for shit like matrix!
// merely resets the matrix to normal ass one!
spr.transformMatrix.identity();
spr.setPosition();
/* for (swagMatrix in i.matrixArray)
{
var alsoSwag:FlxMatrix = new FlxMatrix(swagMatrix[0], swagMatrix[1], swagMatrix[4], swagMatrix[5], swagMatrix[12], swagMatrix[13]);
spr.matrixExposed = true;
spr.transformMatrix.concat(alsoSwag);
}*/
// i.fullMatrix.concat
spr.matrixExposed = true;
// trace(i.fullMatrix);
if (i.fullMatrix.a < 0)
{
trace('negative?');
trace(i.fullMatrix);
}
spr.transformMatrix.concat(i.fullMatrix);
if (i.fullMatrix.a < 0)
{
trace('negative?');
trace(i.fullMatrix);
trace(spr.transformMatrix);
}
// trace(spr.transformMatrix);
spr.origin.set();
/* for (trpShit in i.trpArray)
{
spr.origin.x -= trpShit[0];
spr.origin.y -= trpShit[1];
}
*/
// spr.alpha = 0.3;
spr.antialiasing = true;
sprGrp.add(spr);
spr.alpha = 0.5;
/* if (i == "0225")
{
trace('FUNNY MATRIX!');
trace(spr._matrix);
trace("\n\n MATRIX MAP");
for (m in ParseAnimate.matrixMap.get("0225"))
{
trace(m);
}
trace('\n\n');
}*/
}
}
// trace(sprGrp.length);
}
// fix render order of ALL layers!
// seperate frameList into layers
// go thru animate file to see how it should all be ordered
// per frame symbol stuff to fix lip sync (in ParseAnimate?)
// definitely need to dig through Animate.json stuff
// something with TRP stuff, look through tighterBars (GF scene)
// redo map stuff incase there's multiple assets
// ONE CENTRAL THING FOR THIS DUMBASS BULLSHIT
// sorted framelist put it all in there, then make i actually mean something
function actualFrameRender()
{
sprGrp.draw();
}
// notes to self
@ -73,6 +186,8 @@ class FlxAnimate extends FlxSymbol
var frameTickTypeShit:Float = 0;
var animFrameRate:Int = 24;
// redo all the matrix animation stuff
override function update(elapsed:Float)
{
super.update(elapsed);
@ -89,16 +204,29 @@ class FlxAnimate extends FlxSymbol
{
changeFrame(1);
frameTickTypeShit = 0;
ParseAnimate.resetFrameList();
ParseAnimate.parseTimeline(jsonAnim.AN.TL, 0, daFrame);
generateSpriteShit();
}
}
if (FlxG.keys.justPressed.RIGHT)
{
changeFrame(1);
ParseAnimate.resetFrameList();
ParseAnimate.parseTimeline(jsonAnim.AN.TL, 0, daFrame);
generateSpriteShit();
}
if (FlxG.keys.justPressed.LEFT)
changeFrame(-1);
}
// This stuff is u
/**
* PARSES THE 'spritemap1.png' or whatever into a FlxAtlasFrames!!!
*/
public static function fromAnimate(Source:FlxGraphicAsset, Description:String):FlxAtlasFrames
{
var graphic:FlxGraphic = FlxG.bitmap.add(Source);
@ -114,11 +242,11 @@ class FlxAnimate extends FlxSymbol
frames = new FlxAtlasFrames(graphic);
var data:AnimateObject;
var data:Spritemap;
var json:String = Description;
trace(json);
// trace(json);
var funnyJson:Dynamic = {};
if (Assets.exists(json))
@ -126,12 +254,14 @@ class FlxAnimate extends FlxSymbol
// trace(json);
data = cast funnyJson.ATLAS;
// data = c
for (sprite in data.SPRITES)
data = cast funnyJson;
for (sprite in data.ATLAS.SPRITES)
{
// probably nicer way to do this? Oh well
var swagSprite:AnimateSprite = sprite.SPRITE;
var swagSprite:Sprite = sprite.SPRITE;
var rect = FlxRect.get(swagSprite.x, swagSprite.y, swagSprite.w, swagSprite.h);
@ -147,10 +277,7 @@ class FlxAnimate extends FlxSymbol
}
}
/**
* HL json encoding fix for some wacky bullshit
* https://github.com/HaxeFoundation/haxe/issues/6930#issuecomment-384570392
*/
// handy json function that has some hashlink fix, see the thing in CoolUtils file to see the link / where i stole it from
class JaySon
{
public static function parseFile(name:String)
@ -164,18 +291,3 @@ class JaySon
0));
}
}
typedef AnimateObject =
{
SPRITES:Array<Dynamic>
}
typedef AnimateSprite =
{
var name:String;
var x:Int;
var y:Int;
var w:Int;
var h:Int;
var rotated:Bool;
}

View file

@ -1,5 +1,12 @@
package animate;
import animate.ParseAnimate.AnimJson;
import animate.ParseAnimate.Animation;
import animate.ParseAnimate.Frame;
import animate.ParseAnimate.Sprite;
import animate.ParseAnimate.Spritemap;
import animate.ParseAnimate.SymbolDictionary;
import animate.ParseAnimate.Timeline;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
@ -8,251 +15,41 @@ import flixel.math.FlxMath;
import flixel.math.FlxMatrix;
import flixel.math.FlxPoint;
import lime.system.System;
import openfl.Assets;
import openfl.geom.Matrix;
class FlxSymbol extends FlxSprite
{
public var coolParse:Parsed;
public var oldMatrix:Array<Float> = [];
// Loop types shit
public static inline var LOOP:String = 'LP';
public static inline var PLAY_ONCE:String = 'PO';
public static inline var SINGLE_FRAME:String = 'SF';
/**
* This gets set in some nest animation bullshit in animation render code
*/
public var firstFrame:Int = 0;
public var transformMatrix:Matrix = new Matrix();
public var daLoopType:String = 'LP'; // LP by default, is set below!!!
public function new(x:Float, y:Float, coolParsed:Parsed)
/**
* Bool flag showing whether transformMatrix is used for rendering or not.
* False by default, which means that transformMatrix isn't used for rendering
*/
public var matrixExposed:Bool = true;
public function new(x:Float, y:Float)
{
super(x, y);
// trace(System.deviceModel);
// trace(System.deviceVendor);
// trace(System.platformLabel);
// trace(System.platformName);
this.coolParse = coolParsed;
var hasSymbolDictionary:Bool = Reflect.hasField(coolParse, "SD");
if (hasSymbolDictionary)
symbolAtlasShit = parseSymbolDictionary(coolParse);
}
var symbolAtlasShit:Map<String, String> = new Map();
public static var nestedShit:Map<Int, Array<FlxSymbol>> = new Map();
var symbolMap:Map<String, Animation> = new Map();
public var daFrame:Int = 0;
public var transformMatrix:Matrix = new Matrix();
function renderFrame(TL:Timeline, coolParsed:Parsed, ?traceShit:Bool = false)
{
for (layer in TL.L)
{
if (FlxG.keys.justPressed.TWO)
trace(layer.LN);
// layer.FR.reverse();
// for (frame in layer.FR)
// {
var keyFrames:Array<Int> = [];
var keyFrameMap:Map<Int, Frame> = new Map();
// probably dumb to generate this every single frame for every layer?
// prob want to generate it first when loading
for (frm in layer.FR)
{
keyFrameMap[frm.I] = frm;
keyFrames.push(frm.I);
for (thing in 0...frm.DU - 1)
keyFrames.push(frm.I);
}
if (FlxG.keys.justPressed.THREE)
{
trace(layer.LN);
trace(keyFrames);
}
var newFrameNum:Int = daFrame;
// need to account for movie clip / Graphic bullshit?
switch (daLoopType)
{
case LOOP:
var tempFrame = layer.FR[newFrameNum + firstFrame % layer.FR.length];
// trace(tempFrame);
// newFrameNum += firstFrame;
// newFrameNum = newFrameNum % (tempFrame.I + tempFrame.DU);
// newFrameNum = FlxMath.wrap(newFrameNum, tempFrame.I, tempFrame.I + tempFrame.DU);
// trace(newFrameNum % keyFrames.length);
// trace(newFrameNum);
// trace(keyFrames);
newFrameNum = keyFrames[newFrameNum % keyFrames.length]; // temp, fix later for good looping
case PLAY_ONCE:
// trace(newFrameNum);
// trace(keyFrames.length - 1);
// trace(keyFrameMap.get(newFrameNum + firstFrame));
// trace(keyFrameMap.get(keyFrames[keyFrames.length - 1]));
// trace(layer.LN);
// trace(keyFrames);
newFrameNum = Std.int(Math.min(newFrameNum + firstFrame, keyFrames.length - 1));
case SINGLE_FRAME:
// trace(layer);
// trace(firstFrame);
// trace(newFrameNum);
// trace(layer.LN);
// trace(keyFrames);
newFrameNum = keyFrames[firstFrame];
}
// trace(daLoopType);
// trace(newFrameNum);
// trace(layer.FR.length);
// trace(newFrameNum % layer.FR.length);
// var swagFrame:Frame = layer.FR[newFrameNum % layer.FR.length]; // has modulo just in case????
// doesnt actually use position in the array?3
var swagFrame:Frame = keyFrameMap.get(newFrameNum);
// get frame by going through
// if (newFrameNum >= frame.I && newFrameNum < frame.I + frame.DU)
// {
// trace(daLoopType);
for (element in swagFrame.E)
{
if (Reflect.hasField(element, 'ASI'))
{
var m3d = element.ASI.M3D;
var dumbassMatrix:Matrix = new Matrix(m3d[0], m3d[1], m3d[4], m3d[5], m3d[12], m3d[13]);
var spr:FlxSymbol = new FlxSymbol(0, 0, coolParsed);
spr.setPosition(x, y);
matrixExposed = true;
spr.frames = frames;
spr.frame = spr.frames.getByName(element.ASI.N);
// dumbassMatrix.translate(origin.x, origin.y);
dumbassMatrix.concat(_matrix);
spr.matrixExposed = true;
spr.transformMatrix.concat(dumbassMatrix);
// spr._matrix.concat(spr.transformMatrix);
spr.origin.set();
// Prob dont need these offset thingies???
// spr.origin.x += origin.x;
// spr.origin.y += origin.y;
// spr.antialiasing = true;
spr.draw();
if (FlxG.keys.justPressed.ONE)
{
trace("ASI - " + layer.LN + ": " + element.ASI.N);
}
}
else
{
var nestedSymbol = symbolMap.get(element.SI.SN);
var nestedShit:FlxSymbol = new FlxSymbol(x, y, coolParse);
nestedShit.frames = frames;
var swagMatrix:FlxMatrix = new FlxMatrix(element.SI.M3D[0], element.SI.M3D[1], element.SI.M3D[4], element.SI.M3D[5], element.SI.M3D[12],
element.SI.M3D[13]);
swagMatrix.concat(_matrix);
nestedShit._matrix.concat(swagMatrix);
nestedShit.origin.set(element.SI.TRP.x, element.SI.TRP.y);
// nestedShit.angle += ((180 / Math.PI) * Math.atan2(swagMatrix.b, swagMatrix.a));
// nestedShit.angle += angle;
if (symbolAtlasShit.exists(nestedSymbol.SN))
{
// nestedShit.frames.getByName(symbolAtlasShit.get(nestedSymbol.SN));
// nestedShit.draw();
}
// scale.y = Math.sqrt(_matrix.c * _matrix.c + _matrix.d * _matrix.d);
// scale.x = Math.sqrt(_matrix.a * _matrix.a + _matrix.b * _matrix.b);
// nestedShit.oldMatrix = element.SI.M3D;
if (FlxG.keys.justPressed.ONE)
{
trace("SI - " + layer.LN + ": " + element.SI.SN + " - LOOP TYPE: " + element.SI.LP);
}
nestedShit.firstFrame = element.SI.FF;
// nestedShit.daFrame += nestedShit.firstFrame;
nestedShit.daLoopType = element.SI.LP;
nestedShit.daFrame = daFrame;
nestedShit.scrollFactor.set(1, 1);
nestedShit.renderFrame(nestedSymbol.TL, coolParsed);
// renderFrame(nestedSymbol.TL, coolParsed);
}
}
// }
// }
}
}
function changeFrame(frameChange:Int = 0):Void
{
daFrame += frameChange;
}
function parseSymbolDictionary(coolParsed:Parsed):Map<String, String>
{
var awesomeMap:Map<String, String> = new Map();
for (symbol in coolParsed.SD.S)
{
symbolMap.set(symbol.SN, symbol);
var symbolName = symbol.SN;
// one time reverse?
symbol.TL.L.reverse();
for (layer in symbol.TL.L)
{
for (frame in layer.FR)
{
for (element in frame.E)
{
if (Reflect.hasField(element, 'ASI'))
{
awesomeMap.set(symbolName, element.ASI.N);
}
}
}
}
}
return awesomeMap;
}
function getFrame() {}
/**
* custom "homemade" (nabbed from FlxSkewSprite) draw function, to make having a matrix transform slightly
* less painful
*/
override function drawComplex(camera:FlxCamera):Void
{
_frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
@ -263,155 +60,23 @@ class FlxSymbol extends FlxSprite
{
_matrix.concat(transformMatrix);
}
else
if (bakedRotationAngle <= 0)
{
if (bakedRotationAngle <= 0)
{
updateTrig();
updateTrig();
if (angle != 0)
_matrix.rotateWithTrig(_cosAngle, _sinAngle);
}
// updateSkewMatrix();
_matrix.concat(_skewMatrix);
if (angle != 0)
_matrix.rotateWithTrig(_cosAngle, _sinAngle);
}
_point.addPoint(origin);
if (isPixelPerfectRender(camera))
_point.floor();
_matrix.translate(_point.x, _point.y);
camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing);
}
var _skewMatrix:Matrix = new Matrix();
// public var transformMatrix(default, null):Matrix = new Matrix();
/**
* Bool flag showing whether transformMatrix is used for rendering or not.
* False by default, which means that transformMatrix isn't used for rendering
*/
public var matrixExposed:Bool = false;
public var skew(default, null):FlxPoint = FlxPoint.get();
function updateSkewMatrix():Void
{
_skewMatrix.identity();
if (skew.x != 0 || skew.y != 0)
if (isPixelPerfectRender(camera))
{
_skewMatrix.b = Math.tan(skew.y * FlxAngle.TO_RAD);
_skewMatrix.c = Math.tan(skew.x * FlxAngle.TO_RAD);
_matrix.tx = Math.floor(_matrix.tx);
_matrix.ty = Math.floor(_matrix.ty);
}
camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
}
}
// TYPEDEFS FOR ANIMATION.JSON PARSING
typedef Parsed =
{
var MD:Metadata;
var AN:Animation;
var SD:SymbolDictionary; // Doesn't always have symbol dictionary!!
}
typedef Metadata =
{
/** Framerate */
var FRT:Int;
}
/** Basically treated like one big symbol*/
typedef Animation =
{
/** symbolName */
var SN:String;
var TL:Timeline;
/** IDK what STI stands for, Symbole Type Instance?
Anyways, it is NOT used in SYMBOLS, only the main AN animation
*/
var STI:Dynamic;
}
/** DISCLAIMER, MAY NOT ACTUALLY BE CALLED
SYMBOL TYPE ISNTANCE, IM JUST MAKING ASSUMPTION!! */
typedef SymbolTypeInstance =
{
// var TL:Timeline;
// var SN:String;
}
typedef SymbolDictionary =
{
var S:Array<Animation>;
}
typedef Timeline =
{
/** Layers */
var L:Array<Layer>;
}
// Singular layer, not to be confused with LAYERS
typedef Layer =
{
var LN:String;
/** Frames */
var FR:Array<Frame>;
}
typedef Frame =
{
/** Frame index*/
var I:Int;
/** Duration, in frames*/
var DU:Int;
/** Elements*/
var E:Array<Element>;
}
typedef Element =
{
var SI:SymbolInstance;
var ASI:AtlasSymbolInstance;
}
/**
Symbol instance, for SYMBOLS and refers to SYMBOLS
*/
typedef SymbolInstance =
{
var SN:String;
/** SymbolType (Graphic, Movieclip, Button)*/
var ST:String;
/** First frame*/
var FF:Int;
/** Loop type (Loop, play once, single frame)*/
var LP:String;
var TRP:TransformationPoint;
var M3D:Array<Float>;
}
typedef AtlasSymbolInstance =
{
var N:String;
var M3D:Array<Float>;
}
typedef TransformationPoint =
{
var x:Float;
var y:Float;
}

View file

@ -0,0 +1,515 @@
package animate;
import haxe.format.JsonParser;
import openfl.Assets;
import openfl.geom.Matrix3D;
import openfl.geom.Matrix;
import sys.io.File;
/**
* Generally designed / written in a way that can be easily taken out of FNF and used elsewhere
* I don't think it even has ties to OpenFL? Could probably just use it for ANY haxe
* project if needed, DOES NEED A LOT OF CLEANUP THOUGH!
*/
class ParseAnimate
{
// make list of frames needed to render (with ASI)
// make GIANT list of all the frames ever and have them in order?
public static var symbolMap:Map<String, Symbol> = new Map();
public static var actualSprites:Map<String, Sprite> = new Map();
private var _atlas:Map<String, Sprite>;
private var _symbolData:Map<String, Symbol>;
private var _defaultSymbolName:String;
public function new(data:AnimJson, atlas:Spritemap)
{
// bitmap data could prob be instead
// this code is mostly nabbed from https://github.com/miltoncandelero/OpenFLAnimateAtlas/blob/master/Source/animateatlas/displayobject/SpriteAnimationLibrary.hx
parseAnimationData(data);
parseAtlasData(atlas);
}
private function parseAnimationData(data:AnimJson):Void
{
_symbolData = new Map();
var symbols = data.SD.S;
for (symbol in symbols)
_symbolData[symbol.SN] = preprocessSymbolData(symbol);
var defaultSymbol:Symbol = preprocessSymbolData(data.AN);
_defaultSymbolName = defaultSymbol.SN;
_symbolData.set(_defaultSymbolName, defaultSymbol);
}
// at little redundant, does exactly the same thing as genSpritemap()
private function parseAtlasData(atlas:Spritemap):Void
{
_atlas = new Map<String, Sprite>();
if (atlas.ATLAS != null && atlas.ATLAS.SPRITES != null)
{
for (s in atlas.ATLAS.SPRITES)
_atlas.set(s.SPRITE.name, s.SPRITE);
}
}
/**
* Not used, was used for testing stuff though!
*/
public static function init()
{
// Main.gids
var folder:String = 'tightestBars';
// var spritemap:Spritemap =
// var spritemap:Spritemap = genSpritemap('test/$folder/spritemap1.json');
actualSprites = genSpritemap('test/$folder/spritemap1.json');
var animation:AnimJson = cast CoolUtil.coolJSON(Assets.getText('src/$folder/Animation.json'));
generateSymbolmap(animation.SD.S);
trace("\n\nANIMATION SHIT\n");
var timelineLength:Int = 0;
for (lyr in animation.AN.TL.L)
timelineLength = Std.int(Math.max(lyr.FR.length, timelineLength));
var content:String = animation.AN.TL.L[0].LN;
content += "TOTAL FRAMES NEEDED: " + timelineLength + "\n";
for (frm in 0...timelineLength)
{
trace('FRAME NUMBER ' + frm);
try
{
parseTimeline(animation.AN.TL, 1, frm);
content += 'Good write on frame: ' + frm + "\n";
}
catch (e)
{
content += "BAD WRITE : " + frm + "\n";
content += "\t" + e + "\n";
trace(e);
}
// File.saveContent("output.txt", content);
}
parseTimeline(animation.AN.TL, 1, 0);
trace(actualSprites);
}
/**
* a MAP of SPRITES, not to be confused with Spritemap... lol
*/
public static function genSpritemap(json:String):Map<String, Sprite>
{
var sprShitty:Spritemap = cast CoolUtil.coolJSON(json);
var sprMap:Map<String, Sprite> = new Map();
for (spr in sprShitty.ATLAS.SPRITES)
sprMap.set(spr.SPRITE.name, spr.SPRITE);
return sprMap;
}
// should change dis to all private?
public static function generateSymbolmap(symbols:Array<Symbol>)
{
for (symbol in symbols)
{
// trace(symbol.SN + "has: " + symbol.TL.L.length + " LAYERS");
symbolMap.set(symbol.SN, symbol);
// parseTimeline(symbol.TL);
}
}
public static function preprocessSymbolData(anim:Symbol):Symbol
{
var timelineData:Timeline = anim.TL;
var layerData:Array<Layer> = timelineData.L;
if (!timelineData.sortedForRender)
{
timelineData.sortedForRender = true;
layerData.reverse();
}
for (layerStuff in layerData)
{
var frames:Array<Frame> = layerStuff.FR;
for (frame in frames)
{
var elements:Array<Element> = frame.E;
for (e in 0...elements.length)
{
var element:Element = elements[e];
if (element.ASI != null)
{
element = elements[e] = {
SI: {
SN: "ATLAS_SYMBOL_SPRITE",
LP: "LP",
TRP: {x: 0, y: 0},
M3D: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
FF: 0,
ST: "G",
ASI: element.ASI
}
}
}
}
}
}
return anim;
}
public static var curLoopType:String;
/**
* Stuff for debug parsing
*/
public static var depthTypeBeat:String = "";
/**
* Array of bullshit that will eventually be RENDERED by whoever wanna use it!
*/
public static var frameList:Array<Array<VALIDFRAME>> = [];
// for loop stuf
/**
* Similar to frameList, keeps track of shit according to framess?
* That amount of arrays within arrays is fuckin dumb
* but innermost array is basically just x and y value, cuz im dum
*/
public static var matrixHelp:Array<Array<Array<Float>>> = [];
public static var trpHelpIDK:Array<Array<Array<Float>>> = [];
public static var loopedFrameShit:Int = 0;
public static var funnyMatrix:Matrix = new Matrix();
public static var matrixFlipper:Array<Matrix> = [];
// clean up all the crazy ass arrays
public static function resetFrameList()
{
// funnyMatrix.identity();
frameList = [];
frameList.push([]);
matrixHelp = [];
matrixHelp.push([]);
trpHelpIDK = [];
trpHelpIDK.push([]);
}
public static var isFlipped:Bool = false;
public static function parseTimeline(TL:Timeline, tabbed:Int = 0, ?frameInput:Int)
{
var strTab:String = "";
for (i in 0...tabbed)
strTab += '\t';
for (layer in TL.L)
{
var frameArray:Array<Int> = [];
var frameMap:Map<Int, Frame> = new Map();
for (frms in layer.FR)
{
for (i in 0...frms.DU)
frameArray.push(frms.I);
frameMap.set(frms.I, frms);
}
if (frameInput == null)
frameInput = 0;
var oldFrm:Int = frameInput;
/*
if (curLoopType == "SF")
{
trace(layer.LN);
trace(frameArray);
trace(frameInput);
trace(curLoopType);
}*/
if (curLoopType == "LP")
frameInput = frameArray[frameInput % frameArray.length];
else if (curLoopType == "SF")
{
frameInput = frameArray[loopedFrameShit];
// see what happens when something has more than 2 layer?
// single frame stuff isn't fully implemented
}
else
frameInput = frameArray[frameInput];
// trace(frameMap.get(frameInput));
var frame:Frame = frameMap.get(frameInput);
// get somethin sorted per element list, which would essentially be per symbol things properly sorted
// seperate data types if symbol or atlassymbolinstance? would probably be maybe slightly less memory intensive? i dunno
// goes thru each layer, and then each element
// after it gets thru each element it adds to the layer frame stuff.
// make somethin that works recursively, maybe thats the symbol dictionary type shit?
for (element in frame.E)
{
if (Reflect.hasField(element, "ASI"))
{
matrixHelp[matrixHelp.length - 1].push(element.ASI.M3D);
var m3D = element.ASI.M3D;
var lilMatrix:Matrix = new Matrix(m3D[0], m3D[1], m3D[4], m3D[5], m3D[12], m3D[13]);
matrixFlipper.push(lilMatrix);
// matrixFlipper.reverse();
// funnyMatrix.identity();
// for (m in matrixFlipper)
// funnyMatrix.concat(m);
if (isFlipped)
{
trace("MORE FLIPPED SHIT");
trace("MORE FLIPPED SHIT");
trace("MORE FLIPPED SHIT");
trace(funnyMatrix);
trace(matrixFlipper);
}
// trace(funnyMatrix);
funnyMatrix.concat(lilMatrix);
// trace(funnyMatrix);
frameList[frameList.length - 1].push({
frameName: element.ASI.N,
depthString: depthTypeBeat,
matrixArray: matrixHelp[matrixHelp.length - 1],
trpArray: trpHelpIDK[trpHelpIDK.length - 1],
fullMatrix: funnyMatrix.clone()
});
// flips the matrix once?? I cant remember exactly why it needs to be flipped
// matrixHelp[matrixHelp.length - 1].reverse();
// trpHelpIDK = [];
// push the matrix array after each symbol?
funnyMatrix.identity();
matrixFlipper = [];
depthTypeBeat = "";
curLoopType = "";
loopedFrameShit = 0;
isFlipped = false;
}
else
{
var m3D = element.SI.M3D;
var lilMatrix:Matrix = new Matrix(m3D[0], m3D[1], m3D[4], m3D[5], m3D[12], m3D[13]);
if (lilMatrix.a == -1)
{
isFlipped = true;
trace('IS THE NEGATIVE ONE');
}
if (isFlipped)
trace(lilMatrix);
funnyMatrix.concat(lilMatrix);
matrixFlipper.push(lilMatrix);
// trace(funnyMatrix);
matrixHelp[matrixHelp.length - 1].push(element.SI.M3D);
trpHelpIDK[trpHelpIDK.length - 1].push([element.SI.TRP.x, element.SI.TRP.y]); // trpHelpIDK.push();
depthTypeBeat += "->" + element.SI.SN;
curLoopType = element.SI.LP;
var inputFrame:Int = element.SI.FF;
// JANKY FIX, MAY NOT ACCOUNT FOR ALL SCENARIOS OF SINGLE FRAME ANIMATIONS!!
if (curLoopType == "SF")
{
// trace("LOOP SHIT: " + inputFrame);
loopedFrameShit = inputFrame;
}
// condense the animation code, so it automatically already fills up animation shit per symbol
parseTimeline(symbolMap.get(element.SI.SN).TL, tabbed + 1, inputFrame);
}
// idk if this should go per layer or per element / object?
matrixHelp.push([]);
trpHelpIDK.push([]);
}
if (tabbed == 0)
{
frameList[frameList.length - 1].reverse();
frameList.push([]); // new layer essentially
}
}
frameList.reverse();
}
}
typedef VALIDFRAME =
{
frameName:String,
depthString:String,
matrixArray:Array<Array<Float>>,
trpArray:Array<Array<Float>>,
fullMatrix:Matrix
}
typedef AnimJson =
{
AN:Animation,
SD:SymbolDictionary,
MD:MetaData
}
typedef Animation =
{
N:String,
SN:String,
TL:Timeline
}
typedef SymbolDictionary =
{
S:Array<Symbol>
}
typedef Symbol =
{
/**Symbol name*/
SN:String,
TL:Timeline
}
typedef Timeline =
{
?sortedForRender:Bool,
L:Array<Layer>
}
typedef Layer =
{
LN:String,
FR:Array<Frame>
}
typedef Frame =
{
E:Array<Element>,
I:Int,
DU:Int
// maybe need to implement names if it has frame labels?
}
typedef Element =
{
SI:SymbolInstance,
?ASI:AlsoSymbolInstance
// lmfao idk what ASI stands for lmfaoo, i dont think its "also"
}
typedef SymbolInstance =
{
SN:String,
ASI:AlsoSymbolInstance,
/**Symbol type, prob either G (graphic), or movie clip?*/ ST:String,
/**First frame*/ FF:Int,
/**Loop type, loop ping pong, etc.*/ LP:String,
/**3D matrix*/ M3D:Array<Float>,
TRP:
{
x:Float, y:Float
}
}
typedef AlsoSymbolInstance =
{
N:String,
M3D:Array<Float>
}
typedef MetaData =
{
/**
* Framerate
*/
FRT:Int
}
// SPRITEMAP BULLSHIT
typedef Spritemap =
{
ATLAS:
{
SPRITES:Array<SpriteBullshit>
},
meta:Meta
}
typedef SpriteBullshit =
{
SPRITE:Sprite
}
typedef Sprite =
{
name:String,
x:Int,
y:Int,
w:Int,
h:Int,
rotated:Bool
}
typedef Meta =
{
app:String,
verstion:String,
image:String,
format:String,
size:
{
w:Int, h:Float
},
resolution:Float
}

80
source/dsp/Complex.hx Normal file
View file

@ -0,0 +1,80 @@
package dsp;
/**
Complex number representation.
**/
@:forward(real, imag) @:notNull @:pure
abstract Complex({
final real:Float;
final imag:Float;
})
{
public inline function new(real:Float, imag:Float)
this = {real: real, imag: imag};
/**
Makes a Complex number with the given Float as its real part and a zero imag part.
**/
@:from
public static inline function fromReal(r:Float)
return new Complex(r, 0);
/**
Complex argument, in radians.
**/
public var angle(get, never):Float;
inline function get_angle()
return Math.atan2(this.imag, this.real);
/**
Complex module.
**/
public var magnitude(get, never):Float;
inline function get_magnitude()
return Math.sqrt(this.real * this.real + this.imag * this.imag);
@:op(A + B)
public inline function add(rhs:Complex):Complex
return new Complex(this.real + rhs.real, this.imag + rhs.imag);
@:op(A - B)
public inline function sub(rhs:Complex):Complex
return new Complex(this.real - rhs.real, this.imag - rhs.imag);
@:op(A * B)
public inline function mult(rhs:Complex):Complex
return new Complex(this.real * rhs.real - this.imag * rhs.imag, this.real * rhs.imag + this.imag * rhs.real);
/**
Returns the complex conjugate, does not modify this object.
**/
public inline function conj():Complex
return new Complex(this.real, -this.imag);
/**
Multiplication by a real factor, does not modify this object.
**/
public inline function scale(k:Float):Complex
return new Complex(this.real * k, this.imag * k);
public inline function copy():Complex
return new Complex(this.real, this.imag);
/**
The imaginary unit.
**/
public static final im = new Complex(0, 1);
/**
The complex zero.
**/
public static final zero = new Complex(0, 0);
/**
Computes the complex exponential `e^(iw)`.
**/
public static inline function exp(w:Float)
return new Complex(Math.cos(w), Math.sin(w));
}

154
source/dsp/FFT.hx Normal file
View file

@ -0,0 +1,154 @@
package dsp;
import dsp.Complex;
// these are only used for testing, down in FFT.main()
using dsp.OffsetArray;
using dsp.Signal;
/**
Fast/Finite Fourier Transforms.
**/
class FFT {
/**
Computes the Discrete Fourier Transform (DFT) of a `Complex` sequence.
If the input has N data points (N should be a power of 2 or padding will be added)
from a signal sampled at intervals of 1/Fs, the result will be a sequence of N
samples from the Discrete-Time Fourier Transform (DTFT) - which is Fs-periodic -
with a spacing of Fs/N Hz between them and a scaling factor of Fs.
**/
public static function fft(input:Array<Complex>) : Array<Complex>
return do_fft(input, false);
/**
Like `fft`, but for a real (Float) sequence input.
Since the input time signal is real, its frequency representation is
Hermitian-symmetric so we only return the positive frequencies.
**/
public static function rfft(input:Array<Float>) : Array<Complex> {
final s = fft(input.map(Complex.fromReal));
return s.slice(0, Std.int(s.length / 2) + 1);
}
/**
Computes the Inverse DFT of a periodic input sequence.
If the input contains N (a power of 2) DTFT samples, each spaced Fs/N Hz
from each other, the result will consist of N data points as sampled
from a time signal at intervals of 1/Fs with a scaling factor of 1/Fs.
**/
public static function ifft(input:Array<Complex>) : Array<Complex>
return do_fft(input, true);
// Handles padding and scaling for forwards and inverse FFTs.
private static function do_fft(input:Array<Complex>, inverse:Bool) : Array<Complex> {
final n = nextPow2(input.length);
var ts = [for (i in 0...n) if (i < input.length) input[i] else Complex.zero];
var fs = [for (_ in 0...n) Complex.zero];
ditfft2(ts, 0, fs, 0, n, 1, inverse);
return inverse ? fs.map(z -> z.scale(1 / n)) : fs;
return fs;
}
// Radix-2 Decimation-In-Time variant of CooleyTukey's FFT, recursive.
private static function ditfft2(
time:Array<Complex>, t:Int,
freq:Array<Complex>, f:Int,
n:Int, step:Int, inverse: Bool
) : Void {
if (n == 1) {
freq[f] = time[t].copy();
} else {
final halfLen = Std.int(n / 2);
ditfft2(time, t, freq, f, halfLen, step * 2, inverse);
ditfft2(time, t + step, freq, f + halfLen, halfLen, step * 2, inverse);
for (k in 0...halfLen) {
final twiddle = Complex.exp((inverse ? 1 : -1) * 2 * Math.PI * k / n);
final even = freq[f + k].copy();
final odd = freq[f + k + halfLen].copy();
freq[f + k] = even + twiddle * odd;
freq[f + k + halfLen] = even - twiddle * odd;
}
}
}
// Naive O(n^2) DFT, used for testing purposes.
private static function dft(ts:Array<Complex>, ?inverse:Bool) : Array<Complex> {
if (inverse == null) inverse = false;
final n = ts.length;
var fs = new Array<Complex>();
fs.resize(n);
for (f in 0...n) {
var sum = Complex.zero;
for (t in 0...n) {
sum += ts[t] * Complex.exp((inverse ? 1 : -1) * 2 * Math.PI * f * t / n);
}
fs[f] = inverse ? sum.scale(1 / n) : sum;
}
return fs;
}
/**
Finds the power of 2 that is equal to or greater than the given natural.
**/
static function nextPow2(x:Int) : Int {
if (x < 2) return 1;
else if ((x & (x-1)) == 0) return x;
var pow = 2;
x--;
while ((x >>= 1) != 0) pow <<= 1;
return pow;
}
// testing, but also acts like an example
static function main() {
// sampling and buffer parameters
final Fs = 44100.0;
final N = 512;
final halfN = Std.int(N / 2);
// build a time signal as a sum of sinusoids
final freqs = [5919.911];
final ts = [for (n in 0...N) freqs.map(f -> Math.sin(2 * Math.PI * f * n / Fs)).sum()];
// get positive spectrum and use its symmetry to reconstruct negative domain
final fs_pos = rfft(ts);
final fs_fft = new OffsetArray(
[for (k in -(halfN - 1) ... 0) fs_pos[-k].conj()].concat(fs_pos),
-(halfN - 1)
);
// double-check with naive DFT
final fs_dft = new OffsetArray(
dft(ts.map(Complex.fromReal)).circShift(halfN - 1),
-(halfN - 1)
);
final fs_err = [for (k in -(halfN - 1) ... halfN) fs_fft[k] - fs_dft[k]];
final max_fs_err = fs_err.map(z -> z.magnitude).max();
if (max_fs_err > 1e-6) haxe.Log.trace('FT Error: ${max_fs_err}', null);
// else for (k => s in fs_fft) haxe.Log.trace('${k * Fs / N};${s.scale(1 / Fs).magnitude}', null);
// find spectral peaks to detect signal frequencies
final freqis = fs_fft.array.map(z -> z.magnitude)
.findPeaks()
.map(k -> (k - (halfN - 1)) * Fs / N)
.filter(f -> f >= 0);
if (freqis.length != freqs.length) {
trace('Found frequencies: ${freqis}');
} else {
final freqs_err = [for (i in 0...freqs.length) freqis[i] - freqs[i]];
final max_freqs_err = freqs_err.map(Math.abs).max();
if (max_freqs_err > Fs / N) trace('Frequency Errors: ${freqs_err}');
}
// recover time signal from the frequency domain
final ts_ifft = ifft(fs_fft.array.circShift(-(halfN - 1)).map(z -> z.scale(1 / Fs)));
final ts_err = [for (n in 0...N) ts_ifft[n].scale(Fs).real - ts[n]];
final max_ts_err = ts_err.map(Math.abs).max();
if (max_ts_err > 1e-6) haxe.Log.trace('IFT Error: ${max_ts_err}', null);
// else for (n in 0...ts_ifft.length) haxe.Log.trace('${n / Fs};${ts_ifft[n].scale(Fs).real}', null);
}
}

78
source/dsp/OffsetArray.hx Normal file
View file

@ -0,0 +1,78 @@
package dsp;
/**
A view into an Array with an indexing offset.
Usages include 1-indexed sequences or zero-centered buffers with negative indexing.
**/
@:forward(array, offset)
abstract OffsetArray<T>({
final array : Array<T>;
final offset : Int;
}) {
public inline function new(array:Array<T>, offset:Int)
this = { array: array, offset: offset };
public var length(get,never) : Int;
inline function get_length()
return this.array.length;
@:arrayAccess
public inline function get(index:Int) : T
return this.array[index - this.offset];
@:arrayAccess
public inline function set(index:Int, value:T) : Void
this.array[index - this.offset] = value;
/**
Iterates through items in their original order while providing the altered indexes as keys.
**/
public inline function keyValueIterator() : KeyValueIterator<Int,T>
return new OffsetArrayIterator(this.array, this.offset);
@:from
static inline function fromArray<T>(array:Array<T>)
return new OffsetArray(array, 0);
@:to
inline function toArray()
return this.array;
/**
Makes a shifted version of the given `array`, where elements are in the
same order but shifted by `n` positions (to the right if positive and to
the left if negative) in **circular** fashion (no elements discarded).
**/
public static function circShift<T>(array:Array<T>, n:Int) : Array<T> {
if (n < 0) return circShift(array, array.length + n);
var shifted = new Array<T>();
n = n % array.length;
for (i in array.length - n ... array.length) shifted.push(array[i]);
for (i in 0 ... array.length - n) shifted.push(array[i]);
return shifted;
}
}
private class OffsetArrayIterator<T> {
private final array : Array<T>;
private final offset : Int;
private var enumeration : Int;
public inline function new(array:Array<T>, offset:Int) {
this.array = array;
this.offset = offset;
this.enumeration = 0;
}
public inline function next() : {key:Int, value:T} {
final i = this.enumeration++;
return { key: i + this.offset, value: this.array[i] };
}
public inline function hasNext() : Bool
return this.enumeration < this.array.length;
}

110
source/dsp/Signal.hx Normal file
View file

@ -0,0 +1,110 @@
package dsp;
using Lambda;
/**
Signal processing miscellaneous utilities.
**/
class Signal {
/**
Returns a smoothed version of the input array using a moving average.
**/
public static function smooth(y:Array<Float>, n:Int) : Array<Float> {
if (n <= 0) {
return null;
} else if (n == 1) {
return y.copy();
} else {
var smoothed = new Array<Float>();
smoothed.resize(y.length);
for (i in 0...y.length) {
var m = i + 1 < n ? i : n - 1;
smoothed[i] = sum(y.slice(i - m, i + 1));
}
return smoothed;
}
}
/**
Finds indexes of peaks in the order they appear in the input sequence.
@param threshold Minimal peak height wrt. its neighbours, defaults to 0.
@param minHeight Minimal peak height wrt. the whole input, defaults to global minimum.
**/
public static function findPeaks(
y:Array<Float>,
?threshold:Float,
?minHeight:Float
) : Array<Int> {
threshold = threshold == null ? 0.0 : Math.abs(threshold);
minHeight = minHeight == null ? Signal.min(y) : minHeight;
var peaks = new Array<Int>();
final dy = [for (i in 1...y.length) y[i] - y[i-1]];
for (i in 1...dy.length) {
// peak: function growth positive to its left and negative to its right
if (
dy[i-1] > threshold && dy[i] < -threshold &&
y[i] > minHeight
) {
peaks.push(i);
}
}
return peaks;
}
/**
Returns the sum of all the elements of a given array.
This function tries to minimize floating-point precision errors.
**/
public static function sum(array:Array<Float>) : Float {
// Neumaier's "improved Kahan-Babuska algorithm":
var sum = 0.0;
var c = 0.0; // running compensation for lost precision
for (v in array) {
var t = sum + v;
c += Math.abs(sum) >= Math.abs(v)
? (sum - t) + v // sum is bigger => low-order digits of v are lost
: (v - t) + sum; // v is bigger => low-order digits of sum are lost
sum = t;
}
return sum + c; // correction only applied at the very end
}
/**
Returns the average value of an array.
**/
public static function mean(y:Array<Float>) : Float
return sum(y) / y.length;
/**
Returns the global maximum.
**/
public static function max(y:Array<Float>) : Float
return y.fold(Math.max, y[0]);
/**
Returns the global maximum's index.
**/
public static function maxi(y:Array<Float>) : Int
return y.foldi((yi, m, i) -> yi > y[m] ? i : m, 0);
/**
Returns the global minimum.
**/
public static function min(y:Array<Float>) : Float
return y.fold(Math.min, y[0]);
/**
Returns the global minimum's index.
**/
public static function mini(y:Array<Float>) : Int
return y.foldi((yi, m, i) -> yi < y[m] ? i : m, 0);
}

View file

@ -0,0 +1,52 @@
package freeplayStuff;
import flixel.FlxSprite;
class DJBoyfriend extends FlxSprite
{
public function new(x:Float, y:Float)
{
super(x, y);
animOffsets = new Map<String, Array<Dynamic>>();
frames = Paths.getSparrowAtlas('freeplay/bfFreeplay');
animation.addByPrefix('intro', "boyfriend dj intro", 24, false);
animation.addByPrefix('idle', "Boyfriend DJ0", 24);
animation.addByPrefix('confirm', "Boyfriend DJ confirm", 24);
addOffset('intro', 0, 0);
addOffset('idle', -4, -426);
playAnim('intro');
animation.finishCallback = function(anim)
{
switch (anim)
{
case "intro":
playAnim('idle'); // plays idle anim after playing intro
}
};
}
// playAnim stolen from Character.hx, cuz im lazy lol!
public var animOffsets:Map<String, Array<Dynamic>>;
public function playAnim(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
{
animation.play(AnimName, Force, Reversed, Frame);
var daOffset = animOffsets.get(AnimName);
if (animOffsets.exists(AnimName))
{
offset.set(daOffset[0], daOffset[1]);
}
else
offset.set(0, 0);
}
public function addOffset(name:String, x:Float = 0, y:Float = 0)
{
animOffsets[name] = [x, y];
}
}

View file

@ -0,0 +1,71 @@
package freeplayStuff;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.group.FlxSpriteGroup;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.text.FlxText;
class SongMenuItem extends FlxSpriteGroup
{
var capsule:FlxSprite;
public var selected(default, set):Bool = false;
public var songTitle:String = "Test";
var songText:FlxText;
public var targetPos:FlxPoint = new FlxPoint();
public var doLerp:Bool = false;
public function new(x:Float, y:Float, song:String)
{
super(x, y);
this.songTitle = song;
capsule = new FlxSprite();
capsule.frames = Paths.getSparrowAtlas('freeplay/freeplayCapsule');
capsule.animation.addByPrefix('selected', 'mp3 capsule w backing0', 24);
capsule.animation.addByPrefix('unselected', 'mp3 capsule w backing NOT SELECTED', 24);
// capsule.animation
add(capsule);
songText = new FlxText(120, 40, 0, songTitle, 40);
songText.font = "5by7";
songText.color = 0xFF43C1EA;
add(songText);
selected = selected; // just to kickstart the set_selected
}
override function update(elapsed:Float)
{
if (doLerp)
{
x = CoolUtil.coolLerp(x, targetPos.x, 0.3);
y = CoolUtil.coolLerp(y, targetPos.y, 0.4);
}
if (FlxG.keys.justPressed.ALT)
selected = false;
if (FlxG.keys.justPressed.CONTROL)
selected = true;
super.update(elapsed);
}
function set_selected(value:Bool):Bool
{
trace(value);
// cute one liners, lol!
songText.alpha = value ? 1 : 0.6;
capsule.offset.x = value ? 0 : -5;
capsule.animation.play(value ? "selected" : "unselected");
return value;
}
}

View file

@ -0,0 +1,72 @@
package shaderslmfao;
import flixel.system.FlxAssets.FlxShader;
class ScreenWipeShader extends FlxShader
{
public var daAlphaShit(default, set):Float = 0;
function set_daAlphaShit(alpha:Float):Float
{
alphaShit.value[0] = alpha;
return alpha;
}
@:glFragmentSource('
#pragma header
uniform float alphaShit;
uniform float yPos;
uniform float xPos;
uniform sampler2D funnyShit;
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main()
{
vec2 funnyUv = openfl_TextureCoordv;
vec4 color = flixel_texture2D(bitmap, funnyUv);
vec2 reallyFunnyUv = vec2(vec2(0.0, 0.0) - gl_FragCoord.xy / openfl_TextureSize.xy);
vec4 gf = flixel_texture2D(funnyShit, openfl_TextureCoordv);
vec3 hsvTypeBeat = rgb2hsv(vec3(gf.r, gf.g, gf.b));
vec4 output = color;
// .b here actually means value?
if (hsvTypeBeat.b <= alphaShit)
color = vec4(0.0, 0.0, 0.0, 0.0);
gl_FragColor = color;
}
')
public function new()
{
super();
alphaShit.value = [0];
}
}

View file

@ -0,0 +1,22 @@
package shaderslmfao;
import flixel.system.FlxAssets.FlxShader;
class WaveShader extends FlxShader
{
@:glFragmentSource('
#pragma header
void main()
{
vec4 color = flixel_texture2D(bitmap, openfl_TextureCoordv);
gl_FragColor = color;
}
')
public function new()
{
super();
}
}

View file

@ -3,10 +3,8 @@ package ui;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.text.FlxText;
import flixel.util.FlxColor;
#if cpp
import polymod.Polymod;
import sys.FileSystem;
#end
class ModMenu extends ui.OptionsState.Page
{

View file

@ -32,7 +32,7 @@ class OptionsState extends MusicBeatState
var controls = addPage(Controls, new ControlsMenu());
// var colors = addPage(Colors, new ColorsMenu());
#if cpp
#if polymod
var mods = addPage(Mods, new ModMenu());
#end
@ -43,7 +43,7 @@ class OptionsState extends MusicBeatState
// colors.onExit.add(switchPage.bind(Options));
preferences.onExit.add(switchPage.bind(Options));
#if cpp
#if polymod
mods.onExit.add(switchPage.bind(Options));
#end
}
@ -179,7 +179,7 @@ class OptionsMenu extends Page
createItem('preferences', function() switchPage(Preferences));
createItem("controls", function() switchPage(Controls));
// createItem('colors', function() switchPage(Colors));
#if cpp
#if polymod
createItem('mods', function() switchPage(Mods));
#end