/** ** Make one or more unit leave the transporter. ** ** @param unit Pointer to unit. */ static void LeaveTransporter(CUnit *unit) { int i; int stillonboard; CUnit *goal; stillonboard = 0; goal = unit->Orders[0]->Goal; // // Goal is the specific unit unit that you want to unload. // This can be NULL, in case you want to unload everything. // if (goal) { unit->Orders[0]->Goal = NoUnitP; if (goal->Destroyed) { DebugPrint("destroyed unit unloading?\n"); goal->RefsDecrease(); return; } goal->RefsDecrease(); goal->X = unit->X; goal->Y = unit->Y; // Try to unload the unit. If it doesn't work there is no problem. if (UnloadUnit(goal)) { unit->BoardCount--; } } else { // Unload all units. goal = unit->UnitInside; for (i = unit->InsideCount; i; --i, goal = goal->NextContained) { if (goal->Boarded) { goal->X = unit->X; goal->Y = unit->Y; if (!UnloadUnit(goal)) { ++stillonboard; } else { unit->BoardCount--; } } } } if (IsOnlySelected(unit)) { SelectedUnitChanged(); } // We still have some units to unload, find a piece of free coast. if (stillonboard) { // We tell it to unload at it's current position. This can't be done, // so it will search for a piece of free coast nearby. unit->Orders[0]->Action = UnitActionUnload; unit->Orders[0]->Goal = NoUnitP; unit->Orders[0]->X = unit->X; unit->Orders[0]->Y = unit->Y; unit->SubAction = 0; } else { unit->ClearAction(); } }
/** ** Handle AI of all players each game cycle. */ void PlayersEachCycle() { for (int player = 0; player < NumPlayers; ++player) { CPlayer *p = &Players[player]; if (p->AutoAttackTargets.size() > 0) { CUnitCache &autoatacktargets = p->AutoAttackTargets; /* both loops can not be connected !!!! */ for (unsigned int i = 0; i < autoatacktargets.size();) { CUnit *aatarget = autoatacktargets[i]; if (!aatarget->IsAliveOnMap() || Map.Field(aatarget->Offset)->Guard[player] == 0) { autoatacktargets.Units.erase(autoatacktargets.Units.begin() + i); aatarget->RefsDecrease(); continue; } ++i; } if (autoatacktargets.size() > 0) { for (int j = 0; j < p->TotalNumUnits; ++j) { CUnit &guard = *p->Units[j]; bool stand_ground = guard.CurrentAction() == UnitActionStandGround; if (guard.Type->CanAttack && (stand_ground || guard.IsIdle()) && !guard.IsUnusable()) { AutoAttack(guard, autoatacktargets, stand_ground); } } } } if (p->AiEnabled) { AiEachCycle(p); } } }
/** ** 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"); }
/** ** Free a missile. ** ** @param missiles Missile pointer. ** @param i Index in missiles of missile to free */ static void FreeMissile(std::vector<Missile *> &missiles, size_t i) { Missile *missile; CUnit *unit; missile = missiles[i]; // // Release all unit references. // if ((unit = missile->SourceUnit)) { unit->RefsDecrease(); } if ((unit = missile->TargetUnit)) { unit->RefsDecrease(); } for (std::vector<Missile*>::iterator j = missiles.begin(); j != missiles.end(); ++j) { if (*j == missile) { missiles.erase(j); break; } } delete missile; }
/** ** 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; }
/** ** 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; } }
/** ** The unit boards a transporter. ** ** @todo FIXME: While waiting for the transporter the units must defend themselves. ** ** @param unit Pointer to unit. */ void HandleActionBoard(CUnit *unit) { int i; CUnit *goal; switch (unit->SubAction) { // // Wait for transporter // case 201: if (WaitForTransporter(unit)) { unit->SubAction = 202; } else { UnitShowAnimation(unit, unit->Type->Animations->Still); } break; // // Enter transporter // case 202: EnterTransporter(unit); break; // // Move to transporter // case 0: if (unit->Wait) { unit->Wait--; return; } NewResetPath(unit); unit->SubAction = 1; // FALL THROUGH default: if (unit->SubAction <= 200) { // FIXME: if near transporter wait for enter if ((i = MoveToTransporter(unit))) { if (i == PF_UNREACHABLE) { if (++unit->SubAction == 200) { unit->ClearAction(); if ((goal = unit->Orders[0]->Goal)) { goal->RefsDecrease(); unit->Orders[0]->Goal = NoUnitP; } } else { // // Try with a bigger range. // if (unit->Orders[0]->Range <= Map.Info.MapWidth || unit->Orders[0]->Range <= Map.Info.MapHeight) { unit->Orders[0]->Range++; unit->SubAction--; } } } else if (i == PF_REACHED) { unit->SubAction = 201; } } } break; } }
/** ** Auto attack nearby units if possible */ static void AutoAttack(CUnit *unit, bool stand_ground) { CUnit *temp; CUnit *goal; if (unit->Wait) { unit->Wait--; return; } // Cowards don't attack unless ordered. if (unit->Type->CanAttack && !unit->Type->Coward) { // Normal units react in reaction range. if (CanMove(unit) && !unit->Removed && !stand_ground) { if ((goal = AttackUnitsInReactRange(unit))) { // Weak goal, can choose other unit, come back after attack CommandAttack(unit, goal->X, goal->Y, NULL, FlushCommands); Assert(unit->SavedOrder.Action == UnitActionStill); Assert(!unit->SavedOrder.Goal); unit->SavedOrder.Action = UnitActionAttack; unit->SavedOrder.Range = 0; unit->SavedOrder.X = unit->X; unit->SavedOrder.Y = unit->Y; unit->SavedOrder.Goal = NoUnitP; } else { unit->Wait = 15; } // Removed units can only attack in AttackRange, from bunker } else if ((goal = AttackUnitsInRange(unit))) { temp = unit->Orders[0]->Goal; if (temp && temp->Orders[0]->Action == UnitActionDie) { temp->RefsDecrease(); unit->Orders[0]->Goal = temp = NoUnitP; } if (!unit->SubAction || temp != goal) { // New target. if (temp) { temp->RefsDecrease(); } unit->Orders[0]->Goal = goal; goal->RefsIncrease(); unit->State = 0; unit->SubAction = 1; // Mark attacking. UnitHeadingFromDeltaXY(unit, goal->X + (goal->Type->TileWidth - 1) / 2 - unit->X, goal->Y + (goal->Type->TileHeight - 1) / 2 - unit->Y); } return; } } else { unit->Wait = 15; } if (unit->SubAction) { // was attacking. if ((temp = unit->Orders[0]->Goal)) { temp->RefsDecrease(); unit->Orders[0]->Goal = NoUnitP; } unit->SubAction = unit->State = 0; // No attacking, restart } Assert(!unit->Orders[0]->Goal); }
/** ** Work for missile hit. ** ** @param missile Missile reaching end-point. */ void MissileHit(Missile *missile) { CUnit *goal; int x; int y; CUnit *table[UnitMax]; int n; int i; int splash; if (missile->Type->ImpactSound.Sound) { PlayMissileSound(missile, missile->Type->ImpactSound.Sound); } x = missile->X + missile->Type->Width / 2; y = missile->Y + missile->Type->Height / 2; // // The impact generates a new missile. // if (missile->Type->ImpactMissile) { MakeMissile(missile->Type->ImpactMissile, x, y, x, y); } if (missile->Type->ImpactParticle) { missile->Type->ImpactParticle->pushPreamble(); missile->Type->ImpactParticle->pushInteger(x); missile->Type->ImpactParticle->pushInteger(y); missile->Type->ImpactParticle->run(); } if (!missile->SourceUnit) { // no owner - green-cross ... return; } x /= TileSizeX; y /= TileSizeY; if (x < 0 || y < 0 || x >= Map.Info.MapWidth || y >= Map.Info.MapHeight) { // FIXME: this should handled by caller? DebugPrint("Missile gone outside of map!\n"); return; // outside the map. } // // Choose correct goal. // if (!missile->Type->Range) { if (missile->TargetUnit) { // // Missiles without range only hits the goal always. // goal = missile->TargetUnit; if (goal->Destroyed) { // Destroyed goal->RefsDecrease(); missile->TargetUnit = NoUnitP; return; } MissileHitsGoal(missile, goal, 1); return; } return; } // // Hits all units in range. // i = missile->Type->Range; n = UnitCache.Select(x - i + 1, y - i + 1, x + i, y + i, table, UnitMax); Assert(missile->SourceUnit != NULL); for (i = 0; i < n; ++i) { goal = table[i]; // // Can the unit attack the this unit-type? // NOTE: perhaps this should be come a property of the missile. // if (CanTarget(missile->SourceUnit->Type, goal->Type)) { splash = MapDistanceToUnit(x, y, goal); if (splash) { splash *= missile->Type->SplashFactor; } else { splash = 1; } MissileHitsGoal(missile, goal, splash); } } }