mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-12-26 15:07:14 +00:00
First WIP of scripted characters (NOT WORKING)
This commit is contained in:
parent
7c0cb9c69c
commit
1a85b4a1ce
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.charting.ChartingState;
|
||||
|
@ -122,7 +123,7 @@ class InitState extends FlxTransitionableState
|
|||
FlxTransitionableState.skipNextTransIn = true;
|
||||
|
||||
StageDataParser.loadStageCache();
|
||||
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
#if song
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
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;
|
||||
|
||||
import funkin.play.Strumline.StrumlineArrow;
|
||||
import funkin.play.character.CharacterBase;
|
||||
import flixel.addons.effects.FlxTrail;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxCamera;
|
||||
|
@ -27,8 +27,10 @@ import funkin.modding.events.ScriptEventDispatcher;
|
|||
import funkin.modding.IHook;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.Note;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.Strumline.StrumlineArrow;
|
||||
import funkin.play.Strumline.StrumlineStyle;
|
||||
import funkin.Section.SwagSection;
|
||||
import funkin.SongLoad.SwagSong;
|
||||
|
@ -126,8 +128,11 @@ class PlayState extends MusicBeatState implements IHook
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
|
@ -240,7 +245,6 @@ class PlayState extends MusicBeatState implements IHook
|
|||
var songScore:Int = 0;
|
||||
var doof:DialogueBox;
|
||||
var grpNoteSplashes:FlxTypedGroup<NoteSplash>;
|
||||
var camPos:FlxPoint;
|
||||
var comboPopUps:PopUpStuff;
|
||||
var perfectMode:Bool = false;
|
||||
var previousFrameTime:Int = 0;
|
||||
|
@ -315,6 +319,14 @@ class PlayState extends MusicBeatState implements IHook
|
|||
initDiscord();
|
||||
#end
|
||||
|
||||
// Configure camera follow point.
|
||||
if (previousCameraFollowPoint != null)
|
||||
{
|
||||
cameraFollowPoint.setPosition(previousCameraFollowPoint.x, previousCameraFollowPoint.y);
|
||||
previousCameraFollowPoint = null;
|
||||
}
|
||||
add(cameraFollowPoint);
|
||||
|
||||
comboPopUps = new PopUpStuff();
|
||||
add(comboPopUps);
|
||||
|
||||
|
@ -328,16 +340,6 @@ class PlayState extends MusicBeatState implements IHook
|
|||
|
||||
generateSong();
|
||||
|
||||
cameraFollowPoint = new FlxObject(0, 0, 1, 1);
|
||||
cameraFollowPoint.setPosition(camPos.x, camPos.y);
|
||||
|
||||
if (previousCameraFollowPoint != null)
|
||||
{
|
||||
cameraFollowPoint = previousCameraFollowPoint;
|
||||
previousCameraFollowPoint = null;
|
||||
}
|
||||
|
||||
add(cameraFollowPoint);
|
||||
resetCamera();
|
||||
|
||||
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
|
||||
|
@ -467,7 +469,11 @@ class PlayState extends MusicBeatState implements IHook
|
|||
|
||||
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';
|
||||
|
||||
switch (currentStageId)
|
||||
|
@ -483,35 +489,34 @@ class PlayState extends MusicBeatState implements IHook
|
|||
}
|
||||
|
||||
if (currentSong.player1 == "pico")
|
||||
{
|
||||
gfVersion = "nene";
|
||||
}
|
||||
|
||||
if (currentSong.song.toLowerCase() == 'stress')
|
||||
gfVersion = 'pico-speaker';
|
||||
|
||||
var gf = new Character(400, 130, gfVersion);
|
||||
gf.scrollFactor.set(0.95, 0.95);
|
||||
|
||||
switch (gfVersion)
|
||||
var girlfriend:Character = new Character(350, -70, gfVersion);
|
||||
girlfriend.scrollFactor.set(0.95, 0.95);
|
||||
if (gfVersion == 'pico-speaker')
|
||||
{
|
||||
case 'pico-speaker':
|
||||
gf.x -= 50;
|
||||
gf.y -= 200;
|
||||
girlfriend.x -= 50;
|
||||
girlfriend.y -= 200;
|
||||
}
|
||||
|
||||
//
|
||||
// DAD
|
||||
//
|
||||
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)
|
||||
{
|
||||
case 'gf':
|
||||
dad.setPosition(gf.x, gf.y);
|
||||
gf.visible = false;
|
||||
dad.setPosition(girlfriend.x, girlfriend.y);
|
||||
girlfriend.visible = false;
|
||||
if (isStoryMode)
|
||||
{
|
||||
camPos.x += 600;
|
||||
cameraFollowPoint.x += 600;
|
||||
tweenCamIn();
|
||||
}
|
||||
case "spooky":
|
||||
|
@ -521,25 +526,40 @@ class PlayState extends MusicBeatState implements IHook
|
|||
case 'monster-christmas':
|
||||
dad.y += 130;
|
||||
case 'dad':
|
||||
camPos.x += 400;
|
||||
cameraFollowPoint.x += 400;
|
||||
case 'pico':
|
||||
camPos.x += 600;
|
||||
cameraFollowPoint.x += 600;
|
||||
dad.y += 300;
|
||||
case 'parents-christmas':
|
||||
dad.x -= 500;
|
||||
case 'senpai' | 'senpai-angry':
|
||||
dad.x += 150;
|
||||
dad.y += 360;
|
||||
camPos.set(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
||||
cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
||||
case 'spirit':
|
||||
dad.x -= 150;
|
||||
dad.y += 100;
|
||||
camPos.set(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
||||
cameraFollowPoint.setPosition(dad.getGraphicMidpoint().x + 300, dad.getGraphicMidpoint().y);
|
||||
case 'tankman':
|
||||
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
|
||||
switch (currentStageId)
|
||||
|
@ -550,8 +570,8 @@ class PlayState extends MusicBeatState implements IHook
|
|||
evilTrail.zIndex = 190;
|
||||
add(evilTrail);
|
||||
case "tank":
|
||||
gf.y += 10;
|
||||
gf.x -= 30;
|
||||
girlfriend.y += 10;
|
||||
girlfriend.x -= 30;
|
||||
boyfriend.x += 40;
|
||||
boyfriend.y += 0;
|
||||
dad.y += 60;
|
||||
|
@ -559,8 +579,8 @@ class PlayState extends MusicBeatState implements IHook
|
|||
|
||||
if (gfVersion != 'pico-speaker')
|
||||
{
|
||||
gf.x -= 170;
|
||||
gf.y -= 75;
|
||||
girlfriend.x -= 170;
|
||||
girlfriend.y -= 75;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -568,16 +588,16 @@ class PlayState extends MusicBeatState implements IHook
|
|||
{
|
||||
// We're using Eric's stage handler.
|
||||
// Characters get added to the stage, not the main scene.
|
||||
currentStage.addCharacter(gf, GF);
|
||||
currentStage.addCharacter(boyfriend, BF);
|
||||
currentStage.addCharacter(dad, DAD);
|
||||
currentStage.addCharacterOld(girlfriend, GF);
|
||||
currentStage.addCharacterOld(dad, DAD);
|
||||
|
||||
// Redo z-indexes.
|
||||
currentStage.refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
add(gf);
|
||||
add(girlfriend);
|
||||
add(dad);
|
||||
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.
|
||||
StageDataParser.loadStageCache();
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
// 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.x += senpaiEvil.width / 5;
|
||||
|
||||
cameraFollowPoint.setPosition(camPos.x, camPos.y);
|
||||
|
||||
if (currentSong.song.toLowerCase() == 'roses' || currentSong.song.toLowerCase() == 'thorns')
|
||||
{
|
||||
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.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;
|
||||
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
|
||||
|
@ -1708,7 +1727,7 @@ class PlayState extends MusicBeatState implements IHook
|
|||
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);
|
||||
|
||||
|
@ -1800,7 +1819,7 @@ class PlayState extends MusicBeatState implements IHook
|
|||
if (curBeat % 2 == 0)
|
||||
{
|
||||
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"))
|
||||
currentStage.getDad().dance();
|
||||
}
|
||||
|
@ -1812,7 +1831,7 @@ class PlayState extends MusicBeatState implements IHook
|
|||
|
||||
if (curBeat % 8 == 7 && currentSong.song == 'Bopeebo')
|
||||
{
|
||||
currentStage.getBoyfriend().playAnim('hey', true);
|
||||
currentStage.getBoyfriend().playAnimation('hey', true);
|
||||
}
|
||||
|
||||
if (curBeat % 16 == 15
|
||||
|
@ -1821,7 +1840,7 @@ class PlayState extends MusicBeatState implements IHook
|
|||
&& curBeat > 16
|
||||
&& curBeat < 48)
|
||||
{
|
||||
currentStage.getBoyfriend().playAnim('hey', true);
|
||||
currentStage.getBoyfriend().playAnimation('hey', 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.
|
||||
* Set to 0 to disable idle animation.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public var xOffset:Float = 0;
|
||||
public var xOffset(default, set):Float = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -55,7 +64,15 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -73,7 +90,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
|
||||
function update_shouldAlternate():Void
|
||||
{
|
||||
if (this.animation.getByName('danceLeft') != null)
|
||||
if (hasAnimation('danceLeft'))
|
||||
{
|
||||
this.shouldAlternate = true;
|
||||
}
|
||||
|
@ -84,7 +101,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
if (event.beat % danceEvery == 0)
|
||||
if (danceEvery > 0 && event.beat % danceEvery == 0)
|
||||
{
|
||||
dance();
|
||||
}
|
||||
|
@ -109,20 +126,42 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
|||
{
|
||||
if (hasDanced)
|
||||
{
|
||||
this.animation.play('danceRight$idleSuffix');
|
||||
playAnimation('danceRight$idleSuffix');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.animation.play('danceLeft$idleSuffix');
|
||||
playAnimation('danceLeft$idleSuffix');
|
||||
}
|
||||
hasDanced = !hasDanced;
|
||||
}
|
||||
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 onCreate(event:ScriptEvent) {}
|
||||
|
|
|
@ -4,7 +4,4 @@ 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 {}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import funkin.play.character.CharacterBase;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
|
@ -10,7 +11,7 @@ import flixel.group.FlxSpriteGroup;
|
|||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.modding.IHook;
|
||||
import funkin.play.character.Character.CharacterType;
|
||||
import funkin.play.character.CharacterBase.CharacterType;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.util.SortUtil;
|
||||
|
||||
|
@ -29,7 +30,8 @@ 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, CharacterBase> = new Map<String, CharacterBase>();
|
||||
var charactersOld:Map<String, Character> = new Map<String, Character>();
|
||||
var boppers:Array<Bopper> = new Array<Bopper>();
|
||||
|
||||
/**
|
||||
|
@ -149,12 +151,12 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -234,7 +236,7 @@ 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:CharacterBase, charType:CharacterType)
|
||||
{
|
||||
// Apply position and z-index.
|
||||
switch (charType)
|
||||
|
@ -255,7 +257,38 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
character.x = _data.characters.dad.position[0];
|
||||
character.y = _data.characters.dad.position[1];
|
||||
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.
|
||||
|
@ -265,24 +298,25 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte
|
|||
/**
|
||||
* Retrieves a given character from the stage.
|
||||
*/
|
||||
public function getCharacter(id:String):Character
|
||||
public function getCharacter(id:String):CharacterBase
|
||||
{
|
||||
return this.characters.get(id);
|
||||
}
|
||||
|
||||
public function getBoyfriend():Character
|
||||
public function getBoyfriend():CharacterBase
|
||||
{
|
||||
return getCharacter('bf');
|
||||
// return this.charactersOld.get('bf');
|
||||
}
|
||||
|
||||
public function getGirlfriend():Character
|
||||
{
|
||||
return getCharacter('gf');
|
||||
return this.charactersOld.get('gf');
|
||||
}
|
||||
|
||||
public function getDad():Character
|
||||
{
|
||||
return getCharacter('dad');
|
||||
return this.charactersOld.get('dad');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import openfl.Assets;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import haxe.Json;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import openfl.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -194,12 +194,18 @@ class StageDataParser
|
|||
return null;
|
||||
}
|
||||
|
||||
if (input.version != STAGE_DATA_VERSION)
|
||||
if (input.version == null)
|
||||
{
|
||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version');
|
||||
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)
|
||||
{
|
||||
trace('[STAGEDATA] WARN: Stage data for "$id" missing name');
|
||||
|
@ -301,9 +307,9 @@ class StageDataParser
|
|||
inputAnimation.frameIndices = DEFAULT_FRAMEINDICES;
|
||||
}
|
||||
|
||||
if (inputAnimation.loop == null)
|
||||
if (inputAnimation.looped == null)
|
||||
{
|
||||
inputAnimation.loop = true;
|
||||
inputAnimation.looped = true;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipX == null)
|
||||
|
@ -432,7 +438,7 @@ typedef StageDataProp =
|
|||
* An optional array of animations which the prop can play.
|
||||
* @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.
|
||||
|
@ -448,52 +454,6 @@ typedef StageDataProp =
|
|||
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 =
|
||||
{
|
||||
/**
|
||||
|
@ -505,5 +465,6 @@ typedef StageDataCharacter =
|
|||
|
||||
/**
|
||||
* The position to render the character at.
|
||||
*/ position:Array<Float>
|
||||
*/
|
||||
position:Array<Float>
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue