/** ** 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; }
/** ** Check if the spell can be auto cast and cast it. ** ** @param caster Unit who can cast the spell. ** @param spell Spell-type pointer. ** ** @return 1 if spell is casted, 0 if not. */ int AutoCastSpell(CUnit &caster, const SpellType &spell) { // Check for mana and cooldown time, trivial optimization. if (!SpellIsAvailable(*caster.Player, spell.Slot) || caster.Variable[MANA_INDEX].Value < spell.ManaCost || caster.SpellCoolDownTimers[spell.Slot]) { return 0; } Target *target = SelectTargetUnitsOfAutoCast(caster, spell); if (target == NULL) { return 0; } else { // Save previous order COrder *savedOrder = NULL; if (caster.CurrentAction() != UnitActionStill && caster.CanStoreOrder(caster.CurrentOrder())) { savedOrder = caster.CurrentOrder()->Clone(); } // Must move before ? CommandSpellCast(caster, target->targetPos, target->Unit, spell, FlushCommands); delete target; if (savedOrder != NULL) { caster.SavedOrder = savedOrder; } } return 1; }
/** ** Check for button enabled, if the unit isn't working. ** Working is training, upgrading, researching. ** ** @param unit Pointer to unit for button. ** @param button Pointer to button to check/enable. ** ** @return True if enabled. */ bool ButtonCheckNoWork(const CUnit &unit, const ButtonAction &) { int action = unit.CurrentAction(); return action != UnitActionTrain && action != UnitActionUpgradeTo && action != UnitActionResearch; }
/** ** Building starts training an unit. ** ** @param unit pointer to unit. ** @param type unit type to train. ** @param flush if true, flush command queue. */ void CommandTrainUnit(CUnit &unit, CUnitType &type, int) { if (IsUnitValidForNetwork(unit) == false) { return ; } // Check if enough resources remains? (NETWORK!) // FIXME: wrong if append to message queue!!! if (unit.Player->CheckLimits(type) < 0 || unit.Player->CheckUnitType(type)) { return; } // Not already training? if (!EnableTrainingQueue && unit.CurrentAction() == UnitActionTrain) { DebugPrint("Unit queue disabled!\n"); return; } const int noFlushCommands = 0; COrderPtr *order = GetNextOrder(unit, noFlushCommands); if (order == NULL) { return; } *order = COrder::NewActionTrain(unit, type); ClearSavedAction(unit); }
/** ** @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; }
/** ** Check for button enabled, if all requirements for an upgrade to unit ** are met. ** ** @param unit Pointer to unit for button. ** @param button Pointer to button to check/enable. ** ** @return True if enabled. */ bool ButtonCheckUpgradeTo(const CUnit &unit, const ButtonAction &button) { if (unit.CurrentAction() != UnitActionStill) { return false; } return CheckDependByIdent(*unit.Player, button.ValueStr); }
/** ** Missile hits the goal. ** ** @param missile Missile hitting the goal. ** @param goal Goal of the missile. ** @param splash Splash damage divisor. */ static void MissileHitsGoal(const Missile &missile, CUnit &goal, int splash) { if (!missile.Type->CanHitOwner && missile.SourceUnit == &goal) { return; } if (goal.CurrentAction() != UnitActionDie) { int damage; if (missile.Type->Damage) { // custom formula Assert(missile.SourceUnit != NULL); damage = CalculateDamage(*missile.SourceUnit, goal, missile.Type->Damage) / splash; } else if (missile.Damage) { // direct damage, spells mostly damage = missile.Damage / splash; } else { Assert(missile.SourceUnit != NULL); damage = CalculateDamage(*missile.SourceUnit, goal, Damage) / splash; } if (missile.Type->Pierce) { // Handle pierce factor for (size_t i = 0; i < (missile.PiercedUnits.size() - 1); ++i) { damage *= (double)missile.Type->ReduceFactor / 100; } } HitUnit(missile.SourceUnit, goal, damage, &missile); } }
/** ** Repair a unit. ** ** @param unit unit repairing ** @param goal unit being repaired ** ** @return true when action is finished/canceled. */ bool COrder_Repair::RepairUnit(const CUnit &unit, CUnit &goal) { CPlayer &player = *unit.Player; if (goal.CurrentAction() == UnitActionBuilt) { COrder_Built &order = *static_cast<COrder_Built *>(goal.CurrentOrder()); order.ProgressHp(goal, 100 * this->RepairCycle); this->RepairCycle = 0; if (ResourcesMultiBuildersMultiplier && SubRepairCosts(unit, player, goal)) { return true; } return false; } if (goal.Variable[HP_INDEX].Value >= goal.Variable[HP_INDEX].Max) { return true; } Assert(goal.Stats->Variables[HP_INDEX].Max); if (SubRepairCosts(unit, player, goal)) { return true; } goal.Variable[HP_INDEX].Value += goal.Type->RepairHP; if (goal.Variable[HP_INDEX].Value >= goal.Variable[HP_INDEX].Max) { goal.Variable[HP_INDEX].Value = goal.Variable[HP_INDEX].Max; return true; } return false; }
/** ** Cancel the building construction, or kill a unit. ** ** @param unit pointer to unit. */ void CommandDismiss(CUnit &unit) { // Check if building is still under construction? (NETWORK!) if (unit.CurrentAction() == UnitActionBuilt) { unit.CurrentOrder()->Cancel(unit); } else { DebugPrint("Suicide unit ... \n"); LetUnitDie(unit, true); } ClearSavedAction(unit); }
/** ** Cancel building upgrading to. ** ** @param unit pointer to unit. */ void CommandCancelUpgradeTo(CUnit &unit) { // Check if unit is still upgrading? (NETWORK!) if (unit.CurrentAction() == UnitActionUpgradeTo) { unit.CurrentOrder()->Cancel(unit); RemoveOrder(unit, 0); if (Selected) { SelectedUnitChanged(); } } ClearSavedAction(unit); }
/** ** Cancel Building researching. ** ** @param unit Pointer to unit. */ void CommandCancelResearch(CUnit &unit) { // Check if unit is still researching? (NETWORK!) if (unit.CurrentAction() == UnitActionResearch) { unit.CurrentOrder()->Cancel(unit); RemoveOrder(unit, 0); if (!Selected.empty()) { SelectedUnitChanged(); } } ClearSavedAction(unit); }
/** ** Draw additional informations of a unit. ** ** @param unit Unit pointer of drawn unit. ** @param type Unit-type pointer. ** @param x X screen pixel position of unit. ** @param y Y screen pixel position of unit. ** ** @todo FIXME: The different styles should become a function call. */ static void DrawInformations(const CUnit &unit, const CUnitType *type, int x, int y) { const CUnitStats *stats; int r; #if 0 && DEBUG // This is for showing vis counts and refs. char buf[10]; sprintf(buf, "%d%c%c%d", unit.VisCount[ThisPlayer->Index], unit.Seen.ByPlayer & (1 << ThisPlayer->Index) ? 'Y' : 'N', unit.Seen.Destroyed & (1 << ThisPlayer->Index) ? 'Y' : 'N', unit.Refs); VideoDrawTextClip(x + 10, y + 10, 1, buf); #endif stats = unit.Stats; // // For debug draw sight, react and attack range! // if (NumSelected == 1 && unit.Selected) { if (Preference.ShowSightRange) { // Radius -1 so you can see all ranges Video.DrawCircleClip(ColorGreen, x + type->TileWidth * TileSizeX / 2, y + type->TileHeight * TileSizeY / 2, ((stats->Variables[SIGHTRANGE_INDEX].Max + (type->TileWidth - 1)) * TileSizeX) - 1); } if (type->CanAttack) { if (Preference.ShowReactionRange) { r = (unit.Player->Type == PlayerPerson) ? type->ReactRangePerson : type->ReactRangeComputer; if (r) { Video.DrawCircleClip(ColorBlue, x + type->TileWidth * TileSizeX / 2, y + type->TileHeight * TileSizeY / 2, (r + (type->TileWidth - 1)) * TileSizeX); } } if (Preference.ShowAttackRange && stats->Variables[ATTACKRANGE_INDEX].Max) { // Radius + 1 so you can see all ranges Video.DrawCircleClip(ColorRed, x + type->TileWidth * TileSizeX / 2, y + type->TileHeight * TileSizeY / 2, (stats->Variables[ATTACKRANGE_INDEX].Max + (type->TileWidth - 1)) * TileSizeX + 1); } } } // FIXME: johns: ugly check here, should be removed! if (unit.CurrentAction() != UnitActionDie && unit.IsVisible(ThisPlayer)) { DrawDecoration(unit, type, x, y); } }
/** ** Draw additional informations of a unit. ** ** @param unit Unit pointer of drawn unit. ** @param type Unit-type pointer. ** @param screenPos screen pixel (top left) position of unit. ** ** @todo FIXME: The different styles should become a function call. */ static void DrawInformations(const CUnit &unit, const CUnitType &type, const PixelPos &screenPos) { #if 0 && DEBUG // This is for showing vis counts and refs. char buf[10]; sprintf(buf, "%d%c%c%d", unit.VisCount[ThisPlayer->Index], unit.Seen.ByPlayer & (1 << ThisPlayer->Index) ? 'Y' : 'N', unit.Seen.Destroyed & (1 << ThisPlayer->Index) ? 'Y' : 'N', unit.Refs); CLabel(GetSmallFont()).Draw(screenPos.x + 10, screenPos.y + 10, buf); #endif const CUnitStats &stats = *unit.Stats; // For debug draw sight, react and attack range! if (IsOnlySelected(unit)) { const PixelPos center(screenPos + type.GetPixelSize() / 2); if (Preference.ShowSightRange) { const int value = stats.Variables[SIGHTRANGE_INDEX].Max; const int radius = value * PixelTileSize.x + (type.TileWidth - 1) * PixelTileSize.x / 2; if (value) { // Radius -1 so you can see all ranges Video.DrawCircleClip(ColorGreen, center.x, center.y, radius - 1); } } if (type.CanAttack) { if (Preference.ShowReactionRange) { const int value = (unit.Player->Type == PlayerPerson) ? type.ReactRangePerson : type.ReactRangeComputer; const int radius = value * PixelTileSize.x + (type.TileWidth - 1) * PixelTileSize.x / 2; if (value) { Video.DrawCircleClip(ColorBlue, center.x, center.y, radius); } } if (Preference.ShowAttackRange) { const int value = stats.Variables[ATTACKRANGE_INDEX].Max; const int radius = value * PixelTileSize.x + (type.TileWidth - 1) * PixelTileSize.x / 2; if (value) { // Radius +1 so you can see all ranges Video.DrawCircleClip(ColorGreen, center.x, center.y, radius - 1); } } } } // FIXME: johns: ugly check here, should be removed! if (unit.CurrentAction() != UnitActionDie && (unit.IsVisible(*ThisPlayer) || ReplayRevealMap)) { DrawDecoration(unit, type, screenPos); } }
static bool IsReadyToRepair(const CUnit &unit) { if (unit.IsIdle()) { return true; } else if (unit.Orders.size() == 1 && unit.CurrentAction() == UnitActionResource) { COrder_Resource &order = *static_cast<COrder_Resource *>(unit.CurrentOrder()); if (order.IsGatheringStarted() == false) { return true; } } return false; }
/** ** Show selection marker around a unit. ** ** @param unit Pointer to unit. */ void DrawUnitSelection(const CViewport *vp, const CUnit &unit) { Uint32 color; // FIXME: make these colors customizable with scripts. if (Editor.Running && UnitUnderCursor == &unit && Editor.State == EditorSelecting) { color = ColorWhite; } else if (unit.Selected || unit.TeamSelected || (unit.Blink & 1)) { if (unit.Player->Index == PlayerNumNeutral) { color = ColorYellow; } else if ((unit.Selected || (unit.Blink & 1)) && (unit.Player == ThisPlayer || ThisPlayer->IsTeamed(unit))) { color = ColorGreen; } else if (ThisPlayer->IsEnemy(unit)) { color = ColorRed; } else { int i; for (i = 0; i < PlayerMax; ++i) { if (unit.TeamSelected & (1 << i)) { break; } } if (i == PlayerMax) { color = unit.Player->Color; } else { color = Players[i].Color; } } } else if (CursorBuilding && unit.Type->Building && unit.CurrentAction() != UnitActionDie && (unit.Player == ThisPlayer || ThisPlayer->IsTeamed(unit))) { // If building mark all own buildings color = ColorGray; } else { return; } const CUnitType *type = unit.Type; int x = vp->Map2ViewportX(unit.tilePos.x) + unit.IX + type->TileWidth * TileSizeX / 2 - type->BoxWidth / 2 - (type->Width - type->Sprite->Width) / 2; int y = vp->Map2ViewportY(unit.tilePos.y) + unit.IY + type->TileHeight * TileSizeY / 2 - type->BoxHeight / 2 - (type->Height - type->Sprite->Height) / 2; DrawSelection(color, x, y, x + type->BoxWidth, y + type->BoxHeight); }
/** ** Missile hits the goal. ** ** @param missile Missile hitting the goal. ** @param goal Goal of the missile. ** @param splash Splash damage divisor. */ static void MissileHitsGoal(const Missile &missile, CUnit &goal, int splash) { if (!missile.Type->CanHitOwner && missile.SourceUnit == &goal) { return; } //Wyrmgus start if (CalculateHit(*missile.SourceUnit, *goal.Stats, &goal) == false) { if (splash == 1 && missile.Type->SplashFactor == 0) { return; } else if (splash == 1 && missile.Type->SplashFactor > 0) { splash = missile.Type->SplashFactor; // if missile has splash factor but missed, apply splash damage } } //Wyrmgus end if (goal.CurrentAction() != UnitActionDie) { int damage; if (missile.Type->Damage) { // custom formula Assert(missile.SourceUnit != NULL); damage = CalculateDamage(*missile.SourceUnit, goal, missile.Type->Damage) / splash; } else if (missile.Damage) { // direct damage, spells mostly damage = missile.Damage / splash; } else { Assert(missile.SourceUnit != NULL); damage = CalculateDamage(*missile.SourceUnit, goal, Damage) / splash; } if (missile.Type->Pierce) { // Handle pierce factor for (size_t i = 0; i < (missile.PiercedUnits.size() - 1); ++i) { damage *= (double)missile.Type->ReduceFactor / 100; } } HitUnit(missile.SourceUnit, goal, damage, &missile); //Wyrmgus start //apply Thorns damage if attacker is at melee range if (&goal && goal.Variable[THORNSDAMAGE_INDEX].Value && missile.SourceUnit->MapDistanceTo(goal) <= 1) { int thorns_damage = std::max<int>(goal.Variable[THORNSDAMAGE_INDEX].Value - missile.SourceUnit->Variable[ARMOR_INDEX].Value, 1); if (GameSettings.NoRandomness) { thorns_damage -= ((thorns_damage + 2) / 2) / 2; //if no randomness setting is used, then the damage will always return what would have been the average damage with randomness } else { thorns_damage -= SyncRand() % ((thorns_damage + 2) / 2); } HitUnit(&goal, *missile.SourceUnit, thorns_damage); } //Wyrmgus end } }
/** ** Move to dropzone. ** ** @param unit Pointer to unit. ** ** @return -1 if unreachable, True if reached, False otherwise. */ static int MoveToDropZone(CUnit &unit) { switch (DoActionMove(unit)) { // reached end-point? case PF_UNREACHABLE: return PF_UNREACHABLE; case PF_REACHED: break; default: return 0; } Assert(unit.CurrentAction() == UnitActionUnload); return 1; }
int GetNumWaitingWorkers(const CUnit &mine) { int ret = 0; CUnit *worker = mine.Resource.Workers; for (int i = 0; NULL != worker; worker = worker->NextWorker, ++i) { Assert(worker->CurrentAction() == UnitActionResource); COrder_Resource &order = *static_cast<COrder_Resource *>(worker->CurrentOrder()); if (order.IsGatheringWaiting()) { ret++; } Assert(i <= mine.Resource.Assigned); } return ret; }
/** ** Assign worker to gather a certain resource. ** ** @param unit pointer to the unit. ** @param resource resource identification. ** ** @return 1 if the worker was assigned, 0 otherwise. */ static int AiAssignHarvester(CUnit &unit, int resource) { // It can't. //Wyrmgus start // if (unit.Removed) { if (unit.Removed || unit.CurrentAction() == UnitActionBuild) { //prevent units building from outside to being assigned to gather a resource, and then leaving the construction unbuilt forever and ever //Wyrmgus end return 0; } if (std::find(AiPlayer->Scouts.begin(), AiPlayer->Scouts.end(), &unit) != AiPlayer->Scouts.end()) { //if a scouting unit was assigned to harvest, remove it from the scouts vector AiPlayer->Scouts.erase(std::remove(AiPlayer->Scouts.begin(), AiPlayer->Scouts.end(), &unit), AiPlayer->Scouts.end()); } const ResourceInfo &resinfo = *unit.Type->ResInfo[resource]; Assert(&resinfo); //Wyrmgus start /* if (resinfo.TerrainHarvester) { return AiAssignHarvesterFromTerrain(unit, resource); } else { return AiAssignHarvesterFromUnit(unit, resource); } */ int ret = 0; int resource_range = 0; for (int i = 0; i < 3; ++i) { //search for resources first in a 16 tile radius, then in a 32 tile radius, and then in the whole map resource_range += 16; if (i == 2) { resource_range = 1000; } ret = AiAssignHarvesterFromUnit(unit, resource, resource_range); if (ret == 0) { ret = AiAssignHarvesterFromTerrain(unit, resource, resource_range); } if (ret != 0) { return ret; } } return ret; //Wyrmgus end }
//Wyrmgus start //void CommandTrainUnit(CUnit &unit, CUnitType &type, int) void CommandTrainUnit(CUnit &unit, CUnitType &type, int player, int) //Wyrmgus end { if (IsUnitValidForNetwork(unit) == false) { return ; } // Check if enough resources remains? (NETWORK!) // FIXME: wrong if append to message queue!!! //Wyrmgus start // if (unit.Player->CheckLimits(type) < 0 // || unit.Player->CheckUnitType(type)) { if (Players[player].CheckLimits(type) < 0 || Players[player].CheckUnitType(type)) { //Wyrmgus end return; } //Wyrmgus start if (unit.Type->Stats[unit.Player->Index].UnitStock[type.Slot] != 0 && unit.UnitStock[type.Slot] == 0) { if (player == ThisPlayer->Index) { ThisPlayer->Notify(NotifyYellow, unit.tilePos, "%s", _("The stock is empty, wait until it is replenished.")); } return; } //Wyrmgus end // Not already training? if (!EnableTrainingQueue && unit.CurrentAction() == UnitActionTrain) { DebugPrint("Unit queue disabled!\n"); return; } const int noFlushCommands = 0; COrderPtr *order = GetNextOrder(unit, noFlushCommands); if (order == NULL) { return; } //Wyrmgus start // *order = COrder::NewActionTrain(unit, type); *order = COrder::NewActionTrain(unit, type, player); //Wyrmgus end ClearSavedAction(unit); }
/** ** Show selection marker around a unit. ** ** @param unit Pointer to unit. */ void DrawUnitSelection(const CViewport &vp, const CUnit &unit) { IntColor color; // FIXME: make these colors customizable with scripts. if (Editor.Running && UnitUnderCursor == &unit && Editor.State == EditorSelecting) { color = ColorWhite; } else if (unit.Selected || unit.TeamSelected || (unit.Blink & 1)) { if (unit.Player->Index == PlayerNumNeutral) { color = ColorYellow; } else if ((unit.Selected || (unit.Blink & 1)) && (unit.Player == ThisPlayer || ThisPlayer->IsTeamed(unit))) { color = ColorGreen; } else if (ThisPlayer->IsEnemy(unit)) { color = ColorRed; } else { color = PlayerColors[GameSettings.Presets[unit.Player->Index].PlayerColor][0]; for (int i = 0; i < PlayerMax; ++i) { if (unit.TeamSelected & (1 << i)) { color = PlayerColors[GameSettings.Presets[i].PlayerColor][0]; } } } } else if (CursorBuilding && unit.Type->Building && unit.CurrentAction() != UnitActionDie && (unit.Player == ThisPlayer || ThisPlayer->IsTeamed(unit))) { // If building mark all own buildings color = ColorGray; } else { return; } const CUnitType &type = *unit.Type; const PixelPos screenPos = vp.MapToScreenPixelPos(unit.GetMapPixelPosCenter()); const int x = screenPos.x - type.BoxWidth / 2 - (type.Width - (type.Sprite ? type.Sprite->Width : 0)) / 2; const int y = screenPos.y - type.BoxHeight / 2 - (type.Height - (type.Sprite ? type.Sprite->Height : 0)) / 2; DrawSelection(color, x + type.BoxOffsetX, y + type.BoxOffsetY, x + type.BoxWidth + type.BoxOffsetX, y + type.BoxHeight + type.BoxOffsetY); }
bool SubRepairCosts(const CUnit &unit, CPlayer &player, CUnit &goal) { int RepairCosts[MaxCosts]; // Check if enough resources are available for (int i = 1; i < MaxCosts; ++i) { RepairCosts[i] = goal.Type->RepairCosts[i] * (goal.CurrentAction() == UnitActionBuilt ? ResourcesMultiBuildersMultiplier : 1); } for (int i = 1; i < MaxCosts; ++i) { if (!player.CheckResource(i, RepairCosts[i])) { player.Notify(NotifyYellow, unit.tilePos, _("We need more %s for repair!"), DefaultResourceNames[i].c_str()); return true; } } // Subtract the resources player.SubCosts(RepairCosts); return false; }
/** ** Cancel the training of an unit. ** ** @param unit pointer to unit. ** @param slot slot number to cancel. ** @param type Unit-type to cancel. */ void CommandCancelTraining(CUnit &unit, int slot, const CUnitType *type) { DebugPrint("Cancel %d type: %s\n" _C_ slot _C_ type ? type->Ident.c_str() : "-any-"); ClearSavedAction(unit); // Check if unit is still training 'slot'? (NETWORK!) if (slot == -1) { // Cancel All training while (unit.CurrentAction() == UnitActionTrain) { unit.CurrentOrder()->Cancel(unit); RemoveOrder(unit, 0); } if (unit.Player == ThisPlayer && unit.Selected) { SelectedUnitChanged(); } } else if (unit.Orders.size() <= static_cast<size_t>(slot)) { // Order has moved return; } else if (unit.Orders[slot]->Action != UnitActionTrain) { // Order has moved, we are not training return; } else if (unit.Orders[slot]->Action == UnitActionTrain) { COrder_Train &order = *static_cast<COrder_Train *>(unit.Orders[slot]); // Still training this order, same unit? if (type && &order.GetUnitType() != type) { // Different unit being trained return; } order.Cancel(unit); RemoveOrder(unit, slot); // Update interface. if (unit.Player == ThisPlayer && unit.Selected) { SelectedUnitChanged(); } } }
static bool DrawUnitInfo_single_selection(const CUnit &unit) { switch (unit.CurrentAction()) { case UnitActionTrain: { // Building training units. DrawUnitInfo_Training(unit); return true; } case UnitActionUpgradeTo: { // Building upgrading to better type. if (UI.UpgradingButton) { const COrder_UpgradeTo &order = *static_cast<COrder_UpgradeTo *>(unit.CurrentOrder()); CIcon &icon = *order.GetUnitType().Icon.Icon; unsigned int flag = (ButtonAreaUnderCursor == ButtonAreaUpgrading && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0; const PixelPos pos(UI.UpgradingButton->X, UI.UpgradingButton->Y); icon.DrawUnitIcon(*UI.UpgradingButton->Style, flag, pos, "", unit.RescuedFrom ? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor : GameSettings.Presets[unit.Player->Index].PlayerColor); } return true; } case UnitActionResearch: { // Building research new technology. if (UI.ResearchingButton) { COrder_Research &order = *static_cast<COrder_Research *>(unit.CurrentOrder()); CIcon &icon = *order.GetUpgrade().Icon; int flag = (ButtonAreaUnderCursor == ButtonAreaResearching && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0; PixelPos pos(UI.ResearchingButton->X, UI.ResearchingButton->Y); icon.DrawUnitIcon(*UI.ResearchingButton->Style, flag, pos, "", unit.RescuedFrom ? GameSettings.Presets[unit.RescuedFrom->Index].PlayerColor : GameSettings.Presets[unit.Player->Index].PlayerColor); } return true; } default: return false; } }
/* virtual */ bool COrder_Attack::OnAiHitUnit(CUnit &unit, CUnit *attacker, int /*damage*/) { CUnit *goal = this->GetGoal(); if (goal) { if (goal->IsAlive() == false) { this->ClearGoal(); this->goalPos = goal->tilePos; return false; } if (goal == attacker) { return true; } if (goal->CurrentAction() == UnitActionAttack) { const COrder_Attack &order = *static_cast<COrder_Attack *>(goal->CurrentOrder()); if (order.GetGoal() == &unit) { //we already fight with one of attackers; return true; } } } return false; }
/* 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; } } } }
/** ** Repair a unit. ** ** @param unit unit repairing ** @param goal unit being repaired */ static void RepairUnit(CUnit &unit, CUnit &goal) { CPlayer *player; int animlength; int hp; char buf[100]; player = unit.Player; if (goal.CurrentAction() != UnitActionBuilt) { // // Calculate the repair costs. // Assert(goal.Stats->Variables[HP_INDEX].Max); // // Check if enough resources are available // for (int i = 1; i < MaxCosts; ++i) { if (player->Resources[i] < goal.Type->RepairCosts[i]) { snprintf(buf, 100, _("We need more %s for repair!"), DefaultResourceNames[i].c_str()); player->Notify(NotifyYellow, unit.tilePos.x, unit.tilePos.y, buf); if (player->AiEnabled) { // FIXME: call back to AI? unit.CurrentOrder()->ClearGoal(); if (!unit.RestoreOrder()) { unit.ClearAction(); unit.State = 0; } } // FIXME: We shouldn't animate if no resources are available. return; } } // // Subtract the resources // player->SubCosts(goal.Type->RepairCosts); goal.Variable[HP_INDEX].Value += goal.Type->RepairHP; if (goal.Variable[HP_INDEX].Value > goal.Variable[HP_INDEX].Max) { goal.Variable[HP_INDEX].Value = goal.Variable[HP_INDEX].Max; } } else { int costs = goal.Stats->Costs[TimeCost] * 600; // hp is the current damage taken by the unit. hp = (goal.Data.Built.Progress * goal.Variable[HP_INDEX].Max) / costs - goal.Variable[HP_INDEX].Value; // // Calculate the length of the attack (repair) anim. // animlength = unit.Data.Repair.Cycles; unit.Data.Repair.Cycles = 0; // FIXME: implement this below: //unit.Data.Built.Worker->Type->BuilderSpeedFactor; goal.Data.Built.Progress += 100 * animlength * SpeedBuild; // Keep the same level of damage while increasing HP. goal.Variable[HP_INDEX].Value = (goal.Data.Built.Progress * goal.Stats->Variables[HP_INDEX].Max) / costs - hp; if (goal.Variable[HP_INDEX].Value > goal.Variable[HP_INDEX].Max) { goal.Variable[HP_INDEX].Value = goal.Variable[HP_INDEX].Max; } } }
/** ** Unit repairs ** ** @param unit Unit, for that the attack is handled. */ void HandleActionRepair(CUnit &unit) { CUnit *goal; int err; switch (unit.SubAction) { case 0: NewResetPath(unit); unit.SubAction = 1; // FALL THROUGH // // Move near to target. // case 1: // FIXME: RESET FIRST!! Why? We move first and than check if // something is in sight. err = DoActionMove(unit); if (!unit.Anim.Unbreakable) { // // No goal: if meeting damaged building repair it. // goal = unit.CurrentOrder()->GetGoal(); // // Target is dead, choose new one. // // Check if goal is correct unit. if (goal) { if (!goal->IsVisibleAsGoal(unit.Player)) { DebugPrint("repair target gone.\n"); unit.CurrentOrder()->goalPos = goal->tilePos; // FIXME: should I clear this here? unit.CurrentOrder()->ClearGoal(); goal = NULL; NewResetPath(unit); } } else if (unit.Player->AiEnabled) { // Ai players workers should stop if target is killed err = -1; } // // Have reached target? FIXME: could use return value // if (goal && unit.MapDistanceTo(*goal) <= unit.Type->RepairRange && goal->Variable[HP_INDEX].Value < goal->Variable[HP_INDEX].Max) { unit.State = 0; unit.SubAction = 2; unit.Data.Repair.Cycles = 0; const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); } else if (err < 0) { unit.CurrentOrder()->ClearGoal(); if (!unit.RestoreOrder()) { unit.ClearAction(); unit.State = 0; } return; } // FIXME: Should be it already? Assert(unit.CurrentAction() == UnitActionRepair); } break; // // Repair the target. // case 2: AnimateActionRepair(unit); unit.Data.Repair.Cycles++; if (!unit.Anim.Unbreakable) { goal = unit.CurrentOrder()->GetGoal(); // // Target is dead, choose new one. // // Check if goal is correct unit. // FIXME: should I do a function for this? if (goal) { if (!goal->IsVisibleAsGoal(unit.Player)) { DebugPrint("repair goal is gone\n"); unit.CurrentOrder()->goalPos = goal->tilePos; // FIXME: should I clear this here? unit.CurrentOrder()->ClearGoal(); goal = NULL; NewResetPath(unit); } else { int dist = unit.MapDistanceTo(*goal); if (dist <= unit.Type->RepairRange) { RepairUnit(unit, *goal); goal = unit.CurrentOrder()->GetGoal(); } else if (dist > unit.Type->RepairRange) { // If goal has move, chase after it unit.State = 0; unit.SubAction = 0; } } } // // Target is fine, choose new one. // if (!goal || goal->Variable[HP_INDEX].Value >= goal->Variable[HP_INDEX].Max) { unit.CurrentOrder()->ClearGoal(); if (!unit.RestoreOrder()) { unit.ClearAction(); unit.State = 0; } return; } // FIXME: automatic repair } break; } }
static bool IsUnitValidForNetwork(const CUnit &unit) { return !unit.Removed && unit.CurrentAction() != UnitActionDie; }
/* virtual */ void COrder_SpellCast::Execute(CUnit &unit) { COrder_SpellCast &order = *this; if (unit.Wait) { unit.Wait--; return ; } const SpellType &spell = order.GetSpell(); switch (this->State) { case 0: // Check if we can cast the spell. if (!CanCastSpell(unit, spell, order.GetGoal(), order.goalPos)) { // Notify player about this problem if (unit.Variable[MANA_INDEX].Value < spell.ManaCost) { unit.Player->Notify(NotifyYellow, unit.tilePos, _("%s: not enough mana for spell: %s"), unit.Type->Name.c_str(), spell.Name.c_str()); } else { unit.Player->Notify(NotifyYellow, unit.tilePos, _("%s: can't cast spell: %s"), unit.Type->Name.c_str(), spell.Name.c_str()); } if (unit.Player->AiEnabled) { DebugPrint("FIXME: do we need an AI callback?\n"); } this->Finished = true; return ; } if (CheckForDeadGoal(unit)) { return; } // FIXME FIXME FIXME: Check if already in range and skip straight to 2(casting) unit.ReCast = 0; // repeat spell on next pass? (defaults to `no') this->State = 1; // FALL THROUGH case 1: // Move to the target. if (spell.Range && spell.Range != INFINITE_RANGE) { if (SpellMoveToTarget(unit) == true) { this->Finished = true; return ; } return ; } else { this->State = 2; } // FALL THROUGH case 2: // Cast spell on the target. if (!spell.IsCasterOnly()) { AnimateActionSpellCast(unit, *this); if (unit.Anim.Unbreakable) { return ; } } else { // FIXME: what todo, if unit/goal is removed? CUnit *goal = order.GetGoal(); if (goal && goal != &unit && !goal->IsVisibleAsGoal(*unit.Player)) { unit.ReCast = 0; } else { unit.ReCast = SpellCast(unit, spell, goal, order.goalPos); } } // Target is dead ? Change order ? if (CheckForDeadGoal(unit)) { return; } // Check, if goal has moved (for ReCast) if (unit.ReCast && order.GetGoal() && unit.MapDistanceTo(*order.GetGoal()) > this->Range) { this->State = 0; return; } if (!unit.ReCast && unit.CurrentAction() != UnitActionDie) { this->Finished = true; return ; } break; default: this->State = 0; // Reset path, than move to target break; } }