mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-23 02:19:46 +00:00
Merge pull request #14 from ninjamuffin99/feature/scripted-characters
YOLO MERRRRRRRRRRRGE Polymod - Scripted Characters
This commit is contained in:
commit
bc86724393
18
.github/actions/setup-haxeshit/action.yml
vendored
Normal file
18
.github/actions/setup-haxeshit/action.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: setup-haxeshit
|
||||
description: "sets up haxe shit, using HMM!"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: krdlab/setup-haxe@v1.1.6
|
||||
with:
|
||||
haxe-version: 4.2.4
|
||||
- name: Config haxelib
|
||||
run: |
|
||||
haxelib config
|
||||
shell: bash
|
||||
- name: Installing Haxe lol
|
||||
run: |
|
||||
haxe -version
|
||||
haxelib --global install hmm
|
||||
haxelib --global run hmm install --quiet
|
||||
shell: bash
|
44
.github/actions/upload-itch/action.yml
vendored
Normal file
44
.github/actions/upload-itch/action.yml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
name: upload-itch
|
||||
description: "installs Butler, and uploads to itch.io!"
|
||||
inputs:
|
||||
butler-key:
|
||||
description: "Butler API secret key"
|
||||
required: true
|
||||
build-dir:
|
||||
description: "Directory of the game build"
|
||||
required: true
|
||||
target:
|
||||
description: "Target (html5, win, linux, mac)"
|
||||
required: true
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install butler Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
curl -L -o butler.zip https://broth.itch.ovh/butler/windows-amd64/LATEST/archive/default
|
||||
7z x butler.zip
|
||||
./butler -v
|
||||
shell: bash
|
||||
- name: Install butler Mac
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
curl -L -o butler.zip https://broth.itch.ovh/butler/darwin-amd64/LATEST/archive/default
|
||||
unzip butler.zip
|
||||
./butler -V
|
||||
shell: bash
|
||||
- name: Install butler Linux
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
curl -L -o butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
|
||||
unzip butler.zip
|
||||
chmod +x butler
|
||||
./butler -V
|
||||
shell: bash
|
||||
- name: Upload game to itch.io
|
||||
env:
|
||||
BUTLER_API_KEY: ${{inputs.butler-key}}
|
||||
run: |
|
||||
./butler login
|
||||
./butler push ${{inputs.build-dir}} ninja-muffin24/funkin-secret:${{inputs.target}}-${GITHUB_REF##*/}
|
||||
shell: bash
|
63
.github/workflows/build-shit.yml
vendored
Normal file
63
.github/workflows/build-shit.yml
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
name: build-upload
|
||||
on: [push, workflow_dispatch]
|
||||
jobs:
|
||||
create-nightly-html5:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Build game?
|
||||
run: |
|
||||
haxelib run lime build html5 -debug
|
||||
ls
|
||||
- uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
build-dir: export/debug/html5/bin
|
||||
target: html5
|
||||
create-nightly-win:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build windows -debug
|
||||
dir
|
||||
- uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
build-dir: export/debug/windows/bin
|
||||
target: win
|
||||
create-nightly-mac:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Build game?
|
||||
run: |
|
||||
haxelib run lime build mac -debug
|
||||
ls
|
||||
- uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
build-dir: export/debug/macos/bin
|
||||
target: mac
|
||||
create-nightly-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Setting up Linux
|
||||
run: |
|
||||
haxelib run lime setup linux
|
||||
- name: Build game?
|
||||
run: |
|
||||
haxelib run lime build linux -debug
|
||||
ls
|
||||
- uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
build-dir: export/debug/linux/bin
|
||||
target: linux
|
||||
|
48
.github/workflows/learn-github-actions.yml
vendored
48
.github/workflows/learn-github-actions.yml
vendored
|
@ -1,48 +0,0 @@
|
|||
name: learn-github-actions
|
||||
on: [push]
|
||||
jobs:
|
||||
create-nightly-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: krdlab/setup-haxe@v1.1.6
|
||||
with:
|
||||
haxe-version: 4.2.4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache Haxe Stuff
|
||||
run: |
|
||||
haxelib config
|
||||
- name: Installing Haxe Stuff
|
||||
run: |
|
||||
haxe -version
|
||||
haxelib install openfl --quiet
|
||||
haxelib install lime --quiet
|
||||
haxelib install flixel --quiet
|
||||
haxelib install flixel-addons --quiet
|
||||
haxelib install hscript --quiet
|
||||
haxelib install flixel-ui --quiet
|
||||
haxelib install firetongue --quiet
|
||||
haxelib install hxcpp-debug-server --quiet
|
||||
haxelib install hxcpp --quiet
|
||||
haxelib git polymod https://github.com/larsiusprime/polymod develop --quiet
|
||||
haxelib run lime setup linux
|
||||
- name: Build game?
|
||||
run: |
|
||||
haxelib run lime build html5 -debug -clean
|
||||
ls
|
||||
- name: Install butler and shit
|
||||
run: |
|
||||
curl -L -o butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
|
||||
unzip butler.zip
|
||||
chmod +x butler
|
||||
./butler -V
|
||||
- name: Upload game to itch.io
|
||||
env:
|
||||
BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY}}
|
||||
run: |
|
||||
./butler login
|
||||
./butler push export/debug/html5/bin ninja-muffin24/funkin-secret:html5
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: funkinSecret-${{ runner.os }}-${{ github.sha }}
|
||||
path: export/debug/html5/bin
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -2,4 +2,5 @@ export/
|
|||
.vscode/
|
||||
APIStuff.hx
|
||||
.DS_STORE
|
||||
RECOVER_*.fla
|
||||
RECOVER_*.fla
|
||||
.haxelib/
|
|
@ -128,6 +128,7 @@
|
|||
<!--haxelib name="newgrounds" unless="switch"/> -->
|
||||
<haxelib name="faxe" if='switch' />
|
||||
<haxelib name="polymod" />
|
||||
|
||||
<haxelib name="thx.semver" />
|
||||
|
||||
<!-- <haxelib name="colyseus"/> -->
|
||||
|
|
18
README.md
18
README.md
|
@ -39,27 +39,21 @@ First you need to install Haxe and HaxeFlixel. I'm too lazy to write and keep up
|
|||
1. [Install Haxe 4.1.5](https://haxe.org/download/version/4.1.5/) (Download 4.1.5 instead of 4.2.0 because 4.2.0 is broken and is not working with gits properly...)
|
||||
2. [Install HaxeFlixel](https://haxeflixel.com/documentation/install-haxeflixel/) after downloading Haxe
|
||||
|
||||
Other installations you'd need is the additional libraries, a fully updated list will be in `Project.xml` in the project root. Currently, these are all of the things you need to install:
|
||||
Other installations you'd need is the additional libraries, a fully updated list will be in `hmm.json` in the project root. Currently, these are all of the things you need to install:
|
||||
```
|
||||
flixel
|
||||
flixel-addons
|
||||
flixel-ui
|
||||
hscript
|
||||
newgrounds
|
||||
haxelib --global install hmm
|
||||
haxelib --global run hmm setup
|
||||
hmm install
|
||||
```
|
||||
So for each of those type `haxelib install [library]` so shit like `haxelib install newgrounds`
|
||||
|
||||
You'll also need to install a couple things that involve Gits. To do this, you need to do a few things first.
|
||||
<!-- You'll also need to install a couple things that involve Gits. To do this, you need to do a few things first.
|
||||
1. Download [git-scm](https://git-scm.com/downloads). Works for Windows, Mac, and Linux, just select your build.
|
||||
2. Follow instructions to install the application properly.
|
||||
3. Run `haxelib git polymod https://github.com/larsiusprime/polymod.git` to install Polymod.
|
||||
4. Run `haxelib git discord_rpc https://github.com/Aidan63/linc_discord-rpc` to install Discord RPC.
|
||||
4. Run `haxelib git discord_rpc https://github.com/Aidan63/linc_discord-rpc` to install Discord RPC. -->
|
||||
|
||||
You should have everything ready for compiling the game! Follow the guide below to continue!
|
||||
|
||||
At the moment, you can optionally fix the transition bug in songs with zoomed out cameras.
|
||||
- Run `haxelib git flixel-addons https://github.com/HaxeFlixel/flixel-addons` in the terminal/command-prompt.
|
||||
|
||||
### Ignored files
|
||||
|
||||
I gitignore the API keys for the game, so that no one can nab them and post fake highscores on the leaderboards. But because of that the game
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
swagshit--moneymoney
|
Binary file not shown.
Before Width: | Height: | Size: 2.3 MiB |
74
hmm.json
Normal file
74
hmm.json
Normal file
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "discord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2d83fa8",
|
||||
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
||||
},
|
||||
{
|
||||
"name": "firetongue",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c5666c8",
|
||||
"url": "https://github.com/larsiusprime/firetongue"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "93a049d6",
|
||||
"url": "https://github.com/haxeflixel/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "haxelib",
|
||||
"version": "2.11.0"
|
||||
},
|
||||
{
|
||||
"name": "flixel-ui",
|
||||
"type": "haxelib",
|
||||
"version": "2.4.0"
|
||||
},
|
||||
{
|
||||
"name": "hscript",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a1b7f74",
|
||||
"url": "https://github.com/mastereric/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "haxelib",
|
||||
"version": "4.2.1"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
"type": "haxelib",
|
||||
"version": "1.2.4"
|
||||
},
|
||||
{
|
||||
"name": "lime",
|
||||
"type": "haxelib",
|
||||
"version": "7.9.0"
|
||||
},
|
||||
{
|
||||
"name": "openfl",
|
||||
"type": "haxelib",
|
||||
"version": "9.1.0"
|
||||
},
|
||||
{
|
||||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c858b48",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
"name": "thx.semver",
|
||||
"type": "haxelib",
|
||||
"version": "0.2.2"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Boyfriend extends Character
|
||||
{
|
||||
// public var stunned:Bool = false;
|
||||
public function new(x:Float, y:Float, ?char:String = 'bf')
|
||||
{
|
||||
super(x, y, char, true);
|
||||
}
|
||||
|
||||
public var startedDeath:Bool = false;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
if (!debugMode)
|
||||
{
|
||||
if (animation.curAnim.name.startsWith('sing'))
|
||||
{
|
||||
holdTimer += elapsed;
|
||||
}
|
||||
else
|
||||
holdTimer = 0;
|
||||
|
||||
if (animation.curAnim.name.endsWith('miss') && animation.curAnim.finished && !debugMode)
|
||||
{
|
||||
playAnim('idle', true, false, 10);
|
||||
}
|
||||
|
||||
if (animation.curAnim.name == 'firstDeath' && animation.curAnim.finished && startedDeath)
|
||||
{
|
||||
playAnim('deathLoop');
|
||||
}
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
}
|
|
@ -1,766 +0,0 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import funkin.Note.NoteData;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
import funkin.Section.SwagSection;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.animation.FlxBaseAnimation;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.util.FlxSort;
|
||||
import haxe.io.Path;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class Character extends FlxSprite
|
||||
{
|
||||
public var animOffsets:Map<String, Array<Dynamic>>;
|
||||
public var debugMode:Bool = false;
|
||||
|
||||
public var isPlayer:Bool = false;
|
||||
public var curCharacter:String = 'bf';
|
||||
|
||||
public var holdTimer:Float = 0;
|
||||
|
||||
public var animationNotes:Array<NoteData> = [];
|
||||
|
||||
public function new(x:Float, y:Float, ?character:String = "bf", ?isPlayer:Bool = false)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
animOffsets = new Map<String, Array<Dynamic>>();
|
||||
curCharacter = character;
|
||||
this.isPlayer = isPlayer;
|
||||
|
||||
var tex:FlxAtlasFrames;
|
||||
antialiasing = true;
|
||||
|
||||
switch (curCharacter)
|
||||
{
|
||||
case 'gf':
|
||||
// GIRLFRIEND CODE
|
||||
tex = Paths.getSparrowAtlas('characters/GF_assets');
|
||||
frames = tex;
|
||||
quickAnimAdd('cheer', 'GF Cheer');
|
||||
quickAnimAdd('singLEFT', 'GF left note');
|
||||
quickAnimAdd('singRIGHT', 'GF Right Note');
|
||||
quickAnimAdd('singUP', 'GF Up Note');
|
||||
quickAnimAdd('singDOWN', 'GF Down Note');
|
||||
animation.addByIndices('sad', 'gf sad', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "", 24, true);
|
||||
animation.addByIndices('danceLeft', 'GF Dancing Beat', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||
animation.addByIndices('danceRight', 'GF Dancing Beat', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
||||
animation.addByIndices('hairBlow', "GF Dancing Beat Hair blowing", [0, 1, 2, 3], "", 24);
|
||||
animation.addByIndices('hairFall', "GF Dancing Beat Hair Landing", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "", 24, false);
|
||||
animation.addByPrefix('scared', 'GF FEAR', 24, true);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('danceRight');
|
||||
|
||||
case 'gf-christmas':
|
||||
tex = Paths.getSparrowAtlas('characters/gfChristmas');
|
||||
frames = tex;
|
||||
quickAnimAdd('cheer', 'GF Cheer');
|
||||
quickAnimAdd('singLEFT', 'GF left note');
|
||||
quickAnimAdd('singRIGHT', 'GF Right Note');
|
||||
quickAnimAdd('singUP', 'GF Up Note');
|
||||
quickAnimAdd('singDOWN', 'GF Down Note');
|
||||
animation.addByIndices('sad', 'gf sad', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "", 24, false);
|
||||
animation.addByIndices('danceLeft', 'GF Dancing Beat', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||
animation.addByIndices('danceRight', 'GF Dancing Beat', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
||||
animation.addByIndices('hairBlow', "GF Dancing Beat Hair blowing", [0, 1, 2, 3], "", 24);
|
||||
animation.addByIndices('hairFall', "GF Dancing Beat Hair Landing", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "", 24, false);
|
||||
animation.addByPrefix('scared', 'GF FEAR', 24, true);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('danceRight');
|
||||
case 'gf-tankmen':
|
||||
frames = Paths.getSparrowAtlas('characters/gfTankmen');
|
||||
animation.addByIndices('sad', 'GF Crying at Gunpoint', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "", 24, true);
|
||||
animation.addByIndices('danceLeft', 'GF Dancing at Gunpoint', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||
animation.addByIndices('danceRight', 'GF Dancing at Gunpoint', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
||||
|
||||
loadOffsetFile('gf');
|
||||
playAnim('danceRight');
|
||||
|
||||
case 'bf-holding-gf':
|
||||
frames = Paths.getSparrowAtlas('characters/bfAndGF');
|
||||
quickAnimAdd('idle', 'BF idle dance');
|
||||
quickAnimAdd('singDOWN', 'BF NOTE DOWN0');
|
||||
quickAnimAdd('singLEFT', 'BF NOTE LEFT0');
|
||||
quickAnimAdd('singRIGHT', 'BF NOTE RIGHT0');
|
||||
quickAnimAdd('singUP', 'BF NOTE UP0');
|
||||
|
||||
quickAnimAdd('singDOWNmiss', 'BF NOTE DOWN MISS');
|
||||
quickAnimAdd('singLEFTmiss', 'BF NOTE LEFT MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'BF NOTE RIGHT MISS');
|
||||
quickAnimAdd('singUPmiss', 'BF NOTE UP MISS');
|
||||
quickAnimAdd('bfCatch', 'BF catches GF');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
flipX = true;
|
||||
|
||||
case 'gf-car':
|
||||
tex = Paths.getSparrowAtlas('characters/gfCar');
|
||||
frames = tex;
|
||||
animation.addByIndices('singUP', 'GF Dancing Beat Hair blowing CAR', [0], "", 24, false);
|
||||
animation.addByIndices('danceLeft', 'GF Dancing Beat Hair blowing CAR', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||
animation.addByIndices('danceRight', 'GF Dancing Beat Hair blowing CAR', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24,
|
||||
false);
|
||||
animation.addByIndices('idleHair', 'GF Dancing Beat Hair blowing CAR', [10, 11, 12, 25, 26, 27], "", 24, true);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('danceRight');
|
||||
|
||||
case 'gf-pixel':
|
||||
tex = Paths.getSparrowAtlas('characters/gfPixel');
|
||||
frames = tex;
|
||||
animation.addByIndices('singUP', 'GF IDLE', [2], "", 24, false);
|
||||
animation.addByIndices('danceLeft', 'GF IDLE', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||
animation.addByIndices('danceRight', 'GF IDLE', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('danceRight');
|
||||
|
||||
setGraphicSize(Std.int(width * Constants.PIXEL_ART_SCALE));
|
||||
updateHitbox();
|
||||
antialiasing = false;
|
||||
|
||||
case 'dad':
|
||||
// DAD ANIMATION LOADING CODE
|
||||
tex = Paths.getSparrowAtlas('characters/DADDY_DEAREST');
|
||||
frames = tex;
|
||||
quickAnimAdd('idle', 'Dad idle dance');
|
||||
quickAnimAdd('singUP', 'Dad Sing Note UP');
|
||||
quickAnimAdd('singRIGHT', 'Dad Sing Note RIGHT');
|
||||
quickAnimAdd('singDOWN', 'Dad Sing Note DOWN');
|
||||
quickAnimAdd('singLEFT', 'Dad Sing Note LEFT');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
case 'spooky':
|
||||
tex = Paths.getSparrowAtlas('characters/spooky_kids_assets');
|
||||
frames = tex;
|
||||
quickAnimAdd('singUP', 'spooky UP NOTE');
|
||||
quickAnimAdd('singDOWN', 'spooky DOWN note');
|
||||
quickAnimAdd('singLEFT', 'note sing left');
|
||||
quickAnimAdd('singRIGHT', 'spooky sing right');
|
||||
animation.addByIndices('danceLeft', 'spooky dance idle', [0, 2, 6], "", 12, false);
|
||||
animation.addByIndices('danceRight', 'spooky dance idle', [8, 10, 12, 14], "", 12, false);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('danceRight');
|
||||
case 'mom':
|
||||
tex = Paths.getSparrowAtlas('characters/Mom_Assets');
|
||||
frames = tex;
|
||||
|
||||
quickAnimAdd('idle', "Mom Idle");
|
||||
quickAnimAdd('singUP', "Mom Up Pose");
|
||||
quickAnimAdd('singDOWN', "MOM DOWN POSE");
|
||||
quickAnimAdd('singLEFT', 'Mom Left Pose');
|
||||
// ANIMATION IS CALLED MOM LEFT POSE BUT ITS FOR THE RIGHT
|
||||
// CUZ DAVE IS DUMB!
|
||||
quickAnimAdd('singRIGHT', 'Mom Pose Left');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
case 'mom-car':
|
||||
tex = Paths.getSparrowAtlas('characters/momCar');
|
||||
frames = tex;
|
||||
|
||||
quickAnimAdd('idle', "Mom Idle");
|
||||
quickAnimAdd('singUP', "Mom Up Pose");
|
||||
quickAnimAdd('singDOWN', "MOM DOWN POSE");
|
||||
quickAnimAdd('singLEFT', 'Mom Left Pose');
|
||||
// ANIMATION IS CALLED MOM LEFT POSE BUT ITS FOR THE RIGHT
|
||||
// CUZ DAVE IS DUMB!
|
||||
quickAnimAdd('singRIGHT', 'Mom Pose Left');
|
||||
animation.addByIndices('idleHair', "Mom Idle", [10, 11, 12, 13], "", 24, true);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
case 'monster':
|
||||
tex = Paths.getSparrowAtlas('characters/Monster_Assets');
|
||||
frames = tex;
|
||||
quickAnimAdd('idle', 'monster idle');
|
||||
quickAnimAdd('singUP', 'monster up note');
|
||||
quickAnimAdd('singDOWN', 'monster down');
|
||||
quickAnimAdd('singLEFT', 'Monster left note');
|
||||
quickAnimAdd('singRIGHT', 'Monster Right note');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
case 'monster-christmas':
|
||||
tex = Paths.getSparrowAtlas('characters/monsterChristmas');
|
||||
frames = tex;
|
||||
quickAnimAdd('idle', 'monster idle');
|
||||
quickAnimAdd('singUP', 'monster up note');
|
||||
quickAnimAdd('singDOWN', 'monster down');
|
||||
quickAnimAdd('singLEFT', 'Monster left note');
|
||||
quickAnimAdd('singRIGHT', 'Monster Right note');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
case 'pico':
|
||||
tex = Paths.getSparrowAtlas('characters/Pico_FNF_assetss');
|
||||
frames = tex;
|
||||
quickAnimAdd('idle', "Pico Idle Dance");
|
||||
quickAnimAdd('singUP', 'pico Up note0');
|
||||
quickAnimAdd('singDOWN', 'Pico Down Note0');
|
||||
|
||||
// isPlayer = true;
|
||||
|
||||
// Need to be flipped! REDO THIS LATER!
|
||||
quickAnimAdd('singLEFT', 'Pico Note Right0');
|
||||
quickAnimAdd('singRIGHT', 'Pico NOTE LEFT0');
|
||||
quickAnimAdd('singRIGHTmiss', 'Pico NOTE LEFT miss');
|
||||
quickAnimAdd('singLEFTmiss', 'Pico Note Right Miss');
|
||||
|
||||
quickAnimAdd('singUPmiss', 'pico Up note miss');
|
||||
quickAnimAdd('singDOWNmiss', 'Pico Down Note MISS');
|
||||
|
||||
// right now it loads a seperate offset file for pico, would be cool if could generalize it!
|
||||
var playerShit:String = "";
|
||||
|
||||
if (isPlayer)
|
||||
playerShit += "Player";
|
||||
|
||||
loadOffsetFile(curCharacter + playerShit);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
flipX = true;
|
||||
|
||||
case 'pico-speaker':
|
||||
frames = Paths.getSparrowAtlas('characters/picoSpeaker');
|
||||
|
||||
quickAnimAdd('shoot1', "Pico shoot 1");
|
||||
quickAnimAdd('shoot2', "Pico shoot 2");
|
||||
quickAnimAdd('shoot3', "Pico shoot 3");
|
||||
quickAnimAdd('shoot4', "Pico shoot 4");
|
||||
|
||||
// here for now, will be replaced later for less copypaste
|
||||
loadOffsetFile(curCharacter);
|
||||
playAnim('shoot1');
|
||||
|
||||
loadMappedAnims();
|
||||
|
||||
case 'bf':
|
||||
var tex = Paths.getSparrowAtlas('characters/BOYFRIEND');
|
||||
frames = tex;
|
||||
quickAnimAdd('idle', 'BF idle dance');
|
||||
quickAnimAdd('singUP', 'BF NOTE UP0');
|
||||
quickAnimAdd('singLEFT', 'BF NOTE LEFT0');
|
||||
quickAnimAdd('singRIGHT', 'BF NOTE RIGHT0');
|
||||
quickAnimAdd('singDOWN', 'BF NOTE DOWN0');
|
||||
quickAnimAdd('singUPmiss', 'BF NOTE UP MISS');
|
||||
quickAnimAdd('singLEFTmiss', 'BF NOTE LEFT MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'BF NOTE RIGHT MISS');
|
||||
quickAnimAdd('singDOWNmiss', 'BF NOTE DOWN MISS');
|
||||
quickAnimAdd('preAttack', 'bf pre attack');
|
||||
quickAnimAdd('attack', 'boyfriend attack');
|
||||
quickAnimAdd('hey', 'BF HEY');
|
||||
|
||||
quickAnimAdd('firstDeath', "BF dies");
|
||||
animation.addByPrefix('deathLoop', "BF Dead Loop", 24, true);
|
||||
quickAnimAdd('deathConfirm', "BF Dead confirm");
|
||||
|
||||
animation.addByPrefix('scared', 'BF idle shaking', 24, true);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
flipX = true;
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
case 'bf-christmas':
|
||||
var tex = Paths.getSparrowAtlas('characters/bfChristmas');
|
||||
frames = tex;
|
||||
quickAnimAdd('idle', 'BF idle dance');
|
||||
quickAnimAdd('singUP', 'BF NOTE UP0');
|
||||
quickAnimAdd('singLEFT', 'BF NOTE LEFT0');
|
||||
quickAnimAdd('singRIGHT', 'BF NOTE RIGHT0');
|
||||
quickAnimAdd('singDOWN', 'BF NOTE DOWN0');
|
||||
quickAnimAdd('singUPmiss', 'BF NOTE UP MISS');
|
||||
quickAnimAdd('singLEFTmiss', 'BF NOTE LEFT MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'BF NOTE RIGHT MISS');
|
||||
quickAnimAdd('singDOWNmiss', 'BF NOTE DOWN MISS');
|
||||
quickAnimAdd('hey', 'BF HEY');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
flipX = true;
|
||||
case 'bf-car':
|
||||
var tex = Paths.getSparrowAtlas('characters/bfCar');
|
||||
frames = tex;
|
||||
quickAnimAdd('idle', 'BF idle dance');
|
||||
quickAnimAdd('singUP', 'BF NOTE UP0');
|
||||
quickAnimAdd('singLEFT', 'BF NOTE LEFT0');
|
||||
quickAnimAdd('singRIGHT', 'BF NOTE RIGHT0');
|
||||
quickAnimAdd('singDOWN', 'BF NOTE DOWN0');
|
||||
quickAnimAdd('singUPmiss', 'BF NOTE UP MISS');
|
||||
quickAnimAdd('singLEFTmiss', 'BF NOTE LEFT MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'BF NOTE RIGHT MISS');
|
||||
quickAnimAdd('singDOWNmiss', 'BF NOTE DOWN MISS');
|
||||
animation.addByIndices('idleHair', 'BF idle dance', [10, 11, 12, 13], "", 24, true);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
flipX = true;
|
||||
case 'bf-pixel':
|
||||
frames = Paths.getSparrowAtlas('characters/bfPixel');
|
||||
quickAnimAdd('idle', 'BF IDLE');
|
||||
quickAnimAdd('singUP', 'BF UP NOTE');
|
||||
quickAnimAdd('singLEFT', 'BF LEFT NOTE');
|
||||
quickAnimAdd('singRIGHT', 'BF RIGHT NOTE');
|
||||
quickAnimAdd('singDOWN', 'BF DOWN NOTE');
|
||||
quickAnimAdd('singUPmiss', 'BF UP MISS');
|
||||
quickAnimAdd('singLEFTmiss', 'BF LEFT MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'BF RIGHT MISS');
|
||||
quickAnimAdd('singDOWNmiss', 'BF DOWN MISS');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
setGraphicSize(Std.int(width * 6));
|
||||
updateHitbox();
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
width -= 100;
|
||||
height -= 100;
|
||||
|
||||
antialiasing = false;
|
||||
|
||||
flipX = true;
|
||||
case 'bf-pixel-dead':
|
||||
frames = Paths.getSparrowAtlas('characters/bfPixelsDEAD');
|
||||
quickAnimAdd('singUP', "BF Dies pixel");
|
||||
quickAnimAdd('firstDeath', "BF Dies pixel");
|
||||
animation.addByPrefix('deathLoop', "Retry Loop", 24, true);
|
||||
quickAnimAdd('deathConfirm', "RETRY CONFIRM");
|
||||
animation.play('firstDeath');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('firstDeath');
|
||||
// pixel bullshit
|
||||
setGraphicSize(Std.int(width * 6));
|
||||
updateHitbox();
|
||||
antialiasing = false;
|
||||
flipX = true;
|
||||
|
||||
case 'bf-holding-gf-dead':
|
||||
frames = Paths.getSparrowAtlas('characters/bfHoldingGF-DEAD');
|
||||
quickAnimAdd('singUP', 'BF Dead with GF Loop');
|
||||
quickAnimAdd('firstDeath', 'BF Dies with GF');
|
||||
animation.addByPrefix('deathLoop', 'BF Dead with GF Loop', 24, true);
|
||||
quickAnimAdd('deathConfirm', 'RETRY confirm holding gf');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('firstDeath');
|
||||
|
||||
flipX = true;
|
||||
|
||||
case 'senpai':
|
||||
frames = Paths.getSparrowAtlas('characters/senpai');
|
||||
quickAnimAdd('idle', 'Senpai Idle');
|
||||
// at framerate 16.8 animation plays over 2 beats at 144bpm,
|
||||
// but if the game lags or the bpm is > 144 (mods etc.)
|
||||
// he may miss his next dance
|
||||
// animation.getByName('idle').frameRate = 16.8;
|
||||
|
||||
quickAnimAdd('singUP', 'SENPAI UP NOTE');
|
||||
quickAnimAdd('singLEFT', 'SENPAI LEFT NOTE');
|
||||
quickAnimAdd('singRIGHT', 'SENPAI RIGHT NOTE');
|
||||
quickAnimAdd('singDOWN', 'SENPAI DOWN NOTE');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
setGraphicSize(Std.int(width * 6));
|
||||
updateHitbox();
|
||||
|
||||
antialiasing = false;
|
||||
case 'senpai-angry':
|
||||
frames = Paths.getSparrowAtlas('characters/senpai');
|
||||
quickAnimAdd('idle', 'Angry Senpai Idle');
|
||||
quickAnimAdd('singUP', 'Angry Senpai UP NOTE');
|
||||
quickAnimAdd('singLEFT', 'Angry Senpai LEFT NOTE');
|
||||
quickAnimAdd('singRIGHT', 'Angry Senpai RIGHT NOTE');
|
||||
quickAnimAdd('singDOWN', 'Angry Senpai DOWN NOTE');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
setGraphicSize(Std.int(width * 6));
|
||||
updateHitbox();
|
||||
|
||||
antialiasing = false;
|
||||
|
||||
case 'spirit':
|
||||
frames = Paths.getPackerAtlas('characters/spirit');
|
||||
quickAnimAdd('idle', "idle spirit_");
|
||||
quickAnimAdd('singUP', "up_");
|
||||
quickAnimAdd('singRIGHT', "right_");
|
||||
quickAnimAdd('singLEFT', "left_");
|
||||
quickAnimAdd('singDOWN', "spirit down_");
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
setGraphicSize(Std.int(width * 6));
|
||||
updateHitbox();
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
antialiasing = false;
|
||||
|
||||
case 'parents-christmas':
|
||||
frames = Paths.getSparrowAtlas('characters/mom_dad_christmas_assets');
|
||||
quickAnimAdd('idle', 'Parent Christmas Idle');
|
||||
quickAnimAdd('singUP', 'Parent Up Note Dad');
|
||||
quickAnimAdd('singDOWN', 'Parent Down Note Dad');
|
||||
quickAnimAdd('singLEFT', 'Parent Left Note Dad');
|
||||
quickAnimAdd('singRIGHT', 'Parent Right Note Dad');
|
||||
|
||||
quickAnimAdd('singUP-alt', 'Parent Up Note Mom');
|
||||
|
||||
quickAnimAdd('singDOWN-alt', 'Parent Down Note Mom');
|
||||
quickAnimAdd('singLEFT-alt', 'Parent Left Note Mom');
|
||||
quickAnimAdd('singRIGHT-alt', 'Parent Right Note Mom');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
case 'tankman':
|
||||
frames = Paths.getSparrowAtlas('characters/tankmanCaptain');
|
||||
|
||||
quickAnimAdd('idle', "Tankman Idle Dance");
|
||||
|
||||
if (isPlayer)
|
||||
{
|
||||
quickAnimAdd('singLEFT', 'Tankman Note Left ');
|
||||
quickAnimAdd('singRIGHT', 'Tankman Right Note ');
|
||||
quickAnimAdd('singLEFTmiss', 'Tankman Note Left MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'Tankman Right Note MISS');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to be flipped! REDO THIS LATER
|
||||
quickAnimAdd('singLEFT', 'Tankman Right Note ');
|
||||
quickAnimAdd('singRIGHT', 'Tankman Note Left ');
|
||||
quickAnimAdd('singLEFTmiss', 'Tankman Right Note MISS');
|
||||
quickAnimAdd('singRIGHTmiss', 'Tankman Note Left MISS');
|
||||
}
|
||||
|
||||
quickAnimAdd('singUP', 'Tankman UP note ');
|
||||
quickAnimAdd('singDOWN', 'Tankman DOWN note ');
|
||||
quickAnimAdd('singUPmiss', 'Tankman UP note MISS');
|
||||
quickAnimAdd('singDOWNmiss', 'Tankman DOWN note MISS');
|
||||
|
||||
// PRETTY GOOD tankman
|
||||
// TANKMAN UGH instanc
|
||||
|
||||
quickAnimAdd('singDOWN-alt', 'PRETTY GOOD');
|
||||
quickAnimAdd('singUP-alt', 'TANKMAN UGH');
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
flipX = true;
|
||||
case 'darnell':
|
||||
frames = Paths.getSparrowAtlas('characters/darnell');
|
||||
|
||||
quickAnimAdd('idle', 'Darnell Idle');
|
||||
quickAnimAdd('singUP', "Darnell pose up");
|
||||
quickAnimAdd('singDOWN', 'Darnell Pose Down');
|
||||
quickAnimAdd('singRIGHT', 'darnell pose left');
|
||||
quickAnimAdd('singLEFT', 'Darnell pose right'); // naming is reversed for left/right for darnell!
|
||||
quickAnimAdd('laugh', 'darnell laugh');
|
||||
|
||||
// temp
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('idle');
|
||||
|
||||
animation.finishCallback = function(animShit:String)
|
||||
{
|
||||
if (animShit.startsWith('sing'))
|
||||
{
|
||||
// loop the anim
|
||||
// this way is a little verbose, but basically sets it to the same animation, but 8 frames before finish
|
||||
playAnim(animShit, true, false, animation.getByName(animShit).frames.length - 8);
|
||||
}
|
||||
}
|
||||
case 'darnell-fighter':
|
||||
frames = Paths.getSparrowAtlas('fightDarnell');
|
||||
|
||||
quickAnimAdd('idle', "fight idle darnell");
|
||||
quickAnimAdd('block', 'block');
|
||||
quickAnimAdd('hit high', 'hit high');
|
||||
quickAnimAdd('hit low', 'hit low');
|
||||
quickAnimAdd('punch low', 'punch low');
|
||||
quickAnimAdd('punch high', 'punch high');
|
||||
quickAnimAdd('dodge', 'dodge');
|
||||
playAnim('idle');
|
||||
|
||||
addOffset('punch low', -90);
|
||||
addOffset('punch high', -90);
|
||||
addOffset('block', 50, 20);
|
||||
addOffset('dodge', 50, -20);
|
||||
|
||||
case 'pico-fighter':
|
||||
frames = Paths.getSparrowAtlas('fightPico');
|
||||
|
||||
quickAnimAdd('idle', 'fight idle pico');
|
||||
quickAnimAdd('block', 'block');
|
||||
quickAnimAdd('hit high', 'hit high');
|
||||
quickAnimAdd('hit low', 'hit low');
|
||||
quickAnimAdd('punch low', 'punch low');
|
||||
quickAnimAdd('punch high', 'punch high');
|
||||
quickAnimAdd('dodge', 'dodge');
|
||||
playAnim('idle');
|
||||
|
||||
addOffset('punch low', 160);
|
||||
addOffset('punch high', 160);
|
||||
|
||||
case 'nene':
|
||||
// GIRLFRIEND CODE
|
||||
tex = Paths.getSparrowAtlas('characters/Nene_assets');
|
||||
frames = tex;
|
||||
|
||||
animation.addByIndices('danceLeft', 'nenebeforeyougetawoopin', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
|
||||
animation.addByIndices('danceRight', 'nenebeforeyougetawoopin', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
|
||||
|
||||
loadOffsetFile(curCharacter);
|
||||
|
||||
playAnim('danceRight');
|
||||
}
|
||||
|
||||
dance();
|
||||
animation.finish();
|
||||
|
||||
if (isPlayer)
|
||||
{
|
||||
flipX = !flipX;
|
||||
|
||||
// Doesn't flip for BF, since his are already in the right place???
|
||||
if (!curCharacter.startsWith('bf'))
|
||||
{
|
||||
// var animArray
|
||||
var oldRight = animation.getByName('singRIGHT').frames;
|
||||
animation.getByName('singRIGHT').frames = animation.getByName('singLEFT').frames;
|
||||
animation.getByName('singLEFT').frames = oldRight;
|
||||
|
||||
// IF THEY HAVE MISS ANIMATIONS??
|
||||
if (animation.getByName('singRIGHTmiss') != null)
|
||||
{
|
||||
var oldMiss = animation.getByName('singRIGHTmiss').frames;
|
||||
animation.getByName('singRIGHTmiss').frames = animation.getByName('singLEFTmiss').frames;
|
||||
animation.getByName('singLEFTmiss').frames = oldMiss;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function loadMappedAnims()
|
||||
{
|
||||
var swagshit:SwagSong = SongLoad.loadFromJson('stress', 'stress');
|
||||
|
||||
var notes:Array<SwagSection> = swagshit.noteMap.get('picospeaker');
|
||||
|
||||
for (section in notes)
|
||||
{
|
||||
for (noteData in section.sectionNotes)
|
||||
{
|
||||
animationNotes.push(noteData);
|
||||
}
|
||||
}
|
||||
|
||||
trace(animationNotes);
|
||||
animationNotes.sort(sortAnims);
|
||||
}
|
||||
|
||||
function sortAnims(val1:NoteData, val2:NoteData):Int
|
||||
{
|
||||
return FlxSort.byValues(FlxSort.ASCENDING, val1.strumTime, val2.strumTime);
|
||||
}
|
||||
|
||||
function quickAnimAdd(name:String, prefix:String)
|
||||
{
|
||||
animation.addByPrefix(name, prefix, 24, false);
|
||||
}
|
||||
|
||||
private function loadOffsetFile(offsetCharacter:String)
|
||||
{
|
||||
var daFile:Array<String> = CoolUtil.coolTextFile(Paths.file("images/characters/" + offsetCharacter + "Offsets.txt", TEXT, 'shared'));
|
||||
|
||||
for (i in daFile)
|
||||
{
|
||||
var splitWords:Array<String> = i.split(" ");
|
||||
addOffset(splitWords[0], Std.parseInt(splitWords[1]), Std.parseInt(splitWords[2]));
|
||||
}
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
if (!curCharacter.startsWith('bf'))
|
||||
{
|
||||
if (animation.curAnim.name.startsWith('sing'))
|
||||
{
|
||||
holdTimer += elapsed;
|
||||
}
|
||||
|
||||
var dadVar:Float = 4;
|
||||
|
||||
if (curCharacter == 'dad')
|
||||
dadVar = 6.1;
|
||||
if (holdTimer >= Conductor.stepCrochet * dadVar * 0.001)
|
||||
{
|
||||
dance();
|
||||
holdTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (curCharacter.endsWith('-car'))
|
||||
{
|
||||
// looping hair anims after idle finished
|
||||
if (!animation.curAnim.name.startsWith('sing') && animation.curAnim.finished)
|
||||
playAnim('idleHair');
|
||||
}
|
||||
|
||||
switch (curCharacter)
|
||||
{
|
||||
case 'gf':
|
||||
if (animation.curAnim.name == 'hairFall' && animation.curAnim.finished)
|
||||
playAnim('danceRight');
|
||||
case "pico-speaker":
|
||||
// for pico??
|
||||
if (animationNotes.length > 0)
|
||||
{
|
||||
if (Conductor.songPosition > animationNotes[0].strumTime)
|
||||
{
|
||||
trace('played shoot anim' + animationNotes[0].noteData);
|
||||
|
||||
var shootAnim:Int = 1;
|
||||
|
||||
if ((cast animationNotes[0].noteData) >= 2)
|
||||
shootAnim = 3;
|
||||
|
||||
shootAnim += FlxG.random.int(0, 1);
|
||||
|
||||
playAnim('shoot' + shootAnim, true);
|
||||
animationNotes.shift();
|
||||
}
|
||||
}
|
||||
|
||||
if (animation.curAnim.finished)
|
||||
{
|
||||
playAnim(animation.curAnim.name, false, false, animation.curAnim.numFrames - 3);
|
||||
}
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
private var danced:Bool = false;
|
||||
|
||||
/**
|
||||
* FOR GF DANCING SHIT
|
||||
*/
|
||||
public function dance()
|
||||
{
|
||||
if (animation == null)
|
||||
return;
|
||||
if (!debugMode)
|
||||
{
|
||||
switch (curCharacter)
|
||||
{
|
||||
case 'gf' | 'gf-christmas' | 'gf-car' | 'gf-pixel' | 'gf-tankmen' | "nene":
|
||||
if (!animation.curAnim.name.startsWith('hair'))
|
||||
{
|
||||
danced = !danced;
|
||||
|
||||
if (danced)
|
||||
playAnim('danceRight');
|
||||
else
|
||||
playAnim('danceLeft');
|
||||
}
|
||||
|
||||
case 'tankman':
|
||||
if (!animation.curAnim.name.endsWith('DOWN-alt'))
|
||||
playAnim('idle');
|
||||
|
||||
case 'spooky':
|
||||
danced = !danced;
|
||||
|
||||
if (danced)
|
||||
playAnim('danceRight');
|
||||
else
|
||||
playAnim('danceLeft');
|
||||
default:
|
||||
playAnim('idle');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function playAnim(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
|
||||
{
|
||||
if (animation == null)
|
||||
return;
|
||||
animation.play(AnimName, Force, Reversed, Frame);
|
||||
|
||||
var daOffset = animOffsets.get(AnimName);
|
||||
if (animOffsets.exists(AnimName))
|
||||
{
|
||||
offset.set(daOffset[0], daOffset[1]);
|
||||
}
|
||||
else
|
||||
offset.set(0, 0);
|
||||
|
||||
if (curCharacter == 'gf')
|
||||
{
|
||||
if (AnimName == 'singLEFT')
|
||||
{
|
||||
danced = true;
|
||||
}
|
||||
else if (AnimName == 'singRIGHT')
|
||||
{
|
||||
danced = false;
|
||||
}
|
||||
|
||||
if (AnimName == 'singUP' || AnimName == 'singDOWN')
|
||||
{
|
||||
danced = !danced;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addOffset(name:String, x:Float = 0, y:Float = 0)
|
||||
{
|
||||
animOffsets[name] = [x, y];
|
||||
}
|
||||
}
|
|
@ -10,13 +10,13 @@ import flixel.math.FlxRect;
|
|||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.shaderslmfao.ScreenWipeShader;
|
||||
import haxe.Json;
|
||||
import haxe.format.JsonParser;
|
||||
import lime.math.Rectangle;
|
||||
import lime.utils.Assets;
|
||||
import openfl.filters.ShaderFilter;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.shaderslmfao.ScreenWipeShader;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -68,18 +68,34 @@ class CoolUtil
|
|||
static var oldCamPos:FlxPoint = new FlxPoint();
|
||||
static var oldMousePos:FlxPoint = new FlxPoint();
|
||||
|
||||
public static function mouseCamDrag():Void
|
||||
/**
|
||||
* Used to be for general camera middle click dragging, now generalized for any click and drag type shit!
|
||||
* Listen I don't make the rules here
|
||||
* @param target what you want to be dragged, defaults to CAMERA SCROLL
|
||||
* @param jusPres the "justPressed", should be a button of some sort
|
||||
* @param pressed the "pressed", which should be the same button as `jusPres`
|
||||
*/
|
||||
public static function mouseCamDrag(?target:FlxPoint, ?jusPres:Bool, ?pressed:Bool):Void
|
||||
{
|
||||
if (FlxG.mouse.justPressedMiddle)
|
||||
if (target == null)
|
||||
target = FlxG.camera.scroll;
|
||||
|
||||
if (jusPres == null)
|
||||
jusPres = FlxG.mouse.justPressedMiddle;
|
||||
|
||||
if (pressed == null)
|
||||
pressed = FlxG.mouse.pressedMiddle;
|
||||
|
||||
if (jusPres)
|
||||
{
|
||||
oldCamPos.set(FlxG.camera.scroll.x, FlxG.camera.scroll.y);
|
||||
oldCamPos.set(target.x, target.y);
|
||||
oldMousePos.set(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
}
|
||||
|
||||
if (FlxG.mouse.pressedMiddle)
|
||||
if (pressed)
|
||||
{
|
||||
FlxG.camera.scroll.x = oldCamPos.x - (FlxG.mouse.screenX - oldMousePos.x);
|
||||
FlxG.camera.scroll.y = oldCamPos.y - (FlxG.mouse.screenY - oldMousePos.y);
|
||||
target.x = oldCamPos.x - (FlxG.mouse.screenX - oldMousePos.x);
|
||||
target.y = oldCamPos.y - (FlxG.mouse.screenY - oldMousePos.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,6 +134,16 @@ class CoolUtil
|
|||
FlxG.camera.setFilters([new ShaderFilter(screenWipeShit)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just saves the json with some default values hehe
|
||||
* @param json
|
||||
* @return String
|
||||
*/
|
||||
public static inline function jsonStringify(data:Dynamic):String
|
||||
{
|
||||
return Json.stringify(data, null, "\t");
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashlink json encoding fix for some wacky bullshit
|
||||
* https://github.com/HaxeFoundation/haxe/issues/6930#issuecomment-384570392
|
||||
|
|
|
@ -25,6 +25,7 @@ import funkin.freeplayStuff.BGScrollingText;
|
|||
import funkin.freeplayStuff.DJBoyfriend;
|
||||
import funkin.freeplayStuff.FreeplayScore;
|
||||
import funkin.freeplayStuff.SongMenuItem;
|
||||
import funkin.play.HealthIcon;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.shaderslmfao.AngleMask;
|
||||
import funkin.shaderslmfao.PureColor;
|
||||
|
@ -295,7 +296,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
// grpSongs.add(songText);
|
||||
|
||||
var icon:HealthIcon = new HealthIcon(songs[i].songCharacter);
|
||||
icon.sprTracker = songText;
|
||||
// icon.sprTracker = songText;
|
||||
|
||||
// using a FlxGroup is too much fuss!
|
||||
iconArray.push(icon);
|
||||
|
|
|
@ -4,83 +4,89 @@ import flixel.FlxObject;
|
|||
import flixel.system.FlxSound;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* A substate which renders over the PlayState when the player dies.
|
||||
* Displays the player death animation, plays the music, and handles restarting the song.
|
||||
*
|
||||
* The newest implementation uses a substate, which prevents having to reload the song and stage each reset.
|
||||
*/
|
||||
class GameOverSubstate extends MusicBeatSubstate
|
||||
{
|
||||
var bf:Boyfriend;
|
||||
var camFollow:FlxObject;
|
||||
/**
|
||||
* The boyfriend character.
|
||||
*/
|
||||
var boyfriend:BaseCharacter;
|
||||
|
||||
var stageSuffix:String = "";
|
||||
var randomGameover:Int = 1;
|
||||
/**
|
||||
* The invisible object in the scene which the camera focuses on.
|
||||
*/
|
||||
var cameraFollowPoint:FlxObject;
|
||||
|
||||
var gameOverMusic:FlxSound;
|
||||
/**
|
||||
* The music playing in the background of the state.
|
||||
*/
|
||||
var gameOverMusic:FlxSound = new FlxSound();
|
||||
|
||||
/**
|
||||
* Whether the player has confirmed and prepared to restart the level.
|
||||
* This means the animation and transition have already started.
|
||||
*/
|
||||
var isEnding:Bool = false;
|
||||
|
||||
/**
|
||||
* Music variant to use.
|
||||
* TODO: De-hardcode this somehow.
|
||||
*/
|
||||
var musicVariant:String = "";
|
||||
|
||||
public function new()
|
||||
{
|
||||
gameOverMusic = new FlxSound();
|
||||
FlxG.sound.list.add(gameOverMusic);
|
||||
|
||||
var daStage = PlayState.instance.currentStageId;
|
||||
var daBf:String = '';
|
||||
switch (daStage)
|
||||
{
|
||||
case 'school' | 'schoolEvil':
|
||||
stageSuffix = '-pixel';
|
||||
daBf = 'bf-pixel-dead';
|
||||
default:
|
||||
daBf = 'bf';
|
||||
}
|
||||
|
||||
var daSong = PlayState.currentSong.song.toLowerCase();
|
||||
|
||||
switch (daSong)
|
||||
{
|
||||
case 'stress':
|
||||
daBf = 'bf-holding-gf-dead';
|
||||
}
|
||||
|
||||
super();
|
||||
|
||||
FlxG.sound.list.add(gameOverMusic);
|
||||
gameOverMusic.stop();
|
||||
|
||||
Conductor.songPosition = 0;
|
||||
|
||||
var bfXPos = PlayState.instance.currentStage.getBoyfriend().getScreenPosition().x;
|
||||
var bfYPos = PlayState.instance.currentStage.getBoyfriend().getScreenPosition().y;
|
||||
bf = new Boyfriend(bfXPos, bfYPos, daBf);
|
||||
add(bf);
|
||||
playBlueBalledSFX();
|
||||
|
||||
camFollow = new FlxObject(bf.getGraphicMidpoint().x, bf.getGraphicMidpoint().y, 1, 1);
|
||||
add(camFollow);
|
||||
|
||||
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + stageSuffix));
|
||||
// Conductor.bpm = 100;
|
||||
|
||||
switch (PlayState.currentSong.player1)
|
||||
switch (PlayState.instance.currentStageId)
|
||||
{
|
||||
case 'pico':
|
||||
stageSuffix = 'Pico';
|
||||
case 'school' | 'schoolEvil':
|
||||
musicVariant = "-pixel";
|
||||
default:
|
||||
if (PlayState.instance.currentStage.getBoyfriend().characterId == 'pico')
|
||||
{
|
||||
musicVariant = "Pico";
|
||||
}
|
||||
else
|
||||
{
|
||||
musicVariant = "";
|
||||
}
|
||||
}
|
||||
|
||||
// FlxG.camera.followLerp = 1;
|
||||
// FlxG.camera.focusOn(FlxPoint.get(FlxG.width / 2, FlxG.height / 2));
|
||||
// We have to remove boyfriend from the stage. Then we can add him back at the end.
|
||||
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
||||
boyfriend.isDead = true;
|
||||
boyfriend.playAnimation('firstDeath');
|
||||
add(boyfriend);
|
||||
|
||||
// commented out for now
|
||||
FlxG.camera.scroll.set();
|
||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||
add(cameraFollowPoint);
|
||||
|
||||
// FlxG.camera.scroll.set();
|
||||
FlxG.camera.target = null;
|
||||
|
||||
bf.playAnim('firstDeath');
|
||||
|
||||
var randomCensor:Array<Int> = [];
|
||||
|
||||
if (PreferencesMenu.getPref('censor-naughty'))
|
||||
randomCensor = [1, 3, 8, 13, 17, 21];
|
||||
|
||||
randomGameover = FlxG.random.int(1, 25, randomCensor);
|
||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);
|
||||
}
|
||||
|
||||
var playingDeathSound:Bool = false;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
// makes the lerp non-dependant on the framerate
|
||||
|
@ -93,14 +99,14 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
var touch = FlxG.touches.getFirst();
|
||||
if (touch != null)
|
||||
{
|
||||
if (touch.overlaps(bf))
|
||||
endBullshit();
|
||||
if (touch.overlaps(boyfriend))
|
||||
confirmDeath();
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.ACCEPT)
|
||||
{
|
||||
endBullshit();
|
||||
confirmDeath();
|
||||
}
|
||||
|
||||
if (controls.BACK)
|
||||
|
@ -116,74 +122,129 @@ class GameOverSubstate extends MusicBeatSubstate
|
|||
FlxG.switchState(new FreeplayState());
|
||||
}
|
||||
|
||||
if (bf.animation.curAnim.name == 'firstDeath' && bf.animation.curAnim.curFrame == 12)
|
||||
// Start panning the camera to BF after 12 frames.
|
||||
// TODO: Should this be de-hardcoded?
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.animation.curAnim.curFrame == 12)
|
||||
{
|
||||
FlxG.camera.follow(camFollow, LOCKON, 0.01);
|
||||
}
|
||||
|
||||
switch (PlayState.storyWeek)
|
||||
{
|
||||
case 7:
|
||||
if (bf.animation.curAnim.name == 'firstDeath' && bf.animation.curAnim.finished && !playingDeathSound)
|
||||
{
|
||||
playingDeathSound = true;
|
||||
|
||||
bf.startedDeath = true;
|
||||
coolStartDeath(0.2);
|
||||
|
||||
FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + randomGameover), 1, false, null, true, function()
|
||||
{
|
||||
if (!isEnding)
|
||||
{
|
||||
gameOverMusic.fadeIn(4, 0.2, 1);
|
||||
}
|
||||
// FlxG.sound.music.fadeIn(4, 0.2, 1);
|
||||
});
|
||||
}
|
||||
default:
|
||||
if (bf.animation.curAnim.name == 'firstDeath' && bf.animation.curAnim.finished)
|
||||
{
|
||||
bf.startedDeath = true;
|
||||
coolStartDeath();
|
||||
}
|
||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
||||
}
|
||||
|
||||
if (gameOverMusic.playing)
|
||||
{
|
||||
Conductor.songPosition = gameOverMusic.time;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (PlayState.storyWeek)
|
||||
{
|
||||
case 7:
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
|
||||
{
|
||||
playingJeffQuote = true;
|
||||
playJeffQuote();
|
||||
|
||||
startDeathMusic(0.2);
|
||||
}
|
||||
default:
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
||||
private function coolStartDeath(?vol:Float = 1):Void
|
||||
override function dispatchEvent(event:ScriptEvent)
|
||||
{
|
||||
super.dispatchEvent(event);
|
||||
|
||||
ScriptEventDispatcher.callEvent(boyfriend, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the death music at the appropriate volume.
|
||||
* @param startingVolume
|
||||
*/
|
||||
function startDeathMusic(?startingVolume:Float = 1):Void
|
||||
{
|
||||
if (!isEnding)
|
||||
{
|
||||
gameOverMusic.loadEmbedded(Paths.music('gameOver' + stageSuffix));
|
||||
gameOverMusic.volume = vol;
|
||||
gameOverMusic.loadEmbedded(Paths.music('gameOver' + musicVariant));
|
||||
gameOverMusic.volume = startingVolume;
|
||||
gameOverMusic.play();
|
||||
}
|
||||
else
|
||||
{
|
||||
gameOverMusic.loadEmbedded(Paths.music('gameOverEnd' + musicVariant));
|
||||
gameOverMusic.volume = startingVolume;
|
||||
gameOverMusic.play();
|
||||
}
|
||||
// FlxG.sound.playMusic();
|
||||
}
|
||||
|
||||
var isEnding:Bool = false;
|
||||
/**
|
||||
* Play the sound effect that occurs when
|
||||
* boyfriend's testicles get utterly annihilated.
|
||||
*/
|
||||
function playBlueBalledSFX()
|
||||
{
|
||||
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + musicVariant));
|
||||
}
|
||||
|
||||
function endBullshit():Void
|
||||
var playingJeffQuote:Bool = false;
|
||||
|
||||
/**
|
||||
* Week 7-specific hardcoded behavior, to play a custom death quote.
|
||||
* TODO: Make this a module somehow.
|
||||
*/
|
||||
function playJeffQuote()
|
||||
{
|
||||
var randomCensor:Array<Int> = [];
|
||||
|
||||
if (PreferencesMenu.getPref('censor-naughty'))
|
||||
randomCensor = [1, 3, 8, 13, 17, 21];
|
||||
|
||||
FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function()
|
||||
{
|
||||
// Once the quote ends, fade in the game over music.
|
||||
if (!isEnding && gameOverMusic != null)
|
||||
{
|
||||
gameOverMusic.fadeIn(4, 0.2, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Do behavior which occurs when you confirm and move to restart the level.
|
||||
*/
|
||||
function confirmDeath():Void
|
||||
{
|
||||
if (!isEnding)
|
||||
{
|
||||
isEnding = true;
|
||||
bf.playAnim('deathConfirm', true);
|
||||
gameOverMusic.stop();
|
||||
// FlxG.sound.music.stop();
|
||||
FlxG.sound.play(Paths.music('gameOverEnd' + stageSuffix));
|
||||
startDeathMusic(); // isEnding changes this function's behavior.
|
||||
|
||||
boyfriend.playAnimation('deathConfirm', true);
|
||||
|
||||
// After the animation finishes...
|
||||
new FlxTimer().start(0.7, function(tmr:FlxTimer)
|
||||
{
|
||||
// ...fade out the graphics. Then after that happens...
|
||||
FlxG.camera.fade(FlxColor.BLACK, 2, false, function()
|
||||
{
|
||||
// ...close the GameOverSubstate.
|
||||
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
||||
PlayState.needsReset = true;
|
||||
|
||||
// Readd Boyfriend to the stage.
|
||||
boyfriend.isDead = false;
|
||||
remove(boyfriend);
|
||||
PlayState.instance.currentStage.addCharacter(boyfriend, BF);
|
||||
|
||||
// Close the substate.
|
||||
close();
|
||||
// LoadingState.loadAndSwitchState(new PlayState());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class HealthIcon extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* Used for FreeplayState! If you use it elsewhere, prob gonna annoying
|
||||
*/
|
||||
public var sprTracker:FlxSprite;
|
||||
|
||||
public var char:String = '';
|
||||
|
||||
var isPlayer:Bool = false;
|
||||
|
||||
public function new(char:String = 'bf', isPlayer:Bool = false)
|
||||
{
|
||||
super();
|
||||
|
||||
this.isPlayer = isPlayer;
|
||||
|
||||
antialiasing = true;
|
||||
changeIcon(char);
|
||||
scrollFactor.set();
|
||||
}
|
||||
|
||||
public var isOldIcon:Bool = false;
|
||||
|
||||
public function swapOldIcon():Void
|
||||
{
|
||||
isOldIcon = !isOldIcon;
|
||||
|
||||
if (isOldIcon)
|
||||
changeIcon('bf-old');
|
||||
else
|
||||
changeIcon(PlayState.currentSong.player1);
|
||||
}
|
||||
|
||||
var pixelArrayFunny:Array<String> = CoolUtil.coolTextFile(Paths.file('images/icons/pixelIcons.txt'));
|
||||
|
||||
public function changeIcon(newChar:String):Void
|
||||
{
|
||||
if (newChar != 'bf-pixel' && newChar != 'bf-old')
|
||||
newChar = newChar.split('-')[0].trim();
|
||||
|
||||
if (!Assets.exists(Paths.image('icons/icon-' + newChar)))
|
||||
{
|
||||
FlxG.log.warn('No icon with data: $newChar : using default placeholder face instead!');
|
||||
newChar = "face";
|
||||
}
|
||||
|
||||
if (newChar != char)
|
||||
{
|
||||
if (animation.getByName(newChar) == null)
|
||||
{
|
||||
var imgSize:Int = 150;
|
||||
|
||||
if (newChar.endsWith('pixel') || pixelArrayFunny.contains(newChar))
|
||||
imgSize = 32;
|
||||
|
||||
loadGraphic(Paths.image('icons/icon-' + newChar), true, imgSize, imgSize);
|
||||
|
||||
animation.add(newChar, [0, 1], 0, false, isPlayer);
|
||||
}
|
||||
animation.play(newChar);
|
||||
char = newChar;
|
||||
|
||||
if (newChar.endsWith('pixel') || pixelArrayFunny.contains(newChar))
|
||||
antialiasing = false;
|
||||
else
|
||||
antialiasing = true;
|
||||
setGraphicSize(150);
|
||||
updateHitbox();
|
||||
}
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (sprTracker != null)
|
||||
setPosition(sprTracker.x + sprTracker.width + 10, sprTracker.y - 30);
|
||||
}
|
||||
}
|
|
@ -8,11 +8,10 @@ import flixel.math.FlxPoint;
|
|||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.charting.ChartingState;
|
||||
import funkin.charting.ChartingState;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.PicoFight;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.animDebugShit.DebugBoundingState;
|
||||
|
@ -125,7 +124,8 @@ class InitState extends FlxTransitionableState
|
|||
FlxTransitionableState.skipNextTransIn = true;
|
||||
|
||||
StageDataParser.loadStageCache();
|
||||
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
#if song
|
||||
|
@ -179,7 +179,11 @@ class InitState extends FlxTransitionableState
|
|||
#elseif FIGHT
|
||||
FlxG.switchState(new PicoFight());
|
||||
#elseif ANIMDEBUG
|
||||
<<<<<<< HEAD
|
||||
FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState());
|
||||
=======
|
||||
FlxG.switchState(new DebugBoundingState());
|
||||
>>>>>>> origin/feature/scripted-modules
|
||||
#elseif NETTEST
|
||||
FlxG.switchState(new netTest.NetTest());
|
||||
#else
|
||||
|
|
|
@ -19,11 +19,13 @@ import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
|||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.shaderslmfao.ScreenWipeShader;
|
||||
import funkin.ui.AtlasMenuList;
|
||||
import funkin.ui.MenuList.MenuItem;
|
||||
import funkin.ui.MenuList;
|
||||
import funkin.ui.OptionsState;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.WindowUtil;
|
||||
import lime.app.Application;
|
||||
import openfl.filters.ShaderFilter;
|
||||
|
||||
|
@ -39,7 +41,7 @@ import io.newgrounds.NG;
|
|||
|
||||
class MainMenuState extends MusicBeatState
|
||||
{
|
||||
var menuItems:MainMenuList;
|
||||
var menuItems:MenuTypedList<AtlasMenuItem>;
|
||||
|
||||
var magenta:FlxSprite;
|
||||
var camFollow:FlxObject;
|
||||
|
@ -87,7 +89,7 @@ class MainMenuState extends MusicBeatState
|
|||
add(magenta);
|
||||
// magenta.scrollFactor.set();
|
||||
|
||||
menuItems = new MainMenuList();
|
||||
menuItems = new MenuTypedList<AtlasMenuItem>();
|
||||
add(menuItems);
|
||||
menuItems.onChange.add(onMenuItemChange);
|
||||
menuItems.onAcceptPress.add(function(_)
|
||||
|
@ -103,31 +105,32 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
|
||||
menuItems.enabled = true; // can move on intro
|
||||
menuItems.createItem('story mode', function() startExitState(new StoryMenuState()));
|
||||
menuItems.createItem('freeplay', function()
|
||||
createMenuItem('storymode', 'mainmenu/storymode', function() startExitState(new StoryMenuState()));
|
||||
createMenuItem('freeplay', 'mainmenu/freeplay', function()
|
||||
{
|
||||
persistentDraw = true;
|
||||
persistentUpdate = false;
|
||||
openSubState(new FreeplayState());
|
||||
});
|
||||
// addMenuItem('options', function () startExitState(new OptionMenu()));
|
||||
#if CAN_OPEN_LINKS
|
||||
var hasPopupBlocker = #if web true #else false #end;
|
||||
|
||||
if (VideoState.seenVideo)
|
||||
menuItems.createItem('kickstarter', selectDonate, hasPopupBlocker);
|
||||
{
|
||||
createMenuItem('kickstarter', 'mainmenu/kickstarter', selectDonate, hasPopupBlocker);
|
||||
}
|
||||
else
|
||||
menuItems.createItem('donate', selectDonate, hasPopupBlocker);
|
||||
{
|
||||
createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker);
|
||||
}
|
||||
#end
|
||||
menuItems.createItem('options', function() startExitState(new OptionsState()));
|
||||
// #if newgrounds
|
||||
// if (NGio.isLoggedIn)
|
||||
// menuItems.createItem("logout", selectLogout);
|
||||
// else
|
||||
// menuItems.createItem("login", selectLogin);
|
||||
// #end
|
||||
|
||||
// center vertically
|
||||
createMenuItem('options', 'mainmenu/options', function()
|
||||
{
|
||||
startExitState(new OptionsState());
|
||||
});
|
||||
|
||||
// Reset position of menu items.
|
||||
var spacing = 160;
|
||||
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
||||
for (i in 0...menuItems.length)
|
||||
|
@ -145,19 +148,26 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
// This has to come AFTER!
|
||||
this.leftWatermarkText.text = Constants.VERSION;
|
||||
this.rightWatermarkText.text = "blablabla test";
|
||||
|
||||
// var versionStr = 'v${Application.current.meta.get('version')}';
|
||||
// versionStr += ' (secret week 8 build do not leak)';
|
||||
//
|
||||
// var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, versionStr, 12);
|
||||
// versionShit.scrollFactor.set();
|
||||
// versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
// add(versionShit);
|
||||
// this.rightWatermarkText.text = "blablabla test";
|
||||
|
||||
// NG.core.calls.event.logEvent('swag').send();
|
||||
}
|
||||
|
||||
function createMenuItem(name:String, atlas:String, callback:Void->Void, fireInstantly:Bool = false):Void
|
||||
{
|
||||
var item = new AtlasMenuItem(name, Paths.getSparrowAtlas(atlas), callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.ID = menuItems.length;
|
||||
|
||||
item.scrollFactor.set();
|
||||
|
||||
// Set the offset of the item so the sprite is centered on the origin.
|
||||
item.centered = true;
|
||||
item.changeAnim('idle');
|
||||
|
||||
menuItems.addItem(name, item);
|
||||
}
|
||||
|
||||
override function closeSubState()
|
||||
{
|
||||
magenta.visible = false;
|
||||
|
@ -185,17 +195,7 @@ class MainMenuState extends MusicBeatState
|
|||
#if CAN_OPEN_LINKS
|
||||
function selectDonate()
|
||||
{
|
||||
#if linux
|
||||
// Sys.command('/usr/bin/xdg-open', ["https://ninja-muffin24.itch.io/funkin", "&"]);
|
||||
Sys.command('/usr/bin/xdg-open', [
|
||||
"https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/",
|
||||
"&"
|
||||
]);
|
||||
#else
|
||||
// FlxG.openURL('https://ninja-muffin24.itch.io/funkin');
|
||||
|
||||
FlxG.openURL('https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/');
|
||||
#end
|
||||
WindowUtil.openURL(Constants.URL_KICKSTARTER);
|
||||
}
|
||||
#end
|
||||
|
||||
|
@ -317,46 +317,3 @@ class MainMenuState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MainMenuList extends MenuTypedList<MainMenuItem>
|
||||
{
|
||||
public var atlas:FlxAtlasFrames;
|
||||
|
||||
public function new()
|
||||
{
|
||||
atlas = Paths.getSparrowAtlas('main_menu');
|
||||
super(Vertical);
|
||||
}
|
||||
|
||||
public function createItem(x = 0.0, y = 0.0, name:String, callback, fireInstantly = false)
|
||||
{
|
||||
var item = new MainMenuItem(x, y, name, atlas, callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.ID = length;
|
||||
|
||||
return addItem(name, item);
|
||||
}
|
||||
|
||||
override function destroy()
|
||||
{
|
||||
super.destroy();
|
||||
atlas = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class MainMenuItem extends AtlasMenuItem
|
||||
{
|
||||
public function new(x = 0.0, y = 0.0, name, atlas, callback)
|
||||
{
|
||||
super(x, y, name, atlas, callback);
|
||||
scrollFactor.set();
|
||||
}
|
||||
|
||||
override function changeAnim(anim:String)
|
||||
{
|
||||
super.changeAnim(anim);
|
||||
// position by center
|
||||
centerOrigin();
|
||||
offset.copyFrom(origin);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import funkin.Conductor.BPMChangeEvent;
|
|||
import funkin.modding.PolymodHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.util.SortUtil;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.util.Constants;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.Strumline.StrumlineStyle;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.Constants;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -93,7 +94,10 @@ class Note extends FlxSprite
|
|||
// anything below sick threshold is sick
|
||||
public static var arrowColors:Array<Float> = [1, 1, 1, 1];
|
||||
|
||||
public function new(strumTime:Float = 0, noteData:NoteType, ?prevNote:Note, ?sustainNote:Bool = false)
|
||||
// Which note asset to load?
|
||||
public var style:StrumlineStyle = NORMAL;
|
||||
|
||||
public function new(strumTime:Float = 0, noteData:NoteType, ?prevNote:Note, ?sustainNote:Bool = false, ?style:StrumlineStyle = NORMAL)
|
||||
{
|
||||
super();
|
||||
|
||||
|
@ -110,10 +114,15 @@ class Note extends FlxSprite
|
|||
|
||||
data.noteData = noteData;
|
||||
|
||||
this.style = style;
|
||||
|
||||
if (this.style == null)
|
||||
this.style = StrumlineStyle.NORMAL;
|
||||
|
||||
// TODO: Make this logic more generic
|
||||
switch (PlayState.instance.currentStageId)
|
||||
switch (this.style)
|
||||
{
|
||||
case 'school' | 'schoolEvil':
|
||||
case PIXEL:
|
||||
loadGraphic(Paths.image('weeb/pixelUI/arrows-pixels'), true, 17, 17);
|
||||
|
||||
animation.add('greenScroll', [6]);
|
||||
|
@ -227,6 +236,7 @@ class Note extends FlxSprite
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// mustPress indicates the player is the one pressing the key
|
||||
if (mustPress)
|
||||
{
|
||||
// miss on the NEXT frame so lag doesnt make u miss notes
|
||||
|
@ -244,7 +254,8 @@ class Note extends FlxSprite
|
|||
}
|
||||
|
||||
if (data.strumTime > Conductor.songPosition - HIT_WINDOW)
|
||||
{ // * 0.5 if sustain note, so u have to keep holding it closer to all the way thru!
|
||||
{
|
||||
// * 0.5 if sustain note, so u have to keep holding it closer to all the way thru!
|
||||
if (data.strumTime < Conductor.songPosition + (HIT_WINDOW * (isSustainNote ? 0.5 : 1)))
|
||||
canBeHit = true;
|
||||
}
|
||||
|
@ -281,14 +292,14 @@ typedef RawNoteData =
|
|||
var strumTime:Float;
|
||||
var noteData:NoteType;
|
||||
var sustainLength:Float;
|
||||
var altNote:Bool;
|
||||
var altNote:String;
|
||||
var noteKind:NoteKind;
|
||||
}
|
||||
|
||||
@:forward
|
||||
abstract NoteData(RawNoteData)
|
||||
{
|
||||
public function new(strumTime = 0.0, noteData:NoteType = 0, sustainLength = 0.0, altNote = false, noteKind = NORMAL)
|
||||
public function new(strumTime = 0.0, noteData:NoteType = 0, sustainLength = 0.0, altNote = "", noteKind = NORMAL)
|
||||
{
|
||||
this = {
|
||||
strumTime: strumTime,
|
||||
|
@ -455,7 +466,12 @@ enum abstract NoteColor(NoteType) from Int to Int from NoteType
|
|||
|
||||
enum abstract NoteKind(String) from String to String
|
||||
{
|
||||
/**
|
||||
* The default note type.
|
||||
*/
|
||||
var NORMAL = "normal";
|
||||
|
||||
// Testing shiz
|
||||
var PYRO_LIGHT = "pyro_light";
|
||||
var PYRO_KICK = "pyro_kick";
|
||||
var PYRO_TOSS = "pyro_toss";
|
||||
|
|
|
@ -107,7 +107,7 @@ class Paths
|
|||
return 'assets/fonts/$key';
|
||||
}
|
||||
|
||||
inline static public function getSparrowAtlas(key:String, ?library:String)
|
||||
static public function getSparrowAtlas(key:String, ?library:String)
|
||||
{
|
||||
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
|
||||
}
|
||||
|
|
|
@ -189,8 +189,11 @@ class SongLoad
|
|||
noteStuff[sectionIndex].sectionNotes[noteIndex].strumTime = arrayDipshit[0];
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].noteData = arrayDipshit[1];
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].sustainLength = arrayDipshit[2];
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].altNote = arrayDipshit[3];
|
||||
if (arrayDipshit.length >= 5)
|
||||
if (arrayDipshit.length > 3)
|
||||
{
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].altNote = arrayDipshit[3];
|
||||
}
|
||||
if (arrayDipshit.length > 4)
|
||||
{
|
||||
noteStuff[sectionIndex].sectionNotes[noteIndex].noteKind = arrayDipshit[4];
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ class StoryMenuState extends MusicBeatState
|
|||
];
|
||||
var curDifficulty:Int = 1;
|
||||
|
||||
// TODO: This info is just hardcoded right now.
|
||||
// We should probably make it so that weeks must be completed in order to unlock the next week.
|
||||
public static var weekUnlocked:Array<Bool> = [true, true, true, true, true, true, true, true];
|
||||
|
||||
var weekCharacters:Array<Dynamic> = [
|
||||
|
@ -44,7 +46,7 @@ class StoryMenuState extends MusicBeatState
|
|||
['mom', 'bf', 'gf'],
|
||||
['parents-christmas', 'bf', 'gf'],
|
||||
['senpai', 'bf', 'gf'],
|
||||
['tankman', 'bf', 'gf']
|
||||
['tankman', 'bf', 'gf'],
|
||||
];
|
||||
|
||||
var weekNames:Array<String> = [
|
||||
|
@ -114,8 +116,6 @@ class StoryMenuState extends MusicBeatState
|
|||
grpLocks = new FlxTypedGroup<FlxSprite>();
|
||||
add(grpLocks);
|
||||
|
||||
trace("Line 70");
|
||||
|
||||
#if discord_rpc
|
||||
// Updating Discord Rich Presence
|
||||
DiscordClient.changePresence("In the Menus", null);
|
||||
|
@ -145,8 +145,6 @@ class StoryMenuState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
trace("Line 96");
|
||||
|
||||
for (char in 0...3)
|
||||
{
|
||||
var weekCharacterThing:MenuCharacter = new MenuCharacter((FlxG.width * 0.25) * (1 + char) - 150, weekCharacters[curWeek][char]);
|
||||
|
@ -178,8 +176,6 @@ class StoryMenuState extends MusicBeatState
|
|||
difficultySelectors = new FlxGroup();
|
||||
add(difficultySelectors);
|
||||
|
||||
trace("Line 124");
|
||||
|
||||
leftArrow = new FlxSprite(grpWeekText.members[0].x + grpWeekText.members[0].width + 10, grpWeekText.members[0].y + 10);
|
||||
leftArrow.frames = ui_tex;
|
||||
leftArrow.animation.addByPrefix('idle', "arrow left");
|
||||
|
@ -204,8 +200,6 @@ class StoryMenuState extends MusicBeatState
|
|||
rightArrow.animation.play('idle');
|
||||
difficultySelectors.add(rightArrow);
|
||||
|
||||
trace("Line 150");
|
||||
|
||||
add(yellowBG);
|
||||
add(grpWeekCharacters);
|
||||
|
||||
|
@ -220,8 +214,6 @@ class StoryMenuState extends MusicBeatState
|
|||
|
||||
updateText();
|
||||
|
||||
trace("Line 165");
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
||||
|
|
|
@ -515,7 +515,7 @@ class TitleState extends MusicBeatState
|
|||
|
||||
lime.ui.Haptic.vibrate(100, 100);
|
||||
|
||||
var coolText:AtlasText = new AtlasText(0, 0, text, AtlasFont.BOLD);
|
||||
var coolText:AtlasText = new AtlasText(0, 0, text.trim(), AtlasFont.BOLD);
|
||||
coolText.screenCenter(X);
|
||||
coolText.y += (textGroup.length * 60) + 200;
|
||||
textGroup.add(coolText);
|
||||
|
@ -554,7 +554,7 @@ class TitleState extends MusicBeatState
|
|||
switch (i + 1)
|
||||
{
|
||||
case 1:
|
||||
createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8er']);
|
||||
createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8r']);
|
||||
case 3:
|
||||
addMoreText('present');
|
||||
case 4:
|
||||
|
|
78
source/funkin/api/newgrounds/NGUnsafe.hx
Normal file
78
source/funkin/api/newgrounds/NGUnsafe.hx
Normal file
|
@ -0,0 +1,78 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.app.Application;
|
||||
import openfl.display.Stage;
|
||||
#if newgrounds
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.NGLite;
|
||||
import io.newgrounds.components.ScoreBoardComponent.Period;
|
||||
import io.newgrounds.objects.Error;
|
||||
import io.newgrounds.objects.Medal;
|
||||
import io.newgrounds.objects.Score;
|
||||
import io.newgrounds.objects.ScoreBoard;
|
||||
import io.newgrounds.objects.events.Response;
|
||||
import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
||||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Contains any script functions which should be BLOCKED from use by modded scripts.
|
||||
*/
|
||||
class NGUnsafe
|
||||
{
|
||||
static public function logEvent(event:String)
|
||||
{
|
||||
#if newgrounds
|
||||
NG.core.calls.event.logEvent(event).send();
|
||||
trace('should have logged: ' + event);
|
||||
#else
|
||||
#if debug
|
||||
trace('event:$event - not logged, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
static public function unlockMedal(id:Int)
|
||||
{
|
||||
#if newgrounds
|
||||
if (isLoggedIn)
|
||||
{
|
||||
var medal = NG.core.medals.get(id);
|
||||
if (!medal.unlocked)
|
||||
medal.sendUnlock();
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
static public function postScore(score:Int = 0, song:String)
|
||||
{
|
||||
#if newgrounds
|
||||
if (isLoggedIn)
|
||||
{
|
||||
for (id in NG.core.scoreBoards.keys())
|
||||
{
|
||||
var board = NG.core.scoreBoards.get(id);
|
||||
|
||||
if (song == board.name)
|
||||
{
|
||||
board.postScore(score, "Uhh meow?");
|
||||
}
|
||||
|
||||
// trace('loaded scoreboard id:$id, name:${board.name}');
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
}
|
260
source/funkin/api/newgrounds/NGUtil.hx
Normal file
260
source/funkin/api/newgrounds/NGUtil.hx
Normal file
|
@ -0,0 +1,260 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.app.Application;
|
||||
import openfl.display.Stage;
|
||||
#if newgrounds
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.NGLite;
|
||||
import io.newgrounds.components.ScoreBoardComponent.Period;
|
||||
import io.newgrounds.objects.Error;
|
||||
import io.newgrounds.objects.Medal;
|
||||
import io.newgrounds.objects.Score;
|
||||
import io.newgrounds.objects.ScoreBoard;
|
||||
import io.newgrounds.objects.events.Response;
|
||||
import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
||||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Contains any script functions which should be ALLOWD for use by modded scripts.
|
||||
*/
|
||||
class NGUtil
|
||||
{
|
||||
#if newgrounds
|
||||
/**
|
||||
* True, if the saved sessionId was used in the initial login, and failed to connect.
|
||||
* Used in MainMenuState to show a popup to establish a new connection
|
||||
*/
|
||||
public static var savedSessionFailed(default, null):Bool = false;
|
||||
|
||||
public static var scoreboardsLoaded:Bool = false;
|
||||
public static var isLoggedIn(get, never):Bool;
|
||||
|
||||
inline static function get_isLoggedIn()
|
||||
{
|
||||
return NG.core != null && NG.core.loggedIn;
|
||||
}
|
||||
|
||||
public static var scoreboardArray:Array<Score> = [];
|
||||
|
||||
public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal();
|
||||
public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
public static var GAME_VER:String = "";
|
||||
|
||||
static public function checkVersion(callback:String->Void)
|
||||
{
|
||||
trace('checking NG.io version');
|
||||
GAME_VER = "v" + Application.current.meta.get('version');
|
||||
|
||||
NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response)
|
||||
{
|
||||
GAME_VER = response.result.data.currentVersion;
|
||||
trace('CURRENT NG VERSION: ' + GAME_VER);
|
||||
callback(GAME_VER);
|
||||
}).send();
|
||||
}
|
||||
|
||||
static public function init()
|
||||
{
|
||||
var api = APIStuff.API;
|
||||
if (api == null || api.length == 0)
|
||||
{
|
||||
trace("Missing Newgrounds API key, aborting connection");
|
||||
return;
|
||||
}
|
||||
trace("connecting to newgrounds");
|
||||
|
||||
#if NG_FORCE_EXPIRED_SESSION
|
||||
var sessionId:String = "fake_session_id";
|
||||
function onSessionFail(error:Error)
|
||||
{
|
||||
trace("Forcing an expired saved session. " + "To disable, comment out NG_FORCE_EXPIRED_SESSION in Project.xml");
|
||||
savedSessionFailed = true;
|
||||
}
|
||||
#else
|
||||
var sessionId:String = NGLite.getSessionId();
|
||||
if (sessionId != null)
|
||||
trace("found web session id");
|
||||
|
||||
#if (debug)
|
||||
if (sessionId == null && APIStuff.SESSION != null)
|
||||
{
|
||||
trace("using debug session id");
|
||||
sessionId = APIStuff.SESSION;
|
||||
}
|
||||
#end
|
||||
|
||||
var onSessionFail:Error->Void = null;
|
||||
if (sessionId == null && FlxG.save.data.sessionId != null)
|
||||
{
|
||||
trace("using stored session id");
|
||||
sessionId = FlxG.save.data.sessionId;
|
||||
onSessionFail = function(error) savedSessionFailed = true;
|
||||
}
|
||||
#end
|
||||
|
||||
NG.create(api, sessionId, #if NG_DEBUG true #else false #end, onSessionFail);
|
||||
|
||||
#if NG_VERBOSE
|
||||
NG.core.verbose = true;
|
||||
#end
|
||||
// Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet
|
||||
NG.core.initEncryption(APIStuff.EncKey); // Found in you NG project view
|
||||
|
||||
if (NG.core.attemptingLogin)
|
||||
{
|
||||
/* a session_id was found in the loadervars, this means the user is playing on newgrounds.com
|
||||
* and we should login shortly. lets wait for that to happen
|
||||
*/
|
||||
trace("attempting login");
|
||||
NG.core.onLogin.add(onNGLogin);
|
||||
}
|
||||
// GK: taking out auto login, adding a login button to the main menu
|
||||
// else
|
||||
// {
|
||||
// /* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to.
|
||||
// * Note: This will cause a new browser window to pop up where they can log in to newgrounds
|
||||
// */
|
||||
// NG.core.requestLogin(onNGLogin);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to log in to newgrounds by requesting a new session ID, only call if no session ID was found automatically
|
||||
* @param popupLauncher The function to call to open the login url, must be inside
|
||||
* a user input event or the popup blocker will block it.
|
||||
* @param onComplete A callback with the result of the connection.
|
||||
*/
|
||||
static public function login(?popupLauncher:(Void->Void)->Void, onComplete:ConnectionResult->Void)
|
||||
{
|
||||
trace("Logging in manually");
|
||||
var onPending:Void->Void = null;
|
||||
if (popupLauncher != null)
|
||||
{
|
||||
onPending = function() popupLauncher(NG.core.openPassportUrl);
|
||||
}
|
||||
|
||||
var onSuccess:Void->Void = onNGLogin;
|
||||
var onFail:Error->Void = null;
|
||||
var onCancel:Void->Void = null;
|
||||
if (onComplete != null)
|
||||
{
|
||||
onSuccess = function()
|
||||
{
|
||||
onNGLogin();
|
||||
onComplete(Success);
|
||||
}
|
||||
onFail = function(e) onComplete(Fail(e.message));
|
||||
onCancel = function() onComplete(Cancelled);
|
||||
}
|
||||
|
||||
NG.core.requestLogin(onSuccess, onPending, onFail, onCancel);
|
||||
}
|
||||
|
||||
inline static public function cancelLogin():Void
|
||||
{
|
||||
NG.core.cancelLoginRequest();
|
||||
}
|
||||
|
||||
static function onNGLogin():Void
|
||||
{
|
||||
trace('logged in! user:${NG.core.user.name}');
|
||||
FlxG.save.data.sessionId = NG.core.sessionId;
|
||||
FlxG.save.flush();
|
||||
// Load medals then call onNGMedalFetch()
|
||||
NG.core.requestMedals(onNGMedalFetch);
|
||||
|
||||
// Load Scoreboards hten call onNGBoardsFetch()
|
||||
NG.core.requestScoreBoards(onNGBoardsFetch);
|
||||
|
||||
ngDataLoaded.dispatch();
|
||||
}
|
||||
|
||||
static public function logout()
|
||||
{
|
||||
NG.core.logOut();
|
||||
|
||||
FlxG.save.data.sessionId = null;
|
||||
FlxG.save.flush();
|
||||
}
|
||||
|
||||
// --- MEDALS
|
||||
static function onNGMedalFetch():Void
|
||||
{
|
||||
/*
|
||||
// Reading medal info
|
||||
for (id in NG.core.medals.keys())
|
||||
{
|
||||
var medal = NG.core.medals.get(id);
|
||||
trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}');
|
||||
}
|
||||
|
||||
// Unlocking medals
|
||||
var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer
|
||||
if (!unlockingMedal.unlocked)
|
||||
unlockingMedal.sendUnlock();
|
||||
*/
|
||||
}
|
||||
|
||||
// --- SCOREBOARDS
|
||||
static function onNGBoardsFetch():Void
|
||||
{
|
||||
/*
|
||||
// Reading medal info
|
||||
for (id in NG.core.scoreBoards.keys())
|
||||
{
|
||||
var board = NG.core.scoreBoards.get(id);
|
||||
trace('loaded scoreboard id:$id, name:${board.name}');
|
||||
}
|
||||
*/
|
||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
||||
|
||||
// Posting a score thats OVER 9000!
|
||||
// board.postScore(FlxG.random.int(0, 1000));
|
||||
|
||||
// --- To view the scores you first need to select the range of scores you want to see ---
|
||||
|
||||
// add an update listener so we know when we get the new scores
|
||||
// board.onUpdate.add(onNGScoresFetch);
|
||||
trace("shoulda got score by NOW!");
|
||||
// board.requestScores(20);// get the best 10 scores ever logged
|
||||
// more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores
|
||||
}
|
||||
|
||||
static function onNGScoresFetch():Void
|
||||
{
|
||||
scoreboardsLoaded = true;
|
||||
|
||||
ngScoresLoaded.dispatch();
|
||||
/*
|
||||
for (score in NG.core.scoreBoards.get(8737).scores)
|
||||
{
|
||||
trace('score loaded user:${score.user.name}, score:${score.formatted_value}');
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
||||
// board.postScore(HighScore.score);
|
||||
|
||||
// NGUtil.scoreboardArray = NG.core.scoreBoards.get(8004).scores;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
enum ConnectionResult
|
||||
{
|
||||
/** Log in successful */
|
||||
Success;
|
||||
|
||||
/** Could not login */
|
||||
Fail(msg:String);
|
||||
|
||||
/** User cancelled the login */
|
||||
Cancelled;
|
||||
}
|
104
source/funkin/api/newgrounds/NgPrompt.hx
Normal file
104
source/funkin/api/newgrounds/NgPrompt.hx
Normal file
|
@ -0,0 +1,104 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if newgrounds
|
||||
import funkin.NGio;
|
||||
import funkin.ui.Prompt;
|
||||
|
||||
class NgPrompt extends Prompt
|
||||
{
|
||||
public function new(text:String, style:ButtonStyle = Yes_No)
|
||||
{
|
||||
super(text, style);
|
||||
}
|
||||
|
||||
static public function showLogin()
|
||||
{
|
||||
return showLoginPrompt(true);
|
||||
}
|
||||
|
||||
static public function showSavedSessionFailed()
|
||||
{
|
||||
return showLoginPrompt(false);
|
||||
}
|
||||
|
||||
static function showLoginPrompt(fromUi:Bool)
|
||||
{
|
||||
var prompt = new NgPrompt("Talking to server...", None);
|
||||
prompt.openCallback = NGUtil.login.bind(function popupLauncher(openPassportUrl)
|
||||
{
|
||||
var choiceMsg = fromUi ? #if web "Log in to Newgrounds?" #else null #end // User-input needed to allow popups
|
||||
: "Your session has expired.\n Please login again.";
|
||||
|
||||
if (choiceMsg != null)
|
||||
{
|
||||
prompt.setText(choiceMsg);
|
||||
prompt.setButtons(Yes_No);
|
||||
#if web
|
||||
prompt.buttons.getItem("yes").fireInstantly = true;
|
||||
#end
|
||||
prompt.onYes = function()
|
||||
{
|
||||
prompt.setText("Connecting..." #if web + "\n(check your popup blocker)" #end);
|
||||
prompt.setButtons(None);
|
||||
openPassportUrl();
|
||||
};
|
||||
prompt.onNo = function()
|
||||
{
|
||||
prompt.close();
|
||||
prompt = null;
|
||||
NGio.cancelLogin();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
prompt.setText("Connecting...");
|
||||
openPassportUrl();
|
||||
}
|
||||
}, function onLoginComplete(result:ConnectionResult)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case Success:
|
||||
{
|
||||
prompt.setText("Login Successful");
|
||||
prompt.setButtons(Ok);
|
||||
prompt.onYes = prompt.close;
|
||||
}
|
||||
case Fail(msg):
|
||||
{
|
||||
trace("Login Error:" + msg);
|
||||
prompt.setText("Login failed");
|
||||
prompt.setButtons(Ok);
|
||||
prompt.onYes = prompt.close;
|
||||
}
|
||||
case Cancelled:
|
||||
{
|
||||
if (prompt != null)
|
||||
{
|
||||
prompt.setText("Login cancelled by user");
|
||||
prompt.setButtons(Ok);
|
||||
prompt.onYes = prompt.close;
|
||||
}
|
||||
else
|
||||
trace("Login cancelled via prompt");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
static public function showLogout()
|
||||
{
|
||||
var user = io.newgrounds.NG.core.user.name;
|
||||
var prompt = new NgPrompt('Log out of $user?', Yes_No);
|
||||
prompt.onYes = function()
|
||||
{
|
||||
NGio.logout();
|
||||
prompt.close();
|
||||
};
|
||||
prompt.onNo = prompt.close;
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
#end
|
9
source/funkin/api/newgrounds/README.md
Normal file
9
source/funkin/api/newgrounds/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# funkin.api.newgrounds
|
||||
|
||||
This package contains two main classes:
|
||||
- `NGUtil` contains utility functions for interacting with the Newgrounds API.
|
||||
- This includes any functions which scripts should be able to use,
|
||||
such as retrieving achievement status.
|
||||
- `NGUnsafe` contains sensitive utility functions for interacting with the Newgrounds API.
|
||||
- This includes any functions which scripts should not be able to use,
|
||||
such as writing high scores or posting achievements.
|
|
@ -23,6 +23,7 @@ import funkin.SongLoad.SwagSong;
|
|||
import funkin.audiovis.ABotVis;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
import funkin.audiovis.SpectogramSprite;
|
||||
import funkin.play.HealthIcon;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.rendering.MeshRender;
|
||||
import haxe.Json;
|
||||
|
@ -110,6 +111,9 @@ class ChartingState extends MusicBeatState
|
|||
leftIcon.setGraphicSize(0, 45);
|
||||
rightIcon.setGraphicSize(0, 45);
|
||||
|
||||
leftIcon.autoUpdate = false;
|
||||
rightIcon.autoUpdate = false;
|
||||
|
||||
add(leftIcon);
|
||||
add(rightIcon);
|
||||
|
||||
|
@ -705,7 +709,7 @@ class ChartingState extends MusicBeatState
|
|||
{
|
||||
if (FlxG.mouse.overlaps(leftIcon))
|
||||
{
|
||||
if (leftIcon.char == _song.player1)
|
||||
if (leftIcon.characterId == _song.player1)
|
||||
{
|
||||
p1Muted = !p1Muted;
|
||||
leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
|
||||
|
@ -727,7 +731,7 @@ class ChartingState extends MusicBeatState
|
|||
// sloppy copypaste lol deal with it!
|
||||
if (FlxG.mouse.overlaps(rightIcon))
|
||||
{
|
||||
if (rightIcon.char == _song.player1)
|
||||
if (rightIcon.characterId == _song.player1)
|
||||
{
|
||||
p1Muted = !p1Muted;
|
||||
rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
|
||||
|
@ -1012,7 +1016,7 @@ class ChartingState extends MusicBeatState
|
|||
if (curSelectedNote != null)
|
||||
{
|
||||
trace('ALT NOTE SHIT');
|
||||
curSelectedNote.altNote = !curSelectedNote.altNote;
|
||||
curSelectedNote.altNote = (curSelectedNote.altNote == "alt") ? "" : "alt";
|
||||
trace(curSelectedNote.altNote);
|
||||
}
|
||||
}
|
||||
|
@ -1129,19 +1133,25 @@ class ChartingState extends MusicBeatState
|
|||
{
|
||||
if (check_mustHitSection.checked)
|
||||
{
|
||||
leftIcon.changeIcon(_song.player1);
|
||||
rightIcon.changeIcon(_song.player2);
|
||||
leftIcon.characterId = (_song.player1);
|
||||
rightIcon.characterId = (_song.player2);
|
||||
|
||||
leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
|
||||
rightIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
|
||||
// leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
|
||||
// rightIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
|
||||
|
||||
leftIcon.playAnimation(p1Muted ? LOSING : IDLE);
|
||||
rightIcon.playAnimation(p2Muted ? LOSING : IDLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
leftIcon.changeIcon(_song.player2);
|
||||
rightIcon.changeIcon(_song.player1);
|
||||
leftIcon.characterId = (_song.player2);
|
||||
rightIcon.characterId = (_song.player1);
|
||||
|
||||
leftIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
|
||||
rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
|
||||
leftIcon.playAnimation(p2Muted ? LOSING : IDLE);
|
||||
rightIcon.playAnimation(p1Muted ? LOSING : IDLE);
|
||||
|
||||
// leftIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0;
|
||||
// rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0;
|
||||
}
|
||||
leftIcon.setGraphicSize(0, 45);
|
||||
rightIcon.setGraphicSize(0, 45);
|
||||
|
@ -1348,7 +1358,7 @@ class ChartingState extends MusicBeatState
|
|||
var noteStrum = getStrumTime(dummyArrow.y) + sectionStartTime();
|
||||
var noteData = Math.floor(FlxG.mouse.x / GRID_SIZE);
|
||||
var noteSus = 0;
|
||||
var noteAlt = false;
|
||||
var noteAlt = "";
|
||||
|
||||
justPlacedNote = true;
|
||||
|
||||
|
|
|
@ -23,14 +23,14 @@ class DJBoyfriend extends FlxSprite
|
|||
addOffset('intro', 0, 0);
|
||||
addOffset('idle', -4, -426);
|
||||
|
||||
playAnim('intro');
|
||||
playAnimation('intro');
|
||||
animation.finishCallback = function(anim)
|
||||
{
|
||||
switch (anim)
|
||||
{
|
||||
case "intro":
|
||||
animHITsignal.dispatch();
|
||||
playAnim('idle'); // plays idle anim after playing intro
|
||||
playAnimation('idle'); // plays idle anim after playing intro
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class DJBoyfriend extends FlxSprite
|
|||
// playAnim stolen from Character.hx, cuz im lazy lol!
|
||||
public var animOffsets:Map<String, Array<Dynamic>>;
|
||||
|
||||
public function playAnim(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
|
||||
public function playAnimation(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
|
||||
{
|
||||
animation.play(AnimName, Force, Reversed, Frame);
|
||||
|
||||
|
|
|
@ -144,6 +144,8 @@ class PolymodHandler
|
|||
// Ensure script files have merge support.
|
||||
output.addType("hscript", TextFileFormat.PLAINTEXT);
|
||||
output.addType("hxs", TextFileFormat.PLAINTEXT);
|
||||
output.addType("hxc", TextFileFormat.PLAINTEXT);
|
||||
output.addType("hx", TextFileFormat.PLAINTEXT);
|
||||
|
||||
// You can specify the format of a specific file, with file extension.
|
||||
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
||||
|
|
|
@ -4,7 +4,4 @@ import flixel.FlxSprite;
|
|||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedFlxSprite extends FlxSprite implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
||||
class ScriptedFlxSprite extends FlxSprite implements IHook {}
|
||||
|
|
|
@ -4,7 +4,4 @@ import flixel.group.FlxSpriteGroup;
|
|||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
||||
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements IHook {}
|
||||
|
|
|
@ -47,6 +47,16 @@ class ModuleHandler
|
|||
trace("[MODULEHANDLER] Module cache loaded.");
|
||||
}
|
||||
|
||||
public static function buildModuleCallbacks():Void
|
||||
{
|
||||
FlxG.signals.postStateSwitch.add(onStateSwitchComplete);
|
||||
}
|
||||
|
||||
static function onStateSwitchComplete():Void
|
||||
{
|
||||
callEvent(new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_END, FlxG.state, true));
|
||||
}
|
||||
|
||||
static function addToModuleCache(module:Module):Void
|
||||
{
|
||||
moduleCache.set(module.moduleId, module);
|
||||
|
|
|
@ -3,7 +3,4 @@ package funkin.modding.module;
|
|||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedModule extends Module implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
||||
class ScriptedModule extends Module implements IHook {}
|
||||
|
|
|
@ -28,19 +28,23 @@ class Countdown
|
|||
* Performs the countdown.
|
||||
* Pauses the song, plays the countdown graphics/sound, and then starts the song.
|
||||
* This will automatically stop and restart the countdown if it is already running.
|
||||
* @returns `false` if the countdown was cancelled by a script.
|
||||
*/
|
||||
public static function performCountdown(isPixelStyle:Bool):Void
|
||||
public static function performCountdown(isPixelStyle:Bool):Bool
|
||||
{
|
||||
countdownStep = BEFORE;
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
if (cancelled)
|
||||
return false;
|
||||
|
||||
// Stop any existing countdown.
|
||||
stopCountdown();
|
||||
|
||||
PlayState.isInCountdown = true;
|
||||
Conductor.songPosition = Conductor.crochet * -5;
|
||||
countdownStep = BEFORE;
|
||||
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
if (cancelled)
|
||||
return;
|
||||
// Handle onBeatHit events manually
|
||||
@:privateAccess
|
||||
PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// The timer function gets called based on the beat of the song.
|
||||
countdownTimer = new FlxTimer();
|
||||
|
@ -49,9 +53,9 @@ class Countdown
|
|||
{
|
||||
countdownStep = decrement(countdownStep);
|
||||
|
||||
// Play the dance animations manually.
|
||||
// Handle onBeatHit events manually
|
||||
@:privateAccess
|
||||
PlayState.instance.danceOnBeat();
|
||||
PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// Countdown graphic.
|
||||
showCountdownGraphic(countdownStep, isPixelStyle);
|
||||
|
@ -69,7 +73,9 @@ class Countdown
|
|||
{
|
||||
stopCountdown();
|
||||
}
|
||||
}, 6); // Before, 3, 2, 1, GO!, After
|
||||
}, 5); // Before, 3, 2, 1, GO!, After
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,11 +97,9 @@ class Countdown
|
|||
return true;
|
||||
}
|
||||
|
||||
// Stage
|
||||
ScriptEventDispatcher.callEvent(PlayState.instance.currentStage, event);
|
||||
|
||||
// Modules
|
||||
ModuleHandler.callEvent(event);
|
||||
// Modules, stages, characters.
|
||||
@:privateAccess
|
||||
PlayState.instance.dispatchEvent(event);
|
||||
|
||||
return event.eventCanceled;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import flixel.FlxSprite;
|
||||
|
||||
class Fighter extends Character
|
||||
class Fighter extends BaseCharacter
|
||||
{
|
||||
public function new(?x:Float = 0, ?y:Float = 0, ?char:String = "pico-fighter")
|
||||
{
|
||||
super(x, y, char);
|
||||
super(char);
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
animation.finishCallback = function(anim:String)
|
||||
{
|
||||
switch anim
|
||||
{
|
||||
case "punch low" | "punch high" | "block" | 'dodge':
|
||||
dance();
|
||||
dance(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -42,20 +45,20 @@ class Fighter extends Character
|
|||
|
||||
function dodge()
|
||||
{
|
||||
playAnim('dodge');
|
||||
playAnimation('dodge');
|
||||
curAction = DODGE;
|
||||
}
|
||||
|
||||
public function block()
|
||||
{
|
||||
playAnim('block');
|
||||
playAnimation('block');
|
||||
curAction = BLOCK;
|
||||
}
|
||||
|
||||
public function punch()
|
||||
{
|
||||
curAction = PUNCH;
|
||||
playAnim('punch ' + (FlxG.random.bool() ? "low" : "high"));
|
||||
playAnimation('punch ' + (FlxG.random.bool() ? "low" : "high"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
441
source/funkin/play/HealthIcon.hx
Normal file
441
source/funkin/play/HealthIcon.hx
Normal file
|
@ -0,0 +1,441 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* This is a rework of the health icon with the following changes:
|
||||
* - The health icon now owns its own state logic. It queries health and updates the sprite itself,
|
||||
* rather than relying on PlayState to command it.
|
||||
* - The health icon now supports animations.
|
||||
* - The health icon will now search for a SparrowV2 (XML) spritesheet, and use that for rendering if it can.
|
||||
* - If it can't find a spritesheet, it will the old format; a two-frame 300x150 image.
|
||||
* - If the spritesheet is found, the health icon will attempt to load and use the following animations as appropriate:
|
||||
* - `idle`, `winning`, `losing`, `toWinning`, `fromWinning`, `toLosing`, `fromLosing`
|
||||
* - The health icon is now easier to control via scripts.
|
||||
* - Set `autoUpdate` to false to prevent the health icon from changing its own animations.
|
||||
* - Once `autoUpdate` is false, you can manually call `playAnimation()` to play a specific animation.
|
||||
* - i.e. `PlayState.instance.iconP1.playAnimation("losing")`
|
||||
* - Scripts can also utilize all functionality that a normal FlxSprite would have access to, such as adding supplimental animations.
|
||||
* - i.e. `PlayState.instance.iconP1.animation.addByPrefix("jumpscare", "jumpscare", 24, false);`
|
||||
* @author MasterEric
|
||||
*/
|
||||
class HealthIcon extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* The character this icon is representing.
|
||||
* Setting this variable will automatically update the graphic.
|
||||
*/
|
||||
public var characterId(default, set):String;
|
||||
|
||||
/**
|
||||
* Whether this health icon should automatically update its state based on the character's health.
|
||||
* Note that turning this off means you have to manually do the following:
|
||||
* - Bumping the icon on the beat.
|
||||
* - Switching between winning/losing/idle animations.
|
||||
* - Repositioning the icon as health changes.
|
||||
*/
|
||||
public var autoUpdate:Bool = true;
|
||||
|
||||
/**
|
||||
* Since the `scale` of the sprite dynamically changes over time,
|
||||
* this value allows you to set a relative scale for the icon.
|
||||
* @default 1x scale
|
||||
*/
|
||||
public var size:FlxPoint = new FlxPoint(1, 1);
|
||||
|
||||
/**
|
||||
* Apply the "bump" animation once every X steps.
|
||||
*/
|
||||
public var bumpEvery:Int = 4;
|
||||
|
||||
/**
|
||||
* The player the health icon is attached to.
|
||||
*/
|
||||
var playerId:Int = 0;
|
||||
|
||||
/**
|
||||
* Whether the sprite is pixel art or not.
|
||||
* Calculated when loading an icon.
|
||||
*/
|
||||
var isPixel:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether this is a legacy icon or not.
|
||||
*/
|
||||
var isLegacyStyle:Bool = false;
|
||||
|
||||
/**
|
||||
* At this amount of health, play the Winning animation instead of the idle.
|
||||
*/
|
||||
static final WINNING_THRESHOLD = 0.8 * 2;
|
||||
|
||||
/**
|
||||
* At this amount of health, play the Losing animation instead of the idle.
|
||||
*/
|
||||
static final LOSING_THRESHOLD = 0.2 * 2;
|
||||
|
||||
/**
|
||||
* The maximum health of the player.
|
||||
*/
|
||||
static final MAXIMUM_HEALTH = 2;
|
||||
|
||||
/**
|
||||
* The size of a non-pixel icon when using the legacy format.
|
||||
* Remember, modern icons can be any size.
|
||||
*/
|
||||
static final LEGACY_ICON_SIZE = 150;
|
||||
|
||||
/**
|
||||
* The size of a pixel icon when using the legacy format.
|
||||
* Remember, modern icons can be any size.
|
||||
*/
|
||||
static final LEGACY_PIXEL_SIZE = 32;
|
||||
|
||||
/**
|
||||
* shitty hardcoded value for a specific positioning!!!
|
||||
*/
|
||||
static final POSITION_OFFSET = 26;
|
||||
|
||||
public function new(char:String = 'bf', playerId:Int = 0)
|
||||
{
|
||||
super(0, 0);
|
||||
this.playerId = playerId;
|
||||
this.scrollFactor.set();
|
||||
|
||||
this.characterId = char;
|
||||
|
||||
this.antialiasing = !isPixel;
|
||||
|
||||
this.flipX = playerId == 0;
|
||||
|
||||
initTargetSize();
|
||||
}
|
||||
|
||||
function set_characterId(value:String):String
|
||||
{
|
||||
if (value == characterId)
|
||||
return value;
|
||||
|
||||
characterId = value;
|
||||
loadCharacter(characterId);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Easter egg; press 9 in the PlayState to use the old player icon.
|
||||
*/
|
||||
public function toggleOldIcon():Void
|
||||
{
|
||||
if (characterId == 'bf-old')
|
||||
{
|
||||
characterId = PlayState.currentSong.player1;
|
||||
}
|
||||
else
|
||||
{
|
||||
characterId = 'bf-old';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Flixel every frame. Includes logic to manage the currently playing animation.
|
||||
*/
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (PlayState.instance == null)
|
||||
return;
|
||||
|
||||
// Auto-update the state of the icon based on the player's health.
|
||||
if (autoUpdate)
|
||||
{
|
||||
switch (playerId)
|
||||
{
|
||||
case 0: // Boyfriend
|
||||
// Update the animation based on the current state.
|
||||
updateHealthIcon(PlayState.instance.health);
|
||||
// Update the position to match the health bar.
|
||||
this.x = PlayState.instance.healthBar.x
|
||||
+ (PlayState.instance.healthBar.width * (FlxMath.remapToRange(PlayState.instance.healthBar.value, 0, 2, 100, 0) * 0.01)
|
||||
- POSITION_OFFSET);
|
||||
case 1: // Dad
|
||||
// Update the animation based on the current state.
|
||||
updateHealthIcon(MAXIMUM_HEALTH - PlayState.instance.health);
|
||||
// Update the position to match the health bar.
|
||||
this.x = PlayState.instance.healthBar.x
|
||||
+ (PlayState.instance.healthBar.width * (FlxMath.remapToRange(PlayState.instance.healthBar.value, 0, 2, 100, 0) * 0.01))
|
||||
- (this.width - POSITION_OFFSET);
|
||||
}
|
||||
|
||||
// Lerp the health icon back to its normal size,
|
||||
// while maintaining aspect ratio.
|
||||
if (this.width > this.height)
|
||||
{
|
||||
// Apply linear interpolation while accounting for frame rate.
|
||||
var targetSize = Std.int(CoolUtil.coolLerp(this.width, 150 * this.size.x, 0.15));
|
||||
|
||||
setGraphicSize(targetSize, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetSize = Std.int(CoolUtil.coolLerp(this.height, 150 * this.size.y, 0.15));
|
||||
|
||||
setGraphicSize(0, targetSize);
|
||||
}
|
||||
this.updateHitbox();
|
||||
}
|
||||
}
|
||||
|
||||
public function onStepHit(curStep:Int)
|
||||
{
|
||||
if (curStep % bumpEvery == 0 && isLegacyStyle)
|
||||
{
|
||||
// Make the health icons bump (the update function causes them to lerp back down).
|
||||
if (this.width > this.height)
|
||||
{
|
||||
var targetSize = Std.int(CoolUtil.coolLerp(this.width + 30, 150, 0.15));
|
||||
|
||||
setGraphicSize(targetSize, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetSize = Std.int(CoolUtil.coolLerp(this.height + 30, 150, 0.15));
|
||||
|
||||
setGraphicSize(0, targetSize);
|
||||
}
|
||||
this.updateHitbox();
|
||||
}
|
||||
}
|
||||
|
||||
inline function initTargetSize()
|
||||
{
|
||||
setGraphicSize(150);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
function updateHealthIcon(health:Float)
|
||||
{
|
||||
// We want to efficiently handle animation playback
|
||||
|
||||
// Here, we use the current animation name to track the current state
|
||||
// of a simple state machine. Neat!
|
||||
|
||||
switch (getCurrentAnimation())
|
||||
{
|
||||
case IDLE:
|
||||
if (health < LOSING_THRESHOLD)
|
||||
playAnimation(TO_LOSING, LOSING);
|
||||
else if (health > WINNING_THRESHOLD)
|
||||
playAnimation(TO_WINNING, WINNING);
|
||||
else
|
||||
playAnimation(IDLE);
|
||||
case WINNING:
|
||||
if (health < WINNING_THRESHOLD)
|
||||
playAnimation(FROM_WINNING, IDLE);
|
||||
else
|
||||
playAnimation(WINNING, IDLE);
|
||||
case LOSING:
|
||||
if (health > LOSING_THRESHOLD)
|
||||
playAnimation(FROM_LOSING, IDLE);
|
||||
else
|
||||
playAnimation(LOSING, IDLE);
|
||||
case TO_LOSING:
|
||||
if (isAnimationFinished())
|
||||
playAnimation(LOSING, IDLE);
|
||||
case TO_WINNING:
|
||||
if (isAnimationFinished())
|
||||
playAnimation(WINNING, IDLE);
|
||||
case FROM_LOSING | FROM_WINNING:
|
||||
if (isAnimationFinished())
|
||||
playAnimation(IDLE);
|
||||
case "":
|
||||
playAnimation(IDLE);
|
||||
default:
|
||||
playAnimation(IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load
|
||||
* @param charId
|
||||
*/
|
||||
function loadAnimationNew(charId:String):Void
|
||||
{
|
||||
this.animation.addByPrefix(IDLE, IDLE, 24, true);
|
||||
this.animation.addByPrefix(WINNING, WINNING, 24, true);
|
||||
this.animation.addByPrefix(LOSING, LOSING, 24, true);
|
||||
this.animation.addByPrefix(TO_WINNING, TO_WINNING, 24, true);
|
||||
this.animation.addByPrefix(TO_LOSING, TO_LOSING, 24, true);
|
||||
this.animation.addByPrefix(FROM_WINNING, FROM_WINNING, 24, true);
|
||||
this.animation.addByPrefix(FROM_LOSING, FROM_LOSING, 24, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load health icon animations using the legacy format.
|
||||
* Simply assumes two icons, one on
|
||||
* @param charId
|
||||
*/
|
||||
function loadAnimationOld(charId:String):Void
|
||||
{
|
||||
this.animation.add(IDLE, [0], 0, false, this.playerId == 0);
|
||||
this.animation.add(LOSING, [1], 0, false, this.playerId == 0);
|
||||
}
|
||||
|
||||
function correctCharacterId(charId:String):String
|
||||
{
|
||||
if (!Assets.exists(Paths.image('icons/icon-$charId')))
|
||||
{
|
||||
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
|
||||
return "face";
|
||||
}
|
||||
|
||||
return charId;
|
||||
}
|
||||
|
||||
function isNewSpritesheet(charId:String):Bool
|
||||
{
|
||||
return Assets.exists(Paths.file('images/icons/icon-$characterId.xml'));
|
||||
}
|
||||
|
||||
function fetchIsPixel(charId:String):Bool
|
||||
{
|
||||
var charData = CharacterDataParser.fetchCharacterData(charId);
|
||||
if (charData == null)
|
||||
{
|
||||
FlxG.log.warn('No character data found for character: $charId');
|
||||
return false;
|
||||
}
|
||||
return charData.isPixel;
|
||||
}
|
||||
|
||||
function loadCharacter(charId:String):Void
|
||||
{
|
||||
if (correctCharacterId(charId) != charId)
|
||||
{
|
||||
characterId = correctCharacterId(charId);
|
||||
return;
|
||||
}
|
||||
|
||||
isPixel = fetchIsPixel(charId);
|
||||
|
||||
isLegacyStyle = !isNewSpritesheet(charId);
|
||||
|
||||
if (!isLegacyStyle)
|
||||
{
|
||||
frames = Paths.getSparrowAtlas('icons/icon-$charId');
|
||||
|
||||
loadAnimationNew(charId);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadGraphic(Paths.image('icons/icon-$charId'), true, isPixel ? LEGACY_PIXEL_SIZE : LEGACY_ICON_SIZE,
|
||||
isPixel ? LEGACY_PIXEL_SIZE : LEGACY_ICON_SIZE);
|
||||
|
||||
loadAnimationOld(charId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name of the current animation being played by this health icon.
|
||||
*/
|
||||
public function getCurrentAnimation():String
|
||||
{
|
||||
if (this.animation == null || this.animation.curAnim == null)
|
||||
return "";
|
||||
return this.animation.curAnim.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether this sprite posesses the given animation.
|
||||
* Only true if the animation was successfully loaded from the XML.
|
||||
*/
|
||||
public function hasAnimation(id:String):Bool
|
||||
{
|
||||
if (this.animation == null)
|
||||
return false;
|
||||
|
||||
return this.animation.getByName(id) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the current animation is in the finished state.
|
||||
*/
|
||||
public function isAnimationFinished():Bool
|
||||
{
|
||||
return this.animation.finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the animation with the given name.
|
||||
* @param name The name of the animation to play.
|
||||
* @param fallback The fallback animation to play if the given animation is not found.
|
||||
* @param restart Whether to forcibly restart the animation if it is already playing.
|
||||
*/
|
||||
public function playAnimation(name:String, fallback:String = null, restart = false):Void
|
||||
{
|
||||
// Attempt to play the animation
|
||||
if (hasAnimation(name))
|
||||
{
|
||||
this.animation.play(name, restart, false, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Play the fallback animation if the requested animation was not found
|
||||
if (fallback != null && hasAnimation(fallback))
|
||||
{
|
||||
this.animation.play(fallback, restart, false, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have an animation, we're done.
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract HealthIconState(String) to String from String
|
||||
{
|
||||
/**
|
||||
* Indicates the health icon is in the default animation.
|
||||
* Plays as long as health is between 20% and 80%.
|
||||
*/
|
||||
var IDLE = "idle";
|
||||
|
||||
/**
|
||||
* Indicates the health icon is playing the Winning animation.
|
||||
* Plays as long as health is above 80%.
|
||||
*/
|
||||
var WINNING = "winning";
|
||||
|
||||
/**
|
||||
* Indicates the health icon is playing the Losing animation.
|
||||
* Plays as long as health is below 20%.
|
||||
*/
|
||||
var LOSING = "losing";
|
||||
|
||||
/**
|
||||
* Indicates that the health icon is transitioning between `idle` and `winning`.
|
||||
* The next animation will play once the current animation finishes.
|
||||
*/
|
||||
var TO_WINNING = "toWinning";
|
||||
|
||||
/**
|
||||
* Indicates that the health icon is transitioning between `idle` and `losing`.
|
||||
* The next animation will play once the current animation finishes.
|
||||
*/
|
||||
var TO_LOSING = "toLosing";
|
||||
|
||||
/**
|
||||
* Indicates that the health icon is transitioning between `winning` and `idle`.
|
||||
* The next animation will play once the current animation finishes.
|
||||
*/
|
||||
var FROM_WINNING = "fromWinning";
|
||||
|
||||
/**
|
||||
* Indicates that the health icon is transitioning between `losing` and `idle`.
|
||||
* The next animation will play once the current animation finishes.
|
||||
*/
|
||||
var FROM_LOSING = "fromLosing";
|
||||
}
|
|
@ -178,7 +178,7 @@ class PicoFight extends MusicBeatState
|
|||
pico.punch();
|
||||
}
|
||||
if (controls.NOTE_LEFT_R)
|
||||
pico.playAnim('idle');
|
||||
pico.playAnimation('idle');
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
@ -190,8 +190,10 @@ class PicoFight extends MusicBeatState
|
|||
|
||||
override function beatHit():Bool
|
||||
{
|
||||
// super.beatHit() returns false if a module cancelled the event.
|
||||
if (!super.beatHit())
|
||||
return false;
|
||||
|
||||
funnyWave.thickness = 10;
|
||||
funnyWave.waveAmplitude = 300;
|
||||
funnyWave.realtimeVisLenght = 0.1;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,6 +2,7 @@ package funkin.play;
|
|||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
|
@ -9,7 +10,7 @@ import flixel.util.FlxTimer;
|
|||
|
||||
/**
|
||||
* Static methods for playing cutscenes in the PlayState.
|
||||
* TODO: Softcode this shit!!!!!1!
|
||||
* TODO: Un-hardcode this shit!!!!!1!
|
||||
*/
|
||||
class VanillaCutscenes
|
||||
{
|
||||
|
@ -64,7 +65,7 @@ class VanillaCutscenes
|
|||
@:privateAccess
|
||||
PlayState.instance.startCountdown();
|
||||
@:privateAccess
|
||||
PlayState.instance.cameraMovement();
|
||||
PlayState.instance.controlCamera();
|
||||
}
|
||||
|
||||
public static function playHorrorStartCutscene()
|
||||
|
|
446
source/funkin/play/character/BaseCharacter.hx
Normal file
446
source/funkin/play/character/BaseCharacter.hx
Normal file
|
@ -0,0 +1,446 @@
|
|||
package funkin.play.character;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.Note.NoteDir;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.stage.Bopper;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* A Character is a stage prop which bops to the music as well as controlled by the strumlines.
|
||||
*
|
||||
* Remember: The character's origin is at its FEET. (horizontal center, vertical bottom)
|
||||
*/
|
||||
class BaseCharacter extends Bopper
|
||||
{
|
||||
// Metadata about a character.
|
||||
public var characterId(default, null):String;
|
||||
public var characterName(default, null):String;
|
||||
|
||||
/**
|
||||
* Whether the player is an active character (Boyfriend) or not.
|
||||
*/
|
||||
public var characterType:CharacterType = OTHER;
|
||||
|
||||
/**
|
||||
* Tracks how long, in seconds, the character has been playing the current `sing` animation.
|
||||
* This is used to ensure that characters play the `sing` animations for at least one beat,
|
||||
* preventing them from reverting to the `idle` animation between notes.
|
||||
*/
|
||||
public var holdTimer:Float = 0;
|
||||
|
||||
public var isDead:Bool = false;
|
||||
public var debugMode:Bool = false;
|
||||
|
||||
final _data:CharacterData;
|
||||
final singTimeCrochet:Float;
|
||||
|
||||
/**
|
||||
* The offset between the corner of the sprite and the origin of the sprite (at the character's feet).
|
||||
* cornerPosition = stageData - characterOrigin
|
||||
*/
|
||||
public var characterOrigin(get, null):FlxPoint;
|
||||
|
||||
function get_characterOrigin():FlxPoint
|
||||
{
|
||||
var xPos = (width / 2); // Horizontal center
|
||||
var yPos = (height); // Vertical bottom
|
||||
return new FlxPoint(xPos, yPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* The absolute position of the top-left of the character.
|
||||
* @return
|
||||
*/
|
||||
public var cornerPosition(get, null):FlxPoint;
|
||||
|
||||
function get_cornerPosition():FlxPoint
|
||||
{
|
||||
return new FlxPoint(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* The absolute position of the character's feet, at the bottom-center of the sprite.
|
||||
*/
|
||||
public var feetPosition(get, null):FlxPoint;
|
||||
|
||||
function get_feetPosition():FlxPoint
|
||||
{
|
||||
return new FlxPoint(x + characterOrigin.x, y + characterOrigin.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the point the camera should focus on.
|
||||
* Should be approximately centered on the character, and should not move based on the current animation.
|
||||
*
|
||||
* Set the position of this rather than reassigning it, so that anything referencing it will not be affected.
|
||||
*/
|
||||
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
|
||||
|
||||
override function set_animOffsets(value:Array<Float>)
|
||||
{
|
||||
if (animOffsets == null)
|
||||
animOffsets = [0, 0];
|
||||
if (animOffsets == value)
|
||||
return value;
|
||||
|
||||
var xDiff = animOffsets[0] - value[0];
|
||||
var yDiff = animOffsets[1] - value[1];
|
||||
|
||||
// Call the super function so that camera focus point is not affected.
|
||||
super.set_x(this.x + xDiff);
|
||||
super.set_y(this.y + yDiff);
|
||||
|
||||
return animOffsets = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the x position changes, other than via changing the animation offset,
|
||||
* then we need to update the camera focus point.
|
||||
*/
|
||||
override function set_x(value:Float):Float
|
||||
{
|
||||
if (value == this.x)
|
||||
return value;
|
||||
|
||||
var xDiff = value - this.x;
|
||||
this.cameraFocusPoint.x += xDiff;
|
||||
|
||||
return super.set_x(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the y position changes, other than via changing the animation offset,
|
||||
* then we need to update the camera focus point.
|
||||
*/
|
||||
override function set_y(value:Float):Float
|
||||
{
|
||||
if (value == this.y)
|
||||
return value;
|
||||
|
||||
var yDiff = value - this.y;
|
||||
this.cameraFocusPoint.y += yDiff;
|
||||
|
||||
return super.set_y(value);
|
||||
}
|
||||
|
||||
public function new(id:String)
|
||||
{
|
||||
super();
|
||||
this.characterId = id;
|
||||
|
||||
_data = CharacterDataParser.fetchCharacterData(this.characterId);
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not find character data for characterId: $characterId';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.characterName = _data.name;
|
||||
this.singTimeCrochet = _data.singTime;
|
||||
this.globalOffsets = _data.offsets;
|
||||
this.flipX = _data.flipX;
|
||||
}
|
||||
|
||||
shouldBop = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sprite scale to the appropriate value.
|
||||
* @param scale
|
||||
*/
|
||||
function setScale(scale:Null<Float>):Void
|
||||
{
|
||||
if (scale == null)
|
||||
scale = 1.0;
|
||||
|
||||
var feetPos:FlxPoint = feetPosition;
|
||||
this.scale.x = scale;
|
||||
this.scale.y = scale;
|
||||
this.updateHitbox();
|
||||
// Reposition with newly scaled sprite.
|
||||
this.x = feetPos.x - characterOrigin.x + globalOffsets[0];
|
||||
this.y = feetPos.y - characterOrigin.y + globalOffsets[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* The per-character camera offset.
|
||||
*/
|
||||
var characterCameraOffsets(get, null):Array<Float>;
|
||||
|
||||
function get_characterCameraOffsets():Array<Float>
|
||||
{
|
||||
return _data.cameraOffsets;
|
||||
}
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
// Camera focus point
|
||||
var charCenterX = this.x + this.width / 2;
|
||||
var charCenterY = this.y + this.height / 2;
|
||||
this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]);
|
||||
super.onCreate(event);
|
||||
}
|
||||
|
||||
public function initHealthIcon(isOpponent:Bool):Void
|
||||
{
|
||||
if (!isOpponent)
|
||||
{
|
||||
PlayState.instance.iconP1.characterId = _data.healthIcon.id;
|
||||
PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||
PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0];
|
||||
PlayState.instance.iconP1.offset.y = _data.healthIcon.offsets[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayState.instance.iconP2.characterId = _data.healthIcon.id;
|
||||
PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||
PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0];
|
||||
PlayState.instance.iconP2.offset.y = _data.healthIcon.offsets[1];
|
||||
}
|
||||
}
|
||||
|
||||
public override function onUpdate(event:UpdateScriptEvent):Void
|
||||
{
|
||||
super.onUpdate(event);
|
||||
|
||||
// Reset hold timer for each note pressed.
|
||||
if (justPressedNote() && this.characterType == BF)
|
||||
{
|
||||
holdTimer = 0;
|
||||
}
|
||||
|
||||
if (isDead)
|
||||
{
|
||||
playDeathAnimation();
|
||||
}
|
||||
|
||||
if (hasAnimation('idle-end') && getCurrentAnimation() == "idle" && isAnimationFinished())
|
||||
playAnimation('idle-end');
|
||||
if (hasAnimation('singLEFT-end') && getCurrentAnimation() == "singLEFT" && isAnimationFinished())
|
||||
playAnimation('singLEFT-end');
|
||||
if (hasAnimation('singDOWN-end') && getCurrentAnimation() == "singDOWN" && isAnimationFinished())
|
||||
playAnimation('singDOWN-end');
|
||||
if (hasAnimation('singUP-end') && getCurrentAnimation() == "singUP" && isAnimationFinished())
|
||||
playAnimation('singUP-end');
|
||||
if (hasAnimation('singRIGHT-end') && getCurrentAnimation() == "singRIGHT" && isAnimationFinished())
|
||||
playAnimation('singRIGHT-end');
|
||||
|
||||
// Handle character note hold time.
|
||||
if (getCurrentAnimation().startsWith("sing"))
|
||||
{
|
||||
holdTimer += event.elapsed;
|
||||
var singTimeMs:Float = singTimeCrochet * (Conductor.crochet * 0.001); // x beats, to ms.
|
||||
|
||||
if (getCurrentAnimation().endsWith("miss"))
|
||||
singTimeMs *= 2; // makes it feel more awkward when you miss
|
||||
|
||||
// Without this check here, the player character would only play the `sing` animation
|
||||
// for one beat, as opposed to holding it as long as the player is holding the button.
|
||||
var shouldStopSinging:Bool = (this.characterType == BF) ? !isHoldingNote() : true;
|
||||
|
||||
FlxG.watch.addQuick('singTimeMs-${characterId}', singTimeMs);
|
||||
if (holdTimer > singTimeMs && shouldStopSinging)
|
||||
{
|
||||
trace(getCurrentAnimation());
|
||||
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeMs}), stopping sing animation');
|
||||
holdTimer = 0;
|
||||
dance(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
holdTimer = 0;
|
||||
// super.onBeatHit handles the regular `dance()` calls.
|
||||
}
|
||||
FlxG.watch.addQuick('holdTimer-${characterId}', holdTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since no `onBeatHit` or `dance` calls happen in GameOverSubstate,
|
||||
* this regularly gets called instead.
|
||||
*/
|
||||
public function playDeathAnimation(force:Bool = false):Void
|
||||
{
|
||||
if (force || (getCurrentAnimation().startsWith("firstDeath") && isAnimationFinished()))
|
||||
{
|
||||
playAnimation("deathLoop");
|
||||
}
|
||||
}
|
||||
|
||||
override function dance(force:Bool = false)
|
||||
{
|
||||
// Prevent default dancing behavior.
|
||||
if (debugMode)
|
||||
return;
|
||||
|
||||
if (!force)
|
||||
{
|
||||
if (getCurrentAnimation().startsWith("sing"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (["hey", "cheer"].contains(getCurrentAnimation()) && !isAnimationFinished())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent dancing while another animation is playing.
|
||||
if (!force && getCurrentAnimation().startsWith("sing"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, fallback to the super dance() method, which handles playing the idle animation.
|
||||
super.dance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the player just pressed a note.
|
||||
* Used when determing whether a the player character should revert to the `idle` animation.
|
||||
* On non-player characters, this should be ignored.
|
||||
*/
|
||||
function justPressedNote(player:Int = 1):Bool
|
||||
{
|
||||
// Returns true if at least one of LEFT, DOWN, UP, or RIGHT is being held.
|
||||
switch (player)
|
||||
{
|
||||
case 1:
|
||||
return [
|
||||
PlayerSettings.player1.controls.NOTE_LEFT_P,
|
||||
PlayerSettings.player1.controls.NOTE_DOWN_P,
|
||||
PlayerSettings.player1.controls.NOTE_UP_P,
|
||||
PlayerSettings.player1.controls.NOTE_RIGHT_P,
|
||||
].contains(true);
|
||||
case 2:
|
||||
return [
|
||||
PlayerSettings.player2.controls.NOTE_LEFT_P,
|
||||
PlayerSettings.player2.controls.NOTE_DOWN_P,
|
||||
PlayerSettings.player2.controls.NOTE_UP_P,
|
||||
PlayerSettings.player2.controls.NOTE_RIGHT_P,
|
||||
].contains(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the player is holding a note.
|
||||
* Used when determing whether a the player character should revert to the `idle` animation.
|
||||
* On non-player characters, this should be ignored.
|
||||
*/
|
||||
function isHoldingNote(player:Int = 1):Bool
|
||||
{
|
||||
// Returns true if at least one of LEFT, DOWN, UP, or RIGHT is being held.
|
||||
switch (player)
|
||||
{
|
||||
case 1:
|
||||
return [
|
||||
PlayerSettings.player1.controls.NOTE_LEFT,
|
||||
PlayerSettings.player1.controls.NOTE_DOWN,
|
||||
PlayerSettings.player1.controls.NOTE_UP,
|
||||
PlayerSettings.player1.controls.NOTE_RIGHT,
|
||||
].contains(true);
|
||||
case 2:
|
||||
return [
|
||||
PlayerSettings.player2.controls.NOTE_LEFT,
|
||||
PlayerSettings.player2.controls.NOTE_DOWN,
|
||||
PlayerSettings.player2.controls.NOTE_UP,
|
||||
PlayerSettings.player2.controls.NOTE_RIGHT,
|
||||
].contains(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time a note is hit, check if the note is from the same strumline.
|
||||
* If it is, then play the sing animation.
|
||||
*/
|
||||
public override function onNoteHit(event:NoteScriptEvent)
|
||||
{
|
||||
super.onNoteHit(event);
|
||||
|
||||
if (event.note.mustPress && characterType == BF)
|
||||
{
|
||||
// If the note is from the same strumline, play the sing animation.
|
||||
this.playSingAnimation(event.note.data.dir, false, event.note.data.altNote);
|
||||
holdTimer = 0;
|
||||
}
|
||||
else if (!event.note.mustPress && characterType == DAD)
|
||||
{
|
||||
// If the note is from the same strumline, play the sing animation.
|
||||
this.playSingAnimation(event.note.data.dir, false, event.note.data.altNote);
|
||||
holdTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time a note is missed, check if the note is from the same strumline.
|
||||
* If it is, then play the sing animation.
|
||||
*/
|
||||
public override function onNoteMiss(event:NoteScriptEvent)
|
||||
{
|
||||
super.onNoteMiss(event);
|
||||
|
||||
if (event.note.mustPress && characterType == BF)
|
||||
{
|
||||
// If the note is from the same strumline, play the sing animation.
|
||||
this.playSingAnimation(event.note.data.dir, true, event.note.data.altNote);
|
||||
}
|
||||
else if (!event.note.mustPress && characterType == DAD)
|
||||
{
|
||||
// If the note is from the same strumline, play the sing animation.
|
||||
this.playSingAnimation(event.note.data.dir, true, event.note.data.altNote);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time a wrong key is pressed, play the miss animation if we are Boyfriend.
|
||||
*/
|
||||
public override function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
||||
{
|
||||
super.onNoteGhostMiss(event);
|
||||
|
||||
if (event.eventCanceled || !event.playAnim)
|
||||
{
|
||||
// Skipping...
|
||||
return;
|
||||
}
|
||||
|
||||
if (characterType == BF)
|
||||
{
|
||||
trace('Playing ghost miss animation...');
|
||||
// If the note is from the same strumline, play the sing animation.
|
||||
this.playSingAnimation(event.dir, true, null);
|
||||
}
|
||||
}
|
||||
|
||||
public override function onDestroy(event:ScriptEvent):Void
|
||||
{
|
||||
this.characterType = OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the appropriate singing animation, for the given note direction.
|
||||
* @param dir The direction of the note.
|
||||
* @param miss If true, play the miss animation instead of the sing animation.
|
||||
* @param suffix A suffix to append to the animation name, like `alt`.
|
||||
*/
|
||||
public function playSingAnimation(dir:NoteDir, miss:Bool = false, suffix:String = ""):Void
|
||||
{
|
||||
var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != "" ? '-${suffix}' : ''}';
|
||||
|
||||
// restart even if already playing, because the character might sing the same note twice.
|
||||
playAnimation(anim, true);
|
||||
}
|
||||
}
|
||||
|
||||
enum CharacterType
|
||||
{
|
||||
BF;
|
||||
DAD;
|
||||
GF;
|
||||
OTHER;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package funkin.play.character;
|
||||
|
||||
enum CharacterType
|
||||
{
|
||||
BF;
|
||||
GF;
|
||||
DAD;
|
||||
OTHER;
|
||||
}
|
591
source/funkin/play/character/CharacterData.hx
Normal file
591
source/funkin/play/character/CharacterData.hx
Normal file
|
@ -0,0 +1,591 @@
|
|||
package funkin.play.character;
|
||||
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.MultiSparrowCharacter;
|
||||
import funkin.play.character.PackerCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedBaseCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedMultiSparrowCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedPackerCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
|
||||
import funkin.play.character.SparrowCharacter;
|
||||
import funkin.util.VersionUtil;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import haxe.Json;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class CharacterDataParser
|
||||
{
|
||||
/**
|
||||
* The current version string for the stage data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final CHARACTER_DATA_VERSION:String = "1.0.0";
|
||||
|
||||
/**
|
||||
* The current version rule check for the stage data format.
|
||||
*/
|
||||
public static final CHARACTER_DATA_VERSION_RULE:String = "1.0.x";
|
||||
|
||||
static final characterCache:Map<String, CharacterData> = new Map<String, CharacterData>();
|
||||
static final characterScriptedClass:Map<String, String> = new Map<String, String>();
|
||||
|
||||
static final DEFAULT_CHAR_ID:String = 'UNKNOWN';
|
||||
|
||||
/**
|
||||
* Parses and preloads the game's stage data and scripts when the game starts.
|
||||
*
|
||||
* If you want to force stages to be reloaded, you can just call this function again.
|
||||
*/
|
||||
public static function loadCharacterCache():Void
|
||||
{
|
||||
// Clear any stages that are cached if there were any.
|
||||
clearCharacterCache();
|
||||
trace("[CHARDATA] Loading character cache...");
|
||||
|
||||
//
|
||||
// UNSCRIPTED CHARACTERS
|
||||
//
|
||||
var charIdList:Array<String> = DataAssets.listDataFilesInPath('characters/');
|
||||
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool
|
||||
{
|
||||
return !characterCache.exists(charId);
|
||||
});
|
||||
trace(' Fetching data for ${unscriptedCharIds.length} characters...');
|
||||
for (charId in unscriptedCharIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var charData:CharacterData = parseCharacterData(charId);
|
||||
if (charData != null)
|
||||
{
|
||||
trace(' Loaded character data: ${charId}');
|
||||
characterCache.set(charId, charData);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// Assume error was already logged.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SCRIPTED CHARACTERS
|
||||
//
|
||||
|
||||
// Fuck I wish scripted classes supported static functions.
|
||||
|
||||
var scriptedCharClassNames1:Array<String> = ScriptedSparrowCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames1.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames1.length} (Sparrow) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames1)
|
||||
{
|
||||
var character = ScriptedSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
}
|
||||
|
||||
var scriptedCharClassNames2:Array<String> = ScriptedPackerCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames2.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames2.length} (Packer) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames2)
|
||||
{
|
||||
var character = ScriptedPackerCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
}
|
||||
|
||||
var scriptedCharClassNames3:Array<String> = ScriptedMultiSparrowCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames3.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames3)
|
||||
{
|
||||
var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
if (character == null)
|
||||
{
|
||||
trace(' Failed to instantiate scripted character: ${charCls}');
|
||||
continue;
|
||||
}
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Only instantiate the ones not populated above.
|
||||
// ScriptedBaseCharacter.listScriptClasses() will pick up scripts extending the other classes.
|
||||
var scriptedCharClassNames:Array<String> = ScriptedBaseCharacter.listScriptClasses();
|
||||
scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool
|
||||
{
|
||||
return !(scriptedCharClassNames1.contains(charCls)
|
||||
|| scriptedCharClassNames2.contains(charCls)
|
||||
|| scriptedCharClassNames3.contains(charCls));
|
||||
});
|
||||
|
||||
if (scriptedCharClassNames.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames)
|
||||
{
|
||||
var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
if (character == null)
|
||||
{
|
||||
trace(' Failed to instantiate scripted character: ${charCls}');
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace(' Successfully instantiated scripted character: ${charCls}');
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace(' Successfully loaded ${Lambda.count(characterCache)} stages.');
|
||||
}
|
||||
|
||||
public static function fetchCharacter(charId:String):Null<BaseCharacter>
|
||||
{
|
||||
if (charId == null || charId == '')
|
||||
{
|
||||
// Gracefully handle songs that don't use this character.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (characterCache.exists(charId))
|
||||
{
|
||||
var charData:CharacterData = characterCache.get(charId);
|
||||
var charScriptClass:String = characterScriptedClass.get(charId);
|
||||
|
||||
var char:BaseCharacter;
|
||||
|
||||
if (charScriptClass != null)
|
||||
{
|
||||
switch (charData.renderType)
|
||||
{
|
||||
case CharacterRenderType.MULTISPARROW:
|
||||
char = ScriptedMultiSparrowCharacter.init(charScriptClass, charId);
|
||||
case CharacterRenderType.SPARROW:
|
||||
char = ScriptedSparrowCharacter.init(charScriptClass, charId);
|
||||
case CharacterRenderType.PACKER:
|
||||
char = ScriptedPackerCharacter.init(charScriptClass, charId);
|
||||
default:
|
||||
// We're going to assume that the script class does the rendering.
|
||||
char = ScriptedBaseCharacter.init(charScriptClass, charId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (charData.renderType)
|
||||
{
|
||||
case CharacterRenderType.MULTISPARROW:
|
||||
char = new MultiSparrowCharacter(charId);
|
||||
case CharacterRenderType.SPARROW:
|
||||
char = new SparrowCharacter(charId);
|
||||
case CharacterRenderType.PACKER:
|
||||
char = new PackerCharacter(charId);
|
||||
default:
|
||||
trace('[WARN] Creating character with undefined renderType ${charData.renderType}');
|
||||
char = new BaseCharacter(charId);
|
||||
}
|
||||
}
|
||||
|
||||
trace('[CHARDATA] Successfully instantiated character: ${charId}');
|
||||
|
||||
// Call onCreate only in the fetchCharacter() function, not at application initialization.
|
||||
ScriptEventDispatcher.callEvent(char, new ScriptEvent(ScriptEvent.CREATE));
|
||||
|
||||
return char;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[CHARDATA] Failed to build character, not found in cache: ${charId}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function fetchCharacterData(charId:String):Null<CharacterData>
|
||||
{
|
||||
if (characterCache.exists(charId))
|
||||
{
|
||||
return characterCache.get(charId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static function clearCharacterCache():Void
|
||||
{
|
||||
if (characterCache != null)
|
||||
{
|
||||
characterCache.clear();
|
||||
}
|
||||
if (characterScriptedClass != null)
|
||||
{
|
||||
characterScriptedClass.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a character's JSON file, parse its data, and return it.
|
||||
*
|
||||
* @param charId The character to load.
|
||||
* @return The character data, or null if validation failed.
|
||||
*/
|
||||
public static function parseCharacterData(charId:String):Null<CharacterData>
|
||||
{
|
||||
var rawJson:String = loadCharacterFile(charId);
|
||||
|
||||
var charData:CharacterData = migrateCharacterData(rawJson, charId);
|
||||
|
||||
return validateCharacterData(charId, charData);
|
||||
}
|
||||
|
||||
static function loadCharacterFile(charPath:String):String
|
||||
{
|
||||
var charFilePath:String = Paths.json('characters/${charPath}');
|
||||
var rawJson = StringTools.trim(Assets.getText(charFilePath));
|
||||
|
||||
while (!StringTools.endsWith(rawJson, "}"))
|
||||
{
|
||||
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||
}
|
||||
|
||||
return rawJson;
|
||||
}
|
||||
|
||||
static function migrateCharacterData(rawJson:String, charId:String)
|
||||
{
|
||||
// If you update the character data format in a breaking way,
|
||||
// handle migration here by checking the `version` value.
|
||||
|
||||
try
|
||||
{
|
||||
var charData:CharacterData = cast Json.parse(rawJson);
|
||||
return charData;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' Error parsing data for character: ${charId}');
|
||||
trace(' ${e}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default time the character should sing for, in beats.
|
||||
* Values that are too low will cause the character to stop singing between notes.
|
||||
* Originally, this value was set to 1, but it was changed to 2 because that became
|
||||
* too low after some other code changes.
|
||||
*/
|
||||
static final DEFAULT_SINGTIME:Float = 2.0;
|
||||
|
||||
static final DEFAULT_DANCEEVERY:Int = 1;
|
||||
static final DEFAULT_FLIPX:Bool = false;
|
||||
static final DEFAULT_FLIPY:Bool = false;
|
||||
static final DEFAULT_FRAMERATE:Int = 24;
|
||||
static final DEFAULT_ISPIXEL:Bool = false;
|
||||
static final DEFAULT_LOOP:Bool = false;
|
||||
static final DEFAULT_NAME:String = "Untitled Character";
|
||||
static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
||||
static final DEFAULT_HEALTHICON_OFFSETS:Array<Int> = [0, 25];
|
||||
static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.SPARROW;
|
||||
static final DEFAULT_SCALE:Float = 1;
|
||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||
static final DEFAULT_STARTINGANIM:String = "idle";
|
||||
|
||||
/**
|
||||
* Set unspecified parameters to their defaults.
|
||||
* If the parameter is mandatory, print an error message.
|
||||
* @param id
|
||||
* @param input
|
||||
* @return The validated character data
|
||||
*/
|
||||
static function validateCharacterData(id:String, input:CharacterData):Null<CharacterData>
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
// trace('[CHARDATA] ERROR: Could not parse character data for "${id}".');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.version == null)
|
||||
{
|
||||
trace('[CHARDATA] WARN: No semantic version specified for character data file "$id", assuming ${CHARACTER_DATA_VERSION}');
|
||||
input.version = CHARACTER_DATA_VERSION;
|
||||
}
|
||||
|
||||
if (!VersionUtil.validateVersion(input.version, CHARACTER_DATA_VERSION_RULE))
|
||||
{
|
||||
trace('[CHARDATA] ERROR: Could not load character data for "$id": bad version (got ${input.version}, expected ${CHARACTER_DATA_VERSION_RULE})');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.name == null)
|
||||
{
|
||||
trace('[CHARDATA] WARN: Character data for "$id" missing name');
|
||||
input.name = DEFAULT_NAME;
|
||||
}
|
||||
|
||||
if (input.renderType == null)
|
||||
{
|
||||
input.renderType = DEFAULT_RENDERTYPE;
|
||||
}
|
||||
|
||||
if (input.assetPath == null)
|
||||
{
|
||||
trace('[CHARDATA] ERROR: Could not load character data for "$id": missing assetPath');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.offsets == null)
|
||||
{
|
||||
input.offsets = DEFAULT_OFFSETS;
|
||||
}
|
||||
|
||||
if (input.cameraOffsets == null)
|
||||
{
|
||||
input.cameraOffsets = DEFAULT_OFFSETS;
|
||||
}
|
||||
|
||||
if (input.healthIcon == null)
|
||||
{
|
||||
input.healthIcon = {
|
||||
id: null,
|
||||
scale: null,
|
||||
offsets: null
|
||||
};
|
||||
}
|
||||
|
||||
if (input.healthIcon.id == null)
|
||||
{
|
||||
input.healthIcon.id = id;
|
||||
}
|
||||
|
||||
if (input.healthIcon.scale == null)
|
||||
{
|
||||
input.healthIcon.scale = DEFAULT_SCALE;
|
||||
}
|
||||
|
||||
if (input.healthIcon.offsets == null)
|
||||
{
|
||||
input.healthIcon.offsets = DEFAULT_OFFSETS;
|
||||
}
|
||||
|
||||
if (input.startingAnimation == null)
|
||||
{
|
||||
input.startingAnimation = DEFAULT_STARTINGANIM;
|
||||
}
|
||||
|
||||
if (input.scale == null)
|
||||
{
|
||||
input.scale = DEFAULT_SCALE;
|
||||
}
|
||||
|
||||
if (input.isPixel == null)
|
||||
{
|
||||
input.isPixel = DEFAULT_ISPIXEL;
|
||||
}
|
||||
|
||||
if (input.danceEvery == null)
|
||||
{
|
||||
input.danceEvery = DEFAULT_DANCEEVERY;
|
||||
}
|
||||
|
||||
if (input.singTime == null)
|
||||
{
|
||||
input.singTime = DEFAULT_SINGTIME;
|
||||
}
|
||||
|
||||
if (input.animations == null || input.animations.length == 0)
|
||||
{
|
||||
trace('[CHARDATA] ERROR: Could not load character data for "$id": missing animations');
|
||||
input.animations = [];
|
||||
}
|
||||
|
||||
if (input.flipX == null)
|
||||
{
|
||||
input.flipX = DEFAULT_FLIPX;
|
||||
}
|
||||
|
||||
if (input.animations.length == 0 && input.startingAnimation != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (inputAnimation in input.animations)
|
||||
{
|
||||
if (inputAnimation.name == null)
|
||||
{
|
||||
trace('[CHARDATA] ERROR: Could not load character data for "$id": missing animation name for prop "${input.name}"');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inputAnimation.frameRate == null)
|
||||
{
|
||||
inputAnimation.frameRate = DEFAULT_FRAMERATE;
|
||||
}
|
||||
|
||||
if (inputAnimation.offsets == null)
|
||||
{
|
||||
inputAnimation.offsets = DEFAULT_OFFSETS;
|
||||
}
|
||||
|
||||
if (inputAnimation.looped == null)
|
||||
{
|
||||
inputAnimation.looped = DEFAULT_LOOP;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipX == null)
|
||||
{
|
||||
inputAnimation.flipX = DEFAULT_FLIPX;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipY == null)
|
||||
{
|
||||
inputAnimation.flipY = DEFAULT_FLIPY;
|
||||
}
|
||||
}
|
||||
|
||||
// All good!
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract CharacterRenderType(String) from String to String
|
||||
{
|
||||
var SPARROW = 'sparrow';
|
||||
var PACKER = 'packer';
|
||||
var MULTISPARROW = 'multisparrow';
|
||||
// TODO: FlxSpine?
|
||||
// https://api.haxeflixel.com/flixel/addons/editors/spine/FlxSpine.html
|
||||
// TODO: Aseprite?
|
||||
// https://lib.haxe.org/p/openfl-aseprite/
|
||||
// TODO: Animate?
|
||||
// https://lib.haxe.org/p/flxanimate
|
||||
// TODO: REDACTED
|
||||
}
|
||||
|
||||
typedef CharacterData =
|
||||
{
|
||||
/**
|
||||
* The sematic version number of the character data JSON format.
|
||||
*/
|
||||
var version:String;
|
||||
|
||||
/**
|
||||
* The readable name of the character.
|
||||
*/
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The type of rendering system to use for the character.
|
||||
* @default sparrow
|
||||
*/
|
||||
var renderType:CharacterRenderType;
|
||||
|
||||
/**
|
||||
* Behavior varies by render type:
|
||||
* - SPARROW: Path to retrieve both the spritesheet and the XML data from.
|
||||
* - PACKER: Path to retrieve both the spritsheet and the TXT data from.
|
||||
*/
|
||||
var assetPath:String;
|
||||
|
||||
/**
|
||||
* The scale of the graphic as a float.
|
||||
* Pro tip: On pixel-art levels, save the sprites small and set this value to 6 or so to save memory.
|
||||
* @default 1
|
||||
*/
|
||||
var scale:Null<Float>;
|
||||
|
||||
/**
|
||||
* Optional data about the health icon for the character.
|
||||
*/
|
||||
var healthIcon:Null<HealthIconData>;
|
||||
|
||||
/**
|
||||
* The global offset to the character's position, in pixels.
|
||||
* @default [0, 0]
|
||||
*/
|
||||
var offsets:Null<Array<Float>>;
|
||||
|
||||
/**
|
||||
* The amount to offset the camera by while focusing on this character.
|
||||
* Default value focuses on the character directly.
|
||||
* @default [0, 0]
|
||||
*/
|
||||
var cameraOffsets:Array<Float>;
|
||||
|
||||
/**
|
||||
* Setting this to true disables anti-aliasing for the character.
|
||||
* @default false
|
||||
*/
|
||||
var isPixel:Null<Bool>;
|
||||
|
||||
/**
|
||||
* The frequency at which the character will play its idle animation, in beats.
|
||||
* Increasing this number will make the character dance less often.
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
var danceEvery:Null<Int>;
|
||||
|
||||
/**
|
||||
* The minimum duration that a character will play a note animation for, in beats.
|
||||
* If this number is too low, you may see the character start playing the idle animation between notes.
|
||||
* If this number is too high, you may see the the character play the sing animation for too long after the notes are gone.
|
||||
*
|
||||
* Examples:
|
||||
* - Daddy Dearest uses a value of `1.525`.
|
||||
* @default 1.0
|
||||
*/
|
||||
var singTime:Null<Float>;
|
||||
|
||||
/**
|
||||
* An optional array of animations which the character can play.
|
||||
*/
|
||||
var animations:Array<AnimationData>;
|
||||
|
||||
/**
|
||||
* If animations are used, this is the name of the animation to play first.
|
||||
* @default idle
|
||||
*/
|
||||
var startingAnimation:Null<String>;
|
||||
|
||||
/**
|
||||
* Whether or not the whole ass sprite is flipped by default.
|
||||
* Useful for characters that could also be played (Pico)
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
var flipX:Null<Bool>;
|
||||
};
|
||||
|
||||
typedef HealthIconData =
|
||||
{
|
||||
/**
|
||||
* The ID to use for the health icon.
|
||||
* @default The character's ID
|
||||
*/
|
||||
var id:Null<String>;
|
||||
|
||||
/**
|
||||
* The scale of the health icon.
|
||||
*/
|
||||
var scale:Null<Float>;
|
||||
|
||||
/**
|
||||
* The offset of the health icon, in pixels.
|
||||
* @default [0, 25]
|
||||
*/
|
||||
var offsets:Null<Array<Float>>;
|
||||
}
|
212
source/funkin/play/character/MultiSparrowCharacter.hx
Normal file
212
source/funkin/play/character/MultiSparrowCharacter.hx
Normal file
|
@ -0,0 +1,212 @@
|
|||
package funkin.play.character;
|
||||
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
|
||||
/**
|
||||
* For some characters which use Sparrow atlases, the spritesheets need to be split
|
||||
* into multiple files. This character renderer handles by showing the appropriate sprite.
|
||||
*
|
||||
* Examples in base game include BF Holding GF (most of the sprites are in one file
|
||||
* but the death animation is in a separate file).
|
||||
* Only example I can think of in mods is Tricky (which has a separate file for each animation).
|
||||
*
|
||||
* BaseCharacter has game logic, SparrowCharacter has only rendering logic.
|
||||
* KEEP THEM SEPARATE!
|
||||
*/
|
||||
class MultiSparrowCharacter extends BaseCharacter
|
||||
{
|
||||
/**
|
||||
* The actual group which holds all spritesheets this character uses.
|
||||
*/
|
||||
private var members:Map<String, FlxFramesCollection> = new Map<String, FlxFramesCollection>();
|
||||
|
||||
/**
|
||||
* A map between animation names and what frame collection the animation should use.
|
||||
*/
|
||||
private var animAssetPath:Map<String, String> = new Map<String, String>();
|
||||
|
||||
/**
|
||||
* The current frame collection being used.
|
||||
*/
|
||||
private var activeMember:String;
|
||||
|
||||
public function new(id:String)
|
||||
{
|
||||
super(id);
|
||||
}
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating MULTI SPARROW CHARACTER: ' + this.characterId);
|
||||
|
||||
buildSprites();
|
||||
super.onCreate(event);
|
||||
}
|
||||
|
||||
function buildSprites()
|
||||
{
|
||||
buildSpritesheets();
|
||||
buildAnimations();
|
||||
|
||||
playAnimation(_data.startingAnimation);
|
||||
|
||||
if (_data.isPixel)
|
||||
{
|
||||
this.antialiasing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.antialiasing = true;
|
||||
}
|
||||
}
|
||||
|
||||
function buildSpritesheets()
|
||||
{
|
||||
// Build the list of asset paths to use.
|
||||
// Ignore nulls and duplicates.
|
||||
var assetList = [_data.assetPath];
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
if (anim.assetPath != null && !assetList.contains(anim.assetPath))
|
||||
{
|
||||
assetList.push(anim.assetPath);
|
||||
}
|
||||
animAssetPath.set(anim.name, anim.assetPath);
|
||||
}
|
||||
|
||||
// Load the Sparrow atlas for each path and store them in the members map.
|
||||
for (asset in assetList)
|
||||
{
|
||||
var texture:FlxFramesCollection = Paths.getSparrowAtlas(asset, 'shared');
|
||||
// If we don't do this, the unused textures will be removed as soon as they're loaded.
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
trace('Multi-Sparrow atlas could not load texture: ${asset}');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Adding multi-sparrow atlas: ${asset}');
|
||||
texture.parent.destroyOnNoUse = false;
|
||||
members.set(asset, texture);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the default frame collection to start.
|
||||
loadFramesByAssetPath(_data.assetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace this sprite's animation frames with the ones at this asset path.
|
||||
*/
|
||||
function loadFramesByAssetPath(assetPath:String):Void
|
||||
{
|
||||
if (_data.assetPath == null)
|
||||
{
|
||||
trace('[ERROR] Multi-Sparrow character has no default asset path!');
|
||||
return;
|
||||
}
|
||||
if (assetPath == null)
|
||||
{
|
||||
// trace('Asset path is null, falling back to default. This is normal!');
|
||||
loadFramesByAssetPath(_data.assetPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeMember == assetPath)
|
||||
{
|
||||
// trace('Already using this asset path: ${assetPath}');
|
||||
return;
|
||||
}
|
||||
|
||||
if (members.exists(assetPath))
|
||||
{
|
||||
// Switch to a new set of sprites.
|
||||
trace('Loading frames from asset path: ${assetPath}');
|
||||
this.frames = members.get(assetPath);
|
||||
this.activeMember = assetPath;
|
||||
this.setScale(_data.scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] MultiSparrow character ${characterId} could not find asset path: ${assetPath}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace this sprite's animation frames with the ones needed to play this animation.
|
||||
*/
|
||||
function loadFramesByAnimName(animName)
|
||||
{
|
||||
if (animAssetPath.exists(animName))
|
||||
{
|
||||
loadFramesByAssetPath(animAssetPath.get(animName));
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] MultiSparrow character ${characterId} could not find animation: ${animName}');
|
||||
}
|
||||
}
|
||||
|
||||
function buildAnimations()
|
||||
{
|
||||
trace('[MULTISPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
||||
|
||||
// We need to swap to the proper frame collection before adding the animations, I think?
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
loadFramesByAnimName(anim.name);
|
||||
FlxAnimationUtil.addAtlasAnimation(this, anim);
|
||||
|
||||
if (anim.offsets == null)
|
||||
{
|
||||
setAnimationOffsets(anim.name, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
||||
}
|
||||
}
|
||||
|
||||
var animNames = this.animation.getNameList();
|
||||
trace('[MULTISPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
||||
}
|
||||
|
||||
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
||||
{
|
||||
loadFramesByAnimName(name);
|
||||
super.playAnimation(name, restart, ignoreOther);
|
||||
}
|
||||
|
||||
override function set_frames(value:FlxFramesCollection):FlxFramesCollection
|
||||
{
|
||||
// DISABLE THIS SO WE DON'T DESTROY OUR HARD WORK
|
||||
// WE WILL MAKE SURE TO LOAD THE PROPER SPRITESHEET BEFORE PLAYING AN ANIM
|
||||
// if (animation != null)
|
||||
// {
|
||||
// animation.destroyAnimations();
|
||||
// }
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
graphic = value.parent;
|
||||
this.frames = value;
|
||||
this.frame = value.getByIndex(0);
|
||||
this.numFrames = value.numFrames;
|
||||
resetHelpers();
|
||||
this.bakedRotationAngle = 0;
|
||||
this.animation.frameIndex = 0;
|
||||
graphicLoaded();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.frames = null;
|
||||
this.frame = null;
|
||||
this.graphic = null;
|
||||
}
|
||||
|
||||
return this.frames;
|
||||
}
|
||||
}
|
77
source/funkin/play/character/PackerCharacter.hx
Normal file
77
source/funkin/play/character/PackerCharacter.hx
Normal file
|
@ -0,0 +1,77 @@
|
|||
package funkin.play.character;
|
||||
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
|
||||
/**
|
||||
* A PackerCharacter is a Character which is rendered by
|
||||
* displaying an animation derived from a Packer spritesheet file.
|
||||
*/
|
||||
class PackerCharacter extends BaseCharacter
|
||||
{
|
||||
public function new(id:String)
|
||||
{
|
||||
super(id);
|
||||
}
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating PACKER CHARACTER: ' + this.characterId);
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
|
||||
playAnimation(_data.startingAnimation);
|
||||
|
||||
super.onCreate(event);
|
||||
}
|
||||
|
||||
function loadSpritesheet()
|
||||
{
|
||||
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath, 'shared');
|
||||
if (tex == null)
|
||||
{
|
||||
trace('Could not load Packer sprite: ${_data.assetPath}');
|
||||
return;
|
||||
}
|
||||
|
||||
this.frames = tex;
|
||||
|
||||
if (_data.isPixel)
|
||||
{
|
||||
this.antialiasing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.antialiasing = true;
|
||||
}
|
||||
|
||||
this.setScale(_data.scale);
|
||||
}
|
||||
|
||||
function loadAnimations()
|
||||
{
|
||||
trace('[PACKERCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
||||
|
||||
FlxAnimationUtil.addAtlasAnimations(this, _data.animations);
|
||||
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
if (anim.offsets == null)
|
||||
{
|
||||
setAnimationOffsets(anim.name, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
||||
}
|
||||
}
|
||||
|
||||
var animNames = this.animation.getNameList();
|
||||
trace('[PACKERCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
||||
}
|
||||
}
|
23
source/funkin/play/character/ScriptedCharacter.hx
Normal file
23
source/funkin/play/character/ScriptedCharacter.hx
Normal file
|
@ -0,0 +1,23 @@
|
|||
package funkin.play.character;
|
||||
|
||||
import funkin.play.character.PackerCharacter;
|
||||
import funkin.play.character.SparrowCharacter;
|
||||
import funkin.play.character.MultiSparrowCharacter;
|
||||
import funkin.modding.IHook;
|
||||
|
||||
/**
|
||||
* Note: Making a scripted class extending BaseCharacter is not recommended.
|
||||
* Do so ONLY if are handling all the character rendering yourself,
|
||||
* and can't use one of the built-in render modes.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedBaseCharacter extends BaseCharacter implements IHook {}
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedSparrowCharacter extends SparrowCharacter implements IHook {}
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedMultiSparrowCharacter extends MultiSparrowCharacter implements IHook {}
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedPackerCharacter extends PackerCharacter implements IHook {}
|
79
source/funkin/play/character/SparrowCharacter.hx
Normal file
79
source/funkin/play/character/SparrowCharacter.hx
Normal file
|
@ -0,0 +1,79 @@
|
|||
package funkin.play.character;
|
||||
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
|
||||
/**
|
||||
* A SparrowCharacter is a Character which is rendered by
|
||||
* displaying an animation derived from a SparrowV2 atlas spritesheet file.
|
||||
*
|
||||
* BaseCharacter has game logic, SparrowCharacter has only rendering logic.
|
||||
* KEEP THEM SEPARATE!
|
||||
*/
|
||||
class SparrowCharacter extends BaseCharacter
|
||||
{
|
||||
public function new(id:String)
|
||||
{
|
||||
super(id);
|
||||
}
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating SPARROW CHARACTER: ' + this.characterId);
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
|
||||
playAnimation(_data.startingAnimation);
|
||||
|
||||
super.onCreate(event);
|
||||
}
|
||||
|
||||
function loadSpritesheet()
|
||||
{
|
||||
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath, 'shared');
|
||||
if (tex == null)
|
||||
{
|
||||
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
||||
return;
|
||||
}
|
||||
|
||||
this.frames = tex;
|
||||
|
||||
if (_data.isPixel)
|
||||
{
|
||||
this.antialiasing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.antialiasing = true;
|
||||
}
|
||||
|
||||
this.setScale(_data.scale);
|
||||
}
|
||||
|
||||
function loadAnimations()
|
||||
{
|
||||
trace('[SPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
||||
|
||||
FlxAnimationUtil.addAtlasAnimations(this, _data.animations);
|
||||
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
if (anim.offsets == null)
|
||||
{
|
||||
setAnimationOffsets(anim.name, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
||||
}
|
||||
}
|
||||
|
||||
var animNames = this.animation.getNameList();
|
||||
trace('[SPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
@ -36,6 +37,14 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
*/
|
||||
public var idleSuffix(default, set):String = "";
|
||||
|
||||
/**
|
||||
* Whether this bopper should bop every beat. By default it's true, but when used
|
||||
* for characters/players, it should be false so it doesn't cut off their animations!!!!!
|
||||
*/
|
||||
public var shouldBop:Bool = true;
|
||||
|
||||
public var finishCallbackMap:Map<String, Void->Void> = new Map<String, Void->Void>();
|
||||
|
||||
function set_idleSuffix(value:String):String
|
||||
{
|
||||
this.idleSuffix = value;
|
||||
|
@ -76,6 +85,11 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
{
|
||||
super();
|
||||
this.danceEvery = danceEvery;
|
||||
this.animation.finishCallback = function(name)
|
||||
{
|
||||
if (finishCallbackMap[name] != null)
|
||||
finishCallbackMap[name]();
|
||||
};
|
||||
}
|
||||
|
||||
function update_shouldAlternate():Void
|
||||
|
@ -93,14 +107,14 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
{
|
||||
if (danceEvery > 0 && event.beat % danceEvery == 0)
|
||||
{
|
||||
dance(true);
|
||||
dance(shouldBop);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every `danceEvery` beats of the song.
|
||||
*/
|
||||
public function dance(force:Bool = false):Void
|
||||
public function dance(forceRestart:Bool = false):Void
|
||||
{
|
||||
if (this.animation == null)
|
||||
{
|
||||
|
@ -116,17 +130,17 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
{
|
||||
if (hasDanced)
|
||||
{
|
||||
playAnimation('danceRight$idleSuffix', true);
|
||||
playAnimation('danceRight$idleSuffix', forceRestart);
|
||||
}
|
||||
else
|
||||
{
|
||||
playAnimation('danceLeft$idleSuffix', true);
|
||||
playAnimation('danceLeft$idleSuffix', forceRestart);
|
||||
}
|
||||
hasDanced = !hasDanced;
|
||||
}
|
||||
else
|
||||
{
|
||||
playAnimation('idle$idleSuffix', true);
|
||||
playAnimation('idle$idleSuffix', forceRestart);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,18 +187,39 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
}
|
||||
}
|
||||
|
||||
public var canPlayOtherAnims:Bool = true;
|
||||
|
||||
/**
|
||||
* @param name The name of the animation to play.
|
||||
* @param restart Whether to restart the animation if it is already playing.
|
||||
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
|
||||
*/
|
||||
public function playAnimation(name:String, restart:Bool = false):Void
|
||||
public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
||||
{
|
||||
if (ignoreOther == null)
|
||||
ignoreOther = false;
|
||||
|
||||
if (!canPlayOtherAnims)
|
||||
return;
|
||||
|
||||
var correctName = correctAnimationName(name);
|
||||
if (correctName == null)
|
||||
return;
|
||||
|
||||
this.animation.play(correctName, restart, false, 0);
|
||||
|
||||
if (ignoreOther)
|
||||
{
|
||||
canPlayOtherAnims = false;
|
||||
|
||||
// doing it with this funny map, since overriding the animation.finishCallback is a bit messier IMO
|
||||
finishCallbackMap[name] = function()
|
||||
{
|
||||
canPlayOtherAnims = true;
|
||||
finishCallbackMap[name] = null;
|
||||
};
|
||||
}
|
||||
|
||||
applyAnimationOffsets(correctName);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,4 @@ package funkin.play.stage;
|
|||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
@:keep
|
||||
class ScriptedBopper extends Bopper implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
||||
class ScriptedBopper extends Bopper implements IHook {}
|
||||
|
|
|
@ -3,7 +3,4 @@ package funkin.play.stage;
|
|||
import funkin.modding.IHook;
|
||||
|
||||
@:hscriptClass
|
||||
class ScriptedStage extends Stage implements IHook
|
||||
{
|
||||
// No body needed for this class, it's magic ;)
|
||||
}
|
||||
class ScriptedStage extends Stage implements IHook {}
|
||||
|
|
|
@ -2,12 +2,14 @@ package funkin.play.stage;
|
|||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.modding.IHook;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.Character.CharacterType;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.stage.StageData.StageDataCharacter;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
|
@ -27,7 +29,7 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
public var camZoom:Float = 1.0;
|
||||
|
||||
var namedProps:Map<String, FlxSprite> = new Map<String, FlxSprite>();
|
||||
var characters:Map<String, Character> = new Map<String, Character>();
|
||||
var characters:Map<String, BaseCharacter> = new Map<String, BaseCharacter>();
|
||||
var boppers:Array<Bopper> = new Array<Bopper>();
|
||||
|
||||
/**
|
||||
|
@ -158,6 +160,14 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(propSprite, Bopper))
|
||||
{
|
||||
for (propAnim in dataProp.animations)
|
||||
{
|
||||
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataProp.startingAnimation != null)
|
||||
{
|
||||
propSprite.animation.play(dataProp.startingAnimation);
|
||||
|
@ -206,7 +216,6 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
public function refresh()
|
||||
{
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
trace('Stage sorted by z-index');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,53 +241,116 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
/**
|
||||
* Used by the PlayState to add a character to the stage.
|
||||
*/
|
||||
public function addCharacter(character:Character, charType:CharacterType)
|
||||
public function addCharacter(character:BaseCharacter, charType:CharacterType)
|
||||
{
|
||||
if (character == null)
|
||||
return;
|
||||
|
||||
#if debug
|
||||
// Temporary marker that shows where the character's location is relative to.
|
||||
// Should display at the stage position of the character (before any offsets).
|
||||
// TODO: Make this a toggle? It's useful to turn on from time to time.
|
||||
var debugIcon:FlxSprite = new FlxSprite(0, 0);
|
||||
debugIcon.makeGraphic(8, 8, 0xffff00ff);
|
||||
debugIcon.visible = false;
|
||||
debugIcon.zIndex = 1000000;
|
||||
#end
|
||||
|
||||
// Apply position and z-index.
|
||||
var charData:StageDataCharacter = null;
|
||||
switch (charType)
|
||||
{
|
||||
case BF:
|
||||
this.characters.set("bf", character);
|
||||
character.zIndex = _data.characters.bf.zIndex;
|
||||
character.x = _data.characters.bf.position[0];
|
||||
character.y = _data.characters.bf.position[1];
|
||||
charData = _data.characters.bf;
|
||||
character.flipX = !character.flipX;
|
||||
// flip offsets if flipX
|
||||
character.initHealthIcon(false);
|
||||
case GF:
|
||||
this.characters.set("gf", character);
|
||||
character.zIndex = _data.characters.gf.zIndex;
|
||||
character.x = _data.characters.gf.position[0];
|
||||
character.y = _data.characters.gf.position[1];
|
||||
charData = _data.characters.gf;
|
||||
case DAD:
|
||||
this.characters.set("dad", character);
|
||||
character.zIndex = _data.characters.dad.zIndex;
|
||||
character.x = _data.characters.dad.position[0];
|
||||
character.y = _data.characters.dad.position[1];
|
||||
charData = _data.characters.dad;
|
||||
// flip offsets if flipX
|
||||
character.initHealthIcon(true);
|
||||
default:
|
||||
this.characters.set(character.curCharacter, character);
|
||||
this.characters.set(character.characterId, character);
|
||||
}
|
||||
if (charData != null)
|
||||
{
|
||||
character.zIndex = charData.zIndex;
|
||||
|
||||
// Start with the per-stage character position.
|
||||
// Subtracting the origin ensures characters are positioned relative to their feet.
|
||||
// Subtracting the global offset allows positioning on a per-character basis.
|
||||
character.x = charData.position[0] - character.characterOrigin.x + character.globalOffsets[0];
|
||||
character.y = charData.position[1] - character.characterOrigin.y + character.globalOffsets[1];
|
||||
|
||||
character.cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
character.cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
|
||||
// Draw the debug icon at the character's feet.
|
||||
debugIcon.x = charData.position[0];
|
||||
debugIcon.y = charData.position[1];
|
||||
}
|
||||
|
||||
// Add the character to the scene.
|
||||
this.add(character);
|
||||
this.add(debugIcon);
|
||||
}
|
||||
|
||||
public inline function getGirlfriendPosition():FlxPoint
|
||||
{
|
||||
return new FlxPoint(_data.characters.gf.position[0], _data.characters.gf.position[1]);
|
||||
}
|
||||
|
||||
public inline function getBoyfriendPosition():FlxPoint
|
||||
{
|
||||
return new FlxPoint(_data.characters.bf.position[0], _data.characters.bf.position[1]);
|
||||
}
|
||||
|
||||
public inline function getDadPosition():FlxPoint
|
||||
{
|
||||
return new FlxPoint(_data.characters.dad.position[0], _data.characters.dad.position[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a given character from the stage.
|
||||
*/
|
||||
public function getCharacter(id:String):Character
|
||||
public function getCharacter(id:String):BaseCharacter
|
||||
{
|
||||
return this.characters.get(id);
|
||||
}
|
||||
|
||||
public function getBoyfriend():Character
|
||||
/**
|
||||
* Retrieve the Boyfriend character.
|
||||
* @param pop If true, the character will be removed from the stage as well.
|
||||
*/
|
||||
public function getBoyfriend(?pop:Bool = false):BaseCharacter
|
||||
{
|
||||
return getCharacter('bf');
|
||||
if (pop)
|
||||
{
|
||||
var boyfriend:BaseCharacter = getCharacter("bf");
|
||||
|
||||
// Remove the character from the stage.
|
||||
this.remove(boyfriend);
|
||||
this.characters.remove("bf");
|
||||
|
||||
return boyfriend;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getCharacter('bf');
|
||||
}
|
||||
}
|
||||
|
||||
public function getGirlfriend():Character
|
||||
public function getGirlfriend():BaseCharacter
|
||||
{
|
||||
return getCharacter('gf');
|
||||
}
|
||||
|
||||
public function getDad():Character
|
||||
public function getDad():BaseCharacter
|
||||
{
|
||||
return getCharacter('dad');
|
||||
}
|
||||
|
@ -309,6 +381,32 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an event to all the characters in the stage.
|
||||
* @param event The script event to dispatch.
|
||||
*/
|
||||
public function dispatchToCharacters(event:ScriptEvent):Void
|
||||
{
|
||||
for (characterId in characters.keys())
|
||||
{
|
||||
dispatchToCharacter(characterId, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an event to a specific character.
|
||||
* @param characterId The ID of the character to dispatch to.
|
||||
* @param event The script event to dispatch.
|
||||
*/
|
||||
public function dispatchToCharacter(characterId:String, event:ScriptEvent):Void
|
||||
{
|
||||
var character:BaseCharacter = getCharacter(characterId);
|
||||
if (character != null)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(character, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* onDestroy gets called when the player is leaving the PlayState,
|
||||
* and is used to clean up any objects that need to be destroyed.
|
||||
|
|
|
@ -175,6 +175,8 @@ class StageDataParser
|
|||
static final DEFAULT_ISPIXEL:Bool = false;
|
||||
static final DEFAULT_NAME:String = "Untitled Stage";
|
||||
static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
||||
static final DEFAULT_CAMERA_OFFSETS_BF:Array<Float> = [-100, -100];
|
||||
static final DEFAULT_CAMERA_OFFSETS_DAD:Array<Float> = [150, -100];
|
||||
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
||||
static final DEFAULT_SCALE:Float = 1.0;
|
||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||
|
@ -339,10 +341,12 @@ class StageDataParser
|
|||
if (input.characters.bf == null)
|
||||
{
|
||||
input.characters.bf = DEFAULT_CHARACTER_DATA;
|
||||
input.characters.bf.cameraOffsets = DEFAULT_CAMERA_OFFSETS_BF;
|
||||
}
|
||||
if (input.characters.dad == null)
|
||||
{
|
||||
input.characters.dad = DEFAULT_CHARACTER_DATA;
|
||||
input.characters.dad.cameraOffsets = DEFAULT_CAMERA_OFFSETS_DAD;
|
||||
}
|
||||
if (input.characters.gf == null)
|
||||
{
|
||||
|
@ -361,7 +365,14 @@ class StageDataParser
|
|||
}
|
||||
if (inputCharacter.cameraOffsets == null || inputCharacter.cameraOffsets.length != 2)
|
||||
{
|
||||
inputCharacter.cameraOffsets = [0, 0];
|
||||
if (inputCharacter == input.characters.bf)
|
||||
inputCharacter.cameraOffsets = DEFAULT_CAMERA_OFFSETS_BF;
|
||||
else if (inputCharacter == input.characters.dad)
|
||||
inputCharacter.cameraOffsets = DEFAULT_CAMERA_OFFSETS_DAD;
|
||||
else
|
||||
{
|
||||
inputCharacter.cameraOffsets = [0, 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -484,7 +495,7 @@ typedef StageDataCharacter =
|
|||
|
||||
/**
|
||||
* The camera offsets to apply when focusing on the character on this stage.
|
||||
* @default [0, 0]
|
||||
* @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF
|
||||
*/
|
||||
cameraOffsets:Array<Float>,
|
||||
};
|
||||
|
|
|
@ -5,6 +5,9 @@ import flixel.graphics.frames.FlxAtlasFrames;
|
|||
|
||||
typedef AtlasAsset = flixel.util.typeLimit.OneOfTwo<String, FlxAtlasFrames>;
|
||||
|
||||
/**
|
||||
* A menulist whose items share a single texture atlas.
|
||||
*/
|
||||
class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
|
||||
{
|
||||
public var atlas:FlxAtlasFrames;
|
||||
|
@ -33,11 +36,16 @@ class AtlasMenuList extends MenuTypedList<AtlasMenuItem>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu list item which uses single texture atlas.
|
||||
*/
|
||||
class AtlasMenuItem extends MenuItem
|
||||
{
|
||||
var atlas:FlxAtlasFrames;
|
||||
|
||||
public function new(x = 0.0, y = 0.0, name:String, atlas:FlxAtlasFrames, callback)
|
||||
public var centered:Bool = false;
|
||||
|
||||
public function new(x = 0.0, y = 0.0, name:String, atlas, callback)
|
||||
{
|
||||
this.atlas = atlas;
|
||||
super(x, y, name, callback);
|
||||
|
@ -52,10 +60,17 @@ class AtlasMenuItem extends MenuItem
|
|||
super.setData(name, callback);
|
||||
}
|
||||
|
||||
function changeAnim(animName:String)
|
||||
public function changeAnim(animName:String)
|
||||
{
|
||||
animation.play(animName);
|
||||
updateHitbox();
|
||||
|
||||
if (centered)
|
||||
{
|
||||
// position by center
|
||||
centerOrigin();
|
||||
offset.copyFrom(origin);
|
||||
}
|
||||
}
|
||||
|
||||
override function idle()
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package funkin.ui.animDebugShit;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.character.SparrowCharacter;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.ui.FlxInputText;
|
||||
import flixel.addons.ui.FlxUIDropDownMenu;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.group.FlxGroup;
|
||||
|
@ -15,6 +17,7 @@ import flixel.text.FlxText;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
import openfl.Assets;
|
||||
import openfl.events.Event;
|
||||
|
@ -249,7 +252,7 @@ class DebugBoundingState extends FlxState
|
|||
swagChar.offset.x = (FlxG.mouse.x - mouseOffset.x) * -1;
|
||||
swagChar.offset.y = (FlxG.mouse.y - mouseOffset.y) * -1;
|
||||
|
||||
swagChar.animOffsets.set(animDropDownMenu.selectedLabel, [swagChar.offset.x, swagChar.offset.y]);
|
||||
swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, [Std.int(swagChar.offset.x), Std.int(swagChar.offset.y)]);
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + swagChar.offset;
|
||||
}
|
||||
|
@ -391,9 +394,9 @@ class DebugBoundingState extends FlxState
|
|||
if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.UP || FlxG.keys.justPressed.DOWN)
|
||||
{
|
||||
var animName = animDropDownMenu.selectedLabel;
|
||||
var coolValues:Array<Dynamic> = swagChar.animOffsets.get(animName);
|
||||
var coolValues:Array<Float> = swagChar.animationOffsets.get(animName);
|
||||
|
||||
var multiplier:Float = 5;
|
||||
var multiplier:Int = 5;
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
multiplier = 1;
|
||||
|
@ -410,8 +413,8 @@ class DebugBoundingState extends FlxState
|
|||
else if (FlxG.keys.justPressed.DOWN)
|
||||
coolValues[1] -= 1 * multiplier;
|
||||
|
||||
swagChar.animOffsets.set(animDropDownMenu.selectedLabel, coolValues);
|
||||
swagChar.playAnim(animName);
|
||||
swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, coolValues);
|
||||
swagChar.playAnimation(animName);
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + coolValues;
|
||||
|
||||
|
@ -422,9 +425,9 @@ class DebugBoundingState extends FlxState
|
|||
{
|
||||
var outputString:String = "";
|
||||
|
||||
for (i in swagChar.animOffsets.keys())
|
||||
for (i in swagChar.animationOffsets.keys())
|
||||
{
|
||||
outputString += i + " " + swagChar.animOffsets.get(i)[0] + " " + swagChar.animOffsets.get(i)[1] + "\n";
|
||||
outputString += i + " " + swagChar.animationOffsets.get(i)[0] + " " + swagChar.animationOffsets.get(i)[1] + "\n";
|
||||
}
|
||||
|
||||
outputString.trim();
|
||||
|
@ -432,7 +435,7 @@ class DebugBoundingState extends FlxState
|
|||
}
|
||||
}
|
||||
|
||||
var swagChar:Character;
|
||||
var swagChar:BaseCharacter;
|
||||
|
||||
function loadAnimShit(char:String)
|
||||
{
|
||||
|
@ -442,8 +445,10 @@ class DebugBoundingState extends FlxState
|
|||
swagChar.destroy();
|
||||
}
|
||||
|
||||
swagChar = new Character(100, 100, char);
|
||||
swagChar.debugMode = true;
|
||||
swagChar = CharacterDataParser.fetchCharacter(char);
|
||||
swagChar.x = 100;
|
||||
swagChar.y = 100;
|
||||
// swagChar.debugMode = true;
|
||||
offsetView.add(swagChar);
|
||||
|
||||
generateOutlines(swagChar.frames.frames);
|
||||
|
@ -451,11 +456,11 @@ class DebugBoundingState extends FlxState
|
|||
|
||||
var animThing:Array<String> = [];
|
||||
|
||||
for (i in swagChar.animOffsets.keys())
|
||||
for (i in swagChar.animationOffsets.keys())
|
||||
{
|
||||
animThing.push(i);
|
||||
trace(i);
|
||||
trace(swagChar.animOffsets[i]);
|
||||
trace(swagChar.animationOffsets[i]);
|
||||
}
|
||||
|
||||
animDropDownMenu.setData(FlxUIDropDownMenu.makeStrIdLabelArray(animThing, true));
|
||||
|
@ -468,8 +473,8 @@ class DebugBoundingState extends FlxState
|
|||
onionSkinChar.alpha = 0.6;
|
||||
|
||||
var animName = animThing[Std.parseInt(str)];
|
||||
swagChar.playAnim(animName, true); // trace();
|
||||
trace(swagChar.animOffsets.get(animName));
|
||||
swagChar.playAnimation(animName, true); // trace();
|
||||
trace(swagChar.animationOffsets.get(animName));
|
||||
|
||||
txtOffsetShit.text = 'Offset: ' + swagChar.offset;
|
||||
};
|
||||
|
@ -486,7 +491,7 @@ class DebugBoundingState extends FlxState
|
|||
_file.addEventListener(Event.COMPLETE, onSaveComplete);
|
||||
_file.addEventListener(Event.CANCEL, onSaveCancel);
|
||||
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
||||
_file.save(saveString, swagChar.curCharacter + "Offsets.txt");
|
||||
_file.save(saveString, swagChar.characterId + "Offsets.txt");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
103
source/funkin/ui/stageBuildShit/StageOffsetSubstate.hx
Normal file
103
source/funkin/ui/stageBuildShit/StageOffsetSubstate.hx
Normal file
|
@ -0,0 +1,103 @@
|
|||
package funkin.ui.stageBuildShit;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.ui.FlxButton;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.stage.StageData;
|
||||
import haxe.Json;
|
||||
import openfl.Assets;
|
||||
|
||||
class StageOffsetSubstate extends MusicBeatSubstate
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
FlxG.mouse.visible = true;
|
||||
PlayState.instance.pauseMusic();
|
||||
FlxG.camera.target = null;
|
||||
|
||||
var btn:FlxButton = new FlxButton(200, 10, "SAVE COMPILE", function()
|
||||
{
|
||||
var stageLol:StageData = StageDataParser.parseStageData(PlayState.instance.currentStageId);
|
||||
|
||||
var bfPos = PlayState.instance.currentStage.getBoyfriend().feetPosition;
|
||||
stageLol.characters.bf.position[0] = Std.int(bfPos.x);
|
||||
stageLol.characters.bf.position[1] = Std.int(bfPos.y);
|
||||
|
||||
var dadPos = PlayState.instance.currentStage.getDad().feetPosition;
|
||||
|
||||
stageLol.characters.dad.position[0] = Std.int(dadPos.x);
|
||||
stageLol.characters.dad.position[1] = Std.int(dadPos.y);
|
||||
|
||||
var GF_FEET_SNIIIIIIIIIIIIIFFFF = PlayState.instance.currentStage.getGirlfriend().feetPosition;
|
||||
stageLol.characters.gf.position[0] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.x);
|
||||
stageLol.characters.gf.position[1] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.y);
|
||||
|
||||
var outputJson = CoolUtil.jsonStringify(stageLol);
|
||||
|
||||
#if sys
|
||||
// save "local" to the current export.
|
||||
sys.io.File.saveContent('./assets/data/stages/' + PlayState.instance.currentStageId + '.json', outputJson);
|
||||
|
||||
// save to the dev version
|
||||
sys.io.File.saveContent('../../../../assets/preload/data/stages/' + PlayState.instance.currentStageId + '.json', outputJson);
|
||||
#end
|
||||
// trace(dipshitJson);
|
||||
|
||||
// put character position data to a file of some sort
|
||||
});
|
||||
btn.scrollFactor.set();
|
||||
add(btn);
|
||||
btn.cameras = [PlayState.instance.camHUD];
|
||||
}
|
||||
|
||||
var mosPosOld:FlxPoint = new FlxPoint();
|
||||
var sprOld:FlxPoint = new FlxPoint();
|
||||
|
||||
var char:BaseCharacter = null;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
CoolUtil.mouseCamDrag();
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
CoolUtil.mouseWheelZoom();
|
||||
|
||||
if (FlxG.mouse.pressed)
|
||||
{
|
||||
if (FlxG.mouse.justPressed)
|
||||
{
|
||||
for (thing in PlayState.instance.currentStage)
|
||||
{
|
||||
if (FlxG.mouse.overlaps(thing) && Std.isOfType(thing, BaseCharacter))
|
||||
char = cast thing;
|
||||
}
|
||||
|
||||
if (char != null)
|
||||
{
|
||||
sprOld.x = char.x;
|
||||
sprOld.y = char.y;
|
||||
}
|
||||
|
||||
mosPosOld.x = FlxG.mouse.x;
|
||||
mosPosOld.y = FlxG.mouse.y;
|
||||
}
|
||||
|
||||
if (char != null)
|
||||
{
|
||||
char.x = sprOld.x - (mosPosOld.x - FlxG.mouse.x);
|
||||
char.y = sprOld.y - (mosPosOld.y - FlxG.mouse.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.Y)
|
||||
{
|
||||
PlayState.instance.resetCamera();
|
||||
FlxG.mouse.visible = false;
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,8 @@ class SortUtil
|
|||
*/
|
||||
public static inline function byZIndex(Order:Int, Obj1:FlxBasic, Obj2:FlxBasic):Int
|
||||
{
|
||||
if (Obj1 == null || Obj2 == null)
|
||||
return 0;
|
||||
return FlxSort.byValues(Order, Obj1.zIndex, Obj2.zIndex);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,10 @@ class DataAssets
|
|||
{
|
||||
var pathNoSuffix = textPath.substring(0, textPath.length - ext.length);
|
||||
var pathNoPrefix = pathNoSuffix.substring(queryPath.length);
|
||||
results.push(pathNoPrefix);
|
||||
|
||||
// No duplicates! Why does this happen?
|
||||
if (!results.contains(pathNoPrefix))
|
||||
results.push(pathNoPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue