/*! \file history.c This module keeps track of and reports historical data including logon/logoff statistics, playing time, character creation/deletion, and user counts. \author Jon A. Lambert \version 1.96 \date 06/27/2002 \remarks This code copyright (C) 2000-2002 by Jon A. Lambert<BR> <antilochos@ix.netcom.com> - All rights reserved.<BR> No part of this code may be used without my written consent.<BR> This code released to the PUBLIC DOMAIN on 4/2/2006 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "merc.h" #include "recycle.h" #include "dates.h" #include "history.h" #define HIST_DIR "../log" /* in with log data */ struct stats g_stats = { 0, 0, "\0", 99, 99, 0, 0, 0, 0, 0 }; struct history_record { char hist_date[10]; int hist_weekday; int hist_hour; int hist_play_time; int hist_logins; int hist_logouts; int hist_deletions; int hist_creates; int hist_max_on; int hist_count_on; }; struct history_record *history_table; /* local functions */ static int load_history_table (int report_type, char *start_date, char *end_date); static void print_report (CHAR_DATA * ch, int report_type, int history_size, char *start_date, char *end_date); void do_history (CHAR_DATA * ch, char *argument) { char arg1[MAX_INPUT_LENGTH]; char arg2[MAX_INPUT_LENGTH]; char arg3[MAX_INPUT_LENGTH]; char arg4[MAX_INPUT_LENGTH]; char start_date[MAX_INPUT_LENGTH]; char end_date[MAX_INPUT_LENGTH]; time_t t; struct tm *time_block; int report_type = 0; int retval; history_table = NULL; if (argument == NULL || argument[0] == '\0') { send_to_char ("Syntax : HISTORY daily|hourly|weekday totals|averages [start_date [end_date]]\r\n", ch); send_to_char (" Date formats are MMDDYY.\r\n", ch); send_to_char (" Default start_date is the beginning of the current month.\r\n", ch); send_to_char (" Default end_date is the current date.\r\n", ch); send_to_char ("Example: history hourly averages 072300 102200\r\n", ch); return; } argument = one_argument (argument, arg1); argument = one_argument (argument, arg2); argument = one_argument (argument, arg3); argument = one_argument (argument, arg4); if (str_prefix (arg1, "daily") && str_prefix (arg1, "hourly") && str_prefix (arg1, "weekday")) { send_to_char ("Report type must be daily, hourly or weekday.\r\n", ch); return; } if (str_prefix (arg2, "totals") && str_prefix (arg2, "averages")) { send_to_char ("Report format must be totals or averages.\r\n", ch); return; } time (&t); time_block = localtime (&t); if (arg3 == NULL || arg3[0] == '\0') { time_block->tm_mday = 1; MakeDateString (start_date, time_block); } else if (is_number (arg3) && strlen (arg3) == 6 && ValidateDate (arg3)) { strcpy (start_date, arg3); } else { send_to_char ("Start date must be in MMDDYY format.\r\n", ch); return; } time_block = localtime (&t); if (arg4 == NULL || arg4[0] == '\0') { MakeDateString (end_date, time_block); } else if (is_number (arg4) && strlen (arg4) == 6 && ValidateDate (arg4)) { strcpy (end_date, arg4); } else { send_to_char ("End date must be in MMDDYY format.\r\n", ch); return; } if (DaysSince1800 (end_date) < DaysSince1800 (start_date)) { send_to_char ("End date must be greater than start date.\r\n", ch); return; } if (!str_prefix (arg1, "daily")) { report_type = 10; if (!str_prefix (arg2, "totals")) report_type = 11; if (DaysSince1800 (end_date) - DaysSince1800 (start_date) + 1 > 31) report_type += 2; } else if (!str_prefix (arg1, "hourly")) { report_type = 20; if (!str_prefix (arg2, "totals")) report_type = 21; } else if (!str_prefix (arg1, "weekday")) { report_type = 30; if (!str_prefix (arg2, "totals")) report_type = 31; } retval = load_history_table (report_type, start_date, end_date); if (retval != 0) { print_report (ch, report_type, retval, start_date, end_date); free (history_table); } return; } static int load_history_table (int report_type, char *start_date, char *end_date) { int *counts; FILE *fp; struct history_record temp; signed int start_yr; signed int orig_start_yr; signed int start_mo; signed int orig_start_mo; signed int start_dy; signed int end_yr; signed int end_mo; signed int end_dy; int history_size; char name[MAX_STRING_LENGTH]; int i; int indexval = 0; /* translate dates to numeric values */ start_yr = Year (start_date); orig_start_yr = Year (start_date); start_mo = Month (start_date); orig_start_mo = Month (start_date); start_dy = Day (start_date); end_yr = Year (end_date); end_mo = Month (end_date); end_dy = Day (end_date); /* calculate sizes of history table */ if (report_type == 10 || report_type == 11) { history_size = DaysSince1800 (end_date) - DaysSince1800 (start_date) + 1; } else if (report_type == 12 || report_type == 13) { history_size = ((end_yr - start_yr) * 12) + (end_mo - start_mo) + 1; } else if (report_type == 20 || report_type == 21) { history_size = 24; } else if (report_type == 30 || report_type == 31) { history_size = 7; } else { return 0; } history_table = (struct history_record *) calloc (history_size, sizeof (struct history_record)); if (history_table == NULL) return 0; counts = (int *) calloc (history_size, sizeof (counts)); if (counts == NULL) { free (history_table); return 0; } for (;;) { if (start_yr > end_yr) break; if (start_yr == end_yr && start_mo > end_mo) break; sprintf (name, "%sY%dM%d", HIST_DIR, start_yr, start_mo); if ((fp = fopen (name, "r")) == NULL) { start_mo++; if (start_mo > 12) { start_yr++; start_mo = 1; } continue; } for (;;) { fscanf (fp, "%s %d %d %d %d %d %d %d %d %d\r\n", temp.hist_date, &temp.hist_weekday, &temp.hist_hour, &temp.hist_play_time, &temp.hist_logins, &temp.hist_logouts, &temp.hist_deletions, &temp.hist_creates, &temp.hist_max_on, &temp.hist_count_on); if (Month (temp.hist_date) == orig_start_mo && Day (temp.hist_date) < start_dy) continue; if (Month (temp.hist_date) == end_mo && Day (temp.hist_date) > end_dy) { start_mo++; if (start_mo > 12) { start_yr++; start_mo = 1; } fclose (fp); break; } /* calculate index into history table based on report type */ if (report_type < 20) { if (report_type < 12) { indexval = DaysSince1800 (temp.hist_date) - DaysSince1800 (start_date); } else { indexval = ((Year (temp.hist_date) - orig_start_yr) * 12) + (Month (temp.hist_date) - orig_start_mo); } } else if (report_type < 30) { indexval = temp.hist_hour; } else if (report_type < 40) { indexval = temp.hist_weekday; } /* populate it */ counts[indexval]++; strcpy (history_table[indexval].hist_date, temp.hist_date); history_table[indexval].hist_play_time += temp.hist_play_time; history_table[indexval].hist_logins += temp.hist_logins; history_table[indexval].hist_logouts += temp.hist_logouts; history_table[indexval].hist_deletions += temp.hist_deletions; history_table[indexval].hist_creates += temp.hist_creates; history_table[indexval].hist_count_on += temp.hist_count_on; history_table[indexval].hist_max_on = UMAX (history_table[indexval].hist_max_on, temp.hist_max_on); if (feof (fp)) { fclose (fp); start_mo++; if (start_mo > 12) { start_yr++; start_mo = 1; } break; } } } /* Calculate averages */ if (report_type % 2 == 0) { for (i = 0; i < history_size; i++) { int tempcount; if (report_type == 12 || report_type == 10 || report_type == 30) { tempcount = counts[i] / 24; } else { tempcount = counts[i]; } tempcount = UMAX (tempcount, 1); history_table[i].hist_play_time /= UMAX (history_table[i].hist_logouts, 1); history_table[i].hist_logins /= tempcount; history_table[i].hist_logouts /= tempcount; history_table[i].hist_deletions /= tempcount; history_table[i].hist_creates /= tempcount; if (report_type == 12 || report_type == 10 || report_type == 30) { history_table[i].hist_count_on /= UMAX (counts[i], 1); } else { history_table[i].hist_count_on /= tempcount; } } } free (counts); return history_size; } static void print_report (CHAR_DATA * ch, int report_type, int history_size, char *start_date, char *end_date) { BUFFER *fBuf; char buf[MAX_STRING_LENGTH]; char y_axis[MAX_STRING_LENGTH]; int i; char *const dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; char *const month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; char *const hrs[] = { "12:00a", "01:00a", "02:00a", "03:00a", "04:00a", "05:00a", "06:00a", "07:00a", "08:00a", "09:00a", "10:00a", "11:00a", "12:00p", "01:00p", "02:00p", "03:00p", "04:00p", "05:00p", "06:00p", "07:00p", "08:00p", "09:00p", "10:00p", "11:00p" }; fBuf = new_buf (); sprintf (buf, " Report for %d/%d/%d to %d/%d/%d\r\n", Month (start_date), Day (start_date), Year (start_date), Month (end_date), Day (end_date), Year (end_date) ); add_buf (fBuf, buf); if (report_type == 10) { sprintf (buf, " Daily Averages (per Day)\r\n"); add_buf (fBuf, buf); } else if (report_type == 11) { sprintf (buf, " Daily Totals\r\n"); add_buf (fBuf, buf); } else if (report_type == 12) { sprintf (buf, " Monthly Averages (per Day)\r\n"); add_buf (fBuf, buf); } else if (report_type == 13) { sprintf (buf, " Monthly Totals\r\n"); add_buf (fBuf, buf); } else if (report_type == 20) { sprintf (buf, " Hourly Averages (per Hour)\r\n"); add_buf (fBuf, buf); } else if (report_type == 21) { sprintf (buf, " Hourly Totals\r\n"); add_buf (fBuf, buf); } else if (report_type == 30) { sprintf (buf, " Weekday Averages (per Day)\r\n"); add_buf (fBuf, buf); } else if (report_type == 31) { sprintf (buf, " Weekday Totals\r\n"); add_buf (fBuf, buf); } sprintf (buf, " | Playing Number Number Number New Count Max \r\n"); add_buf (fBuf, buf); sprintf (buf, " | Time of of of Char. on the Players\r\n"); add_buf (fBuf, buf); sprintf (buf, " | (mins.) Logins Logouts Deletes Creates Hour [NoAvg]\r\n"); add_buf (fBuf, buf); sprintf (buf, " |--------------------------------------------------------\r\n"); add_buf (fBuf, buf); for (i = 0; i < history_size; i++) { if (report_type < 12) { if (history_table[i].hist_date == NULL || history_table[i].hist_date[0] == '\0') sprintf (y_axis, "NoData"); else sprintf (y_axis, "%02d/%02d ", Month (history_table[i].hist_date), Day (history_table[i].hist_date)); } else if (report_type < 20) { if (history_table[i].hist_date == NULL || history_table[i].hist_date[0] == '\0') sprintf (y_axis, "NoData"); else sprintf (y_axis, "%3s ", month[Month (history_table[i].hist_date) - 1]); } else if (report_type < 30) { sprintf (y_axis, "%6s", hrs[i]); } else { sprintf (y_axis, "%3s ", dow[i]); } sprintf (buf, "%6s | %7d %7d %7d %7d %7d %7d %7d\r\n", y_axis, history_table[i].hist_play_time / 60, history_table[i].hist_logins, history_table[i].hist_logouts, history_table[i].hist_deletions, history_table[i].hist_creates, history_table[i].hist_count_on, history_table[i].hist_max_on); add_buf (fBuf, buf); } page_to_char (buf_string (fBuf), ch); free_buf (fBuf); } /*! This routine maintains accurate high player counts and logs player history on an hourly basis */ void update_stats () { FILE *fp; time_t t; struct tm *time_block; char name[MAX_STRING_LENGTH]; int current_count = 0; DESCRIPTOR_DATA *d; for ( d = descriptor_list; d != NULL; d = d->next ) if ( d->connected == CON_PLAYING ) current_count++; g_stats.max_on = UMAX (g_stats.max_on, current_count); time (&t); time_block = localtime (&t); /* we must be starting up - set the fields up and leave */ if (g_stats.current_hour == 99) { MakeDateString (g_stats.current_date, time_block); g_stats.current_weekday = time_block->tm_wday; g_stats.current_hour = time_block->tm_hour; log_string ("Copyover/Startup: Refresh time counters"); return; } if (g_stats.current_hour != time_block->tm_hour) { /* Update history file */ sprintf (name, "%sY%dM%d", HIST_DIR, Year (g_stats.current_date), Month (g_stats.current_date)); if ((fp = fopen (name, "a")) == NULL) { bug("save_history: fopen", 0); } else { fprintf (fp, "%s %d %d %d %d %d %d %d %d %d\n\r", g_stats.current_date, g_stats.current_weekday, g_stats.current_hour, g_stats.current_play_time, g_stats.current_logins, g_stats.current_logouts, g_stats.current_deletions, g_stats.current_creates, g_stats.max_on, current_count); } fclose (fp); MakeDateString (g_stats.current_date, time_block); g_stats.current_weekday = time_block->tm_wday; g_stats.current_hour = time_block->tm_hour; g_stats.current_play_time = 0; g_stats.current_logins = 0; g_stats.current_logouts = 0; g_stats.current_deletions = 0; g_stats.current_creates = 0; } /* The date has changed - let us refresh count and current julian day */ if (time_block->tm_yday != g_stats.current_juldate) { g_stats.current_juldate = time_block->tm_yday; g_stats.max_on = current_count; } return; }