public static void ApplyCooldown(NWPlayer pc, CooldownCategory cooldown, IPerkHandler ability, int spellFeatID) { float finalCooldown = ability.CooldownTime(pc, (float)cooldown.BaseCooldownTime, spellFeatID); int cooldownSeconds = (int)finalCooldown; int cooldownMillis = (int)((finalCooldown - cooldownSeconds) * 100); PCCooldown pcCooldown = DataService.GetAll <PCCooldown>().Single(x => x.PlayerID == pc.GlobalID && x.CooldownCategoryID == cooldown.ID); pcCooldown.DateUnlocked = DateTime.UtcNow.AddSeconds(cooldownSeconds).AddMilliseconds(cooldownMillis); DataService.SubmitDataChange(pcCooldown, DatabaseActionType.Update); }
private static void RegisterPerkHandlers() { // Use reflection to get all of IPerkHandler implementations. var classes = Assembly.GetCallingAssembly().GetTypes() .Where(p => typeof(IPerkHandler).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract).ToArray(); foreach (var type in classes) { IPerkHandler instance = Activator.CreateInstance(type) as IPerkHandler; if (instance == null) { throw new NullReferenceException("Unable to activate instance of type: " + type); } _perkHandlers.Add(instance.PerkType, instance); } }
private static void HandleQueueWeaponSkill(NWCreature activator, Data.Entity.Perk entity, IPerkHandler ability, Feat spellFeatID) { var perkFeat = DataService.PerkFeat.GetByFeatID((int)spellFeatID); int? cooldownCategoryID = ability.CooldownCategoryID(activator, entity.CooldownCategoryID, perkFeat.PerkLevelUnlocked); var cooldownCategory = DataService.CooldownCategory.GetByID(Convert.ToInt32(cooldownCategoryID)); string queueUUID = Guid.NewGuid().ToString(); activator.SetLocalInt("ACTIVE_WEAPON_SKILL", entity.ID); activator.SetLocalString("ACTIVE_WEAPON_SKILL_UUID", queueUUID); activator.SetLocalInt("ACTIVE_WEAPON_SKILL_FEAT_ID", (int)spellFeatID); activator.SendMessage("Weapon skill '" + entity.Name + "' queued for next attack."); SendAOEMessage(activator, activator.Name + " readies weapon skill '" + entity.Name + "'."); ApplyCooldown(activator, cooldownCategory, ability, perkFeat.PerkLevelUnlocked, 0.0f); // Player must attack within 30 seconds after queueing or else it wears off. _.DelayCommand(30f, () => { if (activator.GetLocalString("ACTIVE_WEAPON_SKILL_UUID") == queueUUID) { activator.DeleteLocalInt("ACTIVE_WEAPON_SKILL"); activator.DeleteLocalString("ACTIVE_WEAPON_SKILL_UUID"); activator.DeleteLocalInt("ACTIVE_WEAPON_SKILL_FEAT_ID"); activator.SendMessage("Your weapon skill '" + entity.Name + "' is no longer queued."); SendAOEMessage(activator, activator.Name + " no longer has weapon skill '" + entity.Name + "' readied."); } }); }
public static void ApplyCooldown(NWCreature creature, CooldownCategory cooldown, IPerkHandler handler, int spellTier, float armorPenalty) { if (armorPenalty <= 0.0f) { armorPenalty = 1.0f; } // If player has a a cooldown recovery bonus on their equipment, apply that change now. if (creature.IsPlayer) { var effectiveStats = PlayerStatService.GetPlayerItemEffectiveStats(creature.Object); if (effectiveStats.CooldownRecovery > 0) { armorPenalty -= (effectiveStats.CooldownRecovery * 0.01f); } } // There's a cap of 50% cooldown reduction from equipment. if (armorPenalty < 0.5f) { armorPenalty = 0.5f; } float finalCooldown = handler.CooldownTime(creature, (float)cooldown.BaseCooldownTime, spellTier) * armorPenalty; int cooldownSeconds = (int)finalCooldown; int cooldownMillis = (int)((finalCooldown - cooldownSeconds) * 100); DateTime unlockDate = DateTime.UtcNow.AddSeconds(cooldownSeconds).AddMilliseconds(cooldownMillis); if (creature.IsPlayer) { PCCooldown pcCooldown = DataService.PCCooldown.GetByPlayerAndCooldownCategoryID(creature.GlobalID, cooldown.ID); pcCooldown.DateUnlocked = unlockDate; DataService.SubmitDataChange(pcCooldown, DatabaseActionType.Update); } else { string unlockDateString = unlockDate.ToString("yyyy-MM-dd hh:mm:ss"); creature.SetLocalString("ABILITY_COOLDOWN_ID_" + (int)handler.PerkType, unlockDateString); } }
private static void ActivateAbility( NWCreature activator, NWObject target, Data.Entity.Perk entity, IPerkHandler perkHandler, int pcPerkLevel, PerkExecutionType executionType, int spellTier) { string uuid = Guid.NewGuid().ToString(); float baseActivationTime = perkHandler.CastingTime(activator, (float)entity.BaseCastingTime, spellTier); float activationTime = baseActivationTime; var vfxID = VisualEffect.Invalid; var animationID = Animation.Invalid; if (baseActivationTime > 0f && activationTime < 1.0f) { activationTime = 1.0f; } // Force ability armor penalties float armorPenalty = 0.0f; if (executionType == PerkExecutionType.ForceAbility || executionType == PerkExecutionType.ConcentrationAbility) { string penaltyMessage = string.Empty; foreach (var item in activator.EquippedItems) { if (item.CustomItemType == CustomItemType.HeavyArmor) { armorPenalty = 2; penaltyMessage = "Heavy armor slows your force cooldown by 100%."; break; } else if (item.CustomItemType == CustomItemType.LightArmor) { armorPenalty = 1.25f; penaltyMessage = "Light armor slows your force cooldown by 25%."; } } // If there's an armor penalty, send a message to the player. if (armorPenalty > 0.0f) { activator.SendMessage(penaltyMessage); } } // If player is in stealth mode, force them out of stealth mode. if (_.GetActionMode(activator.Object, ActionMode.Stealth)) { _.SetActionMode(activator.Object, ActionMode.Stealth, false); } // Make the player face their target. _.ClearAllActions(); BiowarePosition.TurnToFaceObject(target, activator); // Force and Concentration Abilities will display a visual effect during the casting process. if (executionType == PerkExecutionType.ForceAbility || executionType == PerkExecutionType.ConcentrationAbility) { vfxID = VisualEffect.Vfx_Dur_Iounstone_Yellow; animationID = Animation.LoopingConjure1; } if (executionType == PerkExecutionType.ConcentrationAbility) { activator.SetLocalObject("CONCENTRATION_TARGET", target); } // If a VFX ID has been specified, play that effect instead of the default one. if (vfxID != VisualEffect.Invalid) { var vfx = _.EffectVisualEffect(vfxID); vfx = _.TagEffect(vfx, "ACTIVATION_VFX"); _.ApplyEffectToObject(DurationType.Temporary, vfx, activator.Object, activationTime + 0.2f); } // If an animation has been specified, make the player play that animation now. // bypassing if perk is throw saber due to couldn't get the animation to work via db table edit if (animationID != Animation.Invalid && entity.ID != (int)PerkType.ThrowSaber) { activator.AssignCommand(() => _.ActionPlayAnimation(animationID, 1.0f, activationTime - 0.1f)); } // Mark player as busy. Busy players can't take other actions (crafting, harvesting, etc.) activator.IsBusy = true; // Non-players can't be interrupted via movement. if (!activator.IsPlayer) { // Begin the check for spell interruption. If the activator moves, the spell will be canceled. CheckForSpellInterruption(activator, uuid, activator.Position); } activator.SetLocalInt(uuid, (int)SpellStatusType.Started); // If there's a casting delay, display a timing bar on-screen. if (activationTime > 0) { NWNXPlayer.StartGuiTimingBar(activator, (int)activationTime, string.Empty); } // Run the FinishAbilityUse event at the end of the activation time. int perkID = entity.ID; var @event = new OnFinishAbilityUse(activator, uuid, perkID, target, pcPerkLevel, spellTier, armorPenalty); activator.DelayEvent(activationTime + 0.2f, @event); }
public bool Run(params object[] args) { using (new Profiler(nameof(FinishAbilityUse))) { // These arguments are sent from the AbilityService's ActivateAbility method. NWCreature activator = (NWCreature)args[0]; string spellUUID = Convert.ToString(args[1]); int perkID = (int)args[2]; NWObject target = (NWObject)args[3]; int pcPerkLevel = (int)args[4]; int spellTier = (int)args[5]; float armorPenalty = (float)args[6]; // Get the relevant perk information from the database. Data.Entity.Perk dbPerk = DataService.Single <Data.Entity.Perk>(x => x.ID == perkID); // The execution type determines how the perk behaves and the rules surrounding it. PerkExecutionType executionType = dbPerk.ExecutionTypeID; // Get the class which handles this perk's behaviour. IPerkHandler perk = PerkService.GetPerkHandler(perkID); // Pull back cooldown information. int?cooldownID = perk.CooldownCategoryID(activator, dbPerk.CooldownCategoryID, spellTier); CooldownCategory cooldown = cooldownID == null ? null : DataService.SingleOrDefault <CooldownCategory>(x => x.ID == cooldownID); // If the activator interrupted the spell or died, we can bail out early. if (activator.GetLocalInt(spellUUID) == (int)SpellStatusType.Interrupted || // Moved during casting activator.CurrentHP < 0 || activator.IsDead) // Or is dead/dying { activator.DeleteLocalInt(spellUUID); return(false); } // Remove the temporary UUID which is tracking this spell cast. activator.DeleteLocalInt(spellUUID); // Force Abilities, Combat Abilities, Stances, and Concentration Abilities if (executionType == PerkExecutionType.ForceAbility || executionType == PerkExecutionType.CombatAbility || executionType == PerkExecutionType.Stance || executionType == PerkExecutionType.ConcentrationAbility) { // Run the impact script. perk.OnImpact(activator, target, pcPerkLevel, spellTier); // If an animation is specified for this perk, play it now. if (dbPerk.CastAnimationID != null && dbPerk.CastAnimationID > 0) { activator.AssignCommand(() => { _.ActionPlayAnimation((int)dbPerk.CastAnimationID, 1f, 1f); }); } // If the target is an NPC, assign enmity towards this creature for that NPC. if (target.IsNPC) { AbilityService.ApplyEnmity(activator, target.Object, dbPerk); } } // Adjust creature's current FP, if necessary. // Adjust FP only if spell cost > 0 PerkFeat perkFeat = DataService.Single <PerkFeat>(x => x.PerkID == perkID && x.PerkLevelUnlocked == spellTier); int fpCost = perk.FPCost(activator, perkFeat.BaseFPCost, spellTier); if (fpCost > 0) { int currentFP = AbilityService.GetCurrentFP(activator); int maxFP = AbilityService.GetMaxFP(activator); currentFP -= fpCost; AbilityService.SetCurrentFP(activator, currentFP); activator.SendMessage(ColorTokenService.Custom("FP: " + currentFP + " / " + maxFP, 32, 223, 219)); } // Notify activator of concentration ability change and also update it in the DB. if (executionType == PerkExecutionType.ConcentrationAbility) { AbilityService.StartConcentrationEffect(activator, perkID, spellTier); activator.SendMessage("Concentration ability activated: " + dbPerk.Name); // The Skill Increase effect icon and name has been overwritten. Apply the effect to the player now. // This doesn't do anything - it simply gives a visual cue that the player has an active concentration effect. _.ApplyEffectToObject(_.DURATION_TYPE_PERMANENT, _.EffectSkillIncrease(_.SKILL_USE_MAGIC_DEVICE, 1), activator); } // Handle applying cooldowns, if necessary. if (cooldown != null) { AbilityService.ApplyCooldown(activator, cooldown, perk, spellTier, armorPenalty); } // Mark the creature as no longer busy. activator.IsBusy = false; // Mark the spell cast as complete. activator.SetLocalInt(spellUUID, (int)SpellStatusType.Completed); return(true); } }
public bool Run(params object[] args) { using (new Profiler(nameof(FinishAbilityUse))) { NWPlayer pc = (NWPlayer)args[0]; string spellUUID = Convert.ToString(args[1]); int perkID = (int)args[2]; NWObject target = (NWObject)args[3]; int pcPerkLevel = (int)args[4]; int featID = (int)args[5]; Data.Entity.Perk entity = DataService.Single <Data.Entity.Perk>(x => x.ID == perkID); PerkExecutionType executionType = (PerkExecutionType)entity.ExecutionTypeID; IPerkHandler perk = PerkService.GetPerkHandler(perkID); int?cooldownID = perk.CooldownCategoryID(pc, entity.CooldownCategoryID, featID); CooldownCategory cooldown = cooldownID == null ? null : DataService.SingleOrDefault <CooldownCategory>(x => x.ID == cooldownID); if (pc.GetLocalInt(spellUUID) == (int)SpellStatusType.Interrupted || // Moved during casting pc.CurrentHP < 0 || pc.IsDead) // Or is dead/dying { pc.DeleteLocalInt(spellUUID); return(false); } pc.DeleteLocalInt(spellUUID); if (executionType == PerkExecutionType.ForceAbility || executionType == PerkExecutionType.CombatAbility || executionType == PerkExecutionType.Stance) { perk.OnImpact(pc, target, pcPerkLevel, featID); if (entity.CastAnimationID != null && entity.CastAnimationID > 0) { pc.AssignCommand(() => { _.ActionPlayAnimation((int)entity.CastAnimationID, 1f, 1f); }); } if (target.IsNPC) { AbilityService.ApplyEnmity(pc, (target.Object), entity); } } else if (executionType == PerkExecutionType.QueuedWeaponSkill) { AbilityService.HandleQueueWeaponSkill(pc, entity, perk, featID); } // Adjust FP only if spell cost > 0 Data.Entity.Player pcEntity = DataService.Single <Data.Entity.Player>(x => x.ID == pc.GlobalID); int fpCost = perk.FPCost(pc, entity.BaseFPCost, featID); if (fpCost > 0) { pcEntity.CurrentFP = pcEntity.CurrentFP - fpCost; DataService.SubmitDataChange(pcEntity, DatabaseActionType.Update); pc.SendMessage(ColorTokenService.Custom("FP: " + pcEntity.CurrentFP + " / " + pcEntity.MaxFP, 32, 223, 219)); } bool hasChainspell = CustomEffectService.DoesPCHaveCustomEffect(pc, CustomEffectType.Chainspell) && executionType == PerkExecutionType.ForceAbility; if (!hasChainspell && cooldown != null) { // Mark cooldown on category AbilityService.ApplyCooldown(pc, cooldown, perk, featID); } pc.IsBusy = false; pc.SetLocalInt(spellUUID, (int)SpellStatusType.Completed); return(true); } }
public static void HandleQueueWeaponSkill(NWPlayer pc, Data.Entity.Perk entity, IPerkHandler ability, int spellFeatID) { int? cooldownCategoryID = ability.CooldownCategoryID(pc, entity.CooldownCategoryID, spellFeatID); var cooldownCategory = DataService.Get <CooldownCategory>(cooldownCategoryID); string queueUUID = Guid.NewGuid().ToString(); pc.SetLocalInt("ACTIVE_WEAPON_SKILL", entity.ID); pc.SetLocalString("ACTIVE_WEAPON_SKILL_UUID", queueUUID); pc.SetLocalInt("ACTIVE_WEAPON_SKILL_FEAT_ID", spellFeatID); pc.SendMessage("Weapon skill '" + entity.Name + "' queued for next attack."); ApplyCooldown(pc, cooldownCategory, ability, spellFeatID); // Player must attack within 30 seconds after queueing or else it wears off. _.DelayCommand(30f, () => { if (pc.GetLocalString("ACTIVE_WEAPON_SKILL_UUID") == queueUUID) { pc.DeleteLocalInt("ACTIVE_WEAPON_SKILL"); pc.DeleteLocalString("ACTIVE_WEAPON_SKILL_UUID"); pc.DeleteLocalInt("ACTIVE_WEAPON_SKILL_FEAT_ID"); pc.SendMessage("Your weapon skill '" + entity.Name + "' is no longer queued."); } }); }
private static void ActivateAbility(NWPlayer pc, NWObject target, Data.Entity.Perk entity, IPerkHandler perkHandler, int pcPerkLevel, PerkExecutionType executionType, int spellFeatID) { string uuid = Guid.NewGuid().ToString(); var effectiveStats = PlayerStatService.GetPlayerItemEffectiveStats(pc); int itemBonus = effectiveStats.CastingSpeed; float baseActivationTime = perkHandler.CastingTime(pc, (float)entity.BaseCastingTime, spellFeatID); float activationTime = baseActivationTime; int vfxID = -1; int animationID = -1; // Activation Bonus % - Shorten activation time. if (itemBonus > 0) { float activationBonus = Math.Abs(itemBonus) * 0.01f; activationTime = activationTime - activationTime * activationBonus; } // Activation Penalty % - Increase activation time. else if (itemBonus < 0) { float activationPenalty = Math.Abs(itemBonus) * 0.01f; activationTime = activationTime + activationTime * activationPenalty; } if (baseActivationTime > 0f && activationTime < 1.0f) { activationTime = 1.0f; } // Force ability armor penalties if (executionType == PerkExecutionType.ForceAbility) { float armorPenalty = 0.0f; string penaltyMessage = string.Empty; foreach (var item in pc.EquippedItems) { if (item.CustomItemType == CustomItemType.HeavyArmor) { armorPenalty = 2; penaltyMessage = "Heavy armor slows your force activation speed by 100%."; break; } else if (item.CustomItemType == CustomItemType.LightArmor) { armorPenalty = 1.25f; penaltyMessage = "Light armor slows your force activation speed by 25%."; } } if (armorPenalty > 0.0f) { activationTime = baseActivationTime * armorPenalty; pc.SendMessage(penaltyMessage); } } if (_.GetActionMode(pc.Object, ACTION_MODE_STEALTH) == 1) { _.SetActionMode(pc.Object, ACTION_MODE_STEALTH, 0); } _.ClearAllActions(); BiowarePosition.TurnToFaceObject(target, pc); if (executionType == PerkExecutionType.ForceAbility) { vfxID = VFX_DUR_IOUNSTONE_YELLOW; animationID = ANIMATION_LOOPING_CONJURE1; } if (vfxID > -1) { var vfx = _.EffectVisualEffect(vfxID); vfx = _.TagEffect(vfx, "ACTIVATION_VFX"); _.ApplyEffectToObject(DURATION_TYPE_TEMPORARY, vfx, pc.Object, activationTime + 0.2f); } if (animationID > -1) { pc.AssignCommand(() => _.ActionPlayAnimation(animationID, 1.0f, activationTime - 0.1f)); } pc.IsBusy = true; CheckForSpellInterruption(pc, uuid, pc.Position); pc.SetLocalInt(uuid, (int)SpellStatusType.Started); NWNXPlayer.StartGuiTimingBar(pc, (int)activationTime, ""); int perkID = entity.ID; pc.DelayEvent <FinishAbilityUse>(activationTime + 0.2f, pc, uuid, perkID, target, pcPerkLevel, spellFeatID); }