From 2c72d358cec50ffbb79220c5a6bbd011a4812f94 Mon Sep 17 00:00:00 2001 From: Marco Cawthorne Date: Thu, 23 Mar 2023 20:42:52 -0700 Subject: [PATCH] Sentences: Speed up the word search using hashtables --- src/client/event.qc | 5 +- src/client/include.src | 1 - src/client/text.qc | 27 +++ src/gs-entbase/server/func_plat.qc | 2 +- src/gs-entbase/server/game_text.qc | 2 +- src/gs-entbase/server/scripted_sentence.qc | 2 +- src/gs-entbase/server/speaker.qc | 2 +- src/gs-entbase/shared/ambient_generic.qc | 2 +- src/gs-entbase/shared/info_waypoint.qc | 2 +- src/plugins/banner.qc | 2 +- src/server/defs.h | 1 - src/server/include.src | 1 - src/server/sentences.h | 29 ---- src/server/sentences.qc | 125 -------------- src/shared/NSIO.qc | 28 --- src/shared/NSTalkMonster.qc | 4 +- src/shared/defs.h | 2 + src/shared/events.h | 1 + src/shared/include.src | 1 + src/shared/sentences.h | 83 +++++++++ src/{client => shared}/sentences.qc | 189 ++++++++++++++++----- 21 files changed, 270 insertions(+), 241 deletions(-) mode change 100755 => 100644 src/plugins/banner.qc delete mode 100644 src/server/sentences.h delete mode 100644 src/server/sentences.qc create mode 100644 src/shared/sentences.h rename src/{client => shared}/sentences.qc (52%) diff --git a/src/client/event.qc b/src/client/event.qc index f9afb9ce..54e6e84b 100644 --- a/src/client/event.qc +++ b/src/client/event.qc @@ -164,7 +164,10 @@ Event_Parse(float type) EnvSprite_ParseEvent(); break; case EV_TEXT: - GameText_Parse(); + GameText_ParseString(); + break; + case EV_TEXT_STRING: + GameText_ParseString(); break; case EV_MESSAGE: GameMessage_Parse(); diff --git a/src/client/include.src b/src/client/include.src index 26570420..e02cdefa 100644 --- a/src/client/include.src +++ b/src/client/include.src @@ -5,7 +5,6 @@ fog.qc font.qc sky.qc music.qc -sentences.qc prints.qc voice.qc fade.qc diff --git a/src/client/text.qc b/src/client/text.qc index c8c6414b..868966f2 100644 --- a/src/client/text.qc +++ b/src/client/text.qc @@ -142,6 +142,33 @@ GameText_Draw(void) } } +void +GameText_ParseString(void) +{ + int chan = readbyte(); + + /* last channel is reserved for text menus */ + if (!(chan >= 0 && chan <= 4)) { + return; + } + + g_textchannels[chan].m_strMessage = Titles_ParseFunString(readstring()); + g_textchannels[chan].m_flPosX = readfloat(); + g_textchannels[chan].m_flPosY = readfloat(); + g_textchannels[chan].m_iEffect = readbyte(); + g_textchannels[chan].m_vecColor1[0] = readbyte() / 255; + g_textchannels[chan].m_vecColor1[1] = readbyte() / 255; + g_textchannels[chan].m_vecColor1[2] = readbyte() / 255; + g_textchannels[chan].m_vecColor2[0] = readbyte() / 255; + g_textchannels[chan].m_vecColor2[1] = readbyte() / 255; + g_textchannels[chan].m_vecColor2[2] = readbyte() / 255; + g_textchannels[chan].m_flFadeIn = readfloat(); + g_textchannels[chan].m_flFadeOut = readfloat(); + g_textchannels[chan].m_flHoldTime = readfloat(); + g_textchannels[chan].m_flFXTime = readfloat(); + g_textchannels[chan].m_flTime = 0.0f; +} + void GameText_Parse(void) { diff --git a/src/gs-entbase/server/func_plat.qc b/src/gs-entbase/server/func_plat.qc index 0b5c02b4..bc3c238f 100644 --- a/src/gs-entbase/server/func_plat.qc +++ b/src/gs-entbase/server/func_plat.qc @@ -127,7 +127,7 @@ func_plat::Restore(string strKey, string strValue) m_strSndStop = ReadString(strValue); break; case "m_handler": - m_handler = ReadEntity(strValue); + m_handler = (func_plat_helper)ReadEntity(strValue); break; default: super::Restore(strKey, strValue); diff --git a/src/gs-entbase/server/game_text.qc b/src/gs-entbase/server/game_text.qc index 2fdb6788..568dc88f 100644 --- a/src/gs-entbase/server/game_text.qc +++ b/src/gs-entbase/server/game_text.qc @@ -183,7 +183,7 @@ void game_text::Trigger(entity act, triggermode_t state) { WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); - WriteByte(MSG_MULTICAST, EV_TEXT); + WriteByte(MSG_MULTICAST, EV_TEXT_STRING); WriteByte(MSG_MULTICAST, m_iChannel); WriteString(MSG_MULTICAST, m_strMessage); WriteFloat(MSG_MULTICAST, m_flPosX); diff --git a/src/gs-entbase/server/scripted_sentence.qc b/src/gs-entbase/server/scripted_sentence.qc index 1fd79007..af05680a 100644 --- a/src/gs-entbase/server/scripted_sentence.qc +++ b/src/gs-entbase/server/scripted_sentence.qc @@ -160,7 +160,7 @@ scripted_sentence::Trigger(entity act, triggermode_t unused) WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, EV_SENTENCE); WriteEntity(MSG_MULTICAST, npc); - WriteString(MSG_MULTICAST, m_strSentence); + WriteInt(MSG_MULTICAST, Sentences_GetID(m_strSentence)); msg_entity = npc; multicast(npc.origin, MULTICAST_PVS); npc.m_flNextSentence = time + m_flDuration; diff --git a/src/gs-entbase/server/speaker.qc b/src/gs-entbase/server/speaker.qc index d89f736b..86c81ea0 100644 --- a/src/gs-entbase/server/speaker.qc +++ b/src/gs-entbase/server/speaker.qc @@ -161,7 +161,7 @@ speaker::Announce(void) WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, EV_SENTENCE); WriteEntity(MSG_MULTICAST, this); - WriteString(MSG_MULTICAST, seq); + WriteInt(MSG_MULTICAST, Sentences_GetID(seq)); msg_entity = this; multicast(origin, MULTICAST_PVS); diff --git a/src/gs-entbase/shared/ambient_generic.qc b/src/gs-entbase/shared/ambient_generic.qc index 4ff6b5ad..fc35d0d9 100644 --- a/src/gs-entbase/shared/ambient_generic.qc +++ b/src/gs-entbase/shared/ambient_generic.qc @@ -272,7 +272,7 @@ ambient_generic::UseNormal(entity act, triggermode_t state) WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, EV_SENTENCE); WriteEntity(MSG_MULTICAST, this); - WriteString(MSG_MULTICAST, seq); + WriteInt(MSG_MULTICAST, Sentences_GetID(m_strActivePath)); msg_entity = this; multicast(origin, MULTICAST_PHS); } else diff --git a/src/gs-entbase/shared/info_waypoint.qc b/src/gs-entbase/shared/info_waypoint.qc index dc22eede..916e7623 100644 --- a/src/gs-entbase/shared/info_waypoint.qc +++ b/src/gs-entbase/shared/info_waypoint.qc @@ -229,7 +229,7 @@ info_waypoint::postdraw(void) if (drawicon_visible(origin) != 0) { float textLength = Font_StringWidth(m_strText, true, FONT_CON); vector vecProj = project(origin) - [32, 32]; - vector projectedPos = project(origin) - (textLength/2) + [0, 114]; + vector projectedPos = project(origin) + [-(textLength/2), 48]; float a = (visible == 2) ? 0.25 : 1.0f; float dist = vlen(origin - g_view.GetCameraOrigin()) / WAYPOINT_METER; string distText = sprintf("Distance: %d m", dist); diff --git a/src/plugins/banner.qc b/src/plugins/banner.qc old mode 100755 new mode 100644 index 091821a0..b6498002 --- a/src/plugins/banner.qc +++ b/src/plugins/banner.qc @@ -24,7 +24,7 @@ BannerPlug_Broadcast(string bmsg) { WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); - WriteByte(MSG_MULTICAST, EV_TEXT); + WriteByte(MSG_MULTICAST, EV_TEXT_STRING); WriteByte(MSG_MULTICAST, 1); WriteString(MSG_MULTICAST, bmsg); WriteFloat(MSG_MULTICAST, -1); diff --git a/src/server/defs.h b/src/server/defs.h index a4ad1dc0..963140d6 100644 --- a/src/server/defs.h +++ b/src/server/defs.h @@ -16,7 +16,6 @@ #include "NSOutput.h" #include "NSGameRules.h" -#include "sentences.h" #include "skill.h" #include "logging.h" #include "nodes.h" diff --git a/src/server/include.src b/src/server/include.src index 74a96dc0..2ed3ebf5 100644 --- a/src/server/include.src +++ b/src/server/include.src @@ -4,7 +4,6 @@ plugins.qc logging.qc nodes.qc skill.qc -sentences.qc spawn.qc NSGameRules.qc client.qc diff --git a/src/server/sentences.h b/src/server/sentences.h deleted file mode 100644 index b233e74e..00000000 --- a/src/server/sentences.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2016-2020 Marco Cawthorne - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define DYNAMIC_SENTENCES - -#ifdef DYNAMIC_SENTENCES - string *g_sentences; - int g_sentences_count; -#else - #define SENTENCES_LIMIT 1024 - string g_sentences[SENTENCES_LIMIT]; - int g_sentences_count; -#endif - -void Sentences_Init(void); -string Sentences_GetSamples(string); diff --git a/src/server/sentences.qc b/src/server/sentences.qc deleted file mode 100644 index 8b852795..00000000 --- a/src/server/sentences.qc +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2016-2022 Vera Visions LLC. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/* voice sentence samples for AI and other triggers that are supposed to talk. - * the formatting is messy as hell and I feel dirty for even bothering with all - * this to begin with. - * - * the server will send a short string identifer over and we'll look it up. - * what's annoying is that some NPCs got their own pitch overrides so I guess - * we'll just default to those whenever there's no custom value set. - */ - -void -Sentences_Init(void) -{ - filestream fs_sentences; - string temp; - int c; - - print("--------- Initializing SentencesDef (SERVER) ----------\n"); - - if (g_sentences_count > 0) { - g_sentences_count = 0; -#ifndef DYNAMIC_SENTENCES - if (g_sentences) { - memfree(g_sentences); - } -#endif - } - - fs_sentences = fopen("sound/sentences.txt", FILE_READ); - - if (fs_sentences < 0) { - print("^1could not load sound/sentences.txt\n"); - return; - } - - while ((temp = fgets(fs_sentences))) { - /* tons of comments/garbage in those files, - * so tokenize appropriately */ - c = tokenize_console(temp); - - /* not enough for an entry. */ - if (c < 2) { - continue; - } - - /* starts of at 0, for every line increases */ - int x = g_sentences_count; - - /* allocate memory and increase count */ -#ifdef DYNAMIC_SENTENCES - g_sentences_count++; - g_sentences = (string *)memrealloc(g_sentences, - sizeof(string), - x, - g_sentences_count); -#else - if (g_sentences_count + 1 >= SENTENCES_LIMIT) { - print("^1reached limit of max sentences!\n"); - return; - } - - g_sentences_count++; -#endif - - g_sentences[x] = strtoupper(strcat("!", argv(0))); - } - - fclose(fs_sentences); - print(sprintf("SentencesDef initialized with %i entries.\n", g_sentences_count)); -} - -string -Sentences_GetSamples(string word) -{ - int len; - int gc = 0; - - /* you never know what NPCs might do */ - if (word == "") { - return (""); - } - - /* check if the word is present at all */ - for (int i = 0; i < g_sentences_count; i++) { - if (g_sentences[i] == word) { - NSLog("^3Sentences_GetSamples^7: Found %s", word); - return word; - } - } - - /* it may be a random group of words. */ - len = strlen(word); - for (int i = 0; i < g_sentences_count; i++) { - string sub = substring(g_sentences[i], 0, len); - if (sub == word) { - gc++; - } - } - - /* if we've got one, choose a random sample of them */ - if (gc) { - int r = floor(random(0, gc)); - NSLog("^3Sentences_GetSamples^7: Choosing %s%i", word, r); - return sprintf("%s%i", word, r); - } - - /* we've somehow messed up catastrophically */ - print(sprintf("^1ERROR: Invalid sentence keyword %s\n", word)); - return (""); -} diff --git a/src/shared/NSIO.qc b/src/shared/NSIO.qc index 727dc4dd..f6402ecf 100644 --- a/src/shared/NSIO.qc +++ b/src/shared/NSIO.qc @@ -374,11 +374,7 @@ NSIO::Save(float handle) SaveFloat(handle, "baseframe", baseframe); SaveFloat(handle, "drawflags", drawflags); SaveString(handle, "customphysics", getentityfieldstring(findentityfield("customphysics"), this)); - SaveFloat(handle, "dimension_see", dimension_see); - SaveFloat(handle, "dimension_seen", dimension_seen); - SaveFloat(handle, "dimension_seen", dimension_seen); SaveString(handle, "SendEntity", getentityfieldstring(findentityfield("SendEntity"), this)); - SaveFloat(handle, "Version", Version); SaveFloat(handle, "viewzoom", viewzoom); SaveFloat(handle, "uniquespawnid", uniquespawnid); @@ -386,9 +382,6 @@ NSIO::Save(float handle) SaveFloat(handle, "jumptime", jumptime); SaveFloat(handle, "identity", identity); SaveFloat(handle, "iBleeds", iBleeds); - SaveFloat(handle, "subblend2frac", subblend2frac); - SaveFloat(handle, "subblendfrac", subblendfrac); - SaveFloat(handle, "baseframe1time", baseframe1time); SaveString(handle, "m_strOnTrigger", m_strOnTrigger); SaveString(handle, "m_strOnUser1", m_strOnUser1); @@ -659,21 +652,9 @@ NSIO::Restore(string strKey, string strValue) case "customphysics": customphysics = externvalue(-1, strValue); break; - case "dimension_see": - dimension_see = ReadFloat(strValue); - break; - case "dimension_seen": - dimension_seen = ReadFloat(strValue); - break; - case "dimension_seen": - dimension_seen = ReadFloat(strValue); - break; case "SendEntity": SendEntity = externvalue(-1, strValue); break; - case "Version": - Version = ReadFloat(strValue); - break; case "viewzoom": viewzoom = ReadFloat(strValue); break; @@ -691,15 +672,6 @@ NSIO::Restore(string strKey, string strValue) case "iBleeds": iBleeds = ReadFloat(strValue); break; - case "subblend2frac": - subblend2frac = ReadFloat(strValue); - break; - case "subblendfrac": - subblendfrac = ReadFloat(strValue); - break; - case "baseframe1time": - baseframe1time = ReadFloat(strValue); - break; /* END: all the stock Quake fields the engine is aware of */ case "m_strOnTrigger": diff --git a/src/shared/NSTalkMonster.qc b/src/shared/NSTalkMonster.qc index 865ee135..ceaad199 100644 --- a/src/shared/NSTalkMonster.qc +++ b/src/shared/NSTalkMonster.qc @@ -250,7 +250,7 @@ NSTalkMonster::Sentence(string sentence) WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, EV_SENTENCE); WriteEntity(MSG_MULTICAST, this); - WriteString(MSG_MULTICAST, seq); + WriteInt(MSG_MULTICAST, Sentences_GetID(seq)); msg_entity = this; multicast(origin, MULTICAST_PVS); } @@ -891,7 +891,7 @@ NSTalkMonster_ParseSentence(void) /* parse packets */ e = readentitynum(); - sentence = readstring(); + sentence = Sentences_GetString(readint()); ent = findfloat(world, entnum, e); diff --git a/src/shared/defs.h b/src/shared/defs.h index 269e53b2..50b428a0 100644 --- a/src/shared/defs.h +++ b/src/shared/defs.h @@ -46,6 +46,8 @@ string __fullspawndata; #include "../gs-entbase/server/defs.h" #endif +#include "sentences.h" + #include "NSIO.h" #include "NSTrigger.h" #include "NSEntity.h" diff --git a/src/shared/events.h b/src/shared/events.h index eb7f29a4..cec7fe4a 100644 --- a/src/shared/events.h +++ b/src/shared/events.h @@ -37,6 +37,7 @@ enum EV_HUDHINT, EV_FADE, EV_TEXT, + EV_TEXT_STRING, EV_MESSAGE, EV_SPRITE, EV_MODELGIB, diff --git a/src/shared/include.src b/src/shared/include.src index a1cd2286..b50fa96e 100644 --- a/src/shared/include.src +++ b/src/shared/include.src @@ -29,6 +29,7 @@ propdata.qc surfaceproperties.qc decalgroups.qc materials.qc +sentences.qc NSSpraylogo.qc util.qc weapons.qc diff --git a/src/shared/sentences.h b/src/shared/sentences.h new file mode 100644 index 00000000..6cef1d0b --- /dev/null +++ b/src/shared/sentences.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016-2020 Marco Cawthorne + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* voice sentence samples for AI and other triggers that are supposed to talk. + * the formatting is messy as hell and I feel dirty for even bothering with all + * this to begin with. + * + * the server will send a short string identifer over and we'll look it up. + * what's annoying is that some NPCs got their own pitch overrides so I guess + * we'll just default to those whenever there's no custom value set. + */ + +/* sentences are the voice-acting backbone of the sound system. + * http://articles.thewavelength.net/230/ + * has pretty good documentation of how the format is meant to work */ + +/* Sentences Documentation + + Each line is a new sentence group. + [GROUPNAME] [...PARAMETERS] [...SAMPLES] + + If a sample is not in a sub-directory, it'll be assumed to be part + of the 'vox' sub-directory, or the last valid path of a previous sample. + For example + attention male/hello how are you + becomes + vox/attention.wav male/hello.wav male/how.wav male/are.wav male/you.wav + + When parameters are surrounded by spaces, this means they apply + to all current samples. They can be overwritten later down the parsing. + When a parameter is attached to a sample, e.g. + attention(p120) + Then this parameter only applies to said keyword. + Whereas... + (p120) attention everyone alive + Will apply the pitch effect to all three succeeding samples. + + Parameters: + (pXX) = Pitch. Valid values are from 50 to 150. + (vXX) = Volume. Valid values are from 0 to 100. + (sXX) = Start point in %. E.g. 10 skips the first 10% of the sample. + (eXX) = End point in %. E.g. 75 ends playback 75% into the sample. + (tXX) = Time shift/compression in %. 100 is unaltered speed, + wheras 50 plays the sample back in half the time. +*/ + +#ifdef SERVER +#define DYNAMIC_SENTENCES + +#ifdef DYNAMIC_SENTENCES + string *g_sentences; + int g_sentences_count; +#else + #define SENTENCES_LIMIT 1024 + string g_sentences[SENTENCES_LIMIT]; + int g_sentences_count; +#endif + +string Sentences_GetSamples(string); +int Sentences_GetID(string); +#endif + +#ifdef CLIENT +string Sentences_GetString(int id); +void Sentences_Shutdown(void); +#endif + +void Sentences_Init(void); + +var hashtable g_hashsentences; \ No newline at end of file diff --git a/src/client/sentences.qc b/src/shared/sentences.qc similarity index 52% rename from src/client/sentences.qc rename to src/shared/sentences.qc index 27c3cef9..171e649c 100644 --- a/src/client/sentences.qc +++ b/src/shared/sentences.qc @@ -14,49 +14,7 @@ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* voice sentence samples for AI and other triggers that are supposed to talk. - * the formatting is messy as hell and I feel dirty for even bothering with all - * this to begin with. - * - * the server will send a short string identifer over and we'll look it up. - * what's annoying is that some NPCs got their own pitch overrides so I guess - * we'll just default to those whenever there's no custom value set. - */ - -/* sentences are the voice-acting backbone of the sound system. - * http://articles.thewavelength.net/230/ - * has pretty good documentation of how the format is meant to work */ - -/* Sentences Documentation - - Each line is a new sentence group. - [GROUPNAME] [...PARAMETERS] [...SAMPLES] - - If a sample is not in a sub-directory, it'll be assumed to be part - of the 'vox' sub-directory, or the last valid path of a previous sample. - For example - attention male/hello how are you - becomes - vox/attention.wav male/hello.wav male/how.wav male/are.wav male/you.wav - - When parameters are surrounded by spaces, this means they apply - to all current samples. They can be overwritten later down the parsing. - When a parameter is attached to a sample, e.g. - attention(p120) - Then this parameter only applies to said keyword. - Whereas... - (p120) attention everyone alive - Will apply the pitch effect to all three succeeding samples. - - Parameters: - (pXX) = Pitch. Valid values are from 50 to 150. - (vXX) = Volume. Valid values are from 0 to 100. - (sXX) = Start point in %. E.g. 10 skips the first 10% of the sample. - (eXX) = End point in %. E.g. 75 ends playback 75% into the sample. - (tXX) = Time shift/compression in %. 100 is unaltered speed, - wheras 50 plays the sample back in half the time. -*/ - +#ifdef CLIENT /* enable this if you want to use memalloc */ #define DYNAMIC_SENTENCES @@ -106,6 +64,11 @@ Sentences_Init(void) return; } + /* create the hash-table if it doesn't exist */ + if (!g_hashsentences) { + g_hashsentences = hash_createtab(2, HASH_ADD); + } + while ((temp = fgets(fs_sentences))) { /* tons of comments/garbage in those files, * so tokenize appropriately */ @@ -140,6 +103,7 @@ Sentences_Init(void) /* first entry is the id, prefix with ! as well */ if (i==0) { g_sentences[x].m_strID = strtoupper(strcat("!", argv(0))); + hash_add(g_hashsentences, g_sentences[x].m_strID, x); } else { if (i == 1) { g_sentences[x].m_strSamples = sprintf("%s", argv(i)); @@ -182,11 +146,144 @@ string Sentences_GetSamples(string msg) { Sentences_ResetSample(); + int i = (int)hash_get(g_hashsentences, msg, -1i); - for (int i = 0; i < g_sentences_count; i++) { - if (g_sentences[i].m_strID == msg) { - return g_sentences[i].m_strSamples; + if (i != -1i) + return g_sentences[i].m_strSamples; + else { + print(sprintf("^1ERROR: Cannot find sentence %S\n", msg)); + return ""; + } +} + +string +Sentences_GetString(int id) +{ + return g_sentences[id].m_strID; +} +#endif + +#ifdef SERVER +void +Sentences_Init(void) +{ + filestream fs_sentences; + string temp; + int c; + + print("--------- Initializing SentencesDef (SERVER) ----------\n"); + + if (g_sentences_count > 0) { + g_sentences_count = 0; +#ifndef DYNAMIC_SENTENCES + if (g_sentences) { + memfree(g_sentences); + } +#endif + } + + fs_sentences = fopen("sound/sentences.txt", FILE_READ); + + if (fs_sentences < 0) { + print("^1could not load sound/sentences.txt\n"); + return; + } + + /* create the hash-table if it doesn't exist */ + if (!g_hashsentences) { + g_hashsentences = hash_createtab(2, HASH_ADD); + } + + while ((temp = fgets(fs_sentences))) { + /* tons of comments/garbage in those files, + * so tokenize appropriately */ + c = tokenize_console(temp); + + /* not enough for an entry. */ + if (c < 2) { + continue; + } + + /* starts of at 0, for every line increases */ + int x = g_sentences_count; + + /* allocate memory and increase count */ +#ifdef DYNAMIC_SENTENCES + g_sentences_count++; + g_sentences = (string *)memrealloc(g_sentences, + sizeof(string), + x, + g_sentences_count); +#else + if (g_sentences_count + 1 >= SENTENCES_LIMIT) { + print("^1reached limit of max sentences!\n"); + return; + } + + g_sentences_count++; +#endif + + g_sentences[x] = strtoupper(strcat("!", argv(0))); + hash_add(g_hashsentences, g_sentences[x], x); + } + + fclose(fs_sentences); + print(sprintf("SentencesDef initialized with %i entries.\n", g_sentences_count)); +} + +string +Sentences_GetSamples(string word) +{ + int len; + int gc = 0; + int r, x; + + /* you never know what NPCs might do */ + if (word == "") { + return (""); + } + + /* check if the word is present at all */ + x = (int)hash_get(g_hashsentences, word, -1i); + if (x != -1i) { + return g_sentences[x]; + } + + /* it may be a random group of words. */ + /* start at [WORD]0 */ + r = (int)hash_get(g_hashsentences, strcat(word, "0"), 0i); + + len = strlen(word); + for (int i = r; i < g_sentences_count; i++) { + string sub = substring(g_sentences[i], 0, len); + if (sub == word) { + gc++; } } + + /* if we've got one, choose a random sample of them */ + if (gc) { + r = floor(random(0, gc)); + NSLog("^3Sentences_GetSamples^7: Choosing %s%i", word, r); + return sprintf("%s%i", word, r); + } + + /* we've somehow messed up catastrophically */ + print(sprintf("^1ERROR: Invalid sentence keyword %s\n", word)); return (""); } + +int +Sentences_GetID(string sentence) +{ + int i = (int)hash_get(g_hashsentences, sentence, -1i); + + if (i != -1i) { + print(sprintf("^2SUCCESS: Found sentence %S\n", sentence)); + return i; + } else { + print(sprintf("^1ERROR: Cannot find sentence %S\n", sentence)); + return 0; + } +} +#endif \ No newline at end of file