server-1.12/common/exp.c

272 lines
8.6 KiB
C

/*
* static char *rcsid_arch_c =
* "$Id: exp.c 11578 2009-02-23 22:02:27Z lalo $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 2006 Mark Wedel & Crossfire Development Team
Copyright (C) 1992 Frank Tore Johansen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The authors can be reached via e-mail at crossfire-devel@real-time.com
*/
/**
* @file exp.c
* Experience management. reading data from files and such.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <global.h>
sint64 *levels; /**< Number of levels for which we have experience. */
#define TRUE 1
#define FALSE 0
static const float exp_att_mult[NROFATTACKS+2] = {
0.0, /* AT_PHYSICAL */
0.0, /* AT_MAGIC */
0.0, /* AT_FIRE */
0.0, /* AT_ELECTRICITY */
0.0, /* AT_COLD */
0.0, /* AT_WATER *//*AT_CONFUSION!*/
0.4, /* AT_ACID */
1.5, /* AT_DRAIN */
0.0, /* AT_WEAPONMAGIC */
0.1, /* AT_GHOSTHIT */
0.3, /* AT_POISON */
0.2, /* AT_DISEASE */
0.3, /* AT_PARALYZE */
0.0, /* AT_TURN_UNDEAD */
0.0, /* AT_FEAR */
0.0, /* AT_CANCELLATION */
0.0, /* AT_DEPLETE */
0.0, /* AT_DEATH */
0.0, /* AT_CHAOS */
0.0 /* AT_COUNTERSPELL */
};
static const float exp_prot_mult[NROFATTACKS+2] = {
0.4, /* AT_PHYSICAL */
0.5, /* AT_MAGIC */
0.1, /* AT_FIRE */
0.1, /* AT_ELECTRICITY */
0.1, /* AT_COLD */
0.1, /* AT_WATER */
0.1, /* AT_ACID */
0.1, /* AT_DRAIN */
0.1, /* AT_WEAPONMAGIC */
0.1, /* AT_GHOSTHIT */
0.1, /* AT_POISON */
0.1, /* AT_DISEASE */
0.1, /* AT_PARALYZE */
0.1, /* AT_TURN_UNDEAD */
0.1, /* AT_FEAR */
0.0, /* AT_CANCELLATION */
0.0, /* AT_DEPLETE */
0.0, /* AT_DEATH */
0.0, /* AT_CHAOS */
0.0 /* AT_COUNTERSPELL */
};
/**
* Alternative way to calculate experience based
* on the ability of a monster.
*
* It's far from perfect, and doesn't consider everything which
* can be considered, thus it's only used in debugging.
* this is only used with one of the dumpflags,
* and not anyplace in the code.
*
* @param ob
* object for which to return experience
* @return
* experience computed from object's properties.
*/
sint64 new_exp(const object *ob) {
double att_mult, prot_mult, spec_mult;
double exp;
int i;
long mask = 1;
att_mult = prot_mult = spec_mult = 1.0;
for (i = 0; i < NROFATTACKS; i++) {
mask = 1<<i;
att_mult += (exp_att_mult[i]*((ob->attacktype&mask) != FALSE));
/* We multiply & then divide to prevent roundoffs on the floats.
* the doubling is to take into account the table and resistances
* are lower than they once were.
*/
prot_mult += (exp_prot_mult[i] * 200 * ob->resist[i]) / 100.0;
}
if (prot_mult < 0)
prot_mult = 1;
spec_mult += (0.3*(QUERY_FLAG(ob, FLAG_SEE_INVISIBLE) != FALSE))+
(0.5*(QUERY_FLAG(ob, FLAG_SPLITTING) != FALSE))+
(0.3*(QUERY_FLAG(ob, FLAG_HITBACK) != FALSE))+
(0.1*(QUERY_FLAG(ob, FLAG_REFL_MISSILE) != FALSE))+
(0.3*(QUERY_FLAG(ob, FLAG_REFL_SPELL) != FALSE))+
(1.0*(QUERY_FLAG(ob, FLAG_NO_MAGIC) != FALSE))+
(0.1*(QUERY_FLAG(ob, FLAG_USE_SCROLL) != FALSE))+
(0.2*(QUERY_FLAG(ob, FLAG_USE_RANGE) != FALSE))+
(0.1*(QUERY_FLAG(ob, FLAG_USE_BOW) != FALSE));
exp = MAX(ob->stats.maxhp, 5);
exp *= (QUERY_FLAG(ob, FLAG_CAST_SPELL) && has_ability(ob)) ? (40+MIN(ob->stats.maxsp, 80))/40 : 1;
exp *= (80.0/(70.0+ob->stats.wc))*(80.0/(70.0+ob->stats.ac))*(50.0+ob->stats.dam)/50.0;
exp *= att_mult*prot_mult*spec_mult;
exp *= 2.0/(2.0-(MIN(FABS(ob->speed), 0.95)));
exp *= (20.0+ob->stats.Con)/20.0;
if (QUERY_FLAG(ob, FLAG_STAND_STILL))
exp /= 2;
return (sint64)exp;
}
/**
* Checks whether object has innate abilities (spell/spellbook in inventory).
* @return
* 1 if monster has any innate abilities, 0 else
*/
int has_ability(const object *ob) {
object *tmp;
for (tmp = ob->inv; tmp != NULL; tmp = tmp->below)
if (tmp->type == SPELL || tmp->type == SPELLBOOK)
return TRUE;
return FALSE;
}
/**
* This loads the experience table from the exp_table
* file. This tends to exit on any errors, since it
* populates the table as it goes along, so if there
* are errors, the table is likely in an inconsistent
* state.
*
* @note
* will call exit() if file is invalid or not found.
*/
void init_experience(void) {
char buf[MAX_BUF], *cp;
int lastlevel = 0, comp;
sint64 lastexp = -1, tmpexp;
FILE *fp;
snprintf(buf, sizeof(buf), "%s/exp_table", settings.confdir);
if ((fp = open_and_uncompress(buf, 0, &comp)) == NULL) {
LOG(llevError, "Fatal error: could not open experience table (%s)\n", buf);
exit(1);
}
while (fgets(buf, MAX_BUF-1, fp) != NULL) {
if (buf[0] == '#')
continue;
/* eliminate newline */
if ((cp = strrchr(buf, '\n')) != NULL)
*cp = '\0';
/* Skip over empty lines */
if (buf[0] == 0)
continue;
cp = buf;
while (isspace(*cp) && *cp != 0)
cp++;
if (!strncasecmp(cp, "max_level", 9)) {
if (settings.max_level) {
LOG(llevDebug, "Got more than one max_level value from exp_table file?\n");
free(levels);
}
settings.max_level = atoi(cp+9);
if (!settings.max_level) {
LOG(llevDebug, "Got invalid max_level from exp_table file? %s\n", buf);
} else {
levels = calloc(settings.max_level+1, sizeof(sint64));
}
}
while (isdigit(*cp) && *cp != 0) {
if (!settings.max_level) {
LOG(llevError, "max_level is not set in exp_table file. Did you remember to update it?\n");
exit(1);
}
tmpexp = atoll(cp);
/* Do some sanity checking - if value is bogus, just exit because
* the table otherwise is probably in an inconsistent state
*/
if (tmpexp <= lastexp) {
#ifndef WIN32
LOG(llevError, "Experience for level %d is lower than previous level (%lld <= %lld)\n", lastlevel+1, tmpexp, lastexp);
#else
LOG(llevError, "Experience for level %d is lower than previous level (%I64d <= %I64d)\n", lastlevel+1, tmpexp, lastexp);
#endif
exit(1);
}
lastlevel++;
if (lastlevel > settings.max_level) {
LOG(llevError, "Too many levels specified in table (%d > %d)\n", lastlevel, settings.max_level);
exit(1);
}
levels[lastlevel] = tmpexp;
lastexp = tmpexp;
/* First, skip over the number we just processed. Then skip over
* any spaces, commas, etc.
*/
while (isdigit(*cp) && *cp != 0)
cp++;
while (!isdigit(*cp) && *cp != 0)
cp++;
}
}
close_and_delete(fp, comp);
if (settings.max_level == 0 || lastlevel != settings.max_level) {
LOG(llevError, "Fatal: exp_table does not have any level definition or not %d as defined, found %d.\n", settings.max_level, lastlevel);
exit(1);
}
if (lastlevel != settings.max_level && lastlevel != 0) {
LOG(llevError, "Warning: exp_table does not have %d entries (%d)\n", settings.max_level, lastlevel);
exit(1);
}
}
/**
* Dump the experience table, then calls exit() - useful in terms of debugging to make sure the
* format of the exp_table is correct.
*/
void dump_experience(void) {
int i;
for (i = 1; i <= settings.max_level; i++) {
fprintf(logfile, "%4d %20"FMT64"\n", i, levels[i]);
}
exit(0);
}
/**
* Frees experience-related memory.
*/
void free_experience(void) {
FREE_AND_CLEAR(levels);
}