예제 #1
0
/**
**  PickUpItem
**
**  @return  true if the unit picks up an item, false otherwise
*/
static bool PickUpItem(CUnit &unit)
{
	if (
		!unit.Type->BoolFlag[ORGANIC_INDEX].value
		|| !unit.Player->AiEnabled
	) {
		return false;
	}
	
	if (unit.Variable[HP_INDEX].Value == unit.GetModifiedVariable(HP_INDEX, VariableMax) && !unit.HasInventory()) { //only look for items to pick up if the unit is damaged or has an inventory
		return false;
	}

	// look for nearby items to pick up
	std::vector<CUnit *> table;
	SelectAroundUnit(unit, unit.GetReactionRange(), table);

	for (size_t i = 0; i != table.size(); ++i) {
		if (!table[i]->Removed) {
			if (CanPickUp(unit, *table[i])) {
				if (table[i]->Variable[HITPOINTHEALING_INDEX].Value > 0 && (unit.GetModifiedVariable(HP_INDEX, VariableMax) - unit.Variable[HP_INDEX].Value) > 0) {
					if (UnitReachable(unit, *table[i], 1, unit.GetReactionRange() * 8)) {
						CommandPickUp(unit, *table[i], FlushCommands);
						return true;
					}
				}
			}
		}
	}
	return false;
}
예제 #2
0
	inline void operator()(CUnit *const dest)
	{
		/* Only resource depots */
		if (dest->Type->CanStore[resource]
			&& dest->IsAliveOnMap()
			&& dest->CurrentAction() != UnitActionBuilt) {
			// Unit in range?

			if (NEARLOCATION) {
				int d = dest->MapDistanceTo(u_near.loc);

				//
				// Take this depot?
				//
				if (d <= range && d < best_dist) {
					best_depot = dest;
					best_dist = d;
				}
			} else {
				int d;
				const CUnit *worker = u_near.worker;
				if (!worker->Container) {
					d = worker->MapDistanceTo(*dest);
				} else {
					d = worker->Container->MapDistanceTo(*dest);
				}

				// Use Circle, not square :)
				if (d > range) {
					return;
				}

				if (best_dist == INT_MAX) {
					best_depot = dest;
				}

				// calck real travel distance
				if (worker->Container) {
					UnmarkUnitFieldFlags(*worker->Container);
				}
				d = UnitReachable(*worker, *dest, 1);
				if (worker->Container) {
					MarkUnitFieldFlags(*worker->Container);
				}			
				//
				// Take this depot?
				//
				if (d && d < best_dist) {
					best_depot = dest;
					best_dist = d;
				}
			}
		}
	}
예제 #3
0
/**
**  Attack units in distance.
**
**  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.
**
**  @return       Unit to be attacked.
**
*/
CUnit *AttackUnitsInDistance(const CUnit *unit, int range)
{
	CUnit *dest;
	const CUnitType *type;
	const CUnitType *dtype;
	CUnit *table[UnitMax];
	int x;
	int y;
	int n;
	int i;
	int d;
	int attackrange;
	int cost;
	int best_cost;
	const CPlayer *player;
	CUnit *best_unit;

	// if necessary, take possible damage on allied units into account...
	if (unit->Type->Missile.Missile->Range > 1 &&
			(range + unit->Type->Missile.Missile->Range < 15)) {
		return FindRangeAttack(unit, range);
	}

	//
	// Select all units in range.
	//
	x = unit->X;
	y = unit->Y;
	type = unit->Type;
	n = UnitCache.Select(x - range, y - range, x + range + type->TileWidth,
		y + range + type->TileHeight, table, UnitMax);
	
	if (range > 25 && n > 9) {
		referenceunit = unit;
		qsort((void*)table, n, sizeof(CUnit*), &CompareUnitDistance);
	}

	best_unit = NoUnitP;
	best_cost = INT_MAX;

	player = unit->Player;
	attackrange = unit->Stats->Variables[ATTACKRANGE_INDEX].Max;

	//
	// Find the best unit to attack
	//

	for (i = 0; i < n; ++i) {
		dest = table[i];

		if (!dest->IsVisibleAsGoal(unit->Player)) {
			continue;
		}

		if (!player->IsEnemy(dest)) { // a friend or neutral
			continue;
		}

		dtype = dest->Type;
		if (!CanTarget(type, dtype)) { // can't be attacked.
			continue;
		}

		//
		// Calculate the costs to attack the unit.
		// Unit with the smallest attack costs will be taken.
		//
		cost = 0;
		//
		// Priority 0-255
		//
		cost -= dtype->Priority * PRIORITY_FACTOR;
		//
		// Remaining HP (Health) 0-65535
		//
		cost += dest->Variable[HP_INDEX].Value * HEALTH_FACTOR;
		//
		// Unit in attack range?
		//
		d = MapDistanceBetweenUnits(unit, dest);

		// Use Circle, not square :)
		if (d > range) {
			continue;
		}
		if (d <= attackrange && d >= type->MinAttackRange) {
			cost += d * INRANGE_FACTOR;
			cost -= INRANGE_BONUS;
		} else {
			cost += d * DISTANCE_FACTOR;
		}
		//
		// Unit can attack back.
		//
		if (CanTarget(dtype, type)) {
			cost -= CANATTACK_BONUS;
		}

		//
		// Take this target?
		//
		if (cost < best_cost && (d <= attackrange ||
				UnitReachable(unit, dest, attackrange))) {
			best_unit = dest;
			best_cost = cost;
		}
	}

	return best_unit;
}
예제 #4
0
/**
**  Attack units in distance, with large missile
**
**  Choose the best target, that can be attacked. It takes into
**  account allied unit which could be hit by the missile
**
**  @param u      Find in distance for this unit.
**  @param range  Distance range to look.
**
**  @return       Unit to be attacked.
**
**  @note This could be improved, for better performance / better trade.
**  @note   Limited to attack range smaller than 16.
**  @note Will be moved to unit_ai.c soon.
*/
static CUnit *FindRangeAttack(const CUnit *u, int range)
{
	int x;
	int y;
	int n;
	int cost;
	int d;
	int effective_hp;
	int enemy_count;
	int missile_range;
	int attackrange;
	int hp_damage_evaluate;
	int good[32][32];
	int bad[32][32];
	CUnit *table[UnitMax];
	CUnit *dest;
	const CUnitType *dtype;
	const CUnitType *type;
	const CPlayer *player;
	int xx;
	int yy;
	int best_cost;
	int i;
	int sbad;
	int sgood;
	CUnit *best;

	type = u->Type;
	player = u->Player;

	//  If catapult, count units near the target...
	//   FIXME : make it configurable
	//

	missile_range = type->Missile.Missile->Range + range - 1;
	attackrange = u->Stats->Variables[ATTACKRANGE_INDEX].Max;
	// Evaluation of possible damage...
	hp_damage_evaluate = u->Stats->Variables[BASICDAMAGE_INDEX].Value
						+ u->Stats->Variables[PIERCINGDAMAGE_INDEX].Value;

	Assert(2 * missile_range + 1 < 32);

	//
	// If unit is removed, use containers x and y
	if (u->Removed) {
		x = u->Container->X;
		y = u->Container->Y;
		n = UnitCache.Select(x - missile_range, y - missile_range,
			x + missile_range + u->Container->Type->TileWidth,
			y + missile_range + u->Container->Type->TileHeight, table, UnitMax);
	} else {
		x = u->X;
		y = u->Y;
		n = UnitCache.Select(x - missile_range, y - missile_range,
			x + missile_range + u->Type->TileWidth,
			y + missile_range + u->Type->TileHeight, table, UnitMax);
	}

	if (!n) {
		return NoUnitP;
	}

	for (y = 0; y < 2 * missile_range + 1; ++y) {
		for (x = 0; x < 2 * missile_range + 1; ++x) {
			good[y][x] = 0;
			bad[y][x] = 0;
		}
	}

	enemy_count = 0;
	// FILL good/bad...
	for (i = 0; i < n; ++i) {
		dest = table[i];
		dtype = dest->Type;
		if (!dest->IsVisibleAsGoal(u->Player)) {
			table[i] = 0;
			continue;
		}

		// won't be a target...
		if (!CanTarget(type, dtype)) { // can't be attacked.
			table[i] = 0;
			continue;
		}

		if (!player->IsEnemy(dest)) { // a friend or neutral
			table[i] = 0;

			// Calc a negative cost
			// The gost is more important when the unit would be killed
			// by our fire.

			// It costs (is positive) if hp_damage_evaluate>dest->HP ...)
			// FIXME : assume that PRIORITY_FACTOR>HEALTH_FACTOR
			cost = HEALTH_FACTOR * (2 * hp_damage_evaluate - dest->Variable[HP_INDEX].Value) /
				(dtype->TileWidth * dtype->TileWidth);
			if (cost < 1) {
				cost = 1;
			}
			cost = (-cost);
		} else {
			//
			//  Calculate the costs to attack the unit.
			//  Unit with the smallest attack costs will be taken.
			//
			cost = 0;
			//
			//  Priority 0-255
			//
			cost += dtype->Priority * PRIORITY_FACTOR;
			//
			//  Remaining HP (Health) 0-65535
			//
			// Give a boost to unit we can kill in one shoot only

			//
			// calculate HP which will remain in the enemy unit, after hit
			//
			effective_hp = (dest->Variable[HP_INDEX].Value - 2 * hp_damage_evaluate);

			//
			// Unit we won't kill are evaluated the same
			//
			if (effective_hp > 0) {
				effective_hp = 0;
			}

			//
			// Unit we are sure to kill are all evaluated the same (except PRIORITY)
			//
			if (effective_hp < -hp_damage_evaluate) {
				effective_hp = -hp_damage_evaluate;
			}

			//
			// Here, effective_hp vary from -hp_damage_evaluate (unit will be killed) to 0 (unit can't be killed)
			// => we prefer killing rather than only hitting...
			//
			cost += -effective_hp * HEALTH_FACTOR;

			//
			//  Unit can attack back.
			//
			if (CanTarget(dtype, type)) {
				cost += CANATTACK_BONUS;
			}

			//
			// the cost may be divided accros multiple cells
			//
			cost = cost / (dtype->TileWidth * dtype->TileWidth);
			if (cost < 1) {
				cost = 1;
			}

			//
			// Removed Unit's are in bunkers
			//
			if (u->Removed) {
				d = MapDistanceBetweenUnits(u->Container, dest);
			} else {
				d = MapDistanceBetweenUnits(u, dest);
			}

			if (d <= attackrange || (d <= range && UnitReachable(u, dest, attackrange))) {
				++enemy_count;
			} else {
					table[i] = 0;
			}
		}

		x = dest->X - u->X + missile_range + 1;
		y = dest->Y - u->Y + missile_range + 1;

		// Mark the good/bad array...
		for (xx = 0; xx < dtype->TileWidth; ++xx) {
			for (yy = 0; yy < dtype->TileWidth; ++yy) {
				if ((x + xx < 0) || (y + yy < 0) ||
						(x + xx >= 2 * missile_range + 1) ||
						(y + yy >= 2 * missile_range + 1)) {
					continue;
				}
				if (cost < 0) {
					good[y + yy][x + xx] -= cost;
				} else {
					bad[y + yy][x + xx] += cost;
				}
			}
		}
	}

	if (!enemy_count) {
		return NoUnitP;
	}

	// Find the best area...
	// The target which provide the best bad/good ratio is choosen...
	best_cost = -1;
	best = NoUnitP;
	for (i = 0; i < n; ++i) {
		if (!table[i]) {
			continue;
		}
		dest = table[i];
		dtype = dest->Type;

		// put in x-y the real point which will be hit...
		// (only meaningful when dtype->TileWidth > 1)
		if (u->X < dest->X) {
			x = dest->X;
		} else if (u->X > dest->X + dtype->TileWidth - 1) {
			x = dest->X + dtype->TileWidth - 1;
		} else {
			x = u->X;
		}

		if (u->Y < dest->Y) {
			y = dest->Y;
		} else if (u->Y > dest->Y + dtype->TileHeight - 1) {
			y = dest->Y + dtype->TileHeight - 1;
		} else {
			y = u->Y;
		}

		// Make x,y relative to u->x...
		x = x - u->X + missile_range + 1;
		y = y - u->Y + missile_range + 1;

		sbad = 0;
		sgood = 0;
		for (yy = -(type->Missile.Missile->Range - 1);
			yy <= type->Missile.Missile->Range - 1; ++yy) {
			for (xx = -(type->Missile.Missile->Range - 1);
				xx <= type->Missile.Missile->Range - 1; ++xx) {
				if ((x + xx < 0) || (y + yy < 0) ||
						((x + xx) >= 2 * missile_range + 1) ||
						((y + yy) >= 2 * missile_range + 1)) {
					continue;
				}

				sbad += bad[y + yy][x + xx];
				sgood += good[y + yy][x + xx];
				if (!yy && !xx) {
					sbad += bad[y + yy][x + xx];
					sgood += good[y + yy][x + xx];
				}
			}
		}

		// don't consider small damages...
		if (sgood < 20) {
			sgood = 20;
		}

		cost = sbad / sgood;
		if (cost > best_cost) {
			best_cost = cost;
			best = dest;
		}
	}
	return best;
}
예제 #5
0
/**
**  Check if we can repair the building.
**
**  @param type      Unit that can repair the building.
**  @param building  Building to be repaired.
**
**  @return          True if can repair, false if can't repair..
*/
static int AiRepairBuilding(const CUnitType &type, CUnit &building)
{
	// Remove all workers not mining. on the way building something
	// FIXME: It is not clever to use workers with gold
	// Idea: Antonis: Put the rest of the workers in a table in case
	// miners can't reach but others can. This will be useful if AI becomes
	// more flexible (e.g.: transports workers to an island)
	// FIXME: too hardcoded, not nice, needs improvement.
	// FIXME: too many workers repair the same building!

	// Selection of mining workers.
	CUnit *table[UnitMax];
	int nunits = FindPlayerUnitsByType(AiPlayer->Player, type, table);
	int num = 0;
	for (int i = 0; i < nunits; ++i) {
		CUnit &unit = *table[i];

		if (unit.Type->RepairRange && unit.OrderCount == 1 &&
			((unit.CurrentAction() == UnitActionResource && unit.SubAction <= 55) /* SUB_START_GATHERING */ ||
				unit.CurrentAction() == UnitActionStill)) {
			table[num++] = &unit;
		}
	}

	// Sort by distance loops -Antonis-
	int distance[UnitMax];
	for (int i = 0; i < num; ++i) {
		CUnit &unit = *table[i];
		int rX = unit.tilePos.x - building.tilePos.x;
		int rY = unit.tilePos.y - building.tilePos.y;

		// FIXME: Probably calculated from top left corner of building
		rX = std::max(rX, -rX);
		rY = std::max(rY, -rY);
		distance[i] = std::min(rX, rY);
	}
	for (int i = 0; i < num; ++i) {
		int r_temp = distance[i];
		int index_temp = i;
		for (int j = i; j < num; ++j) {
			if (distance[j] < r_temp) {
				r_temp = distance[j];
				index_temp = j;
			}
		}
		if (index_temp > i) {
			std::swap(distance[i], distance[index_temp]);
			std::swap(table[i], table[index_temp]);
		}
	}

	// Check if building is reachable and try next trio of workers

	int nbworker = AiPlayer->TriedRepairWorkers[UnitNumber(building)];
	if (nbworker > num) {
		nbworker = AiPlayer->TriedRepairWorkers[UnitNumber(building)] = 0;
	}

	int k = nbworker;
	for (int i = nbworker; i < num && i < nbworker + 3; ++i) {

		CUnit &unit = *table[i];

		if (UnitReachable(unit, building, unit.Type->RepairRange)) {
			const Vec2i invalidPos = {-1, -1};
			CommandRepair(unit, invalidPos, &building, FlushCommands);
			return 1;
		}
		k = i;
	}
	AiPlayer->TriedRepairWorkers[UnitNumber(building)] = k + 1;
	return 0;
}
예제 #6
0
		void Compute(CUnit *const dest)
		{
			const CPlayer &player = *attacker->Player;

			if (!dest->IsVisibleAsGoal(player)) {
				dest->CacheLock = 1;
				return;
			}

			const CUnitType &type =  *attacker->Type;
			const CUnitType &dtype = *dest->Type;
			// won't be a target...
			if (!CanTarget(type, dtype)) { // can't be attacked.
				dest->CacheLock = 1;
				return;
			}
			// Don't attack invulnerable units
			if (dtype.BoolFlag[INDESTRUCTIBLE_INDEX].value || dest->Variable[UNHOLYARMOR_INDEX].Value) {
				dest->CacheLock = 1;
				return;
			}

			//  Calculate the costs to attack the unit.
			//  Unit with the smallest attack costs will be taken.

			int cost = 0;
			int hp_damage_evaluate;
			if (Damage) {
				hp_damage_evaluate = CalculateDamage(*attacker, *dest, Damage);
			} else {
				hp_damage_evaluate = attacker->Stats->Variables[BASICDAMAGE_INDEX].Value
									 + attacker->Stats->Variables[PIERCINGDAMAGE_INDEX].Value;
			}
			if (!player.IsEnemy(*dest)) { // a friend or neutral
				dest->CacheLock = 1;

				// Calc a negative cost
				// The gost is more important when the unit would be killed
				// by our fire.

				// It costs (is positive) if hp_damage_evaluate>dest->HP ...)
				// FIXME : assume that PRIORITY_FACTOR>HEALTH_FACTOR
				cost = HEALTH_FACTOR * (2 * hp_damage_evaluate -
										dest->Variable[HP_INDEX].Value) /
					   (dtype.TileWidth * dtype.TileWidth);
				cost = std::max(cost, 1);
				cost = -cost;
			} else {
				//  Priority 0-255
				cost += dtype.DefaultStat.Variables[PRIORITY_INDEX].Value * 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

				// calculate HP which will remain in the enemy unit, after hit
				int effective_hp = (dest->Variable[HP_INDEX].Value - 2 * hp_damage_evaluate);

				// Unit we won't kill are evaluated the same
				// Unit we are sure to kill are all evaluated the same (except PRIORITY)
				clamp(&effective_hp, -hp_damage_evaluate, 0);

				// Here, effective_hp vary from -hp_damage_evaluate (unit will be killed) to 0 (unit can't be killed)
				// => we prefer killing rather than only hitting...
				cost += -effective_hp * HEALTH_FACTOR;

				//  Unit can attack back.
				if (CanTarget(dtype, type)) {
					cost += CANATTACK_BONUS;
				}

				// the cost may be divided across multiple cells
				cost = cost / (dtype.TileWidth * dtype.TileWidth);
				cost = std::max(cost, 1);

				// Removed Unit's are in bunkers
				int d;
				if (attacker->Removed) {
					d = attacker->Container->MapDistanceTo(*dest);
				} else {
					d = attacker->MapDistanceTo(*dest);
				}

				int attackrange = attacker->Stats->Variables[ATTACKRANGE_INDEX].Max;
				if (d <= attackrange ||
					(d <= range && UnitReachable(*attacker, *dest, attackrange))) {
					++enemy_count;
				} else {
					dest->CacheLock = 1;
				}
				// Attack walls only if we are stuck in them
				if (dtype.BoolFlag[WALL_INDEX].value && d > 1) {
					dest->CacheLock = 1;
				}
			}

			// cost map is relative to attacker position
			const int x = dest->tilePos.x - attacker->tilePos.x + (size / 2);
			const int y = dest->tilePos.y - attacker->tilePos.y + (size / 2);
			Assert(x >= 0 && y >= 0);

			// Mark the good/bad array...
			for (int yy = 0; yy < dtype.TileHeight; ++yy) {
				for (int xx = 0; xx < dtype.TileWidth; ++xx) {
					int pos = (y + yy) * (size / 2) + (x + xx);
					if (pos >= good->size()) {
						printf("BUG: RangeTargetFinder::FillBadGood.Compute out of range. "\
						       "size: %d, pos: %d, " \
						       "x: %d, xx: %d, y: %d, yy: %d",
						       size, pos, x, xx, y, yy);
						break;
					}
					if (cost < 0) {
						good->at(pos) -= cost;
					} else {
						bad->at(pos) += cost;
					}
				}
			}
		}
예제 #7
0
	int ComputeCost(CUnit *const dest) const
	{
		const CPlayer &player = *attacker->Player;
		const CUnitType &type = *attacker->Type;
		const CUnitType &dtype = *dest->Type;
		const int attackrange = attacker->Stats->Variables[ATTACKRANGE_INDEX].Max;

		if (!player.IsEnemy(*dest) // a friend or neutral
			|| !dest->IsVisibleAsGoal(player)
			|| !CanTarget(type, dtype)) {
			return INT_MAX;
		}
		// Don't attack invulnerable units
		if (dtype.BoolFlag[INDESTRUCTIBLE_INDEX].value || dest->Variable[UNHOLYARMOR_INDEX].Value) {
			return INT_MAX;
		}
		// Unit in range ?
		const int d = attacker->MapDistanceTo(*dest);

		if (d > attackrange && !UnitReachable(*attacker, *dest, attackrange)) {
			return INT_MAX;
		}

		// Attack walls only if we are stuck in them
		if (dtype.BoolFlag[WALL_INDEX].value && d > 1) {
			return INT_MAX;
		}

		if (dtype.UnitType == UnitTypeFly && dest->IsAgressive() == false) {
			return INT_MAX / 2;
		}

		// Calculate the costs to attack the unit.
		// Unit with the smallest attack costs will be taken.
		int cost = 0;

		// Priority 0-255
		cost -= dtype.DefaultStat.Variables[PRIORITY_INDEX].Value * PRIORITY_FACTOR;
		// Remaining HP (Health) 0-65535
		cost += dest->Variable[HP_INDEX].Value * 100 / dest->Variable[HP_INDEX].Max * HEALTH_FACTOR;

		if (d <= attackrange && 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;
	}
예제 #8
0
/**
**	Attack units in distance.
**
**		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.
**
**	@return		Unit to be attacked.
**
**	@note	This could be improved, for better performance.
*/
global Unit* AttackUnitsInDistance(const Unit* unit,int range)
{
    const Unit* dest;
    const UnitType* type;
    const UnitType* dtype;
    Unit* table[UnitMax];
    int x;
    int y;
    int n;
    int i;
    int d;
    int attackrange;
    int cost;
    const Player* player;
    const Unit* best_unit;
    int best_cost;

    DebugLevel3Fn("(%d)%s\n" _C_ UnitNumber(unit) _C_ unit->Type->Ident);

    // if necessary, take possible damage on allied units into account...
    if (unit->Type->Missile.Missile->Range>1
	    && (range+unit->Type->Missile.Missile->Range<15)) {
        return FindRangeAttack(unit,range);
    }

    //
    //	Select all units in range.
    //
    x=unit->X;
    y=unit->Y;
    n=SelectUnits(x-range,y-range,x+range+1,y+range+1,table);

    best_unit=NoUnitP;
    best_cost=INT_MAX;

    player=unit->Player;
    type=unit->Type;
    attackrange=unit->Stats->AttackRange;
    //
    //	Find the best unit to attack
    //
    for( i=0; i<n; ++i ) {
	dest=table[i];
	//
	//	unusable unit
	//
	// FIXME: did SelectUnits already filter this.
	if( dest->Removed || dest->Invisible || !unit->HP
		|| !(dest->Visible&(1<<player->Player))
		|| dest->Orders[0].Action==UnitActionDie ) {
	    continue;
	}

	if( !IsEnemy(player,dest) ) {	// a friend or neutral
	    continue;
	}

	dtype=dest->Type;
	if( !CanTarget(type,dtype) ) {	// can't be attacked.
	    continue;
	}

	//
	//	Calculate the costs to attack the unit.
	//	Unit with the smallest attack costs will be taken.
	//
	cost=0;
	//
	//	Priority 0-255
	//
	cost-=dtype->Priority*PRIORITY_FACTOR;
	//
	//	Remaining HP (Health) 0-65535
	//
	cost+=dest->HP*HEALTH_FACTOR;
	//
	//	Unit in attack range?
	//
	d=MapDistanceBetweenUnits(unit,dest);
	if( d<type->MinAttackRange ) {	// FIXME: we don't support moving away!
	    continue;
	}
	if( d<attackrange && d>type->MinAttackRange ) {
	    cost+=d*INRANGE_FACTOR;
	    cost-=INRANGE_BONUS;
	} else {
	    cost+=d*DISTANCE_FACTOR;
	}
	//
	//	Unit can attack back.
	//
	if( CanTarget(dtype,type) ) {
	    cost-=CANATTACK_BONUS;
	}

	DebugLevel3Fn("%s -> %s\t%08x\n" _C_ type->Ident _C_ dtype->Ident _C_ cost);
	//
	//	Take this target?
	//
	if( cost<best_cost
		&& (d<attackrange || UnitReachable(unit,dest,attackrange)) ) {
	    best_unit=dest;
	    best_cost=cost;
	}
    }

/*
    if( best_unit ) {
	DebugLevel3Fn("Attacking (%d)%s -> %s\n" _C_ UnitNumber(unit) _C_
		unit->Type->Ident _C_ best_unit->Type->Ident);
    }
*/

    // FIXME: No idea how to make this correct, without cast!!
    return (Unit*)best_unit;
}
예제 #9
0
/**
**	Attack units in distance, with large missile
**
**		Choose the best target, that can be attacked. It takes into
**		account allied unit which could be hit by the missile
**
**	@param u	Find in distance for this unit.
**	@param range	Distance range to look.
**
**	@return		Unit to be attacked.
**
**	@note	This could be improved, for better performance / better trade.
**      @note   Limited to attack range smaller than 16.
**	@note	Will be moved to unit_ai.c soon.
*/
local Unit* FindRangeAttack(const Unit* u, int range)
{
    int x, y, n, cost,d,effective_hp,enemy_count;
    int missile_range,attackrange,hp_damage_evaluate;
    int good[32][32], bad[32][32];
    Unit* table[UnitMax];
    Unit* dest;
    const UnitType* dtype;
    const UnitType* type;
    const Player* player;
    int xx, yy;
    int best_x, best_y, best_cost;
    int i, sbad, sgood;
    Unit* best;

    type = u->Type;
    player = u->Player;

    //  If catapult, count units near the target...
    //      FIXME : make it configurable
    //

    missile_range = type->Missile.Missile->Range + range - 1;
    attackrange=u->Stats->AttackRange;
    // Evaluation of possible damage...
    hp_damage_evaluate=u->Stats->BasicDamage+u->Stats->PiercingDamage;

    DebugCheck(2 * missile_range + 1 >= 32);

    x = u->X;
    y = u->Y;
    n = SelectUnits(x - missile_range, y - missile_range,
	x + missile_range + 1, y + missile_range + 1, table);

    if (!n) {
	return NoUnitP;
    }

    for (y = 0; y < 2 * missile_range + 1; y++) {
	for (x = 0; x < 2 * missile_range + 1; x++) {
	    good[y][x] = 0;
	    bad[y][x] = 0;
	}
    }

    enemy_count=0;
    // FILL good/bad...
    for (i = 0; i < n; i++) {
	dest = table[i];
	dtype = dest->Type;
	//
	//      unusable unit
	//
	// FIXME: did SelectUnits already filter this.
	if (dest->Removed || dest->Invisible || !u->HP
		|| !(dest->Visible & (1 << player->Player))
		|| dest->Orders[0].Action == UnitActionDie) {
	    table[i] = 0;
	    continue;
	}

        // won't be a target...
        if (!CanTarget(type, dtype)) {	// can't be attacked.
	    table[i] = 0;
	    continue;
	}

	if (!IsEnemy(player, dest)) {	// a friend or neutral
	    table[i] = 0;

	    // Calc a negative cost
            // The gost is more important when the unit would be killed
            // by our fire.

            // It costs ( is positive ) if hp_damage_evaluate>dest->HP ... )
	    // FIXME : assume that PRIORITY_FACTOR>HEALTH_FACTOR
	    cost = HEALTH_FACTOR*(2*hp_damage_evaluate-dest->HP) / (dtype->TileWidth * dtype->TileWidth);
	    if (cost < 1) {
		cost = 1;
	    }
	    cost = (-cost);
	} else {
	    //
	    //  Calculate the costs to attack the unit.
	    //  Unit with the smallest attack costs will be taken.
	    //
	    cost = 0;
	    //
	    //  Priority 0-255
	    //
	    cost += dtype->Priority * PRIORITY_FACTOR;
	    //
	    //  Remaining HP (Health) 0-65535
	    //
	    // Give a boost to unit we can kill in one shoot only

	    //
	    // calculate HP which will remain in the enemy unit, after hit
	    //
	    effective_hp=(dest->HP-2*hp_damage_evaluate);

	    //
	    // Unit we won't kill are evaluated the same
	    //
	    if (effective_hp>0){
	      effective_hp=0;
	    }

	    //
	    // Unit we are sure to kill are all evaluated the same ( except PRIORITY )
	    //
	    if (effective_hp<(-hp_damage_evaluate)){
	      effective_hp=(-hp_damage_evaluate);
	    }

	    //
	    // Here, effective_hp vary from -hp_damage_evaluate ( unit will be killed) to 0 ( unit can't be killed )
	    // => we prefer killing rather than only hitting...
	    //
	    cost += (-effective_hp) * HEALTH_FACTOR;

	    //
	    //  Unit can attack back.
	    //
	    if (CanTarget(dtype, type)) {
		cost += CANATTACK_BONUS;
	    }

            //
            // the cost may be divided accros multiple cells
            //
	    cost=cost / (dtype->TileWidth * dtype->TileWidth);
	    if (cost < 1) {
		cost = 1;
	    }
     	    d=MapDistanceBetweenUnits(u,dest);

            // FIXME: we don't support moving away!
            if((d<type->MinAttackRange)||(!UnitReachable(u,dest,attackrange))) {
	    	table[i]=0;
	    } else {
	    	enemy_count++;
	    }
	}

	x = dest->X - u->X + missile_range+1;
	y = dest->Y - u->Y + missile_range+1;

	// Mark the good/bad array...
	for (xx = 0; xx < dtype->TileWidth; xx++) {
	    for (yy = 0; yy < dtype->TileWidth; yy++) {
		if ((x + xx < 0) || (y + yy < 0)
			|| (x + xx >= 2 * missile_range + 1)
			|| (y + yy >= 2 * missile_range + 1)) {
		    continue;
		}
		if (cost < 0) {
		    good[y + yy][x + xx] -= cost;
		} else {
		    bad[y + yy][x + xx] += cost;
		}
	    }
	}
    }

    if (!enemy_count) {
	return NoUnitP;
    }

    // Find the best area...
    // The target which provide the best bad/good ratio is choosen...
    best_x = -1;
    best_y = -1;
    best_cost = -1;
    best = NoUnitP;
    for (i = 0; i < n; i++) {
	if (!table[i]) {
	    continue;
	}
	dest = table[i];
	dtype = dest->Type;

	// put in x-y the real point which will be hit...
	// ( only meaningfull when dtype->TileWidth>1 )
        if (u->X<dest->X){
	    x=dest->X;
	} else if (u->X>dest->X+dtype->TileWidth-1){
	    x=dest->X+dtype->TileWidth-1;
	} else {
	    x=u->X;
	}

	if (u->Y<dest->Y){
	    y=dest->Y;
	} else if(u->Y>dest->Y+dtype->TileWidth-1){
	    y=dest->Y+dtype->TileWidth-1;
	} else {
	    y=u->Y;
	}

 	// Make x,y relative to u->x...
	x = x - u->X + missile_range+1;
	y = y - u->Y + missile_range+1;

	sbad = 0;
	sgood = 0;
	for (yy = -(type->Missile.Missile->Range - 1);
	    yy <= type->Missile.Missile->Range - 1; yy++) {
	    for (xx = -(type->Missile.Missile->Range - 1);
		xx <= type->Missile.Missile->Range - 1; xx++) {
		if ((x + xx < 0) || (y + yy < 0)
			|| ((x + xx) >= 2 * missile_range + 1)
			|| ((y + yy) >= 2 * missile_range + 1)) {
		    continue;
		}

		sbad += bad[y + yy][x + xx];
		sgood += good[y + yy][x + xx];
		if ((!yy) && (!xx)) {
		    sbad += bad[y + yy][x + xx];
		    sgood += good[y + yy][x + xx];
		}
	    }
	}

	// don't consider small damages...
	if (sgood < 20) {
	    sgood = 20;
	}

	cost = sbad / sgood;
	if (cost > best_cost) {
	    best_cost = cost;
	    best = dest;
	}
    }
    return best;
}