asgard/
asgard/.settings/
asgard/area/
asgard/data/clans/
asgard/data/clans/history/
asgard/data/rosters/
asgard/src/notice/
/*
 * achievements.c
 *
 *  Created on: 22 Jun 2011
 *      Author: Nico
 *  Achievements system.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "merc.h"
#include "recycle.h"
#include "achievements.h"

bool pach_learned1(CHAR_DATA *ch);
bool pach_learned2(CHAR_DATA *ch);
bool pach_stance1(CHAR_DATA *ch);
bool pach_stance2(CHAR_DATA *ch);

// Table of achievements.
const ACH_DEFINITION achievements_table[] = {
	{ "lgd", 	ACH_LGD,		1,		"I Am Legend",					1000,	"reach level 101 at tier 3"					},

	{ "kNPC1",	ACH_NPCKILLS,	1,		"You murderer!",				100,	"kill an NPC"								},
	{ "kNPC2",	ACH_NPCKILLS,	2000,	"Seasoned killer",				2000,	"kill %lu NPCs"								},
	{ "kNPC3",	ACH_NPCKILLS,	50000,	"Bathed in blood",				5000,	"kill %lu NPCs"								},
	{ "kNPC4",	ACH_NPCKILLS,	100000,	"NPC GENOCIDE!!",				9000,	"kill %lu NPCs"								},

	{ "kPK1",	ACH_PKKILLS,	1,		"Antisocial",					1000,	"kill a player outside the arena"			},
	{ "kPK2",	ACH_PKKILLS,	50,		"Player-slayer",				5000,	"kill %lu players outside the arena"		},
	{ "kPK3",	ACH_PKKILLS,	200,	"Unstoppable",					10000,	"kill %lu players outside the arena"		},

	{ "kAre1",	ACH_AKILLS,		1,		"Ring fighter",					500,	"kill a player in the arena"				},
	{ "kAre2",	ACH_AKILLS,		100,	"Gladiator",					2000,	"kill %lu players in the arena"				},
	{ "kAre3",	ACH_AKILLS,		500,	"A sure thing",					5000,	"kill %lu players in the arena"				},

	{ "kBab1",	ACH_BABKILLS,	5,		"Babblicious",					500,	"kill %lu babbles"							},
	{ "kBab2",	ACH_BABKILLS,	100,	"The Bane of the Babbles",		1000,	"kill %lu babbles"							},
	{ "kBab3",	ACH_BABKILLS,	500,	"Kiss my ass, NINJA BABBLE",	5000,	"kill %lu babbles"							},

	{ "ques1",	ACH_QUESTS,		1,		"Q-q-q-quest!",					100,	"complete a quest"							},
	{ "ques2",	ACH_QUESTS,		500,	"Compulsive questor",			3000,	"complete %lu quests"						},
	{ "ques3",	ACH_QUESTS,		5000,	"*COUGH*Shryp*COUGH*",			9000,	"complete %lu quests"						},

	{ "hour1",	ACH_HOURS,		1,		"This MUD is OK, I guess...",	500,	"play over an hour total"					},
	{ "hour2",	ACH_HOURS,		500,	"Zeit meister",					4000,	"play for a total of %lu hours"				},
	{ "hour3",	ACH_HOURS,		1500,	"This is SO FUN!",				8000,	"play for a total of %lu hours"				},
	{ "hour4",	ACH_HOURS,		2500,	"I MUD, therefore I am",		15000,	"play for a total of %lu hours"				},

	{ "run1",	ACH_RUNS,		1,		"Sprinter",						100,	"run somewhere"								},
	{ "run2",	ACH_RUNS,		1000,	"Regular exerciser",			1000,	"run anywhere %lu times"					},
	{ "run3",	ACH_RUNS,		3000,	"Marathon runner",				2000,	"run anywhere %lu times"					},

	{ "vchr1",	ACH_VOUCHERS,	1,		"Modified",						100,	"redeem a modifier voucher"					},
	{ "vchr2",	ACH_VOUCHERS,	10,		"Redeemer",						1000,	"redeem %lu modifier vouchers"				},
	{ "vchr3",	ACH_VOUCHERS,	100,	"Voucher-mania!",				5000,	"redeem %lu modifier vouchers"				},

	{ "rand1",	ACH_RANDOMS,	1,		"WTF?! This piece sucks!",		200,	"pop a random item"							},
	{ "rand2",	ACH_RANDOMS,	100,	"Randomiser",					750,	"pop %lu random items"						},
	{ "rand3",	ACH_RANDOMS,	1000,	"Sir Pop-A-Lot",				1300,	"pop %lu random items"						},
	{ "rand4",	ACH_RANDOMS,	5000,	"BOTTER!",						2000,	"pop %lu random items"						},

	{ "quaf1",	ACH_QUAFFS,		10,		"I don't drink, I quaff!",		100,	"quaff %lu potions"							},
	{ "quaf2",	ACH_QUAFFS,		250,	"Self-medicator",				1500,	"quaff %lu potions"							},
	{ "quaf3",	ACH_QUAFFS,		1500,	"Quaffoholic",					2000,	"quaff %lu potions"							},

	{ "bank1",	ACH_BALANCE,	100,	"I can buy something!",			500,	"have %lu platinum in the bank"				},
	{ "bank2",	ACH_BALANCE,	5000,	"Plat-runner",					1000,	"have %lu platinum in the bank"				},
	{ "bank3",	ACH_BALANCE,	100000,	"I'm rich...RICH!",				5000,	"have %lu platinum in the bank"				},
	{ "bank4",	ACH_BALANCE,	1000000,"Millionaires are overrated",	10000,	"have %lu platinum in the bank"				},

	{ "stab1",	ACH_BACKSTABS,	1,		"Backstabber",					100,	"backstab an NPC or player"					},
	{ "stab2",	ACH_BACKSTABS,	50,		"Pain-in-the-back(side)",		500,	"backstab %lu NPCs or players"				},
	{ "stab3",	ACH_BACKSTABS,	2000,	"Bastard",						2000,	"backstab %lu NPCs or players"				},

	{ "hari1",	ACH_HARAKIRI,	1,		"OMG! WTF Are You Doing?!",		100,	"perform hara kiri"							},
	{ "hari2",	ACH_HARAKIRI,	100,	"Masochist",					7500,	"perform hara kiri %lu times"				},
	{ "hari3",	ACH_HARAKIRI,	1000,	"Its only a flesh wound...",	2000,	"perform hara kiri %lu times"				},
	{ "hari4",	ACH_HARAKIRI,	5000,	"Seriously...you need help",	5000,	"perform hara kiri %lu times"				},

	{ "cast1",	ACH_CASTS,		1000,	"Spell spammer",				2000,	"successfully cast %lu spells"				},
	{ "cast2",	ACH_CASTS,		10000,	"Can I OD on magic?",			5000,	"successfully cast %lu spells"				},

	{ "lght1",	ACH_LIGHTNING,	1,		"You've been...Thunderstruck!",	100,	"experience the lightning dream"			},
	{ "lght2",	ACH_LIGHTNING,	5,		"Zzzzizzling",					500,	"experience the lightning dream %lu times"	},
	{ "lght3",	ACH_LIGHTNING,	20,		"Ride The Lightning",			2000,	"experience the lightning dream %lu times"	},

	{ "pill1",	ACH_PILLS,		1,		"One won't hurt, will it?",		100,	"eat a shimmering white pill"				},
	{ "pill2",	ACH_PILLS,		10,		"Taste the rainbow",			500,	"eat %lu shimmering white pills"			},
	{ "pill3",	ACH_PILLS,		100,	"Addict!",						800,	"eat %lu shimmering white pills"			},
	{ "pill4",	ACH_PILLS,		500,	"Pill popper",					1500,	"eat %lu shimmering white pills"			},
	{ "pill5",	ACH_PILLS,		10000,	"*COUGH*Bree*COUGH*",			3000,	"eat %lu shimmering white pills"			},

	// Start of psuedo achievements
	{ "learn1",	ACH_PSEUDO,	(unsigned long)&pach_learned1,	"I'm learnding",				1000,	"Reach 50 in one magic type"	},
	{ "learn2",	ACH_PSEUDO,	(unsigned long)&pach_learned2,	"I'm learning!",				5000,	"Reach 50 in all magic types"	},

	{ "stanc1",	ACH_PSEUDO,	(unsigned long)&pach_stance1,	"Patience, young grasshopper",	1000,	"Reach 200 in one stance"		},
	{ "stanc2",	ACH_PSEUDO,	(unsigned long)&pach_stance2,	"I know kung fu!",				5000,	"Reach 200 in all stances"		},

	// End marker.
	{ NULL,		-1,				-1,		NULL,							-1,		NULL										}
};

// Memory management for achievment done structure.
static ACH_DONE ach_done_zero, *ach_done_free;
ACH_DONE *new_ach_done(const ACH_DEFINITION *def) {
	ACH_DONE *done;
	if (ach_done_free == NULL)
		done = malloc(sizeof(ACH_DONE));
	else {
		done = ach_done_free;
		ach_done_free = done->next;
	}
	*done = ach_done_zero;
	done->type = def;
	return done;
}
void free_ach_done(ACH_DONE *done) {
	done->next = ach_done_free;
	ach_done_free = done;
}

void ach_increment(CHAR_DATA *ch, ACH_METRIC metric, unsigned long amount) {
	// Safety checks.
	if (ch == NULL || ch->pcdata == NULL)
		return;

	// Cap amount, to prevent overflow.
	amount = UMIN(amount, 294967294UL);
	// Update the metric on the player record.
	ch->pcdata->achievement_metrics[metric]
		= UMIN(ch->pcdata->achievement_metrics[metric]+amount, 4000000000UL);
	// Update character.
	ach_update(ch);
}

bool ach_unlocked(CHAR_DATA *ch, const ACH_DEFINITION *achievement) {
	ACH_DONE *done;
	for (done = ch->pcdata->achievements_done; done != NULL; done = done->next)
		if (done->type == achievement)
			return TRUE;
	return FALSE;
}

// Unlock an achievement.
static void unlock_achievement(CHAR_DATA *ch, const ACH_DEFINITION *achievement) {
	char buf[MAX_STRING_LENGTH];
	ACH_DONE *done;

	// Break out if we need to.
	if (ach_unlocked(ch, achievement))
		return;

	// Tell the player.
	sprintf(buf, "{D<< {YYou have unlocked the {D\"{G%s{D\"{Y achievement for {G%d{Y experience! {D>>{x\r\n",
		achievement->name, achievement->reward);
	send_to_char(buf, ch);

	// Now tell everyone in the room.
	sprintf(buf, "{D<< {Y$n has unlocked the {D\"{G%s{D\"{Y achievement! {D>>{x\r\n",
		achievement->name);
	act(buf, ch, NULL, NULL, TO_ROOM);

	// Gain experience quietly, since we say how much they get already.
	gain_exp(ch, achievement->reward, TRUE, TRUE);

	// Add the achievement to the player.
	done = new_ach_done(achievement);
	done->next = ch->pcdata->achievements_done;
	ch->pcdata->achievements_done = done;
}

void ach_update(CHAR_DATA *ch) {
	// Safety checks.
	if (ch == NULL || ch->pcdata == NULL)
		return;

	const ACH_DEFINITION *def;
	for (def = achievements_table; def->name != NULL; ++def) {
		if (def->metric > ACH_MAX) {
			if (((bool(*)(CHAR_DATA*))def->threshold)(ch))
				unlock_achievement(ch, def);
		} else if (ch->pcdata->achievement_metrics[def->metric] >= def->threshold) {
			// Achievement!
			unlock_achievement(ch, def);
		}
	}
}

static char *progress_bar(CHAR_DATA *ch, const ACH_DEFINITION *achievement) {
	static char buf[MAX_STRING_LENGTH];
	char percentage_buf[MAX_STRING_LENGTH];
	unsigned long percentage = UMIN(100, ch->pcdata->achievement_metrics[achievement->metric]*100/achievement->threshold);

	if (ach_unlocked(ch, achievement))	// If its 100%, show it.
		sprintf(buf, "{B[{G*UNLOCKED*{B]");
	else if (achievement->metric > ACH_MAX) {
		sprintf(buf, "{B[{y----------{B]");
	} else {
		if (percentage < 10)
			sprintf(percentage_buf, " {r%lu{W%% ",
				percentage);
		else
			sprintf(percentage_buf, "%c{%c%2.2lu{W%%",
				percentage >= 40 ? '=' : ' ',
				percentage < 50 ? 'y' : 'g', percentage);

		sprintf(buf, "{B[{D%c%c%c%s{D%c%c%c{B]",
			percentage >= 10 ? '=' : ' ', percentage >= 20 ? '=' : ' ',
			percentage >= 30 ? '=' : ' ', percentage_buf,
			percentage >= 80 ? '=' : ' ', percentage >= 90 ? '=' : ' ',
			percentage >= 100 ? '=' : ' ');
	}
	return buf;
}

static char *description_format(const ACH_DEFINITION *def) {
	static char buf[MAX_STRING_LENGTH];

	sprintf(buf, def->description, def->threshold);
	return buf;
}

void do_achievements(CHAR_DATA *ch, char *argument) {
	BUFFER *buffer;
	char buf[MAX_STRING_LENGTH];

	buffer = new_buf();
	add_buf(buffer, "{B+--------------------------------------- {GYour Achievements{B ---------------------------------------+\r\n");

	// Loop through achievements_table, printing progress.
	const ACH_DEFINITION *def;
	for (def = achievements_table; def->name != NULL; ++def) {
		sprintf(buf, "| %s {w%5d{Wxp {B- {w%-29s {B- {w%-40s {B|\r\n",
			progress_bar(ch, def),
			def->reward,
			def->name,
			description_format(def));
		add_buf(buffer, buf);
	}

	add_buf(buffer, "+-------------------------------------------------------------------------------------------------+{x\r\n");
	page_to_char(buffer->string, ch);
	free_buf(buffer);
}


// Start of psuedo-achievement functions.
bool pach_learned1(CHAR_DATA *ch) {
	int i;
	for (i = 0; i < 6; ++i)
		if (ch->magic[i] >= 50)
			return TRUE;
	return FALSE;
}
bool pach_learned2(CHAR_DATA *ch) {
	int i;
	for (i = 0; i < 6; ++i)
		if (ch->magic[i] < 50)
			return FALSE;
	return TRUE;
}

bool pach_stance1(CHAR_DATA *ch) {
	int i;
	for (i = 1; i < 11; ++i)
		if (ch->stance[i] >= 200)
			return TRUE;
	return FALSE;
}
bool pach_stance2(CHAR_DATA *ch) {
	int i;
	for (i = 1; i < 11; ++i)
		if (ch->stance[i] < 200)
			return FALSE;
	return TRUE;
}