/** ** Handle moving to the target. ** ** @param unit Unit, for that the spell cast is handled. */ bool COrder_SpellCast::SpellMoveToTarget(CUnit &unit) { // Unit can't move int err = 1; if (unit.CanMove()) { err = DoActionMove(unit); if (unit.Anim.Unbreakable) { return false; } } // when reached DoActionMove changes unit action // FIXME: use return codes from pathfinder CUnit *goal = this->GetGoal(); if (goal && unit.MapDistanceTo(*goal) <= this->Range) { // there is goal and it is in range UnitHeadingFromDeltaXY(unit, goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos); this->State++; // cast the spell return false; } else if (!goal && unit.MapDistanceTo(this->goalPos) <= this->Range) { // there is no goal and target spot is in range UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos); this->State++; // cast the spell return false; } else if (err == PF_UNREACHABLE || !unit.CanMove()) { // goal/spot unreachable and out of range -- give up return true; } return false; }
/* virtual */ void COrder_Move::Execute(CUnit &unit) { Assert(unit.CanMove()); if (unit.Wait) { if (!unit.Waiting) { unit.Waiting = 1; unit.WaitBackup = unit.Anim; } UnitShowAnimation(unit, unit.Type->Animations->Still); unit.Wait--; return; } if (unit.Waiting) { unit.Anim = unit.WaitBackup; unit.Waiting = 0; } // FIXME: (mr-russ) Make a reachable goal here with GoalReachable ... switch (DoActionMove(unit)) { // reached end-point? case PF_UNREACHABLE: // Some tries to reach the goal this->Range++; break; case PF_REACHED: this->Finished = true; break; default: break; } }
/** ** Let a unit patrol from current to new position ** ** FIXME: want to support patroling between units. ** ** @param unit pointer to unit. ** @param pos map position to patrol between. ** @param flush if true, flush command queue. */ void CommandPatrolUnit(CUnit &unit, const Vec2i &pos, int flush) { Assert(Map.Info.IsPointOnMap(pos)); if (IsUnitValidForNetwork(unit) == false) { return ; } //Wyrmgus start CMapField &mf = *Map.Field(unit.tilePos); if ((mf.Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) { std::vector<CUnit *> table; Select(unit.tilePos, unit.tilePos, table); for (size_t i = 0; i != table.size(); ++i) { if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) { CommandStopUnit(*table[i]); //always stop the raft if a new command is issued } } } //Wyrmgus end COrderPtr *order; if (!unit.CanMove()) { ClearNewAction(unit); order = &unit.NewOrder; } else { order = GetNextOrder(unit, flush); if (order == NULL) { return; } } *order = COrder::NewActionPatrol(unit.tilePos, pos); ClearSavedAction(unit); }
/* virtual */ void COrder_Still::Execute(CUnit &unit) { // If unit is not bunkered and removed, wait if (unit.Removed //Wyrmgus start // && (unit.Container == nullptr || unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value == false)) { && (unit.Container == nullptr || !unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value || !unit.Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value)) { // make both the unit and the transporter have the tag be necessary for the attack to be possible if (unit.Container != nullptr) { LeaveShelter(unit); // leave shelter if surrounded } //Wyrmgus end return ; } this->Finished = false; switch (this->State) { case SUB_STILL_STANDBY: //Wyrmgus start // UnitShowAnimation(unit, unit.Type->Animations->Still); if (unit.Variable[STUN_INDEX].Value == 0) { //only show the idle animation when still if the unit is not stunned UnitShowAnimation(unit, unit.GetAnimations()->Still); } if (SyncRand(100000) == 0) { PlayUnitSound(unit, VoiceIdle); } unit.StepCount = 0; //Wyrmgus end break; case SUB_STILL_ATTACK: // attacking unit in attack range. AnimateActionAttack(unit, *this); break; } if (unit.Anim.Unbreakable) { // animation can't be aborted here return; } //Wyrmgus start if (unit.Variable[STUN_INDEX].Value > 0) { //if unit is stunned, remain still return; } //Wyrmgus end this->State = SUB_STILL_STANDBY; this->Finished = (this->Action == UnitActionStill); if (this->Action == UnitActionStandGround || unit.Removed || unit.CanMove() == false) { if (unit.AutoCastSpell) { this->AutoCastStand(unit); } if (unit.IsAgressive()) { this->AutoAttackStand(unit); } } else { if (AutoCast(unit) || (unit.IsAgressive() && AutoAttack(unit)) || AutoRepair(unit) //Wyrmgus start // || MoveRandomly(unit)) { || MoveRandomly(unit) || PickUpItem(unit)) { //Wyrmgus end } } }
/** ** Unit attacks! ** ** if (SubAction & WEAK_TARGET) is true the goal is a weak goal. ** This means the unit AI (little AI) could choose a new better goal. ** ** @todo Lets do some tries to reach the target. ** If target place is not reachable, choose better goal to reduce ** the pathfinder load. ** ** @param unit Unit, for that the attack is handled. */ /* virtual */ void COrder_Attack::Execute(CUnit &unit) { Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos)); if (unit.Wait) { unit.Wait--; return; } switch (this->State) { case 0: { // First entry // did Order change ? if (CheckForTargetInRange(unit)) { return; } // Can we already attack ? if (this->HasGoal()) { CUnit &goal = *this->GetGoal(); const int dist = goal.MapDistanceTo(unit); if (unit.Type->MinAttackRange < dist && dist <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { const Vec2i dir = goal.tilePos + goal.Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); this->State = ATTACK_TARGET; AttackTarget(unit); return; } } this->State = MOVE_TO_TARGET; // FIXME: should use a reachable place to reduce pathfinder time. } // FALL THROUGH case MOVE_TO_TARGET: case MOVE_TO_TARGET + WEAK_TARGET: if (!unit.CanMove()) { this->Finished = true; return; } MoveToTarget(unit); break; case ATTACK_TARGET: case ATTACK_TARGET + WEAK_TARGET: AttackTarget(unit); break; case WEAK_TARGET: DebugPrint("FIXME: wrong entry.\n"); break; } }
/** ** Follow unit to new position ** ** @param unit pointer to unit. ** @param dest unit to be followed ** @param flush if true, flush command queue. */ void CommandFollow(CUnit &unit, CUnit &dest, int flush) { if (IsUnitValidForNetwork(unit) == false) { return ; } COrderPtr *order; if (!unit.CanMove()) { ClearNewAction(unit); order = &unit.NewOrder; } else { order = GetNextOrder(unit, flush); if (order == NULL) { return; } } *order = COrder::NewActionFollow(dest); ClearSavedAction(unit); }
/** ** Move unit to new position ** ** @param unit pointer to unit. ** @param pos map position to move to. ** @param flush if true, flush command queue. */ void CommandMove(CUnit &unit, const Vec2i &pos, int flush) { Assert(Map.Info.IsPointOnMap(pos)); if (IsUnitValidForNetwork(unit) == false) { return ; } COrderPtr *order; if (!unit.CanMove()) { ClearNewAction(unit); order = &unit.NewOrder; } else { order = GetNextOrder(unit, flush); if (order == NULL) { return; } } *order = COrder::NewActionMove(pos); ClearSavedAction(unit); }
/** ** Move unit to new position ** ** @param unit pointer to unit. ** @param pos map position to move to. ** @param flush if true, flush command queue. */ void CommandMove(CUnit &unit, const Vec2i &pos, int flush) { Assert(Map.Info.IsPointOnMap(pos)); if (IsUnitValidForNetwork(unit) == false) { return ; } //Wyrmgus start CMapField &mf = *Map.Field(unit.tilePos); CMapField &new_mf = *Map.Field(pos); //if the unit is a land unit over a raft, move the raft instead of the unit if ((mf.Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) { std::vector<CUnit *> table; Select(unit.tilePos, unit.tilePos, table); for (size_t i = 0; i != table.size(); ++i) { if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) { CommandStopUnit(*table[i]); //always stop the raft if a new command is issued if ((new_mf.Flags & MapFieldWaterAllowed) || (new_mf.Flags & MapFieldCoastAllowed) || (mf.Flags & MapFieldWaterAllowed)) { // if is standing on water, tell the raft to go to the nearest coast, even if the ultimate goal is on land CommandStopUnit(unit); CommandMove(*table[i], pos, flush); return; } } } } //Wyrmgus end COrderPtr *order; if (!unit.CanMove()) { ClearNewAction(unit); order = &unit.NewOrder; } else { order = GetNextOrder(unit, flush); if (order == NULL) { return; } } *order = COrder::NewActionMove(pos); ClearSavedAction(unit); }
/** ** Show the current order of a unit. ** ** @param unit Pointer to the unit. */ void ShowOrder(const CUnit &unit) { int x1; int y1; COrderPtr order; if (unit.Destroyed) { return; } if (unit.Player != ThisPlayer && !ThisPlayer->IsAllied(unit)) { return; } // Get current position x1 = CurrentViewport->Map2ViewportX( unit.tilePos.x) + unit.IX + unit.Type->TileWidth * TileSizeX / 2; y1 = CurrentViewport->Map2ViewportY( unit.tilePos.y) + unit.IY + unit.Type->TileHeight * TileSizeY / 2; // If the current order is cancelled show the next one if (unit.OrderCount > 1 && unit.OrderFlush) { order = unit.Orders[1]; } else { order = unit.Orders[0]; } ShowSingleOrder(unit, x1, y1, order); // Show the rest of the orders for (int i = 1 + (unit.OrderFlush ? 1 : 0); i < unit.OrderCount; ++i) { GetOrderPosition(unit, unit.Orders[i - 1], &x1, &y1); ShowSingleOrder(unit, x1, y1, unit.Orders[i]); } // Show order for new trained units if (!unit.CanMove()) { ShowSingleOrder(unit, x1, y1, (COrderPtr)(&unit.NewOrder)); } }
/* virtual */ void COrder_Move::Execute(CUnit &unit) { Assert(unit.CanMove()); if (unit.Wait) { unit.Wait--; return ; } // FIXME: (mr-russ) Make a reachable goal here with GoalReachable ... switch (DoActionMove(unit)) { // reached end-point? case PF_UNREACHABLE: // Some tries to reach the goal this->Range++; break; case PF_REACHED: this->Finished = true; break; default: break; } }
/* virtual */ void COrder_Still::Execute(CUnit &unit) { // If unit is not bunkered and removed, wait if (unit.Removed && (unit.Container == NULL || unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value == false)) { return ; } this->Finished = false; switch (this->State) { case SUB_STILL_STANDBY: UnitShowAnimation(unit, unit.Type->Animations->Still); break; case SUB_STILL_ATTACK: // attacking unit in attack range. AnimateActionAttack(unit, *this); break; } if (unit.Anim.Unbreakable) { // animation can't be aborted here return; } this->State = SUB_STILL_STANDBY; this->Finished = (this->Action == UnitActionStill); if (this->Action == UnitActionStandGround || unit.Removed || unit.CanMove() == false) { if (unit.AutoCastSpell) { this->AutoCastStand(unit); } if (unit.IsAgressive()) { this->AutoAttackStand(unit); } } else { if (AutoCast(unit) || (unit.IsAgressive() && AutoAttack(unit)) || AutoRepair(unit) || MoveRandomly(unit)) { } } }
/** ** Unit moves! Generic function called from other actions. ** ** @param unit Pointer to unit. ** ** @return >0 remaining path length, 0 wait for path, -1 ** reached goal, -2 can't reach the goal. */ int DoActionMove(CUnit &unit) { Vec2i posd; // movement in tile. int d; Assert(unit.CanMove()); if (!unit.Moving && (unit.Type->Animations->Move != unit.Anim.CurrAnim || !unit.Anim.Wait)) { Assert(!unit.Anim.Unbreakable); // FIXME: So units flying up and down are not affected. unit.IX = 0; unit.IY = 0; UnmarkUnitFieldFlags(unit); d = NextPathElement(unit, &posd.x, &posd.y); MarkUnitFieldFlags(unit); switch (d) { case PF_UNREACHABLE: // Can't reach, stop if (unit.Player->AiEnabled) { AiCanNotMove(unit); } unit.Moving = 0; return d; case PF_REACHED: // Reached goal, stop unit.Moving = 0; return d; case PF_WAIT: // No path, wait // Reset frame to still frame while we wait // FIXME: Unit doesn't animate. unit.Frame = unit.Type->StillFrame; UnitUpdateHeading(unit); unit.Wait = 10; unit.Moving = 0; return d; default: // On the way moving unit.Moving = 1; break; } if (unit.Type->UnitType == UnitTypeNaval) { // Boat (un)docking? const CMapField &mf_cur = *Map.Field(unit.Offset); const CMapField &mf_next = *Map.Field(unit.tilePos + posd); if (mf_cur.WaterOnMap() && mf_next.CoastOnMap()) { PlayUnitSound(unit, VoiceDocking); } else if (mf_cur.CoastOnMap() && mf_next.WaterOnMap()) { PlayUnitSound(unit, VoiceDocking); // undocking } } Vec2i pos = unit.tilePos + posd; unit.MoveToXY(pos); // Remove unit from the current selection if (unit.Selected && !Map.Field(pos)->playerInfo.IsTeamVisible(*ThisPlayer)) { if (NumSelected == 1) { // Remove building cursor CancelBuildingMode(); } if (!ReplayRevealMap) { UnSelectUnit(unit); SelectionChanged(); } } unit.IX = -posd.x * PixelTileSize.x; unit.IY = -posd.y * PixelTileSize.y; unit.Frame = unit.Type->StillFrame; UnitHeadingFromDeltaXY(unit, posd); } else { posd.x = Heading2X[unit.Direction / NextDirection]; posd.y = Heading2Y[unit.Direction / NextDirection]; d = unit.pathFinderData->output.Length + 1; } unit.pathFinderData->output.Cycles++;//reset have to be manualy controled by caller. int move = UnitShowAnimationScaled(unit, unit.Type->Animations->Move, Map.Field(unit.Offset)->Cost); unit.IX += posd.x * move; unit.IY += posd.y * move; // Finished move animation, set Moving to 0 so we recalculate the path // next frame // FIXME: this is broken for subtile movement if (!unit.Anim.Unbreakable && !unit.IX && !unit.IY) { unit.Moving = 0; } return d; }
/* virtual */ void COrder_Follow::Execute(CUnit &unit) { if (unit.Wait) { if (!unit.Waiting) { unit.Waiting = 1; unit.WaitBackup = unit.Anim; } //Wyrmgus start // UnitShowAnimation(unit, unit.Type->Animations->Still); UnitShowAnimation(unit, unit.GetAnimations()->Still); //Wyrmgus end unit.Wait--; return; } if (unit.Waiting) { unit.Anim = unit.WaitBackup; unit.Waiting = 0; } CUnit *goal = this->GetGoal(); // Reached target if (this->State == State_TargetReached) { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Goal gone\n"); this->Finished = true; return ; } // Don't follow after immobile units if (goal && goal->CanMove() == false) { this->Finished = true; return; } //Wyrmgus start // if (goal->tilePos == this->goalPos) { if (goal->tilePos == this->goalPos && goal->MapLayer == this->MapLayer) { //Wyrmgus end // Move to the next order if (unit.Orders.size() > 1) { this->Finished = true; return ; } unit.Wait = 10; if (this->Range > 1) { this->Range = 1; this->State = State_Init; } return ; } this->State = State_Init; } if (this->State == State_Init) { // first entry this->State = State_Initialized; } switch (DoActionMove(unit)) { // reached end-point? case PF_UNREACHABLE: //Wyrmgus start if ((Map.Field(unit.tilePos, unit.MapLayer)->Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) { std::vector<CUnit *> table; Select(unit.tilePos, unit.tilePos, table, unit.MapLayer); for (size_t i = 0; i != table.size(); ++i) { if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) { if (table[i]->CurrentAction() == UnitActionStill) { CommandStopUnit(*table[i]); CommandMove(*table[i], this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, FlushCommands, this->HasGoal() ? this->GetGoal()->MapLayer : this->MapLayer); } return; } } } //Wyrmgus end // Some tries to reach the goal this->Range++; break; case PF_REACHED: { if (!goal) { // goal has died this->Finished = true; return ; } // Handle Teleporter Units // FIXME: BAD HACK // goal shouldn't be busy and portal should be alive if (goal->Type->BoolFlag[TELEPORTER_INDEX].value && goal->Goal && goal->Goal->IsAlive() && unit.MapDistanceTo(*goal) <= 1) { if (!goal->IsIdle()) { // wait unit.Wait = 10; return; } // Check if we have enough mana if (goal->Goal->Type->TeleportCost > goal->Variable[MANA_INDEX].Value) { this->Finished = true; return; } else { goal->Variable[MANA_INDEX].Value -= goal->Goal->Type->TeleportCost; } // Everything is OK, now teleport the unit unit.Remove(NULL); if (goal->Type->TeleportEffectIn) { goal->Type->TeleportEffectIn->pushPreamble(); goal->Type->TeleportEffectIn->pushInteger(UnitNumber(unit)); goal->Type->TeleportEffectIn->pushInteger(UnitNumber(*goal)); goal->Type->TeleportEffectIn->pushInteger(unit.GetMapPixelPosCenter().x); goal->Type->TeleportEffectIn->pushInteger(unit.GetMapPixelPosCenter().y); goal->Type->TeleportEffectIn->run(); } unit.tilePos = goal->Goal->tilePos; //Wyrmgus start unit.MapLayer = goal->Goal->MapLayer; //Wyrmgus end DropOutOnSide(unit, unit.Direction, NULL); // FIXME: we must check if the units supports the new order. CUnit &dest = *goal->Goal; if (dest.Type->TeleportEffectOut) { dest.Type->TeleportEffectOut->pushPreamble(); dest.Type->TeleportEffectOut->pushInteger(UnitNumber(unit)); dest.Type->TeleportEffectOut->pushInteger(UnitNumber(dest)); dest.Type->TeleportEffectOut->pushInteger(unit.GetMapPixelPosCenter().x); dest.Type->TeleportEffectOut->pushInteger(unit.GetMapPixelPosCenter().y); dest.Type->TeleportEffectOut->run(); } if (dest.NewOrder == NULL || (dest.NewOrder->Action == UnitActionResource && !unit.Type->BoolFlag[HARVESTER_INDEX].value) //Wyrmgus start // || (dest.NewOrder->Action == UnitActionAttack && !unit.Type->CanAttack) || (dest.NewOrder->Action == UnitActionAttack && !unit.CanAttack(true)) //Wyrmgus end || (dest.NewOrder->Action == UnitActionBoard && unit.Type->UnitType != UnitTypeLand)) { this->Finished = true; return ; } else { if (dest.NewOrder->HasGoal()) { if (dest.NewOrder->GetGoal()->Destroyed) { delete dest.NewOrder; dest.NewOrder = NULL; this->Finished = true; return ; } unit.Orders.insert(unit.Orders.begin() + 1, dest.NewOrder->Clone()); this->Finished = true; return ; } } } this->goalPos = goal->tilePos; //Wyrmgus start this->MapLayer = goal->MapLayer; //Wyrmgus end this->State = State_TargetReached; } // FALL THROUGH default: break; } // Target destroyed? if (goal && !goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Goal gone\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); //Wyrmgus start this->MapLayer = goal->MapLayer; //Wyrmgus end this->ClearGoal(); goal = NULL; } if (unit.Anim.Unbreakable) { return ; } // If our leader is dead or stops or attacks: // Attack any enemy in reaction range. // If don't set the goal, the unit can than choose a // better goal if moving nearer to enemy. //Wyrmgus start // if (unit.Type->CanAttack if (unit.CanAttack() //Wyrmgus end && (!goal || goal->CurrentAction() == UnitActionAttack || goal->CurrentAction() == UnitActionStill)) { CUnit *target = AttackUnitsInReactRange(unit); if (target) { // Save current command to come back. COrder *savedOrder = NULL; if (unit.CanStoreOrder(unit.CurrentOrder())) { savedOrder = this->Clone(); } this->Finished = true; //Wyrmgus start // unit.Orders.insert(unit.Orders.begin() + 1, COrder::NewActionAttack(unit, target->tilePos)); unit.Orders.insert(unit.Orders.begin() + 1, COrder::NewActionAttack(unit, target->tilePos, target->MapLayer)); //Wyrmgus end if (savedOrder != NULL) { unit.SavedOrder = savedOrder; } } } }
/** ** Handle attacking the target. ** ** @param unit Unit, for that the attack is handled. */ void COrder_Attack::AttackTarget(CUnit &unit) { Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos)); AnimateActionAttack(unit, *this); if (unit.Anim.Unbreakable) { return; } if (!this->HasGoal() && (this->Action == UnitActionAttackGround || Map.WallOnMap(this->goalPos))) { return; } // Target is dead ? Change order ? if (CheckForDeadGoal(unit)) { return; } CUnit *goal = this->GetGoal(); bool dead = !goal || goal->IsAlive() == false; // No target choose one. if (!goal) { goal = AttackUnitsInReactRange(unit); // No new goal, continue way to destination. if (!goal) { // Return to old task ? if (unit.RestoreOrder()) { return; } this->State = MOVE_TO_TARGET; return; } // Save current command to come back. COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos); if (unit.CanStoreOrder(savedOrder) == false) { delete savedOrder; savedOrder = NULL; } else { unit.SavedOrder = savedOrder; } this->SetGoal(goal); this->goalPos = goal->tilePos; this->MinRange = unit.Type->MinAttackRange; this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max; this->State |= WEAK_TARGET; // Have a weak target, try a better target. // FIXME: if out of range also try another target quick } else { if ((this->State & WEAK_TARGET)) { CUnit *newTarget = AttackUnitsInReactRange(unit); if (newTarget && ThreatCalculate(unit, *newTarget) < ThreatCalculate(unit, *goal)) { if (unit.CanStoreOrder(this)) { unit.SavedOrder = this->Clone(); } goal = newTarget; this->SetGoal(newTarget); this->goalPos = newTarget->tilePos; this->MinRange = unit.Type->MinAttackRange; this->State = MOVE_TO_TARGET; } } } // Still near to target, if not goto target. const int dist = unit.MapDistanceTo(*goal); if (dist > unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { // towers don't chase after goal if (unit.CanMove()) { if (unit.CanStoreOrder(this)) { if (dead) { unit.SavedOrder = COrder::NewActionAttack(unit, this->goalPos); } else { unit.SavedOrder = this->Clone(); } } } unit.Frame = 0; this->State &= WEAK_TARGET; this->State |= MOVE_TO_TARGET; } if (dist < unit.Type->MinAttackRange) { this->State = MOVE_TO_TARGET; } // Turn always to target if (goal) { const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); } }
/** ** Controls moving a unit to its target when attacking ** ** @param unit Unit that is attacking and moving */ void COrder_Attack::MoveToTarget(CUnit &unit) { Assert(!unit.Type->Vanishes && !unit.Destroyed && !unit.Removed); Assert(unit.CurrentOrder() == this); Assert(unit.CanMove()); Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos)); int err = DoActionMove(unit); if (unit.Anim.Unbreakable) { return; } // Look if we have reached the target. if (err == 0 && !this->HasGoal()) { // Check if we're in range when attacking a location and we are waiting if (unit.MapDistanceTo(this->goalPos) <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { err = PF_REACHED; } } if (err >= 0) { if (CheckForTargetInRange(unit)) { return; } return; } if (err == PF_REACHED) { CUnit *goal = this->GetGoal(); // Have reached target? FIXME: could use the new return code? if (goal && unit.MapDistanceTo(*goal) <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { // Reached another unit, now attacking it const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); this->State++; return; } // Attacking wall or ground. if (((goal && goal->Type && goal->Type->Wall) || (!goal && (Map.WallOnMap(this->goalPos) || this->Action == UnitActionAttackGround))) && unit.MapDistanceTo(this->goalPos) <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { // Reached wall or ground, now attacking it UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos); this->State &= WEAK_TARGET; this->State |= ATTACK_TARGET; return; } } // Unreachable. if (err == PF_UNREACHABLE) { if (!this->HasGoal()) { // When attack-moving we have to allow a bigger range this->Range++; unit.Wait = 5; return; } else { this->ClearGoal(); } } // Return to old task? if (!unit.RestoreOrder()) { this->Finished = true; } }
/* virtual */ void COrder_Unload::Execute(CUnit &unit) { const int maxSearchRange = 20; if (!unit.CanMove()) { this->State = 2; } if (unit.Wait) { if (!unit.Waiting) { unit.Waiting = 1; unit.WaitBackup = unit.Anim; } UnitShowAnimation(unit, unit.Type->Animations->Still); unit.Wait--; return; } if (unit.Waiting) { unit.Anim = unit.WaitBackup; unit.Waiting = 0; } if (this->State == 1 && this->Range >= 5) { // failed to reach the goal this->State = 2; } switch (this->State) { case 0: // Choose destination if (!this->HasGoal()) { Vec2i pos; if (!ClosestFreeDropZone(unit, this->goalPos, maxSearchRange, &pos)) { this->Finished = true; return ; } this->goalPos = pos; } this->State = 1; // follow on next case case 1: // Move unit to destination // The Goal is the unit that we have to unload. if (!this->HasGoal()) { const int moveResult = MoveToDropZone(unit); // We have to unload everything if (moveResult) { if (moveResult == PF_REACHED) { if (++this->State == 1) { this->Finished = true; return ; } } else if (moveResult == PF_UNREACHABLE) { unit.Wait = 30; this->Range++; break; } else { this->State = 2; } } return ; } case 2: { // Leave the transporter // FIXME: show still animations ? if (LeaveTransporter(unit)) { this->Finished = true; return ; } return ; } default: return ; } }