Merge remote-tracking branch 'dev/main'
@@ -2,6 +2,7 @@
|
||||
name: Windows Build
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
name: Windows Build
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,2 +1 @@
|
||||
//
|
||||
function onNoteHit(event) if (event.noteType == "Alt Anim Note") event.animSuffix = "-alt";
|
||||
@@ -0,0 +1 @@
|
||||
function onNoteHit(event) if (event.noteType == "No Anim Note") event.animCancelled = true;
|
||||
@@ -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,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,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,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,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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,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,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,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>
|
||||
|
||||
|
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>
|
||||
|
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>
|
||||
|
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>
|
||||
|
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>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
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>
|
||||
|
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>
|
||||
|
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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,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();}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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"}]}
|
||||
@@ -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}"');
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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?
|
||||
|
||||