this.GoalType = "PLANT";
this.Version = 1;
this.DefaultPriority = 0.81;
this.CreateOnLoad = false;
this.Region = null;
this.ExplosiveTypeDynamite = 1<<0;
this.ExplosiveTypeSatchel = 1<<1;
this.RolePriorityBonus = 0.1;
this.RemoveWithEntity = false;

//////////////////////////////////////////////////////////////////////////

this.InitNewGoal = function()
{
	this.TraceInfo = {};
	this.ExplosiveType = this.ExplosiveTypeDynamite;
	this.CreateOnLoad = true; // user created goals need this
};

//////////////////////////////////////////////////////////////////////////

this.UpgradeVersion = function(Props)
{
	// version 0 is string,string key/values from the old waypoint format
	if(Props.Version==0)
	{
		// Version 1 Schema, upgrade from waypoint format
		Props.Version = 1;
		Props.ExplosiveType = ToInt(Props.ExplosiveType,this.ExplosiveTypeDynamite);
		Props.IgnoreTargets = ToBool(Props.IgnoreTargets,false);
	}
	else if(Props.Version == this.Version)
	{
		this.ExplosiveType = Props.ExplosiveType;
		this.IgnoreTargets = Props.IgnoreTargets;
		this.TraceInfo = Props.TraceInfo;
		this.IgnoreEntity = Props.IgnoreEntity;
		this.DetonateSpots = Props.DetonateSpots;
		this.CoverSpots = Props.CoverSpots;
		this.CoverSplit = Props.CoverSplit;

		if ( this.CoverSpots )
		{
			// clear any occupied slots
			foreach ( spot in this.CoverSpots ) {
				spot.occupied = null;
			}
			// delete empty table
			if (tableCount(this.CoverSpots)==0) {
				this.CoverSpots = null;
			}
		}

		// verify fields and types.
		assert(IsInt(this.ExplosiveType));
	}
};

//////////////////////////////////////////////////////////////////////////

this.Render = function(editMode)
{
	if (this.TraceEntity && EntityIsValid(this.TraceEntity))
	{
		tracePos = GetEntPosition(this.TraceEntity);
		goalPos = this.GetPosition();
		bbEnt = this.TraceEntity;
	}

	topOffset = Vector3(0,0,64);

	while(this.RenderGoal == true)
	{
		if(this.ExtraDebugText==null)
		{
			this.ExtraDebugText = "";

			if (this.GoalType != "DYNAMITE")
			{
				if (this.TraceEntity && EntityIsValid(this.TraceEntity))
				{
					this.ExtraDebugText += format("TraceEnt: %d\n",this.TraceEntity);
				}

				switch(this.ExplosiveType)
				{
					case this.ExplosiveTypeDynamite:
					{
						this.ExtraDebugText += "ExplosiveType: Dynamite\n";
					}
					case this.ExplosiveTypeSatchel:
					{
						this.ExtraDebugText += "ExplosiveType: Satchel\n";
					}
					case 3: // both
					{
						this.ExtraDebugText += "ExplosiveType: Both\n";
					}
					default:
					{
						this.ExtraDebugText += "ExplosiveType: INVALID\n";
					}
				}

				if (this.IgnoreEntity)
				{
					this.ExtraDebugText += "IgnoreEntity\n";
				}
			}
		}

		this.RenderDefault();

		if (tracePos && goalPos)
		{
			DrawArrow(goalPos,tracePos,COLOR.YELLOW,2);
		}
		else if (this.TraceEntity && EntityIsValid(this.TraceEntity))
		{
			tracePos = GetEntPosition(this.TraceEntity);
			goalPos = this.GetPosition();
			bbEnt = this.TraceEntity;
		}

		if (bbEnt)
		{
			DrawEntityOBB( bbEnt, 20, COLOR.GREEN );
		}

		if ( this.CoverSpots )
		{
			foreach ( id and spot in this.CoverSpots )
			{
				base = GroundPoint(spot.position);

				if ( spot.stance == "crouch" )
				{
					stanceOffset = -24;
				}
				else if ( spot.stance == "prone" )
				{
					stanceOffset = -50;
				}
				else
				{
					stanceOffset = 0;
				}

				DrawLine(base,base+topOffset,COLOR.BLUE,2);
				coverText = "";
				if (spot.Timeout) {
					coverText = "\nTimeout: " + spot.Timeout;
				}
				if ( spot.DisableCM > 0 ) {
					coverText += "\nDisableCM: 1";
				}
				DrawText3d(base+topOffset, this.GetName() + " Cover Spot " + ToString(id) + "\nStance: " + spot.stance + coverText,COLOR.WHITE,2,textDrawRadius);

				aimPos = base+topOffset+Vector3(0,0,stanceOffset);
				foreach ( id and face in spot.AimVectors )
				{
					DrawArrow(aimPos,aimPos+face*32,COLOR.BLUE,2);
					DrawText3d(aimPos+face*32,ToString(id),COLOR.WHITE,2,512);
				}
			}
		}

		if ( this.DetonateSpots )
		{
			foreach ( id and spot in this.DetonateSpots )
			{
				base = GroundPoint(spot);
				DrawLine(base,base+topOffset,COLOR.BLUE,2);
				DrawText3d(base+topOffset, this.GetName() + " Detonate Spot " + id,COLOR.WHITE,2,textDrawRadius);
			}
		}

		if ( this.CoverSplit )
		{
			base = this.GetPosition();
			DrawLine(base,base+this.CoverSplit*50,COLOR.BLUE,2);
		}

		sleep(2);
	}
};

//////////////////////////////////////////////////////////////////////////

this.SaveToTable = function(SaveTable)
{
	// save the goal specific properties
	SaveTable.ExplosiveType = this.ExplosiveType;
	if(this.IgnoreTargets){ SaveTable.IgnoreTargets = this.IgnoreTargets; }
	SaveTable.TraceInfo  = this.TraceInfo;
	SaveTable.CoverSpots = this.CoverSpots;
	SaveTable.IgnoreEntity = this.IgnoreEntity;
	SaveTable.DetonateSpots = this.DetonateSpots;
	SaveTable.CoverSplit = this.CoverSplit;
};

//////////////////////////////////////////////////////////////////////////

this.Help = function()
{
	print(this.GoalType,"goal, version",this.Version);
	print("Requires:");
	print("     trace - the traceinfo of the entity to destroy,");
	print("             this should be an OID entity, use /oidlist to confirm");
	print("  or");
	print("     ignoreentity - goal will be enabled/disabled in map script");
	print("Optional:");
	print("     etype - dyno, satchel, both");
	print("     coverspot - location to go to after planting dynamite, supports multiple");
	print("     coverfacing - Direction the bot should face at cover spot");
	print("     coverstance - stand, crouch, prone");
	print("     covertimeout - how long to stay on cover spot, default value is 25 seconds");
	print("     coverdisablecm - disable combat movement at this cover spot");
	print("     coversplit - facing from use point to goal center, only if goal has 2 use points and multiple cover spots");
	print("     detonatespot - location to go to after placing satchel, supports multiple");
};

//////////////////////////////////////////////////////////////////////////

this.GetNearestCoverSpot = function()
{
	spot = null;
	dist = null;
	if ( this.CoverSpots )
	{
		// get the closest one
		foreach (item in this.CoverSpots)
		{
			testDist = DistanceBetween(GetLocalPosition(), item.position);
			if (!dist || testDist < dist)
			{
				dist = testDist;
				spot = item;
			}
		}
	}
	if (!spot)
	{
		print(this.GoalType, ": No cover spots found!");
	}
	return spot;
};

this.SetProperty = function(property, value)
{
	switch(property)
	{
		case "trace":
		{
			if (value == "clear")
			{
				this.TraceInfo = null;
				this.TraceEntity = null;
			}
			else
			{
				targetinfo = Util.ShowEntityInfo(TRACE.ALL);
				if (tableCount(targetinfo) > 0 && targetinfo.id && (GetEntClass(targetinfo.id) == CLASS.GOAL /* || GetEntClass(targetinfo.id) == null */))
				{
					// store the trace positions. this is so we can handle ETPro map scripts
					// since adding ents with the 'create' command makes ent numbers unreliable
					this.TraceInfo = targetinfo;
					this.IgnoreEntity = null;
				}
				else
				{
					print("Invalid or no entity found");
				}
			}
		}
		case "ignoreentity":
		{
			if (value == "0" || value == "false")
			{
				this.IgnoreEntity = null;
			}
			else
			{
				this.TraceInfo = null;
				this.TraceEntity = null;
				this.IgnoreEntity = true;
			}
		}
		case "etype":
		{
			if(value == "dyno")
			{
				this.ExplosiveType = this.ExplosiveTypeDynamite;
			}
			else if(value == "satchel")
			{
				this.ExplosiveType = this.ExplosiveTypeSatchel;
			}
			else if(value == "both")
			{
				this.ExplosiveType = 3;
			}
		}
		case "coverspot":
		{
			if ( value == "clearall" || value == "clear" )
			{
				this.CoverSpots = null;
			}
			else
			{
				i = ToInt(value);
				if ( typeId(i) == 1)
				{
					if (!this.CoverSpots)
					{
						this.CoverSpots = {};
					}
					spotCount = tableCount(this.CoverSpots);
					if ( i < 0 || i > spotCount )
					{
						i = spotCount;
					}

					this.CoverSpots[ i ] = {
						position = GetLocalPosition(),
						AimVectors = { GetLocalFacing() },
						stance = "stand"
					};

					print("Cover Spot",i," added");
				}
				else
				{
					print(this.GoalType, ": Invalid Cover Spot index, expecting int");
				}
			}
		}
		case "coverfacing":
		{
			spot = this.GetNearestCoverSpot();
			if (spot)
			{
				if ( value != "clearall" && value != "clear" ) {
					ind = ToInt(value, 0);
					if (typeId(ind) != 1) {
						print(this.GoalType, ": Invalid facing index, expecting int");
						return;
					}
					facingCount = tableCount(spot.AimVectors);
					if ( ind < 0 || ind > facingCount ) {
						ind = facingCount;
					}

					playerFace = GetLocalFacing();
					spot.AimVectors[ ind ] = playerFace;
					print("Facing",ind,"set to",playerFace);
				}
				else {
					tableClear(spot.AimVectors);
					print("all facings cleared");
				}
			}
		}
		case "coverstance":
		{
			spot = this.GetNearestCoverSpot();
			if (spot)
			{
				spotStance = "stand";

				if ( value == "crouch" || value == "prone" )
				{
					spotStance = value;
				}

				spot.stance = spotStance;
				print("Stance set to",spotStance);
			}
		}
		case "covertimeout":
		{
			spot = this.GetNearestCoverSpot();
			if (spot)
			{
				timeout = ToInt(value, 0);
				if ( timeout == 0 )
				{
					spot.Timeout = null;
					print("Timeout cleared");
				}
				else
				{
					spot.Timeout = timeout;
					print("Timeout set to",timeout);
				}
			}
		}
		case "coverdisablecm":
		{
			spot = this.GetNearestCoverSpot();
			if (spot)
			{
				if (value == ""){ i = !spot.DisableCM; }
				else { i = ToBool(value); }
				if (!i){ i = null; }
				spot.DisableCM = i;
			}
		}
		case "detonatespot":
		{
			if ( value == "clearall" || value == "clear" )
			{
				this.DetonateSpots = null;
			}
			else
			{
				i = ToInt(value);
				if ( typeId(i) == 1)
				{
					if (!this.DetonateSpots)
					{
						this.DetonateSpots = {};
					}
					spotCount = tableCount(this.DetonateSpots);
					if ( i < 0 || i > spotCount )
					{
						i = spotCount;
					}

					this.DetonateSpots[ i ] = GetLocalPosition();

					print("Detonate spot",i," added");
				}
				else
				{
					print(this.GoalType, ": Invalid detonate spot index, expecting int");
				}
			}
		}
		case "coversplit":
		{
			 if ( value == "clear" )
			 {
			 	 this.CoverSplit = null;
			 }
			 else
			 {
			 	 this.CoverSplit = GetLocalFacing();
			 	 if (this.CoverSpots)
			 	 {
			 	 	 foreach(id and spot in this.CoverSpots)
			 	 	 {
			 	 	 	if((spot.position - this.GetPosition()).DotProduct(this.CoverSplit) > 0) {
			 	 	 		s="other";
		 	 	 		}else{
			 	 	 		s="this";
			 	 	 	}
			 	 	 	print("cover spot",id,"-",s,"side");
			 	 	 }
			 	 }
			 	 if (this.DetonateSpots)
			 	 {
			 	 	 foreach(id and spot in this.DetonateSpots)
			 	 	 {
			 	 	 	if((spot - this.GetPosition()).DotProduct(this.CoverSplit) > 0) {
			 	 	 		s="other";
		 	 	 		}else{
			 	 	 		s="this";
			 	 	 	}
			 	 	 	print("detonate spot",id,"-",s,"side");
			 	 	 }
			 	 }
			 }
		}
		default:
		{
			print(this.GoalType, ": Invalid property:", property);
		}
	}

	this.ExtraDebugText = null;
};

//////////////////////////////////////////////////////////////////////////

// give it some time to find it since some games spawn ents late
this.Update = function()
{
	while(1)
	{
		sleep(1);
		if ( !this.TraceInfo || tableCount(this.TraceInfo) < 1 )
		{
			block(0);
		}

		if (!this.TraceEntity
				&& (this.IsAvailable(TEAM.AXIS) || this.IsAvailable(TEAM.ALLIES))
				&& this.TraceInfo.eyePos
				&& this.TraceInfo.endPos)
		{
			this.TraceEntity = Util.TraceForEntityInfo(this.TraceInfo.eyePos, this.TraceInfo.endPos, TRACE.ALL);

			if ( this.TraceEntity && GetEntClass(this.TraceEntity) != CLASS.GOAL )
			{
				this.TraceEntity = null;
			}
			else
			{
				this.ExtraDebugText = null;
				block(0);
			}
		}
	}
};