Gobligine/src/action/action_resource.cpp
cybermind a2ef204b23 [-] Fixed crash when AI player tried to build a resource depot
[-] Fixed polymorph spell for air units
[-] Fixed rescued unit ucon drawing
2015-05-20 11:58:47 +05:00

1358 lines
36 KiB
C++

// _________ __ __
// / _____// |_____________ _/ |______ ____ __ __ ______
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
// \/ \/ \//_____/ \/
// ______________________ ______________________
// T H E W A R B E G I N S
// Stratagus - A free fantasy real time strategy game engine
//
/**@name action_resource.cpp - The generic resource action. */
//
// (c) Copyright 2001-2005 by Crestez Leonard 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.
//
//@{
/*----------------------------------------------------------------------------
-- Includes
----------------------------------------------------------------------------*/
#include "stratagus.h"
#include "action/action_resource.h"
#include "ai.h"
#include "animation.h"
#include "interface.h"
#include "iolib.h"
#include "map.h"
#include "pathfinder.h"
#include "player.h"
#include "script.h"
#include "sound.h"
#include "tileset.h"
#include "translate.h"
#include "ui.h"
#include "unit.h"
#include "unit_find.h"
#include "unittype.h"
#include "video.h"
#include "../ai/ai_local.h"
/*----------------------------------------------------------------------------
-- Declarations
----------------------------------------------------------------------------*/
#define SUB_START_RESOURCE 0
#define SUB_MOVE_TO_RESOURCE 5
#define SUB_UNREACHABLE_RESOURCE 31
#define SUB_START_GATHERING 55
#define SUB_GATHER_RESOURCE 60
#define SUB_STOP_GATHERING 65
#define SUB_MOVE_TO_DEPOT 70
#define SUB_UNREACHABLE_DEPOT 100
#define SUB_RETURN_RESOURCE 120
/*----------------------------------------------------------------------------
-- Functions
----------------------------------------------------------------------------*/
class NearReachableTerrainFinder
{
public:
NearReachableTerrainFinder(const CPlayer &player, int maxDist, int movemask, int resmask, Vec2i *resPos) :
player(player), maxDist(maxDist), movemask(movemask), resmask(resmask), resPos(resPos) {}
VisitResult Visit(TerrainTraversal &terrainTraversal, const Vec2i &pos, const Vec2i &from);
private:
const CPlayer &player;
int maxDist;
int movemask;
int resmask;
Vec2i *resPos;
};
VisitResult NearReachableTerrainFinder::Visit(TerrainTraversal &terrainTraversal, const Vec2i &pos, const Vec2i &from)
{
if (!player.AiEnabled && !Map.Field(pos)->playerInfo.IsExplored(player)) {
return VisitResult_DeadEnd;
}
// Look if found what was required.
if (CanMoveToMask(pos, movemask)) {
if (resPos) {
*resPos = from;
}
return VisitResult_Finished;
}
if (Map.Field(pos)->CheckMask(resmask)) { // reachable
if (terrainTraversal.Get(pos) <= maxDist) {
return VisitResult_Ok;
} else {
return VisitResult_DeadEnd;
}
} else { // unreachable
return VisitResult_DeadEnd;
}
}
static bool FindNearestReachableTerrainType(int movemask, int resmask, int range,
const CPlayer &player, const Vec2i &startPos, Vec2i *terrainPos)
{
TerrainTraversal terrainTraversal;
terrainTraversal.SetSize(Map.Info.MapWidth, Map.Info.MapHeight);
terrainTraversal.Init();
Assert(Map.Field(startPos)->CheckMask(resmask));
terrainTraversal.PushPos(startPos);
NearReachableTerrainFinder nearReachableTerrainFinder(player, range, movemask, resmask, terrainPos);
return terrainTraversal.Run(nearReachableTerrainFinder);
}
/* static */ COrder *COrder::NewActionResource(CUnit &harvester, const Vec2i &pos)
{
COrder_Resource *order = new COrder_Resource(harvester);
Vec2i ressourceLoc;
// Find the closest piece of wood next to a tile where the unit can move
if (!FindNearestReachableTerrainType(harvester.Type->MovementMask, MapFieldForest, 20, *harvester.Player, pos, &ressourceLoc)) {
DebugPrint("FIXME: Give up???\n");
ressourceLoc = pos;
}
order->goalPos = ressourceLoc;
order->CurrentResource = WoodCost; // Hard-coded resource.
return order;
}
/* static */ COrder *COrder::NewActionResource(CUnit &harvester, CUnit &mine)
{
COrder_Resource *order = new COrder_Resource(harvester);
order->SetGoal(&mine);
order->Resource.Mine = &mine;
order->Resource.Pos = mine.tilePos + mine.Type->GetHalfTileSize();
order->CurrentResource = mine.Type->GivesResource;
return order;
}
/* static */ COrder *COrder::NewActionReturnGoods(CUnit &harvester, CUnit *depot)
{
COrder_Resource *order = new COrder_Resource(harvester);
// Destination could be killed. NETWORK!
if (depot && depot->Destroyed) {
depot = NULL;
}
order->CurrentResource = harvester.CurrentResource;
order->DoneHarvesting = true;
if (depot == NULL) {
depot = FindDeposit(harvester, 1000, harvester.CurrentResource);
}
if (depot) {
order->Depot = depot;
order->UnitGotoGoal(harvester, depot, SUB_MOVE_TO_DEPOT);
} else {
order->State = SUB_UNREACHABLE_DEPOT;
order->goalPos = harvester.tilePos;
}
return order;
}
Vec2i COrder_Resource::GetHarvestLocation() const
{
if (this->Resource.Mine != NULL) {
return this->Resource.Mine->tilePos;
} else {
return this->Resource.Pos;
}
}
bool COrder_Resource::IsGatheringStarted() const
{
return this->State > SUB_START_GATHERING;
}
bool COrder_Resource::IsGatheringFinished() const
{
return this->State >= SUB_STOP_GATHERING;
}
bool COrder_Resource::IsGatheringWaiting() const
{
return this->State == SUB_START_GATHERING && this->worker->Wait != 0;
}
COrder_Resource::~COrder_Resource()
{
CUnit *mine = this->Resource.Mine;
if (mine && mine->IsAlive()) {
worker->DeAssignWorkerFromMine(*mine);
}
CUnit *goal = this->GetGoal();
if (goal) {
// If mining decrease the active count on the resource.
if (this->State == SUB_GATHER_RESOURCE) {
goal->Resource.Active--;
Assert(goal->Resource.Active >= 0);
}
}
}
/* virtual */ void COrder_Resource::Save(CFile &file, const CUnit &unit) const
{
file.printf("{\"action-resource\",");
if (this->Finished) {
file.printf(" \"finished\",");
}
if (this->HasGoal()) {
file.printf(" \"goal\", \"%s\",", UnitReference(this->GetGoal()).c_str());
}
file.printf(" \"tile\", {%d, %d},", this->goalPos.x, this->goalPos.y);
Assert(this->worker != NULL && worker->IsAlive());
file.printf(" \"worker\", \"%s\",", UnitReference(worker).c_str());
file.printf(" \"current-res\", %d,", this->CurrentResource);
file.printf(" \"res-pos\", {%d, %d},", this->Resource.Pos.x, this->Resource.Pos.y);
if (this->Resource.Mine != NULL) {
file.printf(" \"res-mine\", \"%s\",", UnitReference(this->Resource.Mine).c_str());
}
if (this->Depot != NULL) {
file.printf(" \"res-depot\", \"%s\",", UnitReference(this->Depot).c_str());
}
if (this->DoneHarvesting) {
file.printf(" \"done-harvesting\",");
}
file.printf(" \"timetoharvest\", %d,", this->TimeToHarvest);
file.printf(" \"state\", %d", this->State);
file.printf("}");
}
/* virtual */ bool COrder_Resource::ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit)
{
if (!strcmp(value, "current-res")) {
++j;
this->CurrentResource = LuaToNumber(l, -1, j + 1);
} else if (!strcmp(value, "done-harvesting")) {
this->DoneHarvesting = true;
} else if (!strcmp(value, "res-depot")) {
++j;
lua_rawgeti(l, -1, j + 1);
this->Depot = CclGetUnitFromRef(l);
lua_pop(l, 1);
} else if (!strcmp(value, "res-mine")) {
++j;
lua_rawgeti(l, -1, j + 1);
this->Resource.Mine = CclGetUnitFromRef(l);
lua_pop(l, 1);
} else if (!strcmp(value, "res-pos")) {
++j;
lua_rawgeti(l, -1, j + 1);
CclGetPos(l, &this->Resource.Pos.x , &this->Resource.Pos.y);
lua_pop(l, 1);
} else if (!strcmp(value, "state")) {
++j;
this->State = LuaToNumber(l, -1, j + 1);
} else if (!strcmp(value, "timetoharvest")) {
++j;
this->TimeToHarvest = LuaToNumber(l, -1, j + 1);
} else if (!strcmp(value, "worker")) {
++j;
lua_rawgeti(l, -1, j + 1);
this->worker = CclGetUnitFromRef(l);
lua_pop(l, 1);
} else if (!strcmp(value, "tile")) {
++j;
lua_rawgeti(l, -1, j + 1);
CclGetPos(l, &this->goalPos.x , &this->goalPos.y);
lua_pop(l, 1);
} else {
return false;
}
return true;
}
/* virtual */ bool COrder_Resource::IsValid() const
{
return true;
}
/* virtual */ PixelPos COrder_Resource::Show(const CViewport &vp, const PixelPos &lastScreenPos) const
{
PixelPos targetPos;
if (this->HasGoal()) {
targetPos = vp.MapToScreenPixelPos(this->GetGoal()->GetMapPixelPosCenter());
} else {
targetPos = vp.TilePosToScreen_Center(this->goalPos);
}
Video.FillCircleClip(ColorYellow, lastScreenPos, 2);
Video.DrawLineClip(ColorYellow, lastScreenPos, targetPos);
Video.FillCircleClip(ColorYellow, targetPos, 3);
return targetPos;
}
/* virtual */ void COrder_Resource::UpdatePathFinderData(PathFinderInput &input)
{
input.SetMinRange(0);
input.SetMaxRange(1);
Vec2i tileSize;
if (this->HasGoal()) {
CUnit *goal = this->GetGoal();
tileSize.x = goal->Type->TileWidth;
tileSize.y = goal->Type->TileHeight;
input.SetGoal(goal->tilePos, tileSize);
} else {
tileSize.x = 0;
tileSize.y = 0;
input.SetGoal(this->goalPos, tileSize);
}
}
/* virtual */ bool COrder_Resource::OnAiHitUnit(CUnit &unit, CUnit *attacker, int /* damage*/)
{
if (this->IsGatheringFinished()) {
// Normal return to depot
return true;
}
if (this->IsGatheringStarted() && unit.ResourcesHeld > 0) {
// escape to Depot with what you have
this->DoneHarvesting = true;
return true;
}
return false;
}
/**
** Move unit to terrain.
**
** @return 1 if reached, -1 if unreacheable, 0 if on the way.
*/
int COrder_Resource::MoveToResource_Terrain(CUnit &unit)
{
Vec2i pos = this->goalPos;
// Wood gone, look somewhere else.
if ((Map.Info.IsPointOnMap(pos) == false || Map.Field(pos)->IsTerrainResourceOnMap(CurrentResource) == false)
&& (!unit.IX) && (!unit.IY)) {
if (!FindTerrainType(unit.Type->MovementMask, MapFieldForest, 16, *unit.Player, this->goalPos, &pos)) {
// no wood in range
return -1;
} else {
this->goalPos = pos;
}
}
switch (DoActionMove(unit)) {
case PF_UNREACHABLE:
unit.Wait = 10;
if (unit.Player->AiEnabled) {
this->Range++;
if (this->Range >= 5) {
this->Range = 0;
AiCanNotMove(unit);
}
}
if (FindTerrainType(unit.Type->MovementMask, MapFieldForest, 9999, *unit.Player, unit.tilePos, &pos)) {
this->goalPos = pos;
DebugPrint("Found a better place to harvest %d,%d\n" _C_ pos.x _C_ pos.y);
// FIXME: can't this overflow? It really shouldn't, since
// x and y are really supossed to be reachable, checked thorugh a flood fill.
// I don't know, sometimes stuff happens.
return 0;
}
return -1;
case PF_REACHED:
return 1;
case PF_WAIT:
if (unit.Player->AiEnabled) {
this->Range++;
if (this->Range >= 5) {
this->Range = 0;
AiCanNotMove(unit);
}
}
default:
return 0;
}
}
/**
** Move unit to unit resource.
**
** @return 1 if reached, -1 if unreacheable, 0 if on the way.
*/
int COrder_Resource::MoveToResource_Unit(CUnit &unit)
{
const CUnit *goal = this->GetGoal();
Assert(goal);
switch (DoActionMove(unit)) { // reached end-point?
case PF_UNREACHABLE:
return -1;
case PF_REACHED:
break;
case PF_WAIT:
if (unit.Player->AiEnabled) {
this->Range++;
if (this->Range >= 5) {
this->Range = 0;
AiCanNotMove(unit);
}
}
default:
// Goal gone or something.
if (unit.Anim.Unbreakable || goal->IsVisibleAsGoal(*unit.Player)) {
return 0;
}
break;
}
return 1;
}
/**
** Move unit to resource.
**
** @param unit Pointer to unit.
**
** @return 1 if reached, -1 if unreacheable, 0 if on the way.
*/
int COrder_Resource::MoveToResource(CUnit &unit)
{
const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource];
if (resinfo.TerrainHarvester) {
return MoveToResource_Terrain(unit);
} else {
return MoveToResource_Unit(unit);
}
}
void COrder_Resource::UnitGotoGoal(CUnit &unit, CUnit *const goal, int state)
{
if (this->GetGoal() != goal) {
this->SetGoal(goal);
if (goal) {
this->goalPos.x = this->goalPos.y = -1;
}
}
this->State = state;
if (state == SUB_MOVE_TO_DEPOT || state == SUB_MOVE_TO_RESOURCE) {
unit.pathFinderData->output.Cycles = 0; //moving counter
}
}
/**
** 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);
if (resinfo.TerrainHarvester) {
// 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.
UnitHeadingFromDeltaXY(unit, goal->tilePos - unit.tilePos + goal->Type->GetHalfTileSize());
// 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.
// FIXME: Think minerals in st*rcr*ft!!
// However the CPU usage is really low (no pathfinding stuff).
unit.Wait = 10;
return 0;
}
// Place unit inside the resource
if (!resinfo.HarvestFromOutside) {
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) {
this->TimeToHarvest = std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]);
} else {
this->TimeToHarvest = 1;
}
this->DoneHarvesting = 0;
return 1;
}
/**
** Animate a unit that is harvesting
**
** @param unit Unit to animate
*/
static void AnimateActionHarvest(CUnit &unit)
{
Assert(unit.Type->Animations->Harvest[unit.CurrentResource]);
UnitShowAnimation(unit, unit.Type->Animations->Harvest[unit.CurrentResource]);
}
/**
** Find something else to do when the resource is exhausted.
** This is called from GatherResource when the resource is empty.
**
** @param unit pointer to harvester unit.
** @param source pointer to resource unit.
*/
void COrder_Resource::LoseResource(CUnit &unit, CUnit &source)
{
CUnit *depot;
const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource];
Assert((unit.Container == &source && !resinfo.HarvestFromOutside)
|| (!unit.Container && resinfo.HarvestFromOutside));
if (resinfo.HarvestFromOutside) {
this->ClearGoal();
--source.Resource.Active;
}
// Continue to harvest if we aren't fully loaded
if (resinfo.HarvestFromOutside && unit.ResourcesHeld < resinfo.ResourceCapacity) {
CUnit *goal = UnitFindResource(unit, unit, 15, this->CurrentResource, 1);
if (goal) {
this->goalPos.x = -1;
this->goalPos.y = -1;
this->State = SUB_START_RESOURCE;
this->SetGoal(goal);
return;
}
}
// If we are fully loaded first search for a depot.
if (unit.ResourcesHeld && (depot = FindDeposit(unit, 1000, unit.CurrentResource))) {
if (unit.Container) {
DropOutNearest(unit, depot->tilePos + depot->Type->GetHalfTileSize(), &source);
}
// Remember where it mined, so it can look around for another resource.
//
//FIXME!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//unit.CurrentOrder()->Arg1.ResourcePos = (unit.X << 16) | unit.Y;
this->DoneHarvesting = true;
UnitGotoGoal(unit, depot, SUB_MOVE_TO_DEPOT);
DebugPrint("%d: Worker %d report: Resource is exhausted, Going to depot\n"
_C_ unit.Player->Index _C_ UnitNumber(unit));
return;
}
// No depot found, or harvester empty
// Dump the unit outside and look for something to do.
if (unit.Container) {
Assert(!resinfo.HarvestFromOutside);
DropOutOnSide(unit, LookingW, &source);
}
this->goalPos.x = -1;
this->goalPos.y = -1;
//use depot as goal
depot = UnitFindResource(unit, unit, 15, this->CurrentResource, unit.Player->AiEnabled);
if (depot) {
DebugPrint("%d: Worker %d report: Resource is exhausted, Found another resource.\n"
_C_ unit.Player->Index _C_ UnitNumber(unit));
this->State = SUB_START_RESOURCE;
this->SetGoal(depot);
} else {
DebugPrint("%d: Worker %d report: Resource is exhausted, Just sits around confused.\n"
_C_ unit.Player->Index _C_ UnitNumber(unit));
this->Finished = true;
}
}
/**
** 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;
if (resinfo.HarvestFromOutside || resinfo.TerrainHarvester) {
AnimateActionHarvest(unit);
} else {
unit.Anim.CurrAnim = NULL;
}
this->TimeToHarvest--;
if (this->DoneHarvesting) {
Assert(resinfo.HarvestFromOutside || resinfo.TerrainHarvester);
return !unit.Anim.Unbreakable;
}
// Target gone?
if (resinfo.TerrainHarvester && !Map.Field(this->goalPos)->IsTerrainResourceOnMap(this->CurrentResource)) {
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) {
this->TimeToHarvest += std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]);
} else {
this->TimeToHarvest += 1;
}
// Calculate how much we can load.
if (resinfo.ResourceStep) {
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;
}
if (resinfo.TerrainHarvester) {
unit.ResourcesHeld += addload;
if (addload && unit.ResourcesHeld == resinfo.ResourceCapacity) {
Map.ClearWoodTile(this->goalPos);
}
} else {
if (resinfo.HarvestFromOutside) {
source = this->GetGoal();
} else {
source = unit.Container;
}
Assert(source);
Assert(source->ResourcesHeld <= 655350);
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
&& source->Variable[GIVERESOURCE_INDEX].Max > DefaultIncomes[this->CurrentResource]) {
unit.Player->Notify(NotifyYellow, source->tilePos, _("%s has collapsed!"), source->Type->Name.c_str());
}
LetUnitDie(*source);
// FIXME: make the workers inside look for a new resource.
}
source = NULL;
return 0;
}
}
if (resinfo.TerrainHarvester) {
if (unit.ResourcesHeld == resinfo.ResourceCapacity) {
// Mark as complete.
this->DoneHarvesting = true;
}
return 0;
} else {
if (resinfo.HarvestFromOutside) {
if ((unit.ResourcesHeld == resinfo.ResourceCapacity) || (source == NULL)) {
// Mark as complete.
this->DoneHarvesting = true;
}
return 0;
} else {
return unit.ResourcesHeld == resinfo.ResourceCapacity && source;
}
}
}
return 0;
}
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;
}
/**
** Stop gathering from the resource, go home.
**
** @param unit Poiner to unit.
**
** @return TRUE if ready, otherwise FALSE.
*/
int COrder_Resource::StopGathering(CUnit &unit)
{
CUnit *source = 0;
const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource];
if (!resinfo.TerrainHarvester) {
if (resinfo.HarvestFromOutside) {
source = this->GetGoal();
this->ClearGoal();
} else {
source = unit.Container;
}
source->Resource.Active--;
Assert(source->Resource.Active >= 0);
//Store resource position.
this->Resource.Mine = source;
if (Preference.MineNotifications && unit.Player->Index == ThisPlayer->Index
&& source->IsAlive()
&& !source->MineLow
&& source->ResourcesHeld * 100 / source->Variable[GIVERESOURCE_INDEX].Max <= 10
&& source->Variable[GIVERESOURCE_INDEX].Max > DefaultIncomes[this->CurrentResource]) {
unit.Player->Notify(NotifyYellow, source->tilePos, _("%s is running low!"), source->Type->Name.c_str());
source->MineLow = 1;
}
if (source->Type->MaxOnBoard) {
int count = 0;
CUnit *worker = source->Resource.Workers;
CUnit *next = NULL;
for (; NULL != worker; worker = worker->NextWorker) {
Assert(worker->CurrentAction() == UnitActionResource);
COrder_Resource &order = *static_cast<COrder_Resource *>(worker->CurrentOrder());
if (worker != &unit && order.IsGatheringWaiting()) {
count++;
if (next) {
if (next->Wait > worker->Wait) {
next = worker;
}
} else {
next = worker;
}
}
}
if (next) {
if (!unit.Player->AiEnabled) {
DebugPrint("%d: Worker %d report: Unfreez resource gathering of %d <Wait %d> on %d [Assigned: %d Waiting %d].\n"
_C_ unit.Player->Index _C_ UnitNumber(unit)
_C_ UnitNumber(*next) _C_ next->Wait
_C_ UnitNumber(*source) _C_ source->Resource.Assigned
_C_ count);
}
next->Wait = 0;
//source->Data.Resource.Waiting = count - 1;
//Assert(source->Data.Resource.Assigned >= source->Data.Resource.Waiting);
//StartGathering(next);
}
}
} else {
// Store resource position.
this->Resource.Pos = unit.tilePos;
Assert(this->Resource.Mine == NULL);
}
#ifdef DEBUG
if (!unit.ResourcesHeld) {
DebugPrint("Unit %d is empty???\n" _C_ UnitNumber(unit));
}
#endif
// Find and send to resource deposit.
CUnit *depot = FindDeposit(unit, 1000, unit.CurrentResource);
if (!depot || !unit.ResourcesHeld || this->Finished) {
if (!(resinfo.HarvestFromOutside || resinfo.TerrainHarvester)) {
Assert(unit.Container);
DropOutOnSide(unit, LookingW, source);
}
CUnit *mine = this->Resource.Mine;
if (mine) {
unit.DeAssignWorkerFromMine(*mine);
this->Resource.Mine = NULL;
}
DebugPrint("%d: Worker %d report: Can't find a resource [%d] deposit.\n"
_C_ unit.Player->Index _C_ UnitNumber(unit) _C_ unit.CurrentResource);
this->Finished = true;
return 0;
} else {
if (!(resinfo.HarvestFromOutside || resinfo.TerrainHarvester)) {
Assert(unit.Container);
DropOutNearest(unit, depot->tilePos + depot->Type->GetHalfTileSize(), source);
}
UnitGotoGoal(unit, depot, SUB_MOVE_TO_DEPOT);
}
if (IsOnlySelected(unit)) {
SelectedUnitChanged();
}
#if 1
return 1;
#endif
}
/**
** Move to resource depot
**
** @param unit Pointer to unit.
**
** @return TRUE if reached, otherwise FALSE.
*/
int COrder_Resource::MoveToDepot(CUnit &unit)
{
const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource];
CUnit &goal = *this->GetGoal();
CPlayer &player = *unit.Player;
Assert(&goal);
switch (DoActionMove(unit)) { // reached end-point?
case PF_UNREACHABLE:
return -1;
case PF_REACHED:
break;
case PF_WAIT:
if (unit.Player->AiEnabled) {
this->Range++;
if (this->Range >= 5) {
this->Range = 0;
AiCanNotMove(unit);
}
}
default:
if (unit.Anim.Unbreakable || goal.IsVisibleAsGoal(player)) {
return 0;
}
break;
}
//
// Target is dead, stop getting resources.
//
if (!goal.IsVisibleAsGoal(player)) {
DebugPrint("%d: Worker %d report: Destroyed depot\n" _C_ player.Index _C_ UnitNumber(unit));
unit.CurrentOrder()->ClearGoal();
CUnit *depot = FindDeposit(unit, 1000, unit.CurrentResource);
if (depot) {
UnitGotoGoal(unit, depot, SUB_MOVE_TO_DEPOT);
DebugPrint("%d: Worker %d report: Going to new deposit.\n" _C_ player.Index _C_ UnitNumber(unit));
} else {
DebugPrint("%d: Worker %d report: Can't find a new resource deposit.\n"
_C_ player.Index _C_ UnitNumber(unit));
// FIXME: perhaps we should choose an alternative
this->Finished = true;
}
return 0;
}
// If resource depot is still under construction, wait!
if (goal.CurrentAction() == UnitActionBuilt) {
unit.Wait = 10;
return 0;
}
this->ClearGoal();
unit.Wait = resinfo.WaitAtDepot;
// Place unit inside the depot
if (unit.Wait) {
int selected = unit.Selected;
unit.Remove(&goal);
if (selected && !Preference.DeselectInMine) {
unit.Removed = 0;
SelectUnit(unit);
SelectionChanged();
unit.Removed = 1;
}
unit.Anim.CurrAnim = NULL;
}
// Update resource.
const int rindex = resinfo.FinalResource;
player.ChangeResource(rindex, (unit.ResourcesHeld * player.Incomes[rindex]) / 100, true);
player.TotalResources[rindex] += (unit.ResourcesHeld * player.Incomes[rindex]) / 100;
unit.ResourcesHeld = 0;
unit.CurrentResource = 0;
if (unit.Wait) {
unit.Wait /= std::max(1, unit.Player->SpeedResourcesReturn[resinfo.ResourceId] / SPEEDUP_FACTOR);
if (unit.Wait) {
unit.Wait--;
}
}
return 1;
}
/**
** Wait in depot, for the resources stored.
**
** @param unit Pointer to unit.
**
** @return TRUE if ready, otherwise FALSE.
*/
bool COrder_Resource::WaitInDepot(CUnit &unit)
{
const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource];
const CUnit *depot = ResourceDepositOnMap(unit.tilePos, resinfo.ResourceId);
//Assert(depot);
// Range hardcoded. don't stray too far though
if (resinfo.TerrainHarvester) {
Vec2i pos = this->Resource.Pos;
if (FindTerrainType(unit.Type->MovementMask, MapFieldForest, 10, *unit.Player, pos, &pos)) {
if (depot) {
DropOutNearest(unit, pos, depot);
}
this->goalPos = pos;
} else {
if (depot) {
DropOutOnSide(unit, LookingW, depot);
}
this->Finished = true;
return false;
}
} else {
const unsigned int tooManyWorkers = 15;
CUnit *mine = this->Resource.Mine;
const int range = 15;
CUnit *newdepot = NULL;
CUnit *goal = NULL;
const bool longWay = unit.pathFinderData->output.Cycles > 500;
if (unit.Player->AiEnabled && AiPlayer && AiPlayer->BuildDepots) {
// If the depot is overused, we need first to try to switch into another depot
// Use depot's ref counter for that
if (longWay || !mine || (depot->Refs > tooManyWorkers)) {
newdepot = AiGetSuitableDepot(unit, *depot, &goal);
if (newdepot == NULL && longWay) {
// We need a new depot
AiNewDepotRequest(unit);
}
}
}
// If goal is not NULL, then we got it in AiGetSuitableDepot
if (!goal) {
goal = UnitFindResource(unit, newdepot ? *newdepot : (mine ? *mine : unit), mine ? range : 1000,
this->CurrentResource, unit.Player->AiEnabled, newdepot ? newdepot : depot);
}
if (goal) {
if (depot) {
DropOutNearest(unit, goal->tilePos + goal->Type->GetHalfTileSize(), depot);
}
if (goal != mine) {
if (mine) {
unit.DeAssignWorkerFromMine(*mine);
}
unit.AssignWorkerToMine(*goal);
this->Resource.Mine = goal;
}
this->SetGoal(goal);
this->goalPos.x = this->goalPos.y = -1;
} else {
#ifdef DEBUG
const Vec2i &pos = mine ? mine->tilePos : unit.tilePos;
DebugPrint("%d: Worker %d report: [%d,%d] Resource gone near [%d,%d] in range %d. Sit and play dumb.\n"
_C_ unit.Player->Index _C_ UnitNumber(unit)
_C_ unit.tilePos.x _C_ unit.tilePos.y
_C_ pos.x _C_ pos.y _C_ range);
#endif // DEBUG
if (depot) {
DropOutOnSide(unit, LookingW, depot);
}
if (mine) {
unit.DeAssignWorkerFromMine(*mine);
this->Resource.Mine = NULL;
}
this->Finished = true;
return false;
}
}
return true;
}
void COrder_Resource::DropResource(CUnit &unit)
{
if (unit.CurrentResource) {
const ResourceInfo &resinfo = *unit.Type->ResInfo[unit.CurrentResource];
if (!resinfo.TerrainHarvester) {
CUnit *mine = this->Resource.Mine;
if (mine) {
unit.DeAssignWorkerFromMine(*mine);
}
}
//fast clean both resource data: pos and mine
this->Resource.Mine = NULL;
unit.CurrentResource = 0;
unit.ResourcesHeld = 0;
}
}
/**
** Give up on gathering.
**
** @param unit Pointer to unit.
*/
void COrder_Resource::ResourceGiveUp(CUnit &unit)
{
DebugPrint("%d: Worker %d report: Gave up on resource gathering.\n" _C_ unit.Player->Index _C_ UnitNumber(unit));
if (this->HasGoal()) {
DropResource(unit);
this->ClearGoal();
}
this->Finished = true;
}
/**
** Try to find another resource before give up
**
** return false if failed, true otherwise.
*/
bool COrder_Resource::FindAnotherResource(CUnit &unit)
{
if (this->CurrentResource) {
const ResourceInfo *resinfo = unit.Type->ResInfo[this->CurrentResource];
if (resinfo) {
if (!resinfo->TerrainHarvester) {
CUnit *newGoal = UnitFindResource(unit, this->Resource.Mine ? *this->Resource.Mine : unit, 8, this->CurrentResource, 1);
if (newGoal) {
CUnit *mine = this->Resource.Mine;
if (mine) {
unit.DeAssignWorkerFromMine(*mine);
}
unit.AssignWorkerToMine(*newGoal);
this->Resource.Mine = newGoal;
this->goalPos.x = -1;
this->goalPos.y = -1;
this->State = SUB_MOVE_TO_RESOURCE;
this->SetGoal(newGoal);
return true;
}
} else {
Vec2i resPos;
if (FindTerrainType(unit.Type->MovementMask, MapFieldForest, 8, *unit.Player, unit.tilePos, &resPos)) {
this->goalPos = resPos;
this->State = SUB_MOVE_TO_RESOURCE;
DebugPrint("Found a better place to harvest %d,%d\n" _C_ resPos.x _C_ resPos.y);
return true;
}
}
}
}
return false;
}
/**
** Initialize
**
** return false if action is canceled, true otherwise.
*/
bool COrder_Resource::ActionResourceInit(CUnit &unit)
{
Assert(this->State == SUB_START_RESOURCE);
this->Range = 0;
CUnit *const goal = this->GetGoal();
CUnit *mine = this->Resource.Mine;
if (mine) {
unit.DeAssignWorkerFromMine(*mine);
this->Resource.Mine = NULL;
}
if (goal && goal->IsAlive() == false) {
return false;
}
if (goal && goal->CurrentAction() != UnitActionBuilt) {
unit.AssignWorkerToMine(*goal);
this->Resource.Mine = goal;
}
UnitGotoGoal(unit, goal, SUB_MOVE_TO_RESOURCE);
return true;
}
/**
** Control the unit action: getting a resource.
**
** This the generic function for oil, gold, ...
**
** @param unit Pointer to unit.
*/
void COrder_Resource::Execute(CUnit &unit)
{
// can be different by Cloning (trained unit)...
this->worker = &unit;
if (unit.Wait) {
if (!unit.Waiting) {
unit.Waiting = 1;
unit.WaitBackup = unit.Anim;
}
UnitShowAnimation(unit, unit.Type->Animations->Still);
unit.Wait--;
return;
}
if (unit.Waiting) {
unit.Anim = unit.WaitBackup;
unit.Waiting = 0;
}
// Let's start mining.
if (this->State == SUB_START_RESOURCE) {
if (ActionResourceInit(unit) == false) {
ResourceGiveUp(unit);
return;
}
}
// Move to the resource location.
if (SUB_MOVE_TO_RESOURCE <= this->State && this->State < SUB_UNREACHABLE_RESOURCE) {
const int ret = MoveToResource(unit);
switch (ret) {
case -1: { // Can't Reach
this->State++;
unit.Wait = 5;
return;
}
case 1: { // Reached
this->State = SUB_START_GATHERING;
break;
}
case 0: // Move along.
return;
default: {
Assert(0);
break;
}
}
}
// Resource seems to be unreachable
if (this->State == SUB_UNREACHABLE_RESOURCE) {
if (this->FindAnotherResource(unit) == false) {
ResourceGiveUp(unit);
return;
}
}
// Start gathering the resource
if (this->State == SUB_START_GATHERING) {
if (StartGathering(unit)) {
this->State = SUB_GATHER_RESOURCE;
} else {
return;
}
}
// Gather the resource.
if (this->State == SUB_GATHER_RESOURCE) {
if (GatherResource(unit)) {
this->State = SUB_STOP_GATHERING;
} else {
return;
}
}
// Stop gathering the resource.
if (this->State == SUB_STOP_GATHERING) {
if (StopGathering(unit)) {
this->State = SUB_MOVE_TO_DEPOT;
unit.pathFinderData->output.Cycles = 0; //moving counter
} else {
return;
}
}
// Move back home.
if (SUB_MOVE_TO_DEPOT <= this->State && this->State < SUB_UNREACHABLE_DEPOT) {
const int ret = MoveToDepot(unit);
switch (ret) {
case -1: { // Can't Reach
this->State++;
unit.Wait = 5;
return;
}
case 1: { // Reached
this->State = SUB_RETURN_RESOURCE;
return;
}
case 0: // Move along.
return;
default: {
Assert(0);
return;
}
}
}
// Depot seems to be unreachable
if (this->State == SUB_UNREACHABLE_DEPOT) {
ResourceGiveUp(unit);
return;
}
// Unload resources at the depot.
if (this->State == SUB_RETURN_RESOURCE) {
if (WaitInDepot(unit)) {
this->State = SUB_START_RESOURCE;
// It's posible, though very rare that the unit's goal blows up
// this cycle, but after this unit. Thus, next frame the unit
// will start mining a destroyed site. If, on the otherhand we
// are already in SUB_MOVE_TO_RESOURCE then we can handle it.
// So, we pass through SUB_START_RESOURCE the very instant it
// goes out of the depot.
//HandleActionResource(order, unit);
}
}
}
//@}