From dcf411bb11104b3194b1b619d46c29cd8b448c93 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 14 Sep 2024 00:11:42 +0300 Subject: [PATCH 1/9] models: add mda file to override list --- README.md | 7 +++++-- src/common/models.c | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbe195a6a..397ba146e 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ Note: with tranparency or some animations flags and properties. * If you like support some other maps type, create pull request for Mod_Load2QBSP function and provide a link to demo maps. +* Use `maptype 1` before load any Heretic 2 maps. Look to + [maptype_t](src/common/header/cmodel.h#L42) for more info. Games: @@ -120,14 +122,15 @@ Goals: * [x] suport Daikatana/SiN .pak/.sin format from pakextract, * [x] Support flow/scale flags for Q64 maps, * [x] Add debug progress loading code for maps, -* [x] MDR model format in Star Trek: Voyager – Elite Force, +* [x] MDR model format from Star Trek: Voyager – Elite Force, +* [ ] MDA entity format from Anachronox, * [x] RGB particles support instead palette based one, * [x] Get rid of VID_PaletteColor client internal api use, * [x] Broken maps groups from base2 to next, * [ ] Single player ReRelease support, * [ ] Support effects and additional flags for ReRelease when possible. * [ ] Use shared model cache in client code insted reimplemnet in each render, -* [ ] Check load soft colormap as 24bit color, +* [x] Check load soft colormap as 24bit color from loaded image, * [ ] Fix transparent textures in Daikatana/SiN maps, * [ ] Use separete texture hi-color buffer for ui in soft render, * [ ] Cleanup function declarations in game save code, diff --git a/src/common/models.c b/src/common/models.c index f0b19e81f..f98c088f9 100644 --- a/src/common/models.c +++ b/src/common/models.c @@ -225,6 +225,7 @@ Mod_LoadFileWithoutExt(const char *namewe, void **buffer, const char* ext) if (!strcmp(ext, "fm") || !strcmp(ext, "def") || !strcmp(ext, "dkm") || + !strcmp(ext, "mda") || !strcmp(ext, "md2") || !strcmp(ext, "md3") || !strcmp(ext, "mdr") || @@ -296,6 +297,16 @@ Mod_LoadFileWithoutExt(const char *namewe, void **buffer, const char* ext) return filesize; } + /* Check Anachronox model definition */ + Q_strlcpy(newname + tlen, ".mda", sizeof(newname)); + filesize = FS_LoadFile(newname, buffer); + if (filesize > 0) + { + Com_DPrintf("%s: %s loaded as mda (Anachronox)\n", + __func__, namewe); + return filesize; + } + /* Check Kingpin model */ Q_strlcpy(newname + tlen, ".mdx", sizeof(newname)); filesize = FS_LoadFile(newname, buffer); From 7509200287cda96baf39711f4921ca61b8c2b1a6 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 14 Sep 2024 00:31:21 +0300 Subject: [PATCH 2/9] game: code cleanup --- src/game/g_utils.c | 8 +++++--- src/game/header/local.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/game/g_utils.c b/src/game/g_utils.c index ff076f59d..388487fcf 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -66,7 +66,7 @@ G_ProjectSource2(vec3_t point, vec3_t distance, vec3_t forward, * if the end of the list is reached. */ edict_t * -G_Find(edict_t *from, int fieldofs, char *match) +G_Find(edict_t *from, int fieldofs, const char *match) { char *s; @@ -409,7 +409,7 @@ G_UseTargets(edict_t *ent, edict_t *activator) if (t == ent) { - gi.dprintf("WARNING: Entity used itself.\n"); + gi.dprintf ("WARNING: %s used itself.\n", t->classname); } else { @@ -771,7 +771,9 @@ G_Spawn(void) edict_t *e = G_SpawnOptional(); if (!e) - gi.error ("ED_Alloc: no free edicts"); + { + gi.error("%s: no free edicts", __func__); + } return e; } diff --git a/src/game/header/local.h b/src/game/header/local.h index 7b5004d4c..23a2a1264 100644 --- a/src/game/header/local.h +++ b/src/game/header/local.h @@ -765,7 +765,7 @@ void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, qboolean KillBox(edict_t *ent); void G_ProjectSource(vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); -edict_t *G_Find(edict_t *from, int fieldofs, char *match); +edict_t *G_Find(edict_t *from, int fieldofs, const char *match); edict_t *findradius(edict_t *from, vec3_t org, float rad); edict_t *G_PickTarget(char *targetname); void G_UseTargets(edict_t *ent, edict_t *activator); From daa471e0f89bfb6c177878ccb176ca0863ee6160 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Fri, 13 Sep 2024 22:46:04 +0300 Subject: [PATCH 3/9] filesystem: load system pak from remaster --- src/common/filesystem.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/common/filesystem.c b/src/common/filesystem.c index 975c2561c..ebe46bf5d 100644 --- a/src/common/filesystem.c +++ b/src/common/filesystem.c @@ -2238,6 +2238,19 @@ FS_AddDirToSearchPath(char *dir, qboolean create) { search->next = fs_searchPaths; fs_searchPaths = search; + /* remaster additional files */ + pack = FS_LoadPK3("Q2Game.kpf"); + if (pack) + { + pack->isProtectedPak = true; + + FS_SortPack(pack); + + search = Z_Malloc(sizeof(fsSearchPath_t)); + search->pack = pack; + search->next = fs_searchPaths; + fs_searchPaths = search; + } // Numbered paks contain the official game data, they // need to be added first and are marked protected. From 51d092660b42e442c7723eabd56fec0918699e46 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Fri, 13 Sep 2024 23:40:25 +0300 Subject: [PATCH 4/9] ci/cd: Update package versions --- .github/workflows/macos.yml | 15 +++++++++++++++ .github/workflows/win32.yml | 4 ++-- .github/workflows/win64.yml | 10 +++++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3b3ee7862..602e359de 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -4,6 +4,8 @@ on: push: branches: - 'master' + tags: + - "*" pull_request: types: - edited @@ -58,3 +60,16 @@ jobs: name: quake2-macos-${{github.sha}} path: publish/ if-no-files-found: error + - name: Create tag package + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: | + # create archive + mkdir yquake2remaster-${{matrix.env}}-${{github.ref_name}} + cp -rv publish/quake2-macos-${{github.sha}}/* yquake2remaster-${{matrix.env}}-${{github.ref_name}} + zip -9r yquake2remaster-${{matrix.env}}-${{github.ref_name}}.zip yquake2remaster-${{matrix.env}}-${{github.ref_name}} + - name: Upload Release Asset + uses: softprops/action-gh-release@v2 + if: ${{ startsWith(github.ref, 'refs/tags/') }} + with: + files: | + yquake2remaster-${{matrix.env}}-${{github.ref_name}}.zip diff --git a/.github/workflows/win32.yml b/.github/workflows/win32.yml index b4ba5bc20..4cb429e2f 100644 --- a/.github/workflows/win32.yml +++ b/.github/workflows/win32.yml @@ -70,8 +70,8 @@ jobs: cp doc/080_contributing.md publish/quake2-win32-${{github.sha}}/misc/docs/080_contributing.txt cp doc/090_filelists.md publish/quake2-win32-${{github.sha}}/misc/docs/090_filelists.md # SDL2 - wget -c https://github.com/libsdl-org/SDL/releases/download/release-2.30.6/SDL2-2.30.6-win32-x86.zip - unzip -o SDL2-2.30.6-win32-x86.zip + wget -c https://github.com/libsdl-org/SDL/releases/download/release-2.30.7/SDL2-2.30.7-win32-x86.zip + unzip -o SDL2-2.30.7-win32-x86.zip cp SDL2.dll publish/quake2-win32-${{github.sha}}/ # openal-soft wget -c https://github.com/kcat/openal-soft/releases/download/1.23.1/openal-soft-1.23.1-bin.zip diff --git a/.github/workflows/win64.yml b/.github/workflows/win64.yml index c39870d54..9c143b6df 100644 --- a/.github/workflows/win64.yml +++ b/.github/workflows/win64.yml @@ -70,13 +70,13 @@ jobs: cp doc/080_contributing.md publish/quake2-win64-${{github.sha}}/misc/docs/080_contributing.txt cp doc/090_filelists.md publish/quake2-win64-${{github.sha}}/misc/docs/090_filelists.md # SDL2 - wget -c https://github.com/libsdl-org/SDL/releases/download/release-2.30.6/SDL2-2.30.6-win32-x86.zip - unzip -o SDL2-2.30.6-win32-x86.zip + wget -c https://github.com/libsdl-org/SDL/releases/download/release-2.30.7/SDL2-2.30.7-win32-x64.zip + unzip -o SDL2-2.30.7-win32-x64.zip cp SDL2.dll publish/quake2-win64-${{github.sha}}/ # static ffmpeg - wget -c https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2023-11-30-12-55/ffmpeg-n6.0.1-win64-lgpl-shared-6.0.zip - unzip -o ffmpeg-n6.0.1-win64-lgpl-shared-6.0.zip - cp ffmpeg-n6.0.1-win64-lgpl-shared-6.0/bin/*.dll publish/quake2-win64-${{github.sha}}/ + wget -c https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-08-31-12-50/ffmpeg-n7.0.2-6-g7e69129d2f-win64-lgpl-shared-7.0.zip + unzip -o ffmpeg-n7.0.2-6-g7e69129d2f-win64-lgpl-shared-7.0.zip + cp ffmpeg-n7.0.2-6-g7e69129d2f-win64-lgpl-shared-7.0/bin/*.dll publish/quake2-win64-${{github.sha}}/ # remove unused libraries rm -fv publish/quake2-win64-${{github.sha}}/avdevice*.dll publish/quake2-win64-${{github.sha}}/avfilter*.dll publish/quake2-win64-${{github.sha}}/postproc*.dll # openal-soft From 1f0e67621c4a9cd7648a459ac3a12152e381bc3a Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 14 Sep 2024 20:59:33 +0300 Subject: [PATCH 5/9] game: Add remaster translation support --- Makefile | 1 + src/game/g_func.c | 2 +- src/game/g_newfnc.c | 2 +- src/game/g_spawn.c | 4 +- src/game/g_translate.c | 218 +++++++++++++++++++++++++++++++++++ src/game/g_utils.c | 2 +- src/game/header/game.h | 9 ++ src/game/header/local.h | 12 ++ src/game/player/hud.c | 4 +- src/game/savegame/savegame.c | 3 + src/server/sv_game.c | 4 + 11 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 src/game/g_translate.c diff --git a/Makefile b/Makefile index bd4c167f9..c756866c3 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 993b43c2b..88642387e 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 e48b899ce..7a69e590d 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 72815cd37..ed9a91733 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 000000000..ae6344019 --- /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 388487fcf..08429b41c 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 5b61f956c..7dac50dcc 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 23a2a1264..42629a53d 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 d475b375f..1d961769a 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 77705d8d9..410c7f30e 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 6e03254e7..124fb8aef 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) From 3dd4dbf2ce17413aacc912af04a88bca7beeb1f9 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 14 Sep 2024 21:08:07 +0300 Subject: [PATCH 6/9] game: use binary search for message translation --- src/game/g_translate.c | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/game/g_translate.c b/src/game/g_translate.c index ae6344019..a38e06689 100644 --- a/src/game/g_translate.c +++ b/src/game/g_translate.c @@ -193,6 +193,38 @@ LocalizationInit(void) } } +static int +LocalizationSearch(const char *name) +{ + int start, end; + + start = 0; + end = nlocalmessages - 1; + + while (start <= end) + { + int i, res; + + i = start + (end - start) / 2; + + res = Q_stricmp(localmessages[i].key, name); + if (res == 0) + { + return i; + } + else if (res < 0) + { + start = i + 1; + } + else + { + end = i - 1; + } + } + + return -1; +} + const char* LocalizationMessage(const char *message) { @@ -205,12 +237,10 @@ LocalizationMessage(const char *message) { int i; - for (i = 0; i < nlocalmessages; i++) + i = LocalizationSearch(message); + if (i >= 0) { - if (!strcmp(localmessages[i].key, message)) - { - return localmessages[i].value; - } + return localmessages[i].value; } } From 466ca3cba44398849e66a53ac9b7f4eb8275cf60 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 14 Sep 2024 21:39:25 +0300 Subject: [PATCH 7/9] game: prepare for use additional sources for level messages --- src/game/g_translate.c | 86 +++++++++++++++++++++++++++-------------- src/game/header/local.h | 8 ---- 2 files changed, 56 insertions(+), 38 deletions(-) diff --git a/src/game/g_translate.c b/src/game/g_translate.c index a38e06689..a086de16c 100644 --- a/src/game/g_translate.c +++ b/src/game/g_translate.c @@ -27,8 +27,15 @@ #include "header/local.h" -localmessages_t *localmessages = NULL; -int nlocalmessages = 0; +typedef struct +{ + char *key; + char *value; + char *sound; +} localmessages_t; + +static localmessages_t *localmessages = NULL; +static int nlocalmessages = 0; static int LocalizationSort(const void *p1, const void *p2) @@ -44,7 +51,8 @@ void LocalizationInit(void) { byte *raw = NULL; - int len; + char *buf_loc = NULL; + int len, curr_pos; localmessages = NULL; nlocalmessages = 0; @@ -53,15 +61,19 @@ LocalizationInit(void) len = gi.FS_LoadFile("localization/loc_english.txt", (void **)&raw); if (len > 1) { - char *buf, *curr; - int i; + buf_loc = malloc(len + 1); + memcpy(buf_loc, raw, len); + buf_loc[len] = 0; + gi.FS_FreeFile(raw); + } - buf = malloc(len + 1); - memcpy(buf, raw, len); - buf[len] = 0; + /* localization lines count */ + if (buf_loc) + { + char *curr; /* get lines count */ - curr = buf; + curr = buf_loc; while(*curr) { size_t linesize = 0; @@ -73,28 +85,35 @@ LocalizationInit(void) nlocalmessages ++; } curr += linesize; - if (curr >= (buf + len)) + if (curr >= (buf_loc + len)) { break; } /* skip our endline */ curr++; } + } - if (nlocalmessages) - { - localmessages = gi.TagMalloc(nlocalmessages * sizeof(*localmessages), TAG_GAME); - memset(localmessages, 0, nlocalmessages * sizeof(*localmessages)); - } + if (nlocalmessages) + { + localmessages = gi.TagMalloc(nlocalmessages * sizeof(*localmessages), TAG_GAME); + memset(localmessages, 0, nlocalmessages * sizeof(*localmessages)); + } + + curr_pos = 0; + + /* localization load */ + if (buf_loc) + { + char *curr; /* parse lines */ - curr = buf; - i = 0; + curr = buf_loc; while(*curr) { size_t linesize = 0; - if (i == nlocalmessages) + if (curr_pos == nlocalmessages) { break; } @@ -167,30 +186,37 @@ LocalizationInit(void) *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 ++; + localmessages[curr_pos].key = gi.TagMalloc(strlen(curr) + 2, TAG_GAME); + localmessages[curr_pos].key[0] = '$'; + strcpy(localmessages[curr_pos].key + 1, curr); + localmessages[curr_pos].value = gi.TagMalloc(strlen(sign) + 1, TAG_GAME); + strcpy(localmessages[curr_pos].value, sign); + /* ReRelease does not have merged sound files to message */ + localmessages[curr_pos].sound = NULL; + curr_pos ++; } } curr += linesize; - if (curr >= (buf + len)) + if (curr >= (buf_loc + len)) { break; } /* skip our endline */ curr++; } + free(buf_loc); + } - nlocalmessages = i; - /* sort messages */ - qsort(localmessages, nlocalmessages, sizeof(localmessages_t), LocalizationSort); + /* save last used position */ + nlocalmessages = curr_pos; - gi.FS_FreeFile(raw); - free(buf); + if (!curr_pos) + { + return; } + + /* sort messages */ + qsort(localmessages, nlocalmessages, sizeof(localmessages_t), LocalizationSort); } static int diff --git a/src/game/header/local.h b/src/game/header/local.h index 42629a53d..121727511 100644 --- a/src/game/header/local.h +++ b/src/game/header/local.h @@ -995,14 +995,6 @@ 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); From c51ccea8021e812b8be5fb6c2894c3f84fdc2a62 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 14 Sep 2024 23:09:46 +0300 Subject: [PATCH 8/9] game: support heretic 2 level message --- src/game/g_translate.c | 143 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 17 deletions(-) diff --git a/src/game/g_translate.c b/src/game/g_translate.c index a086de16c..80a9d6d27 100644 --- a/src/game/g_translate.c +++ b/src/game/g_translate.c @@ -51,41 +51,75 @@ void LocalizationInit(void) { byte *raw = NULL; - char *buf_loc = NULL; - int len, curr_pos; + char *buf_local = NULL, *buf_level = NULL; + int len_local, len_level, curr_pos; localmessages = NULL; nlocalmessages = 0; - /* load the file */ - len = gi.FS_LoadFile("localization/loc_english.txt", (void **)&raw); - if (len > 1) + /* load the localization file */ + len_local = gi.FS_LoadFile("localization/loc_english.txt", (void **)&raw); + if (len_local > 1) { - buf_loc = malloc(len + 1); - memcpy(buf_loc, raw, len); - buf_loc[len] = 0; + buf_local = malloc(len_local + 1); + memcpy(buf_local, raw, len_local); + buf_local[len_local] = 0; + gi.FS_FreeFile(raw); + } + + /* load the heretic 2 messages file */ + len_level = gi.FS_LoadFile("levelmsg.txt", (void **)&raw); + if (len_level > 1) + { + buf_level = malloc(len_level + 1); + memcpy(buf_level, raw, len_level); + buf_level[len_level] = 0; gi.FS_FreeFile(raw); } /* localization lines count */ - if (buf_loc) + if (buf_local) { char *curr; /* get lines count */ - curr = buf_loc; + curr = buf_local; while(*curr) { size_t linesize = 0; linesize = strcspn(curr, "\n\r"); - if (*curr && strncmp(curr, "//", 2) && + if (strncmp(curr, "//", 2) && *curr != '\n' && *curr != '\r') { nlocalmessages ++; } curr += linesize; - if (curr >= (buf_loc + len)) + if (curr >= (buf_local + len_local)) + { + break; + } + /* skip our endline */ + curr++; + } + } + + /* heretic 2 lines count */ + if (buf_level) + { + char *curr; + + /* get lines count */ + curr = buf_level; + while(*curr) + { + size_t linesize = 0; + + linesize = strcspn(curr, "\n"); + /* skip lines with both endline codes */ + nlocalmessages ++; + curr += linesize; + if (curr >= (buf_level + len_level)) { break; } @@ -103,12 +137,12 @@ LocalizationInit(void) curr_pos = 0; /* localization load */ - if (buf_loc) + if (buf_local) { char *curr; /* parse lines */ - curr = buf_loc; + curr = buf_local; while(*curr) { size_t linesize = 0; @@ -197,14 +231,86 @@ LocalizationInit(void) } } curr += linesize; - if (curr >= (buf_loc + len)) + if (curr >= (buf_local + len_local)) { break; } /* skip our endline */ curr++; } - free(buf_loc); + free(buf_local); + } + + /* heretic 2 translate load */ + if (buf_level) + { + char *curr; + int i; + + curr = buf_level; + i = 1; + while(*curr) + { + char *sign, *currend; + size_t linesize = 0; + + linesize = strcspn(curr, "\n"); + curr[linesize] = 0; + if (curr[0] != ';') + { + /* remove caret back */ + if (curr[linesize - 1] == '\r') + { + curr[linesize - 1] = 0; + } + + sign = strchr(curr, '#'); + /* clean up end of key */ + if (sign) + { + *sign = 0; + sign ++; + } + + /* replace @ with new line */ + currend = curr; + while(*currend) + { + if (*currend == '@') + { + *currend = '\n'; + } + + currend++; + } + + localmessages[curr_pos].key = gi.TagMalloc(6, TAG_GAME); + snprintf(localmessages[curr_pos].key, 5, "%d", i); + localmessages[curr_pos].value = gi.TagMalloc(strlen(curr) + 1, TAG_GAME); + strcpy(localmessages[curr_pos].value, curr); + /* Some Heretic message could have no sound effects */ + localmessages[curr_pos].sound = NULL; + if (sign) + { + /* has some sound aligned with message */ + localmessages[curr_pos].sound = gi.TagMalloc(strlen(sign) + 1, TAG_GAME); + strcpy(localmessages[curr_pos].sound, sign); + } + + curr_pos ++; + } + i ++; + + curr += linesize; + if (curr >= (buf_level + len_level)) + { + break; + } + /* skip our endline */ + curr++; + } + + free(buf_level); } /* save last used position */ @@ -215,6 +321,8 @@ LocalizationInit(void) return; } + gi.dprintf("Found %d translated lines\n", nlocalmessages); + /* sort messages */ qsort(localmessages, nlocalmessages, sizeof(localmessages_t), LocalizationSort); } @@ -259,7 +367,8 @@ LocalizationMessage(const char *message) return message; } - if (message[0] == '$') + if ((message[0] == '$') || /* ReRelease */ + (strspn(message, "1234567890") == strlen(message))) /* Heretic 2 */ { int i; From 3a5d0afea5a6f42385c183b99c85d33be7d34449 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 14 Sep 2024 23:44:01 +0300 Subject: [PATCH 9/9] game: support translated sounds in messages (Heretic 2) --- src/game/g_func.c | 7 +++++-- src/game/g_newfnc.c | 10 +++++++++- src/game/g_spawn.c | 3 ++- src/game/g_translate.c | 21 +++++++++++++++++++-- src/game/g_utils.c | 10 ++++++---- src/game/header/local.h | 2 +- src/game/player/hud.c | 4 ++-- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/game/g_func.c b/src/game/g_func.c index 88642387e..d5c1e0a3e 100644 --- a/src/game/g_func.c +++ b/src/game/g_func.c @@ -2601,6 +2601,8 @@ void door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) { + int sound_index; + if (!self || !other) { return; @@ -2618,8 +2620,9 @@ door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, self->touch_debounce_time = level.time + 5.0; - gi.centerprintf(other, "%s", LocalizationMessage(self->message)); - gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); + sound_index = gi.soundindex("misc/talk1.wav"); + gi.centerprintf(other, "%s", LocalizationMessage(self->message, &sound_index)); + gi.sound(other, CHAN_AUTO, sound_index, 1, ATTN_NORM, 0); } void diff --git a/src/game/g_newfnc.c b/src/game/g_newfnc.c index 7a69e590d..e2463d9ba 100644 --- a/src/game/g_newfnc.c +++ b/src/game/g_newfnc.c @@ -228,7 +228,15 @@ secret_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurfa if (self->message) { - gi.centerprintf(other, LocalizationMessage(self->message)); + int sound_index; + + sound_index = 0; + gi.centerprintf(other, LocalizationMessage(self->message, &sound_index)); + + if (sound_index) + { + gi.sound(other, CHAN_AUTO, sound_index, 1, ATTN_NORM, 0); + } } } diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c index ed9a91733..4cf0ea736 100644 --- a/src/game/g_spawn.c +++ b/src/game/g_spawn.c @@ -865,7 +865,8 @@ SP_worldspawn(edict_t *ent) /* make some data visible to the server */ if (ent->message && ent->message[0]) { - Q_strlcpy(level.level_name, LocalizationMessage(ent->message), sizeof(level.level_name)); + Q_strlcpy(level.level_name, LocalizationMessage(ent->message, NULL), + 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 index 80a9d6d27..38479846e 100644 --- a/src/game/g_translate.c +++ b/src/game/g_translate.c @@ -270,9 +270,21 @@ LocalizationInit(void) { *sign = 0; sign ++; + + /* replace '\' with '/' in sound path */ + currend = sign; + while(*currend) + { + if (*currend == '\\') + { + *currend = '/'; + } + + currend++; + } } - /* replace @ with new line */ + /* replace @ in message with new line */ currend = curr; while(*currend) { @@ -360,7 +372,7 @@ LocalizationSearch(const char *name) } const char* -LocalizationMessage(const char *message) +LocalizationMessage(const char *message, int *sound_index) { if (!message || !localmessages || !nlocalmessages) { @@ -375,6 +387,11 @@ LocalizationMessage(const char *message) i = LocalizationSearch(message); if (i >= 0) { + if (sound_index && localmessages[i].sound) + { + *sound_index = gi.soundindex(localmessages[i].sound); + } + return localmessages[i].value; } } diff --git a/src/game/g_utils.c b/src/game/g_utils.c index 08429b41c..0ac727255 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -321,17 +321,19 @@ G_UseTargets(edict_t *ent, edict_t *activator) /* print the message */ if (activator && (ent->message) && !(activator->svflags & SVF_MONSTER)) { - gi.centerprintf(activator, "%s", LocalizationMessage(ent->message)); + int sound_index; if (ent->noise_index) { - gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + sound_index = ent->noise_index; } else { - gi.sound(activator, CHAN_AUTO, gi.soundindex( - "misc/talk1.wav"), 1, ATTN_NORM, 0); + sound_index = gi.soundindex("misc/talk1.wav"); } + + gi.centerprintf(activator, "%s", LocalizationMessage(ent->message, &sound_index)); + gi.sound(activator, CHAN_AUTO, sound_index, 1, ATTN_NORM, 0); } /* kill killtargets */ diff --git a/src/game/header/local.h b/src/game/header/local.h index 121727511..d3a944808 100644 --- a/src/game/header/local.h +++ b/src/game/header/local.h @@ -996,7 +996,7 @@ void EndDMLevel(void); /* g_translate.c */ void LocalizationInit(void); -const char* LocalizationMessage(const char *message); +const char* LocalizationMessage(const char *message, int *sound_index); /* g_chase.c */ void UpdateChaseCam(edict_t *ent); diff --git a/src/game/player/hud.c b/src/game/player/hud.c index 1d961769a..3d8541280 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, - LocalizationMessage(game.helpmessage1), - LocalizationMessage(game.helpmessage2), + LocalizationMessage(game.helpmessage1, NULL), + LocalizationMessage(game.helpmessage2, NULL), level.killed_monsters, level.total_monsters, level.found_goals, level.total_goals, level.found_secrets, level.total_secrets);