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

@ -1,7 +1,9 @@
***Files for Miranthas MUD.***
***Files for Cataclysm MUD.***
Miranthas MUD is a continuation of tbaMUD/CircleMUD, which is built on DIKU MUD.
The code here is freeware to honor that tradition.
Cataclysm MUD is a continuation of tbaMUD/CircleMUD, which is built on DIKU MUD.
The code here is freeware to honor that tradition. Licensing and use should be based
on what was outlined previously. Any new code added here is released under the same
license.
Due to the sensitive nature of topics found in this setting, all characters and
players are 18+. The game world is derived from several inspirational sources,
@ -9,7 +11,7 @@ most notably the former Armageddon MUD.
Roleplay is highly encouraged, but not enforced.
Features in Miranthas MUD Alpha release:
Features in Cataclysm MUD Alpha release:
* The city of Caleran is available for exploration
* Experience points and levels are removed in favor of skill based progression
@ -43,12 +45,22 @@ Features in Miranthas MUD Alpha release:
* PC's now use a short description for identification instead of name
* Backgrounds are now available for PC's and NPC's
* Account system for tracking players/characters over long periods of time
Alpha 1.1 release:
* Cleaned up legacy practice system code
* Added skill caps for classes to limit ability of everyone to reach skill leve 100 (and respective proficiency)
* Race/species selection and stat ranges (elves have higher dex, dwarves have higher str, etc)
* Renamed move to stamina in code to reflect how much energy is used for certain actions
* Species have base hit/mana/stamina now, plus their class modifier rolls
* Prioritized stats during character generation
* Ability to change ldesc of PC/NPC's in some situations
* Ability to look in certain directions to see what is 1-3 rooms away
* PC's and NPC's can now have an age set between 18-65
Features to be implemented in the next few releases:
* "acaudit" command to be "audit ac", allowing for further audit commands in the future
* Subclass selection to personalize character further
* Combat is slowed down so it isn't over in < 15 seconds (unless you're far outmatched)
* Mounts added to help with long trips
@ -78,6 +90,10 @@ Features to be implemented in the next few releases:
* Additional zones/cities based on Miranthas world map
* Resources on the world map can be claimed by different city-states or independent factions
* Claimed resources improve quality of armor/weapons/food/prices available
* Death from old age if you roll badly on your birthday after the expected lifespan of a species
* Attacks hit different parts of the body and have different damage effects
* Armor degradation based on damage taken per body part
* Weapon degradation based on damage dealt - potentially shattering weapons
...and down the road:

View file

@ -19,6 +19,7 @@ B
~
6218 0 0 0 0 0 0 0 0 E
1 3d20+40
@ -37,17 +38,17 @@ Skill 145 5
Skill 146 5
Skill 147 5
E
L 17 127 1
L 16 117 1
L 15 117 1
L 11 111 1
L 10 107 1
L 9 124 1
L 8 115 1
L 7 108 1
L 6 110 1
L 5 131 1
L 3 118 1
L 5 131 1
L 6 110 1
L 7 108 1
L 8 115 1
L 9 124 1
L 10 107 1
L 11 111 1
L 15 117 1
L 16 117 1
L 17 127 1
#101
Sally~
slim lanky human soldier guard~
@ -70,6 +71,7 @@ B
~
6218 0 0 0 0 0 0 0 0 E
1 3d20+40
@ -90,17 +92,17 @@ Skill 152 5
Skill 156 5
Skill 163 5
E
L 3 118 1
L 5 131 1
L 6 110 1
L 7 108 1
L 8 115 1
L 9 124 1
L 10 107 1
L 11 111 1
L 15 117 1
L 16 117 1
L 17 127 1
L 16 117 1
L 15 117 1
L 11 111 1
L 10 107 1
L 9 124 1
L 8 115 1
L 7 108 1
L 6 110 1
L 5 131 1
L 3 118 1
#102
Baldy~
barkeep stocky bald~
@ -123,13 +125,14 @@ B
~
10 0 0 0 0 0 0 0 0 E
1 3d12+60
8 8 1
E
L 9 112 1
L 14 113 1
L 9 112 1
#103
Lanky~
woman lanky scarred~
@ -153,6 +156,7 @@ B
~
10 0 0 0 0 0 0 0 0 E
1 3d8+60
@ -186,18 +190,20 @@ B
It's a rat.
~
8 0 0 0 0 0 0 0 0 E
1 0d0+10
8 8 2
Str: 2
Str: 3
Dex: 7
Int: 1
Int: 3
Wis: 8
Con: 8
Cha: 1
Cha: 3
Species: 25
Age: 42
AtkT 4
E
$

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)