static bool Prefix(RuleCalculateAttackBonus __instance, RulebookEventContext context) { FlankingParameters flankedParameters = new FlankingParameters(typeof(RuleCalculateAttackBonus_OnTrigger_Patch), __instance.Initiator); UnitCombatState_get_IsFlanked_Patch.PushFlankingParameters(flankedParameters); return(true); }
public override IEnumerator <AbilityDeliveryTarget> Deliver(AbilityExecutionContext context, TargetWrapper target) { UnitEntityData caster = context.MaybeCaster; if (caster == null) { UberDebug.LogError("Caster is missing", Array.Empty <object>()); yield break; } RulebookEventContext rulebookContext = context.RulebookContext; RuleAttackWithWeapon attackWithWeapon = (rulebookContext != null) ? rulebookContext.AllEvents.LastOfType <RuleAttackWithWeapon>() : null; RuleAttackWithWeapon ruleAttackWithWeapon = attackWithWeapon; RuleAttackRoll attackRoll = (ruleAttackWithWeapon != null) ? ruleAttackWithWeapon.AttackRoll : null; attackRoll = (attackRoll ?? context.TriggerRule <RuleAttackRoll>(new RuleAttackRoll(caster, target.Unit, caster.GetFirstWeapon(), 0))); if (attackWithWeapon == null) { attackRoll.ConsumeMirrorImageIfNecessary(); } yield return(new AbilityDeliveryTarget(target) { AttackRoll = attackRoll }); yield break; }
//static public Dictionary<(MechanicsContext, UnitEntityData), bool> spell_target_map = new Dictionary<(MechanicsContext, UnitEntityData), bool>(); static bool Prefix(RulePrepareDamage __instance, RulebookEventContext context) { if (!Main.settings.one_sneak_attack_per_target_per_spell) { return(true); } AbilityData ability = __instance.ParentRule?.Reason?.Ability; if (ability == null || __instance.Target == null) { return(true); } var context2 = __instance.ParentRule?.Reason?.Context; if (context2 == null) { return(true); } var unit_part_affected_by_spell = __instance.Target.Ensure <UnitPartAffectedBySpell>(); if (!unit_part_affected_by_spell.contexts.Contains(context2)) { unit_part_affected_by_spell.contexts.Add(context2); return(true); } __instance.IsSurpriseSpell = false; __instance.ParentRule.AttackRoll?.UseSneakAttack(); return(true); }
static bool Prefix(RuleCheckCastingDefensively __instance, RulebookEventContext context) { if (__instance.Spell.Blueprint.GetComponent <AbilityKineticist>() == null) { return(true); } var kineticist_part = __instance.Initiator.Get <UnitPartKineticist>(); if (kineticist_part == null) { return(true); } var tr = Harmony12.Traverse.Create(__instance); var rule = Rulebook.Trigger <RuleCalculateAbilityParams>(new RuleCalculateAbilityParams(__instance.Initiator, __instance.Spell)); var ability_params = rule.Result; tr.Property("DC").SetValue(15 + ability_params.SpellLevel * 2); var bonus_concentration = Helpers.GetField <int>(rule, "m_BonusConcentration"); tr.Property("Concentration").SetValue(bonus_concentration + kineticist_part.ClassLevel + kineticist_part.MainStatBonus); tr.Property("ResultRollRaw").SetValue(RulebookEvent.Dice.D20); return(false); }
static bool Prefix(RulePrepareDamage __instance, RulebookEventContext context) { FlankingParameters flankedParameters = new FlankingParameters(typeof(RulePrepareDamage_OnTrigger_Patch), __instance.Initiator, true, null, null); UnitCombatState_get_IsFlanked_Patch.PushFlankingParameters(flankedParameters); return(true); }
static public bool Prefix(RuleDealDamage __instance, RulebookEventContext context) { if (__instance.Target == null) { return(true); } var context2 = __instance.Reason.Context; if (context2?.AssociatedBlueprint != null && context2.AssociatedBlueprint is BlueprintBuff) {//do not apply shadow twice return(true); } var summoned_context = ShadowSpells.getShadowBuff(__instance.Initiator.Descriptor)?.MaybeContext; if (context2 == null && summoned_context == null) { return(true); } var shadow_descriptor2 = (context2?.SpellDescriptor).GetValueOrDefault() & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow; var shadow_summon = (summoned_context?.SpellDescriptor).GetValueOrDefault() & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow; if (shadow_summon == SpellDescriptor.None && shadow_descriptor2 == SpellDescriptor.None) { return(true); } if (shadow_summon > shadow_descriptor2) { context2 = summoned_context; } if (!__instance.Target.Ensure <UnitPartDisbelief>().disbelief_contexts.ContainsKey(context2)) { RuleSavingThrow ruleSavingThrow = context2.TriggerRule <RuleSavingThrow>(new RuleSavingThrow(__instance.Target, SavingThrowType.Will, context2.Params.DC)); __instance.Target.Ensure <UnitPartDisbelief>().disbelief_contexts[context2] = ruleSavingThrow.IsPassed; } if (__instance.Target.Ensure <UnitPartDisbelief>().disbelief_contexts[context2]) { if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow20) { __instance.ReducedBecauseOfShadowEvocation = true; } else if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow60) { __instance.ReducedBecauseOfShadowEvocationGreater = true; } else if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow80) { __instance.Modifier = new float?((__instance.Modifier.HasValue ? __instance.Modifier.GetValueOrDefault() : 1f) * 0.8f); } } return(true); }
public override void OnTrigger(RulebookEventContext context) { if (!Weapon.IsMelee) { return; } Result = Initiator.GetActiveCombatManeuverToggle(); if (Result == CombatManeuver.Trip) { UnitState state = Target.Descriptor.State; // same checks as in UnitProneController, if this is true (and the unit is not in a cutscene), state.Prone.Active will be true on the next tick and we also don't want to trip again. if (state.Prone.Active || state.Prone.ShouldBeActive || !state.IsConscious || state.HasCondition(UnitCondition.Prone) || state.HasCondition(UnitCondition.Sleeping) || state.HasCondition(UnitCondition.Unconscious)) { Result = CombatManeuver.None; } } else if (Result == CombatManeuver.Disarm) { bool canBeDisarmed = false; // same checks as in RuleCombatManeuver. If the unit cannot be disarmed (further), don't attempt to disarm. ItemEntityWeapon maybeWeapon = Target.Body.PrimaryHand.MaybeWeapon; ItemEntityWeapon maybeWeapon2 = Target.Body.SecondaryHand.MaybeWeapon; if (maybeWeapon != null && !maybeWeapon.Blueprint.IsUnarmed && !maybeWeapon.Blueprint.IsNatural && !Target.Descriptor.Buffs.HasFact(BlueprintRoot.Instance.SystemMechanics.DisarmMainHandBuff)) { canBeDisarmed = true; } else if (maybeWeapon2 != null && !maybeWeapon2.Blueprint.IsUnarmed && !maybeWeapon2.Blueprint.IsNatural && !Target.Descriptor.Buffs.HasFact(BlueprintRoot.Instance.SystemMechanics.DisarmOffHandBuff)) { canBeDisarmed = true; } if (!canBeDisarmed) { Result = CombatManeuver.None; } } else if (Result == CombatManeuver.SunderArmor) { if (Target.Descriptor.Buffs.HasFact(BlueprintRoot.Instance.SystemMechanics.SunderArmorBuff)) { Result = CombatManeuver.None; } } if (ForceNormalAttack) { Result = CombatManeuver.None; } }
static bool Prefix(RuleCalculateAC __instance, RulebookEventContext context) { Cover cover = Rulebook.Trigger(new RuleCheckSoftCover(__instance.Initiator, __instance.Target, __instance.AttackType)).Result; if (cover == Cover.Full) { __instance.AddBonus(4, SoftCoverFact); } else if (cover == Cover.Partial) { __instance.AddBonus(2, SoftCoverPartialFact); } return(true); }
static bool Prefix(RuleCombatManeuver __instance, RulebookEventContext context) { bool provokeAoO; if (!provokeAoOOnCombatManeuverAttempt.TryGetValue(__instance.Initiator.UniqueId, out provokeAoO) || provokeAoO) { if (!__instance.Target.CombatState.IsEngage(__instance.Initiator)) { return(true); } __instance.Target.CombatState.AttackOfOpportunity(__instance.Initiator); } return(true); }
static bool Prefix(RuleCalculateAC __instance, RulebookEventContext context) { var current_cover = __instance.Target.hasCoverFrom(__instance.Initiator, __instance.AttackType); //Main.logger.Log(current_cover.ToString() + " " + __instance.AttackType.ToString()); if (current_cover.isFull()) { __instance.AddBonus(Cover.cover_ac_bonus, Cover.soft_cover_fact); } else if (current_cover.isPartial()) { __instance.AddBonus(Cover.partial_cover_ac_bonus, Cover.partial_soft_cover_fact); } return(true); }
static bool Prefix(RuleCalculateAC __instance, RulebookEventContext context) { if (__instance.AttackType == Kingmaker.RuleSystem.AttackType.Melee || __instance.AttackType == Kingmaker.RuleSystem.AttackType.Touch) { var state = __instance.Target.Descriptor.State; bool target_unable_to_fly = !state.CanMove || __instance.IsTargetFlatFooted; bool target_fly = __instance.Target.Ensure <UnitPartFlying>().isFlying() && !target_unable_to_fly; bool attacker_fly = __instance.Initiator.Ensure <UnitPartFlying>().isFlying() && __instance.Initiator.Descriptor.State.CanMove; if (target_fly && !attacker_fly) { __instance.AddBonus(FixFlying.fly_ac_bonus, FixFlying.flying_fact); } } return(true); }
public override void OnTrigger(RulebookEventContext context) { float multiplier = Modifier ?? 1f; int healAmount = Math.Max(0, (int)((Dice.D(HealFormula) + Bonus) * multiplier)); var value = Math.Min(healAmount, Target.Damage); setValue(this, value); Target.Damage -= value; Log.Write($"{GetType().Name}: healed {Target.CharacterName} for {Value} HP ({value}), damage now at: {Target.Damage}"); UnitPartDualCompanion.HandleHealing(Target, healAmount); EventBus.RaiseEvent((IHealingHandler h) => h.HandleHealing(Initiator, Target, Value)); var overflow = healAmount - Target.Damage; if (overflow > 0) { OverflowHealing = overflow; } }
static public bool Prefix(RuleHealDamage __instance, RulebookEventContext context) { if (__instance.Target == null) { return(true); } var context2 = __instance.Reason.Context; if (context2?.AssociatedBlueprint != null && context2.AssociatedBlueprint is BlueprintBuff) {//do not apply shadow twice return(true); } context2 = ShadowSpells.extractMainContext(context2, __instance.Initiator); if (context2 == null) { return(true); } if (!__instance.Target.Ensure <UnitPartDisbelief>().attemptedDisbelief(context2)) { if (__instance.Target.Descriptor.State.HasCondition(UnitCondition.TrueSeeing)) { __instance.Target.Ensure <UnitPartDisbelief>().register(context2, true); } else { __instance.Target.Ensure <UnitPartDisbelief>().register(context2, ShadowSpells.makeDisbeliefSave(context2, __instance.Target)); } } if (__instance.Target.Ensure <UnitPartDisbelief>().disbelieved(context2)) { int illusion_reality = ShadowSpells.getSpellReality(context2); if (illusion_reality > 0) { __instance.Modifier = new float?((__instance.Modifier.HasValue ? __instance.Modifier.GetValueOrDefault() : 1f) * 0.01f * illusion_reality); Common.AddBattleLogMessage(__instance.Target.CharacterName + " reduces healing from " + context2.SourceAbility.Name + " to " + illusion_reality.ToString() + "% due to disbelief"); } } return(true); }
/*static IEnumerable<Harmony12.CodeInstruction> Transpiler(IEnumerable<Harmony12.CodeInstruction> instructions) * { * var codes = instructions.ToList(); * var check_is_unarmed = codes.FindIndex(x => x.opcode == System.Reflection.Emit.OpCodes.Callvirt && x.operand.ToString().Contains("IsUnarmed")); //checking is unarmed on weapon on primary hand * * codes[check_is_unarmed] = new Harmony12.CodeInstruction(System.Reflection.Emit.OpCodes.Ldarg_0); * codes.Insert(check_is_unarmed + 1, new Harmony12.CodeInstruction(System.Reflection.Emit.OpCodes.Call, new Func<BlueprintItemWeapon, RuleCalculateAttacksCount, bool>(considerUnarmedAndIgnore).Method)); * * check_is_unarmed = codes.IndexOf(codes.FindAll(x => x.opcode == System.Reflection.Emit.OpCodes.Callvirt && x.operand.ToString().Contains("IsUnarmed"))[2]); * codes[check_is_unarmed] = new Harmony12.CodeInstruction(System.Reflection.Emit.OpCodes.Ldarg_0); * codes.Insert(check_is_unarmed + 1, new Harmony12.CodeInstruction(System.Reflection.Emit.OpCodes.Call, new Func<BlueprintItemWeapon, RuleCalculateAttacksCount, bool>(considerUnarmedAndIgnoreOffHand).Method)); * return codes.AsEnumerable(); * }*/ static bool Prefix(RuleCalculateAttacksCount __instance, RulebookEventContext context) { int bab = (int)__instance.Initiator.Stats.BaseAttackBonus; int num_penalized_attacks = Math.Min(Math.Max(0, bab / 5 - (bab % 5 == 0 ? 1 : 0)), 3); HandSlot primary_hand = __instance.Initiator.Body.PrimaryHand; HandSlot secondary_hand = __instance.Initiator.Body.SecondaryHand; BlueprintItemWeapon blueprint1 = primary_hand.MaybeWeapon?.Blueprint; BlueprintItemWeapon blueprint2 = secondary_hand.MaybeShield != null ? ((bool)__instance.Initiator.Descriptor.State.Features.ShieldBash ? secondary_hand.MaybeShield.WeaponComponent?.Blueprint : null) : secondary_hand.MaybeWeapon?.Blueprint; int num = primary_hand.MaybeWeapon == null ? 0 : (primary_hand.MaybeWeapon.HoldInTwoHands ? 1 : 0); if ((secondary_hand.MaybeWeapon?.HoldInTwoHands).GetValueOrDefault() == false && (blueprint1 != null) && (!considerUnarmedAndIgnore(blueprint1, __instance) || blueprint2 == null || blueprint2.IsUnarmed) ) { ++__instance.PrimaryHand.MainAttacks; if (!blueprint1.IsNatural || (bool)__instance.Initiator.Descriptor.State.Features.IterativeNaturalAttacks || __instance.ForceIterativeNaturealAttacks || blueprint1.IsUnarmed) { __instance.PrimaryHand.PenalizedAttacks += Math.Max(0, num_penalized_attacks); } } if ((secondary_hand.MaybeWeapon?.HoldInTwoHands).GetValueOrDefault() == true || (blueprint2 == null) || considerUnarmedAndIgnoreOffHand(blueprint2, __instance) && (blueprint1 != null) || (primary_hand.MaybeWeapon?.HoldInTwoHands).GetValueOrDefault() == true ) { return(false); } ++__instance.SecondaryHand.MainAttacks; if (blueprint1 == null || !considerUnarmedAndIgnore(blueprint1, __instance) && (blueprint1 == null || !blueprint1.IsNatural || !(bool)__instance.Initiator.Descriptor.State.Features.IterativeNaturalAttacks && !__instance.ForceIterativeNaturealAttacks) || (blueprint2.IsUnarmed) ) { return(false); } __instance.SecondaryHand.PenalizedAttacks += Math.Max(0, num_penalized_attacks); return(false); }
static public bool Prefix(RuleApplyBuff __instance, RulebookEventContext context) { if (ShadowSpells.isShadowBuff(__instance.Blueprint)) { return(true); } var context2 = __instance.Reason.Context; if (context2?.AssociatedBlueprint != null && context2.AssociatedBlueprint is BlueprintBuff) {//do not apply shadow twice return(true); } var summoned_context = ShadowSpells.getShadowBuff(__instance.Initiator.Descriptor)?.MaybeContext; if (context2 == null && summoned_context == null) { return(true); } var shadow_descriptor2 = (context2?.SpellDescriptor).GetValueOrDefault() & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow; var shadow_summon = (summoned_context?.SpellDescriptor).GetValueOrDefault() & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow; if (shadow_summon == SpellDescriptor.None && shadow_descriptor2 == SpellDescriptor.None) { return(true); } if (shadow_summon > shadow_descriptor2) { context2 = summoned_context; } if (__instance.Initiator == null) { return(true); } if (!__instance.Initiator.Ensure <UnitPartDisbelief>().disbelief_contexts.ContainsKey(context2)) { RuleSavingThrow ruleSavingThrow = context2.TriggerRule <RuleSavingThrow>(new RuleSavingThrow(__instance.Initiator, SavingThrowType.Will, context2.Params.DC)); __instance.Initiator.Ensure <UnitPartDisbelief>().disbelief_contexts[context2] = ruleSavingThrow.IsPassed; } if (__instance.Initiator.Ensure <UnitPartDisbelief>().disbelief_contexts[context2]) { if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow20) { if (RulebookEvent.Dice.D(new DiceFormula(1, DiceType.D100)) > 20) { __instance.CanApply = false; Common.AddBattleLogMessage(__instance.Initiator.CharacterName + " avoids " + context2.SourceAbility.Name + " effect due to disbelief"); return(false); } } else if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow60) { if (RulebookEvent.Dice.D(new DiceFormula(1, DiceType.D100)) > 60) { __instance.CanApply = false; Common.AddBattleLogMessage(__instance.Initiator.CharacterName + " avoids " + context2.SourceAbility.Name + " effect due to disbelief"); return(false); } } else if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow80) { if (RulebookEvent.Dice.D(new DiceFormula(1, DiceType.D100)) > 80) { __instance.CanApply = false; Common.AddBattleLogMessage(__instance.Initiator.CharacterName + " avoids " + context2.SourceAbility.Name + " effect due to disbelief"); return(false); } } } return(true); }
static void Postfix(RuleAttackRoll __instance, RulebookEventContext context) { OutflankPatch.OutflankProvokeAttackPatch(__instance, context); }
internal static void OutflankProvokeAttackPatch(RuleAttackRoll attackRollInstance, RulebookEventContext context) { if (!attackRollInstance.IsCriticalConfirmed) { return; } var outflankFeature = library.Get <BlueprintFeature>("422dab7309e1ad343935f33a4d6e9f11"); Func <UnitEntityData, UnitEntityData, UnitEntityData, bool> outflankParticipantConditions = (target, aooTestUnit, attacker) => aooTestUnit.Descriptor.State.Features.SoloTactics || aooTestUnit.Descriptor.HasFact(outflankFeature) && attacker.Descriptor.HasFact(outflankFeature); foreach (UnitEntityData aooTestUnit in attackRollInstance.Target.CombatState.EngagedBy) { if (attackRollInstance.Target.IsFlankedByUnits(aooTestUnit, attackRollInstance.Initiator, outflankParticipantConditions)) { Main.Logger?.Write("Outflank provoked AoO"); Game.Instance.CombatEngagementController.ForceAttackOfOpportunity(aooTestUnit, attackRollInstance.Target); } } }
static public bool Prefix(RuleApplyBuff __instance, RulebookEventContext context) { if (__instance.Blueprint?.GetComponent <IgnoreShadowReality>() != null) { return(true); } var rule_summon = Rulebook.CurrentContext.AllEvents.LastOfType <RuleSummonUnit>(); if (rule_summon != null) {//do not interrupt summon buffs return(true); } var context2 = __instance.Reason.Context; //there are also actions after summon that should not be affected //we need to check if we are still inside SpawnMonsterComponent var summon_context = __instance.Initiator.Buffs?.GetBuff(Game.Instance.BlueprintRoot.SystemMechanics.SummonedUnitBuff)?.MaybeContext?.ParentContext; if (summon_context == context2) { return(true); } if (context2?.AssociatedBlueprint != null && context2.AssociatedBlueprint is BlueprintBuff) {//do not apply shadow twice return(true); } context2 = ShadowSpells.extractMainContext(context2, context2?.MaybeCaster); if (context2 == null) { return(true); } if (__instance.Initiator == null) { return(true); } if (!__instance.Initiator.Ensure <UnitPartDisbelief>().attemptedDisbelief(context2)) { if (__instance.Initiator.Descriptor.State.HasCondition(UnitCondition.TrueSeeing)) { __instance.Initiator.Ensure <UnitPartDisbelief>().register(context2, true); } else { __instance.Initiator.Ensure <UnitPartDisbelief>().register(context2, ShadowSpells.makeDisbeliefSave(context2, __instance.Initiator)); } } if (__instance.Initiator.Ensure <UnitPartDisbelief>().disbelieved(context2)) { int illusion_reality = ShadowSpells.getSpellReality(context2); int result = RulebookEvent.Dice.D(new DiceFormula(1, DiceType.D100)); if (illusion_reality > 0 && result > illusion_reality) { __instance.CanApply = false; Common.AddBattleLogMessage(__instance.Initiator.CharacterName + " avoids " + context2.SourceAbility.Name + $" effect due to disbelief (rolled {result} vs {illusion_reality}%)"); return(false); } } return(true); }
public override void OnTrigger(RulebookEventContext context) { CanTargetUnit = !IsImmune; //Log.Write($"RuleSpellTargetCheck: caster {Initiator} can target {Target}? {CanTargetUnit}"); }
static public bool Prefix(RuleApplyBuff __instance, RulebookEventContext context) { /*if (ShadowSpells.isShadowBuff(__instance.Blueprint)) * { * return true; * }*/ var rule_summon = Rulebook.CurrentContext.AllEvents.LastOfType <RuleSummonUnit>(); if (rule_summon != null) {//do not interrupt summon buffs return(true); } var context2 = __instance.Reason.Context; //there are also actions after summon that should not be affected //we need to check if we are still inside SpawnMonsterComponent var summon_context = __instance.Initiator.Buffs?.GetBuff(Game.Instance.BlueprintRoot.SystemMechanics.SummonedUnitBuff)?.MaybeContext?.ParentContext; if (summon_context == context2) { return(true); } if (context2?.AssociatedBlueprint != null && context2.AssociatedBlueprint is BlueprintBuff) {//do not apply shadow twice return(true); } var summoned_context = ShadowSpells.getShadowBuff(__instance.Initiator.Descriptor)?.MaybeContext; if (context2 == null && summoned_context == null) { return(true); } var shadow_descriptor2 = (context2?.SpellDescriptor).GetValueOrDefault() & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow; var shadow_summon = (summoned_context?.SpellDescriptor).GetValueOrDefault() & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow; if (shadow_summon == SpellDescriptor.None && shadow_descriptor2 == SpellDescriptor.None) { return(true); } if (shadow_summon > shadow_descriptor2) { context2 = summoned_context; } if (__instance.Initiator == null) { return(true); } if (!__instance.Initiator.Ensure <UnitPartDisbelief>().disbelief_contexts.ContainsKey(context2)) { RuleSavingThrow ruleSavingThrow = context2.TriggerRule <RuleSavingThrow>(new RuleSavingThrow(__instance.Initiator, SavingThrowType.Will, context2.Params.DC)); __instance.Initiator.Ensure <UnitPartDisbelief>().disbelief_contexts[context2] = ruleSavingThrow.IsPassed; } if (__instance.Initiator.Ensure <UnitPartDisbelief>().disbelief_contexts[context2]) { if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow20) { if (RulebookEvent.Dice.D(new DiceFormula(1, DiceType.D100)) > 20) { __instance.CanApply = false; Common.AddBattleLogMessage(__instance.Initiator.CharacterName + " avoids " + context2.SourceAbility.Name + " effect due to disbelief"); return(false); } } else if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow60) { if (RulebookEvent.Dice.D(new DiceFormula(1, DiceType.D100)) > 60) { __instance.CanApply = false; Common.AddBattleLogMessage(__instance.Initiator.CharacterName + " avoids " + context2.SourceAbility.Name + " effect due to disbelief"); return(false); } } else if ((context2.SpellDescriptor & (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow) == (SpellDescriptor)AdditionalSpellDescriptors.ExtraSpellDescriptor.Shadow80) { if (RulebookEvent.Dice.D(new DiceFormula(1, DiceType.D100)) > 80) { __instance.CanApply = false; Common.AddBattleLogMessage(__instance.Initiator.CharacterName + " avoids " + context2.SourceAbility.Name + " effect due to disbelief"); return(false); } } } return(true); }
public override void OnTrigger(RulebookEventContext context) { if (!Main.Settings.UseSoftCover) { Main.Logger?.Write("Soft Cover Rules disabled, result of soft cover check is Cover.None"); Result = Cover.None; return; } Main.Logger?.Append("RuleCheckSoftCover Triggered"); Main.Logger?.Append($"Attacker = {Initiator.CharacterName} ({Initiator.UniqueId})"); Main.Logger?.Append($"Target = {Target.CharacterName} ({Target.UniqueId})"); Main.Logger?.Append($"#IgnoreUnits = {IgnoreUnits.Count}"); Main.Logger?.Append($"AttackerIgnoresCover = {AttackerIgnoresCover}"); Main.Logger?.Append($"AttackType = {AttackType}"); if (AttackerIgnoresCover) { Main.Logger?.Flush(); return; } Vector2 toPosition = Target.Position.To2D(); Vector2 fromPosition = Initiator.Position.To2D(); int i = 0; List <UnitEntityData> unitsToCheck = new List <UnitEntityData>(); Main.Logger?.Append("Looping over units in combat..."); foreach (UnitEntityData unit in Game.Instance.State.AwakeUnits) { i++; if (unit == Initiator || unit == Target) { Main.Logger?.Append($"{i}: unit {unit.CharacterName} ({unit.UniqueId}) is either the Initiator or the Target and does not count for cover"); continue; } if (IgnoreUnits.Contains(unit)) { Main.Logger?.Append($"{i}: unit {unit.CharacterName} ({unit.UniqueId}) is in the IgnoreUnits list and does not count for cover"); continue; } if (unit.Descriptor.State.IsDead) { Main.Logger?.Append($"{i}: unit {unit.CharacterName} ({unit.UniqueId}) is dead and does not count for cover"); continue; } if (unit.Descriptor.State.Prone.Active) { Main.Logger?.Append($"{i}: unit {unit.CharacterName} ({unit.UniqueId}) is prone and does not count for cover"); continue; } int sizeDifference = Target.Descriptor.State.Size - unit.Descriptor.State.Size; // e.g. a Small character cannot provide soft cover for a Large character if (sizeDifference >= 2) { Main.Logger?.Append($"{i}: the target is of size {Target.Descriptor.State.Size}, while unit {unit.CharacterName} ({unit.UniqueId}) is of size {unit.Descriptor.State.Size}, so the unit does not count for cover."); continue; } Vector2 unitPosition = unit.Position.To2D(); // A unit that is closer to the attacker than to the target and is smaller than the attacker does not provide cover to the target if (unit.Descriptor.State.Size < Initiator.Descriptor.State.Size && Vector2.Distance(fromPosition, unitPosition) < Vector2.Distance(unitPosition, toPosition)) { Main.Logger?.Append($"{i}: the attacker is of size {Initiator.Descriptor.State.Size}, the unit is of size {unit.Descriptor.State.Size}, and the unit is {Vector2.Distance(fromPosition, unitPosition)} away from the attacker and {Vector2.Distance(unitPosition, toPosition)} away from the target, so the unit does not count for cover."); continue; } // Filter nonsensical cases if (Vector2.Distance(fromPosition, toPosition) < Vector2.Distance(fromPosition, unitPosition) || VectorMath.AngleBetweenPoints(unitPosition, fromPosition, toPosition) < 90.0f) { Main.Logger?.Append($"{i}: Possibility of {unit.CharacterName} ({unit.UniqueId}) providing cover considered nonsensical: "); Main.Logger?.Append($" - Distance from attacker to target: {Vector2.Distance(fromPosition, toPosition)}"); Main.Logger?.Append($" - Distance from attacker to unit: {Vector2.Distance(fromPosition, unitPosition)}"); Main.Logger?.Append($" - Angle between attacker - unit - target: {VectorMath.AngleBetweenPoints(unitPosition, fromPosition, toPosition)}"); continue; } unitsToCheck.Add(unit); Main.Logger?.Append($"{i}: unit {unit.CharacterName} ({unit.UniqueId}) added to list of potential cover-providing units"); } if (unitsToCheck.Count < 1) { Main.Logger?.Append("No units could possibly provide cover."); Main.Logger?.Flush(); return; } Vector2 direction = (toPosition - fromPosition).normalized; Vector2 up = new Vector2(direction.y, -direction.x); Vector2 down = new Vector2(-direction.y, direction.x); Vector2 fromPointsStart = fromPosition + up * Initiator.Corpulence; Vector2 fromPointsEnd = fromPosition + down * Initiator.Corpulence; Vector2 tangent1 = Vector2.zero; Vector2 tangent2 = Vector2.zero; VectorMath.TangentPointsOnCircleFromPoint(fromPointsStart, toPosition, Target.Corpulence, out tangent1, out tangent2); Vector2 toPointsStart = Vector2.zero; float normDist = 0.0f; if (Pathfinding.VectorMath.SegmentsIntersect2D(fromPosition, toPosition, fromPointsStart, tangent1, out normDist)) { toPointsStart = tangent2; } else { toPointsStart = tangent1; } Vector2 tangent3 = Vector2.zero; Vector2 tangent4 = Vector2.zero; VectorMath.TangentPointsOnCircleFromPoint(fromPointsEnd, toPosition, Target.Corpulence, out tangent3, out tangent4); Vector2 toPointsEnd = Vector2.zero; if (Pathfinding.VectorMath.SegmentsIntersect2D(fromPosition, toPosition, fromPointsEnd, tangent3, out normDist)) { toPointsEnd = tangent4; } else { toPointsEnd = tangent3; } Vector2[] fromPoints = VectorMath.FindEquidistantPointsOnArc(fromPointsStart, fromPointsEnd, fromPosition, Initiator.Corpulence, 10); Vector2[] toPoints = VectorMath.FindEquidistantPointsOnArc(toPointsStart, toPointsEnd, toPosition, Target.Corpulence, 10); Main.Logger?.Append("Looping over lines..."); int raysBlocked = 0; for (i = 0; i < fromPoints.Length; i++) { Main.Logger?.Append($" - Checking line {i} ({fromPoints[i]} to {toPoints[i]})..."); #if DEBUG if (i > 0) { Main.Logger?.Append($" * Validate: lines {i} and {i - 1} intersect: {Pathfinding.VectorMath.SegmentsIntersect2D(fromPoints[i - 1], toPoints[i - 1], fromPoints[i], toPoints[i], out normDist)}"); } #endif for (int j = 0; j < unitsToCheck.Count; j++) { var p = VectorMath.NearestPointOnSegmentToPoint(fromPoints[i], toPoints[i], unitsToCheck[j].Position.To2D()); var d = Vector2.Distance(p, unitsToCheck[j].Position.To2D()); Main.Logger?.Append($" * closest distance of line {i} to unit {unitsToCheck[j].CharacterName} ({unitsToCheck[j].UniqueId}): {d}. Unit's corpulence: {unitsToCheck[j].Corpulence}"); if (Vector2.Distance(p, unitsToCheck[j].Position.To2D()) < unitsToCheck[j].Corpulence) { raysBlocked++; Main.Logger?.Append($" * line {i} is obstructed by unit {unitsToCheck[j].CharacterName} ({unitsToCheck[j].UniqueId}). Number of obstructed lines so far = {raysBlocked}. Continuing to next line..."); break; } else { Main.Logger?.Append($" * line {i} is not obstructed by unit {unitsToCheck[j].CharacterName} ({unitsToCheck[j].UniqueId})"); } } if (raysBlocked > 6) { break; } } Main.Logger?.Append($"Finished looping over target lines. Total lines blocked: {raysBlocked}"); if (raysBlocked > 6) { Result = Cover.Full; } else if (raysBlocked > 2) { Result = Cover.Partial; } else { Result = Cover.None; } Main.Logger?.Append($"Final cover result: {Result}"); Main.Logger?.Flush(); }
static bool Prefix(RuleCalculateWeaponStats __instance, RulebookEventContext context) { __instance.Initiator?.Get <UnitPartDoubleWeaponSize>()?.maybeApply(__instance); return(true); }
static void Postfix(RuleCombatManeuver __instance, RulebookEventContext context) { provokeAoOOnCombatManeuverAttempt[__instance.Initiator.UniqueId] = true; }
internal static bool Prefix(RuleCalculateAttackBonusWithoutTarget __instance, RulebookEventContext context) { if (__instance.Weapon == null || !__instance.Weapon.Blueprint.IsMelee) { return(true); } var blade_tutor_part = __instance?.Initiator?.Get <BladeTutorUnitPart>(); if (blade_tutor_part == null) { return(true); } var res = blade_tutor_part.getValue(); if (res.Item2 == null || res.Item1 == 0) { return(true); } int total_penalty = 0; var attack_bonus = __instance.Initiator.Stats.GetStat(StatType.AdditionalAttackBonus); foreach (var m in attack_bonus.Modifiers) { if (m.Source?.Blueprint != null && facts.Contains(m.Source.Blueprint)) { total_penalty -= m.ModValue; } } foreach (var m in __instance.BonusSources) { if (m.Source?.Blueprint != null && facts.Contains(m.Source.Blueprint)) { total_penalty -= m.Bonus; } } int bonus = Math.Min(total_penalty, res.Item1); __instance.AddBonus(bonus, res.Item2); return(true); }
public override void OnTrigger(RulebookEventContext context) { }
static void Postfix(RulePrepareDamage __instance, RulebookEventContext context) { UnitCombatState_get_IsFlanked_Patch.PopFlankingParametersIfTypeMatches(typeof(RulePrepareDamage_OnTrigger_Patch)); }