1+ import funkin .backend .assets .ModsFolder ;
2+ import funkin .backend .utils .XMLUtil ;
3+ import funkin .backend .utils .CoolUtil ;
4+ import funkin .editors .character .CharacterAnimScreen ;
5+ import funkin .editors .ui .UIState ;
6+ import funkin .editors .ui .UIText ;
7+ import funkin .editors .ui .UIButton ;
8+ import funkin .editors .ui .UITextBox ;
9+ import funkin .editors .ui .UINumericStepper ;
10+ import funkin .editors .ui .UICheckbox ;
11+ import funkin .editors .ui .UIColorwheel ;
12+ import funkin .editors .ui .UIButtonList ;
13+ import funkin .editors .ui .UIWarningSubstate ;
14+ import funkin .game .Character ;
15+
16+ import haxe .xml .Printer ;
17+ import haxe .io .Path ;
18+
19+ import lime .ui .FileDialog ;
20+ import lime .ui .FileDialogType ;
21+
22+ import openfl .utils .Assets ;
23+
24+ import sys .io .File ;
25+ import sys .FileSystem ;
26+
27+ import CharacterAnimInfoButton ; // Custom class
28+
29+ import Xml ;
30+ import Std ;
31+
32+ public var spriteTextBox : UITextBox ;
33+ public var iconTextBox : UITextBox ;
34+ public var iconSprite : FlxSprite ;
35+ public var gameOverCharTextBox : UITextBox ;
36+ public var antialiasingCheckbox : UICheckbox ;
37+ public var flipXCheckbox : UICheckbox ;
38+ public var iconColorWheel : UIColorwheel ;
39+ public var positionXStepper : UINumericStepper ;
40+ public var positionYStepper : UINumericStepper ;
41+ public var cameraXStepper : UINumericStepper ;
42+ public var cameraYStepper : UINumericStepper ;
43+ public var scaleStepper : UINumericStepper ;
44+ public var singTimeStepper : UINumericStepper ;
45+ public var animationsButtonList : UIButtonList <CharacterAnimInfoButton >;
46+ public var isPlayerCheckbox : UICheckbox ;
47+ public var isGFCheckbox : UICheckbox ;
48+ public var scriptExtension : UITextBox ;
49+ public var isShortLived : UICheckbox ;
50+ public var loadBefore : UICheckbox ;
51+
52+ public var saveButton : UIButton ;
53+ public var closeButton : UIButton ;
54+
55+ public var onSave : (xml : Xml ) -> Void = null ;
56+
57+ var template : String =
58+ ' <character isPlayer="false" flipX="false" holdTime="4" color="#AF66CE">
59+ <anim name="idle" anim="Dad idle dance" fps="24" loop="false" x="0" y="0"/>
60+ <anim name="singUP" anim="Dad Sing note UP" fps="24" loop="false" x="-6" y="50"/>
61+ <anim name="singLEFT" anim="dad sing note right" fps="24" loop="false" x="-10" y="10"/>
62+ <anim name="singRIGHT" anim="Dad Sing Note LEFT" fps="24" loop="false" x="0" y="27"/>
63+ <anim name="singDOWN" anim="Dad Sing Note DOWN" fps="24" loop="false" x="0" y="-30"/>
64+ </character>' ;
65+
66+ var curData = {
67+ anim : []
68+ };
69+
70+ function create () {
71+ winTitle = " Creating Character" ;
72+ winWidth = 1014 ;
73+ winHeight = 600 ;
74+ /*
75+ curData = {
76+ anim: []
77+ };
78+ */
79+ }
80+
81+ function postCreate () {
82+ function addLabelOn (ui , text : String )
83+ add (new UIText (ui .x , ui .y - 24 , 0 , text ));
84+
85+ var title : UIText ;
86+ add (title = new UIText (windowSpr .x + 20 , windowSpr .y + 30 + 16 , 0 , " Sprite Data" , 28 ));
87+
88+ spriteTextBox = new UITextBox (title .x , title .y + title .height + 38 , ' sprite' , 200 );
89+ spriteTextBox .onChange = (sprite : String ) -> {checkSpriteFile (sprite );}
90+ add (spriteTextBox );
91+ addLabelOn (spriteTextBox , " Sprite" );
92+
93+ iconTextBox = new UITextBox (spriteTextBox .x + 200 + 26 , spriteTextBox .y , ' sprite-icon' , 150 );
94+ iconTextBox .onChange = (newIcon : String ) -> {updateIcon (newIcon );}
95+ add (iconTextBox );
96+ addLabelOn (iconTextBox , " Icon" );
97+
98+ updateIcon (' face' );
99+
100+ gameOverCharTextBox = new UITextBox (iconTextBox .x + 150 + (75 + 12 ), iconTextBox .y , " bf-dead" , 200 );
101+ gameOverCharTextBox .onChange = (sprite : String ) -> {checkSpriteFile (sprite );}
102+ add (gameOverCharTextBox );
103+ addLabelOn (gameOverCharTextBox , " Game Over Character" );
104+
105+ antialiasingCheckbox = new UICheckbox (spriteTextBox .x , spriteTextBox .y + 10 + 32 + 28 , " Antialiasing" , true );
106+ add (antialiasingCheckbox );
107+ addLabelOn (antialiasingCheckbox , " Antialiased" );
108+
109+ flipXCheckbox = new UICheckbox (antialiasingCheckbox .x + 172 , spriteTextBox .y + 10 + 32 + 28 , " FlipX" , false );
110+ add (flipXCheckbox );
111+ addLabelOn (flipXCheckbox , " Flipped" );
112+
113+ iconColorWheel = new UIColorwheel (gameOverCharTextBox .x + 200 + 20 , gameOverCharTextBox .y , 0xFFFFFFFF );
114+ add (iconColorWheel );
115+ addLabelOn (iconColorWheel , " Icon Color" );
116+
117+ add (title = new UIText (spriteTextBox .x , spriteTextBox .y + 10 + 46 + 84 , 0 , " Character Data" , 28 ));
118+
119+ positionXStepper = new UINumericStepper (title .x , title .y + title .height + 36 , 0 , 0.001 , 2 , null , null , 84 );
120+ add (positionXStepper );
121+ addLabelOn (positionXStepper , " Position (X,Y)" );
122+
123+ add (new UIText (positionXStepper .x + 84 - 32 + 0 , positionXStepper .y + 9 , 0 , " ," , 22 ));
124+
125+ positionYStepper = new UINumericStepper (positionXStepper .x + 84 - 32 + 26 , positionXStepper .y , 0 , 0.001 , 2 , null , null , 84 );
126+ add (positionYStepper );
127+
128+ cameraXStepper = new UINumericStepper (positionYStepper .x + 36 + 84 - 32 , positionYStepper .y , 0 , 0.001 , 2 , null , null , 84 );
129+ add (cameraXStepper );
130+ addLabelOn (cameraXStepper , " Camera Position (X,Y)" );
131+
132+ add (new UIText (cameraXStepper .x + 84 - 32 + 0 , cameraXStepper .y + 9 , 0 , " ," , 22 ));
133+
134+ cameraYStepper = new UINumericStepper (cameraXStepper .x + 84 - 32 + 26 , cameraXStepper .y , 0 , 0.001 , 2 , null , null , 84 );
135+ add (cameraYStepper );
136+
137+ scaleStepper = new UINumericStepper (cameraYStepper .x + 84 - 32 + 90 , cameraYStepper .y , 1 , 0.001 , 2 , null , null , 74 );
138+ add (scaleStepper );
139+ addLabelOn (scaleStepper , " Scale" );
140+
141+ singTimeStepper = new UINumericStepper (scaleStepper .x + 74 - 32 + 36 , scaleStepper .y , 4 , 0.001 , 2 , null , null , 74 );
142+ add (singTimeStepper );
143+ addLabelOn (singTimeStepper , " Sing Duration (Steps)" );
144+
145+ animationsButtonList = new UIButtonList /* <CharacterAnimInfoButton>*/ (singTimeStepper .x + singTimeStepper .width + 200 , singTimeStepper .y , 290 , 200 , ' ' , FlxPoint .get (280 , 35 ), null , 5 );
146+ animationsButtonList .frames = Paths .getFrames (' editors/ui/inputbox' );
147+ animationsButtonList .cameraSpacing = 0 ;
148+ animationsButtonList .addButton .callback = function () {
149+ // customPropertiesButtonList.add(new PropertyButton("newProperty", "valueHere", customPropertiesButtonList));
150+ openSubState (new CharacterAnimScreen (null , (_ ) -> {
151+ if (_ != null ) addAnim (_ );
152+ }));
153+ }
154+
155+ var tempXML = Xml .parse (template ).firstElement ();
156+ var c : Int = 0 ;
157+ for (i in tempXML .elements ()) {
158+ var animData = XMLUtil .extractAnimFromXML (i );
159+ addAnim (animData , c );
160+ c ++ ;
161+ }
162+
163+ add (animationsButtonList );
164+ addLabelOn (animationsButtonList , " Animations" );
165+
166+ isPlayerCheckbox = new UICheckbox (positionXStepper .x , positionXStepper .y + 10 + 32 + 28 , " isPlayer" , false );
167+ add (isPlayerCheckbox );
168+ addLabelOn (isPlayerCheckbox , " Is Player" );
169+
170+ isGFCheckbox = new UICheckbox (isPlayerCheckbox .x + 128 , positionXStepper .y + 10 + 32 + 28 , " isGF" , false );
171+ add (isGFCheckbox );
172+ addLabelOn (isGFCheckbox , " Is GF" );
173+
174+ for (checkbox in [isPlayerCheckbox , isGFCheckbox , antialiasingCheckbox , flipXCheckbox ])
175+ {checkbox .y + = 4 ; checkbox .x + = 6 ;}
176+
177+ scriptExtension = new UITextBox (250 , 388 , " " , 200 );
178+ add (scriptExtension );
179+ addLabelOn (scriptExtension , " Script Extension" );
180+
181+ isShortLived = new UICheckbox (scriptExtension .x , (scriptExtension .y + scriptExtension .bHeight ) + 15 , " isShortLived" , false );
182+ add (isShortLived );
183+
184+ loadBefore = new UICheckbox (isShortLived .x , isShortLived .y + 30 , " loadBefore" , true );
185+ add (loadBefore );
186+
187+ saveButton = new UIButton (windowSpr .x + windowSpr .bWidth - 20 , windowSpr .y + windowSpr .bHeight - 20 , " Save & Close" , function () {
188+ buildCharacter ();
189+ // CharacterCreationScreen.instance = null;
190+ close ();
191+ }, 125 );
192+ saveButton .x - = saveButton .bWidth ;
193+ saveButton .y - = saveButton .bHeight ;
194+
195+ closeButton = new UIButton (saveButton .x - 20 , saveButton .y , " Close" , function () {
196+ if (onSave != null ) onSave (null );
197+ // CharacterCreationScreen.instance = null;
198+ close ();
199+ }, 125 );
200+ closeButton .x - = closeButton .bWidth ;
201+ closeButton .color = 0xFFFF0000 ;
202+ add (closeButton );
203+ add (saveButton );
204+ }
205+
206+ function checkSpriteFile (sprite : String ) {
207+ if (sprite != null ) {
208+ var spriteFile = Paths .image (" characters/" + sprite ); // Common spritesheet file
209+ var spriteAtlas = Path .withoutExtension (spriteFile ) + " /Animation.json" ; // Texture Atlas
210+ if (! Assets .exists (spriteFile ) && ! Assets .exists (spriteAtlas )) {
211+ openSubState (new UIWarningSubstate (" Missing Sprite file!" , " The provided filename doesn't exist or is inaccessible. Try again." , [
212+ {label : " Ok" , color : 0xFFFF0000 , onClick : function (t ) {}}
213+ ]));
214+ }
215+ }
216+ }
217+
218+ function updateIcon (icon : String ) {
219+ if (iconSprite == null ) add (iconSprite = new FlxSprite ());
220+ /*
221+ if (iconSprite.animation.exists(icon)) return;
222+ @:privateAccess iconSprite.animation.clearAnimations();
223+ */
224+ var path : String = Paths .image (" icons/" + icon );
225+
226+ if (! Assets .exists (path )) path = Paths .image (' icons/face' );
227+
228+ iconSprite .loadGraphic (path , true , 150 , 150 );
229+ iconSprite .animation .add (icon , [0 ], 0 , false );
230+ iconSprite .antialiasing = true ;
231+ iconSprite .animation .play (icon );
232+
233+ iconSprite .scale .set (0.5 , 0.5 );
234+ iconSprite .updateHitbox ();
235+ iconSprite .setPosition (iconTextBox .x + 150 + 8 , (iconTextBox .y + 16 ) - (iconSprite .height / 2 ));
236+ }
237+
238+ function saveCharacterInfo () {
239+ /*
240+ for (stepper in [positionXStepper, positionYStepper, cameraXStepper, cameraYStepper, singTimeStepper, scaleStepper])
241+ @:privateAccess stepper.__onChange(stepper.label.text);
242+ */
243+ var xml = Xml .createElement (" character" );
244+ // Avoids redundant default values
245+ if (positionXStepper .value != 0 ) xml .set (" x" , Std .string (positionXStepper .value ));
246+ if (positionYStepper .value != 0 ) xml .set (" y" , Std .string (positionYStepper .value ));
247+ if (gameOverCharTextBox .label .text != Character .FALLBACK_DEAD_CHARACTER ) xml .set (" gameOverChar" , gameOverCharTextBox .label .text );
248+ if (cameraXStepper .value != 0 ) xml .set (" camx" , Std .string (cameraXStepper .value ));
249+ if (cameraXStepper .value != 0 ) xml .set (" camy" , Std .string (cameraYStepper .value ));
250+ if (singTimeStepper .value != 4 ) xml .set (" holdTime" , Std .string (singTimeStepper .value ));
251+ if (flipXCheckbox .checked ) xml .set (" flipX" , Std .string (flipXCheckbox .checked ));
252+ if (scaleStepper .value != 1 ) xml .set (" scale" , Std .string (scaleStepper .value ));
253+ if (iconColorWheel .colorChanged )
254+ xml .set (" color" , iconColorWheel .curColorString );
255+
256+ xml .set (" isPlayer" , isPlayerCheckbox .checked ? " true" : " false" );
257+ xml .set (" icon" , iconTextBox .label .text );
258+ xml .set (" antialiasing" , antialiasingCheckbox .checked ? " true" : " false" );
259+ xml .set (" sprite" , spriteTextBox .label .text );
260+
261+ for (anim in curData .anim )
262+ {
263+ var animXml : Xml = Xml .createElement (' anim' );
264+ animXml .set (" name" , anim .name );
265+ animXml .set (" anim" , anim .anim );
266+ animXml .set (" loop" , Std .string (anim .loop ));
267+ animXml .set (" fps" , Std .string (anim .fps ));
268+ // var offset:FlxPoint = character.getAnimOffset(anim.name);
269+ var offset : FlxPoint = FlxPoint .get (anim .x , anim .y );
270+ animXml .set (" x" , Std .string (offset .x ));
271+ animXml .set (" y" , Std .string (offset .y ));
272+ offset .put ();
273+
274+ if (anim .indices .length > 0 )
275+ animXml .set (" indices" , CoolUtil .formatNumberRange (anim .indices ));
276+ xml .addChild (animXml );
277+ }
278+
279+ if (StringTools .trim (scriptExtension .label .text ) != " " ) {
280+ var extXml : Xml = Xml .createElement (' extension' );
281+ var _scriptFile : String = StringTools .trim (scriptExtension .label .text );
282+ var _scriptFolder : String = null ;
283+ var _scriptPath : Array <String > = _scriptFile .split (" /" );
284+ if (_scriptPath .length > 1 ) {
285+ _scriptFile = _scriptPath .pop ();
286+ _scriptFolder = _scriptPath .join (" /" ) + " /" ;
287+ }
288+ extXml .set (" script" , _scriptFile );
289+ if (_scriptFolder != null ) extXml .set (" folder" , _scriptFolder );
290+ if (isShortLived .checked ) extXml .set (" isShortLived" , Std .string (isShortLived .checked ));
291+ if (! loadBefore .checked ) extXml .set (" loadBefore" , Std .string (loadBefore .checked ));
292+ xml .addChild (extXml );
293+ }
294+
295+ // End of writing XML, time to save it
296+ var data : String = " <!DOCTYPE codename-engine-character>\n " + Printer .print (xml , true );
297+ var fileDialog = new FileDialog ();
298+ fileDialog .onCancel .add (function () close ());
299+ fileDialog .onSelect .add (function (str )
300+ {
301+ CoolUtil .safeSaveFile (str , data );
302+ close ();
303+ FlxG .resetState ();
304+ });
305+ var fullPath = FileSystem .fullPath (Paths .xml (' characters/character' ));
306+ fileDialog .browse (FileDialogType .SAVE , " xml" , fullPath );
307+ if (onSave != null ) onSave (xml );
308+ }
309+
310+ function addAnim (animData : AnimData , animID : Int = - 1 ) {
311+ var newButton = new CharacterAnimInfoButton (0 , 0 , animData .name , FlxPoint .get (animData .x ,animData .y ));
312+ // Edit/Delete callbacks
313+ newButton .editButton .callback = function () {
314+ editAnim (newButton .anim );
315+ };
316+ newButton .deleteButton .callback = function () {
317+ deleteAnim (newButton .anim );
318+ };
319+
320+ if (animID == - 1 ){
321+ animationsButtonList .add (newButton );
322+ curData .anim .push (animData );
323+ }
324+ else {
325+ animationsButtonList .insert (newButton , animID );
326+ curData .anim .insert (animID , animData );
327+ }
328+ }
329+
330+ public function editAnim (name : String ) {
331+ var _anim : AnimData = null ;
332+ for (anim in curData .anim ) {
333+ if (anim .name == name ) {
334+ _anim = anim ;
335+ break ;
336+ }
337+ }
338+ openSubState (new CharacterAnimScreen (_anim , (_ ) -> {
339+ if (_ != null ) _edit_anim (name , _anim , _ );
340+ }));
341+ }
342+
343+ function _edit_anim (name : String , oldAnimData : AnimData , animData : AnimData ) {
344+ var buttoner : CharacterAnimInfoButton = null ;
345+ for (button in animationsButtonList .buttons .members )
346+ if (button .anim == name ) buttoner = button ;
347+ buttoner .updateInfo (animData .name );
348+
349+ // Replace the old data
350+ var index = curData .anim .indexOf (oldAnimData );
351+ curData .anim [index ] = animData ;
352+ }
353+
354+ public function deleteAnim (name : String ) {
355+ for (button in animationsButtonList .buttons .members )
356+ {
357+ if (button .anim == name )
358+ animationsButtonList .remove (button );
359+ }
360+ for (anim in curData .anim )
361+ {
362+ if (anim .name == name )
363+ {
364+ curData .anim .remove (anim );
365+ break ;
366+ }
367+ }
368+ }
369+
370+ // ???
371+ function buildCharacter () {
372+ saveCharacterInfo ();
373+ }
374+ /*
375+ typedef CharacterCreationData = {
376+ var anim:Array<AnimData>;
377+ }
378+ */
0 commit comments