diff --git a/Makefile b/Makefile index bd4c167f..c756866c 100644 --- a/Makefile +++ b/Makefile @@ -963,6 +963,7 @@ GAME_OBJS_ = \ src/game/g_sphere.o \ src/game/g_svcmds.o \ src/game/g_target.o \ + src/game/g_translate.o \ src/game/g_trigger.o \ src/game/g_turret.o \ src/game/g_utils.o \ diff --git a/src/game/g_func.c b/src/game/g_func.c index 993b43c2..88642387 100644 --- a/src/game/g_func.c +++ b/src/game/g_func.c @@ -2618,7 +2618,7 @@ door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, self->touch_debounce_time = level.time + 5.0; - gi.centerprintf(other, "%s", self->message); + gi.centerprintf(other, "%s", LocalizationMessage(self->message)); gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); } diff --git a/src/game/g_newfnc.c b/src/game/g_newfnc.c index e48b899c..7a69e590 100644 --- a/src/game/g_newfnc.c +++ b/src/game/g_newfnc.c @@ -228,7 +228,7 @@ secret_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurfa if (self->message) { - gi.centerprintf(other, self->message); + gi.centerprintf(other, LocalizationMessage(self->message)); } } diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c index 72815cd3..ed9a9173 100644 --- a/src/game/g_spawn.c +++ b/src/game/g_spawn.c @@ -865,8 +865,8 @@ SP_worldspawn(edict_t *ent) /* make some data visible to the server */ if (ent->message && ent->message[0]) { - gi.configstring(CS_NAME, ent->message); - Q_strlcpy(level.level_name, ent->message, sizeof(level.level_name)); + Q_strlcpy(level.level_name, LocalizationMessage(ent->message), sizeof(level.level_name)); + gi.configstring(CS_NAME, level.level_name); } else { diff --git a/src/game/g_translate.c b/src/game/g_translate.c new file mode 100644 index 00000000..ae634401 --- /dev/null +++ b/src/game/g_translate.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * ======================================================================= + * + * Localization logic. + * + * ======================================================================= + */ + +#include "header/local.h" + +localmessages_t *localmessages = NULL; +int nlocalmessages = 0; + +static int +LocalizationSort(const void *p1, const void *p2) +{ + localmessages_t *msg1, *msg2; + + msg1 = (localmessages_t*)p1; + msg2 = (localmessages_t*)p2; + return Q_stricmp(msg1->key, msg2->key); +} + +void +LocalizationInit(void) +{ + byte *raw = NULL; + int len; + + localmessages = NULL; + nlocalmessages = 0; + + /* load the file */ + len = gi.FS_LoadFile("localization/loc_english.txt", (void **)&raw); + if (len > 1) + { + char *buf, *curr; + int i; + + buf = malloc(len + 1); + memcpy(buf, raw, len); + buf[len] = 0; + + /* get lines count */ + curr = buf; + while(*curr) + { + size_t linesize = 0; + + linesize = strcspn(curr, "\n\r"); + if (*curr && strncmp(curr, "//", 2) && + *curr != '\n' && *curr != '\r') + { + nlocalmessages ++; + } + curr += linesize; + if (curr >= (buf + len)) + { + break; + } + /* skip our endline */ + curr++; + } + + if (nlocalmessages) + { + localmessages = gi.TagMalloc(nlocalmessages * sizeof(*localmessages), TAG_GAME); + memset(localmessages, 0, nlocalmessages * sizeof(*localmessages)); + } + + /* parse lines */ + curr = buf; + i = 0; + while(*curr) + { + size_t linesize = 0; + + if (i == nlocalmessages) + { + break; + } + + linesize = strcspn(curr, "\n\r"); + curr[linesize] = 0; + if (*curr && strncmp(curr, "//", 2) && + *curr != '\n' && *curr != '\r') + { + char *sign; + + sign = strchr(curr, '='); + /* clean up end of key */ + if (sign) + { + char *signend; + + signend = sign - 1; + while ((*signend == ' ' || *signend == '\t') && + (signend > curr)) + { + *signend = 0; + /* go back */ + signend --; + } + *sign = 0; + sign ++; + } + + /* skip value prefix */ + if (sign && *sign) + { + size_t valueskip; + + valueskip = strcspn(sign, " \t"); + sign += valueskip; + if (*sign) + { + sign++; + } + } + + /* start real value */ + if (sign && *sign == '"') + { + char *currend; + + sign ++; + + currend = sign; + while (*currend && *currend != '"') + { + if (*currend == '\\') + { + /* escaped */ + *currend = ' '; + currend++; + if (*currend == 'n') + { + /* new line */ + *currend = '\n'; + } + } + currend++; + } + + if (*currend == '"') + { + /* mark as end of string */ + *currend = 0; + } + + localmessages[i].key = gi.TagMalloc(strlen(curr) + 2, TAG_GAME); + localmessages[i].key[0] = '$'; + strcpy(localmessages[i].key + 1, curr); + localmessages[i].value = gi.TagMalloc(strlen(sign) + 1, TAG_GAME); + strcpy(localmessages[i].value, sign); + i ++; + } + } + curr += linesize; + if (curr >= (buf + len)) + { + break; + } + /* skip our endline */ + curr++; + } + + nlocalmessages = i; + /* sort messages */ + qsort(localmessages, nlocalmessages, sizeof(localmessages_t), LocalizationSort); + + gi.FS_FreeFile(raw); + free(buf); + } +} + +const char* +LocalizationMessage(const char *message) +{ + if (!message || !localmessages || !nlocalmessages) + { + return message; + } + + if (message[0] == '$') + { + int i; + + for (i = 0; i < nlocalmessages; i++) + { + if (!strcmp(localmessages[i].key, message)) + { + return localmessages[i].value; + } + } + } + + return message; +} diff --git a/src/game/g_utils.c b/src/game/g_utils.c index 388487fc..08429b41 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -321,7 +321,7 @@ G_UseTargets(edict_t *ent, edict_t *activator) /* print the message */ if (activator && (ent->message) && !(activator->svflags & SVF_MONSTER)) { - gi.centerprintf(activator, "%s", ent->message); + gi.centerprintf(activator, "%s", LocalizationMessage(ent->message)); if (ent->noise_index) { diff --git a/src/game/header/game.h b/src/game/header/game.h index 5b61f956..7dac50dc 100644 --- a/src/game/header/game.h +++ b/src/game/header/game.h @@ -189,6 +189,15 @@ typedef struct void (*AddCommandString)(const char *text); void (*DebugGraph)(float value, int color); + + /* Extended to classic Quake2 API. + files will be memory mapped read only + the returned buffer may be part of a larger pak file, + or a discrete file from anywhere in the quake search path + a -1 return means the file does not exist + NULL can be passed for buf to just determine existance */ + int (*FS_LoadFile) (const char *name, void **buf); + void (*FS_FreeFile) (void *buf); } game_import_t; /* functions exported by the game subsystem */ diff --git a/src/game/header/local.h b/src/game/header/local.h index 23a2a126..42629a53 100644 --- a/src/game/header/local.h +++ b/src/game/header/local.h @@ -994,6 +994,18 @@ void SV_AddGravity(edict_t *ent); void SaveClientData(void); void EndDMLevel(void); +/* g_translate.c */ +typedef struct +{ + char *key; + char *value; +} localmessages_t; + +extern localmessages_t *localmessages; +extern int nlocalmessages; +void LocalizationInit(void); +const char* LocalizationMessage(const char *message); + /* g_chase.c */ void UpdateChaseCam(edict_t *ent); void ChaseNext(edict_t *ent); diff --git a/src/game/player/hud.c b/src/game/player/hud.c index d475b375..1d961769 100644 --- a/src/game/player/hud.c +++ b/src/game/player/hud.c @@ -421,8 +421,8 @@ HelpComputerMessage(edict_t *ent) "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ", sk, level.level_name, - game.helpmessage1, - game.helpmessage2, + LocalizationMessage(game.helpmessage1), + LocalizationMessage(game.helpmessage2), level.killed_monsters, level.total_monsters, level.found_goals, level.total_goals, level.found_secrets, level.total_secrets); diff --git a/src/game/savegame/savegame.c b/src/game/savegame/savegame.c index 77705d8d..410c7f30 100644 --- a/src/game/savegame/savegame.c +++ b/src/game/savegame/savegame.c @@ -264,6 +264,9 @@ InitGame(void) g_quick_weap = gi.cvar("g_quick_weap", "1", CVAR_ARCHIVE); g_swap_speed = gi.cvar("g_swap_speed", "1", 0); + /* initilize localization */ + LocalizationInit(); + /* items */ InitItems(); diff --git a/src/server/sv_game.c b/src/server/sv_game.c index 6e03254e..124fb8ae 100644 --- a/src/server/sv_game.c +++ b/src/server/sv_game.c @@ -453,6 +453,10 @@ SV_InitGameProgs(void) import.SetAreaPortalState = CM_SetAreaPortalState; import.AreasConnected = CM_AreasConnected; + /* Extension to classic Quake2 API */ + import.FS_LoadFile = FS_LoadFile; + import.FS_FreeFile = FS_FreeFile; + ge = (game_export_t *)Sys_GetGameAPI(&import); if (!ge)