Skip to content

Commit fe9ba91

Browse files
committed
Add custom message lists support
Closes #130 See #200
1 parent 6c03e4e commit fe9ba91

18 files changed

+337
-0
lines changed

src/combat.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,8 @@ int combatInit()
20222022
return -1;
20232023
}
20242024

2025+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_COMBAT, &gCombatMessageList);
2026+
20252027
// SFALL
20262028
criticalsInit();
20272029
burstModInit();
@@ -2061,6 +2063,7 @@ void combatReset()
20612063
// 0x420E14
20622064
void combatExit()
20632065
{
2066+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_COMBAT, nullptr);
20642067
messageListFree(&gCombatMessageList);
20652068

20662069
// SFALL

src/combat_ai.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3433,6 +3433,8 @@ static int aiMessageListInit()
34333433
messageListFilterBadwords(&gCombatAiMessageList);
34343434
}
34353435

3436+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_COMBAT_AI, &gCombatAiMessageList);
3437+
34363438
return 0;
34373439
}
34383440

@@ -3441,6 +3443,7 @@ static int aiMessageListInit()
34413443
// 0x42BBD8
34423444
static int aiMessageListFree()
34433445
{
3446+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_COMBAT_AI, nullptr);
34443447
if (!messageListFree(&gCombatAiMessageList)) {
34453448
return -1;
34463449
}

src/critter.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ int critterInit()
178178
return -1;
179179
}
180180

181+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_SCRNAME, &gCritterMessageList);
182+
181183
return 0;
182184
}
183185

@@ -193,6 +195,7 @@ void critterReset()
193195
// 0x42D004
194196
void critterExit()
195197
{
198+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_SCRNAME, nullptr);
196199
messageListFree(&gCritterMessageList);
197200
}
198201

src/game.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4
151151
return -1;
152152
}
153153

154+
// Message list repository is considered a specialized file manager, so
155+
// it should be initialized early in the process.
156+
messageListRepositoryInit();
157+
154158
runElectronicRegistration();
155159
programWindowSetTitle(windowTitle);
156160
_initWindow(1, a4);
@@ -358,6 +362,8 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4
358362
return -1;
359363
}
360364

365+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_MISC, &gMiscMessageList);
366+
361367
return 0;
362368
}
363369

@@ -402,6 +408,7 @@ void gameReset()
402408
// SFALL
403409
sfallGlobalVarsReset();
404410
sfallListsReset();
411+
messageListRepositoryReset();
405412
}
406413

407414
// 0x442C34
@@ -415,6 +422,7 @@ void gameExit()
415422
premadeCharactersExit();
416423

417424
tileDisable();
425+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_MISC, nullptr);
418426
messageListFree(&gMiscMessageList);
419427
combatExit();
420428
gameDialogExit();
@@ -448,6 +456,7 @@ void gameExit()
448456
endgameDeathEndingExit();
449457
interfaceFontsExit();
450458
_windowClose();
459+
messageListRepositoryExit();
451460
dbExit();
452461
settingsExit(true);
453462
sfallConfigExit();

src/item.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ int itemsInit()
201201
return -1;
202202
}
203203

204+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_ITEM, &gItemsMessageList);
205+
204206
// SFALL
205207
booksInit();
206208
explosionsInit();
@@ -219,6 +221,7 @@ void itemsReset()
219221
// 0x477148
220222
void itemsExit()
221223
{
224+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_ITEM, nullptr);
222225
messageListFree(&gItemsMessageList);
223226

224227
// SFALL

src/map.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ void _map_init()
306306
tickersAdd(gameMouseRefresh);
307307
_gmouse_disable(0);
308308
windowUnhide(gIsoWindow);
309+
310+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_MAP, &gMapMessageList);
309311
}
310312

311313
// 0x482084
@@ -314,6 +316,8 @@ void _map_exit()
314316
windowHide(gIsoWindow);
315317
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
316318
tickersRemove(gameMouseRefresh);
319+
320+
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_MAP, nullptr);
317321
if (!messageListFree(&gMapMessageList)) {
318322
debugPrint("\nError exiting map_msg_file!");
319323
}

src/message.cc

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include <stdlib.h>
66
#include <string.h>
77

8+
#include <array>
9+
#include <unordered_map>
10+
811
#include "debug.h"
912
#include "memory.h"
1013
#include "platform_compat.h"
@@ -17,11 +20,33 @@ namespace fallout {
1720

1821
#define BADWORD_LENGTH_MAX 80
1922

23+
static constexpr int kFirstStandardMessageListId = 0;
24+
static constexpr int kLastStandardMessageListId = kFirstStandardMessageListId + STANDARD_MESSAGE_LIST_COUNT - 1;
25+
26+
static constexpr int kFirstProtoMessageListId = 0x1000;
27+
static constexpr int kLastProtoMessageListId = kFirstProtoMessageListId + PROTO_MESSAGE_LIST_COUNT - 1;
28+
29+
static constexpr int kFirstPersistentMessageListId = 0x2000;
30+
static constexpr int kLastPersistentMessageListId = 0x2FFF;
31+
32+
static constexpr int kFirstTemporaryMessageListId = 0x3000;
33+
static constexpr int kLastTemporaryMessageListId = 0x3FFF;
34+
35+
struct MessageListRepositoryState {
36+
std::array<MessageList*, STANDARD_MESSAGE_LIST_COUNT> standardMessageLists;
37+
std::array<MessageList*, PROTO_MESSAGE_LIST_COUNT> protoMessageLists;
38+
std::unordered_map<int, MessageList*> persistentMessageLists;
39+
std::unordered_map<int, MessageList*> temporaryMessageLists;
40+
int nextTemporaryMessageListId = kFirstTemporaryMessageListId;
41+
};
42+
2043
static bool _message_find(MessageList* msg, int num, int* out_index);
2144
static bool _message_add(MessageList* msg, MessageListItem* new_entry);
2245
static bool _message_parse_number(int* out_num, const char* str);
2346
static int _message_load_field(File* file, char* str);
2447

48+
static MessageList* messageListRepositoryLoad(const char* path);
49+
2550
// 0x50B79C
2651
static char _Error_1[] = "Error";
2752

@@ -47,6 +72,8 @@ static char* _message_error_str = _Error_1;
4772
// 0x63207C
4873
static char _bad_copy[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE];
4974

75+
static MessageListRepositoryState* _messageListRepositoryState;
76+
5077
// 0x484770
5178
int badwordsInit()
5279
{
@@ -604,4 +631,187 @@ void messageListFilterGenderWords(MessageList* messageList, int gender)
604631
}
605632
}
606633

634+
bool messageListRepositoryInit()
635+
{
636+
_messageListRepositoryState = new (std::nothrow) MessageListRepositoryState();
637+
if (_messageListRepositoryState == nullptr) {
638+
return false;
639+
}
640+
641+
char* fileList;
642+
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_EXTRA_MESSAGE_LISTS_KEY, &fileList);
643+
if (fileList != nullptr && *fileList == '\0') {
644+
fileList = nullptr;
645+
}
646+
647+
char path[COMPAT_MAX_PATH];
648+
int nextMessageListId = 0;
649+
while (fileList != nullptr) {
650+
char* pch = strchr(fileList, ',');
651+
if (pch != nullptr) {
652+
*pch = '\0';
653+
}
654+
655+
char* sep = strchr(fileList, ':');
656+
if (sep != nullptr) {
657+
*sep = '\0';
658+
nextMessageListId = atoi(sep + 1);
659+
}
660+
661+
sprintf(path, "%s\\%s.msg", "game", fileList);
662+
663+
if (sep != nullptr) {
664+
*sep = ':';
665+
}
666+
667+
MessageList* messageList = messageListRepositoryLoad(path);
668+
if (messageList != nullptr) {
669+
_messageListRepositoryState->persistentMessageLists[kFirstPersistentMessageListId + nextMessageListId] = messageList;
670+
}
671+
672+
if (pch != nullptr) {
673+
*pch = ',';
674+
fileList = pch + 1;
675+
} else {
676+
fileList = nullptr;
677+
}
678+
679+
// Sfall's implementation is a little bit odd. |nextMessageListId| can
680+
// be set via "key:value" pair in the config, so if the first pair is
681+
// "msg:12287", then this check will think it's the end of the loop.
682+
// In order to maintain compatibility we'll use the same approach,
683+
// however it looks like the whole idea of auto-numbering extra message
684+
// lists is a bad one. To use these extra message lists we need to
685+
// specify their ids from user-space scripts. Without explicitly
686+
// specifying message list ids as "key:value" pairs a mere change of
687+
// order in the config will break such scripts in an unexpected way.
688+
nextMessageListId++;
689+
if (nextMessageListId == kLastPersistentMessageListId - kFirstPersistentMessageListId + 1) {
690+
break;
691+
}
692+
}
693+
694+
return true;
695+
}
696+
697+
void messageListRepositoryReset()
698+
{
699+
for (auto& pair : _messageListRepositoryState->temporaryMessageLists) {
700+
messageListFree(pair.second);
701+
delete pair.second;
702+
}
703+
_messageListRepositoryState->temporaryMessageLists.clear();
704+
_messageListRepositoryState->nextTemporaryMessageListId = kFirstTemporaryMessageListId;
705+
}
706+
707+
void messageListRepositoryExit()
708+
{
709+
if (_messageListRepositoryState != nullptr) {
710+
for (auto& pair : _messageListRepositoryState->temporaryMessageLists) {
711+
messageListFree(pair.second);
712+
delete pair.second;
713+
}
714+
715+
for (auto& pair : _messageListRepositoryState->persistentMessageLists) {
716+
messageListFree(pair.second);
717+
delete pair.second;
718+
}
719+
720+
delete _messageListRepositoryState;
721+
_messageListRepositoryState = nullptr;
722+
}
723+
}
724+
725+
void messageListRepositorySetStandardMessageList(int standardMessageList, MessageList* messageList)
726+
{
727+
_messageListRepositoryState->standardMessageLists[standardMessageList] = messageList;
728+
}
729+
730+
void messageListRepositorySetProtoMessageList(int protoMessageList, MessageList* messageList)
731+
{
732+
_messageListRepositoryState->protoMessageLists[protoMessageList] = messageList;
733+
}
734+
735+
int messageListRepositoryAddExtra(int messageListId, const char* path)
736+
{
737+
if (messageListId != 0) {
738+
// CE: Probably there is a bug in Sfall, when |messageListId| is
739+
// non-zero, it is enforced to be within persistent id range. That is
740+
// the scripting engine is allowed to add persistent message lists.
741+
// Everything added/changed by scripting engine should be temporary by
742+
// design.
743+
if (messageListId < kFirstPersistentMessageListId || messageListId > kLastPersistentMessageListId) {
744+
return -1;
745+
}
746+
747+
// CE: Sfall stores both persistent and temporary message lists in
748+
// one map, however since we've passed check above, we should only
749+
// check in persistent message lists.
750+
if (_messageListRepositoryState->persistentMessageLists.find(messageListId) != _messageListRepositoryState->persistentMessageLists.end()) {
751+
return 0;
752+
}
753+
} else {
754+
if (_messageListRepositoryState->nextTemporaryMessageListId > kLastTemporaryMessageListId) {
755+
return -3;
756+
}
757+
}
758+
759+
MessageList* messageList = messageListRepositoryLoad(path);
760+
if (messageList == nullptr) {
761+
return -2;
762+
}
763+
764+
if (messageListId == 0) {
765+
messageListId == _messageListRepositoryState->nextTemporaryMessageListId++;
766+
}
767+
768+
_messageListRepositoryState->temporaryMessageLists[messageListId] = messageList;
769+
770+
return messageListId;
771+
}
772+
773+
char* messageListRepositoryGetMsg(int messageListId, int messageId)
774+
{
775+
MessageList* messageList = nullptr;
776+
777+
if (messageListId >= kFirstStandardMessageListId && messageListId <= kLastStandardMessageListId) {
778+
messageList = _messageListRepositoryState->standardMessageLists[messageListId - kFirstStandardMessageListId];
779+
} else if (messageListId >= kFirstProtoMessageListId && messageListId <= kLastProtoMessageListId) {
780+
messageList = _messageListRepositoryState->protoMessageLists[messageListId - kFirstProtoMessageListId];
781+
} else if (messageListId >= kFirstPersistentMessageListId && messageListId <= kLastPersistentMessageListId) {
782+
auto it = _messageListRepositoryState->persistentMessageLists.find(messageListId);
783+
if (it != _messageListRepositoryState->persistentMessageLists.end()) {
784+
messageList = it->second;
785+
}
786+
} else if (messageListId >= kFirstTemporaryMessageListId && messageListId <= kLastTemporaryMessageListId) {
787+
auto it = _messageListRepositoryState->temporaryMessageLists.find(messageListId);
788+
if (it != _messageListRepositoryState->temporaryMessageLists.end()) {
789+
messageList = it->second;
790+
}
791+
}
792+
793+
MessageListItem messageListItem;
794+
return getmsg(messageList, &messageListItem, messageId);
795+
}
796+
797+
static MessageList* messageListRepositoryLoad(const char* path)
798+
{
799+
MessageList* messageList = new (std::nothrow) MessageList();
800+
if (messageList == nullptr) {
801+
return nullptr;
802+
}
803+
804+
if (!messageListInit(messageList)) {
805+
delete messageList;
806+
return nullptr;
807+
}
808+
809+
if (!messageListLoad(messageList, path)) {
810+
delete messageList;
811+
return nullptr;
812+
}
813+
814+
return messageList;
815+
}
816+
607817
} // namespace fallout

0 commit comments

Comments
 (0)