mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-01-13 15:47:51 +00:00
First WIP of scripted characters (NOT WORKING)
This commit is contained in:
parent
7c0cb9c69c
commit
1a85b4a1ce
|
@ -1,5 +1,6 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
import funkin.charting.ChartingState;
|
import funkin.charting.ChartingState;
|
||||||
|
@ -122,7 +123,7 @@ class InitState extends FlxTransitionableState
|
||||||
FlxTransitionableState.skipNextTransIn = true;
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
|
|
||||||
StageDataParser.loadStageCache();
|
StageDataParser.loadStageCache();
|
||||||
|
CharacterDataParser.loadCharacterCache();
|
||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.loadModuleCache();
|
||||||
|
|
||||||
#if song
|
#if song
|
||||||
|
|
|
@ -4,7 +4,4 @@ import flixel.FlxSprite;
|
||||||
import funkin.modding.IHook;
|
import funkin.modding.IHook;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedFlxSprite extends FlxSprite implements IHook
|
class ScriptedFlxSprite extends FlxSprite implements IHook {}
|
||||||
{
|
|
||||||
// No body needed for this class, it's magic ;)
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,4 @@ import flixel.group.FlxSpriteGroup;
|
||||||
import funkin.modding.IHook;
|
import funkin.modding.IHook;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements IHook
|
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements IHook {}
|
||||||
{
|
|
||||||
// No body needed for this class, it's magic ;)
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,4 @@ package funkin.modding.module;
|
||||||
import funkin.modding.IHook;
|
import funkin.modding.IHook;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedModule extends Module implements IHook
|
class ScriptedModule extends Module implements IHook {}
|
||||||
{
|
|
||||||
// No body needed for this class, it's magic ;)
|
|
||||||
}
|
|
||||||
|
|
56
source/funkin/play/AnimationData.hx
Normal file
56
source/funkin/play/AnimationData.hx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package funkin.play;
|
||||||
|
|
||||||
|
typedef AnimationData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name for the animation.
|
||||||
|
* This should match the animation name queried by the game;
|
||||||
|
* for example, characters need animations with names `idle`, `singDOWN`, `singUPmiss`, etc.
|
||||||
|
*/
|
||||||
|
var name:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix for the frames of the animation as defined by the XML file.
|
||||||
|
* This will may or may not differ from the `name` of the animation,
|
||||||
|
* depending on how your animator organized their FLA or whatever.
|
||||||
|
*/
|
||||||
|
var prefix:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset the character's position by this amount when playing this animation.
|
||||||
|
* @default [0, 0]
|
||||||
|
*/
|
||||||
|
var offsets:Null<Array<Float>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the animation should loop when it finishes.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
var looped:Null<Bool>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the animation's sprites should be flipped horizontally.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
var flipX:Null<Bool>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the animation's sprites should be flipped vertically.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
var flipY:Null<Bool>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frame rate of the animation.
|
||||||
|
* @default 24
|
||||||
|
*/
|
||||||
|
var frameRate:Null<Int>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you want this animation to use only certain frames of an animation with a given prefix,
|
||||||
|
* select them here.
|
||||||
|
* @example [0, 1, 2, 3] (use only the first four frames)
|
||||||
|
* @default [] (all frames)
|
||||||
|
*/
|
||||||
|
var frameIndices:Null<Array<Int>>;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package funkin.play;
|
package funkin.play;
|
||||||
|
|
||||||
import funkin.play.Strumline.StrumlineArrow;
|
import funkin.play.character.CharacterBase;
|
||||||
import flixel.addons.effects.FlxTrail;
|
import flixel.addons.effects.FlxTrail;
|
||||||
import flixel.addons.transition.FlxTransitionableState;
|
import flixel.addons.transition.FlxTransitionableState;
|
||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
|
@ -27,8 +27,10 @@ import funkin.modding.events.ScriptEventDispatcher;
|
||||||
import funkin.modding.IHook;
|
import funkin.modding.IHook;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.Note;
|
import funkin.Note;
|
||||||
|
import funkin.play.character.CharacterData;
|
||||||
import funkin.play.stage.Stage;
|
import funkin.play.stage.Stage;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
|
import funkin.play.Strumline.StrumlineArrow;
|
||||||
import funkin.play.Strumline.StrumlineStyle;
|
import funkin.play.Strumline.StrumlineStyle;
|
||||||
import funkin.Section.SwagSection;
|
import funkin.Section.SwagSection;
|
||||||
import funkin.SongLoad.SwagSong;
|
import funkin.SongLoad.SwagSong;
|
||||||
|
@ -126,8 +128,11 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
/**
|
/**
|
||||||
* An empty FlxObject contained in the scene.
|
* An empty FlxObject contained in the scene.
|
||||||
* The current gameplay camera will be centered on this object. Tween its position to move the camera smoothly.
|
* The current gameplay camera will be centered on this object. Tween its position to move the camera smoothly.
|
||||||
|
*
|
||||||
|
* NOTE: This must be an FlxObject, not an FlxPoint, because it needs to be added to the scene.
|
||||||
|
* Once it's added to the scene, the camera can be configured to follow it.
|
||||||
*/
|
*/
|
||||||
public var cameraFollowPoint:FlxObject;
|
public var cameraFollowPoint:FlxObject = new FlxObject(0, 0, 1, 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PRIVATE INSTANCE VARIABLES
|
* PRIVATE INSTANCE VARIABLES
|
||||||
|
@ -240,7 +245,6 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
var songScore:Int = 0;
|
var songScore:Int = 0;
|
||||||
var doof:DialogueBox;
|
var doof:DialogueBox;
|
||||||
var grpNoteSplashes:FlxTypedGroup<NoteSplash>;
|
var grpNoteSplashes:FlxTypedGroup<NoteSplash>;
|
||||||
var camPos:FlxPoint;
|
|
||||||
var comboPopUps:PopUpStuff;
|
var comboPopUps:PopUpStuff;
|
||||||
var perfectMode:Bool = false;
|
var perfectMode:Bool = false;
|
||||||
var previousFrameTime:Int = 0;
|
var previousFrameTime:Int = 0;
|
||||||
|
@ -315,6 +319,14 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
initDiscord();
|
initDiscord();
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
// Configure camera follow point.
|
||||||
|
if (previousCameraFollowPoint != null)
|
||||||
|
{
|
||||||
|
cameraFollowPoint.setPosition(previousCameraFollowPoint.x, previousCameraFollowPoint.y);
|
||||||
|
previousCameraFollowPoint = null;
|
||||||
|
}
|
||||||
|
add(cameraFollowPoint);
|
||||||
|
|
||||||
comboPopUps = new PopUpStuff();
|
comboPopUps = new PopUpStuff();
|
||||||
add(comboPopUps);
|
add(comboPopUps);
|
||||||
|
|
||||||
|
@ -328,16 +340,6 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
|
|
||||||
generateSong();
|
generateSong();
|
||||||
|
|
||||||
cameraFollowPoint = new FlxObject(0, 0, 1, 1);
|
|
||||||
cameraFollowPoint.setPosition(camPos.x, camPos.y);
|
|
||||||
|
|
||||||
if (previousCameraFollowPoint != null)
|
|
||||||
{
|
|
||||||
cameraFollowPoint = previousCameraFollowPoint;
|
|
||||||
previousCameraFollowPoint = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(cameraFollowPoint);
|
|
||||||
resetCamera();
|
resetCamera();
|
||||||
|
|
||||||
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
|
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
|
||||||
|
@ -467,7 +469,11 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
|
|
||||||
function initCharacters()
|
function initCharacters()
|
||||||
{
|
{
|
||||||
// all dis is shitty, redo later for stage shit
|
//
|
||||||
|
// GIRLFRIEND
|
||||||
|
//
|
||||||
|
|
||||||
|
// TODO: Tie the GF version to the song data, not the stage ID or the current player.
|
||||||
var gfVersion:String = 'gf';
|
var gfVersion:String = 'gf';
|
||||||
|
|
||||||
switch (currentStageId)
|
switch (currentStageId)
|
||||||
|
@ -483,35 +489,34 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSong.player1 == "pico")
|
if (currentSong.player1 == "pico")
|
||||||
{
|
|
||||||
gfVersion = "nene";
|
gfVersion = "nene";
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSong.song.toLowerCase() == 'stress')
|
if (currentSong.song.toLowerCase() == 'stress')
|
||||||
gfVersion = 'pico-speaker';
|
gfVersion = 'pico-speaker';
|
||||||
|
|
||||||
var gf = new Character(400, 130, gfVersion);
|
var girlfriend:Character = new Character(350, -70, gfVersion);
|
||||||
gf.scrollFactor.set(0.95, 0.95);
|
girlfriend.scrollFactor.set(0.95, 0.95);
|
||||||
|
if (gfVersion == 'pico-speaker')
|
||||||
switch (gfVersion)
|
|
||||||
{
|
{
|
||||||
case 'pico-speaker':
|
girlfriend.x -= 50;
|
||||||
gf.x -= 50;
|
girlfriend.y -= 200;
|
||||||
gf.y -= 200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// DAD
|
||||||
|
//
|
||||||
var dad = new Character(100, 100, currentSong.player2);
|
var dad = new Character(100, 100, currentSong.player2);
|
||||||
|
|
||||||
camPos = new FlxPoint(dad.getGraphicMidpoint().x, dad.getGraphicMidpoint().y);
|
cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x, dad.getGraphicMidpoint().y);
|
||||||
|
|
||||||
switch (currentSong.player2)
|
switch (currentSong.player2)
|
||||||
{
|
{
|
||||||
case 'gf':
|
case 'gf':
|
||||||
dad.setPosition(gf.x, gf.y);
|
dad.setPosition(girlfriend.x, girlfriend.y);
|
||||||
gf.visible = false;
|
girlfriend.visible = false;
|
||||||
if (isStoryMode)
|
if (isStoryMode)
|
||||||
{
|
{
|
||||||
camPos.x += 600;
|
cameraFollowPoint.x += 600;
|
||||||
tweenCamIn();
|
tweenCamIn();
|
||||||
}
|
}
|
||||||
case "spooky":
|
case "spooky":
|
||||||
|
@ -521,25 +526,40 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
case 'monster-christmas':
|
case 'monster-christmas':
|
||||||
dad.y += 130;
|
dad.y += 130;
|
||||||
case 'dad':
|
case 'dad':
|
||||||
camPos.x += 400;
|
cameraFollowPoint.x += 400;
|
||||||
case 'pico':
|
case 'pico':
|
||||||
camPos.x += 600;
|
cameraFollowPoint.x += 600;
|
||||||
dad.y += 300;
|
dad.y += 300;
|
||||||
case 'parents-christmas':
|
case 'parents-christmas':
|
||||||
dad.x -= 500;
|
dad.x -= 500;
|
||||||
case 'senpai' | 'senpai-angry':
|
case 'senpai' | 'senpai-angry':
|
||||||
dad.x += 150;
|
dad.x += 150;
|
||||||
dad.y += 360;
|
dad.y += 360;
|
||||||
camPos.set(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
||||||
case 'spirit':
|
case 'spirit':
|
||||||
dad.x -= 150;
|
dad.x -= 150;
|
||||||
dad.y += 100;
|
dad.y += 100;
|
||||||
camPos.set(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
||||||
case 'tankman':
|
case 'tankman':
|
||||||
dad.y += 180;
|
dad.y += 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
var boyfriend = new Boyfriend(770, 450, currentSong.player1);
|
if (currentSong.player1 == "pico")
|
||||||
|
{
|
||||||
|
dad.x -= 100;
|
||||||
|
dad.y -= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// BOYFRIEND
|
||||||
|
//
|
||||||
|
var boyfriend:CharacterBase;
|
||||||
|
switch (currentSong.player1)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
boyfriend = CharacterDataParser.fetchCharacter(currentSong.player1);
|
||||||
|
boyfriend.characterType = CharacterType.BF;
|
||||||
|
}
|
||||||
|
|
||||||
// REPOSITIONING PER STAGE
|
// REPOSITIONING PER STAGE
|
||||||
switch (currentStageId)
|
switch (currentStageId)
|
||||||
|
@ -550,8 +570,8 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
evilTrail.zIndex = 190;
|
evilTrail.zIndex = 190;
|
||||||
add(evilTrail);
|
add(evilTrail);
|
||||||
case "tank":
|
case "tank":
|
||||||
gf.y += 10;
|
girlfriend.y += 10;
|
||||||
gf.x -= 30;
|
girlfriend.x -= 30;
|
||||||
boyfriend.x += 40;
|
boyfriend.x += 40;
|
||||||
boyfriend.y += 0;
|
boyfriend.y += 0;
|
||||||
dad.y += 60;
|
dad.y += 60;
|
||||||
|
@ -559,8 +579,8 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
|
|
||||||
if (gfVersion != 'pico-speaker')
|
if (gfVersion != 'pico-speaker')
|
||||||
{
|
{
|
||||||
gf.x -= 170;
|
girlfriend.x -= 170;
|
||||||
gf.y -= 75;
|
girlfriend.y -= 75;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,16 +588,16 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
{
|
{
|
||||||
// We're using Eric's stage handler.
|
// We're using Eric's stage handler.
|
||||||
// Characters get added to the stage, not the main scene.
|
// Characters get added to the stage, not the main scene.
|
||||||
currentStage.addCharacter(gf, GF);
|
|
||||||
currentStage.addCharacter(boyfriend, BF);
|
currentStage.addCharacter(boyfriend, BF);
|
||||||
currentStage.addCharacter(dad, DAD);
|
currentStage.addCharacterOld(girlfriend, GF);
|
||||||
|
currentStage.addCharacterOld(dad, DAD);
|
||||||
|
|
||||||
// Redo z-indexes.
|
// Redo z-indexes.
|
||||||
currentStage.refresh();
|
currentStage.refresh();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
add(gf);
|
add(girlfriend);
|
||||||
add(dad);
|
add(dad);
|
||||||
add(boyfriend);
|
add(boyfriend);
|
||||||
}
|
}
|
||||||
|
@ -612,6 +632,7 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
|
|
||||||
// Reload the stages in cache. This might cause a lag spike but who cares this is a debug utility.
|
// Reload the stages in cache. This might cause a lag spike but who cares this is a debug utility.
|
||||||
StageDataParser.loadStageCache();
|
StageDataParser.loadStageCache();
|
||||||
|
CharacterDataParser.loadCharacterCache();
|
||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.loadModuleCache();
|
||||||
|
|
||||||
// Reload the level. This should use new data from the assets folder.
|
// Reload the level. This should use new data from the assets folder.
|
||||||
|
@ -685,8 +706,6 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
senpaiEvil.screenCenter();
|
senpaiEvil.screenCenter();
|
||||||
senpaiEvil.x += senpaiEvil.width / 5;
|
senpaiEvil.x += senpaiEvil.width / 5;
|
||||||
|
|
||||||
cameraFollowPoint.setPosition(camPos.x, camPos.y);
|
|
||||||
|
|
||||||
if (currentSong.song.toLowerCase() == 'roses' || currentSong.song.toLowerCase() == 'thorns')
|
if (currentSong.song.toLowerCase() == 'roses' || currentSong.song.toLowerCase() == 'thorns')
|
||||||
{
|
{
|
||||||
remove(black);
|
remove(black);
|
||||||
|
@ -1664,7 +1683,7 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
&& PlayState.instance.currentStage.getBoyfriend().animation.curAnim.name.startsWith('sing')
|
&& PlayState.instance.currentStage.getBoyfriend().animation.curAnim.name.startsWith('sing')
|
||||||
&& !PlayState.instance.currentStage.getBoyfriend().animation.curAnim.name.endsWith('miss'))
|
&& !PlayState.instance.currentStage.getBoyfriend().animation.curAnim.name.endsWith('miss'))
|
||||||
{
|
{
|
||||||
PlayState.instance.currentStage.getBoyfriend().playAnim('idle');
|
PlayState.instance.currentStage.getBoyfriend().playAnimation('idle');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1695,7 +1714,7 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
vocals.volume = 0;
|
vocals.volume = 0;
|
||||||
FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2));
|
FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2));
|
||||||
|
|
||||||
currentStage.getBoyfriend().playAnim('sing' + direction.nameUpper + 'miss', true);
|
currentStage.getBoyfriend().playAnimation('sing' + direction.nameUpper + 'miss', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function goodNoteHit(note:Note):Void
|
function goodNoteHit(note:Note):Void
|
||||||
|
@ -1708,7 +1727,7 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
popUpScore(note.data.strumTime, note);
|
popUpScore(note.data.strumTime, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentStage.getBoyfriend().playAnim('sing' + note.dirNameUpper, true);
|
currentStage.getBoyfriend().playAnimation('sing' + note.dirNameUpper, true);
|
||||||
|
|
||||||
playerStrumline.getArrow(note.data.noteData).playAnimation('confirm', true);
|
playerStrumline.getArrow(note.data.noteData).playAnimation('confirm', true);
|
||||||
|
|
||||||
|
@ -1800,7 +1819,7 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
if (curBeat % 2 == 0)
|
if (curBeat % 2 == 0)
|
||||||
{
|
{
|
||||||
if (currentStage.getBoyfriend().animation != null && !currentStage.getBoyfriend().animation.curAnim.name.startsWith("sing"))
|
if (currentStage.getBoyfriend().animation != null && !currentStage.getBoyfriend().animation.curAnim.name.startsWith("sing"))
|
||||||
currentStage.getBoyfriend().playAnim('idle');
|
currentStage.getBoyfriend().playAnimation('idle');
|
||||||
if (currentStage.getDad().animation != null && !currentStage.getDad().animation.curAnim.name.startsWith("sing"))
|
if (currentStage.getDad().animation != null && !currentStage.getDad().animation.curAnim.name.startsWith("sing"))
|
||||||
currentStage.getDad().dance();
|
currentStage.getDad().dance();
|
||||||
}
|
}
|
||||||
|
@ -1812,7 +1831,7 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
|
|
||||||
if (curBeat % 8 == 7 && currentSong.song == 'Bopeebo')
|
if (curBeat % 8 == 7 && currentSong.song == 'Bopeebo')
|
||||||
{
|
{
|
||||||
currentStage.getBoyfriend().playAnim('hey', true);
|
currentStage.getBoyfriend().playAnimation('hey', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curBeat % 16 == 15
|
if (curBeat % 16 == 15
|
||||||
|
@ -1821,7 +1840,7 @@ class PlayState extends MusicBeatState implements IHook
|
||||||
&& curBeat > 16
|
&& curBeat > 16
|
||||||
&& curBeat < 48)
|
&& curBeat < 48)
|
||||||
{
|
{
|
||||||
currentStage.getBoyfriend().playAnim('hey', true);
|
currentStage.getBoyfriend().playAnimation('hey', true);
|
||||||
currentStage.getDad().playAnim('cheer', true);
|
currentStage.getDad().playAnim('cheer', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package funkin.play.character;
|
|
||||||
|
|
||||||
enum CharacterType
|
|
||||||
{
|
|
||||||
BF;
|
|
||||||
GF;
|
|
||||||
DAD;
|
|
||||||
OTHER;
|
|
||||||
}
|
|
140
source/funkin/play/character/CharacterBase.hx
Normal file
140
source/funkin/play/character/CharacterBase.hx
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
package funkin.play.character;
|
||||||
|
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||||
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
|
import funkin.Note.NoteDir;
|
||||||
|
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
||||||
|
import funkin.play.stage.Bopper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 CharacterBase extends Bopper
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
public var attachedStrumlines(default, null):Array<Int>;
|
||||||
|
|
||||||
|
final _data:CharacterData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
final singTimeCrochet:Float;
|
||||||
|
|
||||||
|
public function new(id:String)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
this.characterId = id;
|
||||||
|
this.attachedStrumlines = [];
|
||||||
|
|
||||||
|
_data = CharacterDataParser.parseCharacterData(this.characterId);
|
||||||
|
if (_data == null)
|
||||||
|
{
|
||||||
|
throw 'Could not find character data for characterId: $characterId';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.characterName = _data.name;
|
||||||
|
this.singTimeCrochet = _data.singTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function onUpdate(event:UpdateScriptEvent):Void
|
||||||
|
{
|
||||||
|
super.onUpdate(event);
|
||||||
|
|
||||||
|
// Handle character note hold time.
|
||||||
|
holdTimer += event.elapsed;
|
||||||
|
var singTimeMs:Float = singTimeCrochet * Conductor.crochet;
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
if (holdTimer > singTimeMs && shouldStopSinging)
|
||||||
|
{
|
||||||
|
holdTimer = 0;
|
||||||
|
dance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 is from the same strumline as this character, then sing.
|
||||||
|
// if (this.attachedStrumlines.indexOf(event.note.strumline) != -1)
|
||||||
|
// {
|
||||||
|
// this.playSingAnimation(event.note.dir, false, note.alt);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
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`.
|
||||||
|
*/
|
||||||
|
function playSingAnimation(dir:NoteDir, miss:Bool = false, suffix:String = ""):Void
|
||||||
|
{
|
||||||
|
var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != "" ? '-${suffix}' : ''}';
|
||||||
|
playAnimation(anim, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CharacterType
|
||||||
|
{
|
||||||
|
BF;
|
||||||
|
DAD;
|
||||||
|
GF;
|
||||||
|
OTHER;
|
||||||
|
}
|
414
source/funkin/play/character/CharacterData.hx
Normal file
414
source/funkin/play/character/CharacterData.hx
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
package funkin.play.character;
|
||||||
|
|
||||||
|
import openfl.Assets;
|
||||||
|
import haxe.Json;
|
||||||
|
import funkin.play.character.render.PackerCharacter;
|
||||||
|
import funkin.play.character.render.SparrowCharacter;
|
||||||
|
import funkin.util.assets.DataAssets;
|
||||||
|
import funkin.play.character.CharacterBase;
|
||||||
|
import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
|
||||||
|
import funkin.play.character.ScriptedCharacter.ScriptedPackerCharacter;
|
||||||
|
import flixel.util.typeLimit.OneOfTwo;
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
static final characterCache:Map<String, CharacterBase> = new Map<String, CharacterBase>();
|
||||||
|
|
||||||
|
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...");
|
||||||
|
|
||||||
|
//
|
||||||
|
// SCRIPTED CHARACTERS
|
||||||
|
//
|
||||||
|
|
||||||
|
// Generic (Sparrow) characters
|
||||||
|
var scriptedCharClassNames:Array<String> = ScriptedCharacter.listScriptClasses();
|
||||||
|
trace(' Instantiating ${scriptedCharClassNames.length} scripted characters...');
|
||||||
|
for (charCls in scriptedCharClassNames)
|
||||||
|
{
|
||||||
|
_storeChar(ScriptedCharacter.init(charCls, DEFAULT_CHAR_ID), charCls);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sparrow characters
|
||||||
|
scriptedCharClassNames = ScriptedSparrowCharacter.listScriptClasses();
|
||||||
|
if (scriptedCharClassNames.length > 0)
|
||||||
|
{
|
||||||
|
trace(' Instantiating ${scriptedCharClassNames.length} scripted characters (SPARROW)...');
|
||||||
|
for (charCls in scriptedCharClassNames)
|
||||||
|
{
|
||||||
|
_storeChar(ScriptedSparrowCharacter.init(charCls, DEFAULT_CHAR_ID), charCls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Packer characters
|
||||||
|
// scriptedCharClassNames = ScriptedPackerCharacter.listScriptClasses();
|
||||||
|
// if (scriptedCharClassNames.length > 0)
|
||||||
|
// {
|
||||||
|
// trace(' Instantiating ${scriptedCharClassNames.length} scripted characters (PACKER)...');
|
||||||
|
// for (charCls in scriptedCharClassNames)
|
||||||
|
// {
|
||||||
|
// _storeChar(ScriptedPackerCharacter.init(charCls, DEFAULT_CHAR_ID), charCls);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Add more character types.
|
||||||
|
|
||||||
|
//
|
||||||
|
// UNSCRIPTED STAGES
|
||||||
|
//
|
||||||
|
var charIdList:Array<String> = DataAssets.listDataFilesInPath('characters/');
|
||||||
|
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool
|
||||||
|
{
|
||||||
|
return !characterCache.exists(charId);
|
||||||
|
});
|
||||||
|
trace(' Instantiating ${unscriptedCharIds.length} non-scripted characters...');
|
||||||
|
for (charId in unscriptedCharIds)
|
||||||
|
{
|
||||||
|
var char:CharacterBase = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var charData:CharacterData = parseCharacterData(charId);
|
||||||
|
if (charData != null)
|
||||||
|
{
|
||||||
|
switch (charData.renderType)
|
||||||
|
{
|
||||||
|
case CharacterRenderType.PACKER:
|
||||||
|
char = new PackerCharacter(charId);
|
||||||
|
case CharacterRenderType.SPARROW:
|
||||||
|
// default
|
||||||
|
char = new SparrowCharacter(charId);
|
||||||
|
default:
|
||||||
|
trace(' Failed to instantiate character: ${charId} (Bad render type ${charData.renderType})');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (char != null)
|
||||||
|
{
|
||||||
|
trace(' Loaded character data: ${char.characterName}');
|
||||||
|
characterCache.set(charId, char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
// Assume error was already logged.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace(' Successfully loaded ${Lambda.count(characterCache)} stages.');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function _storeChar(char:CharacterBase, charCls:String):Void
|
||||||
|
{
|
||||||
|
if (char != null)
|
||||||
|
{
|
||||||
|
trace(' Loaded scripted character: ${char.characterName}');
|
||||||
|
// Disable the rendering logic for stage until it's loaded.
|
||||||
|
// Note that kill() =/= destroy()
|
||||||
|
char.kill();
|
||||||
|
|
||||||
|
// Then store it.
|
||||||
|
characterCache.set(char.characterId, char);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace(' Failed to instantiate scripted character class: ${charCls}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fetchCharacter(charId:String):Null<CharacterBase>
|
||||||
|
{
|
||||||
|
if (characterCache.exists(charId))
|
||||||
|
{
|
||||||
|
trace('[CHARDATA] Successfully fetch stage: ${charId}');
|
||||||
|
var character:CharacterBase = characterCache.get(charId);
|
||||||
|
character.revive();
|
||||||
|
return character;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[CHARDATA] Failed to fetch character, not found in cache: ${charId}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function clearCharacterCache():Void
|
||||||
|
{
|
||||||
|
if (characterCache != null)
|
||||||
|
{
|
||||||
|
for (char in characterCache)
|
||||||
|
{
|
||||||
|
char.destroy();
|
||||||
|
}
|
||||||
|
characterCache.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 = Assets.getText(charFilePath).trim();
|
||||||
|
|
||||||
|
while (!rawJson.endsWith("}"))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final DEFAULT_NAME:String = "Untitled Character";
|
||||||
|
static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.SPARROW;
|
||||||
|
static final DEFAULT_STARTINGANIM:String = "idle";
|
||||||
|
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||||
|
static final DEFAULT_ISPIXEL:Bool = false;
|
||||||
|
static final DEFAULT_DANCEEVERY:Int = 1;
|
||||||
|
static final DEFAULT_FRAMERATE:Int = 24;
|
||||||
|
static final DEFAULT_FLIPX:Bool = false;
|
||||||
|
static final DEFAULT_SCALE:Float = 1;
|
||||||
|
static final DEFAULT_FLIPY:Bool = false;
|
||||||
|
static final DEFAULT_LOOP:Bool = false;
|
||||||
|
static final DEFAULT_FRAMEINDICES:Array<Int> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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] ERROR: Could not load character data for "$id": missing version');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.version == CHARACTER_DATA_VERSION)
|
||||||
|
{
|
||||||
|
trace('[CHARDATA] ERROR: Could not load character data for "$id": bad/outdated version (got ${input.version}, expected ${CHARACTER_DATA_VERSION})');
|
||||||
|
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.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.animations == null || input.animations.length == 0)
|
||||||
|
{
|
||||||
|
trace('[CHARDATA] ERROR: Could not load character data for "$id": missing animations');
|
||||||
|
input.animations = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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.frameIndices == null)
|
||||||
|
{
|
||||||
|
inputAnimation.frameIndices = DEFAULT_FRAMEINDICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
// TODO: Aesprite?
|
||||||
|
// TODO: Animate?
|
||||||
|
// TODO: Experimental...
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef CharacterData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The sematic version of the chart data 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either the scale of the graphic as a float, or the [w, h] scale as an array of two floats.
|
||||||
|
* 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:OneOfTwo<Float, 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>;
|
||||||
|
};
|
14
source/funkin/play/character/ScriptedCharacter.hx
Normal file
14
source/funkin/play/character/ScriptedCharacter.hx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package funkin.play.character;
|
||||||
|
|
||||||
|
import funkin.play.character.render.PackerCharacter;
|
||||||
|
import funkin.play.character.render.SparrowCharacter;
|
||||||
|
import funkin.modding.IHook;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedCharacter extends SparrowCharacter implements IHook {}
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedSparrowCharacter extends SparrowCharacter implements IHook {}
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedPackerCharacter extends PackerCharacter implements IHook {}
|
15
source/funkin/play/character/render/PackerCharacter.hx
Normal file
15
source/funkin/play/character/render/PackerCharacter.hx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package funkin.play.character.render;
|
||||||
|
|
||||||
|
import funkin.play.character.CharacterBase.CharacterType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A PackerCharacter is a Character which is rendered by
|
||||||
|
* displaying an animation derived from a Packer spritesheet file.
|
||||||
|
*/
|
||||||
|
class PackerCharacter extends CharacterBase
|
||||||
|
{
|
||||||
|
public function new(id:String)
|
||||||
|
{
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
}
|
15
source/funkin/play/character/render/SparrowCharacter.hx
Normal file
15
source/funkin/play/character/render/SparrowCharacter.hx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package funkin.play.character.render;
|
||||||
|
|
||||||
|
import funkin.play.character.CharacterBase.CharacterType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SparrowCharacter is a Character which is rendered by
|
||||||
|
* displaying an animation derived from a SparrowV2 atlas spritesheet file.
|
||||||
|
*/
|
||||||
|
class SparrowCharacter extends CharacterBase
|
||||||
|
{
|
||||||
|
public function new(id:String)
|
||||||
|
{
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The bopper plays the dance animation once every `danceEvery` beats.
|
* The bopper plays the dance animation once every `danceEvery` beats.
|
||||||
|
* Set to 0 to disable idle animation.
|
||||||
*/
|
*/
|
||||||
public var danceEvery:Int = 1;
|
public var danceEvery:Int = 1;
|
||||||
|
|
||||||
|
@ -31,11 +32,19 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
/**
|
/**
|
||||||
* Set this value to define an additional horizontal offset to this sprite's position.
|
* Set this value to define an additional horizontal offset to this sprite's position.
|
||||||
*/
|
*/
|
||||||
public var xOffset:Float = 0;
|
public var xOffset(default, set):Float = 0;
|
||||||
|
|
||||||
override function set_x(value:Float):Float
|
override function set_x(value:Float):Float
|
||||||
{
|
{
|
||||||
this.x = value + this.xOffset;
|
this.x = this.xOffset + value;
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_xOffset(value:Float):Float
|
||||||
|
{
|
||||||
|
var diff = value - this.xOffset;
|
||||||
|
this.xOffset = value;
|
||||||
|
this.x += diff;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +64,15 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
|
|
||||||
override function set_y(value:Float):Float
|
override function set_y(value:Float):Float
|
||||||
{
|
{
|
||||||
this.y = value + this.yOffset;
|
this.y = this.yOffset + value;
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_yOffset(value:Float):Float
|
||||||
|
{
|
||||||
|
var diff = value - this.yOffset;
|
||||||
|
this.yOffset = value;
|
||||||
|
this.y += diff;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +90,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
|
|
||||||
function update_shouldAlternate():Void
|
function update_shouldAlternate():Void
|
||||||
{
|
{
|
||||||
if (this.animation.getByName('danceLeft') != null)
|
if (hasAnimation('danceLeft'))
|
||||||
{
|
{
|
||||||
this.shouldAlternate = true;
|
this.shouldAlternate = true;
|
||||||
}
|
}
|
||||||
|
@ -84,7 +101,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
*/
|
*/
|
||||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (event.beat % danceEvery == 0)
|
if (danceEvery > 0 && event.beat % danceEvery == 0)
|
||||||
{
|
{
|
||||||
dance();
|
dance();
|
||||||
}
|
}
|
||||||
|
@ -109,20 +126,42 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
{
|
{
|
||||||
if (hasDanced)
|
if (hasDanced)
|
||||||
{
|
{
|
||||||
this.animation.play('danceRight$idleSuffix');
|
playAnimation('danceRight$idleSuffix');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.animation.play('danceLeft$idleSuffix');
|
playAnimation('danceLeft$idleSuffix');
|
||||||
}
|
}
|
||||||
hasDanced = !hasDanced;
|
hasDanced = !hasDanced;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.animation.play('idle$idleSuffix');
|
playAnimation('idle$idleSuffix');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasAnimation(id:String):Bool
|
||||||
|
{
|
||||||
|
return this.animation.getByName(id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param AnimName The string name of the animation you want to play.
|
||||||
|
* @param Force Whether to force the animation to restart.
|
||||||
|
*/
|
||||||
|
public function playAnimation(name:String, force:Bool = false):Void
|
||||||
|
{
|
||||||
|
this.animation.play(name, force, false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the animation that is currently playing.
|
||||||
|
*/
|
||||||
|
public function getCurrentAnimation():String
|
||||||
|
{
|
||||||
|
return this.animation.curAnim.name;
|
||||||
|
}
|
||||||
|
|
||||||
public function onScriptEvent(event:ScriptEvent) {}
|
public function onScriptEvent(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onCreate(event:ScriptEvent) {}
|
public function onCreate(event:ScriptEvent) {}
|
||||||
|
|
|
@ -4,7 +4,4 @@ import funkin.modding.IHook;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
@:keep
|
@:keep
|
||||||
class ScriptedBopper extends Bopper implements IHook
|
class ScriptedBopper extends Bopper implements IHook {}
|
||||||
{
|
|
||||||
// No body needed for this class, it's magic ;)
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,4 @@ package funkin.play.stage;
|
||||||
import funkin.modding.IHook;
|
import funkin.modding.IHook;
|
||||||
|
|
||||||
@:hscriptClass
|
@:hscriptClass
|
||||||
class ScriptedStage extends Stage implements IHook
|
class ScriptedStage extends Stage implements IHook {}
|
||||||
{
|
|
||||||
// No body needed for this class, it's magic ;)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.play.stage;
|
package funkin.play.stage;
|
||||||
|
|
||||||
|
import funkin.play.character.CharacterBase;
|
||||||
import funkin.modding.events.ScriptEventDispatcher;
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||||
|
@ -10,7 +11,7 @@ import flixel.group.FlxSpriteGroup;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.util.FlxSort;
|
import flixel.util.FlxSort;
|
||||||
import funkin.modding.IHook;
|
import funkin.modding.IHook;
|
||||||
import funkin.play.character.Character.CharacterType;
|
import funkin.play.character.CharacterBase.CharacterType;
|
||||||
import funkin.play.stage.StageData.StageDataParser;
|
import funkin.play.stage.StageData.StageDataParser;
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
|
|
||||||
|
@ -29,7 +30,8 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
public var camZoom:Float = 1.0;
|
public var camZoom:Float = 1.0;
|
||||||
|
|
||||||
var namedProps:Map<String, FlxSprite> = new Map<String, FlxSprite>();
|
var namedProps:Map<String, FlxSprite> = new Map<String, FlxSprite>();
|
||||||
var characters:Map<String, Character> = new Map<String, Character>();
|
var characters:Map<String, CharacterBase> = new Map<String, CharacterBase>();
|
||||||
|
var charactersOld:Map<String, Character> = new Map<String, Character>();
|
||||||
var boppers:Array<Bopper> = new Array<Bopper>();
|
var boppers:Array<Bopper> = new Array<Bopper>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,12 +151,12 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
{
|
{
|
||||||
if (propAnim.frameIndices.length == 0)
|
if (propAnim.frameIndices.length == 0)
|
||||||
{
|
{
|
||||||
propSprite.animation.addByPrefix(propAnim.name, propAnim.prefix, propAnim.frameRate, propAnim.loop, propAnim.flipX,
|
propSprite.animation.addByPrefix(propAnim.name, propAnim.prefix, propAnim.frameRate, propAnim.looped, propAnim.flipX,
|
||||||
propAnim.flipY);
|
propAnim.flipY);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
propSprite.animation.addByIndices(propAnim.name, propAnim.prefix, propAnim.frameIndices, "", propAnim.frameRate, propAnim.loop,
|
propSprite.animation.addByIndices(propAnim.name, propAnim.prefix, propAnim.frameIndices, "", propAnim.frameRate, propAnim.looped,
|
||||||
propAnim.flipX, propAnim.flipY);
|
propAnim.flipX, propAnim.flipY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +236,7 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
/**
|
/**
|
||||||
* Used by the PlayState to add a character to the stage.
|
* Used by the PlayState to add a character to the stage.
|
||||||
*/
|
*/
|
||||||
public function addCharacter(character:Character, charType:CharacterType)
|
public function addCharacter(character:CharacterBase, charType:CharacterType)
|
||||||
{
|
{
|
||||||
// Apply position and z-index.
|
// Apply position and z-index.
|
||||||
switch (charType)
|
switch (charType)
|
||||||
|
@ -255,7 +257,38 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
character.x = _data.characters.dad.position[0];
|
character.x = _data.characters.dad.position[0];
|
||||||
character.y = _data.characters.dad.position[1];
|
character.y = _data.characters.dad.position[1];
|
||||||
default:
|
default:
|
||||||
this.characters.set(character.curCharacter, character);
|
this.characters.set(character.characterId, character);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the character to the scene.
|
||||||
|
this.add(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the PlayState to add a character to the stage.
|
||||||
|
*/
|
||||||
|
public function addCharacterOld(character:Character, charType:CharacterType)
|
||||||
|
{
|
||||||
|
// Apply position and z-index.
|
||||||
|
switch (charType)
|
||||||
|
{
|
||||||
|
case BF:
|
||||||
|
this.charactersOld.set("bf", character);
|
||||||
|
character.zIndex = _data.characters.bf.zIndex;
|
||||||
|
character.x = _data.characters.bf.position[0];
|
||||||
|
character.y = _data.characters.bf.position[1];
|
||||||
|
case GF:
|
||||||
|
this.charactersOld.set("gf", character);
|
||||||
|
character.zIndex = _data.characters.gf.zIndex;
|
||||||
|
character.x = _data.characters.gf.position[0];
|
||||||
|
character.y = _data.characters.gf.position[1];
|
||||||
|
case DAD:
|
||||||
|
this.charactersOld.set("dad", character);
|
||||||
|
character.zIndex = _data.characters.dad.zIndex;
|
||||||
|
character.x = _data.characters.dad.position[0];
|
||||||
|
character.y = _data.characters.dad.position[1];
|
||||||
|
default:
|
||||||
|
this.charactersOld.set(character.curCharacter, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the character to the scene.
|
// Add the character to the scene.
|
||||||
|
@ -265,24 +298,25 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
||||||
/**
|
/**
|
||||||
* Retrieves a given character from the stage.
|
* Retrieves a given character from the stage.
|
||||||
*/
|
*/
|
||||||
public function getCharacter(id:String):Character
|
public function getCharacter(id:String):CharacterBase
|
||||||
{
|
{
|
||||||
return this.characters.get(id);
|
return this.characters.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBoyfriend():Character
|
public function getBoyfriend():CharacterBase
|
||||||
{
|
{
|
||||||
return getCharacter('bf');
|
return getCharacter('bf');
|
||||||
|
// return this.charactersOld.get('bf');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGirlfriend():Character
|
public function getGirlfriend():Character
|
||||||
{
|
{
|
||||||
return getCharacter('gf');
|
return this.charactersOld.get('gf');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDad():Character
|
public function getDad():Character
|
||||||
{
|
{
|
||||||
return getCharacter('dad');
|
return this.charactersOld.get('dad');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package funkin.play.stage;
|
package funkin.play.stage;
|
||||||
|
|
||||||
import openfl.Assets;
|
import flixel.util.typeLimit.OneOfTwo;
|
||||||
import funkin.util.assets.DataAssets;
|
import funkin.util.assets.DataAssets;
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
import flixel.util.typeLimit.OneOfTwo;
|
import openfl.Assets;
|
||||||
|
|
||||||
using StringTools;
|
using StringTools;
|
||||||
|
|
||||||
|
@ -194,12 +194,18 @@ class StageDataParser
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.version != STAGE_DATA_VERSION)
|
if (input.version == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version');
|
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.version != STAGE_DATA_VERSION)
|
||||||
|
{
|
||||||
|
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": bad/outdated version (got ${input.version}, expected ${STAGE_DATA_VERSION})');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (input.name == null)
|
if (input.name == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] WARN: Stage data for "$id" missing name');
|
trace('[STAGEDATA] WARN: Stage data for "$id" missing name');
|
||||||
|
@ -301,9 +307,9 @@ class StageDataParser
|
||||||
inputAnimation.frameIndices = DEFAULT_FRAMEINDICES;
|
inputAnimation.frameIndices = DEFAULT_FRAMEINDICES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAnimation.loop == null)
|
if (inputAnimation.looped == null)
|
||||||
{
|
{
|
||||||
inputAnimation.loop = true;
|
inputAnimation.looped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAnimation.flipX == null)
|
if (inputAnimation.flipX == null)
|
||||||
|
@ -432,7 +438,7 @@ typedef StageDataProp =
|
||||||
* An optional array of animations which the prop can play.
|
* An optional array of animations which the prop can play.
|
||||||
* @default Prop has no animations.
|
* @default Prop has no animations.
|
||||||
*/
|
*/
|
||||||
var animations:Array<StageDataPropAnimation>;
|
var animations:Array<AnimationData>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If animations are used, this is the name of the animation to play first.
|
* If animations are used, this is the name of the animation to play first.
|
||||||
|
@ -448,52 +454,6 @@ typedef StageDataProp =
|
||||||
var animType:String;
|
var animType:String;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef StageDataPropAnimation =
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name of the animation.
|
|
||||||
*/
|
|
||||||
var name:String;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The common beginning of image names in atlas for this animation's frames.
|
|
||||||
* For example, if the frames are named "test0001.png", "test0002.png", etc., use "test".
|
|
||||||
*/
|
|
||||||
var prefix:String;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If you want this animation to use only certain frames of an animation with a given prefix,
|
|
||||||
* select them here.
|
|
||||||
* @example [0, 1, 2, 3] (use only the first four frames)
|
|
||||||
* @default [] (all frames)
|
|
||||||
*/
|
|
||||||
var frameIndices:Array<Int>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The speed of the animation in frames per second.
|
|
||||||
* @default 24
|
|
||||||
*/
|
|
||||||
var frameRate:Null<Int>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the animation should loop.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
var loop:Null<Bool>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to flip the sprite horizontally while animating.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
var flipX:Null<Bool>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to flip the sprite vertically while animating.
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
var flipY:Null<Bool>;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef StageDataCharacter =
|
typedef StageDataCharacter =
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -505,5 +465,6 @@ typedef StageDataCharacter =
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The position to render the character at.
|
* The position to render the character at.
|
||||||
*/ position:Array<Float>
|
*/
|
||||||
|
position:Array<Float>
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue