Advertisement
Darknessfmy

rA Localization system

May 15th, 2016
463
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Diff 66.66 KB | None | 0 0
  1. .gitignore                 |   1 +
  2.  conf/languages.conf        |  39 +++
  3.  conf/map_athena.conf       |   3 +
  4.  conf/msg_conf/map_msg.conf |  22 +-
  5.  lang/README.md             | 107 +++++++
  6.  src/common/cli.c           |  20 ++
  7.  src/common/cli.h           |   4 +
  8.  src/map/atcommand.c        | 170 ++++++++--
  9.  src/map/atcommand.h        |  17 +
  10.  src/map/map.c              | 116 ++-----
  11.  src/map/map.h              |  25 +-
  12.  src/map/npc.c              |   4 +
  13.  src/map/pc.c               |  31 +-
  14.  src/map/pc.h               |   3 +-
  15.  src/map/script.c           | 754 ++++++++++++++++++++++++++++++++++++++++++---
  16.  src/map/script.h           |  40 +++
  17.  16 files changed, 1164 insertions(+), 192 deletions(-)
  18.  
  19. diff --git a/.gitignore b/.gitignore
  20. index 1f3e0bf..eedaaad 100644
  21. --- a/.gitignore
  22. +++ b/.gitignore
  23. @@ -31,6 +31,7 @@ Thumbs.db
  24.  /config.status
  25.  /core
  26.  /ipch
  27. +/lang/*
  28.  /login-server
  29.  /Makefile
  30.  /Makefile.cache
  31. diff --git a/conf/languages.conf b/conf/languages.conf
  32. new file mode 100644
  33. index 0000000..2e6d12c
  34. --- /dev/null
  35. +++ b/conf/languages.conf
  36. @@ -0,0 +1,39 @@
  37. +//===== rAthena Configuration File ===========================
  38. +//
  39. +// Translations generated with '—-generate-translations' should be pointed to here
  40. +//============================================================
  41. +
  42. +// When employing more than one language, this setting is used as a fallback.
  43. +default_language: "English"
  44. +
  45. +// List of languages.
  46. +languages: {
  47. +   //===== Structure ============================================
  48. +   //
  49. +   //LanguageName: {                   // Name of language, used for looking up user's language on @langtype.
  50. +   //  lang: (                         // List of translations file(s) for NPC Script dialogs,
  51. +   //      "path/to/translation.po",   // use comma (,) to separated next file, don't forget the quotes, and
  52. +   //      "path/to/translation2a.po   // DO NOT use comma (,) for last file.
  53. +   //  )
  54. +   //  motd: "path/to/motd.file"       // For MOTD file, alternative conf/motd.txt for this language. TODO: Use PO.
  55. +   //  help: "path/to/help.file"       // For @Help file, alternative conf/help.txt for this language. TODO: Use PO.
  56. +   //}
  57. +   //============================================================
  58. +   // Examples:
  59. +   //
  60. +   //Spanish: {
  61. +   //  lang: (
  62. +   //      "lang/Spanish.po"
  63. +   //  )
  64. +   //}
  65. +   //Indonesian: {
  66. +   //  motd: "lang/Indonesian/motd.txt"
  67. +   //  help: "lang/Indonesian/help.txt"
  68. +   //  lang: (
  69. +   //      "lang/Indonesian/msg_conf.po",
  70. +   //      "lang/Indonesian/npc/airports/airship.po",
  71. +   //      "lang/Indonesian/npc/re/cities/malangdo.po",
  72. +   //      "lang/Indonesian/npc/re/jobs/novice/novice.po"
  73. +   //  )
  74. +   //}
  75. +}
  76. diff --git a/conf/map_athena.conf b/conf/map_athena.conf
  77. index 5df09c3..2f084d5 100644
  78. --- a/conf/map_athena.conf
  79. +++ b/conf/map_athena.conf
  80. @@ -119,6 +119,9 @@ help_txt: conf/help.txt
  81.  help2_txt: conf/help2.txt
  82.  charhelp_txt: conf/charhelp.txt
  83.  
  84. +// Translation file
  85. +language_conf: conf/languages.conf
  86. +
  87.  // Maps:
  88.  import: conf/maps_athena.conf
  89.  
  90. diff --git a/conf/msg_conf/map_msg.conf b/conf/msg_conf/map_msg.conf
  91. index 2e50a20..1cac0e7 100644
  92. --- a/conf/msg_conf/map_msg.conf
  93. +++ b/conf/msg_conf/map_msg.conf
  94. @@ -805,7 +805,7 @@
  95.  900: Usage:
  96.  901:   @send len <packet hex number>
  97.  902:   @send <packet hex number> {<value>}*
  98. -903:   Value: <type=B(default),W,L><number> or S<length>"<string>"
  99. +903:   Value: <type=B(default),W,L><number> or S<length>\"<string>\"
  100.  904: Packet 0x%x length: %d
  101.  905: Unknown packet: 0x%x
  102.  906: Not a string:
  103. @@ -1162,8 +1162,8 @@
  104.  1193: You're currently not autolooting this item.
  105.  1194: Removed item: '%s'/'%s' {%hu} from your autolootitem list.
  106.  1195: You can have %d items on your autolootitem list.
  107. -1196: To add an item to the list, use "@alootid +<item name or ID>". To remove an item, use "@alootid -<item name or ID>".
  108. -1197: "@alootid reset" will clear your autolootitem list.
  109. +1196: To add an item to the list, use \"@alootid +<item name or ID>\". To remove an item, use \"@alootid -<item name or ID>\".
  110. +1197: \"@alootid reset\" will clear your autolootitem list.
  111.  1198: Your autolootitem list is empty.
  112.  1199: Items on your autolootitem list:
  113.  1200: Your autolootitem list has been reset.
  114. @@ -1368,8 +1368,8 @@
  115.  
  116.  // @mapflag
  117.  1311: Enabled Mapflags in this map:
  118. -1312: Usage: "@mapflag monster_noteleport 1" (0=Off | 1=On)
  119. -1313: Type "@mapflag available" to list the available mapflags.
  120. +1312: Usage: \"@mapflag monster_noteleport 1\" (0=Off | 1=On)
  121. +1313: Type \"@mapflag available\" to list the available mapflags.
  122.  1314: Invalid flag name or flag.
  123.  1315: Available Flags:
  124.  
  125. @@ -1448,13 +1448,13 @@
  126.  
  127.  // @accinfo
  128.  1365: Usage: @accinfo/@accountinfo <account_id/char name>
  129. -1366: You may search partial name by making use of '%' in the search, ex. "@accinfo %Mario%" lists all characters whose name contains "Mario".
  130. +1366: You may search partial name by making use of '%' in the search, ex. \"@accinfo %Mario%\" lists all characters whose name contains \"Mario\".
  131.  
  132.  // @set
  133.  1367: Usage: @set <variable name> <value>
  134. -1368: Usage: ex. "@set PoringCharVar 50"
  135. -1369: Usage: ex. "@set PoringCharVarSTR$ Super Duper String"
  136. -1370: Usage: ex. "@set PoringCharVarSTR$" outputs its value, Super Duper String.
  137. +1368: Usage: ex. \"@set PoringCharVar 50\"
  138. +1369: Usage: ex. \"@set PoringCharVarSTR$ Super Duper String\"
  139. +1370: Usage: ex. \"@set PoringCharVarSTR$\" outputs its value, Super Duper String.
  140.  1371: NPC variables may not be used with @set.
  141.  1372: Instance variables may not be used with @set.
  142.  1373: %s value is now: %d
  143. @@ -1596,9 +1596,9 @@
  144.  1483: Autolooting item type: '%s' {%d}
  145.  1484: You're currently not autolooting this item type.
  146.  1485: Removed item type: '%s' {%d} from your autoloottype list.
  147. -1486: To add an item type to the list, use "@aloottype +<type name or ID>". To remove an item type, use "@aloottype -<type name or ID>".
  148. +1486: To add an item type to the list, use \"@aloottype +<type name or ID>\". To remove an item type, use \"@aloottype -<type name or ID>\".
  149.  1487: Type List: healing = 0, usable = 2, etc = 3, armor = 4, weapon = 5, card = 6, petegg = 7, petarmor = 8, ammo = 10
  150. -1488: "@aloottype reset" will clear your autoloottype list.
  151. +1488: \"@aloottype reset\" will clear your autoloottype list.
  152.  1489: Your autoloottype list is empty.
  153.  1490: Item types on your autoloottype list:
  154.  1491: Your autoloottype list has been reset.
  155. diff --git a/lang/README.md b/lang/README.md
  156. new file mode 100644
  157. index 0000000..62637bc
  158. --- /dev/null
  159. +++ b/lang/README.md
  160. @@ -0,0 +1,107 @@
  161. +# rAthena Language Library
  162. +=========
  163. +
  164. +### Table of Contents
  165. +---------
  166. +1. Generating Translation (.pot) File
  167. +2. Translating Language (.po) File
  168. +3. Adding New Language
  169. +4. Set Default Language
  170. +5. Change Language
  171. +6. Other Translation
  172. +7. Contributing
  173. +8. Known Translation Project
  174. +
  175. +## 1. Generating Translation (.pot) File
  176. +---------
  177. +Please makes sure map-server was compiled.
  178. +### Windows:
  179. +````
  180. +map-server.exe --generate-translations [path/to/generated.file]
  181. +````
  182. +### Linux:
  183. +````
  184. +map-server --generate-translations [path/to/generated.file]
  185. +````
  186. +
  187. +## 2. Translating Language (.po) File
  188. +---------
  189. +* After generating the translation/template .pot file, rename it to "My_Translation.po"
  190. +* Open the file, then try find this dialog.
  191. +````
  192. +#: npc/re/jobs/novice/novice.txt
  193. +# mes "Hello there! Welcome to the World of Ragnarok Online. My name is Sprakki and I'm in charge of giving you basic gameplay tips.";
  194. +msgctxt "Sprakki#newbe01::NvSprakkiA"
  195. +msgid "Hello there! Welcome to the World of Ragnarok Online. My name is Sprakki and I'm in charge of giving you basic gameplay tips."
  196. +msgstr ""
  197. +````
  198. +* Add/edit the dialog inside `msgstr ""` (Example in Indonesian from idRO) ````
  199. +#: npc/re/jobs/novice/novice.txt
  200. +# mes "Hello there! Welcome to the World of Ragnarok Online. My name is Sprakki and I'm in charge of giving you basic gameplay tips.";
  201. +msgctxt "Sprakki#newbe01::NvSprakkiA"
  202. +msgid "Hello there! Welcome to the World of Ragnarok Online. My name is Sprakki and I'm in charge of giving you basic gameplay tips."
  203. +msgstr "Halo! Selamat datang di Ragnarok Online Indonesia. Namaku adalah Sprakki dan aku disini untuk membantumu mengenai pengetahuan dasar bermain."
  204. +````
  205. +* Save the file.
  206. +**PS:** Steps above are translating manual using text editor.
  207. +
  208. +## 3. Adding New Language
  209. +---------
  210. +1. Open conf/translation.conf
  211. +2. Add your language name and the file for translations.
  212. +````
  213. +   My_Language: {                      // Langauge Name
  214. +       lang: (
  215. +           "lang/My_Translation.po"    // Translation file
  216. +       )
  217. +   }
  218. +````
  219. +
  220. +Notes:
  221. +* More than 1 language can be added
  222. +* More than 1 file can be listed for a language, use comma to separated the file pathname.
  223. +
  224. +
  225. +## 4. Set Default Language
  226. +---------
  227. +1. Before changing default language, amke sure the language is added.
  228. +2. Change the default language value in
  229. +````
  230. +default_language: "My_Language"
  231. +````
  232. +
  233. +
  234. +## 5. Change Language
  235. +---------
  236. +Use **@langtype** in-game. Example: `@langtype My_Language`.
  237. +The language changed will be stored stored as player's account variable.
  238. +
  239. +
  240. +## 6. Other Translation
  241. +---------
  242. +Besides NPC dialogs, translation is also available for:
  243. +* Atcommand messages (since multilanguage of msg_conf was depreciated).
  244. +* Atcommand Help message (@help)
  245. +* Message of The Day (MoTD)
  246. +````
  247. +translations: {
  248. +   My_Language: {                              // Langauge Name
  249. +       motd: "lang/Indonesian/motd.txt"        // MOTD Translation
  250. +       help: "lang/Indonesian/help.txt"        // Help Translation
  251. +       lang: (
  252. +           "lang/My_Translation/msg_conf.po"   // map_athena.conf Translation
  253. +       )
  254. +   }
  255. +}
  256. +````
  257. +
  258. +## 7. Contributing
  259. +---------
  260. +* rAthena **DOES NOT** officially manage the translation files. Please contact respective community that provide translation project.
  261. +* rAthena **ONLY** will lists the translation project that fit our regulations.
  262. +* Try visit [rAthena International Forums](https://rathena.org/board/forum/6-international-forums/) to dig informations.
  263. +* Originally by @Hercules https://github.com/HerculesWS/Hercules/commit/330e31cc71ece055908acb1eb967b4009ebc9c46, first ported to rAthena by @aleos89, restructured by @cydh.
  264. +
  265. +## 8. Known Translation Project
  266. +---------
  267. +* *No informations yet*
  268. diff --git a/src/common/cli.c b/src/common/cli.c
  269. index 2c9318a..ba5528f 100644
  270. --- a/src/common/cli.c
  271. +++ b/src/common/cli.c
  272. @@ -20,6 +20,7 @@
  273.  #include "showmsg.h"
  274.  #include "core.h"
  275.  #include "cli.h"
  276. +#include "malloc.h"
  277.  
  278.  //map confs
  279.  char* MAP_CONF_NAME;
  280. @@ -37,6 +38,9 @@
  281.  //common conf (used by multiple serv)
  282.  char* LAN_CONF_NAME; //char-login
  283.  char* MSG_CONF_NAME_EN; //all
  284. +char* TRANSLATION_CONF_FILE;
  285. +FILE *lang_export_fp;
  286. +char *lang_export_file;
  287.  
  288.  /**
  289.   * Function to check if the specified option has an argument following it.
  290. @@ -56,6 +60,12 @@ bool opt_has_next_value(const char* option, int i, int argc){
  291.     return true;
  292.  }
  293.  
  294. +bool opt_has_next_value_(const char* option, int i, int argc){
  295. +   if (i >= argc - 1)
  296. +       return false;
  297. +   return true;
  298. +}
  299. +
  300.  /**
  301.   * Display some information about the emulator, such as:
  302.   *   svn version
  303. @@ -171,6 +181,16 @@ int cli_get_options(int argc, char ** argv) {
  304.                 else if (strcmp(arg, "log-config") == 0) {
  305.                     if (opt_has_next_value(arg, i, argc))
  306.                         LOG_CONF_NAME = argv[++i];
  307. +               } else if (strcmp(arg, "generate-translations") == 0) {
  308. +                   if (opt_has_next_value_(arg, i, argc))
  309. +                       lang_export_file = aStrdup(argv[++i]);
  310. +                   else
  311. +                       lang_export_file = aStrdup("./lang/generated_translations.pot");
  312. +  
  313. +                   if (!(lang_export_fp = fopen(lang_export_file,"wb")))
  314. +                       ShowError("export-dialog: failed to open '%s' for writing!\n", lang_export_file);
  315. +  
  316. +                   runflag = CORE_ST_STOP;
  317.                 }
  318.                 else {
  319.                     ShowError("Unknown option '%s'.\n", argv[i]);
  320. diff --git a/src/common/cli.h b/src/common/cli.h
  321. index 4819dfa..7f28cdc 100644
  322. --- a/src/common/cli.h
  323. +++ b/src/common/cli.h
  324. @@ -24,6 +24,9 @@ extern "C" {
  325.   extern char* ATCOMMAND_CONF_FILENAME;
  326.   extern char* SCRIPT_CONF_NAME;
  327.   extern char* GRF_PATH_FILENAME;
  328. +// Set during startup when attempting to export the lang, unset after server initialization is over
  329. + extern FILE *lang_export_fp;
  330. + extern char *lang_export_file; // for lang_export_fp
  331.  //char
  332.   extern char* CHAR_CONF_NAME;
  333.   extern char* SQL_CONF_NAME;
  334. @@ -37,6 +40,7 @@ extern void display_helpscreen(bool exit);
  335.  bool cli_hasevent();
  336.  void display_versionscreen(bool do_exit);
  337.  bool opt_has_next_value(const char* option, int i, int argc);
  338. +bool opt_has_next_value_(const char* option, int i, int argc);
  339.  int cli_get_options(int argc, char ** argv);
  340.  int parse_console_timer(int tid, unsigned int tick, int id, intptr_t data);
  341.  extern int parse_console(const char* buf); //particular for each serv
  342. diff --git a/src/map/atcommand.c b/src/map/atcommand.c
  343. index deb6a73..4545dd6 100644
  344. --- a/src/map/atcommand.c
  345. +++ b/src/map/atcommand.c
  346. @@ -67,6 +67,8 @@ struct AliasInfo {
  347.     char alias[ATCOMMAND_LENGTH];
  348.  };
  349.  
  350. +char*** msg_table;
  351. +uint8 max_message_table;
  352.  
  353.  char atcommand_symbol = '@'; // first char of the commands
  354.  char charcommand_symbol = '#';
  355. @@ -96,6 +98,123 @@ struct atcmd_binding_data* get_atcommandbind_byname(const char* name) {
  356.     return ( i < atcmd_binding_count ) ? atcmd_binding[i] : NULL;
  357.  }
  358.  
  359. +const char* atcommand_msgsd(struct map_session_data *sd, int msg_number)
  360. +{
  361. +   if (!(msg_number >= 0 && msg_number < MAX_MSG))
  362. +       return "??";
  363. +   if (!sd || sd->lang_id >= max_message_table || !msg_table[sd->lang_id][msg_number] )
  364. +       return msg_table[0][msg_number];
  365. +   return msg_table[sd->lang_id][msg_number];
  366. +}
  367. +
  368. +/**
  369. + * Return the message string of the specified number by [Yor]
  370. + */
  371. +const char* atcommand_msg(int msg_number)
  372. +{
  373. +   if (msg_number >= 0 && msg_number < MAX_MSG) {
  374. +       if(msg_table[default_lang_id][msg_number] != NULL && msg_table[default_lang_id][msg_number][0] != '\0')
  375. +           return msg_table[default_lang_id][msg_number];
  376. +
  377. +       if(msg_table[0][msg_number] != NULL && msg_table[0][msg_number][0] != '\0')
  378. +           return msg_table[0][msg_number];
  379. +   }
  380. +
  381. +   return "??";
  382. +}
  383. +
  384. +/**
  385. + * Reads Message Data
  386. + *
  387. + * @param[in] cfg_name       configuration filename to read.
  388. + * @param[in] allow_override whether to allow duplicate message IDs to override the original value.
  389. + * @return success state.
  390. + */
  391. +bool msg_config_read(const char *cfg_name, bool allow_override)
  392. +{
  393. +   int msg_number, msg_count = 0;
  394. +   char line[1024], w1[16], w2[1024];
  395. +   FILE *fp;
  396. +   static int called = 0;
  397. +
  398. +   if ((fp = fopen(cfg_name, "r")) == NULL) {
  399. +       ShowError("Messages file not found: %s\n", cfg_name);
  400. +       return false;
  401. +   }
  402. +
  403. +   if (!max_message_table)
  404. +       atcommand_expand_message_table();
  405. +
  406. +   while(fgets(line, sizeof(line), fp)) {
  407. +       if (line[0] == '/' && line[1] == '/')
  408. +           continue;
  409. +       if (sscanf(line, "%15[^:]: %1023[^\r\n]", w1, w2) != 2)
  410. +           continue;
  411. +
  412. +       if (strcmpi(w1, "import") == 0) {
  413. +           msg_config_read(w2, true);
  414. +       } else {
  415. +           msg_number = atoi(w1);
  416. +           if (msg_number >= 0 && msg_number < MAX_MSG) {
  417. +               if (msg_table[0][msg_number] != NULL) {
  418. +                   if (!allow_override) {
  419. +                       ShowError("Duplicate message: ID '%d' was already used for '%s'. Message '%s' will be ignored.\n",
  420. +                           msg_number, w2, msg_table[0][msg_number]);
  421. +                       continue;
  422. +                   }
  423. +                   aFree(msg_table[0][msg_number]);
  424. +               }
  425. +               // This could easily become consecutive memory like get_str() and save the malloc overhead for over 1k calls [Ind]
  426. +               msg_table[0][msg_number] = (char *)aMalloc((strlen(w2) + 1)*sizeof (char));
  427. +               strcpy(msg_table[0][msg_number],w2);
  428. +               msg_count++;
  429. +           }
  430. +       }
  431. +   }
  432. +
  433. +   ShowInfo("Done reading "CL_WHITE"'%d'"CL_RESET" messages in "CL_WHITE"'%s'"CL_RESET".\n",msg_count,cfg_name);
  434. +   fclose(fp);
  435. +
  436. +   if (++called == 1) { // Original
  437. +       if (lang_export_fp) {
  438. +           int i;
  439. +
  440. +           for(i = 0; i < MAX_MSG;i++) {
  441. +               if (msg_table[0][i] != NULL ) {
  442. +                   fprintf(lang_export_fp, "#: map_msg.conf::[%d]\n"
  443. +                       "msgctxt \"map_msg.conf\"\n"
  444. +                       "msgid \"%s\"\n"
  445. +                       "msgstr \"\"\n",
  446. +                       i,
  447. +                       msg_table[0][i]
  448. +                   );
  449. +               }
  450. +           }
  451. +       }
  452. +   }
  453. +
  454. +   return true;
  455. +}
  456. +
  457. +/**
  458. + * Cleanup Message Data
  459. + */
  460. +void do_final_msg(void)
  461. +{
  462. +   int i, j;
  463. +
  464. +   for(i = 0; i < max_message_table; i++) {
  465. +       for (j = 0; j < MAX_MSG; j++) {
  466. +           if (msg_table[i][j])
  467. +               aFree(msg_table[i][j]);
  468. +       }
  469. +       aFree(msg_table[i]);
  470. +   }
  471. +
  472. +   if (msg_table)
  473. +       aFree(msg_table);
  474. +}
  475. +
  476.  /**
  477.   * retrieves the help string associated with a given command.
  478.   *
  479. @@ -3869,7 +3988,8 @@ static void atcommand_raise_sub(struct map_session_data* sd) {
  480.  
  481.         clif_displaymessage(fd, msg_txt(sd,100)); // Scripts have been reloaded.
  482.     } else if (strstr(command, "msgconf") || strncmp(message, "msgconf", 3) == 0) {
  483. -       map_msg_reload();
  484. +       do_final_msg();
  485. +       msg_config_read(MSG_CONF_NAME_EN, true);
  486.         clif_displaymessage(fd, msg_txt(sd,463)); // Message configuration has been reloaded.
  487.     } else if (strstr(command, "questdb") || strncmp(message, "questdb", 3) == 0) {
  488.         do_reload_quest();
  489. @@ -9424,38 +9544,31 @@ static inline void atcmd_channel_help(struct map_session_data *sd, const char *c
  490.     return 0;
  491.  }
  492.  
  493. -ACMD_FUNC(langtype)
  494. -{
  495. -   char langstr[8];
  496. -   int i=0, fail=0;
  497. +ACMD_FUNC(langtype) {
  498. +   char langstr[16];
  499. +   int i = 0;
  500.  
  501.     memset(langstr, '\0', sizeof(langstr));
  502.  
  503. -   if(sscanf(message, "%3s", langstr) >= 1){
  504. -       int lang=0;
  505. -       lang = msg_langstr2langtype(langstr); //Switch langstr to associated langtype
  506. -       if( msg_checklangtype(lang,false) == 1 ){ //Verify it's enabled and set it
  507. -           char output[100];
  508. -           pc_setaccountreg(sd, add_str("#langtype"), lang); //For login/char
  509. -           sd->langtype = lang;
  510. -           sprintf(output,msg_txt(sd,461),msg_langtype2langstr(lang)); // Language is now set to %s.
  511. -           clif_displaymessage(fd,output);
  512. -           return 0;
  513. -       } else if (lang != -1) { //defined langage but failed check
  514. -           clif_displaymessage(fd,msg_txt(sd,462)); // This langage is currently disabled.
  515. -           return -1;
  516. +   if(sscanf(message, "%15s", langstr) >= 1){
  517. +       for (i = 0; i < max_lang_id; i++) {
  518. +           if (strcmpi(languages[i], langstr) == 0)
  519. +               break;
  520.         }
  521. +       return pc_set_language(sd, i) ? 0 : -1;
  522.     }
  523.  
  524.     //wrong or no entry
  525.     clif_displaymessage(fd,msg_txt(sd,460)); // Please enter a valid language (usage: @langtype <language>).
  526.     clif_displaymessage(fd,msg_txt(sd,464)); // ---- Available languages:
  527. -   while(fail != -1){ //out of range
  528. -       fail = msg_checklangtype(i,false);
  529. -       if(fail == 1)
  530. -           clif_displaymessage(fd,msg_langtype2langstr(i));
  531. -       i++;
  532. +   for (i = 0; i < max_lang_id; i++) {
  533. +       char output[CHAT_SIZE_MAX];
  534. +       sprintf(output, "- %s", languages[i]);
  535. +       if (sd->lang_id == i)
  536. +           strcat(output, "*");
  537. +       clif_displaymessage(fd, output);
  538.     }
  539. +
  540.     return -1;
  541.  }
  542.  
  543. @@ -10550,6 +10663,17 @@ void atcommand_doload(void) {
  544.     atcommand_config_read(ATCOMMAND_CONF_FILENAME);
  545.  }
  546.  
  547. +void atcommand_expand_message_table(void) {
  548. +   RECREATE(msg_table, char **, ++max_message_table);
  549. +   CREATE(msg_table[max_message_table - 1], char *, MAX_MSG);
  550. +}
  551. +
  552. +void atcommand_msg_set(uint8 lang_id, uint16 num, char *ptr) {
  553. +   if (msg_table[lang_id][num] != NULL)
  554. +       aFree(msg_table[lang_id][num]);
  555. +   msg_table[lang_id][num] = aStrdup(ptr);
  556. +}
  557. +
  558.  void do_init_atcommand(void) {
  559.     atcommand_doload();
  560.  }
  561. diff --git a/src/map/atcommand.h b/src/map/atcommand.h
  562. index e1f02e8..210bcb9 100644
  563. --- a/src/map/atcommand.h
  564. +++ b/src/map/atcommand.h
  565. @@ -12,11 +12,20 @@ struct map_session_data;
  566.  //Note: The range is unlimited unless this define is set.
  567.  //#define AUTOLOOT_DISTANCE AREA_SIZE
  568.  
  569. +#define MAX_MSG 1500
  570. +#define msg_txt(sd, msg_number) atcommand_msgsd((sd), (msg_number))
  571. +
  572.  //global var
  573.  extern char atcommand_symbol;
  574.  extern char charcommand_symbol;
  575.  extern int atcmd_binding_count;
  576.  
  577. +/**
  578. + * msg_table[lang_id][msg_id]
  579. + **/
  580. +extern char*** msg_table;
  581. +extern uint8 max_message_table;
  582. +
  583.  typedef enum {
  584.     COMMAND_ATCOMMAND = 1,
  585.     COMMAND_CHARCOMMAND = 2,
  586. @@ -42,4 +51,12 @@ struct atcmd_binding_data {
  587.  struct atcmd_binding_data** atcmd_binding;
  588.  struct atcmd_binding_data* get_atcommandbind_byname(const char* name);
  589.  
  590. +const char* atcommand_msg(int msg_number);
  591. +const char* atcommand_msgsd(struct map_session_data *sd, int msg_number);
  592. +void atcommand_expand_message_table(void);
  593. +void atcommand_msg_set(uint8 lang_id, uint16 num, char *ptr);
  594. +
  595. +bool msg_config_read(const char *cfg_name, bool allow_override);
  596. +void do_final_msg(void);
  597. +
  598.  #endif /* _ATCOMMAND_H_ */
  599. diff --git a/src/map/map.c b/src/map/map.c
  600. index 13ebb5e..b4b2282 100644
  601. --- a/src/map/map.c
  602. +++ b/src/map/map.c
  603. @@ -15,6 +15,7 @@
  604.  #include "../common/cli.h"
  605.  #include "../common/ers.h"
  606.  
  607. +#include "atcommand.h"
  608.  #include "map.h"
  609.  #include "path.h"
  610.  #include "chrif.h"
  611. @@ -53,6 +54,9 @@
  612.  Sql* mmysql_handle;
  613.  Sql* qsmysql_handle; /// For query_sql
  614.  
  615. +const char *default_lang_str = "English";
  616. +uint8 default_lang_id = 0;
  617. +
  618.  int db_use_sqldbs = 0;
  619.  char buyingstores_db[32] = "buyingstores";
  620.  char buyingstore_items_db[32] = "buyingstore_items";
  621. @@ -102,8 +106,6 @@
  622.  static struct block_list *bl_list[BL_LIST_MAX];
  623.  static int bl_list_count = 0;
  624.  
  625. -#define MAP_MAX_MSG 1500
  626. -
  627.  struct map_data map[MAX_MAP_PER_SERVER];
  628.  int map_num = 0;
  629.  int map_port=0;
  630. @@ -146,6 +148,7 @@ struct map_cache_map_info {
  631.  char help_txt[256] = "conf/help.txt";
  632.  char help2_txt[256] = "conf/help2.txt";
  633.  char charhelp_txt[256] = "conf/charhelp.txt";
  634. +char language_conf[256] = "conf/languages.conf";
  635.  
  636.  char wisp_server_name[NAME_LENGTH] = "Server"; // can be modified in char-server configuration file
  637.  
  638. @@ -3882,6 +3885,8 @@ int map_config_read(char *cfgName)
  639.             console_msg_log = atoi(w2);//[Ind]
  640.         else if (strcmpi(w1, "console_log_filepath") == 0)
  641.             safestrncpy(console_log_filepath, w2, sizeof(console_log_filepath));
  642. +       else if (strcmpi(w1, "language_conf") == 0)
  643. +           safestrncpy(language_conf, w2, sizeof(language_conf));
  644.         else if (strcmpi(w1, "import") == 0)
  645.             map_config_read(w2);
  646.         else
  647. @@ -4503,6 +4508,7 @@ void display_helpscreen(bool do_exit)
  648.     ShowInfo("  --grf-path <file>\t\tAlternative GRF path configuration.\n");
  649.     ShowInfo("  --inter-config <file>\t\tAlternative inter-server configuration.\n");
  650.     ShowInfo("  --log-config <file>\t\tAlternative logging configuration.\n");
  651. +   ShowInfo("  --generate-translations <outfile>\tCreates 'outfile' (or default is './lang/generated_translations.pot') file with all translateable strings from scripts, server terminates afterwards.");
  652.     if( do_exit )
  653.         exit(EXIT_SUCCESS);
  654.  }
  655. @@ -4515,86 +4521,6 @@ void set_server_type(void)
  656.     SERVER_TYPE = ATHENA_SERVER_MAP;
  657.  }
  658.  
  659. -/*======================================================
  660. - * Message System
  661. - *------------------------------------------------------*/
  662. -struct msg_data {
  663. -   char* msg[MAP_MAX_MSG];
  664. -};
  665. -struct msg_data *map_lang2msgdb(uint8 lang){
  666. -   return (struct msg_data*)idb_get(map_msg_db, lang);
  667. -}
  668. -
  669. -void map_do_init_msg(void){
  670. -   int test=0, i=0, size;
  671. -   char * listelang[] = {
  672. -       MSG_CONF_NAME_EN,   //default
  673. -       MSG_CONF_NAME_RUS,
  674. -       MSG_CONF_NAME_SPN,
  675. -       MSG_CONF_NAME_GRM,
  676. -       MSG_CONF_NAME_CHN,
  677. -       MSG_CONF_NAME_MAL,
  678. -       MSG_CONF_NAME_IDN,
  679. -       MSG_CONF_NAME_FRN,
  680. -       MSG_CONF_NAME_POR,
  681. -       MSG_CONF_NAME_THA
  682. -   };
  683. -
  684. -   map_msg_db = idb_alloc(DB_OPT_BASE);
  685. -   size = ARRAYLENGTH(listelang); //avoid recalc
  686. -   while(test!=-1 && size>i){ //for all enable lang +(English default)
  687. -       test = msg_checklangtype(i,false);
  688. -       if(test == 1) msg_config_read(listelang[i],i); //if enabled read it and assign i to langtype
  689. -       i++;
  690. -   }
  691. -}
  692. -void map_do_final_msg(void){
  693. -   DBIterator *iter = db_iterator(map_msg_db);
  694. -   struct msg_data *mdb;
  695. -
  696. -   for (mdb = (struct msg_data *)dbi_first(iter); dbi_exists(iter); mdb = (struct msg_data *)dbi_next(iter)) {
  697. -       _do_final_msg(MAP_MAX_MSG,mdb->msg);
  698. -       aFree(mdb);
  699. -   }
  700. -   dbi_destroy(iter);
  701. -   map_msg_db->destroy(map_msg_db, NULL);
  702. -}
  703. -void map_msg_reload(void){
  704. -   map_do_final_msg(); //clear data
  705. -   map_do_init_msg();
  706. -}
  707. -int map_msg_config_read(char *cfgName, int lang){
  708. -   struct msg_data *mdb;
  709. -
  710. -   if( (mdb = map_lang2msgdb(lang)) == NULL )
  711. -       CREATE(mdb, struct msg_data, 1);
  712. -   else
  713. -       idb_remove(map_msg_db, lang);
  714. -   idb_put(map_msg_db, lang, mdb);
  715. -
  716. -   if(_msg_config_read(cfgName,MAP_MAX_MSG,mdb->msg)!=0){ //an error occur
  717. -       idb_remove(map_msg_db, lang); //@TRYME
  718. -       aFree(mdb);
  719. -   }
  720. -   return 0;
  721. -}
  722. -const char* map_msg_txt(struct map_session_data *sd, int msg_number){
  723. -   struct msg_data *mdb;
  724. -   uint8 lang = 0; //default
  725. -   if(sd && sd->langtype) lang = sd->langtype;
  726. -
  727. -   if( (mdb = map_lang2msgdb(lang)) != NULL){
  728. -       const char *tmp = _msg_txt(msg_number,MAP_MAX_MSG,mdb->msg);
  729. -       if(strcmp(tmp,"??")) //to verify result
  730. -           return tmp;
  731. -       ShowDebug("Message #%d not found for langtype %d.\n",msg_number,lang);
  732. -   }
  733. -   ShowDebug("Selected langtype %d not loaded, trying fallback...\n",lang);
  734. -   if(lang != 0 && (mdb = map_lang2msgdb(0)) != NULL) //fallback
  735. -       return _msg_txt(msg_number,MAP_MAX_MSG,mdb->msg);
  736. -   return "??";
  737. -}
  738. -
  739.  
  740.  /// Called when a terminate signal is received.
  741.  void do_shutdown(void)
  742. @@ -4628,22 +4554,10 @@ int do_init(int argc, char *argv[])
  743.     BATTLE_CONF_FILENAME = "conf/battle_athena.conf";
  744.     ATCOMMAND_CONF_FILENAME = "conf/atcommand_athena.conf";
  745.     SCRIPT_CONF_NAME = "conf/script_athena.conf";
  746. +   MSG_CONF_NAME_EN = "conf/msg_conf/map_msg.conf";
  747.     GRF_PATH_FILENAME = "conf/grf-files.txt";
  748.     safestrncpy(console_log_filepath, "./log/map-msg_log.log", sizeof(console_log_filepath));
  749.  
  750. -   /* Multilanguage */
  751. -   MSG_CONF_NAME_EN = "conf/msg_conf/map_msg.conf"; // English (default)
  752. -   MSG_CONF_NAME_RUS = "conf/msg_conf/map_msg_rus.conf";   // Russian
  753. -   MSG_CONF_NAME_SPN = "conf/msg_conf/map_msg_spn.conf";   // Spanish
  754. -   MSG_CONF_NAME_GRM = "conf/msg_conf/map_msg_grm.conf";   // German
  755. -   MSG_CONF_NAME_CHN = "conf/msg_conf/map_msg_chn.conf";   // Chinese
  756. -   MSG_CONF_NAME_MAL = "conf/msg_conf/map_msg_mal.conf";   // Malaysian
  757. -   MSG_CONF_NAME_IDN = "conf/msg_conf/map_msg_idn.conf";   // Indonesian
  758. -   MSG_CONF_NAME_FRN = "conf/msg_conf/map_msg_frn.conf";   // French
  759. -   MSG_CONF_NAME_POR = "conf/msg_conf/map_msg_por.conf";   // Brazilian Portuguese
  760. -   MSG_CONF_NAME_THA = "conf/msg_conf/map_msg_tha.conf";   // Thai
  761. -   /* Multilanguage */
  762. -
  763.     // Default map
  764.     safestrncpy(map_default.mapname, "prontera", MAP_NAME_LENGTH);
  765.     map_default.x = 156;
  766. @@ -4681,6 +4595,7 @@ int do_init(int argc, char *argv[])
  767.             chrif_setip(ip_str);
  768.     }
  769.  
  770. +   msg_config_read(MSG_CONF_NAME_EN, false);
  771.     battle_config_read(BATTLE_CONF_FILENAME);
  772.     script_config_read(SCRIPT_CONF_NAME);
  773.     inter_config_read(INTER_CONF_NAME);
  774. @@ -4714,8 +4629,7 @@ int do_init(int argc, char *argv[])
  775.     add_timer_func_list(map_clearflooritem_timer, "map_clearflooritem_timer");
  776.     add_timer_func_list(map_removemobs_timer, "map_removemobs_timer");
  777.     add_timer_interval(gettick()+1000, map_freeblock_timer, 0, 0, 60*1000);
  778. -  
  779. -   map_do_init_msg();
  780. +
  781.     do_init_atcommand();
  782.     do_init_battle();
  783.     do_init_instance();
  784. @@ -4746,6 +4660,14 @@ int do_init(int argc, char *argv[])
  785.  
  786.     npc_event_do_oninit();  // Init npcs (OnInit)
  787.  
  788. +   if (lang_export_fp) {
  789. +       ShowInfo("Lang exported to '"CL_WHITE"%s"CL_RESET"'\n", lang_export_file);
  790. +       fclose(lang_export_fp);
  791. +       aFree(lang_export_file);
  792. +       lang_export_file= NULL;
  793. +       lang_export_fp = NULL;
  794. +   }
  795. +
  796.     if (battle_config.pk_mode)
  797.         ShowNotice("Server is running on '"CL_WHITE"PK Mode"CL_RESET"'.\n");
  798.  
  799. diff --git a/src/map/map.h b/src/map/map.h
  800. index 6bfa24d..daad808 100644
  801. --- a/src/map/map.h
  802. +++ b/src/map/map.h
  803. @@ -9,7 +9,6 @@
  804.  #include "../common/mmo.h"
  805.  #include "../common/mapindex.h"
  806.  #include "../common/db.h"
  807. -#include "../common/msg_conf.h"
  808.  
  809.  #include "../config/core.h"
  810.  
  811. @@ -26,14 +25,6 @@ enum E_MAPSERVER_ST {
  812.     MAPSERVER_ST_LAST
  813.  };
  814.  
  815. -#define msg_config_read(cfgName,isnew) map_msg_config_read(cfgName,isnew)
  816. -#define msg_txt(sd,msg_number) map_msg_txt(sd,msg_number)
  817. -#define do_final_msg() map_do_final_msg()
  818. -int map_msg_config_read(char *cfgName,int lang);
  819. -const char* map_msg_txt(struct map_session_data *sd,int msg_number);
  820. -void map_do_final_msg(void);
  821. -void map_msg_reload(void);
  822. -
  823.  #define MAX_NPC_PER_MAP 512
  824.  #define AREA_SIZE battle_config.area_size
  825.  #define DAMAGELOG_SIZE 30
  826. @@ -731,6 +722,7 @@ extern char motd_txt[];
  827.  extern char help_txt[];
  828.  extern char help2_txt[];
  829.  extern char charhelp_txt[];
  830. +extern char language_conf[];
  831.  
  832.  extern char wisp_server_name[];
  833.  
  834. @@ -906,16 +898,8 @@ extern char *ATCOMMAND_CONF_FILENAME;
  835.  extern char *SCRIPT_CONF_NAME;
  836.  extern char *MSG_CONF_NAME_EN;
  837.  extern char *GRF_PATH_FILENAME;
  838. -//Other languages supported
  839. -char *MSG_CONF_NAME_RUS;
  840. -char *MSG_CONF_NAME_SPN;
  841. -char *MSG_CONF_NAME_GRM;
  842. -char *MSG_CONF_NAME_CHN;
  843. -char *MSG_CONF_NAME_MAL;
  844. -char *MSG_CONF_NAME_IDN;
  845. -char *MSG_CONF_NAME_FRN;
  846. -char *MSG_CONF_NAME_POR;
  847. -char *MSG_CONF_NAME_THA;
  848. +extern FILE *lang_export_fp;
  849. +extern char *lang_export_file;
  850.  
  851.  //Useful typedefs from jA [Skotlex]
  852.  typedef struct map_session_data TBL_PC;
  853. @@ -958,6 +942,9 @@ extern Sql* mmysql_handle;
  854.  extern Sql* qsmysql_handle;
  855.  extern Sql* logmysql_handle;
  856.  
  857. +extern const char *default_lang_str;
  858. +extern uint8 default_lang_id;
  859. +
  860.  extern char buyingstores_db[32];
  861.  extern char buyingstore_items_db[32];
  862.  extern char item_db_db[32];
  863. diff --git a/src/map/npc.c b/src/map/npc.c
  864. index 7f9f224..8d6331a 100644
  865. --- a/src/map/npc.c
  866. +++ b/src/map/npc.c
  867. @@ -2906,7 +2906,9 @@ static const char* npc_parse_script(char* w1, char* w2, char* w3, char* w4, cons
  868.     if( end == NULL )
  869.         return NULL;// (simple) parse error, don't continue
  870.  
  871. +   parser_current_npc_name = w3;
  872.     script = parse_script(script_start, filepath, strline(buffer,script_start-buffer), SCRIPT_USE_LABEL_DB);
  873. +   parser_current_npc_name = NULL;
  874.     label_list = NULL;
  875.     label_list_num = 0;
  876.     if( script )
  877. @@ -3613,7 +3615,9 @@ static const char* npc_parse_function(char* w1, char* w2, char* w3, char* w4, co
  878.     if( end == NULL )
  879.         return NULL;// (simple) parse error, don't continue
  880.  
  881. +   parser_current_npc_name = w3;
  882.     script = parse_script(script_start, filepath, strline(buffer,start-buffer), SCRIPT_RETURN_EMPTY_SCRIPT);
  883. +   parser_current_npc_name = NULL;
  884.     if( script == NULL )// parse error, continue
  885.         return end;
  886.  
  887. diff --git a/src/map/pc.c b/src/map/pc.c
  888. index d411e35..90cc1ef 100755
  889. --- a/src/map/pc.c
  890. +++ b/src/map/pc.c
  891. @@ -1338,9 +1338,7 @@ void pc_reg_received(struct map_session_data *sd)
  892.     sd->change_level_3rd = pc_readglobalreg(sd, add_str("jobchange_level_3rd"));
  893.     sd->die_counter = pc_readglobalreg(sd, add_str("PC_DIE_COUNTER"));
  894.  
  895. -   sd->langtype = pc_readaccountreg(sd, add_str("#langtype"));
  896. -   if (msg_checklangtype(sd->langtype,true) < 0)
  897. -       sd->langtype = 0; //invalid langtype reset to default
  898. +   pc_set_language(sd, pc_readaccountreg(sd, add_str("#langtype")));
  899.  
  900.     // Cash shop
  901.     sd->cashPoints = pc_readaccountreg(sd, add_str("#CASHPOINTS"));
  902. @@ -12025,6 +12023,33 @@ void pc_show_questinfo_reinit(struct map_session_data *sd) {
  903.  #endif
  904.  }
  905.  
  906. +/**
  907. + * Set language for player
  908. + * @param sd
  909. + * @param lang_id
  910. + * @return True if changed successfully, False if failed.
  911. + **/
  912. +bool pc_set_language(struct map_session_data *sd, uint8 lang_id) {
  913. +   nullpo_retr(false, sd);
  914. +
  915. +   if (lang_id < max_lang_id) {
  916. +       char output[CHAT_SIZE_MAX];
  917. +       pc_setaccountreg(sd, add_str("#langtype"), lang_id);
  918. +       sd->lang_id = lang_id;
  919. +       sprintf(output,msg_txt(sd,461),languages[lang_id]); // Language is now set to %s.
  920. +       clif_displaymessage(sd->fd,output);
  921. +       return true;
  922. +   }
  923. +
  924. +   // On invalid
  925. +   if (sd->lang_id != default_lang_id) {
  926. +       pc_setaccountreg(sd, add_str("#langtype"), lang_id);
  927. +       sd->lang_id = lang_id;
  928. +   }
  929. +
  930. +   clif_displaymessage(sd->fd,msg_txt(sd,462)); // This language is currently disabled.
  931. +   return false;
  932. +}
  933.  
  934.  /*==========================================
  935.   * pc Init/Terminate
  936. diff --git a/src/map/pc.h b/src/map/pc.h
  937. index 8bed5a3..b68302e 100644
  938. --- a/src/map/pc.h
  939. +++ b/src/map/pc.h
  940. @@ -267,7 +267,7 @@ struct map_session_data {
  941.     unsigned int permissions;/* group permissions */
  942.     int count_rewarp; //count how many time we being rewarped
  943.  
  944. -   int langtype;
  945. +   uint8 lang_id;
  946.     uint32 packet_ver;  // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04, 11: 21sept04, 12: 18oct04, 13: 25oct04 ... 18
  947.     struct mmo_charstatus status;
  948.  
  949. @@ -1234,6 +1234,7 @@ bool pc_is_same_equip_index(enum equip_index eqi, short *equip_index, short inde
  950.  int pc_autotrade_timer(int tid, unsigned int tick, int id, intptr_t data);
  951.  
  952.  void pc_validate_skill(struct map_session_data *sd);
  953. +bool pc_set_language(struct map_session_data *sd, uint8 lang_id);
  954.  
  955.  void pc_show_questinfo(struct map_session_data *sd);
  956.  void pc_show_questinfo_reinit(struct map_session_data *sd);
  957. diff --git a/src/map/script.c b/src/map/script.c
  958. index 9441d78..3598839 100644
  959. --- a/src/map/script.c
  960. +++ b/src/map/script.c
  961. @@ -12,6 +12,7 @@
  962.  #endif
  963.  
  964.  #include "../common/cbasetypes.h"
  965. +#include "../common/conf.h"
  966.  #include "../common/malloc.h"
  967.  #include "../common/md5calc.h"
  968.  #include "../common/nullpo.h"
  969. @@ -156,6 +157,9 @@ static inline void SETVALUE(unsigned char* buf, int i, int n)
  970.  static int str_size = 0; // size of the buffer
  971.  static int str_pos = 0; // next position to be assigned
  972.  
  973. +char **languages;
  974. +char **lang_motd_txt;
  975. +char **lang_help_txt;
  976.  
  977.  // Using a prime number for SCRIPT_HASH_SIZE should give better distributions
  978.  #define SCRIPT_HASH_SIZE 1021
  979. @@ -176,6 +180,12 @@ static inline void SETVALUE(unsigned char* buf, int i, int n)
  980.  static int buildin_callsub_ref = 0;
  981.  static int buildin_callfunc_ref = 0;
  982.  static int buildin_getelementofarray_ref = 0;
  983. +static int buildin_mes_offset = 0;
  984. +static int buildin_select_offset = 0;
  985. +static int buildin_menu_offset = 0;
  986. +static int buildin_dispbottom_offset = 0;
  987. +static int buildin_message_offset = 0;
  988. +static int buildin_lang_macro_offset = 0;
  989.  
  990.  // Caches compiled autoscript item code.
  991.  // Note: This is not cleared when reloading itemdb.
  992. @@ -234,9 +244,14 @@ enum e_arglist
  993.         int count;
  994.         int flag;
  995.         struct linkdb_node *case_label;
  996. -   } curly[256];       // Information right parenthesis
  997. -   int curly_count;    // The number of right brackets
  998. -   int index;          // Number of the syntax used in the script
  999. +   } curly[256];               // Information right parenthesis
  1000. +   int curly_count;            // The number of right brackets
  1001. +   int index;                  // Number of the syntax used in the script
  1002. +   int last_func;              // buildin index of the last parsed function
  1003. +   unsigned int nested_call;   // Don't really know what to call this
  1004. +   bool lang_macro_active;
  1005. +   DBMap *strings;             // string map parsed (used when exporting strings only)
  1006. +   DBMap *translation_db;      // non-null if this NPC has any translated strings to be linked
  1007.  } syntax;
  1008.  
  1009.  const char* parse_curly_close(const char* p);
  1010. @@ -364,6 +379,33 @@ enum {
  1011.     MF_SKILL_DAMAGE //60
  1012.  };
  1013.  
  1014. +static inline void script_string_buf_ensure(struct script_string_buf *buf, size_t ensure)
  1015. +{
  1016. +   if (buf->pos + ensure >= buf->size) {
  1017. +       do {
  1018. +           buf->size += 512;
  1019. +       } while (buf->pos+ensure >= buf->size);
  1020. +       RECREATE(buf->ptr, char, buf->size);
  1021. +   }
  1022. +}
  1023. +
  1024. +static inline void script_string_buf_addb(struct script_string_buf *buf, uint8 b)
  1025. +{
  1026. +   if (buf->pos + 1 >= buf->size) {
  1027. +       buf->size += 512;
  1028. +       RECREATE(buf->ptr, char, buf->size);
  1029. +   }
  1030. +
  1031. +   buf->ptr[buf->pos++] = b;
  1032. +}
  1033. +
  1034. +static inline void script_string_buf_destroy(struct script_string_buf *buf)
  1035. +{
  1036. +   if (buf->ptr)
  1037. +       aFree(buf->ptr);
  1038. +   memset(buf, 0, sizeof(struct script_string_buf));
  1039. +}
  1040. +
  1041.  const char* script_op2name(int op)
  1042.  {
  1043.  #define RETURN_OP_NAME(type) case type: return #type
  1044. @@ -382,6 +424,7 @@ const char* script_op2name(int op)
  1045.     RETURN_OP_NAME(C_RETINFO);
  1046.     RETURN_OP_NAME(C_USERFUNC);
  1047.     RETURN_OP_NAME(C_USERFUNC_POS);
  1048. +   RETURN_OP_NAME(C_LSTR);
  1049.  
  1050.     // operators
  1051.     RETURN_OP_NAME(C_OP3);
  1052. @@ -912,15 +955,32 @@ static int add_word(const char* p)
  1053.  static
  1054.  const char* parse_callfunc(const char* p, int require_paren, int is_custom)
  1055.  {
  1056. -   const char* p2;
  1057. -   const char* arg=NULL;
  1058. +   const char *p2;
  1059. +   const char *arg = NULL;
  1060.     int func;
  1061. +   bool nested_call = false, macro = false;
  1062.  
  1063.     func = add_word(p);
  1064. -   if( str_data[func].type == C_FUNC ){
  1065. -       // buildin function
  1066. -       add_scriptl(func);
  1067. -       add_scriptc(C_ARG);
  1068. +   if (str_data[func].type == C_FUNC) {
  1069. +       /** only when unset (-1), valid values are >= 0 **/
  1070. +       if (syntax.last_func == -1 )
  1071. +           syntax.last_func = str_data[func].val;
  1072. +       else { //Nested function call
  1073. +           syntax.nested_call++;
  1074. +           nested_call = true;
  1075. +          
  1076. +           if (str_data[func].val == buildin_lang_macro_offset) {
  1077. +               syntax.lang_macro_active = true;
  1078. +               macro = true;
  1079. +           }
  1080. +       }
  1081. +      
  1082. +       if (!macro) {
  1083. +           // buildin function
  1084. +           add_scriptl(func);
  1085. +           add_scriptc(C_ARG);
  1086. +       }
  1087. +
  1088.         arg = buildin_func[str_data[func].val].arg;
  1089.     } else if( str_data[func].type == C_USERFUNC || str_data[func].type == C_USERFUNC_POS ){
  1090.         // script defined function
  1091. @@ -1002,8 +1062,19 @@ const char* parse_callfunc(const char* p, int require_paren, int is_custom)
  1092.         if( *p != ')' )
  1093.             disp_error_message("parse_callfunc: expected ')' to close argument list",p);
  1094.         ++p;
  1095. +
  1096. +       if (str_data[func].val == buildin_lang_macro_offset)
  1097. +           syntax.lang_macro_active = false;
  1098.     }
  1099. -   add_scriptc(C_FUNC);
  1100. +
  1101. +   if (nested_call)
  1102. +       syntax.nested_call--;
  1103. +  
  1104. +   if (!syntax.nested_call)
  1105. +       syntax.last_func = -1;
  1106. +  
  1107. +   if (!macro)
  1108. +       add_scriptc(C_FUNC);
  1109.     return p;
  1110.  }
  1111.  
  1112. @@ -1199,6 +1270,25 @@ bool is_number(const char *p) {
  1113.     return false;
  1114.  }
  1115.  
  1116. +/**
  1117. + *
  1118. + **/
  1119. +int script_string_dup(char *str)
  1120. +{
  1121. +   size_t len = strlen(str);
  1122. +   int pos = string_list_pos;
  1123. +
  1124. +   while(pos + len + 1 >= string_list_size) {
  1125. +       string_list_size += (1024 * 1024) / 2;
  1126. +       RECREATE(string_list, char, string_list_size);
  1127. +   }
  1128. +
  1129. +   safestrncpy(string_list + pos, str, len + 1);
  1130. +   string_list_pos += len + 1;
  1131. +
  1132. +   return pos;
  1133. +}
  1134. +
  1135.  /*==========================================
  1136.   * Analysis section
  1137.   *------------------------------------------*/
  1138. @@ -1240,28 +1330,147 @@ const char* parse_simpleexpr(const char *p)
  1139.         add_scripti((int)i);
  1140.         p=np;
  1141.     } else if(*p=='"'){
  1142. -       add_scriptc(C_STR);
  1143. -       p++;
  1144. -       while( *p && *p != '"' ){
  1145. -           if( (unsigned char)p[-1] <= 0x7e && *p == '\\' )
  1146. -           {
  1147. -               char buf[8];
  1148. -               size_t len = skip_escaped_c(p) - p;
  1149. -               size_t n = sv_unescape_c(buf, p, len);
  1150. -               if( n != 1 )
  1151. -                   ShowDebug("parse_simpleexpr: unexpected length %d after unescape (\"%.*s\" -> %.*s)\n", (int)n, (int)len, p, (int)n, buf);
  1152. -               p += len;
  1153. -               add_scriptb(*buf);
  1154. -               continue;
  1155. +       struct string_translation *st = NULL;
  1156. +       const char *start_point = p;
  1157. +       bool duplicate = true;
  1158. +       struct script_string_buf *sbuf = &parse_simpleexpr_str;
  1159. +
  1160. +       do {
  1161. +           p++;
  1162. +           while(*p && *p != '"') {
  1163. +               if ((unsigned char)p[-1] <= 0x7e && *p == '\\') {
  1164. +                   char buf[8];
  1165. +                   size_t len = skip_escaped_c(p) - p;
  1166. +                   size_t n = sv_unescape_c(buf, p, len);
  1167. +                   if (n != 1)
  1168. +                       ShowDebug("parse_simpleexpr: unexpected length %d after unescape (\"%.*s\" -> %.*s)\n", (int)n, (int)len, p, (int)n, buf);
  1169. +                   p += len;
  1170. +                   script_string_buf_addb(sbuf, *buf);
  1171. +                   continue;
  1172. +               } else if( *p == '\n' ) {
  1173. +                   disp_error_message("parse_simpleexpr: unexpected newline @ string",p);
  1174. +               }
  1175. +               script_string_buf_addb(sbuf, *p++);
  1176. +           }
  1177. +           if (!*p)
  1178. +               disp_error_message("parse_simpleexpr: unexpected eof @ string",p);
  1179. +           p++; //'"'
  1180. +           p = skip_space(p);
  1181. +       } while(*p && *p == '"');
  1182. +
  1183. +       script_string_buf_addb(sbuf, 0);
  1184. +
  1185. +       if (!(syntax.translation_db && (st = strdb_get(syntax.translation_db, sbuf->ptr))) ) {
  1186. +           add_scriptc(C_STR);
  1187. +
  1188. +           if (script_pos + sbuf->pos >= script_size) {
  1189. +               do {
  1190. +                   script_size += SCRIPT_BLOCK_SIZE;
  1191. +               } while(script_pos + sbuf->pos >= script_size );
  1192. +               RECREATE(script_buf, unsigned char, script_size);
  1193.             }
  1194. -           else if( *p == '\n' )
  1195. -               disp_error_message("parse_simpleexpr: unexpected newline @ string",p);
  1196. -           add_scriptb(*p++);
  1197. -       }
  1198. -       if(!*p)
  1199. -           disp_error_message("parse_simpleexpr: unexpected eof @ string",p);
  1200. -       add_scriptb(0);
  1201. -       p++;    //'"'
  1202. +
  1203. +           memcpy(script_buf + script_pos, sbuf->ptr, sbuf->pos);
  1204. +           script_pos += sbuf->pos;
  1205. +       } else {
  1206. +           int expand = sizeof(int) + sizeof(uint8);
  1207. +           unsigned char j;
  1208. +           unsigned int st_cursor = 0;
  1209. +
  1210. +           add_scriptc(C_LSTR);
  1211. +
  1212. +           expand += (sizeof(char*) + sizeof(uint8)) * st->translations;
  1213. +
  1214. +           while(script_pos + expand >= script_size) {
  1215. +               script_size += SCRIPT_BLOCK_SIZE;
  1216. +               RECREATE(script_buf, unsigned char, script_size);
  1217. +           }
  1218. +
  1219. +           *((int *)(&script_buf[script_pos])) = st->string_id;
  1220. +           *((uint8 *)(&script_buf[script_pos + sizeof(int)])) = st->translations;
  1221. +
  1222. +           script_pos += sizeof(int) + sizeof(uint8);
  1223. +
  1224. +           for(j = 0; j < st->translations; j++) {
  1225. +               *((uint8 *)(&script_buf[script_pos])) = RBUFB(st->buf, st_cursor);
  1226. +               *((char **)(&script_buf[script_pos+sizeof(uint8)])) = &st->buf[st_cursor + sizeof(uint8)];
  1227. +               script_pos += sizeof(char*) + sizeof(uint8);
  1228. +               st_cursor += sizeof(uint8);
  1229. +               while(st->buf[st_cursor++]);
  1230. +               st_cursor += sizeof(uint8);
  1231. +           }
  1232. +       }
  1233. +
  1234. +       //* When exporting we don't know what is a translation and what isn't
  1235. +       if (lang_export_fp && sbuf->pos > 1) { //sbuf->pos will always be at least 1 because of the '\0'
  1236. +           if (!syntax.strings) {
  1237. +               syntax.strings = strdb_alloc(DB_OPT_DUP_KEY|DB_OPT_ALLOW_NULL_DATA, 0);
  1238. +           }
  1239. +
  1240. +           if (!strdb_exists(syntax.strings, sbuf->ptr)) {
  1241. +               strdb_put(syntax.strings, sbuf->ptr, NULL);
  1242. +               duplicate = false;
  1243. +           }
  1244. +       }
  1245. +
  1246. +       if (lang_export_fp && !duplicate &&
  1247. +           (((syntax.last_func == buildin_mes_offset ||
  1248. +                syntax.last_func == buildin_select_offset ||
  1249. +                syntax.last_func == buildin_menu_offset ||
  1250. +                syntax.last_func == buildin_message_offset ||
  1251. +                syntax.last_func == buildin_dispbottom_offset) && !syntax.nested_call
  1252. +               ) || syntax.lang_macro_active)) {
  1253. +           const char *line_start = start_point;
  1254. +           const char *line_end = start_point;
  1255. +           struct script_string_buf *lbuf = &lang_export_line_buf;
  1256. +           struct script_string_buf *ubuf = &lang_export_unescaped_buf;
  1257. +           size_t line_length, cursor;
  1258. +
  1259. +           while(line_start > parser_current_src) {
  1260. +               if (*line_start != '\n')
  1261. +                   line_start--;
  1262. +               else
  1263. +                   break;
  1264. +           }
  1265. +
  1266. +           while(*line_end != '\n' && *line_end != '\0')
  1267. +               line_end++;
  1268. +
  1269. +           line_length = (size_t)(line_end - line_start);
  1270. +
  1271. +           if (line_length > 0) {
  1272. +               script_string_buf_ensure(lbuf,line_length + 1);
  1273. +
  1274. +               memcpy(lbuf->ptr, line_start, line_length);
  1275. +               lbuf->pos = line_length;
  1276. +               script_string_buf_addb(lbuf, 0);
  1277. +
  1278. +               normalize_name(lbuf->ptr, "\r\n\t ");
  1279. +           }
  1280. +
  1281. +           for(cursor = 0; cursor < sbuf->pos; cursor++) {
  1282. +               if (sbuf->ptr[cursor] == '"')
  1283. +                   script_string_buf_addb(ubuf, '\\');
  1284. +               script_string_buf_addb(ubuf, sbuf->ptr[cursor]);
  1285. +           }
  1286. +           script_string_buf_addb(ubuf, 0);
  1287. +
  1288. +           fprintf(lang_export_fp, "#: %s\n"
  1289. +                                   "# %s\n"
  1290. +                                   "msgctxt \"%s\"\n"
  1291. +                                   "msgid \"%s\"\n"
  1292. +                                   "msgstr \"\"\n",
  1293. +                   parser_current_file ? parser_current_file : "Unknown File",
  1294. +                   lbuf->ptr,
  1295. +                   parser_current_npc_name ? parser_current_npc_name : "Unknown NPC",
  1296. +                   ubuf->ptr
  1297. +           );
  1298. +
  1299. +           lbuf->pos = 0;
  1300. +           ubuf->pos = 0;
  1301. +       }
  1302. +
  1303. +       sbuf->pos = 0;
  1304.     } else {
  1305.         int l;
  1306.         const char* pv;
  1307. @@ -2151,8 +2360,14 @@ static void add_buildin_func(void)
  1308.             if (!strcmp(buildin_func[i].name, "set")) buildin_set_ref = n;
  1309.             else if (!strcmp(buildin_func[i].name, "callsub")) buildin_callsub_ref = n;
  1310.             else if (!strcmp(buildin_func[i].name, "callfunc")) buildin_callfunc_ref = n;
  1311. -           else if( !strcmp(buildin_func[i].name, "getelementofarray") ) buildin_getelementofarray_ref = n;
  1312. -       }
  1313. +           else if( !strcmp(buildin_func[i].name, "getelementofarray")) buildin_getelementofarray_ref = n;
  1314. +           else if( !strcmp(buildin_func[i].name, "mes")) buildin_mes_offset = i;
  1315. +           else if( !strcmp(buildin_func[i].name, "select")) buildin_select_offset = i;
  1316. +           else if( !strcmp(buildin_func[i].name, "menu")) buildin_menu_offset = i;
  1317. +           else if( !strcmp(buildin_func[i].name, "dispbottom")) buildin_dispbottom_offset = i;
  1318. +           else if( !strcmp(buildin_func[i].name, "message")) buildin_message_offset = i;
  1319. +           else if( !strcmp(buildin_func[i].name, "_")) buildin_lang_macro_offset = i;
  1320. +       }
  1321.     }
  1322.  }
  1323.  
  1324. @@ -2333,14 +2548,28 @@ struct script_code* parse_script(const char *src,const char *file,int line,int o
  1325.     if( src == NULL )
  1326.         return NULL;// empty script
  1327.  
  1328. +   if (parse_cleanup_timer_id == INVALID_TIMER)
  1329. +       parse_cleanup_timer_id = add_timer(gettick() + 10, script_parse_cleanup_timer, 0, 0);
  1330. +
  1331. +   if (syntax.strings) // Used only when generating translation file
  1332. +       db_destroy(syntax.strings);
  1333. +
  1334.     memset(&syntax,0,sizeof(syntax));
  1335. -   if(first){
  1336. +   if (first) {
  1337.         add_buildin_func();
  1338.         read_constdb();
  1339.         script_hardcoded_constants();
  1340.         first=0;
  1341.     }
  1342.  
  1343. +   syntax.last_func = -1; // As valid values are >= 0
  1344. +   if (parser_current_npc_name) {
  1345. +       if (!translation_db)
  1346. +           script_load_translations();
  1347. +       if (translation_db)
  1348. +           syntax.translation_db = strdb_get(translation_db, parser_current_npc_name);
  1349. +   }
  1350. +
  1351.     script_buf=(unsigned char *)aMalloc(SCRIPT_BLOCK_SIZE*sizeof(unsigned char));
  1352.     script_pos=0;
  1353.     script_size=SCRIPT_BLOCK_SIZE;
  1354. @@ -4054,14 +4283,40 @@ void run_script_main(struct script_state *st)
  1355.             push_str(stack,C_CONSTSTR,(char*)(st->script->script_buf+st->pos));
  1356.             while(st->script->script_buf[st->pos++]);
  1357.             break;
  1358. +       case C_LSTR:
  1359. +           {
  1360. +               int string_id = *((int *)(&st->script->script_buf[st->pos]));
  1361. +               uint8 translations = *((uint8 *)(&st->script->script_buf[st->pos+sizeof(int)]));
  1362. +               struct map_session_data *lsd = NULL;
  1363. +
  1364. +               st->pos += sizeof(int) + sizeof(uint8);
  1365. +               if( (!st->rid || !(lsd = map_id2sd(st->rid)) || !lsd->lang_id) && !default_lang_id )
  1366. +                   script_pushconststr(st, string_list + string_id);
  1367. +               else {
  1368. +                   uint8 k, wlang_id = lsd ? lsd->lang_id : default_lang_id;
  1369. +                   int offset = st->pos;
  1370. +
  1371. +                   for(k = 0; k < translations; k++) {
  1372. +                       uint8 lang_id = *(uint8 *)(&st->script->script_buf[offset]);
  1373. +                       offset += sizeof(uint8);
  1374. +                       if (lang_id == wlang_id)
  1375. +                           break;
  1376. +                       offset += sizeof(char*);
  1377. +                   }
  1378. +
  1379. +                   script_pushconststr(st, (k == translations) ? string_list + string_id : *(char**)(&st->script->script_buf[offset]));
  1380. +               }
  1381. +               st->pos += ((sizeof(char*) + sizeof(uint8)) * translations);
  1382. +           }
  1383. +           break;
  1384.         case C_FUNC:
  1385.             run_func(st);
  1386. -           if(st->state==GOTO){
  1387. +           if (st->state == GOTO) {
  1388.                 st->state = RUN;
  1389. -               if( !st->freeloop && gotocount>0 && (--gotocount)<=0 ){
  1390. -                   ShowError("script:run_script_main: infinity loop !\n");
  1391. +               if (!st->freeloop && gotocount>0 && (--gotocount)<=0) {
  1392. +                   ShowError("run_script: infinity loop !\n");
  1393.                     script_reportsrc(st);
  1394. -                   st->state=END;
  1395. +                   st->state = END;
  1396.                 }
  1397.             }
  1398.             break;
  1399. @@ -4653,7 +4908,412 @@ void do_final_script() {
  1400.  
  1401.     aFree(logThreadData.entry);
  1402.  #endif
  1403. +
  1404. +   script_clear_translations(false);
  1405. +  
  1406. +   script_parser_clean_leftovers();
  1407. +  
  1408. +   if (lang_export_file)
  1409. +       aFree(lang_export_file);
  1410. +}
  1411. +
  1412. +/**
  1413. + *
  1414. + **/
  1415. +uint8 script_add_language(const char *name)
  1416. +{
  1417. +   uint8 lang_id = max_lang_id;
  1418. +  
  1419. +   RECREATE(languages, char *, ++max_lang_id);
  1420. +   RECREATE(lang_motd_txt, char *, max_lang_id);
  1421. +   RECREATE(lang_help_txt, char *, max_lang_id);
  1422. +
  1423. +   ShowInfo("Lang: '"CL_WHITE"%d"CL_RESET"': '"CL_WHITE"%s"CL_RESET"'.\n", lang_id, name);
  1424. +   languages[lang_id] = aStrdup(name);
  1425. +   lang_motd_txt[lang_id] = NULL;
  1426. +   lang_help_txt[lang_id] = NULL;
  1427. +
  1428. +   return lang_id;
  1429. +}
  1430. +
  1431. +static void script_add_motd_language(uint8 lang_id, const char *filepath) {
  1432. +   lang_motd_txt[lang_id] = aStrdup(filepath);
  1433.  }
  1434. +
  1435. +static void script_add_help_language(uint8 lang_id, const char *filepath) {
  1436. +   lang_help_txt[lang_id] = aStrdup(filepath);
  1437. +}
  1438. +
  1439. +/**
  1440. + * Parses conf/translations.conf file
  1441. + **/
  1442. +void script_load_translations(void) {
  1443. +   config_t translations_conf;
  1444. +   const char *config_filename = language_conf;
  1445. +   config_setting_t *languages_t = NULL;
  1446. +   int i, size;
  1447. +   uint32 total = 0;
  1448. +   uint8 k;
  1449. +
  1450. +   translation_db = strdb_alloc(DB_OPT_DUP_KEY, NAME_LENGTH * 2 + 1);
  1451. +  
  1452. +   if (languages) {
  1453. +       for(i = 0; i < max_lang_id; i++)
  1454. +           aFree(languages[i]);
  1455. +       aFree(languages);
  1456. +   }
  1457. +   if (lang_motd_txt) {
  1458. +       for(i = 0; i < max_lang_id; i++)
  1459. +           aFree(lang_motd_txt[i]);
  1460. +       aFree(lang_motd_txt);
  1461. +   }
  1462. +   if (lang_help_txt) {
  1463. +       for(i = 0; i < max_lang_id; i++)
  1464. +           aFree(lang_help_txt[i]);
  1465. +       aFree(lang_help_txt);
  1466. +   }
  1467. +   languages = NULL;
  1468. +   lang_motd_txt = NULL;
  1469. +   lang_help_txt = NULL;
  1470. +   max_lang_id = 0;
  1471. +
  1472. +   script_add_language("English"); // 0 is default, which is whatever is in the NPC files hardcoded (in our case, English)
  1473. +
  1474. +   if (conf_read_file(&translations_conf, config_filename)) {
  1475. +       ShowError("script_load_translations: Can't read '%s'\n", config_filename);
  1476. +       return;
  1477. +   }
  1478. +
  1479. +   if (config_lookup_string(&translations_conf, "default_language", &default_lang_str)) {
  1480. +       ShowInfo("Default language '%s'.\n", default_lang_str);
  1481. +   }
  1482. +
  1483. +   if (!(languages_t = config_lookup(&translations_conf, "languages")) ) {
  1484. +       ShowError("script_load_translations: Invalid 'languages' format on '%s'\n", config_filename);
  1485. +       return;
  1486. +   }
  1487. +
  1488. +   if (string_list)
  1489. +       aFree(string_list);
  1490. +
  1491. +   string_list = NULL;
  1492. +   string_list_pos = 0;
  1493. +   string_list_size = 0;
  1494. +
  1495. +   size = config_setting_length(languages_t);
  1496. +
  1497. +   for(i = 0; i < size; i++) {
  1498. +       uint8 j, lang_id = 0;
  1499. +       int count = 0;
  1500. +       config_setting_t *language_t = NULL, *lang_files = NULL;
  1501. +       const char *lang_name = NULL, *str = NULL;
  1502. +
  1503. +       if (!(language_t = config_setting_get_elem(languages_t, i)))
  1504. +           continue;
  1505. +
  1506. +       lang_name = config_setting_name(language_t);
  1507. +       lang_id = script_add_language(lang_name);
  1508. +
  1509. +       // MOTD
  1510. +       if (config_setting_lookup_string(language_t, "motd", &str)) {
  1511. +           script_add_motd_language(lang_id, str);
  1512. +           //ShowInfo("Added MOTD for %s: '"CL_WHITE"%s"CL_RESET"'\n", lang_name, str);
  1513. +       }
  1514. +
  1515. +       // @help
  1516. +       if (config_setting_lookup_string(language_t, "help", &str)) {
  1517. +           script_add_help_language(lang_id, str);
  1518. +           //ShowInfo("Added @help for %s: '"CL_WHITE"%s"CL_RESET"'\n", lang_name, str);
  1519. +       }
  1520. +
  1521. +       // Translations
  1522. +       if (!(lang_files = config_setting_get_member(language_t, "lang")) ) {
  1523. +           ShowError("script_load_translations: Invalid 'lang' format on '%s'\n", config_filename);
  1524. +           continue;
  1525. +       }
  1526. +
  1527. +       count = config_setting_length(lang_files);
  1528. +       if (!count) {
  1529. +           ShowWarning("script_load_translations: Language '%s' is defined with no translation file. Skipping...\n", lang_name);
  1530. +           continue;
  1531. +       }
  1532. +
  1533. +       for (j = 0; j < count; j++) {
  1534. +           const char *translation_file = config_setting_get_string_elem(lang_files, j);;
  1535. +           total += script_load_translation(lang_id, translation_file);
  1536. +       }
  1537. +   }
  1538. +
  1539. +   if (total) {
  1540. +       DBIterator *main_iter;
  1541. +       DBIterator *sub_iter;
  1542. +       DBMap *string_db;
  1543. +       struct string_translation *st = NULL;
  1544. +       uint32 j = 0;
  1545. +
  1546. +       CREATE(translation_buf, char *, total);
  1547. +       translation_buf_size = total;
  1548. +
  1549. +       main_iter = db_iterator(translation_db);
  1550. +      
  1551. +       for(string_db = dbi_first(main_iter); dbi_exists(main_iter); string_db = dbi_next(main_iter) ) {
  1552. +           sub_iter = db_iterator(string_db);
  1553. +
  1554. +           for(st = dbi_first(sub_iter); dbi_exists(sub_iter); st = dbi_next(sub_iter) ) {
  1555. +               translation_buf[j++] = st->buf;
  1556. +           }
  1557. +
  1558. +           dbi_destroy(sub_iter);
  1559. +       }
  1560. +
  1561. +       dbi_destroy(main_iter);
  1562. +   }
  1563. +
  1564. +   for(k = 0; k < max_lang_id; k++) {
  1565. +       if( !strcmpi(languages[k], default_lang_str) ) {
  1566. +           break;
  1567. +       }
  1568. +   }
  1569. +
  1570. +   if (k == max_lang_id) {
  1571. +       ShowError("script_load_translations: Map server 'default_language' setting '%s' is not a loaded language.\n", default_lang_str);
  1572. +       default_lang_id = 0;
  1573. +   } else
  1574. +       default_lang_id = k;
  1575. +
  1576. +   config_destroy(&translations_conf);
  1577. +}
  1578. +
  1579. +/**
  1580. + * Parses a individual translation file
  1581. + **/
  1582. +uint32 script_load_translation(uint8 lang_id, const char *file) {
  1583. +   uint32 translations = 0;
  1584. +   char line[1024];
  1585. +   char msgctxt[NAME_LENGTH * 2 + 1] = { 0 };
  1586. +   DBMap *string_db;
  1587. +   size_t i;
  1588. +   FILE *fp;
  1589. +   struct script_string_buf msgid = { 0 }, msgstr = { 0 };
  1590. +
  1591. +   if (file == NULL)
  1592. +       return 0;
  1593. +
  1594. +   if (!(fp = fopen(file,"rb")) ) {
  1595. +       ShowError("script_load_translation: Failed to open '%s' for reading.\n", file);
  1596. +       return 0;
  1597. +   }
  1598. +
  1599. +   if (lang_id >= max_message_table)
  1600. +       atcommand_expand_message_table();
  1601. +
  1602. +   while(fgets(line, sizeof(line), fp)) {
  1603. +       size_t len = strlen(line), cursor = 0;
  1604. +
  1605. +       if (len <= 1)
  1606. +           continue;
  1607. +
  1608. +       if (line[0] == '#')
  1609. +           continue;
  1610. +
  1611. +       if (strncasecmp(line,"msgctxt \"", 9) == 0) {
  1612. +           msgctxt[0] = '\0';
  1613. +           for(i = 9; i < len - 2; i++) {
  1614. +               if (line[i] == '\\' && line[i+1] == '"') {
  1615. +                   msgctxt[cursor] = '"';
  1616. +                   i++;
  1617. +               } else
  1618. +                   msgctxt[cursor] = line[i];
  1619. +               if (++cursor >= sizeof(msgctxt) - 1)
  1620. +                   break;
  1621. +           }
  1622. +           msgctxt[cursor] = '\0';
  1623. +       } else if (strncasecmp(line, "msgid \"", 7) == 0) {
  1624. +           msgid.pos = 0;
  1625. +           for(i = 7; i < len - 2; i++) {
  1626. +               if (line[i] == '\\' && line[i+1] == '"') {
  1627. +                   script_string_buf_addb(&msgid, '"');
  1628. +                   i++;
  1629. +               } else
  1630. +                   script_string_buf_addb(&msgid, line[i]);
  1631. +           }
  1632. +           script_string_buf_addb(&msgid,0);
  1633. +       } else if (len > 9 && line[9] != '"' && strncasecmp(line, "msgstr \"",8) == 0) {
  1634. +           msgstr.pos = 0;
  1635. +           for(i = 8; i < len - 2; i++) {
  1636. +               if (line[i] == '\\' && line[i+1] == '"') {
  1637. +                   script_string_buf_addb(&msgstr, '"');
  1638. +                   i++;
  1639. +               } else
  1640. +                   script_string_buf_addb(&msgstr, line[i]);
  1641. +           }
  1642. +           script_string_buf_addb(&msgstr,0);
  1643. +       }
  1644. +
  1645. +       if( msgctxt[0] && msgid.pos > 1 && msgstr.pos > 1 ) {
  1646. +           size_t msgstr_len = msgstr.pos;
  1647. +           unsigned int inner_len = 1 + (uint32)msgstr_len + 1; //uint8 lang_id + msgstr_len + '\0'
  1648. +
  1649. +           if (strcasecmp(msgctxt, "map_msg.conf") == 0) {
  1650. +               int k;
  1651. +              
  1652. +               for(k = 0; k < MAX_MSG; k++) {
  1653. +                   if (msgid.ptr != NULL && msg_table[0][k] && strcmpi(msg_table[0][k],msgid.ptr) == 0) {
  1654. +                       atcommand_msg_set(lang_id,k,msgstr.ptr);
  1655. +                       break;
  1656. +                   }
  1657. +               }
  1658. +           } else {
  1659. +               struct string_translation *st = NULL;
  1660. +
  1661. +               if (!( string_db = strdb_get(translation_db, msgctxt))) {
  1662. +                   string_db = strdb_alloc(DB_OPT_DUP_KEY, 0);
  1663. +
  1664. +                   strdb_put(translation_db, msgctxt, string_db);
  1665. +               }
  1666. +
  1667. +               if (!(st = strdb_get(string_db, msgid.ptr))) {
  1668. +                   CREATE(st, struct string_translation, 1);
  1669. +
  1670. +                   st->string_id = script_string_dup(msgid.ptr);
  1671. +
  1672. +                   strdb_put(string_db, msgid.ptr, st);
  1673. +               }
  1674. +
  1675. +               RECREATE(st->buf, char, st->len + inner_len);
  1676. +
  1677. +               WBUFB(st->buf, st->len) = lang_id;
  1678. +               safestrncpy((char*)WBUFP(st->buf, st->len + 1), msgstr.ptr, msgstr_len + 1);
  1679. +
  1680. +               st->translations++;
  1681. +               st->len += inner_len;
  1682. +           }
  1683. +           msgctxt[0] = '\0';
  1684. +           msgid.pos = msgstr.pos = 0;
  1685. +           translations++;
  1686. +       }
  1687. +   }
  1688. +
  1689. +   fclose(fp);
  1690. +
  1691. +   script_string_buf_destroy(&msgid);
  1692. +   script_string_buf_destroy(&msgstr);
  1693. +
  1694. +   ShowStatus("Done reading '"CL_WHITE"%u"CL_RESET"' translations in '"CL_WHITE"%s"CL_RESET"'.\n", translations, file);
  1695. +   return translations;
  1696. +}
  1697. +
  1698. +/**
  1699. + *
  1700. + **/
  1701. +void script_clear_translations(bool reload)
  1702. +{
  1703. +   uint32 i;
  1704. +
  1705. +   if (string_list)
  1706. +       aFree(string_list);
  1707. +
  1708. +   string_list = NULL;
  1709. +   string_list_pos = 0;
  1710. +   string_list_size = 0;
  1711. +
  1712. +   if (translation_buf) {
  1713. +       for(i = 0; i < translation_buf_size; i++) {
  1714. +           aFree(translation_buf[i]);
  1715. +       }
  1716. +       aFree(translation_buf);
  1717. +   }
  1718. +
  1719. +   translation_buf = NULL;
  1720. +   translation_buf_size = 0;
  1721. +
  1722. +   if (languages) {
  1723. +       for(i = 0; i < max_lang_id; i++)
  1724. +           aFree(languages[i]);
  1725. +       aFree(languages);
  1726. +   }
  1727. +   if (lang_motd_txt) {
  1728. +       for(i = 0; i < max_lang_id; i++)
  1729. +           aFree(lang_motd_txt[i]);
  1730. +       aFree(lang_motd_txt);
  1731. +   }
  1732. +   if (lang_help_txt) {
  1733. +       for(i = 0; i < max_lang_id; i++)
  1734. +           aFree(lang_help_txt[i]);
  1735. +       aFree(lang_help_txt);
  1736. +   }
  1737. +   languages = NULL;
  1738. +   lang_motd_txt = NULL;
  1739. +   lang_help_txt = NULL;
  1740. +   max_lang_id = 0;
  1741. +
  1742. +   if (translation_db) {
  1743. +       translation_db->clear(translation_db, script_translation_db_destroyer);
  1744. +   }
  1745. +
  1746. +   if (reload)
  1747. +       script_load_translations();
  1748. +}
  1749. +
  1750. +/**
  1751. + *
  1752. + **/
  1753. +int script_translation_db_destroyer(DBKey key, DBData *data, va_list ap)
  1754. +{
  1755. +   DBMap *string_db = db_data2ptr(data);
  1756. +
  1757. +   if (db_size(string_db)) {
  1758. +       DBIterator *iter = db_iterator(string_db);
  1759. +       struct string_translation *st = NULL;
  1760. +
  1761. +       for(st = dbi_first(iter); dbi_exists(iter); st = dbi_next(iter)) {
  1762. +           aFree(st);
  1763. +       }
  1764. +
  1765. +       dbi_destroy(iter);
  1766. +   }
  1767. +
  1768. +   db_destroy(string_db);
  1769. +   return 0;
  1770. +}
  1771. +
  1772. +/**
  1773. + *
  1774. + **/
  1775. +void script_parser_clean_leftovers(void) {
  1776. +  
  1777. +   //if (script_buf)
  1778. +   //  aFree(script_buf);
  1779. +  
  1780. +   script_buf = NULL;
  1781. +   script_size = 0;
  1782. +
  1783. +   if (translation_db) {
  1784. +       translation_db->destroy(translation_db, script_translation_db_destroyer);
  1785. +       translation_db = NULL;
  1786. +   }
  1787. +
  1788. +   if (syntax.strings) { // Used only when generating translation file
  1789. +       db_destroy(syntax.strings);
  1790. +       syntax.strings = NULL;
  1791. +   }
  1792. +
  1793. +   script_string_buf_destroy(&parse_simpleexpr_str);
  1794. +   script_string_buf_destroy(&lang_export_line_buf);
  1795. +   script_string_buf_destroy(&lang_export_unescaped_buf);
  1796. +}
  1797. +
  1798. +/**
  1799. + * Performs cleanup after all parsing is processed
  1800. + **/
  1801. +int script_parse_cleanup_timer(int tid, unsigned int tick, int id, intptr_t data)
  1802. +{
  1803. +   script_parser_clean_leftovers();
  1804. +   parse_cleanup_timer_id = INVALID_TIMER;
  1805. +
  1806. +   return 0;
  1807. +}
  1808. +
  1809.  /*==========================================
  1810.   * Initialization
  1811.   *------------------------------------------*/
  1812. @@ -4662,6 +5322,7 @@ void do_init_script(void) {
  1813.     userfunc_db = strdb_alloc(DB_OPT_DUP_KEY,0);
  1814.     scriptlabel_db = strdb_alloc(DB_OPT_DUP_KEY,50);
  1815.     autobonus_db = strdb_alloc(DB_OPT_DUP_KEY,0);
  1816. +   parse_cleanup_timer_id = INVALID_TIMER;
  1817.  
  1818.     st_ers = ers_new(sizeof(struct script_state), "script.c::st_ers", ERS_CACHE_OPTIONS);
  1819.     stack_ers = ers_new(sizeof(struct script_stack), "script.c::script_stack", ERS_OPT_FLEX_CHUNK);
  1820. @@ -4697,6 +5358,8 @@ void do_init_script(void) {
  1821.  
  1822.     add_timer_func_list(queryThread_timer, "queryThread_timer");
  1823.  #endif
  1824. +
  1825. +   script_load_translations();
  1826.  }
  1827.  
  1828.  void script_reload(void) {
  1829. @@ -4741,6 +5404,14 @@ void script_reload(void) {
  1830.     dbi_destroy(iter);
  1831.     db_clear(st_db);
  1832.  
  1833. +
  1834. +   script_clear_translations(true);
  1835. +
  1836. +   if (parse_cleanup_timer_id != INVALID_TIMER) {
  1837. +       delete_timer(parse_cleanup_timer_id, script_parse_cleanup_timer);
  1838. +       parse_cleanup_timer_id = INVALID_TIMER;
  1839. +   }
  1840. +
  1841.     mapreg_reload();
  1842.  }
  1843.  
  1844. @@ -21302,6 +21973,11 @@ static int atcommand_cleanfloor_sub(struct block_list *bl, va_list ap)
  1845.  
  1846.  #include "../custom/script.inc"
  1847.  
  1848. +/** place holder for the translation macro **/
  1849. +BUILDIN_FUNC(_) {
  1850. +   return true;
  1851. +}
  1852. +
  1853.  // declarations that were supposed to be exported from npc_chat.c
  1854.  #ifdef PCRE_SUPPORT
  1855.  BUILDIN_FUNC(defpattern);
  1856. @@ -21877,5 +22553,7 @@ struct script_function buildin_func[] = {
  1857.  
  1858.  #include "../custom/script_def.inc"
  1859.  
  1860. +   BUILDIN_DEF(_, "s"),
  1861. +
  1862.     {NULL,NULL,NULL},
  1863.  };
  1864. diff --git a/src/map/script.h b/src/map/script.h
  1865. index b121c6c..eea37de 100644
  1866. --- a/src/map/script.h
  1867. +++ b/src/map/script.h
  1868. @@ -177,6 +177,7 @@ typedef enum c_op {
  1869.     C_USERFUNC, // internal script function
  1870.     C_USERFUNC_POS, // internal script function label
  1871.     C_REF, // the next call to c_op2 should push back a ref to the left operand
  1872. +   C_LSTR,
  1873.  
  1874.     // operators
  1875.     C_OP3, // a ? b : c
  1876. @@ -291,6 +292,18 @@ struct script_array {
  1877.     unsigned int *members; ///< member list
  1878.  };
  1879.  
  1880. +struct script_string_buf {
  1881. +   char *ptr;
  1882. +   size_t pos,size;
  1883. +};
  1884. +
  1885. +struct string_translation {
  1886. +   int string_id;
  1887. +   uint8 translations;
  1888. +   unsigned int len;
  1889. +   char *buf;
  1890. +};
  1891. +
  1892.  enum script_parse_options {
  1893.     SCRIPT_USE_LABEL_DB = 0x1,// records labels in scriptlabel_db
  1894.     SCRIPT_IGNORE_EXTERNAL_BRACKETS = 0x2,// ignores the check for {} brackets around the script
  1895. @@ -644,6 +657,24 @@ unsigned int next_id;
  1896.  struct eri *st_ers;
  1897.  struct eri *stack_ers;
  1898.  
  1899. +/// Script String Storage
  1900. +char *string_list;
  1901. +int string_list_size;
  1902. +int string_list_pos;
  1903. +// Set and unset on npc_parse_script
  1904. +char *parser_current_npc_name;
  1905. +DBMap *translation_db; // npc_name => DBMap (strings)
  1906. +char **translation_buf;
  1907. +uint32 translation_buf_size;
  1908. +extern char **languages;
  1909. +extern char **lang_motd_txt;
  1910. +extern char **lang_help_txt;
  1911. +uint8 max_lang_id;
  1912. +struct script_string_buf parse_simpleexpr_str;
  1913. +struct script_string_buf lang_export_line_buf;
  1914. +struct script_string_buf lang_export_unescaped_buf;
  1915. +int parse_cleanup_timer_id;
  1916. +
  1917.  const char* skip_space(const char* p);
  1918.  void script_error(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos);
  1919.  void script_warning(const char* src, const char* file, int start_line, const char* error_msg, const char* error_pos);
  1920. @@ -685,6 +716,15 @@ int add_str(const char* p);
  1921.  const char* get_str(int id);
  1922.  void script_reload(void);
  1923.  
  1924. +int string_dup(char *str);
  1925. +void script_load_translations(void);
  1926. +uint32 script_load_translation(uint8 lang_id, const char *file);
  1927. +int script_translation_db_destroyer(DBKey key, DBData *data, va_list ap);
  1928. +void script_clear_translations(bool reload);
  1929. +int script_parse_cleanup_timer(int tid, unsigned int tick, int id, intptr_t data);
  1930. +uint8 script_add_language(const char *name);
  1931. +void script_parser_clean_leftovers(void);
  1932. +
  1933.  // @commands (script based)
  1934.  void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct reg_db *ref);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement