/* Crimson2 Mud Server
* All source written/copyright Ryan Haksi 1995 *
* This source code is proprietary. Use in whole or in part without
* explicity permission by the author is strictly prohibited
*
* Current email address(es): cryogen@infoserve.net
* Phone number: (604) 591-5295
*
* C4 Script Language written/copyright Cam Lesiuk 1995
* Email: clesiuk@engr.uvic.ca
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#ifndef WIN32
#include <unistd.h>
#include <dirent.h>
#else
#include <io.h>
#include <direct.h>
#endif
#include <ctype.h>
#include "crimson2.h"
#include "macro.h"
#include "queue.h"
#include "log.h"
#include "mem.h"
#include "str.h"
#include "ini.h"
#include "send.h"
#include "extra.h"
#include "property.h"
#include "code.h"
#include "file.h"
#include "thing.h"
#include "index.h"
#include "world.h"
#include "area.h"
#include "reset.h"
#include "edit.h"
#include "history.h"
#include "socket.h"
#include "base.h"
#include "object.h"
#include "char.h"
#include "affect.h"
#include "effect.h"
#include "skill.h"
#include "player.h"
#include "parse.h"
#include "cmd_inv.h"
#include "cmd_misc.h"
#ifdef WIN32
/* including winsock.h etc auto includes the windows types */
/* turn off warning: benign type redefinition */
#pragma warning( disable : 4142 )
#include <winsock.h>
#pragma warning( default : 4142 )
#endif
#define PLAYER_ALLOC_SIZE 4096
#define PLAYER_INDEX_SIZE 1024
INDEX playerIndex;
BYTE playerInitLog;
LWORD playerStartRoom;
LWORD playerIdleRoom;
LWORD playerIdleTick;
LWORD playerDropTick;
LWORD playerAFKDropTick;
BYTE *pSystemList[] = {
"LOG",
"KILLER",
"HELPER",
"CRASHSAVED",
"NOHASSLE",
""
};
BYTE *pAutoList[] = {
"A-LOOK",
"A-EXIT",
"A-LOOT",
"A-ASSIST",
"A-AGGRESSIVE",
"A-JUNK",
"A-FLEE",
"A-EAT",
"A-DRINK",
"NOEMAIL",
"HINT",
"EXPERT",
"CONSIDER",
"A-RESCUE",
"AFK",
""
};
BYTE *pSpeechColor[] = {
"rA",
"gA",
"pA",
"cA",
"CA",
"wA",
"WA",
"WR",
"gR",
"GR",
"yR",
"pR",
"cR",
"CR",
"WR",
"AG",
"RG",
"yG",
"BG",
"PG",
"cG",
"AY",
"rY",
"RY",
"gY",
"BY",
"PY",
"cY",
"wY",
"rB",
"gB",
"yB",
"pB",
"cB",
"wB",
"WB",
"aP",
"gP",
"yP",
"YP",
"cP",
"CP",
"WP",
"AC",
"rC",
"RC",
"gC",
"yC",
"bC",
"BC",
"PC",
"wC",
"rW",
"AW",
"RW",
"bW",
"BW",
"pW",
"PW",
"GW",
""
};
LWORD pSpeechColorNum = 0;
void PlayerInit(void) {
BYTE buf[256];
#ifndef WIN32
DIR *playerDir;
struct dirent *playerEnt;
#else
struct _finddata_t playerFind;
LWORD findError;
#endif
LWORD sLen;
BYTE playerName[256];
BYTE playerFile[256];
LWORD playerNum;
LWORD playerDelete = FALSE;
LWORD deleteThreshold;
LWORD deleteNum;
THING *player;
IndexInit(&playerIndex, PLAYER_INDEX_SIZE, "playerIndex", 0);
playerInitLog = INILWordRead("crimson2.ini", "playerInitLog", 0);
playerStartRoom = INILWordRead("crimson2.ini", "pStartRoom", 3001);
playerIdleRoom = INILWordRead("crimson2.ini", "pIdleRoom", 0);
playerIdleTick = INILWordRead("crimson2.ini", "pIdleTick", 12);
playerDropTick = INILWordRead("crimson2.ini", "pDropTick", 48);
playerAFKDropTick=INILWordRead("crimson2.ini", "pAFKTick", 360);
if (PLAYER_MAX_SKILL < skillNum) {
sprintf(buf, "Increase PLAYER_MAX_SKILL to at least %hd\n", skillNum);
Log(LOG_ERROR, buf);
EXIT(ERROR_BADFILE);
}
for (pSpeechColorNum=0; *pSpeechColor[pSpeechColorNum]; pSpeechColorNum++);
/* Scan Crash directory - Count objects as online */
playerNum = 0;
Log(LOG_BOOT, "Scanning crash/ for active files\n");
#ifndef WIN32
playerDir = opendir("crash");
if (!playerDir) {
Log(LOG_ERROR, "Directory crash/ doesnt exist... create it!\n");
EXIT(ERROR_BADFILE);
}
while( (playerEnt=readdir(playerDir)) ) {
sLen = strlen(playerEnt->d_name);
strcpy(playerName, playerEnt->d_name);
#else
findError = _findfirst( "crash\\*.plr", &playerFind );
while( findError != -1 ) {
strcpy(playerName, playerFind.name);
sLen = strlen(playerName);
#endif
if (sLen > 4 && !strcmp(playerName+sLen-4, ".plr")) {
playerName[sLen-4] = '\0';
/* see if the crash file is stale, if it is, move it! */
player = PlayerRead(playerName, PREAD_SCAN);
/* its stale when its >48 hours old */
if (time(0) - Plr(player)->pTimeLastOn > 48*3600) {
#ifdef WIN32
sprintf(buf, "move crash\\%s.plr player\\%s.plr", playerName, playerName);
#else
sprintf(buf, "mv crash/%s.plr player/%s.plr", playerName, playerName);
#endif
system(buf);
} else {
#ifndef WIN32
PlayerCountObjects("crash", playerEnt->d_name, PCO_ONLINE);
#else
PlayerCountObjects("crash", playerFind.name, PCO_ONLINE);
#endif
if (playerInitLog) {
if (playerNum%5 == 4) {
sprintf(buf, " %-14s\n", playerName);
} else {
sprintf(buf, " %-14s", playerName);
}
if (playerNum%5==0)
Log(LOG_BOOT, buf);
else
LogPrintf(LOG_BOOT, buf);
}
}
THINGFREE(player);
playerNum++;
}
#ifdef WIN32
if ( _findnext( findError, &playerFind ) !=0) {
_findclose(findError);
break;
}
#endif
}
if (playerNum>0 && playerNum%5!=0)
LogPrintf(LOG_BOOT, "\n");
#ifndef WIN32
closedir(playerDir);
#endif
sprintf(buf, "%ld crashed players found.\n\n", playerNum);
Log(LOG_BOOT, buf);
/* Scan Players directory - Count offline objects */
playerNum = 0;
deleteNum = 0;
Log(LOG_BOOT, "Scanning players/ for active files\n");
#ifndef WIN32
playerDir = opendir("player");
if (!playerDir) {
Log(LOG_ERROR, "Directory player/ doesnt exist... create it!\n");
EXIT(ERROR_BADFILE);
}
while( (playerEnt=readdir(playerDir)) ) {
sLen = strlen(playerEnt->d_name);
strcpy(playerName, playerEnt->d_name);
strcpy(playerFile, playerEnt->d_name);
#else
findError = _findfirst( "player\\*.plr", &playerFind );
while( findError != -1 ) {
strcpy(playerName, playerFind.name);
strcpy(playerFile, playerFind.name);
sLen = strlen(playerName);
#endif
if (sLen > 4 && !strcmp(playerName+sLen-4, ".plr")) {
playerName[sLen-4] = '\0';
/* Check for auto-delete its stale when its > ?? days */
player = PlayerRead(playerName, PREAD_SCAN);
if (Character(player)->cExp <= 0 && Character(player)->cLevel <= 1)
deleteThreshold = 10 * 24 * 3600;
else if (Character(player)->cLevel <= 3)
deleteThreshold = 30 * 24 * 3600;
else if (Character(player)->cLevel <= 10)
deleteThreshold = 60 * 24 * 3600;
else if (Character(player)->cLevel <= 20)
deleteThreshold = 120 * 24 * 3600;
else if (Character(player)->cLevel <= 30)
deleteThreshold = 150 * 24 * 3600;
else
deleteThreshold = 365 * 24 * 3600;
playerDelete = FALSE;
if (Plr(player)->pTimeLastOn && (time(NULL) - Plr(player)->pTimeLastOn) > deleteThreshold) {
playerDelete = TRUE;
deleteNum += 1;
}
THINGFREE(player);
if (playerDelete) {
/* They're history */
#ifdef WIN32
sprintf(buf, "move player\\%s.plr player\\%s.plr.del", playerName, playerName);
#else
/* THERE IS AS BUG deleting player files at random, so I took this out. -- CAM TAG TAG */
/* sprintf(buf, "mv player/%s.plr player/%s.plr.del", playerName, playerName);
*/
#endif
/*system(buf);
*/
} else {
/* Still with us, collect some stats on them */
PlayerCountObjects("player", playerFile, PCO_OFFLINE);
if (playerInitLog) {
if (playerNum%5 == 4) {
sprintf(buf, " %-14s\n", playerName);
} else {
sprintf(buf, " %-14s", playerName);
}
if (playerNum%5==0)
Log(LOG_BOOT, buf);
else
LogPrintf(LOG_BOOT, buf);
}
playerNum++;
}
}
#ifdef WIN32
if ( _findnext( findError, &playerFind ) !=0) {
_findclose(findError);
break;
}
#endif
}
if (playerNum>0 && playerNum%5!=0)
LogPrintf(LOG_BOOT, "\n");
#ifndef WIN32
closedir(playerDir);
#endif
sprintf(buf, "%ld offline players found.\n", playerNum);
Log(LOG_BOOT, buf);
sprintf(buf, "%ld old players turfed.\n\n", deleteNum);
Log(LOG_BOOT, buf);
}
/* Count all the Objects in a player file - call before boot-time reset */
void PlayerCountObjects(BYTE *directory, BYTE *playerFileName, LWORD online) {
FILE *playerFile;
BYTE playerFileBuf[128];
BYTE buf[256];
OBJTEMPLATE *objTemplate;
LWORD oVirtual;
sprintf(playerFileBuf, "%s/%s", directory, playerFileName);
playerFile = fopen(playerFileBuf, "rb");
if (!playerFile) {
Log(LOG_ERROR, "PlayerCountObjects: Unable to open: ");
LogPrintf(LOG_ERROR, playerFileBuf);
LogPrintf(LOG_ERROR, "\n");
return;
}
if (PlayerReadHeader(playerFile, "[Objects]")){
while(!feof(playerFile)) {
fscanf(playerFile, " ");
buf[0]=0;
fgets(buf, sizeof(buf), playerFile);
if (*buf=='[') break;
switch(buf[0]) {
case '#':
sscanf(buf, "# %ld", &oVirtual);
if (oVirtual != -1)
objTemplate = ObjectOf(oVirtual);
else
objTemplate = NULL;
if (objTemplate) {
if (online) {
objTemplate->oOnline++;
} else {
objTemplate->oOffline++;
}
}
break;
}
}
}
fclose(playerFile);
}
THING *PlayerFind(BYTE *playerName) {
LWORD i;
for (i=0; i<playerIndex.iNum; i++) {
if (!STRICMP(Thing(playerIndex.iThing[i])->tSDesc->sText, playerName))
return playerIndex.iThing[i]; /* allready playing */
}
return NULL;
}
/* reformat a player name to correct case, guard against caps lock morons */
/* returns true/false name string is valid */
BYTE PlayerName(BYTE *playerName) {
BYTE allUpper = TRUE;
LWORD i;
if (!playerName) return PNAME_INVALID; /* null */
if (strlen(playerName)<3) return PNAME_INVALID; /* too short */
if (strlen(playerName)>15) return PNAME_INVALID; /* too long */
if (islower(playerName[0]))
playerName[0] = toupper(playerName[0]);
/* ensure no punctuation in name */
for (i=0; playerName[i] != '\0'; i++) {
if (islower(playerName[i]))
allUpper = FALSE;
if (!isalpha(playerName[i]))
return PNAME_INVALID;
}
/* HAHA upper case bozo's are in for a shock */
if (allUpper) /* lowercase everything but first char */
for (i=1; playerName[i] != '\0'; i++)
playerName[i] = tolower(playerName[i]);
return PNAME_VALID;
}
THING *PlayerAlloc(BYTE *playerName) {
PLR *player;
LWORD i;
MEMALLOC(player, PLR, PLAYER_ALLOC_SIZE);
memset(player, 0, sizeof(PLR));
IndexAppend(&playerIndex, Thing(player));
/* Complete set of defaults */
Thing(player)->tType = TTYPE_PLR;
Thing(player)->tSDesc = STRCREATE(playerName); /* name */
Thing(player)->tDesc = STRCREATE("You see nothing special.\n"); /* paragraph description */
Base(player)->bKey = STRCREATE(playerName); /* keywords that you can use to access player */
Base(player)->bLDesc = STRCREATE("$n the Titleless"); /* name & title */
Base(player)->bWeight = 150;
Character(player)->cLevel = 1;
Character(player)->cPos = POS_STANDING;
Plr(player)->pPassword = STRCREATE(""); /* default password */
Plr(player)->pEnter = STRCREATE("$n appears from a cloud of blue sparkles\n"); /* default password */
Plr(player)->pExit = STRCREATE("$n dissolves into a cloud of blue sparkles\n"); /* default password */
Plr(player)->pPrompt = STRCREATE(""); /* default password */
Plr(player)->pLastLogin = STRCREATE(""); /* default password */
Plr(player)->pEmail = STRCREATE(""); /* default password */
Plr(player)->pPlan = STRCREATE(""); /* default password */
Plr(player)->pHunger = -1;
Plr(player)->pThirst = -1;
Plr(player)->pAuto = PA_AUTOLOOK | PA_AUTOEXIT | PA_AUTOEAT | PA_AUTOFLEE
| PA_NOEMAIL | PA_AUTODRINK | PA_HINT | PA_CONSIDER;
Plr(player)->pSockPref = sockDefFlag;
Plr(player)->pScreenLines = 23;
Plr(player)->pStartRoom = -1;
Plr(player)->pRecallRoom = -1;
Plr(player)->pDeathRoom = 100;
memcpy(Plr(player)->pColorPref, defaultPref, sizeof(COLORPREF)*COLORPREF_MAX);
/* assign a random speech color */
i = Number(0, pSpeechColorNum-1);
Plr(player)->pColorPref[6].cFG = *pSpeechColor[i];
Plr(player)->pColorPref[6].cBG = *(pSpeechColor[i]+1);
return Thing(player);
}
BYTE PlayerCrashExists(THING *thing) {
FILE *file;
BYTE playerFileBuf[128];
BYTE playerName[128];
if (!thing) return FALSE;
/* obtain filename version of players name */
strcpy(playerName, thing->tSDesc->sText);
StrToLower(playerName);
/* plr file */
sprintf(playerFileBuf, "crash/%s.plr", playerName);
file = fopen(playerFileBuf, "rb");
if (file) {
fclose(file);
return TRUE;
} else
return FALSE;
}
BYTE PlayerCloneExists(THING *thing) {
FILE *file;
BYTE playerFileBuf[128];
BYTE playerName[128];
if (!thing) return FALSE;
/* obtain filename version of players name */
strcpy(playerName, thing->tSDesc->sText);
StrToLower(playerName);
/* plr file */
sprintf(playerFileBuf, "clone/%s.plr", playerName);
file = fopen(playerFileBuf, "rb");
if (file) {
fclose(file);
return TRUE;
} else
return FALSE;
}
/* Since the online totals of objects are adjusted when a crash player file
is read in, in the event that the password check fails (after the file
has allready been read) the online totals to be re-adjusted.
*/
void PlayerAddOnline(THING *thing) {
if (!thing) return;
if (thing->tType == TTYPE_OBJ) {
Obj(thing)->oTemplate->oOnline++;
}
PlayerAddOnline(thing->tContain);
PlayerAddOnline(thing->tNext);
}
/* the ftell/fseek business prevents a missing setting from preventing reads
of later ones due to zipping down to the end of the file */
BYTE PlayerReadHeader(FILE *playerFile, BYTE *header) {
BYTE buf[512];
LWORD playerPos;
playerPos = ftell(playerFile);
while (!feof(playerFile)) {
fscanf(playerFile, " %s", buf);
if (!STRICMP(header, buf)) return TRUE;
fgets(buf, 511, playerFile); /* must be some other line */
}
if (feof(playerFile))
fseek(playerFile, playerPos, SEEK_SET);
return FALSE;
}
/* Misplaced {'s and }'s will really screw this thing up, the return value
* is used so that during recursion it can tell itself not to overrun the
* end of file or the end of the group!
*/
BYTE PlayerReadObject(THING *player, FILE *playerFile, THING *thing, BYTE mode) {
THING *object;
OBJTEMPLATE *objTemplate;
LWORD oVirtual;
LWORD apply;
BYTE aType[256];
LWORD aValue;
BYTE buf[256];
LWORD i;
LWORD typeCheck = -1;
fgets(buf, sizeof(buf), playerFile);
for (i=0;buf[i]==' ';i++);
object = NULL;
while(!feof(playerFile)) {
if (buf[i]=='{') /* objects within objects */
if (!PlayerReadObject(player, playerFile, object, mode))
return FALSE;
if (buf[i]=='}') return TRUE; /* end of nested objects level */
if (buf[i]=='[') return FALSE; /* End of section */
switch(buf[i]) {
case '#':
apply=0;
sscanf(buf+i, "# %ld", &oVirtual);
if (oVirtual != -1)
objTemplate = ObjectOf(oVirtual);
else
objTemplate = NULL;
if (objTemplate) {
object = ObjectCreate(objTemplate, thing);
typeCheck = -1;
/* trailing comment of object name is ignored */
sscanf(buf+i,
"# %*d %ld %ld %ld %ld %ld %ld %ld",
&Obj(object)->oDetail.lValue[0],
&Obj(object)->oDetail.lValue[1],
&Obj(object)->oDetail.lValue[2],
&Obj(object)->oDetail.lValue[3],
&Obj(object)->oEquip,
&Obj(object)->oAct,
&typeCheck);
if (Obj(object)->oEquip && objTemplate->oType == OTYPE_WEAPON)
Character(player)->cWeapon = object;
BITSET(Character(player)->cEquip, Obj(object)->oEquip);
/* Now that the object is really loaded remove extra online entry */
if (mode == PREAD_CRASH)
objTemplate->oOnline--;
/* In the game now, no longer offline */
else if (mode == PREAD_PLAYER)
objTemplate->oOffline--;
} else {
object = NULL;
}
break;
case 'A':
if (object) {
sscanf(buf+i, "A %s %ld", aType, &aValue);
Obj(object)->oApply[apply].aType = TYPESSCANF(aType, applyList);
TYPECHECK(Obj(object)->oApply[apply].aType, applyList);
Obj(object)->oApply[apply].aValue = aValue;
apply++;
}
break;
}
fgets(buf, sizeof(buf), playerFile);
for (i=0;buf[i]==' ';i++);
/* if we've finished reading this object see if its been obsoleted */
switch(buf[i]) {
case '{':
case '}':
case '[':
case '#':
if (object && typeCheck!=-1 && typeCheck!=objTemplate->oType) {
SendThing("^wRemoving out of play object\n", thing);
THINGFREE(object);
}
}
}
/* End of file */
return FALSE;
}
/* will return NULL if player does not exist, it should be noted
that due to internal structure lines greater than 511 characters
are not supported.
BTW: in case you are wondering why the settings are all named,
experience has shown that the player file structure is frequently
changed and that requests to change level, abilities etc are *VERY*
common.
*/
THING *PlayerRead(BYTE *playerName, BYTE mode) {
THING *player;
FILE *playerFile;
BYTE playerFileBuf[128];
BYTE pathBuf[64] = "crash";
BYTE buf[256];
BYTE *scan;
LWORD i;
STR *sKey;
STR *sDesc;
AFFECT *affect;
/* check to see if a crash recovery file exists first */
switch (mode) {
case PREAD_NORMAL:
case PREAD_SCAN:
sprintf(playerFileBuf, "crash/%s.plr", playerName);
StrPath(playerFileBuf);
StrToLower(playerFileBuf);
playerFile = fopen(playerFileBuf, "rb");
if (!playerFile) {
/* check to see if a normal player save file exists */
strcpy(pathBuf, "player");
sprintf(playerFileBuf, "player/%s.plr", playerName);
StrPath(playerFileBuf);
StrToLower(playerFileBuf);
playerFile = fopen(playerFileBuf, "rb");
if (mode == PREAD_NORMAL)
mode = PREAD_PLAYER;
} else
if (mode == PREAD_NORMAL)
mode = PREAD_CRASH;
break;
case PREAD_CLONE:
sprintf(playerFileBuf, "clone/%s.plr", playerName);
StrPath(playerFileBuf);
StrToLower(playerFileBuf);
playerFile = fopen(playerFileBuf, "rb");
break;
}
if (playerFile) {
/* the basics */
player = PlayerAlloc(playerName);
FILESTRREAD(playerFile, Thing(player)->tSDesc);
if (fileError) {
sprintf(buf, "Error reading SDesc for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
FILESTRREAD(playerFile, Plr(player)->pPassword);
if (fileError) {
sprintf(buf, "Error reading Password for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
FILESTRREAD(playerFile, Base(player)->bLDesc);
if (fileError) {
sprintf(buf, "Error reading LDesc for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
FILESTRREAD(playerFile, Thing(player)->tDesc);
if (fileError) {
sprintf(buf, "Error reading Desc for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
if (PlayerReadHeader(playerFile, "Messages:")) {
fgets(buf, sizeof(buf), playerFile);
FILESTRREAD(playerFile, Plr(player)->pEnter);
if (fileError) {
sprintf(buf, "Error reading EnterMsg for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
FILESTRREAD(playerFile, Plr(player)->pExit);
if (fileError) {
sprintf(buf, "Error reading ExitMsg for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
FILESTRREAD(playerFile, Plr(player)->pEmail);
if (fileError) {
sprintf(buf, "Error reading Email for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
FILESTRREAD(playerFile, Plr(player)->pPlan);
if (fileError) {
sprintf(buf, "Error reading Plan for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
}
if (PlayerReadHeader(playerFile, "Prompt:")) {
fscanf(playerFile, " ");
FILESTRREAD(playerFile, Plr(player)->pPrompt);
if (fileError) {
sprintf(buf, "Error reading Prompt for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
}
if (PlayerReadHeader(playerFile, "LastLogin:")) {
fscanf(playerFile, " ");
FILESTRREAD(playerFile, Plr(player)->pLastLogin);
if (fileError) {
sprintf(buf, "Error reading LastLogin for player %s\n", playerName);
Log(LOG_ERROR, buf);
}
}
if (PlayerReadHeader(playerFile, "Level:"))
Character(player)->cLevel = FileByteRead(playerFile);
if (PlayerReadHeader(playerFile, "Race:"))
Plr(player)->pRace = FILETYPEREAD(playerFile, raceList);
if (PlayerReadHeader(playerFile, "Class:"))
Plr(player)->pClass = FILETYPEREAD(playerFile, classList);
if (PlayerReadHeader(playerFile, "Position:"))
Character(player)->cPos = FILETYPEREAD(playerFile, posList);
if (Character(player)->cPos >= POS_STUNNED)
Character(player)->cPos = POS_STANDING;
if (PlayerReadHeader(playerFile, "Sex:"))
Character(player)->cSex = FILETYPEREAD(playerFile, sexList);
if (PlayerReadHeader(playerFile, "AutoAction:"))
Plr(player)->pAuto = FileFlagRead(playerFile, pAutoList);
if (PlayerReadHeader(playerFile, "System:"))
Plr(player)->pSystem = FileFlagRead(playerFile, pSystemList);
if (PlayerReadHeader(playerFile, "SockPref:"))
Plr(player)->pSockPref = FileFlagRead(playerFile, sfPrefList);
if (PlayerReadHeader(playerFile, "ScreenLines:"))
Plr(player)->pScreenLines = FileByteRead(playerFile);
if (PlayerReadHeader(playerFile, "ColorPref:")) {
fscanf(playerFile, " ");
fgets(buf, sizeof(buf), playerFile);
scan = buf;
for (i=0; i<COLORPREF_MAX; i++) {
if (!*scan) break;
Plr(player)->pColorPref[i].cFG = scan[0];
Plr(player)->pColorPref[i].cBG = scan[1];
scan = StrOneWord(scan, NULL);
}
}
/*
if (PlayerReadHeader(playerFile, "Equip:"))
fscanf(playerFile, " %ld", &Character(player)->cEquip);
*/
if (PlayerReadHeader(playerFile, "Practice:"))
fscanf(playerFile, " %ld", &Plr(player)->pPractice);
if (PlayerReadHeader(playerFile, "HitP:"))
fscanf(playerFile, " %ld", &Character(player)->cHitP);
if (PlayerReadHeader(playerFile, "MoveP:"))
fscanf(playerFile, " %ld", &Character(player)->cMoveP);
if (PlayerReadHeader(playerFile, "PowerP:"))
fscanf(playerFile, " %ld", &Character(player)->cPowerP);
if (PlayerReadHeader(playerFile, "GainPractice:"))
fscanf(playerFile, " %hd", &Plr(player)->pGainPractice);
if (PlayerReadHeader(playerFile, "GainHit:"))
fscanf(playerFile, " %hd", &Plr(player)->pGainHit);
if (PlayerReadHeader(playerFile, "GainMove:"))
fscanf(playerFile, " %hd", &Plr(player)->pGainMove);
if (PlayerReadHeader(playerFile, "GainPower:"))
fscanf(playerFile, " %hd", &Plr(player)->pGainPower);
if (PlayerReadHeader(playerFile, "Fame:"))
fscanf(playerFile, " %hd", &Plr(player)->pFame);
if (PlayerReadHeader(playerFile, "Infamy:"))
fscanf(playerFile, " %hd", &Plr(player)->pInfamy);
if (PlayerReadHeader(playerFile, "HitPMax:"))
fscanf(playerFile, " %ld", &Character(player)->cHitPMax);
if (PlayerReadHeader(playerFile, "MovePMax:"))
fscanf(playerFile, " %ld", &Plr(player)->pMovePMax);
if (PlayerReadHeader(playerFile, "PowerPMax:"))
fscanf(playerFile, " %ld", &Plr(player)->pPowerPMax);
if (PlayerReadHeader(playerFile, "Hunger:"))
fscanf(playerFile, " %hd", &Plr(player)->pHunger);
if (PlayerReadHeader(playerFile, "Thirst:"))
fscanf(playerFile, " %hd", &Plr(player)->pThirst);
if (PlayerReadHeader(playerFile, "Intox:"))
fscanf(playerFile, " %hd", &Plr(player)->pIntox);
if (PlayerReadHeader(playerFile, "Str:"))
fscanf(playerFile, " %hd", &Plr(player)->pStr);
/* temporary fix */
/*MAXSET(Plr(player)->pStr, raceList[Plr(player)->pRace].rMaxStat[SA_STR]);*/
if (PlayerReadHeader(playerFile, "Dex:"))
fscanf(playerFile, " %hd", &Plr(player)->pDex);
if (PlayerReadHeader(playerFile, "Con:"))
fscanf(playerFile, " %hd", &Plr(player)->pCon);
if (PlayerReadHeader(playerFile, "Wis:"))
fscanf(playerFile, " %hd", &Plr(player)->pWis);
if (PlayerReadHeader(playerFile, "Int:"))
fscanf(playerFile, " %hd", &Plr(player)->pInt);
/*
if (PlayerReadHeader(playerFile, "HitBonus:"))
fscanf(playerFile, " %hd", &Character(player)->cHitBonus);
if (PlayerReadHeader(playerFile, "DamBonus:"))
fscanf(playerFile, " %hd", &Character(player)->cDamBonus);
if (PlayerReadHeader(playerFile, "Armor:"))
fscanf(playerFile, " %hd", &Character(player)->cArmor);
for (i=0; i<CHAR_MAX_RESIST; i++) {
sprintf(buf, "Resist%ld:", i);
if (PlayerReadHeader(playerFile, buf))
fscanf(playerFile, " %hd", &Character(player)->cResist[i]);
}
*/
if (mode!=PREAD_CLONE && PlayerReadHeader(playerFile, "Money:"))
fscanf(playerFile, " %ld", &Character(player)->cMoney);
if (mode!=PREAD_CLONE && PlayerReadHeader(playerFile, "Bank:"))
fscanf(playerFile, " %ld", &Plr(player)->pBank);
if (PlayerReadHeader(playerFile, "Exp:"))
fscanf(playerFile, " %ld", &Character(player)->cExp);
if (PlayerReadHeader(playerFile, "TimeTotal:"))
fscanf(playerFile, " %lu", &Plr(player)->pTimeTotal);
if (PlayerReadHeader(playerFile, "TimeLastOn:"))
fscanf(playerFile, " %lu", &Plr(player)->pTimeLastOn);
if (PlayerReadHeader(playerFile, "Attempts:"))
fscanf(playerFile, " %hd", &Plr(player)->pAttempts);
if (PlayerReadHeader(playerFile, "StartRoom:"))
fscanf(playerFile, " %ld", &Plr(player)->pStartRoom);
if (PlayerReadHeader(playerFile, "RecallRoom:"))
fscanf(playerFile, " %ld", &Plr(player)->pRecallRoom);
if (PlayerReadHeader(playerFile, "DeathRoom:"))
fscanf(playerFile, " %ld", &Plr(player)->pDeathRoom);
/* Read in the Skills */
if (PlayerReadHeader(playerFile, "[Skills]")){
while(fscanf(playerFile, " %s", buf)==1) {
if (buf[0]=='[') break;
i = TYPEFIND(buf, skillList);
if (i!=-1) {
fscanf(playerFile, " %hd ", &Plr(player)->pSkill[i]);
}
}
}
/* Read all the Objects */
if (PlayerReadHeader(playerFile, "[Objects]") && mode != PREAD_SCAN){
fscanf(playerFile, " ");
PlayerReadObject(player, playerFile, Thing(player), mode);
}
/* apply the affects of equipment, saved in virgin state */
AffectApplyAll(player);
/* Read all the Affects - these are applied as they load */
if (PlayerReadHeader(playerFile, "[Affects]") && mode != PREAD_SCAN){
while(fscanf(playerFile, " %s", buf)==1) {
if (buf[0]=='[') break;
i = TYPEFIND(buf, effectList);
if (i!=-1) {
affect = AffectCreate(player, AFFECT_NONE, 0, 0, i);
AffectRead(player, affect, playerFile);
} else
fgets(buf, sizeof(buf), playerFile);
}
}
/* Read all the Properties */
if (PlayerReadHeader(playerFile, "[Properties]")){
fscanf(playerFile, " ");
fgets(buf, sizeof(buf), playerFile);
while(!feof(playerFile)) {
if (buf[0]!='P') break;
sKey = FileStrRead(playerFile);
sDesc = FileStrRead(playerFile);
Thing(player)->tProperty = PropertyCreate(Thing(player)->tProperty, sKey, sDesc);
/* if this is code - compile it */
if ( sKey->sText[0]=='@' && !CodeIsCompiled(Thing(player)->tProperty) )
if (!CodeCompileProperty(Thing(player)->tProperty, NULL))
CodeSetFlag(Thing(player), Thing(player)->tProperty);
fgets(buf, sizeof(buf), playerFile);
}
}
/* pack it in */
fclose(playerFile);
} else
player = NULL;
return Thing(player);
}
void PlayerWriteObject(FILE *playerFile, THING *object, BYTE indent, BYTE mode) {
LWORD apply;
if (!object) return;
if (object->tType != TTYPE_OBJ || Obj(object)->oTemplate->oVirtual<0) {
/* cant save the system objects - save any contents though */
PlayerWriteObject(playerFile, object->tContain, indent, mode);
PlayerWriteObject(playerFile, object->tNext, indent, mode);
return;
}
{
LWORD i; /* this way this isnt tying up stack frame space during recursion */
for(i=0;i<indent;i++) fprintf(playerFile, " ");
fprintf(playerFile,
"#%-5ld %ld %ld %ld %ld %ld %ld ",
Obj(object)->oTemplate->oVirtual,
Obj(object)->oDetail.lValue[0],
Obj(object)->oDetail.lValue[1],
Obj(object)->oDetail.lValue[2],
Obj(object)->oDetail.lValue[3],
Obj(object)->oEquip,
Obj(object)->oAct);
FileByteWrite(playerFile, Obj(object)->oTemplate->oType, ' ');
fprintf(playerFile, "%s\n", object->tSDesc->sText);
if (mode == PWRITE_PLAYER)
Obj(object)->oTemplate->oOffline++;
for (apply=0; apply<OBJECT_MAX_APPLY; apply++) {
if (Obj(object)->oApply[apply].aType) {
for(i=0;i<indent;i++) fprintf(playerFile, " ");
fprintf(playerFile, "A ");
FILETYPEWRITE(playerFile, Obj(object)->oApply[apply].aType, applyList, ' ');
FileByteWrite(playerFile, Obj(object)->oApply[apply].aValue, '\n');
}
}
}
if (object->tContain) {
{
LWORD i; /* this way this isnt tying up stack frame space during recursion */
for(i=0;i<indent;i++) fprintf(playerFile, " ");
}
fprintf(playerFile, " {\n");
PlayerWriteObject(playerFile, object->tContain, indent+1, mode);
{
LWORD i; /* this way this isnt tying up stack frame space during recursion */
for(i=0;i<indent;i++) fprintf(playerFile, " ");
}
fprintf(playerFile, " }\n");
}
PlayerWriteObject(playerFile, object->tNext, indent, mode);
}
void PlayerUpdateTime(THING *player) {
ULWORD timeNow;
/* Update timeOnline etc */
timeNow = time(0);
Plr(player)->pTimeTotal += timeNow-Plr(player)->pTimeLastOn;
Plr(player)->pTimeLastOn = timeNow;
}
/* path should be either "crash" or "player", nothing else */
void PlayerWrite(THING *player, BYTE mode) {
FILE *playerFile;
BYTE playerFileBuf[128];
BYTE buf[256];
LWORD i;
PROPERTY *property;
AFFECT *affect;
if (!player) return;
AffectUnapplyAll(player);
switch(mode) {
case PWRITE_PLAYER:
sprintf(playerFileBuf, "crash/%s.plr", Thing(player)->tSDesc->sText);
StrToLower(playerFileBuf);
unlink(playerFileBuf);
sprintf(playerFileBuf, "player/%s.plr", Thing(player)->tSDesc->sText);
StrToLower(playerFileBuf);
break;
case PWRITE_CRASH:
sprintf(playerFileBuf, "player/%s.plr", Thing(player)->tSDesc->sText);
StrToLower(playerFileBuf);
unlink(playerFileBuf);
sprintf(playerFileBuf, "crash/%s.plr", Thing(player)->tSDesc->sText);
StrToLower(playerFileBuf);
break;
case PWRITE_CLONE:
sprintf(playerFileBuf, "clone/%s.plr", Thing(player)->tSDesc->sText);
StrToLower(playerFileBuf);
break;
}
playerFile = fopen(playerFileBuf, "wb");
if (!playerFile) {
sprintf(buf, "Unable to write %s\n", playerFileBuf);
Log(LOG_ERROR, buf);
PERROR("PlayerWrite");
return;
}
FileStrWrite(playerFile, Thing(player)->tSDesc);
FileStrWrite(playerFile, Plr(player)->pPassword);
FileStrWrite(playerFile, Base(player)->bLDesc);
FileStrWrite(playerFile, Thing(player)->tDesc);
fprintf(playerFile, "Messages: \n");
FileStrWrite(playerFile, Plr(player)->pEnter);
FileStrWrite(playerFile, Plr(player)->pExit);
FileStrWrite(playerFile, Plr(player)->pEmail);
FileStrWrite(playerFile, Plr(player)->pPlan);
fprintf(playerFile, "%-20s", "Prompt:");
FileStrWrite(playerFile, Plr(player)->pPrompt);
fprintf(playerFile, "%-20s", "LastLogin:");
FileStrWrite(playerFile, Plr(player)->pLastLogin);
fprintf(playerFile, "%-20s", "Level:");
FileByteWrite(playerFile, Character(player)->cLevel, '\n');
fprintf(playerFile, "%-20s", "Race:");
FILETYPEWRITE(playerFile, Plr(player)->pRace, raceList, '\n');
fprintf(playerFile, "%-20s", "Class:");
FILETYPEWRITE(playerFile, Plr(player)->pClass, classList, '\n');
fprintf(playerFile, "%-20s", "Position:");
FILETYPEWRITE(playerFile, Character(player)->cPos, posList, '\n');
fprintf(playerFile, "%-20s", "Sex:");
FILETYPEWRITE(playerFile, Character(player)->cSex, sexList, '\n');
fprintf(playerFile, "%-20s", "AutoAction:");
FileFlagWrite(playerFile, Plr(player)->pAuto, pAutoList, '\n');
fprintf(playerFile, "%-20s", "System:");
FileFlagWrite(playerFile, Plr(player)->pSystem, pSystemList, '\n');
fprintf(playerFile, "%-20s", "SockPref:");
{
SOCK *sock;
sock = BaseControlFind(player);
if (sock && sock->sMode==MODE_PLAY)
Plr(player)->pSockPref = sock->sPref;
}
FileFlagWrite(playerFile, Plr(player)->pSockPref, sfPrefList, '\n');
fprintf(playerFile, "%-20s", "ScreenLines:");
FileByteWrite(playerFile, Plr(player)->pScreenLines, '\n');
fprintf(playerFile, "%-20s", "ColorPref:");
for (i=0; i<COLORPREF_MAX; i++) {
if (!*colorPrefList[i].cName) break;
fprintf(playerFile, "%c%c ", Plr(player)->pColorPref[i].cFG, Plr(player)->pColorPref[i].cBG);
}
fprintf(playerFile, "\n");
/*
fprintf(playerFile, "%-20s%ld\n", "Equip:", Character(player)->cEquip);
*/
fprintf(playerFile, "%-20s%ld\n", "Practice:", Plr(player)->pPractice);
fprintf(playerFile, "%-20s%ld\n", "HitP:", Character(player)->cHitP);
fprintf(playerFile, "%-20s%ld\n", "MoveP:", Character(player)->cMoveP);
fprintf(playerFile, "%-20s%ld\n", "PowerP:", Character(player)->cPowerP);
fprintf(playerFile, "%-20s%hd\n", "GainPractice:",Plr(player)->pGainPractice);
fprintf(playerFile, "%-20s%hd\n", "GainHit:", Plr(player)->pGainHit);
fprintf(playerFile, "%-20s%hd\n", "GainMove:", Plr(player)->pGainMove);
fprintf(playerFile, "%-20s%hd\n", "GainPower:", Plr(player)->pGainPower);
fprintf(playerFile, "%-20s%hd\n", "Fame:", Plr(player)->pFame);
fprintf(playerFile, "%-20s%hd\n", "Infamy:", Plr(player)->pInfamy);
fprintf(playerFile, "%-20s%ld\n", "HitPMax:", Character(player)->cHitPMax);
fprintf(playerFile, "%-20s%ld\n", "MovePMax:", Plr(player)->pMovePMax);
fprintf(playerFile, "%-20s%ld\n", "PowerPMax:", Plr(player)->pPowerPMax);
fprintf(playerFile, "%-20s%hd\n", "Hunger:", Plr(player)->pHunger);
fprintf(playerFile, "%-20s%hd\n", "Thirst:", Plr(player)->pThirst);
fprintf(playerFile, "%-20s%hd\n", "Intox:", Plr(player)->pIntox);
fprintf(playerFile, "%-20s%hd\n", "Str:", Plr(player)->pStr);
fprintf(playerFile, "%-20s%hd\n", "Dex:", Plr(player)->pDex);
fprintf(playerFile, "%-20s%hd\n", "Con:", Plr(player)->pCon);
fprintf(playerFile, "%-20s%hd\n", "Wis:", Plr(player)->pWis);
fprintf(playerFile, "%-20s%hd\n", "Int:", Plr(player)->pInt);
/*
fprintf(playerFile, "%-20s%hd\n", "HitBonus:", Character(player)->cHitBonus);
fprintf(playerFile, "%-20s%hd\n", "DamBonus:", Character(player)->cDamBonus);
fprintf(playerFile, "%-20s%hd\n", "Armor:", Character(player)->cArmor);
for (i=0; i<CHAR_MAX_RESIST; i++) {
sprintf(buf, "Resist%ld:", i);
fprintf(playerFile, "%-20s%hd\n", buf, Character(player)->cResist[i]);
}
*/
if (mode != PWRITE_CLONE) {
fprintf(playerFile, "%-20s%ld\n", "Money:", Character(player)->cMoney);
fprintf(playerFile, "%-20s%ld\n", "Bank:", Plr(player)->pBank);
} else {
fprintf(playerFile, "%-20s%d\n", "Money:", 0);
fprintf(playerFile, "%-20s%d\n", "Bank:", 0);
}
fprintf(playerFile, "%-20s%ld\n", "Exp:", Character(player)->cExp);
fprintf(playerFile, "%-20s%lu\n", "TimeTotal:", Plr(player)->pTimeTotal);
fprintf(playerFile, "%-20s%lu\n", "TimeLastOn:", Plr(player)->pTimeLastOn);
fprintf(playerFile, "%-20s%hd\n", "Attempts:", Plr(player)->pAttempts);
fprintf(playerFile, "%-20s%ld\n", "StartRoom:", Plr(player)->pStartRoom);
fprintf(playerFile, "%-20s%ld\n", "RecallRoom:", Plr(player)->pRecallRoom);
fprintf(playerFile, "%-20s%ld\n", "DeathRoom:", Plr(player)->pDeathRoom);
/* Write out all the Skills */
fprintf(playerFile, "\n[Skills]\n");
for(i=0; i<skillNum; i++) {
if (Plr(player)->pSkill[i] != 0) {
fprintf(playerFile, "%-20s%hd\n", skillList[i].sName, Plr(player)->pSkill[i]);
}
}
fprintf(playerFile, "[End of Skills]\n");
if (mode != PWRITE_CLONE) {
/* Write out all the Objects */
fprintf(playerFile, "\n[Objects]\n");
PlayerWriteObject(playerFile, Thing(player)->tContain, 0, mode);
fprintf(playerFile, "[End of Objects]\n");
/* Write out affects */
fprintf(playerFile, "\n[Affects]\n");
for(affect = Character(player)->cAffect; affect; affect=affect->aNext)
AffectWrite(player, affect, playerFile);
fprintf(playerFile, "[End of Affects]\n");
}
/* Properties */
fprintf(playerFile, "\n[Properties]\n");
for (property=player->tProperty; property; property=property->pNext) {
fprintf(playerFile, "P\n");
FileStrWrite(playerFile, property->pKey);
if ( CodeIsCompiled(property) ){
CodeDecompProperty(property, NULL);
/* warn if it didnt decompile */
if (CodeIsCompiled(property)) {
sprintf(buf, "PlayerWrite: Property %s failed to decompile for player %s\n",
property->pKey->sText,
player->tSDesc->sText);
Log(LOG_ERROR, buf);
fprintf(playerFile, "/* <Decompile Error> */\n~\n");
} else {
FileStrWrite(playerFile, property->pDesc);
}
} else
FileStrWrite(playerFile, property->pDesc);
}
fprintf(playerFile, "[End of Properties]\n");
AffectApplyAll(player);
/* all done close the file */
fclose(playerFile);
}
/* the following routine is called by ThingFree, dont call it directly */
void PlayerFree(THING *thing) {
STRFREE(Plr(thing)->pPassword);
STRFREE(Plr(thing)->pEnter);
STRFREE(Plr(thing)->pExit);
STRFREE(Plr(thing)->pPrompt);
STRFREE(Plr(thing)->pLastLogin);
STRFREE(Plr(thing)->pEmail);
STRFREE(Plr(thing)->pPlan);
/* turf the index entry for this player */
IndexDelete(&playerIndex, thing, NULL);
/* give up the memory */
MEMFREE(Plr(thing), PLR);
}
/* Eat the first thing we find whatever it is,
poison not withstanding */
LWORD PlayerAutoEat(THING *thing, THING *search) {
if (!thing) return FALSE;
if (Character(thing)->cPos < POS_SITTING) return FALSE;
if (!search) return FALSE;
if (search->tType != TTYPE_OBJ) {
return PlayerAutoEat(thing, search->tNext);
}
if (Obj(search)->oTemplate->oType == OTYPE_CONTAINER) {
if (!PlayerAutoEat(thing, search->tContain))
return PlayerAutoEat(thing, search->tNext);
return TRUE;
}
if (Obj(search)->oTemplate->oType != OTYPE_FOOD
||!InvUnEquip(thing, search, IUE_BLOCKABLE) ) {
return PlayerAutoEat(thing, search->tNext);
}
InvEat(thing, search);
return TRUE;
}
/* drink from the first drinkcon we find, whatever its
contents */
LWORD PlayerAutoDrink(THING *thing, THING *search) {
if (!thing) return FALSE;
if (Character(thing)->cPos < POS_SITTING) return FALSE;
if (!search) return FALSE;
if (search->tType != TTYPE_OBJ) {
return PlayerAutoDrink(thing, search->tNext);
}
if (Obj(search)->oTemplate->oType == OTYPE_CONTAINER) {
if (!PlayerAutoDrink(thing, search->tContain))
return PlayerAutoDrink(thing, search->tNext);
return TRUE;
}
if (Obj(search)->oTemplate->oType != OTYPE_DRINKCON
||OBJECTGETFIELD(search, OF_DRINKCON_CONTAIN)==0 ) {
return PlayerAutoDrink(thing, search->tNext);
}
InvDrink(thing, search);
return TRUE;
}
void PlayerIdle() {
BYTE crashSave = 2; /* save this many players per beat */
LWORD i;
THING *thing;
SOCK *sock;
if (playerIndex.iNum==0) return;
for (i=0; i<playerIndex.iNum && crashSave>0; i++) {
thing = playerIndex.iThing[i];
/* Crash save 'em as necessary */
sock = BaseControlFind(thing);
if ( (!sock || sock->sMode == MODE_PLAY)
&& !BIT(Plr(thing)->pSystem, PS_CRASHSAVED)) {
crashSave--;
if (Base(playerIndex.iThing[i])->bInside
&& Base(playerIndex.iThing[i])->bInside->tType == TTYPE_WLD)
Plr(playerIndex.iThing[i])->pStartRoom = Wld(Base(playerIndex.iThing[i])->bInside)->wVirtual;
PlayerWrite(thing, PWRITE_CRASH);
BITSET(Plr(playerIndex.iThing[i])->pSystem, PS_CRASHSAVED);
printf("AS_: Autosaving %s\n", thing->tSDesc->sText);
}
}
/* if everyone has been crash saved, start over */
if (crashSave>0) {
for(i=0;i<playerIndex.iNum;i++)
BITCLR(Plr(playerIndex.iThing[i])->pSystem, PS_CRASHSAVED);
}
}
void PlayerTick() {
LWORD i=0;
THING *thing=NULL;
THING *obj=NULL;
LWORD gain;
SOCK *sock;
BYTE buf[256];
if (playerIndex.iNum==0) return;
while(1) {
/* I know this while thing is a bit of a strange way to do it
however at some point in the future we need to be able to
set thing to null and then kill/drop link to the thing in
question and still continue through the loop */
if (thing == playerIndex.iThing[i]) i++;
if (i>=playerIndex.iNum) break;
thing = playerIndex.iThing[i];
/* Update the the amount of time they've played */
PlayerUpdateTime(thing);
/* Check for Affect timeouts ie spells expiring */
if (CharTick(thing)) continue;
/* Check for equipped items for stuff */
for (obj = Base(thing)->bInside; obj; obj=obj->tNext) {
if (obj->tType == TTYPE_OBJ && Obj(obj)->oEquip) {
/* Check for Good items */
if (BIT(Obj(obj)->oAct, OA_GOOD)) {
if (Character(thing)->cAura < 400)
Character(thing)->cAura +=1;
}
/* Check for evil items */
if (BIT(Obj(obj)->oAct, OA_EVIL)) {
if (Character(thing)->cAura > -400)
Character(thing)->cAura -=1;
}
}
}
sock = BaseControlFind(thing);
if (sock) {
PlayerUpdateTime(thing);
}
if (Base(thing)->bInside) {
if (!BIT(Plr(thing)->pAuto, PA_AFK)) {
/* gain hit points */
gain = classList[Plr(thing)->pClass].cGainHitP;
gain = gain*raceList[Plr(thing)->pRace].rGainHitP/100;
gain += Character(thing)->cLevel/20;
gain += Plr(thing)->pCon/20;
gain = CharGainAdjust(thing, GAIN_HITP, gain);
Character(thing)->cHitP += gain;
MAXSET(Character(thing)->cHitP, CharGetHitPMax(thing));
if (sock && BIT(sock->sPref, SP_CHANTICK)) {
sprintf(buf, "^pYou gain: ^w%ld ^ghp", gain);
SendThing(buf, thing);
}
/* gain move points */
gain = classList[Plr(thing)->pClass].cGainMoveP;
gain = gain*raceList[Plr(thing)->pRace].rGainMoveP/100;
gain += Character(thing)->cLevel/20;
gain += Plr(thing)->pDex/20;
gain = CharGainAdjust(thing, GAIN_MOVEP, gain);
Character(thing)->cMoveP += gain;
MAXSET(Character(thing)->cMoveP, CharGetMovePMax(thing));
if (sock && BIT(sock->sPref, SP_CHANTICK)) {
sprintf(buf, ", ^w%ld ^gmv", gain);
SendThing(buf, thing);
}
/* gain power points */
gain = classList[Plr(thing)->pClass].cGainPowerP;
gain = gain*raceList[Plr(thing)->pRace].rGainPowerP/100;
gain += Character(thing)->cLevel/20;
gain += Plr(thing)->pInt/20;
gain = CharGainAdjust(thing, GAIN_POWERP, gain);
Character(thing)->cPowerP += gain;
MAXSET(Character(thing)->cPowerP, CharGetPowerPMax(thing));
if (sock && BIT(sock->sPref, SP_CHANTICK)) {
sprintf(buf, ", and ^w%ld ^gpp\n", gain);
SendThing(buf, thing);
}
} else {
if (sock && BIT(sock->sPref, SP_CHANTICK)) {
SendThing("^yWhile AFK you do not heal/rest or get hungry/thirsty!\n", thing);
}
}
if ( Character(thing)->cPowerP == CharGetPowerPMax(thing)
&& Character(thing)->cMoveP == CharGetMovePMax(thing)
&& Character(thing)->cHitP == CharGetHitPMax(thing)
&& Character(thing)->cPos >= POS_SLEEPING
)
if ( Character(thing)->cPos == POS_SLEEPING && !BIT(Plr(thing)->pAuto, PA_EXPERT))
SendHint("^;HINT: You're fully rested! You should ^<WAKE^; up now and carry on!\n", thing);
else if ( Character(thing)->cPos <= POS_SITTING && !BIT(Plr(thing)->pAuto, PA_EXPERT))
SendHint("^;HINT: You're fully rested! You should ^<STAND^; up now and carry on!\n", thing);
if (!ParseCommandCheck(PARSE_COMMAND_WCREATE, sock, "")
&& Character(thing)->cLevel >= LEVEL_HUNGER
&& !BIT(Plr(thing)->pAuto, PA_AFK)) {
/* Gain Hunger */
/* if (Plr(thing)->pHunger>=(-1*raceList[Plr(thing)->pRace].rMaxHunger)){ */
gain = raceList[Plr(thing)->pRace].rGainHunger;
gain = CharGainAdjust(thing, GAIN_HUNGER, gain);
MINSET(gain, 1);
Plr(thing)->pHunger += gain;
MAXSET(Plr(thing)->pHunger, raceList[Plr(thing)->pRace].rMaxHunger);
if (Plr(thing)->pHunger>0) {
SendThing("You are hungry!\n", thing);
if (BIT(Plr(thing)->pAuto, PA_AUTOEAT)) {
/* search your inventory for something to eat */
PlayerAutoEat(thing, thing->tContain);
}
}
/* Gain Thirst */
/* if (Plr(thing)->pThirst>=(-1*raceList[Plr(thing)->pRace].rMaxThirst)){ */
gain = raceList[Plr(thing)->pRace].rGainThirst;
gain = CharGainAdjust(thing, GAIN_THIRST, gain);
MINSET(gain, 1);
Plr(thing)->pThirst += gain;
MAXSET(Plr(thing)->pThirst, raceList[Plr(thing)->pRace].rMaxThirst);
if (Plr(thing)->pThirst>0) {
SendThing("You are thirsty!\n", thing);
if (BIT(Plr(thing)->pAuto, PA_AUTODRINK)) {
/* search your inventory for something to drink */
PlayerAutoDrink(thing, thing->tContain);
}
}
}
/* Lose Intox */
/* if (Plr(thing)->pIntox>=(-1*raceList[Plr(thing)->pRace].rMaxIntox)){ */
Plr(thing)->pIntox -= raceList[Plr(thing)->pRace].rGainIntox;
MAXSET(Plr(thing)->pIntox, raceList[Plr(thing)->pRace].rMaxIntox);
MINSET(Plr(thing)->pIntox, -raceList[Plr(thing)->pRace].rMaxIntox);
if (Plr(thing)->pIntox>0) {
SendThing("You are intoxicated!\n", thing);
}
if (Character(thing)->cLevel >= LEVEL_GOD ) {
/* gods dont get hungry etc */
Plr(thing)->pHunger = -1;
Plr(thing)->pThirst = -1;
Plr(thing)->pIntox = -1;
}
/* Check to see if they have been idle too long */
Plr(thing)->pIdleTick++;
if ((!Character(thing)->cFight)&&(Character(thing)->cLevel<LEVEL_GOD)){
if (Plr(thing)->pIdleTick > playerIdleTick && !Plr(thing)->pIdleRoom) {
/* Suck them into the void */
SendThing("^wYou have been idle and are pulled into nothingness\n", thing);
SendAction("$n disappears into the void\n",
thing, NULL, SEND_ROOM|SEND_VISIBLE|SEND_AUDIBLE|SEND_CAPFIRST);
Plr(thing)->pIdleRoom = Base(thing)->bInside;
ThingTo(thing, WorldOf(playerIdleRoom));
} else if ( (Plr(thing)->pIdleTick > playerDropTick
&&!BIT(Plr(thing)->pAuto, PA_AFK)
&&sock)
||(Plr(thing)->pIdleTick > playerAFKDropTick)) {
if (sock)
sock->sMode = MODE_KILLSOCKET;
/*
* rent out char here (if they have the money)
*/
/* Update where they start */
if (thing->tType == TTYPE_PLR && Base(thing)->bInside && Base(thing)->bInside->tType==TTYPE_WLD)
Plr(thing)->pStartRoom = Wld(Base(thing)->bInside)->wVirtual;
else
Plr(thing)->pStartRoom = -1;
PlayerWrite(thing, PWRITE_PLAYER);
}
}
}
}
}
/* Turns out that normalize is way too generous */
#define NORMALIZE 0
void PlayerRollAbilities(THING *thing, LWORD reroll) {
LWORD min;
LWORD max;
LWORD bonus;
LWORD str = -1;
LWORD dex = -1;
LWORD con = -1;
LWORD wis = -1;
LWORD intel = -1; /* int is of course reserved.... */
LWORD newStr;
LWORD newDex;
LWORD newCon;
LWORD newWis;
LWORD newIntel;
LWORD i;
#if (NORMALIZE)
float normal;
float actual;
#endif
/* Roll Abilities */
for (i=0; i<reroll; i++) {
min = raceList[Plr(thing)->pRace].rMinStat[SA_STR];
max = raceList[Plr(thing)->pRace].rMaxStat[SA_STR];
bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_STR];
bonus += classList[Plr(thing)->pClass].cBonusStat[SA_STR];
newStr = Number(min, max) + bonus;
min = raceList[Plr(thing)->pRace].rMinStat[SA_DEX];
max = raceList[Plr(thing)->pRace].rMaxStat[SA_DEX];
bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_DEX];
bonus += classList[Plr(thing)->pClass].cBonusStat[SA_DEX];
newDex = Number(min, max) + bonus;
min = raceList[Plr(thing)->pRace].rMinStat[SA_CON];
max = raceList[Plr(thing)->pRace].rMaxStat[SA_CON];
bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_CON];
bonus += classList[Plr(thing)->pClass].cBonusStat[SA_CON];
newCon = Number(min, max) + bonus;
min = raceList[Plr(thing)->pRace].rMinStat[SA_WIS];
max = raceList[Plr(thing)->pRace].rMaxStat[SA_WIS];
bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_WIS];
bonus += classList[Plr(thing)->pClass].cBonusStat[SA_WIS];
newWis = Number(min, max) + bonus;
min = raceList[Plr(thing)->pRace].rMinStat[SA_INT];
max = raceList[Plr(thing)->pRace].rMaxStat[SA_INT];
bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_INT];
bonus += classList[Plr(thing)->pClass].cBonusStat[SA_INT];
newIntel = Number(min, max) + bonus;
/* Keep best average */
if (newStr+newDex+newCon+newWis+newIntel > str+dex+con+wis+intel) {
str = newStr;
dex = newDex;
con = newCon;
wis = newWis;
intel = newIntel;
}
}
#if (NORMALIZE)
/* Normalize */
normal = raceList[Plr(thing)->pRace].rNormalize;
if (normal > 0) { /* -1 or 0 in race table disables normalization */
actual = (float)
(str
+ dex
+ con
+ wis
+ intel);
actual = actual / 5.0;
/* okay changed my mind - ensure actual average is at least the normal one */
if (actual < normal) {
str = (LWORD) ( (float)str * normal / actual );
dex = (LWORD) ( (float)dex * normal / actual );
con = (LWORD) ( (float)con * normal / actual );
wis = (LWORD) ( (float)wis * normal / actual );
intel = (LWORD) ( (float)intel * normal / actual );
}
}
#endif
/* watch the maximums */
MAXSET(str, raceList[Plr(thing)->pRace].rMaxStat[SA_STR]);
MAXSET(dex, raceList[Plr(thing)->pRace].rMaxStat[SA_DEX]);
MAXSET(con, raceList[Plr(thing)->pRace].rMaxStat[SA_CON]);
MAXSET(wis, raceList[Plr(thing)->pRace].rMaxStat[SA_WIS]);
MAXSET(intel, raceList[Plr(thing)->pRace].rMaxStat[SA_INT]);
/* watch the minimums */
MINSET(str, raceList[Plr(thing)->pRace].rMinStat[SA_STR]);
MINSET(dex, raceList[Plr(thing)->pRace].rMinStat[SA_DEX]);
MINSET(con, raceList[Plr(thing)->pRace].rMinStat[SA_CON]);
MINSET(wis, raceList[Plr(thing)->pRace].rMinStat[SA_WIS]);
MINSET(intel, raceList[Plr(thing)->pRace].rMinStat[SA_INT]);
Plr(thing)->pStr = str;
Plr(thing)->pDex = dex;
Plr(thing)->pCon = con;
Plr(thing)->pWis = wis;
Plr(thing)->pInt = intel;
/* Set starting hit points etc if they are 1st level */
if (Character(thing)->cLevel == 1) {
/* Set Practices */
Plr(thing)->pPractice = Plr(thing)->pWis/30+5;
if (Plr(thing)->pInt<60) Plr(thing)->pPractice+=1; /* Dumber people get more pracs */
if (Plr(thing)->pInt<30) Plr(thing)->pPractice+=2; /* Just starting out - they'll need 'em */
/* Set Hit Points */
max = classList[Plr(thing)->pClass].cStartHitP;
max = max*raceList[Plr(thing)->pRace].rMaxHitP/100;
Character(thing)->cHitPMax = max;
Character(thing)->cHitP = CharGetHitPMax(thing);
/* gain move points */
max = classList[Plr(thing)->pClass].cStartMoveP;
max = max*raceList[Plr(thing)->pRace].rMaxMoveP/100;
Plr(thing)->pMovePMax = max;
Character(thing)->cMoveP = CharGetMovePMax(thing);
/* gain power points */
max = classList[Plr(thing)->pClass].cStartPowerP;
max = max*raceList[Plr(thing)->pRace].rMaxPowerP/100;
Plr(thing)->pPowerPMax = max;
Character(thing)->cPowerP = CharGetPowerPMax(thing);
}
}
/* How much do we need for next level */
LWORD PlayerExpNeeded(THING *thing) {
LWORD needed;
needed = (1+Character(thing)->cLevel);
needed *= 50;
needed *= Character(thing)->cLevel;
needed *= Character(thing)->cLevel;
/* create plateau right before Hero level */
if (Character(thing)->cLevel >= LEVEL_LEGEND-1) {
needed += 20000000;
needed += (Character(thing)->cLevel-( LEVEL_LEGEND-2 ))*2000000;
}
needed = needed * classList[Plr(thing)->pClass].cGainLevel / 100;
needed = needed * raceList[Plr(thing)->pRace].rGainLevel / 100;
return MAXV(needed,0);
}
LWORD PlayerExpUntilLevel(THING *thing) {
LWORD expUntilLevel;
if (thing->tType!=TTYPE_PLR) return -1;
expUntilLevel = PlayerExpNeeded(thing)-Character(thing)->cExp;
MINSET(expUntilLevel, 0);
return expUntilLevel;
}
void PlayerGainLevel(THING *thing, LWORD message) {
BYTE buf[256];
LWORD gain;
SOCK *sock;
BYTE expert = FALSE;
sock = BaseControlFind(thing);
if (sock && BIT(Plr(sock->sHomeThing)->pAuto, PA_EXPERT)) expert = TRUE;
Character(thing)->cLevel += 1;
MAXSET(Character(thing)->cExp, PlayerExpNeeded(thing)-1);
if (message) SendThing("^wGRATZ! You just gained a level!\n", thing);
/* gain practices */
if (!Plr(thing)->pGainPractice) {
gain = Plr(thing)->pWis/20;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain += Number(1, 10);
else
gain += Number(1, 8);
} else {
gain = Plr(thing)->pGainPractice;
}
Plr(thing)->pGainPractice = 0;
Plr(thing)->pPractice += gain;
sprintf(buf, "^wYou gain %ld practices (You now have %ld available)\n", gain, Plr(thing)->pPractice);
SendThing(buf, thing);
/* gain hit points */
if (!Plr(thing)->pGainHit) {
gain = classList[Plr(thing)->pClass].cMaxHitP;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain = Number(1, gain);
} else {
gain = Plr(thing)->pGainHit;
}
Plr(thing)->pGainHit = 0;
gain = gain*raceList[Plr(thing)->pRace].rMaxHitP/100;
Character(thing)->cHitPMax += gain;
if (message) {
sprintf(buf, "^wYou gain %ld hit points (Your maximum is now %ld)\n", gain, CharGetHitPMax(thing));
SendThing(buf, thing);
}
/* gain move points */
if (!Plr(thing)->pGainMove) {
gain = classList[Plr(thing)->pClass].cMaxMoveP;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain = Number(1, gain);
} else {
gain = Plr(thing)->pGainMove;
}
Plr(thing)->pGainMove = 0;
gain = gain*raceList[Plr(thing)->pRace].rMaxMoveP/100;
Plr(thing)->pMovePMax += gain;
if (message) {
if (expert || Character(thing)->cLevel >= 5) {
sprintf(buf, "^wYou gain %ld move points (Your maximum is now %ld)\n", gain, CharGetMovePMax(thing));
SendThing(buf, thing);
}
}
/* gain power points */
if (!Plr(thing)->pGainPower) {
gain = classList[Plr(thing)->pClass].cMaxPowerP;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain = Number(1, gain);
} else {
gain = Plr(thing)->pGainPower;
}
Plr(thing)->pGainPower = 0;
gain = gain*raceList[Plr(thing)->pRace].rMaxPowerP/100;
Plr(thing)->pPowerPMax += gain;
if (message) {
if (expert || Character(thing)->cLevel >= 10) {
sprintf(buf, "^wYou gain %ld power points (Your maximum is now %ld)\n", gain, CharGetPowerPMax(thing));
SendThing(buf, thing);
}
}
if (Character(thing)->cLevel == LEVEL_MOVETIRING) {
if (message) SendThing("NEW: Moving will now tire you, and you will have to rest periodically\n", thing);
} else if (Character(thing)->cLevel == LEVEL_HUNGER) {
if (message) SendThing("NEW: You will now need food and drink periodically\n", thing);
Plr(thing)->pThirst = -1*raceList[Plr(thing)->pRace].rMaxThirst;
Plr(thing)->pHunger = -1*raceList[Plr(thing)->pRace].rMaxHunger;
} else if (Character(thing)->cLevel == LEVEL_FIGHTTIRING) {
if (message) SendThing("NEW: Fighting will now tire you, and you will have to rest periodically\n", thing);
}
if (!Character(thing)->cLevel < 5) {
if (message) SendHint("^;HINT: You have practices! Locate Duah and improve your skills\n", thing);
}
if (message) SendHint("^;HINT: Save your progress! Visit Club Med and have a brain dump done!\n", thing);
}
void PlayerGainExp(THING *thing, LWORD exp) {
BYTE buf[256];
THING *world;
LWORD expUntilLevel;
LWORD gain;
if (thing->tType != TTYPE_PLR) return;
/* guard against no gain zones etc here */
world = Base(thing)->bInside;
if (world->tType==TTYPE_WLD
&& BIT(areaList[Wld(world)->wArea].aResetFlag, RF_NOGAIN)) {
sprintf(buf, "^yYou ^rWOULD ^yhave gained %ld experience points! (This zone isnt open yet)\n", exp);
SendThing(buf, thing);
return;
}
/* check if they're a god */
if (Character(thing)->cLevel>=LEVEL_GOD-1) {
sprintf(buf, "^yYou ^rWOULD ^yhave gained %ld experience points! (except that you're a god)\n", exp);
SendThing(buf, thing);
return;
}
/* Check if they're a area editor */
if (ParseCommandCheck(PARSE_COMMAND_WGOTO, BaseControlFind(thing), "")) {
sprintf(buf, "^yYou ^rWOULD ^yhave gained %ld experience points! (except that you're the area creator)\n", exp);
SendThing(buf, thing);
return;
}
expUntilLevel = PlayerExpNeeded(thing)-Character(thing)->cExp-exp;
if (expUntilLevel>0) {
sprintf(buf, "^yYou just gained %ld experience points! (%ld until next level)\n", exp, expUntilLevel);
SendThing(buf, thing);
} else {
sprintf(buf, "^yYou just gained %ld experience points! (^wLEVEL!!!^y)\n", exp);
SendThing(buf, thing);
}
/* Predetermine Practice roll to prevent kill/relevel "bug" */
if (!Plr(thing)->pGainPractice) {
gain = Plr(thing)->pWis/20;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain += Number(1, 10);
else
gain += Number(1, 8);
Plr(thing)->pGainPractice = gain;
}
/* Predetermine hit roll to prevent kill/relevel "bug" */
if (!Plr(thing)->pGainHit && !Number(0,5)) {
gain = classList[Plr(thing)->pClass].cMaxHitP;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain = Number(1, gain);
Plr(thing)->pGainHit= gain;
}
/* Predetermine move roll to prevent kill/relevel "bug" */
if (!Plr(thing)->pGainMove && !Number(0,5)) {
gain = classList[Plr(thing)->pClass].cMaxMoveP;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain = Number(1, gain);
Plr(thing)->pGainMove= gain;
}
/* Predetermine power roll to prevent kill/relevel "bug" */
if (!Plr(thing)->pGainPower && !Number(0,5)) {
gain = classList[Plr(thing)->pClass].cMaxPowerP;
if (Character(thing)->cLevel > LEVEL_GREEN)
gain = Number(1, gain);
Plr(thing)->pGainPower = gain;
}
Character(thing)->cExp += exp;
if (Character(thing)->cExp > PlayerExpNeeded(thing)) {
PlayerGainLevel(thing, TRUE);
}
}
void PlayerGainFame(THING *player, LWORD fame) {
BYTE buf[256];
if (!fame) return;
if (!player || player->tType!=TTYPE_PLR) return;
if (fame>0)
sprintf(buf, "^yYou gain %ld points of fame!\n", fame);
else
sprintf(buf, "^rYou lose %ld points of fame!\n", fame);
Plr(player)->pFame+=fame;
SendThing(buf, player);
}
void PlayerGainInfamy(THING *player, LWORD infamy, BYTE *message) {
BYTE buf[256];
LWORD infamous;
if (!infamy) return;
if (!player || player->tType!=TTYPE_PLR) return;
infamous = PlayerIsInfamous(player);
if (!message) {
if (infamy>0)
sprintf(buf, "^rYou gain %ld points of infamy!\n", infamy);
else
sprintf(buf, "^yYou lose %ld points of infamy!\n", infamy);
SendThing(buf, player);
} else {
SendThing(message, player);
}
Plr(player)->pInfamy+=infamy;
if (!infamous && PlayerIsInfamous(player))
SendThing("^wUh Oh, start running now - you just became infamous\n", player);
}
LWORD PlayerIsInfamous(THING *player) {
if (player->tType!=TTYPE_PLR) return FALSE;
if (Plr(player)->pInfamy > Character(player)->cLevel + Plr(player)->pFame)
return Plr(player)->pInfamy - ( Character(player)->cLevel + Plr(player)->pFame );
return FALSE;
}
void PlayerDelete(THING *player) {
SOCK *sock;
BYTE buf[256];
BYTE playerName[256];
/* get rid of the playerFile - but keep a backup in case we change our mind */
PlayerWrite(player, PWRITE_CRASH);
strcpy(playerName, player->tSDesc->sText);
StrToLower(playerName);
#ifdef WIN32
sprintf(buf, "move crash\\%s.plr player\\%s.plr.del", playerName, playerName);
#else
sprintf(buf, "mv crash/%s.plr player/%s.plr.del", playerName, playerName);
#endif
system(buf);
sprintf(buf, "clone/%s.plr", playerName);
unlink(buf);
sprintf(buf, "player/%s.plr", playerName);
unlink(buf);
sprintf(buf, "alias/%s.als", playerName);
unlink(buf);
sock = BaseControlFind(player);
if (sock) {
BaseControlFree(sock->sControlThing, sock);
sock->sMode = MODE_KILLSOCKET; /* they're not long for this world.... */
}
/* turf the playerThing */
THINGFREE(player);
}
BYTE *PlayerGetLevelDesc(THING *thing) {
if (!thing) return "NONE";
if (Character(thing)->cLevel < LEVEL_GREEN)
return "NEWBIE";
if (Character(thing)->cLevel < LEVEL_NOVICE)
return "GREEN";
if (Character(thing)->cLevel < LEVEL_EXPERIENCED)
return "NOVICE";
if (Character(thing)->cLevel < LEVEL_ELITE)
return "EXPERIENCED";
if (Character(thing)->cLevel < LEVEL_ULTRAELITE)
return "ELITE";
if (Character(thing)->cLevel < LEVEL_LEGEND)
return "ULTRAELITE";
if (Character(thing)->cLevel < LEVEL_GOD)
return "LEGEND";
if (Character(thing)->cLevel < LEVEL_ADMIN)
return "GOD";
if (Character(thing)->cLevel < LEVEL_CODER)
return "ADMIN";
else
return "CODER";
}