/***************************************************************************
                          vehicle.cpp  -  description
                             -------------------
    begin                : Fri Sep 29 2000
    copyright            : (C) 2000 by Martin Bickel
    email                : bickel@asc-hq.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <algorithm>
#include <math.h>
#include "research.h"
#include "vehicletype.h"
#include "vehicle.h"
#include "buildingtype.h"
#include "viewcalculation.h"
#include "errors.h"
#include "basegfx.h"
#include "graphicset.h"
#include "errors.h"
#include "gameoptions.h"
#include "basegfx.h"
#include "spfst.h"
#include "itemrepository.h"


const float repairEfficiencyVehicle[resourceTypeNum*resourceTypeNum] = { 0,  0,  0,
                                                                         0,  0.5, 0,
                                                                         0.5, 0,  1 };




Vehicle :: Vehicle (  )
          : ContainerBase ( NULL, NULL, 0 ), repairEfficiency ( repairEfficiencyVehicle ), typ ( NULL ), reactionfire ( this )
{
}

Vehicle :: Vehicle ( const Vehicle& v )
          : ContainerBase ( NULL, NULL, 0 ), repairEfficiency ( repairEfficiencyVehicle ), typ ( NULL ), reactionfire ( this )
{
}


Vehicle :: Vehicle ( const Vehicletype* t, pmap actmap, int player )
          : ContainerBase ( t, actmap, player ), repairEfficiency ( repairEfficiencyVehicle ), typ ( t ), reactionfire ( this )
{
   viewOnMap = false;

   if ( player > 8 )
      fatalError ( "Vehicle :: Vehicle ; invalid player ");

   init();

   gamemap->player[player].vehicleList.push_back ( this );
   gamemap->unitnetworkid++;
   networkid = gamemap->unitnetworkid;
   gamemap->vehicleLookupCache[networkid] = this;
}

Vehicle :: Vehicle ( const Vehicletype* t, pmap actmap, int player, int networkID )
          : ContainerBase ( t, actmap, player ), repairEfficiency ( repairEfficiencyVehicle ), typ ( t ), reactionfire ( this )
{
   viewOnMap = false;

   if ( player > 8 )
      fatalError ( "Vehicle :: Vehicle ; invalid player ");

   init();

   gamemap->player[player].vehicleList.push_back ( this );
   if ( networkID == -1 ) {
      gamemap->unitnetworkid++;
      networkid = gamemap->unitnetworkid;
   } else
      if ( networkID >= 0 ) {
         networkid = networkID;
      }

   if ( networkID >= 0 )
      gamemap->vehicleLookupCache[networkid] = this;
}


Vehicle :: ~Vehicle (  )
{
   #ifndef karteneditor
   if ( !typ->wreckageObject.empty() && gamemap && !gamemap->__mapDestruction && !cleanRemove) {
      pfield fld = getMap()->getField(getPosition());
      if ( fld->vehicle ==  this ) {
         for ( vector<int>::const_iterator i = typ->wreckageObject.begin(); i != typ->wreckageObject.end(); ++i ) {
            pobjecttype obj = getMap()->getobjecttype_byid( *i );
            if ( obj )
               getMap()->getField(getPosition()) -> addobject ( obj );
         }
      }
   }
   #endif

   if ( viewOnMap && gamemap && !gamemap->__mapDestruction ) {
      removeview();
      viewOnMap = false;
   }

   if ( gamemap ) {
      int c = color/8;

      tmap::Player::VehicleList::iterator i = find ( gamemap->player[c].vehicleList.begin(), gamemap->player[c].vehicleList.end(), this );
      if ( i != gamemap->player[c].vehicleList.end() )
         gamemap->player[c].vehicleList.erase ( i );

      tmap::VehicleLookupCache::iterator j = gamemap->vehicleLookupCache.find(networkid);
      if ( j != gamemap->vehicleLookupCache.end() )
         gamemap->vehicleLookupCache.erase(j);
   }

   for ( int i = 0; i < 32; i++ )
      if ( loading[i] )
         delete loading[i];

   pfield fld = gamemap->getField( xpos, ypos);
   if ( fld ) {
      if ( fld->vehicle  == this )
        fld->vehicle = NULL;
      else {
        if ( fld->building )
           fld->building->searchAndRemove(this);
        if ( fld->vehicle )
           fld->vehicle->searchAndRemove(this);
      }
   }
}




void Vehicle :: init ( void )
{
   for ( int j = 0; j < 32; j++ )
       loading[j] = NULL;

   xpos = -1;
   ypos = -1;

   for ( int i = 0; i < 16; i++ ) {
      weapstrength[i] = 0;
      ammo[i] = 0;
   }

   damage = 0;

   experience = 0;
   attacked = false;

   if ( typ ) {
      height = 1 << log2( typ->height );

      // These are the preferred levels of height
      if (typ->height & chfahrend )
         height = chfahrend;
      else
         if (typ->height & chschwimmend )
            height = chschwimmend;

      armor = typ->armor;
      for ( int m = 0; m < typ->weapons.count ; m++)
         weapstrength[m] = typ->weapons.weapon[m].maxstrength;

      setMovement ( typ->movement[log2(height)], 0 );
   } else {
      height = 0;
      armor = 0;

      setMovement ( 0, 0 );
   }

   direction = 0;
   xpos = -1;
   ypos = -1;
   connection = 0;
   networkid = -1;
   reactionfire.status = ReactionFire::off;
   reactionfire.enemiesAttackable = 0;

   generatoractive = 0;

   cleanRemove = false;

   for ( int a = 0; a < 8 ; a++ )
      aiparam[a] = NULL;
}


bool Vehicle :: canRepair( const ContainerBase* item )
{
   return (typ->functions & cfrepair) || (item == this && typ->autorepairrate ) ;
}

int Vehicle :: putResource ( int amount, int resourcetype, bool queryonly, int scope )
{
   //  if units start using/storing resources that will not be stored in the unit itself, the replays will fail !

   if ( amount < 0 ) {
      return -getResource( -amount, resourcetype, queryonly, scope );
   } else {
      if ( resourcetype == 0 )  // no energy storable
         return 0;

      int spaceAvail = typ->tank.resource( resourcetype ) - tank.resource(resourcetype);
      if ( spaceAvail < 0 )
         spaceAvail = 0;

      int tostore = min ( spaceAvail, amount);
      if ( !queryonly )
         tank.resource(resourcetype) += tostore;

      return tostore;
   }
}

int Vehicle :: getResource ( int amount, int resourcetype, bool queryonly, int scope )
{
   //  if units start using/storing resources that will not be stored in the unit itself, the replays will fail !

   if ( amount < 0 ) {
      return -putResource( -amount, resourcetype, queryonly, scope );
   } else {
      if ( resourcetype == 0 && !getGeneratorStatus() )
         return 0;

      int toget = min ( tank.resource(resourcetype), amount);
      if ( !queryonly )
         tank.resource(resourcetype) -= toget;


      return toget;
   }
}



void Vehicle :: setGeneratorStatus ( bool status )
{
   if ( typ->functions & cfgenerator ) {
      generatoractive = status;
   } else
     generatoractive = 0;
}



int Vehicle::weight( void ) const
{
   return typ->weight + cargo();
}

int Vehicle::size ( void )
{
   return typ->weight;
}

void Vehicle::transform ( const pvehicletype type )
{
   typ = type;

   tank.fuel = typ->tank.fuel;
   tank.material = typ->tank.material;
   tank.energy = 0;
   generatoractive = 0;

   for ( int m = 0; m < typ->weapons.count ; m++) {
      ammo[m] = typ->weapons.weapon[m].count;
      weapstrength[m] = typ->weapons.weapon[m].maxstrength;
   }
   armor = typ->armor;
}

void Vehicle :: postRepair ( int oldDamage )
{
   for ( int i = 0; i < experienceDecreaseDamageBoundaryNum; i++)
      if ( oldDamage > experienceDecreaseDamageBoundaries[i] && damage < experienceDecreaseDamageBoundaries[i] )
         if ( experience > 0 )
            experience-=1;
}


/*
void Vehicle :: repairunit(pvehicle vehicle, int maxrepair )
{
   if ( vehicle->damage  &&  tank.fuel  &&  tank.material ) {

      int orgdam = vehicle->damage;

      int dam;
      if ( vehicle->damage > maxrepair )
         dam = maxrepair;
      else
         dam = vehicle->damage;

      int fkost = dam * vehicle->typ->productionCost.energy / (100 * repairefficiency_unit );
      int mkost = dam * vehicle->typ->productionCost.material / (100 * repairefficiency_unit );
      int w;

      if (mkost <= material)
         w = 10000;
      else
         w = 10000 * material / mkost;

      if (fkost > fuel)
         if (10000 * fuel / fkost < w)
            w = 10000 * fuel / fkost;


      vehicle->damage -= dam * w / 10000;

      for ( int i = 0; i < experienceDecreaseDamageBoundaryNum; i++)
         if ( orgdam > experienceDecreaseDamageBoundaries[i] && vehicle->damage < experienceDecreaseDamageBoundaries[i] )
            if ( vehicle->experience > 0 )
               vehicle->experience-=1;


      if ( vehicle != this ) {
         if ( vehicle->getMovement() > movement_cost_for_repaired_unit )
            vehicle->setMovement ( vehicle->getMovement() -  movement_cost_for_repaired_unit );
         else
            vehicle->setMovement ( 0 );

         if ( !attack_after_repair )
            vehicle->attacked = 0;

         int unitloaded = 0;
         for ( int i = 0; i < 32; i++ )
            if ( loading[i] == vehicle )
               unitloaded = 1;

         if ( !unitloaded )
            if ( getMovement() > movement_cost_for_repairing_unit )
               setMovement ( getMovement() - movement_cost_for_repairing_unit );
            else
               setMovement ( 0 );
      }

      material -= w * mkost / 10000;
      fuel -= w * fkost / 10000;

   }
}
*/

void Vehicle :: endRound ( void )
{
   if ( typ->functions & cfgenerator ) {
      int endiff = typ->tank.energy - tank.energy;
      if ( tank.fuel < endiff * generatortruckefficiency )
         endiff = tank.fuel / generatortruckefficiency;

      tank.energy += endiff;
      tank.fuel -= endiff * generatortruckefficiency ;
   }
}

void Vehicle :: endAnyTurn()
{
   reactionfire.endAnyTurn();
}


void Vehicle :: endOwnTurn()
{
   if ( typ->autorepairrate > 0 )
      if ( damage )
         repairItem ( this, max ( damage - typ->autorepairrate, 0 ) );

   reactionfire.endOwnTurn();

   if ( !gamemap->getField(getPosition())->unitHere(this)) {
      int mx = -1;
      for ( int h = 0; h < 8; h++ )
         if ( typ->height & ( 1 << h))
            if ( typ->movement[h] > mx ) {
               mx = typ->movement[h];
               height = 1 << h;
            }
   }

   for ( int w = 0; w < typ->weapons.count; w++ )
      if ( typ->weapons.weapon[w].gettype() & cwlaserb ) {
         int cnt = 0;
         while ( cnt < typ->weapons.weapon[w].laserRechargeRate  && ammo[w] < typ->weapons.weapon[w].count ) {
            for ( int r = 0; r < 3; r++ )
               if ( typ->weapons.weapon[w].laserRechargeCost.resource(r) < 0 )
                  fatalError (" negative Laser recharge cost !");
            if ( ! (ContainerBase::getResource( typ->weapons.weapon[w].laserRechargeCost, 1 ) < typ->weapons.weapon[w].laserRechargeCost )) {
               ContainerBase::getResource( typ->weapons.weapon[w].laserRechargeCost, 0 );
               ammo[w] += 1;
            }
            ++cnt;
         }
      }

   resetMovement();
   attacked = false;

}

void Vehicle :: resetMovement ( void )
{
    int move = typ->movement[log2(height)];
    setMovement ( move, 0 );
    /*
    if (actvehicle->typ->fuelconsumption == 0)
       actvehicle->movement = 0;
    else {
       if ((actvehicle->fuel << 3) / actvehicle->typ->fuelconsumption < move)
          actvehicle->movement = (actvehicle->fuel << 3) / actvehicle->typ->fuelconsumption ;
       else
          actvehicle->movement = move;
    }
    */
}


void Vehicle :: setNewHeight( int newHeight )
{
  if ( maxMovement() ) {
     float oldperc = float(getMovement ( false )) / float(maxMovement());
     height = newHeight;
     setMovement ( int(floor(maxMovement() * oldperc + 0.5)) , 0 );
  } else {
     height = newHeight;
     warning("Internal error: unit has invalid height");
  }
}


void Vehicle :: setMovement ( int newmove, double cargoDivisor )
{

   if ( cargoDivisor < 0 )
      cargoDivisor = typ->cargoMovementDivisor;

   if ( newmove < 0 )
      newmove = 0;

   if ( cargoDivisor > 0 && typ)
      if ( typ->movement[ log2 ( height ) ] ) {
         double diff = _movement - newmove;
         double perc = diff / typ->movement[ log2 ( height ) ] ;
         for ( int i = 0; i < 32; i++ ) {
            if ( loading[i] ) {
               double lperc = perc;
               if ( cargoDivisor )
                  lperc /= cargoDivisor;

               loading[i]->decreaseMovement ( max( 1, int( ceil( lperc * double(loading[i]->typ->movement[ log2 ( loading[i]->height)] )))));
            }
         } /* endfor */
   }
   _movement = newmove;
}

bool Vehicle::hasMoved ( void ) const
{
   return _movement != typ->movement[ log2 ( height )];
}


int Vehicle :: getMovement ( bool checkFuel ) const
{
   if ( !reactionfire.canMove() )
      return 0;

   if ( typ->fuelConsumption && checkFuel ) {
      if ( tank.fuel * minmalq / typ->fuelConsumption < _movement )
         return tank.fuel * minmalq / typ->fuelConsumption;
      else
         return _movement;
   } else
      return _movement;
}

void Vehicle :: decreaseMovement ( int amount )
{
  int newMovement = _movement - amount;
  if ( newMovement < 0 )
    newMovement = 0;
  if ( newMovement > typ->movement[log2(height)] )
    newMovement = typ->movement[log2(height)];
  setMovement ( newMovement );
}

bool Vehicle::movementLeft() const
{
   int mv  = getMovement();
   if ( mv <= 0 )
      return false;
   if ( mv >= 10 )
      return true;

   if ( height & ( chtieffliegend |chfliegend | chhochfliegend )) {
      WindMovement wm ( this );
      for ( int i = 0; i < 6; i++ )
         if ( 10 - wm.getDist( i ) <= mv )
            return true;
   }
   return false;
}

bool Vehicle :: canMove ( void ) const
{
   // if ( attacked && !(typ->functions & cf_moveafterattack))
   //   return false;

   if ( movementLeft() && reactionfire.canMove() ) {
      pfield fld = gamemap->getField ( getPosition() );
      if ( fld->unitHere ( this ) ) {
         if ( terrainaccessible ( fld, this ) || actmap->getgameparameter( cgp_movefrominvalidfields))
            return true;
      } else {
         ContainerBase* cnt = fld->getContainer();
         if ( cnt )
            for ( int i = 0; i < 32; i++ )
               if ( cnt->loading[i] == this )
                  if ( cnt->vehicleUnloadable( typ ) > 0 || cnt->vehicleDocking( this, true ) > 0 )
                     return true;
      }
   }
   return false;
}

void Vehicle::spawnMoveObjects( const MapCoordinate& start, const MapCoordinate& dest )
{
   if ( start == dest )
      return;
      
   if ((typ->functions & ( cffahrspur | cficebreaker )) && (height == chfahrend || height == chschwimmend))  {
     int dir = getdirection( start, dest );

     pfield startField = gamemap->getField(start);
     pfield destField = gamemap->getField(dest);
     if ( typ->functions & cffahrspur )
        if ( fahrspurobject )
           if ( (startField->bdt & getTerrainBitType(cbfahrspur)).any() )
              startField->addobject ( fahrspurobject, 1 << dir );

     if ( typ->functions & cficebreaker )
        if ( eisbrecherobject )
              if (   (startField->bdt & getTerrainBitType(cbicebreaking) ).any()
                   || startField->checkforobject ( eisbrecherobject ) ) {
                 startField->addobject ( eisbrecherobject, 1 << dir );
              }

     dir = (dir + sidenum/2) % sidenum;

     if ( typ->functions & cffahrspur )
        if ( fahrspurobject )
           if ( (destField->bdt & getTerrainBitType(cbfahrspur)).any() )
              destField->addobject ( fahrspurobject, 1 << dir );

     if ( typ->functions & cficebreaker )
        if ( eisbrecherobject )
              if (   (destField->bdt & getTerrainBitType(cbicebreaking) ).any()
                   || destField->checkforobject ( eisbrecherobject ) ) {
                 destField->addobject ( eisbrecherobject, 1 << dir );
              }
   }
}


Vehicle::ReactionFire::ReactionFire ( Vehicle* _unit ) : unit ( _unit )
{
   weaponShots.resize(unit->typ->weapons.count);
   resetShotCount();
}


void Vehicle::ReactionFire::checkData ( )
{
   // the size could have changed because a unit was saved to disk with 1 weapon system, the type modified to 2 and the unit loaded again
   if ( weaponShots.size() < unit->typ->weapons.count ) {
      size_t oldsize = weaponShots.size();
      weaponShots.resize( unit->typ->weapons.count );
      for ( size_t i = oldsize; i < unit->typ->weapons.count; ++i )
         weaponShots[i] = unit->typ->weapons.weapon[i].reactionFireShots;
   }
}



void Vehicle::ReactionFire::resetShotCount()
{
   assert( unit->typ->weapons.count <= weaponShots.size() );
   for ( int i = 0; i < unit->typ->weapons.count; ++i ) 
      weaponShots[i] = unit->typ->weapons.weapon[i].reactionFireShots;
}


int Vehicle::ReactionFire::enable ( void )
{
   if ( unit->typ->functions & cfno_reactionfire )
      return -216;
      
   #ifdef karteneditor
   status = ready;
   #else
   if ( status == off ) {
      int weaponCount = 0;
      int shootableWeaponCount = 0;
      for ( int w = 0; w < unit->typ->weapons.count; w++ )
         if ( unit->typ->weapons.weapon[w].shootable() ) {
              weaponCount++;
              if ( unit->typ->weapons.weapon[w].sourceheight & unit->height )
                 shootableWeaponCount++;
         }

      if ( weaponCount == 0 )
         return -214;

      if ( shootableWeaponCount == 0 )
         return -213;

      if ( unit->typ->wait ) {
         if ( unit->hasMoved())
            status = init1a;
         else
            status = init2;
      } else {
         status = init2;
      }
   }
   #endif
   return 0;
}

void Vehicle::ReactionFire::disable ( void )
{
   if ( status != off ) {
       if ( status != init1a && status != init2 ) {
          enemiesAttackable = 0;
          unit->setMovement ( 0, 0 );
       }
       status = off;
   }
}

void Vehicle::ReactionFire::endAnyTurn()
{
   resetShotCount();
}


void Vehicle::ReactionFire::endOwnTurn()
{
   if ( status != off ) {
      if ( status == init1a )
         status = init1b;
      else
         if ( status == init2 || status == init1b )
            status = ready;

      if ( status == ready )
         enemiesAttackable = 0xff;
      else
         enemiesAttackable = 0;
   }
   nonattackableUnits.clear();
}

bool Vehicle::ReactionFire::canMove() const
{
   if ( unit->typ->functions & cfmovewithRF )
      return true;
   if ( status == off )
      return true;
   return false;
}


const Vehicletype::HeightChangeMethod* Vehicle::getHeightChange( int dir, int height ) const
{
   if ( !reactionfire.canMove() )
      return NULL;

   if ( height == 0 )
      height = this->height;

   for ( int i = 0; i < typ->heightChangeMethodNum; i++ )
      if ( typ->heightChangeMethod[i].startHeight & height )
         if ( ( dir > 0 && typ->heightChangeMethod[i].heightDelta > 0) || ( dir < 0 && typ->heightChangeMethod[i].heightDelta < 0))
            if ( (1 << (log2(height) + typ->heightChangeMethod[i].heightDelta)) & typ->height )
               return &typ->heightChangeMethod[i];

   return NULL;
}



bool Vehicle :: weapexist( void )
{
   if ( typ->weapons.count > 0)
      for ( int b = 0; b < typ->weapons.count ; b++)
         if ( typ->weapons.weapon[b].shootable() )
            if ( typ->weapons.weapon[b].offensive() )
               if ( ammo[b] )
                  return 1;
    return 0;
}


void Vehicle :: putimage ( int x, int y )
{
  #ifdef sgmain
   int shaded = !canMove() && maxMovement() && ( color == gamemap->actplayer*8) && (attacked || !typ->weapons.count || CGameOptions::Instance()->units_gray_after_move );
  #else
   int shaded = 0;
  #endif
    if ( height <= chgetaucht ) {
       if ( shaded )
          putpicturemix ( x, y, xlatpict(xlatpictgraytable,  typ->picture[  direction ]),  color, (char*) colormixbuf );
       else
          putpicturemix ( x, y,  typ->picture[ direction],  color, (char*) colormixbuf );
    } else {
          if ( height >= chtieffliegend ) {
             int d = 6 * ( log2 ( height) - log2 ( chfahrend ));
             putshadow ( x + d, y + d, typ->picture[direction] , &xlattables.a.dark3);
          } else
             if ( height == chfahrend )
                putshadow ( x + 1, y + 1,  typ->picture[ direction] , &xlattables.a.dark3);

          if ( shaded )
             putrotspriteimage( x, y, xlatpict(xlatpictgraytable,  typ->picture[  direction ]),  color);
          else
             putrotspriteimage( x, y,  typ->picture[ direction],  color);
    }
}


void Vehicle :: setnewposition ( int x , int y )
{
  xpos = x;
  ypos = y;
  if ( typ->maxLoadableUnits > 0)
     for ( int i = 0; i <= 31; i++)
        if ( loading[i] )
           loading[i]->setnewposition ( x , y );
}

void Vehicle :: setnewposition ( const MapCoordinate& mc )
{
   setnewposition ( mc.x, mc.y );
}

/*
int Vehicle :: getstrongestweapon( int aheight, int distance)
{
   int str = 0;
   int hw = 255;   //  error-wert  ( keine waffe gefunden )
   for ( int i = 0; i < typ->weapons.count; i++) {

      if (( ammo[i]) &&
          ( typ->weapons.weapon[i].mindistance <= distance) &&
          ( typ->weapons.weapon[i].maxdistance >= distance) &&
          ( typ->weapons.weapon[i].targ & aheight ) &&
          ( typ->weapons.weapon[i].sourceheight & height )) {
            int astr = int( weapstrength[i] * weapDist.getWeapStrength( &typ->weapons.weapon[i], distance, height, aheight));
            if ( astr > str ) {
               str = astr;
               hw  = i;
            }
       }
   }
   return hw;
}
*/

void Vehicle::convert ( int col )
{
  if ( col > 8)
     fatalError("convertvehicle: \n color muá im bereich 0..8 sein ",2);

   int oldcol = getOwner();

   tmap::Player::VehicleList::iterator i = find ( gamemap->player[oldcol].vehicleList.begin(), gamemap->player[oldcol].vehicleList.end(), this );
   if ( i != gamemap->player[oldcol].vehicleList.end())
      gamemap->player[oldcol].vehicleList.erase ( i );

   gamemap->player[col].vehicleList.push_back( this );

   color = col << 3;

   for ( int i = 0; i < 32; i++)
      if ( loading[i] )
         loading[i]->convert( col );

   // emit signal
   conquered();
}

Vehicle* Vehicle :: constructvehicle ( pvehicletype tnk, int x, int y )
{
   if ( gamemap && vehicleconstructable( tnk, x, y )) {
      pvehicle v = new Vehicle( tnk, gamemap, color/8 );
      v->xpos = x;
      v->ypos = y;

      for ( int j = 0; j < 8; j++ ) {
         int a = int(height) << j;
         int b = int(height) >> j;
         if ( v->typ->height & a ) {
            v->height = a;
            break;
         }
         if ( v->typ->height & b ) {
            v->height = b;
            break;
         }
      }
      v->setMovement ( 0 );


      gamemap->getField ( x, y )->vehicle = v;
      tank.material -= tnk->productionCost.material;
      tank.fuel -= tnk->productionCost.energy;


      int refuel = 0;
      for ( int i = 0; i < typ->weapons.count; i++ )
         if ( typ->weapons.weapon[i].service()  )
            for ( int j = 0; j < typ->weapons.count ; j++) {
               if ( typ->weapons.weapon[j].canRefuel() )
                  refuel = 1;
               if ( typ->functions & (cffuelref | cfmaterialref) )
                  refuel = 1;
            }

      v->attacked = 1;
      return v;
   } else
      return NULL;
}

bool  Vehicle :: vehicleconstructable ( pvehicletype tnk, int x, int y )
{
   if ( !tnk->techDependency.available ( gamemap->player[getOwner()].research))
      return 0;

//              if ( getheightdelta( log2(tnk->height), log2(height) ) == 0 )
   if( (tnk->height & height ) || (( tnk->height & (chfahrend | chschwimmend)) && (height & (chfahrend | chschwimmend)))) {
      int hgt = height;
      if ( !(tnk->height & height))
         hgt = 1 << log2(tnk->height);
      if ( terrainaccessible2( gamemap->getField(x,y), tnk->terrainaccess, hgt ) > 0 )
   //   tnk->terrainaccess.accessible ( gamemap->getField(x,y)->bdt ) > 0 || height >= chtieffliegend)
         if ( tnk->productionCost.material <= tank.material &&
              tnk->productionCost.energy   <= tank.fuel  )
              if ( beeline (x, y, xpos, ypos) <= maxmalq )
                 return 1;

   }
   return 0;
}


bool Vehicle :: buildingconstructable ( pbuildingtype building )
{
   if ( !building )
      return 0;


   if ( gamemap->getgameparameter(cgp_forbid_building_construction) )
       return 0;

   if ( !building->techDependency.available ( gamemap->player[getOwner()].research))
      return 0;


   int mf = gamemap->getgameparameter ( cgp_building_material_factor );
   int ff = gamemap->getgameparameter ( cgp_building_fuel_factor );

   if ( !mf )
      mf = 100;
   if ( !ff )
      ff = 100;

   int hd = getheightdelta ( log2 ( height ), log2 ( building->buildingheight ));

   if ( hd != 0 ) // && !(hd ==-1 && (height == chschwimmend || height == chfahrend)) ???  what was that ??
      return 0;


   if ( building->productionCost.material * mf / 100 <= tank.material   &&   building->productionCost.fuel * ff / 100 <= tank.fuel ) {
      int found = 0;
      if ( typ->functions & cfputbuilding )
         found = 1;
      if ( typ->functions & cfspecificbuildingconstruction )
         for ( int i = 0; i < typ->buildingsBuildable.size(); i++ )
            if ( typ->buildingsBuildable[i].from <= building->id &&
                 typ->buildingsBuildable[i].to   >= building->id )
                 found = 1;

      return found;
   } else
      return false;
}

int Vehicle :: searchstackforfreeweight ( Vehicle* searchedInnerVehicle )
{
   if ( searchedInnerVehicle == this ) {
      return typ->maxLoadableWeight - cargo();
   } else {
      int currentFreeWeight = typ->maxLoadableWeight - cargo();
      int innerFreeWeight = -1;
      for ( int i = 0; i < 32; i++ )
         if ( loading[i] ) {
            int w = loading[i]->searchstackforfreeweight ( searchedInnerVehicle );
            if ( w >= 0 )
               innerFreeWeight = w;
         }

      if ( innerFreeWeight != -1 )
         return min ( currentFreeWeight, innerFreeWeight );
      else
         return -1;
   }
}


int Vehicle :: freeWeight ()
{
   pfield fld = gamemap->getField ( xpos, ypos );
   if ( fld->vehicle )
      return fld->vehicle->searchstackforfreeweight ( this );
   else
      if ( fld->building ) {
         for ( int i = 0; i < 32; i++ )
            if ( fld->building->loading[i] ) {
               int w3 = fld->building->loading[i]->searchstackforfreeweight ( this );
               if ( w3 >= 0 )
                  return w3;
            }
      }

   return -2;
}


void Vehicle :: addview ( void )
{
   if ( viewOnMap )
      fatalError ("void Vehicle :: addview - the vehicle is already viewing the map");

   viewOnMap = true;
   tcomputevehicleview bes ( gamemap );
   bes.init( this, +1 );
   bes.startsearch();
}

void Vehicle :: removeview ( void )
{
   if ( !viewOnMap )
      fatalError ("void Vehicle :: removeview - the vehicle is not viewing the map");

   tcomputevehicleview bes ( gamemap );
   bes.init( this, -1 );
   bes.startsearch();

   viewOnMap = false;
}


void Vehicle :: postAttack()
{
   attacked = true;
   if ( typ->functions & cf_moveafterattack )
      decreaseMovement ( maxMovement() * attackmovecost / 100 );
   else
      if ( reactionfire.getStatus() == ReactionFire::off )
         setMovement ( 0 );
}


void Vehicle::setAttacked()
{
   attacked = true;
   for ( int i = 0; i < 32; i++ )
      if ( loading[i] )
         loading[i]->setAttacked();
}


class tsearchforminablefields: public SearchFields {
      int shareview;
    public:
      int numberoffields;
      int run ( pvehicle     eht );
      virtual void testfield ( const MapCoordinate& mc );
      tsearchforminablefields ( pmap _gamemap ) : SearchFields ( _gamemap ) {};
      static bool available( pvehicle eht );
  };

bool Vehicle::searchForMineralResourcesAvailable()
{
   return tsearchforminablefields::available( this );
}



void         tsearchforminablefields::testfield( const MapCoordinate& mc )
{
    pfield fld = gamemap->getField ( mc );
    if ( !fld->building  ||  fld->building->color == gamemap->actplayer*8  ||  fld->building->color == 8*8)
       if ( !fld->vehicle  ||  fld->vehicle->color == gamemap->actplayer*8 ||  fld->vehicle->color == 8*8) {
          if ( !fld->resourceview )
             fld->resourceview = new tfield::Resourceview;

          for ( int c = 0; c < 8; c++ )
             if ( shareview & (1 << c) ) {
                fld->resourceview->visible |= ( 1 << c );
                fld->resourceview->fuelvisible[c] = fld->fuel;
                fld->resourceview->materialvisible[c] = fld->material;
             }
       }
}


bool tsearchforminablefields::available( pvehicle eht )
{
   if ( (eht->typ->functions & cfmanualdigger) && !(eht->typ->functions & cfautodigger) )
      if ( eht->attacked ||
          (eht->typ->wait && eht->hasMoved() ) ||
          (eht->getMovement() < searchforresorcesmovedecrease ))
          return false;
   return true;
}


int  tsearchforminablefields::run( pvehicle eht )
{
   if ( !available( eht ) )
      return -311;


   shareview = 1 << ( eht->color / 8);
   if ( gamemap->shareview )
      for ( int i = 0; i < 8; i++ )
         if ( i*8 != eht->color )
            if ( gamemap->player[i].exist() )
               if ( gamemap->shareview->mode[eht->color/8][i] )
                  shareview += 1 << i;

   numberoffields = 0;
   initsearch( eht->getPosition(), eht->typ->digrange, 0 );
   if ( eht->typ->digrange )
      startsearch();

   if ( (eht->typ->functions & cfmanualdigger) && !(eht->typ->functions & cfautodigger) )
      eht->setMovement ( eht->getMovement() - searchforresorcesmovedecrease );

   if ( !gamemap->mineralResourcesDisplayed && (eht->typ->functions & cfmanualdigger) && !(eht->typ->functions & cfautodigger))
      gamemap->mineralResourcesDisplayed = 1;

   return 1;
}


int Vehicle::maxMovement ( void )
{
   return typ->movement[log2(height)];
}

int Vehicle::searchForMineralResources ( void )
{
    tsearchforminablefields sfmf ( gamemap );
    return sfmf.run( this );
}


void Vehicle :: fillMagically( void )
{
   #ifndef karteneditor
   fprintf(stderr, "ASC: Warning, Vehicle :: fillMagically called outside mapeditor");
   #endif
   tank = typ->tank;

   for ( int m = 0; m < typ->weapons.count ; m++) {
      ammo[m] = typ->weapons.weapon[m].count;
      weapstrength[m] = typ->weapons.weapon[m].maxstrength;
   }
   armor = typ->armor;
}




#define cem_experience    0x1
#define cem_damage        0x2
#define cem_fuel          0x4
#define cem_ammunition    0x8
#define cem_weapstrength  0x10
#define cem_loading       0x20
#define cem_attacked      0x40
#define cem_height        0x80
#define cem_movement      0x100
#define cem_direction     0x200
#define cem_material      0x400
#define cem_energy        0x800
#define cem_class         0x1000
#define cem_networkid     0x2000
#define cem_name          0x4000
#define cem_armor         0x8000
#define cem_reactionfire  0x10000
#define cem_reactionfire2 0x20000
#define cem_poweron       0x40000
#define cem_weapstrength2 0x80000
#define cem_ammunition2   0x100000
#define cem_energyUsed    0x200000
#define cem_position      0x400000
#define cem_aiparam       0x800000
#define cem_version       0x1000000




const int vehicleVersion = 3;

void   Vehicle::write ( tnstream& stream, bool includeLoadedUnits )
{
    stream.writeWord ( 0 );
    stream.writeInt( vehicleVersion );
    stream.writeInt( typ->id );

    stream.writeChar ( color );

    int bm = cem_version;

    if ( experience )
       bm |= cem_experience;
    if ( damage    )
       bm |= cem_damage;

    bm |= cem_energy;
    bm |= cem_fuel;
    bm |= cem_material;

    if ( typ->weapons.count )
       for (char m = 0; m < typ->weapons.count ; m++) {
          if ( ammo[m] < typ->weapons.weapon[m].count )
             bm |= cem_ammunition2;
          if ( weapstrength[m] != typ->weapons.weapon[m].maxstrength )
             bm |= cem_weapstrength2;

       }
    if ( includeLoadedUnits )
       for ( int i = 0; i < 32; i++ )
          if ( loading[i] )
              bm |= cem_loading;

    if ( attacked  )
       bm |= cem_attacked;
    if ( height != chfahrend )
       bm |= cem_height;

//    if ( _movement < typ->movement[log2(height)] )
    bm |= cem_movement;

    if ( direction )
       bm |= cem_direction;


    if ( armor != typ->armor )
       bm |= cem_armor;

    if ( networkid )
       bm |= cem_networkid;

    if ( !name.empty() )
       bm |= cem_name;

    if ( reactionfire.status )
       bm |= cem_reactionfire;

    if ( reactionfire.enemiesAttackable )
       bm |= cem_reactionfire2;

    if ( generatoractive )
       bm |= cem_poweron;

    if ( xpos != 0 || ypos != 0 )
       bm |= cem_position;

    for ( int i = 0; i < 8; i++ )
       if ( aiparam[i] )
          bm |= cem_aiparam;



    stream.writeInt( bm );

    stream.writeInt( vehicleVersion );

    if ( bm & cem_experience )
         stream.writeChar ( experience );

    if ( bm & cem_damage )
         stream.writeChar ( damage );

    if ( bm & cem_fuel )
         stream.writeInt ( tank.fuel );

    if ( bm & cem_ammunition2 )
       for ( int j= 0; j < 16; j++ )
         stream.writeInt ( ammo[j] );

    if ( bm & cem_weapstrength2 )
       for ( int j = 0; j < 16; j++ )
         stream.writeInt ( weapstrength[j] );

    if ( bm & cem_loading ) {
       char c=0;
       for ( int k = 0; k <= 31; k++)
          if ( loading[k] )
             c++;

       stream.writeChar ( c );

       if ( c )
          for ( int k = 0; k <= 31; k++)
             if ( loading[k] )
                loading[k]->write ( stream );
    }

    if ( bm & cem_height )
         stream.writeChar ( height );

    if ( bm & cem_movement )
         stream.writeChar ( _movement );

    if ( bm & cem_direction )
         stream.writeChar ( direction );

    if ( bm & cem_material )
         stream.writeInt ( tank.material );

    if ( bm & cem_energy )
         stream.writeInt ( tank.energy );

    if ( bm & cem_armor )
         stream.writeWord ( armor );

    if ( bm & cem_networkid )
         stream.writeInt ( networkid );

    if ( bm & cem_attacked )
         stream.writeChar ( attacked );

    if ( bm & cem_name     )
         stream.writeString ( name );

    if ( bm & cem_reactionfire )
       stream.writeChar ( reactionfire.status );

    if ( bm & cem_reactionfire2 )
       stream.writeChar ( reactionfire.enemiesAttackable );

    if ( bm & cem_poweron )
       stream.writeInt ( generatoractive );

    if ( bm & cem_position ) {
       stream.writeInt ( xpos );
       stream.writeInt ( ypos );
    }

    if ( bm & cem_aiparam ) {
       stream.writeInt( 0x23451234 );
       for ( int i = 0; i < 8; i++ )
          stream.writeInt ( aiparam[i] != NULL );

       for ( int i = 0; i < 8; i++ )
          if ( aiparam[i] )
             aiparam[i]->write ( stream );

       stream.writeInt( 0x23451234 );
    }

    writeClassContainer( reactionfire.weaponShots, stream );
    writeClassContainer( reactionfire.nonattackableUnits, stream );
}

void   Vehicle::read ( tnstream& stream )
{
    int _id = stream.readWord ();
    int version = 0;
    if ( _id == 0 ) {
       version = stream.readInt();
       _id = stream.readInt();
    }

    stream.readChar (); // color
    if ( _id != typ->id )
       fatalError ( "Vehicle::read - trying to read a unit of different type" );

    readData ( stream );
}

void   Vehicle::readData ( tnstream& stream )
{

    int bm = stream.readInt();

    int version = 0;
    if ( bm & cem_version )
       version = stream.readInt();

    if ( bm & cem_experience )
       experience = stream.readChar();
    else
       experience = 0;

    if ( bm & cem_damage )
       damage = stream.readChar();
    else
       damage = 0;

    if ( bm & cem_fuel ) {
       tank.fuel = stream.readInt();
       if ( tank.fuel > typ->tank.fuel )
          tank.fuel = typ->tank.fuel;
    } else
       tank.fuel = typ->tank.fuel;

    if ( bm & cem_ammunition ) {
       for ( int i = 0; i < 8; i++ )
         ammo[i] = stream.readWord();
    } else
     if ( bm & cem_ammunition2 ) {
        for ( int i = 0; i < 16; i++ ) {
          ammo[i] = stream.readInt();
          if ( ammo[i] > typ->weapons.weapon[i].count )
             ammo[i] = typ->weapons.weapon[i].count;
          if ( ammo[i] < 0 )
             ammo[i] = 0;
        }

     } else
       for (int i=0; i < typ->weapons.count ;i++ )
          ammo[i] = typ->weapons.weapon[i].count;


    if ( bm & cem_weapstrength ) {
       for ( int i = 0; i < 8; i++ )
         weapstrength[i] = stream.readWord();

    } else
     if ( bm & cem_weapstrength2 ) {
        for ( int i = 0; i < 16; i++ )
           weapstrength[i] = stream.readInt();
     } else
       for (int i=0; i < typ->weapons.count ;i++ )
          weapstrength[i] = typ->weapons.weapon[i].maxstrength;

    if ( bm & cem_loading ) {
       char c = stream.readChar();

       if ( c ) {
          for (int k = 0; k < c; k++) {
             loading[k] = Vehicle::newFromStream ( gamemap, stream );
             if ( loading[k]->color != color )
                loading[k]->convert( color/8 );

          }

          for ( int l = c; l < 32; l++ )
             loading[l] = NULL;
       }
    }

    if ( bm & cem_height )
       height = stream.readChar();
    else
       height = chfahrend;

    if ( ! (height & typ->height) )
       height = 1 << log2 ( typ->height );

    if ( bm & cem_movement )
       setMovement ( stream.readChar ( ), 0 );
    else
       setMovement ( typ->movement [ log2 ( height ) ], 0 );

    if ( bm & cem_direction )
       direction = stream.readChar();
    else
       direction = 0;

    if ( bm & cem_material ){
       tank.material = min( stream.readInt(), typ->tank.material);
    } else
       tank.material = typ->tank.material;

    if ( bm & cem_energy ) {
       tank.energy = min ( stream.readInt(), typ->tank.energy);
       if ( tank.energy < 0 )
          tank.energy = 0;
    } else
       tank.energy = typ->tank.energy;

    if ( bm & cem_class )
       stream.readChar(); // was: class

    if ( bm & cem_armor )
       armor = stream.readWord();
    else
       armor = typ->armor;

    if ( bm & cem_networkid )
       networkid = stream.readInt();
    else
       networkid = 0;

    if ( bm & cem_attacked )
       attacked = stream.readChar();
    else
       attacked = false;

    if ( bm & cem_name )
       name = stream.readString ( );

    int reactionfirestatus = 0;
    if ( bm & cem_reactionfire )
       reactionfirestatus = stream.readChar();

    if ( bm & cem_reactionfire2 )
       reactionfire.enemiesAttackable = stream.readChar();
    else
       reactionfire.enemiesAttackable = 0;

    if ( reactionfirestatus >= 8 && reactionfire.enemiesAttackable <= 4 ) { // for transition from the old reactionfire system ( < ASC1.2.0 ) to the new one ( >= ASC1.2.0 )
       reactionfire.status = ReactionFire::Status ( reactionfire.enemiesAttackable );
       reactionfire.enemiesAttackable = reactionfirestatus;
       setMovement ( typ->movement [ log2 ( height ) ], 0 );
    } else
       reactionfire.status = ReactionFire::Status ( reactionfirestatus );

    if ( bm & cem_poweron )
       generatoractive = stream.readInt();
    else
       generatoractive = 0;

    if ( bm & cem_energyUsed )
       stream.readInt ();

    if ( bm & cem_position ) {
       int x = stream.readInt ( );
       int y = stream.readInt ( );
       setnewposition ( x, y );
    } else
       setnewposition ( 0, 0 );

    if ( bm & cem_aiparam ) {
       int magic = stream.readInt();
       if ( magic != 0x23451234 )
          throw ASCmsgException ( "Vehicle::read() - inconsistent data stream" );
       for ( int i = 0; i < 8; i++ ) {
          bool b = stream.readInt ( );
          if ( b )
             aiparam[i] = new AiParameter ( this );
          else
             if ( aiparam[i] ) {
               delete aiparam[i];
               aiparam[i] = NULL;
             }
       }

       for ( int i = 0; i < 8; i++ )
          if ( aiparam[i] )
             aiparam[i]->read ( stream );

       magic = stream.readInt();
       if ( magic != 0x23451234 )
          throw ASCmsgException ( "Vehicle::read() - inconsistent data stream" );
    }

    armor = typ->armor;
    for ( int m = 0; m < typ->weapons.count ; m++)
       if ( typ->weapons.weapon[m].getScalarWeaponType() >= 0 )
          weapstrength[m] = typ->weapons.weapon[m].maxstrength;
       else
          weapstrength[m] = 0;

    if ( version >= 1 ) {
      readClassContainer( reactionfire.weaponShots, stream );
      reactionfire.checkData();
    }
    if ( version >= 2 )
      readClassContainer( reactionfire.nonattackableUnits, stream );
}

MapCoordinate3D Vehicle :: getPosition ( ) const
{
   return MapCoordinate3D ( xpos, ypos, height );
}

MapCoordinate3D Vehicle :: getPosition3D ( ) const
{
   if ( gamemap->getField(xpos,ypos)->unitHere(this) )
      return MapCoordinate3D ( xpos, ypos, height );
   else {
      MapCoordinate3D pos;
      pos.setnum ( xpos, ypos, -1 );
      return pos;
   }
}


const ASCString&  Vehicle::getName() const
{
   if ( name.empty() )
      return typ->getName();
   else
      return name;
}


Vehicle* Vehicle::newFromStream ( pmap gamemap, tnstream& stream, int forceNetworkID )
{
   int id = stream.readWord ();
   int version = 0;
   if ( id == 0 ) {
      version = stream.readInt();
      id = stream.readInt();
   }

   pvehicletype fzt = gamemap->getvehicletype_byid ( id );
   if ( !fzt )
      throw InvalidID ( "vehicle", id );

   int color = stream.readChar ();

   Vehicle* v = new Vehicle ( fzt, gamemap, color/8, -2 );

   v->readData ( stream );

   if ( forceNetworkID > 0 )
      v->networkid = forceNetworkID;

   tmap::VehicleLookupCache::iterator j = gamemap->vehicleLookupCache.find( v->networkid);
   if ( j != gamemap->vehicleLookupCache.end() ) {
      // warning("duplicate network id detected !");
      gamemap->unitnetworkid++;
      v->networkid = gamemap->unitnetworkid;
   }

   gamemap->vehicleLookupCache[v->networkid] = v;
   return v;
}

/** Returns the SingleWeapon corresponding to the weaponNum for this
 *  vehicle.
 */
const SingleWeapon* Vehicle::getWeapon( unsigned weaponNum )
{
  if ( weaponNum <= typ->weapons.count )
     return &typ->weapons.weapon[weaponNum];
  else
     return NULL;
}

