public bool CheckAttack(Unit target, UNIT_ATTACK type) { if (target == null || this == target) return false; if (Player.player_is_ally(this.player, target.player)) return false; if (((this.sel_prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) && (target.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) if (this.sel_prop.rng == 0) if (this.x != target.x || this.y != target.y) return false; /* range 0 means above unit for an aircraft */ /* if the target flys and the unit is ground with a range of 0 the aircraft may only be harmed when above unit */ if (((this.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) && (target.sel_prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) if (this.sel_prop.rng == 0) if (this.x != target.x || this.y != target.y) return false; /* only destroyers may harm submarines */ if (((target.sel_prop.flags & UnitFlags.DIVING) == UnitFlags.DIVING) && (this.sel_prop.flags & UnitFlags.DESTROYER) != UnitFlags.DESTROYER) return false; if ((Engine.terrain.weatherTypes[Scenario.cur_weather].flags & WEATHER_FLAGS.NO_AIR_ATTACK) == WEATHER_FLAGS.NO_AIR_ATTACK) { if ((this.sel_prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) return false; if ((target.sel_prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) return false; } if (type == UNIT_ATTACK.UNIT_ACTIVE_ATTACK) { /* agressor */ if (this.cur_ammo <= 0) return false; if (this.sel_prop.atks[target.sel_prop.trgt_type] <= 0) return false; if (this.cur_atk_count == 0) return false; if (!this.IsClose(target) && Misc.get_dist(this.x, this.y, target.x, target.y) > this.sel_prop.rng) return false; } else if (type == UNIT_ATTACK.UNIT_DEFENSIVE_ATTACK) { /* defensive fire */ if (this.sel_prop.atks[target.sel_prop.trgt_type] <= 0) return false; if (this.cur_ammo <= 0) return false; if ((this.sel_prop.flags & (UnitFlags.INTERCEPTOR | UnitFlags.ARTILLERY | UnitFlags.AIR_DEFENSE)) == 0) return false; if ((target.sel_prop.flags & (UnitFlags.ARTILLERY | UnitFlags.AIR_DEFENSE | UnitFlags.SWIMMING)) != 0) return false; if ((this.sel_prop.flags & UnitFlags.INTERCEPTOR) == UnitFlags.INTERCEPTOR) { /* the interceptor is propably not beside the attacker so the range check is different * can't be done here because the unit the target attacks isn't passed so * GetDefensiveFireUnits() must have a look itself */ } else if (Misc.get_dist(this.x, this.y, target.x, target.y) > this.sel_prop.rng) return false; } else { /* counter-attack */ if (this.cur_ammo <= 0) return false; if (!this.IsClose(target) && Misc.get_dist(this.x, this.y, target.x, target.y) > this.sel_prop.rng) return false; if (this.sel_prop.atks[target.sel_prop.trgt_type] == 0) return false; /* artillery may only defend against close units */ if ((this.sel_prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) if (!this.IsClose(target)) return false; /* you may defend against artillery only when close */ if ((target.sel_prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) if (!this.IsClose(target)) return false; } return true; }
/* ==================================================================== Compute damage/supression the target takes when unit attacks the target. No properties will be changed. If 'real' is set the dices are rolled else it's a stochastical prediction. ==================================================================== */ public static void GetDamage(Unit aggressor, Unit unit, Unit target, UNIT_ATTACK type, bool real, bool rugged_def, out int damage, out int suppr) { int atk_strength, max_roll, min_roll, die_mod; int atk_grade, def_grade, diff, result; float suppr_chance, kill_chance; /* use PG's formula to compute the attack/defense grade*/ /* basic attack */ atk_grade = Math.Abs(unit.sel_prop.atks[target.sel_prop.trgt_type]); #if DEBUG if (real) Console.WriteLine("{0} attacks:", unit.name); if (real) Console.WriteLine(" base: {0}", atk_grade); if (real) Console.WriteLine(" exp: +{0}", unit.exp_level); #endif /* experience */ atk_grade += unit.exp_level; /* target on a river? */ if ((target.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) if ((target.terrain.flags[Scenario.cur_weather] & Terrain_flags.RIVER) == Terrain_flags.RIVER) { atk_grade += 4; #if DEBUG if (real) Console.WriteLine(" river: +4"); #endif } /* counterattack of rugged defense unit? */ if (type == UNIT_ATTACK.UNIT_PASSIVE_ATTACK && rugged_def) { atk_grade += 4; #if DEBUG if (real) Console.WriteLine(" rugdef: +4"); #endif } #if DEBUG if (real) Console.WriteLine("---\n{0} defends:", target.name); #endif /* basic defense */ if ((unit.sel_prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) def_grade = target.sel_prop.def_air; else { def_grade = target.sel_prop.def_grnd; /* apply close defense? */ if ((unit.sel_prop.flags & UnitFlags.INFANTRY) == UnitFlags.INFANTRY) if ((target.sel_prop.flags & UnitFlags.INFANTRY) != UnitFlags.INFANTRY) if ((target.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) if ((target.sel_prop.flags & UnitFlags.SWIMMING) != UnitFlags.SWIMMING) { if (target == aggressor) if ((unit.terrain.flags[Scenario.cur_weather] & Terrain_flags.INF_CLOSE_DEF) == Terrain_flags.INF_CLOSE_DEF) def_grade = target.sel_prop.def_cls; if (unit == aggressor) if ((target.terrain.flags[Scenario.cur_weather] & Terrain_flags.INF_CLOSE_DEF) == Terrain_flags.INF_CLOSE_DEF) def_grade = target.sel_prop.def_cls; } } #if DEBUG if (real) Console.WriteLine(" base: {0}", def_grade); if (real) Console.WriteLine(" exp: +{0}", target.exp_level); #endif /* experience */ def_grade += target.exp_level; /* attacker on a river or swamp? */ if ((unit.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) if ((unit.sel_prop.flags & UnitFlags.SWIMMING) != UnitFlags.SWIMMING) if ((target.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) { if ((unit.terrain.flags[Scenario.cur_weather] & Terrain_flags.SWAMP) == Terrain_flags.SWAMP) { def_grade += 4; #if DEBUG if (real) Console.WriteLine(" swamp: +4"); #endif } else if ((unit.terrain.flags[Scenario.cur_weather] & Terrain_flags.RIVER) == Terrain_flags.RIVER) { def_grade += 4; #if DEBUG if (real) Console.WriteLine(" river: +4"); #endif } } /* rugged defense? */ if (type == UNIT_ATTACK.UNIT_ACTIVE_ATTACK && rugged_def) { def_grade += 4; #if DEBUG if (real) Console.WriteLine(" rugdef: +4"); #endif } /* entrenchment */ if ((unit.sel_prop.flags & UnitFlags.IGNORE_ENTR) == UnitFlags.IGNORE_ENTR) def_grade += 0; else { if ((unit.sel_prop.flags & UnitFlags.INFANTRY) == UnitFlags.INFANTRY) def_grade += target.entr / 2; else def_grade += target.entr; #if DEBUG if (real) Console.WriteLine(" entr: +{0}", ((unit.sel_prop.flags & UnitFlags.INFANTRY) == UnitFlags.INFANTRY) ? target.entr / 2 : target.entr); #endif } /* naval vs ground unit */ if ((unit.sel_prop.flags & UnitFlags.SWIMMING) != UnitFlags.SWIMMING) if ((unit.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) if ((target.sel_prop.flags & UnitFlags.SWIMMING) == UnitFlags.SWIMMING) { def_grade += 8; #if DEBUG if (real) Console.WriteLine(" naval: +8"); #endif } /* bad weather? */ if (unit.sel_prop.rng > 0) if ((Engine.terrain.weatherTypes[Scenario.cur_weather].flags & WEATHER_FLAGS.BAD_SIGHT) == WEATHER_FLAGS.BAD_SIGHT) { def_grade += 3; #if DEBUG if (real) Console.WriteLine(" sight: +3"); #endif } /* initiating attack against artillery? */ if (type == UNIT_ATTACK.UNIT_PASSIVE_ATTACK) if ((unit.sel_prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) { def_grade += 3; #if DEBUG if (real) Console.WriteLine(" def vs art: +3"); #endif } /* infantry versus anti_tank? */ if ((target.sel_prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) if ((unit.sel_prop.flags & UnitFlags.ANTI_TANK) == UnitFlags.ANTI_TANK) { def_grade += 2; #if DEBUG if (real) Console.WriteLine(" antitnk:+2"); #endif } /* no fuel makes attacker less effective */ if (unit.CheckFuelUsage() && unit.cur_fuel == 0) { def_grade += 4; #if DEBUG if (real) Console.WriteLine(" lowfuel:+4"); #endif } /* attacker strength */ atk_strength = unit_get_cur_str(unit); #if DEBUG if (real && atk_strength != unit_get_cur_str(unit)) Console.WriteLine("---\n{0} with half strength", unit.name); #endif /* PG's formula: get difference between attack and defense strike for each strength point with if ( diff <= 4 ) D20 + diff else D20 + 4 + 0.4 * ( diff - 4 ) suppr_fire flag set: 1-10 miss, 11-18 suppr, 19+ kill normal: 1-10 miss, 11-12 suppr, 13+ kill */ diff = atk_grade - def_grade; if (diff < -7) diff = -7; damage = 0; suppr = 0; #if DEBUG if (real) { Console.WriteLine("---\n{0} x {1} -. {2} x {3}", atk_strength, atk_grade, unit_get_cur_str(target), def_grade); } #endif /* get the chances for suppression and kills (computed here to use also for debug info */ suppr_chance = kill_chance = 0; die_mod = (diff <= 4 ? diff : 4 + 2 * (diff - 4) / 5); min_roll = 1 + die_mod; max_roll = 20 + die_mod; /* get chances for suppression and kills */ if ((unit.sel_prop.flags & UnitFlags.SUPPR_FIRE) == UnitFlags.SUPPR_FIRE) { int limit = (type == UNIT_ATTACK.UNIT_DEFENSIVE_ATTACK) ? 20 : 18; if (limit - min_roll >= 0) suppr_chance = 0.05f * (Math.Min(limit, max_roll) - Math.Max(11, min_roll) + 1); if (max_roll > limit) kill_chance = 0.05f * (max_roll - Math.Max(limit + 1, min_roll) + 1); } else { if (12 - min_roll >= 0) suppr_chance = 0.05f * (Math.Min(12, max_roll) - Math.Max(11, min_roll) + 1); if (max_roll > 12) kill_chance = 0.05f * (max_roll - Math.Max(13, min_roll) + 1); } if (suppr_chance < 0) suppr_chance = 0; if (kill_chance < 0) kill_chance = 0; if (real) { #if DEBUG Console.WriteLine("Roll: D20 + {0} (Kill: {1}, Suppr: {2})", diff <= 4 ? diff : 4 + 2 * (diff - 4) / 5, (int)(100 * kill_chance), (int)(100 * suppr_chance)); #endif while (atk_strength-- > 0) { if (diff <= 4) result = Misc.DICE(20) + diff; else result = Misc.DICE(20) + 4 + 2 * (diff - 4) / 5; if ((unit.sel_prop.flags & UnitFlags.SUPPR_FIRE) == UnitFlags.SUPPR_FIRE) { int limit = (type == UNIT_ATTACK.UNIT_DEFENSIVE_ATTACK) ? 20 : 18; if (result >= 11 && result <= limit) suppr++; else if (result >= limit + 1) damage++; } else { if (result >= 11 && result <= 12) suppr++; else if (result >= 13) damage++; } } #if DEBUG Console.WriteLine("Kills: {0}, Suppression: {1}", damage, suppr); #endif } else { suppr = (int)(suppr_chance * atk_strength); damage = (int)(kill_chance * atk_strength); } }
public FIGHT_TYPES Attack(Unit target, UNIT_ATTACK type, bool real, bool force_rugged) { int unit_old_str = this.str;//, target_old_str = target.str; int unit_old_ini = this.sel_prop.ini, target_old_ini = target.sel_prop.ini; int unit_dam = 0, unit_suppr = 0, target_dam = 0, target_suppr = 0; int rugged_chance; bool rugged_def = false; int exp_mod; FIGHT_TYPES ret = FIGHT_TYPES.AR_NONE; /* clear flags */ ATTACK_FLAGS strike; /* check if rugged defense occurs */ if (real && type == UNIT_ATTACK.UNIT_ACTIVE_ATTACK) if (this.CheckRuggedDefense(target) || (force_rugged && this.IsClose(target))) { rugged_chance = this.GetRuggedDefenseChance(target); if (Misc.DICE(100) <= rugged_chance || force_rugged) rugged_def = true; } /* PG's formula for initiative is min { base initiative, terrain max initiative } + ( exp_level + 1 ) / 2 + D3 */ /* against aircrafts the initiative is used since terrain does not matter */ /* target's terrain is used for fight */ if ((this.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING && (target.sel_prop.flags & UnitFlags.FLYING) != UnitFlags.FLYING) { this.sel_prop.ini = Math.Min(this.sel_prop.ini, target.terrain.max_ini); target.sel_prop.ini = Math.Min(target.sel_prop.ini, target.terrain.max_ini); } this.sel_prop.ini += (this.exp_level + 1) / 2; target.sel_prop.ini += (target.exp_level + 1) / 2; /* special initiative rules: antitank inits attack tank|recon: atk 0, def 99 tank inits attack against anti-tank: atk 0, def 99 defensive fire: atk 99, def 0 submarine attacks: atk 99, def 0 ranged attack: atk 99, def 0 rugged defense: atk 0 air unit attacks air defense: atk = def non-art vs art: atk 0, def 99 */ if ((this.sel_prop.flags & UnitFlags.ANTI_TANK) == UnitFlags.ANTI_TANK) if ((target.sel_prop.flags & UnitFlags.TANK) == UnitFlags.TANK) { this.sel_prop.ini = 0; target.sel_prop.ini = 99; } if (((this.sel_prop.flags & UnitFlags.DIVING) == UnitFlags.DIVING) || ((this.sel_prop.flags & UnitFlags.ARTILLERY) == UnitFlags.ARTILLERY) || ((this.sel_prop.flags & UnitFlags.AIR_DEFENSE) == UnitFlags.AIR_DEFENSE) || type == UNIT_ATTACK.UNIT_DEFENSIVE_ATTACK ) { this.sel_prop.ini = 99; target.sel_prop.ini = 0; } if ((this.sel_prop.flags & UnitFlags.FLYING) == UnitFlags.FLYING) if ((target.sel_prop.flags & UnitFlags.AIR_DEFENSE) == UnitFlags.AIR_DEFENSE) this.sel_prop.ini = target.sel_prop.ini; if (rugged_def) this.sel_prop.ini = 0; if (force_rugged) target.sel_prop.ini = 99; /* the dice is rolled after these changes */ if (real) { this.sel_prop.ini += Misc.DICE(3); target.sel_prop.ini += Misc.DICE(3); } #if DEBUG if (real) { Console.WriteLine("{0} Initiative: {1}", this.name, this.sel_prop.ini); Console.WriteLine("{0} Initiative: {1}", target.name, target.sel_prop.ini); if (this.CheckRuggedDefense(target)) Console.WriteLine("Rugged Defense: {0} ({1})", rugged_def ? "yes" : "no", this.GetRuggedDefenseChance(target)); } #endif /* in a real combat a submarine may evade */ if (real && type == UNIT_ATTACK.UNIT_ACTIVE_ATTACK && ((target.sel_prop.flags & UnitFlags.DIVING) == UnitFlags.DIVING)) { if (Misc.DICE(10) <= 7 + (target.exp_level - this.exp_level) / 2) { strike = ATTACK_FLAGS.ATK_NO_STRIKE; ret |= FIGHT_TYPES.AR_EVADED; } else strike = ATTACK_FLAGS.ATK_UNIT_FIRST; #if DEBUG Console.WriteLine("\nSubmarine Evasion: {0} ({1}%)\n", (strike == ATTACK_FLAGS.ATK_NO_STRIKE) ? "yes" : "no", 10 * (7 + (target.exp_level - this.exp_level) / 2)); #endif } else /* who is first? */ if (this.sel_prop.ini == target.sel_prop.ini) strike = ATTACK_FLAGS.ATK_BOTH_STRIKE; else if (this.sel_prop.ini > target.sel_prop.ini) strike = ATTACK_FLAGS.ATK_UNIT_FIRST; else strike = ATTACK_FLAGS.ATK_TARGET_FIRST; /* the one with the highest initiative begins first if not defensive fire or artillery */ if (strike == ATTACK_FLAGS.ATK_BOTH_STRIKE) { /* both strike at the same time */ GetDamage(this, this, target, type, real, rugged_def, out target_dam, out target_suppr); if (target.CheckAttack(this, UNIT_ATTACK.UNIT_PASSIVE_ATTACK)) GetDamage(this, target, this, UNIT_ATTACK.UNIT_PASSIVE_ATTACK, real, rugged_def, out unit_dam, out unit_suppr); target.ApplyDamage(target_dam, target_suppr, this); this.ApplyDamage(unit_dam, unit_suppr, target); } else if (strike == ATTACK_FLAGS.ATK_UNIT_FIRST) { /* unit strikes first */ GetDamage(this, this, target, type, real, rugged_def, out target_dam, out target_suppr); if (target.ApplyDamage(target_dam, target_suppr, this) != 0) if (target.CheckAttack(this, UNIT_ATTACK.UNIT_PASSIVE_ATTACK) && type != UNIT_ATTACK.UNIT_DEFENSIVE_ATTACK) { GetDamage(this, target, this, UNIT_ATTACK.UNIT_PASSIVE_ATTACK, real, rugged_def, out unit_dam, out unit_suppr); this.ApplyDamage(unit_dam, unit_suppr, target); } } else if (strike == ATTACK_FLAGS.ATK_TARGET_FIRST) { /* target strikes first */ if (target.CheckAttack(this, UNIT_ATTACK.UNIT_PASSIVE_ATTACK)) { GetDamage(this, target, this, UNIT_ATTACK.UNIT_PASSIVE_ATTACK, real, rugged_def, out unit_dam, out unit_suppr); if (this.ApplyDamage(unit_dam, unit_suppr, target) == 0) ret |= FIGHT_TYPES.AR_UNIT_ATTACK_BROKEN_UP; } if (unit_get_cur_str(this) > 0) { GetDamage(this, this, target, type, real, rugged_def, out target_dam, out target_suppr); target.ApplyDamage(target_dam, target_suppr, this); } } /* check return value */ if (this.str == 0) ret |= FIGHT_TYPES.AR_UNIT_KILLED; else if (unit_get_cur_str(this) == 0) ret |= FIGHT_TYPES.AR_UNIT_SUPPRESSED; if (target.str == 0) ret |= FIGHT_TYPES.AR_TARGET_KILLED; else if (unit_get_cur_str(target) == 0) ret |= FIGHT_TYPES.AR_TARGET_SUPPRESSED; if (rugged_def) ret |= FIGHT_TYPES.AR_RUGGED_DEFENSE; if (real) { /* cost ammo */ if (Config.supply) { //if (DICE(10)<=target_old_str) this.cur_ammo--; if (target.CheckAttack(this, UNIT_ATTACK.UNIT_PASSIVE_ATTACK) && target.cur_ammo > 0) //if (DICE(10)<=unit_old_str) target.cur_ammo--; } /* costs attack */ if (this.cur_atk_count > 0) this.cur_atk_count--; /* target: loose entrenchment if damage was taken or with a unit.str*10% chance */ if (target.entr > 0) if (target_dam > 0 || Misc.DICE(10) <= unit_old_str) target.entr--; /* attacker looses entrenchment if it got hurt */ if (this.entr > 0 && unit_dam > 0) this.entr--; /* gain experience */ exp_mod = target.exp_level - this.exp_level; if (exp_mod < 1) exp_mod = 1; this.AddExperience(exp_mod * target_dam + unit_dam); exp_mod = this.exp_level - target.exp_level; if (exp_mod < 1) exp_mod = 1; target.AddExperience(exp_mod * unit_dam + target_dam); if (this.IsClose(target)) { this.AddExperience(10); target.AddExperience(10); } /* adjust life bars */ update_bar(this); update_bar(target); } this.sel_prop.ini = unit_old_ini; target.sel_prop.ini = target_old_ini; return ret; }
public FIGHT_TYPES NormalAttack(Unit target, UNIT_ATTACK type) { return this.Attack(target, type, true, false); }