[+] 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:
parent
17540efd29
commit
cba6378cc7
17 changed files with 419 additions and 132 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(); }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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?
|
||||
*/
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]) {
|
||||
|
|
Loading…
Add table
Reference in a new issue