Beispiel #1
0
/**
**  Called if a member of Group is Attacked
**
**  @param attacker  Pointer to attacker unit.
**  @param defender  Pointer to unit that is being attacked.
*/
void GroupHelpMe(CUnit *attacker, CUnit &defender)
{
	/* Freandly Fire - typical splash */
	if (!attacker || attacker->Player->Index == defender.Player->Index) {
		return;
	}

	DebugPrint("%d: GroupHelpMe %d(%s) attacked at %d,%d\n" _C_
		defender.Player->Index _C_ UnitNumber(defender) _C_
		defender.Type->Ident.c_str() _C_ defender.tilePos.x _C_ defender.tilePos.y);

	//
	//  Don't send help to scouts (zeppelin,eye of vision).
	//
	if (!defender.Type->CanAttack && defender.Type->UnitType == UnitTypeFly) {
		return;
	}

	if (defender.GroupId) {
		int mask = 0;
		CUnitGroup *group;
		for (int num = 0; num < NUM_GROUPS; ++num) {

			//  Unit belongs to an group, check if brothers in arms can help
			if (defender.GroupId & (1<<num)) {
				mask |= (1<<num);
				group = &Groups[num];

				for (int i = 0; i < group->NumUnits; ++i) {
					CUnit &gunit = *group->Units[i];

					if (&defender == &gunit) {
						continue;
					}

					// if brother is idle or attack no-agressive target and
					// can attack our attacker then ask for help
					if (gunit.IsAgressive() && (gunit.IsIdle() ||
						!(gunit.CurrentAction() == UnitActionAttack &&
						gunit.CurrentOrder()->HasGoal() &&
						gunit.CurrentOrder()->GetGoal()->IsAgressive()))
						&& CanTarget(gunit.Type, attacker->Type)) {

						if (gunit.SavedOrder.Action == UnitActionStill) {
							// FIXME: should rewrite command handling
							CommandAttack(gunit, gunit.tilePos, NoUnitP, FlushCommands);
							gunit.SavedOrder = *gunit.Orders[1];
						}
						CommandAttack(gunit, attacker->tilePos, attacker, FlushCommands);
					}
				}
				if (!(defender.GroupId & ~mask)) {
					return;
				}
			}
		}
	}
}
Beispiel #2
0
/**
**  Auto attack nearby units if possible
*/
bool AutoAttack(CUnit &unit)
{
	if (unit.Type->CanAttack == false) {
		return false;
	}
	// Normal units react in reaction range.
	CUnit *goal = AttackUnitsInReactRange(unit);

	if (goal == NULL) {
		return false;
	}
	COrder *savedOrder = NULL;

	if (unit.CurrentAction() == UnitActionStill) {
		savedOrder = COrder::NewActionAttack(unit, unit.tilePos);
	} else if (unit.CanStoreOrder(unit.CurrentOrder())) {
		savedOrder = unit.CurrentOrder()->Clone();
	}
	// Weak goal, can choose other unit, come back after attack
	CommandAttack(unit, goal->tilePos, NULL, FlushCommands);

	if (savedOrder != NULL) {
		unit.SavedOrder = savedOrder;
	}
	return true;
}
Beispiel #3
0
/**
**	@brief	Auto attack nearby units if possible
*/
bool AutoAttack(CUnit &unit)
{
	//Wyrmgus start
//	if (unit.Type->CanAttack == false) {
	if (unit.CanAttack() == false) {
	//Wyrmgus end
		return false;
	}
	// Normal units react in reaction range.
	CUnit *goal = AttackUnitsInReactRange(unit);

	if (goal == nullptr) {
		return false;
	}
	COrder *savedOrder = nullptr;

	if (unit.CurrentAction() == UnitActionStill) {
		//Wyrmgus start
//		savedOrder = COrder::NewActionAttack(unit, unit.tilePos);
		savedOrder = COrder::NewActionAttack(unit, unit.tilePos, unit.MapLayer->ID);
		//Wyrmgus end
	} else if (unit.CanStoreOrder(unit.CurrentOrder())) {
		savedOrder = unit.CurrentOrder()->Clone();
	}
	// Weak goal, can choose other unit, come back after attack
	CommandAttack(unit, goal->tilePos, nullptr, FlushCommands, goal->MapLayer->ID);

	if (savedOrder != nullptr) {
		unit.SavedOrder = savedOrder;
	}
	return true;
}
Beispiel #4
0
/**
** Send command: Unit attack unit or at position.
**
** @param unit     pointer to unit.
** @param pos      map tile position to attack.
** @param attack   or !=NoUnitP unit to be attacked.
** @param flush    Flag flush all pending commands.
*/
void SendCommandAttack(CUnit &unit, const Vec2i &pos, CUnit *attack, int flush)
{
	if (!IsNetworkGame()) {
		CommandLog("attack", &unit, flush, pos.x, pos.y, attack, NULL, -1);
		CommandAttack(unit, pos, attack, flush);
	} else {
		NetworkSendCommand(MessageCommandAttack, unit, pos.x, pos.y, attack, 0, flush);
	}
}
Beispiel #5
0
void AiForceManager::Update()
{
	for (unsigned int f = 0; f < forces.size(); ++f) {
		AiForce &force = forces[f];
		//  Look if our defenders still have enemies in range.

		if (force.Defending) {
			force.RemoveDeadUnit();

			if (force.Size() == 0) {
				force.Attacking = false;
				force.Defending = false;
				continue;
			}
			const int nearDist = 5;

			if (Map.Info.IsPointOnMap(force.GoalPos) == false) {
				force.ReturnToHome();
				//  Check if some unit from force reached goal point
			} else if (force.Units[0]->MapDistanceTo(force.GoalPos) <= nearDist) {
				//  Look if still enemies in attack range.
				const CUnit *dummy = NULL;
				if (!AiForceEnemyFinder<AIATTACK_RANGE>(force, &dummy).found()) {
					force.ReturnToHome();
				}
			} else { // Find idle units and order them to defend
				std::vector<CUnit *> idleUnits;
				for (unsigned int i = 0; i != force.Size(); ++i) {
					CUnit &aiunit = *force.Units[i];

					if (aiunit.IsIdle() && 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();
			force.Update();
		}
	}
}
Beispiel #6
0
void AiForce::Attack(const Vec2i &pos)
{
	RemoveDeadUnit();

	if (Units.size() == 0) {
		this->Attacking = false;
		return;
	}
	if (!this->Attacking) {
		// Remember the original force position so we can return there after attack
		if (this->Role == AiForceRoleDefend
			|| (this->Role == AiForceRoleAttack && this->State == AiForceAttackingState_Waiting)) {
			this->HomePos = this->Units[this->Units.size() - 1]->tilePos;
		}
		this->Attacking = true;
	}
	Vec2i goalPos(pos);

	if (Map.Info.IsPointOnMap(goalPos) == false) {
		/* Search in entire map */
		const CUnit *enemy = NULL;
		AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &enemy);
		if (enemy) {
			goalPos = enemy->tilePos;
		}
	}
	this->GoalPos = goalPos;
	if (Map.Info.IsPointOnMap(goalPos) == false) {
		DebugPrint("%d: Need to plan an attack with transporter\n" _C_ AiPlayer->Player->Index);
		if (State == AiForceAttackingState_Waiting && !PlanAttack()) {
			DebugPrint("%d: Can't transport\n" _C_ AiPlayer->Player->Index);
			Attacking = false;
		}
		return;
	}
	//  Send all units in the force to enemy.
	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);
			}
		}
	}
}
Beispiel #7
0
/**
**	Attack opponent with force.
**
**	@param force	Force number to attack with.
*/
global void AiAttackWithForce(int force)
{
    const AiUnit* aiunit;
    const Unit* enemy;
    int x;
    int y;

    AiCleanForce(force);

    AiPlayer->Force[force].Attacking=0;
    if( (aiunit=AiPlayer->Force[force].Units) ) {
	AiPlayer->Force[force].Attacking=1;

	enemy=NoUnitP;
	while( aiunit && !enemy ) {	// Use an unit that can attack
	    if( aiunit->Unit->Type->CanAttack ) {
		enemy = AttackUnitsInDistance(aiunit->Unit, MaxMapWidth);
	    }
	    aiunit=aiunit->Next;
	}

	if (!enemy) {
	    DebugLevel0Fn("Need to plan an attack with transporter\n");
	    if( !AiPlayer->Force[force].State 
		    && !AiPlanAttack(&AiPlayer->Force[force]) ) {
		DebugLevel0Fn("Can't transport, look for walls\n");
		if( !AiFindWall(&AiPlayer->Force[force]) ) {
		    AiPlayer->Force[force].Attacking=0;
		}
	    }
	    return;
	}
	AiPlayer->Force[force].State=0;
	x = enemy->X;
	y = enemy->Y;

	//
	//	Send all units in the force to enemy.
	//
	aiunit=AiPlayer->Force[force].Units;
	while( aiunit ) {
	    if( aiunit->Unit->Type->CanAttack ) {
		CommandAttack(aiunit->Unit, x, y, NULL,FlushCommands);
	    } else {
		CommandMove(aiunit->Unit, x, y, FlushCommands);
	    }
	    aiunit=aiunit->Next;
	}
    }
}
Beispiel #8
0
    void ArAIProperty::InitNPCBehaviorTree()
    {
        mAIRoot->addChild(DI_NEW BoolCondition([this](void*){
            
            float sight = DiK2Pos::FromWorldScale(mEntity->GetAttribute()->GetEntityConfig()->sightrangenight);
            if (!mEntity->CheckDistance(ArEntityManager::GetHeroID(), sight))
            {
                // change to idle
                mEntity->GetEntity<ArDynEntity>()->GetMotionProperty()->ModalityChange(MODALITY_STAND);
                return false;
            }
            else
            {
                return true;
            }
        }, true));

        mAIRoot->addChild(DI_NEW BoolCondition([this](void*){
            float range = DiK2Pos::FromWorldScale(mEntity->GetAttribute()->GetEntityConfig()->attackrange*1.3f);
            auto heroEntity = ArGameApp::Get()->GetEntityManager()->GetHero();
            auto thisEntity = dynamic_cast<ArDynEntity*>(mEntity);
            if (!thisEntity)
            {
                return false;
            }

            if (!mEntity->CheckAbsDistance(ArEntityManager::GetHeroID(), range))
            {
                // change to follow
                CommandFollowTo(ArEntityManager::GetHeroID(), range-0.1f);
                return false;
            }
            else
            {
                if (!(heroEntity->GetMoveProperty()->GetWalkMode() == ENUM_WALK_MODE_WALK))
                {
                    // attack
                    CommandAttack(ArEntityManager::GetHeroID());
                }
                return true;
            }
        }, true));
    }
Beispiel #9
0
/**
**	Attack at position with force.
**
**	@param force	Force number to attack with.
**	@param x	X tile map position to be attacked.
**	@param y	Y tile map position to be attacked.
*/
global void AiAttackWithForceAt(int force,int x,int y)
{
    const AiUnit* aiunit;

    AiCleanForce(force);

    if( (aiunit=AiPlayer->Force[force].Units) ) {
	AiPlayer->Force[force].Attacking=1;

	//
	//	Send all units in the force to enemy.
	//
	while( aiunit ) {
	    if( aiunit->Unit->Type->CanAttack ) {
		CommandAttack(aiunit->Unit, x, y, NULL,FlushCommands);
	    } else {
		CommandMove(aiunit->Unit, x, y, FlushCommands);
	    }
	    aiunit=aiunit->Next;
	}
    }
}
Beispiel #10
0
/**
**	Step 2)
**	Send force awaay in transporters, to unload at target position.
**
**	@param force	Force pointer.
**
**	@todo	The transporter should avoid enemy contact and should land
**		at an unfortified coast. If we send more transporters they
**		should land on different positions.
*/
local void AiSendTransporter(AiForce* force)
{
    AiUnit* aiunit;

    //
    //	Find all transporters.
    //
    aiunit=force->Units;
    while( aiunit ) {
	//	Transporter to unload units
	if( aiunit->Unit->Type->Transporter ) {
	    CommandUnload(aiunit->Unit, force->GoalX, force->GoalY, NoUnitP,
		    FlushCommands);
	//	Ships to defend transporter
	} else if( aiunit->Unit->Type->UnitType==UnitTypeNaval ) {
	    CommandAttack(aiunit->Unit, force->GoalX, force->GoalY, NoUnitP,
		    FlushCommands);
	}
	aiunit=aiunit->Next;
    }
    ++force->State;
}
Beispiel #11
0
/**
** Execute a command (from network).
**
** @param msgnr    Network message type
** @param unum     Unit number (slot) that receive the command.
** @param x        optional X map position.
** @param y        optional y map position.
** @param dstnr    optional destination unit.
*/
void ExecCommand(unsigned char msgnr, UnitRef unum,
				 unsigned short x, unsigned short y, UnitRef dstnr)
{
	CUnit &unit = UnitManager.GetSlotUnit(unum);
	const Vec2i pos(x, y);
	const int arg1 = x;
	const int arg2 = y;
	//
	// Check if unit is already killed?
	//
	if (unit.Destroyed) {
		DebugPrint(" destroyed unit skipping %d\n" _C_ UnitNumber(unit));
		return;
	}
	Assert(unit.Type);

	const int status = (msgnr & 0x80) >> 7;
	// Note: destroyed destination unit is handled by the action routines.

	switch (msgnr & 0x7F) {
		case MessageSync:
			return;
		case MessageQuit:
			return;
		case MessageChat:
			return;

		case MessageCommandStop:
			CommandLog("stop", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1);
			CommandStopUnit(unit);
			break;
		case MessageCommandStand:
			CommandLog("stand-ground", &unit, status, -1, -1, NoUnitP, NULL, -1);
			CommandStandGround(unit, status);
			break;
		case MessageCommandDefend: {
			if (dstnr != (unsigned short)0xFFFF) {
				CUnit &dest = UnitManager.GetSlotUnit(dstnr);
				Assert(dest.Type);
				CommandLog("defend", &unit, status, -1, -1, &dest, NULL, -1);
				CommandDefend(unit, dest, status);
			}
			break;
		}
		case MessageCommandFollow: {
			if (dstnr != (unsigned short)0xFFFF) {
				CUnit &dest = UnitManager.GetSlotUnit(dstnr);
				Assert(dest.Type);
				CommandLog("follow", &unit, status, -1, -1, &dest, NULL, -1);
				CommandFollow(unit, dest, status);
			}
			break;
		}
		case MessageCommandMove:
			//Wyrmgus start
//			CommandLog("move", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1);
//			CommandMove(unit, pos, status);
			if (!unit.CanMove()) { //FIXME: find better way to identify whether the unit should move or set a rally point
				CommandLog("rally-point", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1);
				CommandRallyPoint(unit, pos);
			} else {
				CommandLog("move", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1);
				CommandMove(unit, pos, status);
			}
			//Wyrmgus end
			break;
		//Wyrmgus start
		case MessageCommandPickUp: {
			if (dstnr != (unsigned short)0xFFFF) {
				CUnit &dest = UnitManager.GetSlotUnit(dstnr);
				Assert(dest.Type);
				CommandLog("pick-up", &unit, status, -1, -1, &dest, NULL, -1);
				CommandPickUp(unit, dest, status);
			}
			break;
		}
		//Wyrmgus end
		case MessageCommandRepair: {
			CUnit *dest = NoUnitP;
			if (dstnr != (unsigned short)0xFFFF) {
				dest = &UnitManager.GetSlotUnit(dstnr);
				Assert(dest && dest->Type);
			}
			CommandLog("repair", &unit, status, pos.x, pos.y, dest, NULL, -1);
			CommandRepair(unit, pos, dest, status);
			break;
		}
		case MessageCommandAutoRepair:
			CommandLog("auto-repair", &unit, status, arg1, arg2, NoUnitP, NULL, 0);
			CommandAutoRepair(unit, arg1);
			break;
		case MessageCommandAttack: {
			CUnit *dest = NoUnitP;
			if (dstnr != (unsigned short)0xFFFF) {
				dest = &UnitManager.GetSlotUnit(dstnr);
				Assert(dest && dest->Type);
			}
			CommandLog("attack", &unit, status, pos.x, pos.y, dest, NULL, -1);
			CommandAttack(unit, pos, dest, status);
			break;
		}
		case MessageCommandGround:
			CommandLog("attack-ground", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1);
			CommandAttackGround(unit, pos, status);
			break;
		//Wyrmgus start
		case MessageCommandUse: {
			if (dstnr != (unsigned short)0xFFFF) {
				CUnit &dest = UnitManager.GetSlotUnit(dstnr);
				Assert(dest.Type);
				CommandLog("use", &unit, status, -1, -1, &dest, NULL, -1);
				CommandUse(unit, dest, status);
			}
			break;
		}
		//Wyrmgus end
		case MessageCommandPatrol:
			CommandLog("patrol", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1);
			CommandPatrolUnit(unit, pos, status);
			break;
		case MessageCommandBoard: {
			if (dstnr != (unsigned short)0xFFFF) {
				CUnit &dest = UnitManager.GetSlotUnit(dstnr);
				Assert(dest.Type);
				CommandLog("board", &unit, status, arg1, arg2, &dest, NULL, -1);
				CommandBoard(unit, dest, status);
			}
			break;
		}
		case MessageCommandUnload: {
			CUnit *dest = NULL;
			if (dstnr != (unsigned short)0xFFFF) {
				dest = &UnitManager.GetSlotUnit(dstnr);
				Assert(dest && dest->Type);
			}
			CommandLog("unload", &unit, status, pos.x, pos.y, dest, NULL, -1);
			CommandUnload(unit, pos, dest, status);
			break;
		}
		case MessageCommandBuild:
			CommandLog("build", &unit, status, pos.x, pos.y, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1);
			CommandBuildBuilding(unit, pos, *UnitTypes[dstnr], status);
			break;
		case MessageCommandDismiss:
			CommandLog("dismiss", &unit, FlushCommands, -1, -1, NULL, NULL, -1);
			CommandDismiss(unit);
			break;
		case MessageCommandResourceLoc:
			CommandLog("resource-loc", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1);
			CommandResourceLoc(unit, pos, status);
			break;
		case MessageCommandResource: {
			if (dstnr != (unsigned short)0xFFFF) {
				CUnit &dest = UnitManager.GetSlotUnit(dstnr);
				Assert(dest.Type);
				CommandLog("resource", &unit, status, -1, -1, &dest, NULL, -1);
				CommandResource(unit, dest, status);
			}
			break;
		}
		case MessageCommandReturn: {
			CUnit *dest = (dstnr != (unsigned short)0xFFFF) ? &UnitManager.GetSlotUnit(dstnr) : NULL;
			CommandLog("return", &unit, status, -1, -1, dest, NULL, -1);
			CommandReturnGoods(unit, dest, status);
			break;
		}
		case MessageCommandTrain:
			//Wyrmgus start
//			CommandLog("train", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1);
//			CommandTrainUnit(unit, *UnitTypes[dstnr], status);
			CommandLog("train", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), arg1); // use X as a way to mark the player
			CommandTrainUnit(unit, *UnitTypes[dstnr], arg1, status);
			//Wyrmgus end
			break;
		case MessageCommandCancelTrain:
			// We need (short)x for the last slot -1
			if (dstnr != (unsigned short)0xFFFF) {
				CommandLog("cancel-train", &unit, FlushCommands, -1, -1, NoUnitP,
						   UnitTypes[dstnr]->Ident.c_str(), (short)x);
				CommandCancelTraining(unit, (short)x, UnitTypes[dstnr]);
			} else {
				CommandLog("cancel-train", &unit, FlushCommands, -1, -1, NoUnitP, NULL, (short)x);
				CommandCancelTraining(unit, (short)x, NULL);
			}
			break;
		case MessageCommandUpgrade:
			//Wyrmgus start
			/*
			CommandLog("upgrade-to", &unit, status, -1, -1, NoUnitP,
					   UnitTypes[dstnr]->Ident.c_str(), -1);
			CommandUpgradeTo(unit, *UnitTypes[dstnr], status);
			break;
			*/
			if (arg1 == 2) { //use X as a way to mark whether this is an upgrade or a transformation
				CommandLog("transform-into", &unit, status, -1, -1, NoUnitP,
						   UnitTypes[dstnr]->Ident.c_str(), -1);
				CommandTransformIntoType(unit, *UnitTypes[dstnr]);
			} else {
				CommandLog("upgrade-to", &unit, status, -1, -1, NoUnitP,
						   UnitTypes[dstnr]->Ident.c_str(), -1);
				CommandUpgradeTo(unit, *UnitTypes[dstnr], status);
			}
			break;
			//Wyrmgus end
		case MessageCommandCancelUpgrade:
			CommandLog("cancel-upgrade-to", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1);
			CommandCancelUpgradeTo(unit);
			break;
		case MessageCommandResearch:
			CommandLog("research", &unit, status, -1, -1, NoUnitP,
					   AllUpgrades[arg1]->Ident.c_str(), -1);
			CommandResearch(unit, *AllUpgrades[arg1], status);
			break;
		case MessageCommandCancelResearch:
			CommandLog("cancel-research", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1);
			CommandCancelResearch(unit);
			break;
		//Wyrmgus start
		case MessageCommandQuest: {
			CommandLog("quest", &unit, 0, 0, 0, NoUnitP, Quests[arg1]->Ident.c_str(), -1);
			CommandQuest(unit, Quests[arg1]);
			break;
		}
		case MessageCommandBuy: {
			if (dstnr != (unsigned short)0xFFFF) {
				CUnit &dest = UnitManager.GetSlotUnit(dstnr);
				Assert(dest.Type);
				CommandLog("buy", &unit, 0, -1, -1, &dest, NULL, arg1);
				CommandBuy(unit, &dest, arg1);
			}
			break;
		}
		//Wyrmgus end
		default: {
			int id = (msgnr & 0x7f) - MessageCommandSpellCast;
			if (arg2 != (unsigned short)0xFFFF) {
				CUnit *dest = NULL;
				if (dstnr != (unsigned short)0xFFFF) {
					dest = &UnitManager.GetSlotUnit(dstnr);
					Assert(dest && dest->Type);
				}
				CommandLog("spell-cast", &unit, status, pos.x, pos.y, dest, NULL, id);
				CommandSpellCast(unit, pos, dest, *SpellTypeTable[id], status);
			} else {
				CommandLog("auto-spell-cast", &unit, status, arg1, -1, NoUnitP, NULL, id);
				CommandAutoSpellCast(unit, id, arg1);
			}
			break;
		}
	}
}
Beispiel #12
0
void MonsterFSM::orderTheAttack(void)
{
	ASSERT(m_Owner!=0, "Owner was NULL");
	m_Owner->QueueCommand(CommandAttack(targetCreatureHANDLE, FRAND_RANGE(0.7f, 1.0f)));
}
Beispiel #13
0
/**
**  Unit Patrol:
**    The unit patrols between two points.
**    Any enemy unit in reaction range is attacked.
**  @todo FIXME:
**    Should do some tries to reach the end-points.
**    Should support patrol between more points!
**    Patrol between units.
**
**  @param unit  Patroling unit pointer.
*/
void HandleActionPatrol(CUnit *unit)
{
	if (unit->Wait) {
		unit->Wait--;
		return;
	}

	if (!unit->SubAction) { // first entry.
		NewResetPath(unit);
		unit->SubAction = 1;
	}

	switch (DoActionMove(unit)) {
		case PF_FAILED:
			unit->SubAction = 1;
			break;
		case PF_UNREACHABLE:
			// Increase range and try again
			unit->SubAction = 1;
			if (unit->Orders[0]->Range <= Map.Info.MapWidth ||
					unit->Orders[0]->Range <= Map.Info.MapHeight) {
				unit->Orders[0]->Range++;
				break;
			}
			// FALL THROUGH
		case PF_REACHED:
			unit->SubAction = 1;
			unit->Orders[0]->Range = 0;
			SwapPatrolPoints(unit);
			break;
		case PF_WAIT:
			// Wait for a while then give up
			unit->SubAction++;
			if (unit->SubAction == 5) {
				unit->SubAction = 1;
				unit->Orders[0]->Range = 0;
				SwapPatrolPoints(unit);
			}
			break;
		default: // moving
			unit->SubAction = 1;
			break;
	}

	if (!unit->Anim.Unbreakable) {
		//
		// Attack any enemy in reaction range.
		//  If don't set the goal, the unit can then choose a
		//  better goal if moving nearer to enemy.
		//
		if (unit->Type->CanAttack) {
			const CUnit *goal = AttackUnitsInReactRange(unit);
			if (goal) {
				DebugPrint("Patrol attack %d\n" _C_ UnitNumber(goal));
				CommandAttack(unit, goal->X, goal->Y, NULL, FlushCommands);
				// Save current command to come back.
				unit->SavedOrder = *unit->Orders[0];
				unit->Orders[0]->Action = UnitActionStill;
				unit->Orders[0]->Goal = NoUnitP;
				unit->SubAction = 0;
			}
		}
	}
}
Beispiel #14
0
/**
**  Called if a Unit is Attacked
**
**  @param attacker  Pointer to attacker unit.
**  @param defender  Pointer to unit that is being attacked.
*/
void AiHelpMe(const CUnit *attacker, CUnit &defender)
{
	/* Friendly Fire - typical splash */
	if (!attacker || attacker->Player->Index == defender.Player->Index) {
		//FIXME - try react somehow
		return;
	}

	DebugPrint("%d: %d(%s) attacked at %d,%d\n" _C_
			   defender.Player->Index _C_ UnitNumber(defender) _C_
			   defender.Type->Ident.c_str() _C_ defender.tilePos.x _C_ defender.tilePos.y);

	//  Don't send help to scouts (zeppelin,eye of vision).
	if (!defender.Type->CanAttack && defender.Type->UnitType == UnitTypeFly) {
		return;
	}
	// Summoned unit, don't help
	if (defender.Summoned) {
		return;
	}

	PlayerAi &pai = *defender.Player->Ai;
	AiPlayer = &pai;

	//  If unit belongs to an attacking force, check if force members can help.
	if (defender.GroupId) {
		AiForce &aiForce = pai.Force[defender.GroupId - 1];

		//  Unit belongs to an force, check if brothers in arms can help
		for (unsigned int i = 0; i < aiForce.Units.size(); ++i) {
			CUnit &aiunit = *aiForce.Units[i];

			if (&defender == &aiunit) {
				continue;
			}

			// if brother is idle or attack no-agressive target and
			// can attack our attacker then ask for help
			// FIXME ad support for help from Coward type units
			if (aiunit.IsAgressive() && CanTarget(*aiunit.Type, *attacker->Type)
				&& aiunit.CurrentOrder()->GetGoal() != attacker) {
				bool shouldAttack = aiunit.IsIdle() && aiunit.Threshold == 0;

				if (aiunit.CurrentAction() == UnitActionAttack) {
					const COrder_Attack &orderAttack = *static_cast<COrder_Attack *>(aiunit.CurrentOrder());
					const CUnit *oldGoal = orderAttack.GetGoal();

					if (oldGoal == NULL || (ThreatCalculate(defender, *attacker) < ThreatCalculate(defender, *oldGoal)
											&& aiunit.MapDistanceTo(defender) <= aiunit.Stats->Variables[ATTACKRANGE_INDEX].Max)) {
						shouldAttack = true;
					}
				}

				if (shouldAttack) {
					CommandAttack(aiunit, attacker->tilePos, const_cast<CUnit *>(attacker), FlushCommands);
					COrder *savedOrder = COrder::NewActionAttack(aiunit, attacker->tilePos);

					if (aiunit.CanStoreOrder(savedOrder) == false) {
						delete savedOrder;
						savedOrder = NULL;
					} else {
						aiunit.SavedOrder = savedOrder;
					}
				}
			}
		}
		if (!aiForce.Defending && aiForce.State > 0) {
			DebugPrint("%d: %d(%s) belong to attacking force, don't defend it\n" _C_
					   defender.Player->Index _C_ UnitNumber(defender) _C_ defender.Type->Ident.c_str());
			// unit belongs to an attacking force,
			// so don't send others force in such case.
			// FIXME: there may be other attacking the same place force who can help
			return;
		}
	}

	// Send defending forces, also send attacking forces if they are home/traning.
	// This is still basic model where we suspect only one base ;(
	const Vec2i &pos = attacker->tilePos;

	for (unsigned int i = 0; i < pai.Force.Size(); ++i) {
		AiForce &aiForce = pai.Force[i];

		if (aiForce.Size() > 0
			&& ((aiForce.Role == AiForceRoleDefend && !aiForce.Attacking)
				|| (aiForce.Role == AiForceRoleAttack && !aiForce.Attacking && !aiForce.State))) {  // none attacking
			aiForce.Defending = true;
			aiForce.Attack(pos);
		}
	}
}
Beispiel #15
0
/**
** Force on attack ride. We attack until there is no unit or enemy left.
**
** @param force Force pointer.
*/
void AiForce::Update()
{
	Assert(Defending == false);
	if (Size() == 0) {
		Attacking = false;
		if (!Defending && State > AiForceAttackingState_Waiting) {
			DebugPrint("%d: Attack force #%lu was destroyed, giving up\n"
					   _C_ AiPlayer->Player->Index _C_(long unsigned int)(this  - & (AiPlayer->Force[0])));
			Reset(true);
		}
		return;
	}
	Attacking = false;
	for (unsigned int i = 0; i < Size(); ++i) {
		CUnit *aiunit = Units[i];

		if (aiunit->Type->CanAttack) {
			Attacking = true;
			break;
		}
	}
	if (Attacking == false) {
		if (!Defending && State > AiForceAttackingState_Waiting) {
			DebugPrint("%d: Attack force #%lu has lost all agresive units, giving up\n"
					   _C_ AiPlayer->Player->Index _C_(long unsigned int)(this  - & (AiPlayer->Force[0])));
			Reset(true);
		}
		return ;
	}
#if 0
	if (State == AiForceAttackingState_Waiting) {
		if (!this->PlanAttack()) {
			DebugPrint("Can't transport, look for walls\n");
			if (!AiFindWall(this)) {
				Attacking = false;
				return ;
			}
		}
		State = AiForceAttackingState_Boarding;
	}
#endif
	if (State == AiForceAttackingState_Boarding) {
		AiGroupAttackerForTransport(*this);
		return ;
	}
	if (State == AiForceAttackingState_AttackingWithTransporter) {
		// Move transporters to goalpos
		std::vector<CUnit *> transporters;
		bool emptyTrans = true;
		for (unsigned int i = 0; i != Size(); ++i) {
			CUnit &aiunit = *Units[i];

			if (aiunit.CanMove() && aiunit.Type->MaxOnBoard) {
				transporters.push_back(&aiunit);
				if (aiunit.BoardCount > 0) {
					emptyTrans = false;
				}
			}
		}
		if (transporters.empty()) {
			// Our transporters have been destroyed
			DebugPrint("%d: Attack force #%lu has lost all agresive units, giving up\n"
				_C_ AiPlayer->Player->Index _C_(long unsigned int)(this  - & (AiPlayer->Force[0])));
			Reset(true);
		} else if (emptyTrans) {
			// We have emptied our transporters, go go go
			State = AiForceAttackingState_GoingToRallyPoint;
		} else {
			for (size_t i = 0; i != transporters.size(); ++i) {
				CUnit &trans = *transporters[i];
				const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference.

				trans.Wait = delay;
				CommandUnload(trans, this->GoalPos, NULL, FlushCommands);
			}
		}
		return;
	}
	CUnit *leader = NULL;
	for (unsigned int i = 0; i != Size(); ++i) {
		CUnit &aiunit = *Units[i];

		if (aiunit.IsAgressive()) {
			leader = &aiunit;
			break;
		}
	}

	const int thresholdDist = 5; // Hard coded value
	Assert(Map.Info.IsPointOnMap(GoalPos));
	if (State == AiForceAttackingState_GoingToRallyPoint) {
		// Check if we are near the goalpos
		int minDist = Units[0]->MapDistanceTo(this->GoalPos);
		int maxDist = minDist;

		for (size_t i = 0; i != Size(); ++i) {
			const int distance = Units[i]->MapDistanceTo(this->GoalPos);
			minDist = std::min(minDist, distance);
			maxDist = std::max(maxDist, distance);
		}

		if (WaitOnRallyPoint > 0 && minDist <= thresholdDist) {
			--WaitOnRallyPoint;
		}
		if (maxDist <= thresholdDist || !WaitOnRallyPoint) {
			const CUnit *unit = NULL;

			AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &unit);
			if (!unit) {
				AiForceEnemyFinder<AIATTACK_ALLMAP>(*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;
					State = AiForceAttackingState_Waiting;
					return;
				}
			}
			this->GoalPos = unit->tilePos;
			State = AiForceAttackingState_Attacking;
			for (size_t i = 0; i != this->Size(); ++i) {
				CUnit &aiunit = *this->Units[i];
				const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference.

				aiunit.Wait = delay;
				if (aiunit.IsAgressive()) {
					CommandAttack(aiunit, this->GoalPos, NULL, FlushCommands);
				} else {
					if (leader) {
						CommandDefend(aiunit, *leader, FlushCommands);
					} else {
						CommandMove(aiunit, this->GoalPos, FlushCommands);
					}
				}
			}
		}
	}

	std::vector<CUnit *> idleUnits;
	for (unsigned int i = 0; i != Size(); ++i) {
		CUnit &aiunit = *Units[i];

		if (aiunit.IsIdle()) {
			idleUnits.push_back(&aiunit);
		}
	}

	if (idleUnits.empty()) {
		return;
	}

	if (State == AiForceAttackingState_Attacking && idleUnits.size() == this->Size()) {
		const CUnit *unit = NULL;

		bool isNaval = false;
		for (size_t i = 0; i != this->Units.size(); ++i) {
			CUnit *const unit = this->Units[i];
			if (unit->Type->UnitType == UnitTypeNaval && unit->Type->CanAttack) {
				isNaval = true;
				break;
			}
		}
		if (isNaval) {
			AiForceEnemyFinder<AIATTACK_ALLMAP>(*this, &unit);
		} else {
			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;
			State = AiForceAttackingState_Waiting;
			return;
		} else {
			Vec2i resultPos;
			NewRallyPoint(unit->tilePos, &resultPos);
			this->GoalPos = resultPos;
			this->State = AiForceAttackingState_GoingToRallyPoint;
		}
	}
	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 (leader) {
			if (aiunit.IsAgressive()) {
				if (State == AiForceAttackingState_Attacking) {
					CommandAttack(aiunit, leader->tilePos, NULL, FlushCommands);
				} else {
					CommandAttack(aiunit, this->GoalPos, NULL, FlushCommands);
				}
			} else {
				CommandDefend(aiunit, *leader, FlushCommands);
			}
		} else {
			if (aiunit.IsAgressive()) {
				CommandAttack(aiunit, this->GoalPos, NULL, FlushCommands);
			} else {
				CommandMove(aiunit, this->GoalPos, FlushCommands);
			}
		}
	}
}
Beispiel #16
0
void AiForce::Attack(const Vec2i &pos)
{
	bool isDefenceForce = false;
	RemoveDeadUnit();

	if (Units.size() == 0) {
		this->Attacking = false;
		this->State = AiForceAttackingState_Waiting;
		return;
	}
	if (!this->Attacking) {
		// Remember the original force position so we can return there after attack
		if (this->Role == AiForceRoleDefend
			|| (this->Role == AiForceRoleAttack && this->State == AiForceAttackingState_Waiting)) {
			this->HomePos = this->Units[this->Units.size() - 1]->tilePos;
		}
		this->Attacking = true;
	}
	Vec2i goalPos(pos);

	bool isNaval = false;
	for (size_t i = 0; i != this->Units.size(); ++i) {
		CUnit *const unit = this->Units[i];
		if (unit->Type->UnitType == UnitTypeNaval && unit->Type->CanAttack) {
			isNaval = true;
			break;
		}
	}
	bool isTransporter = false;
	for (size_t i = 0; i != this->Units.size(); ++i) {
		CUnit *const unit = this->Units[i];
		if (unit->Type->CanTransport() && unit->IsAgressive() == false) {
			isTransporter = true;
			break;
		}
	}
	if (Map.Info.IsPointOnMap(goalPos) == false) {
		/* Search in entire map */
		const CUnit *enemy = NULL;
		if (isTransporter) {
			AiForceEnemyFinder<AIATTACK_AGRESSIVE>(*this, &enemy);
		} else if (isNaval) {
			AiForceEnemyFinder<AIATTACK_ALLMAP>(*this, &enemy);
		} else {
			AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &enemy);
		}
		if (enemy) {
			goalPos = enemy->tilePos;
		}
	} else {
		isDefenceForce = true;
	}
	if (Map.Info.IsPointOnMap(goalPos) == false || isTransporter) {
		DebugPrint("%d: Need to plan an attack with transporter\n" _C_ AiPlayer->Player->Index);
		if (State == AiForceAttackingState_Waiting && !PlanAttack()) {
			DebugPrint("%d: Can't transport\n" _C_ AiPlayer->Player->Index);
			Attacking = false;
		}
		return;
	}
	if (this->State == AiForceAttackingState_Waiting && isDefenceForce == false) {
		Vec2i resultPos;
		NewRallyPoint(goalPos, &resultPos);
		this->GoalPos = resultPos;
		this->State = AiForceAttackingState_GoingToRallyPoint;
	} else {
		this->GoalPos = goalPos;
		this->State = AiForceAttackingState_Attacking;
	}
	//  Send all units in the force to enemy.

	CUnit *leader = NULL;
	for (size_t i = 0; i != this->Units.size(); ++i) {
		CUnit *const unit = this->Units[i];

		if (unit->IsAgressive()) {
			leader = unit;
			break;
		}
	}
	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->IsAgressive()) {
				CommandAttack(*unit, this->GoalPos,  NULL, FlushCommands);
			} else {
				if (leader) {
					CommandDefend(*unit, *leader, FlushCommands);
				} else {
					CommandMove(*unit, this->GoalPos, FlushCommands);
				}
			}
		}
	}
}
Beispiel #17
0
/**
** Force on attack ride. We attack until there is no unit or enemy left.
**
** @param force Force pointer.
*/
void AiForce::Update()
{
	if (Size() == 0) {
		Attacking = false;
		if (!Defending && State > AiForceAttackingState_Waiting) {
			DebugPrint("%d: Attack force #%lu was destroyed, giving up\n"
					   _C_ AiPlayer->Player->Index _C_(long unsigned int)(this  - & (AiPlayer->Force[0])));
			Reset(true);
		}
		return;
	}
	Attacking = false;
	for (unsigned int i = 0; i < Size(); ++i) {
		CUnit *aiunit = Units[i];

		if (aiunit->Type->CanAttack) {
			Attacking = true;
			break;
		}
	}
	if (Attacking == false) {
		if (!Defending && State > AiForceAttackingState_Waiting) {
			DebugPrint("%d: Attack force #%lu has lost all agresive units, giving up\n"
					   _C_ AiPlayer->Player->Index _C_(long unsigned int)(this  - & (AiPlayer->Force[0])));
			Reset(true);
		}
		return ;
	}
#if 0
	if (State == AiForceAttackingState_Waiting) {
		if (!this->PlanAttack()) {
			DebugPrint("Can't transport, look for walls\n");
			if (!AiFindWall(this)) {
				Attacking = false;
				return ;
			}
		}
		State = AiForceAttackingState_Boarding;
	}
#endif
	if (State == AiForceAttackingState_Boarding) {
		AiGroupAttackerForTransport(*this);
		return ;
	}

	Assert(Map.Info.IsPointOnMap(GoalPos));
	std::vector<CUnit *> idleUnits;
	const CUnit *leader = NULL;
	for (unsigned int i = 0; i != Size(); ++i) {
		CUnit &aiunit = *Units[i];

		if (aiunit.IsIdle()) {
			if (aiunit.IsAliveOnMap()) {
				idleUnits.push_back(&aiunit);
			}
		} else if (leader == NULL && aiunit.CurrentAction() == UnitActionAttack) {
			const COrder_Attack &order = *static_cast<COrder_Attack *>(aiunit.CurrentOrder());

			if (order.HasGoal() && order.IsValid()) {
				leader = &aiunit;
			}
		}
	}
	if (idleUnits.empty()) {
		return ;
	}
	if (leader == NULL) {
		const int thresholdDist = 5; // Hard coded value
		int maxDist = 0;

		for (size_t i = 0; i != idleUnits.size(); ++i) {
			maxDist = std::max(maxDist, idleUnits[i]->MapDistanceTo(this->GoalPos));
		}
		if (maxDist < thresholdDist) {
			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;
		}
	}
	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);
			}
		} else {
			CommandMove(aiunit, pos, FlushCommands);
		}
	}
}
Beispiel #18
0
/**
**  Order a unit
**
**  @param l  Lua state.
**
**  OrderUnit(player, unit-type, sloc, dloc, order)
*/
static int CclOrderUnit(lua_State *l)
{
	LuaCheckArgs(l, 5);

	lua_pushvalue(l, 1);
	const int plynr = TriggerGetPlayer(l);
	lua_pop(l, 1);
	lua_pushvalue(l, 2);
	const CUnitType *unittype = TriggerGetUnitType(l);
	lua_pop(l, 1);
	if (!lua_istable(l, 3)) {
		LuaError(l, "incorrect argument");
	}
	Vec2i pos1;
	pos1.x = LuaToNumber(l, 3, 1);
	pos1.y = LuaToNumber(l, 3, 2);

	Vec2i pos2;
	if (lua_rawlen(l, 3) == 4) {
		pos2.x = LuaToNumber(l, 3, 3);
		pos2.y = LuaToNumber(l, 3, 4);
	} else {
		pos2 = pos1;
	}
	if (!lua_istable(l, 4)) {
		LuaError(l, "incorrect argument");
	}
	Vec2i dpos1;
	Vec2i dpos2;
	dpos1.x = LuaToNumber(l, 4, 1);
	dpos1.y = LuaToNumber(l, 4, 2);
	if (lua_rawlen(l, 4) == 4) {
		dpos2.x = LuaToNumber(l, 4, 3);
		dpos2.y = LuaToNumber(l, 4, 4);
	} else {
		dpos2 = dpos1;
	}
	const char *order = LuaToString(l, 5);
	std::vector<CUnit *> table;
	Select(pos1, pos2, table);
	for (size_t i = 0; i != table.size(); ++i) {
		CUnit &unit = *table[i];

		if (unittype == ANY_UNIT
			|| (unittype == ALL_FOODUNITS && !unit.Type->Building)
			|| (unittype == ALL_BUILDINGS && unit.Type->Building)
			|| unittype == unit.Type) {
			if (plynr == -1 || plynr == unit.Player->Index) {
				if (!strcmp(order, "move")) {
					CommandMove(unit, (dpos1 + dpos2) / 2, 1);
				} else if (!strcmp(order, "attack")) {
					CUnit *attack = TargetOnMap(unit, dpos1, dpos2);

					CommandAttack(unit, (dpos1 + dpos2) / 2, attack, 1);
				} else if (!strcmp(order, "patrol")) {
					CommandPatrolUnit(unit, (dpos1 + dpos2) / 2, 1);
				} else {
					LuaError(l, "Unsupported order: %s" _C_ order);
				}
			}
		}
	}
	return 0;
}
/**
**  Auto attack nearby units if possible
*/
static void AutoAttack(CUnit *unit, bool stand_ground)
{
	CUnit *temp;
	CUnit *goal;

	if (unit->Wait) {
		unit->Wait--;
		return;
	}

	// Cowards don't attack unless ordered.
	if (unit->Type->CanAttack && !unit->Type->Coward) {
		// Normal units react in reaction range.
		if (CanMove(unit) && !unit->Removed && !stand_ground) {
			if ((goal = AttackUnitsInReactRange(unit))) {
				// Weak goal, can choose other unit, come back after attack
				CommandAttack(unit, goal->X, goal->Y, NULL, FlushCommands);
				Assert(unit->SavedOrder.Action == UnitActionStill);
				Assert(!unit->SavedOrder.Goal);
				unit->SavedOrder.Action = UnitActionAttack;
				unit->SavedOrder.Range = 0;
				unit->SavedOrder.X = unit->X;
				unit->SavedOrder.Y = unit->Y;
				unit->SavedOrder.Goal = NoUnitP;
			} else {
				unit->Wait = 15;
			}
		// Removed units can only attack in AttackRange, from bunker
		} else if ((goal = AttackUnitsInRange(unit))) {
			temp = unit->Orders[0]->Goal;
			if (temp && temp->Orders[0]->Action == UnitActionDie) {
				temp->RefsDecrease();
				unit->Orders[0]->Goal = temp = NoUnitP;
			}
			if (!unit->SubAction || temp != goal) {
				// New target.
				if (temp) {
					temp->RefsDecrease();
				}
				unit->Orders[0]->Goal = goal;
				goal->RefsIncrease();
				unit->State = 0;
				unit->SubAction = 1; // Mark attacking.
				UnitHeadingFromDeltaXY(unit,
					goal->X + (goal->Type->TileWidth - 1) / 2 - unit->X,
					goal->Y + (goal->Type->TileHeight - 1) / 2 - unit->Y);
			}
			return;
		}
	} else {
		unit->Wait = 15;
	}

	if (unit->SubAction) { // was attacking.
		if ((temp = unit->Orders[0]->Goal)) {
			temp->RefsDecrease();
			unit->Orders[0]->Goal = NoUnitP;
		}
		unit->SubAction = unit->State = 0; // No attacking, restart
	}
	Assert(!unit->Orders[0]->Goal);
}