/***********************************************************************/ /** Player skills are divided into trees/groups (sword, alchemy, signs). /** Each skill tree has its ring levels (reflecting the rings in GUI, where /** the innermost ring is the highest one). In order to learn a skill of /** Nth level you must know at least one skill on the (N-1)th level (you /** can always learn ring 1 skills). The lowest ring level is 1. /** Some skills have a ring level of -1 -> this means that this is a skill /** that is learned automatically without spending skill points (e.g. some /** mastery skill). /** /** Skills are grouped in skill definitions. A definition holds information /** about which skills are available, to which trees and rings they belong /** etc. Basically if you would like to make several character classes then /** each of them would use a different skill definition (e.g. mage, warrior). /** Each definition can make it's own skill placement so for example skill A /** might be a sword tree ring 2 skill in warriors definition and a combat /** tree ring 5 skill in rogue definition. /** This manager loads whole definition and caches it - that's all the skill /** data you need for this player object. /** /** This can be used in multiplayer, addons or for replacers. /***********************************************************************/ /** Copyright © 2012-2014 /** Author : Tomek Kozera /** Bartosz Bigaj /***********************************************************************/ class W3PlayerAbilityManager extends W3AbilityManager { private saved var skills : array; //all skills in all skill trees private saved var resistStatsItems : array>; //holds cached resist stats from items private saved var toxicityOffset : float; //mutagens, locked percent of max toxicity private var pathPointsSpent : array; //amount of skillpoints spent in each skill path private saved var skillSlots : array; //skill slots for skills chosen by player protected saved var skillAbilities : array; //cached list of non-blocked non-GlobalPassive skill abilities private var totalSkillSlotsCount : int; //amount of skill slots private var tempSkills : array; //list of temporarily added skills private saved var mutagenSlots : array; //list of mutagen slots private var temporaryTutorialSkills : array; //temp skills added for duration of mutagens tutorial in character panel private saved var ep1SkillsInitialized : bool; private saved var ep2SkillsInitialized : bool; private const var LINK_BONUS_BLUE, LINK_BONUS_GREEN, LINK_BONUS_RED : name; //ability added on link color match private saved var isMutationSystemEnabled : bool; //if system was unlocked through quest private saved var equippedMutation : EPlayerMutationType; //currently active mutation private saved var mutations : array< SMutation >; //all mutations private saved var mutationUnlockedSlotsIndexes : array< int >; //array holding indexes of skillSlots that can be unlocked via Master Mutation private saved var mutationSkillSlotsInitialized : bool; //if mutation skill slots were initialized private const var MUTATION_SKILL_GROUP_ID : int; //group id for skill slots unlocked by Mutation System default LINK_BONUS_BLUE = 'SkillLinkBonus_Blue'; default LINK_BONUS_GREEN = 'SkillLinkBonus_Green'; default LINK_BONUS_RED = 'SkillLinkBonus_Red'; default ep1SkillsInitialized = false; default ep2SkillsInitialized = false; public final function Init(ownr : CActor, cStats : CCharacterStats, isFromLoad : bool, diff : EDifficultyMode) : bool { var skillDefs : array; var i : int; isInitialized = false; if(!ownr) { LogAssert(false, "W3PlayerAbilityManager.Init: owner is NULL!!!!"); return false; } else if(!( (CPlayer)ownr )) { LogAssert(false, "W3PlayerAbilityManager.Init: trying to create for non-player object!! Aborting!!"); return false; } //array init resistStatsItems.Resize(EnumGetMax('EEquipmentSlots')+1); pathPointsSpent.Resize(EnumGetMax('ESkillPath')+1); //add default player character ability ownr.AddAbility(theGame.params.GLOBAL_PLAYER_ABILITY); if(!super.Init(ownr,cStats, isFromLoad, diff)) return false; LogChannel('CHR', "Init W3PlayerAbilityManager "+isFromLoad); // init skills if(!isFromLoad) { InitSkillSlots(); //set skill definitions skillDefs = charStats.GetAbilitiesWithTag('SkillDefinitionName'); LogAssert(skillDefs.Size()>0, "W3PlayerAbilityManager.Init: actor <<" + owner + ">> has no skills!!"); for(i=0; i; var skillDefs : array; var fixedSomething : bool; skillDefs = charStats.GetAbilitiesWithTag('SkillDefinitionName'); LogAssert(skillDefs.Size()>0, "W3PlayerAbilityManager.Init: actor <<" + owner + ">> has no skills!!"); fixedSomething = false; for( i = 0; i < skillDefs.Size(); i+=1 ) CacheSkills(skillDefs[i], newSkills); for(i=0; i= skills.Size()) { skills.PushBack( newSkills[i] ); fixedSomething = true; continue; } //missing skill in the middle of array if(skills[i].skillType == S_SUndefined && newSkills[i].skillType != S_SUndefined) { skills[i] = newSkills[i]; fixedSomething = true; } } return fixedSomething; } public function OnOwnerRevived() { var i : int; super.OnOwnerRevived(); if(owner == GetWitcherPlayer()) GetWitcherPlayer().RemoveTemporarySkills(); } private final function PrecacheModifierSkills() { var i, j : int; var dm : CDefinitionsManagerAccessor; var skill : SSkill; var skillIT : int; dm = theGame.GetDefinitionsManager(); if( !dm ) { return; } for( skillIT = 0; skillIT < skills.Size(); skillIT += 1 ) { //skill = skills[ skillIT ]; for( i = 0; i < skills.Size(); i += 1 ) { if( i != skillIT ) { for( j = 0; j < skills[ skillIT ].modifierTags.Size(); j += 1) { //if skill has modifier tag if( dm.AbilityHasTag( skills[ i ].abilityName, skills[ skillIT ].modifierTags[ j ] ) ) { skills[ skillIT ].precachedModifierSkills.PushBack( i ); } } } } } } // Called after Init() when other managers are initialized (since effect manager is and must be initialized after ability manager) public final function PostInit() { var i, playerLevel : int; if(CanUseSkill(S_Sword_5)) AddPassiveSkillBuff(S_Sword_5); //fill skill slot availability if( (W3PlayerWitcher)owner ) { playerLevel = ((W3PlayerWitcher)owner).GetLevel(); for(i=0; i= skillSlots[i].unlockedOnLevel); } } } public final function GetPlayerSkills() : array //#B //cache indexes of skill slots unlocked via mutation system isMutationSystemEnabled = true; { return skills; } public final function AddTempNonAlchemySkills() : array { var i, cnt, j : int; var ret : array; var temp : SSimpleSkill; tempSkills.Clear(); for(i=0; i= 1) owner.AddEffectDefault(EET_BattleTrance, owner, "BattleTranceSkill"); } private final function ReloadAcquiredSkills(out acquiredSkills : array) { var i, j : int; for(j=acquiredSkills.Size()-1; j>=0; j-=1) { for(i=0; i= 1 && !owner.HasBuff(EET_BattleTrance)) { if(CanUseSkill(S_Sword_5)) owner.AddEffectDefault(EET_BattleTrance, owner, "BattleTranceSkill"); } if ( points >= owner.GetStatMax(BCS_Focus) && owner.HasAbility('Runeword 8 _Stats', true) && !owner.HasBuff(EET_Runeword8) ) { owner.AddEffectDefault(EET_Runeword8, owner, "max focus"); } } // Called when Vitality Stat current value has changed protected final function OnVitalityChanged() { var vitPerc : float; vitPerc = GetStatPercents(BCS_Vitality); if(vitPerc < theGame.params.LOW_HEALTH_EFFECT_SHOW && !owner.HasBuff(EET_LowHealth)) owner.AddEffectDefault(EET_LowHealth, owner, 'vitality_change'); else if(vitPerc >= theGame.params.LOW_HEALTH_EFFECT_SHOW && owner.HasBuff(EET_LowHealth)) owner.RemoveBuff(EET_LowHealth); if(vitPerc < 1.f) ResetOverhealBonus(); theTelemetry.SetCommonStatFlt(CS_VITALITY, GetStat(BCS_Vitality)); } // Called when Air Stat current value has changed protected final function OnAirChanged() { if(GetStat(BCS_Air) > 0) { if ( owner.HasBuff(EET_Drowning) ) owner.RemoveBuff(EET_Drowning); if( owner.HasBuff(EET_Choking) ) owner.RemoveBuff(EET_Choking); } } // Called when Toxicity Stat current value has changed protected final function OnToxicityChanged() { var tox : float; if( !((W3PlayerWitcher)owner) ) return; tox = GetStat(BCS_Toxicity); //apply toxicity debuff if(tox == 0 && owner.HasBuff(EET_Toxicity)) owner.RemoveBuff(EET_Toxicity); else if(tox > 0 && !owner.HasBuff(EET_Toxicity)) owner.AddEffectDefault(EET_Toxicity,owner,'toxicity_change'); theTelemetry.SetCommonStatFlt(CS_TOXICITY, GetStat(BCS_Toxicity)); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////// ---=== @MUTAGENS ===--- ///////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public final function GetPlayerSkillMutagens() : array { return mutagenSlots; } public final function GetSkillGroupIdOfMutagenSlot(eqSlot : EEquipmentSlots) : int { var i : int; i = GetMutagenSlotIndex(eqSlot); if(i<0) return -1; return mutagenSlots[i].skillGroupID; } //returns true if given mutagen slot is unlocked and can be used public final function IsSkillMutagenSlotUnlocked(eqSlot : EEquipmentSlots) : bool { var i : int; i = GetMutagenSlotIndex(eqSlot); if(i<0) return false; if( equippedMutation != EPMT_None ) { return false; } */ return ((W3PlayerWitcher)owner).GetLevel() >= mutagenSlots[i].unlockedAtLevel; } private final function GetMutagenSlotForGroupId(groupID : int) : EEquipmentSlots { var i : int; for(i=0; i= 0 && idx ; owner.GetInventory().GetItemContainedAbilities(mutagenItemId, abs); for(i=0; i; var item : SItemUniqueId; //get skills' colors for(i=0; i; var item : SItemUniqueId; //get skills' colors for(i=0; i 0) { costReduction = GetSkillAttributeValue(ability, 'stamina_cost_reduction_after_1', false, false) * reductionCounter; ret -= costReduction; } return CalculateAttributeValue(ret); } public final function GetSkillAttributeValue(abilityName: name, attributeName : name, addBaseCharAttribute : bool, addSkillModsAttribute : bool) : SAbilityAttributeValue { // OPTIMIZE var min, max, ret : SAbilityAttributeValue; var i, j : int; var dm : CDefinitionsManagerAccessor; var skill : SSkill; var skillEnum : ESkill; var skillLevel : int; //value from skill ability ret = super.GetSkillAttributeValue(abilityName, attributeName, addBaseCharAttribute, addSkillModsAttribute); //bonus from other skills that modify this value if(addSkillModsAttribute) { //find skill/perk/bookperk structure for given ability skillEnum = SkillNameToEnum( abilityName ); if( skillEnum != S_SUndefined ) { skill = skills[skillEnum]; } else { LogAssert(false, "W3PlayerAbilityManager.GetSkillAttributeValue: cannot find skill for ability <<" + abilityName + ">>! Aborting"); return min; } dm = theGame.GetDefinitionsManager(); for( j = 0; j < skill.precachedModifierSkills.Size(); j += 1 ) { i = skill.precachedModifierSkills[ j ]; if( CanUseSkill( skills[i].skillType ) ) { dm.GetAbilityAttributeValue(skills[i].abilityName, attributeName, min, max); skillLevel = GetSkillLevel(i); ret += GetAttributeRandomizedValue( min * skillLevel, max * skillLevel ); } } } //value from character stats if(addBaseCharAttribute) { ret += GetAttributeValueInternal(attributeName); } return ret; } protected final function GetStaminaActionCostInternal(action : EStaminaActionType, isPerSec : bool, out cost : SAbilityAttributeValue, out delay : SAbilityAttributeValue, optional abilityName : name) { var attributeName : name; var skill : ESkill; super.GetStaminaActionCostInternal(action, isPerSec, cost, delay, abilityName); if(isPerSec) { attributeName = theGame.params.STAMINA_COST_PER_SEC_DEFAULT; } else { attributeName = theGame.params.STAMINA_COST_DEFAULT; } if(action == ESAT_LightAttack && CanUseSkill(S_Sword_1) ) cost += GetSkillAttributeValue(SkillEnumToName(S_Sword_1), attributeName, false, true); else if(action == ESAT_HeavyAttack && CanUseSkill(S_Sword_2) ) cost += GetSkillAttributeValue(SkillEnumToName(S_Sword_2), attributeName, false, true); else if ((action == ESAT_Sprint || action == ESAT_Jump) && thePlayer.HasBuff(EET_Mutagen24) && !thePlayer.IsInCombat()) { cost.valueAdditive = 0; cost.valueBase = 0; cost.valueMultiplicative = 0; } //level 3 blizzard potion removes stamina cost if you also have battle trance and maxed focus if(thePlayer.HasBuff(EET_Blizzard) && owner == GetWitcherPlayer() && GetWitcherPlayer().GetPotionBuffLevel(EET_Blizzard) == 3 && thePlayer.HasBuff(EET_BattleTrance) && GetStatPercents(BCS_Focus) == 1) { cost.valueAdditive = 0; cost.valueBase = 0; cost.valueMultiplicative = 0; } } /* TK - apparently not wanted currently - commenting out entire final function //returns action's stamina cost and delay public final function GetStaminaActionCost(action : EStaminaActionType, out cost : float, out delay : float, optional fixedCost : float, optional fixedDelay : float, optional abilityName : name, optional dt : float, optional costMult : float) { super.GetStaminaActionCost(action, cost, delay, fixedCost, fixedDelay, abilityName, dt, costMult); if(dt == 0) { //round up to full stamina segments if not a continuous mode //cost = CeilF(cost / theGame.params.STAMINA_SEGMENT_SIZE) * theGame.params.STAMINA_SEGMENT_SIZE; //i commented it because it was taking 10 stamina instead of 0.2.../ PF - which is as intended... } } */ /* Returns list of skill related abilities that the character has: - only known skills - only unblocked abilities - all abilities having one of the tags passed (can be empty) */ protected final function GetNonBlockedSkillAbilitiesList( optional tags : array ) : array { var i, j : int; var ret : array; var dm : CDefinitionsManagerAccessor; var abilityName : name; if(tags.Size() == 0) return ret; dm = theGame.GetDefinitionsManager(); for(i=0; i 0 && cooldown <= skills[skill].remainingBlockedTime) ) return false; //already locked for good or locked for longer //lock if(cooldown > 0) skills[skill].remainingBlockedTime = cooldown; else skills[skill].remainingBlockedTime = -1; //find next timer call time min = 1000000; for(i=0; i 0) { min = MinF(min, skills[i].remainingBlockedTime); } } //schedule next update if(min != 1000000) GetWitcherPlayer().AddTimer('CheckBlockedSkills', min, , , , true); //also block skill's ability if(theGame.GetDefinitionsManager().IsAbilityDefined(skills[skill].abilityName) && charStats.HasAbility(skills[skill].abilityName)) BlockAbility(GetSkillAbilityName(skill), block, cooldown); // J_Slash: Active Learner if(HasLearnedSkill(skill)) //IsSkillEquipped(skill)) OnSkillUnequip(skill); return true; } else { if(skills[skill].remainingBlockedTime == 0) return false; //already unlocked skills[skill].remainingBlockedTime = 0; if(theGame.GetDefinitionsManager().IsAbilityDefined(skills[skill].abilityName) && charStats.HasAbility(skills[skill].abilityName)) BlockAbility(GetSkillAbilityName(skill), false); // J_Slash: Active Learner if(HasLearnedSkill(skill)) //(IsSkillEquipped(skill)) OnSkillEquip(skill); return true; } } // Runs through all skills and checks their cooldowns. Unblocks those that have their cooldown finished. // Returns time till next call or -1 if no calls needed public final function CheckBlockedSkills(dt : float) : float { var i : int; var cooldown, min : float; min = 1000000; for(i=0; i 0) { skills[i].remainingBlockedTime = MaxF(0, skills[i].remainingBlockedTime - dt); if(skills[i].remainingBlockedTime == 0) { BlockSkill(skills[i].skillType, false); } else { min = MinF(min, skills[i].remainingBlockedTime); } } } if(min == 1000000) min = -1; return min; } //@Override public final function BlockAbility(abilityName : name, block : bool, optional cooldown : float) : bool { var i : int; if( super.BlockAbility(abilityName, block, cooldown)) { //if ability was blocked then remove it from cached arrays if(block) { skillAbilities.Remove(abilityName); } else { //if added then if it's a skill ability then put it to proper cached array for(i=0; i; var i, size : int; var skillEnum : ESkill; charStats.GetAllContainedAbilities(atts); size = atts.Size(); for( i = 0; i < size; i += 1 ) { skillEnum = SkillNameToEnum( atts[i] ); if( skillEnum != S_SUndefined ) { if( !IsAbilityBlocked( atts[i] ) ) { AddSkillInternal( skillEnum, false, false, true ); } continue; } } } protected final function IsCoreSkill(skill : ESkill) : bool { return skills[skill].isCoreSkill; } // Loads a single skill definition for this player from the XML and caches it (basically loads all skills data) protected final function CacheSkills(skillDefinitionName : name, out cache : array) { var dm : CDefinitionsManagerAccessor; var main, sks : SCustomNode; var i, size, size2, j : int; var skillType : ESkill; var bFound : bool; var tmpName : name; var skillDefs : array; dm = theGame.GetDefinitionsManager(); sks = dm.GetCustomDefinition('skills'); //find definition bFound = false; size = sks.subNodes.Size(); cache.Clear(); cache.Resize( S_Perk_MAX ); for( i = 0; i < size; i += 1 ) { if(dm.GetCustomNodeAttributeValueName(sks.subNodes[i], 'def_name', tmpName)) { if(tmpName == skillDefinitionName) { bFound = true; main = sks.subNodes[i]; //do the caching size2 = main.subNodes.Size(); for( j = 0; j < size2; j += 1 ) { dm.GetCustomNodeAttributeValueName(main.subNodes[j], 'skill_name', tmpName); skillType = SkillNameToEnum(tmpName); if( skillType != S_SUndefined ) { if( cache[skillType].skillType == skillType ) { LogChannel('Skills', "W3AbilityManager.CacheSkills: actor's <<" + this + ">> skill <<" + skillType + ">> is already defined!!! Skipping!!!"); continue; } CacheSkill( skillType, tmpName, main.subNodes[j], cache[skillType] ); } else { LogAssert(false, "W3PlayerAbilityManager.CacheSkills: skill <<" + tmpName + ">> is not defined in PST enum, ignoring skill!"); } } } } } if( !bFound ) { LogAssert(false, "W3AbilityManager.CacheSkills: cannot find skill definition named <<" + skillDefinitionName + ">> aborting!"); } } private final function CacheSkill( skillType : int, abilityName : name, definitionNode : SCustomNode, out skill : SSkill ) { var dm : CDefinitionsManagerAccessor = theGame.GetDefinitionsManager(); var modifiers, reqSkills : SCustomNode; var pathType : ESkillPath; var subpathType : ESkillSubPath; var tmpName : name; var tmpInt, k, size : int; var tmpString : string; var tmpBool : bool; skill.wasEquippedOnUIEnter = false; skill.level = 0; //skill type skill.skillType = skillType; skill.abilityName = abilityName; //path type if(dm.GetCustomNodeAttributeValueName(definitionNode, 'pathType_name', tmpName)) { pathType = SkillPathNameToType(tmpName); if(pathType != ESP_NotSet) skill.skillPath = pathType; else if(skill.skillType != S_Perk_08) //perk 08 is a hidden skill now LogAssert(false, "W3PlayerAbilityManager.CacheSkill: skill <<" + skill.skillType + ">> has wrong path type set <<" + tmpName + ">>"); } //subpath type if(dm.GetCustomNodeAttributeValueName(definitionNode, 'subpathType_name', tmpName)) { subpathType = SkillSubPathNameToType(tmpName); if(subpathType != ESSP_NotSet) skill.skillSubPath = subpathType; else if(skill.skillType != S_Perk_08) //perk 08 is a hidden skill now LogAssert(false, "W3PlayerAbilityManager.CacheSkill: skill <<" + skill.skillType + ">> has wrong subpath type set <<" + tmpName + ">>"); } //required skills list reqSkills = dm.GetCustomDefinitionSubNode(definitionNode,'required_skills'); if(reqSkills.values.Size() > 0) { size = reqSkills.values.Size(); for(k=0; k 0) { size = modifiers.values.Size(); for(k=0; k EES_SkillMutagen4) { LogAssert(false, "W3PlayerAbilityManager.LoadMutagenSlotsDataFromXML: too many slots defined in XML!!! Aborting"); return; } mutagenSlots.PushBack(mutagen); } } //Acquires skill. //The temporary flag informs that the skill was not developed through character development but as a temporary bonus and will be lost soon public final function AddSkill(skill : ESkill, isTemporary : bool) { var i : int; var learnedAll, ret : bool; var tree : ESkillPath; var uiStateCharDev : W3TutorialManagerUIHandlerStateCharacterDevelopment; var uiStateSpecialAttacks : W3TutorialManagerUIHandlerStateSpecialAttacks; ret = AddSkillInternal(skill, true, isTemporary); if(!ret) return; //dendrology achievement - fully develop one skill tree tree = GetSkillPathType(skill); learnedAll = true; for(i=0; i= skills[skill].maxLevel) { LogAssert(false,"W3AbilityManager.AddSkill: trying to add skill already known <<" + SkillEnumToName(skill) + ">>, aborting!"); return false; } // J_Slash: Active Learner if(!isTemporary && skills[skill].level > 0 && skills[skill].remainingBlockedTime <= 0 && !IsAbilityBlocked(skills[skill].abilityName)) OnSkillUnequip(skill); //add skill skills[skill].level += 1; //add path point spent if not core skill //add path point spent if not core skill if(!skills[skill].isCoreSkill) pathPointsSpent[skills[skill].skillPath] = pathPointsSpent[skills[skill].skillPath] + 1; if(!isTemporary) { LogSkills("Skill <<" + skills[skill].abilityName + ">> learned"); if(spendPoints) ((W3PlayerWitcher)owner).levelManager.SpendPoints(ESkillPoint, skills[skill].cost); // J_Slash: Active Learner if(!IsAbilityBlocked(skills[skill].abilityName) && skills[skill].remainingBlockedTime <= 0) OnSkillEquip(skill); //if ( this.IsSkillEquipped(skill) ) //OnSkillEquippedLevelChange(skill, GetSkillLevel(skill) - 1, GetSkillLevel(skill)); theTelemetry.LogWithValueStr(TE_HERO_SKILL_UP, SkillEnumToName(skill)); } return true; } //removes temporary skill granted through another skill's bonus //FIXME - update for skill slots - used by non-tutorial testing fakes and skill_swors_s19 public final function RemoveTemporarySkill(skill : SSimpleSkill) : bool { var ind : int; LogAssert( skill.skillType >= S_SUndefined, "W3AbilityManager.RemoveTemporarySkill: trying to remove undefined skill" ); if(!skills[skill.skillType].isCoreSkill) pathPointsSpent[skills[skill.skillType].skillPath] = pathPointsSpent[skills[skill.skillType].skillPath] - (skills[skill.skillType].level - skill.level); skills[skill.skillType].level = skill.level; if(skills[skill.skillType].level < 1) { ind = GetSkillSlotID(skill.skillType); if(ind >= 0) UnequipSkill(ind); } tempSkills.Remove(skill.skillType); return true; } public final function HasLearnedSkill(skill : ESkill) : bool { return skills[skill].level > 0; } private final function GetSkillFromAbilityName(abilityName : name) : ESkill { var i : int; for(i=0; i= skills[skill].maxLevel) return false; //if requirements are not met // #J removed this logic since it does not apply to current design /*if(skills[skill].requiredSkills.Size() > 0) { if(skills[skill].requiredSkillsIsAlternative) hasSomeRequiredSkill = false; else hasSomeRequiredSkill = true; for(j=0; j 0 && pathPointsSpent[skills[skill].skillPath] < skills[skill].requiredPointsSpent) return false; //cost if(((W3PlayerWitcher)owner).levelManager.GetPointsFree(ESkillPoint) < skills[skill].cost) return false; //all conditions ok return true; } public final function HasSpentEnoughPoints(skill : ESkill) : bool // #J { if (skills[skill].requiredPointsSpent > 0 && pathPointsSpent[skills[skill].skillPath] < skills[skill].requiredPointsSpent) { return false; } return true; } public final function GetPathPointsSpent(skillPath : ESkillPath) : int { return pathPointsSpent[skillPath]; } public final function PathPointsSpentInSkillPathOfSkill(skill : ESkill) : int // #J { return pathPointsSpent[skills[skill].skillPath]; } // Returns ability name that this skill grants public final function GetSkillAbilityName(skill : ESkill) : name { return skills[skill].abilityName; } public final function GetSkillLocalisationKeyName(skill : ESkill) : string //#B { return skills[skill].localisationNameKey; } public final function GetSkillLocalisationKeyDescription(skill : ESkill, optional level : int) : string //#B { switch (level) { case 2: return skills[skill].localisationDescriptionLevel2Key; case 3: return skills[skill].localisationDescriptionLevel3Key; case 4: return skills[skill].localisationDescriptionLevel3Key; case 5: return skills[skill].localisationDescriptionLevel3Key; default: return skills[skill].localisationDescriptionKey; } } public final function GetSkillIconPath(skill : ESkill) : string //#B { return skills[skill].iconPath; } public final function GetSkillSubPathType(skill : ESkill) : ESkillSubPath { return skills[skill].skillSubPath; } public final function GetSkillPathType(skill : ESkill) : ESkillPath { return skills[skill].skillPath; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////// ---=== @RESISTS ===--- ////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected function GetItemResistStatIndex( slot : EEquipmentSlots, stat : ECharacterDefenseStats ) : int { var i, size : int; size = resistStatsItems[slot].Size(); for ( i = 0; i < size; i+=1 ) { if ( resistStatsItems[slot][i].type == stat ) { return i; } } return -1; } //@Override //updates resist stat value - Overrides parent, we need to take armor durability into considreation protected final function RecalcResistStat(stat : ECharacterDefenseStats) { var witcher : W3PlayerWitcher; var item : SItemUniqueId; var slot, idxItems : int; var itemResists : array; var resistStat : SResistanceValue; //take all resists super.RecalcResistStat(stat); //check if character can have item slots => durability witcher = (W3PlayerWitcher)owner; if(!witcher) return; GetResistStat( stat, resistStat ); for(slot=0; slot < resistStatsItems.Size(); slot+=1) { //get item if it has durability if( witcher.GetItemEquippedOnSlot(slot, item) && witcher.inv.HasItemDurability(item)) { itemResists = witcher.inv.GetItemResistanceTypes(item); //check if item boosts resist stat if(itemResists.Contains(stat)) { //remove resists from items resistStat.points.valueBase -= CalculateAttributeValue(witcher.inv.GetItemAttributeValue(item, ResistStatEnumToName(stat, true))); resistStat.percents.valueBase -= CalculateAttributeValue(witcher.inv.GetItemAttributeValue(item, ResistStatEnumToName(stat, false))); //calculate item durability modified resistances SetItemResistStat(slot, stat); //then add resists from items with durability modification idxItems = GetItemResistStatIndex( slot, stat ); if(idxItems >= 0) { resistStat.percents.valueBase += CalculateAttributeValue(resistStatsItems[slot][idxItems].percents); resistStat.points.valueBase += CalculateAttributeValue(resistStatsItems[slot][idxItems].points); } } } } SetResistStat( stat, resistStat ); } // Updates cached durability-modified item resist private final function SetItemResistStat(slot : EEquipmentSlots, stat : ECharacterDefenseStats) { var item : SItemUniqueId; var tempResist : SResistanceValue; var witcher : W3PlayerWitcher; var i : int; witcher = (W3PlayerWitcher)owner; if(!witcher) return; //get cached stat index i = GetItemResistStatIndex( slot, stat ); //get equipped item if( witcher.GetItemEquippedOnSlot(slot, item) && witcher.inv.HasItemDurability(item) ) { //set item resist with durability if(i >= 0) { //if this resist is already cached then update the value witcher.inv.GetItemResistStatWithDurabilityModifiers(item, stat, resistStatsItems[slot][i].points, resistStatsItems[slot][i].percents); } else { //if this resist is not cached then add it to cached array witcher.inv.GetItemResistStatWithDurabilityModifiers(item, stat, tempResist.points, tempResist.percents); tempResist.type = stat; resistStatsItems[slot].PushBack(tempResist); } } else if(i >= 0) { //no item in that slot but something cached - delete the cached item resist resistStatsItems[slot].Erase(i); } } // called when item durability has changed to update the cached durability-modified resists from that item public final function RecalcItemResistDurability(slot : EEquipmentSlots, itemId : SItemUniqueId) { var i : int; var witcher : W3PlayerWitcher; var itemResists : array; witcher = (W3PlayerWitcher)owner; if(!witcher) return; itemResists = witcher.inv.GetItemResistanceTypes(itemId); for(i=0; i 0 && dt > 0) { //if it's continuous cost then set up a timer that will do the flooring once the continuous cost stops owner.AddTimer('AbilityManager_FloorStaminaSegment', 0.1, , , , true); } // Mutagen 21 - action costing stamina heal geralt, Whirl and Rend handled separately due to their hacks if (cost > 0 && dt <= 0 && owner == thePlayer && thePlayer.HasBuff(EET_Mutagen21) && abilityName != 'sword_s1' && abilityName != 'sword_s2') { mutagen = (W3Mutagen21_Effect)thePlayer.GetBuff(EET_Mutagen21); mutagen.Heal(); } //Force abort sign cast if stamina reached 0. Otherwise if we have regen, stamina might regenerate before it is checked in //next tick and as a result making even per tick test will always see your stamina >0 if(owner == GetWitcherPlayer() && GetStat(BCS_Stamina, true) <= 0.f) { GetWitcherPlayer().GetSignEntity(GetWitcherPlayer().GetCurrentlyCastSign()).OnSignAborted(true); } return cost; } public function GainStat( stat : EBaseCharacterStats, amount : float ) { //while under runeword 8 effect, don't add focus if(stat == BCS_Focus && owner.HasBuff(EET_Runeword8)) return; super.GainStat(stat, amount); } //floors current stamina to full segment public final function FloorStaminaSegment() { //someone forgot to disable stamina segments when they disabled stamina segments... I want to strangle them... /* var wastedStamina : float; wastedStamina = ModF(GetStat(BCS_Stamina, true), theGame.params.STAMINA_SEGMENT_SIZE); InternalReduceStat(BCS_Stamina, wastedStamina); */ } //needs to make a locked stamina check public final function GetStat(stat : EBaseCharacterStats, optional skipLock : bool) : float { var value, lock : float; var i : int; value = super.GetStat(stat, skipLock); if(stat == BCS_Toxicity && !skipLock && toxicityOffset > 0) { value += toxicityOffset; } return value; } public final function AddToxicityOffset(val : float) { if(val > 0) toxicityOffset += val; } public final function SetToxicityOffset( val : float) { if(val >= 0) toxicityOffset = val; } public final function RemoveToxicityOffset(val : float) { if(val > 0) toxicityOffset -= val; if (toxicityOffset < 0) toxicityOffset = 0; } // #Y TODO: Implement calculation public final function GetOffenseStat():int { var steelDmg, silverDmg : float; var steelCritChance, steelCritDmg : float; var silverCritChance, silverCritDmg : float; var attackPower : SAbilityAttributeValue; var item : SItemUniqueId; var value : SAbilityAttributeValue; // steel and silve ap bonus if (CanUseSkill(S_Sword_s04)) attackPower += GetSkillAttributeValue(SkillEnumToName(S_Sword_s04), PowerStatEnumToName(CPS_AttackPower), false, true) * GetSkillLevel(S_Sword_s04); if (CanUseSkill(S_Sword_s21)) attackPower += GetSkillAttributeValue(SkillEnumToName(S_Sword_s21), PowerStatEnumToName(CPS_AttackPower), false, true) * GetSkillLevel(S_Sword_s21); attackPower = attackPower * 0.5; // steel and silve crit and crit dmg bonus if (CanUseSkill(S_Sword_s08)) { steelCritChance += CalculateAttributeValue(GetSkillAttributeValue(SkillEnumToName(S_Sword_s08), theGame.params.CRITICAL_HIT_CHANCE, false, true)) * GetSkillLevel(S_Sword_s08); steelCritDmg += CalculateAttributeValue(GetSkillAttributeValue(SkillEnumToName(S_Sword_s08), theGame.params.CRITICAL_HIT_DAMAGE_BONUS, false, true)) * GetSkillLevel(S_Sword_s08); } if (CanUseSkill(S_Sword_s17)) { steelCritChance += CalculateAttributeValue(GetSkillAttributeValue(SkillEnumToName(S_Sword_s17), theGame.params.CRITICAL_HIT_CHANCE, false, true)) * GetSkillLevel(S_Sword_s17); steelCritDmg += CalculateAttributeValue(GetSkillAttributeValue(SkillEnumToName(S_Sword_s17), theGame.params.CRITICAL_HIT_DAMAGE_BONUS, false, true)) * GetSkillLevel(S_Sword_s17); } steelCritChance /= 2; steelCritDmg /= 2; silverCritChance = steelCritChance; silverCritDmg = steelCritDmg; if (GetWitcherPlayer().GetItemEquippedOnSlot(EES_SteelSword, item)) { value = thePlayer.GetInventory().GetItemAttributeValue(item, theGame.params.DAMAGE_NAME_SLASHING); steelDmg += value.valueBase; steelCritChance += CalculateAttributeValue(thePlayer.GetInventory().GetItemAttributeValue(item, theGame.params.CRITICAL_HIT_CHANCE)); steelCritDmg += CalculateAttributeValue(thePlayer.GetInventory().GetItemAttributeValue(item, theGame.params.CRITICAL_HIT_DAMAGE_BONUS)); } else { steelDmg += 0; steelCritChance += 0; steelCritDmg +=0; } if (GetWitcherPlayer().GetItemEquippedOnSlot(EES_SilverSword, item)) { value = thePlayer.GetInventory().GetItemAttributeValue(item, theGame.params.DAMAGE_NAME_SILVER); silverDmg += value.valueBase; silverCritChance += CalculateAttributeValue(thePlayer.GetInventory().GetItemAttributeValue(item, theGame.params.CRITICAL_HIT_CHANCE)); silverCritDmg += CalculateAttributeValue(thePlayer.GetInventory().GetItemAttributeValue(item, theGame.params.CRITICAL_HIT_DAMAGE_BONUS)); } else { silverDmg += 0; silverCritChance += 0; silverCritDmg +=0; } steelCritChance += CalculateAttributeValue(GetWitcherPlayer().GetAttributeValue(theGame.params.CRITICAL_HIT_CHANCE)); silverCritChance += CalculateAttributeValue(GetWitcherPlayer().GetAttributeValue(theGame.params.CRITICAL_HIT_CHANCE)); steelCritDmg += CalculateAttributeValue(GetWitcherPlayer().GetAttributeValue(theGame.params.CRITICAL_HIT_DAMAGE_BONUS)); silverCritDmg += CalculateAttributeValue(GetWitcherPlayer().GetAttributeValue(theGame.params.CRITICAL_HIT_DAMAGE_BONUS)); attackPower += GetWitcherPlayer().GetPowerStatValue(CPS_AttackPower); steelCritChance *= 100; silverCritChance *= 100; steelDmg = steelDmg * (100 - steelCritChance) + steelDmg * (1 + steelCritDmg) * steelCritChance; steelDmg *= attackPower.valueMultiplicative; steelDmg /= 100; silverDmg = silverDmg * (100 - silverCritChance) + silverDmg * (1 + silverCritDmg) * silverCritChance; silverDmg *= attackPower.valueMultiplicative; silverDmg /= 100; return RoundMath((steelDmg + silverDmg)/2); } // #Y TODO: Implement calculation public final function GetDefenseStat():int { var valArmor : SAbilityAttributeValue; var valResists : float; var fVal1, fVal2 : float; valArmor = thePlayer.GetTotalArmor(); thePlayer.GetResistValue(CDS_SlashingRes, fVal1, fVal2); valResists += fVal2; thePlayer.GetResistValue(CDS_PiercingRes, fVal1, fVal2); valResists += fVal2; thePlayer.GetResistValue(CDS_BludgeoningRes, fVal1, fVal2); valResists += fVal2; thePlayer.GetResistValue(CDS_RendingRes, fVal1, fVal2); valResists += fVal2; thePlayer.GetResistValue(CDS_ElementalRes, fVal1, fVal2); valResists += fVal2; valResists = valResists / 5; fVal1 = 100 - valArmor.valueBase; fVal1 *= valResists; fVal1 += valArmor.valueBase; return RoundMath(fVal1); } // #Y TODO: Implement calculation public final function GetSignsStat():float { var sp : SAbilityAttributeValue; sp += thePlayer.GetSkillAttributeValue(S_Magic_1, PowerStatEnumToName(CPS_SpellPower), true, true); sp += thePlayer.GetSkillAttributeValue(S_Magic_2, PowerStatEnumToName(CPS_SpellPower), true, true); sp += thePlayer.GetSkillAttributeValue(S_Magic_3, PowerStatEnumToName(CPS_SpellPower), true, true); sp += thePlayer.GetSkillAttributeValue(S_Magic_4, PowerStatEnumToName(CPS_SpellPower), true, true); sp += thePlayer.GetSkillAttributeValue(S_Magic_5, PowerStatEnumToName(CPS_SpellPower), true, true); sp.valueMultiplicative /= 5; return sp.valueMultiplicative; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////// @SLOTS ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// event OnLevelGained(currentLevel : int) { var i : int; for(i=0; i= skillSlots[i].unlockedOnLevel) skillSlots[i].unlocked = true; } } //loads skill slots data from XML private final function InitSkillSlots() { var slot : SSkillSlot; var dm : CDefinitionsManagerAccessor; var main : SCustomNode; var i, tmpInt : int; dm = theGame.GetDefinitionsManager(); main = dm.GetCustomDefinition('skill_slots'); for(i=0; i= 0 && skillSlotIndex < skillSlots.Size()) return skillSlots[skillSlotIndex].id; return -1; } /* Returns index of skillSlot for given slot ID. Returns -1 if not found. If checkIfUnlocked flag is set will return -1 if given slot in locked. */ public final function GetSkillSlotIndex(slotID : int, checkIfUnlocked : bool) : int { var i : int; for(i=0; i; var buff : W3Effect_Toxicity; var witcher : W3PlayerWitcher; var i, skillLevel : int; var isPassive, isNight : bool; var m_alchemyManager : W3AlchemyManager; var recipe : SAlchemyRecipe; //var uiState : W3TutorialManagerUIHandlerStateCharacterDevelopment; // J_Slash: Active Learner var battleTrance : W3Effect_BattleTrance; var mutagens : array; var trophy : SItemUniqueId; var horseManager : W3HorseManager; var weapon, armor : W3RepairObjectEnhancement; var foodBuff : W3Effect_WellFed; var commonMenu : CR4CommonMenu; var guiMan : CR4GuiManager; //always active //always active if(IsCoreSkill(skill)) return; witcher = GetWitcherPlayer(); //add passive Buff that this skill grants AddPassiveSkillBuff(skill); //cache skill ability isPassive = theGame.GetDefinitionsManager().AbilityHasTag(skills[skill].abilityName, theGame.params.SKILL_GLOBAL_PASSIVE_TAG); for( i = 0; i < GetSkillLevel(skill); i += 1 ) { if(isPassive) owner.AddAbility(skills[skill].abilityName, true); else skillAbilities.PushBack(skills[skill].abilityName); } //M.J. - adrenaline hack for sword skills if(GetSkillPathType(skill) == ESP_Sword) { owner.AddAbilityMultiple('sword_adrenalinegain', GetSkillLevel(skill) ); } //some stamina hack for magic skills if(GetSkillPathType(skill) == ESP_Signs) { owner.AddAbilityMultiple('magic_staminaregen', GetSkillLevel(skill) ); } //M.J. - potion duration hack for alchemy skills if(GetSkillPathType(skill) == ESP_Alchemy) { owner.AddAbilityMultiple('alchemy_potionduration', GetSkillLevel(skill) ); } // Update Synergy bonus if ( CanUseSkill(S_Alchemy_s19) ) { MutagensSyngergyBonusProcess(false, GetSkillLevel(S_Alchemy_s19)); MutagensSyngergyBonusProcess(true, GetSkillLevel(S_Alchemy_s19)); } else if(skill == S_Alchemy_s20) { if ( GetWitcherPlayer().GetStatPercents(BCS_Toxicity) >= GetWitcherPlayer().GetToxicityDamageThreshold() ) owner.AddEffectDefault(EET_IgnorePain, owner, 'IgnorePain'); } //custom instant skill checks if(skill == S_Alchemy_s18) { m_alchemyManager = new W3AlchemyManager in this; m_alchemyManager.Init(); names = witcher.GetAlchemyRecipes(); skillName = SkillEnumToName(S_Alchemy_s18); for(i=0; i 0) charStats.AddAbilityMultiple(GetSkillAbilityName(skill), GetSkillLevel(skill)); } else if(skill == S_Magic_s11) //yrden damaging { ((W3YrdenEntity) (witcher.GetSignEntity(ST_Yrden))).SkillEquipped(skill); } else if(skill == S_Magic_s07) //battle trance spell power bonus { if(owner.HasBuff(EET_BattleTrance)) owner.AddAbility( GetSkillAbilityName(S_Magic_s07) ); } else if(skill == S_Perk_08) { //change level 3 items abilities from 2 to 3 thePlayer.ChangeAlchemyItemsAbilities(true); } else if(skill == S_Alchemy_s19) { // MutagensSyngergyBonusProcess(true, GetSkillLevel(skill)); } else if(skill == S_Perk_01) { isNight = theGame.envMgr.IsNight(); SetPerk01Abilities(!isNight, isNight); } else if(skill == S_Perk_05) { SetPerkArmorBonus(S_Perk_05, true); } else if(skill == S_Perk_06) { SetPerkArmorBonus(S_Perk_06, true); } else if(skill == S_Perk_07) { SetPerkArmorBonus(S_Perk_07, true); } else if(skill == S_Perk_11) { battleTrance = (W3Effect_BattleTrance)owner.GetBuff(EET_BattleTrance); if(battleTrance) battleTrance.OnPerk11Equipped(); } else if(skill == S_Perk_19 && witcher.HasBuff(EET_BattleTrance)) { skillLevel = FloorF(witcher.GetStat(BCS_Focus)); witcher.RemoveAbilityMultiple(thePlayer.GetSkillAbilityName(S_Sword_5), skillLevel); witcher.AddAbilityMultiple(thePlayer.GetSkillAbilityName(S_Perk_19), skillLevel); } else if(skill == S_Perk_22) { GetWitcherPlayer().UpdateEncumbrance(); guiMan = theGame.GetGuiManager(); if(guiMan) { commonMenu = theGame.GetGuiManager().GetCommonMenu(); if(commonMenu) { commonMenu.UpdateItemsCounter(); } } } if(GetSkillPathType(skill) == ESP_Alchemy) witcher.RecalcPotionsDurations(); //tutorial // J_Slash: Active Learner /*if(ShouldProcessTutorial('TutorialCharDevEquipSkill')) { uiState = (W3TutorialManagerUIHandlerStateCharacterDevelopment)theGame.GetTutorialSystem().uiHandler.GetCurrentState(); if(uiState) uiState.EquippedSkill(); }*/ //trial of grasses achievement theGame.GetGamerProfile().CheckTrialOfGrasses(); } private final function OnSkillUnequip(skill : ESkill) { var i, skillLevel : int; var isPassive : bool; var petard : W3Petard; var ents : array; var mutagens : array; var tox : W3Effect_Toxicity; var names, abs : array; var skillName : name; var battleTrance : W3Effect_BattleTrance; var trophy : SItemUniqueId; var horseManager : W3HorseManager; var witcher : W3PlayerWitcher; var weapon, armor : W3RepairObjectEnhancement; var foodBuff : W3Effect_WellFed; var commonMenu : CR4CommonMenu; var guiMan : CR4GuiManager; //always active //always active if(IsCoreSkill(skill)) return; //cache skill ability isPassive = theGame.GetDefinitionsManager().AbilityHasTag(skills[skill].abilityName, theGame.params.SKILL_GLOBAL_PASSIVE_TAG); skillLevel = skills[skill].level; for( i = 0; i < skillLevel; i += 1 ) { if(isPassive) owner.RemoveAbility(skills[skill].abilityName); else skillAbilities.Remove(skills[skill].abilityName); } //M.J. - adrenaline hack for sword skills if(GetSkillPathType(skill) == ESP_Sword) { owner.RemoveAbilityMultiple('sword_adrenalinegain', skillLevel ); } //some hack for magic skills if(GetSkillPathType(skill) == ESP_Signs) { owner.RemoveAbilityMultiple('magic_staminaregen', GetSkillLevel(skill) ); } //M.J. - potion duration hack for alchemy skills if(GetSkillPathType(skill) == ESP_Alchemy) { owner.RemoveAbilityMultiple('alchemy_potionduration', GetSkillLevel(skill) ); } //custom skill stuff if(skill == S_Magic_s11) //yrden damaging { ((W3YrdenEntity) (GetWitcherPlayer().GetSignEntity(ST_Yrden))).SkillUnequipped(skill); } else if(skill == S_Magic_s07) //battle trance spell power bonus { owner.RemoveAbility( GetSkillAbilityName(S_Magic_s07) ); } else if(skill == S_Alchemy_s04) //bonus random potion effect when drinking potion { owner.RemoveEffect(GetWitcherPlayer().GetSkillBonusPotionEffect()); } /* else if(skill == PROXIMITY_BOMBS) //proximity -> explode existing proximities, disable proxy on flying ones { FindGameplayEntitiesInSphere(ents, owner.GetWorldPosition(), theGame.params.MAX_THROW_RANGE + 0.5, 1000); for(i=ents.Size()-1; i>=0; i-=1) { petard = (W3Petard)ents[i]; if(petard) { if(petard.IsStuck()) petard.ProcessEffect(); else if(petard.IsProximity()) petard.DisableProximity(); } } }*/ { FindGameplayEntitiesInSphere(ents, owner.GetWorldPosition(), theGame.params.MAX_THROW_RANGE + 0.5, 1000); for(i=ents.Size()-1; i>=0; i-=1) { petard = (W3Petard)ents[i]; if(petard) { if(petard.IsStuck()) petard.ProcessEffect(); else if(petard.IsProximity()) petard.DisableProximity(); } } }*/ else if(skill == S_Alchemy_s13) { mutagens = GetWitcherPlayer().GetDrunkMutagens(); if(mutagens.Size() > 0) charStats.RemoveAbilityMultiple(GetSkillAbilityName(S_Alchemy_s13), GetSkillLevel(skill)); } else if(skill == S_Alchemy_s20) { owner.RemoveBuff(EET_IgnorePain); } else if(skill == S_Alchemy_s15 && owner.HasBuff(EET_Toxicity)) { tox = (W3Effect_Toxicity)owner.GetBuff(EET_Toxicity); tox.RecalcEffectValue(); } else if(skill == S_Alchemy_s18) //toxicity pool upgrade per known recipe { names = GetWitcherPlayer().GetAlchemyRecipes(); skillName = SkillEnumToName(S_Alchemy_s18); for(i=0; i; var light, medium, heavy, i, cnt : int; var armorType : EArmorType; var witcher : W3PlayerWitcher; if(skill != S_Perk_05 && skill != S_Perk_06 && skill != S_Perk_07) return; witcher = GetWitcherPlayer(); armors.Resize(4); if(witcher.inv.GetItemEquippedOnSlot(EES_Armor, item)) armors[0] = item; if(witcher.inv.GetItemEquippedOnSlot(EES_Boots, item)) armors[1] = item; if(witcher.inv.GetItemEquippedOnSlot(EES_Pants, item)) armors[2] = item; if(witcher.inv.GetItemEquippedOnSlot(EES_Gloves, item)) armors[3] = item; light = 0; medium = 0; heavy = 0; for(i=0; i 0) UpdatePerkArmorBonus(skill, enable, cnt); } // adds/removes perk armor bonus public final function UpdatePerkArmorBonus(skill : ESkill, enable : bool, optional count : int) { var abilityName : name; abilityName = GetSkillAbilityName(skill); if(count == 0) count = 1; if(enable) charStats.AddAbilityMultiple(abilityName, count); else charStats.RemoveAbilityMultiple(abilityName, count); } //sets day/night perk01's abilities public final function SetPerk01Abilities(enableDay : bool, enableNight : bool) { var abilityName : name; var i : int; var dm : CDefinitionsManagerAccessor; var abs : array; var enable : bool; abilityName = GetSkillAbilityName(S_Perk_01); dm = theGame.GetDefinitionsManager(); dm.GetContainedAbilities(abilityName, abs); for(i=0; i; var skillAbilityName : name; var mutagens : array; var recipe : SAlchemyRecipe; var m_alchemyManager : W3AlchemyManager; var ignorePain : W3Effect_IgnorePain; //never changes levels if(IsCoreSkill(skill)) return; if(skill == S_Alchemy_s08) { if(currLevel < prevLevel) thePlayer.SkillReduceBombAmmoBonus(); } else if(skill == S_Alchemy_s18) { m_alchemyManager = new W3AlchemyManager in this; m_alchemyManager.Init(); names = GetWitcherPlayer().GetAlchemyRecipes(); skillAbilityName = SkillEnumToName(S_Alchemy_s18); cnt = 0; //count how much we should have for(i=0; i 0) charStats.AddAbilityMultiple(skillAbilityName, cnt); else if(cnt < 0) charStats.RemoveAbilityMultiple(skillAbilityName, -cnt); } else if(skill == S_Alchemy_s13) { mutagens = GetWitcherPlayer().GetDrunkMutagens(); skillAbilityName = GetSkillAbilityName(S_Alchemy_s13); if(mutagens.Size() > 0) charStats.AddAbilityMultiple(skillAbilityName, GetSkillLevel(skill)); else charStats.RemoveAbilityMultiple(skillAbilityName, GetSkillLevel(skill)); } else if(skill == S_Alchemy_s19) { //remove old, add new if ( CanUseSkill(S_Alchemy_s19) ) { MutagensSyngergyBonusProcess(false, prevLevel); MutagensSyngergyBonusProcess(true, currLevel); } } else if(skill == S_Alchemy_s20) { if(owner.HasBuff(EET_IgnorePain)) { ignorePain = (W3Effect_IgnorePain)owner.GetBuff(EET_IgnorePain); ignorePain.OnSkillLevelChanged(currLevel - prevLevel); } } else if(skill == S_Perk_08) { if(currLevel == 3) thePlayer.ChangeAlchemyItemsAbilities(true); else if(currLevel == 2 && prevLevel == 3) thePlayer.ChangeAlchemyItemsAbilities(false); } //some hack for sword skills if(GetSkillPathType(skill) == ESP_Sword) { if ( (currLevel - prevLevel) > 0) owner.AddAbilityMultiple('sword_adrenalinegain', currLevel - prevLevel ); else if ( (currLevel - prevLevel) < 0) owner.RemoveAbilityMultiple('sword_adrenalinegain', currLevel - prevLevel ); } //some hack for magic skills if(GetSkillPathType(skill) == ESP_Signs) { if ( (currLevel - prevLevel) > 0) owner.AddAbilityMultiple('magic_staminaregen', currLevel - prevLevel ); else if ( (currLevel - prevLevel) < 0) owner.RemoveAbilityMultiple('magic_staminaregen', currLevel - prevLevel ); } //some hack for alchemy skills if(GetSkillPathType(skill) == ESP_Alchemy) { if ( (currLevel - prevLevel) > 0) owner.AddAbilityMultiple('alchemy_potionduration', currLevel - prevLevel ); else if ( (currLevel - prevLevel) < 0) owner.RemoveAbilityMultiple('alchemy_potionduration', currLevel - prevLevel ); } if(GetSkillPathType(skill) == ESP_Alchemy) GetWitcherPlayer().RecalcPotionsDurations(); } public final function CanUseSkill(skill : ESkill) : bool { var ind : int; // J_Slash: Active Learner //if(!IsSkillEquipped(skill)) //return false; if(skills[skill].level < 1) return false; if(skills[skill].remainingBlockedTime != 0) return false; if(theGame.GetDefinitionsManager().IsAbilityDefined(skills[skill].abilityName) && charStats.HasAbility(skills[skill].abilityName)) return !IsAbilityBlocked(skills[skill].abilityName); return true; } public final function IsSkillEquipped(skill : ESkill) : bool { var i, idx : int; //core skills always equipped if(IsCoreSkill(skill)) return true; //skill slots for(i=0; i 0 && slotID <= totalSkillSlotsCount) { idx = GetSkillSlotIndex(slotID, true); if(idx >= 0) { skill = skillSlots[idx].socketedSkill; return true; } } skill = S_SUndefined; return false; } public final function GetSkillSlots() : array { return skillSlots; } public final function GetSkillSlotsCount() : int { return totalSkillSlotsCount; } public final function IsSkillSlotUnlocked(slotIndex : int) : bool { if(slotIndex >= 0 && slotIndex < skillSlots.Size()) return skillSlots[slotIndex].unlocked; return false; } //resets character dev public final function ResetCharacterDev() { var i : int; var skillType : ESkill; for(i=0; i { var savedSkills : array; //array of skills that were initially in the slots, needed to restore them after tutorial is done var i : int; var slots : array; //slot IDs of slots that are in the group connected to equipped mutagen slot var equippedSkill : ESkill; var savedSkill : STutorialSavedSkill; //get three skill slots' indexes of group in which we have the mutagen slots = TutorialGetConnectedSkillsSlotsIDs(); //save equipped skills and clear slots for(i=0; i; //get three skill slots' indexes of group in which we have the mutagen slots = TutorialGetConnectedSkillsSlotsIDs(); //select temp skill TutorialSelectAndAddTempSkill(); //equip temp skill to first slot EquipSkill(temporaryTutorialSkills[0].skillType, ArrayFindMinInt(slots)); //update UI TutorialUpdateUI(); } //Adds one improper temp skill to second slot public final function TutorialMutagensEquipOneGoodOneBadSkill() { var slots : array; //add temp skill TutorialSelectAndAddTempSkill(true); //equip to second slot slots = TutorialGetConnectedSkillsSlotsIDs(); ArraySortInts(slots); EquipSkill(temporaryTutorialSkills[1].skillType, slots[1] ); //refresh UI TutorialUpdateUI(); } //Removes improper skill from second slot and adds two proper ones to slot 2 & 3 public final function TutorialMutagensEquipThreeGoodSkills() { var slots : array; //we no longer need the temp wrong color skill - remove it TutorialGetRidOfTempSkill(1); //add two proper color temp skills TutorialSelectAndAddTempSkill(false, 1); TutorialSelectAndAddTempSkill(false, 2); //equip to second & third slots slots = TutorialGetConnectedSkillsSlotsIDs(); ArraySortInts(slots); EquipSkill(temporaryTutorialSkills[1].skillType, slots[1]); EquipSkill(temporaryTutorialSkills[2].skillType, slots[2]); //refresh UI TutorialUpdateUI(); } //removes all temp skills of tutorial and restores previous skills public final function TutorialMutagensCleanupTempSkills(savedEquippedSkills : array) { //remove 3 temp skills TutorialGetRidOfTempSkill(2); TutorialGetRidOfTempSkill(1); TutorialGetRidOfTempSkill(0); //restore skills you had previously equipped EquipSkill(savedEquippedSkills[0].skillType, savedEquippedSkills[0].skillSlotID); EquipSkill(savedEquippedSkills[1].skillType, savedEquippedSkills[1].skillSlotID); EquipSkill(savedEquippedSkills[2].skillType, savedEquippedSkills[2].skillSlotID); TutorialUpdateUI(); } private final function TutorialGetRidOfTempSkill(tutTempArrIdx : int) { var tempSkill : ESkill; var i, ind : int; tempSkill = temporaryTutorialSkills[tutTempArrIdx].skillType; if(temporaryTutorialSkills[tutTempArrIdx].wasLearned) { if(!skills[tempSkill].isCoreSkill) pathPointsSpent[skills[tempSkill].skillPath] = pathPointsSpent[skills[tempSkill].skillPath] - 1; skills[tempSkill].level = 0; } ind = GetSkillSlotID(tempSkill); if(ind >= 0) UnequipSkill(ind); temporaryTutorialSkills.EraseFast(tutTempArrIdx); tempSkills.Remove(tempSkill); } //Selects and 'learns' temp skill matching for mutagen on EES_SkillMutange1 slot. //If 'of wrong' color is set, temp skill will have different color than the mutagen. //If 'index' is set then it picks next in line skill. Eg. we have 3 skills prepared for chosing so index =1 will select the second in line. private final function TutorialSelectAndAddTempSkill(optional ofWrongColor : bool, optional index : int) { var witcher : W3PlayerWitcher; var mutagenColor : ESkillColor; //color of equipped mutagen var tempSkill : ESkill; var tutSkill : STutorialTemporarySkill; var mutagenItemId : SItemUniqueId; //get mutagen color witcher = GetWitcherPlayer(); witcher.GetItemEquippedOnSlot(EES_SkillMutagen1, mutagenItemId); mutagenColor = witcher.inv.GetSkillMutagenColor(mutagenItemId); if(!ofWrongColor) { if(mutagenColor == SC_Blue) { if(index == 0) tempSkill = S_Magic_s01; else if(index == 1) tempSkill = S_Magic_s02; else if(index == 2) tempSkill = S_Magic_s03; } else if(mutagenColor == SC_Red) { if(index == 0) tempSkill = S_Sword_s01; else if(index == 1) tempSkill = S_Sword_s02; else if(index == 2) tempSkill = S_Sword_s03; } else if(mutagenColor == SC_Green) { if(index == 0) tempSkill = S_Alchemy_s01; else if(index == 1) tempSkill = S_Alchemy_s02; else if(index == 2) tempSkill = S_Alchemy_s03; } } else { if(mutagenColor == SC_Green) tempSkill = S_Magic_s01; else tempSkill = S_Alchemy_s01; } //add temp skill if not known if(GetSkillLevel(tempSkill) <= 0) { tempSkills.PushBack(tempSkill); AddSkill(tempSkill, true); tutSkill.wasLearned = true; } else { tutSkill.wasLearned = false; } tutSkill.skillType = tempSkill; temporaryTutorialSkills.PushBack(tutSkill); } //returns array of Slot IDs of those three slots that are connected to EES_SkillMutagen1 mutagen slot private final function TutorialGetConnectedSkillsSlotsIDs() : array { var i, connectedSkillsGroupID, processedSlots : int; var slots : array; connectedSkillsGroupID = GetSkillGroupIdOfMutagenSlot(EES_SkillMutagen1); for(i=0; i