<?php
 
function getStufenSkillMaximum($level) {
	assert(is_int($level) && $level >= 0);
	
	$stufen_skill_maximas = array(0 => 8, 1 => 9, 2 => 9, 3 => 9, 4 => 10, 5 => 10, 6 => 10, 7 => 11, 8 => 11, 9 => 11, 10 => 12, 11 => 12, 12 => 12, 13 => 13, 14 => 13, 15 => 14, 16 => 15, 17 => 16, 18 => 17, 19 => 17, 20 => 18, 21 => 19, 22 => 20);
	
	if($level > 22) return(20);
	return($stufen_skill_maximas[$level]);
}

function getNeededSkillingPoints($old, $new) {
	assert($old >= 0 && $old <=20 && $new >= 0 && $new <= 20);
	$signum = 1;  // Vorzeichen
	if($old > $new) {
		$signum = -1; // Punkte werden zurückgegeben
		// old und new vertauschen;
		$temp = $old;
		$old = $new;
		$new = $temp;
	}
	
	$needed = 0;
	for($i=$old;$i<$new;$i++) {
		if($i < 5) $needed++;
		elseif($i < 10) $needed+=2;
		elseif($i < 15) $needed+=3;
		elseif($i < 20) $needed+=4;
	}
	return($needed*$signum);
}


/*define('UNSETABLE', array('serial', 'charid'));  // kann nicht von aussen gesetzt geändert werden
define('PLAYER_UNSETABLE', array('titel', 'accountid','accesslevel','chartype',));  // kann nur mit Privs geändert oder gesetzt werden
define('UNCHANGEABLE', array('serial','charid'));  // kann überhaupt nicht geändert werden
*/
$STATS = array('klugheit', 'intuition', 'fingerfertigkeit', 'gewandtheit', 'konstitution', 'koerperkraft');
$SKILLS = array('fernkampf', 'klingenwaffen', 'schaftwaffen', 'stangenwaffen', 'waffenlos', 'parieren', 'taktik',
				'waffenkunde', 'ruestungskunde', 'alchemie', 'giftkunde', 'pflanzenkraeuterkunde', 'anatomie',
				'identifizieren', 'stofflederkunde', 'lebensmittelkunde', 'werkzeugkunde', 'forstwirtschaft', 'tierzucht',
				'naturkunde', 'ackerbau', 'orientierung', 'fischen', 'tierehueten', 'spurenlesen', 'finden', 'verstecken',
				'schleichen', 'spezialfertigkeit', 'kartographie', 'schreibenzeichnen', 'musizieren', 'kunstfertigkeit',
				'schmecken', 'heilen', 'heilentiere', 'schloesserknacken', 'taschendiebstahl', 'durchsuchen', 'schmieden',
				'bergbau', 'schneidern', 'steinbearbeitung', 'feinwerken', 'holzbearbeitung', 'kochenbacken', 'gravieren',
				'holzfaellen', 'entschaerfen', 'tierezaehmen');
		

$UNSAFE_HTML[] = "/javascript\s*/is";
$UNSAFE_HTML[] = "/vbscri?pt\s*:/is";
$UNSAFE_HTML[] = "/<\s*embed.*swf/is";
$UNSAFE_HTML[] = "/onabort\s*/is";
$UNSAFE_HTML[] = "/onblur\s*/is";
$UNSAFE_HTML[] = "/onchange\s*/is";
$UNSAFE_HTML[] = "/onfocus\s*/is";
$UNSAFE_HTML[] = "/onmouseout\s*/is";
$UNSAFE_HTML[] = "/onmouseover\s*/is";
$UNSAFE_HTML[] = "/onload\s*/is";
$UNSAFE_HTML[] = "/onreset\s*/is";
$UNSAFE_HTML[] = "/onselect\s*/is";
$UNSAFE_HTML[] = "/onsubmit\s*/is";
$UNSAFE_HTML[] = "/onunload\s*/is";
$UNSAFE_HTML[] = "/onerror\s*/is";
$UNSAFE_HTML[] = "/onclick\s*/is";

/** Checks text for strings that might be part of an hacking attempt and repairs them. Also replaces chars with their html entities
 * @param $html text that should be made secure
 */
function tools_safeHtml($html) {
	global $UNSAFE_HTML;
	$html = htmlspecialchars($html, ENT_QUOTES, 'ISO-8859-1');
	foreach ( $UNSAFE_HTML as $match ) {
		if(preg_match($match, $html)) {
			return false;
		}
	}
	return $html;
}

function tools_charstoryTheftDetector($charid, $intensity = 'weak', $numeric_results = true) {

	return(0);  // 0 - no indication; 9 - strong indication
}


class IException extends Exception {
	public $file;
	public $line;
	public $message;
	public $trace;
	public $notice;
	
	public function __construct($notice) {
		$this->notice = $notice;
		
	}
	
	public function getNotice() {
		/*echo "<br />";
		Print_R($this);
		echo "<br />";*/
		return($this->notice);
		// TODO remove trace
	}
}

function getValidCharString($char, $item) {
	// für Ausgabe; wenn Property gesetzt wurde, dann nehme den Wert aus dem Char Objekt, sonst aus $_REQUEST
	assert(is_a($char, 'Char'));
	
	if(isset($_REQUEST[$item])) {
		if(is_null($char->get($item))) {
			return($_REQUEST[$item]);
		}
	}
	return($char->get($item));
}


class Char {
	private $mode;  // create, gain, edit (Steigern); Edit ist auch Bearbeiten eines noch nicht erstellten Chars
	
	private $serial = NULL;
	private $charid = false;
	private $name;
	private $titel;
	private $accountid = false; // warum false
	private $testserver;
	private $accesslevel;
	private $chartype;
	private $volk;
	private $geschlecht;
	private $rp_alter;
	private $hautfarbe;
	private $profession_1 = array('id' => NULL, 'stufe' => NULL);
	private $profession_2 = array('id' => NULL, 'stufe' => NULL);
	private $profession_3 = array('id' => NULL, 'stufe' => NULL);
	/*private $professionen_ = array('profession_1' => NULL, 'professionsstufe_1' => NULL,
									'profession_2' => NULL, 'professionsstufe_2' => NULL, 
									'profession_3' => NULL, 'professionsstufe_3' => NULL);*/
//	private $ausbildungsprofession;
	private $ausbildungspunkte;
	private $verfuegbare_ausbilderpunkte;
	private $letzter_ausbildungszeitpunkt;
	private $level;
	private $xp;
	private $leben;
	private $forschungspunkte;
	private $statsteigerungspunkte;
	private $old_statsteigerungspunkte;
	private $skillsteigerungspunkte;
	private $old_skillsteigerungspunkte;
	private $umskillpunkte;
	private $professionssteigerungspunkte;
	private $aktiviert;  // 0 - nicht aktiviert&eingereicht; 1 - aktiviert; 2 - zurückgestellt; 3 - nicht eingereicht; 9 - gelöscht
	private $aktiviert_von;
	private $aktivierungszeitpunkt;
//	private $aktivierungskommentar;
	private $anzahl_zurueckstellungen;
	private $charstory;
	private $cst;
	private $fix_profil;
	private $dyn_profil;
	private $block;
	private $jailed;
	private $lastchange = false;
	private $stats = array();
	private $skills = array();
	private $old_stats = array();  // here were store old values for checks
	private $min_stats = array();
	private $max_stats = array();
	private $old_skills = array();
	private $min_skills = array();
	private $max_skills = array();
	
	
	private $gumpid_body;
	
	public $equipped_items = array(1 => NULL, // One handed weapon
						   2 => NULL, // Two handed weapon, shield, or misc. 
						   3 => NULL, // Shoes
						   4 => NULL, // Pants
						   5 => NULL, // Shirt
						   6 => NULL, // Helm/Hat 
						   7 => NULL, // Gloves 
						   8 => NULL, // Ring
						   9 => NULL, // Unused
						   10 => NULL, // Neck
						   11 => NULL, // Hair
						   12 => NULL, // Waist (half apron) 
						   13 => NULL, // Torso (inner) (chest armor) 
						   14 => NULL, // Bracelet 
						   15 => NULL, // Unused (backpack, but backpacks go to 21) 
						   16 => NULL, // Facial Hair 
						   17 => NULL, // Torso (middle) (sircoat, tunic, full apron, sash) 
						   18 => NULL, // Earrings 
						   19 => NULL, // Arms 
						   20 => NULL, // Back (cloak) 
						   22 => NULL, // Torso (outer) (robe) 
						   23 => NULL, // Legs (outer) (skirt/kilt) 
						   24 => NULL // Legs (inner) (leg armor) 
						);			  
	
	public $unequipped_items = array();
	
	public function __construct($charid = false, $accountid = false, $chartype = false, $testserver = false, $activation_display = false) {
		global $user, $MySQL, $settings, $STATS, $SKILLS;
			
		// ***BEGIN*** Parametervalidierung
		if($charid != false AND is_int($charid) === false)
			logError();
		if($accountid != false AND is_int($accountid) === false)
			logError(); 
		// chartype wird weiter unten validiert
		if(is_bool($testserver) === false)
			logError();
		// ***END*** Parametervalidierung
						
		// User muss existieren
		if(isset($user) === false) {
			access_error();
		}
		
		if($accountid === false) $accountid = $user->ID;
		
		// Darstellung für Freischalter
		if($activation_display === false) {		
			// Check ob Charbearbeitung für angegebenen Account überhaupt erlaubt
			if($accountid != $user->ID AND $user->can_access( 'char_admin' ) === false) {
				logError();
			}
			if($testserver === true AND $user->can_access( 'testserver' ) === false) {
				logError();
			}
		}
		
		// *** BEGINN Initialisierung ***
		// assoziative Array für stats und skills nach dem Muster array('statname1' => 0, 'statname2' => 0, ....)
		$this->stats = array_combine($STATS, array_pad(array(), count($STATS), NULL));
		$this->skills = array_combine($SKILLS, array_pad(array(), count($SKILLS), NULL));
		
		$this->old_stats 	= $this->stats;
		$this->min_stats 	= $this->stats;
		$this->max_stats	= $this->stats;
		$this->old_skills 	= $this->skills;
		$this->min_skills 	= $this->skills;
		$this->max_skills 	= $this->skills;
		
		if($charid === false) {
			// neuer Char soll neu erstellt werden
			$this->mode = 'create';
			
			$this->serial = '-1';
			$this->titel = '';
			
			if($accountid===false) {
				$this->accountid = $user->ID;
			} else {
				$this->accountid = $accountid;
			}	
			if($testserver === true) {
				$this->testserver = 1;
			} else {
				$this->testserver = 0;
			}
			
			switch($chartype) {
				case 'player':
					$this->chartype = 'player';
					$this->accesslevel = 'Player';
				break;
				case 'quest':
					$this->chartype = 'quest';
				break;
				case 'staff':
					$this->chartype = 'staff';
					$this->accesslevel = 'Counselor';
				break;
				default:
					logError();
			}
			
			$this->profession_1['id'] = 0;
			$this->profession_1['stufe'] = 0;
			$this->profession_2['id'] = 0;
			$this->profession_2['stufe'] = 0;
			$this->profession_3['id'] = 0;
			$this->profession_3['stufe'] = 0;
			
			$this->ausbildungspunkte = 0;
			$this->verfuegbare_ausbilderpunkte = 0;
			$this->letzter_ausbildungszeitpunkt = false;
			$this->level = 0;
			$this->xp = 0;
			$this->leben = 0;
			$this->forschungspunkte = 0;			
				
			// Skill- und Statsteigerungspunkte festlegen; wir behandeln steigern und erstellen technisch gleich; old wird für Validierungen gebraucht
			$this->statsteigerungspunkte = $settings['chargen_punkte'];
			$this->old_statsteigerungspunkte = $settings['chargen_punkte'];
			$this->skillsteigerungspunkte = $settings['chargen_skillpunkte'];
			$this->old_skillsteigerungspunkte = $settings['chargen_skillpunkte'];
			$this->umskillpunkte = 0;
			
			$this->aktiviert = 3;
			$this->aktiviert_von = false;
			$this->aktivierungszeitpunkt = false;
			$this->anzahl_zurueckstellungen = 0;
			$this->block = 0;
			$this->jailed = 0;
			
			// noch freie Chars?
			if($this->checkFreeChars($this->accountid, $this->chartype, $this->testserver) === false) {
				unset($GLOBALS['$char'], $_SESSION['chargen']);
				throw new IException('Kein freier Charslot');
			}
			
		} else {
			// bestehender Char (aktiviert oder noch nicht aktiviert)
			if(is_int($charid) === false) {
				// sollte nicht passieren
				logError();
			}
			
			$sql = sprintf("SELECT * FROM server.chars WHERE charid = %d AND accountid = %d AND aktiviert != 9 LIMIT 0,1", intval($charid), intval($accountid));
			$row = $MySQL->query_first($sql);
			if(isset($row['charid'])===false) throw new IException("Dieser Charakter existiert nicht!");
			
			switch($row['aktiviert']) {
				case 0:
				case 2:
				case 3:
					$this->mode = 'createedit';
					break;
				case 1:
					$this->mode = 'edit';
					break;
				default:
					logError();
			}
			
			$this->serial = (int)$row['serial'];
			$this->charid = (int)$row['charid'];
			$this->name = (string)$row['name'];
			$this->titel = (string)$row['titel'];
			$this->accountid = (int)$row['accountid']; // warum false
			$this->testserver = (int)$row['testserver'];
			$this->accesslevel = (string)$row['accesslevel'];
			$this->chartype = (string)$row['chartype'];
			$this->volk = (int)$row['volk'];
			$this->geschlecht = (string)$row['geschlecht'];
			$this->rp_alter = (int)$row['rp_alter'];
			$this->hautfarbe = (int)$row['hautfarbe'];
			$this->profession_1 = array('id' => (int)$row['profession_1'], 'stufe' => (int)$row['professionsstufe_1']);
			$this->profession_2 = array('id' => (int)$row['profession_2'], 'stufe' => (int)$row['professionsstufe_2']);
			$this->profession_3 = array('id' => (int)$row['profession_3'], 'stufe' => (int)$row['professionsstufe_3']);
			
			$this->verfuegbare_ausbilderpunkte = (int)$row['verfuegbare_ausbilderpunkte'];
			$this->letzter_ausbildungszeitpunkt = (int)$row['letzter_ausbildungszeitpunkt'];
			$this->level = (int)$row['level'];
			$this->xp = (int)$row['xp'];
			$this->leben = (int)$row['leben'];
			$this->forschungspunkte = (int)$row['forschungspunkte'];
			$this->statsteigerungspunkte = (int)$row['statsteigerungspunkte'];
			$this->old_statsteigerungspunkte =(int)$row['statsteigerungspunkte'];
			$this->skillsteigerungspunkte = (int)$row['skillsteigerungspunkte'];
			$this->old_skillsteigerungspunkte = (int)$row['skillsteigerungspunkte'];
			$this->umskillpunkte = (int)$row['umskillpunkte'];
			$this->professionssteigerungspunkte = (int)$row['professionssteigerungspunkte'];
			$this->aktiviert = (int)$row['aktiviert'];
			$this->aktiviert_von = $row['aktiviert_von'];
			$this->aktivierungszeitpunkt = $row['aktivierungszeitpunkt'];
			$this->anzahl_zurueckstellungen = $row['anzahl_zurueckstellungen'];
			$this->charstory = (string)$row['charstory'];
			$this->cst = (int)$row['cst'];
			$this->fix_profil = (string)$row['fix_profil'];
			$this->dyn_profil = (string)$row['dyn_profil'];
			$this->block = (int)$row['block'];
			$this->jailed = (int)$row['jailed'];
			$this->lastchange = $row['lastchange'];
			
			$this->stats = array();
			$this->skills = array();
			$this->old_stats = array();  // here were store old values for checks
			$this->min_stats = array();
			$this->max_stats = array();
			$this->old_skills = array();
			$this->min_skills = array();
			$this->max_skills = array();
			
			// Limits setzen
			$this->setLimits($row);
			$this->gumpid_body= (int)$row['bodyid'];
			
			// Steigern möglich?
			if($this->aktiviert == 1) { 
				$this->gain();
				$this->updateSkillMaximas();
			}
			
			// Startitems
			require_once('lib/char_shopping.cls.php');
			$shop = new CharShopping(450, $this); // TODO
			$sql = sprintf("SELECT * FROM server.startitems WHERE charid = %d", intval($this->charid));
			$result = $MySQL->fetch_complete($sql);
			$listid = 0;
			foreach($result as $item) {
				if($item['herstellungsid']==1898 || $item['herstellungsid']== 1899 || $item['herstellungsid'] == 1900) {
					// Geld
					continue;	
				}
				
				if($item['not_sellable'] == 1) {
					// nichtverkaufbaresstartpaketitem
					$shop->buyItem((int)$item['herstellungsid'], (int)$item['anzahl'], true, true);
				} else {
					$shop->buyItem(intval($item['herstellungsid']),intval($item['anzahl']));
				}
				if($shop->shoppinglist[$item['herstellungsid']]['faerbbar']==1 && $item['farbe'] != $shop->shoppinglist[$item['herstellungsid']]['farbe']) {
					try {
                    	$shop->colourize(intval($item['farbe']), intval($listid));
	                } catch(IException $e) {
						// tue nichts; Gegenstand bleibt ungefärbt
					}
				}
				if($item['equipped'] == 1) {
					assert($item['anzahl']==1);
					$shop->equip(intval($listid));
				}; 
				$listid++;
			}
			
			// Haar & Bart setzen
			$this->setHaar((int)$row['haare']);
			$this->setHaarfarbe((int)$row['haarfarbe']);
			
			$this->setBart((int)$row['bart']);
			$this->setBartfarbe((int)$row['bartfarbe']);
			
		}
		// *** ENDE Initialisierung ***
	}
	
	public function switchToGain() {
		if($this->mode == 'edit' && $this->aktiviert = 1) {
			$this->mode = 'gain';
			return true;
		}
		return false;
	}
	
	public function addItem($itemid, $hexfarbe, $anzahl, $her_info = NULL, $layer = false, $isgump = false) {
		assert(is_int($itemid) AND (is_int($hexfarbe) OR is_null($hexfarbe)) AND is_int($anzahl) AND $anzahl > 0 AND (is_array($her_info) OR is_null($her_info)));
		
		if($layer != false) {
			// soll gleich Equipped werden (z.B. Haare, Bärte)
			assert(is_int($layer) AND ($layer > 0 AND $layer < 25) AND $layer != 21 AND $layer != 15 AND $layer != 9);
			
			$this->equipped_items[$layer] = array('her_info' => $her_info, 'itemid' => $itemid, 'hexfarbe' => $hexfarbe, 'anzahl' => $anzahl, 'isgump' => $isgump);
					
			return(true);
		} else {
			assert(is_array($her_info) AND $her_info['herstellungsid'] > 0);
			$herstellungsid = $her_info['herstellungsid'];
			// suchen, ob item schon im bagpack
			
			if($her_info['stackable']) {
				// stapelbar
				if(count($this->unequipped_items)>0 && isset($value['her_info']['not_sellable']) === false) {
					// es sind schon Produkte im Rucksack
					foreach($this->unequipped_items as $key => $value) {
						// schaue, ob es dieses Item schon gibt
						if($value['her_info']['herstellungsid']!=$her_info['herstellungsid']) continue;
						if(isset($value['her_info']['not_sellable'])) continue;
						// gefunden
						$this->unequipped_items[$key]['anzahl'] += $anzahl;
						return(true);
					}
				}
				// item gibt es noch nicht => lege in Rucksack
				$this->unequipped_items[] = array('her_info' => $her_info, 'itemid' => $itemid, 'hexfarbe' => $hexfarbe, 'anzahl' => $anzahl, 'isgump' => $isgump);					
				return(true);
			} else {
				// Item ist nicht stapelbar
				if($anzahl == 1) {
					$this->unequipped_items[] = array('her_info' => $her_info, 'itemid' => $itemid, 'hexfarbe' => $hexfarbe, 'anzahl' => 1, 'isgump' => $isgump);
					return(true);
				} else {
					// es wurden mehr Items gekauft => diese werden einzeln im Rucksack abgelegt
					for($i=0;$i<$anzahl;$i++) {
						$this->unequipped_items[] = array('her_info' => $her_info, 'itemid' => $itemid, 'hexfarbe' => $hexfarbe, 'anzahl' => 1, 'isgump' => $isgump);
					}
					return(true);
				}
			}
		}
		// sollte nicht passieren
		assert(false);
	}
	
	// Funktion property_exists wohlt erst mit PHP5.1 definiert; liefert im Gegensatz zu isset auch true zurück, wenn die Variablen einen NULL-Wert haben
	private function my_property_exists($property) {
		if(array_key_exists($property, get_class_vars('Char'))) {
			return(true);
		} else {
			return(false);
		}
	}
	
	public function setAktiviert($aktiviert) {
		$this->aktiviert = NULL;
		
		if(is_int($aktiviert)===false) {
			logError();
		}
		
		switch($aktiviert) {
			case 0:
				// zur Freischaltung eingereicht
				$this->aktiviert = 0;
			break;
			case 1:
				// freigeschaltet
				// TODO
				logError();
			break;
			case 2:
				// zurückgestellt
				logError();
			break;
			case 3:
				// nicht eingereicht
				$this->aktiviert = 3;
			break;
			case 9:
				// gelöscht
				logError();
			break;
			default:
				logError();
			break;
		}
	}
	
	// mit dieser Funktion können alle (private) Variablen gelesen werden
	public function get($variable, $index = false) {
		if($this->my_property_exists($variable)) {
			if($index != false) {
				// für $stats und co
				$temp = $this->$variable;
				if(isset($temp[$index])) {
					return($temp[$index]);
				} else {
					logError();
				}
			}
			return($this->$variable);
		} else {
			return(false);
			// TODO: better
		}	
	}
	
	public function setName($name) {
		global $MySQL;
		
		$this->name = NULL;
		
		// Existiert ein Charakter mit dem Namen bereits?
		$sql_ext = '';
		if($this->charid != null) $sql_ext = sprintf(" AND charid != %d", $this->charid);
		$sql = sprintf("SELECT COUNT(*) FROM server.chars WHERE name LIKE '%s' AND testserver = %d AND aktiviert != 9".$sql_ext, me_escape($name), intval($this->testserver)); 
		list( $ncount ) = $MySQL->query_first( $sql );
		if($ncount > 0) {
			throw new IException("Ein Charakter mit diesem Namen existiert bereits!");
		}

		// zulässige Zeichen
		// TODO Elinweths Liste
		
		/*
		 * Hobbits üöäß abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
			Menschen üöäß áâéëêíîóôúû abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
			Ostlinge âêíîôû abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPRSTUVWYZ
			Rohirrim âêíîôûæ abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPRSTUVWYZ
			Dúnedain áâéëêíîóôúû abcdefghiklmnoprstuvwy ABCDEFGHIKLMNOPRSTUY
			Haradrim áâéëêíîóôúû abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPRSTUVWYZ
			Nandor áâéëêíîóôúû abcdefghiklmnoprstuvwy ABCDEFGHIKLMNOPRSTUY 
			Sindar áâéëêíîóôúû abcdefghiklmnoprstuvwy ABCDEFGHIKLMNOPRSTUY
			Noldor áâéëêíîóôúû abcdefghiklmnoprstuvwy ABCDEFGHIKLMNOPRSTUY
			Snagas âêíîôû abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPRSTUVWYZ
			Uruks âêíîôû abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPR
		 */
		
		switch($this->volk) {
			case 0:
				$zeichen = "[a-zA-Z äöüßÄÖÜ]";
				break;
			case 5:
				$zeichen = "[a-zA-Z áâéëêíîóôúûÁÂÄÉÊËÍÎÓÔÚÛ]";
				break;
			case 7:  // noch nicht stimmend
				$zeichen = "[a-zA-Z áâéëêíîóôúûÁÂÄÉÊËÍÎÓÔÚÛ]";
				break;
			case 8:
				$zeichen = "[a-zA-Z üöäßÜÖÄ]";
				break;
			case 9:
				$zeichen = "[a-zA-Z âêíîôûÂÊÍÎÔÛ]";
				break;
			default:	
				$zeichen = "[a-zA-Z äöüßÄÖÜ]";
		}
		
		if(!eregi("^".$zeichen."{3,25}$",$name)) {
			throw new IException('Der Name darf nur Buchstaben und Leerzeichen enthalten und muss 3-16 Zeichen lang sein.');
		}
		
		// nicht zulässige Namen
		$link = $MySQL->query(sprintf("SELECT name FROM server.verbotenenamen WHERE name LIKE '%s'", me_escape($name)));
		if($MySQL->num_rows($link)>0) {
			throw new IException('Dieser Name steht auf der Liste der nicht erlaubten Namen.');
		}
		
		$this->name = me_escape($name);
	}
	
	private function updateSkillMaximas() {
		global $MySQL, $SKILLS;

		if(is_null($this->volk) || $this->aktiviert != 1)
			logError();
			
		// hole Minimas und Maximas aus der Datenbank
		$sql = sprintf("SELECT * FROM server.voelker WHERE id = %d", $this->volk);
		$values = $MySQL->query_first($sql);
		
		for($i=0;$i<count($SKILLS);$i++) {	
			$skill = $SKILLS[$i];
			
			if(isset($values['min_'.$skill])=== false OR isset($values['max_'.$skill]) === false) {
				logError();
			}	
			
			// Maximas entsprechen dem min(Stufe, Völkermaxima)
			$this->max_skills[$skill] = min(getStufenSkillMaximum($this->level), $values['max_'.$skill]);
		}
	}
	
	private function setLimits($char_values = null) {
		global $MySQL, $STATS, $SKILLS;
		
		if(is_null($this->volk))
			logError();
			
		// hole Minimas und Maximas aus der Datenbank
		$sql = sprintf("SELECT * FROM server.voelker WHERE id = %d", $this->volk);
		$values = $MySQL->query_first($sql);
		
		// STATS
		for($i=0;$i<count($STATS);$i++) {
			$stat = $STATS[$i];
			if(isset($values['min_'.$stat])=== false OR isset($values['max_'.$stat]) === false) {				
				logError();
			}	
			
			// setze aktuelle Stats
			if($this->mode == 'create') {
				// old_stats und aktuelle Stats entsprechen dem Minimum des Volkes
				$this->stats[$stat] = $values['min_'.$stat];
			} else {
				assert(!is_null($char_values));
				// stats entsprechen den aktuellen Stats des Chars
				$this->stats[$stat] = $char_values[$stat];
			}
			$this->old_stats[$stat] = $this->stats[$stat];
			
			// setze Minimas
			/*if($this->aktiviert == 1) {
				assert($this->mode == 'gain' || $this->mode == 'edit');
				// Minimas entsprechen den aktuellen Stats
				$this->min_stats[$stat] = $char_values[$stat];
			} else {
				assert($this->mode == 'create' || $this->mode == 'edit' || $this->mode == 'createedit');
				// Minimas entsprechen den Völkerminimas
				$this->min_stats[$stat] = $values['min_'.$stat];
			}*/
			// Minimas aus Völkerminimas
			$this->min_stats[$stat] = $values['min_'.$stat];
			// Maximas aus Völkermaxima)
			$this->max_stats[$stat] = $values['max_'.$stat];
			
			// Test, ob die Stats des Charakters noch den Erfordernissen entspricht
			if($this->max_stats[$stat] < $this->stats[$stat]) {
				logError("Stats liegen außerhalb des zulässigen Bereichs!");
			}
			if($this->stats[$stat] < $this->min_stats[$stat]) {
				logError("Stats liegen unterhalb des zulässigen Bereichs)!");
			}
			
			// Zusicherungen
			assert($this->max_stats[$stat] >= $this->min_stats[$stat]);
			assert($this->old_stats[$stat] == $this->stats[$stat]);
		}
		
		// SKILLS:
		for($i=0;$i<count($SKILLS);$i++) {	
			$skill = $SKILLS[$i];
			
			if(isset($values['min_'.$skill])=== false OR isset($values['max_'.$skill]) === false) {
				logError();
			}	
			
			// setze aktuelle Skills
			if($this->mode == 'create') {
				// aktuelle skills entsprechen dem Minimum des Volkes
				$this->skills[$skill] = $values['min_'.$skill];
			} else {
				assert(!is_null($char_values));
				// skills entsprechen den aktuellen skills des Chars
				$this->skills[$skill] = $char_values[$skill];
			}
			$this->old_skills[$skill] = $this->skills[$skill];
			
			// setze Minimas
			/*
			if($this->aktiviert == 1) {
				assert($this->mode == 'gain' || $this->mode == 'edit');
				// Minimas entsprechen den aktuellen skills
				$this->min_skills[$skill] = $char_values[$skill];
			} else {
				assert($this->mode == 'create' || $this->mode == 'edit' || $this->mode == 'createedit');
				// Minimas entsprechen den Völkerminimas
				$this->min_skills[$skill] = $values['min_'.$skill];
			}*/
			// Minimas entsprechen den Völkerminimas
			$this->min_skills[$skill] = $values['min_'.$skill];
			
			// Maximas entsprechen dem min(Stufe, Völkermaxima)
			$this->max_skills[$skill] = min(getStufenSkillMaximum($this->level), $values['max_'.$skill]);
			
			// Test, ob die Skills des Charakters noch den Erfordernissen entspricht
			if($this->max_skills[$skill] < $this->skills[$skill]) {
				logError("Skills liegen über dem zulässigen Bereichs!");
			}
			if($this->skills[$skill] < $this->min_skills[$skill]) {
				logError("Skills liegen unterhalb des zulässigen Bereichs)!");
			}
			if($this->skills[$skill] > getStufenSkillMaximum($this->level)) {
				logError("Skills liegen außerhalb des für die Stufe zulässigen Bereichs)!");
			}
			
			// Zusicherungen
			assert($this->max_skills[$skill] >= $this->min_skills[$skill]);
			assert($this->old_skills[$skill] == $this->skills[$skill]);
			
		}
	}
	
	
	public function setVolk($volk){
		global $MySQL;
		
		assert($this->mode == 'create');
		
		$this->volk = NULL;
		
		if(is_int($volk)===false)
			logError();
		
		list( $ncount )	= $MySQL->query_first(sprintf("SELECT COUNT(*) as count FROM server.voelker WHERE id = %d AND gesperrt = 0", intval($volk)));
		if($ncount != 1) {
			throw new IException("Dieses Volk ist zur Zeit gesperrt");
		}
		
		$this->volk = $volk;
		$this->setLimits();
	}
	
	public function setGeschlecht($geschlecht){
		global $MySQL;
		
		assert($this->mode == 'create');
		
		$this->geschlecht = NULL;
		
		if(is_null($this->volk) === true)
			logError();
			
		// Zwerge, Snagas und Uruks haben keine weibliche zur Auswahl
		if($geschlecht == 'w' && ($this->volk == 7 || $this->volk == 9 || $this->volk == 10)) {
			throw new IException("Bei diesem Volk kannst du keine weiblichen Chars erstellen.");
		}
		
		if($geschlecht=='w') {
			list($this->gumpid_body) = $MySQL->query_first(sprintf("SELECT gumpid_female_body FROM server.voelker WHERE id = %d", $this->volk));
		} elseif($geschlecht=='m') {
			list($this->gumpid_body) = $MySQL->query_first(sprintf("SELECT gumpid_male_body FROM server.voelker WHERE id = %d", $this->volk));
		} else {
			throw new IException('Ung&uuml;ltiges Geschlecht ausgew&auml;hlt!');
		}
		if(strlen($this->gumpid_body)==0)
			logError();
			
		$this->geschlecht = $geschlecht;
	}
	
	public function setHautfarbe($hautfarbe) {
		global $MySQL;
		
		$this->hautfarbe = NULL;
		
		if(is_int($hautfarbe)===false)
			logError();
			
		if(is_null($this->volk) or is_null($this->geschlecht))
			logError();
			
		$link = $MySQL->query(sprintf("SELECT id FROM server.hautfarben WHERE hexfarbe = %d AND %d_%s = 1", $hautfarbe, $this->volk, $this->geschlecht));
		if($MySQL->num_rows($link)==0) {
			throw new IException('Bitte Hautfarbe auswählen');
		}
		
		$this->hautfarbe = $hautfarbe;
	}
	
	public function setHaar($haar) {
		global $MySQL;
		
		if(is_int($haar)===false)
			logError();
	
		if(is_null($this->volk) or is_null($this->geschlecht))
			logError();
			
		/*if($haar == 0) {
			// Haar soll gelöscht werden
			$this->equipped_items[11] = NULL;
			return;
		}*/
				
		$link = $MySQL->query(sprintf("SELECT id FROM server.haare WHERE gumpid = %d AND %d_%s = 1", intval($haar), $this->volk, $this->geschlecht));
		//TODO aktiviert sauberer
		if($MySQL->num_rows($link)==0 && $this->aktiviert != 1) {
			throw new IException('Bitte gültige Haare auswählen');
		}
		$this->addItem($haar, null, 1, NULL, 11, false);
	}
	
	public function setHaarfarbe($haarfarbe) {
		global $MySQL;

		if(is_int($haarfarbe)===false)
			logError();
			
		if(is_null($this->volk) or is_null($this->geschlecht))
			logError();
			
		// Haar muss vor Farbe gesetzt sein
		if(isset($this->equipped_items[11])===false)
			logError();
		
		
		$link = $MySQL->query(sprintf("SELECT id FROM server.haarfarben WHERE hexfarbe = %d AND %d_%s = 1", intval($haarfarbe), $this->volk, $this->geschlecht));
		if($MySQL->num_rows($link)==0) {
			throw new IException('Bitte Haarfarbe auswählen');	
		}
		$this->equipped_items[11]['hexfarbe'] = $haarfarbe;
	}
	
	public function setBart($bart) {
		global $MySQL;
		
		if(is_int($bart)===false)
			logError();
			
		if(is_null($this->volk) or is_null($this->geschlecht))
			logError();
			
		$link = $MySQL->query(sprintf("SELECT id FROM server.baerte WHERE gumpid = %d AND %d_%s = 1", intval($bart), $this->volk, $this->geschlecht));
		if($MySQL->num_rows($link)==0 AND $this->get('bart') != 0 ) {
			throw new IException('Bitte Bart auswählen');
		}

		if($bart != 0) {
			$this->addItem($bart, null, 1, NULL, 16, false);
		} else {
			if($this->volk == 7) {
				// Zwerge brauchen einen Bart!
				throw new IException('Bitte als Zwerg einen Bart auswählen');
			}
			
			// Bart soll gelöscht werden
			$this->equipped_items[16] = NULL;
		}
	}
	
	public function setBartfarbe($bartfarbe) {
		global $MySQL;
		
		if(is_int($bartfarbe)===false)
			logError();
			
		if(is_null($this->volk) or is_null($this->geschlecht))
			logError();
			
		if(isset($this->equipped_items[16])===false) {
			// hat keinen Bart
			return;
		}
		
	
		$link = $MySQL->query(sprintf("SELECT id FROM server.haarfarben WHERE hexfarbe = %d AND %d_%s = 1", intval($bartfarbe), $this->volk, $this->geschlecht));
		if($MySQL->num_rows($link)==0) {
			throw new IException('Bitte Bartfarbe auswählen');
		}
		
		$this->equipped_items[16]['hexfarbe'] = $bartfarbe;
		
	}
	
	public function setCharstory($charstory){
		global $settings;
		
		$this->charstory = NULL;
		
		if(is_string($charstory) === false) {
			logError();
		}
		$charstory = tools_safeHtml($charstory);
		
		if(strlen($charstory)< $settings['chargen_storylength']) {
			throw new IException('Die Charstory muss mindestens '.$settings['chargen_storylength'].' Zeichen lang sein! Zur Zeit sind es '.strlen($charstory).' Zeichen');
		}
		
		$this->charstory = me_escape($charstory);
	}

	public function setProfession($nr, $id, $stufe) {
		global $MySQL;
						
		// im Moment lassen wir nur 1. Stufe für 1. Profession zu
		// wir schreiben auch nur für Stufe 1 entsprechend Ausbildungseinheiten bei der Freischaltung(!) gut
		if(is_int($nr)===false OR $nr != 1 OR is_int($id)===false OR is_int($stufe) === false or $stufe!=1) {
			logError();
		}
		
		$prof_string = 'profession_'.$nr;
		$this->$prof_string = NULL;	
		
		if($this->aktiviert == 1) {
			logError();
		}
		
		// Wurde überhaupt eine Profession gewählt?
		if($id == 0) {
			throw new IException('Bitte eine Profession als 1. Profession ausw&auml;hlen!');
		}
		
		// Gibt es die Profession überhaupt?
		if($MySQL->num_rows($MySQL->query(sprintf("SELECT id FROM server.professionen WHERE id=%d AND %d_mw = 1", intval($id), $this->volk))) != 1) {
			logError();
		}
		
		// Ist die Profession nur im Moment gesperrt?
		if($MySQL->num_rows($MySQL->query(sprintf("SELECT volk FROM server.anmeldestop WHERE profession=%d AND volk=%d", $id, $this->volk))) != 0) {
			throw new IException('Für die gewählte Volk/Professions-Kombination besteht derzeit ein Anmeldstop.');
		}
		
		$this->$prof_string = array('id' => $id, 'stufe' => $stufe);
	}
	
	public function setSuggestions() {
		global $MySQL, $STATS, $SKILLS;
		
		$empfehlung = $MySQL->query_first(sprintf("SELECT * FROM server.vorgefertigte_chars WHERE profession = %d", $this->get('profession_1', 'id')));

		if(count($empfehlung)==0) {
			logError("Keine Werte für vorgef. Char vorhanden!");
		}
		
		// zuerst die Stats steigern; dann die Skills
		for($i=0;$i<count($STATS);$i++) {
			$curr_stat = $STATS[$i];
			$value = $empfehlung[$curr_stat]+$this->min_stats[$curr_stat];
			$this->updateStat($curr_stat, (int)$value); // wir können hier die Standardfunktion verwenden
		}
		$this->checkUpdatedStats();
		for($i=0;$i<count($SKILLS);$i++) {
			$curr_skill = $SKILLS[$i];
			$this->sug_updateSkills($curr_skill, (int)$empfehlung[$curr_skill]);  // abgewandelte Funktion notwendig
		}
		// es könnten Punkte übrig geblieben sein
		$level = 0;
		while($this->skillsteigerungspunkte>0) {
			switch($level) {
				case 0:
					// versuche eines der schon gesteigerten Attribute noch weiter zu steigern
					for($i=0;$i<count($SKILLS);$i++) {
						$curr_skill = $SKILLS[$i];
						if($empfehlung[$curr_skill]==0) {
							// Skill wurde nicht geändert
							continue;
						}
						$this->sug_updateSkills($curr_skill, 1);  // abgewandelte Funktion notwendig
						if($this->skillsteigerungspunkte==0) break(2); // ganz raus
					}
					$level++;
				break;
				case 1:
					// Steigere identifizieren, naturkunde, orientieren
					for($i=0;$i<5;$i++) {
						$this->sug_updateSkills('identifizieren', 1);
						$this->sug_updateSkills('naturkunde', 1);
						$this->sug_updateSkills('orientierung', 1);
						if($this->skillsteigerungspunkte==0) break(2); // ganz raus	
					}
					$level++;
				break;
				case 2:
					// steigere die erstbesten, die gehen
					for($i=0;$i<count($SKILLS);$i++) {
						$curr_skill = $SKILLS[$i];
						$this->sug_updateSkills($curr_skill, 1);  // abgewandelte Funktion notwendig
						if($this->skillsteigerungspunkte==0) break(2); // ganz raus
					}
					$level++;
				break;
				case 4:
					// sollten wir hierhinkommen, ist alles verloren => sollte aber nicht passieren
					logError();
				break;
			}
		}
		$this->checkUpdatedSkills();
		
	}
	
	public function checkUpdatedStats() {
		global $STATS;
		// diese Funktion muss unbedingt nach dem vollständigen Aktualisieren der Skills aufgerufen werden!
		$differenz = 0;
		for($i=0;$i<count($STATS);$i++) {
			$curr_stat = $STATS[$i];
			// Minimas und Maximas nochmal checken
			// TODO: NULL-CHECK
			if($this->stats[$curr_stat]<$this->min_stats[$curr_stat] || $this->stats[$curr_stat]>$this->max_stats[$curr_stat]) {
				logError();
			}
			
			if($this->mode != 'create' && $this->mode != 'createedit') {
				// neue Stats dürfen nur >= den alten Stats sein
				assert($this->old_stats[$curr_stat] >= $this->min_stats[$curr_stat]);
				if($this->stats[$curr_stat] < $this->old_stats[$curr_stat]) {
					logError("Wurde unter alten Stat gesenkt!");
				}
			}
			// Differenz berechnen
			//echo $stat[1]-$this->old_stats[$stat[0]]
			$differenz += $this->stats[$curr_stat]-$this->old_stats[$curr_stat];
		}
		// Statsteigerungspunkte dürfen nicht größer sein wie vorher und nicht kleiner 0
		if($this->statsteigerungspunkte > $this->old_statsteigerungspunkte OR $this->statsteigerungspunkte < 0) {
			logError();
		}
		// Beim Charerstellen dürfen keine Punkte übrig bleiben!
		if(($this->mode == 'create' OR $this->aktiviert != 1) AND $this->statsteigerungspunkte != 0) {
			logError();
		}
		// alte Punkte - noch verfügbare Punkte - vergebene Punkte != 0 sein=
		//echo $this->old_statsteigerungspunkte."-".$this->statsteigerungspunkte."-".$differenz;
		if($this->old_statsteigerungspunkte-$this->statsteigerungspunkte-$differenz != 0) {
			logError();
		}
		
		return(true);
		
	}
	
	public function updateStat($stat, $new_value) {		
		if(is_int($new_value)===false or $new_value < 0) {
			logError();
		}
		
		if(array_key_exists($stat, $this->stats)==false) {
			logError();
		}
		
		// Stat darf hier auch gesenkt werden! (Überprüfung erst am Ende)
				
		// Stat darf nicht über unterhalb oder überhalb der Limits aus der Datenbank liegen
		if($new_value < $this->min_stats[$stat] or $new_value > $this->max_stats[$stat]) {
			error("Dein Charakter liegt außerhalb der zulässigen Statlimits. Bitte mache einen Bugreport im Forum und Teile vor allem deine Browserversion und die JavaScript-Einstellungen mit.", true);
			// logError();
			// TODO
		}
		
		// Stat darf nicht verfügbare Statsteigerungspunkte überschreiten
		$differenz = $this->stats[$stat]-$new_value; // kann auch negativ sein
		$this->statsteigerungspunkte = $this->statsteigerungspunkte+$differenz;
	
		$this->stats[$stat] = $new_value;
	}
	
	public function checkUpdatedSkills() {
		global $SKILLS;		 
		// diese Funktion muss unbedingt nach dem vollständigen Aktualisieren der Skills aufgerufen werden!
		$differenz = 0;
		for($i=0;$i<count($SKILLS);$i++) {
			$curr_skill = $SKILLS[$i];
			// Minimas und Maximas nochmal checken; sowie checken, dass aktueller Wert nicht unter old_Wert liegt
			
			if($this->skills[$curr_skill]<$this->min_skills[$curr_skill]
				OR $this->skills[$curr_skill]>$this->max_skills[$curr_skill]) {
				logError();
			}
			
			if($this->mode != 'create' && $this->mode != 'createedit') {
				// neue Stats dürfen nur >= den alten Stats sein
				assert($this->old_skills[$curr_skill] >= $this->min_skills[$curr_skill]);
				if($this->skills[$curr_skill] < $this->old_skills[$curr_skill]) {
					logError("Wurde unter alten Skill gesenkt!");
				}
			}
			
			// Differenz berechnen
			$differenz += getNeededSkillingPoints($this->old_skills[$curr_skill],$this->skills[$curr_skill]);	
		}
		
		if($this->skillsteigerungspunkte > $this->old_skillsteigerungspunkte OR $this->skillsteigerungspunkte < 0) {
			logError();
		}
		// Beim Charerstellen dürfen keine Punkte übrig bleiben!
		
		if(($this->mode == 'create' OR $this->aktiviert != 1) AND $this->skillsteigerungspunkte != 0) {
			logError();
		}
		// alte Punkte - noch verfügbare Punkte - vergebene Punkte != 0 sein=
		if($this->old_skillsteigerungspunkte-$this->skillsteigerungspunkte-$differenz != 0) {
			logError();
		}
		
		return(true);
		
	}
	
	private function sug_updateSkills($skill, $points) {
		
		$new_value = $points+$this->skills[$skill];
		 
		if(is_int($new_value)===false or $new_value < 0) {
			logError();
		}

		if(array_key_exists($skill, $this->skills)===false) {
			logError();
		}
	
		// Skill darf hier auch gesenkt werden! (Überprüfung erst am Ende)
		
		// Skill darf nicht über unterhalb oder überhalb der Limits aus der Datenbank liegen
		if($new_value < $this->min_skills[$skill]) {
			return false;
		}
		if($new_value > $this->max_skills[$skill]) {
			// wir probieren es mit $points - 1; Rekursion
			if($points > 1) {
				if($this->sug_updateSkills($skill, $points-1)===true) {
					return true;
				}
			}
			return false;
		}

		// skill darf nicht verfügbare skillsteigerungspunkte überschreiten
		$differenz = getNeededSkillingPoints($new_value,$this->skills[$skill]); // kann auch negativ sein
		
		if($differenz > $this->skillsteigerungspunkte) return false;
		
		//$differenz = $this->skills[$skill]-$new_value; // kann auch negativ sein
		if($this->skillsteigerungspunkte+$differenz <0) return false;
		$this->skillsteigerungspunkte = $this->skillsteigerungspunkte+$differenz;
		
		$this->skills[$skill] = $new_value;
	}
	
	public function updateSkills($skill, $new_value) {
 
		if(is_int($new_value)===false or $new_value < 0) {
			logError();
		}

		if(array_key_exists($skill, $this->skills)===false) {
			logError();
		}
	
		// Skill darf hier auch gesenkt werden! (Überprüfung erst am Ende)
		
		// Skill darf nicht über unterhalb oder überhalb der Limits aus der Datenbank liegen
		if($new_value < $this->min_skills[$skill] or $new_value > $this->max_skills[$skill]) {
			logError();
		}

		// skill darf nicht verfügbare skillsteigerungspunkte überschreiten
		$differenz = getNeededSkillingPoints($new_value,$this->skills[$skill]); // kann auch negativ sein
		
		//$differenz = $this->skills[$skill]-$new_value; // kann auch negativ sein
		$this->skillsteigerungspunkte = $this->skillsteigerungspunkte+$differenz;
		
		$this->skills[$skill] = $new_value;
	}
	

	public function setFixProfil($fix_profil) {
		global $settings;
		
		$this->fix_profil = NULL;
		
		if(is_string($fix_profil)===false) {
			logError();
		}
		
		// TODO: zulässige Zeichen checken
		if(strlen($fix_profil)<$settings['min_fix_profil'] OR strlen($fix_profil)>$settings['max_fix_profil']) {
		//		if(!eregi('^[a-zA-Z äöüßáâéëêíîóôúûæÁÂÄÉÊËÍÎÓÔÚÛ! ";\(\)\*\+,\./:\?_~-]{'.$settings['min_fix_profil'].','.$settings['max_fix_profil'].'}$',$fix_profil)) {
			throw new IException('Das langfristige Profil enthält ungültige Zeichen oder hat nicht die richtige Länge.  Momentan hat es '.strlen($fix_profil).' Zeichen.');
		}
		
		$this->fix_profil = me_escape($fix_profil);  // Achtung: SAFE_HTML bei Ausgabe TODO
	}
	
	public function setDynProfil($dyn_profil) {
		global $settings;
		
		$this->dyn_profil = NULL;
		
		if(is_string($dyn_profil)===false) {
			logError();
		}
		
		// TODO: zulässige Zeichen checken
		if(strlen($dyn_profil)<$settings['min_dyn_profil'] OR strlen($dyn_profil)>$settings['max_dyn_profil']) {
		//if(!eregi('^[a-zA-Z äöüßáâéëêíîóôúûæÁÂÄÉÊËÍÎÓÔÚÛ! ";\(\)\*\+,\./:\?_~-\s]{'.$settings['min_dyn_profil'].','.$settings['max_dyn_profil'].'}$',$dyn_profil)) {
			throw new IException('Das kurzfristige Profil enthält ungültige Zeichen oder hat nicht die richtige Länge. Momentan hat es '.strlen($dyn_profil).' Zeichen.');
		}
		
		$this->dyn_profil = me_escape($dyn_profil);  // Achtung: SAFE_HTML bei Ausgabe TODO
	}
	
	public function setRp_alter($rp_alter) {
		global $settings, $MySQL;	
		
		$this->rp_alter = NULL;
		
		// rp_alter
		if(!eregi("^[0-9]{1,5}$",$rp_alter)) {
				throw new IException("Bitte eine Zahl im Altersfeld eingeben. Z.B. '22' für 22 Jahre alt.");
		}
		
		list ($min_age, $max_age) = $MySQL->query_first(sprintf("SELECT min_age, max_age FROM server.voelker WHERE id = %d", $this->volk));
		if($rp_alter<$min_age OR $rp_alter>$max_age) {
			throw new IException("Das Mindestalter für Chars dieses Volkes beträgt '".$min_age."' Jahre und das Maximalalter '".$max_age."' Jahre. Für jüngere oder ältere Chars mache bitte eine Anfrage an die Spielsystemabteilung.");
		}
		
		$this->rp_alter = $rp_alter;
	}
			
	// Überprüft die Gültigkeit der zusätzlichen RP-Informationen (Textlängen, Pflicht-Fragen, etc.)
	public function checkRpInfos() {
		// TODO
		
		return(true);
		
	}
		
	public function checkFreeChars($accountid, $chartype, $testserver) {
		global $MySQL, $settings;
		$accountid = (int)$accountid; $testserver = (int)$testserver;
		
		// Gesamtanzahl überprüfen
		list($n) = $MySQL->query_first(sprintf("SELECT COUNT(*) FROM server.chars WHERE accountid = %d AND aktiviert != 9 AND testserver = %d", intval($accountid), intval($testserver)));
		if($n > $settings['max_chars'])
			return(false);
		
		// erlaubte Anzahl des chartypes überprüfen
		list($n) = $MySQL->query_first(sprintf("SELECT COUNT(*) FROM server.chars WHERE accountid = %d AND aktiviert != 9 AND testserver = %d AND chartype = '%s'", intval($accountid), intval($testserver), me_escape($chartype)));
		switch($chartype) {
			case 'quest':
				// nichts zu tun, da es keine Limits für Questcharaktere gibt und diese nur von Stafflern erstellt werden können
			break;
			case 'staff':
				list($m) = $MySQL->query_first(sprintf("SELECT max_staffcharacters FROM server.accounts WHERE accountID = %d", intval($accountid)));
			break;
			case 'player':
				list($m) = $MySQL->query_first(sprintf("SELECT max_characters FROM server.accounts WHERE accountID = %d", intval($accountid)));
			break;
		}
		if($n>=$m && $chartype != 'quest')
			return(false);
			
		return(true);
	}
	
	private function cst() {
		// TODO
		$this->cst = 0;
	}
	
	private function forceServerCharUpdate() {
		global $user, $MySQL;
		
		if($this->serial == -1 || $this->aktiviert != 1) {
			return;
		}
		
		assert ($this->serial >0 && is_int($this->serial));
		assert($user->ID > 0);
		
		$sql = sprintf("INSERT INTO server.hp_commands SET testserver = %d, ausfuehrungszeitpunkt = NOW(), befehl = 'charupdate', intparam_0 = %d, absender_account = %d", intval($this->testserver), intval($this->serial), $user->ID);
		
		$MySQL->query($sql);	
	}
	
	public function save($gain = false, $tacheck = false) {
		global $MySQL, $user;
			
		// TODO
		$this->cst();
		
		// Transaktion beginnen
		//if($tacheck===false) $MySQL->query('START TRANSACTION');
		
		if($gain === true) {
			$mode = 'gain';
		} else {
			$mode = $this->mode;
		}
		
		switch($mode) {
			case 'createedit':
			case 'create':
				// Überprüfen, ob immer noch freie Chars vorhanden sind
				if($this->charid != false && is_int($this->charid)) {
					// leere Anweiseung
				} else {
					if($this->checkFreeChars($this->accountid, $this->chartype, $this->testserver)===false) { 
						//$MySQL->query('ROLLBACK;');
						throw new IException('Keine freien Charslots vorhanden!');
					}
				} // TODO
				
				if($this->accountid != $user->ID && $user->can_access('char_admin')===false) {
					//$MySQL->query('ROLLBACK;');
					logError("Nicht berechtigt!");
				}
				if($this->testserver == 1 && $user->can_access('testserver') === false) {
					//$MySQL->query('ROLLBACK;');
					logError("Nicht berechtigt!");
				}
				// TODO bei Charadmin
				
				if(is_int($this->charid)=== false) {
					// Charakter ist ganz frisch
					
					$oldname = $this->get('name');
					
					// Name checken
					try {
						$this->setName($this->get('name'));
					} catch(IException $e) {
						try {
							$this->setName($oldname." Ändern ".chr(rand(65,90)));
						} catch(IException $e) {
							error("Es existiert zwischenzeitlich ein anderer Charakter mit diesem Namen.", true);
						}
						$this->aktiviert = 3;
						//TODO
						$message = "Es existiert noch ein anderer Charakter mit diesem Namen. Bitte prüfen!";
					}
					
					$update = false;
					$sql = "INSERT INTO server.chars SET ";
					// Zusätzliche Informationen, die nur beim Einfügen des Datensatzes gesetzt werden
					$sql.= " accountid = '".$this->accountid."',";
					$sql.= " serial = '-1',";
					$sql.= " accesslevel = '".$this->accesslevel."',";
					$sql.= " testserver = '".$this->testserver."',";
					$sql.= " chartype = '".$this->chartype."',";
					$sql.= " profession_2 = 0,";
					$sql.= " professionsstufe_2 = 0,";
					$sql.= " profession_3 = 0,";
					$sql.= " professionsstufe_3 = 0,";				
					$sql.= " vergebene_ausbilderpunkte = 0,";
					$sql.= " verfuegbare_ausbilderpunkte = 0,";
					$sql.= " letzter_ausbildungszeitpunkt = '0000-00-00 00:00:00',";
					$sql.= " level = 1,";
					$sql.= " xp = 0,";
					$sql.= " leben = '".rand(5,8)."',";
					$sql.= " forschungspunkte = 0,"; 
					$sql.= " statsteigerungspunkte = 0,";
					$sql.= " skillsteigerungspunkte = 0,";
					$sql.= " professionssteigerungspunkte = 0,";
					$sql.= " umskillpunkte = 0,";
					$sql.= " aktiviert_von = NULL,";
					$sql.= " aktivierungszeitpunkt = '0000-00-00 00:00:00',";
					$sql.= " anzahl_zurueckstellungen = 0,";
					$sql.= " block = 0,";
					$sql.= " jailed = 0,";
					$sql.= " volk = '".$this->volk."',";
					$sql.= " geschlecht = '".$this->geschlecht."',";
					if($this->testserver == 1 && $this->chartype == 'staff') {
					$sql.= " accessflags = 0xFFFFF,";					
					}
				} else {
					$update = true;
					$sql = "UPDATE server.chars SET ";
				}
				
				$sql.= " name = '".$this->name."',";
				$sql.= " titel = '".$this->titel."',";
				$sql.= " rp_alter = '".$this->rp_alter."',";
				$sql.= " bodyid = '".$this->gumpid_body."',";
				$sql.= " hautfarbe = '".$this->hautfarbe."',";
				$sql.= " profession_1 = '".$this->get('profession_1','id')."',";
				$sql.= " professionsstufe_1 = '".$this->get('profession_1','stufe')."',";
				$sql.= " aktiviert = '".$this->aktiviert."',";
				$sql.= " charstory = '".$this->charstory."',";
				$sql.= " cst = '".$this->cst."',";
				$sql.= " dyn_profil = '".$this->dyn_profil."',";
				$sql.= " fix_profil = '".$this->fix_profil."',";
				$sql.= " lastchange = NOW(),";
				$sql.= " klugheit = '".$this->get('stats','klugheit')."',";
				$sql.= " intuition = '".$this->get('stats','intuition')."',";
				$sql.= " fingerfertigkeit = '".$this->get('stats','fingerfertigkeit')."',";
				$sql.= " gewandtheit = '".$this->get('stats','gewandtheit')."',";
				$sql.= " konstitution = '".$this->get('stats','konstitution')."',";
				$sql.= " koerperkraft = '".$this->get('stats','koerperkraft')."',";
				$sql.= " fernkampf = '".$this->get('skills','fernkampf')."',";
				$sql.= " klingenwaffen = '".$this->get('skills','klingenwaffen')."',";
				$sql.= " schaftwaffen = '".$this->get('skills','schaftwaffen')."',"; 
				$sql.= " stangenwaffen = '".$this->get('skills','stangenwaffen')."',";
				$sql.= " waffenlos = '".$this->get('skills', 'waffenlos')."',";
				$sql.= " parieren = '".$this->get('skills', 'parieren')."',"; 
				$sql.= " taktik = '".$this->get('skills', 'taktik')."',";
				$sql.= " waffenkunde = '".$this->get('skills', 'waffenkunde')."',";
				$sql.= " ruestungskunde = '".$this->get('skills', 'ruestungskunde')."',";
				$sql.= " alchemie = '".$this->get('skills', 'alchemie')."',";
				$sql.= " giftkunde = '".$this->get('skills', 'giftkunde')."',";
				$sql.= " pflanzenkraeuterkunde = '".$this->get('skills', 'pflanzenkraeuterkunde')."',";  
				$sql.= " anatomie = '".$this->get('skills', 'anatomie')."',";
				$sql.= " identifizieren = '".$this->get('skills', 'identifizieren')."',";
				$sql.= " stofflederkunde = '".$this->get('skills', 'stofflederkunde')."',";
				$sql.= " lebensmittelkunde = '".$this->get('skills', 'lebensmittelkunde')."',";
				$sql.= " werkzeugkunde = '".$this->get('skills', 'werkzeugkunde')."',";
				$sql.= " forstwirtschaft = '".$this->get('skills', 'forstwirtschaft')."',";
				$sql.= " tierzucht = '".$this->get('skills', 'tierzucht')."',";
				$sql.= " naturkunde = '".$this->get('skills', 'naturkunde')."',";
				$sql.= " ackerbau = '".$this->get('skills', 'ackerbau')."',";
				$sql.= " orientierung = '".$this->get('skills', 'orientierung')."',";
				$sql.= " fischen = '".$this->get('skills', 'fischen')."',";
				$sql.= " tierehueten = '".$this->get('skills', 'tierehueten')."',";
				$sql.= " spurenlesen = '".$this->get('skills', 'spurenlesen')."',";
				$sql.= " finden = '".$this->get('skills', 'finden')."',";
				$sql.= " verstecken = '".$this->get('skills', 'verstecken')."',";
				$sql.= " schleichen = '".$this->get('skills', 'schleichen')."',";
				$sql.= " spezialfertigkeit = '".$this->get('skills', 'spezialfertigkeit')."',";
				$sql.= " kartographie = '".$this->get('skills', 'kartographie')."',";
				$sql.= " schreibenzeichnen = '".$this->get('skills', 'schreibenzeichnen')."',";
				$sql.= " musizieren = '".$this->get('skills', 'musizieren')."',";
				$sql.= " kunstfertigkeit = '".$this->get('skills', 'kunstfertigkeit')."',";
				$sql.= " schmecken = '".$this->get('skills', 'schmecken')."',";
				$sql.= " heilen = '".$this->get('skills', 'heilen')."',";
				$sql.= " heilentiere = '".$this->get('skills', 'heilentiere')."',";
				$sql.= " schloesserknacken = '".$this->get('skills', 'schloesserknacken')."',"; 
				$sql.= " taschendiebstahl = '".$this->get('skills', 'taschendiebstahl')."',";
				$sql.= " durchsuchen = '".$this->get('skills', 'durchsuchen')."',";
				$sql.= " schmieden = '".$this->get('skills', 'schmieden')."',";  
				$sql.= " bergbau = '".$this->get('skills', 'bergbau')."',";
				$sql.= " schneidern = '".$this->get('skills', 'schneidern')."',";
				$sql.= " steinbearbeitung = '".$this->get('skills', 'steinbearbeitung')."',";
				$sql.= " feinwerken = '".$this->get('skills', 'feinwerken')."',";
				$sql.= " holzbearbeitung = '".$this->get('skills', 'holzbearbeitung')."',";
				$sql.= " kochenbacken = '".$this->get('skills', 'kochenbacken')."',";
				$sql.= " gravieren = '".$this->get('skills', 'gravieren')."',";
				$sql.= " holzfaellen = '".$this->get('skills', 'holzfaellen')."',";
				$sql.= " entschaerfen = '".$this->get('skills', 'entschaerfen')."',";
				$sql.= " tierezaehmen  = '".$this->get('skills', 'tierezaehmen')."',";
				if(isset($this->equipped_items[11]['itemid'])===true) {
					if($this->equipped_items[11]['itemid']==0) {
						error("Ungültige Haare gewählt!", true);
					}
					$sql.= " haare  = '".$this->equipped_items[11]['itemid']."',";
					$sql.= " haarfarbe  = '".$this->equipped_items[11]['hexfarbe']."',";
				} else {
					logError("Haarbug!");
					$sql.= " haare  = 0,";
					$sql.= " haarfarbe  = 0,";
				}
				if(isset($this->equipped_items[16]['itemid'])) {
					$sql.= " bart = '".$this->equipped_items[16]['itemid']."',";
					$sql.= " bartfarbe = '".$this->equipped_items[16]['hexfarbe']."'";
				} else {
					$sql.= " bart = 0,";
					$sql.= " bartfarbe = 0";
				}
				
				// setze Attribut filed, wenn noch nicht zurückgestellt und eingereicht
				if( $this->mode == 'create') {
					$sql.=", filed = NOW()";
				}
							
				if($update === true) {
					$sql.= sprintf(" WHERE charid = %d AND accountid = %d AND lastchange = '%s' LIMIT 1", $this->charid, $this->accountid, $this->lastchange);
					$MySQL->query($sql);
					if($MySQL->affected_rows()==0) {
						error("Der Charakter konnte leider nicht gespeichert werden, da in der Zwischenzeit Änderungen an deinem Charakter gemacht wurden. Dies kann passieren, wenn du deinen Charakter beispielsweise in zwei Browserfenstern gleichzeitig bearbeitest. Schließe alle Browserfenster und beginne bitte von vorne.", true);
						// TODO: Unset (???)
					}
				} else {
					$MySQL->query($sql);
					// hole Charakterid
					$temp = $MySQL->insert_id();
					if($MySQL->num_rows($MySQL->query("SELECT charid FROM server.chars WHERE charid = '$temp' AND name = '".$this->name."'"))!=1) {
						// Das sollte überhaupt nicht passieren!
						//$MySQL->query('ROLLBACK;');
						throw new IException('Keine freien Charslots vorhanden!');
						logError("Sollte nicht passieren!");
					}
					$this->charid = $temp;
				}
				
				// SPEICHERE ITEMS
				// zuerst alte Löschen und dann neue einfügen
				$MySQL->query("DELETE FROM server.startitems WHERE charid = '".$this->charid."'");
				
				foreach($this->unequipped_items as $item) {
					if(!$item['anzahl'] > 0) {
						// TODO
						assert(true);
						continue;
					}
					$sql = "INSERT INTO server.startitems SET charid = '".$this->charid."', herstellungsid = '".$item['her_info']['herstellungsid']."', farbe = '".$item['hexfarbe']."', anzahl = '".$item['anzahl']."', equipped = 0";
					if(isset($item['her_info']) && isset($item['her_info']['not_sellable'])) {
						$sql.= ", not_sellable = 1";
					}
					$MySQL->query($sql);
				}
				
				foreach($this->equipped_items as $key => $item) {
					if($key == 16 || $key == 11 || isset($item['her_info']['herstellungsid'])===false || is_null($item['her_info']['herstellungsid'])) {
						// Haar oder Bart oder bug
						continue;
					}
					if($item['anzahl'] != 1) {
						// TODO
						assert(true);
						continue;
					}
					
					$sql = "INSERT INTO server.startitems SET charid = '".$this->charid."', herstellungsid = '".$item['her_info']['herstellungsid']."', farbe = '".$item['hexfarbe']."', anzahl = 1, equipped = 1";
					if(isset($item['her_info']) && isset($item['her_info']['not_sellable'])) {
						$sql.= ", not_sellable = 1";
					}
					$MySQL->query($sql);
				}
				
				// Fragebogen sichern
				$MySQL->query(sprintf("UPDATE server.chargen_antworten SET version = 'backup' WHERE version = 'old' AND charid = %d", $this->charid));
				$MySQL->query(sprintf("UPDATE server.chargen_antworten SET version = 'old' WHERE charid = %d AND version = 'current'", $this->charid));
				if($this->aktiviert != 1) {
					$delete = false;
					foreach($_SESSION['chargen']['fragensammlung'] as $frage) {
						if(strlen($frage['antwort'])>0) {
							if ($frage['id'] == 0) {
								echo "1";
								continue;	
							}
							$MySQL->query(sprintf("INSERT INTO server.chargen_antworten VALUES (%d, %d, 'current', '%s')", $frage['id'], $this->charid, tools_safeHtml($frage['antwort'])));
							if($delete === false) {
								$MySQL->query(sprintf("DELETE FROM server.chargen_antworten WHERE charid = %d AND version = 'backup'", $this->charid));
								$delete = true;
							}
						}
					}
					if($delete === false) {
						error('Dein Char wurde verarbeitet. Aber bitte trotzdem Aurax im Forum Bescheid geben mit Deinem Charnamen.', true);	
					}
				}
								
			break;
			case 'edit':
				if(is_int($this->charid) === false) {
					logError();
				}
				
				assert($this->aktiviert == 1);
				
				//$validator = new CharValidator($this);
				//assert($validator->evaluate('activation'));
				//unset($validator);
				
				$sql = "UPDATE server.chars SET ";
				if($user->can_access('char_admin')) {
					// Änderung erfordert Begründung
					$sql.= " name = '".$this->name."',";
					$sql.= " titel = '".$this->titel."',";
					$sql.= " chartype = '".$this->chartype."',";
					$sql.= " hautfarbe = '".$this->hautfarbe."',";
					$sql.= " profession_1 = '".$this->get('profession_1','id')."',";
					$sql.= " professionsstufe_1 = '".$this->get('profession_1','stufe')."',";
					$sql.= " profession_2 = '".$this->get('profession_2','id')."',";
					$sql.= " professionsstufe_2 = '".$this->get('profession_2','stufe')."',";
					$sql.= " profession_3 = '".$this->get('profession_3','id')."',";
					$sql.= " professionsstufe_3 = '".$this->get('profession_3','stufe')."',";
					// TODO
					//$sql.= " ausbildungspunkte = '".$this->ausbildungspunkte."',"; 
					
					$sql.= " verfuegbare_ausbilderpunkte = '".$this->verfuegbare_ausbilderpunkte."',";
					$sql.= " leben = '".$this->leben."',"; 
				}
				
				
				// erfordert Freischaltung
				$sql.= " fix_profil = '".$this->fix_profil."',";
								
				// frei änderbar
				$sql.= " rp_alter = '".$this->rp_alter."',";
				$sql.= " charstory = '".$this->charstory."',";
				// TODO
				// $sql.= " cst = '".$this->cst."',";
				$sql.= " dyn_profil = '".$this->dyn_profil."',";
				$sql.= " lastchange = NOW()";
				
				// TODO: better
				$sql.= " WHERE charid = '".$this->charid."' AND accountid = '".$this->accountid."' AND lastchange = '".$this->lastchange."' LIMIT 1";
				
				$MySQL->query($sql);
				// TODO: Better
				assert($MySQL->affected_rows()==1);						
			// weiter
			case 'gain':
				$sql = "UPDATE server.chars SET ";
			
				$sql.= " level = '".$this->level."',";
				$sql.= " statsteigerungspunkte = '".$this->statsteigerungspunkte."',";
				$sql.= " skillsteigerungspunkte = '".$this->skillsteigerungspunkte."',";
				$sql.= " umskillpunkte = '".$this->umskillpunkte."',";
				$sql.= " forschungspunkte = '".$this->forschungspunkte."',";
				$sql.= " professionssteigerungspunkte = '".$this->professionssteigerungspunkte."',";
				
				$sql.= " klugheit = '".$this->get('stats','klugheit')."',";
				$sql.= " intuition = '".$this->get('stats','intuition')."',";
				$sql.= " fingerfertigkeit = '".$this->get('stats','fingerfertigkeit')."',";
				$sql.= " gewandtheit = '".$this->get('stats','gewandtheit')."',";
				$sql.= " konstitution = '".$this->get('stats','konstitution')."',";
				$sql.= " koerperkraft = '".$this->get('stats','koerperkraft')."',";
				$sql.= " fernkampf = '".$this->get('skills','fernkampf')."',";
				$sql.= " klingenwaffen = '".$this->get('skills','klingenwaffen')."',";
				$sql.= " schaftwaffen = '".$this->get('skills','schaftwaffen')."',"; 
				$sql.= " stangenwaffen = '".$this->get('skills','stangenwaffen')."',";
				$sql.= " waffenlos = '".$this->get('skills', 'waffenlos')."',";
				$sql.= " parieren = '".$this->get('skills', 'parieren')."',"; 
				$sql.= " taktik = '".$this->get('skills', 'taktik')."',";
				$sql.= " waffenkunde = '".$this->get('skills', 'waffenkunde')."',";
				$sql.= " ruestungskunde = '".$this->get('skills', 'ruestungskunde')."',";
				$sql.= " alchemie = '".$this->get('skills', 'alchemie')."',";
				$sql.= " giftkunde = '".$this->get('skills', 'giftkunde')."',";
				$sql.= " pflanzenkraeuterkunde = '".$this->get('skills', 'pflanzenkraeuterkunde')."',";  
				$sql.= " anatomie = '".$this->get('skills', 'anatomie')."',";
				$sql.= " identifizieren = '".$this->get('skills', 'identifizieren')."',";
				$sql.= " stofflederkunde = '".$this->get('skills', 'stofflederkunde')."',";
				$sql.= " lebensmittelkunde = '".$this->get('skills', 'lebensmittelkunde')."',";
				$sql.= " werkzeugkunde = '".$this->get('skills', 'werkzeugkunde')."',";
				$sql.= " forstwirtschaft = '".$this->get('skills', 'forstwirtschaft')."',";
				$sql.= " tierzucht = '".$this->get('skills', 'tierzucht')."',";
				$sql.= " naturkunde = '".$this->get('skills', 'naturkunde')."',";
				$sql.= " ackerbau = '".$this->get('skills', 'ackerbau')."',";
				$sql.= " orientierung = '".$this->get('skills', 'orientierung')."',";
				$sql.= " fischen = '".$this->get('skills', 'fischen')."',";
				$sql.= " tierehueten = '".$this->get('skills', 'tierehueten')."',";
				$sql.= " spurenlesen = '".$this->get('skills', 'spurenlesen')."',";
				$sql.= " finden = '".$this->get('skills', 'finden')."',";
				$sql.= " verstecken = '".$this->get('skills', 'verstecken')."',";
				$sql.= " schleichen = '".$this->get('skills', 'schleichen')."',";
				$sql.= " spezialfertigkeit = '".$this->get('skills', 'spezialfertigkeit')."',";
				$sql.= " kartographie = '".$this->get('skills', 'kartographie')."',";
				$sql.= " schreibenzeichnen = '".$this->get('skills', 'schreibenzeichnen')."',";
				$sql.= " musizieren = '".$this->get('skills', 'musizieren')."',";
				$sql.= " kunstfertigkeit = '".$this->get('skills', 'kunstfertigkeit')."',";
				$sql.= " schmecken = '".$this->get('skills', 'schmecken')."',";
				$sql.= " heilen = '".$this->get('skills', 'heilen')."',";
				$sql.= " heilentiere = '".$this->get('skills', 'heilentiere')."',";
				$sql.= " schloesserknacken = '".$this->get('skills', 'schloesserknacken')."',"; 
				$sql.= " taschendiebstahl = '".$this->get('skills', 'taschendiebstahl')."',";
				$sql.= " durchsuchen = '".$this->get('skills', 'durchsuchen')."',";
				$sql.= " schmieden = '".$this->get('skills', 'schmieden')."',";  
				$sql.= " bergbau = '".$this->get('skills', 'bergbau')."',";
				$sql.= " schneidern = '".$this->get('skills', 'schneidern')."',";
				$sql.= " steinbearbeitung = '".$this->get('skills', 'steinbearbeitung')."',";
				$sql.= " feinwerken = '".$this->get('skills', 'feinwerken')."',";
				$sql.= " holzbearbeitung = '".$this->get('skills', 'holzbearbeitung')."',";
				$sql.= " kochenbacken = '".$this->get('skills', 'kochenbacken')."',";
				$sql.= " gravieren = '".$this->get('skills', 'gravieren')."',";
				$sql.= " holzfaellen = '".$this->get('skills', 'holzfaellen')."',";
				$sql.= " entschaerfen = '".$this->get('skills', 'entschaerfen')."',";
				$sql.= " tierezaehmen  = '".$this->get('skills', 'tierezaehmen')."'";
				
				
				$sql.= sprintf(" WHERE charid = %d AND accountid = %d LIMIT 1", $this->charid, $this->accountid);
				
				$MySQL->query($sql);
				$tacheck = true;
				//$MySQL->query('COMMIT');
				// Server zur Aktualisierung veranlassen
				$this->forceServerCharUpdate();
				$message = "Steigern wurde durchgeführt!";
			break;
			default:
				logError();
			break;
		}
		
		
				
		// Besucher zur Validierung holen
		
		// Transaktion commiten
		//if($tacheck===false) $MySQL->query('COMMIT');
		// Nachricht
		if(isset($message)=== false && $this->aktiviert == 0) { 
			$message = "Dein Charakter wurde zur Freischaltung eingereicht. Du kannst ihn noch jederzeit bearbeiten.";
		} elseif(isset($message)=== false && $this->aktiviert == 3) {
			$message = "Dein Charakter wurde gespeichert. Du musst ihn bearbeiten, wenn Du ihn zur Freischaltung einreichen möchtest!";
		} 
		
		return($message);
		
	}
	
	public function gain() {
		global $MySQL;
		assert($this->aktiviert == 1);
		
		// TODO: better; Gain bei normalem Bearbeiten.
		
		if($this->aktiviert != 1) {
			throw new IException("Dein Charakter ist nicht aktiviert.");
		}
		
		// Beginne mit Transaktion
		//$MySQL->query('START TRANSACTION');
		
		// aktualisiere Stufe und xp aus der Datenbank, so dass wir ggf. ganz aktuell sind
		list ( $dbxp, $dblevel ) = $MySQL->query_first(sprintf("SELECT xp, level FROM server.chars WHERE charid = %d AND aktiviert = 1", $this->charid));
		if($dbxp < $this->xp || $dblevel < $this->level) {
			//$MySQL->query("ROLLBACK");
			LogError("Ungültige Exp oder Level");
		}
		$this->xp = intval($dbxp);
		$this->level = intval($dblevel);
		
		// 1. Checke, welche Stufen mit den Erfahrungspunkten erreicht wurden
		$result = $MySQL->fetch_complete(sprintf("SELECT * FROM server.experience WHERE level > %d AND xp <= %d ORDER BY level", $this->level, $this->xp));
		
		// Debug-Sicherheitscheck
		if($this->old_statsteigerungspunkte != $this->statsteigerungspunkte || 
			$this->old_skillsteigerungspunkte != $this->skillsteigerungspunkte) {
			
			//$MySQL->query("ROLLBACK");
			LogError("Ungültige Steigerungspunkte");
		}
		
		foreach($result as $stufenitem) {
			assert($this->level<$stufenitem['level']);
			assert($this->xp >= $stufenitem['xp']);
			// 2. Erhöhe Stufe	
			$this->level = intval($stufenitem['level']);
			
			// 3. Schreibe forschungspunkte, statsteigerungspunkte, skillsteigerungspunkte, umskillpunkte, professionssteigerungspunkte gut
			$this->statsteigerungspunkte += intval($stufenitem['statsteigerungspunkte']);
			$this->skillsteigerungspunkte += intval($stufenitem['skillsteigerungspunkte']);
			$this->professionssteigerungspunkte += intval($stufenitem['professionssteigerungspunkte']);
			$this->forschungspunkte += intval($stufenitem['forschungspunkte']);
			$this->umskillpunkte += intval($stufenitem['umskillpunkte']);
		}
		
		// Alte Statsteigerungspunkte aktualisieren:
		$this->old_statsteigerungspunkte = $this->statsteigerungspunkte;
		$this->old_skillsteigerungspunkte = $this->skillsteigerungspunkte; 
		
		// Speichern
		if(count($result)>0) {
			// Speichere Charakter		
			$this->save(true, true);
			
			//Speichere in char-Archiv
			$MySQL->query(sprintf("INSERT INTO server.char_archive (SELECT * FROM server.chars WHERE charid = %d)", $this->charid));	
		}
		
		
		// Committe Transaktion 	
		//$MySQL->query('COMMIT');
	}
}
