include library.npc; include library.skill; include library.utils; include library.city; include library.jedi; include library.chat; include library.prose; include library.anims; include library.sui; include library.money; include library.xp; include library.fs_quests; include library.features; const string VAR_SKILL_TEMPLATE = "npcSkillTemplate"; //conversation constants: const string CONVONAME = "skill_teacher"; const string CONVOFILE = "skill_teacher"; const string JEDI_TRAINER = "jedi_trainer"; const string JEDI_TRAINER_LIGHT = "jedi_trainer_light"; const string JEDI_TRAINER_DARK = "jedi_trainer_dark"; const string SKILL_N = "skl_n"; const string SKILL_D = "skl_d"; const string SKILL_T = "skl_t"; const string SCRIPT_NPC_CONVERSE = "npc.converse.npc_converse_menu"; const string FACETO_VOLUME_NAME = "faceToTriggerVolume"; /* const string_id[] OPT_DEFAULT = { new string_id(CONVOFILE, "opt1_1"), //skill that speaker can learn new string_id(CONVOFILE, "opt1_2"), //skills that the speaker qualifies for based on skills but not xp new string_id(CONVOFILE, "opt1_3"), //skills the player does not have new string_id(CONVOFILE, "opt1_4") //all skills the teacher has }; */ const string_id[] OPT_DEFAULT = { new string_id(CONVOFILE, "opt1_1"), //skill that speaker can learn new string_id(CONVOFILE, "opt1_2") //skills that the speaker qualifies for based on skills but not xp }; const string_id[] OPT_YES_BACK = { new string_id(CONVOFILE, "yes"), new string_id(CONVOFILE, "back") }; const string_id[] OPT_YES_NO = { new string_id(CONVOFILE, "yes"), new string_id(CONVOFILE, "no") }; const string TBL = "datatables/skill/skills.iff"; //const string COL_COST = "MONEY_REQUIRED"; const string_id PROSE_NSF = new string_id(CONVOFILE, "prose_nsf"); const string_id PROSE_PAY = new string_id(CONVOFILE, "prose_pay"); const string_id PROSE_SKILL_LEARNED = new string_id(CONVOFILE, "prose_skill_learned"); const string_id PROSE_TRAIN_FAILED = new string_id(CONVOFILE, "prose_train_failed"); const string_id SID_TRAINING_COST_REFUNDED = new string_id(CONVOFILE, "training_cost_refunded"); const string_id SID_ALREADY_HAVE_THIS_SKILL = new string_id(CONVOFILE, "already_have_this_skill"); const string_id SID_DO_NOT_HAVE_SKILL = new string_id(CONVOFILE, "do_not_have_skill"); const int STATUS_UNKNOWN = -1; const int STATUS_NONE = 0; const int STATUS_LEARN = 1; const int STATUS_INFO = 2; trigger OnInitialize() { string teacherType = getStringObjVar (self, "trainer"); if ( teacherType != null ) { if ( teacherType.equals("trainer_shipwright") ) { setCondition (self, CONDITION_CONVERSABLE); setCondition( self, CONDITION_SPACE_INTERESTING ); } else setCondition (self, CONDITION_CONVERSABLE); } createTriggerVolume( FACETO_VOLUME_NAME, 8.0f, true ); return SCRIPT_CONTINUE; } trigger OnObjectMenuRequest(obj_id player, menu_info mi) { int mnu = mi.addRootMenu (menu_info_types.CONVERSE_START, null); menu_info_data mdata = mi.getMenuItemById(mnu); mdata.setServerNotify(false); setCondition (self, CONDITION_CONVERSABLE); return SCRIPT_CONTINUE; } trigger OnIncapacitated(obj_id killer) { clearCondition(self, CONDITION_CONVERSABLE); clearCondition(self, CONDITION_SPACE_INTERESTING); detachScript( self, "npc.skillteacher.skillteacher" ); return SCRIPT_CONTINUE; } trigger OnAttach() { if ( !utils.hasScriptVar(self, skill.SCRIPTVAR_SKILLS) ) { string tbl_trainer_skills = "datatables/npc_customization/skill_table.iff"; string teacherType = getStringObjVar (self, "trainer"); string[] skillList = dataTableGetStringColumnNoDefaults (tbl_trainer_skills, teacherType); if ( teacherType != null && (!teacherType.equals("") && teacherType == "trainer_creaturehandler") ) { if (!hasScript(self, "systems.pet_tradein.pet_tradein")) attachScript (self, "systems.pet_tradein.pet_tradein"); } if ( skillList == null || skillList.length == 0 ) { detachScript(self, "npc.skillteacher.skillteacher"); return SCRIPT_OVERRIDE; } else { if ( teacherType != null ) { if ( teacherType.equals("trainer_shipwright") ) { setCondition( self, CONDITION_SPACE_INTERESTING ); setCondition (self, CONDITION_CONVERSABLE); } else setCondition (self, CONDITION_CONVERSABLE); } utils.setBatchScriptVar(self, skill.SCRIPTVAR_SKILLS, skillList); } skillList = dataTableGetStringColumnNoDefaults (tbl_trainer_skills, "trainer_jedi"); if ( skillList != null && skillList.length > 0 ) utils.setBatchScriptVar(self, skill.SCRIPTVAR_JEDI_SKILLS, skillList); } createTriggerVolume( FACETO_VOLUME_NAME, 8.0f, true ); return SCRIPT_CONTINUE; } trigger OnStartNpcConversation (obj_id speaker) { if (hasObjVar (speaker, "jedi.usingSui")) { string_id strSpam = new string_id("jedi_spam", "cant_train_while_converting"); chat.chat(self, speaker, strSpam, chat.ChatFlag_targetOnly); return SCRIPT_CONTINUE; } int city_id = getCityAtLocation( getLocation( self ), 0 ); if ( cityExists( city_id ) && city.isCityBanned( speaker, city_id ) ) { sendSystemMessage( speaker, new string_id( "city/city", "city_banned" ) ); return SCRIPT_CONTINUE; } string trainerType = "trainer_unknown"; if ( hasObjVar(self, "trainer") ) trainerType = getStringObjVar (self, "trainer"); if ( trainerType.equals( "trainer_shipwright" ) && !features.isSpaceEdition( speaker ) ) { doAnimationAction( self, "thumbs_down" ); sendSystemMessage( speaker, new string_id( "skill_teacher", "requires_jtl" ) ); chat.publicChat( self, speaker, new string_id( "skill_teacher", "too_complicated" ) ); return SCRIPT_CONTINUE; } faceTo( self, speaker ); if ( !checkSkillStatus(self, speaker) ) return SCRIPT_CONTINUE; if(jedi.isJediTrainerForPlayer(speaker, self)) { if(hasObjVar(speaker, "jedi.intFindNewTrainer")) { messageTo(speaker, "findNewTrainer", null, 0, false); string_id strSpam = new string_id("jedi_spam", "not_your_trainer"); chat.chat(self, speaker, strSpam, chat.ChatFlag_targetOnly); return SCRIPT_CONTINUE; } } string_id msg = new string_id(CONVOFILE, trainerType); //nunya if ( isJedi(speaker) && jedi.isJediTrainerForPlayer(speaker, self) ) { string jedi_convo = getJediConvoFile(speaker); if ( jedi_convo != null && !jedi_convo.equals("") ) msg = new string_id(jedi_convo, "greeting"); else msg = new string_id(JEDI_TRAINER, "greeting"); } npcStartConversation (speaker, self, CONVONAME, msg, OPT_DEFAULT); return SCRIPT_CONTINUE; } trigger OnEndNpcConversation(obj_id speaker) { utils.removeScriptVar(speaker, self.toString()); removeObjVar(self, "confirmTeach." + speaker); return SCRIPT_CONTINUE; } trigger OnNpcConversationResponse (string convoName, obj_id speaker, string_id sid_response) { string trainerType = getStringObjVar (self, "trainer"); if ( trainerType != null ) { if ( trainerType.equals( "trainer_shipwright" ) && !features.isSpaceEdition( speaker ) ) { doAnimationAction( self, "thumbs_down" ); sendSystemMessage( speaker, new string_id( "skill_teacher", "requires_jtl" ) ); chat.publicChat( self, speaker, new string_id( "skill_teacher", "too_complicated" ) ); return SCRIPT_CONTINUE; } } if (!convoName.equals(CONVONAME) && !convoName.equals(JEDI_TRAINER)) return SCRIPT_CONTINUE; if ( !checkSkillStatus(self, speaker) ) return SCRIPT_CONTINUE; string tbl = sid_response.getTable(); string response = sid_response.getAsciiId(); //debugSpeakMsg(self, "response = " + response); int status = utils.getIntScriptVar(speaker, self.toString()); //debugSpeakMsg(self, getName(speaker) + " status = " + status); boolean checkArray = true; string convo = CONVOFILE; if ( isJedi(speaker) && jedi.isJediTrainerForPlayer(speaker, self) ) { string jedi_convo = getJediConvoFile(speaker); if ( jedi_convo != null && !jedi_convo.equals("") ) convo = jedi_convo; } if ( tbl.equals(SKILL_N) ) { string_id msg = new string_id(CONVOFILE, "msg3_1"); //default boolean skillGranted = false; switch ( status ) { case STATUS_LEARN: string[] qualifiedSkills = skill.getQualifiedTeachableSkills(speaker, self); if ( qualifiedSkills == null ) { //doAnimationAction( self, anims.PLAYER_COUGH_HEAVY ); } else { if ( utils.getElementPositionInArray(qualifiedSkills, response) > -1 ) { string_id sid_skillName = new string_id(SKILL_N,response); // DISABLED int cost = 1; // dataTableGetInt(TBL, response, COL_COST); float skillMod = getEnhancedSkillStatisticModifier (speaker, "force_persuade"); skillMod = skillMod * .01f; float discount = cost * skillMod; cost = cost - (int)discount; boolean newbieTraining = hasObjVar( speaker, "newbie.hasSkill" ); if ( hasObjVar( speaker, "newbie.trained" ) ) newbieTraining = false;//already been trained, so if ( cost > 0 && (!newbieTraining) ) { int totalMoney = getTotalMoney(speaker); if ( totalMoney < cost ) { //insufficient funds prose_package pp = prose.getPackage(PROSE_NSF, sid_skillName, cost); sendSystemMessageProse(speaker, pp); } else { // DISABLED int ptsLeft = 0; //int ptsLeft = getRemainingSkillPoints(speaker); int ptsCost = 1; //int ptsCost = getSkillPointCost(response); if ( ptsLeft < ptsCost ) { int diff = ptsCost - ptsLeft; string_id PROSE_NSF_SKILL_PTS = new string_id(convo, "nsf_skill_pts"); prose_package ppNsfSkillPts = prose.getPackage(PROSE_NSF_SKILL_PTS, sid_skillName, diff); npcSpeak( speaker, ppNsfSkillPts); npcSetConversationResponses(speaker, OPT_DEFAULT); return SCRIPT_CONTINUE; } string ovPath = "confirmTeach." + speaker; setObjVar(self, ovPath + ".sid_skillname", sid_skillName); setObjVar(self, ovPath + ".cost", cost); string_id PROSE_COST = new string_id(convo, "prose_cost"); prose_package ppConfirm = prose.getPackage(PROSE_COST, sid_skillName, cost); npcSpeak( speaker, ppConfirm); npcSetConversationResponses(speaker, OPT_YES_NO); return SCRIPT_CONTINUE; } npcSpeak( speaker, new string_id(convo, "msg1_1")); npcSetConversationResponses(speaker, OPT_DEFAULT); return SCRIPT_CONTINUE; } else { // DISABLED int ptsLeft = 0; //int ptsLeft = getRemainingSkillPoints(speaker); int ptsCost = 1; //int ptsCost = getSkillPointCost(response); if ( ptsLeft < ptsCost ) { int diff = ptsCost - ptsLeft; string_id PROSE_NSF_SKILL_PTS = new string_id(convo, "nsf_skill_pts"); prose_package ppNsfSkillPts = prose.getPackage(PROSE_NSF_SKILL_PTS, sid_skillName, diff); npcSpeak( speaker, ppNsfSkillPts); npcSetConversationResponses(speaker, OPT_DEFAULT); return SCRIPT_CONTINUE; } //debugSpeakMsg(self, "speaker(" + speaker + ":" + getFirstName(speaker) + ") attempting to purchase skill: " + response); if ( completeSkillPurchase(speaker, response) ) { /*string_id success = new string_id (CONVOFILE, "success"); chat.chat (self, null, null, success);*/ if ( response.equals("jedi_light_side_journeyman_novice") || response.equals("jedi_dark_side_journeyman_novice") ) { npcSpeak( speaker, new string_id(JEDI_TRAINER, "chosen_path")); npcEndConversation(speaker); setObjVar(speaker, "jedi.intFindNewTrainer", 1); return SCRIPT_CONTINUE; } msg = new string_id(convo, "msg3_2"); doAnimationAction( self, anims.PLAYER_FC_WINK ); if(response=="combat_bountyhunter_novice") { // hOlocron hooks dictionary dctParams = new dictionary(); dctParams.put("eventName","BountyHunterNoviceGranted"); messageTo(speaker, "handleHolocronEvent", dctParams, 0, false); } skillGranted = true; if ( hasObjVar( speaker, "newbie.hasSkill" ) ) { setObjVar( speaker, "newbie.trained", true );//<- do not remove this objvar! } if ( hasSurpassedTrainer(self, speaker) ) { prose_package ppFarewell = prose.getPackage(new string_id(convo, "surpass_trainer"), speaker); string chatType = chat.getChatType(self); string moodType = chat.getChatMood(self); npcSpeak(speaker, ppFarewell); npcEndConversation(speaker); return SCRIPT_CONTINUE; } } else { msg = new string_id(convo, "error_grant_skill"); doAnimationAction( self, anims.PLAYER_SHRUG_HANDS ); } } } } break; case STATUS_INFO: msg = new string_id(CONVOFILE, "msg3_3"); string[] skillData = getSkillData(response, speaker); if ( (skillData != null) && (skillData.length > 0) ) { //debugSpeakMsg(self, "skillData.length = " + skillData.length); if ( utils.getElementPositionInArray(getSkillListingForPlayer(speaker), response) > -1 ) { sui.listbox(speaker, utils.packStringId(SID_ALREADY_HAVE_THIS_SKILL), "@" + tbl + ":" + response, skillData); } else { prose_package ppDoNotHaveSkill = prose.getPackage(SID_DO_NOT_HAVE_SKILL); prose.setTO(ppDoNotHaveSkill, new string_id("skl_d", response)); string prompt = " \0" + packOutOfBandProsePackage(null, ppDoNotHaveSkill); sui.listbox(speaker, prompt, "@" + tbl + ":" + response, skillData); } } break; default: break; } npcSpeak( speaker, msg ); npcSetConversationResponses(speaker, OPT_DEFAULT); return SCRIPT_CONTINUE; } else { resizeable string_id[] opt = utils.concatArrays(null, OPT_DEFAULT); string[] skills = null; string_id msg = new string_id(convo, "msg2_1"); //default if (response.equals("opt1_1")) //qualified teachable skills { msg = new string_id(convo, "msg2_1"); //debugSpeakMsg(self,"bark"); skills = skill.getQualifiedTeachableSkills(speaker, self); utils.setScriptVar(speaker, self.toString(), STATUS_LEARN); } else if (response.equals("opt1_2")) //teachable skills { msg = new string_id(convoName, "msg2_2"); skills = skill.getTeachableSkills(speaker, self); utils.setScriptVar(speaker, self.toString(), STATUS_INFO); } /*else if (response.equals("opt1_3")) //teacher delta { msg = new string_id(convoName, "msg2_3"); skills = skill.deltaTeacherSkills(speaker, self); } else if (response.equals("opt1_4")) //all teacher skills { msg = new string_id(convoName, "msg2_4"); skills = getSkillListingForPlayer(self); }*/ else if (response.equals("yes")) // yes, wants to train... { string ovPath = "confirmTeach." + speaker; if ( hasObjVar(self, ovPath) ) { string_id sid_skillName = getStringIdObjVar(self, ovPath + ".sid_skillname"); int cost = getIntObjVar(self, ovPath + ".cost"); if ( sid_skillName != null && cost > 0 ) { //attempt payment prose_package pp = prose.getPackage(PROSE_PAY, sid_skillName, cost); sendSystemMessageProse(speaker, pp); dictionary d = new dictionary(); d.put("skillName", sid_skillName.getAsciiId()); money.requestPayment(speaker, self, cost, "attemptedPayment", d, true); } removeObjVar(self, ovPath); } msg = new string_id(convo, "msg_yes"); checkArray = false; utils.removeScriptVar(speaker, self.toString()); } else if ( response.equals("no") ) { msg = new string_id(convo, "msg_no"); checkArray = false; utils.removeScriptVar(speaker, self.toString()); } else { checkArray = false; utils.removeScriptVar(speaker, self.toString()); } if ( (checkArray) && ((skills == null) || (skills.length == 0)) ) { //change message msg = new string_id(convo, "error_empty_category"); } else if ( !checkArray ) { } else { opt.clear(); for ( int i = 0; i < skills.length; i++ ) { opt = utils.addElement(opt, new string_id(SKILL_N, skills[i])); } opt = utils.addElement(opt, new string_id(CONVOFILE, "back")); } npcSpeak( speaker, msg ); npcSetConversationResponses(speaker, opt); return SCRIPT_CONTINUE; } } messageHandler attemptedPayment() { if ( (params == null) || (params.isEmpty()) ) { return SCRIPT_CONTINUE; } int retCode = money.getReturnCode(params); if ( retCode != money.RET_SUCCESS ) { return SCRIPT_CONTINUE; } obj_id player = params.getObjId(money.DICT_PLAYER_ID); if ( !isIdValid(player) || (!isPlayer(player)) ) { return SCRIPT_CONTINUE; } string skillName = params.getString("skillName"); if ( (skillName == null) || (skillName.equals("")) ) { return SCRIPT_CONTINUE; } int cost = params.getInt(money.DICT_TOTAL); if ( completeSkillPurchase(player, skillName) ) { money.bankTo(self, money.ACCT_SKILL_TRAINING, cost); } else { prose_package ppCostRefunded = prose.getPackage(SID_TRAINING_COST_REFUNDED); prose.setDI(ppCostRefunded, cost); sendSystemMessageProse(player, ppCostRefunded); money.bankTo(self, player, cost); } return SCRIPT_CONTINUE; } boolean completeSkillPurchase(obj_id player, string skillName) { if ( !isIdValid(player) || (!isPlayer(player)) ) { return false; } if ( (skillName == null) || (skillName.equals("")) ) { return false; } boolean learned = true; prose_package pp; if ( skill.purchaseSkill(player, skillName) ) { pp = prose.getPackage(PROSE_SKILL_LEARNED, new string_id(SKILL_N, skillName)); // If this is a force sensitive skill, check to see if the player has fulfilled his 6 branch requirement. if (fs_quests.isVillageEligible(player)) { if (!hasObjVar(player, fs_quests.VAR_VILLAGE_COMPLETE)) { if (skillName.indexOf("force_sensitive_") > -1) { //LOG("quest", "branches ->" + fs_quests.getBranchesLearned(player)); if (fs_quests.getBranchesLearned(player) >= 6) { setObjVar(player, fs_quests.VAR_VILLAGE_COMPLETE, 1); CustomerServiceLog("fs_quests", "%TU has completed the village by attaining six FS skill branches.", player, null); } } } } } else { pp = prose.getPackage(PROSE_TRAIN_FAILED, new string_id(SKILL_N, skillName)); learned = false; } sendSystemMessageProse(player, pp); return learned; } string[] getSkillData(string skillName, obj_id player) { if ( skillName.equals("") ) { return null; } resizeable string[] ret = new string[0]; //int cost = dataTableGetInt(TBL, skillName, COL_COST); //float skillMod = getEnhancedSkillStatisticModifier (player, "force_persuade"); //skillMod = skillMod * .01f; //float discount = cost * skillMod; //cost = cost - (int)discount; //ret = utils.addElement(ret, "MONETARY COST"); //ret = utils.addElement(ret, " " + cost + " credits"); //ret = utils.addElement(ret, "SKILL POINT COST"); //ret = utils.addElement(ret, " " + getSkillPointCost(skillName) + " points"); ret = utils.addElement(ret, "REQUIRED SKILLS"); string[] skillReqs = getSkillPrerequisiteSkills(skillName); if ( skillReqs == null ) { ret = utils.addElement(ret, " none"); } else { for (int i = 0; i < skillReqs.length; i++ ) { string sName = getString(new string_id("skl_n", skillReqs[i])); ret = utils.addElement(ret, " " + sName); } } //ret = utils.addElement(ret, " "); ret = utils.addElement(ret, "XP COSTS"); dictionary xpReqs = getSkillPrerequisiteExperience(skillName); if ( (xpReqs == null) || (xpReqs.isEmpty()) ) { ret = utils.addElement(ret, " none"); } else { java.util.Enumeration xp = xpReqs.keys(); while (xp.hasMoreElements()) { string xpType = (string)xp.nextElement(); string sXp = getString(new string_id("exp_n", xpType)); ret = utils.addElement(ret, " " + sXp + " = " + xpReqs.getInt(xpType)); } } return ret; } boolean checkSkillStatus(obj_id trainer, obj_id player) { if ( !isIdValid(trainer) || !isIdValid(player) ) return false; if ( isJedi(player) ) { if ( jedi.isJediTrainerForPlayer(player, trainer) ) return true; //else //{ // chat.chat (trainer, player, new string_id(JEDI_TRAINER, "no_teach_jedi"), chat.ChatFlag_targetOnly); // npcEndConversation(player); // return false; //} } obj_id self = trainer; obj_id speaker = player; string[] pSkills = getSkillListingForPlayer(speaker); string[] tSkills = skill.getTeacherSkills(trainer, speaker); if ( tSkills == null || tSkills.length == 0 ) { //debugSpeakMsg(self, "hrmmm... can't decide what my skills are!"); return false; } string convo = CONVOFILE; if ( jedi.isJediTrainerForPlayer(player, trainer) ) convo = JEDI_TRAINER; string[] lowSkills = getSkillPrerequisiteSkills(tSkills[0]); if ( lowSkills != null && lowSkills.length > 0 ) { if ( !utils.isSubset(pSkills, lowSkills) ) { string_id msg = new string_id(convo, "no_qualify"); //player too low for this trainer! chat.chat(self, speaker, msg, chat.ChatFlag_targetOnly); npcEndConversation(speaker); resizeable string[] entries = new string[0]; for ( int i = 0; i < lowSkills.length; i++ ) { entries = utils.addElement(entries, "@skl_n:" + lowSkills[i]); } if ( entries != null && entries.length > 0 ) { string title = "@skill_teacher:no_qualify_title"; string prompt = "@skill_teacher:no_qualify_prompt"; sui.listbox(self, player, prompt, sui.OK_ONLY, title, entries, "noHandler"); } return false; } } if ( utils.isSubset(pSkills, tSkills) ) { string_id msg = new string_id(convo, "topped_out"); //player has surpassed teacher! chat.chat(self, speaker, msg, chat.ChatFlag_targetOnly); npcEndConversation(speaker); return false; } // DISABLED //if ( (getRemainingSkillPoints(speaker) < 1) && (!isJedi(speaker)&&(jedi.isJediTrainerForPlayer(player, trainer) ))) //{ string_id msg = new string_id(convo, "no_skill_pts"); //player has no skill points! chat.chat(self, speaker, msg, chat.ChatFlag_targetOnly); npcEndConversation(speaker); return false; //} //return true; } boolean hasSurpassedTrainer(obj_id trainer, obj_id player) { string[] pSkills = getSkillListingForPlayer(player); string[] tSkills = skill.getTeacherSkills(trainer, player); if ( tSkills == null || tSkills.length == 0 ) { //debugSpeakMsg(self, "hrmmm... can't decide what my skills are!"); return false; } return utils.isSubset(pSkills, tSkills); } //const string FACETO_VOLUME_NAME = "faceToTriggerVolume"; //createTriggerVolume( FACETO_VOLUME_NAME, 8.0f, true ); trigger OnTriggerVolumeEntered( string volumeName, obj_id breacher ) { if ( !isPlayer( breacher ) ) { //debugSpeakMsg( self, "you are not a player" ); return SCRIPT_CONTINUE; } if ( volumeName != FACETO_VOLUME_NAME ) return SCRIPT_CONTINUE; if ( isInNpcConversation(self) ) return SCRIPT_CONTINUE; if ( canSee( self, breacher ) ) faceTo( self, breacher ); return SCRIPT_CONTINUE; } string getJediConvoFile(obj_id player) { if ( !isIdValid(player) || !isJedi(player) ) return null; if ( hasSkill(player, "jedi_light_side_journeyman_novice") ) return JEDI_TRAINER_LIGHT; if ( hasSkill(player, "jedi_dark_side_journeyman_novice") ) return JEDI_TRAINER_DARK; return JEDI_TRAINER; }