/** ** Enter the transporter. ** ** @param unit Pointer to unit. */ static void EnterTransporter(CUnit &unit, COrder_Board &order) { CUnit *transporter = order.GetGoal(); Assert(transporter != NULL); if (!transporter->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Transporter gone\n"); return; } if (transporter->BoardCount < transporter->Type->MaxOnBoard) { // Place the unit inside the transporter. unit.Remove(transporter); transporter->BoardCount++; unit.Boarded = 1; if (!unit.Player->AiEnabled) { // Don't make anything funny after going out of the transporter. CommandStopUnit(unit); } if (IsOnlySelected(*transporter)) { SelectedUnitChanged(); } return; } DebugPrint("No free slot in transporter\n"); }
/** ** Choose target on map area. ** ** @param source Unit which want to attack. ** @param x1 X position on map, tile-based. ** @param y1 Y position on map, tile-based. ** @param x2 X position on map, tile-based. ** @param y2 Y position on map, tile-based. ** ** @return Returns ideal target on map tile. */ CUnit *TargetOnMap(const CUnit *source, int x1, int y1, int x2, int y2) { CUnit *table[UnitMax]; CUnit *best = NoUnitP; int n; n = UnitCache.Select(x1, y1, x2, y2, table, UnitMax); for (int i = 0; i < n; ++i) { CUnit *unit = table[i]; if (!unit->IsVisibleAsGoal(source->Player)) { continue; } if (x2 < unit->X || x1 >= unit->X + unit->Type->TileWidth || y2 < unit->Y || y1 >= unit->Y + unit->Type->TileHeight) { continue; } if (!CanTarget(source->Type, unit->Type)) { continue; } // // Choose the best target. // if (!best || best->Type->Priority < unit->Type->Priority) { best = unit; } } return best; }
/** ** Call when animation step is "attack" */ /* virtual */ void COrder_SpellCast::OnAnimationAttack(CUnit &unit) { CUnit *goal = GetGoal(); if (goal && !goal->IsVisibleAsGoal(*unit.Player)) { unit.ReCast = 0; } else { unit.ReCast = SpellCast(unit, *this->Spell, goal, goalPos); } UnHideUnit(unit); // unit is invisible until attacks }
/** ** Wait for transporter. ** ** @param unit Pointer to unit. ** ** @return True if ship arrived/present, False otherwise. */ static int WaitForTransporter(CUnit *unit) { CUnit *trans; if (unit->Wait) { unit->Wait--; return 0; } trans = unit->Orders[0]->Goal; if (!trans || !CanTransport(trans, unit)) { // FIXME: destination destroyed?? unit->Wait = 6; return 0; } if (!trans->IsVisibleAsGoal(unit->Player)) { DebugPrint("Transporter Gone\n"); trans->RefsDecrease(); unit->Orders[0]->Goal = NoUnitP; unit->Wait = 6; return 0; } if (MapDistanceBetweenUnits(unit, trans) == 1) { // enter transporter return 1; } // // FIXME: any enemies in range attack them, while waiting. // // n0b0dy: This means we have to search with a smaller range. // It happens only when you reach the shore,and the transporter // is not there. The unit searches with a big range, so it thinks // it's there. This is why we reset the search. The transporter // should be a lot closer now, so it's not as bad as it seems. unit->SubAction = 0; unit->Orders[0]->Range = 1; // Uhh wait a bit. unit->Wait = 10; return 0; }
/** ** Check for dead goal. ** ** @warning The caller must check, if he likes the restored SavedOrder! ** ** @todo If a unit enters an building, than the attack choose an ** other goal, perhaps it is better to wait for the goal? ** ** @param unit Unit using the goal. ** ** @return true if order have changed, false else. */ bool COrder_Attack::CheckForDeadGoal(CUnit &unit) { CUnit *goal = this->GetGoal(); // Position or valid target, it is ok. if (!goal || goal->IsVisibleAsGoal(*unit.Player)) { return false; } // Goal could be destroyed or unseen // So, cannot use type. this->goalPos = goal->tilePos; this->MinRange = 0; this->Range = 0; this->ClearGoal(); // If we have a saved order continue this saved order. if (unit.RestoreOrder()) { return true; } return false; }
/** ** Enter the transporter. ** ** @param unit Pointer to unit. */ static void EnterTransporter(CUnit *unit) { CUnit *transporter; unit->ClearAction(); transporter = unit->Orders[0]->Goal; if (!transporter->IsVisibleAsGoal(unit->Player)) { DebugPrint("Transporter gone\n"); transporter->RefsDecrease(); unit->Orders[0]->Goal = NoUnitP; return; } transporter->RefsDecrease(); unit->Orders[0]->Goal = NoUnitP; // // Place the unit inside the transporter. // if (transporter->BoardCount < transporter->Type->MaxOnBoard) { unit->Remove(transporter); transporter->BoardCount++; unit->Boarded = 1; if (!unit->Player->AiEnabled) { // Don't make anything funny after going out of the transporter. // FIXME: This is probably wrong, but it works for me (n0b0dy) unit->OrderCount = 1; unit->ClearAction(); } if (IsOnlySelected(transporter)) { SelectedUnitChanged(); } return; } DebugPrint("No free slot in transporter\n"); }
/** ** Attack units in distance. ** ** If the unit can attack must be handled by caller. ** Choose the best target, that can be attacked. ** ** @param unit Find in distance for this unit. ** @param range Distance range to look. ** ** @return Unit to be attacked. ** */ CUnit *AttackUnitsInDistance(const CUnit *unit, int range) { CUnit *dest; const CUnitType *type; const CUnitType *dtype; CUnit *table[UnitMax]; int x; int y; int n; int i; int d; int attackrange; int cost; int best_cost; const CPlayer *player; CUnit *best_unit; // if necessary, take possible damage on allied units into account... if (unit->Type->Missile.Missile->Range > 1 && (range + unit->Type->Missile.Missile->Range < 15)) { return FindRangeAttack(unit, range); } // // Select all units in range. // x = unit->X; y = unit->Y; type = unit->Type; n = UnitCache.Select(x - range, y - range, x + range + type->TileWidth, y + range + type->TileHeight, table, UnitMax); if (range > 25 && n > 9) { referenceunit = unit; qsort((void*)table, n, sizeof(CUnit*), &CompareUnitDistance); } best_unit = NoUnitP; best_cost = INT_MAX; player = unit->Player; attackrange = unit->Stats->Variables[ATTACKRANGE_INDEX].Max; // // Find the best unit to attack // for (i = 0; i < n; ++i) { dest = table[i]; if (!dest->IsVisibleAsGoal(unit->Player)) { continue; } if (!player->IsEnemy(dest)) { // a friend or neutral continue; } dtype = dest->Type; if (!CanTarget(type, dtype)) { // can't be attacked. continue; } // // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. // cost = 0; // // Priority 0-255 // cost -= dtype->Priority * PRIORITY_FACTOR; // // Remaining HP (Health) 0-65535 // cost += dest->Variable[HP_INDEX].Value * HEALTH_FACTOR; // // Unit in attack range? // d = MapDistanceBetweenUnits(unit, dest); // Use Circle, not square :) if (d > range) { continue; } if (d <= attackrange && d >= type->MinAttackRange) { cost += d * INRANGE_FACTOR; cost -= INRANGE_BONUS; } else { cost += d * DISTANCE_FACTOR; } // // Unit can attack back. // if (CanTarget(dtype, type)) { cost -= CANATTACK_BONUS; } // // Take this target? // if (cost < best_cost && (d <= attackrange || UnitReachable(unit, dest, attackrange))) { best_unit = dest; best_cost = cost; } } return best_unit; }
/** ** Attack units in distance, with large missile ** ** Choose the best target, that can be attacked. It takes into ** account allied unit which could be hit by the missile ** ** @param u Find in distance for this unit. ** @param range Distance range to look. ** ** @return Unit to be attacked. ** ** @note This could be improved, for better performance / better trade. ** @note Limited to attack range smaller than 16. ** @note Will be moved to unit_ai.c soon. */ static CUnit *FindRangeAttack(const CUnit *u, int range) { int x; int y; int n; int cost; int d; int effective_hp; int enemy_count; int missile_range; int attackrange; int hp_damage_evaluate; int good[32][32]; int bad[32][32]; CUnit *table[UnitMax]; CUnit *dest; const CUnitType *dtype; const CUnitType *type; const CPlayer *player; int xx; int yy; int best_cost; int i; int sbad; int sgood; CUnit *best; type = u->Type; player = u->Player; // If catapult, count units near the target... // FIXME : make it configurable // missile_range = type->Missile.Missile->Range + range - 1; attackrange = u->Stats->Variables[ATTACKRANGE_INDEX].Max; // Evaluation of possible damage... hp_damage_evaluate = u->Stats->Variables[BASICDAMAGE_INDEX].Value + u->Stats->Variables[PIERCINGDAMAGE_INDEX].Value; Assert(2 * missile_range + 1 < 32); // // If unit is removed, use containers x and y if (u->Removed) { x = u->Container->X; y = u->Container->Y; n = UnitCache.Select(x - missile_range, y - missile_range, x + missile_range + u->Container->Type->TileWidth, y + missile_range + u->Container->Type->TileHeight, table, UnitMax); } else { x = u->X; y = u->Y; n = UnitCache.Select(x - missile_range, y - missile_range, x + missile_range + u->Type->TileWidth, y + missile_range + u->Type->TileHeight, table, UnitMax); } if (!n) { return NoUnitP; } for (y = 0; y < 2 * missile_range + 1; ++y) { for (x = 0; x < 2 * missile_range + 1; ++x) { good[y][x] = 0; bad[y][x] = 0; } } enemy_count = 0; // FILL good/bad... for (i = 0; i < n; ++i) { dest = table[i]; dtype = dest->Type; if (!dest->IsVisibleAsGoal(u->Player)) { table[i] = 0; continue; } // won't be a target... if (!CanTarget(type, dtype)) { // can't be attacked. table[i] = 0; continue; } if (!player->IsEnemy(dest)) { // a friend or neutral table[i] = 0; // Calc a negative cost // The gost is more important when the unit would be killed // by our fire. // It costs (is positive) if hp_damage_evaluate>dest->HP ...) // FIXME : assume that PRIORITY_FACTOR>HEALTH_FACTOR cost = HEALTH_FACTOR * (2 * hp_damage_evaluate - dest->Variable[HP_INDEX].Value) / (dtype->TileWidth * dtype->TileWidth); if (cost < 1) { cost = 1; } cost = (-cost); } else { // // Calculate the costs to attack the unit. // Unit with the smallest attack costs will be taken. // cost = 0; // // Priority 0-255 // cost += dtype->Priority * PRIORITY_FACTOR; // // Remaining HP (Health) 0-65535 // // Give a boost to unit we can kill in one shoot only // // calculate HP which will remain in the enemy unit, after hit // effective_hp = (dest->Variable[HP_INDEX].Value - 2 * hp_damage_evaluate); // // Unit we won't kill are evaluated the same // if (effective_hp > 0) { effective_hp = 0; } // // Unit we are sure to kill are all evaluated the same (except PRIORITY) // if (effective_hp < -hp_damage_evaluate) { effective_hp = -hp_damage_evaluate; } // // Here, effective_hp vary from -hp_damage_evaluate (unit will be killed) to 0 (unit can't be killed) // => we prefer killing rather than only hitting... // cost += -effective_hp * HEALTH_FACTOR; // // Unit can attack back. // if (CanTarget(dtype, type)) { cost += CANATTACK_BONUS; } // // the cost may be divided accros multiple cells // cost = cost / (dtype->TileWidth * dtype->TileWidth); if (cost < 1) { cost = 1; } // // Removed Unit's are in bunkers // if (u->Removed) { d = MapDistanceBetweenUnits(u->Container, dest); } else { d = MapDistanceBetweenUnits(u, dest); } if (d <= attackrange || (d <= range && UnitReachable(u, dest, attackrange))) { ++enemy_count; } else { table[i] = 0; } } x = dest->X - u->X + missile_range + 1; y = dest->Y - u->Y + missile_range + 1; // Mark the good/bad array... for (xx = 0; xx < dtype->TileWidth; ++xx) { for (yy = 0; yy < dtype->TileWidth; ++yy) { if ((x + xx < 0) || (y + yy < 0) || (x + xx >= 2 * missile_range + 1) || (y + yy >= 2 * missile_range + 1)) { continue; } if (cost < 0) { good[y + yy][x + xx] -= cost; } else { bad[y + yy][x + xx] += cost; } } } } if (!enemy_count) { return NoUnitP; } // Find the best area... // The target which provide the best bad/good ratio is choosen... best_cost = -1; best = NoUnitP; for (i = 0; i < n; ++i) { if (!table[i]) { continue; } dest = table[i]; dtype = dest->Type; // put in x-y the real point which will be hit... // (only meaningful when dtype->TileWidth > 1) if (u->X < dest->X) { x = dest->X; } else if (u->X > dest->X + dtype->TileWidth - 1) { x = dest->X + dtype->TileWidth - 1; } else { x = u->X; } if (u->Y < dest->Y) { y = dest->Y; } else if (u->Y > dest->Y + dtype->TileHeight - 1) { y = dest->Y + dtype->TileHeight - 1; } else { y = u->Y; } // Make x,y relative to u->x... x = x - u->X + missile_range + 1; y = y - u->Y + missile_range + 1; sbad = 0; sgood = 0; for (yy = -(type->Missile.Missile->Range - 1); yy <= type->Missile.Missile->Range - 1; ++yy) { for (xx = -(type->Missile.Missile->Range - 1); xx <= type->Missile.Missile->Range - 1; ++xx) { if ((x + xx < 0) || (y + yy < 0) || ((x + xx) >= 2 * missile_range + 1) || ((y + yy) >= 2 * missile_range + 1)) { continue; } sbad += bad[y + yy][x + xx]; sgood += good[y + yy][x + xx]; if (!yy && !xx) { sbad += bad[y + yy][x + xx]; sgood += good[y + yy][x + xx]; } } } // don't consider small damages... if (sgood < 20) { sgood = 20; } cost = sbad / sgood; if (cost > best_cost) { best_cost = cost; best = dest; } } return best; }
/** ** 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->Orders[0]->Goal; // // 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->Orders[0]->X = goal->X; unit->Orders[0]->Y = goal->Y; goal->RefsDecrease(); // FIXME: should I clear this here? unit->Orders[0]->Goal = 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 && MapDistanceBetweenUnits(unit, 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; UnitHeadingFromDeltaXY(unit, goal->X + (goal->Type->TileWidth - 1) / 2 - unit->X, goal->Y + (goal->Type->TileHeight - 1) / 2 - unit->Y); } else if (err < 0) { if (goal) { // release reference goal->RefsDecrease(); unit->Orders[0]->Goal = NoUnitP; } unit->Orders[0]->Action = UnitActionStill; unit->State = unit->SubAction = 0; if (unit->Selected) { // update display for new action SelectedUnitChanged(); } return; } // FIXME: Should be it already? Assert(unit->Orders[0]->Action == UnitActionRepair); } break; // // Repair the target. // case 2: AnimateActionRepair(unit); unit->Data.Repair.Cycles++; if (!unit->Anim.Unbreakable) { goal = unit->Orders[0]->Goal; // // 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->Orders[0]->X = goal->X; unit->Orders[0]->Y = goal->Y; goal->RefsDecrease(); // FIXME: should I clear this here? unit->Orders[0]->Goal = goal = NULL; NewResetPath(unit); } } if (goal && MapDistanceBetweenUnits(unit, goal) <= unit->Type->RepairRange) { RepairUnit(unit, goal); goal = unit->Orders[0]->Goal; } else if (goal && MapDistanceBetweenUnits(unit, goal) > 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) { if (goal) { // release reference goal->RefsDecrease(); unit->Orders[0]->Goal = NULL; } unit->Orders[0]->Action = UnitActionStill; unit->SubAction = unit->State = 0; if (unit->Selected) { // update display for new action SelectedUnitChanged(); } return; } // FIXME: automatic repair } break; } }
/* 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; } }
/* virtual */ void COrder_Defend::Execute(CUnit &unit) { if (unit.Wait) { if (!unit.Waiting) { unit.Waiting = 1; unit.WaitBackup = unit.Anim; } //Wyrmgus start // UnitShowAnimation(unit, unit.Type->Animations->Still); VariationInfo *varinfo = unit.Type->VarInfo[unit.Variation]; if (varinfo && varinfo->Animations && varinfo->Animations->Still) { UnitShowAnimation(unit, varinfo->Animations->Still); } else { UnitShowAnimation(unit, unit.Type->Animations->Still); } //Wyrmgus end unit.Wait--; return; } if (unit.Waiting) { unit.Anim = unit.WaitBackup; unit.Waiting = 0; } CUnit *goal = this->GetGoal(); if (this->State == State_Init) { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { this->Finished = true; return; } this->State = State_MovingToTarget; } else if (this->State == State_Defending) { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { this->Finished = true; return; } } if (!unit.Anim.Unbreakable) { if (AutoCast(unit) || AutoAttack(unit) || AutoRepair(unit)) { return; } } switch (DoActionMove(unit)) { case PF_UNREACHABLE: // Some tries to reach the goal this->Range++; break; case PF_REACHED: { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { // goal has died this->Finished = true; return; } // Now defend the goal this->goalPos = goal->tilePos; this->State = State_Defending; } default: break; } // Target destroyed? if (goal && !goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Goal gone\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); this->ClearGoal(); goal = NULL; if (this->State == State_Defending) { this->Finished = true; return; } } }
/* virtual */ void COrder_Defend::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(); if (this->State == State_Init) { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { this->Finished = true; return; } this->State = State_MovingToTarget; } else if (this->State == State_Defending) { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { this->Finished = true; return; } } if (!unit.Anim.Unbreakable) { if (AutoCast(unit) || AutoAttack(unit) || AutoRepair(unit)) { return; } } switch (DoActionMove(unit)) { case PF_UNREACHABLE: //Wyrmgus start //if is unreachable and is on a raft, see if the raft can move closer to the enemy if ((Map.Field(unit.tilePos)->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()) { if (table[i]->CurrentAction() == UnitActionStill) { CommandStopUnit(*table[i]); CommandMove(*table[i], this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, FlushCommands); } return; } } } //Wyrmgus end // Some tries to reach the goal this->Range++; break; case PF_REACHED: { if (!goal || !goal->IsVisibleAsGoal(*unit.Player)) { // goal has died this->Finished = true; return; } // Now defend the goal this->goalPos = goal->tilePos; this->State = State_Defending; } default: break; } // Target destroyed? if (goal && !goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("Goal gone\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); this->ClearGoal(); goal = NULL; if (this->State == State_Defending) { this->Finished = true; return; } } }
/** ** Gather the resource ** ** @param unit Pointer to unit. ** ** @return non-zero if ready, otherwise zero. */ int COrder_Resource::GatherResource(CUnit &unit) { CUnit *source = 0; const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; int addload; //Wyrmgus start bool harvest_from_outside = (this->GetGoal() && this->GetGoal()->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value); // if (resinfo.HarvestFromOutside || resinfo.TerrainHarvester) { if (harvest_from_outside || Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end AnimateActionHarvest(unit); } else { unit.Anim.CurrAnim = NULL; } this->TimeToHarvest--; if (this->DoneHarvesting) { //Wyrmgus start // Assert(resinfo.HarvestFromOutside || resinfo.TerrainHarvester); Assert(harvest_from_outside || Map.Info.IsPointOnMap(this->goalPos)); //Wyrmgus end return !unit.Anim.Unbreakable; } // Target gone? //Wyrmgus start // if (resinfo.TerrainHarvester && !Map.Field(this->goalPos)->IsTerrainResourceOnMap(this->CurrentResource)) { if (Map.Info.IsPointOnMap(this->goalPos) && !Map.Field(this->goalPos)->IsTerrainResourceOnMap(this->CurrentResource)) { //Wyrmgus end if (!unit.Anim.Unbreakable) { // Action now breakable, move to resource again. this->State = SUB_MOVE_TO_RESOURCE; // Give it some reasonable look while searching. // FIXME: which frame? unit.Frame = 0; } return 0; // No wood? Freeze!!! } while (!this->DoneHarvesting && this->TimeToHarvest < 0) { //FIXME: rb - how should it look for WaitAtResource == 0 if (resinfo.WaitAtResource) { // Wyrmgus start // this->TimeToHarvest += std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); int wait_at_resource = resinfo.WaitAtResource; int resource_harvest_speed = unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]; if (!Map.Info.IsPointOnMap(this->goalPos) && !harvest_from_outside) { wait_at_resource = resinfo.WaitAtResource * 100 / resinfo.ResourceStep; } if (this->GetGoal()) { resource_harvest_speed += this->GetGoal()->Variable[TIMEEFFICIENCYBONUS_INDEX].Value; } this->TimeToHarvest += std::max<int>(1, wait_at_resource * SPEEDUP_FACTOR / resource_harvest_speed); //Wyrmgus end } else { this->TimeToHarvest += 1; } // Calculate how much we can load. //Wyrmgus start // if (resinfo.ResourceStep) { if (resinfo.ResourceStep && (harvest_from_outside || Map.Info.IsPointOnMap(this->goalPos))) { //Wyrmgus end addload = resinfo.ResourceStep; } else { addload = resinfo.ResourceCapacity; } // Make sure we don't bite more than we can chew. if (unit.ResourcesHeld + addload > resinfo.ResourceCapacity) { addload = resinfo.ResourceCapacity - unit.ResourcesHeld; } //Wyrmgus start // if (resinfo.TerrainHarvester) { if (Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end //Wyrmgus start CMapField &mf = *Map.Field(this->goalPos); if (addload > mf.Value) { addload = mf.Value; } mf.Value -= addload; //Wyrmgus end unit.ResourcesHeld += addload; //Wyrmgus start // if (addload && unit.ResourcesHeld == resinfo.ResourceCapacity) { if (mf.Value <= 0) { //Wyrmgus end //Wyrmgus start // Map.ClearWoodTile(this->goalPos); if (this->CurrentResource == WoodCost) { Map.ClearWoodTile(this->goalPos); } else if (this->CurrentResource == StoneCost) { Map.ClearRockTile(this->goalPos); } //Wyrmgus end } } else { //Wyrmgus start // if (resinfo.HarvestFromOutside) { if (harvest_from_outside) { //Wyrmgus end source = this->GetGoal(); } else { source = unit.Container; } Assert(source); Assert(source->ResourcesHeld <= 655350); //Wyrmgus start UpdateUnitVariables(*source); //update resource source's variables //Wyrmgus end bool is_visible = source->IsVisibleAsGoal(*unit.Player); // Target is not dead, getting resources. if (is_visible) { // Don't load more that there is. addload = std::min(source->ResourcesHeld, addload); unit.ResourcesHeld += addload; source->ResourcesHeld -= addload; } // End of resource: destroy the resource. // FIXME: implement depleted resources. if ((!is_visible) || (source->ResourcesHeld == 0)) { if (unit.Anim.Unbreakable) { return 0; } DebugPrint("%d: Worker %d report: Resource is destroyed\n" _C_ unit.Player->Index _C_ UnitNumber(unit)); bool dead = source->IsAlive() == false; // Improved version of DropOutAll that makes workers go to the depot. LoseResource(unit, *source); for (CUnit *uins = source->Resource.Workers; uins; uins = uins->NextWorker) { if (uins != &unit && uins->CurrentOrder()->Action == UnitActionResource) { COrder_Resource &order = *static_cast<COrder_Resource *>(uins->CurrentOrder()); if (!uins->Anim.Unbreakable && order.State == SUB_GATHER_RESOURCE) { order.LoseResource(*uins, *source); } } } // Don't destroy the resource twice. // This only happens when it's empty. if (!dead) { if (Preference.MineNotifications && unit.Player->Index == ThisPlayer->Index //Wyrmgus start // && source->Variable[GIVERESOURCE_INDEX].Max > DefaultIncomes[this->CurrentResource]) { && source->Variable[GIVERESOURCE_INDEX].Max > (DefaultIncomes[this->CurrentResource] * 10)) { //Wyrmgus end //Wyrmgus start // unit.Player->Notify(NotifyYellow, source->tilePos, _("%s has collapsed!"), source->Type->Name.c_str()); unit.Player->Notify(NotifyYellow, source->tilePos, _("Our %s has been depleted!"), source->Type->Name.c_str()); //Wyrmgus end } LetUnitDie(*source); // FIXME: make the workers inside look for a new resource. } source = NULL; return 0; } } //Wyrmgus start // if (resinfo.TerrainHarvester) { if (Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end if (unit.ResourcesHeld == resinfo.ResourceCapacity) { // Mark as complete. this->DoneHarvesting = true; } return 0; } else { //Wyrmgus start // if (resinfo.HarvestFromOutside) { if (harvest_from_outside) { //Wyrmgus end if ((unit.ResourcesHeld == resinfo.ResourceCapacity) || (source == NULL)) { // Mark as complete. this->DoneHarvesting = true; } return 0; } else { return unit.ResourcesHeld == resinfo.ResourceCapacity && source; } } } return 0; }
/** ** Start harvesting the resource. ** ** @param unit Pointer to unit. ** ** @return TRUE if ready, otherwise FALSE. */ int COrder_Resource::StartGathering(CUnit &unit) { CUnit *goal; const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; Assert(!unit.IX); Assert(!unit.IY); //Wyrmgus start // if (resinfo.TerrainHarvester) { if (Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end // This shouldn't happened? #if 0 if (!Map.IsTerrainResourceOnMap(unit.Orders->goalPos, this->CurrentResource)) { DebugPrint("Wood gone, just like that?\n"); return 0; } #endif UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos); if (resinfo.WaitAtResource) { this->TimeToHarvest = std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); } else { this->TimeToHarvest = 1; } this->DoneHarvesting = 0; if (this->CurrentResource != unit.CurrentResource) { DropResource(unit); unit.CurrentResource = this->CurrentResource; } return 1; } goal = this->GetGoal(); // Target is dead, stop getting resources. if (!goal || goal->IsVisibleAsGoal(*unit.Player) == false) { // Find an alternative, but don't look too far. this->goalPos.x = -1; this->goalPos.y = -1; if ((goal = UnitFindResource(unit, unit, 15, this->CurrentResource, unit.Player->AiEnabled))) { this->State = SUB_START_RESOURCE; this->SetGoal(goal); } else { this->ClearGoal(); this->Finished = true; } return 0; } // FIXME: 0 can happen, if to near placed by map designer. Assert(unit.MapDistanceTo(*goal) <= 1); // Update the heading of a harvesting unit to looks straight at the resource. //Wyrmgus start // UnitHeadingFromDeltaXY(unit, goal->tilePos - unit.tilePos + goal->Type->GetHalfTileSize()); UnitHeadingFromDeltaXY(unit, Vec2i(goal->tilePos.x * PixelTileSize.x, goal->tilePos.y * PixelTileSize.y) - Vec2i(unit.tilePos.x * PixelTileSize.x, unit.tilePos.y * PixelTileSize.y) + goal->Type->GetHalfTilePixelSize() - unit.Type->GetHalfTilePixelSize()); //Wyrmgus end // If resource is still under construction, wait! if ((goal->Type->MaxOnBoard && goal->Resource.Active >= goal->Type->MaxOnBoard) || goal->CurrentAction() == UnitActionBuilt) { // FIXME: Determine somehow when the resource will be free to use // FIXME: Could we somehow find another resource? Think minerals // FIXME: We should add a flag for that, and a limited range. // However the CPU usage is really low (no pathfinding stuff). unit.Wait = 10; return 0; } // Place unit inside the resource //Wyrmgus start // if (!resinfo.HarvestFromOutside) { if (!goal->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) { //Wyrmgus end if (goal->Variable[MAXHARVESTERS_INDEX].Value == 0 || goal->Variable[MAXHARVESTERS_INDEX].Value > goal->InsideCount) { this->ClearGoal(); int selected = unit.Selected; unit.Remove(goal); if (selected && !Preference.DeselectInMine) { unit.Removed = 0; SelectUnit(unit); SelectionChanged(); unit.Removed = 1; } } else if (goal->Variable[MAXHARVESTERS_INDEX].Value <= goal->InsideCount) { //Resource is full, wait unit.Wait = 10; return 0; } } if (this->CurrentResource != unit.CurrentResource) { DropResource(unit); unit.CurrentResource = this->CurrentResource; } // Activate the resource goal->Resource.Active++; if (resinfo.WaitAtResource) { //Wyrmgus start // this->TimeToHarvest = std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); int wait_at_resource = resinfo.WaitAtResource; if (!goal->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) { wait_at_resource = resinfo.WaitAtResource * 100 / resinfo.ResourceStep; } this->TimeToHarvest = std::max<int>(1, wait_at_resource * SPEEDUP_FACTOR / (unit.Player->SpeedResourcesHarvest[resinfo.ResourceId] + goal->Variable[TIMEEFFICIENCYBONUS_INDEX].Value)); //Wyrmgus end } else { this->TimeToHarvest = 1; } this->DoneHarvesting = 0; return 1; }
/* 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; } } } }
/** ** 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; } }
/* virtual */ void COrder_Repair::Execute(CUnit &unit) { Assert(this->ReparableTarget == this->GetGoal()); switch (this->State) { case 0: this->State = 1; // FALL THROUGH case 1: { // Move near to target. // FIXME: RESET FIRST!! Why? We move first and than check if // something is in sight. int err = DoActionMove(unit); if (!unit.Anim.Unbreakable) { // No goal: if meeting damaged building repair it. CUnit *goal = this->GetGoal(); if (goal) { if (!goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("repair target gone.\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); ReparableTarget = NULL; this->ClearGoal(); goal = NULL; } } 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) { this->State = 2; this->RepairCycle = 0; const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); } else if (err < 0) { this->Finished = true; return ; } } break; } case 2: {// Repair the target. AnimateActionRepair(unit); this->RepairCycle++; if (unit.Anim.Unbreakable) { return ; } CUnit *goal = this->GetGoal(); if (goal) { if (!goal->IsVisibleAsGoal(*unit.Player)) { DebugPrint("repair goal is gone\n"); this->goalPos = goal->tilePos + goal->Type->GetHalfTileSize(); // FIXME: should I clear this here? this->ClearGoal(); ReparableTarget = NULL; goal = NULL; } else { const int dist = unit.MapDistanceTo(*goal); if (dist <= unit.Type->RepairRange) { if (RepairUnit(unit, *goal)) { this->Finished = true; return ; } } else if (dist > unit.Type->RepairRange) { // If goal has move, chase after it this->State = 0; } } } // Target is fine, choose new one. if (!goal || goal->Variable[HP_INDEX].Value >= goal->Variable[HP_INDEX].Max) { this->Finished = true; return ; } // FIXME: automatic repair } break; } }