/*
   Copyright (C) 2004 by James Gregory
   Part of the GalaxyHack project
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.
 
   See the COPYING file for more details.
*/

#include "Group.h"
#include "Globals.h"
#include "Random.h"
#include "PreBattle.h"

#include <string>
#include <stdexcept>

using std::string;
using std::runtime_error;

Group_Base::Group_Base(const string& iDataFilename, const string& iAIFilename, int iMySide, int iMyGroup, const vector<int>& savedGroupsVec, CoordsInt iStartingCoords):
		dataFilename(iDataFilename), aiFilename(iAIFilename), mySide(iMySide), myGroup(iMyGroup),
		drawBound(false), drawNumber(false), alive(true),
myParentCaSh(-1), invertPatrol(false), alreadyDragging(false), selected(false), remSpeedx(0), remSpeedy(0),
alreadyAIError(false), lastAlive(0) {

	for (int i = 0; i != nAIVars; ++i) {
		aiInterpreter.saveGroups[i].x = mySide;
		aiInterpreter.saveGroups[i].y = savedGroupsVec[i];
	}

	startingCoords.x = iStartingCoords.x;
	startingCoords.y = iStartingCoords.y;
}

void Group_Base::SetInitialVars() {
	if (units.size() != 0) {
		speed = units[0].GetSpeed();
		maxSpeedChange = speed / 7;
	}
	else
		speed = 0;

	unitsLeft = units.size();
	unitsRemembered = units.size();

	SetBoundingRect();
}

void Group_Base::ResetForBattle() {
	drawBound = false;
	drawNumber = false;
}

void Group_Base::GroupAddDelInform(int groupNumber, int howMany) {
	//with add it could be equal to, with minus it couldn't
	if (myGroup >= groupNumber) {
		//add or minus
		myGroup+= howMany;

		//tell all the units
		for (int i = 0; i != units.size(); ++i)
			units[i].ChangeGroupNumber(myGroup);
	}

	if (myParentCaSh != -1 && myParentCaSh >= groupNumber) {
		//they've deleted our parent (irrelevant now small ships get deleted when their cap ship does)
		if (myParentCaSh == groupNumber)
			myParentCaSh = -1;
		//add or minus
		else
			myParentCaSh+= howMany;
	}
}

bool Group_Base::SetPos(float ix, float iy) {
	myx = ix;
	myy = iy;

	RandomPlaceUnits();
	if (CheckForCSFrCollision()
	        || myx < sides[mySide].startingRect.x
	        || myy < sides[mySide].startingRect.y
	        || myx + width > sides[mySide].startingRect.x + sides[mySide].startingRect.w
	        || myy + height > sides[mySide].startingRect.y + sides[mySide].startingRect.h)
		return false;
	else
		return true;
}

bool Group_Base::GoToStartCoords() {
	SDL_Rect& startRect = sides[mySide].startingRect;
	int xCoord;
	int yCoord;

	if (mySide == 0 || mySide == 2)
		xCoord = startRect.x + startingCoords.x;
	else
		xCoord = startRect.x + startRect.w - startingCoords.x - width;

	if (mySide == 0 || mySide == 1)
		yCoord = startRect.y + startingCoords.y;
	else
		yCoord = startRect.y + startRect.h - startingCoords.y - height;

	return SetPos(xCoord, yCoord);
}

void Group_Base::SetBoundingRect() {
	int tempWidth, tempHeight;

	units[0].GetDimensions(tempWidth, tempHeight);

	//if just one unit it has no extra space
	if (unitsLeft == 1) {
		width = tempWidth;
		height = tempHeight;
	} else {
		int widthTotal = 0;
		int heightTotal = 0;

		for (int i = 0; i != unitsLeft; ++i) {
			widthTotal += tempWidth;
			heightTotal += tempHeight;
		}

		width = widthTotal * smShGroupSpacing;
		height = heightTotal * smShGroupSpacing;
	}
}

void Group_Base::RandomPlaceUnits() {
	if (units.size() == 1)
		units[0].SetPos(myx, myy);
	else {
		int tempWidth, tempHeight;


		for (int i = 0; i != units.size(); ++i) {
			units[i].GetDimensions(tempWidth, tempHeight);

			int x = Random() % (width - tempWidth) + static_cast<int>(myx);
			int y = Random() % (height - tempHeight) + static_cast<int>(myy);
			units[i].SetPos(x, y);
		}
	}
}

void Group_Base::SetUSRect() {
	USRect.x = static_cast<int>(myx) - viewx;
	USRect.y = static_cast<int>(myy) - viewy;
	USRect.w = width;
	USRect.h = height;

	if (USRect.x < globalSettings.screenWidth
	        && USRect.y < globalSettings.screenHeight
	        && USRect.x + USRect.w > 0
	        && USRect.y + USRect.h > 0)
		onScreen = true;

	else
		onScreen = false;
}

void Group_Base::MouseD(Uint8 button, Uint16 x, Uint16 y) {
	if (alive && button == SDL_BUTTON_LEFT
	&& x > USRect.x
	&& x < USRect.x + USRect.w
	&& y > USRect.y
	&& y < USRect.y + USRect.h) {
		if (gsCurrent == GST_PreBattle) {
			alreadyDragging = true;
			whereClicked.x = x - USRect.x;
			whereClicked.y = y - USRect.y;
		} else
			Select(RTS_GroupStats);
	} else
		selected = false;
}

void Group_Base::MouseU(Uint8 button, Uint16 x, Uint16 y) {
	if (button == SDL_BUTTON_LEFT)
		alreadyDragging = false;
}

void Group_Base::Drag(int state, int x, int y) {
    if (alreadyDragging) {
        float dx = viewx + x - whereClicked.x - myx;
        float dy = viewy + y - whereClicked.y - myy;

		if (GetType() != UT_SmShUnit)
			PixelShuffle(dx, dy);
		
		Relocate(dx, dy);

		if (PreBattle::pbState != PBS_PreBattle && (myx < sides[mySide].startingRect.x || myx + width > sides[mySide].startingRect.x + sides[mySide].startingRect.w))
			Relocate (-dx, 0);

		if (PreBattle::pbState != PBS_PreBattle && (myy < sides[mySide].startingRect.y || myy + height > sides[mySide].startingRect.y + sides[mySide].startingRect.h))
            Relocate (0, -dy);

		startingCoords.x = static_cast<int>(myx) - sides[mySide].startingRect.x;
		startingCoords.y = static_cast<int>(myy) - sides[mySide].startingRect.y;
		
		for (int i = 0; i != sides[mySide].groups.size(); ++i) {
			if (sides[mySide].groups[i].GetParentCaSh() == myGroup)
				sides[mySide].groups[i].GoToStartCoords();
		}
	}
}

void Group_Base::Select(WindowChoice infoType) {
	selected = true;
	viewSide = mySide;
	viewGroup = myGroup;
	myWindows.push_back(GenWindow(0, 0, infoType, mySide, myGroup, WFLAG_TILED));
}

bool Group_Base::CheckForCollision(int nSide, int nGroup) const {
    if (!sides[nSide].groups[nGroup].GetAlive())
        return false;
        
	SDL_Rect tempRect;

	sides[nSide].groups[nGroup].GetRect(tempRect);

    SDL_Rect myRect = {static_cast<int>(myx), static_cast<int>(myy), width, height};
	if (TestOverlap(myRect, tempRect))
		return true;

	return false;
}

bool Group_Base::CheckForCSFrCollision() const {
	CoordsInt throwAway;
	return CheckForCSFrCollision(throwAway);
}

bool Group_Base::CheckForCSFrCollision(CoordsInt& whoHit) const {
	if (GetType() == UT_SmShUnit)
		return false;

	//only concerned with collisions with our own side
	for (int i = 0; i != sides[mySide].groups.size(); ++i) {
		if (sides[mySide].groups[i].GetType() == UT_SmShUnit || (i == myGroup))
			continue;
		
		//if in battle, only concerned if we share the same move target
		if (gsCurrent == GST_Battle) {
			const AICommands* pCommands = sides[mySide].groups[i].GetTheCommands();
			
			if (pCommands->moveTarget.x != theCommands.moveTarget.x
			|| pCommands->moveTarget.y != theCommands.moveTarget.y)
				continue;
		}
			
		if (CheckForCollision(mySide, i)) {
			whoHit.x = mySide;
			whoHit.y = i;
			return true;
		}
	}
	
	return false;
}

void Group_Base::RunFireCommands() {
	for (int i = 0; i != units.size(); ++i)
		units[i].Fire(theCommands);
}

int Group_Base::FindDistanceTo(int nSide, int nGroup) const {
    CoordsInt d = GetDxDyClose(nSide, nGroup);
	return SlowDist(d.x, d.y);
}

void Group_Base::Move() {
    //no processing, no shuffling
	if (!theCommands.aimPropx && !theCommands.aimPropy && !remSpeedx && !remSpeedy) {
		speedPlaneMatchesTarget = true;
		return;
	}
	
	float dx = theCommands.aimPropx * speed;
	float dy = theCommands.aimPropy * speed;
	
	ConvertMaxChange(dx, dy);
	
	if (GetType() != UT_SmShUnit)
		PixelShuffle(dx, dy);
	
	remSpeedx = dx;
	remSpeedy = dy;
	
	Relocate(dx, dy);
   
	if (theCommands.moveCommand == MC_MoveGroup) {
        CoordsInt dtarget = GetDxDyCenter(theCommands.moveTarget.x, theCommands.moveTarget.y);

		if (((dx >= 0 && dtarget.x >= 0) || (dx <= 0 && dtarget.x <= 0))
		        && ((dy >= 0 && dtarget.y >= 0) || (dy <= 0 && dtarget.y <= 0)))
			speedPlaneMatchesTarget = true;
		else
			speedPlaneMatchesTarget = false;
	}
}

void Group_Base::Relocate(float dx, float dy) {
    //move rect first because units use it
	myx += dx;
	myy += dy;
	
	for (int i = 0; i != units.size(); ++i) {
		units[i].Move(dx, dy);
		if (gsCurrent == GST_Battle)
            units[i].AddExtraMoveFrames();
	}
}

///

void Group_Base::InitAI(int iAIStagger)
{
	aiStagger = iAIStagger;
	aiInterpreter.Init(this, &(sides[mySide].aiScripts[aiFilename].script), &theCommands);
}

void Group_Base::RunGroupAI() {
	if (!aiStagger) {
		aiInterpreter.GetCommands();
		ConvertFireTarget();
		aiStagger = staggerFrames;
	}
	//always --, even if we ran this frame
	--aiStagger;

	//if we we ran our script this frame then this is already up to date, so why not gain
	//some tiny amount of efficiency
	if (aiStagger != staggerFrames - 1
	    && (theCommands.moveCommand == MC_MoveGroup || theCommands.moveCommand == MC_Patrol))
			theCommands.moveTargetDist = FindDistanceTo(theCommands.moveTarget.x, theCommands.moveTarget.y);

	switch (theCommands.moveCommand) {
	case MC_MoveCompass:
		switch (theCommands.moveTarget.y) {
		case CD_N:
			theCommands.aimPropx = 0;
			theCommands.aimPropy = -1;
			break;

		case CD_NE:
			theCommands.aimPropx = 0.5;
			theCommands.aimPropy = -0.5;
			break;

		case CD_E:
			theCommands.aimPropx = 1;
			theCommands.aimPropy = 0;
			break;

		case CD_SE:
			theCommands.aimPropx = 0.5;
			theCommands.aimPropy = 0.5;
			break;

		case CD_S:
			theCommands.aimPropx = 0;
			theCommands.aimPropy = 1;
			break;

		case CD_SW:
			theCommands.aimPropx = -0.5;
			theCommands.aimPropy = 0.5;
			break;

		case CD_W:
			theCommands.aimPropx = -1;
			theCommands.aimPropy = 0;
			break;

		case CD_NW:
			theCommands.aimPropx = -0.5;
			theCommands.aimPropy = -0.5;
			break;
		}

		if (theCommands.bInverse) {
			theCommands.aimPropx = -theCommands.aimPropx;
			theCommands.aimPropy = -theCommands.aimPropy;
		}
		break;

		//we work out movement based on the position of a group
		//before they themselves move
		//this is good however, as
		//a) stops earlier groups in global vectors
		//gaining an advantage
		//b) makes groups gradually sweep round to targets
		//rather than immediately heading for them

	case MC_MoveGroup:
		if (!theCommands.bInverse)
			ConvertMoveTarget();
		else
			ConvertMoveTarget(true);

		ConvertCollisions();
		break;

	case MC_Patrol:
		ConvertPatrolTarget();
		break;
	}

	CheckForGoingOffScreen();
	
	//always try to go round patrol circle clockwise when we first start patrolling
	if (theCommands.moveCommand != MC_Patrol)
		invertPatrol = false;

	int smallNumber = units[0].GetSmallNumber();

	if (smallNumber)
		//virtual
		SelectSmallTargets();
}

void Group_Base::ConvertPatrolTarget() {
	int radius = theCommands.patrolDist;

	//note correction combined with circular motion equation
	//means it is possible to exceed maximum speed,
	//oh well

	//if more than desired radius then move towards target
	//need leeway because we'll never get there exactly
	if (theCommands.moveTargetDist - moveLeeway > radius)
		ConvertMoveTarget();

	//if less than desired radius then move away from **centre**, even though we patrol closest point
	else if (theCommands.moveTargetDist + moveLeeway < radius)
		ConvertMoveTarget(true);

	else {
        CoordsInt d = GetDxDyClose(theCommands.moveTarget.x, theCommands.moveTarget.y);

		if (theCommands.patrolDist) {
			theCommands.aimPropx = static_cast<float>(d.y) / theCommands.patrolDist;
			theCommands.aimPropy = static_cast<float>(-d.x) / theCommands.patrolDist;
		}
		
		if (invertPatrol) {
			theCommands.aimPropx = -theCommands.aimPropx;
			theCommands.aimPropy = -theCommands.aimPropy;
		}
	}
}

//inverse is used for two things: "moveaway", and for patrolling
void Group_Base::ConvertMoveTarget(bool inverse) {
	CoordsInt d = GetDxDyCenter(theCommands.moveTarget.x, theCommands.moveTarget.y);

	float propx = 0;
	float propy = 0;

	//normally just get props, but if we want to move away and are currently directly 
	//on top of them, move away regardless of 0 props
	if (!GetMoveProps(propx, propy, d.x, d.y) && inverse == true) {
		propx = static_cast<float>(Random() % 100 + 1) / 100;
		propy = 1 - propx;
	}

	if (!inverse) {
		theCommands.aimPropx = propx;
		theCommands.aimPropy = propy;
	}
	else {
		theCommands.aimPropx = -propx;
		theCommands.aimPropy = -propy;
	}
}

///

int Group_Base::GetPointsValue() const {
	int total = 0;

	for (int i = 0; i != units.size(); ++i)
		total+= units[i].GetPointsValue();

	return total;
}

CoordsInt Group_Base::GetClosestPoint(const CoordsInt you) const {
	CoordsInt ret;
    CoordsInt center = GetCenter();
    int xLeftDist = abs(static_cast<int>(myx) - you.x);
	int xMidDist = abs(center.x - you.x);
	int xRightDist = abs(static_cast<int>(myx) + width - you.x);

	int yTopDist = abs(static_cast<int>(myy) - you.y);
	int yMidDist = abs(center.y - you.y);
	int yBotDist = abs(static_cast<int>(myy) + height - you.y);

	int temp = Min(xLeftDist, xMidDist);
	temp = Min(temp, xRightDist);

	if (temp == xLeftDist)
		ret.x = static_cast<int>(myx);
	else if (temp == xMidDist)
		ret.x = center.x;
	else if (temp == xRightDist)
		ret.x = static_cast<int>(myx) + width;

	temp = Min(yTopDist, yMidDist);
	temp = Min(temp, yBotDist);

	if (temp == yTopDist)
		ret.y = static_cast<int>(myy);
	else if (temp == yMidDist)
		ret.y = center.y;
	else if (temp == yBotDist)
		ret.y = static_cast<int>(myy + height);
		
	return ret;
}

int Group_Base::GetHealth() const {
	int total = 0;

	for (int i = 0; i != units.size(); ++i) {
		if (units[i].GetAlive())
			total+= units[i].GetHealth();
	}

	return total;
}

int Group_Base::GetShield() const {
	int total = 0;

	for (int i = 0; i != units.size(); ++i) {
		if (units[i].GetAlive())
			total+= units[i].GetShield();
	}

	return total;
}

int Group_Base::GetArmour() const {
	int total = 0;

	for (int i = 0; i != units.size(); ++i) {
		if (units[i].GetAlive())
			total+= units[i].GetArmour();
	}

	return total;
}

int Group_Base::GetMaxHealth() const {
	int perUnit = units[0].GetMaxHealth();
	return perUnit * units.size();
}

int Group_Base::GetMaxShield() const {
	int perUnit = units[0].GetMaxShield();
	return perUnit * units.size();
}

int Group_Base::GetMaxArmour() const {
	int perUnit = units[0].GetMaxArmour();
	return perUnit * units.size();
}

int Group_Base::GetSmallPower() const {
	int powerTotal = 0;
	int powerPerUnit = units[0].GetSmallNumber() * weaponLookup[units[0].GetSmallType()].power;

	for (int i = 0; i != units.size(); ++i) {
		if (units[i].GetAlive())
			powerTotal+= powerPerUnit;

	}

	return powerTotal;
}

int Group_Base::GetBigPower() const {
	int powerTotal = 0;
	int powerPerUnit = weaponLookup[units[0].GetBigType()].power;

	for (int i = 0; i != units.size(); ++i) {
		if (units[i].GetAlive())
			powerTotal+= powerPerUnit;
	}

	return powerTotal;
}

int Group_Base::GetBigAmmo() const {
	int ammoTotal = 0;

	for (int i = 0; i != units.size(); ++i) {
		if (units[i].GetAlive())
			ammoTotal+= units[0].GetBigAmmo();
	}

	return ammoTotal;
}

int Group_Base::GetHowFull() const {
	int total = 0;

	for (int i = 0; i != sides[mySide].groups.size(); ++i) {
		if (sides[mySide].groups[i].GetParentCaSh() == myGroup)
			++total;
	}

	return total;
}

void Group_Base::FiredAt(TargetDesc& targetInfo) {
	//select a random unit from the group, but not a dead one
	if (alive) {
		do {
			targetInfo.whichUnit = Random() % units.size();
		} while ( !(units[targetInfo.whichUnit].GetAlive()) );
	}

	//if we are dead, give them the position of the last unit which was alive, rather than
	//one which died ages ago
	else
		targetInfo.whichUnit = lastAlive;

	CoordsInt center = units[targetInfo.whichUnit].GetCenter();

	targetInfo.currentx = center.x;
	targetInfo.currenty = center.y;

	targetInfo.speedx = remSpeedx;
	targetInfo.speedy = remSpeedy;
	  
	targetInfo.weakSpot = units[0].GetWeakSpot();

	targetInfo.difficulty = static_cast<int>(units[0].GetSpeed()) * 2;
}

void Group_Base::Upkeep() {
	for (int i = 0; i != units.size(); ++i)
		units[i].Upkeep();

	//how many units do we have left?
	unitsLeft = 0;
	for (int i = 0; i != units.size(); ++i) {
		if (units[i].GetAlive()) {
			++unitsLeft;
			lastAlive = i;
		}
	}

	if (unitsLeft == 0) {
		alive = false;
		//don't want to set a non-existent bounding rect
		return;
	}

	if (unitsLeft < unitsRemembered) {
		//shuffle the rect vaguely towards the centre...
		int tempWidth, tempHeight;
		units[0].GetDimensions(tempWidth, tempHeight);
		myx += tempWidth;
		myy += tempHeight;
		SetBoundingRect();
	}

	unitsRemembered = unitsLeft;
}

void Group_Base::DeadUpkeep() {
	for (int i = 0; i != units.size(); ++i)
		units[i].Upkeep();
}

void Group_Base::DrawSelfPixels() {
	for (int i = 0; i != units.size(); ++i)
		units[i].DrawSelfPixels();
	
	if (selected) {
		CoordsInt center;
		center = GetCenter();
		if (units[0].GetSmallType() != WT_None)
			JSDL.DrawCircle(center.x - viewx, center.y - viewy, weaponLookup[units[0].GetSmallType()].range + width, white);
		if (units[0].GetBigType() != WT_None)
			JSDL.DrawCircle(center.x - viewx, center.y - viewy, weaponLookup[units[0].GetBigType()].range + width, bigRangeBlue);
	}
}

void Group_Base::DrawBound() {
	if (onScreen) {
		if (selected || drawBound)
			DrawRectBorder(USRect, sides[mySide].color);

		if (selected || drawNumber) {
			char output[10];
			sprintf(output, "%d-%d", mySide+1, myGroup+1);
			normalFonts.BlitString(USRect.x + USRect.w, USRect.y + USRect.h, 0, output);
		}
	}
}

void Group_Base::ChangeSize(int newSize) {
	while (units.size() != newSize) {
		if (newSize < units.size()) {
			units.erase(--units.end());
			--unitsLeft;
		}

		if (newSize > units.size()) {
			units.push_back(RTSUnit(mySide, myGroup, GetType(), dataFilename));
			++unitsLeft;
		}
	}
}

void Group_Base::ChangeStartCoords(int ix, int iy) {
	startingCoords.x = static_cast<int>(myx - sides[mySide].startingRect.x);
	startingCoords.y = static_cast<int>(myy - sides[mySide].startingRect.y);
}

void Group_Base::ChangeUnitPic(const string& newPic) {
	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeUnitPic(newPic);
}

void Group_Base::ChangeSmallType(WeaponType newType) {
	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeSmallType(newType);
}

void Group_Base::ChangeBigType(WeaponType newType) {
	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeBigType(newType);
}


void Group_Base::ChangeEngine(const string& newType) {
	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeEngine(newType);
}

void Group_Base::ChangeArmour(const string& newType) {
	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeArmour(newType);
}

void Group_Base::ChangeShield(const string& newType) {
	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeShield(newType);
}

void Group_Base::ChangeCSType(CapShipType newType) {
	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeCSType(newType);
		
	SetBoundingRect();
}

void Group_Base::ChangeMySide(int newMySide) {
	mySide = newMySide;

	for (int i = 0; i != units.size(); ++i)
		units[i].ChangeMySide(newMySide);
}

void Group_Base::ToggleDrawBound() {
	if (drawBound)
		drawBound = false;
	else
		drawBound = true;
}

void Group_Base::ToggleDrawNumber() {
	if (drawNumber)
		drawNumber = false;
	else
		drawNumber = true;
}

CoordsInt Group_Base::GetDxDyClose(int side, int group) const {
    CoordsInt center = GetCenter();
	CoordsInt target = sides[side].groups[group].GetClosestPoint(center);
	CoordsInt myCoords = GetClosestPoint(target);

	CoordsInt ret = {target.x - myCoords.x, target.y - myCoords.y};
	return ret;
}

CoordsInt Group_Base::GetDxDyCenter(int side, int group) const {
    CoordsInt center = GetCenter();
	CoordsInt target = sides[side].groups[group].GetCenter();

	CoordsInt ret = {target.x - center.x, target.y - center.y};
	return ret;
}

void Group_Base::ConvertCollisions() {
    if (CheckForCollision(theCommands.moveTarget.x, theCommands.moveTarget.y) && theCommands.bInverse == false) {
		theCommands.aimPropx = 0;
		theCommands.aimPropy = 0;
	}
}

void Group_Base::PointsValueCheck() {
	if (units[0].GetPointsValue() > units[0].GetMaxPoints()) {
		string error =  dataFilename + " unit exceeds its maximum points value";
		throw runtime_error(error.c_str());
	}
}

//when moving non small ships, we move 1 pixel at a time, checking for collisions before each move and stopping moving if we see
//that we will collide
void Group_Base::PixelShuffle(float& dx, float& dy) {	
	//if we are already overlapping then try to shuffle away from the person we are overlapping
	CoordsInt whoHit;
	if (CheckForCSFrCollision(whoHit)) {		
		theCommands.moveTarget.x = whoHit.x;
		theCommands.moveTarget.y = whoHit.y;
		ConvertMoveTarget(true);
		dx = theCommands.aimPropx * speed;
		dy = theCommands.aimPropy * speed;
		CheckForGoingOffScreen();
		ConvertMaxChange(dx, dy);
		return;
	}
		
	float xshuffle = 1;
	float yshuffle = 1;
	if (dx < 0)
		xshuffle = -1;
	if (dy < 0)
		yshuffle = -1;
	
	float actualx = myx;
	float actualy = myy;		
	
	while (dx) {
		myx += dx;
			
		if (CheckForCSFrCollision())
			myx -= dx;
		else
			break;
		
		if (dx > -1 && dx < 1)
			dx = 0;
		else
			dx -= xshuffle;
	}
	
	while (dy) {		
		myy += dy;
			
		if (CheckForCSFrCollision())
			myy -= dy;
		else
			break;
			
		if (dy > -1 && dy < 1)
			dy = 0;
		else
			dy -= yshuffle;
	}
		
	dx = myx - actualx;
	dy = myy - actualy;
	myx = actualx;
	myy = actualy;
}

void Group_Base::ConvertMaxChange(float& dx, float& dy) {
	float dxsp = dx - remSpeedx;
	float dysp = dy - remSpeedy;
	
	if (dxsp > maxSpeedChange)
	   dx = remSpeedx + maxSpeedChange;
	else if (dxsp < -maxSpeedChange)
	   dx = remSpeedx - maxSpeedChange;
	   
	if (dysp > maxSpeedChange)
	   dy = remSpeedy + maxSpeedChange;
	else if (dysp < -maxSpeedChange)
	   dy = remSpeedy - maxSpeedChange;
}

void Group_Base::ReportOnScriptError(const char* error, int lineNumber) {  
    if (alreadyAIError)
        return;
        
    alreadyAIError = true;
    
	char output[256];
	if (!sides[mySide].alreadyAIError) {
		sprintf(output, "AI error for side %d, see fleet AI error window for details.\n\nFurther error messages from this fleet will not create this popup,\nand only the first error for each group is reported.", mySide+1);
		CreateInfoString(output, true);
	}
	if (lineNumber != 0)	
		sprintf(output, "%s for group %d-%d at line %d", error, mySide+1, myGroup+1, lineNumber + 1);
	else
		sprintf(output, "%s for group %d-%d", error, mySide+1, myGroup+1);

	WriteLog(output);
	sides[mySide].AddAIError(output);
}

void Group_Base::CheckForGoingOffScreen() {
	float possx = theCommands.aimPropx * speed;
	float possy = theCommands.aimPropy * speed;
	//if we are patrolling and hit the edge of the screen, turn back and go round the circle the other way the next frame
	if ((myx + possx < 0 && theCommands.aimPropx < 0)
	        || (myx + width + possx > worldWidth && theCommands.aimPropx > 0)) {
		theCommands.aimPropx = 0;
		invertPatrol = !invertPatrol;
	}

	if ((myy + possy < 0 && theCommands.aimPropy < 0)
	        || (myy + height + possy > worldHeight) && theCommands.aimPropy > 0) {
		theCommands.aimPropy = 0;
		invertPatrol = !invertPatrol;
	}
}
