Merge remote-tracking branch 'dev/main'

This commit is contained in:
⍚~Nex
2024-02-27 18:01:34 +01:00
110 changed files with 1944 additions and 23012 deletions
+1
View File
@@ -2,6 +2,7 @@
name: Windows Build
on:
push:
workflow_dispatch:
jobs:
build:
name: Windows Build
+36 -21
View File
@@ -4,33 +4,22 @@
### Mods created with beta versions of Codename may not be compatible with the release version
Known issues in the beta:
- Some options are missing
- Week 6 still have no dialogue
Build instructions are below. Press TAB on the main menu to switch mods.
Also, `lime test windows` uses the source assets folder instead of the export one for easier development.
Also, the command `.\cne test` uses the source assets folder instead of the export one for easier development (Although you can still use `lime test windows` normally).
## Codename Engine
Codename Engine is a new Friday Night Funkin' Engine aimed at simplifying modding, along with extensiblity and ease of use.
### It includes many new features, as seen [here](FEATURES.md)
### Wanna see the new features added in the most recent update? Click [here](PATCHNOTES.md)
Codename Engine is a new Friday Night Funkin' Engine aimed at simplifying modding, along with extensiblity and ease of use.<br>
Make sure to read the wiki and join the Discord server linked there!<br>
#### It includes many new features, as seen [here](FEATURES.md)<br>
#### Wanna see the new features added in the most recent update? Click [here](PATCHNOTES.md)<br>
## How to download
Latest builds for the engine can be found in the [Actions](https://github.com/YoshiCrafter29/CodenameEngine/actions) tab.
<details>
<summary><h2>Credits</h2></summary>
- Credits to [Ne_Eo](https://twitter.com/Ne_Eo_Twitch) and the [3D-HaxeFlixel](https://github.com/lunarcleint/3D-HaxeFlixel) repository for Away3D Flixel support
- Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support.
- Credits to Smokey555 for the backup Animate Atlas to spritesheet code.
- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) for video cutscene/mp4 support.
- Credits to Aidan63 for [discord-rpc](https://github.com/Aidan63/linc_discord-rpc) for discord rpc integration.
</details>
Latest builds for the engine can be found in the [Actions](https://github.com/YoshiCrafter29/CodenameEngine/actions) tab.<br>
In the future (when the engine won't be a WIP anymore) we're gonna also publish the engine on platforms like Gamebanana; stay tuned!
<details>
<summary><h2>How to build</h2></summary>
@@ -80,8 +69,34 @@ Latest builds for the engine can be found in the [Actions](https://github.com/Yo
4. Open a terminal in the Codename Engine source folder, and run `update.sh`.
5. Once the libraries are installed, run `haxelib run lime test mac` to compile and launch the game (may take a long time)
- ℹ You can run `haxelib run lime setup` to make the lime command global, allowing you to execute `lime test mac` directly.
</details>
</details>
<details>
<summary><h2>What can you do or not do</h2></summary>
### You can:
- Download and play the engine with its mods and modpacks
- Mod and fork the engine (without using it for illicit purposes)
- Contribute to the engine (for example through *Pull Requests*, *Issues*, etc)
- Create a sub engine with Codename Engine as **TEMPLATE** with **CREDITS** (for example leaving the *credits menu submenu with the GitHub contributors* and putting the *[main devs](https://github.com/FNF-CNE-Devs)* in a *README* specifying that it's a *sub engine from Codename Engine*)
- Release excutable mods that use Codename Engine as source (Specifing that uses Codename Engine by for example the same way written above this)
- Release modpacks
### You can't:
- Create a *side/new/etc* engine (or mod that doesn't use Codename Engine) using Codename Engine's code
- Steal code from Codename Engine for another different project that is not Codename Engine related (Codename Engine mods excluded) without properly crediting
- Release the entire Codename Engine on platforms (Mods that use Codename Engine as source are fine, if it's specified even better)
#### *If you need more info or feel like asking to do something which is not listed here, ask us directly on our discord (linked in the wiki)!*
</details>
<details>
<summary><h2>Credits</h2></summary>
- Credits to [Ne_Eo](https://twitter.com/Ne_Eo_Twitch) and the [3D-HaxeFlixel](https://github.com/lunarcleint/3D-HaxeFlixel) repository for Away3D Flixel support
- Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support
- Credits to Smokey555 for the backup Animate Atlas to spritesheet code
- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) for video cutscene/mp4 support
- Credits to Aidan63 for [discord-rpc](https://github.com/Aidan63/linc_discord-rpc) for discord rpc integration
</details>
+1 -1
View File
@@ -41,7 +41,7 @@
- [ ] New Switch Mod menu
- [x] Port shader camera stuff over to new flixel fork
- [ ] Add note offset
- [ ] Dialogue
- [x] Dialogue
- [ ] Finish Base Game port
- [x] Add credits
- [ ] Verbose mode (`--verbose`)
+9 -14
View File
@@ -1,15 +1,10 @@
<!DOCTYPE codename-engine-character>
<!-- tankman actually have player offsets !-->
<character isPlayer="true" flipX="true" x="0" y="180" camx="0">
<anim name="idle" anim="Tankman Idle Dance instance 100" fps="24" loop="false" x="0" y="0" />
<anim name="singRIGHT" anim="Tankman Right Note instance 1000" fps="24" loop="false" x="-1" y="-7" />
<anim name="singDOWN" anim="Tankman DOWN note instance 1000" fps="24" loop="false" x="98" y="-90" />
<anim name="singUP" anim="Tankman UP note instance 1000" fps="24" loop="false" x="24" y="56" />
<anim name="singLEFT" anim="Tankman Note Left instance 1000" fps="24" loop="false" x="100" y="-14" />
<!-- Heh! Pretty Good! -->
<anim name="singDOWN-alt" anim="PRETTY GOOD tankman instance 100" fps="24" loop="false" x="98" y="-90" />
<!-- Ugh! -->
<anim name="singUP-alt" anim="TANKMAN UGH instance 100" fps="30" loop="false" x="24" y="56" />
</character>
<character x="0" icon="tankman" y="180" isGF="false" flipX="true" isPlayer="true" sprite="tankman">
<anim x="0" loop="false" y="0" fps="24" anim="Tankman Idle Dance instance 100" name="idle"/>
<anim x="-1" loop="false" y="-7" fps="24" anim="Tankman Right Note instance 1000" name="singRIGHT"/>
<anim x="98" loop="false" y="-90" fps="24" anim="Tankman DOWN note instance 1000" name="singDOWN"/>
<anim x="24" loop="false" y="56" fps="24" anim="Tankman UP note instance 1000" name="singUP"/>
<anim x="100" loop="false" y="-14" fps="24" anim="Tankman Note Left instance 1000" name="singLEFT"/>
<anim x="98" loop="false" y="17" fps="24" anim="PRETTY GOOD tankman instance 100" name="good!"/>
<anim x="-16" loop="false" y="-9" fps="30" anim="TANKMAN UGH instance 100" name="singUP-alt"/>
</character>
+5 -5
View File
@@ -1,21 +1,21 @@
<!DOCTYPE codename-engine-dialogue-box>
<dialoguebox scale="0.9" y="75">
<dialoguebox y="75">
<!--
Node containing all text settings
-->
-->
<text font="vcr.ttf" x="107" y="206" width="1053" color="#000000" size="40"/>
<!--
All positions for characters
-->
<charpos name="left" x="-500" y="184" />
<charpos name="left" x="-500" y="184" flipBubble="true" />
<charpos name="middle" x="0" y="184" />
<charpos name="right" x="500" y="184" flipBubble="true" />
<charpos name="right" x="500" y="184" />
<!--
All animations for the dialogue box
For example, if you wanna add an "angry" animation, add an animation named "angry" (box idle), and "angry-open" (not needed, for box apparition)
-->
-->
<anim name="normal" anim="speech bubble normal" fps="24" loop="true" x="-30"/>
<anim name="normal-open" anim="Speech Bubble Normal Open" fps="24" />
@@ -0,0 +1,61 @@
var loopedTimah:FlxTimer; // Hey! a man has fallen into the river in lego city! and if youre asking why this is here, you shall disappear! - Nex
var bgFade:FlxSprite;
var hand:FlxSprite;
function postCreate() {
bgFade = new FlxSprite().makeSolid(FlxG.width + 100, FlxG.height + 100, 0xFFB3DFd8);
bgFade.screenCenter();
bgFade.scrollFactor.set();
bgFade.alpha = 0;
cutscene.insert(0, bgFade);
loopedTimah = new FlxTimer().start(0.83, function(tmr:FlxTimer)
{
bgFade.alpha += (1 / 5) * 0.7;
if (bgFade.alpha > 0.7)
bgFade.alpha = 0.7;
}, 5);
hand = new FlxSprite(0, 600).loadGraphic(Paths.image('stages/school/ui/hand_textbox'));
hand.scale.set(4, 4);
hand.updateHitbox();
}
var finished:Bool = false;
function close(event) {
if(finished) return;
else event.cancelled = true;
cutscene.canProceed = false;
cutscene.curMusic?.fadeOut(1, 0);
for(c in cutscene.charMap) c.visible = false;
loopedTimah.cancel();
loopedTimah = new FlxTimer().start(0.2, function(tmr:FlxTimer)
{
if (tmr.elapsedLoops <= 5) {
cutscene.dialogueBox.alpha -= 1 / 5;
cutscene.dialogueBox.text.alpha -= 1 / 5;
bgFade.alpha -= (1 / 5) * 0.7;
hand.alpha -= 1 / 5;
} else {
finished = true;
cutscene.close();
}
}, 6);
}
var time:Float = 0;
function update(elapsed:Float) {
if(hand.visible = dialogueEnded) {
hand.x = 1060 + Math.sin((time += elapsed) * Math.PI * 2) * 12;
hand.x -= hand.x % hand.scale.x;
hand.y -= hand.y % hand.scale.y;
}
}
function postPlayBubbleAnim() {
cutscene.remove(hand);
if(active && visible)
cutscene.add(hand);
}
@@ -0,0 +1,21 @@
<!DOCTYPE codename-engine-dialogue-box>
<dialoguebox sprite="pixel/dialogueBox" antialiasing="false" graphicSizex="1227" x="-67" y="-325" textSound="pixel/pixelText" nextSound="pixel/clickText" updateHitbox="true">
<!--
Node containing all text settings
-->
<text font="pixel.otf" x="239" y="218" width="768" color="#3f2021" size="32" antialiasing="false" borderSize="1" borderStyle="shadow" borderColor="#D89494" shadowOffsetX="2" shadowOffsetY="2"/>
<!--
All positions for characters
-->
<charpos name="left" x="-500" y="184" />
<charpos name="middle" x="0" y="184" />
<charpos name="right" x="490" y="184" />
<!--
All animations for the dialogue box
For example, if you wanna add an "angry" animation, add an animation named "angry" (box idle), and "angry-open" (not needed, for box apparition)
-->
<anim name="normal-firstOpen" anim="Text Box Appear instance 10" fps="24" />
<anim name="angry-firstOpen" anim="Text Box Bump Appear instance 10" x="-16" y="-59" fps="24" />
</dialoguebox>
@@ -0,0 +1,43 @@
import flixel.tweens.FlxTweenType;
var aberration:CustomShader = null;
var spirit:FlxSprite;
function postCreate() {
spirit = new FlxSprite(320, 170).loadGraphic(Paths.image('game/cutscenes/weeb/spiritFaceForward'));
spirit.setGraphicSize(Std.int(spirit.width * 6));
if(Options.gameplayShaders) {
spirit.shader = aberration = new CustomShader('chromaticAberration');
FlxTween.num(-0.003, 0.003, 3, {ease: FlxEase.sineInOut, type: FlxTweenType.PINGPONG}, function(num) { if(aberration == null) return; aberration.redOff = [0, -num]; aberration.blueOff = [0, num]; });
}
cutscene.add(spirit);
FlxG.camera._fxFadeAlpha = 0;
cutscene.dialogueCamera.bgColor = FlxColor.fromRGBFloat(1, 1, 1, 0.3);
}
var finished:Bool = false;
function close(event) {
if(finished) return;
else event.cancelled = true;
cutscene.canProceed = false;
cutscene.curMusic?.fadeOut(1, 0);
for(c in cutscene.charMap) c.visible = false;
spirit.destroy();
spirit.shader = aberration = null;
new FlxTimer().start(0.4, function(swagTimer:FlxTimer) {
cutscene.dialogueCamera.alpha -= 0.15;
if(cutscene.dialogueCamera.alpha > 0) swagTimer.reset();
else {
finished = true;
cutscene.close();
}
});
}
function popupChar(event) {
if(!active || event.char.positionName != "left") return;
event.char.color = FlxColor.BLACK;
}
@@ -0,0 +1,20 @@
<!DOCTYPE codename-engine-dialogue-box>
<dialoguebox sprite="pixel/dialogueBox-evil" antialiasing="false" graphicSizex="1344" y="107" textSound="pixel/pixelText" nextSound="pixel/clickText" updateHitbox="true">
<!--
Node containing all text settings
-->
<text font="pixel.otf" x="265" y="220" width="768" color="#FFFFFF" size="32" antialiasing="false" borderSize="2" borderStyle="shadow" borderColor="#000000" />
<!--
All positions for characters
-->
<charpos name="left" x="-510" y="183" />
<charpos name="middle" x="0" y="183" />
<charpos name="right" x="510" y="183" />
<!--
All animations for the dialogue box
For example, if you wanna add an "angry" animation, add an animation named "angry" (box idle), and "angry-open" (not needed, for box apparition)
-->
<anim name="normal-firstOpen" anim="Spirit Textbox spawn instance 10" fps="24" />
</dialoguebox>
@@ -0,0 +1,3 @@
function postHide() {
if(curTween != null) curTween.percent = 1;
}
@@ -0,0 +1,4 @@
<!DOCTYPE codename-engine-dialogue-character>
<character sprite="pixel/boyfriend" x="570" y="-599" graphicSizex="1334" antialiasing="false" textSound="pixel/pixelText">
<anim name="normal-show" anim="Boyfriend portrait enter instance 10" fps="24" />
</character>
@@ -0,0 +1,6 @@
function postHide() {
if(curTween != null) {
if(animation.curAnim?.name == 'angry-show') curTween.cancel();
else curTween.percent = 1;
}
}
@@ -0,0 +1,5 @@
<!DOCTYPE codename-engine-dialogue-character>
<character sprite="pixel/senpai" x="-392" y="-599" graphicSizex="1334" antialiasing="false" textSound="pixel/pixelText">
<anim name="normal-show" anim="Senpai Portrait Enter instance 10" fps="24" x="4.4" />
<anim name="angry-show" anim="Senpai Portrait Angry instance 10" x="-56" y="-9" fps="24" />
</character>
-1
View File
@@ -1,2 +1 @@
//
function onNoteHit(event) if (event.noteType == "Alt Anim Note") event.animSuffix = "-alt";
+1
View File
@@ -0,0 +1 @@
function onNoteHit(event) if (event.noteType == "No Anim Note") event.animCancelled = true;
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="0.9" name="limo" folder="stages/limo/">
<stage zoom="0.9" name="limo" folder="stages/limo/" startCamPosY="400">
<!-- BG LIMOS !-->
<sprite name="skyBG" x="-120" y="-50" sprite="limoSunset" scroll="0.1" zoomfactor="0"/>
<sprite name="bgLimo" x="-200" y="480" sprite="bgLimo" scroll="0.4" zoomfactor="0.55" />
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="1.05" name="mall-evil" folder="stages/mall/">
<stage zoom="1.05" name="mall-evil" folder="stages/mall/" startCamPosY="400" startCamPosX="1300">
<sprite name="bg" x="-400" y="-500" sprite="evilBG" scroll="0.2"/>
<sprite name="evilTree" x="300" y="-300" sprite="evilTree" scroll="0.2"/>
<sprite name="evilSnow" x="-200" y="700" sprite="evilSnow" />
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="0.8" name="mall" folder="stages/mall/">
<stage zoom="0.8" name="mall" folder="stages/mall/" startCamPosY="450" startCamPosX="500">
<sprite name="bg" x="-1000" y="-500" sprite="bgWalls" scroll="0.2"/>
<high-memory>
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="1.05" name="philly" folder="stages/philly/">
<stage zoom="1.05" name="philly" folder="stages/philly/" startCamPosY="510" startCamPosX="950">
<sprite name="bg" x="-100" y="0" sprite="sky" scroll="0.1" zoomfactor="0"/>
<sprite name="city" x="-10" y="0" sprite="city" scroll="0.3" scale="0.85" updateHitbox="true" zoomfactor="0.3"/>
<sprite name="light" x="-10" y="0" sprite="win" scroll="0.3" scale="0.85" updateHitbox="true" zoomfactor="0.3"/>
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="1" name="school-evil" folder="stages/school/" >
<stage zoom="1" name="school-evil" folder="stages/school/" startCamPosY="550" startCamPosX="600">
<sprite name="bg" x="400" y="200" sprite="animatedEvilSchool"
antialiasing="false" scale="6" scrollx="0.8" scrolly="0.9" type="loop"/>
</stage>
+4 -9
View File
@@ -1,15 +1,10 @@
function create() {
importScript("data/scripts/pixel");
if (PlayState.SONG.meta.name.toLowerCase() == "roses") {
flabbergastGirls();
bgGirls.animation.remove("danceLeft");
bgGirls.animation.remove("danceRight");
bgGirls.animation.addByIndices('danceLeft', 'BG fangirls dissuaded', CoolUtil.numberArray(14), "", 24, false);
bgGirls.animation.addByIndices('danceRight', 'BG fangirls dissuaded', CoolUtil.numberArray(30, 15), "", 24, false);
}
bgGirls.animation.play("danceLeft", true); // horrible fix, please fix later
}
public function flabbergastGirls() {
bgGirls.animation.remove("danceLeft");
bgGirls.animation.remove("danceRight");
bgGirls.animation.addByIndices('danceLeft', 'BG fangirls dissuaded', CoolUtil.numberArray(14), "", 24, false);
bgGirls.animation.addByIndices('danceRight', 'BG fangirls dissuaded', CoolUtil.numberArray(30, 15), "", 24, false);
bgGirls.animation.play("danceLeft", true);
}
+2 -2
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="1" folder="stages/school/">
<stage zoom="1" folder="stages/school/" startCamPosY="550" startCamPosX="600">
<sprite name="bgSky" x="-12" y="0" sprite="weebSky"
antialiasing="false" scale="6" scroll="0.1" updateHitbox="true"/>
@@ -23,7 +23,7 @@
antialiasing="false" scale="6" scroll="0.85" updateHitbox="true" type="loop"/>
</high-memory>
<sprite name="bgGirls" x="-100" y="190" sprite="bgFreaks" scroll="0.9" scale="6" antialiasing="false" updateHitbox="true">
<sprite name="bgGirls" x="-100" y="190" sprite="bgFreaks" scroll="0.9" scale="6" antialiasing="false" updateHitbox="true" type="onbeat">
<anim name="danceLeft" anim="BG girls group" indices="0,1,2,3,4,5,6,7,8,9,10,11,12,13" fps="24" loop="false"/>
<anim name="danceRight" anim="BG girls group" indices="15,16,17,18,19,20,21,22,23,24,25,26,27,28,29" fps="24" loop="false"/>
</sprite>
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="1.05" name="spooky" folder="stages/spooky/">
<stage zoom="1.05" name="spooky" folder="stages/spooky/" startCamPosY="400" startCamPosX="600">
<sprite name="bg" x="-200" y="-100" sprite="halloween_bg" anim="idle" type="none" scale="1.05">
<anim name="idle" anim="halloweem bg0" type="loop" />
<anim name="lightning" anim="halloweem bg lightning strike" loop="false"/>
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="0.9" name="stage" folder="stages/default/">
<stage zoom="0.9" name="stage" folder="stages/default/" startCamPosY="600" startCamPosX="1000">
<sprite name="bg" x="-600" y="-200" sprite="stageback" scroll="0.9" />
<sprite name="stageFront" x="-600" y="600" sprite="stagefront" scroll="0.9" />
<girlfriend />
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE codename-engine-stage>
<stage zoom="0.9" name="tank" folder="stages/tank/">
<stage zoom="0.9" name="tank" folder="stages/tank/" startCamPosY="400" startCamPosX="300">
<!-- BG STUFF -->
<sprite name="tankSky" x="-400" y="-600" sprite="tankSky" scroll="0" zoomfactor="0"/>
<high-memory>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/
-->
<TextureAtlas imagePath="dialogueBox-evil.png" width="598" height="44">
<SubTexture name="Spirit Textbox spawn instance 10000" x="583" y="1" width="14" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10001" x="583" y="1" width="14" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10002" x="533" y="1" width="47" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10003" x="533" y="1" width="47" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10004" x="452" y="1" width="78" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10005" x="452" y="1" width="78" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10006" x="342" y="1" width="107" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10007" x="342" y="1" width="107" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10008" x="194" y="1" width="145" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10009" x="194" y="1" width="145" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10010" x="1" y="1" width="190" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
<SubTexture name="Spirit Textbox spawn instance 10011" x="1" y="1" width="190" height="42" frameX="-28" frameY="-75" frameWidth="249" frameHeight="145"/>
</TextureAtlas>
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ -->
<!-- HUGE THANKS to Nile that divided the angry animations from the angry box! -->
<TextureAtlas imagePath="dialogueBox.png" width="424" height="220">
<SubTexture name="Text Box Bump Appear instance 10000" x="1" y="1" width="215" height="63" frameX="-6" frameY="-0" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Bump Appear instance 10001" x="210" y="128" width="202" height="56" frameX="-10" frameY="-8" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Bump Appear instance 10002" x="1" y="129" width="205" height="58" frameX="-4" frameY="-4" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Bump Appear instance 10003" x="219" y="50" width="204" height="58" frameX="-8" frameY="-6" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Bump Appear instance 10004" x="1" y="129" width="205" height="58" frameX="-4" frameY="-4" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Bump Appear instance 10005" x="1" y="129" width="205" height="58" frameX="-4" frameY="-4" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Bump Appear instance 10006" x="1" y="67" width="206" height="59" frameX="-6" frameY="-6" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Bump Appear instance 10007" x="1" y="67" width="206" height="59" frameX="-6" frameY="-6" frameWidth="224" frameHeight="65"/>
<SubTexture name="Text Box Appear instance 10000" x="210" y="111" width="202" height="14" frameX="-22" frameY="-62" frameWidth="249" frameHeight="144"/>
<SubTexture name="Text Box Appear instance 10001" x="1" y="190" width="204" height="29" frameX="-22" frameY="-64" frameWidth="249" frameHeight="144"/>
<SubTexture name="Text Box Appear instance 10002" x="219" y="1" width="204" height="46" frameX="-22" frameY="-65" frameWidth="249" frameHeight="144"/>
<SubTexture name="Text Box Appear instance 10003" x="1" y="129" width="205" height="58" frameX="-22" frameY="-66" frameWidth="249" frameHeight="144"/>
<SubTexture name="Text Box Appear instance 10004" x="1" y="129" width="205" height="58" frameX="-22" frameY="-66" frameWidth="249" frameHeight="144"/>
</TextureAtlas>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/
-->
<TextureAtlas imagePath="boyfriend.png" width="243" height="47">
<SubTexture name="Boyfriend portrait enter instance 10000" x="1" y="1" width="58" height="45" frameX="-157" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10001" x="62" y="1" width="58" height="45" frameX="-150" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10002" x="62" y="1" width="58" height="45" frameX="-150" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10003" x="123" y="1" width="58" height="45" frameX="-144" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10004" x="123" y="1" width="58" height="45" frameX="-144" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10005" x="184" y="1" width="58" height="45" frameX="-143" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10006" x="184" y="1" width="58" height="45" frameX="-143" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10007" x="184" y="1" width="58" height="45" frameX="-143" frameY="-30" frameWidth="249" frameHeight="145"/>
<SubTexture name="Boyfriend portrait enter instance 10008" x="184" y="1" width="58" height="45" frameX="-143" frameY="-30" frameWidth="249" frameHeight="145"/>
</TextureAtlas>
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ -->
<!-- HUGE THANKS to Nile that divided the angry animations from the angry box! -->
<TextureAtlas imagePath="senpai.png" width="562" height="64">
<SubTexture name="Senpai Portrait Enter instance 10000" x="110" y="1" width="51" height="57" frameX="-38" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10001" x="164" y="1" width="51" height="57" frameX="-45" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10002" x="164" y="1" width="51" height="57" frameX="-45" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10003" x="218" y="1" width="51" height="57" frameX="-48" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10004" x="218" y="1" width="51" height="57" frameX="-48" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10005" x="272" y="1" width="51" height="57" frameX="-49" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10006" x="272" y="1" width="51" height="57" frameX="-49" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10007" x="272" y="1" width="51" height="57" frameX="-49" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Enter instance 10008" x="272" y="1" width="51" height="57" frameX="-49" frameY="-17" frameWidth="249" frameHeight="145"/>
<SubTexture name="Senpai Portrait Angry instance 10000" x="501" y="1" width="60" height="62" frameX="-0" frameY="-0" frameWidth="60" frameHeight="64"/>
<SubTexture name="Senpai Portrait Angry instance 10001" x="1" y="1" width="51" height="56" frameX="-3" frameY="-2" frameWidth="60" frameHeight="64"/>
<SubTexture name="Senpai Portrait Angry instance 10002" x="441" y="1" width="57" height="60" frameX="-0" frameY="-1" frameWidth="60" frameHeight="64"/>
<SubTexture name="Senpai Portrait Angry instance 10003" x="55" y="1" width="52" height="57" frameX="-2" frameY="-6" frameWidth="60" frameHeight="64"/>
<SubTexture name="Senpai Portrait Angry instance 10004" x="326" y="1" width="54" height="58" frameX="-1" frameY="-4" frameWidth="60" frameHeight="64"/>
<SubTexture name="Senpai Portrait Angry instance 10005" x="326" y="1" width="54" height="58" frameX="-1" frameY="-4" frameWidth="60" frameHeight="64"/>
<SubTexture name="Senpai Portrait Angry instance 10006" x="383" y="1" width="55" height="59" frameX="-1" frameY="-5" frameWidth="60" frameHeight="64"/>
<SubTexture name="Senpai Portrait Angry instance 10007" x="383" y="1" width="55" height="59" frameX="-1" frameY="-5" frameWidth="60" frameHeight="64"/>
</TextureAtlas>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="dialogueBox-evil.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="Spirit Textbox spawn instance 10000" x="0" y="0" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10001" x="0" y="0" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10002" x="259" y="0" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10003" x="259" y="0" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10004" x="0" y="155" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10005" x="0" y="155" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10006" x="259" y="155" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10007" x="259" y="155" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10008" x="0" y="310" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10009" x="0" y="310" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10010" x="259" y="310" width="249" height="145"/>
<SubTexture name="Spirit Textbox spawn instance 10011" x="259" y="310" width="249" height="145"/>
</TextureAtlas>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="dialogueBox-pixel.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="Text Box Appear instance 10000" x="0" y="0" width="249" height="144"/>
<SubTexture name="Text Box Appear instance 10001" x="259" y="0" width="249" height="144"/>
<SubTexture name="Text Box Appear instance 10002" x="0" y="154" width="249" height="144"/>
<SubTexture name="Text Box Appear instance 10003" x="259" y="154" width="249" height="144"/>
<SubTexture name="Text Box Appear instance 10004" x="259" y="154" width="249" height="144"/>
</TextureAtlas>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="dialogueBox-senpaiMad.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10000" x="0" y="0" width="249" height="144"/>
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10001" x="259" y="0" width="249" height="144"/>
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10002" x="0" y="154" width="249" height="144"/>
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10003" x="259" y="154" width="249" height="144"/>
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10004" x="0" y="308" width="249" height="144"/>
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10005" x="0" y="308" width="249" height="144"/>
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10006" x="259" y="308" width="249" height="144"/>
<SubTexture name="SENPAI ANGRY IMPACT SPEECH instance 10007" x="259" y="308" width="249" height="144"/>
</TextureAtlas>
+8
View File
@@ -0,0 +1,8 @@
function next(first:Bool) {
if(first && canProceed) {
canProceed = false;
dialogueLines[0].playSound.play();
new FlxTimer().start(1, (_) -> this.next(true));
}
else if(this.lastLine == null) canProceed = true; // If its not null means the dialogue is not at the first one! - Nex
}
+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE codename-engine-dialogue>
<dialogue box="hating-simulator">
<!-- All characters -->
<char name="senpai" char="senpai" position="left" defaultAnim="angry" />
<char name="pixelBf" char="pixelBf" position="right" />
<!-- Lines -->
<line char="senpai" playSound="pixel/ANGRY_TEXT_BOX" speed="0.08" textSound="pixel/ANGRY" bubble="angry">
Not bad for an ugly worm.
</line>
<line char="senpai">
But this time I'll rip your nuts off right after your girlfriend finishes gargling mine.
</line>
<line char="pixelBf">
Bop beep be be skdoo bep
</line>
</dialogue>
+1 -5
View File
@@ -1,4 +1,3 @@
static var roses_shouldPlayOnThorns = false;
var self = this;
__script__.setParent(PlayState.instance);
@@ -36,10 +35,7 @@ function create() {
FlxG.camera.followLerp = 0;
FlxTween.tween(camGame.scroll, {x: -366, y: 192}, 3, {
ease: FlxEase.cubeInOut,
onComplete: (_) -> {
roses_shouldPlayOnThorns = true;
self.close();
}
onComplete: (_) -> {self.close();}
});
}
});
+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE codename-engine-dialogue>
<dialogue box="hating-simulator">
<!-- All characters -->
<char name="senpai" char="senpai" position="left" />
<char name="pixelBf" char="pixelBf" position="right" />
<!-- Lines -->
<line char="senpai" changeMusic="pixel/Lunchbox">
Ah, a new fair maiden has come in search of true love!
</line>
<line char="senpai">
A serenade between gentlemen shall decide where her beautiful heart shall reside.
</line>
<line char="pixelBf">
Beep bo bop
</line>
</dialogue>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+8 -2
View File
@@ -7,8 +7,8 @@
<char name="niko" char="niko" position="middle" />
<!-- Lines -->
<line char="dad" stay="true">
yo its me your enemy! how are you
<line char="dad">
yo its me your <format color="#FF0000">enemy!</format> how are you
</line>
<line char="boyfriend">
shut up u stupid bitch
@@ -59,4 +59,10 @@
<line char="boyfriend">
ok now stfu and lets rap its getting annoying
</line>
<line char="girlfriend">
Wanna <format font="pixel.otf" size="10">have <format color="#33ff33">sex</format> later</format> cutie
</line>
<line char="boyfriend">
bro <format color="#FF0000">what</format>
</line>
</dialogue>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
<!DOCTYPE codename-engine-dialogue>
<dialogue box="spirit-creepy-box">
<!-- All characters -->
<char name="senpai" char="senpai" position="left" />
<!-- Lines -->
<line char="senpai" changeMusic="pixel/LunchboxScary">
Direct contact with real humans, after being trapped in here for so long...
</line>
<line char="senpai">
and HER of all people.
</line>
<line char="senpai">
I'll make her father pay for what he's done to me and all the others,,,,
</line>
<line char="senpai">
I'll beat you and make you take my place.
</line>
<line char="senpai">
You don't mind your bodies being borrowed right? It's only fair...
</line>
</dialogue>
+5 -24
View File
@@ -1,6 +1,5 @@
import flixel.addons.util.FlxSimplex;
static var roses_shouldPlayOnThorns = false;
var self = this;
__script__.setParent(PlayState.instance);
@@ -9,13 +8,6 @@ var senpaiEvil:FlxSprite;
var dfx = 0;
var dfy = 0;
function create() {
// Cutscene stuff
if(!roses_shouldPlayOnThorns) {
disableScript();
self.close();
return;
}
roses_shouldPlayOnThorns = false;
camHUD.visible = false;
var red:FlxSprite = new FlxSprite().makeSolid(FlxG.width + 100, FlxG.height + 100, 0xFFff1b31);
@@ -36,12 +28,6 @@ function create() {
dfy = senpaiEvil.y;
add(senpaiEvil);
var white:FlxSprite = new FlxSprite().makeSolid(FlxG.width + 100, FlxG.height + 100, FlxColor.WHITE);
white.screenCenter();
white.scrollFactor.set();
white.alpha = 0;
add(white);
new FlxTimer().start(3.2, function(deadTime:FlxTimer)
{
FlxG.camera.fade(FlxColor.WHITE, 1.6, false, function() {
@@ -50,25 +36,20 @@ function create() {
remove(red, true);
red.destroy();
white.alpha = 1;
FlxG.camera._fxFadeAlpha = 1;
});
});
scream = FlxG.sound.play(Paths.sound('cutscenes/weeb/Senpai_Dies'), 1, false, null, true, function()
{
new FlxTimer().start(0.2, function(swagTimer:FlxTimer) {
white.alpha -= 0.15;
new FlxTimer().start(0.4, function(swagTimer:FlxTimer) {
FlxG.camera._fxFadeAlpha -= 0.15;
if(white.alpha > 0) swagTimer.reset();
if(FlxG.camera._fxFadeAlpha > 0.3) swagTimer.reset();
else {
remove(white, true);
white.destroy();
camHUD.visible = true;
self.close();
self.startDialogue("assets/songs/" + PlayState.instance.SONG.meta.name.toLowerCase() + "/creepyDialogue.xml", self.close);
}
});
FlxG.camera.fade(FlxColor.WHITE, 0.0001, true);
});
}
+1
View File
@@ -0,0 +1 @@
{"events":[{"params":[1],"time":5052.63157894737,"name":"Camera Movement"},{"params":[0],"time":10105.2631578947,"name":"Camera Movement"},{"params":[1],"time":15157.8947368421,"name":"Camera Movement"},{"params":[0],"time":20210.5263157895,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":20210.5263157895,"name":"HScript Call"},{"params":[1],"time":25263.1578947368,"name":"Camera Movement"},{"params":[0],"time":30315.7894736842,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":30315.7894736842,"name":"HScript Call"},{"params":[1],"time":35368.4210526316,"name":"Camera Movement"},{"params":[0],"time":40421.0526315789,"name":"Camera Movement"},{"params":[1],"time":45473.6842105263,"name":"Camera Movement"},{"params":[0],"time":50526.3157894737,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":50526.3157894737,"name":"HScript Call"},{"params":[1],"time":55578.947368421,"name":"Camera Movement"},{"params":[0],"time":60631.5789473684,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":60631.5789473684,"name":"HScript Call"},{"params":[1],"time":65684.2105263157,"name":"Camera Movement"},{"params":[0],"time":70736.8421052631,"name":"Camera Movement"},{"params":[1],"time":75789.4736842105,"name":"Camera Movement"},{"params":[0],"time":80842.1052631578,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":80842.1052631578,"name":"HScript Call"},{"params":[1],"time":85894.7368421052,"name":"Camera Movement"},{"params":[0],"time":90947.3684210525,"name":"Camera Movement"},{"params":["aberrationCoolThing",""],"time":90947.3684210525,"name":"HScript Call"},{"params":[1],"time":95999.9999999999,"name":"Camera Movement"}]}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -7
View File
@@ -34,18 +34,14 @@ class Update {
}
for(lib in libs) {
// install libs
var globalism = lib.global == "true" ? "--global" : "";
var globalism:Null<String> = lib.global == "true" ? "--global" : null;
switch(lib.type) {
case "lib":
prettyPrint((lib.global == "true" ? "Globally installing" : "Locally installing") + ' "${lib.name}"...');
Sys.command('haxelib $globalism install ${lib.name} ${lib.version != null ? " " + lib.version : " "}');
Sys.command('haxelib install ${lib.name} ${lib.version != null ? " " + lib.version : " "}${globalism != null ? ' $globalism' : ''} --always');
case "git":
prettyPrint((lib.global == "true" ? "Globally installing" : "Locally installing") + ' "${lib.name}" from git url "${lib.url}"');
if (lib.ref != null)
Sys.command('haxelib $globalism git ${lib.name} ${lib.url} ${lib.ref}');
else
Sys.command('haxelib $globalism git ${lib.name} ${lib.url}');
Sys.command('haxelib git ${lib.name} ${lib.url}${lib.ref != null ? ' ${lib.ref}' : ''}${globalism != null ? ' $globalism' : ''} --always');
default:
prettyPrint('Cannot resolve library of type "${lib.type}"');
}
+1 -1
View File
@@ -9,7 +9,7 @@
<git name="flixel-addons" url="https://github.com/FNF-CNE-Devs/flixel-addons" />
<!-- Other Libraries -->
<git name="hscript-improved" url="https://www.github.com/FNF-CNE-Devs/hscript-improved" ref="custom-classes" />
<git name="hscript-improved" url="https://github.com/FNF-CNE-Devs/hscript-improved" ref="custom-classes" />
<git name="discord-rpc" url="https://github.com/Aidan63/linc_discord-rpc" />
<git name="flxanimate" url="https://github.com/FNF-CNE-Devs/flxanimate" />
<lib name="hxvlc" />
+13 -5
View File
@@ -13,6 +13,7 @@ import flixel.FlxG;
* Help from Ne_Eo
* @see https://twitter.com/Ne_Eo_Twitch
*/
@:access(away3d.core.managers.Stage3DManager)
class Flx3DUtil
{
#if THREE_D_SUPPORT
@@ -25,18 +26,25 @@ class Flx3DUtil
return FlxG.stage.stage3Ds.length;
}
/**
* Returns the total amount of 3D stages that are in use
* @return Int
*/
public static inline function getUsed3D():Int
{
return Stage3DManager._numStageProxies;
}
/**
* Returns if a Stage3D is available
* @return Bool
*/
public static inline function is3DAvailable():Bool
{
@:privateAccess {
if (Stage3DManager._stageProxies == null)
return true;
if (Stage3DManager._stageProxies == null)
return true;
return Stage3DManager._numStageProxies < Stage3DManager._stageProxies.length;
}
return Stage3DManager._numStageProxies < Stage3DManager._stageProxies.length;
}
/**
+1 -1
View File
@@ -36,7 +36,7 @@ class Flx3DView extends FlxView3D {
var meshes:Array<Mesh> = [];
public function new(x:Float = 0, y:Float = 0, width:Int = -1, height:Int = -1) {
if (!Flx3DUtil.is3DAvailable())
throw "[Flx3DView] 3D is not available on this platform. Stages in use: " + Flx3DUtil.getTotal3D() + ", Max stages allowed: " + FlxG.stage.stage3Ds.length + ".";
throw "[Flx3DView] 3D is not available on this platform. Stages in use: " + Flx3DUtil.getUsed3D() + ", Max stages allowed: " + Flx3DUtil.getTotal3D() + ".";
super(x, y, width, height);
__cur3DStageID = __3DIDS++;
}
+1 -1
View File
@@ -123,7 +123,7 @@ class MusicBeatState extends FlxState implements IBeatReceiver
var path = Paths.script('data/states/${scriptName}/LIB_$i');
var script = Script.create(path);
if (script is DummyScript) continue;
script.fileName = '$i:$scriptName';
script.remappedNames.set(script.fileName, '$i:${script.fileName}');
stateScripts.add(script);
script.load();
}
+1 -1
View File
@@ -110,7 +110,7 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
var path = Paths.script('data/states/${scriptName}/LIB_$i');
var script = Script.create(path);
if (script is DummyScript) continue;
script.fileName = '$i:$scriptName';
script.remappedNames.set(script.fileName, '$i:${script.fileName}');
stateScripts.add(script);
script.load();
}
@@ -6,9 +6,6 @@ import lime.utils.AssetLibrary;
class AssetsLibraryList extends AssetLibrary {
public var libraries:Array<AssetLibrary> = [];
public var rootLibs:Array<AssetLibrary> = [];
public var assetsLibs:Array<AssetLibrary> = [];
@:allow(funkin.backend.system.Main)
@:allow(funkin.backend.system.MainState)
private var __defaultLibraries:Array<AssetLibrary> = [];
@@ -19,7 +16,6 @@ class AssetsLibraryList extends AssetLibrary {
libraries.remove(lib);
return lib;
}
public function existsSpecific(id:String, type:String, source:AssetSource = BOTH) {
if (!id.startsWith("assets/") && exists('assets/$id', type))
return true;
@@ -98,29 +94,23 @@ class AssetsLibraryList extends AssetLibrary {
return content;
}
public function getSpecificAsset(id:String, type:String, source:AssetSource = BOTH, ?avoidRoot:Bool = false):Dynamic {
public function getSpecificAsset(id:String, type:String, source:AssetSource = BOTH):Dynamic {
try {
if (!id.startsWith("assets/")) {
var ass = getSpecificAsset('assets/$id', type, source);
if (ass != null)
if (ass != null) {
return ass;
} else {
for (lib in assetsLibs) {
if (shouldSkipLib(libraries.indexOf(lib), source)) continue;
var asset = lib.getAsset(id, type);
if (asset != null)
return asset;
}
}
for(k=>e in libraries) {
if (shouldSkipLib(k, source)) continue;
if (!avoidRoot) {
for (lib in rootLibs) {
if (shouldSkipLib(libraries.indexOf(lib), source)) continue;
var asset = lib.getAsset(id, type);
if (asset != null)
@:privateAccess
if (e.exists(id, e.types.get(id))) {
var asset = e.getAsset(id, type);
if (asset != null) {
return asset;
}
}
}
return null;
@@ -140,9 +130,6 @@ class AssetsLibraryList extends AssetLibrary {
public override inline function getAsset(id:String, type:String):Dynamic
return getSpecificAsset(id, type, BOTH);
public inline function getAssetSafe(id:String, type:String):Dynamic
return getSpecificAsset(id, type, BOTH, true);
public override function isLocal(id:String, type:String) {
return true;
}
@@ -171,17 +158,8 @@ class AssetsLibraryList extends AssetLibrary {
addLibrary(d);
}
public function addLibrary(lib:AssetLibrary) @:privateAccess {
public function addLibrary(lib:AssetLibrary) {
libraries.insert(0, lib);
var finalLib:AssetLibrary = lib;
if (finalLib is openfl.utils.AssetLibrary)
finalLib = cast(finalLib, openfl.utils.AssetLibrary).__proxy;
if (finalLib is IModsAssetLibrary)
assetsLibs.insert(0, finalLib);
else
rootLibs.insert(0, finalLib);
return lib;
}
}
+17
View File
@@ -302,6 +302,23 @@ class Paths
return content;
*/
}
// Used in Script.hx
@:noCompletion public static function getFilenameFromLibFile(path:String) {
var file = new haxe.io.Path(path);
if(file.file.startsWith("LIB_")) {
return file.dir + "." + file.ext;
}
return path;
}
@:noCompletion public static function getLibFromLibFile(path:String) {
var file = new haxe.io.Path(path);
if(file.file.startsWith("LIB_")) {
return file.file.substr(4);
}
return "";
}
}
class ScriptPathInfo {
+29 -3
View File
@@ -18,6 +18,19 @@ class Chart {
*/
public inline static var defaultColor:FlxColor = 0xFF9271FD;
public static function loadEventsJson(songName:String) {
var path = Paths.file('songs/${songName.toLowerCase()}/events.json');
var data:Array<ChartEvent> = null;
if (Assets.exists(path)) {
try {
data = Json.parse(Assets.getText(path)).events;
} catch(e) {
Logs.trace('Failed to load song event data for ${songName} ($path): ${Std.string(e)}', ERROR);
}
}
return data;
}
public static function loadChartMeta(songName:String, difficulty:String = "normal", fromMods:Bool = true) {
var metaPath = Paths.file('songs/${songName.toLowerCase()}/meta.json');
var metaDiffPath = Paths.file('songs/${songName.toLowerCase()}/meta-${difficulty.toLowerCase()}.json');
@@ -117,6 +130,7 @@ class Chart {
3 => "Alt Animation Toggle",
];
if (data.events == null) data.events = [];
for (event in cast(data.events, Array<Dynamic>)) {
if (Reflect.hasField(event, "type")) {
if(event.type != null)
@@ -145,6 +159,16 @@ class Chart {
}
base.meta = loadedMeta;
}
/**
* events.json LOADING
*/
#if REGION
var extraEvents:Array<ChartEvent> = loadEventsJson(songName);
if (extraEvents != null)
base.events = base.events.concat(extraEvents);
#end
return base;
}
@@ -172,7 +196,7 @@ class Chart {
public static function save(songFolderPath:String, chart:ChartData, difficulty:String = "normal", ?saveSettings:ChartSaveSettings):ChartData {
if (saveSettings == null) saveSettings = {};
var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart);
var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveEventsInChart);
var meta = filteredChart.meta;
#if sys
@@ -191,15 +215,16 @@ class Chart {
return filteredChart;
}
public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Null<Bool>):ChartData {
public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Null<Bool>, ?saveEventsInChart:Null<Bool>):ChartData {
var data = Reflect.copy(chart); // make a copy of the chart to leave the OG intact
if (saveMetaInChart != true) {
data.meta = null;
} else {
data.meta = Reflect.copy(chart.meta); // also make a copy of the metadata to leave the OG intact.
if (data.meta != null) data.meta.parsedColor = null;
if(data.meta != null && Reflect.hasField(data.meta, "parsedColor")) Reflect.deleteField(data.meta, "parsedColor");
}
data.events = saveEventsInChart != true ? null : Reflect.copy(chart.events); // same here once again
data.fromMods = null;
var sortedData:Dynamic = {};
@@ -215,5 +240,6 @@ class Chart {
typedef ChartSaveSettings = {
var ?overrideExistingMeta:Bool;
var ?saveMetaInChart:Bool;
var ?saveEventsInChart:Bool;
var ?prettyPrint:Bool;
}
@@ -94,7 +94,7 @@ class GlobalScript {
var path = Paths.script('data/global/LIB_$i');
var script = Script.create(path);
if (script is DummyScript) continue;
script.fileName = '$i:global.hx';
script.remappedNames.set(script.fileName, '$i:${script.fileName}');
scripts.add(script);
script.load();
}
+13 -14
View File
@@ -1,21 +1,17 @@
package funkin.backend.scripting;
import haxe.io.Path;
import hscript.Expr.ClassDecl;
import hscript.Expr.ModuleDecl;
import hscript.Expr.Error;
import hscript.Parser;
import openfl.Assets;
import lime.utils.AssetType;
import hscript.*;
class HScript extends Script {
public var interp:Interp;
public var parser:Parser;
public var expr:Expr;
public var decls:Array<ModuleDecl> = null;
public var code:String;
public var folderlessPath:String;
public var code:String = null;
//public var folderlessPath:String;
var __importedPaths:Array<String>;
public static function initParser() {
@@ -30,10 +26,12 @@ class HScript extends Script {
interp = new Interp();
// We use getAssetSafe to prevent crash from empty file (null which would check root (big crash)) -lunar
code = Assets.exists(path) ? Paths.assetsTree.getAssetSafe(path, TEXT) : null;
try {
if(Assets.exists(rawPath)) code = Assets.getText(rawPath);
} catch(e) Logs.trace('Error while reading $path: ${Std.string(e)}', ERROR);
parser = initParser();
folderlessPath = Path.directory(path);
//folderlessPath = Path.directory(path);
__importedPaths = [path];
interp.errorHandler = _errorHandler;
@@ -48,14 +46,13 @@ class HScript extends Script {
}));
funkin.backend.scripting.GlobalScript.call("onScriptCreated", [this, "hscript"]);
if (code != null && code.trim() != "")
loadFromString(code);
loadFromString(code);
}
public override function loadFromString(code:String) {
try {
expr = parser.parseString(code, fileName);
if (code != null && code.trim() != "")
expr = parser.parseString(code, fileName);
} catch(e:Error) {
_errorHandler(e);
} catch(e) {
@@ -94,7 +91,9 @@ class HScript extends Script {
}
private function _errorHandler(error:Error) {
var fileName = error.origin;
if(remappedNames.exists(fileName))
fileName = remappedNames.get(fileName);
var fn = '$fileName:${error.line}: ';
var err = error.toString();
if (err.startsWith(fn)) err = err.substr(fn.length);
+19
View File
@@ -124,13 +124,22 @@ class Script extends FlxBasic implements IFlxDestroyable {
*/
public var fileName:String;
/**
* Script Extension
*/
public var extension:String;
/**
* Path to the script.
*/
public var path:String = null;
private var rawPath:String = null;
private var didLoad:Bool = false;
public var remappedNames:Map<String, String> = [];
/**
* Creates a script from the specified asset path. The language is automatically determined.
* @param path Path in assets
@@ -177,7 +186,11 @@ class Script extends FlxBasic implements IFlxDestroyable {
public function new(path:String) {
super();
rawPath = path;
path = Paths.getFilenameFromLibFile(path);
fileName = Path.withoutDirectory(path);
extension = Path.extension(path);
this.path = path;
onCreate(path);
for(k=>e in getDefaultVariables(this)) {
@@ -223,6 +236,9 @@ class Script extends FlxBasic implements IFlxDestroyable {
* Traces something as this script.
*/
public function trace(v:Dynamic) {
var fileName = this.fileName;
if(remappedNames.exists(fileName))
fileName = remappedNames.get(fileName);
Logs.traceColored([
Logs.logText('${fileName}: ', GREEN),
Logs.logText(Std.string(v))
@@ -280,6 +296,9 @@ class Script extends FlxBasic implements IFlxDestroyable {
* @param additionalInfo Additional information you could provide.
*/
public function error(text:String, ?additionalInfo:Dynamic):Void {
var fileName = this.fileName;
if(remappedNames.exists(fileName))
fileName = remappedNames.get(fileName);
Logs.traceColored([
Logs.logText(fileName, RED),
Logs.logText(text)
@@ -7,7 +7,7 @@ final class AmountEvent extends CancellableEvent {
public var amount:Int;
/**
* Shows wether or not psych users complained about this class
* Shows whether or not psych users complained about this class
*/
public var psychUsersComplained:Bool = true;
}
}
@@ -0,0 +1,12 @@
package funkin.backend.scripting.events;
/**
* Event with random dynamic values useful for making your own custom events
*/
final class DynamicEvent extends CancellableEvent {
public var value1:Dynamic = null;
public var value2:Dynamic = null;
public var value3:Dynamic = null;
public var value4:Dynamic = null;
public var value5:Dynamic = null;
}
@@ -16,7 +16,7 @@ final class GameOverCreationEvent extends CancellableEvent
public var gameOverSong:String;
public var bpm:Int;
public var bpm:Float;
public var lossSFX:String;
@@ -0,0 +1,10 @@
package funkin.backend.scripting.events.dialogue;
import funkin.game.cutscenes.dialogue.DialogueCharacter;
final class DialogueBoxCharPopupEvent extends CancellableEvent
{
public var char:DialogueCharacter;
public var force:Bool;
}
@@ -0,0 +1,25 @@
package funkin.backend.scripting.events.dialogue;
import funkin.backend.utils.XMLUtil.TextFormat;
import flixel.sound.FlxSound;
final class DialogueBoxPlayBubbleEvent extends CancellableEvent
{
public var bubble:String;
public var suffix:String;
public var text:String;
public var format:Array<TextFormat>;
public var speed:Float;
public var customSFX:FlxSound;
public var customTypeSFX:Array<FlxSound>;
public var setTextAfter:Bool;
public var allowDefault:Bool;
}
@@ -0,0 +1,15 @@
package funkin.backend.scripting.events.dialogue;
import funkin.backend.utils.XMLUtil.TextFormat;
import flixel.sound.FlxSound;
final class DialogueBoxSetTextEvent extends CancellableEvent
{
public var text:String;
public var format:Array<TextFormat>;
public var speed:Null<Float>;
public var customTypeSFX:Array<FlxSound>;
}
@@ -0,0 +1,15 @@
package funkin.backend.scripting.events.dialogue;
import haxe.xml.Access;
/**
* CANCEL this event to customize from 0 the xml structure part!
*/
final class DialogueBoxStructureEvent extends CancellableEvent
{
public var name:String;
public var textTypeSFX:String;
public var dialogueBoxData:Access;
}
@@ -0,0 +1,10 @@
package funkin.backend.scripting.events.dialogue;
import funkin.game.cutscenes.dialogue.DialogueCharacter.DialogueCharAnimContext;
final class DialogueCharHideEvent extends CancellableEvent
{
public var animation:String;
public var lastAnimContext:DialogueCharAnimContext;
}
@@ -0,0 +1,14 @@
package funkin.backend.scripting.events.dialogue;
import funkin.game.cutscenes.dialogue.DialogueCharacter.DialogueCharAnimContext;
final class DialogueCharShowEvent extends CancellableEvent
{
public var x:Float;
public var y:Float;
public var animation:String;
public var lastAnimContext:DialogueCharAnimContext;
}
@@ -0,0 +1,15 @@
package funkin.backend.scripting.events.dialogue;
import haxe.xml.Access;
/**
* CANCEL this event to customize from 0 the xml structure part!
*/
final class DialogueCharStructureEvent extends CancellableEvent
{
public var name:String;
public var position:String;
public var charData:Access;
}
@@ -0,0 +1,11 @@
package funkin.backend.scripting.events.dialogue;
import haxe.xml.Access;
/**
* CANCEL this event to customize from 0 the xml structure part!
*/
final class DialogueStructureEvent extends CancellableEvent
{
public var dialogueData:Access;
}
@@ -0,0 +1,40 @@
package funkin.backend.utils;
import flixel.sound.FlxSound;
import lime.media.AudioBuffer;
// Thank you yosh :DDD -lunar
// (https://github.com/FNF-CNE-Devs/YoshiCrafterEngine/blob/main/source/WaveformSprite.hx)
class AudioAnalyzer {
public var buffer:AudioBuffer;
var __peakByte:Float = 0;
var __timeMulti:Float = 0;
public function new(sound:FlxSound) {
@:privateAccess buffer = sound._sound.__buffer;
__peakByte = Math.pow(2, buffer.bitsPerSample-1)-1;
__timeMulti = 1 / (1 / buffer.sampleRate);
__timeMulti *= buffer.bitsPerSample;
__timeMulti -= __timeMulti % buffer.bitsPerSample;
}
public function analyze(startPos:Float, endPos:Float):Float {
var bytesStartPos:Int = Math.floor(startPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
var bytesEndPos:Int = Math.floor(endPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
bytesStartPos -= bytesStartPos % buffer.bitsPerSample;
bytesEndPos -= bytesEndPos % buffer.bitsPerSample;
var maxByte:Int = 0;
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / buffer.bitsPerSample)) {
var byte:Int = buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample))
| (buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample) + 1) << 8);
if (byte > 256 * 128) byte -= 256 * 256;
if (maxByte < byte) maxByte = byte;
}
return maxByte/__peakByte;
}
}
+2 -2
View File
@@ -19,7 +19,7 @@ class BitmapUtil {
for(x in 0...bmap.width) {
color = bmap.getPixel32(x, y);
fixedColor = 0xFF000000 + (color % 0x1000000);
if (colorMap[fixedColor] == null)
if (!colorMap.exists(fixedColor))
colorMap[fixedColor] = 0;
colorMap[fixedColor] += color.alphaFloat;
}
@@ -50,7 +50,7 @@ class BitmapUtil {
for(x in 0...bmap.width) {
color = bmap.getPixel32(x, y);
fixedColor = 0xFF000000 + (color % 0x1000000);
if (colorMap[fixedColor] == null)
if (!colorMap.exists(fixedColor))
colorMap[fixedColor] = 0;
colorMap[fixedColor] += color.alphaFloat * 0.33 + (0.67 * (color.saturation * (2 * (color.lightness > 0.5 ? 0.5 - (color.lightness) : color.lightness))));
}
+45 -1
View File
@@ -1,5 +1,8 @@
package funkin.backend.utils;
import flixel.text.FlxText;
import openfl.text.TextFormatAlign;
import funkin.backend.utils.XMLUtil.TextFormat;
import flixel.tweens.FlxTween;
import flixel.system.frontEnds.SoundFrontEnd;
import flixel.sound.FlxSound;
@@ -457,6 +460,47 @@ class CoolUtil
return xml.att.resolve(name);
}
/**
* Sets automatically all the compatible formats to a text.
*
* WARNING: These are dependant from the font, so if the font doesn't support for example the `bold` format it won't work!
* @param text Text to set the format for
* @param formats Array of the formats (to get the formats from a node, you can use `XMLUtil.getTextFormats(node)`)
*/
public static function autoSetFormat(text:FlxText, formats:Array<TextFormat>) {
var i = 0;
@:privateAccess
for(format in formats) {
var fmtt = format.format;
var start = i;
var end = i + format.text.length;
i = end;
if(Reflect.fields(fmtt).length == 0) continue;
var fmt = new FlxTextFormat();
fmt.format.color = Reflect.hasField(fmtt, "color") ? FlxColor.fromString(fmtt.color) : text.color;
fmt.format.font = Reflect.hasField(fmtt, "font") ? Paths.font(fmtt.font) : text.font;
fmt.format.size = Reflect.hasField(fmtt, "size") ? Std.parseInt(fmtt.size) : text.size;
fmt.format.italic = Reflect.hasField(fmtt, "italic") ? fmtt.italic == "true" : text.italic;
fmt.format.bold = Reflect.hasField(fmtt, "bold") ? fmtt.bold == "true" : text.bold;
fmt.borderColor = Reflect.hasField(fmtt, "borderColor") ? FlxColor.fromString(fmtt.borderColor) : text.borderColor;
fmt.format.align = Reflect.hasField(fmtt, "align") ? TextFormatAlign.fromString(fmtt.align) : FlxTextAlign.toOpenFL(text.alignment);
if(Reflect.hasField(fmtt, "leading")) fmt.format.leading = Std.parseInt(fmtt.leading);
if(Reflect.hasField(fmtt, "kerning")) fmt.format.kerning = fmtt.kerning == "true";
if(Reflect.hasField(fmtt, "blockIndent")) fmt.format.blockIndent = Std.parseInt(fmtt.blockIndent);
if(Reflect.hasField(fmtt, "bullet")) fmt.format.bullet = fmtt.bullet == "true";
if(Reflect.hasField(fmtt, "indent")) fmt.format.indent = Std.parseInt(fmtt.indent);
if(Reflect.hasField(fmtt, "leftMargin")) fmt.format.leftMargin = Std.parseInt(fmtt.leftMargin);
if(Reflect.hasField(fmtt, "letterSpacing")) fmt.format.letterSpacing = Std.parseFloat(fmtt.letterSpacing);
if(Reflect.hasField(fmtt, "rightMargin")) fmt.format.rightMargin = Std.parseInt(fmtt.rightMargin);
if(Reflect.hasField(fmtt, "tabStops")) fmt.format.tabStops = [for(x in cast(fmtt.tabStops, String).split(",")) Std.parseInt(x)];
if(Reflect.hasField(fmtt, "underline")) fmt.format.underline = fmtt.underline == "true";
text.addFormat(fmt, start, end);
}
return text;
}
/**
* Loads an animated graphic, and automatically animates it.
* @param spr Sprite to load the graphic for
@@ -618,7 +662,7 @@ class CoolUtil
frontEnd.music = music;
}
public static inline function flxeaseFromString(mainEase:String, suffix:String)
@:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String)
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix));
}
+94 -17
View File
@@ -1,14 +1,17 @@
package funkin.backend.utils;
import flixel.animation.FlxAnimation;
import funkin.backend.FunkinSprite;
import funkin.backend.system.ErrorCode;
import funkin.backend.FunkinSprite.XMLAnimType;
import funkin.backend.system.interfaces.IBeatReceiver;
import flixel.util.FlxColor;
import haxe.xml.Access;
import flixel.util.typeLimit.OneOfTwo;
import funkin.backend.system.interfaces.IOffsetCompatible;
using StringTools;
typedef TextFormat = { text:String, format:Dynamic }
/**
* Class made to make XML parsing easier.
*/
@@ -61,16 +64,14 @@ class XMLUtil {
}
/**
* Creates a new sprite based on a XML node.
* Overrides a sprite based on a XML node.
*/
public static function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>):FunkinSprite {
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT):FunkinSprite {
if (parentFolder == null) parentFolder = "";
var spr:FunkinSprite = cl != null ? Type.createInstance(cl, []) : new FunkinSprite();
spr.name = node.getAtt("name");
spr.antialiasing = true;
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite")}', null, true));
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
spr.spriteAnimType = defaultAnimType;
if (node.has.type) {
@@ -88,15 +89,14 @@ class XMLUtil {
if (node.has.scroll) {
var scroll:Null<Float> = Std.parseFloat(node.att.scroll);
if (scroll.isNotNull()) spr.scrollFactor.set(scroll, scroll);
} else {
if (node.has.scrollx) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrollx);
if (scroll.isNotNull()) spr.scrollFactor.x = scroll;
}
if (node.has.scrolly) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrolly);
if (scroll.isNotNull()) spr.scrollFactor.y = scroll;
}
}
if (node.has.scrollx) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrollx);
if (scroll.isNotNull()) spr.scrollFactor.x = scroll;
}
if (node.has.scrolly) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrolly);
if (scroll.isNotNull()) spr.scrollFactor.y = scroll;
}
if (node.has.skewx) {
var skew:Null<Float> = Std.parseFloat(node.att.skewx);
@@ -107,6 +107,14 @@ class XMLUtil {
if (skew.isNotNull()) spr.skew.y = skew;
}
if (node.has.antialiasing) spr.antialiasing = node.att.antialiasing == "true";
if (node.has.width) {
var width:Null<Float> = Std.parseFloat(node.att.width);
if (width.isNotNull()) spr.width = width;
}
if (node.has.height) {
var height:Null<Float> = Std.parseFloat(node.att.height);
if (height.isNotNull()) spr.height = height;
}
if (node.has.scale) {
var scale:Null<Float> = Std.parseFloat(node.att.scale);
if (scale.isNotNull()) spr.scale.set(scale, scale);
@@ -119,14 +127,29 @@ class XMLUtil {
var scale:Null<Float> = Std.parseFloat(node.att.scaley);
if (scale.isNotNull()) spr.scale.y = scale;
}
if (node.has.graphicSize) {
var graphicSize:Null<Int> = Std.parseInt(node.att.graphicSize);
if (graphicSize.isNotNull()) spr.setGraphicSize(graphicSize, graphicSize);
}
if (node.has.graphicSizex) {
var graphicSizex:Null<Int> = Std.parseInt(node.att.graphicSizex);
if (graphicSizex.isNotNull()) spr.setGraphicSize(graphicSizex);
}
if (node.has.graphicSizey) {
var graphicSizey:Null<Int> = Std.parseInt(node.att.graphicSizey);
if (graphicSizey.isNotNull()) spr.setGraphicSize(0, graphicSizey);
}
if (node.has.updateHitbox && node.att.updateHitbox == "true") spr.updateHitbox();
if(node.has.zoomfactor)
if (node.has.zoomfactor)
spr.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefault(spr.zoomFactor);
if (node.has.alpha)
spr.alpha = Std.parseFloat(node.getAtt("alpha")).getDefault(spr.alpha);
if(node.has.color)
spr.color = FlxColor.fromString(node.getAtt("color")).getDefault(0xFFFFFFFF);
if (node.has.playOnCountdown)
spr.skipNegativeBeats = node.att.playOnCountdown == "true";
if (node.has.beatInterval)
@@ -156,6 +179,15 @@ class XMLUtil {
return spr;
}
/**
* Creates a new sprite based on a XML node.
*/
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>):FunkinSprite {
if(cl == null) cl = FunkinSprite;
if(args == null) args = [];
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType);
}
public static function extractAnimFromXML(anim:Access, animType:XMLAnimType = NONE, loop:Bool = false):AnimData {
var animData:AnimData = {
name: null,
@@ -251,6 +283,51 @@ class XMLUtil {
var v:String;
return [for(l in text.split("\n")) if ((v = l.trim()) != "") v].join("\n");
}
/**
* WARNING: will edit directly the node!
*/
public static function fixSpacingInNode(node:Access):Access {
var arr = [for(x in node.x) x];
for(i => n in arr) {
if(n.nodeType == PCData) {
if(i == 0) n.nodeValue = n.nodeValue.ltrim();
if(i == arr.length - 1) n.nodeValue = n.nodeValue.rtrim();
if(n.nodeValue.contains("\n")) {
var a = n.nodeValue.split("\n");
n.nodeValue = [for(i => x in a) i == 0 ? x.rtrim() : i == arr.length - 1 ? x.ltrim() : x.trim()].join("\n");
}
}
}
return node;
}
public static function getTextFormats(_node:OneOfTwo<Xml, Access>, currentFormat:Dynamic = null, parsedSegments:Array<TextFormat> = null):Array<TextFormat> {
var node:Xml = cast _node;
if (currentFormat == null)
currentFormat = {};
if (parsedSegments == null)
parsedSegments = [];
for (child in node) {
switch (child.nodeType) {
case Element:
if (child.nodeName == "format") {
var format:Dynamic = Reflect.copy(currentFormat);
@:privateAccess for (key => name in child.attributeMap) {
Reflect.setField(format, key, name);
}
getTextFormats(child, format, parsedSegments);
}
case PCData:
parsedSegments.push({ text: child.nodeValue, format: Reflect.copy(currentFormat) });
default:
// Ignore other node types
}
}
return parsedSegments;
}
}
typedef AnimData = {
+99 -56
View File
@@ -1,5 +1,7 @@
package funkin.editors.charter;
import openfl.geom.Rectangle;
import flixel.util.FlxSpriteUtil;
import funkin.editors.ui.UIContextMenu.UIContextMenuOptionSpr;
import funkin.editors.ui.UITopMenu.UITopMenuButton;
import funkin.editors.charter.CharterStrumline;
@@ -21,6 +23,8 @@ import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
import funkin.editors.ui.UIState;
import openfl.net.FileReference;
using flixel.util.FlxColor;
class Charter extends UIState {
public static var __song:String;
static var __diff:String;
@@ -123,6 +127,27 @@ class Charter extends UIState {
onSelect: _file_saveas,
},
null,
{
label: "Save Without Events",
keybind: [CONTROL, ALT, TAB, S],
onSelect: _file_save_no_events,
},
{
label: "Save Without Events As...",
keybind: [CONTROL, SHIFT, ALT, TAB, S],
onSelect: _file_saveas_no_events,
},
{
label: "Save Events Separately",
keybind: [CONTROL, TAB, S],
onSelect: _file_events_save,
},
{
label: "Save Events Separately As...",
keybind: [CONTROL, SHIFT, TAB, S],
onSelect: _file_events_saveas,
},
null,
{
label: "Save Meta",
keybind: [CONTROL, ALT, S],
@@ -338,6 +363,8 @@ class Charter extends UIState {
uiCamera.bgColor = 0;
FlxG.cameras.add(uiCamera);
for (camera in FlxG.cameras.list) camera.antialiasing = false;
charterBG = new FunkinSprite(0, 0, Paths.image('menus/menuDesat'));
charterBG.color = 0xFF181818;
charterBG.cameras = [charterCamera];
@@ -359,10 +386,7 @@ class Charter extends UIState {
selectionBox.scrollFactor.set(1, 1);
selectionBox.incorporeal = true;
noteHoverer = new CharterNote();
noteHoverer.snappedToStrumline = noteHoverer.selectable = noteHoverer.autoAlpha = false;
@:privateAccess noteHoverer.__animSpeed = 1.25; noteHoverer.typeText.visible = false;
noteHoverer.alpha = 0; // FUCK YOU NOTE HOVERER >:(((
noteHoverer = new CharterNoteHoverer();
selectionBox.cameras = notesGroup.cameras = gridBackdrops.cameras = noteHoverer.cameras = [charterCamera];
@@ -381,7 +405,7 @@ class Charter extends UIState {
uiGroup.add(scrollBar);
songPosInfo = new UIText(FlxG.width - 30 - 400, scrollBar.y + 10, 400, "00:00\nBeat: 0\nStep: 0\nMeasure: 0\nBPM: 0\nTime Signature: 4/4");
songPosInfo.alignment = RIGHT; songPosInfo.optimized = true;
songPosInfo.alignment = RIGHT;
uiGroup.add(songPosInfo);
playBackSlider = new UISlider(FlxG.width - 160 - 26 - 20, (23/2) - (12/2), 160, 1, [{start: 0.25, end: 1, size: 0.5}, {start: 1, end: 2, size: 0.5}], true);
@@ -529,8 +553,7 @@ class Charter extends UIState {
buildNoteTypesUI();
refreshBPMSensitive();
// Cool shit :DD
__relinkSelection();
// Undo Stuffs :D
__relinkUndos();
__applyPlaytestInfo();
}
@@ -574,6 +597,7 @@ class Charter extends UIState {
else
selection = [cast n];
}
if (FlxG.mouse.justReleasedRight) {
var mousePos = FlxG.mouse.getScreenPosition(uiCamera);
if (!selection.contains(cast n))
@@ -585,7 +609,8 @@ class Charter extends UIState {
}
});
}
for(n in selection) n.selected = true;
selection = __fixSelection(selection);
for(s in selection) s.selected = true;
/**
* NOTE DRAG HANDLING
@@ -621,6 +646,8 @@ class Charter extends UIState {
if (n.handleSelection(selectionBox))
selection.push(n);
}
selection = __fixSelection(selection);
gridActionType = NONE;
}
}
@@ -645,22 +672,16 @@ class Charter extends UIState {
});
currentCursor = HAND;
} else {
var hoverOffset:FlxPoint = FlxPoint.get();
for (s in selection)
if (s.hovered) {
hoverOffset.set(mousePos.x - s.x, mousePos.y - s.y);
break;
}
dragStartPos.set(Std.int(dragStartPos.x / 40) * 40, dragStartPos.y); //credits to burgerballs
var verticalChange:Float = ((mousePos.y - hoverOffset.y) - dragStartPos.y) / 40;
dragStartPos.set(Std.int(dragStartPos.x / 40) * 40, dragStartPos.y);
var verticalChange:Float = (mousePos.y - dragStartPos.y) / 40;
var horizontalChange:Int = CoolUtil.floorInt((mousePos.x - dragStartPos.x) / 40);
var undoDrags:Array<SelectionDragChange> = [];
for (s in selection) {
if (s.draggable) {
var changePoint:FlxPoint = FlxPoint.get(verticalChange, horizontalChange);
if (!FlxG.keys.pressed.SHIFT) changePoint.x -= ((s.step + verticalChange) - quantStepRounded(s.step+verticalChange));
if (!FlxG.keys.pressed.SHIFT)
changePoint.x -= ((s.step + verticalChange) - quantStepRounded(s.step+verticalChange, verticalChange > 0 ? 0.35 : 0.65));
var boundedChange:FlxPoint = changePoint.clone();
@@ -687,12 +708,9 @@ class Charter extends UIState {
undos.addToUndo(CSelectionDrag(undoDrags));
}
hoverOffset.put();
gridActionType = NONE;
currentCursor = ARROW;
}
case NONE:
if (FlxG.mouse.justPressed)
FlxG.mouse.getWorldPosition(charterCamera, dragStartPos);
@@ -751,23 +769,6 @@ class Charter extends UIState {
if (event != null) addEventSpr.updateEdit(event);
else addEventSpr.updatePos(FlxG.keys.pressed.SHIFT ? ((mousePos.y) / 40) : quantStepRounded(mousePos.y/40));
} else addEventSpr.sprAlpha = lerp(addEventSpr.sprAlpha, 0, 0.25);
// Note Hoverer
if (mousePos.x > 0 && mousePos.x < gridBackdrops.strumlinesAmount * 160 && gridActionType == NONE && inBoundsY) {
noteHoverer.alpha = lerp(noteHoverer.alpha, 0.35, 0.25);
if (noteHoverer.id != Math.floor(mousePos.x / 40) % 4) {
@:privateAccess noteHoverer.__doAnim = false; // Un comment to restore this
noteHoverer.updatePos(
FlxMath.bound(FlxG.keys.pressed.SHIFT ? ((mousePos.y) / 40) : quantStep((mousePos.y+20)/40), 0, __endStep-1),
Math.floor(mousePos.x / 40) % 4, 0, 0, null
);
} else {
noteHoverer.step = FlxMath.bound(FlxG.keys.pressed.SHIFT ? ((mousePos.y-20) / 40) : quantStep(mousePos.y/40), 0, __endStep-1);
noteHoverer.y = noteHoverer.step * 40;
}
noteHoverer.x = lerp(noteHoverer.x, Math.floor(mousePos.x / 40) * 40, .65);
} else
noteHoverer.alpha = lerp(noteHoverer.alpha, 0, 0.25);
}
public function quantStep(step:Float):Float {
@@ -775,11 +776,14 @@ class Charter extends UIState {
return Math.floor(step/stepMulti) * stepMulti;
}
public function quantStepRounded(step:Float):Float {
public function quantStepRounded(step:Float, ?roundRatio:Float = 0.5):Float {
var stepMulti:Float = 1/(quant/16);
return Math.round(step/stepMulti) * stepMulti;
return ratioRound(step/stepMulti, roundRatio) * stepMulti;
}
public function ratioRound(val:Float, ratio:Float):Int
return Math.floor(val) + ((Math.abs(val % 1) > ratio ? 1 : 0) * (val > 0 ? 1 : -1));
public function getHoveredEvent(y:Float) {
var eventHovered:CharterEvent = null;
eventsGroup.forEach(function(e) {
@@ -1116,6 +1120,22 @@ class Charter extends UIState {
undos.save();
}
function _file_save_no_events(_) {
#if sys
saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', true);
undos.save();
return;
#end
_file_saveas(_);
}
function _file_saveas_no_events(_) {
openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false, false)), {
defaultSaveFile: '${__diff.toLowerCase()}.json'
}));
undos.save();
}
function _file_meta_save(_) {
#if sys
sys.io.File.saveContent(
@@ -1133,10 +1153,29 @@ class Charter extends UIState {
}));
}
function _file_events_save(_) {
#if sys
sys.io.File.saveContent(
'${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json',
Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events})
);
return;
#end
_file_events_saveas(_);
}
function _file_events_saveas(_) {
#if sys
openSubState(new SaveSubstate(Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}), {
defaultSaveFile: 'events.json'
}));
#end
}
#if sys
function saveTo(path:String) {
function saveTo(path:String, separateEvents:Bool = false) {
buildChart();
Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false});
Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveEventsInChart: !separateEvents});
}
#end
@@ -1426,11 +1465,10 @@ class Charter extends UIState {
if (note.step > Conductor.curMeasure*Conductor.getMeasureLength() && note.step < (Conductor.curMeasure+1)*Conductor.getMeasureLength()) note
];
}
#end
function changeNoteSustain(change:Float) {
if (selection.length <= 0 || change == 0) return;
if (selection.length <= 0 || change == 0 || gridActionType != NONE) return;
var undoChanges:Array<NoteSustainChange> = [];
for(s in selection)
@@ -1578,9 +1616,15 @@ class Charter extends UIState {
public inline function hitsoundsEnabled(id:Int)
return strumLines.members[id] != null && strumLines.members[id].hitsounds;
public inline function __fixSelection(selection:Selection):Selection {
var newSelection:Selection = new Selection();
for (s in selection) if (newSelection.indexOf(s) == -1) newSelection.push(s);
return newSelection.filter((s:ICharterSelectable) -> {return s != null;});
}
// UH OH!!! DANGER ZONE APPOARCHING !!!! LUNARS SHITTY CODE !!!! -lunar
@:noCompletion public function __fixSingleSelection(selectable:ICharterSelectable):ICharterSelectable {
@:noCompletion public function __relinkSingleSelection(selectable:ICharterSelectable):ICharterSelectable {
if (selectable is CharterNote)
return selectable.ID == -1 ? cast(selectable, CharterNote) : notesGroup.members[selectable.ID];
else if (selectable is CharterEvent)
@@ -1588,30 +1632,29 @@ class Charter extends UIState {
return null;
}
@:noCompletion public function __fixSelection(selection:Selection) @:privateAccess {
@:noCompletion public function __relinkSelection(selection:Selection) @:privateAccess {
var newSelection:Selection = new Selection();
for (i => selectable in selection)
newSelection[i] = __fixSingleSelection(selectable);
newSelection[i] = __relinkSingleSelection(selectable);
return newSelection;
}
@:noCompletion public inline function __relinkSelection()
selection = __fixSelection(selection);
@:noCompletion public inline function __relinkUndos() {
selection = __relinkSelection(selection);
for (list => changeList in [undos.undoList, undos.redoList]) {
var newChanges:Array<CharterChange> = [];
for (i => change in changeList) {
switch (change) {
case CCreateSelection(selection):
newChanges[i] = CCreateSelection(__fixSelection(selection));
newChanges[i] = CCreateSelection(__relinkSelection(selection));
case CDeleteSelection(selection):
newChanges[i] = CDeleteSelection(__fixSelection(selection));
newChanges[i] = CDeleteSelection(__relinkSelection(selection));
case CSelectionDrag(selectionDrags):
newChanges[i] = CSelectionDrag([
for (selectionDrag in selectionDrags)
{
selectable: __fixSingleSelection(selectionDrag.selectable),
selectable: __relinkSingleSelection(selectionDrag.selectable),
change: selectionDrag.change
}
]);
@@ -1619,17 +1662,17 @@ class Charter extends UIState {
newChanges[i] = CEditSustains([
for (noteChange in noteChanges)
{
note: cast(__fixSingleSelection(noteChange.note), CharterNote),
note: cast(__relinkSingleSelection(noteChange.note), CharterNote),
before: noteChange.before,
after: noteChange.after
}
]);
case CEditEvent(event, oldEvents, newEvents):
newChanges[i] = CEditEvent(cast(__fixSingleSelection(event), CharterEvent), oldEvents, newEvents);
newChanges[i] = CEditEvent(cast(__relinkSingleSelection(event), CharterEvent), oldEvents, newEvents);
case CEditSpecNotesType(notesChanged, oldNoteTypes, newNoteTypes):
newChanges[i] = CEditSpecNotesType([
for (noteChanged in notesChanged)
cast(__fixSingleSelection(noteChanged), CharterNote)
cast(__relinkSingleSelection(noteChanged), CharterNote)
], oldNoteTypes, newNoteTypes);
default: newChanges[i] = change;
}
@@ -10,6 +10,9 @@ class CharterEventTypeSelection extends UISubstateWindow {
var buttonsBG:UISliceSprite;
var buttonCameras:FlxCamera;
var upIndicator:UIText;
var downIndicator:UIText;
public function new(callback:String->Void) {
super();
this.callback = callback;
@@ -57,10 +60,23 @@ class CharterEventTypeSelection extends UISubstateWindow {
close();
}, w));
upIndicator = new UIText(0, 0, 0, "↑", 18);
upIndicator.cameras = [buttonCameras];
upIndicator.scrollFactor.set();
upIndicator.borderSize = 2;
add(upIndicator);
downIndicator = new UIText(0, 0, 0, "↓", 18);
downIndicator.cameras = [buttonCameras];
downIndicator.scrollFactor.set();
downIndicator.borderSize = 2;
add(downIndicator);
}
var sinner:Float = 0;
override function update(elapsed:Float) {
super.update(elapsed);
sinner += elapsed;
for (button in buttons)
button.selectable = buttonsBG.hovered;
@@ -73,6 +89,12 @@ class CharterEventTypeSelection extends UISubstateWindow {
if (buttons.length > 16)
buttonCameras.scroll.y = FlxMath.bound(buttonCameras.scroll.y - (buttonsBG.hovered ? FlxG.mouse.wheel : 0) * 12, 0,
(buttons[buttons.length-1].y + buttons[buttons.length-1].bHeight) - buttonCameras.height);
upIndicator.setPosition((buttonsBG.bWidth/2) - (upIndicator.fieldWidth/2), 22 + (FlxMath.fastSin(sinner*2) * 4));
downIndicator.setPosition((buttonsBG.bWidth/2) - (downIndicator.fieldWidth/2), (buttonsBG.bHeight-downIndicator.height-22) - (FlxMath.fastSin(sinner*2) * 4));
upIndicator.alpha = CoolUtil.fpsLerp(upIndicator.alpha, (buttons[0].y + buttonCameras.scroll.y > buttons[0].bHeight) ? 1 : 0, 1/3);
downIndicator.alpha = CoolUtil.fpsLerp(downIndicator.alpha, (buttonCameras.scroll.y+buttonCameras.height > buttons[buttons.length-1].y) ? 0 : 1, 1/3);
}
override function destroy() {
@@ -78,6 +78,7 @@ class CharterNote extends UISprite implements ICharterSelectable {
typeText.exists = type != 0;
typeText.text = Std.string(this.type);
typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2));
y = step * 40;
@@ -85,6 +86,7 @@ class CharterNote extends UISprite implements ICharterSelectable {
sustainSpr.updateHitbox();
sustainSpr.exists = susLength != 0;
sustainSpr.alpha = alpha;
sustainSpr.follow(this, 15, 20);
if (angleTween != null) angleTween.cancel();
@@ -0,0 +1,86 @@
package funkin.editors.charter;
class CharterNoteHoverer extends CharterNote {
public function new() {
super();
snappedToStrumline = selectable = autoAlpha = false; visible = sustainSpr.visible = false;
@:privateAccess __animSpeed = 1.25; typeText.visible = false; alpha = 0.4;
}
@:noCompletion var __mousePos:FlxPoint = FlxPoint.get();
public override function update(elapsed:Float) @:privateAccess {
FlxG.mouse.getWorldPosition(FlxG.camera, __mousePos);
switch (Charter.instance.gridActionType) {
case NONE:
var inBoundsY:Bool = (__mousePos.y > 0 && __mousePos.y < (Charter.instance.__endStep)*40);
if (__mousePos.x > 0 && __mousePos.x < Charter.instance.gridBackdrops.strumlinesAmount * 160 && inBoundsY) {
step = FlxMath.bound(FlxG.keys.pressed.SHIFT ? ((__mousePos.y-20) / 40) : Charter.instance.quantStep(__mousePos.y/40), 0, Charter.instance.__endStep-1);
id = Math.floor(__mousePos.x / 40); y = step * 40; x = id * 40; visible = true; sustainSpr.visible = typeText.visible = false;
angle = switch(animation.curAnim.curFrame = (id % 4)) {
case 0: -90;
case 1: 180;
case 2: 0;
case 3: 90;
default: 0; // how is that even possible
};
} else
visible = false;
case DRAG:
visible = sustainSpr.visible = typeText.visible = true; __doAnim = false;
default:
visible = sustainSpr.visible = typeText.visible = false; __doAnim = false;
}
}
public override function draw() @:privateAccess {
switch (Charter.instance.gridActionType) {
case NONE:
super.draw();
case DRAG:
if (Charter.instance.gridActionType == DRAG) {
var verticalChange:Float = (__mousePos.y - Charter.instance.dragStartPos.y) / 40;
var horizontalChange:Int = CoolUtil.floorInt((__mousePos.x - (Std.int(Charter.instance.dragStartPos.x / 40) * 40)) / 40);
for (s in Charter.selection) {
if (s != null && s.draggable && s is CharterNote) {
var draggingNote:CharterNote = cast(s, CharterNote);
y = (draggingNote.step + verticalChange);
if (!FlxG.keys.pressed.SHIFT)
y -= ((draggingNote.step + verticalChange)
- Charter.instance.quantStepRounded(draggingNote.step+verticalChange, verticalChange > 0 ? 0.35 : 0.65));
y *= 40;
var newID:Int = Std.int(FlxMath.bound(draggingNote.fullID + horizontalChange, 0, (Charter.instance.strumLines.members.length*4)-1));
x = (id=newID) * 40; y = FlxMath.bound(y, 0, (Charter.instance.__endStep*40) - height);
angle = switch(animation.curAnim.curFrame = (draggingNote.id % 4)) {
case 0: -90;
case 1: 180;
case 2: 0;
case 3: 90;
default: 0; // how is that even possible
};
sustainSpr.scale.set(10, (40 * draggingNote.susLength) + (height/2));
sustainSpr.color = CharterNote.colors[animation.curAnim.curFrame];
sustainSpr.updateHitbox(); sustainSpr.alpha = alpha; sustainSpr.follow(this, 15, 20);
sustainSpr.exists = draggingNote.susLength != 0;
typeText.text = Std.string(draggingNote.type);
typeText.exists = draggingNote.type != 0;
typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2));
super.draw();
}
}
}
default: // do nothing
}
}
public override function destroy() {
super.destroy();
__mousePos.put();
}
}
@@ -115,6 +115,7 @@ class CharterSelection extends EditorTreeMenu {
#end
var option = new EditorIconOption(creation.meta.name, "Press ACCEPT to choose a difficulty to edit.", creation.meta.icon, function() {
curSong = creation.meta;
var list:Array<OptionType> = [
for(d in creation.meta.difficulties)
if (d != "") new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() {
@@ -153,10 +153,9 @@ class CharterStrumlineScreen extends UISubstateWindow {
}
else if (!suffixlist.contains(i)) suffixlist.push(i);
}
}
vocalsSuffixDropDown = new UIDropDown(typeDropdown.x, hudScaleStepper.y + 128, 200, 32, suffixlist, strumLine.vocalsSuffix != "" ? suffixlist.indexOf(strumLine.vocalsSuffix) : 0);
vocalsSuffixDropDown = new UIDropDown(typeDropdown.x, hudScaleStepper.y + 128, 200, 32, suffixlist, strumLine.vocalsSuffix != null && strumLine.vocalsSuffix != "" ? suffixlist.indexOf(strumLine.vocalsSuffix) : 0);
add(vocalsSuffixDropDown);
addLabelOn(vocalsSuffixDropDown, "Vocal Suffix");
}
+4
View File
@@ -16,6 +16,10 @@ class UIText extends FunkinText {
this.borderSize = 1;
}
this.active = false;
this.antialiasing = false;
// this.textField.antiAliasType = ADVANCED;
// this.textField.sharpness = 400;
}
public override function applyBorderStyle() {
+1 -1
View File
@@ -20,7 +20,7 @@ class GameOverSubstate extends MusicBeatSubstate
public var characterName:String;
public var gameOverSong:String;
public var gameOverSongBPM:Int;
public var gameOverSongBPM:Float;
public var lossSFXName:String;
public var retrySFX:String;
public var player:Bool;
+5
View File
@@ -117,6 +117,11 @@ class Note extends FlxSprite
this.isSustainNote = sustain;
this.sustainLength = sustainLength;
this.strumLine = strumLine;
for(field in Reflect.fields(noteData)) {
if(!["time", "id", "type", "sLen"].contains(field)) {
this.extra.set(field, Reflect.field(noteData, field));
}
}
x += 50;
// MAKE SURE ITS DEFINITELY OFF SCREEN?

Some files were not shown because too many files have changed in this diff Show More