-
Notifications
You must be signed in to change notification settings - Fork 16
/
custom.bas
2210 lines (1914 loc) · 73.4 KB
/
custom.bas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'OHRRPGCE CUSTOM - Main module
'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and the OHRRPGCE Developers
'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability.
'
#include "config.bi"
#include "datetime.bi" 'for date serials
#include "string.bi" 'for date serials
#include "udts.bi"
#include "const.bi"
#include "allmodex.bi"
#include "cmdline.bi"
#include "common.bi"
#include "loading.bi"
#include "customsubs.bi"
#include "flexmenu.bi"
#include "slices.bi"
#include "cglobals.bi"
#include "uiconst.bi"
#include "scrconst.bi"
#include "sliceedit.bi"
#include "reloadedit.bi"
#include "editedit.bi"
#include "os.bi"
#include "distribmenu.bi"
#include "thingbrowser.bi"
#include "plankmenu.bi"
#include "editorkit.bi"
#include "custom.bi"
'''' Local function and type declarations
' Stores information about a previous or ongoing Custom editing session
TYPE SessionInfo
workingdir as string 'The directory containing this session's files
partial_rpg as bool '__danger.tmp exists: was in process of unlumping or deleting lumps
fresh_danger_tmp as bool '__danger.tmp exists and isn't stale: may still be running
info_file_exists as bool 'session_info.txt.tmp exists. If not, everything in this UDT below this point is unknown.
pid as integer 'Process ID or 0
running as bool 'That process is still running
sourcerpg as string 'May be blank
'The following are represented as native FB DateSerials, not Unix mtimes. 0.0 means N/A
sourcerpg_old_mtime as double 'mtime of the sourcerpg when was opened/last saved by that copy of Custom
sourcerpg_current_mtime as double 'mtime of the sourcerpg right now, as seen by us
session_start_time as double 'When the game was last unlumped/saved (or if none, when Custom was launched)
last_lump_mtime as double 'mtime of the most recently modified lump
END TYPE
DECLARE FUNCTION newRPGfile (templatefile as string, newrpg as string) as bool
DECLARE SUB setup_workingdir ()
DECLARE SUB check_for_crashed_workingdirs ()
DECLARE FUNCTION check_a_crashed_workingdir (sessinfo as SessionInfo) as bool
DECLARE FUNCTION empty_workingdir (workdir as string) as bool
DECLARE FUNCTION handle_dirty_workingdir (sessinfo as SessionInfo) as bool
DECLARE FUNCTION check_ok_to_open (filename as string) as bool
DECLARE FUNCTION get_previous_session_info (workdir as string) as SessionInfo
DECLARE SUB secret_menu ()
DECLARE SUB condition_test_menu ()
DECLARE SUB quad_transforms_menu ()
DECLARE SUB rotozoom_tests ()
DECLARE SUB text_test_menu ()
DECLARE SUB new_graphics_tests ()
DECLARE SUB plankmenu_cursor_move_tests
DECLARE SUB HTTP_demo()
DECLARE SUB CreateProcess_tests()
DECLARE SUB mouse_and_window_tests()
DECLARE SUB cleanup_and_terminate (show_quit_msg as bool = YES, retval as integer = 0)
DECLARE SUB import_scripts_and_terminate (scriptfile as string)
DECLARE SUB export_translations_and_terminate (translationfile as string)
DECLARE SUB prompt_for_password()
DECLARE SUB prompt_for_save_and_quit()
DECLARE SUB choose_rpg_to_create_or_load (rpg_browse_default as string)
DECLARE SUB main_editor_menu()
DECLARE SUB gfx_editor_menu()
'=================================== Globals ==================================
DIM activepalette as integer = -1
'The following are set from commandline options
DIM auto_distrib as string 'Which distribution option to package automatically
DIM option_nowait as bool 'Currently only used when importing scripts from the commandline: don't wait
DIM option_hsflags as string 'Used when importing scripts from the commandline: extra args to pass
DIM export_translations_to as string
DIM editing_a_game as bool
DIM last_active_seconds as double
DIM channel_to_Game as IPCChannel = NULL
#IFNDEF NO_TEST_GAME
DIM Game_process as ProcessHandle = 0
#ENDIF
'Should we delete workingdir when quitting normally?
'False if relumping workingdir failed.
DIM cleanup_workingdir_on_exit as bool = YES
'Affects show/fatalerror: have we started editing (ie. finished upgrades and other startup)?
'If not, we should cleanup working.tmp instead of preserving it
DIM cleanup_workingdir_on_error as bool = YES
'======================== Setup directories & debug log =======================
' This is almost identical to startup code in Game; please don't unnecessarily diverge.
orig_dir = CURDIR
'Note: debug log messages go in CURDIR until log_dir set below
'Processes the -appdir commandline flag
set_app_dir
'temporarily set current directory, will be changed to game directory later if writable
'(This is where new .rpg files go by default)
'(This change in working directory is done only by Custom, not Game)
IF diriswriteable(app_dir) THEN
'When CUSTOM is installed read-write, work in CUSTOM's folder
CHDIR app_dir
ELSE
'If CUSTOM is installed read-only, use your Documents dir as the default
'(On Mac, this will also happen due to Gatekeeper Path Randomization (aka App Translocation))
CHDIR get_documents_dir()
END IF
#IFDEF __FB_ANDROID__
'Prevent log_dir from being changed to the .rpg directory
'(But why? If it's on external storage, that seems like great place to put it)
log_dir = orig_dir & SLASH
overrode_log_dir = YES
#ELSE
log_dir = CURDIR & SLASH
#ENDIF
'Once log_dir is set, can create debug log.
start_new_debug "Starting OHRRPGCE Custom"
debuginfo DATE & " " & TIME
debuginfo long_version & build_info
debuginfo "sysinfo: " & get_system_info()
debuginfo "exepath: " & EXEPATH & ", exe: " & COMMAND(0)
debuginfo "orig_dir: " & orig_dir
debuginfo "curdir: " & CURDIR
settings_dir = get_settings_dir()
documents_dir = get_documents_dir() 'may depend on app_dir
debuginfo "documents_dir: " & documents_dir
'FIXME: why do we use different temp dirs in game and custom?
'Plus, tmpdir is shared between all running copies of Custom, which could cause problems.
tmpdir = settings_dir & SLASH
IF NOT isdir(tmpdir) THEN fatalerror "Unable to create temp directory " & tmpdir
set_global_config_file
debuginfo "config: " & global_config_file
flush_gfx_config_settings
'========================== Process commandline flags =========================
'Read the default backend from config first so that --gfx overrides it.
'And if it's missing, default to SDL/SDL2: currently only these support non-320x200
prefer_gfx_backend "sdl"
prefer_gfx_backend "sdl2"
DIM backend as string = read_config_str("gfx.backend")
IF LEN(backend) THEN prefer_gfx_backend backend
REDIM cmdline_args() as string
' This can modify log_dir and restart the debug log
processcommandline cmdline_args(), @gamecustom_setoption, orig_dir & SLASH & "ohrrpgce_arguments.txt"
'======================= Initialise backends/graphics =========================
load_gfx_defaults 'Loads master(), uilook(), boxlook(), current_font()
set_resolution read_config_int("gfx.resolution_w", 480), read_config_int("gfx.resolution_h", 320)
IF overrode_default_zoom = NO THEN
set_scale_factor read_config_int("gfx.zoom", 2), YES
END IF
setmodex
unlock_resolution 320, 200 'Minimum window size
setwindowtitle "O.H.R.RPG.C.E"
showmousecursor
setupmusic
'Cleanups/recovers any working.tmp for any crashed copies of Custom; requires graphics up and running
check_for_crashed_workingdirs
'This also calls write_session_info
setup_workingdir
'=============================== Select a game ================================
DIM scriptfile as string
DIM rpg_browse_default as string
FOR i as integer = 0 TO UBOUND(cmdline_args)
DIM arg as string
arg = simplify_path(absolute_with_orig_path(cmdline_args(i)))
DIM extn as string = LCASE(justextension(arg))
IF (extn = "hs" OR extn = "hss" OR extn = "txt") ANDALSO isfile(arg) THEN
scriptfile = arg
CONTINUE FOR
ELSEIF extn = "rpg" ANDALSO isfile(arg) THEN
sourcerpg = arg
ELSEIF isdir(arg) THEN
IF isfile(arg + SLASH + "archinym.lmp") THEN 'ok, accept it
sourcerpg = trim_trailing_slashes(arg)
ELSE
rpg_browse_default = arg
END IF
ELSE
visible_debug !"File not found/invalid option:\n" & cmdline_args(i)
END IF
NEXT
IF sourcerpg = "" THEN
scriptfile = ""
'Show the title menu. Sets sourcerpg
choose_rpg_to_create_or_load(rpg_browse_default)
END IF
sourcerpg = absolute_path(sourcerpg)
IF check_ok_to_open(sourcerpg) = NO THEN
cleanup_and_terminate NO
END IF
'================= Setup game-specific directories & debug log ================
' Set up game_fname, prefsdir, and game_config_file variables
set_game_config_globals sourcerpg
flush_gfx_config_settings
write_session_info
DIM dir_to_change_into as string = trimfilename(sourcerpg)
end_debug
IF dir_to_change_into <> "" ANDALSO diriswriteable(dir_to_change_into) THEN
CHDIR dir_to_change_into
IF overrode_log_dir = NO THEN log_dir = dir_to_change_into & SLASH
END IF
'otherwise, keep current directory as it was (FIXME: ideally would now be the same as in Game)
'Final log_dir set, no more need to remember.
remember_debug_messages = NO
start_new_debug "Loading a game"
debuginfo DATE & " " & TIME
debuginfo "curdir: " & CURDIR
debuginfo "tmpdir: " & tmpdir
debuginfo "settings_dir: " & settings_dir
debuginfo "config: " & global_config_file
'============================= Unlump, Upgrade, Load ==========================
'Start counting edit_time from now
active_seconds = 0.
idle_time_threshold = large(read_config_int("idle_time", 30), 1)
'For getdisplayname
copylump sourcerpg, "archinym.lmp", workingdir, YES
debuginfo "Editing game " & sourcerpg & " (" & getdisplayname(" ") & ")"
setwindowtitle "O.H.R.RPG.C.E - " + trimpath(sourcerpg)
'--set game according to the archinym
copylump sourcerpg, "archinym.lmp", workingdir, YES
copylump sourcerpg, "*.gen", workingdir, YES
DIM archinym as string
archinym = readarchinym(workingdir, sourcerpg)
game = workingdir + SLASH + archinym
copylump sourcerpg, archinym + ".gen", workingdir
xbload game + ".gen", gen(), "general data is missing: RPG file appears to be corrupt"
IF gen(genVersion) > CURRENT_RPG_VERSION THEN
debug "genVersion = " & gen(genVersion)
future_rpg_warning
END IF
prompt_for_password
clearpage vpage
textcolor uilook(uiText), 0
printstr "UNLUMPING DATA: please wait.", pMenuX, pMenuY, vpage
setvispage vpage, NO
touchfile workingdir + SLASH + "__danger.tmp"
IF isdir(sourcerpg) THEN
'work on an unlumped RPG file. Don't take hidden files
'Convert to lowercase while copying (only needed for ancient unlumped games)
copyfiles sourcerpg, workingdir, , YES
ELSE
unlump sourcerpg, workingdir + SLASH
END IF
safekill workingdir + SLASH + "__danger.tmp"
'Perform additional checks for future rpg files or corruption
rpg_sanity_checks
'upgrade obsolete RPG files
upgrade YES
set_music_volume 0.01 * gen(genMusicVolume)
set_global_sfx_volume 0.01 * gen(genSFXVolume)
'Unload any default graphics (from data/defaultgfx) that might have been cached, load palettes
sprite_empty_cache
palette16_reload_cache
'Load the game's palette, uicolors, font
activepalette = gen(genMasterPal)
load_master_and_uicol activepalette
setpal master()
clearpage dpage
clearpage vpage
xbload game + ".fnt", current_font(), "Font not loaded"
setfont current_font()
loadglobalstrings
getstatnames statnames()
load_special_tag_caches
load_script_triggers_and_names 'Also called in upgrade(), but be sure
IF scriptfile <> "" THEN import_scripts_and_terminate scriptfile
'Set by --export-trans
IF export_translations_to <> "" THEN export_translations_and_terminate export_translations_to
IF auto_distrib <> "" THEN
auto_export_distribs auto_distrib
cleanup_workingdir_on_exit = YES
cleanup_and_terminate NO
END IF
'Reset start of session to after upgrades (to see which lumps are edited)
write_session_info
'From here on, preserve working.tmp if something goes wrong
cleanup_workingdir_on_error = NO
'debuginfo "mem usage " & memory_usage_string()
IF isdir(CURDIR & SLASH & "import") THEN set_browse_default(CURDIR & SLASH & "import")
editing_a_game = YES
main_editor_menu
'Execution ends inside main_editor_menu
'=======================================================================
SUB main_editor_menu()
REDIM menu(20) as string
DIM menu_display(UBOUND(menu)) as string
menu(0) = "Edit Graphics"
menu(1) = "Edit Maps"
menu(2) = "Edit Heroes"
menu(3) = "Edit Enemies"
menu(4) = "Edit Attacks"
menu(5) = "Edit Battle Formations"
menu(6) = "Edit Items"
menu(7) = "Edit Shops"
menu(8) = "Edit Text Boxes"
menu(9) = "Edit Tag Names"
menu(10) = "Edit Menus"
menu(11) = "Edit Slice Collections"
menu(12) = "Edit Vehicles"
menu(13) = "Import Music"
menu(14) = "Import Sound Effects"
menu(15) = "Edit Global Text Strings"
menu(16) = "Edit General Game Settings"
menu(17) = "Script Management"
menu(18) = "Distribute Game"
#IFDEF NO_TEST_GAME
menu(19) = "Quit or Save"
REDIM PRESERVE menu(19)
#ELSE
menu(19) = "Test Game"
menu(20) = "Quit or Save"
#ENDIF
DIM selectst as SelectTypeState
DIM state as MenuState
state.last = UBOUND(menu)
state.autosize = YES
state.autosize_ignore_pixels = 36
setkeys YES
DO
setwait 55
setkeys YES
usemenu state
IF keyval(ccCancel) > 1 THEN
prompt_for_save_and_quit
END IF
IF keyval(scF1) > 1 THEN
show_help "main"
END IF
IF keyval(scF5) > 1 THEN 'Redundant, but for people with muscle memory
reimport_previous_scripts
END IF
IF select_by_typing(selectst) THEN
IF RIGHT(selectst.buffer, 4) = "spam" THEN
select_clear selectst
secret_menu
ELSE
select_on_word_boundary_excluding menu(), selectst, state, "edit"
END IF
END IF
IF enter_space_click(state) THEN
IF state.pt = 0 THEN gfx_editor_menu
IF state.pt = 1 THEN map_picker
IF state.pt = 2 THEN hero_editor_main
IF state.pt = 3 THEN enemy_editor_main
IF state.pt = 4 THEN attack_editor_main
IF state.pt = 5 THEN formation_editor
IF state.pt = 6 THEN item_editor
IF state.pt = 7 THEN shop_editor_main
IF state.pt = 8 THEN textbox_editor_main
IF state.pt = 9 THEN tags_menu
IF state.pt = 10 THEN menu_editor
IF state.pt = 11 THEN slice_editor SL_COLLECT_USERDEFINED
IF state.pt = 12 THEN vehicle_editor
IF state.pt = 13 THEN song_editor_main
IF state.pt = 14 THEN sfx_editor_main
IF state.pt = 15 THEN global_text_strings_editor
IF state.pt = 16 THEN general_data_editor
IF state.pt = 17 THEN script_management
IF state.pt = 18 THEN distribute_game_menu
#IFDEF NO_TEST_GAME
IF state.pt = 19 THEN prompt_for_save_and_quit
#ELSE
IF state.pt = 19 THEN spawn_game_menu(keyval(scShift) > 0, keyval(scCtrl) > 0)
IF state.pt = 20 THEN prompt_for_save_and_quit
#ENDIF
'--always resave .GEN and general.reld after any menu
'(I don't know whether saving GEN is necessary, but saving general.reld
'is just in case we forget wherever it should have been saved)
xbsave game + ".gen", gen(), 1000
write_general_reld()
END IF
clearpage dpage
highlight_menu_typing_selection menu(), menu_display(), selectst, state
standardmenu menu_display(), state, , , dpage
textcolor uilook(eduiNote), 0
printstr version_code, pInfoX, pInfoY - 18, dpage, , fontBuiltinPlain
printstr version_build & " In use: " & gfxbackend & "/" & musicbackend, pInfoX, pInfoY - 9, dpage, , fontBuiltinPlain
textcolor uilook(uiText), 0
printstr "Press F1 for help on any menu!", pInfoX, pInfoY, dpage, , fontBuiltinPlain
SWAP vpage, dpage
setvispage vpage
dowait
LOOP
END SUB
SUB gfx_editor_menu()
DIM menu(16) as string
DIM menu_display(UBOUND(menu)) as string
menu(0) = "Return to Main Menu"
menu(1) = "Edit Tilesets"
menu(2) = "Import/Export Tilesets"
menu(3) = "Draw Walkabout Graphics"
menu(4) = "Draw Hero Battle Graphics"
menu(5) = "Draw Small Enemy Graphics 34x34"
menu(6) = "Draw Medium Enemy Graphics 50x50"
menu(7) = "Draw Big Enemy Graphics 80x80"
menu(8) = "Draw Attacks"
menu(9) = "Draw Weapons"
menu(10) = "Draw Box Edges"
menu(11) = "Draw Portraits"
menu(12) = "Import/Export Backdrops"
menu(13) = "Change User-Interface Colors"
menu(14) = "Change Box Styles"
menu(15) = "Master Palettes"
menu(16) = "Edit Font"
DIM selectst as SelectTypeState
DIM state as MenuState
state.size = 24
state.last = UBOUND(menu)
setkeys YES
DO
setwait 55
setkeys YES
IF keyval(ccCancel) > 1 THEN
EXIT DO
END IF
IF keyval(scF1) > 1 THEN
show_help "gfxmain"
END IF
usemenu state
IF select_by_typing(selectst) THEN
select_on_word_boundary menu(), selectst, state
END IF
IF enter_space_click(state) THEN
IF state.pt = 0 THEN
EXIT DO
END IF
IF state.pt = 1 THEN tileset_editor
IF state.pt = 2 THEN import_export_tilesets
IF state.pt = 3 THEN spriteset_editor sprTypeWalkabout
IF state.pt = 4 THEN spriteset_editor sprTypeHero
IF state.pt = 5 THEN spriteset_editor sprTypeSmallEnemy
IF state.pt = 6 THEN spriteset_editor sprTypeMediumEnemy
IF state.pt = 7 THEN spriteset_editor sprTypeLargeEnemy
IF state.pt = 8 THEN spriteset_editor sprTypeAttack
IF state.pt = 9 THEN spriteset_editor sprTypeWeapon
IF state.pt = 10 THEN spriteset_editor sprTypeBoxBorder
IF state.pt = 11 THEN spriteset_editor sprTypePortrait
IF state.pt = 12 THEN backdrop_browser
IF state.pt = 13 THEN ui_color_editor(activepalette)
IF state.pt = 14 THEN ui_boxstyle_editor(activepalette)
IF state.pt = 15 THEN master_palette_menu
IF state.pt = 16 THEN font_editor current_font()
'--always resave the .GEN lump after any menu
xbsave game + ".gen", gen(), 1000
END IF
clearpage dpage
highlight_menu_typing_selection menu(), menu_display(), selectst, state
standardmenu menu_display(), state, , , dpage
SWAP vpage, dpage
setvispage vpage
dowait
LOOP
END SUB
'This sub sets the sourcerpg global
SUB choose_rpg_to_create_or_load (rpg_browse_default as string)
DIM state as MenuState
state.pt = 1
state.last = 2
state.size = 20
DIM root as Slice ptr
root = NewSliceOfType(slContainer)
SliceLoadFromFile root, finddatafile("choose_rpg.slice")
DIM chooserpg_menu(2) as string
chooserpg_menu(0) = "CREATE NEW GAME"
chooserpg_menu(1) = "LOAD EXISTING GAME"
chooserpg_menu(2) = "EXIT PROGRAM"
DIM opts as MenuOptions
opts.edged = YES
setkeys
DO
setwait 55
setkeys
IF keyval(ccCancel) > 1 THEN cleanup_and_terminate
IF keyval(scF1) > 1 THEN show_help "choose_rpg"
IF keyval(scF6) > 1 THEN slice_editor root, SL_COLLECT_EDITOR, "choose_rpg.slice"
DIM menusl as Slice ptr = LookupSliceSafe(SL_EDITOR_SPLASH_MENU, root)
usemenu state
IF enter_space_click(state) THEN
SELECT CASE state.pt
CASE 0
DIM path as string
path = inputfilename("Filename of New Game?", ".rpg", rpg_browse_default, "input_file_new_game", , NO)
IF path <> "" THEN
sourcerpg = path & ".rpg"
IF NOT newRPGfile(finddatafile("ohrrpgce.new"), sourcerpg) THEN cleanup_and_terminate
EXIT DO
END IF
CASE 1
sourcerpg = browse(browseRPG, rpg_browse_default, , "custom_browse_rpg")
IF sourcerpg <> "" THEN EXIT DO
CASE 2
cleanup_and_terminate
END SELECT
END IF
clearpage dpage
DrawSlice root, dpage
standardmenu chooserpg_menu(), state, menusl->ScreenX, menusl->ScreenY, dpage, opts
wrapprint short_version & " " & gfxbackend & "/" & musicbackend, 8, pBottom - 14, uilook(uiMenuItem), dpage
edgeprint "Press F1 for help on any menu!", 8, pBottom - 4, uilook(uiText), dpage
SWAP vpage, dpage
setvispage vpage
dowait
LOOP
DeleteSlice @root
END SUB
SUB prompt_for_save_and_quit()
xbsave game & ".gen", gen(), 1000
DIM quit_menu(3) as string
quit_menu(0) = "Continue editing"
quit_menu(1) = "Save changes and continue editing"
quit_menu(2) = "Save changes and quit"
quit_menu(3) = "Discard changes and quit"
setquitflag NO 'Stop firing esc's, if the user asked to quit the program
DIM quitnow as integer
quitnow = multichoice("", quit_menu(), 0, 0, "quit_and_save")
IF getquitflag() THEN '2nd quit request? Right away!
DIM basename as string = trimextension(sourcerpg)
DIM lumpfile as string
DIM i as integer = 0
DO
lumpfile = basename & ".rpg_" & i & ".bak"
i += 1
LOOP WHILE isfile(lumpfile)
clearpage vpage
printstr "Saving as " & lumpfile, pMenuX, pMenuY, vpage
printstr "LUMPING DATA: please wait...", pMenuX, pMenuY + 10, vpage
setvispage vpage, NO
write_rpg_or_rpgdir workingdir, lumpfile
cleanup_and_terminate
EXIT SUB
END IF
#IFNDEF NO_TEST_GAME
IF (quitnow = 2 OR quitnow = 3) ANDALSO channel_to_Game THEN
'Prod the channel to see whether it's still up (send ping)
channel_write_line(channel_to_Game, "P ")
IF channel_to_Game THEN
IF yesno("You are still running a copy of this game. Quitting will force " & GAMEEXE & " to quit as well. Really quit?") = NO THEN quitnow = 0
END IF
END IF
#ENDIF
IF quitnow = 1 OR quitnow = 2 THEN
save_current_game
END IF
IF quitnow = 3 THEN
IF twochoice("", "I changed my mind! Don't quit!", "I am sure I don't want to save.", 0, 0) = 0 THEN quitnow = 0
cleanup_workingdir_on_exit = YES 'This only makes a difference if a previous attempt to save failed
END IF
setkeys YES
IF quitnow > 1 THEN cleanup_and_terminate
END SUB
SUB prompt_for_password()
'--Is a password set?
IF checkpassword("") THEN EXIT SUB
'--Input password
DIM pas as string = ""
DIM passcomment as string = ""
DIM tog as integer
passcomment = "If you've forgotten your password, don't panic! It can be easily removed. " _
"Contact the OHRRPGCE developers, or learn to compile the source code yourself."
'Uncomment to display the/a password
'passcomment = getpassword
setkeys YES
DO
setwait 55
setkeys YES
tog = tog XOR 1
IF keyval(ccCancel) > 0 THEN cleanup_and_terminate
IF keyval(scAnyEnter) > 1 THEN
IF checkpassword(pas) THEN
EXIT SUB
ELSE
cleanup_and_terminate
END IF
END IF
strgrabber pas, 17
clearpage dpage
wrapprint "This game requires a password to edit. Type it in and press ENTER", 10, 10, uilook(uiText), dpage
textcolor uilook(uiSelectedItem + tog), 1
printstr STRING(LEN(pas), "*"), 20, 40, dpage
wrapprint passcomment, 15, pBottom - 15, uilook(uiText), dpage, rWidth - 30
SWAP vpage, dpage
setvispage vpage
dowait
LOOP
END SUB
SUB import_scripts_and_terminate (scriptfile as string)
debuginfo "Importing scripts from " & scriptfile
DIM success as bool
success = compile_andor_import_scripts(absolute_with_orig_path(scriptfile), option_nowait)
IF success THEN
xbsave game & ".gen", gen(), 1000
save_current_game
END IF
cleanup_workingdir_on_exit = YES 'Cleanup even if saving the .rpg failed: no loss
IF success = NO AND option_nowait THEN PRINT "Compiling or importing failed"
cleanup_and_terminate NO, IIF(success, 0, 1)
END SUB
SUB export_translations_and_terminate (translationfile as string)
debuginfo "Importing scripts from " & translationfile
DIM success as bool
success = export_translations(translationfile)
PRINT "Exporting " & translationfile & IIF(success, " succeeded", " failed")
cleanup_and_terminate NO, IIF(success, 0, 1)
END SUB
SUB cleanup_and_terminate (show_quit_msg as bool = YES, retval as integer = 0)
debuginfo "Cleaning up and terminating " & retval
save_window_state_to_config
#IFNDEF NO_TEST_GAME
IF channel_to_Game THEN
channel_write_line(channel_to_Game, "Q ")
#IFDEF __FB_WIN32__
'On windows, can't delete workingdir until Game has closed the music. Not too serious though
basic_textbox "Waiting for " & GAMEEXE & " to clean up...", uilook(uiText), vpage
setvispage vpage, NO
IF channel_wait_for_msg(channel_to_Game, "Q", "", 2000) = 0 THEN
basic_textbox "Waiting for " & GAMEEXE & " to clean up... giving up.", uilook(uiText), vpage
setvispage vpage, NO
sleep 700
END IF
#ENDIF
channel_close(channel_to_Game)
END IF
IF Game_process <> 0 THEN
basic_textbox "Waiting for " & GAMEEXE & " to quit...", uilook(uiText), vpage
setvispage vpage, NO
'Under GNU/Linux this calls pclose which will block until Game has quit.
cleanup_process @Game_process
END IF
#ENDIF
closemusic
cleanup_global_reload_doc
clear_binsize_cache
clear_fixbits_cache
game = ""
sourcerpg = ""
'catch sprite leaks
sprite_empty_cache
palette16_reload_cache 'Read default palettes (now that game="")
IF show_quit_msg ANDALSO read_config_bool("show_quit_msg", YES) ANDALSO getquitflag() = NO THEN
clearpage vpage
' Don't let Spoonweaver's cat near your power cord!
pop_warning !"Remember to keep backup copies of your work!\n\nYou never know when an unknown bug, a cat-induced hard-drive crash or a little brother might delete your files!", YES
END IF
IF cleanup_workingdir_on_exit THEN
empty_workingdir workingdir
END IF
restoremode
debuginfo "End."
'Delete c_debug.txt if no errors, regardless of retval: nonzero (e.g. script
'import failed) is not necessarily a serious error
end_debug
terminate_program retval
END SUB
'==========================================================================================
' Global menus
'==========================================================================================
LOCAL FUNCTION volume_controls_callback(menu as MenuDef, state as MenuState, dataptr as any ptr) as bool
' This code is duplicated from player_menu_keys :(
IF keyval(scF1) > 1 THEN show_help("editor_volume")
DIM BYREF mi as MenuDefItem = *menu.items[state.pt]
IF mi.t = mtypeSpecial AND (mi.sub_t = spMusicVolume OR mi.sub_t = spVolumeMenu) THEN
IF keyval(ccLeft) > 1 THEN set_music_volume large(get_music_volume - 1/16, 0.0)
IF keyval(ccRight) > 1 THEN set_music_volume small(get_music_volume + 1/16, 1.0)
END IF
IF mi.t = mtypeSpecial AND mi.sub_t = spSoundVolume THEN
IF keyval(ccLeft) > 1 THEN set_global_sfx_volume large(get_global_sfx_volume - 1/16, 0.0)
IF keyval(ccRight) > 1 THEN set_global_sfx_volume small(get_global_sfx_volume + 1/16, 1.0)
END IF
RETURN NO
END FUNCTION
' Allow changing the in-editor volume
SUB Custom_volume_menu
DIM menu as MenuDef
create_volume_menu menu
run_MenuDef menu, @volume_controls_callback
END SUB
'Record a combined editor+player gif
SUB start_recording_combined_gif()
#IFNDEF NO_TEST_GAME
IF channel_to_Game = NULL THEN EXIT SUB
DIM screenfile as string = tmpdir & "screenshare" & randint(100000) & ".bmp"
channel_write_line(channel_to_Game, "SCREEN " & screenfile)
start_recording_gif screenfile
debuginfo "...recording with secondscreen " & screenfile
#ENDIF
END SUB
TYPE CustomGlobalMenu
items(any) as string
item_codes(any) as integer
DECLARE SUB append(code as integer, text as string)
END TYPE
SUB CustomGlobalMenu.append(code as integer, text as string)
a_append item_codes(), code
a_append items(), text
END SUB
' Accessible with F8 if we are editing a game
SUB Custom_global_menu
DIM holdscreen as integer = duplicatepage(getvispage) 'For screenshots
DIM menu as CustomGlobalMenu
IF editing_a_game THEN
IF inside_importscripts = NO THEN
'Don't reallow importing if we're already in the middle of it
'TODO: maybe this should also be disallowed from inside scriptbrowse, etc?
menu.append 0, "Reimport scripts"
END IF
#IFNDEF NO_TEST_GAME
menu.append 1, "Test Game"
#ENDIF
'menu.append 10, "Save Game"
END IF
menu.append 2, "Volume"
menu.append 3, "Macro record/replay (Shft/Ctrl-F11)"
menu.append 14, "Screenshot (F12)"
menu.append 12, IIF(recording_gif(), "Stop recording", "Record") & " .gif video (Shft/Ctrl-F12)"
IF channel_to_Game ANDALSO recording_gif() = NO THEN
menu.append 11, "Record combined editor+player .gif"
END IF
'On Mac, Cmd-1/2/3/4 is handled by keycombos_logic in gfx_sdl and gfx_sdl2
FOR zoom as integer = 1 TO 4
#IFDEF __FB_DARWIN__
menu.append 3 + zoom, "Zoom to " & zoom & "x (Cmd-" & zoom & ")"
#ELSE
menu.append 3 + zoom, "Zoom to " & zoom & "x"
#ENDIF
NEXT
menu.append 8, "Engine Settings menu (Shft/Ctrl-F7)"
menu.append 15, "View/edit ohrrpgce_config.ini"
IF LEN(sourcerpg) THEN
menu.append 16, "View/edit gameconfig.ini"
END IF
DIM note as string
IF num_logged_errors THEN note = ": " & num_logged_errors & " errors" ELSE note = " log"
menu.append 13, "View c_debug.txt" & note & " (Shft/Ctrl-F8)"
DIM choice as integer
choice = multichoice("Global Editor Options (F9)", menu.items())
IF choice > -1 THEN choice = menu.item_codes(choice)
IF choice = 0 THEN
reimport_previous_scripts
#IFNDEF NO_TEST_GAME
ELSEIF choice = 1 THEN
spawn_game_menu(keyval(scShift) > 0, keyval(scCtrl) > 0)
#ENDIF
ELSEIF choice = 2 THEN
Custom_volume_menu
ELSEIF choice = 3 THEN
macro_controls
ELSEIF choice = 4 THEN
set_scale_factor 1, NO
ELSEIF choice = 5 THEN
set_scale_factor 2, NO
ELSEIF choice = 6 THEN
set_scale_factor 3, NO
ELSEIF choice = 7 THEN
set_scale_factor 4, NO
ELSEIF choice = 8 THEN
engine_settings_menu
ELSEIF choice = 10 THEN
'Warning: data in the current menu may not be saved! So figured it better to avoid this.
save_current_game
ELSEIF choice = 11 THEN
start_recording_combined_gif
ELSEIF choice = 12 THEN
toggle_recording_gif
ELSEIF choice = 13 THEN
open_document log_dir & *app_log_filename
ELSEIF choice = 14 THEN
screenshot , holdscreen
ELSEIF choice = 15 THEN
open_document global_config_file
ELSEIF choice = 16 THEN
IF NOT isfile(game_config_file) THEN
'We didn't ensure the existence of prefsdir in set_game_config_globals
IF NOT isdir(prefsdir) THEN makedir prefsdir
touchfile game_config_file
END IF
open_document game_config_file
END IF
freepage holdscreen
END SUB
' This is called after every setkeys unless we're already inside global_setkeys_hook
' It should be fine to call any allmodex function in here, but beware we might
' not have loaded a game yet!
SUB global_setkeys_hook
IF keyval(scF9) > 1 THEN Custom_global_menu
'The other keys documented in Custom_global_menu are checked in allmodex_controls
END SUB
'==========================================================================================
' Creating/cleaning working.tmp and creating games
'==========================================================================================
' Returns true for success
FUNCTION newRPGfile (templatefile as string, newrpg as string) as bool
IF newrpg = "" THEN RETURN NO
' Error already shown if missing
IF NOT isfile(templatefile) THEN RETURN NO
textcolor uilook(uiSelectedDisabled), 0
printstr "Please Wait...", pMenuX, 100, vpage
printstr "Creating RPG File", pMenuX, 110, vpage
setvispage vpage, NO
writeablecopyfile templatefile, newrpg
printstr "Unlumping", pMenuX, 120, vpage
setvispage vpage, NO
unlump newrpg, workingdir + SLASH
'--create archinym information lump
DIM fh as integer
OPENFILE(workingdir + SLASH + "archinym.lmp", FOR_OUTPUT, fh)
PRINT #fh, "ohrrpgce"
PRINT #fh, short_version
CLOSE #fh
DIM root_node as NodePtr
root_node = get_general_reld()
'--Delete general.reld version info. It will then be set by upgrade()
IF root_node = NULL THEN showerror "Couldn't load general.reld!" : RETURN NO
DIM vernode as NodePtr
vernode = GetChildByName(root_node, "editor_version")
IF vernode THEN FreeNode vernode
vernode = GetChildByName(root_node, "prev_editor_versions")
IF vernode THEN FreeNode vernode
'--Set creation time, wipe edit_time
SetChildNodeDate(root_node, "edit_time", 0.)
close_general_reld
printstr "Finalumping", pMenuX, 130, vpage
setvispage vpage, NO
'--re-lump files as NEW rpg file
RETURN write_rpg_or_rpgdir(workingdir, newrpg)
END FUNCTION
'Returns the last mtime of any file in a directory (excluding *.tmp)
FUNCTION directory_last_mtime(directory as string) as double
DIM lasttime as double = 0
DIM filelist() as string
findfiles directory, ALLFILES, fileTypeFile, NO, filelist()
FOR i as integer = 0 TO UBOUND(filelist)
IF RIGHT(filelist(i), 4) <> ".tmp" THEN
lasttime = large(lasttime, FILEDATETIME(directory + SLASH + filelist(i)))
END IF
NEXT
RETURN lasttime
END FUNCTION
' Write workingdir/session_info.txt.tmp
' Note: we assume that whenever this is called (and sourcerpg is set) that we are
' loading or saving the game.
SUB write_session_info ()
DIM text(11) as string
text(0) = short_version
text(1) = get_process_name(get_process_id()) 'OS-specific meaning. Used to guard against reuse of PIDs
text(2) = "# Custom pid:"
text(3) = STR(get_process_id())
text(4) = "# Editing start (load/save) time:"
text(5) = format_date(NOW)
text(6) = STR(NOW)
text(7) = "# Game path:"
'sourcerpg may be blank if we're not yet editing a game
IF LEN(sourcerpg) THEN
text(8) = absolute_path(sourcerpg)