// ____ _ __ // / __ )____ _____ | | / /___ ___________ // / __ / __ \/ ___/ | | /| / / __ `/ ___/ ___/ // / /_/ / /_/ (__ ) | |/ |/ / /_/ / / (__ ) // /_____/\____/____/ |__/|__/\__,_/_/ /____/ // // A futuristic real-time strategy game. // This file is part of Bos Wars. // /**@name command.cpp - Give units a command. */ // // (c) Copyright 1998-2007 by Lutz Sammer and Jimmy Salmon // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; only version 2 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA // 02111-1307, USA. // // $Id: command.cpp 8749 2007-04-14 05:12:11Z jim4 $ //@{ /*---------------------------------------------------------------------------- -- Includes ----------------------------------------------------------------------------*/ #include #include #include #include "stratagus.h" #include "unittype.h" #include "player.h" #include "unit.h" #include "actions.h" #include "tileset.h" #include "map.h" #include "upgrade.h" #include "pathfinder.h" #include "spells.h" #include "interface.h" #include "ui.h" /*---------------------------------------------------------------------------- -- Functions ----------------------------------------------------------------------------*/ /** ** Release an order. ** ** @param order Pointer to order. */ static void ReleaseOrder(COrder *order) { if (order->Goal) { order->Goal->RefsDecrease(); order->Goal = NoUnitP; } } /** ** Release all orders of a unit. ** ** @param unit Pointer to unit. */ static void ReleaseOrders(CUnit *unit) { int n; if ((n = unit->OrderCount) > 1) { while (--n) { ReleaseOrder(unit->Orders[n]); delete unit->Orders[n]; unit->Orders.pop_back(); } unit->OrderCount = 1; } unit->OrderFlush = 1; // Order 0 must be stopped in the action loop. } /** ** Get next free order slot. ** ** @param unit pointer to unit. ** @param flush if true, flush order queue. ** ** @return Pointer to next free order slot. */ static COrder *GetNextOrder(CUnit *unit, int flush) { if (flush) { // empty command queue ReleaseOrders(unit); } if (unit->OrderCount == 0x7F) { return NULL; } unit->Orders.push_back(new COrder); return unit->Orders[(int)unit->OrderCount++]; } /** ** Remove an order from the list of orders pending ** ** @param unit pointer to unit ** @param order number of the order to remove */ static void RemoveOrder(CUnit *unit, int order) { int i; Assert(0 <= order && order < unit->OrderCount); if (order != 0) { delete unit->Orders[order]; } i = order; while (i < unit->OrderCount - 1) { unit->Orders[i] = unit->Orders[i + 1]; ++i; } if (unit->OrderCount > 1) { unit->Orders.pop_back(); --unit->OrderCount; if (order == 0) { unit->SubAction = 0; } } else { Assert(i == 0); unit->Orders[0]->Init(); unit->Orders[0]->Action = UnitActionStill; unit->SubAction = 0; } } /** ** Clear the saved action. ** ** @param unit Unit pointer, that get the saved action cleared. ** ** @note If we make a new order, we must clear any saved actions. ** @note Internal functions, must protect it, if needed. */ static void ClearSavedAction(CUnit *unit) { ReleaseOrder(&unit->SavedOrder); unit->SavedOrder.Init(); unit->SavedOrder.Action = UnitActionStill; // clear saved action } /*---------------------------------------------------------------------------- -- Commands ----------------------------------------------------------------------------*/ /** ** Stop unit. ** ** @param unit pointer to unit. */ void CommandStopUnit(CUnit *unit) { COrder *order; // Ignore that the unit could be removed. order = GetNextOrder(unit, FlushCommands); // Flush them. Assert(order); order->Init(); order->Action = UnitActionStill; ReleaseOrder(&unit->SavedOrder); ReleaseOrder(&unit->NewOrder); unit->SavedOrder = unit->NewOrder = *order; } /** ** Order an already formed Order structure ** ** @param unit pointer to unit ** @param cpyorder pointer to valid order ** @param flush if true, flush command queue. */ void CommandAnyOrder(CUnit *unit, COrder *cpyorder, int flush) { COrder *order; if (!(order = GetNextOrder(unit, flush))) { return; } *order = *cpyorder; if (order->Goal) { order->Goal->RefsIncrease(); } ClearSavedAction(unit); } /** ** Move an order in the order queue. ** ( Cannot move the order 0 ! ) ** ** @param unit pointer to unit ** @param src the order to move ** @param dst the new position of the order */ void CommandMoveOrder(CUnit *unit, int src, int dst) { COrder *tmp; int i; Assert(src != 0 && dst != 0 && src < unit->OrderCount && dst < unit->OrderCount); if (src == dst) { return; } if (src < dst) { tmp = unit->Orders[src]; for (i = src; i < dst; ++i) { unit->Orders[i] = unit->Orders[i+1]; } unit->Orders[dst] = tmp; } else { // dst < src tmp = unit->Orders[src]; for (i = src - 1 ; i >= dst; --i) { unit->Orders[i + 1] = unit->Orders[i]; } unit->Orders[dst] = tmp; } } /** ** Stand ground. ** ** @param unit pointer to unit. ** @param flush if true, flush command queue. */ void CommandStandGround(CUnit *unit, int flush) { COrder *order; // Ignore that the unit could be removed. if (unit->Type->Building) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionStandGround; ClearSavedAction(unit); } /** ** 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) { COrder *order; // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (!CanMove(unit)) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionFollow; // // Destination could be killed. // Should be handled in action, but is not possible! // Unit::Refs is used as timeout counter. // if (dest->Destroyed) { order->X = dest->X + dest->Type->TileWidth / 2; order->Y = dest->Y + dest->Type->TileHeight / 2; } else { order->Goal = dest; dest->RefsIncrease(); order->Range = 1; } } ClearSavedAction(unit); } /** ** Move unit to new position ** ** @param unit pointer to unit. ** @param x X map position to move to. ** @param y Y map position to move to. ** @param flush if true, flush command queue. */ void CommandMove(CUnit *unit, int x, int y, int flush) { COrder *order; Assert(x >= 0 && y >= 0 && x < Map.Info.MapWidth && y < Map.Info.MapHeight); // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (!CanMove(unit)) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionMove; order->X = x; order->Y = y; } ClearSavedAction(unit); } /** ** Repair unit ** ** @param unit pointer to unit. ** @param x X map position to repair. ** @param y Y map position to repair. ** @param dest or unit to be repaired. FIXME: not supported ** @param flush if true, flush command queue. */ void CommandRepair(CUnit *unit, int x, int y, CUnit *dest, int flush) { COrder *order; // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (unit->Type->Building) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionRepair; // // Destination could be killed. // Should be handled in action, but is not possible! // Unit::Refs is used as timeout counter. // if (dest) { if (dest->Destroyed) { order->X = dest->X + dest->Type->TileWidth / 2; order->Y = dest->Y + dest->Type->TileHeight / 2; } else { order->Goal = dest; dest->RefsIncrease(); order->Range = unit->Type->RepairRange; } } else { order->X = x; order->Y = y; } } ClearSavedAction(unit); } /** ** Auto repair. ** ** @param unit pointer to unit. ** @param on 1 for auto repair on, 0 for off. */ void CommandAutoRepair(CUnit *unit, int on) { // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { unit->AutoRepair = on; } } /** ** Attack with unit at new position ** ** @param unit pointer to unit. ** @param x X map position to attack. ** @param y Y map position to attack. ** @param attack or unit to be attacked. ** @param flush if true, flush command queue. */ void CommandAttack(CUnit *unit, int x, int y, CUnit *attack, int flush) { COrder *order; Assert(x >= 0 && y >= 0 && x < Map.Info.MapWidth && y < Map.Info.MapHeight); // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (!unit->Type->CanAttack) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionAttack; if (attack) { // // Destination could be killed. // Should be handled in action, but is not possible! // Unit::Refs is used as timeout counter. // if (attack->Destroyed) { order->X = attack->X + attack->Type->TileWidth / 2; order->Y = attack->Y + attack->Type->TileHeight / 2; } else { // Removed, Dying handled by action routine. order->Goal = attack; attack->RefsIncrease(); order->Range = unit->Stats->Variables[ATTACKRANGE_INDEX].Max; order->MinRange = unit->Type->MinAttackRange; } } else { order->X = x; order->Y = y; } } ClearSavedAction(unit); } /** ** Attack ground with unit. ** ** @param unit pointer to unit. ** @param x X map position to fire on. ** @param y Y map position to fire on. ** @param flush if true, flush command queue. */ void CommandAttackGround(CUnit *unit, int x, int y, int flush) { COrder *order; Assert(x >= 0 && y >= 0 && x < Map.Info.MapWidth && y < Map.Info.MapHeight); // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (unit->Type->Building) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionAttackGround; order->X = x; order->Y = y; order->Range = unit->Stats->Variables[ATTACKRANGE_INDEX].Max; order->MinRange = unit->Type->MinAttackRange; DebugPrint("FIXME this next\n"); } ClearSavedAction(unit); } /** ** Let a unit patrol from current to new position ** ** FIXME: want to support patroling between units. ** ** @param unit pointer to unit. ** @param x X map position to patrol between. ** @param y Y map position to patrol between. ** @param flush if true, flush command queue. */ void CommandPatrolUnit(CUnit *unit, int x, int y, int flush) { COrder *order; Assert(x >= 0 && y >= 0 && x < Map.Info.MapWidth && y < Map.Info.MapHeight); // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (!CanMove(unit)) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionPatrol; order->X = x; order->Y = y; Assert(!(unit->X & ~0xFFFF) && !(unit->Y & ~0xFFFF)); order->Arg1.Patrol.X = unit->X; order->Arg1.Patrol.Y = unit->Y; } ClearSavedAction(unit); } /** ** Board a transporter with unit. ** ** @param unit pointer to unit. ** @param dest unit to be boarded. ** @param flush if true, flush command queue. */ void CommandBoard(CUnit *unit, CUnit *dest, int flush) { COrder *order; // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { // // Destination could be killed. // Should be handled in action, but is not possible! // Unit::Refs is used as timeout counter. // if (dest->Destroyed) { return; } if (unit->Type->Building) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionBoard; order->Goal = dest; dest->RefsIncrease(); order->Range = 1; } ClearSavedAction(unit); } /** ** Unload a transporter. ** ** @param unit pointer to unit. ** @param x X map position to unload. ** @param y Y map position to unload. ** @param what unit to be unloaded, NoUnitP all. ** @param flush if true, flush command queue. */ void CommandUnload(CUnit *unit, int x, int y, CUnit *what, int flush) { COrder *order; // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionUnload; order->X = x; order->Y = y; // // Destination could be killed. // Should be handled in action, but is not possible! // Unit::Refs is used as timeout counter. // if (what && !what->Destroyed) { order->Goal = what; what->RefsIncrease(); } } ClearSavedAction(unit); } /** ** Send a unit building ** ** @param unit pointer to unit. ** @param x X map position to build. ** @param y Y map position to build. ** @param what Unit type to build. ** @param flush if true, flush command queue. */ void CommandBuildBuilding(CUnit *unit, int x, int y, CUnitType *what, int flush) { COrder *order; // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { if (unit->Type->Building) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionBuild; order->X = x; order->Y = y; order->Width = what->TileWidth; order->Height = what->TileHeight; if (what->BuilderOutside) { order->Range = unit->Type->RepairRange; } else { // If building inside, but be next to stop if (what->ShoreBuilding && unit->Type->UnitType == UnitTypeLand) { // Peon won't dive :-) order->Range = 1; } } order->Type = what; if (what->BuilderOutside) { order->MinRange = 1; } } ClearSavedAction(unit); } /** ** 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->Orders[0]->Action == UnitActionBuilt) { unit->Data.Built.Cancel = 1; } else { DebugPrint("Suicide unit ... \n"); LetUnitDie(unit); } ClearSavedAction(unit); } /** ** Send unit to harvest resources ** ** @param unit pointer to unit. ** @param dest destination unit. ** @param flush if true, flush command queue. */ void CommandResource(CUnit *unit, CUnit *dest, int flush) { COrder *order; // // Check if unit is still valid and Goal still alive? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie && !dest->Destroyed) { if (!unit->Type->Building && !unit->Type->Harvester) { ClearSavedAction(unit); return; } // FIXME: if low-level supports searching, pass NoUnitP down. if (unit->Type->Building) { // FIXME: should find a better way for pending orders. order = &unit->NewOrder; ReleaseOrder(order); } else if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionResource; order->Goal = dest; dest->RefsIncrease(); order->Range = 1; } ClearSavedAction(unit); } /** ** Building starts training a 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 flush) { COrder *order; // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { // // Check if enough resources remains? (NETWORK!) // FIXME: wrong if append to message queue!!! // if (unit->Player->CheckLimits(type) < 0 ) { return; } if (!(order = GetNextOrder(unit, 0))) { return; } order->Init(); order->Action = UnitActionTrain; order->Type = type; } ClearSavedAction(unit); } /** ** Cancel the training of a 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) { if (unit->Orders[0]->Action == UnitActionTrain) { unit->Player->RemoveFromUnitsConsumingResources(unit); } // Cancel All training while (unit->Orders[0]->Action == UnitActionTrain) { RemoveOrder(unit, 0); } unit->Data.Train.Ticks = 0; if (unit->Player == ThisPlayer && unit->Selected) { SelectedUnitChanged(); } } else if (unit->OrderCount <= 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) { // Still training this order, same unit? if (type && unit->Orders[slot]->Type != type) { // Different unit being trained return; } DebugPrint("Cancel training\n"); if (!slot) { // Canceled in work slot unit->Data.Train.Ticks = 0; unit->Player->RemoveFromUnitsConsumingResources(unit); } RemoveOrder(unit, slot); // // Update interface. // if (unit->Player == ThisPlayer && unit->Selected) { SelectedUnitChanged(); } } } /** ** Cast a spell at position or unit. ** ** @param unit Pointer to unit. ** @param x X map position to spell cast on. ** @param y Y map position to spell cast on. ** @param dest Spell cast on unit (if exist). ** @param spell Spell type pointer. ** @param flush If true, flush command queue. */ void CommandSpellCast(CUnit *unit, int x, int y, CUnit *dest, SpellType *spell, int flush) { COrder *order; Assert(x >= 0 && y >= 0 && x < Map.Info.MapWidth && y < Map.Info.MapHeight); DebugPrint(": %d casts %s at %d %d on %d\n" _C_ UnitNumber(unit) _C_ spell->Ident.c_str() _C_ x _C_ y _C_ dest ? UnitNumber(dest) : 0); Assert(unit->Type->CanCastSpell[spell->Slot]); // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { // FIXME: should I check here, if there is still enough mana? if (!(order = GetNextOrder(unit, flush))) { return; } order->Init(); order->Action = UnitActionSpellCast; order->Range = spell->Range; if (dest) { // // Destination could be killed. // Should be handled in action, but is not possible! // Unit::Refs is used as timeout counter. // if (dest->Destroyed) { // FIXME: where check if spell needs a unit as destination? // FIXME: dest->Type is now set to 0. maybe we shouldn't bother. order->X = dest->X /*+ dest->Type->TileWidth / 2*/ - order->Range; order->Y = dest->Y /*+ dest->Type->TileHeight / 2*/ - order->Range; order->Range <<= 1; } else { order->Goal = dest; dest->RefsIncrease(); } } else { order->Range = 1; order->X = x; order->Y = y; } order->Arg1.Spell = spell; } ClearSavedAction(unit); } /** ** Auto spell cast. ** ** @param unit pointer to unit. ** @param spellid Spell id. ** @param on 1 for auto cast on, 0 for off. */ void CommandAutoSpellCast(CUnit *unit, int spellid, int on) { // // Check if unit is still valid? (NETWORK!) // if (!unit->Removed && unit->Orders[0]->Action != UnitActionDie) { unit->AutoCastSpell[spellid] = on; } } /** ** Diplomacy changed. ** ** @param player Player which changes his state. ** @param state New diplomacy state. ** @param opponent Opponent. */ void CommandDiplomacy(int player, int state, int opponent) { switch (state) { case DiplomacyNeutral: Players[player].Enemy &= ~(1 << opponent); Players[player].Allied &= ~(1 << opponent); break; case DiplomacyAllied: Players[player].Enemy &= ~(1 << opponent); Players[player].Allied |= 1 << opponent; break; case DiplomacyEnemy: Players[player].Enemy |= 1 << opponent; Players[player].Allied &= ~(1 << opponent); break; case DiplomacyCrazy: Players[player].Enemy |= 1 << opponent; Players[player].Allied |= 1 << opponent; break; } // FIXME: Should we display a message? } /** ** Shared vision changed. ** ** @param player Player which changes his state. ** @param state New shared vision state. ** @param opponent Opponent. */ void CommandSharedVision(int player, bool state, int opponent) { int before; int after; int x; int y; int i; CMapField *mf; // // Do a real hardcore seen recount. First we unmark EVERYTHING. // for (i = 0; i < NumUnits; ++i) { if (!Units[i]->Destroyed) { MapUnmarkUnitSight(Units[i]); } } // // Compute Before and after. // before = Players[player].IsBothSharedVision(&Players[opponent]); if (state == false) { Players[player].SharedVision &= ~(1 << opponent); } else { Players[player].SharedVision |= (1 << opponent); } after = Players[player].IsBothSharedVision(&Players[opponent]); if (before && !after) { // // Don't share vision anymore. Give each other explored terrain for good-bye. // for (x = 0; x < Map.Info.MapWidth; ++x) { for (y = 0; y < Map.Info.MapHeight; ++y) { mf = Map.Field(x, y); if (mf->Visible[player] && !mf->Visible[opponent]) { mf->Visible[opponent] = 1; if (opponent == ThisPlayer->Index) { Map.MarkSeenTile(x, y); } } if (mf->Visible[opponent] && !mf->Visible[player]) { mf->Visible[player] = 1; if (player == ThisPlayer->Index) { Map.MarkSeenTile(x, y); } } } } } // // Do a real hardcore seen recount. Now we remark EVERYTHING // for (i = 0; i < NumUnits; ++i) { if (!Units[i]->Destroyed) { MapMarkUnitSight(Units[i]); } } } /** ** Player quit. ** ** @param player Player number that quit. */ void CommandQuit(int player) { int i; // Set player to neutral, remove allied/enemy/shared vision status // If the player doesn't have any units then this is pointless? Players[player].Type = PlayerNeutral; for (i = 0; i < NumPlayers; ++i) { if (i != player && Players[i].Team != Players[player].Team) { Players[i].Allied &= ~(1 << player); Players[i].Enemy &= ~(1 << player); Players[player].Enemy &= ~(1 << i); Players[player].Allied &= ~(1 << i); // We clear Shared vision by sending fake shared vision commands. // We do this because Shared vision is a bit complex. CommandSharedVision(i, 0, player); CommandSharedVision(player, 0, i); // Remove Selection from Quit Player ChangeTeamSelectedUnits(&Players[player], NULL, 0, 0); } } if (Players[player].TotalNumUnits != 0) { SetMessage(_("Player \"%s\" has left the game"), Players[player].Name.c_str()); } else { SetMessage(_("Player \"%s\" has been killed"), Players[player].Name.c_str()); } } //@}