<?php
// Show source
if(isset($_GET['source']) AND $_GET['source'] == 1) { print "<pre>".htmlentities(file_get_contents("./npc4e.php"))."</pre>"; die(); }

// Record starting time
$time_start = microtime(true);

// Text inputs
$count = (isset($_GET['n']) AND is_numeric($_GET['n'])) ? min(100, $_GET['n']) : 1;
$inputs = array("class", "race", "level", "sex", "age", "feature", "nature", "alignment", "profession");
$hard = array();
foreach($inputs as $input) { $hard[$input] = (isset($_GET[$input])) ? ucfirst($_GET[$input]) : null; }

// Flag inputs
$family = (isset($_GET['family']) AND $_GET['family'] == 1) ? true : false;							// Generate in family mode
$family_rr = (isset($_GET['family_rr']) AND $_GET['family_rr'] == 0) ? false : true;				// Restrict race combinations when in family mode
$help = (isset($_GET['help']) AND $_GET['help'] == 1) ? true : false;								// Show help screen
$dark = (isset($_GET['dark']) AND $_GET['dark'] == 1) ? true : false;								// Display dark theme

// Temporary variables
$sex = "";
$npcs = array();

// Misc functions
function url($url) { return "http://www.tabletopsociety.com/misc/npc4e.php?{$url}"; }
function href($text, $url) { return "<a href='http://www.tabletopsociety.com/misc/npc4e.php?{$url}'>{$text}</a>"; }
function array_mt_rand(&$arr) { return $arr[ roll(count($arr)) - 1 ]; }
function roll($faces) { return mt_rand(1,$faces); }

// Random age with optional category
function age($n = null)
{
	global $hard; if($hard['age'] != null) { return $hard['age']; }
	
	if($n == null) { $n = roll(6); }
	
	switch($n)
	{
		case 'child': return roll(16); break;
		case 'adolescent': case 1: return 10 + roll(20); break;
		case 'adult': case 2: case 3: return 31 + roll(10); break;
		case 'elder': case 4: case 5: return 41 + roll(8); break;
		case 'old': case 6: return 50 + roll(20); break;
	}
}

// Random alignment
function alignment()
{
	global $hard; if($hard['alignment'] != null) { return $hard['alignment']; }
	
	// Note: These are broken out into separate lines so that each keyword
	// (e.g. "Good", "Chaotic") can be adjusted later with custom colors/styles
	// As-is, it's stupid.
	$good = "Good";
	$evil = "Evil";
	$lawful = "Lawful";
	$chaotic = "Chaotic";
	$unaligned = "Unaligned";
	
	$arr = array(
		"{$good}",
		"{$lawful} {$good}",
		"{$evil}",
		"{$chaotic} {$evil}",
		"{$unaligned}"
	);
	
	$n = roll(10);
	$a = 0;
	
	switch($n)
	{
		case 1: case 3: $a = 0; break;	// Good
		case 4: case 5: $a = 1; break;	// Lawful Good
		case 6: case 8: $a = 4; break;	// Unaligned
		case 9: $a = 2; break;			// Evil
		case 10: $a = 3; break;			// Chaotic Evil
	}
	
	return $arr[$a];
}

// Random level from 1-4
function level()
{
	global $hard; if($hard['level'] !== null) { return $hard['level']; }
	
	$n = roll(10);
	
	if($n >= 1 AND $n <= 3) { return 1; }
	if($n >= 4 AND $n <= 5) { return 2; }
	if($n > 5) { return $n; }
}

// Random sex (Male or Female)
function sex()
{
	global $hard; if($hard['sex'] != null) { return $hard['sex']; }
	
	$n = roll(2);
	
	if($n == 1) { return "Male"; }
	return "Female";
}

// Random race with optional override (used for hybrids) and common/uncommon override
function race($trueRandom = false, $all = false)
{
	global $hard; if(!$trueRandom AND $hard['race'] != null) { return $hard['race']; }
	
	// Common PHB 1-3 races
	$common = array("Human", "Elf", "Dwarf", "Orc", "Halfling", "Half-Elf", "Half-Orc");
	
	// Roll common race
	if(roll(12) > 4) { return array_mt_rand($common); }
	
	// Remaining PHB 1-3 races
	$uncommon = array(
		"Dragonborn", "Eladrin", "Tiefling", "Deva", "Gnome",
		"Goliath", "Shifter", "Githzerai", "Minotaur", "Shardmind", "Wilden"
	);
	
	// Roll uncommon race
	if(!$all AND roll(12) > 4) { return array_mt_rand($uncommon); }
	
	// Races from other sources
	$rare = array(
		"Bladeling", "Bugbear", "Bullywug", "Changeling", "Drow", "Duergar",
		"Genasi", "Githyanki", "Gnoll", "Goblin", "Hamadryad", "Hobgoblin",
		"Kobold", "Mul", "Pixie", "Revenant", "Satyr", "Vryloka", "Warforged"
	);
	
	// Roll rare race
	if(!$all) { return array_mt_rand($rare); }
	
	$all = array_merge($common, $uncommon, $rare);
	
	return array_mt_rand($all);
}

// Random class
function randClass()
{
	global $hard; if($hard['class'] != null) { return $hard['class']; }
	
	// Commoner
	if(roll(4) > 1 OR $hard['profession'] != null) { $hard['level'] = 0; return profession(); }
	
	$arr = array(
		"Cleric", "Fighter", "Paladin", "Ranger", "Rogue", "Warlock",
		"Warlord", "Wizard", "Avenger", "Barbarian", "Bard", "Druid",
		"Invoker", "Shaman", "Sorcerer", "Warden", "Ardent",
		"Battlemind", "Monk", "Psion", "Runepriest", "Seeker"
	);
	
	// Hybrid class
	if(roll(4) == 4) {
		$first = array_mt_rand($arr);
		do { $second = array_mt_rand($arr); }
		while($second == $first);
		
		return "Hybrid {$first}/{$second}";
	}
	
	return array_mt_rand($arr);
}

// Random profession (used for commoners)
function profession()
{
	global $sex;
	global $hard; if($hard['profession'] != null) { return $hard['profession']; }
	
	$arr = array(
		// Special cases (usually with subtypes)
		"#Ruler#", "#Criminal#",
		// The rest
		"Farmer", "Blacksmith", "Clerk", "Mayor", "Town Guard", "Woodcutter",
		"Artisan", "Dancer", "Shepherd", "Priest", "Fletcher",
		"Scholar", "Executioner", "Servant", "Slave", "Tanner", "Hunter",
		"Layabout", "Hermit", "Nomad", "Sailor", "Bartender",
		"Innkeeper", "Minstrel", "Grave Digger", "Town Drunk", "Carpenter",
		"Village Idiot", "Explorer", "Doctor", "Barber", "Miner", "Messenger",
		"Noble", "Juggler", "Actor", "Puppeteer", "Jester", "Bodyguard",
		"Mercenary", "Con Artist", "Nanny", "Brewer", "Town Crier",
		"Philosopher", "Street Preacher", "Stablemaster", "Baker",
		"Architect", "Apothecarist", "Artist", "Astrologer", "Barrister",
		"Bricklayer", "Bowyer", "Cartographer", "Tailor", "Cook", "Diplomat",
		"Engraver", "Fisherman", "Forester", "Fortune-Teller", "Furrier",
		"Gardener", "Glassblower", "Herald", "Herbalist", "Jeweler",
		"Leatherworker", "Locksmith", "Moneylender", "Playwright", "Politician",
		"Potter", "Scribe", "Servant", "Shipwright", "Shoemaker", "Spy",
		"Stonecarver", "Storyteller", "Weaver", "Grain Merchant", "Wool Merchant",
		"Weapons Merchant", "Armor Merchant", "Potion Seller", "Gem Seller",
		"Fish Merchant", "Lumber Merchant", "Stone Merchant",
		"Magical Item Seller", "Trinket Merchant", "Slave Trader",
		"Broker", "General Goods Merchant", "Rat Catcher", "Cat Catcher",
		"Dog Catcher"
	);
	
	$p = array_mt_rand($arr);
	
	// Ruler
	if($p == "#Ruler#") { return ($sex == "Male") ? "King" : "Queen"; }
	
	// Criminal
	if($p == "#Criminal#") {
		$tmp = array( "Thief", "Murderer", "Pickpocket", "Burglar", "Highwayman" );
		return array_mt_rand($tmp);
	}
	
	return $p;
}

// Random NPC feature
function feature()
{
	global $hard; if($hard['feature'] != null) { return $hard['feature']; }
	
	$arr = array(
		// Special cases (usually with subtypes)
		"#Dyed Hair#",
		// Everything else
		"Twitchy", "Lazy Eye", "Stutter", "Missing Tooth", "Toothless",
		"Limps", "Tattoo", "Tattoos", "Earring", "Earrings", "Sunken Eyed",
		"Thousand Yard Stare", "Thin Lips", "Big Ears", "Big Nose", "Pointy Nose",
		"Square Jaw", "Red Faced", "Ugly", "One Leg", "One Arm", "Small Hat",
		"Eyepatch", "Big Hat", "Blind", "Mute", "Deaf", "Scarred", "Spectacles",
		"Monocle", "Pale", "Tanned", "Prominent Lips", "Double Chin",
		"Beer Belly", "Bald", "Garishly Dressed", "Bad Breath",
		"Fresh Breath", "Wall-eyed", "Hook for a hand", "Tall", "Short",
		"Gaunt", "Unibrow", "Itchy", "Broken Nose", "Double Jointed",
		"Leathery Skin", "Gray Hair", "Watery Eyes", "Smelly", "Cough",
		"Illness", "Dying"
	);
	
	$f = array_mt_rand($arr);
	
	// Hair dye
	if($f == "#Dyed Hair#") {
		$tmp = array(
			"Red", "Black", "White", "Green", "Blue", "Blonde", "Pink",
			"Purple", "Brown", "Ginger"
		);
		return array_mt_rand($tmp)." Hair";
	}
	
	return $f;
}

// Random NPC nature
function nature()
{
	global $hard; if($hard['nature'] != null) { return $hard['nature']; }
	
	if(roll(10) == 1) { return "Hates Race: ".race(true); }
	
	$arr = array(
		"Smiling", "Frowning", "Friendly", "Excitable", "Squeamish", "Rude",
		"Oblivious", "Cowardly", "Helpful", "Lazy", "Trustworthy",
		"Shady", "Awkward", "Adroit", "Intuitive", "Curious",
		"Bored", "Energetic", "Clumsy", "Nervous", "Impulsive",
		"Liar", "Distracted", "Short Attention Span", "Cocky", "Gregarious",
		"Shy", "Wise", "Soft-Spoken", "Quick-Tempered", "Naive", "World-Weary",
		"Flirtatious", "Charming", "Off-Putting", "Smart-Ass", "Absent-Minded",
		"Easygoing", "Skeptical", "Aquaphobic", "Agoraphobic", "Petulant",
		"Alcoholic", "Zealous", "Pipe Smoker", "Cigar Smoker", "Animal Lover",
		"Animal Hater", "External Monologue"
	);
	return array_mt_rand($arr);
}

// Random child feature
function feature_child()
{
	$arr = array(
		"Stutter", "Missing Tooth", "Big Ears", "Big Nose", "Pointy Nose",
		"Ugly", "Blind", "Mute", "Deaf"
	);
	return array_mt_rand($arr);
}

// Random child nature
function nature_child()
{
	$arr = array(
		"Smiling", "Frowning", "Friendly", "Excitable", "Rude",
		"Helpful", "Awkward", "Adroit", "Intuitive", "Curious",
		"Bored", "Energetic", "Clumsy", "Nervous", "Distracted",
		"Short Attention Span"
	);
	return array_mt_rand($arr);
}

// Build an NPC Family
function family()
{
	global $hard;
	global $npcs;
	global $count;
	global $family_rr;
	
	// Compatible pairings (other than X->X)
	$pairings = array(
		"Human" => array("Orc" => "Half-Orc", "Elf" => "Half-Elf"),
		"Orc" => array("Human" => "Half-Orc"),
		"Elf" => array("Human" => "Half-Elf")
	);
	
	// Mom
	$race_mom = race();
	$age_mom = age('adult');
	
	$hard['race'] = $race_mom;
	$hard['age'] = $age_mom;
	$hard['sex'] = "Female";
	$npcs[] = npc();
	
	// Dad's race
	$hard['race'] = null;
	$race_dad = ($family_rr) ? $race_mom : race();
	if(isset($pairings[$race_mom]) AND roll(2) == 2) { $race_dad = array_mt_rand(array_keys($pairings[$race_mom])); }
	
	$hard['age'] = null;
	$age_dad = age('adult');
	
	$hard['race'] = $race_dad;
	$hard['age'] = $age_dad;
	$hard['sex'] = "Male";
	$hard['level'] = null;
	$npcs[] = npc();
	
	// Kids' race
	$race_kids = $race_mom;
	if($race_dad != $race_mom) {
		if(isset($pairings[$race_mom][$race_dad])) { $race_kids = $pairings[$race_mom][$race_dad]; }
		else { $race_kids = "{$race_mom}/{$race_dad}"; }
	}
	
	// Number of kids
	$kid_count = ($count !== null) ? $count : roll(8);
	
	// Make babies
	for($i = 0; $i < $kid_count; $i++)
	{
		// Clear vars to allow randomization
		$hard['sex'] = null;
		$hard['age'] = null;
		$hard['level'] = 0;
		
		// Forced variables
		$hard['alignment'] = "Unaligned";
		$hard['class'] = "";
		$hard['profession'] = "Child";
		$hard['feature'] = feature_child();
		$hard['nature'] = nature_child();
		$hard['race'] = $race_kids;
		
		// Random variables
		$hard['age'] = age('child');
		$hard['sex'] = sex();
		
		$npcs[] = npc();
	}
	
	$count = count($npcs);
}

// Build an NPC
function npc()
{
	global $sex;
	
	$age = age();
	$alignment = alignment();
	$sex = sex();
	$race = race();
	$randClass = randClass();
	$level = level();
	$feature = feature();
	$nature = nature();
	
	$full_level = ($level > 0) ? "Level {$level}" : "";
	
	return "{$age}-year old {$alignment} {$full_level} {$sex} {$race} {$randClass} (&nbsp;{$feature}, {$nature}&nbsp;)";
}

// Normal NPC generation
function npcs()
{
	global $count;
	global $npcs;
	
	for($i = 0; $i < $count; $i++) {
		$npcs[] = npc();
	}
}

// Build random NPCs or NPC family
function generate()
{
	global $family;
	if(!$family) { npcs(); } else { family(); }
}

// Go Go Go
if($help != 1)
{
	// Execute NPC creation
	generate();
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
	<title>Instant 4E NPC Generator</title>
	<style type="text/css">
	body { font-family: Georgia, serif; }
	a.npc { color: #000; text-decoration: none; }
	a.toolbar { display: block; font-weight: bold; color: #a0a0a0; line-height: 20px; padding: 2px; float: right; text-decoration: none; }
	
	div.help { margin: auto; width: 900px; padding-top: 50px; font-size: 1.25em; }
	div.npc { margin: auto; width: 900px; }
	
	body.dark { color: #fff !important; background-color: #101010; }
	.dark a { color: #fff; }
	</style>
</head>
<body<?php if($dark) { echo " class='dark'"; } ?>>
	<a href="http://www.tabletopsociety.com/misc/npc4e.php?help=1&dark=<?php echo $dark; ?>" class="toolbar">?</a>
	
<?php if($help != 1) { ?>	
	<?php foreach($npcs as $npc) { ?>
	<div class="npc" style="padding-top: <?php echo (int)(max(20, (50 / $count))); ?>px; font-size: <?php echo min(4, max(2, (4 / ($count/4)))); ?>em;">
		<a href="#" onclick="javascript:location.reload(true);" class="npc"><?php echo $npc; ?></a>
	</div>
	<?php } ?>
<?php } else { ?>

	<div class="help">
		<p><strong>Instant 4E NPC Generator: Usage</strong></p>
		<p>You can override the random generation with a selection of simple URL parameters: n (the number of results), age, level, sex, race, class, alignment, profession (for commoners), feature, and nature.</p>
		<p>You can also build families by setting <em>family=1</em>, remove family race restrictions with <em>family_rr=0</em>, and switch to a dark theme with <em>dark=1</em>.</p>
		<p>
			<em>Handy Form:</em>
		</p>
		<form method="get" style="font-size: 0.7em;">
			<strong>Number: </strong><input size="2" name="n" value="1" /> 
			<strong>Age: </strong><input size="2" name="age" />
			<strong>Alignment: </strong><input size="20" name="alignment" /> 
			<strong>Level: </strong><input size="2" name="level" /> 
			<strong>Sex: </strong><input size="5" name="sex" /><br />
			<strong>Race: </strong><input size="20" name="race" /> 
			<strong>Class: </strong><input size="20" name="class" /> 
			<strong>Profession: </strong><input size="20" name="profession" /><br />
			<strong>Feature: </strong><input size="20" name="feature" /> 
			<strong>Nature: </strong><input size="20" name="nature" /> 
			<strong>Family: </strong><select name="family"><option value="0">No</option><option value="1">Yes</option></select>
			<strong>Family Races: </strong><select name="family_rr"><option value="1">4E</option><option value="0">Any</option></select><br />
			<strong>Visual Style: </strong><select name="dark"><option value="0"<?php echo (!$dark) ? " SELECTED" : ""; ?>>Light</option><option value="1"<?php echo ($dark) ? " SELECTED" : ""; ?>>Dark</option></select><br />
			<input type="submit" value="Go" />
		</form>
		<p>
			<em>Examples:</em>
			<ul>
				<li><?php echo href("Random (Default)", ""); ?></li>
				<li><?php echo href("10 results [n=10]", "n=10"); ?></li>
				<li>Override Race: <?php echo href("Human [race=Human]", "race=Human"); ?>, <?php echo href("Elf", "race=Elf"); ?>, <?php echo href("Dragonborn", "race=Dragonborn"); ?></li>
				<li>Override Class: <?php echo href("Fighter [class=Fighter]", "class=Fighter"); ?>, <?php echo href("Wizard", "class=Wizard"); ?></li>
				<li>Mixed Filters: <?php echo href("25 Half-Orc Warlords [n=25&amp;race=Half-Orc&amp;class=Warlord]", "n=25&amp;race=Half-Orc&amp;class=Warlord"); ?></li>
				<li>Families: <?php echo href("Random [family=1]", "family=1"); ?>, <?php echo href("Orc Family [family=1&amp;race=Orc]", "family=1&amp;race=Orc"); ?></li>
			</ul>
		</p>
		<p>
			<em>Notes:</em>
			<ul>
				<li>Setting <em>family</em> AND <em>n</em> will use <em>n</em> for the number of children.</li>
				<li>Setting <em>class</em> will override <em>profession</em>.</li>
				<li>Bugs / Suggestions / Feedback: <a href="mailto:timberjaw@gmail.com">timberjaw@gmail.com</a></li>
				<li><?php echo href("View Source Code", "source=1"); ?></li>
			</ul>
		</p>
	</div>
<?php } ?>
<?php $time_end = microtime(true); $time = $time_end - $time_start; echo "<!-- Page Built in  {$time}s -->"; ?>
</body>
</html>