/*************************************************************************** valuation.cpp - description ------------------- begin : Fri Mar 30 2001 copyright : (C) 2001 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 "ai_common.h" const int value_armorfactor = 100; const int value_weaponfactor = 3000; const int ccbt_repairfacility = 200; // basic threatvalues for buildings const int ccbt_hq = 10000; const int ccbt_recycling = 50; const int ccbt_training = 150; class CalculateThreat_VehicleType { protected: AI* ai; Vehicletype* fzt; int weapthreat[8]; int value; virtual int getdamage ( void ) { return 0; }; virtual int getexpirience ( void ) { return 0; }; virtual int getammunition( int i ) { return 1; }; virtual int getheight ( void ) { return 255; }; public: void calc_threat_vehicletype ( Vehicletype* _fzt ); CalculateThreat_VehicleType ( AI* _ai ) { ai = _ai; }; }; class CalculateThreat_Vehicle : public CalculateThreat_VehicleType { protected: pvehicle eht; virtual int getdamage ( void ); virtual int getexpirience ( void ); virtual int getammunition( int i ); virtual int getheight ( void ); public: void calc_threat_vehicle ( pvehicle _eht ); CalculateThreat_Vehicle ( AI* _ai ) : CalculateThreat_VehicleType ( _ai ) {}; }; void CalculateThreat_VehicleType :: calc_threat_vehicletype ( Vehicletype* _fzt ) { fzt = _fzt; for ( int j = 0; j < 8; j++ ) weapthreat[j] = 0; for ( int i = 0; i < fzt->weapons.count; i++) if ( fzt->weapons.weapon[i].shootable() ) if ( fzt->weapons.weapon[i].offensive() ) for ( int j = 0; j < 8; j++) if ( fzt->weapons.weapon[i].targ & (1 << j) ) { int d = 0; int m = 0; AttackFormula af; for ( int e = (fzt->weapons.weapon[i].mindistance + maxmalq - 1)/ maxmalq; e <= fzt->weapons.weapon[i].maxdistance / maxmalq; e++ ) { // the distance between two fields is maxmalq d++; int n = int( weapDist.getWeapStrength( &fzt->weapons.weapon[i], e*maxmalq ) * fzt->weapons.weapon[i].maxstrength * af.strength_damage(getdamage()) * ( 1 + af.strength_experience(getexpirience())) ); m += int( n / log10(double(10*d))); } if (getammunition(i) == 0) m /= 2; if ( (fzt->weapons.weapon[i].sourceheight & getheight()) == 0) m /= 2; /* if ( !(getheight() & ( 1 << j ))) m /= 2; */ if (m > weapthreat[j]) weapthreat[j] = m; } if ( !fzt->aiparam[ai->getPlayerNum()] ) fzt->aiparam[ ai->getPlayerNum() ] = new AiValue ( log2 ( fzt->height )); for ( int l = 0; l < 8; l++ ) fzt->aiparam[ ai->getPlayerNum() ]->threat.threat[l] = weapthreat[l]; value = fzt->armor * value_armorfactor * (100 - getdamage()) / 100; for ( int s = 0; s < 7; ) if ( weapthreat[s] < weapthreat[s+1] ) { int temp = weapthreat[s]; weapthreat[s] = weapthreat[s+1]; weapthreat[s+1] = temp; if ( s > 0 ) s--; } else s++; for ( int k = 0; k < 8; k++ ) value += weapthreat[k] * value_weaponfactor / (k+1); fzt->aiparam[ ai->getPlayerNum() ]->setValue ( value ); fzt->aiparam[ ai->getPlayerNum() ]->valueType = 0; } int CalculateThreat_Vehicle :: getammunition( int i ) { return eht->ammo[i]; } int CalculateThreat_Vehicle :: getheight(void) { return eht->height; } int CalculateThreat_Vehicle :: getdamage(void) { return eht->damage; } int CalculateThreat_Vehicle :: getexpirience(void) { return eht->experience; } void CalculateThreat_Vehicle :: calc_threat_vehicle ( pvehicle _eht ) { eht = _eht; calc_threat_vehicletype ( vehicleTypeRepository.getObject_byID ( eht->typ->id ) ); if ( !eht->aiparam[ai->getPlayerNum()] ) eht->aiparam[ai->getPlayerNum()] = new AiParameter ( eht ); AiParameter* aip = eht->aiparam[ai->getPlayerNum()]; for ( int l = 0; l < 8; l++ ) aip->threat.threat[l] = eht->typ->aiparam[ ai->getPlayerNum() ]->threat.threat[l]; int value = eht->typ->aiparam[ ai->getPlayerNum() ]->getValue(); for ( int i = 0; i < 32; i++ ) if ( eht->loading[i] ) { if ( !eht->loading[i]->aiparam[ai->getPlayerNum()] ) { CalculateThreat_Vehicle ctv ( ai ); ctv.calc_threat_vehicle( eht->loading[i] ); } value += eht->loading[i]->aiparam[ai->getPlayerNum()]->getValue(); } aip->setValue ( value ); if ( aip->getJob() == AiParameter::job_undefined ) if ( eht->canMove() ) aip->setJob( AI::chooseJob ( eht->typ, eht->typ->functions )); /* generatethreatvalue(); int l = 0; for ( int b = 0; b <= 7; b++) { eht->threatvalue[b] = weapthreatvalue[b]; if (weapthreatvalue[b] > l) l = weapthreatvalue[b]; } eht->completethreatvalue = threatvalue2 + l; eht->completethreatvaluesurr = threatvalue2 + l; eht->threats = 0; */ } AiParameter::JobList AI::chooseJob ( const Vehicletype* typ, int functions ) { AiParameter::JobList jobList; if ( typ->recommendedAIJob != AiParameter::job_undefined ) { jobList.push_back ( typ->recommendedAIJob ); return jobList; } int maxmove = minint; for ( int i = 0; i< 8; i++ ) if ( typ->height & ( 1 << i )) maxmove = max ( typ->movement[i] , maxmove ); int maxstrength = minint; for ( int w = 0; w < typ->weapons.count; w++ ) if ( typ->weapons.weapon[w].offensive() ) maxstrength= max ( typ->weapons.weapon[w].maxstrength, maxstrength ); bool service = false; for ( int w = 0; w < typ->weapons.count; w++ ) if ( typ->weapons.weapon[w].service() ) service = true; if ( ((functions & cfrepair) || service) && maxmove >= minmalq && maxstrength < 45) jobList.push_back ( AiParameter::job_supply ); if ( functions & cf_conquer ) { /* if ( functions & cf_trooper ) { if ( typ->height & chfahrend ) jobList.push_back ( AiParameter::job_conquer ); } else { */ if ( maxstrength < maxmove ) jobList.push_back ( AiParameter::job_conquer ); // } } if ( ( maxstrength*1.5 < typ->view || (maxstrength*1.5 < typ->jamming && !(typ->functions & cfownFieldJamming))) && maxmove > minmalq ) jobList.push_back ( AiParameter::job_recon ); if ( maxstrength > 0 ) jobList.push_back ( AiParameter::job_fight ); jobList.push_back ( AiParameter::job_undefined ); return jobList; } void AI :: calculateThreat ( pvehicletype vt) { CalculateThreat_VehicleType ctvt ( this ); ctvt.calc_threat_vehicletype( vt ); } void AI :: calculateThreat ( pvehicle eht ) { CalculateThreat_Vehicle ctv ( this ); ctv.calc_threat_vehicle( eht ); } void AI :: calculateThreat ( pbuilding bld ) { calculateThreat ( bld, getPlayerNum()); // calculateThreat ( bld, 8 ); } void AI :: calculateThreat ( pbuilding bld, int player ) { if ( !bld->aiparam[ player ] ) bld->aiparam[ player ] = new AiValue ( log2 ( bld->typ->buildingheight ) ); int b; // Since we have two different resource modes now, this calculation should be rewritten.... int value = (bld->plus.energy / 10) + (bld->plus.fuel / 10) + (bld->plus.material / 10) + (bld->actstorage.energy / 20) + (bld->actstorage.fuel / 20) + (bld->actstorage.material / 20) + (bld->maxresearchpoints / 10) ; for (b = 0; b <= 31; b++) if ( bld->loading[b] ) { if ( !bld->loading[b]->aiparam[ player ] ) calculateThreat ( bld->loading[b] ); value += bld->loading[b]->aiparam[ player ]->getValue(); } for (b = 0; b <= 31; b++) if ( bld->production[b] ) { if ( !bld->production[b]->aiparam[ player ] ) calculateThreat ( bld->production[b] ); value += bld->production[b]->aiparam[ player ]->getValue() / 10; } if (bld->typ->special & cgrepairfacilityb ) value += ccbt_repairfacility; if (bld->typ->special & cghqb ) value += ccbt_hq; if (bld->typ->special & cgtrainingb ) value += ccbt_training; if (bld->typ->special & cgrecyclingplantb ) value += ccbt_recycling; bld->aiparam[ player ]->setValue ( value ); } void AI :: WeaponThreatRange :: run ( pvehicle _veh, int x, int y, AiThreat* _threat ) { threat = _threat; veh = _veh; for ( height = 0; height < 8; height++ ) for ( weap = 0; weap < veh->typ->weapons.count; weap++ ) if ( veh->height & veh->typ->weapons.weapon[weap].sourceheight ) if ( (1 << height) & veh->typ->weapons.weapon[weap].targ ) if ( veh->typ->weapons.weapon[weap].shootable() && veh->typ->weapons.weapon[weap].offensive() ) { initsearch ( MapCoordinate(x, y), veh->typ->weapons.weapon[weap].maxdistance/maxmalq, veh->typ->weapons.weapon[weap].mindistance/maxmalq ); startsearch(); } } void AI :: WeaponThreatRange :: testfield ( const MapCoordinate& mc ) { if ( dist*maxmalq <= veh->typ->weapons.weapon[weap].maxdistance ) if ( dist*maxmalq >= veh->typ->weapons.weapon[weap].mindistance ) { AttackFormula af; int strength = int ( weapDist.getWeapStrength( &veh->typ->weapons.weapon[weap], dist*maxmalq, veh->height, 1 << height ) * veh->typ->weapons.weapon[weap].maxstrength * (1 + af.strength_experience ( veh->experience ) + af.strength_attackbonus ( gamemap->getField(startPos)->getattackbonus() )) * af.strength_damage ( veh->damage ) ); if ( strength ) { int pos = mc.x + mc.y * ai->getMap()->xsize; if ( strength > threat[pos].threat[height] ) threat[pos].threat[height] = strength; } } } void AI :: calculateFieldInformation ( void ) { if ( fieldNum && fieldNum != activemap->xsize * activemap->ysize ) { delete[] fieldInformation; fieldInformation = NULL; fieldNum = 0; } if ( !fieldInformation ) { fieldNum = activemap->xsize * activemap->ysize; fieldInformation = new FieldInformation[ fieldNum ]; } else for ( int a = 0; a < fieldNum; a++ ) fieldInformation[ a ].reset(); AiThreat* singleUnitThreat = new AiThreat[fieldNum]; // we now check the whole map for ( int y = 0; y < activemap->ysize; y++ ) { checkKeys(); for ( int x = 0; x < activemap->xsize; x++ ) { pfield fld = getfield ( x, y ); if ( config.wholeMapVisible || fieldvisiblenow ( fld, getPlayerNum() ) ) if ( fld->vehicle && getdiplomaticstatus2 ( getPlayerNum()*8, fld->vehicle->color) == cawar ) { WeaponThreatRange wr ( this ); if ( !fld->vehicle->typ->wait ) { // The unit may have already moved this turn. // So we give it the maximum movementrange TemporaryContainerStorage tus ( fld->vehicle ); fld->vehicle->setMovement ( fld->vehicle->maxMovement(), 0); VehicleMovement vm ( NULL, NULL ); if ( vm.available ( fld->vehicle )) { vm.execute ( fld->vehicle, -1, -1, 0, -1, -1 ); // Now we cycle through all fields that are reachable... for ( int f = 0; f < vm.reachableFields.getFieldNum(); f++ ) { int xp, yp; vm.reachableFields.getFieldCoordinates ( f, &xp, &yp ); // ... and check for each which fields are threatened if the unit was standing there wr.run ( fld->vehicle, xp, yp, singleUnitThreat ); } for ( int g = 0; g < vm.reachableFieldsIndirect.getFieldNum(); g++ ) { int xp, yp; vm.reachableFieldsIndirect.getFieldCoordinates ( g, &xp, &yp ); wr.run ( fld->vehicle, xp, yp, singleUnitThreat ); } } tus.restore(); } else wr.run ( fld->vehicle, x, y, singleUnitThreat ); for ( int a = 0; a < fieldNum; a++ ) { for ( int b = 0; b < 8; b++ ) fieldInformation[a].threat.threat[b] += singleUnitThreat[a].threat[b]; singleUnitThreat[ a ].reset(); } } FieldInformation& fi = fieldInformation[y*getMap()->xsize+x]; for ( int i = 0; i< sidenum; i++ ) { pfield f = getMap()->getField ( getNeighbouringFieldCoordinate ( MapCoordinate(x,y), i )); if ( f && f->vehicle && f->vehicle->weapexist() && f->vehicle->color < 8*8 ) fi.units[f->vehicle->color/8] += 1; } int n = 0; int c = -1; for ( int i = 0; i < 8; i++ ) { if ( fi.units[i] > n ) { n = fi.units[i]; c = i; } } fi.control = c; } } } void AI :: calculateAllThreats( void ) { // Calculates the basethreats for all vehicle types if ( !baseThreatsCalculated ) { for ( int w = 0; w < vehicleTypeRepository.getNum(); w++) { pvehicletype fzt = vehicleTypeRepository.getObject_byPos(w); if ( fzt ) calculateThreat( fzt ); } baseThreatsCalculated = 1; } // Some further calculations that only need to be done once. if ( maxTrooperMove == 0) { for ( int v = 0; v < vehicleTypeRepository.getNum(); v++) { pvehicletype fzt = vehicleTypeRepository.getObject_byPos( v ); if ( fzt ) if ( fzt->functions & cf_conquer ) if ( fzt->movement[chfahrend] > maxTrooperMove ) // buildings can only be conquered on ground level, or by moving to adjecent field which is less maxTrooperMove = fzt->movement[chfahrend]; } } if ( maxTransportMove == 0 ) { for (int v = 0; v < vehicleTypeRepository.getNum(); v++) { pvehicletype fzt = vehicleTypeRepository.getObject_byPos( v ); if ( fzt ) for ( int w = 0; w <= 7; w++) // cycle through all levels of height if (fzt->movement[w] > maxTransportMove) maxTransportMove = fzt->movement[w]; } maxUnitMove = maxTransportMove; } for ( int height = 0; height < 8; height++ ) if ( maxWeapDist[height] < 0 ) { maxWeapDist[height] = 0; // It may be possible that there is no weapon to shoot to a specific height for ( int v = 0; v < vehicleTypeRepository.getNum(); v++) { pvehicletype fzt = vehicleTypeRepository.getObject_byPos( v ); if ( fzt ) for ( int w = 0; w < fzt->weapons.count ; w++) if ( fzt->weapons.weapon[w].maxdistance > maxWeapDist[height] ) if ( fzt->weapons.weapon[w].targ & ( 1 << height )) // targ is a bitmap, each bit standing for a level of height maxWeapDist[height] = fzt->weapons.weapon[w].maxdistance; } } // There are only 8 players in ASC, but there may be neutral units (player == 8) for ( int v = 0; v < 9; v++) if (activemap->player[v].exist() || v == 8) { // Now we cycle through all units of this player for ( Player::VehicleList::iterator vi = getPlayer(v).vehicleList.begin(); vi != getPlayer(v).vehicleList.end(); vi++ ) { pvehicle veh = *vi; // if ( !veh->aiparam[ getPlayerNum() ] ) calculateThreat ( veh ); } // Now we cycle through all buildings for ( Player::BuildingList::iterator bi = getPlayer(v).buildingList.begin(); bi != getPlayer(v).buildingList.end(); bi++ ) calculateThreat ( *bi ); } } void AI :: FieldInformation :: reset ( ) { threat.reset(); for ( int i = 0; i< 8; i++ ) units[i] = 0; control = -1; } AiThreat& AI :: getFieldThreat ( int x, int y ) { if ( !fieldInformation ) calculateFieldInformation (); return fieldInformation[y * activemap->xsize + x ].threat; } AI::FieldInformation& AI :: getFieldInformation ( int x, int y ) { if ( !fieldInformation ) calculateFieldInformation (); return fieldInformation[y * activemap->xsize + x ]; } void AI :: Section :: init ( AI* _ai, int _x, int _y, int xsize, int ysize, int _xp, int _yp ) { ai = _ai; init ( _x, _y, xsize, ysize, _xp, _yp ); } void AI :: Section :: init ( int _x, int _y, int xsize, int ysize, int _xp, int _yp ) { x1 = _x; y1 = _y; x2 = _x + xsize; y2 = _y + ysize; xp = _xp; yp = _yp; if ( x1 < 0 ) x1 = 0; if ( y1 < 0 ) y1 = 0; if ( x2 >= ai->activemap->xsize ) x2 = ai->activemap->xsize-1; if ( y2 >= ai->activemap->ysize ) y2 = ai->activemap->ysize-1; centerx = (x1 + x2) / 2; centery = (y1 + y2) / 2; numberOfFields = (x2-x1+1) * ( y2-y1+1); absUnitThreat.reset(); absFieldThreat.reset(); for ( int j = 0; j < aiValueTypeNum; j++ ) value[j] = 0; for ( int y = y1; y <= y2; y++ ) for ( int x = x1; x <= x2; x++ ) { absFieldThreat += ai->getFieldThreat ( x, y ); pfield fld = getfield ( x, y ); if ( fld->vehicle && getdiplomaticstatus ( fld->vehicle->color )==cawar) { if ( !fld->vehicle->aiparam[ ai->getPlayerNum() ] ) ai->calculateThreat ( fld->vehicle ); AiParameter& aip = * fld->vehicle->aiparam[ ai->getPlayerNum() ]; absUnitThreat += aip.threat; value[ aip.valueType ] += aip.getValue(); } } for ( int i = 0; i < absUnitThreat.threatTypes; i++ ) { avgUnitThreat.threat[i] = absUnitThreat.threat[i] / numberOfFields; avgFieldThreat.threat[i] = absFieldThreat.threat[i] / numberOfFields; } } int AI :: Section :: numberOfAccessibleFields ( const pvehicle veh ) { int num = 0; for ( int y = y1; y <= y2; y++ ) for ( int x = x1; x <= x2; x++ ) if ( fieldAccessible ( getfield ( x, y ), veh ) == 2) num++; return num; } AI :: Sections :: Sections ( AI* _ai ) : ai ( _ai ) , section ( NULL ) { sizeX = 8; sizeY = 16; numX = ai->activemap->xsize * 2 / sizeX + 1; numY = ai->activemap->ysize * 2 / sizeY + 1; } void AI :: Sections :: reset ( void ) { if ( section ) { delete[] section; section = NULL; } } void AI :: Sections :: calculate ( void ) { if ( !section ) { section = new Section[ numX*numY ]; // ( ai ); for ( int x = 0; x < numX; x++ ) for ( int y = 0; y < numY; y++ ) section[ x + numX * y ].init ( ai, x * ai->activemap->xsize / numX, y * ai->activemap->ysize / numY, sizeX, sizeY, x, y ); } } AI::Section& AI :: Sections :: getForCoordinate ( int xc, int yc ) { if ( !section ) calculate(); int dist = maxint; Section* sec = NULL; for ( int x = 0; x < numX; x++ ) for ( int y = 0; y < numY; y++ ) { Section& s2 = getForPos ( x, y ); int d = beeline ( xc, yc, s2.centerx, s2.centery); if ( d < dist ) { dist = d; sec = &getForPos ( x, y ); } } return *sec; } AI::Section& AI :: Sections :: getForPos ( int xn, int yn ) { if ( xn >= numX || yn >= numY || xn < 0 || yn < 0 ) displaymessage( "AI :: Sections :: getForPos - invalid parameters: %d %d", 2, xn, yn ); return section[xn+yn*numX]; } AI::Section* AI :: Sections :: getBest ( int pass, const pvehicle veh, MapCoordinate3D* dest, bool allowRefuellOrder, bool secondRun ) { /* In the first pass wwe check were all the units would go if there wouldn't be a threat anywhere. In the second pass the threat of a section is devided by the number of units that are going there */ AStar3D* ast = 0; RefuelConstraint* rfc = NULL; if ( RefuelConstraint::necessary ( veh, *ai )) { if ( secondRun ) rfc = new RefuelConstraint ( *ai, veh, veh->maxMovement()*5 ); else rfc = new RefuelConstraint ( *ai, veh ); rfc->findPath(); } else { ast = new AStar3D ( ai->getMap(), veh ); ast->findAllAccessibleFields ( ); } AiParameter& aip = *veh->aiparam[ ai->getPlayerNum() ]; float d = minfloat; // float nd = minfloat; AI::Section* frst = NULL; float maxSectionThread = 0; for ( int y = 0; y < numY; y++ ) for ( int x = 0; x < numX; x++ ) { AI::Section& sec = getForPos( x, y ); int threat = sec.avgUnitThreat.threat[aip.valueType]; if ( threat > maxSectionThread ) maxSectionThread = threat; } TemporaryContainerStorage tus ( veh ); veh->resetMovement(); // to make sure the wait-for-attack flag doesn't hinder the attack veh->attacked = 0; int sectionsPossibleWithMaxFuell = 0; for ( int h = 1; h < 0xff; h<<= 1 ) if ( veh->typ->height & h ) for ( int y = 0; y < numY; y++ ) for ( int x = 0; x < numX; x++ ) { int xtogoSec = -1; int ytogoSec = -1; AI::Section& sec = getForPos( x, y ); float t = 0; for ( int i = 0; i < aiValueTypeNum; i++ ) t += aip.threat.threat[i] * sec.value[i]; float f = t; if ( sec.avgUnitThreat.threat[ veh->getValueType(h) ] ) { int relThreat = int( 4*maxSectionThread / sec.avgUnitThreat.threat[veh->getValueType(h)] + 1); f /= relThreat; } /* if ( sec.avgUnitThreat.threat[aip.valueType] >= 0 ) f = t / log( sec.avgUnitThreat.threat[aip.valueType] ); else f = t; */ int dist = beeline ( veh->xpos, veh->ypos, sec.centerx, sec.centery ) + 3 * veh->maxMovement(); if ( dist ) f /= log(double(dist)); if ( f > d ) { int ac = 0; int nac = 0; int mindist = maxint; int targets = 0; for ( int yp = sec.y1; yp <= sec.y2; yp++ ) for ( int xp = sec.x1; xp <= sec.x2; xp++ ) { pfield fld = ai->getMap()->getField(xp, yp ); if ( fld->a.temp & h ) { int mandist = abs( sec.centerx - xp ) + 2*abs ( sec.centery - yp ); if ( mandist < mindist ) { mindist = mandist; xtogoSec = xp; ytogoSec = yp; } ai->_vision = visible_all; ac++; veh->xpos = xp; veh->ypos = yp; veh->height = h; VehicleAttack va ( NULL, NULL ); if ( va.available ( veh )) { va.execute ( veh, -1, -1, 0, 0, -1 ); targets += va.attackableVehicles.getFieldNum(); } ai->_vision = visible_ago; } else nac++; } if ( xtogoSec >= 0 && ytogoSec >= 0 ) if ( !rfc || rfc->returnFromPositionPossible ( MapCoordinate3D( xtogoSec, ytogoSec, h ))) { int notAccessible = 100 * nac / (nac+ac); if ( notAccessible < 85 && targets ) { // less than 85% of fields not accessible float nf = f * ( 100-notAccessible) / 100; // * ( 100 - notAccessible ); if ( nf > d ) { d = nf; // nd = nf; frst = &getForPos ( x, y ); if ( dest ) *dest = MapCoordinate3D ( xtogoSec, ytogoSec, h ); } } } else if ( allowRefuellOrder ) { if ( rfc && rfc->returnFromPositionPossible ( MapCoordinate3D( xtogoSec, ytogoSec, h ), veh->typ->tank.fuel )) sectionsPossibleWithMaxFuell++; } } } tus.restore(); delete ast; delete rfc; if ( !frst ) { if ( sectionsPossibleWithMaxFuell && allowRefuellOrder ) ai->issueRefuelOrder ( veh, false ); else if ( RefuelConstraint::necessary ( veh, *ai ) && !secondRun ) return getBest ( pass, veh, dest, allowRefuellOrder, true ); } return frst; }