<?php
/**
 * Change Class
 * https://webenginecms.org/
 * 
 * @version 1.1.0
 * @author Lautaro Angelico <http://lautaroangelico.com/>
 * @copyright (c) 2013-2025 Lautaro Angelico, All Rights Reserved
 * @build w641ba01901cb0925ec50f543e59acbd
 */

/**
 * Error Glossary:
 *      error-1 = magic list could not be cleared
 *      error-2 = inventory could not be cleared
 *      error-3 = quests could not be cleared
 *      error-4 = change class query failed
 */

namespace Plugin\ChangeClass;

class ChangeClass {
	
	private $_configFile = 'config.json';
	private $_modulesPath = 'modules';
	private $_classes;
	
	private $_active;
	private $_minimumLevel;
	private $_minimumMasterLevel;
	private $_minimumResets;
	private $_requiredZen;
	private $_creditConfig;
	private $_creditCost;
	private $_wipeInventory;
	private $_wipeSkills;
	private $_wipeQuests;
	private $_giveBackPoints;
	private $_classPoints;
	
	private $_userid;
	private $_username;
	private $_character;
	private $_newClass;
	
	private $_usercpmenu = array(
		array(
			'active' => true,
			'type' => 'internal',
			'phrase' => 'changeclass_title',
			'link' => 'usercp/changeclass',
			'icon' => 'usercp_default.png',
			'visibility' => 'user',
			'newtab' => false,
			'order' => 999,
		),
	);
	
	private $mu;
	private $configFilePath;
	
	// CONSTRUCTOR
	
	function __construct() {
		global $custom;
		
		// load database
		$this->mu = \Connection::Database('MuOnline');
		
		// load configs
		$this->configFilePath = __PATH_CHANGECLASS_ROOT__ . $this->_configFile;
		if(!file_exists($this->configFilePath)) throw new \Exception(lang('changeclass_error_2'));
		$cfgFile = file_get_contents($this->configFilePath);
		if(!$cfgFile) throw new \Exception(lang('changeclass_error_2'));
		$cfg = json_decode($cfgFile, true);
		if(!is_array($cfg)) throw new \Exception(lang('changeclass_error_2'));
		
		$this->_active = $cfg['active'];
		$this->_minimumLevel = $cfg['minimum_level'];
		$this->_minimumMasterLevel = $cfg['minimum_master'];
		$this->_minimumResets = $cfg['minimum_resets'];
		$this->_requiredZen = $cfg['required_zen'];
		$this->_creditConfig = $cfg['credit_config'];
		$this->_creditCost = $cfg['credit_cost'];
		$this->_wipeInventory = $cfg['wipe_inventory'];
		$this->_wipeSkills = $cfg['wipe_skills'];
		$this->_wipeQuests = $cfg['wipe_quests'];
		$this->_giveBackPoints = $cfg['give_back_points'];
		$this->_classPoints = $cfg['class_points'];
		
		// character classes
		$this->_classes = $custom['character_class'];
	}
	
	// PUBLIC FUNCTIONS
	
	public function loadModule($module) {
		if(!\Validator::Alpha($module)) throw new \Exception(lang('changeclass_error_3'));
		if(!$this->_moduleExists($module)) throw new \Exception(lang('changeclass_error_3'));
		if(!@include_once(__PATH_CHANGECLASS_ROOT__ . $this->_modulesPath . '/' . $module . '.php')) throw new \Exception(lang('changeclass_error_3'));
	}
	
	public function setUserid($userid) {
		$this->_userid = $userid;
	}
	
	public function setUsername($username) {
		$this->_username = $username;
	}
	
	public function setCharacter($character) {
		$this->_character = $character;
	}
	
	public function getRequiredLevel() {
		return $this->_minimumLevel;
	}
	
	public function getRequiredMasterLevel() {
		return $this->_minimumMasterLevel;
	}
	
	public function getRequiredResets() {
		return $this->_minimumResets;
	}
	
	public function getRequiredZen() {
		return $this->_requiredZen;
	}
	
	public function getRequiredCredits() {
		return $this->_creditCost;
	}
	
	public function getAccountCharacters() {
		if(!check_value($this->_username)) throw new \Exception(lang('changeclass_error_7'));
		$Character = new \Character();
		$AccountCharacters = $Character->AccountCharacter($this->_username);
		if(!is_array($AccountCharacters)) throw new \Exception(lang('error_46'));
		return $AccountCharacters;
	}
	
	public function getCharacterClasses() {
		$classGroups = array();
		foreach($this->_classes as $key => $row) {
			if(in_array($row['class_group'], $classGroups)) continue;
			$result[$key] = $row;
			$classGroups[] = $row['class_group'];
		}
		return $result;
	}
	
	public function wipeInventoryEnabled() {
		if($this->_wipeInventory == 1) return true;
		return;
	}
	
	public function wipeSkillsEnabled() {
		if($this->_wipeSkills == 1) return true;
		return;
	}
	
	public function wipeQuestsEnabled() {
		if($this->_wipeQuests == 1) return true;
		return;
	}
	
	public function setNewClass($id) {
		if(!\Validator::UnsignedNumber($id)) throw new \Exception(lang('changeclass_error_9'));
		if(!array_key_exists($id, $this->getCharacterClasses())) throw new \Exception(lang('changeclass_error_9'));
		$this->_newClass = $id;
	}
	
	public function performClassChange() {
		if(!$this->_active) throw new \Exception(lang('changeclass_error_22'));
		if(!check_value($this->_username)) throw new \Exception(lang('changeclass_error_7'));
		if(!check_value($this->_character)) throw new \Exception(lang('changeclass_error_10'));
		if(!check_value($this->_newClass)) throw new \Exception(lang('changeclass_error_9'));
		if(!$this->_giveBackPoints) {
			if(!array_key_exists($this->_newClass, $this->_classPoints)) throw new \Exception(lang('changeclass_error_11'));
			$levelUpPoints = $this->_classPoints[$this->_newClass];
		}
		
		// new class base stats
		if(!is_array($this->_classes[$this->_newClass]['base_stats'])) throw new \Exception(lang('changeclass_error_21'));
		$newClassBaseStats = $this->_classes[$this->_newClass]['base_stats'];
		
		// account data
		$Account = new \Account();
		
		// check online status
		if($Account->accountOnline($this->_username)) throw new \Exception(lang('error_14'));
		
		// character data
		$Character = new \Character();
		$characterData = $Character->characterData($this->_character);
		if(!is_array($characterData)) throw new \Exception(lang('changeclass_error_12'));
		
		// check class
		if($this->_newClass == $characterData[_CLMN_CHR_CLASS_]) throw new \Exception(lang('changeclass_error_6'));
		
		// master level data
		if(defined('_TBL_MASTERLVL_')) {
			$characterMasterLvlData = _TBL_CHR_ != _TBL_MASTERLVL_ ? $Character->getMasterLevelInfo($this->_character) : $characterData;
		}
		
		// level requirement
		if($this->_minimumLevel > 0) {
			if($characterData[_CLMN_CHR_LVL_] < $this->_minimumLevel) throw new \Exception(lang('changeclass_error_13'));
		}
		
		// master level requirement
		if($this->_minimumMasterLevel > 0) {
			if(defined('_TBL_MASTERLVL_')) {
				if($characterMasterLvlData[_CLMN_ML_LVL_] < $this->_minimumMasterLevel) throw new \Exception(lang('changeclass_error_14'));
			}
		}
		
		// resets requirement
		if($this->_minimumResets > 0) {
			if($characterData[_CLMN_CHR_RSTS_] < $this->_minimumResets) throw new \Exception(lang('changeclass_error_15'));
		}
		
		// level up points
		if($this->_giveBackPoints) {
			$baseStats = array_sum($this->_classes[$characterData[_CLMN_CHR_CLASS_]]['base_stats']);
			$playerStats = $characterData[_CLMN_CHR_STAT_STR_]+$characterData[_CLMN_CHR_STAT_AGI_]+$characterData[_CLMN_CHR_STAT_VIT_]+$characterData[_CLMN_CHR_STAT_ENE_]+$characterData[_CLMN_CHR_STAT_CMD_];
			$levelUpPoints = $characterData[_CLMN_CHR_LVLUP_POINT_]+$playerStats-$baseStats;
		}
		
		// credit cost check
		if($this->_creditCost > 0 && $this->_creditConfig != 0) {
			$creditSystem = new \CreditSystem();
			$creditSystem->setConfigId($this->_creditConfig);
			$configSettings = $creditSystem->showConfigs(true);
			switch($configSettings['config_user_col_id']) {
				case 'userid':
					$creditSystem->setIdentifier($this->_userid);
					break;
				case 'username':
					$creditSystem->setIdentifier($this->_username);
					break;
				case 'character':
					$creditSystem->setIdentifier($this->_character);
					break;
				default:
					throw new \Exception(lang('changeclass_error_4'));
			}
			
			if($creditSystem->getCredits() < $this->_creditCost) throw new \Exception(lang('changeclass_error_16'));
		}
		
		// zen requirement
		if($this->_requiredZen > 0) {
			if($characterData[_CLMN_CHR_ZEN_] < $this->_requiredZen) throw new \Exception(lang('error_34'));
			if(!$Character->DeductZEN($this->_character, $this->_requiredZen)) throw new \Exception(lang('error_34'));
		}
		
		// clear magic list
		if($this->_wipeSkills) {
			// reset skill tree
			if(defined('_TBL_MASTERLVL_')) {
				$this->_resetSkillTree($characterData, $characterMasterLvlData);
			}
			
			// clear magic list
			$clearSkills = $this->mu->query("UPDATE "._TBL_CHR_." SET "._CLMN_CHR_MAGIC_L_." = null WHERE "._CLMN_CHR_NAME_." = ?", array($this->_character));
			if(!$clearSkills) throw new \Exception(lang('changeclass_error_17'));
		}
		
		// clear inventory
		if($this->_wipeInventory) {
			$wipeInventory = $this->mu->query("UPDATE "._TBL_CHR_." SET "._CLMN_CHR_INV_." = null WHERE "._CLMN_CHR_NAME_." = ?", array($this->_character));
			if(!$wipeInventory) throw new \Exception(lang('changeclass_error_18'));
		}
		
		// clear quests
		if($this->_wipeQuests) {
			$wipeQuests = $this->mu->query("UPDATE "._TBL_CHR_." SET "._CLMN_CHR_QUEST_." = null WHERE "._CLMN_CHR_NAME_." = ?", array($this->_character));
			if(!$wipeQuests) throw new \Exception(lang('changeclass_error_19'));
		}
		
		// subtract credits
		if($this->_creditCost > 0 && $this->_creditConfig != 0) {
			$creditSystem->subtractCredits($this->_creditCost);
		}
		
		// change class
		$query = "UPDATE "._TBL_CHR_." SET ";
		$query .= ""._CLMN_CHR_CLASS_." = ?, ";
		$query .= ""._CLMN_CHR_LVLUP_POINT_." = ?, ";
		$query .= ""._CLMN_CHR_STAT_STR_." = ?, ";
		$query .= ""._CLMN_CHR_STAT_AGI_." = ?, ";
		$query .= ""._CLMN_CHR_STAT_VIT_." = ?, ";
		$query .= ""._CLMN_CHR_STAT_ENE_." = ?, ";
		$query .= ""._CLMN_CHR_STAT_CMD_." = ? ";
		$query .= "WHERE "._CLMN_CHR_NAME_." = ?";
		
		// execute query
		$result = $this->mu->query($query, array($this->_newClass, $levelUpPoints, $newClassBaseStats['str'], $newClassBaseStats['agi'], $newClassBaseStats['vit'], $newClassBaseStats['ene'], $newClassBaseStats['cmd'], $this->_character));
		if(!$result) throw new \Exception(lang('changeclass_error_20'));
		
		// redirect
		redirect(3, __CHANGECLASS_HOME__ . 'success/1');
	}
	
	public function checkPluginUsercpLinks() {
		if(!is_array($this->_usercpmenu)) return;
		$cfg = loadConfig('usercp');
		if(!is_array($cfg)) return;
		foreach($cfg as $usercpMenu) {
			$usercpLinks[] = $usercpMenu['link'];
		}
		foreach($this->_usercpmenu as $pluginMenuLink) {
			if(in_array($pluginMenuLink['link'],$usercpLinks)) continue;
			$cfg[] = $pluginMenuLink;
		}
		usort($cfg, function($a, $b) {
			return $a['order'] - $b['order'];
		});
		$usercpJson = json_encode($cfg, JSON_PRETTY_PRINT);
		$cfgFile = fopen(__PATH_CONFIGS__.'usercp.json', 'w');
		if(!$cfgFile) throw new \Exception(lang('changeclass_error_5'));
		fwrite($cfgFile, $usercpJson);
		fclose($cfgFile);
	}
	
	// PRIVATE FUNCTIONS
	
	private function _moduleExists($module) {
		if(!check_value($module)) return;
		if(!file_exists(__PATH_CHANGECLASS_ROOT__ . $this->_modulesPath . '/' . $module . '.php')) return;
		return true;
	}
	
	private function _resetSkillTree($characterData, $characterMasterLvlData) {
		
		// combined character level
		$characterLevel = $characterData[_CLMN_CHR_LVL_]+$characterMasterLvlData[_CLMN_ML_LVL_];
		
		// skill enhancement tree points
		$skillEnhancementPoints = 0;
		
		// skill enhancement support
		if(defined('_CLMN_ML_I4SP_')) {
			$skillEnhancementTreeEnabled = array_key_exists(_CLMN_ML_I4SP_, $characterMasterLvlData) ? true : false;
		}
		
		// skill enhancement points
		if($skillEnhancementTreeEnabled) {
			if($characterLevel > 800) {
				$skillEnhancementPoints = $characterLevel-800;
			}
		}
		
		// data
		$data = array(
			'player' => $this->_character,
			'masterpoints' => $characterMasterLvlData[_CLMN_ML_LVL_]-$skillEnhancementPoints,
		);
		
		if($skillEnhancementTreeEnabled && $skillEnhancementPoints > 0) {
			$data['skillenhancementpoints'] = $skillEnhancementPoints;
		}
		
		// query
		$query = "UPDATE "._TBL_MASTERLVL_." SET "._CLMN_ML_POINT_." = :masterpoints";
		if(defined('_CLMN_ML_EXP_')) if(array_key_exists(_CLMN_ML_EXP_, $characterMasterLvlData)) $query .= ", "._CLMN_ML_EXP_." = 0";
		if(defined('_CLMN_ML_NEXP_')) if(array_key_exists(_CLMN_ML_NEXP_, $characterMasterLvlData)) $query .= ", "._CLMN_ML_NEXP_." = 0";
		if($skillEnhancementTreeEnabled && $skillEnhancementPoints > 0) $query .= ", "._CLMN_ML_I4SP_." = :skillenhancementpoints";
		$query .= " WHERE "._CLMN_ML_NAME_." = :player";
		
		// clear master skill tree
		$clearMasterSkillTree = $this->mu->query($query, $data);
		if(!$clearMasterSkillTree) throw new \Exception(lang('error_21'));
	}
	
}