| Attached Files | 
 targetBorder.diff  (10,828 bytes) 2007-06-17 15:05   Index: Lua/LuaWeaponDefs.cpp
===================================================================
--- Lua/LuaWeaponDefs.cpp	(revision 3823)
+++ Lua/LuaWeaponDefs.cpp	(working copy)
@@ -75,7 +75,7 @@
 				HSTR_PUSH(L, "__index");
 				lua_pushlightuserdata(L, (void*)wd);
 				lua_pushcclosure(L, WeaponDefIndex, 1);
-				lua_rawset(L, -3); // closure 
+				lua_rawset(L, -3); // closure
 
 				HSTR_PUSH(L, "__newindex");
 				lua_pushlightuserdata(L, (void*)wd);
@@ -110,7 +110,7 @@
 
 static int WeaponDefIndex(lua_State* L)
 {
-	// not a default value	
+	// not a default value
 	if (!lua_isstring(L, 2)) {
 		lua_rawget(L, 1);
 		return 1;
@@ -119,7 +119,7 @@
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
 
-	// not a default value	
+	// not a default value
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawget(L, 1);
 		return 1;
@@ -171,7 +171,7 @@
 
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
-	
+
 	// not a default value, set it
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawset(L, 1);
@@ -186,7 +186,7 @@
 	 	luaL_error(L, "Attempt to write WeaponDefs[%d].%s", wd->id, name);
 	 	return 0;
 	}
-	
+
 	// Definition editing
 	const DataElement& elem = it->second;
 	const char* p = ((const char*)wd) + elem.offset;
@@ -217,7 +217,7 @@
 			luaL_error(L, "ERROR_TYPE in WeaponDefs __newindex");
 		}
 	}
-	
+
 	return 0;
 }
 
@@ -266,7 +266,7 @@
 			}
 			// start the user parameters,
 			// remove the internal key and push a nil
-			lua_settop(L, 1); 
+			lua_settop(L, 1);
 			lua_pushnil(L);
 		}
 	}
@@ -314,8 +314,8 @@
 		LuaPushNamedNumber(L, typeList[i].c_str(), d.damages[i]);
 	}
 	lua_rawset(L, -3);
-	
-	return 1;		
+
+	return 1;
 }
 
 
@@ -417,9 +417,9 @@
 
 static bool InitParamMap()
 {
-	paramMap["next"]  = DataElement(READONLY_TYPE); 
-	paramMap["pairs"] = DataElement(READONLY_TYPE); 
-	
+	paramMap["next"]  = DataElement(READONLY_TYPE);
+	paramMap["pairs"] = DataElement(READONLY_TYPE);
+
 	// dummy WeaponDef for offset generation
 	const WeaponDef wd;
 	const char* start = ADDRESS(wd);
@@ -481,7 +481,7 @@
 	ADD_BOOL("noAutoTarget",   wd.noAutoTarget);
 	ADD_BOOL("manualFire",     wd.manualfire);
 	ADD_INT("targetable",      wd.targetable);
-	ADD_BOOL("stockpile",      wd.stockpile);					
+	ADD_BOOL("stockpile",      wd.stockpile);
 	ADD_INT("interceptor",     wd.interceptor);
 	ADD_FLOAT("coverageRange", wd.coverageRange);
 
@@ -541,6 +541,7 @@
 	ADD_INT("interceptedByShieldType",  wd.interceptedByShieldType);
 
 	ADD_BOOL("avoidFriendly", wd.avoidFriendly);
+	ADD_FLOAT("targetBorder", wd.targetBorder);
 
 //	CExplosionGenerator *explosionGenerator;
 
Index: Sim/Units/CommandAI/MobileCAI.cpp
===================================================================
--- Sim/Units/CommandAI/MobileCAI.cpp	(revision 3823)
+++ Sim/Units/CommandAI/MobileCAI.cpp	(working copy)
@@ -36,6 +36,7 @@
 	commandPos2(ZeroVector),
 	lastPC(-1),
 	cancelDistance(1024),
+	lastCloseInTry(-1),
 	slowGuard(false),
 	moveDir(gs->randFloat() > 0.5)
 {
@@ -597,7 +598,10 @@
 		float3 diff = owner->pos - orderTarget->pos;
 		// if w->AttackUnit() returned true then we are already
 		// in range with our biggest weapon so stop moving
-		if (b2) {
+		// also make sure that we're not locked in close-in/in-range state loop
+		// due to rotates invoked by in-range or out-of-range states
+		// FIXME kill magic frame number
+		if (b2 && gs->frameNum > lastCloseInTry + 40) {
 			StopMove();
 			owner->AttackUnit(orderTarget, c.id == CMD_DGUN);
 			owner->moveType->KeepPointingTo(orderTarget,
@@ -631,6 +635,7 @@
 				> (10 + orderTarget->pos.distance2D(owner->pos) * 0.2f)) {
 			float3 fix = orderTarget->pos + owner->posErrorVector * 128;
 			SetGoal(fix, owner->pos);
+			lastCloseInTry = gs->frameNum;
 		}
 	}
 
Index: Sim/Units/CommandAI/MobileCAI.h
===================================================================
--- Sim/Units/CommandAI/MobileCAI.h	(revision 3823)
+++ Sim/Units/CommandAI/MobileCAI.h	(working copy)
@@ -57,6 +57,7 @@
 
 protected:
 	int cancelDistance;
+	int lastCloseInTry;
 	bool slowGuard;
 	bool moveDir;
 	void PushOrUpdateReturnFight() {
Index: Sim/Units/UnitDef.h
===================================================================
--- Sim/Units/UnitDef.h	(revision 3823)
+++ Sim/Units/UnitDef.h	(working copy)
@@ -89,7 +89,7 @@
 	int imageSizeX;
 	int imageSizeY;
 	std::string buildpicname;
-	
+
 	UnitDef* decoyDef;
 
 	int aihint;
@@ -256,7 +256,7 @@
 	MoveData* movedata;
 //	unsigned char* yardmapLevels[6];
 	unsigned char* yardmaps[4];			//Iterations of the Ymap for building rotation
-	
+
 	int xsize;									//each size is 8 units
 	int ysize;									//each size is 8 units
 
Index: Sim/Units/UnitLoader.cpp
===================================================================
--- Sim/Units/UnitLoader.cpp	(revision 3823)
+++ Sim/Units/UnitLoader.cpp	(working copy)
@@ -464,6 +464,7 @@
 	weapon->fuelUsage = udw->fuelUsage;
 	weapon->avoidFriendly = weapondef->avoidFriendly;
 	weapon->avoidFeature = weapondef->avoidFeature;
+	weapon->targetBorder = weapondef->targetBorder;
 	weapon->collisionFlags = weapondef->collisionFlags;
 	weapon->Init();
 
Index: Sim/Weapons/BeamLaser.cpp
===================================================================
--- Sim/Weapons/BeamLaser.cpp	(revision 3823)
+++ Sim/Weapons/BeamLaser.cpp	(working copy)
@@ -85,6 +85,7 @@
 	}
 
 	float3 dir=pos-weaponPos;
+
 	float length=dir.Length();
 	if(length==0)
 		return true;
@@ -133,6 +134,7 @@
 		}
 	}
 	dir+=(salvoError)*(1-owner->limExperience*0.7f);
+
 	dir.Normalize();
 
 	FireInternal(dir, false);
@@ -152,6 +154,11 @@
 
 	bool tryAgain=true;
 	CUnit* hit;
+
+	if (targetType == Target_Unit && targetUnit && targetBorder != 0) {
+		maxLength += targetUnit->radius*targetBorder;
+	}
+
 	for(int tries=0;tries<5 && tryAgain;++tries){
 		tryAgain=false;
 		hit=0;
@@ -172,6 +179,7 @@
 				tryAgain=true;
 			}
 		}
+
 		hitPos=curPos+dir*length;
 
 		float baseAlpha=weaponDef->intensity*255;
Index: Sim/Weapons/Weapon.cpp
===================================================================
--- Sim/Weapons/Weapon.cpp	(revision 3823)
+++ Sim/Weapons/Weapon.cpp	(working copy)
@@ -83,6 +83,7 @@
 	CR_MEMBER(hasCloseTarget),
 	CR_MEMBER(avoidFriendly),
 	CR_MEMBER(avoidFeature),
+	CR_MEMBER(targetBorder),
 	CR_MEMBER(collisionFlags),
 	CR_MEMBER(fuelUsage)));
 
@@ -149,6 +150,7 @@
 	hasCloseTarget(false),
 	avoidFriendly(true),
 	avoidFeature(true),
+	targetBorder(false),
 	collisionFlags(0),
 	fuelUsage(0)
 {
@@ -541,8 +548,14 @@
 		return false;
 
 	float3 dif=pos-weaponPos;
+	float r=GetRange2D(owner->pos.y-pos.y);
 
-	float r=GetRange2D(owner->pos.y-pos.y);
+	if (targetBorder != 0 && unit) {
+		float3 diff(dif);
+		diff.Normalize();
+		dif -= diff*(unit->radius*targetBorder); // a little bit inside the sphere
+	}
+
 	if(dif.SqLength2D()>=r*r)
 		return false;
 
Index: Sim/Weapons/Weapon.h
===================================================================
--- Sim/Weapons/Weapon.h	(revision 3823)
+++ Sim/Weapons/Weapon.h	(working copy)
@@ -118,12 +118,13 @@
 	int lastErrorVectorUpdate;
 
 	CWeapon* slavedTo;						//use this weapon to choose target
-	
+
 	float3 mainDir;								//main aim dir of weapon
 	float maxMainDirAngleDif;					//how far away from main aim dir the weapon can aim at something (as an acos value)
 
 	bool avoidFriendly;		//if true tried to avoid friendly Units when aiming.
 	bool avoidFeature;      		//if true try to avoid Features while aiming.
+	float targetBorder;  // if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
 	unsigned int collisionFlags;
 
 	float fuelUsage;
Index: Sim/Weapons/WeaponDefHandler.cpp
===================================================================
--- Sim/Weapons/WeaponDefHandler.cpp	(revision 3823)
+++ Sim/Weapons/WeaponDefHandler.cpp	(working copy)
@@ -91,6 +91,16 @@
 	if(!collideFeature)
 		weaponDefs[id].collisionFlags+=COLLISION_NOFEATURE;
 
+	sunparser->GetDef(weaponDefs[id].targetBorder, "0", weaponname + "\\TargetBorder");
+	if (weaponDefs[id].targetBorder > 1.f) {
+		logOutput.Print("warning: targetBorder truncated to 1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = 1.f;
+	} else if (weaponDefs[id].targetBorder < -1.f) {
+		logOutput.Print("warning: targetBorder truncated to -1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = -1.f;
+	}
+
+
 	sunparser->GetDef(weaponDefs[id].dropped, "0", weaponname + "\\dropped");
 	sunparser->GetDef(lineofsight, "0", weaponname + "\\lineofsight");
 	sunparser->GetDef(ballistic, "0", weaponname + "\\ballistic");
@@ -119,7 +129,7 @@
 	sunparser->GetDef(weaponDefs[id].laserflaresize, "15", weaponname + "\\laserflaresize");
 	sunparser->GetDef(weaponDefs[id].intensity, "0.9", weaponname + "\\intensity");
 	sunparser->GetDef(weaponDefs[id].duration, "0.05", weaponname + "\\duration");
-	
+
 	sunparser->GetDef(weaponDefs[id].visuals.sizeDecay,  "0", weaponname + "\\sizeDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.alphaDecay, "1", weaponname + "\\alphaDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.separation, "1", weaponname + "\\separation");
@@ -576,7 +586,7 @@
 
 	if (inverted == true) {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = damages[i] - (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
@@ -591,7 +601,7 @@
 	}
 	else {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
Index: Sim/Weapons/WeaponDefHandler.h
===================================================================
--- Sim/Weapons/WeaponDefHandler.h	(revision 3823)
+++ Sim/Weapons/WeaponDefHandler.h	(working copy)
@@ -86,7 +86,7 @@
 	bool manualfire;			//use dgun button
 	int interceptor;				//anti nuke
 	int targetable;				//nuke (can be shot by interceptor)
-	bool stockpile;					
+	bool stockpile;
 	float coverageRange;		//range of anti nuke
 
 	float intensity;
@@ -138,7 +138,7 @@
 		float tilelength;
 		float scrollspeed;
 		float pulseSpeed;
-		
+
 		int stages;
 		float alphaDecay;
 		float sizeDecay;
@@ -172,6 +172,7 @@
 
 	bool avoidFriendly;		//if true try to avoid friendly Units when aiming.
 	bool avoidFeature;      //if true try to avoid Features while aiming.
+	float targetBorder;     //if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
 	unsigned int collisionFlags;
 
 	CExplosionGenerator *explosionGenerator; // can be zero for default explosions
 targetborder_minintensity_cylinderTargetting_and_weaponMuzzlePos.diff  (32,157 bytes) 2007-06-22 19:01   Index: rts/Game/GameHelper.cpp
===================================================================
--- rts/Game/GameHelper.cpp	(revision 3829)
+++ rts/Game/GameHelper.cpp	(working copy)
@@ -209,16 +209,17 @@
 				closeLength=length;
 			float3 closeVect=dif-dir*closeLength;
 
-			float rad=(*ui)->radius;
-			float tmp = rad * rad - closeVect.SqLength();
-			if(tmp > 0 && length>closeLength+sqrt(tmp)){
+			/*float rad=(*ui)->radius;
+			float tmp = rad * rad - closeVect.SqLength();*/
+
+			/*if(tmp > 0 && length>closeLength+sqrt(tmp)){
 				length=closeLength-sqrt(tmp)*0.5f;
 				hit=*ui;
-			}
-/*			if(closeVect.SqLength() < (*ui)->sqRadius){
+			}*/
+			if(closeVect.SqLength() < (*ui)->sqRadius){
 				length=closeLength;
 				hit=*ui;
-			}*/
+			}
 		}
 	}
 	return length;
Index: rts/Lua/LuaWeaponDefs.cpp
===================================================================
--- rts/Lua/LuaWeaponDefs.cpp	(revision 3829)
+++ rts/Lua/LuaWeaponDefs.cpp	(working copy)
@@ -75,7 +75,7 @@
 				HSTR_PUSH(L, "__index");
 				lua_pushlightuserdata(L, (void*)wd);
 				lua_pushcclosure(L, WeaponDefIndex, 1);
-				lua_rawset(L, -3); // closure 
+				lua_rawset(L, -3); // closure
 
 				HSTR_PUSH(L, "__newindex");
 				lua_pushlightuserdata(L, (void*)wd);
@@ -110,7 +110,7 @@
 
 static int WeaponDefIndex(lua_State* L)
 {
-	// not a default value	
+	// not a default value
 	if (!lua_isstring(L, 2)) {
 		lua_rawget(L, 1);
 		return 1;
@@ -119,7 +119,7 @@
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
 
-	// not a default value	
+	// not a default value
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawget(L, 1);
 		return 1;
@@ -171,7 +171,7 @@
 
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
-	
+
 	// not a default value, set it
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawset(L, 1);
@@ -186,7 +186,7 @@
 	 	luaL_error(L, "Attempt to write WeaponDefs[%d].%s", wd->id, name);
 	 	return 0;
 	}
-	
+
 	// Definition editing
 	const DataElement& elem = it->second;
 	const char* p = ((const char*)wd) + elem.offset;
@@ -217,7 +217,7 @@
 			luaL_error(L, "ERROR_TYPE in WeaponDefs __newindex");
 		}
 	}
-	
+
 	return 0;
 }
 
@@ -266,7 +266,7 @@
 			}
 			// start the user parameters,
 			// remove the internal key and push a nil
-			lua_settop(L, 1); 
+			lua_settop(L, 1);
 			lua_pushnil(L);
 		}
 	}
@@ -314,8 +314,8 @@
 		LuaPushNamedNumber(L, typeList[i].c_str(), d.damages[i]);
 	}
 	lua_rawset(L, -3);
-	
-	return 1;		
+
+	return 1;
 }
 
 
@@ -417,9 +417,9 @@
 
 static bool InitParamMap()
 {
-	paramMap["next"]  = DataElement(READONLY_TYPE); 
-	paramMap["pairs"] = DataElement(READONLY_TYPE); 
-	
+	paramMap["next"]  = DataElement(READONLY_TYPE);
+	paramMap["pairs"] = DataElement(READONLY_TYPE);
+
 	// dummy WeaponDef for offset generation
 	const WeaponDef wd;
 	const char* start = ADDRESS(wd);
@@ -481,7 +481,7 @@
 	ADD_BOOL("noAutoTarget",   wd.noAutoTarget);
 	ADD_BOOL("manualFire",     wd.manualfire);
 	ADD_INT("targetable",      wd.targetable);
-	ADD_BOOL("stockpile",      wd.stockpile);					
+	ADD_BOOL("stockpile",      wd.stockpile);
 	ADD_INT("interceptor",     wd.interceptor);
 	ADD_FLOAT("coverageRange", wd.coverageRange);
 
@@ -541,6 +541,9 @@
 	ADD_INT("interceptedByShieldType",  wd.interceptedByShieldType);
 
 	ADD_BOOL("avoidFriendly", wd.avoidFriendly);
+	ADD_FLOAT("targetBorder", wd.targetBorder);
+	ADD_FLOAT("cylinderTargetting", wd.cylinderTargetting);
+	ADD_FLOAT("minIntensity", wd.minIntensity);
 
 //	CExplosionGenerator *explosionGenerator;
 
Index: rts/Sim/Projectiles/ProjectileHandler.cpp
===================================================================
--- rts/Sim/Projectiles/ProjectileHandler.cpp	(revision 3829)
+++ rts/Sim/Projectiles/ProjectileHandler.cpp	(working copy)
@@ -31,8 +31,8 @@
 
 CProjectileHandler* ph;
 using namespace std;
-extern GLfloat FogBlack[]; 
-extern GLfloat FogLand[]; 
+extern GLfloat FogBlack[];
+extern GLfloat FogLand[];
 
 CR_BIND(CProjectileHandler,);
 
@@ -137,7 +137,7 @@
 	wrecktex            = textureAtlas->GetTextureWithBackup(  "wrecktexture",           "circularthingy"  );
 	plasmatex           = textureAtlas->GetTextureWithBackup(  "plasmatexture",          "circularthingy"  );
 
-	
+
 	groundFXAtlas = SAFE_NEW CTextureAtlas(2048, 2048);
 	//add all textures in groundfx section
 	ptex = resources.GetAllValues("resources\\graphics\\groundfx");
@@ -328,9 +328,9 @@
 
 	int numFlyingPieces = 0;
 	int drawnPieces = 0;
-	
+
 	/* Putting in, say, viewport culling will deserve refactoring. */
-	
+
 	/* 3DO */
 	unitDrawer->SetupForUnitDrawing();
 
@@ -376,27 +376,27 @@
 
 	for (int textureType = 1; textureType < flyings3oPieces.size(); textureType++){
 		/* TODO Skip this if there's no FlyingPieces. */
-		
+
 		texturehandler->SetS3oTexture(textureType);
-		
+
 		for (int team = 0; team < flyings3oPieces[textureType].size(); team++){
 			FlyingPiece_List * fpl = flyings3oPieces[textureType][team];
-		
+
 			unitDrawer->SetS3OTeamColour(team);
-			
+
 			va->Initialize();
-			
+
 			numFlyingPieces += fpl->size();
-		
+
 			for(std::list<FlyingPiece*>::iterator pi=fpl->begin();pi!=fpl->end();++pi){
 				CMatrix44f m;
 				m.Rotate((*pi)->rot,(*pi)->rotAxis);
 				float3 interPos=(*pi)->pos+(*pi)->speed*gu->timeOffset;
-				
+
 				SS3OVertex * verts = (*pi)->verts;
-				
+
 				float3 tp, tn;
-				
+
 				for (int i = 0; i < 4; i++){
 					tp=m.Mul(verts[i].pos);
 					tn=m.Mul(verts[i].normal);
@@ -408,9 +408,9 @@
 			va->DrawArrayTN(GL_QUADS);
 		}
 	}
-	
+
 	unitDrawer->CleanUpS3ODrawing();
-	
+
 	/*
 	 * TODO Nearly cut here.
 	 */
@@ -565,7 +565,7 @@
 						if(readmap->groundBlockingObjectMap[square]!=unit)
 							continue;
 					}
-					//adjust projectile position so explosion happens at the correct position 
+					//adjust projectile position so explosion happens at the correct position
 					p->pos = p->pos + p->speed*closeTime;
 					p->Collision(*ui);
 					break;
@@ -603,7 +603,7 @@
 				}
 			}
 		}
-	}	
+	}
 }
 
 void CProjectileHandler::AddGroundFlash(CGroundFlash* flash)
@@ -687,7 +687,7 @@
 		flyings3oPieces[textureType].push_back(fpl);
 		flyingPieces.push_back(fpl);
 	}
-	
+
 	pieceList=flyings3oPieces[textureType][team];
 
 	FlyingPiece* fp=new FlyingPiece;
@@ -745,7 +745,7 @@
 	glDisable(GL_ALPHA_TEST);
 	glDisable(GL_FOG);
 
-	unsigned char col[4];	
+	unsigned char col[4];
 	float time=gu->lastFrameTime*gs->speedFactor*3;
 	float speed=1;
 	float size=1;
Index: rts/Sim/Projectiles/WeaponProjectile.cpp
===================================================================
--- rts/Sim/Projectiles/WeaponProjectile.cpp	(revision 3829)
+++ rts/Sim/Projectiles/WeaponProjectile.cpp	(working copy)
@@ -45,7 +45,7 @@
 	interceptTarget=0;
 }
 
-CWeaponProjectile::CWeaponProjectile(const float3& pos,const float3& speed,CUnit* owner, CUnit* target,const float3 &targetPos, WeaponDef *weaponDef,CWeaponProjectile* interceptTarget, bool synced) : 
+CWeaponProjectile::CWeaponProjectile(const float3& pos,const float3& speed,CUnit* owner, CUnit* target,const float3 &targetPos, WeaponDef *weaponDef,CWeaponProjectile* interceptTarget, bool synced) :
 	CProjectile(pos,speed,owner, synced),
 	weaponDef(weaponDef),
 	weaponDefName(weaponDef?weaponDef->name:std::string("")),
@@ -120,7 +120,7 @@
 		DamageArray dynDamages;
 		if (weaponDef->dynDamageExp > 0)
 			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, startpos, pos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
-		
+
 		helper->Explosion(pos,weaponDef->dynDamageExp>0?dynDamages:weaponDef->damages,weaponDef->areaOfEffect,weaponDef->edgeEffectiveness,weaponDef->explosionSpeed,owner,true,weaponDef->noExplode? 0.3f:1,weaponDef->noExplode || weaponDef->noSelfDamage, weaponDef->explosionGenerator,0,impactDir, weaponDef->id);
 	}
 
@@ -155,7 +155,7 @@
 		DamageArray dynDamages;
 		if (weaponDef->dynDamageExp > 0)
 			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, startpos, pos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
-			
+
 		helper->Explosion(pos,weaponDef->dynDamageExp>0?dynDamages:weaponDef->damages,weaponDef->areaOfEffect,weaponDef->edgeEffectiveness,weaponDef->explosionSpeed,owner,true,weaponDef->noExplode? 0.3f:1,weaponDef->noExplode,weaponDef->explosionGenerator,unit,impactDir, weaponDef->id);
 	}
 
@@ -226,7 +226,7 @@
 	transMatrix[12]=interPos.x;
 	transMatrix[13]=interPos.y;
 	transMatrix[14]=interPos.z;
-	glMultMatrixf(&transMatrix[0]);		
+	glMultMatrixf(&transMatrix[0]);
 
 	glCallList(modelDispList);
 	glPopMatrix();
Index: rts/Sim/Units/CommandAI/MobileCAI.cpp
===================================================================
--- rts/Sim/Units/CommandAI/MobileCAI.cpp	(revision 3829)
+++ rts/Sim/Units/CommandAI/MobileCAI.cpp	(working copy)
@@ -42,6 +42,8 @@
 				CR_MEMBER(commandPos1),
 				CR_MEMBER(commandPos2),
 
+				CR_MEMBER(lastCloseInTry),
+				
 				CR_MEMBER(cancelDistance),
 				CR_MEMBER(slowGuard),
 				CR_MEMBER(moveDir)
@@ -61,6 +63,7 @@
 	commandPos2(ZeroVector),
 	lastPC(-1),
 	cancelDistance(1024),
+	lastCloseInTry(-1),
 	slowGuard(false),
 	moveDir(gs->randFloat() > 0.5),
 	lastUserGoal(0,0,0)
@@ -81,6 +84,7 @@
 	commandPos2(ZeroVector),
 	lastPC(-1),
 	cancelDistance(1024),
+	lastCloseInTry(-1),
 	slowGuard(false),
 	moveDir(gs->randFloat() > 0.5)
 {
@@ -582,7 +586,8 @@
 			// check if we have valid target parameter and that we aren't attacking ourselves
 			if (uh->units[unitID] != 0 && uh->units[unitID] != owner) {
 				float3 fix = uh->units[unitID]->pos + owner->posErrorVector * 128;
-				SetGoal(fix, owner->pos);
+				float3 diff = float3(fix - owner->pos).Normalize();
+				SetGoal(fix - diff*uh->units[unitID]->radius, owner->pos);
 				// get ID of attack-order target unit
 				orderTarget = uh->units[unitID];
 				AddDeathDependence(orderTarget);
@@ -626,6 +631,7 @@
 		//bool b1 = owner->AttackUnit(orderTarget, c.id == CMD_DGUN);
 		bool b2 = false;
 		bool b3 = false;
+		float edgeFactor = 0.f; // percent offset to target center
 
 		if (owner->weapons.size() > 0) {
 			if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) {
@@ -638,15 +644,21 @@
 			// can hit target with our first (meanest) one
 			b2 = w->TryTargetRotate(orderTarget, c.id == CMD_DGUN);
 			b3 = (w->range - (w->relWeaponPos).Length()) > (orderTarget->pos.distance(owner->pos));
+			edgeFactor = fabs(w->targetBorder);
 		}
 		float3 diff = owner->pos - orderTarget->pos;
 		// if w->AttackUnit() returned true then we are already
 		// in range with our biggest weapon so stop moving
+		// also make sure that we're not locked in close-in/in-range state loop
+		// due to rotates invoked by in-range or out-of-range states
 		if (b2) {
 			StopMove();
 			owner->AttackUnit(orderTarget, c.id == CMD_DGUN);
-			owner->moveType->KeepPointingTo(orderTarget,
-				min((float) (owner->losRadius * SQUARE_SIZE * 2), owner->maxRange * 0.9f), true);
+			// FIXME kill magic frame number
+			if (gs->frameNum > lastCloseInTry + MAX_CLOSE_IN_RETRY_TICKS) {
+				owner->moveType->KeepPointingTo(orderTarget,
+					min((float) (owner->losRadius * SQUARE_SIZE * 2), owner->maxRange * 0.9f), true);
+			}
 		}
 
 		// if (((first weapon range minus first weapon length greater than distance to target
@@ -675,7 +687,10 @@
 		else if ((orderTarget->pos + owner->posErrorVector * 128).distance2D(goalPos)
 				> (10 + orderTarget->pos.distance2D(owner->pos) * 0.2f)) {
 			float3 fix = orderTarget->pos + owner->posErrorVector * 128;
-			SetGoal(fix, owner->pos);
+			float3 norm = float3(fix - owner->pos).Normalize();
+			SetGoal(fix - norm*(orderTarget->radius*edgeFactor*0.8f), owner->pos);
+			if (lastCloseInTry < gs->frameNum + MAX_CLOSE_IN_RETRY_TICKS)
+				lastCloseInTry = gs->frameNum;
 		}
 	}
 
Index: rts/Sim/Units/CommandAI/MobileCAI.h
===================================================================
--- rts/Sim/Units/CommandAI/MobileCAI.h	(revision 3829)
+++ rts/Sim/Units/CommandAI/MobileCAI.h	(working copy)
@@ -59,6 +59,7 @@
 
 protected:
 	int cancelDistance;
+	int lastCloseInTry;
 	bool slowGuard;
 	bool moveDir;
 	void PushOrUpdateReturnFight() {
@@ -66,5 +67,6 @@
 	}
 };
 
+#define MAX_CLOSE_IN_RETRY_TICKS 30
 
 #endif /* MOBILECAI_H */
Index: rts/Sim/Units/UnitLoader.cpp
===================================================================
--- rts/Sim/Units/UnitLoader.cpp	(revision 3829)
+++ rts/Sim/Units/UnitLoader.cpp	(working copy)
@@ -466,6 +466,9 @@
 	weapon->fuelUsage = udw->fuelUsage;
 	weapon->avoidFriendly = weapondef->avoidFriendly;
 	weapon->avoidFeature = weapondef->avoidFeature;
+	weapon->targetBorder = weapondef->targetBorder;
+	weapon->cylinderTargetting = weapondef->cylinderTargetting;
+	weapon->minIntensity = weapondef->minIntensity;
 	weapon->collisionFlags = weapondef->collisionFlags;
 	weapon->Init();
 
Index: rts/Sim/Weapons/BeamLaser.cpp
===================================================================
--- rts/Sim/Weapons/BeamLaser.cpp	(revision 3829)
+++ rts/Sim/Weapons/BeamLaser.cpp	(working copy)
@@ -42,6 +42,7 @@
 {
 	if(targetType!=Target_None){
 		weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
+		weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
 		if(!onlyForward){
 			wantedDir=targetPos-weaponPos;
 			wantedDir.Normalize();
@@ -84,7 +85,8 @@
 			return false;
 	}
 
-	float3 dir=pos-weaponPos;
+	float3 dir=pos-weaponMuzzlePos;
+
 	float length=dir.Length();
 	if(length==0)
 		return true;
@@ -92,14 +94,14 @@
 	dir/=length;
 
 	if(!onlyForward){		//skip ground col testing for aircrafts
-		float g=ground->LineGroundCol(weaponPos,pos);
+		float g=ground->LineGroundCol(weaponMuzzlePos,pos);
 		if(g>0 && g<length*0.9f)
 			return false;
 	}
-	if(avoidFeature && helper->LineFeatureCol(weaponPos,dir,length))
+	if(avoidFeature && helper->LineFeatureCol(weaponMuzzlePos,dir,length))
 		return false;
 
-	if(avoidFriendly && helper->TestCone(weaponPos,dir,length,(accuracy+sprayangle)*(1-owner->limExperience*0.7f),owner->allyteam,owner))
+	if(avoidFriendly && helper->TestCone(weaponMuzzlePos,dir,length,(accuracy+sprayangle)*(1-owner->limExperience*0.7f),owner->allyteam,owner))
 		return false;
 	return true;
 }
@@ -125,7 +127,7 @@
 		if(salvoLeft==salvoSize-1){
 			if(fireSoundId)
 				sound->PlaySample(fireSoundId,owner,fireSoundVolume);
-			dir=targetPos-weaponPos;
+			dir=targetPos-weaponMuzzlePos;
 			dir.Normalize();
 			oldDir=dir;
 		} else {
@@ -133,6 +135,7 @@
 		}
 	}
 	dir+=(salvoError)*(1-owner->limExperience*0.7f);
+
 	dir.Normalize();
 
 	FireInternal(dir, false);
@@ -145,13 +148,27 @@
 	if(owner->directControl)
 		rangeMod=0.95f;
 #endif
+
 	float maxLength=range*rangeMod;
 	float curLength=0;
-	float3 curPos=weaponPos;
+	float3 curPos=weaponMuzzlePos;
 	float3 hitPos;
 
 	bool tryAgain=true;
 	CUnit* hit;
+
+	// increase range if targets are searched for in a cylinder
+	if (cylinderTargetting > 0.01) {
+		const float3 up(0, owner->radius*cylinderTargetting, 0);
+		const float uplen = up.dot(dir);
+		maxLength = sqrt(maxLength*maxLength + uplen*uplen);
+	}
+
+	// increase range if targetting edge of hitsphere
+	if (targetType == Target_Unit && targetUnit && targetBorder != 0) {
+		maxLength += targetUnit->radius*targetBorder;
+	}
+
 	for(int tries=0;tries<5 && tryAgain;++tries){
 		tryAgain=false;
 		hit=0;
@@ -172,6 +189,7 @@
 				tryAgain=true;
 			}
 		}
+
 		hitPos=curPos+dir*length;
 
 		float baseAlpha=weaponDef->intensity*255;
@@ -187,12 +205,20 @@
 		curLength+=length;
 		dir=newDir;
 	}
-	float	intensity=1-(curLength)/(range*2);
+
+	// fix negative damage when hitting big spheres
+	float actualRange = range;
+	if (hit && targetBorder > 0) {
+		actualRange += hit->radius*targetBorder;
+	}
+	// make it possible to always hit with some minimal intensity (melee weapons have use for that)
+	float intensity=max(minIntensity, 1-(curLength)/(actualRange*2));
+
 	if(curLength<maxLength) {
 		// Dynamic Damage
 		DamageArray dynDamages;
 		if (weaponDef->dynDamageExp > 0)
-			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, weaponPos, curPos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
+			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, weaponMuzzlePos, curPos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
 		helper->Explosion(hitPos, weaponDef->dynDamageExp>0?dynDamages*(intensity*damageMul):weaponDef->damages*(intensity*damageMul), areaOfEffect, weaponDef->edgeEffectiveness, weaponDef->explosionSpeed,owner, true, 1.0f, false, weaponDef->explosionGenerator, hit, dir, weaponDef->id);
 	}
 
Index: rts/Sim/Weapons/Weapon.cpp
===================================================================
--- rts/Sim/Weapons/Weapon.cpp	(revision 3829)
+++ rts/Sim/Weapons/Weapon.cpp	(working copy)
@@ -56,9 +56,11 @@
 	CR_MEMBER(subClassReady),
 	CR_MEMBER(onlyForward),
 	CR_MEMBER(weaponPos),
+	CR_MEMBER(weaponMuzzlePos),
 	CR_MEMBER(lastRequest),
 	CR_MEMBER(damages),
 	CR_MEMBER(relWeaponPos),
+	CR_MEMBER(relWeaponMuzzlePos),
 	CR_MEMBER(muzzleFlareSize),
 	CR_MEMBER(lastTargetRetry),
 	CR_MEMBER(areaOfEffect),
@@ -83,6 +85,9 @@
 	CR_MEMBER(hasCloseTarget),
 	CR_MEMBER(avoidFriendly),
 	CR_MEMBER(avoidFeature),
+	CR_MEMBER(targetBorder),
+	CR_MEMBER(cylinderTargetting),
+	CR_MEMBER(minIntensity),
 	CR_MEMBER(collisionFlags),
 	CR_MEMBER(fuelUsage),
 	CR_MEMBER(weaponNum)
@@ -127,8 +132,10 @@
 	subClassReady(true),
 	onlyForward(false),
 	weaponPos(0,0,0),
+	weaponMuzzlePos(0,0,0),
 	lastRequest(0),
 	relWeaponPos(0,1,0),
+	relWeaponMuzzlePos(0,1,0),
 	muzzleFlareSize(1),
 	lastTargetRetry(-100),
 	areaOfEffect(1),
@@ -151,6 +158,9 @@
 	hasCloseTarget(false),
 	avoidFriendly(true),
 	avoidFeature(true),
+	targetBorder(0.f),
+	cylinderTargetting(0.f),
+	minIntensity(0.f),
 	collisionFlags(0),
 	fuelUsage(0)
 {
@@ -164,15 +174,20 @@
 
 void CWeapon::Update()
 {
+	// do not fire at cloaked units
+	if(targetType == Target_Unit && targetUnit && targetUnit->isCloaked) {
+		HoldFire();
+		return;
+	}
+
 	if(hasCloseTarget){
 		std::vector<int> args;
 		args.push_back(0);
-		if(useWeaponPosForAim){
-			owner->cob->Call(COBFN_QueryPrimary+weaponNum,args);
-		} else {
-			owner->cob->Call(COBFN_AimFromPrimary+weaponNum,args);
-		}
+		owner->cob->Call(COBFN_AimFromPrimary+weaponNum,args);
 		relWeaponPos=owner->localmodel->GetPiecePos(args[0]);
+
+		owner->cob->Call(COBFN_QueryPrimary+weaponNum,args);
+		relWeaponMuzzlePos=owner->localmodel->GetPiecePos(args[0]);
 	}
 
 	if(targetType==Target_Unit){
@@ -249,15 +264,15 @@
 	&& subClassReady
 	&& reloadStatus<=gs->frameNum
 	&& (!weaponDef->stockpile || numStockpiled)
-	&& (weaponDef->waterweapon || weaponPos.y>0)
+	&& (weaponDef->waterweapon || weaponMuzzlePos.y>0)
 	&& (owner->unitDef->maxFuel==0 || owner->currentFuel > 0)
 	){
 		if ((weaponDef->stockpile || (gs->Team(owner->team)->metal>=metalFireCost && gs->Team(owner->team)->energy>=energyFireCost))) {
 			std::vector<int> args;
 			args.push_back(0);
 			owner->cob->Call(COBFN_QueryPrimary+weaponNum,args);
-			relWeaponPos=owner->localmodel->GetPiecePos(args[0]);
-			weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
+			relWeaponMuzzlePos=owner->localmodel->GetPiecePos(args[0]);
+			weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
 			useWeaponPosForAim=reloadTime/16+8;
 
 			if(TryTarget(targetPos,haveUserTarget,targetUnit)){
@@ -310,10 +325,16 @@
 
 		std::vector<int> args;
 		args.push_back(0);
-		owner->cob->Call(/*COBFN_AimFromPrimary+weaponNum/*/COBFN_QueryPrimary+weaponNum/**/,args);
+		owner->cob->Call(COBFN_AimFromPrimary+weaponNum,args);
 		relWeaponPos=owner->localmodel->GetPiecePos(args[0]);
+
+		owner->cob->Call(/*COBFN_AimFromPrimary+weaponNum*/COBFN_QueryPrimary+weaponNum/**/,args);
+		relWeaponMuzzlePos=owner->localmodel->GetPiecePos(args[0]);
+
 		weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
 
+		weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
+
 //		logOutput.Print("RelPosFire %f %f %f",relWeaponPos.x,relWeaponPos.y,relWeaponPos.z);
 
 		if (owner->unitDef->decloakOnFire && (owner->scriptCloak <= 2)) {
@@ -353,9 +374,9 @@
 
 	if(!weaponDef->waterweapon && pos.y<1)
 		pos.y=1;
-	weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
-	if(weaponPos.y<ground->GetHeight2(weaponPos.x,weaponPos.z))
-		weaponPos=owner->pos+UpVector*10;		//hope that we are underground because we are a popup weapon and will come above ground later
+	weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
+	if(weaponMuzzlePos.y<ground->GetHeight2(weaponMuzzlePos.x,weaponMuzzlePos.z))
+		weaponMuzzlePos=owner->pos+UpVector*10;		//hope that we are underground because we are a popup weapon and will come above ground later
 
 	if(!TryTarget(pos,userTarget,0))
 		return false;
@@ -378,8 +399,10 @@
 
 	weaponPos= owner->pos + owner->frontdir * relWeaponPos.z
 		+ owner->updir * relWeaponPos.y + owner->rightdir * relWeaponPos.x;
-	if(weaponPos.y < ground->GetHeight2(weaponPos.x, weaponPos.z))
-		weaponPos = owner->pos + UpVector * 10;
+	weaponMuzzlePos= owner->pos + owner->frontdir * relWeaponMuzzlePos.z
+		+ owner->updir * relWeaponMuzzlePos.y + owner->rightdir * relWeaponMuzzlePos.x;
+	if(weaponMuzzlePos.y < ground->GetHeight2(weaponMuzzlePos.x, weaponMuzzlePos.z))
+		weaponMuzzlePos = owner->pos + UpVector * 10;
 	//hope that we are underground because we are a popup weapon and will come above ground later
 
 	if(!unit){
@@ -427,18 +450,17 @@
 #endif
 	std::vector<int> args;
 	args.push_back(0);
-	if(useWeaponPosForAim){
-		owner->cob->Call(COBFN_QueryPrimary+weaponNum,args);
-		if(useWeaponPosForAim>1)
-			useWeaponPosForAim--;
-	} else {
-		owner->cob->Call(COBFN_AimFromPrimary+weaponNum,args);
-	}
+	owner->cob->Call(COBFN_AimFromPrimary+weaponNum,args);
 	relWeaponPos=owner->localmodel->GetPiecePos(args[0]);
 	weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
-	if(weaponPos.y<ground->GetHeight2(weaponPos.x,weaponPos.z))
-		weaponPos=owner->pos+UpVector*10;		//hope that we are underground because we are a popup weapon and will come above ground later
 
+	owner->cob->Call(COBFN_QueryPrimary+weaponNum,args);
+	relWeaponMuzzlePos=owner->localmodel->GetPiecePos(args[0]);
+	weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
+
+	if(weaponMuzzlePos.y<ground->GetHeight2(weaponMuzzlePos.x,weaponMuzzlePos.z))
+		weaponMuzzlePos=owner->pos+UpVector*10;		//hope that we are underground because we are a popup weapon and will come above ground later
+
 	predictSpeedMod=1+(gs->randFloat()-0.5f)*2*(1-owner->limExperience);
 
 	if((targetPos-weaponPos).SqLength() < relWeaponPos.SqLength()*16)
@@ -542,9 +564,33 @@
 	if(weaponDef->stockpile && !numStockpiled)
 		return false;
 
-	float3 dif=pos-weaponPos;
+	float3 dif=pos-weaponMuzzlePos;
 
-	float r=GetRange2D(owner->pos.y-pos.y);
+	if (targetBorder != 0 && unit) {
+		float3 diff(dif);
+		diff.Normalize();
+		// weapon inside target sphere
+		if (dif.SqLength() < unit->sqRadius*targetBorder*targetBorder) {
+			dif -= diff*(dif.Length() - 10); // a hack
+			//logOutput << "inside\n";
+		} else {
+			dif -= diff*(unit->radius*targetBorder);
+			//logOutput << "outside\n";
+		}
+		//geometricObjects->AddLine(weaponMuzzlePos, weaponMuzzlePos+dif, 3, 0, 16);
+	}
+
+	float r;
+	if (!unit || cylinderTargetting < 0.01) {
+		r=GetRange2D(owner->pos.y-pos.y);
+	} else {
+		if (cylinderTargetting * unit->radius > owner->pos.y-pos.y) {
+			r = GetRange2D(0);
+		} else {
+			r = 0;
+		}
+	}
+
 	if(dif.SqLength2D()>=r*r)
 		return false;
 
@@ -586,11 +632,13 @@
 	owner->frontdir = GetVectorFromHeading(owner->heading);
 	owner->rightdir = owner->frontdir.cross(owner->updir);
 	weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
+	weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
 	bool val = TryTarget(tempTargetPos,userTarget,unit);
 	owner->frontdir = tempfrontdir;
 	owner->rightdir = temprightdir;
 	owner->heading = tempHeadding;
 	weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
+	weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
 	return val;
 }
 
@@ -616,11 +664,13 @@
 	owner->frontdir = GetVectorFromHeading(owner->heading);
 	owner->rightdir = owner->frontdir.cross(owner->updir);
 	weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
+	weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
 	bool val = TryTarget(pos, userTarget, 0);
 	owner->frontdir = tempfrontdir;
 	owner->rightdir = temprightdir;
 	owner->heading = tempHeadding;
 	weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
+	weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
 	return val;
 }
 
@@ -631,6 +681,10 @@
 	owner->cob->Call(COBFN_AimFromPrimary+weaponNum,args);
 	relWeaponPos=owner->localmodel->GetPiecePos(args[0]);
 	weaponPos=owner->pos+owner->frontdir*relWeaponPos.z+owner->updir*relWeaponPos.y+owner->rightdir*relWeaponPos.x;
+
+	owner->cob->Call(COBFN_QueryPrimary+weaponNum,args);
+	relWeaponMuzzlePos=owner->localmodel->GetPiecePos(args[0]);
+	weaponMuzzlePos=owner->pos+owner->frontdir*relWeaponMuzzlePos.z+owner->updir*relWeaponMuzzlePos.y+owner->rightdir*relWeaponMuzzlePos.x;
 //	logOutput.Print("RelPos %f %f %f",relWeaponPos.x,relWeaponPos.y,relWeaponPos.z);
 
 	if (range > owner->maxRange) {
Index: rts/Sim/Weapons/Weapon.h
===================================================================
--- rts/Sim/Weapons/Weapon.h	(revision 3829)
+++ rts/Sim/Weapons/Weapon.h	(working copy)
@@ -60,6 +60,10 @@
 
 	float3 relWeaponPos;				//weaponpos relative to the unit
 	float3 weaponPos;						//absolute weapon pos
+
+	float3 relWeaponMuzzlePos;			//position of the firepoint
+	float3 weaponMuzzlePos;
+
 	float muzzleFlareSize;			//size of muzzle flare if drawn
 	int useWeaponPosForAim;			//sometimes weapon pos is better to use than aimpos
 	bool hasCloseTarget;					//might need to update weapon pos more often when enemy is near
@@ -119,12 +123,17 @@
 	int lastErrorVectorUpdate;
 
 	CWeapon* slavedTo;						//use this weapon to choose target
-	
+
 	float3 mainDir;								//main aim dir of weapon
 	float maxMainDirAngleDif;					//how far away from main aim dir the weapon can aim at something (as an acos value)
 
 	bool avoidFriendly;		//if true tried to avoid friendly Units when aiming.
 	bool avoidFeature;      		//if true try to avoid Features while aiming.
+
+	float targetBorder;  // if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
+	float cylinderTargetting;	//if greater than 0, range will be checked in a cylinder (height=unitradius*cylinderTargetting) instead of a sphere
+	float minIntensity;	// for beamlasers - always hit with some minimum intensity (a damage coeffcient normally dependent on distance). do not confuse with intensity tag, it's completely unrelated.
+
 	unsigned int collisionFlags;
 
 	float fuelUsage;
Index: rts/Sim/Weapons/WeaponDefHandler.cpp
===================================================================
--- rts/Sim/Weapons/WeaponDefHandler.cpp	(revision 3829)
+++ rts/Sim/Weapons/WeaponDefHandler.cpp	(working copy)
@@ -91,6 +91,17 @@
 	if(!collideFeature)
 		weaponDefs[id].collisionFlags+=COLLISION_NOFEATURE;
 
+	sunparser->GetDef(weaponDefs[id].targetBorder, "0", weaponname + "\\TargetBorder");
+	if (weaponDefs[id].targetBorder > 1.f) {
+		logOutput.Print("warning: targetBorder truncated to 1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = 1.f;
+	} else if (weaponDefs[id].targetBorder < -1.f) {
+		logOutput.Print("warning: targetBorder truncated to -1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = -1.f;
+	}
+	sunparser->GetDef(weaponDefs[id].cylinderTargetting, "0", weaponname + "\\CylinderTargetting");
+	sunparser->GetDef(weaponDefs[id].minIntensity, "0", weaponname + "\\MinIntensity");
+
 	sunparser->GetDef(weaponDefs[id].dropped, "0", weaponname + "\\dropped");
 	sunparser->GetDef(lineofsight, "0", weaponname + "\\lineofsight");
 	sunparser->GetDef(ballistic, "0", weaponname + "\\ballistic");
@@ -119,7 +130,7 @@
 	sunparser->GetDef(weaponDefs[id].laserflaresize, "15", weaponname + "\\laserflaresize");
 	sunparser->GetDef(weaponDefs[id].intensity, "0.9", weaponname + "\\intensity");
 	sunparser->GetDef(weaponDefs[id].duration, "0.05", weaponname + "\\duration");
-	
+
 	sunparser->GetDef(weaponDefs[id].visuals.sizeDecay,  "0", weaponname + "\\sizeDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.alphaDecay, "1", weaponname + "\\alphaDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.separation, "1", weaponname + "\\separation");
@@ -576,7 +587,7 @@
 
 	if (inverted == true) {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = damages[i] - (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
@@ -591,7 +602,7 @@
 	}
 	else {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
Index: rts/Sim/Weapons/WeaponDefHandler.h
===================================================================
--- rts/Sim/Weapons/WeaponDefHandler.h	(revision 3829)
+++ rts/Sim/Weapons/WeaponDefHandler.h	(working copy)
@@ -86,7 +86,7 @@
 	bool manualfire;			//use dgun button
 	int interceptor;				//anti nuke
 	int targetable;				//nuke (can be shot by interceptor)
-	bool stockpile;					
+	bool stockpile;
 	float coverageRange;		//range of anti nuke
 
 	float intensity;
@@ -138,7 +138,7 @@
 		float tilelength;
 		float scrollspeed;
 		float pulseSpeed;
-		
+
 		int stages;
 		float alphaDecay;
 		float sizeDecay;
@@ -172,6 +172,11 @@
 
 	bool avoidFriendly;		//if true try to avoid friendly Units when aiming.
 	bool avoidFeature;      //if true try to avoid Features while aiming.
+
+	float targetBorder;		//if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
+	float cylinderTargetting;	//if greater than 0, range will be checked in a cylinder (height=unitradius*cylinderTargetting) instead of a sphere
+	float minIntensity;		// for beamlasers - always hit with some minimum intensity (a damage coeffcient normally dependent on distance). do not confuse with intensity tag, it's completely unrelated.
+
 	unsigned int collisionFlags;
 
 	CExplosionGenerator *explosionGenerator; // can be zero for default explosions
 targetborder_minintensity_cylinderTargetting_and_weaponMuzzlePos_v2.patch  (21,890 bytes) 2007-06-24 00:40   Index: Game/GameHelper.cpp
===================================================================
--- Game/GameHelper.cpp	(revision 3842)
+++ Game/GameHelper.cpp	(working copy)
@@ -209,16 +209,17 @@
 				closeLength=length;
 			float3 closeVect=dif-dir*closeLength;
 
-			float rad=(*ui)->radius;
-			float tmp = rad * rad - closeVect.SqLength();
-			if(tmp > 0 && length>closeLength+sqrt(tmp)){
+			/*float rad=(*ui)->radius;
+			float tmp = rad * rad - closeVect.SqLength();*/
+
+			/*if(tmp > 0 && length>closeLength+sqrt(tmp)){
 				length=closeLength-sqrt(tmp)*0.5f;
 				hit=*ui;
-			}
-/*			if(closeVect.SqLength() < (*ui)->sqRadius){
+			}*/
+			if(closeVect.SqLength() < (*ui)->sqRadius){
 				length=closeLength;
 				hit=*ui;
-			}*/
+			}
 		}
 	}
 	return length;
Index: Lua/LuaWeaponDefs.cpp
===================================================================
--- Lua/LuaWeaponDefs.cpp	(revision 3842)
+++ Lua/LuaWeaponDefs.cpp	(working copy)
@@ -75,7 +75,7 @@
 				HSTR_PUSH(L, "__index");
 				lua_pushlightuserdata(L, (void*)wd);
 				lua_pushcclosure(L, WeaponDefIndex, 1);
-				lua_rawset(L, -3); // closure 
+				lua_rawset(L, -3); // closure
 
 				HSTR_PUSH(L, "__newindex");
 				lua_pushlightuserdata(L, (void*)wd);
@@ -110,7 +110,7 @@
 
 static int WeaponDefIndex(lua_State* L)
 {
-	// not a default value	
+	// not a default value
 	if (!lua_isstring(L, 2)) {
 		lua_rawget(L, 1);
 		return 1;
@@ -119,7 +119,7 @@
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
 
-	// not a default value	
+	// not a default value
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawget(L, 1);
 		return 1;
@@ -171,7 +171,7 @@
 
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
-	
+
 	// not a default value, set it
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawset(L, 1);
@@ -186,7 +186,7 @@
 	 	luaL_error(L, "Attempt to write WeaponDefs[%d].%s", wd->id, name);
 	 	return 0;
 	}
-	
+
 	// Definition editing
 	const DataElement& elem = it->second;
 	const char* p = ((const char*)wd) + elem.offset;
@@ -217,7 +217,7 @@
 			luaL_error(L, "ERROR_TYPE in WeaponDefs __newindex");
 		}
 	}
-	
+
 	return 0;
 }
 
@@ -266,7 +266,7 @@
 			}
 			// start the user parameters,
 			// remove the internal key and push a nil
-			lua_settop(L, 1); 
+			lua_settop(L, 1);
 			lua_pushnil(L);
 		}
 	}
@@ -314,8 +314,8 @@
 		LuaPushNamedNumber(L, typeList[i].c_str(), d.damages[i]);
 	}
 	lua_rawset(L, -3);
-	
-	return 1;		
+
+	return 1;
 }
 
 
@@ -417,9 +417,9 @@
 
 static bool InitParamMap()
 {
-	paramMap["next"]  = DataElement(READONLY_TYPE); 
-	paramMap["pairs"] = DataElement(READONLY_TYPE); 
-	
+	paramMap["next"]  = DataElement(READONLY_TYPE);
+	paramMap["pairs"] = DataElement(READONLY_TYPE);
+
 	// dummy WeaponDef for offset generation
 	const WeaponDef wd;
 	const char* start = ADDRESS(wd);
@@ -481,7 +481,7 @@
 	ADD_BOOL("noAutoTarget",   wd.noAutoTarget);
 	ADD_BOOL("manualFire",     wd.manualfire);
 	ADD_INT("targetable",      wd.targetable);
-	ADD_BOOL("stockpile",      wd.stockpile);					
+	ADD_BOOL("stockpile",      wd.stockpile);
 	ADD_INT("interceptor",     wd.interceptor);
 	ADD_FLOAT("coverageRange", wd.coverageRange);
 
@@ -541,6 +541,9 @@
 	ADD_INT("interceptedByShieldType",  wd.interceptedByShieldType);
 
 	ADD_BOOL("avoidFriendly", wd.avoidFriendly);
+	ADD_FLOAT("targetBorder", wd.targetBorder);
+	ADD_FLOAT("cylinderTargetting", wd.cylinderTargetting);
+	ADD_FLOAT("minIntensity", wd.minIntensity);
 
 //	CExplosionGenerator *explosionGenerator;
 
Index: Sim/Projectiles/ProjectileHandler.cpp
===================================================================
--- Sim/Projectiles/ProjectileHandler.cpp	(revision 3842)
+++ Sim/Projectiles/ProjectileHandler.cpp	(working copy)
@@ -31,8 +31,8 @@
 
 CProjectileHandler* ph;
 using namespace std;
-extern GLfloat FogBlack[]; 
-extern GLfloat FogLand[]; 
+extern GLfloat FogBlack[];
+extern GLfloat FogLand[];
 
 CR_BIND(CProjectileHandler,);
 
@@ -137,7 +137,7 @@
 	wrecktex            = textureAtlas->GetTextureWithBackup(  "wrecktexture",           "circularthingy"  );
 	plasmatex           = textureAtlas->GetTextureWithBackup(  "plasmatexture",          "circularthingy"  );
 
-	
+
 	groundFXAtlas = SAFE_NEW CTextureAtlas(2048, 2048);
 	//add all textures in groundfx section
 	ptex = resources.GetAllValues("resources\\graphics\\groundfx");
@@ -328,9 +328,9 @@
 
 	int numFlyingPieces = 0;
 	int drawnPieces = 0;
-	
+
 	/* Putting in, say, viewport culling will deserve refactoring. */
-	
+
 	/* 3DO */
 	unitDrawer->SetupForUnitDrawing();
 
@@ -376,27 +376,27 @@
 
 	for (int textureType = 1; textureType < flyings3oPieces.size(); textureType++){
 		/* TODO Skip this if there's no FlyingPieces. */
-		
+
 		texturehandler->SetS3oTexture(textureType);
-		
+
 		for (int team = 0; team < flyings3oPieces[textureType].size(); team++){
 			FlyingPiece_List * fpl = flyings3oPieces[textureType][team];
-		
+
 			unitDrawer->SetS3OTeamColour(team);
-			
+
 			va->Initialize();
-			
+
 			numFlyingPieces += fpl->size();
-		
+
 			for(std::list<FlyingPiece*>::iterator pi=fpl->begin();pi!=fpl->end();++pi){
 				CMatrix44f m;
 				m.Rotate((*pi)->rot,(*pi)->rotAxis);
 				float3 interPos=(*pi)->pos+(*pi)->speed*gu->timeOffset;
-				
+
 				SS3OVertex * verts = (*pi)->verts;
-				
+
 				float3 tp, tn;
-				
+
 				for (int i = 0; i < 4; i++){
 					tp=m.Mul(verts[i].pos);
 					tn=m.Mul(verts[i].normal);
@@ -408,9 +408,9 @@
 			va->DrawArrayTN(GL_QUADS);
 		}
 	}
-	
+
 	unitDrawer->CleanUpS3ODrawing();
-	
+
 	/*
 	 * TODO Nearly cut here.
 	 */
@@ -565,7 +565,7 @@
 						if(readmap->groundBlockingObjectMap[square]!=unit)
 							continue;
 					}
-					//adjust projectile position so explosion happens at the correct position 
+					//adjust projectile position so explosion happens at the correct position
 					p->pos = p->pos + p->speed*closeTime;
 					p->Collision(*ui);
 					break;
@@ -603,7 +603,7 @@
 				}
 			}
 		}
-	}	
+	}
 }
 
 void CProjectileHandler::AddGroundFlash(CGroundFlash* flash)
@@ -687,7 +687,7 @@
 		flyings3oPieces[textureType].push_back(fpl);
 		flyingPieces.push_back(fpl);
 	}
-	
+
 	pieceList=flyings3oPieces[textureType][team];
 
 	FlyingPiece* fp=new FlyingPiece;
@@ -745,7 +745,7 @@
 	glDisable(GL_ALPHA_TEST);
 	glDisable(GL_FOG);
 
-	unsigned char col[4];	
+	unsigned char col[4];
 	float time=gu->lastFrameTime*gs->speedFactor*3;
 	float speed=1;
 	float size=1;
Index: Sim/Projectiles/WeaponProjectile.cpp
===================================================================
--- Sim/Projectiles/WeaponProjectile.cpp	(revision 3842)
+++ Sim/Projectiles/WeaponProjectile.cpp	(working copy)
@@ -45,7 +45,7 @@
 	interceptTarget=0;
 }
 
-CWeaponProjectile::CWeaponProjectile(const float3& pos,const float3& speed,CUnit* owner, CUnit* target,const float3 &targetPos, WeaponDef *weaponDef,CWeaponProjectile* interceptTarget, bool synced) : 
+CWeaponProjectile::CWeaponProjectile(const float3& pos,const float3& speed,CUnit* owner, CUnit* target,const float3 &targetPos, WeaponDef *weaponDef,CWeaponProjectile* interceptTarget, bool synced) :
 	CProjectile(pos,speed,owner, synced),
 	weaponDef(weaponDef),
 	weaponDefName(weaponDef?weaponDef->name:std::string("")),
@@ -120,7 +120,7 @@
 		DamageArray dynDamages;
 		if (weaponDef->dynDamageExp > 0)
 			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, startpos, pos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
-		
+
 		helper->Explosion(pos,weaponDef->dynDamageExp>0?dynDamages:weaponDef->damages,weaponDef->areaOfEffect,weaponDef->edgeEffectiveness,weaponDef->explosionSpeed,owner,true,weaponDef->noExplode? 0.3f:1,weaponDef->noExplode || weaponDef->noSelfDamage, weaponDef->explosionGenerator,0,impactDir, weaponDef->id);
 	}
 
@@ -155,7 +155,7 @@
 		DamageArray dynDamages;
 		if (weaponDef->dynDamageExp > 0)
 			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, startpos, pos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
-			
+
 		helper->Explosion(pos,weaponDef->dynDamageExp>0?dynDamages:weaponDef->damages,weaponDef->areaOfEffect,weaponDef->edgeEffectiveness,weaponDef->explosionSpeed,owner,true,weaponDef->noExplode? 0.3f:1,weaponDef->noExplode,weaponDef->explosionGenerator,unit,impactDir, weaponDef->id);
 	}
 
@@ -226,7 +226,7 @@
 	transMatrix[12]=interPos.x;
 	transMatrix[13]=interPos.y;
 	transMatrix[14]=interPos.z;
-	glMultMatrixf(&transMatrix[0]);		
+	glMultMatrixf(&transMatrix[0]);
 
 	glCallList(modelDispList);
 	glPopMatrix();
Index: Sim/Units/CommandAI/MobileCAI.cpp
===================================================================
--- Sim/Units/CommandAI/MobileCAI.cpp	(revision 3842)
+++ Sim/Units/CommandAI/MobileCAI.cpp	(working copy)
@@ -42,6 +42,8 @@
 				CR_MEMBER(commandPos1),
 				CR_MEMBER(commandPos2),
 
+				CR_MEMBER(lastCloseInTry),
+				
 				CR_MEMBER(cancelDistance),
 				CR_MEMBER(slowGuard),
 				CR_MEMBER(moveDir)
@@ -61,6 +63,7 @@
 	commandPos2(ZeroVector),
 	lastPC(-1),
 	cancelDistance(1024),
+	lastCloseInTry(-1),
 	slowGuard(false),
 	moveDir(gs->randFloat() > 0.5),
 	lastUserGoal(0,0,0)
@@ -81,6 +84,7 @@
 	commandPos2(ZeroVector),
 	lastPC(-1),
 	cancelDistance(1024),
+	lastCloseInTry(-1),
 	slowGuard(false),
 	moveDir(gs->randFloat() > 0.5)
 {
@@ -582,7 +586,8 @@
 			// check if we have valid target parameter and that we aren't attacking ourselves
 			if (uh->units[unitID] != 0 && uh->units[unitID] != owner) {
 				float3 fix = uh->units[unitID]->pos + owner->posErrorVector * 128;
-				SetGoal(fix, owner->pos);
+				float3 diff = float3(fix - owner->pos).Normalize();
+				SetGoal(fix - diff*uh->units[unitID]->radius, owner->pos);
 				// get ID of attack-order target unit
 				orderTarget = uh->units[unitID];
 				AddDeathDependence(orderTarget);
@@ -626,6 +631,7 @@
 		//bool b1 = owner->AttackUnit(orderTarget, c.id == CMD_DGUN);
 		bool b2 = false;
 		bool b3 = false;
+		float edgeFactor = 0.f; // percent offset to target center
 
 		if (owner->weapons.size() > 0) {
 			if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) {
@@ -638,15 +644,21 @@
 			// can hit target with our first (meanest) one
 			b2 = w->TryTargetRotate(orderTarget, c.id == CMD_DGUN);
 			b3 = (w->range - (w->relWeaponPos).Length()) > (orderTarget->pos.distance(owner->pos));
+			edgeFactor = fabs(w->targetBorder);
 		}
 		float3 diff = owner->pos - orderTarget->pos;
 		// if w->AttackUnit() returned true then we are already
 		// in range with our biggest weapon so stop moving
+		// also make sure that we're not locked in close-in/in-range state loop
+		// due to rotates invoked by in-range or out-of-range states
 		if (b2) {
 			StopMove();
 			owner->AttackUnit(orderTarget, c.id == CMD_DGUN);
-			owner->moveType->KeepPointingTo(orderTarget,
-				min((float) (owner->losRadius * SQUARE_SIZE * 2), owner->maxRange * 0.9f), true);
+			// FIXME kill magic frame number
+			if (gs->frameNum > lastCloseInTry + MAX_CLOSE_IN_RETRY_TICKS) {
+				owner->moveType->KeepPointingTo(orderTarget,
+					min((float) (owner->losRadius * SQUARE_SIZE * 2), owner->maxRange * 0.9f), true);
+			}
 		}
 
 		// if (((first weapon range minus first weapon length greater than distance to target
@@ -675,7 +687,10 @@
 		else if ((orderTarget->pos + owner->posErrorVector * 128).distance2D(goalPos)
 				> (10 + orderTarget->pos.distance2D(owner->pos) * 0.2f)) {
 			float3 fix = orderTarget->pos + owner->posErrorVector * 128;
-			SetGoal(fix, owner->pos);
+			float3 norm = float3(fix - owner->pos).Normalize();
+			SetGoal(fix - norm*(orderTarget->radius*edgeFactor*0.8f), owner->pos);
+			if (lastCloseInTry < gs->frameNum + MAX_CLOSE_IN_RETRY_TICKS)
+				lastCloseInTry = gs->frameNum;
 		}
 	}
 
Index: Sim/Units/CommandAI/MobileCAI.h
===================================================================
--- Sim/Units/CommandAI/MobileCAI.h	(revision 3842)
+++ Sim/Units/CommandAI/MobileCAI.h	(working copy)
@@ -59,6 +59,7 @@
 
 protected:
 	int cancelDistance;
+	int lastCloseInTry;
 	bool slowGuard;
 	bool moveDir;
 	void PushOrUpdateReturnFight() {
@@ -66,5 +67,6 @@
 	}
 };
 
+#define MAX_CLOSE_IN_RETRY_TICKS 30
 
 #endif /* MOBILECAI_H */
Index: Sim/Units/UnitLoader.cpp
===================================================================
--- Sim/Units/UnitLoader.cpp	(revision 3842)
+++ Sim/Units/UnitLoader.cpp	(working copy)
@@ -466,6 +466,9 @@
 	weapon->fuelUsage = udw->fuelUsage;
 	weapon->avoidFriendly = weapondef->avoidFriendly;
 	weapon->avoidFeature = weapondef->avoidFeature;
+	weapon->targetBorder = weapondef->targetBorder;
+	weapon->cylinderTargetting = weapondef->cylinderTargetting;
+	weapon->minIntensity = weapondef->minIntensity;
 	weapon->collisionFlags = weapondef->collisionFlags;
 	weapon->Init();
 
Index: Sim/Weapons/BeamLaser.cpp
===================================================================
--- Sim/Weapons/BeamLaser.cpp	(revision 3842)
+++ Sim/Weapons/BeamLaser.cpp	(working copy)
@@ -85,6 +85,7 @@
 	}
 
 	float3 dir=pos-weaponPos;
+
 	float length=dir.Length();
 	if(length==0)
 		return true;
@@ -133,6 +134,7 @@
 		}
 	}
 	dir+=(salvoError)*(1-owner->limExperience*0.7f);
+
 	dir.Normalize();
 
 	FireInternal(dir, false);
@@ -145,6 +147,7 @@
 	if(owner->directControl)
 		rangeMod=0.95f;
 #endif
+
 	float maxLength=range*rangeMod;
 	float curLength=0;
 	float3 curPos=weaponPos;
@@ -152,6 +155,19 @@
 
 	bool tryAgain=true;
 	CUnit* hit;
+
+	// increase range if targets are searched for in a cylinder
+	if (cylinderTargetting > 0.01) {
+		const float3 up(0, owner->radius*cylinderTargetting, 0);
+		const float uplen = up.dot(dir);
+		maxLength = sqrt(maxLength*maxLength + uplen*uplen);
+	}
+
+	// increase range if targetting edge of hitsphere
+	if (targetType == Target_Unit && targetUnit && targetBorder != 0) {
+		maxLength += targetUnit->radius*targetBorder;
+	}
+
 	for(int tries=0;tries<5 && tryAgain;++tries){
 		tryAgain=false;
 		hit=0;
@@ -172,6 +188,7 @@
 				tryAgain=true;
 			}
 		}
+
 		hitPos=curPos+dir*length;
 
 		float baseAlpha=weaponDef->intensity*255;
@@ -187,7 +204,15 @@
 		curLength+=length;
 		dir=newDir;
 	}
-	float	intensity=1-(curLength)/(range*2);
+
+	// fix negative damage when hitting big spheres
+	float actualRange = range;
+	if (hit && targetBorder > 0) {
+		actualRange += hit->radius*targetBorder;
+	}
+	// make it possible to always hit with some minimal intensity (melee weapons have use for that)
+	float intensity=max(minIntensity, 1-(curLength)/(actualRange*2));
+
 	if(curLength<maxLength) {
 		// Dynamic Damage
 		DamageArray dynDamages;
Index: Sim/Weapons/MeleeWeapon.cpp
===================================================================
--- Sim/Weapons/MeleeWeapon.cpp	(revision 3842)
+++ Sim/Weapons/MeleeWeapon.cpp	(working copy)
@@ -34,7 +34,9 @@
 void CMeleeWeapon::Fire(void)
 {
 	if(targetType==Target_Unit){
-		targetUnit->DoDamage(damages,owner,ZeroVector,weaponDef->id);
+		float3 impulseDir = targetUnit->pos-weaponPos;
+		impulseDir.Normalize();
+		targetUnit->DoDamage(damages,owner,impulseDir,weaponDef->id);
 		if(fireSoundId)
 			sound->PlaySample(fireSoundId,owner,fireSoundVolume);
 	}
Index: Sim/Weapons/Weapon.cpp
===================================================================
--- Sim/Weapons/Weapon.cpp	(revision 3842)
+++ Sim/Weapons/Weapon.cpp	(working copy)
@@ -83,6 +83,9 @@
 	CR_MEMBER(hasCloseTarget),
 	CR_MEMBER(avoidFriendly),
 	CR_MEMBER(avoidFeature),
+	CR_MEMBER(targetBorder),
+	CR_MEMBER(cylinderTargetting),
+	CR_MEMBER(minIntensity),
 	CR_MEMBER(collisionFlags),
 	CR_MEMBER(fuelUsage),
 	CR_MEMBER(weaponNum)
@@ -151,6 +154,9 @@
 	hasCloseTarget(false),
 	avoidFriendly(true),
 	avoidFeature(true),
+	targetBorder(0.f),
+	cylinderTargetting(0.f),
+	minIntensity(0.f),
 	collisionFlags(0),
 	fuelUsage(0)
 {
@@ -164,6 +170,12 @@
 
 void CWeapon::Update()
 {
+	// do not fire at cloaked units
+	if(targetType == Target_Unit && targetUnit && targetUnit->isCloaked) {
+		HoldFire();
+		return;
+	}
+
 	if(hasCloseTarget){
 		std::vector<int> args;
 		args.push_back(0);
@@ -544,7 +556,31 @@
 
 	float3 dif=pos-weaponPos;
 
-	float r=GetRange2D(owner->pos.y-pos.y);
+	if (targetBorder != 0 && unit) {
+		float3 diff(dif);
+		diff.Normalize();
+		// weapon inside target sphere
+		if (dif.SqLength() < unit->sqRadius*targetBorder*targetBorder) {
+			dif -= diff*(dif.Length() - 10); // a hack
+			//logOutput << "inside\n";
+		} else {
+			dif -= diff*(unit->radius*targetBorder);
+			//logOutput << "outside\n";
+		}
+		//geometricObjects->AddLine(weaponMuzzlePos, weaponMuzzlePos+dif, 3, 0, 16);
+	}
+
+	float r;
+	if (!unit || cylinderTargetting < 0.01) {
+		r=GetRange2D(owner->pos.y-pos.y);
+	} else {
+		if (cylinderTargetting * unit->radius > owner->pos.y-pos.y) {
+			r = GetRange2D(0);
+		} else {
+			r = 0;
+		}
+	}
+
 	if(dif.SqLength2D()>=r*r)
 		return false;
 
Index: Sim/Weapons/Weapon.h
===================================================================
--- Sim/Weapons/Weapon.h	(revision 3842)
+++ Sim/Weapons/Weapon.h	(working copy)
@@ -119,12 +119,17 @@
 	int lastErrorVectorUpdate;
 
 	CWeapon* slavedTo;						//use this weapon to choose target
-	
+
 	float3 mainDir;								//main aim dir of weapon
 	float maxMainDirAngleDif;					//how far away from main aim dir the weapon can aim at something (as an acos value)
 
 	bool avoidFriendly;		//if true tried to avoid friendly Units when aiming.
 	bool avoidFeature;      		//if true try to avoid Features while aiming.
+
+	float targetBorder;  // if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
+	float cylinderTargetting;	//if greater than 0, range will be checked in a cylinder (height=unitradius*cylinderTargetting) instead of a sphere
+	float minIntensity;	// for beamlasers - always hit with some minimum intensity (a damage coeffcient normally dependent on distance). do not confuse with intensity tag, it's completely unrelated.
+
 	unsigned int collisionFlags;
 
 	float fuelUsage;
Index: Sim/Weapons/WeaponDefHandler.cpp
===================================================================
--- Sim/Weapons/WeaponDefHandler.cpp	(revision 3842)
+++ Sim/Weapons/WeaponDefHandler.cpp	(working copy)
@@ -91,6 +91,17 @@
 	if(!collideFeature)
 		weaponDefs[id].collisionFlags+=COLLISION_NOFEATURE;
 
+	sunparser->GetDef(weaponDefs[id].targetBorder, "0", weaponname + "\\TargetBorder");
+	if (weaponDefs[id].targetBorder > 1.f) {
+		logOutput.Print("warning: targetBorder truncated to 1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = 1.f;
+	} else if (weaponDefs[id].targetBorder < -1.f) {
+		logOutput.Print("warning: targetBorder truncated to -1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = -1.f;
+	}
+	sunparser->GetDef(weaponDefs[id].cylinderTargetting, "0", weaponname + "\\CylinderTargetting");
+	sunparser->GetDef(weaponDefs[id].minIntensity, "0", weaponname + "\\MinIntensity");
+
 	sunparser->GetDef(weaponDefs[id].dropped, "0", weaponname + "\\dropped");
 	sunparser->GetDef(lineofsight, "0", weaponname + "\\lineofsight");
 	sunparser->GetDef(ballistic, "0", weaponname + "\\ballistic");
@@ -119,7 +130,7 @@
 	sunparser->GetDef(weaponDefs[id].laserflaresize, "15", weaponname + "\\laserflaresize");
 	sunparser->GetDef(weaponDefs[id].intensity, "0.9", weaponname + "\\intensity");
 	sunparser->GetDef(weaponDefs[id].duration, "0.05", weaponname + "\\duration");
-	
+
 	sunparser->GetDef(weaponDefs[id].visuals.sizeDecay,  "0", weaponname + "\\sizeDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.alphaDecay, "1", weaponname + "\\alphaDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.separation, "1", weaponname + "\\separation");
@@ -576,7 +587,7 @@
 
 	if (inverted == true) {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = damages[i] - (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
@@ -591,7 +602,7 @@
 	}
 	else {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
Index: Sim/Weapons/WeaponDefHandler.h
===================================================================
--- Sim/Weapons/WeaponDefHandler.h	(revision 3842)
+++ Sim/Weapons/WeaponDefHandler.h	(working copy)
@@ -86,7 +86,7 @@
 	bool manualfire;			//use dgun button
 	int interceptor;				//anti nuke
 	int targetable;				//nuke (can be shot by interceptor)
-	bool stockpile;					
+	bool stockpile;
 	float coverageRange;		//range of anti nuke
 
 	float intensity;
@@ -138,7 +138,7 @@
 		float tilelength;
 		float scrollspeed;
 		float pulseSpeed;
-		
+
 		int stages;
 		float alphaDecay;
 		float sizeDecay;
@@ -172,6 +172,11 @@
 
 	bool avoidFriendly;		//if true try to avoid friendly Units when aiming.
 	bool avoidFeature;      //if true try to avoid Features while aiming.
+
+	float targetBorder;		//if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
+	float cylinderTargetting;	//if greater than 0, range will be checked in a cylinder (height=unitradius*cylinderTargetting) instead of a sphere
+	float minIntensity;		// for beamlasers - always hit with some minimum intensity (a damage coeffcient normally dependent on distance). do not confuse with intensity tag, it's completely unrelated.
+
 	unsigned int collisionFlags;
 
 	CExplosionGenerator *explosionGenerator; // can be zero for default explosions
 targetborder_minintensity_cylinderTargetting_and_weaponMuzzlePos_v3.patch  (23,250 bytes) 2007-06-25 16:27   Index: Game/GameHelper.cpp
===================================================================
--- Game/GameHelper.cpp	(revision 3848)
+++ Game/GameHelper.cpp	(working copy)
@@ -209,16 +209,17 @@
 				closeLength=length;
 			float3 closeVect=dif-dir*closeLength;
 
-			float rad=(*ui)->radius;
-			float tmp = rad * rad - closeVect.SqLength();
-			if(tmp > 0 && length>closeLength+sqrt(tmp)){
+			/*float rad=(*ui)->radius;
+			float tmp = rad * rad - closeVect.SqLength();*/
+
+			/*if(tmp > 0 && length>closeLength+sqrt(tmp)){
 				length=closeLength-sqrt(tmp)*0.5f;
 				hit=*ui;
-			}
-/*			if(closeVect.SqLength() < (*ui)->sqRadius){
+			}*/
+			if(closeVect.SqLength() < (*ui)->sqRadius){
 				length=closeLength;
 				hit=*ui;
-			}*/
+			}
 		}
 	}
 	return length;
Index: Lua/LuaWeaponDefs.cpp
===================================================================
--- Lua/LuaWeaponDefs.cpp	(revision 3848)
+++ Lua/LuaWeaponDefs.cpp	(working copy)
@@ -75,7 +75,7 @@
 				HSTR_PUSH(L, "__index");
 				lua_pushlightuserdata(L, (void*)wd);
 				lua_pushcclosure(L, WeaponDefIndex, 1);
-				lua_rawset(L, -3); // closure 
+				lua_rawset(L, -3); // closure
 
 				HSTR_PUSH(L, "__newindex");
 				lua_pushlightuserdata(L, (void*)wd);
@@ -110,7 +110,7 @@
 
 static int WeaponDefIndex(lua_State* L)
 {
-	// not a default value	
+	// not a default value
 	if (!lua_isstring(L, 2)) {
 		lua_rawget(L, 1);
 		return 1;
@@ -119,7 +119,7 @@
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
 
-	// not a default value	
+	// not a default value
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawget(L, 1);
 		return 1;
@@ -171,7 +171,7 @@
 
 	const char* name = lua_tostring(L, 2);
 	ParamMap::const_iterator it = paramMap.find(name);
-	
+
 	// not a default value, set it
 	if (paramMap.find(name) == paramMap.end()) {
 		lua_rawset(L, 1);
@@ -186,7 +186,7 @@
 	 	luaL_error(L, "Attempt to write WeaponDefs[%d].%s", wd->id, name);
 	 	return 0;
 	}
-	
+
 	// Definition editing
 	const DataElement& elem = it->second;
 	const char* p = ((const char*)wd) + elem.offset;
@@ -217,7 +217,7 @@
 			luaL_error(L, "ERROR_TYPE in WeaponDefs __newindex");
 		}
 	}
-	
+
 	return 0;
 }
 
@@ -266,7 +266,7 @@
 			}
 			// start the user parameters,
 			// remove the internal key and push a nil
-			lua_settop(L, 1); 
+			lua_settop(L, 1);
 			lua_pushnil(L);
 		}
 	}
@@ -314,8 +314,8 @@
 		LuaPushNamedNumber(L, typeList[i].c_str(), d.damages[i]);
 	}
 	lua_rawset(L, -3);
-	
-	return 1;		
+
+	return 1;
 }
 
 
@@ -417,9 +417,9 @@
 
 static bool InitParamMap()
 {
-	paramMap["next"]  = DataElement(READONLY_TYPE); 
-	paramMap["pairs"] = DataElement(READONLY_TYPE); 
-	
+	paramMap["next"]  = DataElement(READONLY_TYPE);
+	paramMap["pairs"] = DataElement(READONLY_TYPE);
+
 	// dummy WeaponDef for offset generation
 	const WeaponDef wd;
 	const char* start = ADDRESS(wd);
@@ -481,7 +481,7 @@
 	ADD_BOOL("noAutoTarget",   wd.noAutoTarget);
 	ADD_BOOL("manualFire",     wd.manualfire);
 	ADD_INT("targetable",      wd.targetable);
-	ADD_BOOL("stockpile",      wd.stockpile);					
+	ADD_BOOL("stockpile",      wd.stockpile);
 	ADD_INT("interceptor",     wd.interceptor);
 	ADD_FLOAT("coverageRange", wd.coverageRange);
 
@@ -541,6 +541,9 @@
 	ADD_INT("interceptedByShieldType",  wd.interceptedByShieldType);
 
 	ADD_BOOL("avoidFriendly", wd.avoidFriendly);
+	ADD_FLOAT("targetBorder", wd.targetBorder);
+	ADD_FLOAT("cylinderTargetting", wd.cylinderTargetting);
+	ADD_FLOAT("minIntensity", wd.minIntensity);
 
 //	CExplosionGenerator *explosionGenerator;
 
Index: Sim/MoveTypes/groundmovetype.cpp
===================================================================
--- Sim/MoveTypes/groundmovetype.cpp	(revision 3848)
+++ Sim/MoveTypes/groundmovetype.cpp	(working copy)
@@ -194,7 +194,7 @@
 	{
 		skidding = true;
 	}
-	
+
 	if(skidding){
 		UpdateSkid();
 		return;
@@ -415,6 +415,7 @@
 	tracefile << "Start moving called: ";
 	tracefile << owner->pos.x << " " << owner->pos.y << " " << owner->pos.z << " " << owner->id << "\n";
 #endif
+
 	if(progressState == Active) {
 		StopEngine();
 	}
@@ -609,7 +610,7 @@
 	float3& speed=owner->speed;
 	float3& pos=owner->pos;
 	SyncedFloat3& midPos=owner->midPos;
-	
+
 	if(flying){
 		speed.y+=gs->gravity;
 		if(midPos.y < 0)
@@ -665,11 +666,11 @@
 				speed+=newForce;
 				speedf = speed.Length();
 				speed *= 1 - (.1*dir.y);
-			} else 
+			} else
 			{
 				speed*=(speedf-speedReduction)/speedf;
 			}
-			
+
 			float remTime=speedf/speedReduction-1;
 			float rp=floor(skidRotPos2+skidRotSpeed2*remTime+0.5f);
 			skidRotSpeed2=(remTime+1 == 0 ) ? 0 : (rp-skidRotPos2)/(remTime+1);
Index: Sim/Projectiles/ProjectileHandler.cpp
===================================================================
--- Sim/Projectiles/ProjectileHandler.cpp	(revision 3848)
+++ Sim/Projectiles/ProjectileHandler.cpp	(working copy)
@@ -31,8 +31,8 @@
 
 CProjectileHandler* ph;
 using namespace std;
-extern GLfloat FogBlack[]; 
-extern GLfloat FogLand[]; 
+extern GLfloat FogBlack[];
+extern GLfloat FogLand[];
 
 CR_BIND(CProjectileHandler,);
 
@@ -137,7 +137,7 @@
 	wrecktex            = textureAtlas->GetTextureWithBackup(  "wrecktexture",           "circularthingy"  );
 	plasmatex           = textureAtlas->GetTextureWithBackup(  "plasmatexture",          "circularthingy"  );
 
-	
+
 	groundFXAtlas = SAFE_NEW CTextureAtlas(2048, 2048);
 	//add all textures in groundfx section
 	ptex = resources.GetAllValues("resources\\graphics\\groundfx");
@@ -328,9 +328,9 @@
 
 	int numFlyingPieces = 0;
 	int drawnPieces = 0;
-	
+
 	/* Putting in, say, viewport culling will deserve refactoring. */
-	
+
 	/* 3DO */
 	unitDrawer->SetupForUnitDrawing();
 
@@ -376,27 +376,27 @@
 
 	for (int textureType = 1; textureType < flyings3oPieces.size(); textureType++){
 		/* TODO Skip this if there's no FlyingPieces. */
-		
+
 		texturehandler->SetS3oTexture(textureType);
-		
+
 		for (int team = 0; team < flyings3oPieces[textureType].size(); team++){
 			FlyingPiece_List * fpl = flyings3oPieces[textureType][team];
-		
+
 			unitDrawer->SetS3OTeamColour(team);
-			
+
 			va->Initialize();
-			
+
 			numFlyingPieces += fpl->size();
-		
+
 			for(std::list<FlyingPiece*>::iterator pi=fpl->begin();pi!=fpl->end();++pi){
 				CMatrix44f m;
 				m.Rotate((*pi)->rot,(*pi)->rotAxis);
 				float3 interPos=(*pi)->pos+(*pi)->speed*gu->timeOffset;
-				
+
 				SS3OVertex * verts = (*pi)->verts;
-				
+
 				float3 tp, tn;
-				
+
 				for (int i = 0; i < 4; i++){
 					tp=m.Mul(verts[i].pos);
 					tn=m.Mul(verts[i].normal);
@@ -408,9 +408,9 @@
 			va->DrawArrayTN(GL_QUADS);
 		}
 	}
-	
+
 	unitDrawer->CleanUpS3ODrawing();
-	
+
 	/*
 	 * TODO Nearly cut here.
 	 */
@@ -565,7 +565,7 @@
 						if(readmap->groundBlockingObjectMap[square]!=unit)
 							continue;
 					}
-					//adjust projectile position so explosion happens at the correct position 
+					//adjust projectile position so explosion happens at the correct position
 					p->pos = p->pos + p->speed*closeTime;
 					p->Collision(*ui);
 					break;
@@ -603,7 +603,7 @@
 				}
 			}
 		}
-	}	
+	}
 }
 
 void CProjectileHandler::AddGroundFlash(CGroundFlash* flash)
@@ -687,7 +687,7 @@
 		flyings3oPieces[textureType].push_back(fpl);
 		flyingPieces.push_back(fpl);
 	}
-	
+
 	pieceList=flyings3oPieces[textureType][team];
 
 	FlyingPiece* fp=new FlyingPiece;
@@ -745,7 +745,7 @@
 	glDisable(GL_ALPHA_TEST);
 	glDisable(GL_FOG);
 
-	unsigned char col[4];	
+	unsigned char col[4];
 	float time=gu->lastFrameTime*gs->speedFactor*3;
 	float speed=1;
 	float size=1;
Index: Sim/Projectiles/WeaponProjectile.cpp
===================================================================
--- Sim/Projectiles/WeaponProjectile.cpp	(revision 3848)
+++ Sim/Projectiles/WeaponProjectile.cpp	(working copy)
@@ -45,7 +45,7 @@
 	interceptTarget=0;
 }
 
-CWeaponProjectile::CWeaponProjectile(const float3& pos,const float3& speed,CUnit* owner, CUnit* target,const float3 &targetPos, WeaponDef *weaponDef,CWeaponProjectile* interceptTarget, bool synced) : 
+CWeaponProjectile::CWeaponProjectile(const float3& pos,const float3& speed,CUnit* owner, CUnit* target,const float3 &targetPos, WeaponDef *weaponDef,CWeaponProjectile* interceptTarget, bool synced) :
 	CProjectile(pos,speed,owner, synced),
 	weaponDef(weaponDef),
 	weaponDefName(weaponDef?weaponDef->name:std::string("")),
@@ -120,7 +120,7 @@
 		DamageArray dynDamages;
 		if (weaponDef->dynDamageExp > 0)
 			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, startpos, pos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
-		
+
 		helper->Explosion(pos,weaponDef->dynDamageExp>0?dynDamages:weaponDef->damages,weaponDef->areaOfEffect,weaponDef->edgeEffectiveness,weaponDef->explosionSpeed,owner,true,weaponDef->noExplode? 0.3f:1,weaponDef->noExplode || weaponDef->noSelfDamage, weaponDef->explosionGenerator,0,impactDir, weaponDef->id);
 	}
 
@@ -155,7 +155,7 @@
 		DamageArray dynDamages;
 		if (weaponDef->dynDamageExp > 0)
 			dynDamages = weaponDefHandler->DynamicDamages(weaponDef->damages, startpos, pos, weaponDef->dynDamageRange>0?weaponDef->dynDamageRange:weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted);
-			
+
 		helper->Explosion(pos,weaponDef->dynDamageExp>0?dynDamages:weaponDef->damages,weaponDef->areaOfEffect,weaponDef->edgeEffectiveness,weaponDef->explosionSpeed,owner,true,weaponDef->noExplode? 0.3f:1,weaponDef->noExplode,weaponDef->explosionGenerator,unit,impactDir, weaponDef->id);
 	}
 
@@ -226,7 +226,7 @@
 	transMatrix[12]=interPos.x;
 	transMatrix[13]=interPos.y;
 	transMatrix[14]=interPos.z;
-	glMultMatrixf(&transMatrix[0]);		
+	glMultMatrixf(&transMatrix[0]);
 
 	glCallList(modelDispList);
 	glPopMatrix();
Index: Sim/Units/CommandAI/MobileCAI.cpp
===================================================================
--- Sim/Units/CommandAI/MobileCAI.cpp	(revision 3848)
+++ Sim/Units/CommandAI/MobileCAI.cpp	(working copy)
@@ -42,6 +42,8 @@
 				CR_MEMBER(commandPos1),
 				CR_MEMBER(commandPos2),
 
+				CR_MEMBER(lastCloseInTry),
+
 				CR_MEMBER(cancelDistance),
 				CR_MEMBER(slowGuard),
 				CR_MEMBER(moveDir)
@@ -61,6 +63,7 @@
 	commandPos2(ZeroVector),
 	lastPC(-1),
 	cancelDistance(1024),
+	lastCloseInTry(-1),
 	slowGuard(false),
 	moveDir(gs->randFloat() > 0.5),
 	lastUserGoal(0,0,0)
@@ -81,6 +84,7 @@
 	commandPos2(ZeroVector),
 	lastPC(-1),
 	cancelDistance(1024),
+	lastCloseInTry(-1),
 	slowGuard(false),
 	moveDir(gs->randFloat() > 0.5)
 {
@@ -582,7 +586,8 @@
 			// check if we have valid target parameter and that we aren't attacking ourselves
 			if (uh->units[unitID] != 0 && uh->units[unitID] != owner) {
 				float3 fix = uh->units[unitID]->pos + owner->posErrorVector * 128;
-				SetGoal(fix, owner->pos);
+				float3 diff = float3(fix - owner->pos).Normalize();
+				SetGoal(fix - diff*uh->units[unitID]->radius, owner->pos);
 				// get ID of attack-order target unit
 				orderTarget = uh->units[unitID];
 				AddDeathDependence(orderTarget);
@@ -626,6 +631,7 @@
 		//bool b1 = owner->AttackUnit(orderTarget, c.id == CMD_DGUN);
 		bool b2 = false;
 		bool b3 = false;
+		float edgeFactor = 0.f; // percent offset to target center
 
 		if (owner->weapons.size() > 0) {
 			if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) {
@@ -638,15 +644,21 @@
 			// can hit target with our first (meanest) one
 			b2 = w->TryTargetRotate(orderTarget, c.id == CMD_DGUN);
 			b3 = (w->range - (w->relWeaponPos).Length()) > (orderTarget->pos.distance(owner->pos));
+			edgeFactor = fabs(w->targetBorder);
 		}
 		float3 diff = owner->pos - orderTarget->pos;
 		// if w->AttackUnit() returned true then we are already
 		// in range with our biggest weapon so stop moving
+		// also make sure that we're not locked in close-in/in-range state loop
+		// due to rotates invoked by in-range or out-of-range states
 		if (b2) {
 			StopMove();
 			owner->AttackUnit(orderTarget, c.id == CMD_DGUN);
-			owner->moveType->KeepPointingTo(orderTarget,
-				min((float) (owner->losRadius * SQUARE_SIZE * 2), owner->maxRange * 0.9f), true);
+			// FIXME kill magic frame number
+			if (gs->frameNum > lastCloseInTry + MAX_CLOSE_IN_RETRY_TICKS) {
+				owner->moveType->KeepPointingTo(orderTarget,
+					min((float) (owner->losRadius * SQUARE_SIZE * 2), owner->maxRange * 0.9f), true);
+			}
 		}
 
 		// if (((first weapon range minus first weapon length greater than distance to target
@@ -675,7 +687,10 @@
 		else if ((orderTarget->pos + owner->posErrorVector * 128).distance2D(goalPos)
 				> (10 + orderTarget->pos.distance2D(owner->pos) * 0.2f)) {
 			float3 fix = orderTarget->pos + owner->posErrorVector * 128;
-			SetGoal(fix, owner->pos);
+			float3 norm = float3(fix - owner->pos).Normalize();
+			SetGoal(fix - norm*(orderTarget->radius*edgeFactor*0.8f), owner->pos);
+			if (lastCloseInTry < gs->frameNum + MAX_CLOSE_IN_RETRY_TICKS)
+				lastCloseInTry = gs->frameNum;
 		}
 	}
 
Index: Sim/Units/CommandAI/MobileCAI.h
===================================================================
--- Sim/Units/CommandAI/MobileCAI.h	(revision 3848)
+++ Sim/Units/CommandAI/MobileCAI.h	(working copy)
@@ -59,6 +59,7 @@
 
 protected:
 	int cancelDistance;
+	int lastCloseInTry;
 	bool slowGuard;
 	bool moveDir;
 	void PushOrUpdateReturnFight() {
@@ -66,5 +67,6 @@
 	}
 };
 
+#define MAX_CLOSE_IN_RETRY_TICKS 30
 
 #endif /* MOBILECAI_H */
Index: Sim/Units/UnitLoader.cpp
===================================================================
--- Sim/Units/UnitLoader.cpp	(revision 3848)
+++ Sim/Units/UnitLoader.cpp	(working copy)
@@ -466,6 +466,9 @@
 	weapon->fuelUsage = udw->fuelUsage;
 	weapon->avoidFriendly = weapondef->avoidFriendly;
 	weapon->avoidFeature = weapondef->avoidFeature;
+	weapon->targetBorder = weapondef->targetBorder;
+	weapon->cylinderTargetting = weapondef->cylinderTargetting;
+	weapon->minIntensity = weapondef->minIntensity;
 	weapon->collisionFlags = weapondef->collisionFlags;
 	weapon->Init();
 
Index: Sim/Weapons/BeamLaser.cpp
===================================================================
--- Sim/Weapons/BeamLaser.cpp	(revision 3848)
+++ Sim/Weapons/BeamLaser.cpp	(working copy)
@@ -85,6 +85,7 @@
 	}
 
 	float3 dir=pos-weaponPos;
+
 	float length=dir.Length();
 	if(length==0)
 		return true;
@@ -133,6 +134,7 @@
 		}
 	}
 	dir+=(salvoError)*(1-owner->limExperience*0.7f);
+
 	dir.Normalize();
 
 	FireInternal(dir, false);
@@ -145,6 +147,7 @@
 	if(owner->directControl)
 		rangeMod=0.95f;
 #endif
+
 	float maxLength=range*rangeMod;
 	float curLength=0;
 	float3 curPos=weaponPos;
@@ -152,6 +155,19 @@
 
 	bool tryAgain=true;
 	CUnit* hit;
+
+	// increase range if targets are searched for in a cylinder
+	if (cylinderTargetting > 0.01) {
+		const float3 up(0, owner->radius*cylinderTargetting, 0);
+		const float uplen = up.dot(dir);
+		maxLength = sqrt(maxLength*maxLength + uplen*uplen);
+	}
+
+	// increase range if targetting edge of hitsphere
+	if (targetType == Target_Unit && targetUnit && targetBorder != 0) {
+		maxLength += targetUnit->radius*targetBorder;
+	}
+
 	for(int tries=0;tries<5 && tryAgain;++tries){
 		tryAgain=false;
 		hit=0;
@@ -172,6 +188,7 @@
 				tryAgain=true;
 			}
 		}
+
 		hitPos=curPos+dir*length;
 
 		float baseAlpha=weaponDef->intensity*255;
@@ -187,7 +204,15 @@
 		curLength+=length;
 		dir=newDir;
 	}
-	float	intensity=1-(curLength)/(range*2);
+
+	// fix negative damage when hitting big spheres
+	float actualRange = range;
+	if (hit && targetBorder > 0) {
+		actualRange += hit->radius*targetBorder;
+	}
+	// make it possible to always hit with some minimal intensity (melee weapons have use for that)
+	float intensity=max(minIntensity, 1-(curLength)/(actualRange*2));
+
 	if(curLength<maxLength) {
 		// Dynamic Damage
 		DamageArray dynDamages;
Index: Sim/Weapons/MeleeWeapon.cpp
===================================================================
--- Sim/Weapons/MeleeWeapon.cpp	(revision 3848)
+++ Sim/Weapons/MeleeWeapon.cpp	(working copy)
@@ -34,7 +34,9 @@
 void CMeleeWeapon::Fire(void)
 {
 	if(targetType==Target_Unit){
-		targetUnit->DoDamage(damages,owner,ZeroVector,weaponDef->id);
+		float3 impulseDir = targetUnit->pos-weaponPos;
+		impulseDir.Normalize();
+		targetUnit->DoDamage(damages,owner,impulseDir,weaponDef->id);
 		if(fireSoundId)
 			sound->PlaySample(fireSoundId,owner,fireSoundVolume);
 	}
Index: Sim/Weapons/Weapon.cpp
===================================================================
--- Sim/Weapons/Weapon.cpp	(revision 3848)
+++ Sim/Weapons/Weapon.cpp	(working copy)
@@ -83,6 +83,9 @@
 	CR_MEMBER(hasCloseTarget),
 	CR_MEMBER(avoidFriendly),
 	CR_MEMBER(avoidFeature),
+	CR_MEMBER(targetBorder),
+	CR_MEMBER(cylinderTargetting),
+	CR_MEMBER(minIntensity),
 	CR_MEMBER(collisionFlags),
 	CR_MEMBER(fuelUsage),
 	CR_MEMBER(weaponNum)
@@ -151,6 +154,9 @@
 	hasCloseTarget(false),
 	avoidFriendly(true),
 	avoidFeature(true),
+	targetBorder(0.f),
+	cylinderTargetting(0.f),
+	minIntensity(0.f),
 	collisionFlags(0),
 	fuelUsage(0)
 {
@@ -164,6 +170,12 @@
 
 void CWeapon::Update()
 {
+	// do not fire at cloaked units
+	if(targetType == Target_Unit && targetUnit && targetUnit->isCloaked) {
+		HoldFire();
+		return;
+	}
+
 	if(hasCloseTarget){
 		std::vector<int> args;
 		args.push_back(0);
@@ -544,7 +556,31 @@
 
 	float3 dif=pos-weaponPos;
 
-	float r=GetRange2D(owner->pos.y-pos.y);
+	if (targetBorder != 0 && unit) {
+		float3 diff(dif);
+		diff.Normalize();
+		// weapon inside target sphere
+		if (dif.SqLength() < unit->sqRadius*targetBorder*targetBorder) {
+			dif -= diff*(dif.Length() - 10); // a hack
+			//logOutput << "inside\n";
+		} else {
+			dif -= diff*(unit->radius*targetBorder);
+			//logOutput << "outside\n";
+		}
+		//geometricObjects->AddLine(weaponMuzzlePos, weaponMuzzlePos+dif, 3, 0, 16);
+	}
+
+	float r;
+	if (!unit || cylinderTargetting < 0.01) {
+		r=GetRange2D(owner->pos.y-pos.y);
+	} else {
+		if (cylinderTargetting * unit->radius > owner->pos.y-pos.y) {
+			r = GetRange2D(0);
+		} else {
+			r = 0;
+		}
+	}
+
 	if(dif.SqLength2D()>=r*r)
 		return false;
 
Index: Sim/Weapons/Weapon.h
===================================================================
--- Sim/Weapons/Weapon.h	(revision 3848)
+++ Sim/Weapons/Weapon.h	(working copy)
@@ -119,12 +119,17 @@
 	int lastErrorVectorUpdate;
 
 	CWeapon* slavedTo;						//use this weapon to choose target
-	
+
 	float3 mainDir;								//main aim dir of weapon
 	float maxMainDirAngleDif;					//how far away from main aim dir the weapon can aim at something (as an acos value)
 
 	bool avoidFriendly;		//if true tried to avoid friendly Units when aiming.
 	bool avoidFeature;      		//if true try to avoid Features while aiming.
+
+	float targetBorder;  // if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
+	float cylinderTargetting;	//if greater than 0, range will be checked in a cylinder (height=unitradius*cylinderTargetting) instead of a sphere
+	float minIntensity;	// for beamlasers - always hit with some minimum intensity (a damage coeffcient normally dependent on distance). do not confuse with intensity tag, it's completely unrelated.
+
 	unsigned int collisionFlags;
 
 	float fuelUsage;
Index: Sim/Weapons/WeaponDefHandler.cpp
===================================================================
--- Sim/Weapons/WeaponDefHandler.cpp	(revision 3848)
+++ Sim/Weapons/WeaponDefHandler.cpp	(working copy)
@@ -91,6 +91,8 @@
 	if(!collideFeature)
 		weaponDefs[id].collisionFlags+=COLLISION_NOFEATURE;
 
+	sunparser->GetDef(weaponDefs[id].minIntensity, "0", weaponname + "\\MinIntensity");
+
 	sunparser->GetDef(weaponDefs[id].dropped, "0", weaponname + "\\dropped");
 	sunparser->GetDef(lineofsight, "0", weaponname + "\\lineofsight");
 	sunparser->GetDef(ballistic, "0", weaponname + "\\ballistic");
@@ -119,7 +121,7 @@
 	sunparser->GetDef(weaponDefs[id].laserflaresize, "15", weaponname + "\\laserflaresize");
 	sunparser->GetDef(weaponDefs[id].intensity, "0.9", weaponname + "\\intensity");
 	sunparser->GetDef(weaponDefs[id].duration, "0.05", weaponname + "\\duration");
-	
+
 	sunparser->GetDef(weaponDefs[id].visuals.sizeDecay,  "0", weaponname + "\\sizeDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.alphaDecay, "1", weaponname + "\\alphaDecay");
 	sunparser->GetDef(weaponDefs[id].visuals.separation, "1", weaponname + "\\separation");
@@ -188,6 +190,15 @@
 
 //	logOutput.Print("%s as %s",weaponname.c_str(),weaponDefs[id].type.c_str());
 
+	sunparser->GetDef(weaponDefs[id].targetBorder, (weaponDefs[id].type == "Melee"?"1":"0"), weaponname + "\\TargetBorder");
+	if (weaponDefs[id].targetBorder > 1.f) {
+		logOutput.Print("warning: targetBorder truncated to 1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = 1.f;
+	} else if (weaponDefs[id].targetBorder < -1.f) {
+		logOutput.Print("warning: targetBorder truncated to -1 (was %f)", weaponDefs[id].targetBorder);
+		weaponDefs[id].targetBorder = -1.f;
+	}
+	sunparser->GetDef(weaponDefs[id].cylinderTargetting, (weaponDefs[id].type == "Melee"?"1":"0"), weaponname + "\\CylinderTargetting");
 
 
 	weaponDefs[id].range = atof(sunparser->SGetValueDef("10", weaponname + "\\range").c_str());
@@ -576,7 +587,7 @@
 
 	if (inverted == true) {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = damages[i] - (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
@@ -591,7 +602,7 @@
 	}
 	else {
 		for(int i=0; i < damageArrayHandler->numTypes; ++i) {
-			
+
 			dynDamages[i] = (1 - pow(1 / range * travDist, exp)) * damages[i];
 
 			if (damageMin > 0) {
Index: Sim/Weapons/WeaponDefHandler.h
===================================================================
--- Sim/Weapons/WeaponDefHandler.h	(revision 3848)
+++ Sim/Weapons/WeaponDefHandler.h	(working copy)
@@ -86,7 +86,7 @@
 	bool manualfire;			//use dgun button
 	int interceptor;				//anti nuke
 	int targetable;				//nuke (can be shot by interceptor)
-	bool stockpile;					
+	bool stockpile;
 	float coverageRange;		//range of anti nuke
 
 	float intensity;
@@ -138,7 +138,7 @@
 		float tilelength;
 		float scrollspeed;
 		float pulseSpeed;
-		
+
 		int stages;
 		float alphaDecay;
 		float sizeDecay;
@@ -172,6 +172,11 @@
 
 	bool avoidFriendly;		//if true try to avoid friendly Units when aiming.
 	bool avoidFeature;      //if true try to avoid Features while aiming.
+
+	float targetBorder;		//if nonzero, targetting units will TryTarget at the edge of collision sphere (radius*tag value, [-1;1]) instead of its centre
+	float cylinderTargetting;	//if greater than 0, range will be checked in a cylinder (height=unitradius*cylinderTargetting) instead of a sphere
+	float minIntensity;		// for beamlasers - always hit with some minimum intensity (a damage coeffcient normally dependent on distance). do not confuse with intensity tag, it's completely unrelated.
+
 	unsigned int collisionFlags;
 
 	CExplosionGenerator *explosionGenerator; // can be zero for default explosions
 | 
|---|