/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* 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 3 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, see .
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/Main/manage/pagelock.cpp $
* $Revision: 42 $
* $Date: 5/17/99 2:09p $
* $Author: Jeff $
*
* Jason should put something here
*
* $Log: /DescentIII/Main/manage/pagelock.cpp $
*
* 42 5/17/99 2:09p Jeff
* fixed tablefile logging
*
* 41 5/14/99 12:15p Jason
* added page logging
*
* 40 4/30/99 8:53p Matt
* Added a "voice" directory for gamefiles.
*
* 39 4/22/99 7:08p Matt
* Reduced the number of object sounds from 3 to 2, since we were only
* using two.
*
* 38 4/18/99 4:49p Matt
* Took out code that hacked in ammo amounts for weapon powerups, and
* added code to set the counts on the page and read and write to the
* table file.
*
* 37 4/14/99 10:46a Kevin
* Removed OutrageMessageBox from release builds
*
* 36 4/14/99 1:33a Jeff
* fixed case mismatched #includes
*
* 35 4/12/99 12:49p Jeff
* added recoil_force to weapon's page
*
* 34 4/09/99 12:51p Jason
* corrected bug sound resolving code
*
* 33 4/06/99 6:02p Matt
* Added score system
*
* 32 3/30/99 6:11p Jason
* changed pagelock version
*
* 31 3/29/99 11:20a Matt
* Added more flexibility to death delays, and added fade away deaths.
*
* 30 3/04/99 1:47p Jason
* fixed some manage problems
*
*
* 29 3/01/99 12:54a Matt
* Incremented table file version
*
* 28 2/23/99 12:39p Jason
* added more options for generic objects
*
* 27 2/22/99 2:03p Jason
* added different damages for players and generics
*
* 26 2/03/99 1:58a Matt
* Added a system to show all pages locked.
*
* 25 2/02/99 8:44a Chris
* I made buildings with AI work correctly (ie really big robots should be
* buildings)
* anim to and from states are now shorts instead of bytes
*
* 24 12/30/98 5:17p Jeff
* changes made to handle script name override, to override the name of
* the script to use in a module.
*
* 23 12/21/98 5:27p Jeff
* added osiris module information to door page. Fixed up dialogs for
* objects and doors for script interface (added a browse button)
*
* 22 12/17/98 7:26p Jeff
* new script system data
*
* 21 12/17/98 12:50p Jason
* changed doorpage
*
* 20 12/01/98 4:31p Chris
* Checked in a massive amount of AI work. Improved flocking code. :)
* (Still hacked lightly). In addition, I am moving toward using the
* composite dir. :)
*
* 19 11/19/98 8:25p Chris
*
* 18 11/13/98 12:30p Jason
* changes for weapons
*
* 17 11/06/98 11:24a Jason
* fixed length bug
*
* 16 11/05/98 7:55p Jason
* changes for new manage system
*
* 15 10/19/98 11:57a Chris
* Update the sound system to use the import volume
*
* 14 10/14/98 5:15p Jason
* added version checking to the table file
*
* 13 6/23/98 2:43p Matt
* Changed calls to OutrageMessageBox() & Debug_MessageBox() to deal with
* int return value (instead of bool).
*
* 12 6/05/98 12:54p Jason
* fixed stupid pagelock bug
*
* 11 6/03/98 3:46p Jason
* made megacell system more robust
*
* 10 5/04/98 5:13p Jason
* better error handling
*
* 9 3/19/98 1:15p Jason
* more error checking
*
* 8 3/19/98 12:02p Jason
* added better error checking to pagelocks
*
* 7 2/19/98 1:52p Jason
* added emergency override to unlock function
*
* 6 10/06/97 3:30p Samir
* FROM JASON: added error message
*
* 5 9/25/97 2:19p Jason
* added test case to EraseLocker
*
* 4 9/25/97 11:22a Jason
* added error checking to EraseLocker
*
* 3 8/12/97 12:07p Matt
* Treat old robot locks as generic locks
*
* 2 8/08/97 1:48p Matt
* Change mng_MakeLocker() bring up dialog if no network, and give the
* player a chance to retry if someone else has the database locked.
*
* 16 3/17/97 6:52p Jason
* added mprintf to locker file error
*
* 15 3/13/97 7:38p Matt
* Added include of string.h
*
* 14 3/13/97 6:42 PM Jeremy
* #included and unincluded
*
* 13 3/03/97 6:21p Matt
* Changed cfile functions to use D3 naming convention
*
* $NoKeywords: $
*/
#include
#include
#include
#include
#include
#include "cfile.h"
#include "manage.h"
#include "pstypes.h"
#include "pserror.h"
#include "log.h"
#include "mem.h"
#ifndef RELEASE
#include
#endif
#define CURRENT_TABLE_VERSION 22
extern const char *PageNames[];
void mng_InitPagelocks() {
// If there is not a pagelock file, create one with a dummy header.
CFILE *infile = nullptr, *outfile = nullptr;
mngs_Pagelock testlock;
if (!Network_up)
return;
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (infile == NULL) {
outfile = (CFILE *)cfopen(TableLockFilename, "wb");
if (!outfile) {
Error("Error creating table lock file!");
return;
}
testlock.pagetype = PAGETYPE_UNKNOWN;
strcpy(testlock.name, "DUMMY PAGE");
strcpy(testlock.holder, "NOT HELD AT ALL");
mng_WritePagelock(outfile, &testlock);
cfclose(outfile);
} else
cfclose(infile);
}
// Checks to see if the locker file is present.
// Returns pointer to locker's name, or NULL if the file is unlocked
char *mng_CheckIfLockerPresent() {
CFILE *infile;
static char lockname[200];
ASSERT(Network_up);
infile = (CFILE *)cfopen(LockerFile, "rb");
if (!infile)
return NULL;
cf_ReadString(lockname, sizeof(lockname), infile);
LOG_WARNING.printf("%s has already got exclusive access to the table file.", lockname);
cfclose(infile);
return lockname;
}
// Returns 1 if we have the most up to date code for the manage system, else 0
int TableVersionCurrent() {
CFILE *infile;
if (!cfexist(VersionFile))
return 1; // Doesn't exist, so go ahead
infile = (CFILE *)cfopen(VersionFile, "rb");
if (!infile) {
LOG_ERROR << "Couldn't open the version file for querying!";
return 0;
}
int version = cf_ReadInt(infile);
cfclose(infile);
if (version > CURRENT_TABLE_VERSION)
return 0;
return 1;
}
// Call this before any chokepoint functions are executed.
// Locks the whole table system for our exclusive use
int mng_MakeLocker() {
CFILE *outfile = nullptr;
int len;
char *locker;
len = strlen(TableUser);
#ifndef RELEASE
if (!Network_up) {
OutrageMessageBox("Error: You are not connected to the network.");
return 0;
}
// See if someone else has the file locked
do {
locker = mng_CheckIfLockerPresent();
if (locker) {
strupr(locker);
if (OutrageMessageBox(MBOX_YESNO,
"The network database is already locked by %s.\nIt will probably become available if you "
"wait a few moments.\n\nWould you like to try again?",
locker) != IDYES)
return 0;
}
} while (locker);
// Make sure the version is current
if (!TableVersionCurrent()) {
OutrageMessageBox("You must do a source update and recompile. Your table file code is outdated!");
return 0;
}
outfile = (CFILE *)cfopen(LockerFile, "wb");
if (!outfile) {
OutrageMessageBox("Error opening the locker file!");
LOG_ERROR << "Couldn't open the locker file!";
return 0;
}
#endif
for (int i = 0; i < len; i++)
cf_WriteByte(outfile, TableUser[i]);
cfclose(outfile);
return 1;
}
// Simply erases the Lockerfile
void mng_EraseLocker() {
#ifndef RELEASE
int count = 0;
int done = 0;
while (!done) {
CFILE *fp = (CFILE *)cfopen(LockerFile, "rt");
if (!fp)
return;
else
cfclose(fp);
if (remove(LockerFile)) {
if (count >= 10) {
OutrageMessageBox("There was a problem deleting the locker file...get Jason immediately!");
return;
} else
count++;
} else {
done = 1;
}
}
// Now update the version info
count = 0;
while (1) {
CFILE *fp = (CFILE *)cfopen(VersionFile, "wb");
if (!fp) {
if (count >= 10) {
OutrageMessageBox("There was a problem opening the version file...get Jason immediately!");
return;
} else
count++;
} else {
cf_WriteInt(fp, CURRENT_TABLE_VERSION);
cfclose(fp);
return;
}
}
#endif
}
int mng_CheckIfPageLocked(mngs_Pagelock *pl) {
// Given a page name, checks to see if it is locked.
// Returns 1 if locked, 0 if not.
CFILE *infile;
mngs_Pagelock testlock;
int r = -1, done = 0;
if (!Network_up)
return 1;
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (infile == NULL) {
strcpy(ErrorString, "There was a problem opening the table lock file for reading.");
return -1;
}
while (!done) {
r = mng_ReadPagelock(infile, &testlock);
if (r == 0) {
done = 1;
r = 2;
} else {
if (!stricmp(pl->name, testlock.name)) {
if (pl->pagetype == testlock.pagetype) {
if (!stricmp(testlock.holder, "UNLOCKED"))
r = 0;
else {
snprintf(InfoString, sizeof(InfoString), "User %s already has this page locked.", testlock.holder);
if (!stricmp(testlock.holder, TableUser))
r = 0;
else
r = 1;
}
strcpy(pl->holder, testlock.holder);
done = 1;
}
}
}
}
cfclose(infile);
return r;
}
int mng_CheckIfPageOwned(mngs_Pagelock *pl, char *owner) {
// Given a page name, checks to see if it is locked by owner.
// Returns 1 if locked by owner, 0 if not.
CFILE *infile;
mngs_Pagelock testlock;
int r = -1, done = 0;
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (infile == NULL) {
strcpy(ErrorString, "There was a problem opening the table lock file for reading.");
Int3();
return -1;
}
while (!done) {
r = mng_ReadPagelock(infile, &testlock);
if (r == 0) {
// We've reached the end of file, must be a new page and nobody owns it
done = 1;
r = 2;
} else {
if (!stricmp(pl->name, testlock.name) && pl->pagetype == testlock.pagetype) {
if (!stricmp(testlock.holder, owner))
r = 1;
else {
snprintf(InfoString, sizeof(InfoString), "User %s does not have this page locked.", owner);
r = 0;
}
done = 1;
}
}
}
cfclose(infile);
return r;
}
int mng_ReadPagelock(CFILE *fp, mngs_Pagelock *pl) {
// Reads a pagelock from the fp file. Returns 1 if successfully read, 0 if eof was encountered.
char c;
int i;
if (cfeof(fp))
return 0;
// Read type of page
pl->pagetype = cf_ReadByte(fp);
// Convert old robot locks to generic locks
if (pl->pagetype == PAGETYPE_ROBOT)
pl->pagetype = PAGETYPE_GENERIC;
// Read the name of this page
for (i = 0; i < PAGELOCK_NAME_LEN; i++) {
c = cf_ReadByte(fp);
pl->name[i] = c;
}
// Read the user who has this page locked.
for (i = 0; i < PAGELOCK_NAME_LEN; i++) {
c = cf_ReadByte(fp);
pl->holder[i] = c;
}
pl->name[PAGELOCK_NAME_LEN] = 0;
pl->holder[PAGELOCK_NAME_LEN] = 0;
if (pl->pagetype == PAGETYPE_POWERUP) {
LOG_DEBUG.printf("Powerup lock: %d, %s, %s", pl->pagetype, pl->name, pl->holder);
}
return 1;
}
int mng_ReplacePagelock(char *name, mngs_Pagelock *pl) {
// Given a pagelock, replaces the one already inside the lock file, or if not present, adds it to
// the lock file. Returns 0 on error, or 1 if successful.
CFILE *infile, *outfile;
int replaced = 0;
int done = 0;
mngs_Pagelock temp_pl;
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (!infile) {
strcpy(ErrorString, "Couldn't open Table lock file!");
Int3(); // Get Jason
return 0;
}
outfile = (CFILE *)cfopen(TempTableLockFilename, "wb");
if (!outfile) {
cfclose(infile);
strcpy(ErrorString, "Couldn't open temporary table lock file!");
Int3(); // Get Jason
return 0;
}
while (!done) {
if (mng_ReadPagelock(infile, &temp_pl)) {
if (!stricmp(temp_pl.name, name) && pl->pagetype == temp_pl.pagetype) {
// This is the page we want to replace, so write the new one out.
mng_WritePagelock(outfile, pl);
replaced = 1;
} else {
mng_WritePagelock(outfile, &temp_pl);
}
} else {
if (!replaced) {
// This is a new page, so append it to the end of file.
mng_WritePagelock(outfile, pl);
strcpy(InfoString, "Page was successfully added.");
} else
strcpy(InfoString, "Page was successfully replaced.");
done = 1;
}
}
cfclose(infile);
cfclose(outfile);
if (!SwitcherooFiles(TableLockFilename.u8string().c_str(), TempTableLockFilename)) {
Int3();
return 0;
}
// Log this change
#ifndef RELEASE
std::filesystem::path pathstr = std::filesystem::path(NetD3Dir) / "TableLog";
FILE *logfile = fopen(pathstr.u8string().c_str(), "at");
if (logfile) {
char str[255 + 32];
char date[255];
time_t t;
t = time(NULL);
strftime(date, 254, "[%a %m/%d/%y %H:%M:%S]", localtime(&t));
snprintf(str, sizeof(str), "%s Page %s (%s) last touched by %s\n", date, name, PageNames[pl->pagetype], TableUser);
fwrite(str, 1, strlen(str), logfile);
fflush(logfile);
fclose(logfile);
}
#endif
return 1; // successful!
}
// Given a name and a pagetype, deletes the one already inside the lock file
int mng_DeletePagelock(char *name, int pagetype) {
CFILE *infile, *outfile;
int done = 0, deleted = 0;
mngs_Pagelock temp_pl;
LOG_DEBUG.printf("Deleting pagelock %s.", name);
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (!infile) {
strcpy(ErrorString, "Couldn't open Table lock file!");
return 0;
}
outfile = (CFILE *)cfopen(TempTableLockFilename, "wb");
if (!outfile) {
cfclose(infile);
strcpy(ErrorString, "Couldn't open temporary table lock file!");
return 0;
}
while (!done) {
if (mng_ReadPagelock(infile, &temp_pl)) {
if (!stricmp(temp_pl.name, name) && pagetype == temp_pl.pagetype)
deleted = 1;
else
mng_WritePagelock(outfile, &temp_pl);
} else
done = 1;
}
cfclose(infile);
cfclose(outfile);
if (!deleted) {
Int3(); // Get Jason!
}
if (remove(TableLockFilename)) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem deleting the temp file - errno %d", errno);
return (0);
}
if (rename(TempTableLockFilename, TableLockFilename.u8string().c_str())) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem renaming the temp file - errno %d", errno);
return (0);
}
return 1; // successful!
}
// Given a list of names and a pagetype, deletes the ones already inside the lock file
int mng_DeletePagelockSeries(char *names[], int num, int pagetype) {
CFILE *infile, *outfile;
int done = 0;
mngs_Pagelock temp_pl;
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (!infile) {
strcpy(ErrorString, "Couldn't open Table lock file!");
return 0;
}
outfile = (CFILE *)cfopen(TempTableLockFilename, "wb");
if (!outfile) {
cfclose(infile);
strcpy(ErrorString, "Couldn't open temporary table lock file!");
return 0;
}
while (!done) {
if (mng_ReadPagelock(infile, &temp_pl)) {
if (pagetype == temp_pl.pagetype) {
int found = -1;
for (int i = 0; i < num && found == -1; i++) {
if (!stricmp(names[i], temp_pl.name)) {
found = i;
}
}
if (found == -1)
mng_WritePagelock(outfile, &temp_pl);
else
LOG_DEBUG.printf("Deleting pagelock %s.", names[found]);
} else
mng_WritePagelock(outfile, &temp_pl);
} else
done = 1;
}
cfclose(infile);
cfclose(outfile);
if (remove(TableLockFilename)) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem deleting the temp file - errno %d", errno);
return (0);
}
if (rename(TempTableLockFilename, TableLockFilename.u8string().c_str())) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem renaming the temp file - errno %d", errno);
return (0);
}
return 1; // successful!
}
// Goes through the pagelock table and deletes all duplicate entries
int mng_DeleteDuplicatePagelocks() {
CFILE *infile; //,*outfile;
int done = 0;
mngs_Pagelock temp_pl;
mngs_Pagelock *already_read;
int num = 0, duplicates = 0, i;
already_read = mem_rmalloc(8000);
ASSERT(already_read);
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (!infile) {
strcpy(ErrorString, "Couldn't open Table lock file!");
return 0;
}
/* outfile=(CFILE *)cfopen (TempTableLockFilename,"wb");
if (!outfile)
{
cfclose (infile);
strcpy (ErrorString,"Couldn't open temporary table lock file!");
return 0;
}*/
while (!done) {
if (mng_ReadPagelock(infile, &temp_pl)) {
int found = -1;
for (i = 0; i < num; i++) {
if (temp_pl.pagetype == already_read[i].pagetype && !stricmp(temp_pl.name, already_read[i].name)) {
LOG_DEBUG.printf("Found duplicated %s", temp_pl.name);
found = i;
duplicates++;
}
}
if (found == -1) {
// Hurray, a new entry
strcpy(already_read[num].name, temp_pl.name);
already_read[num].pagetype = temp_pl.pagetype;
num++;
ASSERT(num < 8000);
}
} else
done = 1;
}
cfclose(infile);
LOG_DEBUG.printf("Found %d duplicates!", duplicates);
/* cfclose (outfile);
if (remove (TableLockFilename))
{
sprintf (ErrorString,"There was a problem deleting the temp file - errno %d",errno);
return (0);
}
if (rename (TempTableLockFilename,TableLockFilename))
{
sprintf (ErrorString,"There was a problem renaming the temp file - errno %d",errno);
return (0);
}*/
mem_free(already_read);
return 1; // successful!
}
// Given a list of names and a pagetype, unlocks the ones already inside the lock file
int mng_UnlockPagelockSeries(const char *names[], int *pagetypes, int num) {
ASSERT(num < 500);
uint8_t already_done[500];
memset(already_done, 0, 500);
CFILE *infile, *outfile;
int done = 0;
mngs_Pagelock temp_pl;
int total = 0;
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (!infile) {
strcpy(ErrorString, "Couldn't open Table lock file!");
return 0;
}
outfile = (CFILE *)cfopen(TempTableLockFilename, "wb");
if (!outfile) {
cfclose(infile);
strcpy(ErrorString, "Couldn't open temporary table lock file!");
return 0;
}
while (!done) {
if (mng_ReadPagelock(infile, &temp_pl)) {
int found = -1;
for (int i = 0; i < num && found == -1; i++) {
if (!stricmp(names[i], temp_pl.name)) {
found = i;
}
}
if (found == -1)
mng_WritePagelock(outfile, &temp_pl);
else {
if (pagetypes[found] == temp_pl.pagetype) {
if (already_done[found]) {
LOG_DEBUG.printf("Found duplicate=%s", names[found]);
} else {
LOG_DEBUG.printf("Replacing pagelock %s to UNLOCKED.", names[found]);
strcpy(temp_pl.holder, "UNLOCKED");
mng_WritePagelock(outfile, &temp_pl);
total++;
already_done[found] = 1;
}
} else
mng_WritePagelock(outfile, &temp_pl);
}
} else
done = 1;
}
cfclose(infile);
cfclose(outfile);
LOG_DEBUG.printf("Unlocked %d pages\n", total);
if (remove(TableLockFilename)) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem deleting the temp file - errno %d", errno);
return (0);
}
if (rename(TempTableLockFilename, TableLockFilename.u8string().c_str())) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem renaming the temp file - errno %d", errno);
return (0);
}
return 1; // successful!
}
// Writes a given pagelock out to the file pointed to by fp
void mng_WritePagelock(CFILE *fp, mngs_Pagelock *pl) {
int i;
// Write pagetype
cf_WriteByte(fp, pl->pagetype);
// Write name
for (i = 0; i < PAGELOCK_NAME_LEN; i++)
cf_WriteByte(fp, pl->name[i]);
// Who owns this page?
for (i = 0; i < PAGELOCK_NAME_LEN; i++)
cf_WriteByte(fp, pl->holder[i]);
}
int mng_GetListOfLocks(mngs_Pagelock *pl, int max, char *who) {
// Given a list of pagelocks, a max number allowed, and a specified user "who", fills in the
// given pagelocks with all the pages that are locked by "who".
CFILE *infile;
mngs_Pagelock temp_pl;
int done = 0, num = 0;
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (!infile) {
strcpy(ErrorString, "Couldn't open Table lock file!");
return -1;
}
while (!done) {
// Read in a page, compare it with the owner we're searching for. If found, make a copy of it.
if (mng_ReadPagelock(infile, &temp_pl)) {
if (((who == NULL) && stricmp(temp_pl.holder, "UNLOCKED")) ||
((who != NULL) && (!stricmp(temp_pl.holder, who)))) {
pl[num] = temp_pl;
num++;
if (num >= max)
done = 1;
}
} else
done = 1;
}
cfclose(infile);
return num;
}
// Takes a pagelock and sets its holder name to UNLOCKED
void mng_OverrideToUnlocked(mngs_Pagelock *temp_pl) {
int r;
#ifndef RELEASE
r = mng_CheckIfPageOwned(temp_pl, TableUser);
if (r == 1) {
OutrageMessageBox("You own this page, so there is no use in you setting it to unlocked!");
return;
}
int answer = OutrageMessageBox(
MBOX_YESNO,
"Are you sure you wish to override this page to unlocked? (choose 'NO' if you don't know what you're doing!)");
if (answer == IDNO)
return;
answer = OutrageMessageBox(MBOX_YESNO,
"Do you wish to actually erase this pagelock? (Again, answer NO if you are not sure!)");
if (!mng_MakeLocker())
return;
strcpy(temp_pl->holder, "UNLOCKED");
if (answer == IDNO) {
if (!mng_ReplacePagelock(temp_pl->name, temp_pl))
OutrageMessageBox(ErrorString, "Error!");
else {
OutrageMessageBox("Page set to unlocked.");
}
} else {
// Given a name and a pagetype, deletes the one already inside the lock file
if (!mng_DeletePagelock(temp_pl->name, temp_pl->pagetype)) {
OutrageMessageBox(ErrorString, "Error!");
} else
OutrageMessageBox("Pagelock deleted!");
}
#endif
mng_EraseLocker();
}