mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-23 08:07:54 -05:00
Merge branch 'feature/scripted-stages'
This commit is contained in:
commit
2d673e037a
130 changed files with 2397 additions and 1826 deletions
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
|
@ -1,19 +1,17 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "HTML5 Debug",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://127.0.0.1:3001",
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"preLaunchTask": "debug: html"
|
||||
},
|
||||
{
|
||||
// Launch in native/CPP
|
||||
"name": "Lime",
|
||||
"type": "lime",
|
||||
"request": "launch"
|
||||
},
|
||||
{
|
||||
// Evaluate macros with debugging enabled
|
||||
"name": "Haxe Eval",
|
||||
"type": "haxe-eval",
|
||||
"request": "launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
189
Project.xml
189
Project.xml
|
@ -2,7 +2,7 @@
|
|||
<project>
|
||||
<!-- _________________________ Application Settings _________________________ -->
|
||||
|
||||
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.2.8" company="ninjamuffin99" />
|
||||
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.3.0" company="ninjamuffin99" />
|
||||
|
||||
<!--Switch Export with Unique ApplicationID and Icon-->
|
||||
<set name="APP_ID" value="0x0100f6c013bbc000" />
|
||||
|
@ -24,10 +24,10 @@
|
|||
<window if="html5" resizable="true" />
|
||||
|
||||
<!--Desktop-specific-->
|
||||
<window if="desktop" orientation="landscape" fullscreen="false" resizable="true" vsync="false"/>
|
||||
<window if="desktop" orientation="landscape" fullscreen="false" resizable="true" vsync="false" />
|
||||
|
||||
<!--Mobile-specific-->
|
||||
<window if="mobile" orientation="landscape" fullscreen="true" width="0" height="0" resizable="false"/>
|
||||
<window if="mobile" orientation="landscape" fullscreen="true" width="0" height="0" resizable="false" />
|
||||
|
||||
<!-- _____________________________ Path Settings ____________________________ -->
|
||||
|
||||
|
@ -37,78 +37,79 @@
|
|||
|
||||
<classpath name="source" />
|
||||
|
||||
<assets path="assets/preload" rename="assets" exclude="*.ogg" if="web"/>
|
||||
<assets path="assets/preload" rename="assets" exclude="*.mp3" unless="web"/>
|
||||
|
||||
<assets path="assets/preload" rename="assets" exclude="*.ogg" if="web" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.mp3" unless="web" />
|
||||
|
||||
<define name="PRELOAD_ALL" unless="web" />
|
||||
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL"/>
|
||||
|
||||
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL" />
|
||||
|
||||
<section if="PRELOAD_ALL">
|
||||
<library name="songs" preload="true" />
|
||||
<library name="shared" preload="true" />
|
||||
<library name="songs" preload="true" />
|
||||
<library name="shared" preload="true" />
|
||||
<library name="tutorial" preload="true" />
|
||||
<library name="week1" preload="true" />
|
||||
<library name="week2" preload="true" />
|
||||
<library name="week3" preload="true" />
|
||||
<library name="week4" preload="true" />
|
||||
<library name="week5" preload="true" />
|
||||
<library name="week6" preload="true" />
|
||||
<library name="week7" preload="true" />
|
||||
<library name="week8" preload="true" />
|
||||
<library name="week1" preload="true" />
|
||||
<library name="week2" preload="true" />
|
||||
<library name="week3" preload="true" />
|
||||
<library name="week4" preload="true" />
|
||||
<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">
|
||||
<library name="songs" preload="false" />
|
||||
<library name="shared" preload="false" />
|
||||
<library name="songs" preload="false" />
|
||||
<library name="shared" preload="false" />
|
||||
<library name="tutorial" preload="false" />
|
||||
<library name="week1" preload="false" />
|
||||
<library name="week2" preload="false" />
|
||||
<library name="week3" preload="false" />
|
||||
<library name="week4" preload="false" />
|
||||
<library name="week5" preload="false" />
|
||||
<library name="week6" preload="false" />
|
||||
<library name="week7" preload="false" />
|
||||
<library name="week8" preload="false" />
|
||||
<library name="week1" preload="false" />
|
||||
<library name="week2" preload="false" />
|
||||
<library name="week3" preload="false" />
|
||||
<library name="week4" preload="false" />
|
||||
<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" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg" if="web"/>
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg" if="web"/>
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg" if="web"/>
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg" if="web"/>
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg" if="web"/>
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg" if="web"/>
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg" if="web"/>
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3" unless="web"/>
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg" if="web"/>
|
||||
<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="assets/songs" library="songs" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg" if="web" />
|
||||
<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'/> -->
|
||||
|
||||
<assets path='example_mods' rename='mods' embed='false' exclude="*.md"/>
|
||||
<assets path='example_mods' rename='mods' embed='false' exclude="*.md" />
|
||||
<assets path='art/readme.txt' rename='do NOT readme.txt' />
|
||||
|
||||
<assets path="CHANGELOG.md" rename='changelog.txt'/>
|
||||
|
||||
<assets path="CHANGELOG.md" rename='changelog.txt' />
|
||||
|
||||
<!-- NOTE FOR FUTURE SELF SINCE FONTS ARE ALWAYS FUCKY
|
||||
TO FIX ONE OF THEM, I CONVERTED IT TO OTF. DUNNO IF YOU NEED TO
|
||||
THEN UHHH I USED THE NAME OF THE FONT WITH SETFORMAT() ON THE TEXT!!!
|
||||
NOT USING A DIRECT THING TO THE ASSET!!!
|
||||
-->
|
||||
<assets path="assets/fonts" embed='true'/>
|
||||
<assets path="assets/fonts" embed='true' />
|
||||
<!-- _______________________________ Libraries ______________________________ -->
|
||||
|
||||
<haxelib name="openfl" />
|
||||
<haxelib name="flixel" />
|
||||
<haxedev set='webgl' />
|
||||
|
||||
|
@ -119,26 +120,27 @@
|
|||
<!--In case you want to use the ui package-->
|
||||
<haxelib name="flixel-ui" />
|
||||
<!--haxelib name="newgrounds" unless="switch"/> -->
|
||||
<haxelib name="faxe" if='switch'/>
|
||||
<haxelib name="polymod" if="desktop"/>
|
||||
<haxelib name="faxe" if='switch' />
|
||||
<haxelib name="polymod" />
|
||||
<haxelib name="firetongue" />
|
||||
|
||||
|
||||
<!-- <haxelib name="colyseus"/> -->
|
||||
<!-- <haxelib name="colyseus-websocket" /> -->
|
||||
<!-- <haxelib name="newgrounds"/> -->
|
||||
<haxelib name="hxcpp-debug-server" if="desktop debug"/>
|
||||
|
||||
<haxelib name="hxcpp-debug-server" if="desktop debug" />
|
||||
|
||||
<!-- swf stufffff -->
|
||||
<!-- <haxelib name="swf"/> -->
|
||||
<!-- <library path="assets/tanky.swf" preload="true"/> -->
|
||||
<!-- <library path="assets/tankBG.swf" preload="true"/> -->
|
||||
|
||||
|
||||
<!-- <haxelib name="flixel-animate" /> -->
|
||||
<!-- <haxelib name="spinehaxe" /> -->
|
||||
<!-- https://github.com/ninjamuffin99/Flixel-Animate-Atlas-Player -->
|
||||
|
||||
|
||||
<!--<haxelib name="discord_rpc" if="cpp"/> --> <!-- foesn't work with neko -->
|
||||
<!--<haxelib name="discord_rpc" if="cpp"/> -->
|
||||
<!-- foesn't work with neko -->
|
||||
<!-- <haxelib name="hxcpp-debug-server" if="desktop"/> -->
|
||||
|
||||
<!-- <haxelib name="markdown" /> -->
|
||||
|
@ -179,52 +181,57 @@
|
|||
<!--Enable this for Nape release builds for a serious peformance improvement-->
|
||||
<haxedef name="NAPE_RELEASE_BUILD" unless="debug" />
|
||||
|
||||
<haxeflag name="--no-traces" unless="debug" />
|
||||
<haxeflag name="--dce full" if="release" />
|
||||
<!-- _________________________________ Custom _______________________________ -->
|
||||
|
||||
<!-- Disable trace() calls in release builds to bump up performance. -->
|
||||
<haxeflag name="--no-traces" unless="debug" />
|
||||
|
||||
<!-- HScript relies heavily on Reflection, which means we can't use DCE. -->
|
||||
<haxeflag name="-dce no" />
|
||||
<haxeflag name="--macro" value="include('funkin')" />
|
||||
|
||||
<!-- Necessary to provide stack traces for HScript. -->
|
||||
<haxedef name="hscriptPos" />
|
||||
<haxedef name="HXCPP_CHECK_POINTER" />
|
||||
<haxedef name="HXCPP_STACK_LINE" />
|
||||
|
||||
<!-- _________________________________ Custom _______________________________ -->
|
||||
|
||||
<!-- This macro allows addition of new functionality to existing Flixel. -->
|
||||
<haxeflag name="--macro" value="addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
|
||||
|
||||
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
|
||||
|
||||
<icon path="art/icon16.png" size='16'/>
|
||||
<icon path="art/icon32.png" size='32'/>
|
||||
<icon path="art/icon64.png" size='64'/>
|
||||
|
||||
<icon path="art/icon16.png" size='16' />
|
||||
<icon path="art/icon32.png" size='32' />
|
||||
<icon path="art/icon64.png" size='64' />
|
||||
<icon path="art/iconOG.png" />
|
||||
|
||||
<!--
|
||||
aww yeah hackerman time, run a macro on the class rather than edit it
|
||||
took me a while to get this working but turns out the classname comes SECOND
|
||||
-->
|
||||
<haxeflag name="--macro" value="addMetadata('@:build(util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
|
||||
|
||||
<!-- <haxedef name="SKIP_TO_PLAYSTATE" if="debug" /> -->
|
||||
|
||||
<haxedef name="CAN_OPEN_LINKS" unless="switch"/>
|
||||
<haxedef name="CAN_CHEAT" if="switch debug"/>
|
||||
|
||||
|
||||
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
|
||||
<haxedef name="CAN_CHEAT" if="switch debug" />
|
||||
|
||||
<!-- Skip the Intro -->
|
||||
<section if="debug">
|
||||
<!-- Starts the game at the specified week, at the first song -->
|
||||
<!-- <haxedef name="week" value="1" if="debug"/> -->
|
||||
|
||||
|
||||
<!-- Starts the game at the specified song -->
|
||||
<!-- <haxedef name="song" value="bopeebo" if="debug"/> -->
|
||||
|
||||
|
||||
<!-- Difficulty, only used for week or song, defaults to 1 -->
|
||||
<!-- <haxedef name="dif" value="2" if="debug"/> -->
|
||||
</section>
|
||||
|
||||
|
||||
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
|
||||
|
||||
|
||||
<section if="newgrounds">
|
||||
<!-- Enables Ng.core.verbose -->
|
||||
<!-- <haxedef name="NG_VERBOSE" /> -->
|
||||
|
||||
|
||||
<!-- Enables a NG debug session, so medals don't permently unlock -->
|
||||
<!-- <haxedef name="NG_DEBUG" /> -->
|
||||
|
||||
|
||||
<!-- pretends that the saved session Id was expired, forcing the reconnect prompt -->
|
||||
<!-- <haxedef name="NG_FORCE_EXPIRED_SESSION" if="debug" /> -->
|
||||
</section>
|
||||
|
@ -232,8 +239,8 @@
|
|||
<!-- <prebuild haxe="trace('prebuilding');"/> -->
|
||||
<!-- <postbuild haxe="art/Postbuild.hx"/> -->
|
||||
|
||||
<config:ios allow-provisioning-updates="true" team-id=""/>
|
||||
|
||||
<config:ios allow-provisioning-updates="true" team-id="" />
|
||||
|
||||
<!-- Options for Polymod -->
|
||||
<section if="polymod">
|
||||
<!-- Turns on additional debug logging. -->
|
||||
|
@ -259,10 +266,10 @@
|
|||
<section if='TOOLS'>
|
||||
<!-- Compiles tool for old song conversion shit -->
|
||||
<!-- Assumes you use it on windows/desktop!!!! -->
|
||||
<postbuild command='haxe -main art/SongConverter.hx --cs export/songShit'/>
|
||||
<postbuild command='haxe -main art/SongConverter.hx --cs export/songShit' />
|
||||
<assets path='export/songShit/bin/SongConverter.exe' rename='SongConverter.exe' />
|
||||
|
||||
<!-- <postbuild command='ren export/songShit/bin export/songShit/tools '/> -->
|
||||
<!-- <postbuild command='move export/songShit/tools export/release/windows/bin'/> -->
|
||||
</section>
|
||||
</project>
|
||||
</project>
|
BIN
example_mods/introMod/_polymod_icon.png
Normal file
BIN
example_mods/introMod/_polymod_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
8
example_mods/introMod/_polymod_meta.json
Normal file
8
example_mods/introMod/_polymod_meta.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"title": "Intro Mod",
|
||||
"description": "An introductory mod.",
|
||||
"author": "MasterEric",
|
||||
"api_version": "0.1.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"title": "Testing123",
|
||||
"description": "Newgrounds? More like OLDGROUNDS lol.",
|
||||
"author": "MasterEric",
|
||||
"api_version": "0.1.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
{
|
||||
"title": "Testing123",
|
||||
"description": "Newgrounds? More like OLDGROUNDS lol.",
|
||||
"author": "MasterEric",
|
||||
"api_version": "0.1.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
package;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
|
||||
class BGSprite extends FlxSprite
|
||||
{
|
||||
/**
|
||||
Cool lil utility thing just so that it can easy do antialiasing and scrollfactor bullshit
|
||||
*/
|
||||
public var idleAnim:String;
|
||||
|
||||
/**
|
||||
* NOTE: loadOldWay param is just for current backward compatibility! Will be moved later!
|
||||
*/
|
||||
public function new(image:String, x:Float = 0, y:Float = 0, parX:Float = 1, parY:Float = 1, ?daAnimations:Array<String>, ?loopingAnim:Bool = false,
|
||||
?loadOldWay:Bool = true)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
if (loadOldWay)
|
||||
{
|
||||
if (daAnimations != null)
|
||||
{
|
||||
setupSparrow(image, daAnimations, loopingAnim);
|
||||
}
|
||||
else
|
||||
{
|
||||
justLoadImage(image);
|
||||
}
|
||||
}
|
||||
|
||||
scrollFactor.set(parX, parY);
|
||||
antialiasing = true;
|
||||
}
|
||||
|
||||
public function setupSparrow(image:String, daAnimations:Array<String>, ?loopingAnim:Bool = false)
|
||||
{
|
||||
frames = Paths.getSparrowAtlas(image);
|
||||
for (anims in daAnimations)
|
||||
{
|
||||
var daLoop:Bool = loopingAnim;
|
||||
if (loopingAnim == null)
|
||||
daLoop = false;
|
||||
|
||||
animation.addByPrefix(anims, anims, 24, daLoop);
|
||||
animation.play(anims);
|
||||
|
||||
if (idleAnim == null)
|
||||
idleAnim = anims;
|
||||
}
|
||||
}
|
||||
|
||||
public function justLoadImage(image:String)
|
||||
{
|
||||
loadGraphic(Paths.image(image));
|
||||
active = false;
|
||||
}
|
||||
|
||||
public function dance():Void
|
||||
{
|
||||
if (idleAnim != null)
|
||||
animation.play(idleAnim);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
|
||||
class BackgroundDancer extends FlxSprite
|
||||
{
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
frames = Paths.getSparrowAtlas("limo/limoDancer");
|
||||
animation.addByIndices('danceLeft', 'bg dancer sketch PINK', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||
animation.addByIndices('danceRight', 'bg dancer sketch PINK', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
||||
animation.play('danceLeft');
|
||||
animation.finish();
|
||||
antialiasing = true;
|
||||
}
|
||||
|
||||
var danceDir:Bool = false;
|
||||
|
||||
public function dance():Void
|
||||
{
|
||||
danceDir = !danceDir;
|
||||
|
||||
if (danceDir)
|
||||
animation.play('danceRight', true);
|
||||
else
|
||||
animation.play('danceLeft', true);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
|
||||
class BackgroundGirls extends FlxSprite
|
||||
{
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
// BG fangirls dissuaded
|
||||
frames = Paths.getSparrowAtlas('weeb/bgFreaks');
|
||||
|
||||
animation.addByIndices('danceLeft', 'BG girls group', CoolUtil.numberArray(14), "", 24, false);
|
||||
animation.addByIndices('danceRight', 'BG girls group', CoolUtil.numberArray(30, 15), "", 24, false);
|
||||
|
||||
animation.play('danceLeft');
|
||||
animation.finish();
|
||||
}
|
||||
|
||||
var danceDir:Bool = false;
|
||||
|
||||
public function getScared():Void
|
||||
{
|
||||
animation.addByIndices('danceLeft', 'BG fangirls dissuaded', CoolUtil.numberArray(14), "", 24, false);
|
||||
animation.addByIndices('danceRight', 'BG fangirls dissuaded', CoolUtil.numberArray(30, 15), "", 24, false);
|
||||
dance();
|
||||
animation.finish();
|
||||
}
|
||||
|
||||
public function dance():Void
|
||||
{
|
||||
danceDir = !danceDir;
|
||||
|
||||
if (danceDir)
|
||||
animation.play('danceRight', true);
|
||||
else
|
||||
animation.play('danceLeft', true);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package;
|
||||
|
||||
import funkin.InitState;
|
||||
import funkin.MemoryCounter;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import openfl.Lib;
|
||||
|
@ -42,9 +44,9 @@ class Main extends Sprite
|
|||
// A design similar to that of Minecraft resource packs would be intuitive.
|
||||
// 3. The interface should save (to the save file) and output an ordered array of mod IDs.
|
||||
// 4. Replace the call to PolymodHandler.loadAllMods() with a call to PolymodHandler.loadModsById(ids:Array<String>).
|
||||
modding.PolymodHandler.loadAllMods();
|
||||
funkin.modding.PolymodHandler.loadAllMods();
|
||||
|
||||
i18n.FireTongueHandler.init();
|
||||
funkin.i18n.FireTongueHandler.init();
|
||||
|
||||
if (stage != null)
|
||||
{
|
||||
|
@ -98,9 +100,11 @@ class Main extends Sprite
|
|||
#if debug
|
||||
fpsCounter = new FPS(10, 3, 0xFFFFFF);
|
||||
addChild(fpsCounter);
|
||||
#if !html5
|
||||
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
|
||||
addChild(memoryCounter);
|
||||
#end
|
||||
#end
|
||||
|
||||
/*
|
||||
video = new Video();
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
package;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import haxe.display.Display.Package;
|
||||
|
||||
class TankmenBG extends FlxSprite
|
||||
{
|
||||
public static var animationNotes:Array<Dynamic> = [];
|
||||
|
||||
public var strumTime:Float = 0;
|
||||
public var goingRight:Bool = false;
|
||||
public var tankSpeed:Float = 0.7;
|
||||
|
||||
public var endingOffset:Float;
|
||||
|
||||
public function new(x:Float, y:Float, isGoingRight:Bool)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
// makeGraphic(200, 200);
|
||||
|
||||
frames = Paths.getSparrowAtlas('tankmanKilled1');
|
||||
antialiasing = true;
|
||||
animation.addByPrefix('run', 'tankman running', 24, true);
|
||||
animation.addByPrefix('shot', 'John Shot ' + FlxG.random.int(1, 2), 24, false);
|
||||
|
||||
animation.play('run');
|
||||
animation.curAnim.curFrame = FlxG.random.int(0, animation.curAnim.numFrames - 1);
|
||||
|
||||
updateHitbox();
|
||||
|
||||
setGraphicSize(Std.int(width * 0.8));
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
public function resetShit(x:Float, y:Float, isGoingRight:Bool)
|
||||
{
|
||||
setPosition(x, y);
|
||||
goingRight = isGoingRight;
|
||||
endingOffset = FlxG.random.float(50, 200);
|
||||
tankSpeed = FlxG.random.float(0.6, 1);
|
||||
|
||||
if (goingRight)
|
||||
flipX = true;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (x >= FlxG.width * 1.2 || x <= FlxG.width * -0.5)
|
||||
visible = false;
|
||||
else
|
||||
visible = true;
|
||||
|
||||
if (animation.curAnim.name == 'run')
|
||||
{
|
||||
var endDirection:Float = (FlxG.width * 0.74) + endingOffset;
|
||||
|
||||
if (goingRight)
|
||||
{
|
||||
endDirection = (FlxG.width * 0.02) - endingOffset;
|
||||
|
||||
x = (endDirection + (Conductor.songPosition - strumTime) * tankSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
x = (endDirection - (Conductor.songPosition - strumTime) * tankSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
if (Conductor.songPosition > strumTime)
|
||||
{
|
||||
// kill();
|
||||
animation.play('shot');
|
||||
|
||||
if (goingRight)
|
||||
{
|
||||
offset.y = 200;
|
||||
offset.x = 300;
|
||||
}
|
||||
}
|
||||
|
||||
if (animation.curAnim.name == 'shot' && animation.curAnim.curFrame >= animation.curAnim.frames.length - 1)
|
||||
{
|
||||
kill();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSubState;
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Section.SwagSection;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.Section.SwagSection;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.animation.FlxBaseAnimation;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.util.FlxSort;
|
||||
import haxe.io.Path;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -19,7 +22,7 @@ class Character extends FlxSprite
|
|||
|
||||
public var holdTimer:Float = 0;
|
||||
|
||||
public var animationNotes:Array<Dynamic> = [];
|
||||
public var animationNotes:Array<NoteData> = [];
|
||||
|
||||
public function new(x:Float, y:Float, ?character:String = "bf", ?isPlayer:Bool = false)
|
||||
{
|
||||
|
@ -267,6 +270,8 @@ class Character extends FlxSprite
|
|||
quickAnimAdd('singLEFTmiss', 'BF NOTE LEFT MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'BF NOTE RIGHT MISS');
|
||||
quickAnimAdd('singDOWNmiss', 'BF NOTE DOWN MISS');
|
||||
quickAnimAdd('preAttack', 'bf pre attack');
|
||||
quickAnimAdd('attack', 'boyfriend attack');
|
||||
quickAnimAdd('hey', 'BF HEY');
|
||||
|
||||
quickAnimAdd('firstDeath', "BF dies");
|
||||
|
@ -582,27 +587,25 @@ class Character extends FlxSprite
|
|||
|
||||
public function loadMappedAnims()
|
||||
{
|
||||
var swagshit = SongLoad.loadFromJson('picospeaker', 'stress');
|
||||
var swagshit:SwagSong = SongLoad.loadFromJson('stress', 'stress');
|
||||
|
||||
var notes = swagshit.extraNotes.get('picospeaker');
|
||||
var notes:Array<SwagSection> = swagshit.noteMap.get('picospeaker');
|
||||
|
||||
for (section in notes)
|
||||
{
|
||||
for (idk in section.sectionNotes)
|
||||
for (noteData in section.sectionNotes)
|
||||
{
|
||||
animationNotes.push(idk);
|
||||
animationNotes.push(noteData);
|
||||
}
|
||||
}
|
||||
|
||||
TankmenBG.animationNotes = animationNotes;
|
||||
|
||||
trace(animationNotes);
|
||||
animationNotes.sort(sortAnims);
|
||||
}
|
||||
|
||||
function sortAnims(val1:Array<Dynamic>, val2:Array<Dynamic>):Int
|
||||
function sortAnims(val1:NoteData, val2:NoteData):Int
|
||||
{
|
||||
return FlxSort.byValues(FlxSort.ASCENDING, val1[0], val2[0]);
|
||||
return FlxSort.byValues(FlxSort.ASCENDING, val1.strumTime, val2.strumTime);
|
||||
}
|
||||
|
||||
function quickAnimAdd(name:String, prefix:String)
|
||||
|
@ -657,13 +660,13 @@ class Character extends FlxSprite
|
|||
// for pico??
|
||||
if (animationNotes.length > 0)
|
||||
{
|
||||
if (Conductor.songPosition > animationNotes[0][0])
|
||||
if (Conductor.songPosition > animationNotes[0].strumTime)
|
||||
{
|
||||
trace('played shoot anim' + animationNotes[0][1]);
|
||||
trace('played shoot anim' + animationNotes[0].noteData);
|
||||
|
||||
var shootAnim:Int = 1;
|
||||
|
||||
if (animationNotes[0][1] >= 2)
|
||||
if ((cast animationNotes[0].noteData) >= 2)
|
||||
shootAnim = 3;
|
||||
|
||||
shootAnim += FlxG.random.int(0, 1);
|
||||
|
@ -689,6 +692,8 @@ class Character extends FlxSprite
|
|||
*/
|
||||
public function dance()
|
||||
{
|
||||
if (animation == null)
|
||||
return;
|
||||
if (!debugMode)
|
||||
{
|
||||
switch (curCharacter)
|
||||
|
@ -723,6 +728,8 @@ class Character extends FlxSprite
|
|||
|
||||
public function playAnim(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
|
||||
{
|
||||
if (animation == null)
|
||||
return;
|
||||
animation.play(AnimName, Force, Reversed, Frame);
|
||||
|
||||
var daOffset = animOffsets.get(AnimName);
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import SongLoad.SwagSong;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
|
||||
/**
|
||||
* ...
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxObject;
|
||||
import flixel.input.FlxInput;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
|
@ -15,7 +15,8 @@ import haxe.format.JsonParser;
|
|||
import lime.math.Rectangle;
|
||||
import lime.utils.Assets;
|
||||
import openfl.filters.ShaderFilter;
|
||||
import shaderslmfao.ScreenWipeShader;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.shaderslmfao.ScreenWipeShader;
|
||||
|
||||
using StringTools;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.text.FlxTypeText;
|
||||
|
@ -8,6 +8,7 @@ import flixel.input.FlxKeyManager;
|
|||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
|
@ -1,92 +1,92 @@
|
|||
package;
|
||||
|
||||
import Sys.sleep;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if discord_rpc
|
||||
import discord_rpc.DiscordRpc;
|
||||
#end
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
#if discord_rpc
|
||||
public function new()
|
||||
{
|
||||
trace("Discord Client starting...");
|
||||
DiscordRpc.start({
|
||||
clientID: "814588678700924999",
|
||||
onReady: onReady,
|
||||
onError: onError,
|
||||
onDisconnected: onDisconnected
|
||||
});
|
||||
trace("Discord Client started.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
DiscordRpc.process();
|
||||
sleep(2);
|
||||
// trace("Discord Client Update");
|
||||
}
|
||||
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
public static function shutdown()
|
||||
{
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
static function onReady()
|
||||
{
|
||||
DiscordRpc.presence({
|
||||
details: "In the Menus",
|
||||
state: null,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'"
|
||||
});
|
||||
}
|
||||
|
||||
static function onError(_code:Int, _message:String)
|
||||
{
|
||||
trace('Error! $_code : $_message');
|
||||
}
|
||||
|
||||
static function onDisconnected(_code:Int, _message:String)
|
||||
{
|
||||
trace('Disconnected! $_code : $_message');
|
||||
}
|
||||
|
||||
public static function initialize()
|
||||
{
|
||||
var DiscordDaemon = sys.thread.Thread.create(() ->
|
||||
{
|
||||
new DiscordClient();
|
||||
});
|
||||
trace("Discord Client initialized");
|
||||
}
|
||||
|
||||
public static function changePresence(details:String, state:Null<String>, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float)
|
||||
{
|
||||
var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0;
|
||||
|
||||
if (endTimestamp > 0)
|
||||
{
|
||||
endTimestamp = startTimestamp + endTimestamp;
|
||||
}
|
||||
|
||||
DiscordRpc.presence({
|
||||
details: details,
|
||||
state: state,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'",
|
||||
smallImageKey: smallImageKey,
|
||||
// Obtained times are in milliseconds so they are divided so Discord can use it
|
||||
startTimestamp: Std.int(startTimestamp / 1000),
|
||||
endTimestamp: Std.int(endTimestamp / 1000)
|
||||
});
|
||||
|
||||
// trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp');
|
||||
}
|
||||
#end
|
||||
}
|
||||
package funkin;
|
||||
|
||||
import Sys.sleep;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if discord_rpc
|
||||
import discord_rpc.DiscordRpc;
|
||||
#end
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
#if discord_rpc
|
||||
public function new()
|
||||
{
|
||||
trace("Discord Client starting...");
|
||||
DiscordRpc.start({
|
||||
clientID: "814588678700924999",
|
||||
onReady: onReady,
|
||||
onError: onError,
|
||||
onDisconnected: onDisconnected
|
||||
});
|
||||
trace("Discord Client started.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
DiscordRpc.process();
|
||||
sleep(2);
|
||||
// trace("Discord Client Update");
|
||||
}
|
||||
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
public static function shutdown()
|
||||
{
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
static function onReady()
|
||||
{
|
||||
DiscordRpc.presence({
|
||||
details: "In the Menus",
|
||||
state: null,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'"
|
||||
});
|
||||
}
|
||||
|
||||
static function onError(_code:Int, _message:String)
|
||||
{
|
||||
trace('Error! $_code : $_message');
|
||||
}
|
||||
|
||||
static function onDisconnected(_code:Int, _message:String)
|
||||
{
|
||||
trace('Disconnected! $_code : $_message');
|
||||
}
|
||||
|
||||
public static function initialize()
|
||||
{
|
||||
var DiscordDaemon = sys.thread.Thread.create(() ->
|
||||
{
|
||||
new DiscordClient();
|
||||
});
|
||||
trace("Discord Client initialized");
|
||||
}
|
||||
|
||||
public static function changePresence(details:String, state:Null<String>, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float)
|
||||
{
|
||||
var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0;
|
||||
|
||||
if (endTimestamp > 0)
|
||||
{
|
||||
endTimestamp = startTimestamp + endTimestamp;
|
||||
}
|
||||
|
||||
DiscordRpc.presence({
|
||||
details: details,
|
||||
state: state,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'",
|
||||
smallImageKey: smallImageKey,
|
||||
// Obtained times are in milliseconds so they are divided so Discord can use it
|
||||
startTimestamp: Std.int(startTimestamp / 1000),
|
||||
endTimestamp: Std.int(endTimestamp / 1000)
|
||||
});
|
||||
|
||||
// trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp');
|
||||
}
|
||||
#end
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxSprite;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Controls.Control;
|
||||
import funkin.Controls.Control;
|
||||
import flash.text.TextField;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxGame;
|
||||
|
@ -21,15 +21,16 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import freeplayStuff.BGScrollingText;
|
||||
import freeplayStuff.DJBoyfriend;
|
||||
import freeplayStuff.FreeplayScore;
|
||||
import freeplayStuff.SongMenuItem;
|
||||
import funkin.freeplayStuff.BGScrollingText;
|
||||
import funkin.freeplayStuff.DJBoyfriend;
|
||||
import funkin.freeplayStuff.FreeplayScore;
|
||||
import funkin.freeplayStuff.SongMenuItem;
|
||||
import lime.app.Future;
|
||||
import lime.utils.Assets;
|
||||
import shaderslmfao.AngleMask;
|
||||
import shaderslmfao.PureColor;
|
||||
import shaderslmfao.StrokeShader;
|
||||
import funkin.shaderslmfao.AngleMask;
|
||||
import funkin.shaderslmfao.PureColor;
|
||||
import funkin.shaderslmfao.StrokeShader;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -78,6 +79,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
#if debug
|
||||
isDebug = true;
|
||||
addSong('Test', 1, 'bf-pixel');
|
||||
addSong('Pyro', 4, 'bf');
|
||||
#end
|
||||
|
||||
var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
|
||||
|
@ -517,10 +519,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
if (controls.BACK)
|
||||
{
|
||||
FlxG.sound.play(Paths.sound('cancelMenu'));
|
||||
|
||||
close();
|
||||
|
||||
// FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
|
||||
if (accepted)
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSubState;
|
||||
|
@ -6,8 +6,9 @@ import flixel.math.FlxPoint;
|
|||
import flixel.system.FlxSound;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import haxe.display.Display.Package;
|
||||
import ui.PreferencesMenu;
|
||||
import haxe.display.Display;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
class GameOverSubstate extends MusicBeatSubstate
|
||||
{
|
||||
|
@ -24,7 +25,7 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
gameOverMusic = new FlxSound();
|
||||
FlxG.sound.list.add(gameOverMusic);
|
||||
|
||||
var daStage = PlayState.curStage;
|
||||
var daStage = PlayState.curStageId;
|
||||
var daBf:String = '';
|
||||
switch (daStage)
|
||||
{
|
|
@ -1,7 +1,8 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
class GitarooPause extends MusicBeatState
|
||||
{
|
|
@ -1,7 +1,8 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
class Highscore
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
#if !(macro)
|
||||
import charting.ChartingState;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.charting.ChartingState;
|
||||
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.transition.TransitionData;
|
||||
|
@ -10,9 +10,11 @@ import flixel.math.FlxPoint;
|
|||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.display.BitmapData;
|
||||
import play.PicoFight;
|
||||
import ui.PreferencesMenu;
|
||||
import ui.stageBuildShit.StageBuilderState;
|
||||
import funkin.play.PicoFight;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.stageBuildShit.StageBuilderState;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -29,12 +31,17 @@ import sys.io.File;
|
|||
import sys.thread.Thread;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Initializes the game state using custom defines.
|
||||
* Only used in Debug builds.
|
||||
*/
|
||||
class InitState extends FlxTransitionableState
|
||||
{
|
||||
override public function create():Void
|
||||
{
|
||||
trace('This is a debug build, loading InitState...');
|
||||
#if android
|
||||
FlxG.android.preventDefaultKeys = [FlxAndroidKey.BACK];
|
||||
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
|
||||
#end
|
||||
#if newgrounds
|
||||
NGio.init();
|
||||
|
@ -113,6 +120,8 @@ class InitState extends FlxTransitionableState
|
|||
// FlxTransitionableState.skipNextTransOut = true;
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
|
||||
StageDataParser.loadStageCache();
|
||||
|
||||
#if song
|
||||
var song = getSong();
|
||||
|
||||
|
@ -191,21 +200,12 @@ class InitState extends FlxTransitionableState
|
|||
LoadingState.loadAndSwitchState(new PlayState());
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
function getWeek()
|
||||
return Std.parseInt(getDefine("week"));
|
||||
return Std.parseInt(MacroUtil.getDefine("week"));
|
||||
|
||||
function getSong()
|
||||
return getDefine("song");
|
||||
return MacroUtil.getDefine("song");
|
||||
|
||||
function getDif()
|
||||
return Std.parseInt(getDefine("dif", "1"));
|
||||
|
||||
macro function getDefine(key:String, defaultValue:String = null):haxe.macro.Expr
|
||||
{
|
||||
var value = haxe.macro.Context.definedValue(key);
|
||||
if (value == null)
|
||||
value = defaultValue;
|
||||
return macro $v{value};
|
||||
}
|
||||
return Std.parseInt(MacroUtil.getDefine("dif", "1"));
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Controls;
|
||||
import funkin.Controls;
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
|
@ -12,6 +12,7 @@ import lime.utils.AssetLibrary;
|
|||
import lime.utils.AssetManifest;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
class LoadingState extends MusicBeatState
|
||||
{
|
||||
|
@ -187,7 +188,14 @@ class LoadingState extends MusicBeatState
|
|||
|
||||
static function getNextState(target:FlxState, stopMusic = false):FlxState
|
||||
{
|
||||
Paths.setCurrentLevel("week" + PlayState.storyWeek);
|
||||
if (PlayState.storyWeek == 0)
|
||||
{
|
||||
Paths.setCurrentLevel('tutorial');
|
||||
}
|
||||
else
|
||||
{
|
||||
Paths.setCurrentLevel("week" + PlayState.storyWeek);
|
||||
}
|
||||
#if NO_PRELOAD_ALL
|
||||
var loaded = isSoundLoaded(getSongPath())
|
||||
&& (!PlayState.SONG.needsVoices || isSoundLoaded(getVocalPath()))
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import NGio;
|
||||
import funkin.NGio;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
|
@ -17,12 +17,12 @@ 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;
|
||||
import ui.PreferencesMenu;
|
||||
import ui.Prompt;
|
||||
import funkin.shaderslmfao.ScreenWipeShader;
|
||||
import funkin.ui.AtlasMenuList;
|
||||
import funkin.ui.MenuList;
|
||||
import funkin.ui.OptionsState;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.Prompt;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -31,7 +31,7 @@ import Discord.DiscordClient;
|
|||
#end
|
||||
#if newgrounds
|
||||
import io.newgrounds.NG;
|
||||
import ui.NgPrompt;
|
||||
import funkin.ui.NgPrompt;
|
||||
#end
|
||||
|
||||
class MainMenuState extends MusicBeatState
|
|
@ -1,3 +1,5 @@
|
|||
package funkin;
|
||||
|
||||
import openfl.text.TextFormat;
|
||||
import openfl.system.System;
|
||||
import openfl.text.TextField;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Conductor.BPMChangeEvent;
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import flixel.FlxGame;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.ui.FlxUIState;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Conductor.BPMChangeEvent;
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import flixel.FlxSubState;
|
||||
|
||||
class MusicBeatSubstate extends FlxSubState
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
#if newgrounds
|
||||
import flixel.util.FlxSignal;
|
|
@ -1,19 +1,13 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import shaderslmfao.ColorSwap;
|
||||
import ui.PreferencesMenu;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if polymod
|
||||
import polymod.format.ParseRules.TargetSignatureElement;
|
||||
#end
|
||||
|
||||
class Note extends FlxSprite
|
||||
{
|
||||
public var data = new NoteData();
|
||||
|
@ -43,29 +37,41 @@ class Note extends FlxSprite
|
|||
public var isSustainNote:Bool = false;
|
||||
|
||||
public var colorSwap:ColorSwap;
|
||||
|
||||
|
||||
/** the lowercase name of the note, for anim control, i.e. left right up down */
|
||||
public var dirName(get, never):String;
|
||||
inline function get_dirName() return data.dirName;
|
||||
|
||||
|
||||
inline function get_dirName()
|
||||
return data.dirName;
|
||||
|
||||
/** the uppercase name of the note, for anim control, i.e. left right up down */
|
||||
public var dirNameUpper(get, never):String;
|
||||
inline function get_dirNameUpper() return data.dirNameUpper;
|
||||
|
||||
|
||||
inline function get_dirNameUpper()
|
||||
return data.dirNameUpper;
|
||||
|
||||
/** the lowercase name of the note's color, for anim control, i.e. purple blue green red */
|
||||
public var colorName(get, never):String;
|
||||
inline function get_colorName() return data.colorName;
|
||||
|
||||
|
||||
inline function get_colorName()
|
||||
return data.colorName;
|
||||
|
||||
/** the lowercase name of the note's color, for anim control, i.e. purple blue green red */
|
||||
public var colorNameUpper(get, never):String;
|
||||
inline function get_colorNameUpper() return data.colorNameUpper;
|
||||
|
||||
|
||||
inline function get_colorNameUpper()
|
||||
return data.colorNameUpper;
|
||||
|
||||
public var highStakes(get, never):Bool;
|
||||
inline function get_highStakes() return data.highStakes;
|
||||
|
||||
|
||||
inline function get_highStakes()
|
||||
return data.highStakes;
|
||||
|
||||
public var lowStakes(get, never):Bool;
|
||||
inline function get_lowStakes() return data.lowStakes;
|
||||
|
||||
|
||||
inline function get_lowStakes()
|
||||
return data.lowStakes;
|
||||
|
||||
public static var swagWidth:Float = 160 * 0.7;
|
||||
public static var PURP_NOTE:Int = 0;
|
||||
public static var GREEN_NOTE:Int = 2;
|
||||
|
@ -103,8 +109,9 @@ class Note extends FlxSprite
|
|||
|
||||
data.noteData = noteData;
|
||||
|
||||
var daStage:String = PlayState.curStage;
|
||||
var daStage:String = PlayState.curStageId;
|
||||
|
||||
// TODO: Make this logic more generic
|
||||
switch (daStage)
|
||||
{
|
||||
case 'school' | 'schoolEvil':
|
||||
|
@ -187,7 +194,7 @@ class Note extends FlxSprite
|
|||
|
||||
x -= width / 2;
|
||||
|
||||
if (PlayState.curStage.startsWith('school'))
|
||||
if (PlayState.curStageId.startsWith('school'))
|
||||
x += 30;
|
||||
|
||||
if (prevNote.isSustainNote)
|
||||
|
@ -263,7 +270,7 @@ class Note extends FlxSprite
|
|||
alpha = 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static public function fromData(data:NoteData, prevNote:Note, isSustainNote = false)
|
||||
{
|
||||
return new Note(data.strumTime, data.noteData, prevNote, isSustainNote);
|
||||
|
@ -276,101 +283,133 @@ typedef RawNoteData =
|
|||
var noteData:NoteType;
|
||||
var sustainLength:Float;
|
||||
var altNote:Bool;
|
||||
var noteKind:NoteKind;
|
||||
}
|
||||
|
||||
@:forward
|
||||
abstract NoteData(RawNoteData)
|
||||
{
|
||||
public function new (strumTime = 0.0, noteData:NoteType = 0, sustainLength = 0.0, altNote = false)
|
||||
public function new(strumTime = 0.0, noteData:NoteType = 0, sustainLength = 0.0, altNote = false, noteKind = NORMAL)
|
||||
{
|
||||
this =
|
||||
{ strumTime : strumTime
|
||||
, noteData : noteData
|
||||
, sustainLength : sustainLength
|
||||
, altNote : altNote
|
||||
this = {
|
||||
strumTime: strumTime,
|
||||
noteData: noteData,
|
||||
sustainLength: sustainLength,
|
||||
altNote: altNote,
|
||||
noteKind: noteKind
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var note(get, never):NoteType;
|
||||
inline function get_note() return this.noteData.value;
|
||||
|
||||
|
||||
inline function get_note()
|
||||
return this.noteData.value;
|
||||
|
||||
public var int(get, never):Int;
|
||||
inline function get_int() return this.noteData.int;
|
||||
|
||||
|
||||
inline function get_int()
|
||||
return this.noteData.int;
|
||||
|
||||
public var dir(get, never):NoteDir;
|
||||
inline function get_dir() return this.noteData.value;
|
||||
|
||||
|
||||
inline function get_dir()
|
||||
return this.noteData.value;
|
||||
|
||||
public var dirName(get, never):String;
|
||||
inline function get_dirName() return dir.name;
|
||||
|
||||
|
||||
inline function get_dirName()
|
||||
return dir.name;
|
||||
|
||||
public var dirNameUpper(get, never):String;
|
||||
inline function get_dirNameUpper() return dir.nameUpper;
|
||||
|
||||
|
||||
inline function get_dirNameUpper()
|
||||
return dir.nameUpper;
|
||||
|
||||
public var color(get, never):NoteColor;
|
||||
inline function get_color() return this.noteData.value;
|
||||
|
||||
|
||||
inline function get_color()
|
||||
return this.noteData.value;
|
||||
|
||||
public var colorName(get, never):String;
|
||||
inline function get_colorName() return color.name;
|
||||
|
||||
|
||||
inline function get_colorName()
|
||||
return color.name;
|
||||
|
||||
public var colorNameUpper(get, never):String;
|
||||
inline function get_colorNameUpper() return color.nameUpper;
|
||||
|
||||
|
||||
inline function get_colorNameUpper()
|
||||
return color.nameUpper;
|
||||
|
||||
public var highStakes(get, never):Bool;
|
||||
inline function get_highStakes() return this.noteData.highStakes;
|
||||
|
||||
|
||||
inline function get_highStakes()
|
||||
return this.noteData.highStakes;
|
||||
|
||||
public var lowStakes(get, never):Bool;
|
||||
inline function get_lowStakes() return this.noteData.lowStakes;
|
||||
|
||||
inline function get_lowStakes()
|
||||
return this.noteData.lowStakes;
|
||||
}
|
||||
|
||||
enum abstract NoteType(Int) from Int to Int
|
||||
{
|
||||
// public var raw(get, never):Int;
|
||||
// inline function get_raw() return this;
|
||||
|
||||
public var int(get, never):Int;
|
||||
inline function get_int() return this < 0 ? -this : this % 4;
|
||||
|
||||
|
||||
inline function get_int()
|
||||
return this < 0 ? -this : this % 4;
|
||||
|
||||
public var value(get, never):NoteType;
|
||||
inline function get_value() return int;
|
||||
|
||||
|
||||
inline function get_value()
|
||||
return int;
|
||||
|
||||
public var highStakes(get, never):Bool;
|
||||
inline function get_highStakes() return this > 3;
|
||||
|
||||
|
||||
inline function get_highStakes()
|
||||
return this > 3;
|
||||
|
||||
public var lowStakes(get, never):Bool;
|
||||
inline function get_lowStakes() return this < 0;
|
||||
|
||||
inline function get_lowStakes()
|
||||
return this < 0;
|
||||
}
|
||||
|
||||
@:forward
|
||||
enum abstract NoteDir(NoteType) from Int to Int from NoteType
|
||||
{
|
||||
var LEFT = 0;
|
||||
var DOWN = 1;
|
||||
var UP = 2;
|
||||
var LEFT = 0;
|
||||
var DOWN = 1;
|
||||
var UP = 2;
|
||||
var RIGHT = 3;
|
||||
|
||||
var value(get, never):NoteDir;
|
||||
inline function get_value() return this.value;
|
||||
|
||||
|
||||
inline function get_value()
|
||||
return this.value;
|
||||
|
||||
public var name(get, never):String;
|
||||
|
||||
function get_name()
|
||||
{
|
||||
return switch(value)
|
||||
return switch (value)
|
||||
{
|
||||
case LEFT : "left" ;
|
||||
case DOWN : "down" ;
|
||||
case UP : "up" ;
|
||||
case LEFT: "left";
|
||||
case DOWN: "down";
|
||||
case UP: "up";
|
||||
case RIGHT: "right";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var nameUpper(get, never):String;
|
||||
|
||||
function get_nameUpper()
|
||||
{
|
||||
return switch(value)
|
||||
return switch (value)
|
||||
{
|
||||
case LEFT : "LEFT" ;
|
||||
case DOWN : "DOWN" ;
|
||||
case UP : "UP" ;
|
||||
case LEFT: "LEFT";
|
||||
case DOWN: "DOWN";
|
||||
case UP: "UP";
|
||||
case RIGHT: "RIGHT";
|
||||
}
|
||||
}
|
||||
|
@ -380,34 +419,47 @@ enum abstract NoteDir(NoteType) from Int to Int from NoteType
|
|||
enum abstract NoteColor(NoteType) from Int to Int from NoteType
|
||||
{
|
||||
var PURPLE = 0;
|
||||
var BLUE = 1;
|
||||
var GREEN = 2;
|
||||
var RED = 3;
|
||||
|
||||
var BLUE = 1;
|
||||
var GREEN = 2;
|
||||
var RED = 3;
|
||||
var value(get, never):NoteColor;
|
||||
inline function get_value() return this.value;
|
||||
|
||||
|
||||
inline function get_value()
|
||||
return this.value;
|
||||
|
||||
public var name(get, never):String;
|
||||
|
||||
function get_name()
|
||||
{
|
||||
return switch(value)
|
||||
return switch (value)
|
||||
{
|
||||
case PURPLE: "purple";
|
||||
case BLUE : "blue" ;
|
||||
case GREEN : "green" ;
|
||||
case RED : "red" ;
|
||||
case BLUE: "blue";
|
||||
case GREEN: "green";
|
||||
case RED: "red";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var nameUpper(get, never):String;
|
||||
|
||||
function get_nameUpper()
|
||||
{
|
||||
return switch(value)
|
||||
return switch (value)
|
||||
{
|
||||
case PURPLE: "PURPLE";
|
||||
case BLUE : "BLUE" ;
|
||||
case GREEN : "GREEN" ;
|
||||
case RED : "RED" ;
|
||||
case BLUE: "BLUE";
|
||||
case GREEN: "GREEN";
|
||||
case RED: "RED";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract NoteKind(String) from String to String
|
||||
{
|
||||
var NORMAL = "normal";
|
||||
var PYRO_LIGHT = "pyro_light";
|
||||
var PYRO_KICK = "pyro_kick";
|
||||
var PYRO_TOSS = "pyro_toss";
|
||||
var PYRO_COCK = "pyro_cock"; // lol
|
||||
var PYRO_SHOOT = "pyro_shoot";
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import haxe.io.Path;
|
||||
|
@ -34,7 +34,8 @@ class NoteSplash extends FlxSprite
|
|||
|
||||
animation.play('note' + noteData + '-' + FlxG.random.int(0, 1), true);
|
||||
animation.curAnim.frameRate = 24 + FlxG.random.int(-2, 2);
|
||||
animation.finishCallback = function(name){
|
||||
animation.finishCallback = function(name)
|
||||
{
|
||||
kill();
|
||||
};
|
||||
updateHitbox();
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
class Options
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
|
@ -6,6 +6,7 @@ import flixel.text.FlxText;
|
|||
import flixel.util.FlxColor;
|
||||
import lime.app.Application;
|
||||
|
||||
#if newgrounds
|
||||
class OutdatedSubState extends MusicBeatState
|
||||
{
|
||||
public static var leftState:Bool = false;
|
||||
|
@ -42,3 +43,4 @@ class OutdatedSubState extends MusicBeatState
|
|||
super.update(elapsed);
|
||||
}
|
||||
}
|
||||
#end
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.utils.AssetType;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Controls.Control;
|
||||
import funkin.Controls.Control;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
@ -11,6 +11,7 @@ import flixel.text.FlxText;
|
|||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
class PauseSubState extends MusicBeatSubstate
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Controls;
|
||||
import funkin.Controls;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.gamepad.FlxGamepad;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Note.NoteData;
|
||||
import funkin.Note.NoteData;
|
||||
|
||||
typedef SwagSection =
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import Note.NoteData;
|
||||
import Section.SwagSection;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.Section.SwagSection;
|
||||
import haxe.Json;
|
||||
import haxe.format.JsonParser;
|
||||
import lime.utils.Assets;
|
||||
|
@ -48,7 +48,7 @@ class SongLoad
|
|||
|
||||
public static function loadFromJson(jsonInput:String, ?folder:String):SwagSong
|
||||
{
|
||||
var rawJson = Assets.getText(Paths.json(folder.toLowerCase() + '/' + jsonInput.toLowerCase())).trim();
|
||||
var rawJson = Assets.getText(Paths.json('songs/${folder.toLowerCase()}/${jsonInput.toLowerCase()}')).trim();
|
||||
|
||||
while (!rawJson.endsWith("}"))
|
||||
{
|
||||
|
@ -174,6 +174,8 @@ class SongLoad
|
|||
|
||||
for (sectionIndex => section in noteStuff)
|
||||
{
|
||||
if (section == null || section.sectionNotes == null)
|
||||
continue;
|
||||
for (noteIndex => noteDataArray in section.sectionNotes)
|
||||
{
|
||||
var arrayDipshit:Array<Dynamic> = cast noteDataArray; // crackhead
|
||||
|
@ -188,6 +190,10 @@ class SongLoad
|
|||
noteStuff[sectionIndex].sectionNotes[noteIndex].noteData = arrayDipshit[1];
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].sustainLength = arrayDipshit[2];
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].altNote = arrayDipshit[3];
|
||||
if (arrayDipshit.length >= 5)
|
||||
{
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].noteKind = arrayDipshit[4];
|
||||
}
|
||||
}
|
||||
else if (noteDataArray != null)
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
#if discord_rpc
|
||||
import Discord.DiscordClient;
|
||||
|
@ -14,6 +14,7 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.net.curl.CURLCode;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -21,7 +22,7 @@ class StoryMenuState extends MusicBeatState
|
|||
{
|
||||
var scoreText:FlxText;
|
||||
|
||||
var weekData:Array<Dynamic> = [
|
||||
var weekData:Array<Array<String>> = [
|
||||
['Tutorial'],
|
||||
['Bopeebo', 'Fresh', 'Dadbattle'],
|
||||
['Spookeez', 'South', "Monster"],
|
||||
|
@ -430,11 +431,10 @@ class StoryMenuState extends MusicBeatState
|
|||
// grpWeekCharacters.members[0].updateHitbox();
|
||||
}
|
||||
|
||||
var stringThing:Array<String> = weekData[curWeek];
|
||||
|
||||
for (i in stringThing)
|
||||
var trackNames:Array<String> = weekData[curWeek];
|
||||
for (i in trackNames)
|
||||
{
|
||||
txtTracklist.text += "\n" + i;
|
||||
txtTracklist.text += '\n${i}';
|
||||
}
|
||||
|
||||
txtTracklist.text = txtTracklist.text.toUpperCase();
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.FlxSound;
|
|
@ -1,6 +1,6 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import audiovis.SpectogramSprite;
|
||||
import funkin.audiovis.SpectogramSprite;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
|
@ -29,10 +29,10 @@ import openfl.events.NetStatusEvent;
|
|||
import openfl.media.Video;
|
||||
import openfl.net.NetConnection;
|
||||
import openfl.net.NetStream;
|
||||
import shaderslmfao.BuildingShaders;
|
||||
import shaderslmfao.ColorSwap;
|
||||
import shaderslmfao.TitleOutline;
|
||||
import ui.PreferencesMenu;
|
||||
import funkin.shaderslmfao.BuildingShaders;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
import funkin.shaderslmfao.TitleOutline;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -351,17 +351,6 @@ class TitleState extends MusicBeatState
|
|||
FlxG.switchState(new CutsceneAnimTestState());
|
||||
#end
|
||||
|
||||
/*
|
||||
if (FlxG.keys.justPressed.R)
|
||||
{
|
||||
#if polymod
|
||||
polymod.Polymod.init({modRoot: "mods", dirs: ['introMod']});
|
||||
trace('reinitialized');
|
||||
#end
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
Conductor.songPosition = FlxG.sound.music.time;
|
||||
if (FlxG.keys.justPressed.F)
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin;
|
||||
|
||||
import openfl.display.Sprite;
|
||||
import openfl.events.AsyncErrorEvent;
|
|
@ -1,3 +1,5 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.system.FlxSound;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package animate;
|
||||
package funkin.animate;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
|
@ -1,7 +1,5 @@
|
|||
package animate;
|
||||
package funkin.animate;
|
||||
|
||||
// import animate.FlxSymbol.Parsed;
|
||||
// import animate.FlxSymbol.Timeline;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
|
@ -1,10 +1,8 @@
|
|||
package animate;
|
||||
package funkin.animate;
|
||||
|
||||
// import animateAtlasPlayer.assets.AssetManager;
|
||||
// import animateAtlasPlayer.core.Animation;
|
||||
import animate.ParseAnimate.AnimJson;
|
||||
import animate.ParseAnimate.Sprite;
|
||||
import animate.ParseAnimate.Spritemap;
|
||||
import funkin.animate.ParseAnimate.AnimJson;
|
||||
import funkin.animate.ParseAnimate.Sprite;
|
||||
import funkin.animate.ParseAnimate.Spritemap;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.FlxGraphic;
|
|
@ -1,12 +1,12 @@
|
|||
package animate;
|
||||
package funkin.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 funkin.animate.ParseAnimate.AnimJson;
|
||||
import funkin.animate.ParseAnimate.Animation;
|
||||
import funkin.animate.ParseAnimate.Frame;
|
||||
import funkin.animate.ParseAnimate.Sprite;
|
||||
import funkin.animate.ParseAnimate.Spritemap;
|
||||
import funkin.animate.ParseAnimate.SymbolDictionary;
|
||||
import funkin.animate.ParseAnimate.Timeline;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
|
|
@ -1,10 +1,9 @@
|
|||
package animate;
|
||||
package funkin.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
|
|
@ -1,6 +1,6 @@
|
|||
package animate;
|
||||
package funkin.animate;
|
||||
|
||||
import animate.FlxSymbol.Frame;
|
||||
import funkin.animate.ParseAnimate.Frame;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.input.mouse.FlxMouseEventManager;
|
||||
import flixel.util.FlxColor;
|
|
@ -1,13 +1,13 @@
|
|||
package audiovis;
|
||||
package funkin.audiovis;
|
||||
|
||||
import audiovis.dsp.FFT;
|
||||
import funkin.audiovis.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;
|
||||
import funkin.ui.PreferencesMenu.CheckboxThingie;
|
||||
|
||||
using Lambda;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package audiovis;
|
||||
package funkin.audiovis;
|
||||
|
||||
import audiovis.VisShit.CurAudioInfo;
|
||||
import funkin.audiovis.VisShit.CurAudioInfo;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.util.FlxColor;
|
||||
import lime.utils.Int16Array;
|
||||
import rendering.MeshRender;
|
||||
import funkin.rendering.MeshRender;
|
||||
|
||||
class PolygonSpectogram extends MeshRender
|
||||
{
|
|
@ -1,8 +1,8 @@
|
|||
package audiovis;
|
||||
package funkin.audiovis;
|
||||
|
||||
import audiovis.PolygonSpectogram.VISTYPE;
|
||||
import audiovis.VisShit.CurAudioInfo;
|
||||
import audiovis.dsp.FFT;
|
||||
import funkin.audiovis.PolygonSpectogram.VISTYPE;
|
||||
import funkin.audiovis.VisShit.CurAudioInfo;
|
||||
import funkin.audiovis.dsp.FFT;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|
@ -1,6 +1,6 @@
|
|||
package audiovis;
|
||||
package funkin.audiovis;
|
||||
|
||||
import audiovis.dsp.FFT;
|
||||
import funkin.audiovis.dsp.FFT;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.system.FlxSound;
|
||||
import lime.utils.Int16Array;
|
|
@ -1,4 +1,4 @@
|
|||
package audiovis.dsp;
|
||||
package funkin.audiovis.dsp;
|
||||
|
||||
/**
|
||||
Complex number representation.
|
|
@ -1,9 +1,9 @@
|
|||
package audiovis.dsp;
|
||||
package funkin.audiovis.dsp;
|
||||
|
||||
import audiovis.dsp.Complex;
|
||||
import funkin.audiovis.dsp.Complex;
|
||||
|
||||
using audiovis.dsp.OffsetArray;
|
||||
using audiovis.dsp.Signal;
|
||||
using funkin.audiovis.dsp.OffsetArray;
|
||||
using funkin.audiovis.dsp.Signal;
|
||||
|
||||
// these are only used for testing, down in FFT.main()
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package audiovis.dsp;
|
||||
package funkin.audiovis.dsp;
|
||||
|
||||
/**
|
||||
A view into an Array with an indexing offset.
|
|
@ -1,4 +1,4 @@
|
|||
package audiovis.dsp;
|
||||
package funkin.audiovis.dsp;
|
||||
|
||||
using Lambda;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package charting;
|
||||
package funkin.charting;
|
||||
|
||||
import Conductor.BPMChangeEvent;
|
||||
import Note.NoteData;
|
||||
import Section.SwagSection;
|
||||
import SongLoad.SwagSong;
|
||||
import audiovis.ABotVis;
|
||||
import audiovis.PolygonSpectogram;
|
||||
import audiovis.SpectogramSprite;
|
||||
import funkin.Conductor.BPMChangeEvent;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.Section.SwagSection;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.audiovis.ABotVis;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
import funkin.audiovis.SpectogramSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
@ -29,7 +29,8 @@ import lime.utils.Assets;
|
|||
import openfl.events.Event;
|
||||
import openfl.events.IOErrorEvent;
|
||||
import openfl.net.FileReference;
|
||||
import rendering.MeshRender;
|
||||
import funkin.rendering.MeshRender;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
|
@ -1,4 +1,4 @@
|
|||
package freeplayStuff;
|
||||
package funkin.freeplayStuff;
|
||||
|
||||
import flixel.FlxObject;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
|
@ -1,4 +1,4 @@
|
|||
package freeplayStuff;
|
||||
package funkin.freeplayStuff;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxSignal;
|
|
@ -1,4 +1,4 @@
|
|||
package freeplayStuff;
|
||||
package funkin.freeplayStuff;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
|
@ -11,6 +11,8 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
|
|||
|
||||
function set_scoreShit(val):Int
|
||||
{
|
||||
if (group == null || group.members == null)
|
||||
return val;
|
||||
var loopNum:Int = group.members.length - 1;
|
||||
var dumbNumb = Std.parseInt(Std.string(val));
|
||||
var prevNum:ScoreNum;
|
|
@ -1,4 +1,4 @@
|
|||
package freeplayStuff;
|
||||
package funkin.freeplayStuff;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
|
@ -1,4 +1,4 @@
|
|||
package i18n;
|
||||
package funkin.i18n;
|
||||
|
||||
import firetongue.FireTongue;
|
||||
|
3
source/funkin/i18n/README.md
Normal file
3
source/funkin/i18n/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# i18n
|
||||
|
||||
This package contains functions used for internationalization (i18n).
|
|
@ -1,6 +1,6 @@
|
|||
#if !macro
|
||||
// Only import these when we aren't in a macro.
|
||||
import Paths;
|
||||
import funkin.Paths;
|
||||
import flixel.FlxG; // This one in particular causes a compile error if you're using macros.
|
||||
|
||||
#end
|
|
@ -1,13 +1,13 @@
|
|||
package modding;
|
||||
|
||||
import polymod.hscript.HScriptable;
|
||||
|
||||
/**
|
||||
* Add this interface to a class to make it a scriptable object.
|
||||
* Functions annotated with @:hscript will call the relevant script.
|
||||
*/
|
||||
@:hscript({
|
||||
// ALL of these values are added to ALL scripts in the child classes.
|
||||
context: [FlxG, FlxSprite, Math, Paths, Std]
|
||||
})
|
||||
interface IHook extends HScriptable {}
|
||||
package funkin.modding;
|
||||
|
||||
import polymod.hscript.HScriptable;
|
||||
|
||||
/**
|
||||
* Add this interface to a class to make it a scriptable object.
|
||||
* Functions annotated with @:hscript will call the relevant script.
|
||||
*/
|
||||
@:hscript({
|
||||
// ALL of these values are added to ALL scripts in the child classes.
|
||||
context: [FlxG, FlxSprite, Math, Paths, Std]
|
||||
})
|
||||
interface IHook extends HScriptable {}
|
71
source/funkin/modding/PolymodErrorHandler.hx
Normal file
71
source/funkin/modding/PolymodErrorHandler.hx
Normal file
|
@ -0,0 +1,71 @@
|
|||
package funkin.modding;
|
||||
|
||||
import polymod.Polymod;
|
||||
|
||||
class PolymodErrorHandler
|
||||
{
|
||||
/**
|
||||
* Show a popup with the given text.
|
||||
* This displays a system popup, it WILL interrupt the game.
|
||||
* Make sure to only use this when it's important, like when there's a script error.
|
||||
*
|
||||
* @param name The name at the top of the popup.
|
||||
* @param desc The body text of the popup.
|
||||
*/
|
||||
public static function showAlert(name:String, desc:String):Void
|
||||
{
|
||||
lime.app.Application.current.window.alert(desc, name);
|
||||
}
|
||||
|
||||
public static function onPolymodError(error:PolymodError):Void
|
||||
{
|
||||
// Perform an action based on the error code.
|
||||
switch (error.code)
|
||||
{
|
||||
case MOD_LOAD_PREPARE:
|
||||
logInfo('[POLYMOD]: ${error.message}');
|
||||
case MOD_LOAD_DONE:
|
||||
logInfo('[POLYMOD]: ${error.message}');
|
||||
case MISSING_ICON:
|
||||
logWarn('[POLYMOD]: A mod is missing an icon. Please add one.');
|
||||
case SCRIPT_PARSE_ERROR:
|
||||
// A syntax error when parsing a script.
|
||||
logError('[POLYMOD]: ${error.message}');
|
||||
showAlert('Polymod Script Parsing Error', error.message);
|
||||
case SCRIPT_EXCEPTION:
|
||||
// A runtime error when running a script.
|
||||
logError('[POLYMOD]: ${error.message}');
|
||||
showAlert('Polymod Script Execution Error', error.message);
|
||||
case SCRIPT_CLASS_NOT_FOUND:
|
||||
// A scripted class tried to reference an unknown superclass.
|
||||
logError('[POLYMOD]: ${error.message}');
|
||||
showAlert('Polymod Script Parsing Error', error.message);
|
||||
default:
|
||||
// Log the message based on its severity.
|
||||
switch (error.severity)
|
||||
{
|
||||
case NOTICE:
|
||||
logInfo('[POLYMOD]: ${error.message}');
|
||||
case WARNING:
|
||||
logWarn('[POLYMOD]: ${error.message}');
|
||||
case ERROR:
|
||||
logError('[POLYMOD]: ${error.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function logInfo(message:String):Void
|
||||
{
|
||||
trace('[INFO ] ${message}');
|
||||
}
|
||||
|
||||
static function logError(message:String):Void
|
||||
{
|
||||
trace('[ERROR] ${message}');
|
||||
}
|
||||
|
||||
static function logWarn(message:String):Void
|
||||
{
|
||||
trace('[WARN ] ${message}');
|
||||
}
|
||||
}
|
|
@ -1,202 +1,168 @@
|
|||
package modding;
|
||||
|
||||
#if polymod
|
||||
import polymod.Polymod.ModMetadata;
|
||||
import polymod.Polymod;
|
||||
import polymod.backends.OpenFLBackend;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.LinesParseFormat;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
#end
|
||||
|
||||
class PolymodHandler
|
||||
{
|
||||
/**
|
||||
* The API version that mods should comply with.
|
||||
* Format this with Semantic Versioning; <MAJOR>.<MINOR>.<PATCH>.
|
||||
* Bug fixes increment the patch version, new features increment the minor version.
|
||||
* Changes that break old mods increment the major version.
|
||||
*/
|
||||
static final API_VERSION = "0.1.0";
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
*/
|
||||
static final MOD_FOLDER = "mods";
|
||||
|
||||
/**
|
||||
* Loads the game with ALL mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadAllMods()
|
||||
{
|
||||
#if polymod
|
||||
trace("Initializing Polymod (using all mods)...");
|
||||
loadModsById(getAllModIds());
|
||||
#else
|
||||
trace("Polymod not initialized; not supported on this platform.");
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the game without any mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadNoMods()
|
||||
{
|
||||
// We still need to configure the debug print calls etc.
|
||||
#if polymod
|
||||
trace("Initializing Polymod (using no mods)...");
|
||||
loadModsById([]);
|
||||
#else
|
||||
trace("Polymod not initialized; not supported on this platform.");
|
||||
#end
|
||||
}
|
||||
|
||||
public static function loadModsById(ids:Array<String>)
|
||||
{
|
||||
#if polymod
|
||||
if (ids.length == 0)
|
||||
{
|
||||
trace('You attempted to load zero mods.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Attempting to load ${ids.length} mods...');
|
||||
}
|
||||
var loadedModList = polymod.Polymod.init({
|
||||
// Root directory for all mods.
|
||||
modRoot: MOD_FOLDER,
|
||||
// The directories for one or more mods to load.
|
||||
dirs: ids,
|
||||
// Framework being used to load assets.
|
||||
framework: OPENFL,
|
||||
// The current version of our API.
|
||||
apiVersion: API_VERSION,
|
||||
// Call this function any time an error occurs.
|
||||
errorCallback: onPolymodError,
|
||||
// Enforce semantic version patterns for each mod.
|
||||
// modVersions: null,
|
||||
// A map telling Polymod what the asset type is for unfamiliar file extensions.
|
||||
// extensionMap: [],
|
||||
|
||||
frameworkParams: buildFrameworkParams(),
|
||||
|
||||
// List of filenames to ignore in mods. Use the default list to ignore the metadata file, etc.
|
||||
ignoredFiles: Polymod.getDefaultIgnoreList(),
|
||||
|
||||
// Parsing rules for various data formats.
|
||||
parseRules: buildParseRules(),
|
||||
});
|
||||
|
||||
if (loadedModList == null)
|
||||
{
|
||||
trace('[POLYMOD] An error occurred! Failed when loading mods!');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loadedModList.length == 0)
|
||||
{
|
||||
trace('[POLYMOD] Mod loading complete. We loaded no mods / ${ids.length} mods.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[POLYMOD] Mod loading complete. We loaded ${loadedModList.length} / ${ids.length} mods.');
|
||||
}
|
||||
}
|
||||
|
||||
for (mod in loadedModList)
|
||||
trace(' * ${mod.title} v${mod.modVersion} [${mod.id}]');
|
||||
|
||||
#if debug
|
||||
var fileList = Polymod.listModFiles("IMAGE");
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} images.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
|
||||
fileList = Polymod.listModFiles("TEXT");
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} text files.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
|
||||
fileList = Polymod.listModFiles("MUSIC");
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} music files.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
|
||||
fileList = Polymod.listModFiles("SOUND");
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} sound files.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
#end
|
||||
#else
|
||||
trace("[POLYMOD] Mods are not supported on this platform.");
|
||||
#end
|
||||
}
|
||||
|
||||
#if polymod
|
||||
static function buildParseRules():polymod.format.ParseRules
|
||||
{
|
||||
var output = polymod.format.ParseRules.getDefault();
|
||||
// Ensure TXT files have merge support.
|
||||
output.addType("txt", TextFileFormat.LINES);
|
||||
// Ensure script files have merge support.
|
||||
output.addType("hscript", TextFileFormat.PLAINTEXT);
|
||||
|
||||
// You can specify the format of a specific file, with file extension.
|
||||
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
||||
return output;
|
||||
}
|
||||
|
||||
static inline function buildFrameworkParams():polymod.Polymod.FrameworkParams
|
||||
{
|
||||
return {
|
||||
assetLibraryPaths: [
|
||||
"songs" => "./songs", "shared" => "./", "tutorial" => "./tutorial", "scripts" => "./scripts", "week1" => "./week1", "week2" => "./week2",
|
||||
"week3" => "./week3", "week4" => "./week4", "week5" => "./week5", "week6" => "./week6", "week7" => "./week7", "week8" => "./week8",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
static function onPolymodError(error:PolymodError):Void
|
||||
{
|
||||
// Perform an action based on the error code.
|
||||
switch (error.code)
|
||||
{
|
||||
case MOD_LOAD_PREPARE:
|
||||
trace('[POLYMOD] ${error.message}');
|
||||
case MOD_LOAD_DONE:
|
||||
trace('[POLYMOD] ${error.message}');
|
||||
case MISSING_ICON:
|
||||
trace('[POLYMOD] A mod is missing an icon. Please add one.');
|
||||
default:
|
||||
// Log the message based on its severity.
|
||||
switch (error.severity)
|
||||
{
|
||||
case NOTICE:
|
||||
trace('[POLYMOD] ${error.message}');
|
||||
case WARNING:
|
||||
trace('[POLYMOD] ${error.message}');
|
||||
case ERROR:
|
||||
trace('[POLYMOD] ${error.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
public static function getAllMods()
|
||||
{
|
||||
#if polymod
|
||||
trace('Scanning the mods folder...');
|
||||
var modMetadata = Polymod.scan(MOD_FOLDER);
|
||||
trace('Found ${modMetadata.length} mods when scanning.');
|
||||
return modMetadata;
|
||||
#else
|
||||
return new Array<Dynamic>();
|
||||
#end
|
||||
}
|
||||
|
||||
public static function getAllModIds():Array<String>
|
||||
{
|
||||
var modIds = [for (i in getAllMods()) i.id];
|
||||
return modIds;
|
||||
}
|
||||
}
|
||||
package funkin.modding;
|
||||
|
||||
import polymod.Polymod.ModMetadata;
|
||||
import polymod.Polymod;
|
||||
import polymod.backends.OpenFLBackend;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.LinesParseFormat;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
|
||||
class PolymodHandler
|
||||
{
|
||||
/**
|
||||
* The API version that mods should comply with.
|
||||
* Format this with Semantic Versioning; <MAJOR>.<MINOR>.<PATCH>.
|
||||
* Bug fixes increment the patch version, new features increment the minor version.
|
||||
* Changes that break old mods increment the major version.
|
||||
*/
|
||||
static final API_VERSION = "0.1.0";
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
*/
|
||||
static final MOD_FOLDER = "mods";
|
||||
|
||||
/**
|
||||
* Loads the game with ALL mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadAllMods()
|
||||
{
|
||||
trace("Initializing Polymod (using all mods)...");
|
||||
loadModsById(getAllModIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the game without any mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadNoMods()
|
||||
{
|
||||
// We still need to configure the debug print calls etc.
|
||||
trace("Initializing Polymod (using no mods)...");
|
||||
loadModsById([]);
|
||||
}
|
||||
|
||||
public static function loadModsById(ids:Array<String>)
|
||||
{
|
||||
if (ids.length == 0)
|
||||
{
|
||||
trace('You attempted to load zero mods.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Attempting to load ${ids.length} mods...');
|
||||
}
|
||||
var loadedModList = polymod.Polymod.init({
|
||||
// Root directory for all mods.
|
||||
modRoot: MOD_FOLDER,
|
||||
// The directories for one or more mods to load.
|
||||
dirs: ids,
|
||||
// Framework being used to load assets.
|
||||
framework: OPENFL,
|
||||
// The current version of our API.
|
||||
apiVersion: API_VERSION,
|
||||
// Call this function any time an error occurs.
|
||||
errorCallback: PolymodErrorHandler.onPolymodError,
|
||||
// Enforce semantic version patterns for each mod.
|
||||
// modVersions: null,
|
||||
// A map telling Polymod what the asset type is for unfamiliar file extensions.
|
||||
// extensionMap: [],
|
||||
|
||||
frameworkParams: buildFrameworkParams(),
|
||||
|
||||
// List of filenames to ignore in mods. Use the default list to ignore the metadata file, etc.
|
||||
ignoredFiles: Polymod.getDefaultIgnoreList(),
|
||||
|
||||
// Parsing rules for various data formats.
|
||||
parseRules: buildParseRules(),
|
||||
|
||||
// Parse hxc files and register the scripted classes in them.
|
||||
useScriptedClasses: true,
|
||||
});
|
||||
|
||||
if (loadedModList == null)
|
||||
{
|
||||
trace('[POLYMOD] An error occurred! Failed when loading mods!');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loadedModList.length == 0)
|
||||
{
|
||||
trace('[POLYMOD] Mod loading complete. We loaded no mods / ${ids.length} mods.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[POLYMOD] Mod loading complete. We loaded ${loadedModList.length} / ${ids.length} mods.');
|
||||
}
|
||||
}
|
||||
|
||||
for (mod in loadedModList)
|
||||
{
|
||||
trace(' * ${mod.title} v${mod.modVersion} [${mod.id}]');
|
||||
}
|
||||
|
||||
#if debug
|
||||
var fileList = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} images.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.TEXT);
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} text files.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_MUSIC);
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} music files.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_SOUND);
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} sound files.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_GENERIC);
|
||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} generic audio files.');
|
||||
for (item in fileList)
|
||||
trace(' * $item');
|
||||
#end
|
||||
}
|
||||
|
||||
static function buildParseRules():polymod.format.ParseRules
|
||||
{
|
||||
var output = polymod.format.ParseRules.getDefault();
|
||||
// Ensure TXT files have merge support.
|
||||
output.addType("txt", TextFileFormat.LINES);
|
||||
// Ensure script files have merge support.
|
||||
output.addType("hscript", TextFileFormat.PLAINTEXT);
|
||||
output.addType("hxs", TextFileFormat.PLAINTEXT);
|
||||
|
||||
// You can specify the format of a specific file, with file extension.
|
||||
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
||||
return output;
|
||||
}
|
||||
|
||||
static inline function buildFrameworkParams():polymod.Polymod.FrameworkParams
|
||||
{
|
||||
return {
|
||||
assetLibraryPaths: [
|
||||
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public static function getAllMods():Array<ModMetadata>
|
||||
{
|
||||
trace('Scanning the mods folder...');
|
||||
var modMetadata = Polymod.scan(MOD_FOLDER);
|
||||
trace('Found ${modMetadata.length} mods when scanning.');
|
||||
return modMetadata;
|
||||
}
|
||||
|
||||
public static function getAllModIds():Array<String>
|
||||
{
|
||||
var modIds = [for (i in getAllMods()) i.id];
|
||||
return modIds;
|
||||
}
|
||||
}
|
5
source/funkin/modding/base/README.md
Normal file
5
source/funkin/modding/base/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# modding.base
|
||||
|
||||
This package is used to allow modders to create scripted classes which extend these base classes.
|
||||
For example, one script can extend FlxSprite and another can call `ScriptedFlxSprite.init('ClassName')`.
|
||||
Most of these scripted class stubs are not used by the game itself, so this package has been explicitly marked to be ignored by DCE.
|
10
source/funkin/modding/base/ScriptedFlxSprite.hx
Normal file
10
source/funkin/modding/base/ScriptedFlxSprite.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.modding.base;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedFlxSprite extends FlxSprite implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
10
source/funkin/modding/base/ScriptedFlxSpriteGroup.hx
Normal file
10
source/funkin/modding/base/ScriptedFlxSpriteGroup.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.modding.base;
|
||||
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package play;
|
||||
package funkin.play;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package play;
|
||||
package funkin.play;
|
||||
|
||||
import Note.NoteData;
|
||||
import audiovis.PolygonSpectogram;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.effects.FlxTrail;
|
File diff suppressed because it is too large
Load diff
9
source/funkin/play/character/Character.hx
Normal file
9
source/funkin/play/character/Character.hx
Normal file
|
@ -0,0 +1,9 @@
|
|||
package funkin.play.character;
|
||||
|
||||
enum CharacterType
|
||||
{
|
||||
BF;
|
||||
GF;
|
||||
DAD;
|
||||
OTHER;
|
||||
}
|
119
source/funkin/play/stage/Bopper.hx
Normal file
119
source/funkin/play/stage/Bopper.hx
Normal file
|
@ -0,0 +1,119 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
|
||||
/**
|
||||
* A Bopper is a stage prop which plays a dance animation.
|
||||
* Y'know, a thingie that bops. A bopper.
|
||||
*/
|
||||
class Bopper extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* The bopper plays the dance animation once every `danceEvery` beats.
|
||||
*/
|
||||
public var danceEvery:Int = 1;
|
||||
|
||||
/**
|
||||
* Whether the bopper should dance left and right.
|
||||
* - If true, alternate playing `danceLeft` and `danceRight`.
|
||||
* - If false, play `idle` every time.
|
||||
*
|
||||
* You can manually set this value, or you can leave it as `null` to determine it automatically.
|
||||
*/
|
||||
public var shouldAlternate:Null<Bool> = null;
|
||||
|
||||
/**
|
||||
* Set this value to define an additional horizontal offset to this sprite's position.
|
||||
*/
|
||||
public var xOffset:Float = 0;
|
||||
|
||||
override function set_x(value:Float):Float
|
||||
{
|
||||
this.x = value + this.xOffset;
|
||||
return value;
|
||||
}
|
||||
|
||||
public var idleSuffix(default, set):String = "";
|
||||
|
||||
function set_idleSuffix(value:String):String
|
||||
{
|
||||
this.idleSuffix = value;
|
||||
this.dance();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this value to define an additional vertical offset to this sprite's position.
|
||||
*/
|
||||
public var yOffset:Float = 0;
|
||||
|
||||
override function set_y(value:Float):Float
|
||||
{
|
||||
this.y = value + this.yOffset;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to play `danceRight` next iteration.
|
||||
* Only used when `shouldAlternate` is true.
|
||||
*/
|
||||
var hasDanced:Bool = false;
|
||||
|
||||
public function new(danceEvery:Int = 1)
|
||||
{
|
||||
super();
|
||||
this.danceEvery = danceEvery;
|
||||
}
|
||||
|
||||
function update_shouldAlternate():Void
|
||||
{
|
||||
if (this.animation.getByName('danceLeft') != null)
|
||||
{
|
||||
this.shouldAlternate = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once every beat of the song.
|
||||
*/
|
||||
public function onBeatHit(curBeat:Int):Void
|
||||
{
|
||||
if (curBeat % danceEvery == 0)
|
||||
{
|
||||
dance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every `danceEvery` beats of the song.
|
||||
*/
|
||||
public function dance():Void
|
||||
{
|
||||
if (this.animation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldAlternate == null)
|
||||
{
|
||||
update_shouldAlternate();
|
||||
}
|
||||
|
||||
if (shouldAlternate)
|
||||
{
|
||||
if (hasDanced)
|
||||
{
|
||||
this.animation.play('danceRight$idleSuffix');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.animation.play('danceLeft$idleSuffix');
|
||||
}
|
||||
hasDanced = !hasDanced;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.animation.play('idle$idleSuffix');
|
||||
}
|
||||
}
|
||||
}
|
10
source/funkin/play/stage/ScriptedBopper.hx
Normal file
10
source/funkin/play/stage/ScriptedBopper.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
@:keep
|
||||
class ScriptedBopper extends Bopper implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
9
source/funkin/play/stage/ScriptedStage.hx
Normal file
9
source/funkin/play/stage/ScriptedStage.hx
Normal file
|
@ -0,0 +1,9 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedStage extends Stage implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
402
source/funkin/play/stage/Stage.hx
Normal file
402
source/funkin/play/stage/Stage.hx
Normal file
|
@ -0,0 +1,402 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.modding.IHook;
|
||||
import funkin.play.character.Character.CharacterType;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.util.SortUtil;
|
||||
|
||||
/**
|
||||
* A Stage is a group of objects rendered in the PlayState.
|
||||
*
|
||||
* A Stage is comprised of one or more props, each of which is a FlxSprite.
|
||||
*/
|
||||
class Stage extends FlxSpriteGroup implements IHook
|
||||
{
|
||||
public final stageId:String;
|
||||
public final stageName:String;
|
||||
|
||||
final _data:StageData;
|
||||
|
||||
public var camZoom:Float = 1.0;
|
||||
|
||||
var namedProps:Map<String, FlxSprite> = new Map<String, FlxSprite>();
|
||||
var characters:Map<String, Character> = new Map<String, Character>();
|
||||
var boppers:Array<Bopper> = new Array<Bopper>();
|
||||
|
||||
/**
|
||||
* The Stage elements get initialized at the beginning of the game.
|
||||
* They're used to cache the data needed to build the stage,
|
||||
* then accessed and fleshed out when the stage needs to be built.
|
||||
*
|
||||
* @param stageId
|
||||
*/
|
||||
public function new(stageId:String)
|
||||
{
|
||||
super();
|
||||
|
||||
this.stageId = stageId;
|
||||
_data = StageDataParser.parseStageData(this.stageId);
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not find stage data for stageId: $stageId';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.stageName = _data.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default stage construction routine. Called when the stage is going to be played in.
|
||||
* Instantiates each prop and adds it to the stage, while setting its parameters.
|
||||
*/
|
||||
public function buildStage()
|
||||
{
|
||||
trace('Building stage for display: ${this.stageId}');
|
||||
|
||||
this.camZoom = _data.cameraZoom;
|
||||
// this.scrollFactor = new FlxPoint(1, 1);
|
||||
|
||||
for (dataProp in _data.props)
|
||||
{
|
||||
trace(' Placing prop: ${dataProp.name} (${dataProp.assetPath})');
|
||||
|
||||
var isAnimated = dataProp.animations.length > 0;
|
||||
|
||||
var propSprite:FlxSprite;
|
||||
if (dataProp.danceEvery != 0)
|
||||
{
|
||||
propSprite = new Bopper(dataProp.danceEvery);
|
||||
}
|
||||
else
|
||||
{
|
||||
propSprite = new FlxSprite();
|
||||
}
|
||||
|
||||
if (isAnimated)
|
||||
{
|
||||
// Initalize sprite frames.
|
||||
switch (dataProp.animType)
|
||||
{
|
||||
case "packer":
|
||||
propSprite.frames = Paths.getPackerAtlas(dataProp.assetPath);
|
||||
default: // "sparrow"
|
||||
propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Initalize static sprite.
|
||||
propSprite.loadGraphic(Paths.image(dataProp.assetPath));
|
||||
|
||||
// Disables calls to update() for a performance boost.
|
||||
propSprite.active = false;
|
||||
}
|
||||
|
||||
if (propSprite.frames == null || propSprite.frames.numFrames == 0)
|
||||
{
|
||||
trace(' ERROR: Could not build texture for prop.');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Std.isOfType(dataProp.scale, Array))
|
||||
{
|
||||
propSprite.scale.set(dataProp.scale[0], dataProp.scale[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
propSprite.scale.set(dataProp.scale);
|
||||
}
|
||||
propSprite.updateHitbox();
|
||||
|
||||
propSprite.x = dataProp.position[0];
|
||||
propSprite.y = dataProp.position[1];
|
||||
|
||||
// If pixel, disable antialiasing.
|
||||
propSprite.antialiasing = !dataProp.isPixel;
|
||||
|
||||
propSprite.scrollFactor.x = dataProp.scroll[0];
|
||||
propSprite.scrollFactor.y = dataProp.scroll[1];
|
||||
|
||||
propSprite.zIndex = dataProp.zIndex;
|
||||
|
||||
switch (dataProp.animType)
|
||||
{
|
||||
case "packer":
|
||||
for (propAnim in dataProp.animations)
|
||||
{
|
||||
propSprite.animation.add(propAnim.name, propAnim.frameIndices);
|
||||
}
|
||||
default: // "sparrow"
|
||||
for (propAnim in dataProp.animations)
|
||||
{
|
||||
if (propAnim.frameIndices.length == 0)
|
||||
{
|
||||
propSprite.animation.addByPrefix(propAnim.name, propAnim.prefix, propAnim.frameRate, propAnim.loop, propAnim.flipX,
|
||||
propAnim.flipY);
|
||||
}
|
||||
else
|
||||
{
|
||||
propSprite.animation.addByIndices(propAnim.name, propAnim.prefix, propAnim.frameIndices, "", propAnim.frameRate, propAnim.loop,
|
||||
propAnim.flipX, propAnim.flipY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dataProp.startingAnimation != null)
|
||||
{
|
||||
propSprite.animation.play(dataProp.startingAnimation);
|
||||
}
|
||||
|
||||
if (Std.isOfType(propSprite, Bopper))
|
||||
{
|
||||
addBopper(cast propSprite, dataProp.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
addProp(propSprite, dataProp.name);
|
||||
}
|
||||
trace(' Prop placed.');
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sprite to the stage.
|
||||
* @param prop The sprite to add.
|
||||
* @param name (Optional) A unique name for the sprite.
|
||||
* You can call `getNamedProp(name)` to retrieve it later.
|
||||
*/
|
||||
public function addProp(prop:FlxSprite, ?name:String = null)
|
||||
{
|
||||
if (name != null)
|
||||
{
|
||||
namedProps.set(name, prop);
|
||||
}
|
||||
this.add(prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sprite to the stage which animates to the beat of the song.
|
||||
*/
|
||||
public function addBopper(bopper:Bopper, ?name:String = null)
|
||||
{
|
||||
boppers.push(bopper);
|
||||
this.addProp(bopper, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the stage, by redoing the render order of all props.
|
||||
* It does this based on the `zIndex` of each prop.
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
trace('Stage sorted by z-index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the stage and it's props (needs to be overridden with your own logic!)
|
||||
*/
|
||||
public function resetStage()
|
||||
{
|
||||
// Override me in your script to reset stage shit however you please!
|
||||
// also note: maybe add some default behaviour to reset stage stuff?
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that should get called every frame.
|
||||
*/
|
||||
public function onUpdate(elapsed:Float):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called when the player hits a note.
|
||||
*/
|
||||
public function onNoteHit(note:Note):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called when the player hits a note.
|
||||
*/
|
||||
public function onNoteMiss(note:Note):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the position and other properties of the soon-to-be child of this sprite group.
|
||||
* Private helper to avoid duplicate code in `add()` and `insert()`.
|
||||
*
|
||||
* @param Sprite The sprite or sprite group that is about to be added or inserted into the group.
|
||||
*/
|
||||
override function preAdd(Sprite:FlxSprite):Void
|
||||
{
|
||||
var sprite:FlxSprite = cast Sprite;
|
||||
sprite.x += x;
|
||||
sprite.y += y;
|
||||
sprite.alpha *= alpha;
|
||||
// Don't override scroll factors.
|
||||
// sprite.scrollFactor.copyFrom(scrollFactor);
|
||||
sprite.cameras = _cameras; // _cameras instead of cameras because get_cameras() will not return null
|
||||
|
||||
if (clipRect != null)
|
||||
clipRectTransform(sprite, clipRect);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called once per step in the song.
|
||||
* @param curStep The current step number.
|
||||
*/
|
||||
public function onStepHit(curStep:Int):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that gets called once per beat in the song (once every four steps).
|
||||
* @param curStep The current beat number.
|
||||
*/
|
||||
public function onBeatHit(curBeat:Int):Void
|
||||
{
|
||||
// Override me in your scripted stage to perform custom behavior!
|
||||
// Make sure to call super.onBeatHit(curBeat) if you want to keep the boppers dancing.
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
bopper.onBeatHit(curBeat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the PlayState to add a character to the stage.
|
||||
*/
|
||||
public function addCharacter(character:Character, charType:CharacterType)
|
||||
{
|
||||
// Apply position and z-index.
|
||||
switch (charType)
|
||||
{
|
||||
case BF:
|
||||
this.characters.set("bf", character);
|
||||
character.zIndex = _data.characters.bf.zIndex;
|
||||
character.x = _data.characters.bf.position[0];
|
||||
character.y = _data.characters.bf.position[1];
|
||||
case GF:
|
||||
this.characters.set("gf", character);
|
||||
character.zIndex = _data.characters.gf.zIndex;
|
||||
character.x = _data.characters.gf.position[0];
|
||||
character.y = _data.characters.gf.position[1];
|
||||
case DAD:
|
||||
this.characters.set("dad", character);
|
||||
character.zIndex = _data.characters.dad.zIndex;
|
||||
character.x = _data.characters.dad.position[0];
|
||||
character.y = _data.characters.dad.position[1];
|
||||
default:
|
||||
this.characters.set(character.curCharacter, character);
|
||||
}
|
||||
|
||||
// Add the character to the scene.
|
||||
this.add(character);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a given character from the stage.
|
||||
*/
|
||||
public function getCharacter(id:String):Character
|
||||
{
|
||||
return this.characters.get(id);
|
||||
}
|
||||
|
||||
public function getBoyfriend():Character
|
||||
{
|
||||
return getCharacter('bf');
|
||||
}
|
||||
|
||||
public function getGirlfriend():Character
|
||||
{
|
||||
return getCharacter('gf');
|
||||
}
|
||||
|
||||
public function getDad():Character
|
||||
{
|
||||
return getCharacter('dad');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific prop by the name assigned in the JSON file.
|
||||
* @param name The name of the prop to retrieve.
|
||||
* @return The corresponding FlxSprite.
|
||||
*/
|
||||
public function getNamedProp(name:String):FlxSprite
|
||||
{
|
||||
return this.namedProps.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of all the asset paths required to load the stage.
|
||||
* Override this in a scripted class to ensure that all necessary assets are loaded!
|
||||
*
|
||||
* @return An array of file names.
|
||||
*/
|
||||
public function fetchAssetPaths():Array<String>
|
||||
{
|
||||
var result:Array<String> = [];
|
||||
for (dataProp in _data.props)
|
||||
{
|
||||
result.push(Paths.image(dataProp.assetPath));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleanup for when you are leaving the level.
|
||||
*/
|
||||
public override function kill()
|
||||
{
|
||||
super.kill();
|
||||
|
||||
for (prop in this.namedProps)
|
||||
{
|
||||
prop.destroy();
|
||||
}
|
||||
namedProps.clear();
|
||||
|
||||
for (char in this.characters)
|
||||
{
|
||||
char.destroy();
|
||||
}
|
||||
characters.clear();
|
||||
|
||||
for (bopper in boppers)
|
||||
{
|
||||
bopper.destroy();
|
||||
}
|
||||
boppers = [];
|
||||
|
||||
for (sprite in this.group)
|
||||
{
|
||||
sprite.destroy();
|
||||
}
|
||||
group.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleanup for when you are destroying the stage
|
||||
* and removing all its data from cache.
|
||||
*
|
||||
* Call this ONLY when you are performing a hard cache clear.
|
||||
*/
|
||||
public override function destroy()
|
||||
{
|
||||
super.destroy();
|
||||
}
|
||||
}
|
509
source/funkin/play/stage/StageData.hx
Normal file
509
source/funkin/play/stage/StageData.hx
Normal file
|
@ -0,0 +1,509 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import openfl.Assets;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import haxe.Json;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Contains utilities for loading and parsing stage data.
|
||||
*/
|
||||
class StageDataParser
|
||||
{
|
||||
/**
|
||||
* The current version string for the stage data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:String = "1.0";
|
||||
|
||||
static final stageCache:Map<String, Stage> = new Map<String, Stage>();
|
||||
|
||||
static final DEFAULT_STAGE_ID = 'UNKNOWN';
|
||||
|
||||
/**
|
||||
* Parses and preloads the game's stage data and scripts when the game starts.
|
||||
*
|
||||
* If you want to force stages to be reloaded, you can just call this function again.
|
||||
*/
|
||||
public static function loadStageCache():Void
|
||||
{
|
||||
// Clear any stages that are cached if there were any.
|
||||
clearStageCache();
|
||||
trace("[STAGEDATA] Loading stage cache...");
|
||||
|
||||
//
|
||||
// SCRIPTED STAGES
|
||||
//
|
||||
var scriptedStageClassNames:Array<String> = ScriptedStage.listScriptClasses();
|
||||
trace(' Instantiating ${scriptedStageClassNames.length} scripted stages...');
|
||||
for (stageCls in scriptedStageClassNames)
|
||||
{
|
||||
var stage:Stage = ScriptedStage.init(stageCls, DEFAULT_STAGE_ID);
|
||||
if (stage != null)
|
||||
{
|
||||
trace(' Loaded scripted stage: ${stage.stageName}');
|
||||
// Disable the rendering logic for stage until it's loaded.
|
||||
// Note that kill() =/= destroy()
|
||||
stage.kill();
|
||||
|
||||
// Then store it.
|
||||
stageCache.set(stage.stageId, stage);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace(' Failed to instantiate scripted stage class: ${stageCls}');
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// UNSCRIPTED STAGES
|
||||
//
|
||||
var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/');
|
||||
var unscriptedStageIds:Array<String> = stageIdList.filter(function(stageId:String):Bool
|
||||
{
|
||||
return !stageCache.exists(stageId);
|
||||
});
|
||||
trace(' Instantiating ${unscriptedStageIds.length} non-scripted stages...');
|
||||
for (stageId in unscriptedStageIds)
|
||||
{
|
||||
var stage:Stage;
|
||||
try
|
||||
{
|
||||
stage = new Stage(stageId);
|
||||
if (stage != null)
|
||||
{
|
||||
trace(' Loaded stage data: ${stage.stageName}');
|
||||
stageCache.set(stageId, stage);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// Assume error was already logged.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
trace(' Successfully loaded ${Lambda.count(stageCache)} stages.');
|
||||
}
|
||||
|
||||
public static function fetchStage(stageId:String):Null<Stage>
|
||||
{
|
||||
if (stageCache.exists(stageId))
|
||||
{
|
||||
trace('[STAGEDATA] Successfully fetch stage: ${stageId}');
|
||||
var stage:Stage = stageCache.get(stageId);
|
||||
stage.revive();
|
||||
return stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[STAGEDATA] Failed to fetch stage, not found in cache: ${stageId}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static function clearStageCache():Void
|
||||
{
|
||||
if (stageCache != null)
|
||||
{
|
||||
for (stage in stageCache)
|
||||
{
|
||||
stage.destroy();
|
||||
}
|
||||
stageCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a stage's JSON file, parse its data, and return it.
|
||||
*
|
||||
* @param stageId The stage to load.
|
||||
* @return The stage data, or null if validation failed.
|
||||
*/
|
||||
public static function parseStageData(stageId:String):Null<StageData>
|
||||
{
|
||||
var rawJson:String = loadStageFile(stageId);
|
||||
|
||||
var stageData:StageData = migrateStageData(rawJson, stageId);
|
||||
|
||||
return validateStageData(stageId, stageData);
|
||||
}
|
||||
|
||||
static function loadStageFile(stagePath:String):String
|
||||
{
|
||||
var stageFilePath:String = Paths.json('stages/${stagePath}');
|
||||
var rawJson = Assets.getText(stageFilePath).trim();
|
||||
|
||||
while (!rawJson.endsWith("}"))
|
||||
{
|
||||
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||
}
|
||||
|
||||
return rawJson;
|
||||
}
|
||||
|
||||
static function migrateStageData(rawJson:String, stageId:String)
|
||||
{
|
||||
// If you update the stage data format in a breaking way,
|
||||
// handle migration here by checking the `version` value.
|
||||
|
||||
try
|
||||
{
|
||||
var stageData:StageData = cast Json.parse(rawJson);
|
||||
return stageData;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' Error parsing data for stage: ${stageId}');
|
||||
trace(' ${e}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static final DEFAULT_NAME:String = "Untitled Stage";
|
||||
static final DEFAULT_CAMERAZOOM:Float = 1.0;
|
||||
static final DEFAULT_ZINDEX:Int = 0;
|
||||
static final DEFAULT_DANCEEVERY:Int = 0;
|
||||
static final DEFAULT_SCALE:Float = 1.0;
|
||||
static final DEFAULT_ISPIXEL:Bool = false;
|
||||
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||
static final DEFAULT_FRAMEINDICES:Array<Int> = [];
|
||||
static final DEFAULT_ANIMTYPE:String = "sparrow";
|
||||
|
||||
static final DEFAULT_CHARACTER_DATA:StageDataCharacter = {
|
||||
zIndex: DEFAULT_ZINDEX,
|
||||
position: DEFAULT_POSITION,
|
||||
}
|
||||
|
||||
/**
|
||||
* Set unspecified parameters to their defaults.
|
||||
* If the parameter is mandatory, print an error message.
|
||||
* @param id
|
||||
* @param input
|
||||
* @return The validated stage data
|
||||
*/
|
||||
static function validateStageData(id:String, input:StageData):Null<StageData>
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not parse stage data for "${id}".');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.version != STAGE_DATA_VERSION)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.name == null)
|
||||
{
|
||||
trace('[STAGEDATA] WARN: Stage data for "$id" missing name');
|
||||
input.name = DEFAULT_NAME;
|
||||
}
|
||||
|
||||
if (input.cameraZoom == null)
|
||||
{
|
||||
input.cameraZoom = DEFAULT_CAMERAZOOM;
|
||||
}
|
||||
|
||||
if (input.props == null || input.props.length == 0)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing props');
|
||||
return null;
|
||||
}
|
||||
|
||||
for (inputProp in input.props)
|
||||
{
|
||||
// It's fine for inputProp.name to be null
|
||||
|
||||
if (inputProp.assetPath == null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing assetPath for prop "${inputProp.name}"');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inputProp.position == null)
|
||||
{
|
||||
inputProp.position = DEFAULT_POSITION;
|
||||
}
|
||||
|
||||
if (inputProp.zIndex == null)
|
||||
{
|
||||
inputProp.zIndex = DEFAULT_ZINDEX;
|
||||
}
|
||||
|
||||
if (inputProp.isPixel == null)
|
||||
{
|
||||
inputProp.isPixel = DEFAULT_ISPIXEL;
|
||||
}
|
||||
|
||||
if (inputProp.danceEvery == null)
|
||||
{
|
||||
inputProp.danceEvery = DEFAULT_DANCEEVERY;
|
||||
}
|
||||
|
||||
if (inputProp.scale == null)
|
||||
{
|
||||
inputProp.scale = DEFAULT_SCALE;
|
||||
}
|
||||
|
||||
if (inputProp.animType == null)
|
||||
{
|
||||
inputProp.animType = DEFAULT_ANIMTYPE;
|
||||
}
|
||||
|
||||
if (Std.isOfType(inputProp.scale, Float))
|
||||
{
|
||||
inputProp.scale = [inputProp.scale, inputProp.scale];
|
||||
}
|
||||
|
||||
if (inputProp.scroll == null)
|
||||
{
|
||||
inputProp.scroll = DEFAULT_SCROLL;
|
||||
}
|
||||
|
||||
if (Std.isOfType(inputProp.scroll, Float))
|
||||
{
|
||||
inputProp.scroll = [inputProp.scroll, inputProp.scroll];
|
||||
}
|
||||
|
||||
if (inputProp.animations == null)
|
||||
{
|
||||
inputProp.animations = [];
|
||||
}
|
||||
|
||||
if (inputProp.animations.length == 0 && inputProp.startingAnimation != null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animations for prop "${inputProp.name}"');
|
||||
return null;
|
||||
}
|
||||
|
||||
for (inputAnimation in inputProp.animations)
|
||||
{
|
||||
if (inputAnimation.name == null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animation name for prop "${inputProp.name}"');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inputAnimation.frameRate == null)
|
||||
{
|
||||
inputAnimation.frameRate = 24;
|
||||
}
|
||||
|
||||
if (inputAnimation.frameIndices == null)
|
||||
{
|
||||
inputAnimation.frameIndices = DEFAULT_FRAMEINDICES;
|
||||
}
|
||||
|
||||
if (inputAnimation.loop == null)
|
||||
{
|
||||
inputAnimation.loop = true;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipX == null)
|
||||
{
|
||||
inputAnimation.flipX = false;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipY == null)
|
||||
{
|
||||
inputAnimation.flipY = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input.characters == null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing characters');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.characters.bf == null)
|
||||
{
|
||||
input.characters.bf = DEFAULT_CHARACTER_DATA;
|
||||
}
|
||||
if (input.characters.dad == null)
|
||||
{
|
||||
input.characters.dad = DEFAULT_CHARACTER_DATA;
|
||||
}
|
||||
if (input.characters.gf == null)
|
||||
{
|
||||
input.characters.gf = DEFAULT_CHARACTER_DATA;
|
||||
}
|
||||
|
||||
for (inputCharacter in [input.characters.bf, input.characters.dad, input.characters.gf])
|
||||
{
|
||||
if (inputCharacter.zIndex == null)
|
||||
{
|
||||
inputCharacter.zIndex = 0;
|
||||
}
|
||||
if (inputCharacter.position == null || inputCharacter.position.length != 2)
|
||||
{
|
||||
inputCharacter.position = [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
// All good!
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
typedef StageData =
|
||||
{
|
||||
// Uses semantic versioning.
|
||||
var version:String;
|
||||
var name:String;
|
||||
var cameraZoom:Null<Float>;
|
||||
var props:Array<StageDataProp>;
|
||||
var characters:
|
||||
{
|
||||
bf:StageDataCharacter,
|
||||
dad:StageDataCharacter,
|
||||
gf:StageDataCharacter,
|
||||
};
|
||||
};
|
||||
|
||||
typedef StageDataProp =
|
||||
{
|
||||
/**
|
||||
* The name of the prop for later lookup by scripts.
|
||||
* Optional; if unspecified, the prop can't be referenced by scripts.
|
||||
*/
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The asset used to display the prop.
|
||||
*/
|
||||
var assetPath:String;
|
||||
|
||||
/**
|
||||
* The position of the prop as an [x, y] array of two floats.
|
||||
*/
|
||||
var position:Array<Float>;
|
||||
|
||||
/**
|
||||
* A number determining the stack order of the prop, relative to other props and the characters in the stage.
|
||||
* Props with lower numbers render below those with higher numbers.
|
||||
* This is just like CSS, it isn't hard.
|
||||
* @default 0
|
||||
*/
|
||||
var zIndex:Null<Int>;
|
||||
|
||||
/**
|
||||
* If set to true, anti-aliasing will be forcibly disabled on the sprite.
|
||||
* This prevents blurry images on pixel-art levels.
|
||||
* @default false
|
||||
*/
|
||||
var isPixel:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
||||
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
||||
* @default 1
|
||||
*/
|
||||
var scale:OneOfTwo<Float, Array<Float>>;
|
||||
|
||||
/**
|
||||
* If not zero, this prop will play an animation every X beats of the song.
|
||||
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
||||
* they will alternated between, otherwise the `idle` animation will be used.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
var danceEvery:Null<Int>;
|
||||
|
||||
/**
|
||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||
* Represented as a float or as an [x, y] array of two floats.
|
||||
* [1, 1] means the prop moves 1:1 with the camera.
|
||||
* [0.5, 0.5] means the prop half as much as the camera.
|
||||
* [0, 0] means the prop is not moved.
|
||||
* @default [0, 0]
|
||||
*/
|
||||
var scroll:OneOfTwo<Float, Array<Float>>;
|
||||
|
||||
/**
|
||||
* An optional array of animations which the prop can play.
|
||||
* @default Prop has no animations.
|
||||
*/
|
||||
var animations:Array<StageDataPropAnimation>;
|
||||
|
||||
/**
|
||||
* If animations are used, this is the name of the animation to play first.
|
||||
* @default Don't play an animation.
|
||||
*/
|
||||
var startingAnimation:String;
|
||||
|
||||
/**
|
||||
* The animation type to use.
|
||||
* Options: "sparrow", "packer"
|
||||
* @default "sparrow"
|
||||
*/
|
||||
var animType:String;
|
||||
};
|
||||
|
||||
typedef StageDataPropAnimation =
|
||||
{
|
||||
/**
|
||||
* The name of the animation.
|
||||
*/
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The common beginning of image names in atlas for this animation's frames.
|
||||
* For example, if the frames are named "test0001.png", "test0002.png", etc., use "test".
|
||||
*/
|
||||
var prefix:String;
|
||||
|
||||
/**
|
||||
* If you want this animation to use only certain frames of an animation with a given prefix,
|
||||
* select them here.
|
||||
* @example [0, 1, 2, 3] (use only the first four frames)
|
||||
* @default [] (all frames)
|
||||
*/
|
||||
var frameIndices:Array<Int>;
|
||||
|
||||
/**
|
||||
* The speed of the animation in frames per second.
|
||||
* @default 24
|
||||
*/
|
||||
var frameRate:Null<Int>;
|
||||
|
||||
/**
|
||||
* Whether the animation should loop.
|
||||
* @default false
|
||||
*/
|
||||
var loop:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Whether to flip the sprite horizontally while animating.
|
||||
* @default false
|
||||
*/
|
||||
var flipX:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Whether to flip the sprite vertically while animating.
|
||||
* @default false
|
||||
*/
|
||||
var flipY:Null<Bool>;
|
||||
};
|
||||
|
||||
typedef StageDataCharacter =
|
||||
{
|
||||
/**
|
||||
* A number determining the stack order of the character, relative to props and other characters in the stage.
|
||||
* Again, just like CSS.
|
||||
* @default 0
|
||||
*/
|
||||
zIndex:Null<Int>,
|
||||
|
||||
/**
|
||||
* The position to render the character at.
|
||||
*/ position:Array<Float>
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
package rendering;
|
||||
package funkin.rendering;
|
||||
|
||||
import flixel.FlxStrip;
|
||||
import flixel.util.FlxColor;
|
|
@ -1,4 +1,4 @@
|
|||
package shaderslmfao;
|
||||
package funkin.shaderslmfao;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package;
|
||||
package funkin.shaderslmfao;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.display.ShaderParameter;
|
|
@ -1,4 +1,4 @@
|
|||
package shaderslmfao;
|
||||
package funkin.shaderslmfao;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package shaderslmfao;
|
||||
package funkin.shaderslmfao;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxColor;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue