mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
[game] subtitles support (tools + goal + text file). (#1174)
* add subtitles support (tools + goal + text file). * add to build system proper * better handling of line speakers * billy test subtitles * adjust timings * better handling of subtitle timing + citadel subs * press square to toggle cutscene subtitles * improve DirectRenderer performance * clang * dont error out if there's no user files * make system supports multiple inputs for subtitles * clang * oh no typo!! * avoid future issues * fix warp gate crash * remove no longer necessary code in DirectRenderer * remove temp prints * delete triplicate code * i found a better way * fix make issues with subtitles * force avx compilation
This commit is contained in:
parent
f91d1f4056
commit
af447aeab7
|
@ -17,7 +17,7 @@ if(MSVC AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))
|
|||
"-Xclang -fcxx-exceptions \
|
||||
-Xclang -fexceptions \
|
||||
-Xclang -std=c++17 \
|
||||
-march=native \
|
||||
-mavx \
|
||||
-Wno-c++11-narrowing -W3")
|
||||
|
||||
# additional c++ flags for release mode for our projects
|
||||
|
@ -46,7 +46,7 @@ elseif(UNIX)
|
|||
-Wshadow \
|
||||
-Wsign-promo \
|
||||
-fdiagnostics-color=always \
|
||||
-march=native")
|
||||
-mavx")
|
||||
|
||||
# additional c++ flags for release mode for our projects
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Release")
|
||||
|
|
|
@ -292,7 +292,7 @@ void ISONameFromAnimationName(char* dst, const char* src) {
|
|||
|
||||
// upper case
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (dst[i] > '`' && dst[i] < '{') {
|
||||
if (dst[i] >= 'a' && dst[i] <= 'z') {
|
||||
dst[i] -= 0x20;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5829,7 +5829,7 @@
|
|||
;; - Types
|
||||
|
||||
(deftype game-text (structure)
|
||||
((id uint32 :offset-assert 0)
|
||||
((id game-text-id :offset-assert 0)
|
||||
(text string :offset-assert 4)
|
||||
)
|
||||
:pack-me
|
||||
|
|
|
@ -3052,7 +3052,7 @@
|
|||
},
|
||||
|
||||
"print-game-text": {
|
||||
"args": ["str", "font-ctxt", "alpha", "offset-thing"]
|
||||
"args": ["str", "font-ctxt", "opaque", "alpha", "line-height"]
|
||||
},
|
||||
|
||||
"display-frame-start": {
|
||||
|
|
BIN
game/assets/appicon.png
Normal file
BIN
game/assets/appicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
11
game/assets/game_subtitle.txt
Normal file
11
game/assets/game_subtitle.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
;; "project file" for subtitles make tool.
|
||||
;; it's very simple... a list of (version file)
|
||||
;; eventually should also include output filename
|
||||
;; you can find the game-text-version parsing in .cpp and an enum in goal-lib.gc
|
||||
|
||||
(subtitle
|
||||
(jak1-v1 "game/assets/subtitle/jak1/game_subtitle_en.txt")
|
||||
(jak1-v1 "game/assets/subtitle/jak1/game_subtitle_es.txt")
|
||||
)
|
||||
|
||||
|
111
game/assets/subtitle/jak1/game_subtitle_en.txt
Normal file
111
game/assets/subtitle/jak1/game_subtitle_en.txt
Normal file
|
@ -0,0 +1,111 @@
|
|||
(language-id 0 6)
|
||||
|
||||
;; -----------------
|
||||
;; oracle
|
||||
|
||||
("oracle-right-eye-1" (0 #f "ORACLE" "FOR YOUR GIFT, ANOTHER POWER CELL IS YOURS."))
|
||||
("oracle-left-eye-1" (0 #f "ORACLE" "YOU HAVE PROVEN YOURSELF WORTHY. HERE IS A POWER CELL."))
|
||||
("oracle-right-eye-2" (0 #f "ORACLE" "HERE IS ANOTHER POWER CELL FOR YOUR QUEST."))
|
||||
("oracle-left-eye-2" (0 #f "ORACLE" "FOR YOUR SACRIFICE, I OFFER YOU A POWER CELL."))
|
||||
("oracle-right-eye-3" (0 #f "ORACLE" "YOU HAVE OBTAINED ANOTHER POWER CELL."))
|
||||
("oracle-left-eye-3" (0 #f "ORACLE" "FOR YOUR EFFORT, A POWER CELL IS THE REWARD."))
|
||||
|
||||
;; -----------------
|
||||
;; swamp
|
||||
|
||||
("billy-introduction"
|
||||
(6 #f "BILLY" "HOWDY, FRIENDS! ENJOYIN' MY BEAUTIFUL SWAMP? I OWN THESE HERE PARTS. EVERYTHING THAT DOESN'T SINK INTO THE MUD, THAT IS! (LAUGHS)")
|
||||
(349 #f "" "")
|
||||
(369 #f "DAXTER" "JUDGING BY THE SMELL, I'D WAGER YOUR BATHTUB SANK IN THE MUD LONG AGO.")
|
||||
(497 #f "" "")
|
||||
(519 #f "BILLY" "WHAT'S A BATHTUB? ANYWAY I GOT BIGGER PROBLEMS NOW...")
|
||||
(677 #f "" "")
|
||||
(691 #f "BILLY" "SEEMS SOME NASTY LURKER VARMINTS ARE GROUSIN' ABOUTS SNATCHIN' EVERYTHING THEY CAN GET THEIR GRUBBY LITTLE PAWS ON.")
|
||||
(910 #f "BILLY" "AND SCARED AWAY MY PET HIPHOG, FARTHY.")
|
||||
(1000 #f "BILLY" "HE'S BEEN MISSIN' FOR NIGH ON TO A COON'S AGE.")
|
||||
(1136 #t "BILLY" "I'VE BEEN PUTTIN' OUT HIS FAVORITE SNACK, BUT THOSE ORNERY SWAMP RATS KEEP STEALIN' EM!")
|
||||
(1318 #t "BILLY" "IF YOU COULD KEEP THOSE PESKY CRITTERS AWAY LONG ENOUGH, I JUST KNOW FARTHY WOULD SMELL THEM VITTLES AND COME BACK!")
|
||||
(1509 #f "" "")
|
||||
(1527 #f "BILLY" "WILL YA HELP ME OUT?")
|
||||
)
|
||||
|
||||
("billy-reject"
|
||||
(7 #f "BILLY" "WELL, IF YOU CHANGE YOUR MIND, YOU KNOW WHERE TO FIND ME! (LAUGHS)")
|
||||
)
|
||||
|
||||
("billy-accept"
|
||||
(5 #f "BILLY" "GOOD! THOSE RATS WILL BE BACK ANYTIME.")
|
||||
(109 #f "BILLY" "SHOOT ALL THEM RATS, AND KEEP 'EM FROM EATING AT LEAST ONE OF THEM SNACKS.")
|
||||
)
|
||||
|
||||
("billy-resolution"
|
||||
(1 #f "BILLY" "WELL FRY MY HIDE! YOU SURE KNOW HOW TO SHOOT! THANKS A HEAP FOR THE HELP.")
|
||||
)
|
||||
|
||||
("billy-reminder-1"
|
||||
(36 #f "BILLY" "AHH, Y'ALL BACK TO HELP STOP THEM RATS?")
|
||||
)
|
||||
|
||||
;; -----------------
|
||||
;; citadel
|
||||
|
||||
("green-sagecage-introduction"
|
||||
(130 #f "SAGE" "IT'S ABOUT TIME YOU TWO DECIDED TO SHOW UP!")
|
||||
(207 #f "DAXTER" "NICE TO SEE YOU TOO! DO THEY HAVE YOU MOPPING THE FLOORS NOW?")
|
||||
(343 #f "SAGE" "THERE'S NO TIME FOR JOKES, DAXTER. GOL AND MAIA KIDNAPPED US TO SAP OUR ENERGIES TO POWER THEIR ABOMINABLE MACHINE.")
|
||||
(574 #t "SAGE" "IT APPEARS THEY HAVE COMBINED THE FUNCTIONAL REMAINS OF A PRECURSOR ROBOT WITH SCAVENGED ARTIFACTS FROM ACROSS THE LAND.")
|
||||
(800 #t "SAGE" "THEN THEY ADDED A FEW DIABOLICAL ADDITIONS OF THEIR OWN, CREATING THE ONE THING CAPABLE OF OPENING THE DARK ECO SILOS.")
|
||||
(1060 #f "SAGE" "IF YOU CAN FREE THE FOUR OF US, WE CAN USE OUR COMBINED POWERS")
|
||||
(1180 #f "SAGE" "TO BREAK THE FORCE SHIELD SURROUNDING THE ROBOT, BEFORE THEY USE IT TO DESTROY THE WORLD.")
|
||||
)
|
||||
|
||||
("redsage-resolution"
|
||||
(60 #f "RED SAGE" "(CHUCKLES) YOU'VE FINALLY COME TO RESCUE ME.")
|
||||
(190 #f "RED SAGE" "DO YOU KNOW HOW LONG I'VE BEEN IN HERE? WHAT TOOK YOU SO LONG, YOU KNOW? (CHUCKLES) WHAT ARE YOUR NAMES?")
|
||||
(429 #f "DAXTER" "I'M DAXTER! HE'S JAK, HE'S WITH ME.")
|
||||
(540 #t "RED SAGE" "GOOD JOB, DAXTER. YOU'RE A REAL HERO.")
|
||||
(670 #f "" "")
|
||||
(700 #f "RED SAGE" "YOU'VE GOT TO STOP GOL FROM LAUNCHING THE ROBOT. I'LL USE MY ECO POWER TO HELP OPEN THE SHIELD DOOR.")
|
||||
)
|
||||
|
||||
("bluesage-resolution"
|
||||
(60 #f "BLUE SAGE" "GOOD WORK, FELLOWS! OLD SAMOS WAS RIGHT ABOUT YOU!")
|
||||
(165 #f "BLUE SAGE" "GREAT PILES OF PRECURSOR METAL! THAT INSIDIOUS MECHANICAL CREATION MUST NOT BE ALLOWED TO WREAK ITS TERRIBLE HAVOC!")
|
||||
(420 #f "BLUE SAGE" "I WILL TRY TO ACTUATE THE SHIELD DOOR BY ELLICITING A CONDUIT OF ENERGY BETWEEN MYSELF AND THE VAST PORTAL BELOW!")
|
||||
(666 #f "DAXTER" "UH... YEAH. YOU DO THAT. WE'LL UH... JUST GO FIND MORE HELP.")
|
||||
(873 #f "" "")
|
||||
(891 #t "DAXTER" "WEIRDO!")
|
||||
(980 #f "" "")
|
||||
)
|
||||
|
||||
("yellowsage-resolution"
|
||||
(50 #f "YELLOW SAGE" "WHO WOULDA THOUGHT I'D LIVE TO SEE THE DAY WHEN I NEEDED TO BE RESCUED BY A BOY AND HIS MUSKRAT!")
|
||||
(271 #f "YELLOW SAGE" "(SIGHS) I'M GONNA GIVE GOL AND MAIA A LITTLE PAYBACK FOR THIS EMBARRASSMENT!")
|
||||
(480 #f "YELLOW SAGE" "THEN WE'LL SEE ABOUT COOKING UP SOME MUSKRAT STEW...")
|
||||
(606 #f "" "")
|
||||
)
|
||||
|
||||
("green-sagecage-resolution"
|
||||
(30 #f "SAGE" "GOOD WORK, BOYS! YOU'RE REAL HEROES NOW. I'LL COMBINE MY GREEN ECO POWER WITH THE OTHER THREE SAGES")
|
||||
(284 #f "SAGE" "AND TOGETHER WE'LL OPEN THE SHIELD DOOR SURROUNDING THE PRECURSOR ROBOT.")
|
||||
(405 #f "DAXTER" "YEAH YEAH THAT SOUNDS LIKE A GOOD START. AND THEN AFTER YOU GUYS OPEN THAT SHIELD, WHAT ARE YOU GONNA DO ABOUT THE ROBOT?")
|
||||
(576 #f "SAGE" "NOTHING, DAXTER. WE HAVE TO KEEP THE SHIELD OPEN, IT'S UP TO YOU TWO TO FIGURE OUT HOW TO DESTROY THE ROBOT.")
|
||||
(823 #f "DAXTER" "OH, GREAT. I GET TO HELP THE GUY THAT TURNED ME INTO A FURBALL DESTROY THE ONLY PERSON WHO CAN TURN ME BACK!")
|
||||
(1111 #f "" "")
|
||||
(1156 #f "SAGE" "FIRST, SAVE THE WORLD! THEN WE'LL TRY TO CONVINCE GOL TO HELP DAXTER.")
|
||||
)
|
||||
|
||||
("green-sagecage-outro-preboss"
|
||||
(50 #f "GOL" "YOU'RE TOO LATE, SAMOS. ONCE I POSSESS LIMITLESS DARK ECO, I WILL HAVE THE KEY TO CREATION ITSELF!")
|
||||
(335 #f "SAGE" "THIS IS MADNESS! RELEASING THAT MUCH DARK ECO WILL DESTROY EVERYTHING WE KNOW!")
|
||||
(485 #f "SAGE" "JUST LOOK WHAT IT'S DONE TO YOU!")
|
||||
(581 #f "MAIA" "IT HAS GIVEN US A BEAUTY BEYOND ANYTHING YOU COULD UNDERSTAND.")
|
||||
(696 #f "DAXTER" "BEAUTY? HAVE YOU TWO LOOKED IN A MIRROR LATELY?")
|
||||
(828 #f "MAIA" "JUST WAIT UNTIL WE OPEN THE SILOS, LITTLE ONE. YOU THINK SHORT AND FUZZY IS BAD?")
|
||||
(1017 #f "GOL" "AND TO THINK, YOU TWO TRAVELLED ALL THIS WAY FOR MY HELP.")
|
||||
(1180 #f "GOL" "FOOLS! ENJOY YOUR FRONT ROW SEATS TO THE RECREATION OF THE WORLD!")
|
||||
(1382 #f "" "")
|
||||
(1605 #f "SAGE" "JAK! TAKE THE ELEVATOR UP AND STOP THAT ROBOT!")
|
||||
)
|
||||
|
||||
|
6
game/assets/subtitle/jak1/game_subtitle_es.txt
Normal file
6
game/assets/subtitle/jak1/game_subtitle_es.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
(language-id 3)
|
||||
|
||||
;; -----------------
|
||||
;; oracle
|
||||
|
||||
("oracle-left-eye-1" (0 #f "ORÁCULO" "HABÉIS DEMOSTRADO SER HONESTOS. É AQUÍ UNA BATERÍA."))
|
|
@ -189,16 +189,9 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil
|
|||
|
||||
// render!
|
||||
// update buffers:
|
||||
u32 vertex_offset = m_ogl.last_vertex_offset;
|
||||
if (vertex_offset + m_prim_buffer.vert_count >= m_ogl.vertex_buffer_max_verts) {
|
||||
lg::warn("Buffer wrapped in {} ({}/{} (+ {}) verts, {} bytes)", m_name,
|
||||
vertex_offset + m_prim_buffer.vert_count, m_ogl.vertex_buffer_max_verts,
|
||||
m_prim_buffer.vert_count, m_prim_buffer.vert_count * sizeof(Vertex));
|
||||
vertex_offset = 0;
|
||||
}
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, vertex_offset * sizeof(Vertex),
|
||||
m_prim_buffer.vert_count * sizeof(Vertex), m_prim_buffer.vertices.data());
|
||||
glBufferData(GL_ARRAY_BUFFER, m_prim_buffer.vert_count * sizeof(Vertex),
|
||||
m_prim_buffer.vertices.data(), GL_STREAM_DRAW);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
int draw_count = 0;
|
||||
|
@ -212,11 +205,11 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil
|
|||
}
|
||||
|
||||
if (m_sprite_mode.do_first_draw) {
|
||||
glDrawArrays(GL_TRIANGLES, vertex_offset, m_prim_buffer.vert_count);
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
|
||||
draw_count++;
|
||||
}
|
||||
} else {
|
||||
glDrawArrays(GL_TRIANGLES, vertex_offset, m_prim_buffer.vert_count);
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
|
||||
draw_count++;
|
||||
}
|
||||
|
||||
|
@ -224,7 +217,7 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil
|
|||
render_state->shaders[ShaderId::DEBUG_RED].activate();
|
||||
glDisable(GL_BLEND);
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||
glDrawArrays(GL_TRIANGLES, vertex_offset, m_prim_buffer.vert_count);
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
draw_count++;
|
||||
}
|
||||
|
@ -235,8 +228,6 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil
|
|||
prof.add_draw_call(draw_count);
|
||||
m_stats.triangles += n_tris;
|
||||
m_stats.draw_calls += draw_count;
|
||||
m_ogl.last_vertex_offset = vertex_offset + m_prim_buffer.vert_count;
|
||||
m_ogl.last_vertex_offset = (m_ogl.last_vertex_offset + 3) & ~3;
|
||||
m_prim_buffer.vert_count = 0;
|
||||
}
|
||||
|
||||
|
@ -921,7 +912,8 @@ void DirectRenderer::handle_xyzf2_common(u32 x,
|
|||
ASSERT(z < (1 << 24));
|
||||
(void)f; // TODO: do something with this.
|
||||
if (m_prim_buffer.is_full()) {
|
||||
// fmt::print("flush due to fill {} {}\n", m_prim_buffer.vert_count, m_prim_buffer.max_verts);
|
||||
lg::warn("Buffer wrapped in {} ({} verts, {} bytes)", m_name, m_ogl.vertex_buffer_max_verts,
|
||||
m_prim_buffer.vert_count * sizeof(Vertex));
|
||||
flush_pending(render_state, prof);
|
||||
}
|
||||
|
||||
|
@ -1074,8 +1066,6 @@ void DirectRenderer::reset_state() {
|
|||
|
||||
m_prim_building = PrimBuildState();
|
||||
|
||||
m_ogl.last_vertex_offset = 0;
|
||||
|
||||
m_stats = {};
|
||||
}
|
||||
|
||||
|
|
|
@ -239,7 +239,6 @@ class DirectRenderer : public BucketRenderer {
|
|||
GLuint vao;
|
||||
u32 vertex_buffer_bytes = 0;
|
||||
u32 vertex_buffer_max_verts = 0;
|
||||
u32 last_vertex_offset = 0;
|
||||
} m_ogl;
|
||||
|
||||
struct {
|
||||
|
|
|
@ -157,7 +157,7 @@ static std::shared_ptr<GfxDisplay> gl_make_main_display(int width,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
std::string image_path = fmt::format("{}/game/appicon.png", file_util::get_project_path());
|
||||
std::string image_path = fmt::format("{}/game/assets/appicon.png", file_util::get_project_path());
|
||||
|
||||
GLFWimage images[1];
|
||||
images[0].pixels =
|
||||
|
|
|
@ -217,6 +217,7 @@
|
|||
"goal_src/engine/level/level-info.gc"
|
||||
"goal_src/engine/level/level.gc"
|
||||
"goal_src/engine/ui/text.gc"
|
||||
"goal_src/engine/pc/subtitle.gc" ;; added
|
||||
"goal_src/engine/collide/collide-probe.gc"
|
||||
"goal_src/engine/collide/collide-frag.gc"
|
||||
"goal_src/engine/collide/collide-mesh.gc"
|
||||
|
|
|
@ -418,6 +418,7 @@
|
|||
("level-info.o" "level-info")
|
||||
("level.o" "level")
|
||||
("text.o" "text")
|
||||
("subtitle.o" "subtitle") ;; added
|
||||
("collide-probe.o" "collide-probe")
|
||||
("collide-frag.o" "collide-frag")
|
||||
("collide-mesh.o" "collide-mesh")
|
||||
|
|
|
@ -209,6 +209,7 @@
|
|||
("level-info.o" "level-info")
|
||||
("level.o" "level")
|
||||
("text.o" "text")
|
||||
("subtitle.o" "subtitle") ;; added
|
||||
("collide-probe.o" "collide-probe")
|
||||
("collide-frag.o" "collide-frag")
|
||||
("collide-mesh.o" "collide-mesh")
|
||||
|
|
|
@ -205,6 +205,7 @@
|
|||
("level-info.o" "level-info")
|
||||
("level.o" "level")
|
||||
("text.o" "text")
|
||||
("subtitle.o" "subtitle") ;; added
|
||||
("collide-probe.o" "collide-probe")
|
||||
("collide-frag.o" "collide-frag")
|
||||
("collide-mesh.o" "collide-mesh")
|
||||
|
|
|
@ -4271,6 +4271,20 @@
|
|||
)
|
||||
)
|
||||
|
||||
(defun dm-subtitle-language ((blang int) (msg debug-menu-msg))
|
||||
(let ((lang (the pc-subtitle-lang (/ blang 8))))
|
||||
(when (= msg (debug-menu-msg press))
|
||||
(set! (-> *pc-settings* subtitle-language) lang))
|
||||
(= (-> *pc-settings* subtitle-language) lang)
|
||||
)
|
||||
)
|
||||
|
||||
(defun dm-subtitle-setting ((setting symbol) (msg debug-menu-msg))
|
||||
(when (= msg (debug-menu-msg press))
|
||||
(set! (-> *pc-settings* subtitle-speaker?) setting))
|
||||
(= (-> *pc-settings* subtitle-speaker?) setting)
|
||||
)
|
||||
|
||||
(when (-> *debug-menu-context* root-menu)
|
||||
(debug-menu-append-item (-> *debug-menu-context* root-menu) (debug-menu-make-load-menu *debug-menu-context*))
|
||||
(debug-menu-append-item (-> *debug-menu-context* root-menu) (debug-menu-make-part-menu *debug-menu-context*))
|
||||
|
@ -4336,8 +4350,18 @@
|
|||
)
|
||||
(flag "Letterbox" #f ,(dm-lambda-boolean-flag (-> *pc-settings* letterbox?)))
|
||||
;(flag "Skip movies" #f ,(dm-lambda-boolean-flag (-> *pc-settings* skip-movies?)))
|
||||
;(flag "Subtitles" #f ,(dm-lambda-boolean-flag (-> *pc-settings* subtitles?)))
|
||||
(flag "Subtitles" #f ,(dm-lambda-boolean-flag (-> *pc-settings* subtitles?)))
|
||||
;(flag "Hinttitles" #f ,(dm-lambda-boolean-flag (-> *pc-settings* hinttitles?)))
|
||||
(menu "Subtitle speaker"
|
||||
(flag "Off" #f dm-subtitle-setting)
|
||||
(flag "On" #t dm-subtitle-setting)
|
||||
(flag "Auto" auto dm-subtitle-setting)
|
||||
)
|
||||
(menu "Subtitle language"
|
||||
(flag "english" 0 dm-subtitle-language)
|
||||
(flag "spanish" 3 dm-subtitle-language)
|
||||
(flag "uk-english" 6 dm-subtitle-language)
|
||||
)
|
||||
(flag "Discord RPC" #t ,(dm-lambda-boolean-flag (-> *pc-settings* discord-rpc?)))
|
||||
(menu "Game fixes"
|
||||
(flag "sagecage crash" #f ,(dm-lambda-boolean-flag (-> *pc-settings* fixes crash-sagecage)))
|
||||
|
|
|
@ -1923,6 +1923,11 @@
|
|||
|
||||
(#when PC_PORT
|
||||
|
||||
(when *debug-segment*
|
||||
;; temporary string.
|
||||
(define *debug-temp-string* (new 'debug 'string 4096 (the string #f)))
|
||||
)
|
||||
|
||||
;; custom entity functions for pc port
|
||||
(defun-debug entity-inspect-draw ((inspect-info entity-debug-inspect))
|
||||
"draw text about an entity on screen"
|
||||
|
|
|
@ -240,7 +240,7 @@
|
|||
"demo-start"
|
||||
)
|
||||
(*debug-segment*
|
||||
(#if (user? dass) "citadel-generator-end" "village1-hut")
|
||||
(#if (user? dass) "citadel-launch-end" "village1-hut")
|
||||
)
|
||||
(else
|
||||
"title-start"
|
||||
|
|
|
@ -414,10 +414,7 @@
|
|||
)
|
||||
(('inactive)
|
||||
;; no usable data here, fill the buffer
|
||||
;; TODO macro
|
||||
(let ((v1-28 (-> obj heap)))
|
||||
(set! (-> v1-28 current) (-> v1-28 base))
|
||||
)
|
||||
(kheap-reset (-> obj heap))
|
||||
(cond
|
||||
((string= (-> obj load-file) "reserved") ;; we want to reserve this buffer for something (not loading an str file)
|
||||
(cond
|
||||
|
|
|
@ -76,27 +76,27 @@
|
|||
;; subtitle languages.
|
||||
(defenum pc-subtitle-lang
|
||||
:type uint16
|
||||
(english)
|
||||
(french)
|
||||
(german)
|
||||
(spanish)
|
||||
(italian)
|
||||
(japanese)
|
||||
(uk-english)
|
||||
(english 0)
|
||||
(french 1)
|
||||
(german 2)
|
||||
(spanish 3)
|
||||
(italian 4)
|
||||
(japanese 5)
|
||||
(uk-english 6)
|
||||
|
||||
;; additional languages.
|
||||
;; these don't neccessarily have to work but I'm future-proofing a bit here, just in case.
|
||||
|
||||
;; languages that use the existing glyphs
|
||||
(portuguese)
|
||||
(finnish)
|
||||
(swedish)
|
||||
(danish)
|
||||
(norwegian)
|
||||
(portuguese 7)
|
||||
(finnish 8)
|
||||
(swedish 9)
|
||||
(danish 10)
|
||||
(norwegian 11)
|
||||
|
||||
;; jak 1 has no glyphs for korean or cyrillic.
|
||||
(korean) ;; future-proofing here
|
||||
(russian) ;; same thing
|
||||
(korean 12) ;; future-proofing here
|
||||
(russian 13) ;; same thing
|
||||
|
||||
(custom 500) ;; temp
|
||||
)
|
||||
|
@ -128,6 +128,7 @@
|
|||
((crash-sagecage symbol) ;; citadel
|
||||
(crash-dma symbol) ;; general out of memory crash
|
||||
(crash-light-eco symbol) ;; why does this one even happen?
|
||||
(crash-moles symbol) ;; dont know either
|
||||
(lockout-pelican symbol) ;; kill pelican during chase
|
||||
(lockout-pipegame symbol) ;; last buzzer is pipegame
|
||||
(lockout-gambler symbol) ;; talk without completing
|
||||
|
@ -206,10 +207,10 @@
|
|||
(display-actor-counts symbol)
|
||||
|
||||
;; device settings
|
||||
(device-audio pc-device-info :inline) ;; used audio device
|
||||
(device-screen pc-device-info :inline) ;; used display device
|
||||
(device-gpu pc-device-info :inline) ;; used graphics device
|
||||
(device-pad pc-pad-info 4 :inline) ;; used input devices, like controllers.
|
||||
;(device-audio pc-device-info :inline) ;; used audio device
|
||||
;(device-screen pc-device-info :inline) ;; used display device
|
||||
;(device-gpu pc-device-info :inline) ;; used graphics device
|
||||
;(device-pad pc-pad-info 4 :inline) ;; used input devices, like controllers.
|
||||
;(device-keyboard pc-pad-info :inline) ;; keyboard input information. if nothing else, this must be usable.
|
||||
(stick-deadzone float) ;; analog stick deadzone. 0-1
|
||||
|
||||
|
@ -248,6 +249,7 @@
|
|||
(subtitles? symbol) ;; if on, cutscene subtitles will show up
|
||||
(hinttitles? symbol) ;; if on, non-cutscene subtitles will show up
|
||||
(subtitle-language pc-subtitle-lang) ;; language for subtitles
|
||||
(subtitle-speaker? symbol) ;; #f (force off), #t (force on), auto (on for offscreen)
|
||||
|
||||
(fixes pc-fixes :inline) ;; extra game fixes
|
||||
|
||||
|
@ -401,6 +403,7 @@
|
|||
(set! (-> obj skip-movies?) #t)
|
||||
(set! (-> obj subtitles?) #t)
|
||||
(set! (-> obj hinttitles?) #t)
|
||||
(set! (-> obj subtitle-speaker?) 'auto)
|
||||
(set! (-> obj subtitle-language) (pc-subtitle-lang english))
|
||||
(none))
|
||||
|
||||
|
@ -410,6 +413,7 @@
|
|||
(set! (-> obj fixes crash-sagecage) #t)
|
||||
(set! (-> obj fixes crash-dma) #t)
|
||||
(set! (-> obj fixes crash-light-eco) #t)
|
||||
(set! (-> obj fixes crash-moles) #t)
|
||||
(set! (-> obj fixes lockout-pelican) #t)
|
||||
(set! (-> obj fixes lockout-pipegame) #t)
|
||||
(set! (-> obj fixes lockout-gambler) #t)
|
||||
|
|
|
@ -317,6 +317,7 @@
|
|||
(("aspect4x3") 'aspect4x3)
|
||||
(("aspect16x9") 'aspect16x9)
|
||||
(("borderless") 'borderless)
|
||||
(("auto") 'auto)
|
||||
(else #t)
|
||||
)
|
||||
)
|
||||
|
@ -418,6 +419,7 @@
|
|||
(("lod-force-ocean") (set! (-> obj lod-force-ocean) (file-stream-read-int file)))
|
||||
(("lod-force-actor") (set! (-> obj lod-force-actor) (file-stream-read-int file)))
|
||||
(("subtitle-language") (set! (-> obj subtitle-language) (the-as pc-subtitle-lang (file-stream-read-int file))))
|
||||
(("subtitle-speaker") (set! (-> obj subtitle-speaker?) (file-stream-read-symbol file)))
|
||||
(("stick-deadzone") (set! (-> obj stick-deadzone) (file-stream-read-float file)))
|
||||
(("ps2-read-speed?") (set! (-> obj ps2-read-speed?) (file-stream-read-symbol file)))
|
||||
(("ps2-parts?") (set! (-> obj ps2-parts?) (file-stream-read-symbol file)))
|
||||
|
@ -425,11 +427,13 @@
|
|||
(("ps2-se?") (set! (-> obj ps2-se?) (file-stream-read-symbol file)))
|
||||
(("ps2-hints?") (set! (-> obj ps2-hints?) (file-stream-read-symbol file)))
|
||||
(("ps2-lod-dist?") (set! (-> obj ps2-lod-dist?) (file-stream-read-symbol file)))
|
||||
(("force-actors?") (set! (-> obj force-actors?) (file-stream-read-symbol file)))
|
||||
(("music-fade?") (set! (-> obj music-fade?) (file-stream-read-symbol file)))
|
||||
(("use-vis?") (set! (-> obj use-vis?) (file-stream-read-symbol file)))
|
||||
(("skip-movies?") (set! (-> obj skip-movies?) (file-stream-read-symbol file)))
|
||||
(("subtitles?") (set! (-> obj subtitles?) (file-stream-read-symbol file)))
|
||||
(("hinttitles?") (set! (-> obj hinttitles?) (file-stream-read-symbol file)))
|
||||
(("discord-rpc?") (set! (-> obj discord-rpc?) (file-stream-read-symbol file)))
|
||||
(("scenes-seen")
|
||||
(dotimes (i 197)
|
||||
(set! (-> obj scenes-seen i) (file-stream-read-int file))
|
||||
|
@ -441,6 +445,7 @@
|
|||
(("crash-sagecage") (set! (-> obj fixes crash-sagecage) (file-stream-read-symbol file)))
|
||||
(("crash-dma") (set! (-> obj fixes crash-dma) (file-stream-read-symbol file)))
|
||||
(("crash-light-eco") (set! (-> obj fixes crash-light-eco) (file-stream-read-symbol file)))
|
||||
(("crash-moles") (set! (-> obj fixes crash-moles) (file-stream-read-symbol file)))
|
||||
(("lockout-pelican") (set! (-> obj fixes lockout-pelican) (file-stream-read-symbol file)))
|
||||
(("lockout-pipegame") (set! (-> obj fixes lockout-pipegame) (file-stream-read-symbol file)))
|
||||
(("lockout-gambler") (set! (-> obj fixes lockout-gambler) (file-stream-read-symbol file)))
|
||||
|
@ -554,9 +559,12 @@
|
|||
(format file " (music-fade? ~A)~%" (-> obj music-fade?))
|
||||
(format file " (use-vis? ~A)~%" (-> obj use-vis?))
|
||||
(format file " (skip-movies? ~A)~%" (-> obj skip-movies?))
|
||||
(format file " (discord-rpc? ~A)~%" (-> obj discord-rpc?))
|
||||
(format file " (force-actors? ~A)~%" (-> obj force-actors?))
|
||||
(format file " (subtitles? ~A)~%" (-> obj subtitles?))
|
||||
(format file " (hinttitles? ~A)~%" (-> obj hinttitles?))
|
||||
(format file " (subtitle-language ~D)~%" (-> obj subtitle-language))
|
||||
(format file " (subtitle-speaker ~A)~%" (-> obj subtitle-speaker?))
|
||||
|
||||
(format file " (scenes-seen")
|
||||
(dotimes (i 197)
|
||||
|
@ -571,6 +579,7 @@
|
|||
(format file " (crash-sagecage ~A)~%" (-> obj fixes crash-sagecage))
|
||||
(format file " (crash-dma ~A)~%" (-> obj fixes crash-dma))
|
||||
(format file " (crash-light-eco ~A)~%" (-> obj fixes crash-light-eco))
|
||||
(format file " (crash-moles ~A)~%" (-> obj fixes crash-moles))
|
||||
(format file " (lockout-pelican ~A)~%" (-> obj fixes lockout-pelican))
|
||||
(format file " (lockout-pipegame ~A)~%" (-> obj fixes lockout-pipegame))
|
||||
(format file " (lockout-gambler ~A)~%" (-> obj fixes lockout-gambler))
|
||||
|
|
363
goal_src/engine/pc/subtitle.gc
Normal file
363
goal_src/engine/pc/subtitle.gc
Normal file
|
@ -0,0 +1,363 @@
|
|||
;;-*-Lisp-*-
|
||||
(in-package goal)
|
||||
|
||||
#|
|
||||
|
||||
Code for subtitles for the PC port. A PC actor pool is provided, and the subtitle process lives there.
|
||||
It automatically detects the currently playing cutscene and plays the subtitles for it on channel 0.
|
||||
Channel 1 and 2 are reserved for ambient sounds, which cannot be auto-detected, so the ambient-control needs to
|
||||
notify the subtitle process manually (TODO: verify this!)
|
||||
|
||||
Similarly to the generic text file, only one subtitles text file is loaded at once, stored in a specific
|
||||
heap.
|
||||
|
||||
In Jak 2, the subtitles are stored directly within the .STR files. We don't have that luxury here, unfortunately.
|
||||
|
||||
|#
|
||||
|
||||
|
||||
(#when PC_PORT
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; constants
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defconstant PC_SUBTITLE_FILE_SIZE (* 64 1024))
|
||||
(defconstant PC_SUBTITLE_FILE_NAME "subtit")
|
||||
(defglobalconstant PC_SUBTITLE_DEBUG #f)
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; types and enums
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
;;;------------------------
|
||||
;; data
|
||||
;;;------------------------
|
||||
|
||||
;; enum for the subtitle channels, no more than 2 bytes!
|
||||
(defenum pc-subtitle-channel
|
||||
:type int16
|
||||
(invalid -1) (movie 0) (ambient 1) (hint 2))
|
||||
|
||||
|
||||
;; information about a single line of subtitles
|
||||
(deftype subtitle-keyframe (structure)
|
||||
(
|
||||
(frame int32) ;; the frame to play the subtitle line on
|
||||
(string string) ;; the text for the subtitle line
|
||||
(speaker string) ;; name of the speaker. leave blank for no speaker.
|
||||
(offscreen symbol) ;; speaker is offscreen.
|
||||
)
|
||||
:pack-me
|
||||
)
|
||||
|
||||
;; an individual entry to a subtitle text making up a scene, comprised of a series of keyframes (frame+line of text)
|
||||
(deftype subtitle-text (structure)
|
||||
(
|
||||
;; the channel to play the text on, useful for lookup since it can also be used to tell subtitle types apart
|
||||
(kind pc-subtitle-channel)
|
||||
;; the amount of keyframes
|
||||
(length int16)
|
||||
;; data
|
||||
(keyframes (inline-array subtitle-keyframe))
|
||||
|
||||
;; now we store a way to identify and lookup the subtitles.
|
||||
|
||||
;; the game-text-id if this is for a hint
|
||||
(id game-text-id :offset 8)
|
||||
;; the name of the spool-anim
|
||||
(name string :offset 8)
|
||||
;; the 8-character name for an ambient or hint
|
||||
(hash uint64 :offset 8)
|
||||
|
||||
|
||||
)
|
||||
|
||||
:size-assert #x10 ;; compact!
|
||||
)
|
||||
|
||||
;; the global subtitle text info bank
|
||||
(deftype subtitle-text-info (basic)
|
||||
((length int32)
|
||||
(lang pc-subtitle-lang)
|
||||
(dummy int32)
|
||||
(data subtitle-text :inline :dynamic)
|
||||
)
|
||||
|
||||
(:methods
|
||||
(get-scene-by-name (_type_ pc-subtitle-channel string) subtitle-text)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;;;----------------------------------
|
||||
;; process type
|
||||
;;;----------------------------------
|
||||
|
||||
|
||||
;; the subtitle process! it lives on the PC actor pool and awaits for incoming subtitle messages, or a movie
|
||||
(deftype subtitle (process)
|
||||
(
|
||||
(font font-context)
|
||||
( spool-name string)
|
||||
(old-spool-name string)
|
||||
(cur-channel pc-subtitle-channel)
|
||||
)
|
||||
:heap-base #x10
|
||||
|
||||
(:methods
|
||||
(subtitle-format (_type_ subtitle-keyframe) string)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
;;;----------------------------------------------
|
||||
;; globals
|
||||
;;;----------------------------------------------
|
||||
|
||||
|
||||
;; the actor pool for PC processes! it has 1K of space for 1 process.
|
||||
(define *pc-dead-pool* (new 'global 'dead-pool 1 (* 1 1024) '*pc-dead-pool*))
|
||||
(define *subtitle-temp-string* (new 'global 'string 16 (the string #f)))
|
||||
(define *subtitle* (the (pointer subtitle) #f))
|
||||
|
||||
;; subtitle text data
|
||||
(define *subtitle-text* (the subtitle-text-info #f))
|
||||
(kheap-alloc (define *subtitle-text-heap* (new 'global 'kheap)) PC_SUBTITLE_FILE_SIZE)
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; loading files
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defmethod get-scene-by-name subtitle-text-info ((obj subtitle-text-info) (kind pc-subtitle-channel) (name string))
|
||||
"get a subtitle scene info with the corresponding name. #f = none found"
|
||||
|
||||
(dotimes (i (-> obj length))
|
||||
;; name and kind matches, return that!
|
||||
(when (and (= kind (-> obj data i kind)) (string= (-> obj data i name) name))
|
||||
(return (-> obj data i))
|
||||
)
|
||||
)
|
||||
|
||||
(the subtitle-text #f))
|
||||
|
||||
(defun load-subtitle-text-info ((txt-name string) (curr-text symbol) (heap kheap))
|
||||
"load a subtitles text file onto a heap.
|
||||
txt-name = language-agnostic file name
|
||||
curr-text = a symbol to a subtitle-text-info to link the file to
|
||||
heap = the text heap to load the file onto"
|
||||
|
||||
(local-vars (v0-2 int) (heap-sym-heap subtitle-text-info) (lang pc-subtitle-lang) (load-status int) (heap-free int))
|
||||
(set! heap-sym-heap (the-as subtitle-text-info (-> curr-text value)))
|
||||
(set! lang (-> *pc-settings* subtitle-language))
|
||||
(set! load-status 0)
|
||||
(set! heap-free (&- (-> heap top) (the-as uint (-> heap base))))
|
||||
(when (or (= heap-sym-heap #f)
|
||||
(!= (-> heap-sym-heap lang) lang)
|
||||
)
|
||||
(kheap-reset heap)
|
||||
|
||||
(while (not (str-load (string-format "~D~S.TXT" lang txt-name) -1 (logand -64 (&+ (-> heap current) 63)) (&- (-> heap top) (-> heap current))))
|
||||
(return 0)
|
||||
)
|
||||
|
||||
(label retry)
|
||||
(let ((v1-20 (str-load-status (the-as (pointer int32) (& load-status)))))
|
||||
(when (= v1-20 'error)
|
||||
(format 0 "Error loading subtitle~%")
|
||||
(return 0)
|
||||
(goto loaded)
|
||||
)
|
||||
(cond
|
||||
((>= load-status (+ heap-free -300))
|
||||
(format 0 "Game subtitle heap overrun!~%")
|
||||
(return 0)
|
||||
)
|
||||
((= v1-20 'busy)
|
||||
(goto retry)
|
||||
)
|
||||
)
|
||||
)
|
||||
(label loaded)
|
||||
(let ((s2-1 (logand -64 (&+ (-> heap current) 63))))
|
||||
(flush-cache 0)
|
||||
(set! (-> curr-text value) (link s2-1 (-> (string-format "~D~S.TXT" lang txt-name) data) load-status heap 0))
|
||||
)
|
||||
(if (<= (the-as int (-> curr-text value)) 0)
|
||||
(set! (-> curr-text value) (the-as object #f))
|
||||
)
|
||||
)
|
||||
0)
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; subtitle process and drawing!
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defmethod subtitle-format subtitle ((obj subtitle) (keyframe subtitle-keyframe))
|
||||
"check settings and format subtitle accordingly."
|
||||
|
||||
(cond
|
||||
((= 0 (length (-> keyframe speaker)))
|
||||
(string-format "~S" (-> keyframe string))
|
||||
)
|
||||
((or (= #t (-> *pc-settings* subtitle-speaker?))
|
||||
(and (= 'auto (-> *pc-settings* subtitle-speaker?)) (-> keyframe offscreen)))
|
||||
(string-format "~3L~S:~0L ~S" (-> keyframe speaker) (-> keyframe string))
|
||||
)
|
||||
(else
|
||||
(string-format "~S" (-> keyframe string))
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
(defun subtitle? ()
|
||||
"can a subtitle be displayed right now?"
|
||||
(and (-> *pc-settings* subtitles?) ;; subtitles enabled
|
||||
(or *debug-segment* (= *master-mode* 'game)) ;; current mode is game, OR we are just debugging
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(defstate subtitle-process (subtitle)
|
||||
|
||||
:event (behavior ((from process) (argc int) (msg symbol) (block event-message-block))
|
||||
(case msg
|
||||
(('subtitle-start)
|
||||
0
|
||||
)
|
||||
)
|
||||
)
|
||||
:code (behavior ()
|
||||
(loop
|
||||
(suspend))
|
||||
)
|
||||
:trans (behavior ()
|
||||
(load-subtitle-text-info PC_SUBTITLE_FILE_NAME '*subtitle-text* *subtitle-text-heap*)
|
||||
;; reset params
|
||||
(set! (-> self spool-name) #f)
|
||||
(set! (-> self cur-channel) (pc-subtitle-channel invalid))
|
||||
(none))
|
||||
:post (behavior ()
|
||||
;; check what kind of subtitles are running
|
||||
(when (and (movie?) (-> *art-control* spool-lock) (-> *art-control* active-stream))
|
||||
;; there's a cutscene happening and an active spool with a valid owner.
|
||||
(set! (-> self spool-name) (-> *art-control* active-stream))
|
||||
(set! (-> self cur-channel) (pc-subtitle-channel movie))
|
||||
(with-proc ((handle->process (-> *art-control* spool-lock)))
|
||||
(format *stdcon* "current spool frame is ~D~%" (the int (ja-aframe-num 0)))
|
||||
)
|
||||
)
|
||||
|
||||
;; do subtitles
|
||||
(case (-> self cur-channel)
|
||||
(((pc-subtitle-channel movie))
|
||||
;; cutscenes, check if user toggled subtitles
|
||||
(if (and (= 'game *master-mode*) (cpad-pressed? 0 square))
|
||||
(not! (-> *pc-settings* subtitles?)))
|
||||
(when (subtitle?)
|
||||
;; subtitles on! get a subtitle info that matches our current cutscene, and find at what point we're in.
|
||||
(let ((scene (get-scene-by-name *subtitle-text* (-> self cur-channel) (-> self spool-name))))
|
||||
(when scene
|
||||
;; got a scene! get closest subtitle now.
|
||||
(let ((pos 0)
|
||||
(keyframe (the subtitle-keyframe #f)))
|
||||
;; get frame num
|
||||
(with-proc ((handle->process (-> *art-control* spool-lock)))
|
||||
(set! pos (the int (ja-aframe-num 0)))
|
||||
)
|
||||
;; find closest keyframe
|
||||
(dotimes (i (-> scene length))
|
||||
(when (>= pos (-> scene keyframes i frame))
|
||||
(set! keyframe (-> scene keyframes i)))
|
||||
)
|
||||
(when keyframe
|
||||
;; we got the closest keyframe! finally just render the subtitles!
|
||||
(print-game-text (subtitle-format self keyframe) (-> self font) #f 128 22)
|
||||
(#when PC_SUBTITLE_DEBUG
|
||||
(draw-debug-text-box (-> self font))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; keep this for later
|
||||
(set! (-> self old-spool-name) (-> self spool-name))
|
||||
)
|
||||
)
|
||||
)
|
||||
0)
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; helper functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
|
||||
(defmethod length subtitle-text-info ((obj subtitle-text-info))
|
||||
"Get the length (number of subtitle scenes) in a subtitle-text-info."
|
||||
(-> obj length)
|
||||
)
|
||||
|
||||
(defmethod length subtitle-text ((obj subtitle-text))
|
||||
"Get the length (number of subtitle lines) in a subtitle-text."
|
||||
(-> obj length)
|
||||
)
|
||||
|
||||
|
||||
|
||||
(defun subtitle-stop ()
|
||||
"kill the subtitle process"
|
||||
|
||||
(kill-by-type subtitle *active-pool*)
|
||||
(set! *subtitle* (the (pointer subtitle) #f)))
|
||||
|
||||
(defun subtitle-start ()
|
||||
"start the subtitle process"
|
||||
|
||||
(when *subtitle*
|
||||
(subtitle-stop)
|
||||
)
|
||||
|
||||
(set! *subtitle* (make-init-process subtitle
|
||||
(lambda :behavior subtitle ()
|
||||
(set! (-> self font) (new 'process 'font-context *font-default-matrix*
|
||||
(the int (* 0.12 256)) (+ 112 (* 11 5)) 0.0
|
||||
(font-color default)
|
||||
(font-flags shadow kerning middle large)))
|
||||
(set-scale! (-> self font) 0.5)
|
||||
(set-width! (-> self font) (the int (* 0.88 256 2)))
|
||||
(set-height! (-> self font) (* 11 3))
|
||||
(go subtitle-process)
|
||||
)
|
||||
:from *pc-dead-pool* :to *active-pool*
|
||||
:stack-size (* 1 1024)))
|
||||
)
|
||||
|
||||
|
||||
;; start the subtitle process
|
||||
(subtitle-start)
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -460,7 +460,7 @@
|
|||
|
||||
;; an individual string.
|
||||
(deftype game-text (structure)
|
||||
((id uint32 :offset-assert 0)
|
||||
((id game-text-id :offset-assert 0)
|
||||
(text string :offset-assert 4)
|
||||
)
|
||||
:pack-me
|
||||
|
|
|
@ -113,8 +113,7 @@
|
|||
(the-as string #f)
|
||||
)
|
||||
(else
|
||||
(format (clear *temp-string*) "UNKNOWN ID ~D" arg0)
|
||||
*temp-string*
|
||||
(string-format "UNKNOWN ID ~D" arg0)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -220,8 +219,6 @@
|
|||
(s3-1 s2-1 (-> *temp-string* data) load-status heap 0)
|
||||
)
|
||||
)
|
||||
(format 0 "loaded text ~A~%" *common-text*)
|
||||
(format 0 "~A~%" (lookup-text! *common-text* (game-text-id continue-without-saving) #f))
|
||||
)
|
||||
|
||||
;; linking error occured?
|
||||
|
|
|
@ -324,6 +324,13 @@
|
|||
"out/iso/6COMMON.TXT")
|
||||
)
|
||||
|
||||
(defstep :in "game/assets/game_subtitle.txt"
|
||||
:tool 'subtitle
|
||||
:out '("out/iso/0SUBTIT.TXT"
|
||||
"out/iso/3SUBTIT.TXT"
|
||||
"out/iso/6SUBTIT.TXT")
|
||||
)
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -333,6 +340,7 @@
|
|||
|
||||
(group "engine"
|
||||
"out/iso/0COMMON.TXT"
|
||||
"out/iso/0SUBTIT.TXT"
|
||||
"out/iso/KERNEL.CGO"
|
||||
"out/iso/GAME.CGO"
|
||||
)
|
||||
|
@ -345,6 +353,7 @@
|
|||
|
||||
(group "hub1"
|
||||
"out/iso/0COMMON.TXT"
|
||||
"out/iso/0SUBTIT.TXT"
|
||||
"out/iso/KERNEL.CGO"
|
||||
"out/iso/GAME.CGO"
|
||||
"out/iso/VI1.DGO"
|
||||
|
@ -352,6 +361,7 @@
|
|||
"out/iso/FIC.DGO"
|
||||
"out/iso/JUN.DGO"
|
||||
"out/iso/BEA.DGO"
|
||||
"out/iso/MIS.DGO"
|
||||
)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1733,6 +1743,7 @@
|
|||
"level/level-info.gc"
|
||||
"level/level.gc"
|
||||
"ui/text.gc"
|
||||
"pc/subtitle.gc" ;; added
|
||||
"collide/collide-probe.gc"
|
||||
"collide/collide-frag.gc"
|
||||
"collide/collide-mesh.gc"
|
||||
|
@ -1852,6 +1863,7 @@
|
|||
|
||||
(group-list "iso"
|
||||
`("out/iso/0COMMON.TXT"
|
||||
"out/iso/0SUBTIT.TXT"
|
||||
,@*all-cgos*
|
||||
,@*all-vis*
|
||||
,@*all-str*)
|
||||
|
@ -1860,3 +1872,10 @@
|
|||
(group-list "spools"
|
||||
`(,@*all-str*)
|
||||
)
|
||||
|
||||
(group-list "text"
|
||||
`(
|
||||
"out/iso/0COMMON.TXT"
|
||||
"out/iso/0SUBTIT.TXT"
|
||||
)
|
||||
)
|
||||
|
|
|
@ -97,6 +97,15 @@
|
|||
)
|
||||
)
|
||||
|
||||
;; enum for text file encoding versions
|
||||
(defenum game-text-version
|
||||
(jak1-v1 10)
|
||||
(jak1-v2 11)
|
||||
(jak2 20)
|
||||
(jak3 30)
|
||||
(jakx 40)
|
||||
)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; CONDITIONAL COMPILATION
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -404,7 +404,7 @@
|
|||
(fmt #t "Loading user scripts for user: {}...\n" *user*)
|
||||
;; i'm not sure what naming scheme to use here. user/<name>/user.gs?
|
||||
;; the GOAL one is loaded in Compiler.cpp
|
||||
(load-file (fmt #f "goal_src/user/{}/user.gs" *user*))
|
||||
(try-load-file (fmt #f "goal_src/user/{}/user.gs" *user*))
|
||||
)
|
||||
|
||||
(defsmacro user? (&rest users)
|
||||
|
|
|
@ -598,6 +598,14 @@
|
|||
,@body)
|
||||
)
|
||||
|
||||
(defmacro with-proc (bindings &rest body)
|
||||
`(rlet ((pp :reg r13 :reset-here #t :type process))
|
||||
(protect (pp)
|
||||
(set! pp ,(car bindings))
|
||||
,@body
|
||||
))
|
||||
)
|
||||
|
||||
(defmacro defbehavior (name process-type bindings &rest body)
|
||||
(if (and
|
||||
(> (length body) 1) ;; more than one thing in function
|
||||
|
@ -622,6 +630,22 @@
|
|||
)
|
||||
)
|
||||
|
||||
(defmacro kheap-alloc (heap size)
|
||||
"allocate space for a kheap"
|
||||
`(let ((heap ,heap) (size ,size))
|
||||
(set! (-> heap base) (malloc 'global size))
|
||||
(set! (-> heap current) (-> heap base))
|
||||
(set! (-> heap top-base) (&+ (-> heap base) size))
|
||||
(set! (-> heap top) (-> heap top-base))
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro kheap-reset (heap)
|
||||
"reset the kheap, so you can use its memory again"
|
||||
`(let ((heap ,heap))
|
||||
(set! (-> heap current) (-> heap base))
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro with-sp (&rest body)
|
||||
`(rlet ((sp :reg rsp :reset-here #t :type pointer))
|
||||
|
|
|
@ -722,9 +722,3 @@
|
|||
)
|
||||
)
|
||||
|
||||
(#when PC_PORT
|
||||
(when *debug-segment*
|
||||
;; shared temporary string.
|
||||
(define *debug-temp-string* (new 'debug 'string 4096 (the string #f)))
|
||||
)
|
||||
)
|
||||
|
|
|
@ -541,8 +541,16 @@
|
|||
(set! (-> s4-0 param 2) (the-as uint (target-pos 0)))
|
||||
(send-event-function *target* s4-0)
|
||||
)
|
||||
;; NOTE : added case for "training" here. in the original game, the training level does NOT come
|
||||
;; with its own code for warp gates and buttons, and uses the villagep-obs imported from village1
|
||||
;; instead. opengoal loads files different enough that warp from training to anywhere except village1
|
||||
;; crashes the game due to running unlinked code. the original game also crashes, but it is not consistent.
|
||||
;; the citadel/lavatube case makes it so we wait until it's safe to unload both levels in the heaps,
|
||||
;; since the citadel warp gate is located in both levels at once (visually lavatube, technically citadel)
|
||||
;; we add "training" to the list here so that the training warp gate waits until it's safe to
|
||||
;; dispose the old code from memory.
|
||||
(case (-> self level)
|
||||
(('citadel 'lavatube)
|
||||
(('citadel 'lavatube 'training)
|
||||
(while (and *target* (zero? (logand (-> *target* draw status) (draw-status hidden))))
|
||||
(suspend)
|
||||
)
|
||||
|
|
|
@ -31,6 +31,7 @@ add_library(compiler
|
|||
data_compiler/dir_tpages.cpp
|
||||
data_compiler/DataObjectGenerator.cpp
|
||||
data_compiler/game_count.cpp
|
||||
data_compiler/game_subtitle.cpp
|
||||
debugger/Debugger.cpp
|
||||
debugger/DebugInfo.cpp
|
||||
listener/Listener.cpp
|
||||
|
|
|
@ -20,14 +20,20 @@ Compiler::Compiler(const std::string& user_profile, std::unique_ptr<ReplWrapper>
|
|||
|
||||
// let the build system run us
|
||||
m_make.add_tool(std::make_shared<CompilerTool>(this));
|
||||
m_make.add_tool(std::make_shared<SubtitleTool>(this));
|
||||
|
||||
// load GOAL library
|
||||
Object library_code = m_goos.reader.read_from_file({"goal_src", "goal-lib.gc"});
|
||||
compile_object_file("goal-lib", library_code, false);
|
||||
|
||||
if (user_profile != "#f") {
|
||||
Object user_code = m_goos.reader.read_from_file({"goal_src", "user", user_profile, "user.gc"});
|
||||
compile_object_file(user_profile, user_code, false);
|
||||
try {
|
||||
Object user_code =
|
||||
m_goos.reader.read_from_file({"goal_src", "user", user_profile, "user.gc"});
|
||||
compile_object_file(user_profile, user_code, false);
|
||||
} catch (std::exception& e) {
|
||||
print_compiler_warning("REPL Warning: {}\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// add built-in forms to symbol info
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "goalc/emitter/Register.h"
|
||||
#include "goalc/listener/Listener.h"
|
||||
#include "goalc/make/MakeSystem.h"
|
||||
#include "goalc/data_compiler/game_subtitle.h"
|
||||
|
||||
enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID };
|
||||
|
||||
|
@ -53,6 +54,7 @@ class Compiler {
|
|||
listener::Listener& listener() { return m_listener; }
|
||||
void poke_target() { m_listener.send_poke(); }
|
||||
bool connect_to_target();
|
||||
GameSubtitleDB& subtitle_db() { return m_subtitle_db; }
|
||||
Replxx::completions_t find_symbols_by_prefix(std::string const& context,
|
||||
int& contextLen,
|
||||
std::vector<std::string> const& user_data);
|
||||
|
@ -83,6 +85,7 @@ class Compiler {
|
|||
SymbolInfoMap m_symbol_info;
|
||||
std::unique_ptr<ReplWrapper> m_repl;
|
||||
MakeSystem m_make;
|
||||
GameSubtitleDB m_subtitle_db;
|
||||
|
||||
struct DebugStats {
|
||||
int num_spills = 0;
|
||||
|
@ -550,6 +553,7 @@ class Compiler {
|
|||
Val* compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_repl_clear_screen(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_asm_data_file(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_asm_text_file(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_repl_help(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_listen_to_target(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
Val* compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env);
|
||||
|
|
|
@ -147,6 +147,7 @@ const std::unordered_map<
|
|||
{":exit", &Compiler::compile_exit},
|
||||
{"asm-file", &Compiler::compile_asm_file},
|
||||
{"asm-data-file", &Compiler::compile_asm_data_file},
|
||||
{"asm-text-file", &Compiler::compile_asm_text_file},
|
||||
{"listen-to-target", &Compiler::compile_listen_to_target},
|
||||
{"reset-target", &Compiler::compile_reset_target},
|
||||
{":status", &Compiler::compile_poke},
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "goalc/data_compiler/dir_tpages.h"
|
||||
#include "goalc/data_compiler/game_count.h"
|
||||
#include "goalc/data_compiler/game_text.h"
|
||||
#include "goalc/data_compiler/game_subtitle.h"
|
||||
|
||||
/*!
|
||||
* Exit the compiler. Disconnects the listener and tells the target to reset itself.
|
||||
|
@ -66,6 +67,7 @@ Val* Compiler::compile_asm_data_file(const goos::Object& form, const goos::Objec
|
|||
va_check(form, args, {goos::ObjectType::SYMBOL, goos::ObjectType::STRING}, {});
|
||||
auto kind = symbol_string(args.unnamed.at(0));
|
||||
if (kind == "game-text") {
|
||||
// TODO version
|
||||
compile_game_text(as_string(args.unnamed.at(1)));
|
||||
} else if (kind == "game-count") {
|
||||
compile_game_count(as_string(args.unnamed.at(1)));
|
||||
|
@ -77,6 +79,31 @@ Val* Compiler::compile_asm_data_file(const goos::Object& form, const goos::Objec
|
|||
return get_none();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compile a "text data file"
|
||||
*/
|
||||
Val* Compiler::compile_asm_text_file(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||
(void)env;
|
||||
auto args = get_va(form, rest);
|
||||
va_check(form, args, {goos::ObjectType::SYMBOL, goos::ObjectType::INTEGER},
|
||||
{{"files", {true, goos::ObjectType::PAIR}}});
|
||||
auto kind = symbol_string(args.unnamed.at(0));
|
||||
if (kind == "subtitle") {
|
||||
std::vector<std::string> files;
|
||||
for_each_in_list(args.named.at("files"), [this, &files, &form](const goos::Object& o) {
|
||||
if (o.is_string()) {
|
||||
files.push_back(o.as_string()->data);
|
||||
} else {
|
||||
throw_compiler_error(form, "Invalid object {} in asm-text-file files list.", o.print());
|
||||
}
|
||||
});
|
||||
compile_game_subtitle(files, (GameTextVersion)args.unnamed.at(1).as_int(), m_subtitle_db);
|
||||
} else {
|
||||
throw_compiler_error(form, "The option {} was not recognized for asm-text-file.", kind);
|
||||
}
|
||||
return get_none();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compile a file, and optionally color, save, or load.
|
||||
* This should only be used for v3 "code object" files.
|
||||
|
@ -620,4 +647,4 @@ Val* Compiler::compile_print_debug_compiler_stats(const goos::Object& form,
|
|||
fmt::print("Size of autocomplete prefix tree: {}\n", m_symbol_info.symbol_count());
|
||||
|
||||
return get_none();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,10 +56,9 @@ void DataObjectGenerator::link_word_to_word(int source, int target, int offset)
|
|||
}
|
||||
|
||||
void DataObjectGenerator::link_word_to_byte(int source_word, int target_byte) {
|
||||
PointerLinkRecord rec;
|
||||
auto& rec = m_ptr_links.emplace_back();
|
||||
rec.source_word = source_word;
|
||||
rec.target_byte = target_byte;
|
||||
m_ptr_links.push_back(rec);
|
||||
}
|
||||
|
||||
int DataObjectGenerator::add_ref_to_string_in_pool(const std::string& str) {
|
||||
|
|
203
goalc/data_compiler/game_subtitle.cpp
Normal file
203
goalc/data_compiler/game_subtitle.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*!
|
||||
* @file game_subtitle.cpp
|
||||
* Builds the XSUBTIT.TXT text files. Each file contains all the strings used as subtitles for
|
||||
* cutscenes, hints and ambient speech, along with the timings.
|
||||
*
|
||||
* This kind of file is completely custom.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <new>
|
||||
#include <queue>
|
||||
#include "game_subtitle.h"
|
||||
#include "DataObjectGenerator.h"
|
||||
#include "common/goos/Reader.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/goos/ParseHelpers.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
namespace {
|
||||
int64_t get_int(const goos::Object& obj) {
|
||||
if (obj.is_int()) {
|
||||
return obj.integer_obj.value;
|
||||
}
|
||||
throw std::runtime_error(obj.print() + " was supposed to be an integer, but isn't");
|
||||
}
|
||||
|
||||
const goos::Object& car(const goos::Object& x) {
|
||||
if (!x.is_pair()) {
|
||||
throw std::runtime_error("invalid pair");
|
||||
}
|
||||
|
||||
return x.as_pair()->car;
|
||||
}
|
||||
|
||||
const goos::Object& cdr(const goos::Object& x) {
|
||||
if (!x.is_pair()) {
|
||||
throw std::runtime_error("invalid pair");
|
||||
}
|
||||
|
||||
return x.as_pair()->cdr;
|
||||
}
|
||||
|
||||
std::string get_string(const goos::Object& x) {
|
||||
if (x.is_string()) {
|
||||
return x.as_string()->data;
|
||||
}
|
||||
throw std::runtime_error(x.print() + " was supposed to be a string, but isn't");
|
||||
}
|
||||
|
||||
std::string uppercase(const std::string& in) {
|
||||
std::string result;
|
||||
result.reserve(in.size());
|
||||
for (auto c : in) {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
c -= ('a' - 'A');
|
||||
}
|
||||
result.push_back(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Parse a game subtitle file.
|
||||
* Information is added to the game subtitles database.
|
||||
*
|
||||
* The file should begin with (language-id x y z...) with the given language IDs.
|
||||
* Each entry should be (name (frame "line-text-0" "line-text-1") ... )
|
||||
* This adds the subtitle to each of the specified languages.
|
||||
*/
|
||||
void parse(const goos::Object& data, GameTextVersion text_ver, GameSubtitleDB& db) {
|
||||
auto font = get_font_bank(text_ver);
|
||||
std::map<int, GameSubtitleBank*> banks;
|
||||
bool languages_set = false;
|
||||
|
||||
for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) {
|
||||
if (obj.is_pair()) {
|
||||
auto& head = car(obj);
|
||||
if (head.is_symbol() && head.as_symbol()->name == "language-id") {
|
||||
if (languages_set) {
|
||||
throw std::runtime_error("Languages have been set multiple times.");
|
||||
}
|
||||
|
||||
if (cdr(obj).is_empty_list()) {
|
||||
throw std::runtime_error("At least one language must be set.");
|
||||
}
|
||||
|
||||
for_each_in_list(cdr(obj), [&](const goos::Object& obj) {
|
||||
auto lang = get_int(obj);
|
||||
if (!db.bank_exists(lang)) {
|
||||
// database has no lang yet
|
||||
banks[lang] = db.new_bank(lang);
|
||||
} else {
|
||||
banks[lang] = db.bank_by_id(lang);
|
||||
}
|
||||
});
|
||||
|
||||
languages_set = true;
|
||||
}
|
||||
|
||||
else if (head.is_string()) {
|
||||
if (!languages_set) {
|
||||
throw std::runtime_error("At least one language must be set before defining entries.");
|
||||
}
|
||||
GameSubtitleSceneInfo scene(head.as_string()->data);
|
||||
for_each_in_list(cdr(obj), [&](const goos::Object& entry) {
|
||||
if (entry.is_pair()) {
|
||||
if (!car(entry).is_int() || !car(cdr(entry)).is_symbol() ||
|
||||
!car(cdr(cdr(entry))).is_string() || !car(cdr(cdr(cdr(entry)))).is_string()) {
|
||||
throw std::runtime_error(
|
||||
"Each entry must be of format (number symbol \"string\" \"string\")");
|
||||
}
|
||||
|
||||
auto line = font->convert_utf8_to_game(car(cdr(cdr(cdr(entry)))).as_string()->data);
|
||||
auto speaker = font->convert_utf8_to_game(car(cdr(cdr(entry))).as_string()->data);
|
||||
auto offscreen = car(cdr(entry)).as_symbol()->name != "#f";
|
||||
scene.add_line(car(entry).as_int(), line, speaker, offscreen);
|
||||
} else {
|
||||
throw std::runtime_error("Each entry must be a list");
|
||||
}
|
||||
});
|
||||
for (auto& [lang, bank] : banks) {
|
||||
if (!bank->scene_exists(scene.name())) {
|
||||
bank->add_scene(scene);
|
||||
} else {
|
||||
// this should copy the data, so it's safe to delete the new one afterwards.
|
||||
auto& old_scene = bank->scene_by_name(scene.name());
|
||||
old_scene.from_other_scene(scene);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Invalid game subtitles file entry: " + head.print());
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Invalid game subtitles file");
|
||||
}
|
||||
});
|
||||
if (!languages_set) {
|
||||
throw std::runtime_error("At least one language must be set.");
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Write game subtitle data to a file. Uses the V2 object format which is identical between GOAL and
|
||||
* OpenGOAL.
|
||||
*/
|
||||
void compile(GameSubtitleDB& db) {
|
||||
for (const auto& [lang, bank] : db.banks()) {
|
||||
DataObjectGenerator gen;
|
||||
gen.add_type_tag("subtitle-text-info"); // type
|
||||
gen.add_word(bank->scenes().size()); // length
|
||||
gen.add_word(lang); // lang
|
||||
gen.add_word(0); // dummy
|
||||
|
||||
// fifo queue for scene data arrays
|
||||
std::queue<int> array_link_sources;
|
||||
// now add all the scene infos
|
||||
for (auto& [name, scene] : bank->scenes()) {
|
||||
gen.add_word(0 |
|
||||
(scene.lines().size() << 16)); // kind (lower 16 bits), length (upper 16 bits)
|
||||
|
||||
array_link_sources.push(gen.words());
|
||||
gen.add_word(0); // keyframes (linked later)
|
||||
|
||||
gen.add_ref_to_string_in_pool(scene.name()); // name
|
||||
|
||||
gen.add_word(0); // pad (string is 4 bytes but we have 8)
|
||||
}
|
||||
// now add all the scene *data!* (keyframes)
|
||||
for (auto& [name, scene] : bank->scenes()) {
|
||||
// link inline-array with reference from earlier
|
||||
gen.link_word_to_word(array_link_sources.front(), gen.words());
|
||||
array_link_sources.pop();
|
||||
|
||||
for (auto& subtitle : scene.lines()) {
|
||||
gen.add_word(subtitle.frame); // frame
|
||||
gen.add_ref_to_string_in_pool(subtitle.line); // line
|
||||
gen.add_ref_to_string_in_pool(subtitle.speaker); // speaker
|
||||
gen.add_symbol_link(subtitle.offscreen ? "#t" : "#f"); // offscreen
|
||||
}
|
||||
}
|
||||
|
||||
auto data = gen.generate_v2();
|
||||
|
||||
file_util::create_dir_if_needed(file_util::get_file_path({"out", "iso"}));
|
||||
file_util::write_binary_file(
|
||||
file_util::get_file_path(
|
||||
{"out", "iso", fmt::format("{}{}.TXT", lang, uppercase("subtit"))}),
|
||||
data.data(), data.size());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void compile_game_subtitle(const std::vector<std::string>& filenames,
|
||||
GameTextVersion text_ver,
|
||||
GameSubtitleDB& db) {
|
||||
goos::Reader reader;
|
||||
for (auto& filename : filenames) {
|
||||
fmt::print("[Build Game Subtitle] {}\n", filename.c_str());
|
||||
auto code = reader.read_from_file({filename});
|
||||
parse(code, text_ver, db);
|
||||
}
|
||||
compile(db);
|
||||
}
|
92
goalc/data_compiler/game_subtitle.h
Normal file
92
goalc/data_compiler/game_subtitle.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
#include "common/util/FontUtils.h"
|
||||
#include "common/util/Assert.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
class GameSubtitleSceneInfo {
|
||||
public:
|
||||
GameSubtitleSceneInfo() {}
|
||||
GameSubtitleSceneInfo(std::string name) : m_name(name) {}
|
||||
|
||||
struct SubtitleLine {
|
||||
SubtitleLine(int frame, std::string line, std::string speaker, bool offscreen)
|
||||
: frame(frame), line(line), speaker(speaker), offscreen(offscreen) {}
|
||||
|
||||
int frame;
|
||||
std::string line;
|
||||
std::string speaker;
|
||||
bool offscreen;
|
||||
};
|
||||
|
||||
const std::string& name() const { return m_name; }
|
||||
const std::vector<SubtitleLine>& lines() const { return m_lines; }
|
||||
void clear_lines() { m_lines.clear(); }
|
||||
void from_other_scene(GameSubtitleSceneInfo& scene) {
|
||||
m_name = scene.name();
|
||||
m_lines = scene.lines();
|
||||
}
|
||||
|
||||
void add_line(int frame, std::string line, std::string speaker, bool offscreen) {
|
||||
m_lines.emplace_back(frame, line, speaker, offscreen);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::vector<SubtitleLine> m_lines;
|
||||
};
|
||||
|
||||
class GameSubtitleBank {
|
||||
public:
|
||||
GameSubtitleBank(int lang_id) : m_lang_id(lang_id) {}
|
||||
|
||||
int lang() const { return m_lang_id; }
|
||||
const std::map<std::string, GameSubtitleSceneInfo>& scenes() const { return m_scenes; }
|
||||
|
||||
bool scene_exists(const std::string& name) const { return m_scenes.find(name) != m_scenes.end(); }
|
||||
GameSubtitleSceneInfo& scene_by_name(const std::string& name) { return m_scenes.at(name); }
|
||||
void add_scene(GameSubtitleSceneInfo& scene) {
|
||||
ASSERT(!scene_exists(scene.name()));
|
||||
m_scenes[scene.name()] = scene;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_lang_id;
|
||||
|
||||
std::map<std::string, GameSubtitleSceneInfo> m_scenes;
|
||||
};
|
||||
|
||||
/*!
|
||||
* The subtitles database contains a subtitbles bank for each language.
|
||||
* Each subtitles bank contains a series of subtitle scene infos.
|
||||
*/
|
||||
class GameSubtitleDB {
|
||||
public:
|
||||
const std::map<int, GameSubtitleBank*>& banks() const { return m_banks; }
|
||||
|
||||
bool bank_exists(int id) const { return m_banks.find(id) != m_banks.end(); }
|
||||
|
||||
GameSubtitleBank* new_bank(int id) {
|
||||
ASSERT(!bank_exists(id));
|
||||
m_banks[id] = new GameSubtitleBank(id);
|
||||
return m_banks.at(id);
|
||||
}
|
||||
void add_bank(GameSubtitleBank* bank) {
|
||||
ASSERT(!bank_exists(bank->lang()));
|
||||
m_banks[bank->lang()] = bank;
|
||||
}
|
||||
GameSubtitleBank* bank_by_id(int id) {
|
||||
if (!bank_exists(id)) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_banks.at(id);
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<int, GameSubtitleBank*> m_banks;
|
||||
};
|
||||
|
||||
void compile_game_subtitle(const std::vector<std::string>& filenames,
|
||||
GameTextVersion text_ver,
|
||||
GameSubtitleDB& db);
|
|
@ -13,23 +13,10 @@
|
|||
#include "DataObjectGenerator.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/FontUtils.h"
|
||||
#include "common/goos/ParseHelpers.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
void for_each_in_list(const goos::Object& list, const T& f) {
|
||||
const goos::Object* iter = &list;
|
||||
while (iter->is_pair()) {
|
||||
auto lap = iter->as_pair();
|
||||
f(lap->car);
|
||||
iter = &lap->cdr;
|
||||
}
|
||||
|
||||
if (!iter->is_empty_list()) {
|
||||
throw std::runtime_error("Invalid list");
|
||||
}
|
||||
}
|
||||
|
||||
int64_t get_int(const goos::Object& obj) {
|
||||
if (obj.is_int()) {
|
||||
return obj.integer_obj.value;
|
||||
|
@ -95,6 +82,7 @@ std::vector<std::unordered_map<int, std::string>> parse(const goos::Object& data
|
|||
if (languages_set) {
|
||||
throw std::runtime_error("Languages has been set multiple times.");
|
||||
}
|
||||
languages_set = true;
|
||||
|
||||
text.resize(get_int(car(cdr(obj))));
|
||||
if (!cdr(cdr(obj)).is_empty_list()) {
|
||||
|
|
|
@ -11,7 +11,15 @@
|
|||
#include "goalc/make/Tools.h"
|
||||
|
||||
std::string MakeStep::print() const {
|
||||
std::string result = fmt::format("Tool {} with input \"{}\" and deps:\n ", tool, input);
|
||||
std::string result = fmt::format("Tool {} with inputs", tool);
|
||||
int i = 0;
|
||||
for (auto& in : input) {
|
||||
if (i++ > 0) {
|
||||
result += ", ";
|
||||
}
|
||||
result += fmt::format("\"{}\"", in);
|
||||
}
|
||||
result += " and deps:\n ";
|
||||
for (auto& dep : deps) {
|
||||
result += dep;
|
||||
result += '\n';
|
||||
|
@ -75,8 +83,9 @@ goos::Object MakeSystem::handle_defstep(const goos::Object& form,
|
|||
va_check(form, args, {},
|
||||
{{"out", {true, {goos::ObjectType::PAIR}}},
|
||||
{"tool", {true, {goos::ObjectType::SYMBOL}}},
|
||||
{"in", {false, {goos::ObjectType::STRING}}},
|
||||
{"dep", {false, {}}}});
|
||||
{"in", {false, {}}},
|
||||
{"dep", {false, {}}},
|
||||
{"arg", {false, {}}}});
|
||||
|
||||
auto step = std::make_shared<MakeStep>();
|
||||
|
||||
|
@ -91,7 +100,14 @@ goos::Object MakeSystem::handle_defstep(const goos::Object& form,
|
|||
}
|
||||
|
||||
if (args.has_named("in")) {
|
||||
step->input = args.get_named("in").as_string()->data;
|
||||
const auto& in = args.get_named("in");
|
||||
if (in.is_pair()) {
|
||||
step->input.clear();
|
||||
goos::for_each_in_list(
|
||||
in, [&](const goos::Object& o) { step->input.push_back(o.as_string()->data); });
|
||||
} else {
|
||||
step->input = {in.as_string()->data};
|
||||
}
|
||||
}
|
||||
|
||||
if (args.has_named("dep")) {
|
||||
|
@ -100,6 +116,12 @@ goos::Object MakeSystem::handle_defstep(const goos::Object& form,
|
|||
});
|
||||
}
|
||||
|
||||
if (args.has_named("arg")) {
|
||||
step->arg = args.get_named("arg");
|
||||
} else {
|
||||
step->arg = goos::Object::make_empty_list();
|
||||
}
|
||||
|
||||
for (auto& output : step->outputs) {
|
||||
auto existing = m_output_to_step.find(output);
|
||||
if (existing != m_output_to_step.end()) {
|
||||
|
@ -172,8 +194,9 @@ void MakeSystem::get_dependencies(const std::string& master_target,
|
|||
}
|
||||
|
||||
const auto& rule = rule_it->second;
|
||||
for (auto& dep : m_tools.at(rule->tool)
|
||||
->get_additional_dependencies({rule->input, rule->deps, rule->outputs})) {
|
||||
for (auto& dep :
|
||||
m_tools.at(rule->tool)
|
||||
->get_additional_dependencies({rule->input, rule->deps, rule->outputs, rule->arg})) {
|
||||
get_dependencies(master_target, dep, result, result_set);
|
||||
}
|
||||
|
||||
|
@ -212,10 +235,11 @@ std::vector<std::string> MakeSystem::filter_dependencies(const std::vector<std::
|
|||
for (auto& to_make : all_deps) {
|
||||
auto& rule = m_output_to_step.at(to_make);
|
||||
auto& tool = m_tools.at(rule->tool);
|
||||
const ToolInput task = {rule->input, rule->deps, rule->outputs, rule->arg};
|
||||
|
||||
bool added = false;
|
||||
|
||||
if (tool->needs_run({rule->input, rule->deps, rule->outputs})) {
|
||||
if (tool->needs_run(task)) {
|
||||
result.push_back(to_make);
|
||||
stale_deps.insert(to_make);
|
||||
added = true;
|
||||
|
@ -235,8 +259,7 @@ std::vector<std::string> MakeSystem::filter_dependencies(const std::vector<std::
|
|||
|
||||
if (!added) {
|
||||
// check transitive dependencies
|
||||
for (auto& dep :
|
||||
tool->get_additional_dependencies({rule->input, rule->deps, rule->outputs})) {
|
||||
for (auto& dep : tool->get_additional_dependencies(task)) {
|
||||
if (stale_deps.find(dep) != stale_deps.end()) {
|
||||
result.push_back(to_make);
|
||||
stale_deps.insert(to_make);
|
||||
|
@ -253,11 +276,19 @@ std::vector<std::string> MakeSystem::filter_dependencies(const std::vector<std::
|
|||
}
|
||||
|
||||
namespace {
|
||||
void print_input(const std::string& in, char end) {
|
||||
if (in.length() > 70) {
|
||||
fmt::print("{}...{}", in.substr(0, 70 - 3), end);
|
||||
void print_input(const std::vector<std::string>& in, char end) {
|
||||
int i = 0;
|
||||
std::string all_names;
|
||||
for (auto& name : in) {
|
||||
if (i++ > 0) {
|
||||
all_names += ", ";
|
||||
}
|
||||
all_names += name;
|
||||
}
|
||||
if (all_names.length() > 70) {
|
||||
fmt::print("{}...{}", all_names.substr(0, 70 - 3), end);
|
||||
} else {
|
||||
fmt::print("{}{}{}", in, std::string(70 - in.length(), ' '), end);
|
||||
fmt::print("{}{}{}", all_names, std::string(70 - all_names.length(), ' '), end);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
@ -286,7 +317,8 @@ bool MakeSystem::make(const std::string& target, bool force, bool verbose) {
|
|||
auto& tool = m_tools.at(rule->tool);
|
||||
int percent = (100.0 * (1 + (i++)) / (deps.size())) + 0.5;
|
||||
if (verbose) {
|
||||
fmt::print("[{:3d}%] [{:8s}] {}\n", percent, tool->name(), rule->input);
|
||||
fmt::print("[{:3d}%] [{:8s}] {}{}\n", percent, tool->name(), rule->input.at(0),
|
||||
rule->input.size() > 1 ? ", ..." : "");
|
||||
} else {
|
||||
fmt::print("[{:3d}%] [{:8s}] ", percent, tool->name());
|
||||
print_input(rule->input, '\r');
|
||||
|
@ -294,13 +326,14 @@ bool MakeSystem::make(const std::string& target, bool force, bool verbose) {
|
|||
|
||||
bool success = false;
|
||||
try {
|
||||
success = tool->run({rule->input, rule->deps, rule->outputs});
|
||||
success = tool->run({rule->input, rule->deps, rule->outputs, rule->arg});
|
||||
} catch (std::exception& e) {
|
||||
fmt::print("\n");
|
||||
fmt::print("Error: {}\n", e.what());
|
||||
}
|
||||
if (!success) {
|
||||
fmt::print("Build failed on {}.\n", rule->input);
|
||||
fmt::print("Build failed on {}{}.\n", rule->input.at(0),
|
||||
rule->input.size() > 1 ? ", ..." : "");
|
||||
throw std::runtime_error("Build failed.");
|
||||
return false;
|
||||
}
|
||||
|
@ -329,4 +362,4 @@ bool MakeSystem::make(const std::string& target, bool force, bool verbose) {
|
|||
fmt::print("\nSuccessfully built all {} targets in {:.3f}s\n", deps.size(),
|
||||
make_timer.getSeconds());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
#include "goalc/make/Tool.h"
|
||||
|
||||
struct MakeStep {
|
||||
std::string input;
|
||||
std::vector<std::string> input;
|
||||
std::vector<std::string> deps, outputs;
|
||||
goos::Object arg;
|
||||
std::string tool;
|
||||
|
||||
std::string print() const;
|
||||
|
|
|
@ -12,46 +12,48 @@ Tool::Tool(const std::string& name) : m_name(name) {}
|
|||
bool Tool::needs_run(const ToolInput& task) {
|
||||
// for this to return false, all outputs need to be newer than all inputs.
|
||||
|
||||
auto in_file = std::filesystem::path(file_util::get_file_path({task.input}));
|
||||
for (auto& in : task.input) {
|
||||
auto in_file = std::filesystem::path(file_util::get_file_path({in}));
|
||||
|
||||
if (!std::filesystem::exists(in_file)) {
|
||||
throw std::runtime_error(fmt::format("Input file {} does not exist.", task.input));
|
||||
}
|
||||
|
||||
auto newest_input = std::filesystem::last_write_time(in_file);
|
||||
for (auto& dep : task.deps) {
|
||||
auto dep_path = std::filesystem::path(file_util::get_file_path({dep}));
|
||||
if (std::filesystem::exists(dep_path)) {
|
||||
auto dep_time = std::filesystem::last_write_time(dep_path);
|
||||
if (dep_time > newest_input) {
|
||||
newest_input = dep_time;
|
||||
}
|
||||
} else {
|
||||
return true; // don't have a dep.
|
||||
if (!std::filesystem::exists(in_file)) {
|
||||
throw std::runtime_error(fmt::format("Input file {} does not exist.", in));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& dep : get_additional_dependencies(task)) {
|
||||
auto dep_path = std::filesystem::path(file_util::get_file_path({dep}));
|
||||
if (std::filesystem::exists(dep_path)) {
|
||||
auto dep_time = std::filesystem::last_write_time(dep_path);
|
||||
if (dep_time > newest_input) {
|
||||
newest_input = dep_time;
|
||||
auto newest_input = std::filesystem::last_write_time(in_file);
|
||||
for (auto& dep : task.deps) {
|
||||
auto dep_path = std::filesystem::path(file_util::get_file_path({dep}));
|
||||
if (std::filesystem::exists(dep_path)) {
|
||||
auto dep_time = std::filesystem::last_write_time(dep_path);
|
||||
if (dep_time > newest_input) {
|
||||
newest_input = dep_time;
|
||||
}
|
||||
} else {
|
||||
return true; // don't have a dep.
|
||||
}
|
||||
} else {
|
||||
return true; // don't have a dep.
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& out : task.output) {
|
||||
auto out_path = std::filesystem::path(file_util::get_file_path({out}));
|
||||
if (std::filesystem::exists(out_path)) {
|
||||
auto out_time = std::filesystem::last_write_time(out_path);
|
||||
if (out_time < newest_input) {
|
||||
return true;
|
||||
for (auto& dep : get_additional_dependencies(task)) {
|
||||
auto dep_path = std::filesystem::path(file_util::get_file_path({dep}));
|
||||
if (std::filesystem::exists(dep_path)) {
|
||||
auto dep_time = std::filesystem::last_write_time(dep_path);
|
||||
if (dep_time > newest_input) {
|
||||
newest_input = dep_time;
|
||||
}
|
||||
} else {
|
||||
return true; // don't have a dep.
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& out : task.output) {
|
||||
auto out_path = std::filesystem::path(file_util::get_file_path({out}));
|
||||
if (std::filesystem::exists(out_path)) {
|
||||
auto out_time = std::filesystem::last_write_time(out_path);
|
||||
if (out_time < newest_input) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true; // don't have a dep.
|
||||
}
|
||||
} else {
|
||||
return true; // don't have a dep.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/goos/Object.h"
|
||||
|
||||
struct ToolInput {
|
||||
std::string& input; // the input file
|
||||
std::vector<std::string>& input; // the input file
|
||||
std::vector<std::string>& deps; // explicit dependencies
|
||||
std::vector<std::string>& output; // produced output files.
|
||||
goos::Object arg; // optional argument
|
||||
};
|
||||
|
||||
class Tool {
|
||||
|
@ -22,4 +24,4 @@ class Tool {
|
|||
|
||||
private:
|
||||
std::string m_name;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
CompilerTool::CompilerTool(Compiler* compiler) : Tool("goalc"), m_compiler(compiler) {}
|
||||
|
||||
bool CompilerTool::needs_run(const ToolInput& task) {
|
||||
if (!m_compiler->knows_object_file(std::filesystem::path(task.input).stem().u8string())) {
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
|
||||
if (!m_compiler->knows_object_file(std::filesystem::path(task.input.at(0)).stem().u8string())) {
|
||||
return true;
|
||||
}
|
||||
return Tool::needs_run(task);
|
||||
|
@ -25,7 +29,7 @@ bool CompilerTool::run(const ToolInput& task) {
|
|||
// todo check inputs
|
||||
try {
|
||||
m_compiler->run_front_end_on_string(
|
||||
fmt::format("(asm-file \"{}\" :no-time-prints :color :write)", task.input));
|
||||
fmt::format("(asm-file \"{}\" :no-time-prints :color :write)", task.input.at(0)));
|
||||
} catch (std::exception& e) {
|
||||
fmt::print("Compilation failed: {}\n", e.what());
|
||||
return false;
|
||||
|
@ -35,16 +39,16 @@ bool CompilerTool::run(const ToolInput& task) {
|
|||
|
||||
namespace {
|
||||
DgoDescription parse_desc_file(const std::string& filename, goos::Reader& reader) {
|
||||
auto dgo_desc = reader.read_from_file({filename}).as_pair()->cdr;
|
||||
auto& dgo_desc = reader.read_from_file({filename}).as_pair()->cdr;
|
||||
if (goos::list_length(dgo_desc) != 1) {
|
||||
throw std::runtime_error("Invalid DGO description - got too many lists");
|
||||
}
|
||||
auto dgo = dgo_desc.as_pair()->car;
|
||||
auto& dgo = dgo_desc.as_pair()->car;
|
||||
|
||||
DgoDescription desc;
|
||||
auto first = dgo.as_pair()->car;
|
||||
auto& first = dgo.as_pair()->car;
|
||||
desc.dgo_name = first.as_string()->data;
|
||||
auto dgo_rest = dgo.as_pair()->cdr;
|
||||
auto& dgo_rest = dgo.as_pair()->cdr;
|
||||
|
||||
for_each_in_list(dgo_rest, [&](const goos::Object& entry) {
|
||||
goos::Arguments e_arg;
|
||||
|
@ -63,19 +67,49 @@ DgoDescription parse_desc_file(const std::string& filename, goos::Reader& reader
|
|||
});
|
||||
return desc;
|
||||
}
|
||||
|
||||
static const std::unordered_map<std::string, GameTextVersion> s_text_ver_enum_map = {
|
||||
{"jak1-v1", GameTextVersion::JAK1_V1}};
|
||||
|
||||
std::unordered_map<GameTextVersion, std::vector<std::string>> open_subtitle_project(
|
||||
const std::string& filename) {
|
||||
goos::Reader reader;
|
||||
auto& proj = reader.read_from_file({filename}).as_pair()->cdr.as_pair()->car;
|
||||
if (!proj.is_pair() || !proj.as_pair()->car.is_symbol() ||
|
||||
proj.as_pair()->car.as_symbol()->name != "subtitle") {
|
||||
throw std::runtime_error("invalid subtitle project");
|
||||
}
|
||||
|
||||
std::unordered_map<GameTextVersion, std::vector<std::string>> inputs;
|
||||
goos::for_each_in_list(proj.as_pair()->cdr, [&](const goos::Object& o) {
|
||||
if (!o.is_pair()) {
|
||||
throw std::runtime_error("invalid entry in subtitle project");
|
||||
}
|
||||
|
||||
auto& ver = o.as_pair()->car.as_symbol()->name;
|
||||
auto& in = o.as_pair()->cdr.as_pair()->car.as_string()->data;
|
||||
|
||||
inputs[s_text_ver_enum_map.at(ver)].push_back(in);
|
||||
});
|
||||
|
||||
return inputs;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DgoTool::DgoTool() : Tool("dgo") {}
|
||||
|
||||
bool DgoTool::run(const ToolInput& task) {
|
||||
auto desc = parse_desc_file(task.input, m_reader);
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
auto desc = parse_desc_file(task.input.at(0), m_reader);
|
||||
build_dgo(desc);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> DgoTool::get_additional_dependencies(const ToolInput& task) {
|
||||
std::vector<std::string> result;
|
||||
auto desc = parse_desc_file(task.input, m_reader);
|
||||
auto desc = parse_desc_file(task.input.at(0), m_reader);
|
||||
for (auto& x : desc.entries) {
|
||||
result.push_back(fmt::format("out/obj/{}", x.file_name));
|
||||
}
|
||||
|
@ -85,15 +119,21 @@ std::vector<std::string> DgoTool::get_additional_dependencies(const ToolInput& t
|
|||
TpageDirTool::TpageDirTool() : Tool("tpage-dir") {}
|
||||
|
||||
bool TpageDirTool::run(const ToolInput& task) {
|
||||
compile_dir_tpages(task.input);
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
compile_dir_tpages(task.input.at(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
CopyTool::CopyTool() : Tool("copy") {}
|
||||
|
||||
bool CopyTool::run(const ToolInput& task) {
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
for (auto& out : task.output) {
|
||||
std::filesystem::copy(std::filesystem::path(file_util::get_file_path({task.input})),
|
||||
std::filesystem::copy(std::filesystem::path(file_util::get_file_path({task.input.at(0)})),
|
||||
std::filesystem::path(file_util::get_file_path({out})),
|
||||
std::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
|
@ -103,14 +143,20 @@ bool CopyTool::run(const ToolInput& task) {
|
|||
GameCntTool::GameCntTool() : Tool("game-cnt") {}
|
||||
|
||||
bool GameCntTool::run(const ToolInput& task) {
|
||||
compile_game_count(task.input);
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
compile_game_count(task.input.at(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
TextTool::TextTool() : Tool("text") {}
|
||||
|
||||
bool TextTool::run(const ToolInput& task) {
|
||||
compile_game_text(task.input);
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
compile_game_text(task.input.at(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -118,4 +164,27 @@ GroupTool::GroupTool() : Tool("group") {}
|
|||
|
||||
bool GroupTool::run(const ToolInput&) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
SubtitleTool::SubtitleTool(Compiler* compiler) : Tool("subtitle"), m_compiler(compiler) {}
|
||||
|
||||
bool SubtitleTool::needs_run(const ToolInput& task) {
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
|
||||
std::vector<std::string> deps;
|
||||
for (auto& [ver, inputs] : open_subtitle_project(task.input.at(0))) {
|
||||
for (auto& in : inputs) {
|
||||
deps.push_back(in);
|
||||
}
|
||||
}
|
||||
return Tool::needs_run({task.input, deps, task.output, task.arg});
|
||||
}
|
||||
|
||||
bool SubtitleTool::run(const ToolInput& task) {
|
||||
for (auto& [ver, in] : open_subtitle_project(task.input.at(0))) {
|
||||
compile_game_subtitle(in, ver, m_compiler->subtitle_db());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
#include "goalc/make/Tool.h"
|
||||
#include "common/goos/Reader.h"
|
||||
#include "goalc/data_compiler/game_subtitle.h"
|
||||
|
||||
class Compiler;
|
||||
|
||||
class CompilerTool : public Tool {
|
||||
public:
|
||||
CompilerTool(Compiler* compiler);
|
||||
|
||||
bool run(const ToolInput& task) override;
|
||||
bool needs_run(const ToolInput& task) override;
|
||||
|
||||
|
@ -54,4 +54,14 @@ class GroupTool : public Tool {
|
|||
public:
|
||||
GroupTool();
|
||||
bool run(const ToolInput& task) override;
|
||||
};
|
||||
};
|
||||
|
||||
class SubtitleTool : public Tool {
|
||||
public:
|
||||
SubtitleTool(Compiler* compiler);
|
||||
bool run(const ToolInput& task) override;
|
||||
bool needs_run(const ToolInput& task) override;
|
||||
|
||||
private:
|
||||
Compiler* m_compiler;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue