private void FindAvailableFights() { var unitsPositionsEntities = unitsPositions.Entities .Where(e => e.IsNotNullAndAlive() && e.Get <MovementComponent>().IsObjectAlive); var enemyUnitsPositionsEntities = enemyUnits.Entities .Where(e => e.IsNotNullAndAlive() && e.Get <MovementComponent>().IsObjectAlive); foreach (var allyUnit in unitsPositionsEntities) { foreach (var enemyUnit in enemyUnitsPositionsEntities) { var allyUnitAttackComponent = allyUnit.Get <AttackComponent>(); var allyUnitMovementComponent = allyUnit.Get <MovementComponent>(); var enemyUnitMovementComponent = enemyUnit.Get <MovementComponent>(); if (AttackHelper.CanAttack( allyUnitAttackComponent, allyUnitMovementComponent, enemyUnitMovementComponent)) { AttackHelper.CreateAttackEvent(allyUnit, enemyUnit); break; } } } }
public Attack(string url, string mode, int time, int id) { Mode = AttackHelper.StringToAttackType(mode); Url = url; Time = time; Id = id; }
protected void doSkill(SkillItemUIData skillData) { SkillType type = skillData.skillType; switch (type) { case SkillType.normal: //普通技能 距离 水平角度 垂直角度 //AOI管理实体视野周围的所有实体 方便获取一定范围内的实体 这里直接拿所有实体 List <BaseEntity> targetLst = EntityMgr.Instance.getEntityByType(EntityType.monster); AttackInfo info = new AttackInfo(skillData.atkRange, skillData.horAngle, skillData.verAngle); if (targetLst != null && targetLst.Count > 0) { for (int i = 0; i < targetLst.Count; i++) { bool isAttacked = AttackHelper.isAttacked(this.dyAgent, targetLst[i], info); if (isAttacked) { DamageData dt = new DamageData(); dt.casterId = this.dyAgent.UID; dt.targetId = targetLst[i].UID; dt.atkType = skillData.atkType; dt.hitDis = skillData.hitDis; dt.damage = skillData.skillDamage; targetLst[i].onDamage(dt); } } } this.nextAttackTime = -1; break; case SkillType.bullet: //子弹技能 由子弹碰撞做伤害检测 BulletFactroy.createBullet(this.dyAgent, skillData.skillBulletId); this.nextAttackTime = -1; break; } }
private void Attack() { var attackingUnitsEntities = attackingUnits.Entities .Where(u => u.IsNotNullAndAlive()) .Take(attackingUnits.GetEntitiesCount()); foreach (var unit in attackingUnitsEntities) { var attackingComponent = unit.Get <AttackingComponent>(); var unitAttackComponent = unit.Get <AttackComponent>(); var unitMovementComponent = unit.Get <MovementComponent>(); var targetUnit = attackingComponent.TargetEntity; if (!targetUnit.IsNotNullAndAlive()) { AttackHelper.StopAttack(unit); return; } var targetMovementComponent = targetUnit.Get <MovementComponent>(); if (targetMovementComponent == null || !targetMovementComponent.IsObjectAlive) { AttackHelper.StopAttack(unit); return; } if (attackingComponent.TargetEntity.Get <UnitComponent>() != null && AttackHelper.CanAttack(unitAttackComponent, unitMovementComponent, targetMovementComponent)) { unitAttackComponent.LastAttackTime = DateTime.Now; ServerClient.Communication.AttackSender.attacks.Add( new AttackInfo( unit.Get <UnitComponent>().Guid, attackingComponent.TargetEntity.Get <UnitComponent>().Guid, (int)unitAttackComponent.AttackDamage)); } if (attackingComponent.TargetEntity.Get <BuildingComponent>() != null) { var enemyBuildTransform = attackingComponent.TargetEntity.Get <BuildingComponent>().Object.transform; var attackRange = Math.Max(enemyBuildTransform.lossyScale.x, enemyBuildTransform.lossyScale.z) * 5; if (attackingComponent.TargetEntity.Get <BuildingComponent>().Tag == BuildingTag.Base) { attackRange *= 6; } if (AttackHelper.CanBuildingAttack(unitAttackComponent, unitMovementComponent, targetMovementComponent, (int)attackRange)) { unitAttackComponent.LastAttackTime = DateTime.Now; ServerClient.Communication.AttackSender.attacks.Add(new AttackInfo(unit.Get <UnitComponent>().Guid, attackingComponent.TargetEntity.Get <BuildingComponent>().Guid, (int)unitAttackComponent.AttackDamage)); } } } }
// We're just doing straight damage here public override void DealEffects(Unit target, Unit source) { if (target != null) { //deal core damage to central target target.ChangeHealth((GetDamage() * (-1)), source, this); } Vector2Int origin = source.GetMapPosition(); origin += MapMath.DirToRelativeLoc(source.GetDirection()); List <Vector2Int> area = AttackHelper.GetCircleAOE(origin, source.GetDirection(), GetRange()); //skip first one(center) for (int i = 0; i < area.Count; i++) { if (area[i] == source.GetMapPosition()) { continue; } Unit searchResult = source.globalPositionalData.SearchLocation(area[i]); if (searchResult != null) { searchResult.ChangeHealth((GetDamage() * -1) / 3, source, this); Vector2Int diff = searchResult.GetMapPosition() - origin; // AttackHelper.DisplaceUnit(searchResult, source, this, 1, MapMath.LocToDirection(diff)); } } source.ChangeHealth((GetDamage() * (-1)), source, this); }
public void Setup() { helper = new AttackHelper(); damageHelper = new DamageHelper(); collectionSelector = GetNewInstanceOf <ICollectionSelector>(); featsSelector = GetNewInstanceOf <IFeatsSelector>(); creatureDataSelector = GetNewInstanceOf <ICreatureDataSelector>(); }
private Dictionary <Element, int> GenerateDamage(Player player, IWeaponItem weapon) { var weaponDamage = weapon.GenerateDamage(); foreach (var pair in weaponDamage.ToArray()) { weaponDamage[pair.Key] = AttackHelper.CalculateDamage(pair.Value, pair.Key, player); } return(weaponDamage); }
public override void DealEffects(Unit target, Unit source) { if (target != null) { Vector2Int origin = target.GetMapPosition(); target.ChangeHealth((GetDamage() * (-1)), source, this); foreach (Vector2Int tile in MapMath.GetNeighbors(origin).Keys) { Unit searchResult = source.globalPositionalData.SearchLocation(tile); if (searchResult != null) { Vector2Int diff = tile - origin; AttackHelper.DisplaceUnit(searchResult, source, this, 1, MapMath.LocToDirection(diff)); } } } }
public override void DealEffects(Unit target, Unit source) { int pushback = 5; if (target != null) { target.ChangeHealth((GetDamage() * (-1)), source, this); Vector2Int diff = target.GetMapPosition() - source.GetMapPosition(); Vector2Int absDiff = new Vector2Int(Mathf.Abs(diff.x), Mathf.Abs(diff.y)); Debug.Assert(source.GetDirection() != Direction.NO_DIR); Vector2Int newLocation = source.GetMapPosition(); //Debug.Log(newLocation); switch (source.GetDirection()) { case Direction.N: newLocation = new Vector2Int(source.GetMapPosition().x, source.GetMapPosition().y + (absDiff.y - 1)); break; case Direction.S: newLocation = new Vector2Int(source.GetMapPosition().x, source.GetMapPosition().y - (absDiff.y - 1)); break; case Direction.W: newLocation = new Vector2Int(source.GetMapPosition().x - (absDiff.x - 1), source.GetMapPosition().y); break; case Direction.E: newLocation = new Vector2Int(source.GetMapPosition().x + (absDiff.x - 1), source.GetMapPosition().y); break; } //Debug.Log(newLocation); //check new location for issues, if so, stay at current loc // ********************************** source.Move(newLocation.x, newLocation.y, MovementType.DASH); AttackHelper.DisplaceUnit(target, source, this, pushback, source.GetDirection()); } else { Vector2Int newLocation = source.GetMapPosition() + (MapMath.DirToRelativeLoc(source.GetDirection()) * pushback); // ********************************** source.Move(newLocation.x, newLocation.y, MovementType.DASH); } }
private void CalculateDamages(Mech attacker, AbstractActor target) { Mod.MeleeLog.Info?.Write($"Calculating KICK damage for attacker: {CombatantUtils.Label(attacker)} @ {attacker.tonnage} tons " + $"vs. target: {CombatantUtils.Label(target)}"); float damage = attacker.KickDamage(); // Adjust damage for any target resistance damage = target.ApplyKickDamageReduction(damage); this.TargetDamageClusters = AttackHelper.CreateDamageClustersWithExtraAttacks(attacker, damage, ModStats.KickExtraHitsCount); StringBuilder sb = new StringBuilder(" - Target damage clusters: "); foreach (float cluster in this.TargetDamageClusters) { sb.Append(cluster); sb.Append(", "); } Mod.MeleeLog.Info?.Write(sb.ToString()); }
private void CalculateDamages(Mech attacker, AbstractActor target) { Mod.MeleeLog.Info?.Write($"Calculating PHYSICAL WEAPON damage for attacker: {CombatantUtils.Label(attacker)} " + $"vs. target: {CombatantUtils.Label(target)}"); float damage = attacker.PhysicalWeaponDamage(); damage = target.ApplyPhysicalWeaponDamageReduction(damage); // Target damage applies as a single modifier this.TargetDamageClusters = AttackHelper.CreateDamageClustersWithExtraAttacks(attacker, damage, ModStats.PhysicalWeaponExtraHitsCount); StringBuilder sb = new StringBuilder(" - Target damage clusters: "); foreach (float cluster in this.TargetDamageClusters) { sb.Append(cluster); sb.Append(", "); } Mod.MeleeLog.Info?.Write(sb.ToString()); }
public static AttackSelection From(string rawData) { var helper = new AttackHelper(); var data = helper.ParseEntry(rawData); var selection = new AttackSelection(); selection.IsMelee = Convert.ToBoolean(data[DataIndexConstants.AttackData.IsMeleeIndex]); selection.IsNatural = Convert.ToBoolean(data[DataIndexConstants.AttackData.IsNaturalIndex]); selection.IsPrimary = Convert.ToBoolean(data[DataIndexConstants.AttackData.IsPrimaryIndex]); selection.IsSpecial = Convert.ToBoolean(data[DataIndexConstants.AttackData.IsSpecialIndex]); selection.Name = data[DataIndexConstants.AttackData.NameIndex]; selection.DamageEffect = data[DataIndexConstants.AttackData.DamageEffectIndex]; selection.DamageBonusMultiplier = Convert.ToDouble(data[DataIndexConstants.AttackData.DamageBonusMultiplierIndex]); selection.FrequencyQuantity = Convert.ToInt32(data[DataIndexConstants.AttackData.FrequencyQuantityIndex]); selection.FrequencyTimePeriod = data[DataIndexConstants.AttackData.FrequencyTimePeriodIndex]; selection.Save = data[DataIndexConstants.AttackData.SaveIndex]; selection.SaveAbility = data[DataIndexConstants.AttackData.SaveAbilityIndex]; selection.AttackType = data[DataIndexConstants.AttackData.AttackTypeIndex]; selection.SaveDcBonus = Convert.ToInt32(data[DataIndexConstants.AttackData.SaveDcBonusIndex]); var damageHelper = new DamageHelper(); var damageEntries = damageHelper.ParseEntries(data[DataIndexConstants.AttackData.DamageDataIndex]); foreach (var damageData in damageEntries) { var damage = new Damage { Roll = damageData[DataIndexConstants.AttackData.DamageData.RollIndex], Type = damageData[DataIndexConstants.AttackData.DamageData.TypeIndex], Condition = damageData[DataIndexConstants.AttackData.DamageData.ConditionIndex], }; selection.Damages.Add(damage); } return(selection); }
public static void Prefix(MechFallSequence __instance) { Mod.Log.Trace?.Write("MFS:OnComplete - entered."); int damagePointsTT = (int)Math.Ceiling(__instance.OwningMech.tonnage / 10f); Mod.Log.Debug?.Write($"Actor: {CombatantUtils.Label(__instance.OwningMech)} will suffer {damagePointsTT} TT damage points."); // Check for any pilot skill damage reduction float damageReduction = 1.0f - __instance.OwningMech.PilotCheckMod(Mod.Config.Piloting.DFAReductionMulti); float reducedDamage = (float)Math.Max(0f, Math.Floor(damageReduction * damagePointsTT)); Mod.Log.Debug?.Write($" Reducing TT fall damage from: {damagePointsTT} by {damageReduction:P1} to {reducedDamage}"); List <float> locationDamage = new List <float>(); while (damagePointsTT >= 5) { locationDamage.Add(5 * Mod.Config.Piloting.FallingDamagePerTenTons); damagePointsTT -= 5; } if (damagePointsTT > 0) { locationDamage.Add(damagePointsTT * Mod.Config.Piloting.FallingDamagePerTenTons); } Mod.Log.Info?.Write($"FALLING DAMAGE: TT damage: {damagePointsTT} => {damagePointsTT * Mod.Config.Piloting.FallingDamagePerTenTons} falling damage to actor: {CombatantUtils.Label(__instance.OwningMech)}"); try { (Weapon melee, Weapon dfa)fakeWeapons = ModState.GetFakedWeapons(__instance.OwningMech); AttackHelper.CreateImaginaryAttack(__instance.OwningMech, fakeWeapons.melee, __instance.OwningMech, __instance.SequenceGUID, locationDamage.ToArray(), DamageType.KnockdownSelf, MeleeAttackType.NotSet); } catch (Exception e) { Mod.Log.Error?.Write(e, "FAILED TO APPLY FALL DAMAGE"); } }
private StyledLine[] GetWeaponDetails(IWeaponItem weapon) { var result = new List <StyledLine> { new StyledLine { $"Accuracy: {weapon.Accuracy + Player.AccuracyBonus}%" }, StyledLine.Empty }; foreach (Element element in Enum.GetValues(typeof(Element))) { var maxDamage = WeaponItem.GetMaxDamage(weapon, element); var minDamage = WeaponItem.GetMinDamage(weapon, element); maxDamage = AttackHelper.CalculateDamage(maxDamage, element, Player); minDamage = AttackHelper.CalculateDamage(minDamage, element, Player); if (maxDamage == 0 && minDamage == 0) { continue; } var damageLine = new StyledLine { new StyledString($"{TextHelper.GetElementName(element)}", TextHelper.GetElementColor(element)), " Damage: ", $"{minDamage} - {maxDamage}" }; result.Add(damageLine); } return(result.ToArray()); }
// Use this for initialization void Start() { _attackHelper = GetComponent <AttackHelper>(); }
static void Postfix(AttackDirection from, ref Dictionary <ArmorLocation, int> __result) { if (ModState.ForceDamageTable == DamageTable.PUNCH) { Mod.Log.Info?.Write($"Attack against MECH will use the PUNCH damage table"); __result = new Dictionary <ArmorLocation, int>(); if (from == AttackDirection.FromLeft) { __result.Add(ArmorLocation.LeftTorso, 34); // 2 locations __result.Add(ArmorLocation.CenterTorso, 16); __result.Add(ArmorLocation.LeftArm, 34); // 2 locations __result.Add(ArmorLocation.Head, 16); } else if (from == AttackDirection.FromBack) { __result.Add(ArmorLocation.LeftArm, 17); __result.Add(ArmorLocation.LeftTorsoRear, 17); __result.Add(ArmorLocation.CenterTorsoRear, 16); __result.Add(ArmorLocation.RightTorsoRear, 17); __result.Add(ArmorLocation.RightArm, 17); __result.Add(ArmorLocation.Head, 16); } else if (from == AttackDirection.FromRight) { __result.Add(ArmorLocation.RightTorso, 34); // 2 locations __result.Add(ArmorLocation.CenterTorso, 16); __result.Add(ArmorLocation.RightArm, 34); // 2 locations __result.Add(ArmorLocation.Head, 16); } else { __result.Add(ArmorLocation.LeftArm, 17); __result.Add(ArmorLocation.LeftTorso, 17); __result.Add(ArmorLocation.CenterTorso, 16); __result.Add(ArmorLocation.RightTorso, 17); __result.Add(ArmorLocation.RightArm, 17); __result.Add(ArmorLocation.Head, 16); } } else if (ModState.ForceDamageTable == DamageTable.KICK) { Mod.Log.Info?.Write($"Attack against MECH will use the KICK damage table"); __result = new Dictionary <ArmorLocation, int>(); __result.Add(ArmorLocation.LeftLeg, 50); __result.Add(ArmorLocation.RightLeg, 50); } else if (ModState.ForceDamageTable == DamageTable.SWARM) { Mod.Log.Info?.Write($"Attack against MECH will use the SWARM damage table"); __result = new Dictionary <ArmorLocation, int>(); __result.Add(AttackHelper.GetSwarmLocationForMech(), 100); //__result.Add(ArmorLocation.LeftTorso, 17); //__result.Add(ArmorLocation.LeftTorsoRear, 17); //__result.Add(ArmorLocation.CenterTorso, 16); //__result.Add(ArmorLocation.CenterTorsoRear, 16); //__result.Add(ArmorLocation.RightTorso, 17); //__result.Add(ArmorLocation.RightTorsoRear, 17); //__result.Add(ArmorLocation.Head, 16); } else { return; } }
static void Postfix(AttackDirection from, bool log, ref Dictionary <VehicleChassisLocations, int> __result) { if (ModState.ForceDamageTable == DamageTable.PUNCH) { Mod.Log.Info?.Write($"Attack against VEHICLE will use the PUNCH damage table"); __result = new Dictionary <VehicleChassisLocations, int>(); if (from == AttackDirection.FromLeft) { __result.Add(VehicleChassisLocations.Turret, 40); __result.Add(VehicleChassisLocations.Left, 40); __result.Add(VehicleChassisLocations.Front, 8); __result.Add(VehicleChassisLocations.Rear, 8); } else if (from == AttackDirection.FromBack) { __result.Add(VehicleChassisLocations.Turret, 40); __result.Add(VehicleChassisLocations.Rear, 40); __result.Add(VehicleChassisLocations.Left, 8); __result.Add(VehicleChassisLocations.Right, 8); } else if (from == AttackDirection.FromRight) { __result.Add(VehicleChassisLocations.Turret, 40); __result.Add(VehicleChassisLocations.Right, 40); __result.Add(VehicleChassisLocations.Front, 8); __result.Add(VehicleChassisLocations.Rear, 8); } else if (from == AttackDirection.FromTop) { __result.Add(VehicleChassisLocations.Turret, 40); __result.Add(VehicleChassisLocations.Front, 8); __result.Add(VehicleChassisLocations.Left, 8); __result.Add(VehicleChassisLocations.Right, 8); } else { __result.Add(VehicleChassisLocations.Turret, 40); __result.Add(VehicleChassisLocations.Front, 40); __result.Add(VehicleChassisLocations.Left, 8); __result.Add(VehicleChassisLocations.Right, 8); } } else if (ModState.ForceDamageTable == DamageTable.KICK) { Mod.Log.Info?.Write($"Attack against VEHICLE will use the KICK damage table"); __result = new Dictionary <VehicleChassisLocations, int>(); if (from == AttackDirection.FromLeft) { __result.Add(VehicleChassisLocations.Turret, 4); __result.Add(VehicleChassisLocations.Left, 40); __result.Add(VehicleChassisLocations.Front, 8); __result.Add(VehicleChassisLocations.Rear, 8); } else if (from == AttackDirection.FromBack) { __result.Add(VehicleChassisLocations.Turret, 4); __result.Add(VehicleChassisLocations.Rear, 40); __result.Add(VehicleChassisLocations.Left, 8); __result.Add(VehicleChassisLocations.Right, 8); } else if (from == AttackDirection.FromRight) { __result.Add(VehicleChassisLocations.Turret, 4); __result.Add(VehicleChassisLocations.Right, 40); __result.Add(VehicleChassisLocations.Front, 8); __result.Add(VehicleChassisLocations.Rear, 8); } else if (from == AttackDirection.FromTop) { __result.Add(VehicleChassisLocations.Turret, 40); __result.Add(VehicleChassisLocations.Front, 8); __result.Add(VehicleChassisLocations.Left, 8); __result.Add(VehicleChassisLocations.Right, 8); } else { __result.Add(VehicleChassisLocations.Turret, 4); __result.Add(VehicleChassisLocations.Front, 40); __result.Add(VehicleChassisLocations.Left, 8); __result.Add(VehicleChassisLocations.Right, 8); } } else if (ModState.ForceDamageTable == DamageTable.SWARM) { Mod.Log.Info?.Write($"Attack against VEHICLE will use the SWARM damage table"); __result = new Dictionary <VehicleChassisLocations, int>(); __result.Add(AttackHelper.GetSwarmLocationForVehicle(), 100); //__result.Add(VehicleChassisLocations.Turret, 40); //__result.Add(VehicleChassisLocations.Left, 16); //__result.Add(VehicleChassisLocations.Front, 8); //__result.Add(VehicleChassisLocations.Right, 16); //__result.Add(VehicleChassisLocations.Rear, 16); } else { return; } }
public override List <Vector2Int> GetAreaOfEffect(Vector2Int source, Direction direction) { return(AttackHelper.GetLineAOE(source, direction, GetRange())); }
public override List <Vector2Int> GetAreaOfEffect(Vector2Int source, Direction direction) { List <Vector2Int> result = AttackHelper.GetTShapedAOE(source, direction, GetRange()); return(result); }
public void Setup() { selection = new AttackSelection(); attackHelper = new AttackHelper(); damageHelper = new DamageHelper(); }
public void Setup() { helper = new AttackHelper(); damageHelper = new DamageHelper(); }
public override IEnumerable <int> AttackRoutine() { var allDeployElements = Deploy.GetTroops(); List <Point> redPoints = new List <Point>(); foreach (var s in AttackHelper.FindRedPoints(redPoints)) { yield return(s); } var leftCorner = DeployHelper.DeployPointALeft; var topCorner = DeployHelper.DeployPointATop; double tankPaddingFraction = 0.35; var tankDeployA = leftCorner.Lerp(topCorner, tankPaddingFraction); var tankDeployB = leftCorner.Lerp(topCorner, 1 - tankPaddingFraction); double wallBreakerFraction = 0.43; var wallBreakDeployA = leftCorner.Lerp(topCorner, wallBreakerFraction); var wallBreakDeployB = leftCorner.Lerp(topCorner, 1 - wallBreakerFraction); double damageFraction = 0.15; var damageDeployPointA = leftCorner.Lerp(topCorner, damageFraction); var damageDeployPointB = leftCorner.Lerp(topCorner, 1 - damageFraction); // We want to drag our deploy points to the closest redline point // For that we get the vector from left to top, then we get its orthogonal vector (the one of both that points more towards the center) // then we can order the redline points. we want the one whos direction is closest to the orthogonal vector. var vecLeftTop = topCorner.Subtract(leftCorner).Normalize(); var ortho = GetOrthogonalDirection(tankDeployA, new Point((int)(vecLeftTop.Item1 * 30), (int)(vecLeftTop.Item2 * 30)), new PointF(0.5f, 0.5f).ToAbsolute()); tankDeployA = GetBestRedlinePoint(redPoints, tankDeployA, ortho); tankDeployB = GetBestRedlinePoint(redPoints, tankDeployB, ortho); wallBreakDeployA = GetBestRedlinePoint(redPoints, wallBreakDeployA, ortho); wallBreakDeployB = GetBestRedlinePoint(redPoints, wallBreakDeployB, ortho); var attackLine = DeployHelper.GetPointsForLine(damageDeployPointA, damageDeployPointB, 30).ToArray(); for (int i = 0; i < attackLine.Length; i++) { attackLine[i] = GetBestRedlinePoint(redPoints, attackLine[i], ortho); } attackLine = attackLine.Distinct().ToArray(); var validUnits = allDeployElements.Where(u => u.UnitData != null).ToArray(); var clanTroops = allDeployElements.FirstOrDefault(u => u.ElementType == DeployElementType.ClanTroops); //Func<DeployElement, bool> isAir = e => e.UnitData.UnitType == UnitType.Air; Func <DeployElement, bool> isBalloon = e => e.UnitData.NameSimple.Contains("balloon"); Func <DeployElement, bool> isMinion = e => e.UnitData.NameSimple.Contains("minion"); var tankUnits = validUnits.Where(u => u.UnitData.AttackType == AttackType.Tank).ToArray(); var wallBreakerUnits = validUnits.Where(u => u.UnitData.AttackType == AttackType.Wallbreak).ToArray(); var attackUnits = validUnits.Where(u => u.UnitData.AttackType == AttackType.Damage && !isMinion(u)).ToArray(); if (clanTroops != null && UserSettings.UseClanTroops) { attackUnits = attackUnits.Concat(new[] { clanTroops }).ToArray(); } var healUnits = validUnits.Where(u => u.UnitData.AttackType == AttackType.Heal).ToArray(); var balloonUnits = validUnits.Where(isBalloon).ToArray(); var minionUnits = validUnits.Where(isMinion).ToArray(); // Deploy tank units if (tankUnits.Any()) { Logger.Debug($"{tankUnits.Length} tanking element{(tankUnits.Length > 1 ? "s" : "")} available to deploy."); foreach (var s in DeployUnits(tankUnits, new[] { tankDeployA, tankDeployB }, 20)) { yield return(s); } yield return(2000); } // Deploy wallbreakers if (tankUnits.Any()) { Logger.Debug("Wallbreakers available to deploy."); foreach (var s in DeployUnits(wallBreakerUnits, new[] { wallBreakDeployA, wallBreakDeployB }, 40)) { yield return(s); } yield return(1000); } // Check whether we got an air troopset and decide to perform an air attack or not var balloonCount = balloonUnits.Sum(i => i.Count); if (balloonCount > 10) { // Ok, we have an air troopset, so we will deploy the air units first according to different deploy rules. attackUnits = attackUnits.Where(u => !isBalloon(u)).ToArray(); int spotCount = (int)Math.Ceiling(balloonCount / 4.0); // We want to make x spots where balloons are deployed var airPoints = DeployHelper.GetPointsForLine(damageDeployPointA, damageDeployPointB, spotCount); for (int i = 0; i < airPoints.Count; i++) { airPoints[i] = GetBestRedlinePoint(redPoints, airPoints[i], ortho); } airPoints = airPoints.Distinct().ToList(); // Deploy those air units foreach (var s in DeployUnits(balloonUnits, airPoints.ToArray(), firstCycleYield: 1000)) { yield return(s); } } // Deploy atackers if (attackUnits.Any()) { Logger.Debug($"{attackUnits.Length} attacking element{(attackUnits.Length > 1 ? "s" : "")} available to deploy."); foreach (var s in DeployUnits(attackUnits, attackLine, 0, 5, 2500)) { yield return(s); } yield return(500); } // Minions if (minionUnits.Any()) { foreach (var s in DeployUnits(minionUnits, attackLine)) { yield return(s); } } // Deploy healers foreach (var s in DeployUnits(healUnits, attackLine)) { yield return(s); } // Deploy heroes var heroes = allDeployElements .Where(u => (UserSettings.UseKing && u.ElementType == DeployElementType.HeroKing) || (UserSettings.UseQueen && u.ElementType == DeployElementType.HeroQueen) || (UserSettings.UseWarden && u.ElementType == DeployElementType.HeroWarden)) .ToList(); if (heroes.Count > 0) { foreach (var y in DeployHeroes(heroes, attackLine)) { yield return(y); } } }
static void Prefix(AIUtil __instance, AbstractActor unit, AttackType attackType, List <Weapon> weaponList, ICombatant target, Vector3 attackPosition, Vector3 targetPosition, bool useRevengeBonus, AbstractActor unitForBVContext) { Mech attackingMech = unit as Mech; AbstractActor targetActor = target as AbstractActor; Mech targetMech = target as Mech; Mod.AILog.Info?.Write("AITUIL_EDFA - entered."); if (attackingMech == null || targetActor == null) { return; // Nothing to do } if (attackType == AttackType.Shooting || attackType == AttackType.None || attackType == AttackType.Count) { return; // nothing to do } try { Mod.AILog.Info?.Write($"=== Calculating expectedDamage for {attackingMech.DistinctId()} melee attack " + $"from position: {attackPosition} against target: {target.DistinctId()} at position: {targetPosition}"); Mod.AILog.Info?.Write($" useRevengeBonus: {useRevengeBonus}"); Mod.AILog.Info?.Write($" --- weaponList:"); if (weaponList != null) { foreach (Weapon weapon in weaponList) { Mod.AILog.Info?.Write($" {weapon?.UIName}"); } } bool modifyAttack = false; MeleeAttack meleeAttack = null; Weapon meleeWeapon = null; bool isCharge = false; bool isMelee = false; if (attackType == AttackType.Melee && attackingMech?.Pathing?.GetMeleeDestsForTarget(targetActor)?.Count > 0) { Mod.AILog.Info?.Write($"Modifying {attackingMech.DistinctId()}'s melee attack damage for utility"); // Create melee options MeleeState meleeState = ModState.AddorUpdateMeleeState(attackingMech, attackPosition, targetActor); if (meleeState != null) { meleeAttack = meleeState.GetHighestDamageAttackForUI(); ModState.AddOrUpdateSelectedAttack(attackingMech, meleeAttack); if (meleeAttack is ChargeAttack) { isCharge = true; } meleeWeapon = attackingMech.MeleeWeapon; modifyAttack = true; isMelee = true; } } bool isDFA = false; if (attackType == AttackType.DeathFromAbove && attackingMech?.JumpPathing?.GetDFADestsForTarget(targetActor)?.Count > 0) { Mod.AILog.Info?.Write($"Modifying {attackingMech.DistinctId()}'s DFA attack damage for utility"); // Create melee options MeleeState meleeState = ModState.AddorUpdateMeleeState(attackingMech, attackPosition, targetActor); if (meleeState != null) { meleeAttack = meleeState.DFA; ModState.AddOrUpdateSelectedAttack(attackingMech, meleeAttack); meleeWeapon = attackingMech.DFAWeapon; modifyAttack = true; isDFA = true; } } // No pathing dests for melee or DFA - skip if (!isMelee && !isDFA) { return; } if (modifyAttack && meleeAttack == null || !meleeAttack.IsValid) { Mod.AILog.Info?.Write($"Failed to find a valid melee state, marking melee weapons as 1 damage."); meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_DamagePerShot, 0); meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_Instability, 0); return; } if (modifyAttack && meleeAttack != null && meleeAttack.IsValid) { Mod.AILog.Info?.Write($"Evaluating utility against state: {meleeAttack.Label}"); // Set the DFA weapon's damage to our expected damage float totalDamage = meleeAttack.TargetDamageClusters.Sum(); Mod.AILog.Info?.Write($" - totalDamage: {totalDamage}"); // Check to see if the attack will unsteady a target float evasionBreakUtility = 0f; if (targetMech != null && targetMech.EvasivePipsCurrent > 0 && (meleeAttack.OnTargetMechHitForceUnsteady || AttackHelper.WillUnsteadyTarget(meleeAttack.TargetInstability, targetMech)) ) { // Target will lose their evasion pips evasionBreakUtility = targetMech.EvasivePipsCurrent * Mod.Config.Melee.AI.EvasionPipRemovedUtility; Mod.AILog.Info?.Write($" Adding {evasionBreakUtility} virtual damage to EV from " + $"evasivePips: {targetMech.EvasivePipsCurrent} x bonusDamagePerPip: {Mod.Config.Melee.AI.EvasionPipRemovedUtility}"); } float knockdownUtility = 0f; if (targetMech != null && targetMech.pilot != null && AttackHelper.WillKnockdownTarget(meleeAttack.TargetInstability, targetMech, meleeAttack.OnTargetMechHitForceUnsteady)) { float centerTorsoArmorAndStructure = targetMech.GetMaxArmor(ArmorLocation.CenterTorso) + targetMech.GetMaxStructure(ChassisLocations.CenterTorso); if (AttackHelper.WillInjuriesKillTarget(targetMech, 1)) { knockdownUtility = centerTorsoArmorAndStructure * Mod.Config.Melee.AI.PilotInjuryMultiUtility; Mod.AILog.Info?.Write($" Adding {knockdownUtility} virtual damage to EV from " + $"centerTorsoArmorAndStructure: {centerTorsoArmorAndStructure} x injuryMultiUtility: {Mod.Config.Melee.AI.PilotInjuryMultiUtility}"); } else { // Attack won't kill, so only apply a fraction equal to the totalHeath float injuryFraction = (targetMech.pilot.TotalHealth - 1) - (targetMech.pilot.Injuries + 1); knockdownUtility = (centerTorsoArmorAndStructure * Mod.Config.Melee.AI.PilotInjuryMultiUtility) / injuryFraction; Mod.AILog.Info?.Write($" Adding {knockdownUtility} virtual damage to EV from " + $"(centerTorsoArmorAndStructure: {centerTorsoArmorAndStructure} x injuryMultiUtility: {Mod.Config.Melee.AI.PilotInjuryMultiUtility}) " + $"/ injuryFraction: {injuryFraction}"); } } // Check to see how much evasion loss the attacker will have // use current pips + any pips gained from movement (charge) float distance = (attackPosition + unit.CurrentPosition).magnitude; int newPips = unit.GetEvasivePipsResult(distance, isDFA, isCharge, true); int normedNewPips = (unit.EvasivePipsCurrent + newPips) > unit.StatCollection.GetValue <int>("MaxEvasivePips") ? unit.StatCollection.GetValue <int>("MaxEvasivePips") : (unit.EvasivePipsCurrent + newPips); float selfEvasionDamage = 0f; if (meleeAttack.UnsteadyAttackerOnHit || meleeAttack.UnsteadyAttackerOnMiss) { // TODO: Should evaluate chance to hit, and apply these partial damage based upon success chances selfEvasionDamage = normedNewPips * Mod.Config.Melee.AI.EvasionPipLostUtility; Mod.AILog.Info?.Write($" Reducing virtual damage by {selfEvasionDamage} due to potential loss of {normedNewPips} pips."); } // Check to see how much damage the attacker will take float selfDamage = 0f; if (meleeAttack.AttackerDamageClusters.Length > 0) { selfDamage = meleeAttack.AttackerDamageClusters.Sum(); Mod.AILog.Info?.Write($" Reducing virtual damage by {selfDamage} due to attacker damage on attack."); } float virtualDamage = totalDamage + evasionBreakUtility + knockdownUtility - selfEvasionDamage - selfDamage; Mod.AILog.Info?.Write($" Virtual damage calculated as {virtualDamage} = " + $"totalDamage: {totalDamage} + evasionBreakUtility: {evasionBreakUtility} + knockdownUtility: {knockdownUtility}" + $" - selfDamage: {selfDamage} - selfEvasionDamage: {selfEvasionDamage}"); Mod.AILog.Info?.Write($"Setting weapon: {meleeWeapon.UIName} to virtual damage: {virtualDamage} for EV calculation"); meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_DamagePerShot, virtualDamage); meleeWeapon.StatCollection.Set <float>(ModStats.HBS_Weapon_Instability, 0); Mod.AILog.Info?.Write($"=== Done modifying attack!"); } else { Mod.AILog.Debug?.Write($"Attack is not melee {modifyAttack}, or melee state is invalid or null. I assume the normal AI will prevent action."); } } catch (Exception e) { Mod.AILog.Error?.Write(e, $"Failed to calculate melee damage for {unit.DistinctId()} using attackType {attackType} due to error!"); } }
public override IEnumerable <Vector2i> GetAttackableLocations(Map map, Actor actor, WeaponProperties weapon) { return(AttackHelper.GetTargetablePoints(map, actor, weapon.MinRange, weapon.MaxRange, true, false)); }
static void Prefix(MechDFASequence __instance, MessageCenterMessage message, AttackStackSequence ___meleeSequence) { Mod.Log.Trace?.Write("MMS:OMC entered."); AttackCompleteMessage attackCompleteMessage = message as AttackCompleteMessage; Mod.MeleeLog.Info?.Write($"== Resolving cluster damage, instability, and unsteady on DFA attacker: {CombatantUtils.Label(__instance.OwningMech)} and " + $"target: {CombatantUtils.Label(__instance.DFATarget)}."); (MeleeAttack meleeAttack, Weapon fakeWeapon)seqState = ModState.GetMeleeSequenceState(__instance.SequenceGUID); if (attackCompleteMessage.stackItemUID == ___meleeSequence.SequenceGUID && seqState.meleeAttack != null) { // Check to see if the target was hit bool targetWasHit = false; foreach (AttackDirector.AttackSequence attackSequence in ___meleeSequence.directorSequences) { if (!attackSequence.attackCompletelyMissed) { targetWasHit = true; Mod.MeleeLog.Info?.Write($" -- AttackSequence: {attackSequence.stackItemUID} hit the target."); } else { Mod.MeleeLog.Info?.Write($" -- AttackSequence: {attackSequence.stackItemUID} missed the target."); } } if (__instance.OwningMech.isHasStability() && !__instance.OwningMech.IsOrWillBeProne) { // Attacker stability and unsteady - always applies as we're always a mech if ((targetWasHit && seqState.meleeAttack.UnsteadyAttackerOnHit) || (!targetWasHit && seqState.meleeAttack.UnsteadyAttackerOnMiss)) { Mod.MeleeLog.Info?.Write(" -- Forcing attacker to become unsteady from attack!"); __instance.OwningMech.DumpEvasion(); } } // Attacker cluster damage if (targetWasHit && !__instance.OwningMech.IsDead) { // Make sure we use the attackers's damage table ModState.ForceDamageTable = seqState.meleeAttack.AttackerTable; if (seqState.meleeAttack.AttackerDamageClusters.Length > 0) { try { Mod.MeleeLog.Info?.Write($" -- Applying {seqState.meleeAttack.AttackerDamageClusters.Sum()} damage to attacker as {seqState.meleeAttack.AttackerDamageClusters.Length} clusters."); AttackHelper.CreateImaginaryAttack(__instance.OwningMech, seqState.fakeWeapon, __instance.OwningMech, __instance.SequenceGUID, seqState.meleeAttack.AttackerDamageClusters, DamageType.Melee, MeleeAttackType.Kick); } catch (Exception e) { Mod.Log.Error?.Write(e, "FAILED TO APPLY DFA DAMAGE TO ATTACKER"); } } } if (targetWasHit) { // Target mech stability and unsteady if (__instance.DFATarget is Mech targetMech && targetMech.isHasStability() && !targetMech.IsProne) { if (seqState.meleeAttack.TargetInstability != 0) { Mod.MeleeLog.Info?.Write($" -- Adding {seqState.meleeAttack.TargetInstability} absolute instability to target."); targetMech.AddAbsoluteInstability(seqState.meleeAttack.TargetInstability, StabilityChangeSource.Attack, "-1"); } if (seqState.meleeAttack.OnTargetMechHitForceUnsteady) { Mod.MeleeLog.Info?.Write(" -- Forcing target to become unsteady from attack!"); targetMech.DumpEvasion(); } } // Target vehicle evasion damage if (__instance.DFATarget is Vehicle || __instance.DFATarget.FakeVehicle() || __instance.DFATarget.NavalUnit()) { AbstractActor targetActor = __instance.DFATarget as AbstractActor; if (seqState.meleeAttack.OnTargetVehicleHitEvasionPipsRemoved != 0 && targetActor.EvasivePipsCurrent > 0) { Mod.MeleeLog.Info?.Write($" -- Removing {seqState.meleeAttack.OnTargetVehicleHitEvasionPipsRemoved} from target vehicle."); int modifiedPips = targetActor.EvasivePipsCurrent - seqState.meleeAttack.OnTargetVehicleHitEvasionPipsRemoved; if (modifiedPips < 0) { modifiedPips = 0; } targetActor.EvasivePipsCurrent = modifiedPips; SharedState.Combat.MessageCenter.PublishMessage(new EvasiveChangedMessage(targetActor.GUID, targetActor.EvasivePipsCurrent)); } } // Target cluster damage - first attack was applied through melee weapon if (seqState.meleeAttack.TargetDamageClusters.Length > 1 && !__instance.DFATarget.IsDead) { try { // Make sure we use the attackers's damage table ModState.ForceDamageTable = seqState.meleeAttack.TargetTable; // The target already got hit by the first cluster as the weapon damage. Only add the additional hits float[] clusterDamage = seqState.meleeAttack.TargetDamageClusters.SubArray(1, seqState.meleeAttack.TargetDamageClusters.Length); Mod.MeleeLog.Info?.Write($" -- Applying {clusterDamage.Sum()} damage to target as {clusterDamage.Length} clusters."); AttackHelper.CreateImaginaryAttack(__instance.OwningMech, seqState.fakeWeapon, __instance.DFATarget, __instance.SequenceGUID, clusterDamage, DamageType.Melee, MeleeAttackType.DFA); } catch (Exception e) { Mod.Log.Error?.Write(e, "FAILED TO APPLY DFA DAMAGE TO TARGET"); } } } Mod.MeleeLog.Info?.Write($"== Done."); } // Restore the attacker's DFA damage __instance.OwningMech.StatCollection.Set <float>(ModStats.HBS_DFA_Self_Damage, ModState.OriginalDFASelfDamage); __instance.OwningMech.StatCollection.Set <bool>(ModStats.HBS_DFA_Causes_Self_Unsteady, true); // Reset melee state ModState.ForceDamageTable = DamageTable.NONE; ModState.OriginalDFASelfDamage = 0f; }