[+] Great improvement of AI:

- The AI unit will prefer only those targets, which have specified set of flags, use new UnitType field PriorityTarget (same syntax as in CanTargetFlag)
- AI workers prefer to find mines near to depots.
- More effective control to defence forces, the idle units always try to attack back.
- Fixed new goal finding to attacking AI forces.
- Try to prefer buildings as a goal for attacking forces.
- Special threshold counter which didn't allow AI to change target for some time.
- Fixed AiHelpMe for non-building units.
[-] Fixed incorrect CUnit::TTL countdown.
This commit is contained in:
cybermind 2012-05-19 23:25:59 +06:00
parent 17540efd29
commit cba6378cc7
17 changed files with 419 additions and 132 deletions

View file

@ -56,6 +56,7 @@
#include "ui.h"
#include "unit.h"
#include "unittype.h"
#include "spells.h"
/*----------------------------------------------------------------------------
-- Defines
@ -189,7 +190,7 @@ void AnimateActionAttack(CUnit &unit, COrder &order)
{
if (Action == UnitActionAttack) {
if (this->HasGoal()) {
return this->GetGoal()->IsAlive();
return this->GetGoal()->IsAliveOnMap();
} else {
return Map.Info.IsPointOnMap(this->goalPos);
}
@ -336,23 +337,24 @@ bool COrder_Attack::CheckForTargetInRange(CUnit &unit)
this->SetGoal(goal);
this->MinRange = unit.Type->MinAttackRange;
this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max;
this->goalPos.x = this->goalPos.y = -1;
this->goalPos = goal->tilePos;
this->State |= WEAK_TARGET; // weak target
}
// Have a weak target, try a better target.
} else if (this->HasGoal() && (this->State & WEAK_TARGET)) {
} else if (this->HasGoal() && (this->State & WEAK_TARGET || unit.Player->AiEnabled)) {
CUnit *goal = this->GetGoal();
CUnit *newTarget = AttackUnitsInReactRange(unit);
if (newTarget && newTarget->Type->Priority > goal->Type->Priority) {
COrder *savedOrder = this->Clone();
if (newTarget && newTarget->IsAgressive()
&& ThreatCalculate(unit, newTarget) < ThreatCalculate(unit, goal)) {
COrder *savedOrder = this->Clone();
if (unit.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
this->SetGoal(newTarget);
this->goalPos.x = this->goalPos.y = -1;
if (unit.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
this->SetGoal(newTarget);
this->goalPos = newTarget->tilePos;
}
}
@ -482,7 +484,7 @@ void COrder_Attack::AttackTarget(CUnit &unit)
savedOrder = NULL;
}
this->SetGoal(goal);
this->goalPos.x = this->goalPos.y = -1;
this->goalPos = goal->tilePos;
this->MinRange = unit.Type->MinAttackRange;
this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max;
this->State |= WEAK_TARGET;
@ -492,18 +494,19 @@ void COrder_Attack::AttackTarget(CUnit &unit)
} else {
if ((this->State & WEAK_TARGET)) {
CUnit *newTarget = AttackUnitsInReactRange(unit);
if (newTarget && newTarget->Type->Priority > goal->Type->Priority) {
COrder *savedOrder = this->Clone();
if (newTarget && newTarget->IsAgressive()
&& ThreatCalculate(unit, newTarget) < ThreatCalculate(unit, goal)) {
COrder *savedOrder = this->Clone();
if (unit.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
goal = newTarget;
this->SetGoal(newTarget);
this->goalPos.x = this->goalPos.y = -1;
this->MinRange = unit.Type->MinAttackRange;
this->State = MOVE_TO_TARGET;
if (unit.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
goal = newTarget;
this->SetGoal(newTarget);
this->goalPos = newTarget->tilePos;
this->MinRange = unit.Type->MinAttackRange;
this->State = MOVE_TO_TARGET;
}
}
}

View file

@ -274,7 +274,7 @@ static void HandleBuffs(CUnit &unit, int amount)
{
// Look if the time to live is over.
if (unit.TTL && (int)unit.TTL < ((int)GameCycle - unit.Variable[HP_INDEX].Value)) {
if (unit.TTL && unit.TTL < GameCycle) {
DebugPrint("Unit must die %lu %lu!\n" _C_ unit.TTL _C_ GameCycle);
// Hit unit does some funky stuff...
@ -284,6 +284,11 @@ static void HandleBuffs(CUnit &unit, int amount)
}
}
unit.Threshold -= amount;
if (unit.Threshold < 0) {
unit.Threshold = 0;
}
// decrease spells effects time.
unit.Variable[BLOODLUST_INDEX].Increase = -amount;
unit.Variable[HASTE_INDEX].Increase = -amount;

View file

@ -703,20 +703,19 @@ void AiHelpMe(const CUnit *attacker, CUnit &defender)
if (aiunit.CurrentAction() == UnitActionAttack) {
COrder_Attack &orderAttack = *static_cast<COrder_Attack *>(aiunit.CurrentOrder());
if (orderAttack.GetGoal() == NULL || orderAttack.GetGoal()->IsAgressive() == false) {
shouldAttack = true;
if (orderAttack.GetGoal() == NULL || orderAttack.GetGoal()->IsAgressive() == false
|| ThreatCalculate(defender, attacker) < ThreatCalculate(defender, orderAttack.GetGoal())) {
shouldAttack = true;
}
}
if (shouldAttack) {
CommandAttack(aiunit, attacker->tilePos, const_cast<CUnit *>(attacker), FlushCommands);
if (aiunit.SavedOrder == NULL) {
COrder *savedOrder = COrder::NewActionAttack(aiunit, aiunit.tilePos);
COrder *savedOrder = COrder::NewActionAttack(aiunit, attacker->tilePos);
if (aiunit.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
if (aiunit.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
}
}

View file

@ -52,8 +52,11 @@
/*----------------------------------------------------------------------------
-- Types
----------------------------------------------------------------------------*/
#define AIATTACK_RANGE 0
#define AIATTACK_ALLMAP 1
#define AIATTACK_BUILDING 2
template <const bool IN_REACT_RANGE>
template <const int FIND_TYPE>
class AiForceEnemyFinder
{
public:
@ -75,10 +78,16 @@ public:
if (unit->Type->CanAttack == false) {
return *enemy == NULL;
}
if (IN_REACT_RANGE) {
if (FIND_TYPE == AIATTACK_RANGE) {
*enemy = AttackUnitsInReactRange(*unit);
} else {
} else if (FIND_TYPE == AIATTACK_ALLMAP) {
*enemy = AttackUnitsInDistance(*unit, MaxMapWidth);
} else if (FIND_TYPE == AIATTACK_BUILDING) {
*enemy = AttackUnitsInDistance(*unit, MaxMapWidth, true);
Assert(!*enemy || (*enemy)->Type->Building);
if (*enemy == NULL) {
*enemy = AttackUnitsInDistance(*unit, MaxMapWidth);
}
}
return *enemy == NULL;
}
@ -285,7 +294,7 @@ void AiForce::Attack(const Vec2i &pos)
if (Map.Info.IsPointOnMap(goalPos) == false) {
/* Search in entire map */
const CUnit *enemy = NULL;
AiForceEnemyFinder<false>(*this, &enemy);
AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &enemy);
if (enemy) {
goalPos = enemy->tilePos;
}
@ -639,6 +648,8 @@ static void AiGroupAttackerForTransport(AiForce &aiForce)
*/
void AiForce::Update()
{
bool isTransporterAttack = false;
if (Size() == 0) {
Attacking = false;
if (!Defending && State > AiForceAttackingState_Waiting) {
@ -651,6 +662,9 @@ void AiForce::Update()
Attacking = false;
for (unsigned int i = 0; i < Size(); ++i) {
CUnit *aiunit = Units[i];
if (!isTransporterAttack && aiunit->Type->CanTransport()) {
isTransporterAttack = true;
}
if (aiunit->Type->CanAttack) {
Attacking = true;
break;
@ -681,39 +695,91 @@ void AiForce::Update()
return ;
}
std::vector<CUnit *> idleUnits;
const CUnit *leader = NULL;
for (unsigned int i = 0; i != Size(); ++i) {
CUnit &aiunit = *Units[i];
Assert(Map.Info.IsPointOnMap(GoalPos));
std::vector<CUnit *> attackUnits;
CUnit *leader = NULL;
int distance = 0;
Vec2i pos = GoalPos;
if (aiunit.IsIdle()) {
if (aiunit.IsAliveOnMap()) {
idleUnits.push_back(&aiunit);
if (isTransporterAttack) {
for (unsigned int i = 0; i != Size(); ++i) {
CUnit &aiunit = *Units[i];
if (aiunit.IsIdle()) {
if (aiunit.IsAliveOnMap()) {
attackUnits.push_back(&aiunit);
}
} else if (aiunit.CurrentAction() == UnitActionAttack) {
leader = &aiunit;
}
}
} else {
attackUnits = Units.Units;
for (unsigned int i = 0; i < Size(); ++i) {
CUnit &aiunit = *Units[i];
if (aiunit.CurrentAction() == UnitActionAttack) {
leader = &aiunit;
if (leader->CurrentOrder()->HasGoal() && leader->CurrentOrder()->GetGoal()->IsAliveOnMap()) {
pos = leader->CurrentOrder()->GetGoal()->tilePos;
}
break;
}
} else if (aiunit.CurrentAction() == UnitActionAttack) {
leader = &aiunit;
} else if (leader == NULL) {
leader = &aiunit;
}
}
const Vec2i pos = leader != NULL ? leader->tilePos : this->GoalPos;
for (size_t i = 0; i != idleUnits.size(); ++i) {
CUnit &aiunit = *idleUnits[i];
const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference.
aiunit.Wait = delay;
if (aiunit.Type->CanAttack) {
CommandAttack(aiunit, pos, NULL, FlushCommands);
} else if (aiunit.Type->CanTransport()) {
if (aiunit.BoardCount != 0) {
CommandUnload(aiunit, pos, NULL, FlushCommands);
} else {
// FIXME : Retrieve unit blocked (transport previously full)
CommandMove(aiunit, aiunit.Player->StartPos, FlushCommands);
this->Remove(aiunit);
if (leader != NULL) {
for (size_t i = 0; i != attackUnits.size(); ++i) {
CUnit &aiunit = *attackUnits[i];
if (aiunit.CurrentAction() == UnitActionAttack) {
continue;
}
const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference.
aiunit.Wait = delay;
if (aiunit.Type->CanAttack) {
CommandAttack(aiunit, pos, NULL, FlushCommands);
} else if (isTransporterAttack && aiunit.Type->CanTransport()) {
if (aiunit.BoardCount != 0) {
CommandUnload(aiunit, pos, NULL, FlushCommands);
} else {
// FIXME : Retrieve unit blocked (transport previously full)
CommandMove(aiunit, aiunit.Player->StartPos, FlushCommands);
this->Remove(aiunit);
}
} else {
CommandMove(aiunit, pos, FlushCommands);
}
}
} else { // Everyone is idle, find a new target
const CUnit *unit = NULL;
AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &unit);
if (!unit) {
// No enemy found, give up
// FIXME: should the force go home or keep trying to attack?
DebugPrint("%d: Attack force #%lu can't find a target, giving up\n"
_C_ AiPlayer->Player->Index _C_(long unsigned int)(this - & (AiPlayer->Force[0])));
Attacking = false;
return;
}
GoalPos = unit->tilePos;
this->State = AiForceAttackingState_Attacking;
for (size_t i = 0; i != this->Units.size(); ++i) {
CUnit *const unit = this->Units[i];
if (unit->Container == NULL) {
const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference.
unit->Wait = delay;
if (unit->Type->CanAttack) {
CommandAttack(*unit, GoalPos, NULL, FlushCommands);
} else {
CommandMove(*unit, GoalPos, FlushCommands);
}
}
} else {
CommandMove(aiunit, pos, FlushCommands);
}
}
}
@ -738,7 +804,7 @@ void AiForceManager::Update()
&& force.Units[0]->MapDistanceTo(force.GoalPos) <= nearDist) {
// Look if still enemies in attack range.
const CUnit *dummy = NULL;
if (!AiForceEnemyFinder<true>(force, &dummy).found()) {
if (!AiForceEnemyFinder<AIATTACK_RANGE>(force, &dummy).found()) {
if (Map.Info.IsPointOnMap(force.HomePos)) {
for (size_t i = 0; i != force.Units.size(); ++i) {
CUnit &unit = *force.Units[i];
@ -751,6 +817,31 @@ void AiForceManager::Update()
force.Defending = false;
force.Attacking = false;
}
} else { // Find idle units and order them to defend
std::vector<CUnit *> idleUnits;
for (unsigned int i = 0; i != Size(); ++i) {
CUnit &aiunit = *Units[i];
if (aiunit.IsIdle()) {
if (aiunit.IsAliveOnMap()) {
idleUnits.push_back(&aiunit);
}
}
}
for (unsigned int i = 0; i != idleUnits.size(); ++i) {
CUnit *const unit = idleUnits[i];
if (unit->Container == NULL) {
const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference.
unit->Wait = delay;
if (unit->Type->CanAttack) {
CommandAttack(*unit, force.GoalPos, NULL, FlushCommands);
} else {
CommandMove(*unit, force.GoalPos, FlushCommands);
}
}
}
}
} else if (force.Attacking) {
force.RemoveDeadUnit();

View file

@ -141,7 +141,7 @@ public:
}
Units.for_each(InternalRemoveUnit);
Units.clear();
GoalPos.x = GoalPos.y = -1;
HomePos.x = HomePos.y = GoalPos.x = GoalPos.y = -1;
}
inline size_t Size() const { return Units.size(); }

View file

@ -856,8 +856,10 @@ static int AiAssignHarvesterFromTerrain(CUnit &unit, int resource)
*/
static int AiAssignHarvesterFromUnit(CUnit &unit, int resource)
{
// Try to find the nearest depot first.
CUnit *depot = FindDeposit(unit, 1000, resource);
// Find a resource to harvest from.
CUnit *mine = UnitFindResource(unit, unit.tilePos, 1000, resource, true);
CUnit *mine = UnitFindResource(unit, depot ? depot->tilePos : unit.tilePos, 1000, resource, true);
if (mine) {
CommandResource(unit, *mine, FlushCommands);

View file

@ -197,6 +197,15 @@ private:
const CPlayer *player;
};
class HasNotSamePlayerAs
{
public:
explicit HasNotSamePlayerAs(const CPlayer &_player) : player(&_player) {}
bool operator()(const CUnit *unit) const { return unit->Player != player; }
private:
const CPlayer *player;
};
class IsAlliedWith
{
public:
@ -243,6 +252,12 @@ private:
const CUnit *forbidden;
};
class IsBuildingType
{
public:
bool operator()(const CUnit *unit) const { return unit->Type->Building; }
};
template <typename Pred>
class NotPredicate

View file

@ -365,6 +365,17 @@ struct lua_State;
typedef COrder *COrderPtr;
/*
** Configuration of the small (unit) AI.
*/
#define PRIORITY_FACTOR 0x00001000
#define HEALTH_FACTOR 0x00000001
#define DISTANCE_FACTOR 0x00010000
#define INRANGE_FACTOR 0x00001000
#define INRANGE_BONUS 0x00100000
#define CANATTACK_BONUS 0x00010000
#define AIPRIORITY_BONUS 0x01000000
/// Called whenever the selected unit was updated
extern void SelectedUnitChanged();
@ -543,8 +554,8 @@ public:
signed char IY; /// seen Y image displacement to map position
unsigned Constructed : 1; /// Unit seen construction
unsigned State : 3; /// Unit seen build/upgrade state
unsigned Destroyed : PlayerMax; /// Unit seen destroyed or not
unsigned ByPlayer : PlayerMax; /// Track unit seen by player
unsigned Destroyed : PlayerMax; /// Unit seen destroyed or not
unsigned ByPlayer : PlayerMax; /// Track unit seen by player
} Seen;
CVariable *Variable; /// array of User Defined variables.
@ -555,6 +566,7 @@ public:
int LastGroup; /// unit belongs to this last group
unsigned int Wait; /// action counter
int Threshold; /// The counter while ai unit couldn't change target.
struct _unit_anim_ {
const CAnimation *Anim; /// Anim
@ -943,8 +955,10 @@ extern CUnit *UnitOnScreen(CUnit *unit, int x, int y);
/// Let a unit die
extern void LetUnitDie(CUnit &unit);
/// Destory all units inside another unit
/// Destroy all units inside another unit
extern void DestroyAllInside(CUnit &source);
/// Calculate some value to measure the unit's priority for AI
extern int ThreatCalculate(const CUnit &unit, const CUnit *dest);
/// Hit unit with damage, if destroyed give attacker the points
extern void HitUnit(CUnit *attacker, CUnit &target, int damage);
@ -1020,7 +1034,7 @@ extern CUnit *ResourceOnMap(const Vec2i &pos, int resource, bool mine_on_top = t
extern CUnit *ResourceDepositOnMap(const Vec2i &pos, int resource);
/// Find best enemy in numeric range to attack
extern CUnit *AttackUnitsInDistance(const CUnit &unit, int range);
extern CUnit *AttackUnitsInDistance(const CUnit &unit, int range, bool onlyBuildings = false);
/// Find best enemy in attack range to attack
extern CUnit *AttackUnitsInRange(const CUnit &unit);
/// Find best enemy in reaction range to attack

View file

@ -1021,9 +1021,10 @@ public:
CUnitStats DefaultStat;
struct BoolFlags {
bool value; /// User defined flag. Used for (dis)allow target.
char CanTransport; /// Can transport units with this flag.
char CanTargetFlag; /// Flag needed to target with missile.
bool value; /// User defined flag. Used for (dis)allow target.
char CanTransport; /// Can transport units with this flag.
char CanTargetFlag; /// Flag needed to target with missile.
char AiPriorityTarget; /// Attack this units first.
};
std::vector<BoolFlags> BoolFlag;

View file

@ -1479,6 +1479,9 @@ void UIHandleButtonDown(unsigned button)
CUnit *uins;
int i;
// Reset the ShowNameDelay counters
ShowNameDelay = ShowNameTime = GameCycle;
if (LongLeftButton) {
if (!OldValid) {
OldShowSightRange = Preference.ShowSightRange;

View file

@ -305,7 +305,7 @@ CUnit *CanBuildHere(const CUnit *unit, const CUnitType &type, const Vec2i &pos)
}
// Check special rules for AI players
if (unit->Player->AiEnabled) {
if (unit && unit->Player->AiEnabled) {
size_t count = type.AiBuildingRules.size();
if (count > 0) {
ontoptarget = NULL;

View file

@ -475,6 +475,11 @@ static int CclUnit(lua_State *l)
// FIXME : unsigned long should be better handled
unit->TTL = LuaToNumber(l, -1);
lua_pop(l, 1);
} else if (!strcmp(value, "threshold")) {
lua_rawgeti(l, 2, j + 1);
// FIXME : unsigned long should be better handled
unit->Threshold = LuaToNumber(l, -1);
lua_pop(l, 1);
} else if (!strcmp(value, "group-id")) {
lua_rawgeti(l, 2, j + 1);
unit->GroupId = LuaToNumber(l, -1);

View file

@ -126,6 +126,7 @@ static const char SLOT_KEY[] = "Slot";
static const char SHIELD_KEY[] = "ShieldPoints";
static const char POINTS_KEY[] = "Points";
static const char MAXHARVESTERS_KEY[] = "MaxHarvesters";
static const char AITHRESHOLD_KEY[] = "AiThreshold";
/*----------------------------------------------------------------------------
-- Functions
@ -162,7 +163,7 @@ CUnitTypeVar::CVariableKeys::CVariableKeys()
BASICDAMAGE_KEY, POSX_KEY, POSY_KEY, RADARRANGE_KEY,
RADARJAMMERRANGE_KEY, AUTOREPAIRRANGE_KEY, BLOODLUST_KEY, HASTE_KEY,
SLOW_KEY, INVISIBLE_KEY, UNHOLYARMOR_KEY, SLOT_KEY, SHIELD_KEY, POINTS_KEY,
MAXHARVESTERS_KEY
MAXHARVESTERS_KEY, AITHRESHOLD_KEY
};
for (int i = 0; i < NVARALREADYDEFINED; ++i) {
@ -1005,6 +1006,33 @@ static int CclDefineUnitType(lua_State *l)
}
LuaError(l, "Unsupported flag tag for can-target-flag: %s" _C_ value);
}
} else if (!strcmp(value, "PriorityTarget")) {
//
// Warning: ai-priority-target should only be used AFTER all bool flags
// have been defined.
//
if (!lua_istable(l, -1)) {
LuaError(l, "incorrect argument");
}
if (type->BoolFlag.size() < UnitTypeVar.GetNumberBoolFlag()) {
type->BoolFlag.resize(UnitTypeVar.GetNumberBoolFlag());
}
subargs = lua_rawlen(l, -1);
for (k = 0; k < subargs; ++k) {
lua_rawgeti(l, -1, k + 1);
value = LuaToString(l, -1);
lua_pop(l, 1);
++k;
int index = UnitTypeVar.BoolFlagNameLookup[value];
if (index != -1) {
lua_rawgeti(l, -1, k + 1);
value = LuaToString(l, -1);
lua_pop(l, 1);
type->BoolFlag[index].AiPriorityTarget = Ccl2Condition(l, value);
continue;
}
LuaError(l, "Unsupported flag tag for ai-priority-target: %s" _C_ value);
}
} else if (!strcmp(value, "IsNotSelectable")) {
type->IsNotSelectable = LuaToBoolean(l, -1);
} else if (!strcmp(value, "SelectableByRectangle")) {

View file

@ -172,6 +172,7 @@ void CUnit::Init()
memset(&Seen, 0, sizeof(Seen));
Variable = NULL;
TTL = 0;
Threshold = 0;
GroupId = 0;
LastGroup = 0;
ResourcesHeld = 0;
@ -369,7 +370,11 @@ bool CUnit::RestoreOrder()
{
COrder *savedOrder = this->SavedOrder;
if (savedOrder == NULL || savedOrder->IsValid() == false) {
if (savedOrder == NULL) {
return false;
}
if (savedOrder->IsValid() == false) {
delete this->SavedOrder;
this->SavedOrder = NULL;
return false;
@ -400,6 +405,10 @@ bool CUnit::StoreOrder(COrder *order)
Assert(order);
if (this->SavedOrder != NULL) {
if (this->SavedOrder->IsValid() == false) {
delete this->SavedOrder;
this->SavedOrder = NULL;
}
return false;
}
if (order && order->Finished == true) {
@ -2580,6 +2589,11 @@ CUnit *UnitOnScreen(CUnit *ounit, int x, int y)
continue;
}
}
// Check if better units are present on this location
if (unit->Type->IsNotSelectable) {
funit = unit;
continue;
}
//
// This could be taken.
//
@ -2735,6 +2749,47 @@ void DestroyAllInside(CUnit &source)
-- Unit AI
----------------------------------------------------------------------------*/
int ThreatCalculate(const CUnit &unit, const CUnit *dest)
{
int cost = 0;
const CUnitType &type = *unit.Type;
const CUnitType &dtype = *dest->Type;
// Priority 0-255
cost -= dtype.Priority * PRIORITY_FACTOR;
// Remaining HP (Health) 0-65535
cost += dest->Variable[HP_INDEX].Value * 100 / dest->Variable[HP_INDEX].Max * HEALTH_FACTOR;
int d = unit.MapDistanceTo(*dest);
if (d <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max && d >= type.MinAttackRange) {
cost += d * INRANGE_FACTOR;
cost -= INRANGE_BONUS;
} else {
cost += d * DISTANCE_FACTOR;
}
for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) {
if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) {
if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) &
(dtype.BoolFlag[i].value)) {
cost -= AIPRIORITY_BONUS;
}
if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) &
(dtype.BoolFlag[i].value)) {
cost += AIPRIORITY_BONUS;
}
}
}
// Unit can attack back.
if (CanTarget(&dtype, &type)) {
cost -= CANATTACK_BONUS;
}
return cost;
}
/**
** Unit is hit by missile or other damage.
**
@ -2807,7 +2862,7 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage)
}
}
if (attacker && !target.Type->Wall && target.Type->Building && target.Player->AiEnabled) {
if (attacker && !target.Type->Wall && target.Player->AiEnabled) {
AiHelpMe(attacker, target);
}
@ -2816,6 +2871,9 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage)
(target.Variable[HP_INDEX].Value == 0)) { // unit is killed or destroyed
// increase scores of the attacker, but not if attacking it's own units.
// prevents cheating by killing your own units.
// Setting ai threshold counter to 0 so it can target other units
attacker->Threshold = 0;
if (attacker && target.IsEnemy(*attacker)) {
attacker->Player->Score += target.Variable[POINTS_INDEX].Value;
if (type->Building) {
@ -2909,47 +2967,14 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage)
}
}
// Attack units in range (which or the attacker?)
if (attacker && target.IsAgressive() && target.CanMove()) {
if (target.CurrentAction() != UnitActionStill) {
return;
}
CUnit *goal;
if (RevealAttacker && CanTarget(target.Type, attacker->Type)) {
// Reveal Unit that is attacking
goal = attacker;
} else {
if (target.CurrentAction() == UnitActionStandGround) {
goal = AttackUnitsInRange(target);
} else {
// Check for any other units in range
goal = AttackUnitsInReactRange(target);
}
}
if (goal) {
COrder *savedOrder = target.CurrentOrder()->Clone();
CommandAttack(target, goal->tilePos, NoUnitP, FlushCommands);
if (target.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
return;
}
if (!attacker) {
return;
}
/*
What should we do with workers on :
case UnitActionRepair:
Drop orders and run away or return after escape?
*/
//
// Can't attack run away.
//
if (attacker && target.CanMove() && target.CurrentAction() == UnitActionStill) {
if (!target.IsAgressive() && target.CanMove() && target.CurrentAction() == UnitActionStill) {
Vec2i pos = target.tilePos - attacker->tilePos;
int d = isqrt(pos.x * pos.x + pos.y * pos.y);
@ -2962,6 +2987,73 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage)
CommandStopUnit(target);
CommandMove(target, pos, 0);
}
// The rest instructions is only for AI units.
if (!target.Player->AiEnabled) {
return;
}
const int threshold = 30;
if (target.Threshold && target.CurrentOrder()->HasGoal() && target.CurrentOrder()->GetGoal() == attacker) {
target.Threshold = threshold;
return;
}
// If the threshold counter is not zero, ignoring
if (target.Threshold) {
return;
}
// Attack units in range (which or the attacker?)
// Don't bother unit if it casting repeatable spell
if (target.IsAgressive() && target.CanMove() && target.CurrentAction() != UnitActionSpellCast) {
COrder *savedOrder = target.CurrentOrder()->Clone();
CUnit *oldgoal = target.CurrentOrder()->GetGoal();
CUnit *goal, *best = oldgoal;
if (RevealAttacker && CanTarget(target.Type, attacker->Type)) {
// Reveal Unit that is attacking
goal = attacker;
} else {
if (target.CurrentAction() == UnitActionStandGround) {
goal = AttackUnitsInRange(target);
} else {
// Check for any other units in range
goal = AttackUnitsInReactRange(target);
}
}
// Calculate the best target we could attack
best = oldgoal ? oldgoal : (goal ? goal : attacker);
if (best && best != attacker) {
if (goal && ((goal->IsAgressive() && best->IsAgressive() == false)
|| (ThreatCalculate(target, goal) < ThreatCalculate(target, best)))) {
best = goal;
}
if (!RevealAttacker && (best->IsAgressive() == false || ThreatCalculate(target, attacker) < ThreatCalculate(target, best))) {
best = attacker;
}
}
CommandAttack(target, best->tilePos, NoUnitP, FlushCommands);
// Set threshold value only for agressive units
if (best->IsAgressive()) {
target.Threshold = threshold;
}
if (target.StoreOrder(savedOrder) == false) {
delete savedOrder;
savedOrder = NULL;
}
return;
}
/*
What should we do with workers on :
case UnitActionRepair:
Drop orders and run away or return after escape?
*/
}
/*----------------------------------------------------------------------------

View file

@ -666,7 +666,7 @@ void ShowOrder(const CUnit &unit)
if (unit.Destroyed) {
return;
}
if (unit.Player != ThisPlayer && !ThisPlayer->IsAllied(unit)) {
if (!DEBUG && unit.Player != ThisPlayer && !ThisPlayer->IsAllied(unit)) {
return;
}
// Get current position

View file

@ -51,21 +51,12 @@
#include "interface.h"
#include "tileset.h"
#include "pathfinder.h"
#include "spells.h"
/*----------------------------------------------------------------------------
-- Defines
----------------------------------------------------------------------------*/
/*
** Configuration of the small (unit) AI.
*/
#define PRIORITY_FACTOR 0x00010000
#define HEALTH_FACTOR 0x00000001
#define DISTANCE_FACTOR 0x00100000
#define INRANGE_FACTOR 0x00010000
#define INRANGE_BONUS 0x01000000
#define CANATTACK_BONUS 0x00100000
/*----------------------------------------------------------------------------
-- Local Data
----------------------------------------------------------------------------*/
@ -308,7 +299,7 @@ private:
// Priority 0-255
cost -= dtype.Priority * PRIORITY_FACTOR;
// Remaining HP (Health) 0-65535
cost += dest->Variable[HP_INDEX].Value * HEALTH_FACTOR;
cost += dest->Variable[HP_INDEX].Value * 100 / dest->Variable[HP_INDEX].Max * HEALTH_FACTOR;
if (d <= attackrange && d >= type.MinAttackRange) {
cost += d * INRANGE_FACTOR;
@ -317,6 +308,19 @@ private:
cost += d * DISTANCE_FACTOR;
}
for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) {
if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) {
if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) &
(dtype.BoolFlag[i].value)) {
cost -= AIPRIORITY_BONUS;
}
if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) &
(dtype.BoolFlag[i].value)) {
cost += AIPRIORITY_BONUS;
}
}
}
// Unit can attack back.
if (CanTarget(&dtype, &type)) {
cost -= CANATTACK_BONUS;
@ -417,6 +421,19 @@ public:
} else {
// Priority 0-255
cost += dtype.Priority * PRIORITY_FACTOR;
for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) {
if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) {
if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) &
(dtype.BoolFlag[i].value)) {
cost -= AIPRIORITY_BONUS;
} else if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) &
(dtype.BoolFlag[i].value)) {
cost += AIPRIORITY_BONUS;
}
}
}
// Remaining HP (Health) 0-65535
// Give a boost to unit we can kill in one shoot only
@ -612,12 +629,13 @@ struct CompareUnitDistance {
** If the unit can attack must be handled by caller.
** Choose the best target, that can be attacked.
**
** @param unit Find in distance for this unit.
** @param range Distance range to look.
** @param unit Find in distance for this unit.
** @param range Distance range to look.
** @param onlyBuildings Search only buildings (useful when attacking with AI force)
**
** @return Unit to be attacked.
*/
CUnit *AttackUnitsInDistance(const CUnit &unit, int range)
CUnit *AttackUnitsInDistance(const CUnit &unit, int range, bool onlyBuildings)
{
// if necessary, take possible damage on allied units into account...
if (unit.Type->Missile.Missile->Range > 1
@ -632,8 +650,13 @@ CUnit *AttackUnitsInDistance(const CUnit &unit, int range)
// If unit is removed, use containers x and y
const CUnit *firstContainer = unit.Container ? unit.Container : &unit;
std::vector<CUnit *> table;
Map.SelectAroundUnit(*firstContainer, missile_range, table,
MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral])));
if (onlyBuildings) {
Map.SelectAroundUnit(*firstContainer, missile_range, table,
MakeAndPredicate(HasNotSamePlayerAs(Players[PlayerNumNeutral]), IsBuildingType()));
} else {
Map.SelectAroundUnit(*firstContainer, missile_range, table,
MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral])));
}
if (table.empty() == false) {
return BestRangeTargetFinder(unit, range).Find(table);
@ -642,8 +665,13 @@ CUnit *AttackUnitsInDistance(const CUnit &unit, int range)
} else {
std::vector<CUnit *> table;
Map.SelectAroundUnit(unit, range, table,
MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral])));
if (onlyBuildings) {
Map.SelectAroundUnit(unit, range, table,
MakeAndPredicate(HasNotSamePlayerAs(Players[PlayerNumNeutral]), IsBuildingType()));
} else {
Map.SelectAroundUnit(unit, range, table,
MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral])));
}
const int n = static_cast<int>(table.size());
if (range > 25 && table.size() > 9) {

View file

@ -214,6 +214,7 @@ void SaveUnit(const CUnit &unit, CFile &file)
file.printf(" \"active\",");
}
file.printf("\"ttl\", %lu,\n ", unit.TTL);
file.printf("\"threshold\", %lu,\n ", unit.Threshold);
for (size_t i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) {
if (unit.Variable[i] != unit.Type->DefaultStat.Variables[i]) {