Add age and time played functionality

This commit is contained in:
kinther 2025-12-29 18:08:53 -08:00
parent be79603e8f
commit 3344074ea2
14 changed files with 222 additions and 62 deletions

View file

@ -1254,7 +1254,10 @@ ACMD(do_coins)
ACMD(do_score)
{
struct time_info_data playing_time;
time_t played_seconds;
int played_minutes;
int played_hours;
int played_days;
struct ac_breakdown acb;
bool ismob = IS_NPC(ch);
@ -1299,12 +1302,9 @@ ACMD(do_score)
send_to_char(ch, "Stealth Disadvantage: %s\r\n",
has_stealth_disadv(ch) ? "Yes" : "No");
send_to_char(ch, "You are %d years old.", GET_AGE(ch));
send_to_char(ch, "You are %d years old.", GET_ROLEPLAY_AGE(ch));
if (age(ch)->month == 0 && age(ch)->day == 0)
send_to_char(ch, " It's your birthday today.\r\n");
else
send_to_char(ch, "\r\n");
send_to_char(ch, "\r\n");
/* Only players have quest data */
if (!ismob) {
@ -1324,11 +1324,15 @@ ACMD(do_score)
/* Only players have valid playtime data */
if (!ismob) {
playing_time = *real_time_passed((time(0) - ch->player.time.logon) +
ch->player.time.played, 0);
send_to_char(ch, "You have been playing for %d day%s and %d hour%s.\r\n",
playing_time.day, playing_time.day == 1 ? "" : "s",
playing_time.hours, playing_time.hours == 1 ? "" : "s");
played_seconds = get_total_played_seconds(ch);
played_minutes = (played_seconds / SECS_PER_REAL_MIN) % 60;
played_hours = (played_seconds / SECS_PER_REAL_HOUR) % 24;
played_days = played_seconds / SECS_PER_REAL_DAY;
send_to_char(ch,
"You have been playing for %d minute%s, %d hour%s, and %d day%s.\r\n",
played_minutes, played_minutes == 1 ? "" : "s",
played_hours, played_hours == 1 ? "" : "s",
played_days, played_days == 1 ? "" : "s");
}
/* Position */

View file

@ -1793,8 +1793,7 @@ static void do_stat_character(struct char_data *ch, struct char_data *k)
zone_table[world[IN_ROOM(k)].zone].number);
if (!IS_NPC(k)) {
char created[64], logon[64], olc[64] = "";
strftime(created, sizeof(created), "%b %d %Y", localtime(&(k->player.time.birth)));
char logon[64], olc[64] = "";
strftime(logon, sizeof(logon), "%b %d %Y", localtime(&(k->player.time.logon)));
if (GET_LEVEL(k) >= LVL_BUILDER) {
@ -1810,12 +1809,16 @@ static void do_stat_character(struct char_data *ch, struct char_data *k)
snprintf(olc, sizeof(olc), ", OLC %d", GET_OLC_ZONE(k));
}
stat_table_row_fmt(ch, "Account", "Created %s, Last %s%s",
created, logon, olc);
stat_table_row_fmt(ch, "Age/Play", "Age %d, Played %dh %dm",
age(k)->year,
k->player.time.played / 3600,
(k->player.time.played % 3600) / 60);
stat_table_row_fmt(ch, "Account", "Last %s%s",
logon, olc);
{
time_t played_seconds = get_total_played_seconds(k);
int played_minutes = (played_seconds / SECS_PER_REAL_MIN) % 60;
int played_hours = (played_seconds / SECS_PER_REAL_HOUR) % 24;
int played_days = played_seconds / SECS_PER_REAL_DAY;
stat_table_row_fmt(ch, "Age/Playtime", "Age %d, %dd %dh %dm",
GET_ROLEPLAY_AGE(k), played_days, played_hours, played_minutes);
}
}
stat_table_row_fmt(ch, "Class", "%s", CLASS_NAME(k));
@ -3864,10 +3867,8 @@ static int perform_set(struct char_data *ch, struct char_data *vict, int mode, c
send_to_char(ch, "Ages 2 to 200 accepted.\r\n");
return (0);
}
/* NOTE: May not display the exact age specified due to the integer
* division used elsewhere in the code. Seems to only happen for
* some values below the starting age (17) anyway. -gg 5/27/98 */
vict->player.time.birth = time(0) - ((value - 17) * SECS_PER_MUD_YEAR);
GET_ROLEPLAY_AGE(vict) = LIMIT(value, MIN_CHAR_AGE, MAX_CHAR_AGE);
GET_ROLEPLAY_AGE_YEAR(vict) = time_info.year;
break;
case 3: /* align */
GET_ALIGNMENT(vict) = RANGE(-1000, 1000);

View file

@ -326,7 +326,9 @@ const char *connected_types[] = {
"Get new PW",
"Confirm new PW",
"Select sex",
"Select species",
"Select class",
"Short description",
"Reading MOTD",
"Main Menu",
"Get descript.",
@ -350,7 +352,18 @@ const char *connected_types[] = {
"Preference edit",
"IBT edit",
"Message edit",
"Protocol Detection",
"Background/Protocol",
"Connect menu",
"Get account",
"Confirm account",
"Account password",
"New account PW",
"Confirm account PW",
"Account email",
"Account menu",
"Account list",
"Stat preference",
"Select age",
"\n"
};

View file

@ -1713,6 +1713,10 @@ static void interpret_espec(const char *keyword, const char *value, int i, int n
RANGE(SPECIES_UNDEFINED, NUM_SPECIES - 1);
mob_proto[i].player.species = num_arg;
}
CASE("Age") {
RANGE(MIN_CHAR_AGE, MAX_CHAR_AGE);
mob_proto[i].player.roleplay_age = num_arg;
}
/* --- 5e-style Saving Throws --- */
CASE("SaveStr") {
@ -1963,6 +1967,13 @@ void parse_mobile(FILE *mob_f, int nr)
exit(1);
}
if (mob_proto[i].player.time.birth == 0)
mob_proto[i].player.time.birth = time(0);
if (mob_proto[i].player.roleplay_age == 0)
mob_proto[i].player.roleplay_age = MIN_CHAR_AGE;
if (mob_proto[i].player.roleplay_age_year == 0)
mob_proto[i].player.roleplay_age_year = time_info.year;
letter = fread_letter(mob_f);
while (letter == 'L') {
int wpos = -1, vnum = -1, qty = 1;
@ -2757,7 +2768,8 @@ struct char_data *read_mobile(mob_vnum nr, int type) /* and mob_rnum */
mob->points.mana = mob->points.max_mana;
mob->points.stamina = mob->points.max_stamina;
mob->player.time.birth = time(0);
if (mob->player.time.birth == 0)
mob->player.time.birth = time(0);
mob->player.time.played = 0;
mob->player.time.logon = time(0);
@ -3843,7 +3855,12 @@ void init_char(struct char_data *ch)
ch->player_specials->saved.completed_quests = NULL;
GET_QUEST(ch) = NOTHING;
ch->player.time.birth = time(0);
if (ch->player.time.birth == 0)
ch->player.time.birth = time(0);
if (GET_ROLEPLAY_AGE(ch) == 0)
GET_ROLEPLAY_AGE(ch) = MIN_CHAR_AGE;
if (GET_ROLEPLAY_AGE_YEAR(ch) == 0)
GET_ROLEPLAY_AGE_YEAR(ch) = time_info.year;
ch->player.time.logon = time(0);
ch->player.time.played = 0;

View file

@ -363,6 +363,14 @@ int write_mobile_espec(mob_vnum mvnum, struct char_data *mob, FILE *fd)
fprintf(fd, "Species: %d\n", (int)GET_SPECIES(mob));
count++;
}
{
int age_years = GET_ROLEPLAY_AGE(mob);
if (age_years >= MIN_CHAR_AGE && age_years <= MAX_CHAR_AGE &&
age_years != MIN_CHAR_AGE) {
fprintf(fd, "Age: %d\n", age_years);
count++;
}
}
/* --- 5e-style saving throws --- */
if (GET_SAVE(mob, ABIL_STR) != 0) {

View file

@ -1323,6 +1323,11 @@ static void show_species_menu(struct descriptor_data *d)
write_to_output(d, "Species: ");
}
static void show_age_prompt(struct descriptor_data *d)
{
write_to_output(d, "Age (%d-%d): ", MIN_CHAR_AGE, MAX_CHAR_AGE);
}
static bool is_creation_state(int state)
{
switch (state) {
@ -1334,6 +1339,7 @@ static bool is_creation_state(int state)
case CON_QSEX:
case CON_QSPECIES:
case CON_QCLASS:
case CON_QAGE:
case CON_QSTAT_PREF:
case CON_QSHORTDESC:
case CON_PLR_DESC:
@ -2019,9 +2025,33 @@ case CON_QCLASS:
GET_CLASS(d->character) = load_result;
}
show_age_prompt(d);
STATE(d) = CON_QAGE;
return;
case CON_QAGE: {
if (!is_number(arg)) {
write_to_output(d, "\r\nPlease enter a number between %d and %d.\r\n",
MIN_CHAR_AGE, MAX_CHAR_AGE);
show_age_prompt(d);
return;
}
int age_years = atoi(arg);
if (age_years < MIN_CHAR_AGE || age_years > MAX_CHAR_AGE) {
write_to_output(d, "\r\nAge must be between %d and %d.\r\n",
MIN_CHAR_AGE, MAX_CHAR_AGE);
show_age_prompt(d);
return;
}
GET_ROLEPLAY_AGE(d->character) = age_years;
GET_ROLEPLAY_AGE_YEAR(d->character) = time_info.year;
show_stat_pref_prompt(d);
STATE(d) = CON_QSTAT_PREF;
return;
}
case CON_QSTAT_PREF: {
ubyte order[NUM_ABILITIES];

View file

@ -241,6 +241,9 @@ static void init_mobile(struct char_data *mob)
GET_MAX_MANA(mob) = GET_MAX_STAMINA(mob) = 100;
GET_WEIGHT(mob) = 200;
GET_HEIGHT(mob) = 198;
mob->player.time.birth = time(0);
mob->player.roleplay_age = MIN_CHAR_AGE;
mob->player.roleplay_age_year = time_info.year;
/* Only assign defaults if the individual stat is unset (zero) */
if (!mob->real_abils.str) mob->real_abils.str = 11;
@ -436,6 +439,7 @@ static void medit_disp_menu(struct descriptor_data *d)
"%s1%s) Name: %s%s\r\n"
"%s2%s) Keywords: %s%s\r\n"
"%s3%s) Sex: %s%-7.7s%s\r\n"
"%sG%s) Age: %s%d%s\r\n"
"%s4%s) S-Desc: %s%s\r\n"
"%s5%s) L-Desc:-\r\n%s%s\r\n"
"%s6%s) D-Desc:-\r\n%s%s\r\n",
@ -444,6 +448,7 @@ static void medit_disp_menu(struct descriptor_data *d)
grn, nrm, yel, GET_NAME(mob),
grn, nrm, yel, GET_KEYWORDS(mob),
grn, nrm, yel, genders[(int)GET_SEX(mob)], nrm,
grn, nrm, yel, GET_ROLEPLAY_AGE(mob), nrm,
grn, nrm, yel, GET_SDESC(mob),
grn, nrm, yel, GET_LDESC(mob),
grn, nrm, yel, GET_DDESC(mob)
@ -764,6 +769,11 @@ void medit_parse(struct descriptor_data *d, char *arg)
OLC_MODE(d) = MEDIT_SEX;
medit_disp_sex(d);
return;
case 'g':
case 'G':
OLC_MODE(d) = MEDIT_AGE;
write_to_output(d, "Enter age (%d-%d): ", MIN_CHAR_AGE, MAX_CHAR_AGE);
return;
case '4':
OLC_MODE(d) = MEDIT_S_DESC;
i--;
@ -1308,6 +1318,19 @@ void medit_parse(struct descriptor_data *d, char *arg)
GET_SEX(OLC_MOB(d)) = LIMIT(i - 1, 0, NUM_GENDERS - 1);
break;
case MEDIT_AGE:
if (i < MIN_CHAR_AGE || i > MAX_CHAR_AGE) {
write_to_output(d, "Age must be between %d and %d.\r\n",
MIN_CHAR_AGE, MAX_CHAR_AGE);
write_to_output(d, "Enter age (%d-%d): ", MIN_CHAR_AGE, MAX_CHAR_AGE);
return;
}
GET_ROLEPLAY_AGE(OLC_MOB(d)) = i;
GET_ROLEPLAY_AGE_YEAR(OLC_MOB(d)) = time_info.year;
OLC_VAL(d) = TRUE;
medit_disp_menu(d);
return;
case MEDIT_NUM_HP_DICE:
GET_HIT(OLC_MOB(d)) = LIMIT(i, 0, 30);
OLC_VAL(d) = TRUE;

View file

@ -274,6 +274,7 @@ extern const char *nrm, *grn, *cyn, *yel;
/* Numerical responses. */
#define MEDIT_NUMERICAL_RESPONSE 15
#define MEDIT_SEX 16
#define MEDIT_AGE 44
#define MEDIT_NUM_HP_DICE 17
#define MEDIT_SIZE_HP_DICE 18
#define MEDIT_ADD_HP 19

View file

@ -222,6 +222,20 @@ char *get_name_by_id(long id)
return (NULL);
}
static void update_roleplay_age(struct char_data *ch)
{
if (GET_ROLEPLAY_AGE(ch) == 0)
GET_ROLEPLAY_AGE(ch) = MIN_CHAR_AGE;
if (GET_ROLEPLAY_AGE_YEAR(ch) == 0)
GET_ROLEPLAY_AGE_YEAR(ch) = time_info.year;
if (time_info.year > GET_ROLEPLAY_AGE_YEAR(ch)) {
GET_ROLEPLAY_AGE(ch) += (time_info.year - GET_ROLEPLAY_AGE_YEAR(ch));
GET_ROLEPLAY_AGE_YEAR(ch) = time_info.year;
}
}
/* Stuff related to the save/load player system. */
/* New load_char reads ASCII Player Files. Load a char, TRUE if loaded, FALSE
* if not. */
@ -260,6 +274,8 @@ int load_char(const char *name, struct char_data *ch)
GET_LEVEL(ch) = PFDEF_LEVEL;
GET_HEIGHT(ch) = PFDEF_HEIGHT;
GET_WEIGHT(ch) = PFDEF_WEIGHT;
GET_ROLEPLAY_AGE(ch) = 0;
GET_ROLEPLAY_AGE_YEAR(ch) = 0;
GET_ALIGNMENT(ch) = PFDEF_ALIGNMENT;
for (i = 0; i < NUM_OF_SAVING_THROWS; i++)
GET_SAVE(ch, i) = PFDEF_SAVETHROW;
@ -317,6 +333,8 @@ int load_char(const char *name, struct char_data *ch)
switch (*tag) {
case 'A':
if (!strcmp(tag, "Ac ")) GET_AC(ch) = atoi(line);
else if (!strcmp(tag, "AgYr")) GET_ROLEPLAY_AGE_YEAR(ch) = atoi(line);
else if (!strcmp(tag, "Age ")) GET_ROLEPLAY_AGE(ch) = LIMIT(atoi(line), MIN_CHAR_AGE, MAX_CHAR_AGE);
else if (!strcmp(tag, "Acct")) {
if (GET_ACCOUNT(ch))
free(GET_ACCOUNT(ch));
@ -515,6 +533,8 @@ int load_char(const char *name, struct char_data *ch)
}
}
update_roleplay_age(ch);
ch->player.time.birth = time(0) - get_total_played_seconds(ch);
affect_total(ch);
/* initialization for imms */
@ -564,6 +584,9 @@ void save_char(struct char_data * ch)
}
}
update_roleplay_age(ch);
ch->player.time.birth = time(0) - get_total_played_seconds(ch);
if (!get_filename(filename, sizeof(filename), PLR_FILE, GET_NAME(ch)))
return;
if (!(fl = fopen(filename, "w"))) {
@ -631,6 +654,8 @@ void save_char(struct char_data * ch)
fprintf(fl, "Id : %ld\n", GET_IDNUM(ch));
fprintf(fl, "Brth: %ld\n", (long)ch->player.time.birth);
fprintf(fl, "Age : %d\n", GET_ROLEPLAY_AGE(ch));
fprintf(fl, "AgYr: %d\n", GET_ROLEPLAY_AGE_YEAR(ch));
fprintf(fl, "Plyd: %d\n", ch->player.time.played);
fprintf(fl, "Last: %ld\n", (long)ch->player.time.logon);

View file

@ -348,6 +348,7 @@
#define CON_QSPECIES 8 /**< Choose character species */
#define CON_QCLASS 9 /**< Choose character class */
#define CON_QSTAT_PREF 44 /**< Choose character stat preference order */
#define CON_QAGE 45 /**< Choose character age */
#define CON_QSHORTDESC 10 /**< Enter a new character short description prompt */
#define CON_RMOTD 11 /**< Reading the message of the day */
#define CON_MENU 12 /**< At the main menu */
@ -906,7 +907,7 @@ struct time_info_data
/** Player specific time information. */
struct time_data
{
time_t birth; /**< Represents the PCs birthday, used to calculate age. */
time_t birth; /**< Anchor for calculating mechanical age from played time. */
time_t logon; /**< Time of the last logon, used to calculate time played */
int played; /**< This is the total accumulated time played in secs */
};
@ -943,7 +944,9 @@ struct char_player_data
byte chclass; /**< PC / NPC class */
byte species; /**< PC / NPC species */
byte level; /**< PC / NPC level */
struct time_data time; /**< PC AGE in days */
struct time_data time; /**< Playtime tracking */
int roleplay_age; /**< Roleplay age in years */
int roleplay_age_year; /**< Last mud year roleplay age was updated */
ubyte weight; /**< PC / NPC weight */
ubyte height; /**< PC / NPC height */
};

View file

@ -570,17 +570,24 @@ time_t mud_time_to_secs(struct time_info_data *now)
return (time(NULL) - when);
}
/** Calculate a player's MUD age.
* @todo The minimum starting age of 17 is hardcoded in this function. Recommend
* changing the minimum age to a property (variable) external to this function.
time_t get_total_played_seconds(const struct char_data *ch)
{
time_t played = ch->player.time.played;
if (ch->desc && STATE(ch->desc) == CON_PLAYING)
played += time(0) - ch->player.time.logon;
return played;
}
/** Calculate a player's mechanical age based on total played time.
* @param ch A valid player character. */
struct time_info_data *age(struct char_data *ch)
{
static struct time_info_data player_age;
player_age = *mud_time_passed(time(0), ch->player.time.birth);
player_age.year += 17; /* All players start at 17 */
time_t played = get_total_played_seconds(ch);
player_age = *mud_time_passed(time(0), time(0) - played);
return (&player_age);
}

View file

@ -54,6 +54,7 @@ void sprintbitarray(int bitvector[], const char *names[], int maxar, char *resul
int get_line(FILE *fl, char *buf);
int get_filename(char *filename, size_t fbufsize, int mode, const char *orig_name);
time_t mud_time_to_secs(struct time_info_data *now);
time_t get_total_played_seconds(const struct char_data *ch);
struct time_info_data *age(struct char_data *ch);
int num_pc_in_room(struct room_data *room);
void core_dump_real(const char *who, int line);
@ -236,6 +237,9 @@ void char_from_furniture(struct char_data *ch);
* Current calculation ~= 12.4 real life days */
#define SECS_PER_MUD_YEAR (17*SECS_PER_MUD_MONTH)
#define MIN_CHAR_AGE 18
#define MAX_CHAR_AGE 65
/** The number of seconds in a real minute. */
#define SECS_PER_REAL_MIN 60
/** The number of seconds in a real hour. */
@ -516,6 +520,8 @@ do \
#define GET_WAS_IN(ch) ((ch)->was_in_room)
/** How old is PC/NPC, at last recorded time? */
#define GET_AGE(ch) (age(ch)->year)
#define GET_ROLEPLAY_AGE(ch) ((ch)->player.roleplay_age)
#define GET_ROLEPLAY_AGE_YEAR(ch) ((ch)->player.roleplay_age_year)
/** Proper name for PCs and NPCs. */
#define GET_NAME(ch) ((ch)->player.name)