Subtitle.c 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447
  1. /*=============================================================================
  2. Name : Subtitle.c
  3. Purpose : Functions for printing subtitles in time with the speech
  4. Created 2/3/1999 by lmoloney
  5. Copyright Relic Entertainment, Inc. All rights reserved.
  6. =============================================================================*/
  7. #define WIN32_LEAN_AND_MEAN
  8. #include <windows.h>
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include "glinc.h"
  12. #include "debug.h"
  13. #include "memory.h"
  14. #include "font.h"
  15. #include "color.h"
  16. #include "statscript.h"
  17. #include "font.h"
  18. #include "fontReg.h"
  19. #include "main.h"
  20. #include "key.h"
  21. #include "universe.h"
  22. #include "teams.h"
  23. #include "mainrgn.h"
  24. #include "sensors.h"
  25. #include "nis.h"
  26. #include "render.h"
  27. #include "fereg.h"
  28. #include "strings.h"
  29. #include "subtitle.h"
  30. /*=============================================================================
  31. Data:
  32. =============================================================================*/
  33. //strings for testing
  34. #if SUB_MODULE_TEST
  35. char *subTestSubtitle[NINEKEY - ONEKEY + 1] =
  36. {
  37. "A short string",
  38. "This page was originally made to tell what I saw and what had occured to me during my 1994-1995 trip to the U.S. Amundsen-Scott South Pole Station. However the process of writing the text and preparing the pictures took so much time that I had to halt for a while so that I can pass all those courses that I need in order to stay in the Graduate School. The result is that now I have come back from the second (now the third, fourth .. ) trip which I have some thing new to tell, but I still have unfinished first-year stuff to write. So here are some stories from the 1995 trip and writings based on later ones. \n\nPhotographic notes: Most photographic information are not directly put in the text. A lot of pictures are not serious shots. Here are some photographic related information. #n#nView them the right way: There are a lot of pictures in my web pages. If you find pictures on your screen too dark or too bright, probably you need to adjust gamma of your monitor. I created all images that are used here with the gamma set to 2.2.The best viewing environment is when there is no light directly shining on your screen. Adjust your monitor so that the center block in this image is indistinguishable against the grey part when you look at it from 6 to 8 feet away; and the border is as dark as possible. This will make your monitor have a closer presentation to mine. However I was told it could be hard to achieve on Macs. \n\nAnyway, enjoy the stories! ",
  39. "A meduim-length string withSomeReallyLongWordsToReallyMessWithTheWordWrappingLogic.ThisOutghToMessThemUp!",
  40. NULL,
  41. NULL,
  42. NULL,
  43. NULL,
  44. NULL,
  45. NULL
  46. };
  47. real32 subTestSubtitleTime[NINEKEY - ONEKEY + 1] =
  48. {
  49. 1.0f,
  50. 12.0f,
  51. 2.0f,
  52. 1.0f,
  53. 1.0f,
  54. 1.0f,
  55. 1.0f,
  56. 1.0f,
  57. 1.0f
  58. };
  59. #endif //SUB_MODULE_TEST
  60. //semaphore for communicating between stream thread and main game thread
  61. HANDLE subSemaphore;
  62. //pointer to timer to use currently. May be different depending on NIS's etc.
  63. real32 *subTimeElapsed = NULL;
  64. //set to TRUE when a fleet intel event ends for the benifit of KAS
  65. sdword subMessageEnded = FALSE;
  66. //retained copy of the player's race and all team colors so we know when to
  67. //re-load the speech icon textures.
  68. ShipRace subLastRace;
  69. trcolorinfo subLastTeamColors[TE_NumberPlayers];
  70. /*-----------------------------------------------------------------------------
  71. This next section of data should only be accessed under the protection
  72. of the previous semaphore.
  73. -----------------------------------------------------------------------------*/
  74. subnewsubtitle subNewSubtitles[SUB_NumberNewSubtitles];
  75. sdword subNumberNewSubtitles;
  76. #if SUB_VERBOSE_LEVEL >= 1
  77. sdword subMissedSubtitles;
  78. #endif
  79. /*-----------------------------------------------------------------------------
  80. Subtitle themes and printing regions.
  81. -----------------------------------------------------------------------------*/
  82. subregion subRegion[SUB_NumberRegions];
  83. subtheme subTheme[SUB_NumberThemes];
  84. /*-----------------------------------------------------------------------------
  85. General subtitle stuff
  86. -----------------------------------------------------------------------------*/
  87. real32 subScrollDwellStart = SUB_ScrollDwellStart;
  88. real32 subScrollDwellEnd = SUB_ScrollDwellEnd;
  89. real32 subScrollShortest = SUB_ScrollShortest;
  90. real32 subTitleShortest = SUB_TitleShortest;
  91. bool8 subCloseCaptionsEnabled = TRUE;
  92. /*-----------------------------------------------------------------------------
  93. Script parsing table for subtitle.script.
  94. -----------------------------------------------------------------------------*/
  95. void subThemeColorSet(char *directory, char *field, void *dataToFillIn);
  96. void subThemeDropshadowSet(char *directory, char *field, void *dataToFillIn);
  97. void subThemeMarginSet(char *directory, char *field, void *dataToFillIn);
  98. void subThemeFontSet(char *directory, char *field, void *dataToFillIn);
  99. void subThemeFadeSet(char *directory, char *field, void *dataToFillIn);
  100. void subRegionBoundsSet(char *directory, char *field, void *dataToFillIn);
  101. void subRegionScaleRezSet(char *directory, char *field, void *dataToFillIn);
  102. void subRegionMaxLinesSet(char *directory, char *field, void *dataToFillIn);
  103. void subThemeCentredSet(char *directory, char *field, void *dataToFillIn);
  104. void subThemePictureSet(char *directory, char *field, void *dataToFillIn);
  105. void subRegionIconOffsetSet(char *directory, char *field, void *dataToFillIn);
  106. scriptEntry subScriptTable[] =
  107. {
  108. //define theme parameters
  109. { "themeColor", subThemeColorSet, NULL},
  110. { "themeDropshadow", subThemeDropshadowSet, NULL},
  111. { "themeMargin", subThemeMarginSet, NULL},
  112. { "themeFont", subThemeFontSet, NULL},
  113. { "themeFadeIn", subThemeFadeSet, (void *)FALSE},
  114. { "themeFadeOut", subThemeFadeSet, (void *)TRUE},
  115. { "themePicture", subThemePictureSet, (void *)TRUE},
  116. { "themeCentred", subThemeCentredSet, (void *)TRUE},
  117. //define printing regions
  118. { "regionBounds", subRegionBoundsSet, NULL},
  119. { "regionScaleRez", subRegionScaleRezSet, NULL},
  120. { "regionMaxLines", subRegionMaxLinesSet, NULL},
  121. { "regionIconOffset", subRegionIconOffsetSet, NULL},
  122. //general stuff
  123. { "scrollDwellStart", scriptSetReal32CB, &subScrollDwellStart},
  124. { "scrollDwellEnd", scriptSetReal32CB, &subScrollDwellEnd},
  125. { "scrollShortest", scriptSetReal32CB, &subScrollShortest},
  126. { "subtitleShortest", scriptSetReal32CB, &subTitleShortest},
  127. { NULL, NULL, 0 }
  128. };
  129. /*=============================================================================
  130. Functions:
  131. =============================================================================*/
  132. /*-----------------------------------------------------------------------------
  133. Name : subAnyCardsOnScreen
  134. Description : returns TRUE if any subtitle cards are on the screen
  135. Inputs :
  136. Outputs :
  137. Return : returns TRUE if any subtitle cards are on the screen
  138. ----------------------------------------------------------------------------*/
  139. bool subAnyCardsOnScreen(void)
  140. {
  141. sdword index;
  142. for (index = 0; index < SUB_NumberRegions; index++)
  143. {
  144. if (subRegion[index].bEnabled && subRegion[index].cardIndex > 0)
  145. {
  146. return TRUE;
  147. }
  148. }
  149. return FALSE;
  150. }
  151. /*-----------------------------------------------------------------------------
  152. Name : subThemeColorSet
  153. Description : Script-oarsing callback for setting a theme's color
  154. Inputs :
  155. Outputs :
  156. Return :
  157. ----------------------------------------------------------------------------*/
  158. void subThemeColorSet(char *directory, char *field, void *dataToFillIn)
  159. {
  160. udword nScanned, iTheme, red, green, blue;
  161. RemoveCommasFromString(field);
  162. nScanned = sscanf(field, "%d %d %d %d", &iTheme, &red, &green, &blue);
  163. #if SUB_ERROR_CHECKING
  164. if (nScanned != 4)
  165. {
  166. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 4, nScanned);
  167. }
  168. if (iTheme >= SUB_NumberThemes)
  169. {
  170. dbgFatalf(DBG_Loc, "Invalid theme number %d. Must be between 0 and %d.", iTheme, SUB_NumberThemes);
  171. }
  172. if (red > UBYTE_Max || green > UBYTE_Max || blue > UBYTE_Max)
  173. {
  174. dbgFatalf(DBG_Loc, "Invalid color in string '%s'", field);
  175. }
  176. #endif
  177. subTheme[iTheme].textColor = colRGB(red, green, blue);
  178. }
  179. /*-----------------------------------------------------------------------------
  180. Name : subThemeDropshadowSet
  181. Description : Script parse callback for setting the dropshadow flag on a theme.
  182. Inputs :
  183. Outputs :
  184. Return :
  185. ----------------------------------------------------------------------------*/
  186. void subThemeDropshadowSet(char *directory, char *field, void *dataToFillIn)
  187. {
  188. sdword nScanned, iTheme;
  189. char boolString[UBYTE_Max];
  190. RemoveCommasFromString(field);
  191. nScanned = sscanf(field, "%d %s", &iTheme, boolString);
  192. #if SUB_ERROR_CHECKING
  193. if (nScanned != 2)
  194. {
  195. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 2, nScanned);
  196. }
  197. if (iTheme < 0 || iTheme >= SUB_NumberThemes)
  198. {
  199. dbgFatalf(DBG_Loc, "Invalid theme number %d. Must be between 0 and %d.", iTheme, SUB_NumberThemes);
  200. }
  201. #endif
  202. subTheme[iTheme].bDropShadow = (bool8)scriptStringToBool(boolString);
  203. }
  204. /*-----------------------------------------------------------------------------
  205. Name : subThemeCentredSet
  206. Description : Script parse callback for setting the centred flag on a theme.
  207. Inputs :
  208. Outputs :
  209. Return :
  210. ----------------------------------------------------------------------------*/
  211. void subThemeCentredSet(char *directory, char *field, void *dataToFillIn)
  212. {
  213. sdword nScanned, iTheme;
  214. char boolString[UBYTE_Max];
  215. RemoveCommasFromString(field);
  216. nScanned = sscanf(field, "%d %s", &iTheme, boolString);
  217. #if SUB_ERROR_CHECKING
  218. if (nScanned != 2)
  219. {
  220. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 2, nScanned);
  221. }
  222. if (iTheme < 0 || iTheme >= SUB_NumberThemes)
  223. {
  224. dbgFatalf(DBG_Loc, "Invalid theme number %d. Must be between 0 and %d.", iTheme, SUB_NumberThemes);
  225. }
  226. #endif
  227. subTheme[iTheme].bCentred = (bool8)scriptStringToBool(boolString);
  228. }
  229. /*-----------------------------------------------------------------------------
  230. Name : subThemePictureSet
  231. Description : Script parse callback for setting the picture for a theme.
  232. Inputs :
  233. Outputs :
  234. Return :
  235. ----------------------------------------------------------------------------*/
  236. void subThemePictureSet(char *directory, char *field, void *dataToFillIn)
  237. {
  238. sdword nScanned, iTheme, iColorScheme;
  239. char pictureString[UBYTE_Max];
  240. char fileName[UBYTE_Max];
  241. RemoveCommasFromString(field);
  242. nScanned = sscanf(field, "%d %d %s", &iTheme, &iColorScheme, pictureString);
  243. #if SUB_ERROR_CHECKING
  244. if (nScanned != 3)
  245. {
  246. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 3, nScanned);
  247. }
  248. if (iTheme < 0 || iTheme >= SUB_NumberThemes)
  249. {
  250. dbgFatalf(DBG_Loc, "Invalid theme number %d. Must be between 0 and %d.", iTheme, SUB_NumberThemes);
  251. }
  252. if (iColorScheme < 0 || iColorScheme >= TE_NumberPlayers)
  253. {
  254. dbgFatalf(DBG_Loc, "Invalid color scheme %d for '%s'", iColorScheme, pictureString);
  255. }
  256. #endif
  257. if (universe.players[0].race == R1)
  258. {
  259. strcpy(fileName, "SpeechIcons\\R1\\");
  260. }
  261. else
  262. {
  263. strcpy(fileName, "SpeechIcons\\R2\\");
  264. }
  265. strcat(fileName, pictureString);
  266. subTheme[iTheme].picture = trTextureRegister(fileName, &teColorSchemes[iColorScheme].textureColor, (void *)&subTheme[iTheme]);
  267. subTheme[iTheme].pictureColorScheme = iColorScheme;
  268. subTheme[iTheme].bPicture = TRUE;
  269. }
  270. /*-----------------------------------------------------------------------------
  271. Name : subThemeMarginSet
  272. Description : Script parse callback for setting the margin of a theme
  273. Inputs :
  274. Outputs :
  275. Return :
  276. ----------------------------------------------------------------------------*/
  277. void subThemeMarginSet(char *directory, char *field, void *dataToFillIn)
  278. {
  279. sdword nScanned, iTheme, margin;
  280. RemoveCommasFromString(field);
  281. nScanned = sscanf(field, "%d %d", &iTheme, &margin);
  282. #if SUB_ERROR_CHECKING
  283. if (nScanned != 2)
  284. {
  285. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 2, nScanned);
  286. }
  287. if (iTheme < 0 || iTheme >= SUB_NumberThemes)
  288. {
  289. dbgFatalf(DBG_Loc, "Invalid theme number %d. Must be between 0 and %d.", iTheme, SUB_NumberThemes);
  290. }
  291. #endif
  292. subTheme[iTheme].margin = margin;
  293. }
  294. /*-----------------------------------------------------------------------------
  295. Name : subThemeFadeSet
  296. Description : Script parse callback for setting a fade time
  297. Inputs :
  298. Outputs :
  299. Return :
  300. ----------------------------------------------------------------------------*/
  301. void subThemeFadeSet(char *directory, char *field, void *dataToFillIn)
  302. {
  303. sdword nScanned, iTheme;
  304. real32 fadeTime;
  305. RemoveCommasFromString(field);
  306. nScanned = sscanf(field, "%d %f", &iTheme, &fadeTime);
  307. #if SUB_ERROR_CHECKING
  308. if (nScanned != 2)
  309. {
  310. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 2, nScanned);
  311. }
  312. if (iTheme < 0 || iTheme >= SUB_NumberThemes)
  313. {
  314. dbgFatalf(DBG_Loc, "Invalid theme number %d. Must be between 0 and %d.", iTheme, SUB_NumberThemes);
  315. }
  316. dbgAssert(fadeTime >= 0.0f && fadeTime < 80.0f);
  317. #endif
  318. if ((bool)dataToFillIn == FALSE)
  319. {
  320. subTheme[iTheme].fadeIn = fadeTime;
  321. }
  322. else
  323. {
  324. subTheme[iTheme].fadeOut = fadeTime;
  325. }
  326. }
  327. /*-----------------------------------------------------------------------------
  328. Name : subThemeFontSet
  329. Description : Script parse callback for setting the font of a theme
  330. Inputs :
  331. Outputs :
  332. Return :
  333. ----------------------------------------------------------------------------*/
  334. void subThemeFontSet(char *directory, char *field, void *dataToFillIn)
  335. {
  336. sdword nScanned, iTheme;
  337. char fontName[1024];
  338. RemoveCommasFromString(field);
  339. nScanned = sscanf(field, "%d %s", &iTheme, fontName);
  340. #if SUB_ERROR_CHECKING
  341. if (nScanned != 2)
  342. {
  343. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 2, nScanned);
  344. }
  345. if (iTheme < 0 || iTheme >= SUB_NumberThemes)
  346. {
  347. dbgFatalf(DBG_Loc, "Invalid theme number %d. Must be between 0 and %d.", iTheme, SUB_NumberThemes);
  348. }
  349. #endif
  350. subTheme[iTheme].font = frFontRegister(fontName);
  351. }
  352. /*-----------------------------------------------------------------------------
  353. Name : subRegionBoundsSet
  354. Description : Script parse callback to set the bounds of a subtitle region.
  355. Inputs :
  356. Outputs :
  357. Return :
  358. ----------------------------------------------------------------------------*/
  359. void subRegionBoundsSet(char *directory, char *field, void *dataToFillIn)
  360. {
  361. sdword nScanned, iRegion;
  362. rectangle rect;
  363. RemoveCommasFromString(field);
  364. nScanned = sscanf(field, "%d %d %d %d %d", &iRegion, &rect.x0, &rect.y0, &rect.x1, &rect.y1);
  365. #if SUB_ERROR_CHECKING
  366. if (nScanned != 5)
  367. {
  368. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 5, nScanned);
  369. }
  370. if (iRegion < 0 || iRegion >= SUB_NumberRegions)
  371. {
  372. dbgFatalf(DBG_Loc, "Invalid region number %d. Must be between 0 and %d.", iRegion, SUB_NumberRegions);
  373. }
  374. dbgAssert(rect.x0 < 640);
  375. dbgAssert(rect.x1 > 0);
  376. dbgAssert(rect.y0 < 480);
  377. dbgAssert(rect.y1 > 0);
  378. dbgAssert(rect.x0 < rect.x1);
  379. dbgAssert(rect.y0 < rect.y1);
  380. #endif
  381. subRegion[iRegion].defaultRect = rect;
  382. }
  383. /*-----------------------------------------------------------------------------
  384. Name : subRegionScaleRezSet
  385. Description : script parse callback to set the resolution scale flags for a
  386. subtitle region
  387. Inputs :
  388. Outputs :
  389. Return :
  390. ----------------------------------------------------------------------------*/
  391. void subRegionScaleRezSet(char *directory, char *field, void *dataToFillIn)
  392. {
  393. sdword nScanned, iRegion;
  394. char bScaleRezX[UBYTE_Max], bScaleRezY[UBYTE_Max];
  395. RemoveCommasFromString(field);
  396. nScanned = sscanf(field, "%d %s %s", &iRegion, bScaleRezX, bScaleRezY);
  397. #if SUB_ERROR_CHECKING
  398. if (nScanned != 3)
  399. {
  400. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 3, nScanned);
  401. }
  402. if (iRegion < 0 || iRegion >= SUB_NumberRegions)
  403. {
  404. dbgFatalf(DBG_Loc, "Invalid region number %d. Must be between 0 and %d.", iRegion, SUB_NumberRegions);
  405. }
  406. #endif
  407. subRegion[iRegion].bScaleRezX = (bool8)scriptStringToBool(bScaleRezX);
  408. subRegion[iRegion].bScaleRezY = (bool8)scriptStringToBool(bScaleRezY);
  409. }
  410. /*-----------------------------------------------------------------------------
  411. Name : subRegionMaxLinesSet
  412. Description : Script parse callback for setting the max number of lines in
  413. a region.
  414. Inputs :
  415. Outputs :
  416. Return :
  417. ----------------------------------------------------------------------------*/
  418. void subRegionMaxLinesSet(char *directory, char *field, void *dataToFillIn)
  419. {
  420. sdword nScanned, iRegion, maxLines;
  421. RemoveCommasFromString(field);
  422. nScanned = sscanf(field, "%d %d", &iRegion, &maxLines);
  423. #if SUB_ERROR_CHECKING
  424. if (nScanned != 2)
  425. {
  426. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 2, nScanned);
  427. }
  428. if (iRegion < 0 || iRegion >= SUB_NumberRegions)
  429. {
  430. dbgFatalf(DBG_Loc, "Invalid region number %d. Must be between 0 and %d.", iRegion, SUB_NumberRegions);
  431. }
  432. dbgAssert(maxLines > 0 && maxLines < UDWORD_Max);
  433. #endif
  434. subRegion[iRegion].numberCards = maxLines;
  435. }
  436. /*-----------------------------------------------------------------------------
  437. Name : subRegionIconOffsetSet
  438. Description : Script parse callback for setting the speech icon offset (if any) for the region
  439. Inputs :
  440. Outputs :
  441. Return :
  442. ----------------------------------------------------------------------------*/
  443. void subRegionIconOffsetSet(char *directory, char *field, void *dataToFillIn)
  444. {
  445. sdword nScanned, iRegion, x, y;
  446. RemoveCommasFromString(field);
  447. nScanned = sscanf(field, "%d %d %d", &iRegion, &x, &y);
  448. #if SUB_ERROR_CHECKING
  449. if (nScanned != 3)
  450. {
  451. dbgFatalf(DBG_Loc, "Wrong number of fields in '%s': expected %d found %d", field, 3, nScanned);
  452. }
  453. if (iRegion < 0 || iRegion >= SUB_NumberRegions)
  454. {
  455. dbgFatalf(DBG_Loc, "Invalid region number %d. Must be between 0 and %d.", iRegion, SUB_NumberRegions);
  456. }
  457. #endif
  458. subRegion[iRegion].iconOffsetX = x;
  459. subRegion[iRegion].iconOffsetY = y;
  460. }
  461. /*-----------------------------------------------------------------------------
  462. Name : subTextureInfoRemember
  463. Description : Remembers the player's race and team color information.
  464. Inputs :
  465. Outputs :
  466. Return :
  467. ----------------------------------------------------------------------------*/
  468. void subTextureInfoRemember(void)
  469. {
  470. sdword index;
  471. subLastRace = universe.players[0].race;
  472. for (index = 0; index < TE_NumberPlayers; index++)
  473. {
  474. subLastTeamColors[index] = teColorSchemes[index].textureColor;
  475. }
  476. }
  477. /*-----------------------------------------------------------------------------
  478. Name : subRegionsRescale
  479. Description : Rescales all region rectangles for a new screen resolution.
  480. Inputs :
  481. Outputs :
  482. Return :
  483. ----------------------------------------------------------------------------*/
  484. void subRegionsRescale(void)
  485. {
  486. sdword index;
  487. sdword diffX = (MAIN_WindowWidth - 640) / 2;
  488. sdword diffY = (MAIN_WindowHeight - 480) / 2;
  489. for (index = 0; index < SUB_NumberRegions; index++)
  490. {
  491. if (subRegion[index].defaultRect.x1 != 0)
  492. { //if a region was defined
  493. if (subRegion[index].bScaleRezX)
  494. { //if x-scale in resolution
  495. subRegion[index].rect.x0 = subRegion[index].defaultRect.x0 * MAIN_WindowWidth / 640;
  496. subRegion[index].rect.x1 = subRegion[index].defaultRect.x1 * MAIN_WindowWidth / 640;
  497. }
  498. else
  499. {
  500. subRegion[index].rect.x0 = subRegion[index].defaultRect.x0 + diffX;
  501. subRegion[index].rect.x1 = subRegion[index].defaultRect.x1 + diffX;
  502. }
  503. if (subRegion[index].bScaleRezY)
  504. { //if y-scale in resolution
  505. subRegion[index].rect.y0 = subRegion[index].defaultRect.y0 * MAIN_WindowHeight / 480;
  506. subRegion[index].rect.y1 = subRegion[index].defaultRect.y1 * MAIN_WindowHeight / 480;
  507. }
  508. else
  509. {
  510. subRegion[index].rect.y0 = subRegion[index].defaultRect.y0 + diffY;
  511. subRegion[index].rect.y1 = subRegion[index].defaultRect.y1 + diffY;
  512. }
  513. }
  514. }
  515. }
  516. /*-----------------------------------------------------------------------------
  517. Name : subStartup
  518. Description : Starts the subtitle module, reading in the subtitle scripts
  519. Inputs :
  520. Outputs :
  521. Return :
  522. ----------------------------------------------------------------------------*/
  523. void subStartup(void)
  524. {
  525. sdword index;
  526. #if SUB_VERBOSE_LEVEL >= 1
  527. dbgMessage("\nStartup subtitle module");
  528. #endif
  529. subNumberNewSubtitles = 0;
  530. #if SUB_VERBOSE_LEVEL >= 1
  531. subMissedSubtitles = 0;
  532. #endif
  533. subSemaphore = CreateSemaphore(NULL, 1, 1, SUB_SemaphoreName);
  534. dbgAssert(subSemaphore != NULL);
  535. memset(&subRegion, 0, sizeof(subRegion)); //clear out the themes and printing regions
  536. memset(&subTheme, 0, sizeof(subTheme));
  537. scriptSet(NULL, SUB_ScriptFile, subScriptTable); //load in the script file
  538. //post-setup of the output regions
  539. subRegionsRescale();
  540. for (index = 0; index < SUB_NumberRegions; index++)
  541. {
  542. if (subRegion[index].defaultRect.x1 != 0)
  543. { //if a region was defined
  544. dbgAssert(subRegion[index].numberCards > 0 && subRegion[index].numberCards < UWORD_Max);
  545. subRegion[index].card = memAlloc(sizeof(subcard) * subRegion[index].numberCards, "subTitleCards", NonVolatile);
  546. subRegion[index].bEnabled = TRUE;
  547. subRegion[index].bAborted = FALSE;
  548. }
  549. }
  550. subRegion[STR_CloseCaptioned].bEnabled = subCloseCaptionsEnabled;//special case because these are enabled with a command-line switch
  551. subTextureInfoRemember();
  552. }
  553. /*-----------------------------------------------------------------------------
  554. Name : subReset
  555. Description : To be called between levels, clears out any leftover subtitles
  556. from all regions.
  557. Inputs : void
  558. Outputs :
  559. Return : void
  560. ----------------------------------------------------------------------------*/
  561. void subReset(void)
  562. {
  563. sdword index;
  564. //clear all text cards out of all regions
  565. for (index = 0; index < SUB_NumberRegions; index++)
  566. {
  567. subRegion[index].cardIndex = 0;
  568. }
  569. }
  570. /*-----------------------------------------------------------------------------
  571. Name : subTexturesReset
  572. Description : Resets the subtitle textures, re-coloring and ensureing the
  573. correct race of textures is loaded.
  574. Inputs :
  575. Outputs :
  576. Return : void
  577. ----------------------------------------------------------------------------*/
  578. void subTexturesReset(void)
  579. {
  580. sdword index;
  581. char fileName[UBYTE_Max];
  582. texreg *reg;
  583. for (index = 0; index < SUB_NumberRegions; index++)
  584. {
  585. subRegion[index].cardIndex = 0;
  586. }
  587. //clear all text cards out of all regions
  588. for (index = 0; index < SUB_NumberThemes; index++)
  589. {
  590. //recolorize the textures
  591. #if TR_NIL_TEXTURE
  592. if (subTheme[index].bPicture && (!GLOBAL_NO_TEXTURES))
  593. #else
  594. if (subTheme[index].bPicture)
  595. #endif
  596. {
  597. //trTextureColorsUpdate(subTheme[index].picture, &teColorSchemes[subTheme[index].pictureColorScheme].textureColor);
  598. //replace the image with the image for the correct race.
  599. reg = trStructureGet(subTheme[index].picture);
  600. strcpy(fileName, reg->fileName);
  601. if (universe.players[0].race == R1)
  602. {
  603. //Name if texture is:"SpeechIcons\RX\BlahBlah"
  604. // replace this number: ^
  605. #define SUB_NUMBER_INDEX 13
  606. fileName[SUB_NUMBER_INDEX] = '1';
  607. }
  608. else
  609. {
  610. fileName[SUB_NUMBER_INDEX] = '2';
  611. }
  612. if (universe.players[0].race != subLastRace ||
  613. memcmp(&subLastTeamColors[subTheme[index].pictureColorScheme], &teColorSchemes[subTheme[index].pictureColorScheme].textureColor, sizeof(trcolorinfo)))
  614. {
  615. //trTextureUnregister(subTheme[index].picture);
  616. trRegisterRemoval(subTheme[index].picture);
  617. subTheme[index].picture = trTextureRegister(fileName, &teColorSchemes[subTheme[index].pictureColorScheme].textureColor, (void *)&subTheme[index]);
  618. }
  619. }
  620. }
  621. subTextureInfoRemember();
  622. }
  623. /*-----------------------------------------------------------------------------
  624. Name : subShutdown
  625. Description : Shuts down subtitle module, freeing memory as needed.
  626. Inputs :
  627. Outputs :
  628. Return :
  629. ----------------------------------------------------------------------------*/
  630. void subShutdown(void)
  631. {
  632. sdword index;
  633. #if SUB_VERBOSE_LEVEL >= 1
  634. dbgMessage("\nShutdown subtitle module");
  635. #endif
  636. CloseHandle(subSemaphore);
  637. //free any allocated memory
  638. for (index = 0; index < SUB_NumberRegions; index++)
  639. {
  640. if (subRegion[index].card != NULL)
  641. {
  642. memFree(subRegion[index].card);
  643. }
  644. }
  645. }
  646. /*-----------------------------------------------------------------------------
  647. Name : subStringsChop
  648. Description : Chop up a long string to make it fit in the specified region
  649. using the specified type theme.
  650. Inputs : region - screen size to print into
  651. font - what font to print in?
  652. longLength - length of string we're chopping up.
  653. longString - string we are to be chopping up
  654. Outputs : chopBuffer - buffer they'll be chopped up into (in case
  655. NULL-terminators need to be added)
  656. choppedStrings - list of pointers to chopped lines.
  657. Return : number of chopped strings
  658. ----------------------------------------------------------------------------*/
  659. sdword subStringsChop(rectangle *rect, fonthandle font, sdword longLength, char *longString, char *chopBuffer, char **choppedStrings)
  660. {
  661. sdword nChopped = 0;
  662. sdword nBytesUsed = 0;
  663. sdword width = rect->x1 - rect->x0;
  664. char *chopStart, *chopEnd, *nextChopStart, *wrapPtr;
  665. fonthandle current = fontCurrentGet();
  666. sdword state;
  667. fontMakeCurrent(font);
  668. chopStart = longString;
  669. // while (chopStart - longString < longLength)
  670. // { //while there is still some string to chop up
  671. for (chopEnd = chopStart; *chopStart != 0; chopEnd++)
  672. { //find the string to fit on this line
  673. //see if there is a newline or end-of-string
  674. if ((chopEnd[0] == '#' || chopEnd[0] == '\\') && chopEnd[1] == 'n')
  675. { //"#n" or "\\n" - newline
  676. for (nextChopStart = chopEnd + 2; *nextChopStart; nextChopStart++)
  677. { //find next non-whitespace, non-newline character
  678. if (!strchr(" \t\n\r", *nextChopStart))
  679. {
  680. break;
  681. }
  682. }
  683. }
  684. else if (chopEnd[0] == '\n' || chopEnd[0] == '\r')
  685. { // '\n' or '\r'
  686. for (nextChopStart = chopEnd + 1; *nextChopStart; nextChopStart++)
  687. { //find next non-whitespace, non-newline character
  688. if (!strchr(" \t\r", *nextChopStart))
  689. {
  690. break;
  691. }
  692. }
  693. }
  694. else if (*chopEnd == 0)
  695. { //end of string
  696. nextChopStart = chopEnd; //last time through this loop
  697. }
  698. //!!! no support for dashes yet
  699. //note: this method of fontWidth'ing is rather inefficient because
  700. //fontWidth will continuously be parsing the same piece of string. Oh well.
  701. else if (fontWidthN(chopStart, chopEnd - chopStart + 1) > width)
  702. { //if string too long for the region it's in
  703. //scan backwards to find some whitespace to cut out
  704. state = 0; //search for whitespace state
  705. for (wrapPtr = chopEnd; wrapPtr > chopStart; wrapPtr--)
  706. {
  707. if (state == 0)
  708. { //search for whitespace state
  709. if (strchr("\t ", *wrapPtr))
  710. { //if this is whitespace
  711. state = 1; //in the whitespace state
  712. nextChopStart = wrapPtr + 1; //this will be start of next line
  713. }
  714. }
  715. else
  716. { //in the whitespace state
  717. if (!strchr("\t ", *wrapPtr))
  718. {
  719. wrapPtr++; //point to first of the whitespace
  720. break;
  721. }
  722. }
  723. }
  724. //did we find a whitespace break to wrap on?
  725. if (wrapPtr > chopStart)
  726. { //yes, we found some whitespace to wrap on
  727. chopEnd = wrapPtr;
  728. }
  729. else
  730. { //else no whitespace was found
  731. nextChopStart = chopEnd; //next line will start right at the character that went too long
  732. }
  733. }
  734. else
  735. { //still fits, keep going
  736. continue;
  737. }
  738. //now, the string from chopStart to chopEnd will be a line.
  739. memcpy(chopBuffer + nBytesUsed, chopStart, chopEnd - chopStart);//copy the string into the buffer
  740. chopBuffer[nBytesUsed + chopEnd - chopStart] = 0;//NULL-terminate it
  741. choppedStrings[nChopped] = chopBuffer + nBytesUsed;//set pointer to the string
  742. dbgAssert(nBytesUsed < SUB_SubtitleLength + SUB_MaxLinesPerSubtitle);
  743. dbgAssert(nChopped < SUB_MaxLinesPerSubtitle);
  744. nBytesUsed += chopEnd - chopStart + 1; //update usage count
  745. nChopped++; //and number of strings
  746. //chop the next line
  747. chopStart = nextChopStart; //set to start scanning next line
  748. chopEnd = chopStart - 1;
  749. }
  750. // }
  751. fontMakeCurrent(current); //restore the old font
  752. return(nChopped); //return number of strings chopped up
  753. }
  754. /*-----------------------------------------------------------------------------
  755. Name : subCreateScrollyLines
  756. Description : Compute scroll and fade information for these strings.
  757. Inputs : region - what scrolly text region we're adding them to
  758. theme - what type theme we're using
  759. nStrings - number of strings we're adding
  760. strings - array of string pointers
  761. speechTime - length of speech event, in seconds
  762. Outputs :
  763. Return :
  764. ----------------------------------------------------------------------------*/
  765. void subCreateScrollyLines(subregion *region, subtheme *theme, sdword nStrings, char **strings, real32 speechTime)
  766. {
  767. sdword index, linesToScroll, linesInRegion;
  768. sdword x, y;
  769. real32 scrollDistance;
  770. real32 scrollDuration;
  771. real32 delay, duration, scrollSpeed;
  772. real32 totalHeight;
  773. fonthandle fhSave = fontCurrentGet();
  774. fontMakeCurrent(theme->font);
  775. //figure out how far we'll have to scroll
  776. linesInRegion = (region->rect.y1 - region->rect.y0) / (fontHeight(" ") + SUB_InterlineSpacing);
  777. linesToScroll = nStrings - linesInRegion;
  778. totalHeight = (real32)(nStrings * (fontHeight(" ") + SUB_InterlineSpacing));
  779. scrollDistance = (real32)(linesToScroll * (fontHeight(" ") + SUB_InterlineSpacing));
  780. if (scrollDistance < 0.0f)
  781. {
  782. scrollDistance = 0.0f; //no scrolling needed
  783. }
  784. //how long will the scroll take?
  785. scrollDuration = speechTime - subScrollDwellStart - subScrollDwellEnd;
  786. //prep the region to scroll and display new text
  787. region->cardIndex = 0; //kill all previous cards
  788. if (scrollDistance > 0.0f)
  789. { //if we are to scroll at all
  790. region->textScrollStart = *subTimeElapsed + subScrollDwellStart;
  791. if (scrollDuration < 0)
  792. { //does that make 0 scroll time after all the dwelling?
  793. region->textScrollEnd = region->textScrollStart + subScrollShortest;//set some minimum scroll duration (only needed if somebody speaks really fast :)
  794. //!!! this is weird... fix me please!!!
  795. }
  796. else
  797. { //else we can scroll as normal
  798. region->textScrollEnd = region->textScrollStart + scrollDuration;
  799. }
  800. region->scrollDistance = -scrollDistance; //upward scroll is negative
  801. scrollSpeed = scrollDistance / (region->textScrollEnd - region->textScrollStart);
  802. }
  803. else
  804. { //else no scrolling
  805. scrollDuration = speechTime - subScrollDwellStart - subScrollDwellEnd;//just stay around for the duration of string
  806. region->scrollDistance = 0.0f;
  807. scrollSpeed = 0.0f; //should never be divided by anyhow
  808. }
  809. //now that we've computed our scroll time, let's actually add the lines
  810. y = 0;
  811. delay = 0.0f;
  812. for (index = 0; index < nStrings; index++)
  813. {
  814. #if SUB_ERROR_CHECKING
  815. if (index >= region->numberCards)
  816. {
  817. dbgFatalf(DBG_Loc, "Exceeded %d lines with the text '%s'.", region->numberCards, strings[index]);
  818. }
  819. #endif
  820. if (index < linesInRegion)
  821. { //if first lines to appear
  822. delay = 0; //make it show up right now
  823. if (index >= nStrings - linesInRegion)
  824. { //string is starting and ending string
  825. duration = subScrollDwellStart + subScrollDwellEnd + scrollDuration;
  826. }
  827. else
  828. { //else it'll scroll off the top
  829. duration = subScrollDwellStart + (real32)y / scrollSpeed;
  830. }
  831. }
  832. else
  833. { //else it will scroll onto screen
  834. //delay = (real32)y / scrollSpeed; //this is when it scrolls into view
  835. delay = subScrollDwellStart + (real32)((index - linesInRegion) * (fontHeight(" ") + SUB_InterlineSpacing)) / scrollSpeed;
  836. if (index >= nStrings - linesInRegion)
  837. { //if it is an ending string
  838. duration = (totalHeight - (real32)y) / scrollSpeed + subScrollDwellEnd;
  839. }
  840. else
  841. { //else it will scroll on the bottom and off the top
  842. duration = (real32)(linesInRegion * (fontHeight(" ") + SUB_InterlineSpacing)) / scrollSpeed;
  843. }
  844. }
  845. if (theme->bCentred)
  846. {
  847. x = ((region->rect.x1 - region->rect.x0) - fontWidth(strings[index])) / 2;
  848. }
  849. else
  850. {
  851. x = 0;
  852. }
  853. region->card[index].x = x;
  854. region->card[index].y = y;
  855. region->card[index].creationTime = *subTimeElapsed + delay;
  856. region->card[index].duration = duration;
  857. region->card[index].fadeIn = theme->fadeIn;
  858. region->card[index].fadeOut = theme->fadeOut;
  859. region->card[index].c = theme->textColor;
  860. region->card[index].bDropShadow = theme->bDropShadow;
  861. region->card[index].font = theme->font;
  862. region->card[index].text = strings[index];
  863. y += fontHeight(" ") + SUB_InterlineSpacing; //move the next line down
  864. }
  865. region->cardIndex = index;
  866. dbgAssert(region->cardIndex < region->numberCards);
  867. if (theme->bPicture)
  868. {
  869. region->picture = theme->picture;
  870. }
  871. else
  872. {
  873. region->picture = TR_InvalidHandle;
  874. }
  875. fontMakeCurrent(fhSave);
  876. }
  877. /*-----------------------------------------------------------------------------
  878. Name : subCreateNonScrollyLines
  879. Description : Same as above, but the lines won't scroll; intead consecutive
  880. pages will fade in and out.
  881. Inputs :
  882. Outputs :
  883. Return :
  884. ----------------------------------------------------------------------------*/
  885. void subCreateNonScrollyLines(subregion *region, subtheme *theme, sdword nStrings, char **strings, real32 speechTime)
  886. {
  887. sdword index, linesInRegion;
  888. sdword x, y;
  889. real32 delay;
  890. sdword nPages, iPage;
  891. real32 timePerPage;
  892. fonthandle fhSave = fontCurrentGet();
  893. dbgAssert(nStrings != 0);
  894. fontMakeCurrent(theme->font);
  895. speechTime = max(speechTime, subTitleShortest);
  896. //prep the region
  897. region->cardIndex = 0; //kill all previous cards
  898. region->scrollDistance = 0;
  899. //figure out how many pages there will be
  900. linesInRegion = (region->rect.y1 - region->rect.y0) / (fontHeight(" ") + SUB_InterlineSpacing);
  901. if (linesInRegion == 0)
  902. {
  903. linesInRegion = 1;
  904. }
  905. nPages = nStrings / linesInRegion;
  906. if (nStrings % linesInRegion)
  907. {
  908. nPages++;
  909. }
  910. timePerPage = speechTime / (real32)nPages;
  911. //now that we've computed our scroll time, let's actually add the lines
  912. for (index = 0; index < nStrings; index++)
  913. {
  914. #if SUB_ERROR_CHECKING
  915. if (index >= region->numberCards)
  916. {
  917. dbgFatalf(DBG_Loc, "Exceeded %d lines with the text '%s'.", region->numberCards, strings[index]);
  918. }
  919. #endif
  920. if (index < linesInRegion)
  921. { //if first lines to appear
  922. delay = 0; //make it show up right now
  923. }
  924. else
  925. { //else it will scroll onto screen
  926. iPage = index / linesInRegion; //what page am I on?
  927. delay = (real32)iPage * timePerPage;
  928. }
  929. if (theme->bCentred)
  930. {
  931. x = ((region->rect.x1 - region->rect.x0) - fontWidth(strings[index])) / 2;
  932. }
  933. else
  934. {
  935. x = 0;
  936. }
  937. y = (index % linesInRegion) * (fontHeight(" ") + SUB_InterlineSpacing);
  938. region->card[index].x = x;
  939. region->card[index].y = y;
  940. region->card[index].creationTime = *subTimeElapsed + delay;
  941. region->card[index].duration = timePerPage;
  942. region->card[index].fadeIn = theme->fadeIn;
  943. region->card[index].fadeOut = theme->fadeOut;
  944. region->card[index].c = theme->textColor;
  945. region->card[index].bDropShadow = theme->bDropShadow;
  946. region->card[index].font = theme->font;
  947. region->card[index].text = strings[index];
  948. }
  949. region->cardIndex = index;
  950. dbgAssert(region->cardIndex < region->numberCards);
  951. if (theme->bPicture)
  952. {
  953. region->picture = theme->picture;
  954. }
  955. else
  956. {
  957. region->picture = TR_InvalidHandle;
  958. }
  959. fontMakeCurrent(fhSave);
  960. region->bAborted = FALSE;
  961. }
  962. /*-----------------------------------------------------------------------------
  963. Name : subControlsScan
  964. Description : Scan for control escape sequences at the start of a string.
  965. Inputs : start - start of string to parse
  966. Outputs : region - region pointer to modify on #r<n> or #c
  967. theme - theme pointer to modify on #t<n> or #c
  968. bContinuous - set to TRUE on \+ or #+
  969. Return : start of actual string
  970. ----------------------------------------------------------------------------*/
  971. char *subControlsScan(char *start, subregion **region, subtheme **theme, bool8 *bContinuous)
  972. {
  973. sdword scannedIndex;
  974. while ((*start == '#' || *start == '\\') && *start != 0)
  975. {
  976. start++;
  977. switch (*start)
  978. {
  979. case '+':
  980. *bContinuous = TRUE;
  981. break;
  982. case 'r':
  983. start++;
  984. scannedIndex = *start - '0';
  985. dbgAssert(scannedIndex >= 0 && scannedIndex < SUB_NumberRegions);
  986. *region = &subRegion[scannedIndex];
  987. break;
  988. case 't':
  989. start++;
  990. if (*start >= '0' && *start <= '9')
  991. {
  992. scannedIndex = *start - '0';
  993. }
  994. else
  995. {
  996. scannedIndex = 0xa + *start - 'a';
  997. }
  998. dbgAssert(scannedIndex >= 0 && scannedIndex < SUB_NumberThemes);
  999. *theme = &subTheme[scannedIndex];
  1000. break;
  1001. case 'c':
  1002. *region = &subRegion[STR_CloseCaptioned];
  1003. *theme = &subTheme[STT_CloseCaptioned];
  1004. break;
  1005. #if SUB_ERROR_CHECKING
  1006. default:
  1007. dbgFatalf(DBG_Loc, "Invalid format character '%c' in '%s'", *start, start);
  1008. #endif
  1009. }
  1010. start++;
  1011. }
  1012. return(start);
  1013. }
  1014. /*-----------------------------------------------------------------------------
  1015. Name : subTitlesUpdate
  1016. Description : Called every frame, this function updates subtitles,
  1017. handling asynchronous data transfer from the stream thread.
  1018. Inputs : void
  1019. Outputs :
  1020. Return : void
  1021. ----------------------------------------------------------------------------*/
  1022. void subTitlesUpdate(void)
  1023. {
  1024. sdword lastActor, lastSpeechEvent, totalLength, index;
  1025. real32 totalTime;
  1026. char fullNewStringBuffer[SUB_SubtitleLength], *fullNewString;
  1027. char *choppedStrings[SUB_MaxLinesPerSubtitle];
  1028. sdword nChoppedStrings;
  1029. subregion *region;
  1030. subtheme *theme;
  1031. char *newString;
  1032. bool8 bContinuous = FALSE, bContinuousIgnore;
  1033. subTimeElapsed = &universe.totaltimeelapsed;
  1034. #if SUB_MODULE_TEST
  1035. {
  1036. sdword key;
  1037. for (key = ONEKEY; key <= NINEKEY; key++)
  1038. {
  1039. if (keyIsStuck(key))
  1040. {
  1041. keyClearSticky(key);
  1042. if (subTestSubtitle[key - ONEKEY] != NULL)
  1043. {
  1044. subTitleAdd(0, subTestSubtitle[key - ONEKEY],
  1045. strlen(subTestSubtitle[key - ONEKEY]),
  1046. subTestSubtitleTime[key - ONEKEY]);
  1047. }
  1048. }
  1049. }
  1050. }
  1051. #endif //SUB_MODULE_TEST
  1052. WaitForSingleObject(subSemaphore, INFINITE); //make sure nobody else is working with the subtitle transfer buffers
  1053. if (subNumberNewSubtitles > 0)
  1054. {
  1055. //because subtitles are delivered as a series of phrases, we must "stitch"
  1056. //them together here. To do this, we compare the actor index of adjacent
  1057. //new subtitles. We may later want to add a more accurate method of
  1058. //concatenation, such as the speech event index.
  1059. for (index = 0; index < subNumberNewSubtitles; index++)
  1060. {
  1061. strcpy(fullNewStringBuffer, subNewSubtitles[index].text);//intialize the concatenation string
  1062. lastActor = subNewSubtitles[index].actor; //!!! this is the concatenation criteria
  1063. region = &subRegion[STR_LetterboxBar]; //default region to print to
  1064. theme = &subTheme[lastActor]; //default theme for this actor
  1065. lastSpeechEvent = subNewSubtitles[index].speechEvent;
  1066. fullNewString = subControlsScan(fullNewStringBuffer, &region, &theme, &bContinuous);
  1067. totalLength = strlen(fullNewString);
  1068. totalTime = subNewSubtitles[index].time;
  1069. while (index + 1 < subNumberNewSubtitles &&
  1070. subNewSubtitles[index + 1].actor == lastActor &&
  1071. subNewSubtitles[index + 1].speechEvent == lastSpeechEvent)
  1072. {
  1073. index++;
  1074. dbgAssert(totalLength + strlen(subNewSubtitles[index].text) < SUB_SubtitleLength);
  1075. strcat(fullNewString, " "); //put a space between phrases.
  1076. totalLength++;
  1077. newString = subNewSubtitles[index].text;
  1078. newString = subControlsScan(subNewSubtitles[index].text, &region, &theme, &bContinuousIgnore);
  1079. strcpy(fullNewString + totalLength, newString);//append the new string
  1080. totalLength = strlen(fullNewString); //get new total length
  1081. totalTime += subNewSubtitles[index].time; //add all the times together
  1082. }
  1083. }
  1084. #if SUB_VERBOSE_LEVEL >= 1
  1085. dbgMessagef("\nSubtitle: actor %d says 0x%x '%s' (%.2f seconds).", lastActor, lastSpeechEvent, fullNewString, totalTime);
  1086. #endif
  1087. //now that we have a concatenated string; let's chop it up to wrap it around
  1088. nChoppedStrings = subStringsChop(&region->rect, theme->font, totalLength, fullNewString, region->chopBuffer, choppedStrings);
  1089. #if SUB_VERBOSE_LEVEL >= 2
  1090. if (nChoppedStrings > 1)
  1091. {
  1092. sdword index;
  1093. dbgMessage("\nChopped up like this:");
  1094. for (index = 0; index < nChoppedStrings; index++)
  1095. {
  1096. dbgMessagef("\n'%s'", choppedStrings[index]);
  1097. }
  1098. }
  1099. #endif
  1100. //string is all chopped up. Let's figure out how long/far to scroll and how long to stick around at the ends.
  1101. if (strCurLanguage >= 1)
  1102. { //use the standard timing for English; scrolly timing for forign
  1103. subCreateScrollyLines(region, theme, nChoppedStrings, choppedStrings, totalTime);
  1104. }
  1105. else
  1106. {
  1107. subCreateNonScrollyLines(region, theme, nChoppedStrings, choppedStrings, totalTime);
  1108. }
  1109. region->bContinuousEvent = bContinuous;
  1110. subNumberNewSubtitles = 0; //discard all processed new subtitles
  1111. }
  1112. //notify by debug message if we missed any phrases
  1113. #if SUB_VERBOSE_LEVEL >= 1
  1114. if (subMissedSubtitles > 0)
  1115. {
  1116. dbgMessagef("\nsubtitle: %d subtitles were missed.", subMissedSubtitles);
  1117. subMissedSubtitles = 0;
  1118. }
  1119. #endif
  1120. ReleaseSemaphore(subSemaphore, 1, NULL);
  1121. }
  1122. /*-----------------------------------------------------------------------------
  1123. Name : subTitleAdd
  1124. Description : Display a subtitle. This function called from the speech
  1125. streamer when a speech event is streamed in.
  1126. Inputs : actor - integer for an actor. This is used to determine where
  1127. to print the speech even, plus fonts and whanot.
  1128. speechEvnt - what speechEvent it was, straight out of speechevent.h
  1129. text - text to print. Must be in proper language when passed
  1130. here.
  1131. length - length of text string, same as what you would get
  1132. from a strlen() call.
  1133. time - length of text
  1134. Outputs :
  1135. Return : ?
  1136. ----------------------------------------------------------------------------*/
  1137. sdword subTitleAdd(sdword actor, sdword speechEvent, char *text, sdword length, real32 time)
  1138. {
  1139. /*
  1140. #if SUB_VERBOSE_LEVEL >= 1
  1141. dbgMessagef("\nSubtitle: actor %d says '%s'.", actor, text);
  1142. #endif
  1143. */
  1144. WaitForSingleObject(subSemaphore, INFINITE); //make sure nobody else is working with the subtitle transfer buffers
  1145. if (subNumberNewSubtitles < SUB_NumberNewSubtitles - 1)
  1146. {
  1147. dbgAssert(length < SUB_SubtitleLength);
  1148. subNewSubtitles[subNumberNewSubtitles].actor = actor;
  1149. subNewSubtitles[subNumberNewSubtitles].speechEvent = speechEvent;
  1150. subNewSubtitles[subNumberNewSubtitles].length = length;
  1151. subNewSubtitles[subNumberNewSubtitles].time = time;
  1152. //strcpy(subNewSubtitles[subNumberNewSubtitles].text, text);
  1153. memcpy(subNewSubtitles[subNumberNewSubtitles].text, text, length);
  1154. subNewSubtitles[subNumberNewSubtitles].text[length] = 0;
  1155. subNumberNewSubtitles++;
  1156. }
  1157. else
  1158. {
  1159. #if SUB_VERBOSE_LEVEL >= 1
  1160. subMissedSubtitles++;
  1161. #endif
  1162. }
  1163. ReleaseSemaphore(subSemaphore, 1, NULL);
  1164. return(OKAY);
  1165. }
  1166. /*-----------------------------------------------------------------------------
  1167. Name : subTitlesDraw
  1168. Description : Render the subtitles in a given region.
  1169. Inputs : region - what region to render the subtitles for.
  1170. Outputs : may delete one or more of the text cards in the region
  1171. because they time out.
  1172. Return :void
  1173. ----------------------------------------------------------------------------*/
  1174. void subTitlesDraw(subregion *region)
  1175. {
  1176. sdword index;
  1177. fonthandle fhSave = fontCurrentGet();
  1178. char *lineStart;//, *newLine;
  1179. sdword x, y;
  1180. subcard *card;
  1181. color c;
  1182. real32 fadeValue;
  1183. udword multiplier;
  1184. sdword scroll;
  1185. real32 scrollFloat;
  1186. #if SUB_VISUALIZE_REGION
  1187. primRectSolid2(&region->rect, colBlack);
  1188. primRectOutline2(&region->rect, 1, colWhite);
  1189. #endif
  1190. if ((!mrRenderMainScreen && !smFleetIntel) && region == &subRegion[STR_LetterboxBar])
  1191. { //if some full screen gui is up
  1192. rndTextureEnable(FALSE);
  1193. glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
  1194. glBegin(GL_QUADS);
  1195. //top scissor part
  1196. glVertex2f(primScreenToGLX(-1), primScreenToGLY(0));
  1197. glVertex2f(primScreenToGLX(-1), primScreenToGLY(NIS_LetterHeight));
  1198. glVertex2f(primScreenToGLX(MAIN_WindowWidth), primScreenToGLY(NIS_LetterHeight));
  1199. glVertex2f(primScreenToGLX(MAIN_WindowWidth), primScreenToGLY(0));
  1200. glEnd();
  1201. //ferDrawBoxRegion(region->rect, ferMediumTextures, ferInternalGlow, NULL, FALSE);
  1202. }
  1203. //scroll the text, if applicable
  1204. scroll = region->rect.y0;
  1205. if (region->scrollDistance != 0.0f)
  1206. { //if there is a scroll to go on
  1207. if (*subTimeElapsed >= region->textScrollStart)
  1208. { //if the scroll has started
  1209. if (*subTimeElapsed <= region->textScrollEnd)
  1210. { //if currently scrolling
  1211. scrollFloat = region->scrollDistance * (*subTimeElapsed - region->textScrollStart) / (region->textScrollEnd - region->textScrollStart);
  1212. }
  1213. else
  1214. { //else scroll has ended
  1215. scrollFloat = region->scrollDistance;
  1216. }
  1217. scroll = (sdword)scrollFloat + region->rect.y0;
  1218. }
  1219. }
  1220. for (index = 0, card = region->card; index < region->cardIndex; index++, card++)
  1221. {
  1222. if (*subTimeElapsed >= card->creationTime)
  1223. { //if card has started up properly
  1224. if (*subTimeElapsed - card->creationTime > card->duration)
  1225. { //if card elapses
  1226. // dbgMessagef("XXXX%10s", region->card[index].text);
  1227. if (region->cardIndex > 1)
  1228. {
  1229. region->card[index] = region->card[region->cardIndex - 1];
  1230. }
  1231. region->cardIndex--;
  1232. index--;
  1233. card = &region->card[index];
  1234. continue;
  1235. }
  1236. fontMakeCurrent(card->font);
  1237. lineStart = card->text;
  1238. //newLine = strstr(lineStart, NIS_NewLine);
  1239. x = card->x + region->rect.x0;
  1240. y = card->y + scroll;
  1241. //scroll the text, if applicable
  1242. /*
  1243. if (card->scroll != 0.0f)
  1244. { //if the text is scrolling
  1245. y += (sdword)(card->scroll * //move to scrolled-to position
  1246. (card->NIS->timeElapsed - card->creationTime) /
  1247. card->duration);
  1248. }
  1249. */
  1250. c = card->c;
  1251. //fade the text in, if applicable
  1252. if (card->fadeIn != 0.0f)
  1253. {
  1254. fadeValue = (*subTimeElapsed - card->creationTime) / card->fadeIn;
  1255. if (fadeValue >= 1.0f)
  1256. {
  1257. card->fadeIn = 0.0f;
  1258. fadeValue = 1.0f;
  1259. }
  1260. multiplier = (udword)(fadeValue * 256.0f);
  1261. c = colRGB(colRed(c) * multiplier / 256, colGreen(c) * multiplier / 256, colBlue(c) * multiplier / 256);
  1262. }
  1263. //fade the the text out, if applicable
  1264. if (card->fadeOut != 0.0f)
  1265. { //if there's a fadeout
  1266. if (*subTimeElapsed >= card->creationTime + card->duration - card->fadeOut)
  1267. { //if it in the fading part
  1268. fadeValue = (*subTimeElapsed - (card->creationTime + card->duration - card->fadeOut)) / card->fadeOut;
  1269. fadeValue = max(0.0, 1.0f - fadeValue);
  1270. multiplier = (udword)(fadeValue * 256.0f);
  1271. c = colRGB(colRed(c) * multiplier / 256, colGreen(c) * multiplier / 256, colBlue(c) * multiplier / 256);
  1272. // dbgMessagef("<0x%2x>", multiplier);
  1273. }
  1274. }
  1275. //draw the text
  1276. /*
  1277. while (newLine != NULL)
  1278. {
  1279. fontPrintN(x, y, card->c, lineStart, newLine - lineStart);
  1280. y += fontHeight(" ") + 1;
  1281. x = card->margin;
  1282. newLine += NIS_NewLineLength;
  1283. lineStart = newLine;
  1284. newLine = strstr(newLine, NIS_NewLine);
  1285. }
  1286. */
  1287. if (card->bDropShadow)
  1288. {
  1289. fontShadowSet(FS_SE, colBlack);
  1290. }
  1291. fontPrint(x, y, c, lineStart);
  1292. }
  1293. }
  1294. if (region->picture != TR_InvalidHandle)
  1295. {
  1296. rectangle rect;
  1297. rect.x0 = region->rect.x0 + region->iconOffsetX;
  1298. rect.y0 = region->rect.y0 + region->iconOffsetY;
  1299. rect.x1 = rect.x0 + SUB_PictureWidth;
  1300. rect.y1 = rect.y0 + SUB_PictureHeight;
  1301. trMakeCurrent(region->picture);
  1302. glDisable(GL_ALPHA_TEST);
  1303. glEnable(GL_BLEND);
  1304. if (RGLtype == SWtype)
  1305. {
  1306. glTexCoord2f((real32)(-rect.x0), (real32)(-rect.y1));
  1307. glDrawPixels(SUB_PictureWidth, SUB_PictureHeight, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  1308. }
  1309. else
  1310. {
  1311. primRectSolidTextured2(&rect);
  1312. }
  1313. glDisable(GL_BLEND);
  1314. }
  1315. if (region->cardIndex == 0 && region == &subRegion[STR_LetterboxBar] && (!region->bAborted) && !region->bContinuousEvent)
  1316. { //if all fleet intel/command cards expired
  1317. subMessageEnded = 1;
  1318. }
  1319. fontShadowSet(FS_NONE, colBlack);
  1320. fontMakeCurrent(fhSave);
  1321. }
  1322. /*-----------------------------------------------------------------------------
  1323. Name : subTitlesFadeOut
  1324. Description : Causes all remaining subtitles in a region to fade out over
  1325. a specified period.
  1326. Inputs : region - what region to fade teh subtitles out of
  1327. fadeTime - duration of fadeout
  1328. Outputs :
  1329. Return : void
  1330. ----------------------------------------------------------------------------*/
  1331. void subTitlesFadeOut(subregion *region, real32 fadeTime)
  1332. {
  1333. sdword index;
  1334. subcard *card;
  1335. dbgAssert(region != NULL);
  1336. for (index = 0, card = region->card; index < region->cardIndex; index++, card++)
  1337. {
  1338. if (card->creationTime > *subTimeElapsed)
  1339. { //if card not started created yet
  1340. card->creationTime = *subTimeElapsed;
  1341. card->duration = -1; //make sure it never gets drawn
  1342. continue;
  1343. }
  1344. if (*subTimeElapsed - card->creationTime + fadeTime < card->duration)
  1345. { //if the card isn't going to fade out before the time specified on it's own
  1346. card->fadeOut = fadeTime; //start fading it out
  1347. card->duration = *subTimeElapsed - card->creationTime + fadeTime;//kill it when it's faded
  1348. }
  1349. }
  1350. region->bAborted = TRUE;
  1351. }