1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "studio.h"
24
25#if defined(BUILD_EDITORS)
26
27#include "editors/code.h"
28#include "editors/sprite.h"
29#include "editors/map.h"
30#include "editors/world.h"
31#include "editors/sfx.h"
32#include "editors/music.h"
33#include "screens/console.h"
34#include "screens/surf.h"
35#include "ext/history.h"
36#include "net.h"
37#include "wave_writer.h"
38#include "ext/gif.h"
39
40#endif
41
42#include "ext/md5.h"
43#include "config.h"
44#include "cart.h"
45#include "screens/start.h"
46#include "screens/run.h"
47#include "screens/menu.h"
48#include "screens/mainmenu.h"
49
50#include "fs.h"
51
52#include "argparse.h"
53
54#include <ctype.h>
55
56#define _USE_MATH_DEFINES
57#include <math.h>
58
59#define MD5_HASHSIZE 16
60
61#if defined(TIC80_PRO)
62#define TIC_EDITOR_BANKS (TIC_BANKS)
63#else
64#define TIC_EDITOR_BANKS 1
65#endif
66
67#ifdef BUILD_EDITORS
68typedef struct
69{
70 u8 data[MD5_HASHSIZE];
71} CartHash;
72
73static const EditorMode Modes[] =
74{
75 TIC_CODE_MODE,
76 TIC_SPRITE_MODE,
77 TIC_MAP_MODE,
78 TIC_SFX_MODE,
79 TIC_MUSIC_MODE,
80};
81
82static const EditorMode BankModes[] =
83{
84 TIC_SPRITE_MODE,
85 TIC_MAP_MODE,
86 TIC_SFX_MODE,
87 TIC_MUSIC_MODE,
88};
89
90#endif
91
92typedef struct
93{
94 bool down;
95 bool click;
96
97 struct
98 {
99 s32 start;
100 s32 ticks;
101 bool click;
102 } dbl;
103
104 tic_point start;
105 tic_point end;
106
107
108} MouseState;
109
110struct Studio
111{
112 tic_mem* tic;
113
114 bool alive;
115
116 EditorMode mode;
117 EditorMode prevMode;
118 EditorMode toolbarMode;
119
120 struct
121 {
122 MouseState state[3];
123 } mouse;
124
125#if defined(BUILD_EDITORS)
126 EditorMode menuMode;
127
128 struct
129 {
130 CartHash hash;
131 u64 mdate;
132 }cart;
133
134 struct
135 {
136 bool show;
137 bool chained;
138
139 union
140 {
141 struct
142 {
143 s8 sprites;
144 s8 map;
145 s8 sfx;
146 s8 music;
147 } index;
148
149 s8 indexes[COUNT_OF(BankModes)];
150 };
151 } bank;
152
153 struct
154 {
155 struct
156 {
157 s32 popup;
158 } pos;
159
160 Movie* movie;
161
162 Movie idle;
163 Movie show;
164 Movie wait;
165 Movie hide;
166 } anim;
167
168 struct
169 {
170 char message[STUDIO_TEXT_BUFFER_WIDTH];
171 } popup;
172
173 struct
174 {
175 char text[STUDIO_TEXT_BUFFER_WIDTH];
176 } tooltip;
177
178 struct
179 {
180 bool record;
181
182 u32* buffer;
183 s32 frames;
184 s32 frame;
185
186 } video;
187
188 Code* code;
189
190 struct
191 {
192 Sprite* sprite[TIC_EDITOR_BANKS];
193 Map* map[TIC_EDITOR_BANKS];
194 Sfx* sfx[TIC_EDITOR_BANKS];
195 Music* music[TIC_EDITOR_BANKS];
196 } banks;
197
198 Console* console;
199 World* world;
200 Surf* surf;
201
202 tic_net* net;
203#endif
204
205 Start* start;
206 Run* run;
207 Menu* menu;
208 Config* config;
209
210 StudioMainMenu* mainmenu;
211
212 tic_fs* fs;
213 s32 samplerate;
214 tic_font systemFont;
215};
216
217#if defined(BUILD_EDITORS)
218
219#define FRAME_SIZE (TIC80_FULLWIDTH * TIC80_FULLHEIGHT * sizeof(u32))
220
221static const char VideoGif[] = "video%i.gif";
222static const char ScreenGif[] = "screen%i.gif";
223
224#endif
225
226static void emptyDone(void* data) {}
227
228void fadePalette(tic_palette* pal, s32 value)
229{
230 for(u8 *i = pal->data, *end = i + sizeof(tic_palette); i < end; i++)
231 *i = *i * value >> 8;
232}
233
234void map2ram(tic_ram* ram, const tic_map* src)
235{
236 memcpy(ram->map.data, src, sizeof ram->map);
237}
238
239void tiles2ram(tic_ram* ram, const tic_tiles* src)
240{
241 memcpy(ram->tiles.data, src, sizeof ram->tiles * TIC_SPRITE_BANKS);
242}
243
244static inline void sfx2ram(tic_ram* ram, const tic_sfx* src)
245{
246 memcpy(&ram->sfx, src, sizeof ram->sfx);
247}
248
249static inline void music2ram(tic_ram* ram, const tic_music* src)
250{
251 memcpy(&ram->music, src, sizeof ram->music);
252}
253
254s32 calcWaveAnimation(tic_mem* tic, u32 offset, s32 channel)
255{
256 const tic_sound_register* reg = &tic->ram->registers[channel];
257
258 s32 val = tic_tool_noise(&reg->waveform)
259 ? (rand() & 1) * MAX_VOLUME
260 : tic_tool_peek4(reg->waveform.data, ((offset * tic_sound_register_get_freq(reg)) >> 7) % WAVE_VALUES);
261
262 return val * reg->volume;
263}
264
265#if defined(BUILD_EDITORS)
266static const tic_sfx* getSfxSrc(Studio* studio)
267{
268 tic_mem* tic = studio->tic;
269 return &tic->cart.banks[studio->bank.index.sfx].sfx;
270}
271
272static const tic_music* getMusicSrc(Studio* studio)
273{
274 tic_mem* tic = studio->tic;
275 return &tic->cart.banks[studio->bank.index.music].music;
276}
277
278const char* studioExportSfx(Studio* studio, s32 index, const char* filename)
279{
280 tic_mem* tic = studio->tic;
281
282 const char* path = tic_fs_path(studio->fs, filename);
283
284 if(wave_open( studio->samplerate, path ))
285 {
286
287#if TIC80_SAMPLE_CHANNELS == 2
288 wave_enable_stereo();
289#endif
290
291 const tic_sfx* sfx = getSfxSrc(studio);
292
293 sfx2ram(tic->ram, sfx);
294 music2ram(tic->ram, getMusicSrc(studio));
295
296 {
297 const tic_sample* effect = &sfx->samples.data[index];
298
299 enum{Channel = 0};
300 sfx_stop(tic, Channel);
301 tic_api_sfx(tic, index, effect->note, effect->octave, -1, Channel, MAX_VOLUME, MAX_VOLUME, SFX_DEF_SPEED);
302
303 for(s32 ticks = 0, pos = 0; pos < SFX_TICKS; pos = tic_tool_sfx_pos(effect->speed, ++ticks))
304 {
305 tic_core_tick_start(tic);
306 tic_core_tick_end(tic);
307 tic_core_synth_sound(tic);
308
309 wave_write(tic->product.samples.buffer, tic->product.samples.count);
310 }
311
312 sfx_stop(tic, Channel);
313 memset(tic->ram->registers, 0, sizeof(tic_sound_register));
314 }
315
316 wave_close();
317
318 return path;
319 }
320
321 return NULL;
322}
323
324const char* studioExportMusic(Studio* studio, s32 track, const char* filename)
325{
326 tic_mem* tic = studio->tic;
327
328 const char* path = tic_fs_path(studio->fs, filename);
329
330 if(wave_open( studio->samplerate, path ))
331 {
332#if TIC80_SAMPLE_CHANNELS == 2
333 wave_enable_stereo();
334#endif
335
336 const tic_sfx* sfx = getSfxSrc(studio);
337 const tic_music* music = getMusicSrc(studio);
338
339 sfx2ram(tic->ram, sfx);
340 music2ram(tic->ram, music);
341
342 const tic_music_state* state = &tic->ram->music_state;
343 const Music* editor = studio->banks.music[studio->bank.index.music];
344
345 tic_api_music(tic, track, -1, -1, false, editor->sustain, -1, -1);
346
347 s32 frame = state->music.frame;
348 s32 frames = MUSIC_FRAMES * 16;
349
350 while(frames && state->flag.music_status == tic_music_play)
351 {
352 tic_core_tick_start(tic);
353
354 for (s32 i = 0; i < TIC_SOUND_CHANNELS; i++)
355 if(!editor->on[i])
356 tic->ram->registers[i].volume = 0;
357
358 tic_core_tick_end(tic);
359 tic_core_synth_sound(tic);
360
361 wave_write(tic->product.samples.buffer, tic->product.samples.count);
362
363 if(frame != state->music.frame)
364 {
365 --frames;
366 frame = state->music.frame;
367 }
368 }
369
370 tic_api_music(tic, -1, -1, -1, false, false, -1, -1);
371
372 wave_close();
373 return path;
374 }
375
376 return NULL;
377}
378#endif
379
380void sfx_stop(tic_mem* tic, s32 channel)
381{
382 tic_api_sfx(tic, -1, 0, 0, -1, channel, MAX_VOLUME, MAX_VOLUME, SFX_DEF_SPEED);
383}
384
385char getKeyboardText(Studio* studio)
386{
387 char text;
388 if(!tic_sys_keyboard_text(&text))
389 {
390 tic_mem* tic = studio->tic;
391 tic80_input* input = &tic->ram->input;
392
393 static const char Symbols[] = " abcdefghijklmnopqrstuvwxyz0123456789-=[]\\;'`,./ ";
394 static const char Shift[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ)!@#$%^&*(_+{}|:\"~<>? ";
395
396 enum{Count = sizeof Symbols};
397
398 for(s32 i = 0; i < TIC80_KEY_BUFFER; i++)
399 {
400 tic_key key = input->keyboard.keys[i];
401
402 if(key > 0 && key < Count && tic_api_keyp(tic, key, KEYBOARD_HOLD, KEYBOARD_PERIOD))
403 {
404 bool caps = tic_api_key(tic, tic_key_capslock);
405 bool shift = tic_api_key(tic, tic_key_shift);
406
407 return caps
408 ? key >= tic_key_a && key <= tic_key_z
409 ? shift ? Symbols[key] : Shift[key]
410 : shift ? Shift[key] : Symbols[key]
411 : shift ? Shift[key] : Symbols[key];
412 }
413 }
414
415 return '\0';
416 }
417
418 return text;
419}
420
421bool keyWasPressed(Studio* studio, tic_key key)
422{
423 tic_mem* tic = studio->tic;
424 return tic_api_keyp(tic, key, KEYBOARD_HOLD, KEYBOARD_PERIOD);
425}
426
427bool anyKeyWasPressed(Studio* studio)
428{
429 tic_mem* tic = studio->tic;
430
431 for(s32 i = 0; i < TIC80_KEY_BUFFER; i++)
432 {
433 tic_key key = tic->ram->input.keyboard.keys[i];
434
435 if(tic_api_keyp(tic, key, KEYBOARD_HOLD, KEYBOARD_PERIOD))
436 return true;
437 }
438
439 return false;
440}
441
442#if defined(BUILD_EDITORS)
443tic_tiles* getBankTiles(Studio* studio)
444{
445 return &studio->tic->cart.banks[studio->bank.index.sprites].tiles;
446}
447
448tic_map* getBankMap(Studio* studio)
449{
450 return &studio->tic->cart.banks[studio->bank.index.map].map;
451}
452
453tic_palette* getBankPalette(Studio* studio, bool vbank)
454{
455 tic_bank* bank = &studio->tic->cart.banks[studio->bank.index.sprites];
456 return vbank ? &bank->palette.vbank1 : &bank->palette.vbank0;
457}
458
459tic_flags* getBankFlags(Studio* studio)
460{
461 return &studio->tic->cart.banks[studio->bank.index.sprites].flags;
462}
463#endif
464
465void playSystemSfx(Studio* studio, s32 id)
466{
467 const tic_sample* effect = &studio->config->cart->bank0.sfx.samples.data[id];
468 tic_api_sfx(studio->tic, id, effect->note, effect->octave, -1, 0, MAX_VOLUME, MAX_VOLUME, effect->speed);
469}
470
471static void md5(const void* voidData, s32 length, u8 digest[MD5_HASHSIZE])
472{
473 enum {Size = 512};
474
475 const u8* data = voidData;
476
477 MD5_CTX c;
478 MD5_Init(&c);
479
480 while (length > 0)
481 {
482 MD5_Update(&c, data, length > Size ? Size: length);
483
484 length -= Size;
485 data += Size;
486 }
487
488 MD5_Final(digest, &c);
489}
490
491const char* md5str(const void* data, s32 length)
492{
493 static char res[MD5_HASHSIZE * 2 + 1];
494
495 u8 digest[MD5_HASHSIZE];
496
497 md5(data, length, digest);
498
499 for (s32 n = 0; n < MD5_HASHSIZE; ++n)
500 snprintf(res + n*2, sizeof("ff"), "%02x", digest[n]);
501
502 return res;
503}
504
505static u8* getSpritePtr(tic_tile* tiles, s32 x, s32 y)
506{
507 enum { SheetCols = (TIC_SPRITESHEET_SIZE / TIC_SPRITESIZE) };
508 return tiles[x / TIC_SPRITESIZE + y / TIC_SPRITESIZE * SheetCols].data;
509}
510
511
512void setSpritePixel(tic_tile* tiles, s32 x, s32 y, u8 color)
513{
514 // TODO: check spritesheet rect
515 tic_tool_poke4(getSpritePtr(tiles, x, y), (x % TIC_SPRITESIZE) + (y % TIC_SPRITESIZE) * TIC_SPRITESIZE, color);
516}
517
518u8 getSpritePixel(tic_tile* tiles, s32 x, s32 y)
519{
520 // TODO: check spritesheet rect
521 return tic_tool_peek4(getSpritePtr(tiles, x, y), (x % TIC_SPRITESIZE) + (y % TIC_SPRITESIZE) * TIC_SPRITESIZE);
522}
523
524#if defined(BUILD_EDITORS)
525void toClipboard(const void* data, s32 size, bool flip)
526{
527 if(data)
528 {
529 enum {Len = 2};
530
531 char* clipboard = (char*)malloc(size*Len + 1);
532
533 if(clipboard)
534 {
535 char* ptr = clipboard;
536
537 for(s32 i = 0; i < size; i++, ptr+=Len)
538 {
539 sprintf(ptr, "%02x", ((u8*)data)[i]);
540
541 if(flip)
542 {
543 char tmp = ptr[0];
544 ptr[0] = ptr[1];
545 ptr[1] = tmp;
546 }
547 }
548
549 tic_sys_clipboard_set(clipboard);
550 free(clipboard);
551 }
552 }
553}
554
555static void removeWhiteSpaces(char* str)
556{
557 s32 i = 0;
558 s32 len = (s32)strlen(str);
559
560 for (s32 j = 0; j < len; j++)
561 if(!isspace(str[j]))
562 str[i++] = str[j];
563
564 str[i] = '\0';
565}
566
567bool fromClipboard(void* data, s32 size, bool flip, bool remove_white_spaces, bool sameSize)
568{
569 if(tic_sys_clipboard_has())
570 {
571 char* clipboard = tic_sys_clipboard_get();
572
573 SCOPE(tic_sys_clipboard_free(clipboard))
574 {
575 if (remove_white_spaces)
576 removeWhiteSpaces(clipboard);
577
578 bool valid = sameSize
579 ? strlen(clipboard) == size * 2
580 : strlen(clipboard) <= size * 2;
581
582 if(valid) tic_tool_str2buf(clipboard, (s32)strlen(clipboard), data, flip);
583
584 return valid;
585 }
586 }
587
588 return false;
589}
590
591void showTooltip(Studio* studio, const char* text)
592{
593 strncpy(studio->tooltip.text, text, sizeof studio->tooltip.text - 1);
594}
595
596static void drawExtrabar(Studio* studio, tic_mem* tic)
597{
598 enum {Size = 7};
599
600 s32 x = (COUNT_OF(Modes) + 1) * Size + 17 * TIC_FONT_WIDTH;
601 s32 y = 0;
602
603 static struct Icon {u8 id; StudioEvent event; const char* tip;} Icons[] =
604 {
605 {tic_icon_cut, TIC_TOOLBAR_CUT, "CUT [ctrl+x]"},
606 {tic_icon_copy, TIC_TOOLBAR_COPY, "COPY [ctrl+c]"},
607 {tic_icon_paste, TIC_TOOLBAR_PASTE, "PASTE [ctrl+v]"},
608 {tic_icon_undo, TIC_TOOLBAR_UNDO, "UNDO [ctrl+z]"},
609 {tic_icon_redo, TIC_TOOLBAR_REDO, "REDO [ctrl+y]"},
610 };
611
612 u8 color = tic_color_red;
613 FOR(const struct Icon*, icon, Icons)
614 {
615 tic_rect rect = {x, y, Size, Size};
616
617 u8 bg = tic_color_white;
618 u8 fg = tic_color_light_grey;
619
620 if(checkMousePos(studio, &rect))
621 {
622 setCursor(studio, tic_cursor_hand);
623
624 fg = color;
625 showTooltip(studio, icon->tip);
626
627 if(checkMouseDown(studio, &rect, tic_mouse_left))
628 {
629 bg = fg;
630 fg = tic_color_white;
631 }
632 else if(checkMouseClick(studio, &rect, tic_mouse_left))
633 {
634 setStudioEvent(studio, icon->event);
635 }
636 }
637
638 tic_api_rect(tic, x, y, Size, Size, bg);
639 drawBitIcon(studio, icon->id, x, y, fg);
640
641 x += Size;
642 color++;
643 }
644}
645
646struct Sprite* getSpriteEditor(Studio* studio)
647{
648 return studio->banks.sprite[studio->bank.index.sprites];
649}
650#endif
651
652const StudioConfig* studio_config(Studio* studio)
653{
654 return &studio->config->data;
655}
656
657const StudioConfig* getConfig(Studio* studio)
658{
659 return studio_config(studio);
660}
661
662struct Start* getStartScreen(Studio* studio)
663{
664 return studio->start;
665}
666
667#if defined (TIC80_PRO)
668
669static void drawBankIcon(Studio* studio, s32 x, s32 y)
670{
671 tic_mem* tic = studio->tic;
672
673 tic_rect rect = {x, y, TIC_FONT_WIDTH, TIC_FONT_HEIGHT};
674
675 bool over = false;
676 EditorMode mode = 0;
677
678 for(s32 i = 0; i < COUNT_OF(BankModes); i++)
679 if(BankModes[i] == studio->mode)
680 {
681 mode = i;
682 break;
683 }
684
685 if(checkMousePos(studio, &rect))
686 {
687 setCursor(studio, tic_cursor_hand);
688
689 over = true;
690
691 showTooltip(studio, "SWITCH BANK");
692
693 if(checkMouseClick(studio, &rect, tic_mouse_left))
694 studio->bank.show = !studio->bank.show;
695 }
696
697 if(studio->bank.show)
698 {
699 drawBitIcon(studio, tic_icon_bank, x, y, tic_color_red);
700
701 enum{Size = TOOLBAR_SIZE};
702
703 for(s32 i = 0; i < TIC_EDITOR_BANKS; i++)
704 {
705 tic_rect rect = {x + 2 + (i+1)*Size, 0, Size, Size};
706
707 bool over = false;
708 if(checkMousePos(studio, &rect))
709 {
710 setCursor(studio, tic_cursor_hand);
711 over = true;
712
713 if(checkMouseClick(studio, &rect, tic_mouse_left))
714 {
715 if(studio->bank.chained)
716 memset(studio->bank.indexes, i, sizeof studio->bank.indexes);
717 else studio->bank.indexes[mode] = i;
718 }
719 }
720
721 if(i == studio->bank.indexes[mode])
722 tic_api_rect(tic, rect.x, rect.y, rect.w, rect.h, tic_color_red);
723
724 tic_api_print(tic, (char[]){'0' + i, '\0'}, rect.x+1, rect.y+1,
725 i == studio->bank.indexes[mode]
726 ? tic_color_white
727 : over
728 ? tic_color_red
729 : tic_color_light_grey,
730 false, 1, false);
731
732 }
733
734 {
735 tic_rect rect = {x + 4 + (TIC_EDITOR_BANKS+1)*Size, 0, Size, Size};
736
737 bool over = false;
738
739 if(checkMousePos(studio, &rect))
740 {
741 setCursor(studio, tic_cursor_hand);
742
743 over = true;
744
745 if(checkMouseClick(studio, &rect, tic_mouse_left))
746 {
747 studio->bank.chained = !studio->bank.chained;
748
749 if(studio->bank.chained)
750 memset(studio->bank.indexes, studio->bank.indexes[mode], sizeof studio->bank.indexes);
751 }
752 }
753
754 drawBitIcon(studio, tic_icon_pin, rect.x, rect.y, studio->bank.chained ? tic_color_red : over ? tic_color_grey : tic_color_light_grey);
755 }
756 }
757 else
758 {
759 drawBitIcon(studio, tic_icon_bank, x, y, over ? tic_color_red : tic_color_light_grey);
760 }
761}
762
763#endif
764
765static inline s32 lerp(s32 a, s32 b, float d)
766{
767 return (b - a) * d + a;
768}
769
770static inline float bounceOut(float x)
771{
772 const float n1 = 7.5625;
773 const float d1 = 2.75;
774
775 if (x < 1 / d1) return n1 * x * x;
776 else if (x < 2 / d1) return n1 * (x -= 1.5 / d1) * x + 0.75;
777 else if (x < 2.5 / d1) return n1 * (x -= 2.25 / d1) * x + 0.9375;
778 else return n1 * (x -= 2.625 / d1) * x + 0.984375;
779}
780
781static inline float animEffect(AnimEffect effect, float x)
782{
783 const float c1 = 1.70158;
784 const float c2 = c1 * 1.525;
785 const float c3 = c1 + 1;
786 const float c4 = (2 * M_PI) / 3;
787 const float c5 = (2 * M_PI) / 4.5;
788
789 switch(effect)
790 {
791 case AnimLinear:
792 return x;
793
794 case AnimEaseIn:
795 return x * x;
796
797 case AnimEaseOut:
798 return 1 - (1 - x) * (1 - x);
799
800 case AnimEaseInOut:
801 return x < 0.5 ? 2 * x * x : 1 - pow(-2 * x + 2, 2) / 2;
802
803 case AnimEaseInCubic:
804 return x * x * x;
805
806 case AnimEaseOutCubic:
807 return 1 - pow(1 - x, 3);
808
809 case AnimEaseInOutCubic:
810 return x < 0.5 ? 4 * x * x * x : 1 - pow(-2 * x + 2, 3) / 2;
811
812 case AnimEaseInQuart:
813 return x * x * x * x;
814
815 case AnimEaseOutQuart:
816 return 1 - pow(1 - x, 4);
817
818 case AnimEaseInOutQuart:
819 return x < 0.5 ? 8 * x * x * x * x : 1 - pow(-2 * x + 2, 4) / 2;
820
821 case AnimEaseInQuint:
822 return x * x * x * x * x;
823
824 case AnimEaseOutQuint:
825 return 1 - pow(1 - x, 5);
826
827 case AnimEaseInOutQuint:
828 return x < 0.5 ? 16 * x * x * x * x * x : 1 - pow(-2 * x + 2, 5) / 2;
829
830 case AnimEaseInSine:
831 return 1 - cos((x * M_PI) / 2);
832
833 case AnimEaseOutSine:
834 return sin((x * M_PI) / 2);
835
836 case AnimEaseInOutSine:
837 return -(cos(M_PI * x) - 1) / 2;
838
839 case AnimEaseInExpo:
840 return x == 0 ? 0 : pow(2, 10 * x - 10);
841
842 case AnimEaseOutExpo:
843 return x == 1 ? 1 : 1 - pow(2, -10 * x);
844
845 case AnimEaseInOutExpo:
846 return x == 0
847 ? 0
848 : x == 1
849 ? 1
850 : x < 0.5
851 ? pow(2, 20 * x - 10) / 2
852 : (2 - pow(2, -20 * x + 10)) / 2;
853
854 case AnimEaseInCirc:
855 return 1 - sqrt(1 - pow(x, 2));
856
857 case AnimEaseOutCirc:
858 return sqrt(1 - pow(x - 1, 2));
859
860 case AnimEaseInOutCirc:
861 return x < 0.5
862 ? (1 - sqrt(1 - pow(2 * x, 2))) / 2
863 : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2;
864
865 case AnimEaseInBack:
866 return c3 * x * x * x - c1 * x * x;
867
868 case AnimEaseOutBack:
869 return 1 + c3 * pow(x - 1, 3) + c1 * pow(x - 1, 2);
870
871 case AnimEaseInOutBack:
872 return x < 0.5
873 ? (pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2
874 : (pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
875
876 case AnimEaseInElastic:
877 return x == 0
878 ? 0
879 : x == 1
880 ? 1
881 : -pow(2, 10 * x - 10) * sin((x * 10 - 10.75) * c4);
882
883 case AnimEaseOutElastic:
884 return x == 0
885 ? 0
886 : x == 1
887 ? 1
888 : pow(2, -10 * x) * sin((x * 10 - 0.75) * c4) + 1;
889
890 case AnimEaseInOutElastic:
891 return x == 0
892 ? 0
893 : x == 1
894 ? 1
895 : x < 0.5
896 ? -(pow(2, 20 * x - 10) * sin((20 * x - 11.125) * c5)) / 2
897 : (pow(2, -20 * x + 10) * sin((20 * x - 11.125) * c5)) / 2 + 1;
898
899 case AnimEaseInBounce:
900 return 1 - bounceOut(1 - x);
901
902 case AnimEaseOutBounce:
903 return bounceOut(x);
904
905 case AnimEaseInOutBounce:
906 return x < 0.5
907 ? (1 - bounceOut(1 - 2 * x)) / 2
908 : (1 + bounceOut(2 * x - 1)) / 2;
909 }
910
911 return x;
912}
913
914static void animTick(Movie* movie)
915{
916 for(Anim* it = movie->items, *end = it + movie->count; it != end; ++it)
917 *it->value = lerp(it->start, it->end, animEffect(it->effect, (float)movie->tick / it->time));
918}
919
920void processAnim(Movie* movie, void* data)
921{
922 animTick(movie);
923
924 if(movie->tick == movie->time)
925 movie->done(data);
926
927 movie->tick++;
928}
929
930Movie* resetMovie(Movie* movie)
931{
932 movie->tick = 0;
933 animTick(movie);
934
935 return movie;
936}
937
938#if defined(BUILD_EDITORS)
939
940static void drawPopup(Studio* studio)
941{
942 if(studio->anim.movie != &studio->anim.idle)
943 {
944 enum{Width = TIC80_WIDTH, Height = TIC_FONT_HEIGHT + 1};
945
946 tic_api_rect(studio->tic, 0, studio->anim.pos.popup, Width, Height, tic_color_red);
947 tic_api_print(studio->tic, studio->popup.message,
948 (s32)(Width - strlen(studio->popup.message) * TIC_FONT_WIDTH)/2,
949 studio->anim.pos.popup + 1, tic_color_white, true, 1, false);
950
951 // render popup message
952 {
953 tic_mem* tic = studio->tic;
954 const tic_bank* bank = &getConfig(studio)->cart->bank0;
955 u32* dst = tic->product.screen + TIC80_MARGIN_LEFT + TIC80_MARGIN_TOP * TIC80_FULLWIDTH;
956
957 for(s32 i = 0, y = 0; y < (Height + studio->anim.pos.popup); y++, dst += TIC80_MARGIN_RIGHT + TIC80_MARGIN_LEFT)
958 for(s32 x = 0; x < Width; x++)
959 *dst++ = tic_rgba(&bank->palette.vbank0.colors[tic_tool_peek4(tic->ram->vram.screen.data, i++)]);
960 }
961 }
962}
963
964void drawToolbar(Studio* studio, tic_mem* tic, bool bg)
965{
966 if(bg)
967 tic_api_rect(tic, 0, 0, TIC80_WIDTH, TOOLBAR_SIZE, tic_color_white);
968
969 enum {Size = 7};
970
971 static const u8 Icons[] = {tic_icon_code, tic_icon_sprite, tic_icon_map, tic_icon_sfx, tic_icon_music};
972 static const char* Tips[] = {"CODE EDITOR [f1]", "SPRITE EDITOR [f2]", "MAP EDITOR [f3]", "SFX EDITOR [f4]", "MUSIC EDITOR [f5]",};
973
974 s32 mode = -1;
975
976 for(s32 i = 0; i < COUNT_OF(Modes); i++)
977 {
978 tic_rect rect = {i * Size, 0, Size, Size};
979
980 bool over = false;
981
982 if(checkMousePos(studio, &rect))
983 {
984 setCursor(studio, tic_cursor_hand);
985
986 over = true;
987
988 showTooltip(studio, Tips[i]);
989
990 if(checkMouseClick(studio, &rect, tic_mouse_left))
991 studio->toolbarMode = Modes[i];
992 }
993
994 if(getStudioMode(studio) == Modes[i]) mode = i;
995
996 if (mode == i)
997 {
998 drawBitIcon(studio, tic_icon_tab, i * Size, 0, tic_color_grey);
999 drawBitIcon(studio, Icons[i], i * Size, 1, tic_color_black);
1000 }
1001
1002 drawBitIcon(studio, Icons[i], i * Size, 0, mode == i ? tic_color_white : (over ? tic_color_grey : tic_color_light_grey));
1003 }
1004
1005 if(mode >= 0) drawExtrabar(studio, tic);
1006
1007 static const char* Names[] =
1008 {
1009 "CODE EDITOR",
1010 "SPRITE EDITOR",
1011 "MAP EDITOR",
1012 "SFX EDITOR",
1013 "MUSIC EDITOR",
1014 };
1015
1016#if defined (TIC80_PRO)
1017 enum {TextOffset = (COUNT_OF(Modes) + 2) * Size - 2};
1018 if(mode >= 1)
1019 drawBankIcon(studio, COUNT_OF(Modes) * Size + 2, 0);
1020#else
1021 enum {TextOffset = (COUNT_OF(Modes) + 1) * Size};
1022#endif
1023
1024 if(mode == 0 || (mode >= 1 && !studio->bank.show))
1025 {
1026 if(strlen(studio->tooltip.text))
1027 {
1028 tic_api_print(tic, studio->tooltip.text, TextOffset, 1, tic_color_dark_grey, false, 1, false);
1029 }
1030 else
1031 {
1032 tic_api_print(tic, Names[mode], TextOffset, 1, tic_color_grey, false, 1, false);
1033 }
1034 }
1035}
1036
1037void setStudioEvent(Studio* studio, StudioEvent event)
1038{
1039 switch(studio->mode)
1040 {
1041 case TIC_CODE_MODE:
1042 {
1043 Code* code = studio->code;
1044 code->event(code, event);
1045 }
1046 break;
1047 case TIC_SPRITE_MODE:
1048 {
1049 Sprite* sprite = studio->banks.sprite[studio->bank.index.sprites];
1050 sprite->event(sprite, event);
1051 }
1052 break;
1053 case TIC_MAP_MODE:
1054 {
1055 Map* map = studio->banks.map[studio->bank.index.map];
1056 map->event(map, event);
1057 }
1058 break;
1059 case TIC_SFX_MODE:
1060 {
1061 Sfx* sfx = studio->banks.sfx[studio->bank.index.sfx];
1062 sfx->event(sfx, event);
1063 }
1064 break;
1065 case TIC_MUSIC_MODE:
1066 {
1067 Music* music = studio->banks.music[studio->bank.index.music];
1068 music->event(music, event);
1069 }
1070 break;
1071 default: break;
1072 }
1073}
1074
1075ClipboardEvent getClipboardEvent(Studio* studio)
1076{
1077 tic_mem* tic = studio->tic;
1078
1079 bool shift = tic_api_key(tic, tic_key_shift);
1080 bool ctrl = tic_api_key(tic, tic_key_ctrl);
1081
1082 if(ctrl)
1083 {
1084 if(keyWasPressed(studio, tic_key_insert) || keyWasPressed(studio, tic_key_c)) return TIC_CLIPBOARD_COPY;
1085 else if(keyWasPressed(studio, tic_key_x)) return TIC_CLIPBOARD_CUT;
1086 else if(keyWasPressed(studio, tic_key_v)) return TIC_CLIPBOARD_PASTE;
1087 }
1088 else if(shift)
1089 {
1090 if(keyWasPressed(studio, tic_key_delete)) return TIC_CLIPBOARD_CUT;
1091 else if(keyWasPressed(studio, tic_key_insert)) return TIC_CLIPBOARD_PASTE;
1092 }
1093
1094 return TIC_CLIPBOARD_NONE;
1095}
1096
1097static void showPopupMessage(Studio* studio, const char* text)
1098{
1099 memset(studio->popup.message, '\0', sizeof studio->popup.message);
1100 strncpy(studio->popup.message, text, sizeof(studio->popup.message) - 1);
1101
1102 for(char* c = studio->popup.message; c < studio->popup.message + sizeof studio->popup.message; c++)
1103 if(*c) *c = toupper(*c);
1104
1105 studio->anim.movie = resetMovie(&studio->anim.show);
1106}
1107#endif
1108
1109static void exitConfirm(Studio* studio, bool yes, void* data)
1110{
1111 studio->alive = yes;
1112}
1113
1114void studio_exit(Studio* studio)
1115{
1116#if defined(BUILD_EDITORS)
1117 if(studio->mode != TIC_START_MODE && studioCartChanged(studio))
1118 {
1119 static const char* Rows[] =
1120 {
1121 "WARNING!",
1122 "You have unsaved changes",
1123 "Do you really want to exit?",
1124 };
1125
1126 confirmDialog(studio, Rows, COUNT_OF(Rows), exitConfirm, NULL);
1127 }
1128 else
1129#endif
1130 exitConfirm(studio, true, NULL);
1131}
1132
1133void exitStudio(Studio* studio)
1134{
1135 studio_exit(studio);
1136}
1137
1138void drawBitIcon(Studio* studio, s32 id, s32 x, s32 y, u8 color)
1139{
1140 tic_mem* tic = studio->tic;
1141
1142 const tic_tile* tile = &getConfig(studio)->cart->bank0.tiles.data[id];
1143
1144 for(s32 i = 0, sx = x, ex = sx + TIC_SPRITESIZE; i != TIC_SPRITESIZE * TIC_SPRITESIZE; ++i, ++x)
1145 {
1146 if(x == ex)
1147 {
1148 x = sx;
1149 y++;
1150 }
1151
1152 if(tic_tool_peek4(tile, i))
1153 tic_api_pix(tic, x, y, color, false);
1154 }
1155}
1156
1157static void initRunMode(Studio* studio)
1158{
1159 initRun(studio->run,
1160#if defined(BUILD_EDITORS)
1161 studio->console,
1162#else
1163 NULL,
1164#endif
1165 studio->fs, studio);
1166}
1167
1168#if defined(BUILD_EDITORS)
1169static void initWorldMap(Studio* studio)
1170{
1171 initWorld(studio->world, studio, studio->banks.map[studio->bank.index.map]);
1172}
1173
1174static void initSurfMode(Studio* studio)
1175{
1176 initSurf(studio->surf, studio, studio->console);
1177}
1178
1179void gotoSurf(Studio* studio)
1180{
1181 initSurfMode(studio);
1182 setStudioMode(studio, TIC_SURF_MODE);
1183}
1184
1185void gotoCode(Studio* studio)
1186{
1187 setStudioMode(studio, TIC_CODE_MODE);
1188}
1189
1190#endif
1191
1192void setStudioMode(Studio* studio, EditorMode mode)
1193{
1194 if(mode != studio->mode)
1195 {
1196 EditorMode prev = studio->mode;
1197
1198 if(prev == TIC_RUN_MODE)
1199 tic_core_pause(studio->tic);
1200
1201 if(mode != TIC_RUN_MODE)
1202 tic_api_reset(studio->tic);
1203
1204 switch (prev)
1205 {
1206 case TIC_START_MODE:
1207 case TIC_CONSOLE_MODE:
1208 case TIC_MENU_MODE:
1209 break;
1210 default: studio->prevMode = prev; break;
1211 }
1212
1213#if defined(BUILD_EDITORS)
1214 switch(mode)
1215 {
1216 case TIC_RUN_MODE: initRunMode(studio); break;
1217 case TIC_CONSOLE_MODE:
1218 if (prev == TIC_SURF_MODE)
1219 studio->console->done(studio->console);
1220 break;
1221 case TIC_WORLD_MODE: initWorldMap(studio); break;
1222 case TIC_SURF_MODE: studio->surf->resume(studio->surf); break;
1223 default: break;
1224 }
1225
1226 studio->mode = mode;
1227#else
1228 switch (mode)
1229 {
1230 case TIC_START_MODE:
1231 case TIC_MENU_MODE:
1232 studio->mode = mode;
1233 break;
1234 default:
1235 studio->mode = TIC_RUN_MODE;
1236 }
1237#endif
1238 }
1239
1240#if defined(BUILD_EDITORS)
1241 else if(mode == TIC_MUSIC_MODE)
1242 {
1243 Music* music = studio->banks.music[studio->bank.index.music];
1244 music->tab = (music->tab + 1) % MUSIC_TAB_COUNT;
1245 }
1246#endif
1247}
1248
1249EditorMode getStudioMode(Studio* studio)
1250{
1251 return studio->mode;
1252}
1253
1254#if defined(BUILD_EDITORS)
1255static void changeStudioMode(Studio* studio, s32 dir)
1256{
1257 for(size_t i = 0; i < COUNT_OF(Modes); i++)
1258 {
1259 if(studio->mode == Modes[i])
1260 {
1261 setStudioMode(studio, Modes[(i+dir+ COUNT_OF(Modes)) % COUNT_OF(Modes)]);
1262 return;
1263 }
1264 }
1265}
1266#endif
1267
1268void resumeGame(Studio* studio)
1269{
1270 tic_core_resume(studio->tic);
1271 studio->mode = TIC_RUN_MODE;
1272}
1273
1274static inline bool pointInRect(const tic_point* pt, const tic_rect* rect)
1275{
1276 return (pt->x >= rect->x)
1277 && (pt->x < (rect->x + rect->w))
1278 && (pt->y >= rect->y)
1279 && (pt->y < (rect->y + rect->h));
1280}
1281
1282bool checkMousePos(Studio* studio, const tic_rect* rect)
1283{
1284 tic_point pos = tic_api_mouse(studio->tic);
1285 return pointInRect(&pos, rect);
1286}
1287
1288bool checkMouseClick(Studio* studio, const tic_rect* rect, tic_mouse_btn button)
1289{
1290 MouseState* state = &studio->mouse.state[button];
1291
1292 bool value = state->click
1293 && pointInRect(&state->start, rect)
1294 && pointInRect(&state->end, rect);
1295
1296 if(value) state->click = false;
1297
1298 return value;
1299}
1300
1301bool checkMouseDblClick(Studio* studio, const tic_rect* rect, tic_mouse_btn button)
1302{
1303 MouseState* state = &studio->mouse.state[button];
1304
1305 bool value = state->dbl.click
1306 && pointInRect(&state->start, rect)
1307 && pointInRect(&state->end, rect);
1308
1309 if(value) state->dbl.click = false;
1310
1311 return value;
1312}
1313
1314bool checkMouseDown(Studio* studio, const tic_rect* rect, tic_mouse_btn button)
1315{
1316 MouseState* state = &studio->mouse.state[button];
1317
1318 return state->down && pointInRect(&state->start, rect);
1319}
1320
1321void setCursor(Studio* studio, tic_cursor id)
1322{
1323 tic_mem* tic = studio->tic;
1324
1325 VBANK(tic, 0)
1326 {
1327 tic->ram->vram.vars.cursor.sprite = id;
1328 }
1329}
1330
1331#if defined(BUILD_EDITORS)
1332
1333typedef struct
1334{
1335 Studio* studio;
1336 ConfirmCallback callback;
1337 void* data;
1338} ConfirmData;
1339
1340static void confirmHandler(bool yes, void* data)
1341{
1342 ConfirmData* confirmData = data;
1343 SCOPE(free(confirmData))
1344 {
1345 Studio* studio = confirmData->studio;
1346
1347 if(studio->menuMode == TIC_RUN_MODE)
1348 {
1349 tic_core_resume(studio->tic);
1350 studio->mode = TIC_RUN_MODE;
1351 }
1352 else setStudioMode(studio, studio->menuMode);
1353
1354 confirmData->callback(studio, yes, confirmData->data);
1355 }
1356}
1357
1358static void confirmNo(void* data, s32 pos)
1359{
1360 confirmHandler(false, data);
1361}
1362
1363static void confirmYes(void* data, s32 pos)
1364{
1365 confirmHandler(true, data);
1366}
1367
1368void confirmDialog(Studio* studio, const char** text, s32 rows, ConfirmCallback callback, void* data)
1369{
1370 if(studio->mode != TIC_MENU_MODE)
1371 {
1372 studio->menuMode = studio->mode;
1373 studio->mode = TIC_MENU_MODE;
1374
1375 static const MenuItem Answers[] =
1376 {
1377 {"", NULL},
1378 {"NO", confirmNo},
1379 {"YES", confirmYes},
1380 };
1381
1382 s32 count = rows + COUNT_OF(Answers);
1383 MenuItem* items = malloc(sizeof items[0] * count);
1384 SCOPE(free(items))
1385 {
1386 for(s32 i = 0; i != rows; ++i)
1387 items[i] = (MenuItem){text[i], NULL};
1388
1389 memcpy(items + rows, Answers, sizeof Answers);
1390
1391 studio_menu_init(studio->menu, items, count, count - 2, 0,
1392 NULL, MOVE((ConfirmData){studio, callback, data}));
1393
1394 playSystemSfx(studio, 0);
1395 }
1396 }
1397}
1398
1399static void resetBanks(Studio* studio)
1400{
1401 memset(studio->bank.indexes, 0, sizeof studio->bank.indexes);
1402}
1403
1404static void initModules(Studio* studio)
1405{
1406 tic_mem* tic = studio->tic;
1407
1408 resetBanks(studio);
1409
1410 initCode(studio->code, studio);
1411
1412 for(s32 i = 0; i < TIC_EDITOR_BANKS; i++)
1413 {
1414 initSprite(studio->banks.sprite[i], studio, &tic->cart.banks[i].tiles);
1415 initMap(studio->banks.map[i], studio, &tic->cart.banks[i].map);
1416 initSfx(studio->banks.sfx[i], studio, &tic->cart.banks[i].sfx);
1417 initMusic(studio->banks.music[i], studio, &tic->cart.banks[i].music);
1418 }
1419
1420 initWorldMap(studio);
1421}
1422
1423static void updateHash(Studio* studio)
1424{
1425 md5(&studio->tic->cart, sizeof(tic_cartridge), studio->cart.hash.data);
1426}
1427
1428static void updateMDate(Studio* studio)
1429{
1430 studio->cart.mdate = fs_date(studio->console->rom.path);
1431}
1432#endif
1433
1434static void updateTitle(Studio* studio)
1435{
1436 char name[TICNAME_MAX] = TIC_TITLE;
1437
1438#if defined(BUILD_EDITORS)
1439 if(strlen(studio->console->rom.name))
1440 snprintf(name, TICNAME_MAX, "%s [%s]", TIC_TITLE, studio->console->rom.name);
1441#endif
1442
1443 tic_sys_title(name);
1444}
1445
1446#if defined(BUILD_EDITORS)
1447tic_cartridge* loadPngCart(png_buffer buffer)
1448{
1449 png_buffer zip = png_decode(buffer);
1450
1451 if (zip.size)
1452 {
1453 png_buffer buf = png_create(sizeof(tic_cartridge));
1454
1455 buf.size = tic_tool_unzip(buf.data, buf.size, zip.data, zip.size);
1456 free(zip.data);
1457
1458 if(buf.size)
1459 {
1460 tic_cartridge* cart = malloc(sizeof(tic_cartridge));
1461 tic_cart_load(cart, buf.data, buf.size);
1462 free(buf.data);
1463
1464 return cart;
1465 }
1466 }
1467
1468 return NULL;
1469}
1470
1471void studioRomSaved(Studio* studio)
1472{
1473 updateTitle(studio);
1474 updateHash(studio);
1475 updateMDate(studio);
1476}
1477
1478void studioRomLoaded(Studio* studio)
1479{
1480 initModules(studio);
1481
1482 updateTitle(studio);
1483 updateHash(studio);
1484 updateMDate(studio);
1485}
1486
1487bool studioCartChanged(Studio* studio)
1488{
1489 CartHash hash;
1490 md5(&studio->tic->cart, sizeof(tic_cartridge), hash.data);
1491
1492 return memcmp(hash.data, studio->cart.hash.data, sizeof(CartHash)) != 0;
1493}
1494#endif
1495
1496void runGame(Studio* studio)
1497{
1498#if defined(BUILD_EDITORS)
1499 if(studio->console->args.keepcmd
1500 && studio->console->commands.count
1501 && studio->console->commands.current >= studio->console->commands.count)
1502 {
1503 studio->console->commands.current = 0;
1504 setStudioMode(studio, TIC_CONSOLE_MODE);
1505 }
1506 else
1507#endif
1508 {
1509 tic_api_reset(studio->tic);
1510
1511 if(studio->mode == TIC_RUN_MODE)
1512 {
1513 initRunMode(studio);
1514 return;
1515 }
1516
1517 setStudioMode(studio, TIC_RUN_MODE);
1518
1519#if defined(BUILD_EDITORS)
1520 if(studio->mode == TIC_SURF_MODE)
1521 studio->prevMode = TIC_SURF_MODE;
1522#endif
1523 }
1524}
1525
1526#if defined(BUILD_EDITORS)
1527static void saveProject(Studio* studio)
1528{
1529 CartSaveResult rom = studio->console->save(studio->console);
1530
1531 if(rom == CART_SAVE_OK)
1532 {
1533 char buffer[STUDIO_TEXT_BUFFER_WIDTH];
1534 char str_saved[] = " saved :)";
1535
1536 s32 name_len = (s32)strlen(studio->console->rom.name);
1537 if (name_len + strlen(str_saved) > sizeof(buffer)){
1538 char subbuf[sizeof(buffer) - sizeof(str_saved) - 5];
1539 memset(subbuf, '\0', sizeof subbuf);
1540 strncpy(subbuf, studio->console->rom.name, sizeof subbuf-1);
1541
1542 snprintf(buffer, sizeof buffer, "%s[...]%s", subbuf, str_saved);
1543 }
1544 else
1545 {
1546 snprintf(buffer, sizeof buffer, "%s%s", studio->console->rom.name, str_saved);
1547 }
1548
1549 showPopupMessage(studio, buffer);
1550 }
1551 else if(rom == CART_SAVE_MISSING_NAME) showPopupMessage(studio, "error: missing cart name :(");
1552 else showPopupMessage(studio, "error: file not saved :(");
1553}
1554
1555static void screen2buffer(u32* buffer, const u32* pixels, const tic_rect* rect)
1556{
1557 pixels += rect->y * TIC80_FULLWIDTH;
1558
1559 for(s32 i = 0; i < rect->h; i++)
1560 {
1561 memcpy(buffer, pixels + rect->x, rect->w * sizeof(pixels[0]));
1562 pixels += TIC80_FULLWIDTH;
1563 buffer += rect->w;
1564 }
1565}
1566
1567static void setCoverImage(Studio* studio)
1568{
1569 tic_mem* tic = studio->tic;
1570
1571 if(studio->mode == TIC_RUN_MODE)
1572 {
1573 tic_api_sync(tic, tic_sync_screen, 0, true);
1574 showPopupMessage(studio, "cover image saved :)");
1575 }
1576}
1577
1578static void stopVideoRecord(Studio* studio, const char* name)
1579{
1580 if(studio->video.buffer)
1581 {
1582 {
1583 s32 size = 0;
1584 u8* data = malloc(FRAME_SIZE * studio->video.frame);
1585 s32 i = 0;
1586 char filename[TICNAME_MAX];
1587
1588 gif_write_animation(data, &size, TIC80_FULLWIDTH, TIC80_FULLHEIGHT, (const u8*)studio->video.buffer, studio->video.frame, TIC80_FRAMERATE, getConfig(studio)->gifScale);
1589
1590 // Find an available filename to save.
1591 do
1592 {
1593 snprintf(filename, sizeof filename, name, ++i);
1594 }
1595 while(tic_fs_exists(studio->fs, filename));
1596
1597 // Now that it has found an available filename, save it.
1598 if(tic_fs_save(studio->fs, filename, data, size, true))
1599 {
1600 char msg[TICNAME_MAX];
1601 sprintf(msg, "%s saved :)", filename);
1602 showPopupMessage(studio, msg);
1603
1604 tic_sys_open_path(tic_fs_path(studio->fs, filename));
1605 }
1606 else showPopupMessage(studio, "error: file not saved :(");
1607 }
1608
1609 free(studio->video.buffer);
1610 studio->video.buffer = NULL;
1611 }
1612
1613 studio->video.record = false;
1614}
1615
1616static void startVideoRecord(Studio* studio)
1617{
1618 if(studio->video.record)
1619 {
1620 stopVideoRecord(studio, VideoGif);
1621 }
1622 else
1623 {
1624 studio->video.frames = getConfig(studio)->gifLength * TIC80_FRAMERATE;
1625 studio->video.buffer = malloc(FRAME_SIZE * studio->video.frames);
1626
1627 if(studio->video.buffer)
1628 {
1629 studio->video.record = true;
1630 studio->video.frame = 0;
1631 }
1632 }
1633}
1634
1635static void takeScreenshot(Studio* studio)
1636{
1637 studio->video.frames = 1;
1638 studio->video.buffer = malloc(FRAME_SIZE);
1639
1640 if(studio->video.buffer)
1641 {
1642 studio->video.record = true;
1643 studio->video.frame = 0;
1644 }
1645}
1646#endif
1647
1648static inline bool keyWasPressedOnce(Studio* studio, s32 key)
1649{
1650 tic_mem* tic = studio->tic;
1651
1652 return tic_api_keyp(tic, key, -1, -1);
1653}
1654
1655static void gotoFullscreen(Studio* studio)
1656{
1657 tic_sys_fullscreen_set(studio->config->data.options.fullscreen = !tic_sys_fullscreen_get());
1658}
1659
1660#if defined(CRT_SHADER_SUPPORT)
1661static void switchCrtMonitor(Studio* studio)
1662{
1663 studio->config->data.options.crt = !studio->config->data.options.crt;
1664}
1665#endif
1666
1667#if defined(TIC80_PRO)
1668
1669static void switchBank(Studio* studio, s32 bank)
1670{
1671 for(s32 i = 0; i < COUNT_OF(BankModes); i++)
1672 if(BankModes[i] == studio->mode)
1673 {
1674 if(studio->bank.chained)
1675 memset(studio->bank.indexes, bank, sizeof studio->bank.indexes);
1676 else studio->bank.indexes[i] = bank;
1677 break;
1678 }
1679}
1680
1681#endif
1682
1683void gotoMenu(Studio* studio)
1684{
1685 if(studio->mode != TIC_MENU_MODE)
1686 {
1687 tic_core_pause(studio->tic);
1688 tic_api_reset(studio->tic);
1689 studio->mode = TIC_MENU_MODE;
1690 }
1691
1692 studio->mainmenu = studio_mainmenu_init(studio->menu, studio->config);
1693}
1694
1695static void processShortcuts(Studio* studio)
1696{
1697 tic_mem* tic = studio->tic;
1698
1699 if(studio->mode == TIC_START_MODE) return;
1700
1701#if defined(BUILD_EDITORS)
1702 if(!studio->console->active) return;
1703#endif
1704
1705 if(studio_mainmenu_keyboard(studio->mainmenu)) return;
1706
1707 bool alt = tic_api_key(tic, tic_key_alt);
1708 bool ctrl = tic_api_key(tic, tic_key_ctrl);
1709
1710#if defined(CRT_SHADER_SUPPORT)
1711 if(keyWasPressedOnce(studio, tic_key_f6)) switchCrtMonitor(studio);
1712#endif
1713
1714 if(alt)
1715 {
1716 if (keyWasPressedOnce(studio, tic_key_return)) gotoFullscreen(studio);
1717#if defined(BUILD_EDITORS)
1718 else if(studio->mode != TIC_RUN_MODE)
1719 {
1720 if(keyWasPressedOnce(studio, tic_key_grave)) setStudioMode(studio, TIC_CONSOLE_MODE);
1721 else if(keyWasPressedOnce(studio, tic_key_1)) setStudioMode(studio, TIC_CODE_MODE);
1722 else if(keyWasPressedOnce(studio, tic_key_2)) setStudioMode(studio, TIC_SPRITE_MODE);
1723 else if(keyWasPressedOnce(studio, tic_key_3)) setStudioMode(studio, TIC_MAP_MODE);
1724 else if(keyWasPressedOnce(studio, tic_key_4)) setStudioMode(studio, TIC_SFX_MODE);
1725 else if(keyWasPressedOnce(studio, tic_key_5)) setStudioMode(studio, TIC_MUSIC_MODE);
1726 }
1727#endif
1728 }
1729 else if(ctrl)
1730 {
1731 if(keyWasPressedOnce(studio, tic_key_q)) studio_exit(studio);
1732#if defined(BUILD_EDITORS)
1733 else if(keyWasPressedOnce(studio, tic_key_pageup)) changeStudioMode(studio, -1);
1734 else if(keyWasPressedOnce(studio, tic_key_pagedown)) changeStudioMode(studio, +1);
1735 else if(keyWasPressedOnce(studio, tic_key_return)) runGame(studio);
1736 else if(keyWasPressedOnce(studio, tic_key_r)) runGame(studio);
1737 else if(keyWasPressedOnce(studio, tic_key_s)) saveProject(studio);
1738#endif
1739
1740#if defined(TIC80_PRO)
1741
1742 else
1743 for(s32 bank = 0; bank < TIC_BANKS; bank++)
1744 if(keyWasPressedOnce(studio, tic_key_0 + bank))
1745 switchBank(studio, bank);
1746
1747#endif
1748
1749 }
1750 else
1751 {
1752 if (keyWasPressedOnce(studio, tic_key_f11)) gotoFullscreen(studio);
1753#if defined(BUILD_EDITORS)
1754 else if(keyWasPressedOnce(studio, tic_key_escape))
1755 {
1756 switch(studio->mode)
1757 {
1758 case TIC_MENU_MODE:
1759 getConfig(studio)->options.devmode
1760 ? setStudioMode(studio, studio->prevMode)
1761 : studio_menu_back(studio->menu);
1762 break;
1763 case TIC_RUN_MODE:
1764 getConfig(studio)->options.devmode
1765 ? setStudioMode(studio, studio->prevMode)
1766 : gotoMenu(studio);
1767 break;
1768 case TIC_CONSOLE_MODE:
1769 setStudioMode(studio, TIC_CODE_MODE);
1770 break;
1771 case TIC_CODE_MODE:
1772 if(studio->code->mode != TEXT_EDIT_MODE)
1773 {
1774 studio->code->escape(studio->code);
1775 return;
1776 }
1777 default:
1778 setStudioMode(studio, TIC_CONSOLE_MODE);
1779 }
1780 }
1781 else if(keyWasPressedOnce(studio, tic_key_f8)) takeScreenshot(studio);
1782 else if(keyWasPressedOnce(studio, tic_key_f9)) startVideoRecord(studio);
1783 else if(studio->mode == TIC_RUN_MODE && keyWasPressedOnce(studio, tic_key_f7))
1784 setCoverImage(studio);
1785
1786 if(getConfig(studio)->options.devmode || studio->mode != TIC_RUN_MODE)
1787 {
1788 if(keyWasPressedOnce(studio, tic_key_f1)) setStudioMode(studio, TIC_CODE_MODE);
1789 else if(keyWasPressedOnce(studio, tic_key_f2)) setStudioMode(studio, TIC_SPRITE_MODE);
1790 else if(keyWasPressedOnce(studio, tic_key_f3)) setStudioMode(studio, TIC_MAP_MODE);
1791 else if(keyWasPressedOnce(studio, tic_key_f4)) setStudioMode(studio, TIC_SFX_MODE);
1792 else if(keyWasPressedOnce(studio, tic_key_f5)) setStudioMode(studio, TIC_MUSIC_MODE);
1793 }
1794#else
1795 else if(keyWasPressedOnce(studio, tic_key_escape))
1796 {
1797 switch(studio->mode)
1798 {
1799 case TIC_MENU_MODE: studio_menu_back(studio->menu); break;
1800 case TIC_RUN_MODE: gotoMenu(studio); break;
1801 }
1802 }
1803#endif
1804 }
1805}
1806
1807#if defined(BUILD_EDITORS)
1808static void reloadConfirm(Studio* studio, bool yes, void* data)
1809{
1810 if(yes)
1811 studio->console->updateProject(studio->console);
1812 else
1813 updateMDate(studio);
1814}
1815
1816static void checkChanges(Studio* studio)
1817{
1818 switch(studio->mode)
1819 {
1820 case TIC_START_MODE:
1821 break;
1822 default:
1823 {
1824 Console* console = studio->console;
1825
1826 u64 date = fs_date(console->rom.path);
1827
1828 if(studio->cart.mdate && date > studio->cart.mdate)
1829 {
1830 if(studioCartChanged(studio))
1831 {
1832 static const char* Rows[] =
1833 {
1834 "WARNING!",
1835 "The cart has changed!",
1836 "Do you want to reload it?"
1837 };
1838
1839 confirmDialog(studio, Rows, COUNT_OF(Rows), reloadConfirm, NULL);
1840 }
1841 else console->updateProject(console);
1842 }
1843 }
1844 }
1845}
1846
1847static void drawBitIconRaw(Studio* studio, u32* frame, s32 sx, s32 sy, s32 id, tic_color color)
1848{
1849 const tic_bank* bank = &getConfig(studio)->cart->bank0;
1850
1851 u32 *dst = frame + sx + sy * TIC80_FULLWIDTH;
1852 for(s32 src = 0; src != TIC_SPRITESIZE * TIC_SPRITESIZE; dst += TIC80_FULLWIDTH - TIC_SPRITESIZE)
1853 for(s32 i = 0; i != TIC_SPRITESIZE; ++i, ++dst)
1854 if(tic_tool_peek4(&bank->tiles.data[id].data, src++))
1855 *dst = tic_rgba(&bank->palette.vbank0.colors[color]);
1856}
1857
1858static void drawRecordLabel(Studio* studio, u32* frame, s32 sx, s32 sy)
1859{
1860 drawBitIconRaw(studio, frame, sx, sy, tic_icon_rec, tic_color_red);
1861 drawBitIconRaw(studio, frame, sx + TIC_SPRITESIZE, sy, tic_icon_rec2, tic_color_red);
1862}
1863
1864static bool isRecordFrame(Studio* studio)
1865{
1866 return studio->video.record;
1867}
1868
1869static void recordFrame(Studio* studio, u32* pixels)
1870{
1871 if(studio->video.record)
1872 {
1873 if(studio->video.frame < studio->video.frames)
1874 {
1875 tic_rect rect = {0, 0, TIC80_FULLWIDTH, TIC80_FULLHEIGHT};
1876 screen2buffer(studio->video.buffer + (TIC80_FULLWIDTH*TIC80_FULLHEIGHT) * studio->video.frame, pixels, &rect);
1877
1878 if(studio->video.frame % TIC80_FRAMERATE < TIC80_FRAMERATE / 2)
1879 {
1880 drawRecordLabel(studio, pixels, TIC80_WIDTH-24, 8);
1881 }
1882
1883 studio->video.frame++;
1884
1885 }
1886 else
1887 {
1888 stopVideoRecord(studio, studio->video.frame == 1 ? ScreenGif : VideoGif);
1889 }
1890 }
1891}
1892
1893#endif
1894
1895static void renderStudio(Studio* studio)
1896{
1897 tic_mem* tic = studio->tic;
1898
1899#if defined(BUILD_EDITORS)
1900 showTooltip(studio, "");
1901#endif
1902
1903 {
1904 const tic_sfx* sfx = NULL;
1905 const tic_music* music = NULL;
1906
1907 switch(studio->mode)
1908 {
1909 case TIC_RUN_MODE:
1910 sfx = &studio->tic->ram->sfx;
1911 music = &studio->tic->ram->music;
1912 break;
1913 case TIC_START_MODE:
1914 case TIC_MENU_MODE:
1915 case TIC_SURF_MODE:
1916 sfx = &studio->config->cart->bank0.sfx;
1917 music = &studio->config->cart->bank0.music;
1918 break;
1919 default:
1920#if defined(BUILD_EDITORS)
1921 sfx = getSfxSrc(studio);
1922 music = getMusicSrc(studio);
1923#endif
1924 break;
1925 }
1926
1927 sfx2ram(tic->ram, sfx);
1928 music2ram(tic->ram, music);
1929
1930 // restore mapping
1931 studio->tic->ram->mapping = getConfig(studio)->options.mapping;
1932
1933 tic_core_tick_start(tic);
1934 }
1935
1936 // SECURITY: It's important that this comes before `tick` and not after
1937 // to prevent misbehaving cartridges from having an opportunity to
1938 // tamper with the keyboard input.
1939 processShortcuts(studio);
1940
1941 // clear screen for all the modes except the Run mode
1942 if(studio->mode != TIC_RUN_MODE)
1943 {
1944 VBANK(tic, 1)
1945 {
1946 tic_api_cls(tic, 0);
1947 }
1948 }
1949
1950 switch(studio->mode)
1951 {
1952 case TIC_START_MODE: studio->start->tick(studio->start); break;
1953 case TIC_RUN_MODE: studio->run->tick(studio->run); break;
1954 case TIC_MENU_MODE: studio_menu_tick(studio->menu); break;
1955
1956#if defined(BUILD_EDITORS)
1957 case TIC_CONSOLE_MODE: studio->console->tick(studio->console); break;
1958 case TIC_CODE_MODE:
1959 {
1960 Code* code = studio->code;
1961 code->tick(code);
1962 }
1963 break;
1964 case TIC_SPRITE_MODE:
1965 {
1966 Sprite* sprite = studio->banks.sprite[studio->bank.index.sprites];
1967 sprite->tick(sprite);
1968 }
1969 break;
1970 case TIC_MAP_MODE:
1971 {
1972 Map* map = studio->banks.map[studio->bank.index.map];
1973 map->tick(map);
1974 }
1975 break;
1976 case TIC_SFX_MODE:
1977 {
1978 Sfx* sfx = studio->banks.sfx[studio->bank.index.sfx];
1979 sfx->tick(sfx);
1980 }
1981 break;
1982 case TIC_MUSIC_MODE:
1983 {
1984 Music* music = studio->banks.music[studio->bank.index.music];
1985 music->tick(music);
1986 }
1987 break;
1988
1989 case TIC_WORLD_MODE: studio->world->tick(studio->world); break;
1990 case TIC_SURF_MODE: studio->surf->tick(studio->surf); break;
1991#endif
1992 default: break;
1993 }
1994
1995 tic_core_tick_end(tic);
1996
1997 switch(studio->mode)
1998 {
1999 case TIC_RUN_MODE: break;
2000 case TIC_SURF_MODE:
2001 case TIC_MENU_MODE:
2002 tic->input.data = -1;
2003 break;
2004 default:
2005 tic->input.data = -1;
2006 tic->input.gamepad = 0;
2007 }
2008}
2009
2010static void updateSystemFont(Studio* studio)
2011{
2012 tic_mem* tic = studio->tic;
2013
2014 studio->systemFont = (tic_font)
2015 {
2016 .regular =
2017 {
2018 { 0 },
2019 {
2020 {
2021 .width = TIC_FONT_WIDTH,
2022 .height = TIC_FONT_HEIGHT,
2023 },
2024 },
2025 },
2026 .alt =
2027 {
2028 { 0 },
2029 {
2030 {
2031 .width = TIC_ALTFONT_WIDTH,
2032 .height = TIC_FONT_HEIGHT,
2033 },
2034 },
2035 }
2036 };
2037
2038 u8* dst = (u8*)&studio->systemFont;
2039
2040 for(s32 i = 0; i < TIC_FONT_CHARS * 2; i++)
2041 for(s32 y = 0; y < TIC_SPRITESIZE; y++)
2042 for(s32 x = 0; x < TIC_SPRITESIZE; x++)
2043 if(tic_tool_peek4(&studio->config->cart->bank0.sprites.data[i], TIC_SPRITESIZE*y + x))
2044 dst[i*BITS_IN_BYTE+y] |= 1 << x;
2045
2046 tic->ram->font = studio->systemFont;
2047}
2048
2049void studioConfigChanged(Studio* studio)
2050{
2051#if defined(BUILD_EDITORS)
2052 Code* code = studio->code;
2053 if(code->update)
2054 code->update(code);
2055#endif
2056
2057 updateSystemFont(studio);
2058 tic_sys_update_config();
2059}
2060
2061static void processMouseStates(Studio* studio)
2062{
2063 for(s32 i = 0; i < COUNT_OF(studio->mouse.state); i++)
2064 studio->mouse.state[i].dbl.click = studio->mouse.state[i].click = false;
2065
2066 tic_mem* tic = studio->tic;
2067
2068 tic->ram->vram.vars.cursor.sprite = tic_cursor_arrow;
2069 tic->ram->vram.vars.cursor.system = true;
2070
2071 for(s32 i = 0; i < COUNT_OF(studio->mouse.state); i++)
2072 {
2073 MouseState* state = &studio->mouse.state[i];
2074
2075 s32 down = tic->ram->input.mouse.btns & (1 << i);
2076
2077 if(!state->down && down)
2078 {
2079 state->down = true;
2080 state->start = tic_api_mouse(tic);
2081 }
2082 else if(state->down && !down)
2083 {
2084 state->end = tic_api_mouse(tic);
2085
2086 state->click = true;
2087 state->down = false;
2088
2089 state->dbl.click = state->dbl.ticks - state->dbl.start < 1000 / TIC80_FRAMERATE;
2090 state->dbl.start = state->dbl.ticks;
2091 }
2092
2093 state->dbl.ticks++;
2094 }
2095}
2096
2097static void blitCursor(Studio* studio)
2098{
2099 tic_mem* tic = studio->tic;
2100 tic80_mouse* m = &tic->ram->input.mouse;
2101
2102 if(tic->input.mouse && !m->relative && m->x < TIC80_FULLWIDTH && m->y < TIC80_FULLHEIGHT)
2103 {
2104 s32 sprite = tic->ram->vram.vars.cursor.sprite;
2105 const tic_bank* bank = &tic->cart.bank0;
2106
2107 tic_point hot = {0};
2108
2109 if(tic->ram->vram.vars.cursor.system)
2110 {
2111 bank = &getConfig(studio)->cart->bank0;
2112 hot = (tic_point[])
2113 {
2114 {0, 0},
2115 {3, 0},
2116 {2, 3},
2117 }[sprite];
2118 }
2119 else if(sprite == 0) return;
2120
2121 const tic_palette* pal = &bank->palette.vbank0;
2122 const tic_tile* tile = &bank->sprites.data[sprite];
2123
2124 tic_point s = {m->x - hot.x, m->y - hot.y};
2125 u32* dst = tic->product.screen + TIC80_FULLWIDTH * s.y + s.x;
2126
2127 for(s32 y = s.y, endy = MIN(y + TIC_SPRITESIZE, TIC80_FULLHEIGHT), i = 0; y != endy; ++y, dst += TIC80_FULLWIDTH - TIC_SPRITESIZE)
2128 for(s32 x = s.x, endx = x + TIC_SPRITESIZE; x != endx; ++x, ++i, ++dst)
2129 if(x < TIC80_FULLWIDTH)
2130 {
2131 u8 c = tic_tool_peek4(tile->data, i);
2132 if(c)
2133 *dst = tic_rgba(&pal->colors[c]);
2134 }
2135 }
2136}
2137
2138tic_mem* getMemory(Studio* studio)
2139{
2140 return studio->tic;
2141}
2142
2143const tic_mem* studio_mem(Studio* studio)
2144{
2145 return getMemory(studio);
2146}
2147
2148void studio_tick(Studio* studio, tic80_input input)
2149{
2150 tic_mem* tic = studio->tic;
2151 tic->ram->input = input;
2152
2153#if defined(BUILD_EDITORS)
2154 processAnim(studio->anim.movie, studio);
2155 checkChanges(studio);
2156 tic_net_start(studio->net);
2157#endif
2158
2159 if(studio->toolbarMode)
2160 {
2161 setStudioMode(studio, studio->toolbarMode);
2162 studio->toolbarMode = 0;
2163 }
2164
2165 processMouseStates(studio);
2166 renderStudio(studio);
2167
2168 {
2169#if defined(BUILD_EDITORS)
2170 Sprite* sprite = studio->banks.sprite[studio->bank.index.sprites];
2171 Map* map = studio->banks.map[studio->bank.index.map];
2172#endif
2173
2174 tic_blit_callback callback[TIC_MODES_COUNT] =
2175 {
2176 [TIC_MENU_MODE] = {studio_menu_anim_scanline, NULL, NULL, studio->menu},
2177
2178#if defined(BUILD_EDITORS)
2179 [TIC_SPRITE_MODE] = {sprite->scanline, NULL, NULL, sprite},
2180 [TIC_MAP_MODE] = {map->scanline, NULL, NULL, map},
2181 [TIC_WORLD_MODE] = {studio->world->scanline, NULL, NULL, studio->world},
2182 [TIC_SURF_MODE] = {studio->surf->scanline, NULL, NULL, studio->surf},
2183#endif
2184 };
2185
2186 if(studio->mode != TIC_RUN_MODE)
2187 {
2188 memcpy(tic->ram->vram.palette.data, getConfig(studio)->cart->bank0.palette.vbank0.data, sizeof(tic_palette));
2189 tic->ram->font = studio->systemFont;
2190 }
2191
2192 callback[studio->mode].data
2193 ? tic_core_blit_ex(tic, callback[studio->mode])
2194 : tic_core_blit(tic);
2195
2196 blitCursor(studio);
2197
2198#if defined(BUILD_EDITORS)
2199 if(isRecordFrame(studio))
2200 recordFrame(studio, tic->product.screen);
2201
2202 drawPopup(studio);
2203#endif
2204 }
2205
2206#if defined(BUILD_EDITORS)
2207 tic_net_end(studio->net);
2208#endif
2209}
2210
2211void studio_sound(Studio* studio)
2212{
2213 tic_mem* tic = studio->tic;
2214 tic_core_synth_sound(tic);
2215
2216 s32 volume = getConfig(studio)->options.volume;
2217
2218 if(volume != MAX_VOLUME)
2219 {
2220 s32 size = tic->product.samples.count;
2221 for(s16* it = tic->product.samples.buffer, *end = it + size; it != end; ++it)
2222 *it = *it * volume / MAX_VOLUME;
2223 }
2224}
2225
2226#if defined(BUILD_EDITORS)
2227static void onStudioLoadConfirmed(Studio* studio, bool yes, void* data)
2228{
2229 if(yes)
2230 {
2231 const char* file = data;
2232 showPopupMessage(studio, studio->console->loadCart(studio->console, file)
2233 ? "cart successfully loaded :)"
2234 : "error: cart not loaded :(");
2235 }
2236}
2237
2238void confirmLoadCart(Studio* studio, ConfirmCallback callback, void* data)
2239{
2240 static const char* Warning[] =
2241 {
2242 "WARNING!",
2243 "You have unsaved changes",
2244 "Do you really want to load cart?",
2245 };
2246
2247 confirmDialog(studio, Warning, COUNT_OF(Warning), callback, data);
2248}
2249
2250#endif
2251
2252void studio_load(Studio* studio, const char* file)
2253{
2254#if defined(BUILD_EDITORS)
2255 studioCartChanged(studio)
2256 ? confirmLoadCart(studio, onStudioLoadConfirmed, (void*)file)
2257 : onStudioLoadConfirmed(studio, true, (void*)file);
2258#endif
2259}
2260
2261void exitGame(Studio* studio)
2262{
2263 if(studio->prevMode == TIC_SURF_MODE)
2264 {
2265 setStudioMode(studio, TIC_SURF_MODE);
2266 }
2267 else
2268 {
2269 setStudioMode(studio, TIC_CONSOLE_MODE);
2270 }
2271}
2272
2273void studio_delete(Studio* studio)
2274{
2275 {
2276#if defined(BUILD_EDITORS)
2277 for(s32 i = 0; i < TIC_EDITOR_BANKS; i++)
2278 {
2279 freeSprite (studio->banks.sprite[i]);
2280 freeMap (studio->banks.map[i]);
2281 freeSfx (studio->banks.sfx[i]);
2282 freeMusic (studio->banks.music[i]);
2283 }
2284
2285 freeCode (studio->code);
2286 freeConsole (studio->console);
2287 freeWorld (studio->world);
2288 freeSurf (studio->surf);
2289
2290 FREE(studio->anim.show.items);
2291 FREE(studio->anim.hide.items);
2292
2293#endif
2294
2295 freeStart (studio->start);
2296 freeRun (studio->run);
2297 freeConfig (studio->config);
2298
2299 studio_mainmenu_free(studio->mainmenu);
2300 studio_menu_free(studio->menu);
2301 }
2302
2303 tic_core_close(studio->tic);
2304
2305#if defined(BUILD_EDITORS)
2306 tic_net_close(studio->net);
2307#endif
2308
2309 free(studio->fs);
2310 free(studio);
2311}
2312
2313static StartArgs parseArgs(s32 argc, char **argv)
2314{
2315 static const char *const usage[] =
2316 {
2317 "tic80 [cart] [options]",
2318 NULL,
2319 };
2320
2321 StartArgs args = {.volume = -1};
2322
2323 struct argparse_option options[] =
2324 {
2325 OPT_HELP(),
2326#define CMD_PARAMS_DEF(name, ctype, type, post, help) OPT_##type('\0', #name, &args.name, help),
2327 CMD_PARAMS_LIST(CMD_PARAMS_DEF)
2328#undef CMD_PARAMS_DEF
2329 OPT_END(),
2330 };
2331
2332 struct argparse argparse;
2333 argparse_init(&argparse, options, usage, 0);
2334 argparse_describe(&argparse, "\n" TIC_NAME " startup options:", NULL);
2335 argc = argparse_parse(&argparse, argc, (const char**)argv);
2336
2337 if(argc == 1)
2338 args.cart = argv[0];
2339
2340 return args;
2341}
2342
2343#if defined(BUILD_EDITORS)
2344
2345static void setIdle(void* data)
2346{
2347 Studio* studio = data;
2348 studio->anim.movie = resetMovie(&studio->anim.idle);
2349}
2350
2351static void setPopupWait(void* data)
2352{
2353 Studio* studio = data;
2354 studio->anim.movie = resetMovie(&studio->anim.wait);
2355}
2356
2357static void setPopupHide(void* data)
2358{
2359 Studio* studio = data;
2360 studio->anim.movie = resetMovie(&studio->anim.hide);
2361}
2362
2363#endif
2364
2365bool studio_alive(Studio* studio)
2366{
2367 return studio->alive;
2368}
2369
2370Studio* studio_create(s32 argc, char **argv, s32 samplerate, tic80_pixel_color_format format, const char* folder)
2371{
2372 setbuf(stdout, NULL);
2373
2374 StartArgs args = parseArgs(argc, argv);
2375
2376 if (args.version)
2377 {
2378 printf("%s\n", TIC_VERSION);
2379 exit(0);
2380 }
2381
2382 Studio* studio = NEW(Studio);
2383 *studio = (Studio)
2384 {
2385 .mode = TIC_START_MODE,
2386 .prevMode = TIC_CODE_MODE,
2387
2388#if defined(BUILD_EDITORS)
2389 .menuMode = TIC_CONSOLE_MODE,
2390
2391 .bank =
2392 {
2393 .chained = true,
2394 },
2395
2396 .anim =
2397 {
2398 .pos =
2399 {
2400 .popup = -TOOLBAR_SIZE,
2401 },
2402 .idle = {.done = emptyDone,}
2403 },
2404
2405 .popup =
2406 {
2407 .message = "\0",
2408 },
2409
2410 .tooltip =
2411 {
2412 .text = "\0",
2413 },
2414
2415 .samplerate = samplerate,
2416 .net = tic_net_create("http://"TIC_HOST),
2417#endif
2418 .tic = tic_core_create(samplerate, format),
2419 };
2420
2421
2422#if defined(BUILD_EDITORS)
2423
2424#endif
2425
2426 {
2427 const char *path = args.fs ? args.fs : folder;
2428
2429 if (fs_exists(path))
2430 {
2431 studio->fs = tic_fs_create(path,
2432#if defined(BUILD_EDITORS)
2433 studio->net
2434#else
2435 NULL
2436#endif
2437 );
2438 }
2439 else
2440 {
2441 fprintf(stderr, "error: folder `%s` doesn't exist\n", path);
2442 exit(1);
2443 }
2444 }
2445
2446 {
2447
2448#if defined(BUILD_EDITORS)
2449 for(s32 i = 0; i < TIC_EDITOR_BANKS; i++)
2450 {
2451 studio->banks.sprite[i] = calloc(1, sizeof(Sprite));
2452 studio->banks.map[i] = calloc(1, sizeof(Map));
2453 studio->banks.sfx[i] = calloc(1, sizeof(Sfx));
2454 studio->banks.music[i] = calloc(1, sizeof(Music));
2455 }
2456
2457 studio->code = calloc(1, sizeof(Code));
2458 studio->console = calloc(1, sizeof(Console));
2459 studio->world = calloc(1, sizeof(World));
2460 studio->surf = calloc(1, sizeof(Surf));
2461
2462 studio->anim.show = (Movie)MOVIE_DEF(STUDIO_ANIM_TIME, setPopupWait,
2463 {
2464 {-TOOLBAR_SIZE, 0, STUDIO_ANIM_TIME, &studio->anim.pos.popup, AnimEaseIn},
2465 });
2466
2467 studio->anim.wait = (Movie){.time = TIC80_FRAMERATE * 2, .done = setPopupHide};
2468 studio->anim.hide = (Movie)MOVIE_DEF(STUDIO_ANIM_TIME, setIdle,
2469 {
2470 {0, -TOOLBAR_SIZE, STUDIO_ANIM_TIME, &studio->anim.pos.popup, AnimEaseIn},
2471 });
2472
2473 studio->anim.movie = resetMovie(&studio->anim.idle);
2474#endif
2475
2476 studio->start = calloc(1, sizeof(Start));
2477 studio->run = calloc(1, sizeof(Run));
2478 studio->menu = studio_menu_create(studio);
2479 studio->config = calloc(1, sizeof(Config));
2480 }
2481
2482 tic_fs_makedir(studio->fs, TIC_LOCAL);
2483 tic_fs_makedir(studio->fs, TIC_LOCAL_VERSION);
2484
2485 initConfig(studio->config, studio, studio->fs);
2486 initStart(studio->start, studio, args.cart);
2487 initRunMode(studio);
2488
2489#if defined(BUILD_EDITORS)
2490 initConsole(studio->console, studio, studio->fs, studio->net, studio->config, args);
2491 initSurfMode(studio);
2492 initModules(studio);
2493#endif
2494
2495 if(args.scale)
2496 studio->config->data.uiScale = args.scale;
2497
2498 if(args.volume >= 0)
2499 studio->config->data.options.volume = args.volume & 0x0f;
2500
2501#if defined(CRT_SHADER_SUPPORT)
2502 studio->config->data.options.crt |= args.crt;
2503#endif
2504
2505 studio->config->data.options.fullscreen |= args.fullscreen;
2506 studio->config->data.options.vsync |= args.vsync;
2507 studio->config->data.soft |= args.soft;
2508 studio->config->data.cli |= args.cli;
2509
2510 if(args.cli)
2511 args.skip = true;
2512
2513 if(args.skip)
2514 setStudioMode(studio, TIC_CONSOLE_MODE);
2515
2516 return studio;
2517}
2518