/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2019  Warzone 2100 Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * ScriptAI.c
 *
 * Script functions to support the AI system
 *
 */

#include "lib/framework/frame.h"
#include "objects.h"
#include "group.h"
#include "lib/script/script.h"
#include "scripttabs.h"
#include "scriptai.h"
#include "order.h"
#include "map.h"
#include "lib/netplay/netplay.h"
#include "cmddroid.h"
#include "projectile.h"
#include "research.h"
#include "gateway.h"
#include "multiplay.h"
#include "action.h"		//because of .action
#include "power.h"
#include "geometry.h"
#include "src/scriptfuncs.h"
#include "fpath.h"
#include "multigifts.h"
#include "template.h"

static INTERP_VAL	scrFunctionResult;	//function return value to be pushed to stack

// Add a droid to a group
bool scrGroupAddDroid()
{
	DROID_GROUP		*psGroup;
	DROID			*psDroid;

	if (!stackPopParams(2, ST_GROUP, &psGroup, ST_DROID, &psDroid))
	{
		return false;
	}

	ASSERT(psGroup != nullptr,
	       "scrGroupAdd: Invalid group pointer");
	ASSERT(psDroid != nullptr,
	       "scrGroupAdd: Invalid droid pointer");
	if (psDroid == nullptr)
	{
		return false;
	}
	if (psDroid->droidType == DROID_COMMAND)
	{
		debug(LOG_ERROR,
		      "scrGroupAdd: cannot add a command droid to a group");
		return false;
	}
	if (isTransporter(psDroid))
	{
		debug(LOG_ERROR,
		      "scrGroupAdd: cannot add a transporter to a group");
		return false;
	}

	psGroup->add(psDroid);

	return true;
}


// Add droids in an area to a group
bool scrGroupAddArea()
{
	DROID_GROUP		*psGroup;
	DROID			*psDroid;
	SDWORD			x1, y1, x2, y2, player;

	if (!stackPopParams(6, ST_GROUP, &psGroup, VAL_INT, &player,
	                    VAL_INT, &x1, VAL_INT, &y1, VAL_INT, &x2, VAL_INT, &y2))
	{
		return false;
	}

	ASSERT(psGroup != nullptr,
	       "scrGroupAdd: Invalid group pointer");

	if (player < 0 || player >= MAX_PLAYERS)
	{
		ASSERT(false, "scrGroupAddArea: invalid player");
		return false;
	}

	for (psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
	{
		if (((SDWORD)psDroid->pos.x >= x1) && ((SDWORD)psDroid->pos.x <= x2) &&
		    ((SDWORD)psDroid->pos.y >= y1) && ((SDWORD)psDroid->pos.y <= y2) &&
		    psDroid->droidType != DROID_COMMAND &&
		    !isTransporter(psDroid))

		{
			psGroup->add(psDroid);
		}
	}

	return true;
}


// Add groupless droids in an area to a group
bool scrGroupAddAreaNoGroup()
{
	DROID_GROUP		*psGroup;
	DROID			*psDroid;
	SDWORD			x1, y1, x2, y2, player;

	if (!stackPopParams(6, ST_GROUP, &psGroup, VAL_INT, &player,
	                    VAL_INT, &x1, VAL_INT, &y1, VAL_INT, &x2, VAL_INT, &y2))
	{
		return false;
	}

	ASSERT(psGroup != nullptr,
	       "scrGroupAddNoGroup: Invalid group pointer");

	if (player < 0 || player >= MAX_PLAYERS)
	{
		ASSERT(false, "scrGroupAddAreaNoGroup: invalid player");
		return false;
	}

	for (psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
	{
		if (((SDWORD)psDroid->pos.x >= x1) && ((SDWORD)psDroid->pos.x <= x2) &&
		    ((SDWORD)psDroid->pos.y >= y1) && ((SDWORD)psDroid->pos.y <= y2) &&
		    psDroid->droidType != DROID_COMMAND &&
		    !isTransporter(psDroid) &&
		    psDroid->psGroup   == nullptr)
		{
			psGroup->add(psDroid);
		}
	}

	return true;
}


// Move the droids from one group to another
bool scrGroupAddGroup()
{
	DROID_GROUP		*psTo, *psFrom;
	DROID			*psDroid, *psNext;

	if (!stackPopParams(2, ST_GROUP, &psTo, ST_GROUP, &psFrom))
	{
		return false;
	}

	ASSERT_OR_RETURN(false, psTo != nullptr, "Invalid group pointer");
	ASSERT_OR_RETURN(false, psFrom != nullptr, "Invalid group pointer");

	for (psDroid = psFrom->psList; psDroid; psDroid = psNext)
	{
		psNext = psDroid->psGrpNext;
		psTo->add(psDroid);
	}

	return true;
}


// check if a droid is a member of a group
bool scrGroupMember()
{
	DROID_GROUP		*psGroup;
	DROID			*psDroid;
	bool			retval;

	if (!stackPopParams(2, ST_GROUP, &psGroup, ST_DROID, &psDroid))
	{
		return false;
	}

	ASSERT(psGroup != nullptr,
	       "scrGroupMember: Invalid group pointer");
	ASSERT(psDroid != nullptr,
	       "scrGroupMember: Invalid droid pointer");
	if (psDroid == nullptr)
	{
		return false;
	}

	if (psDroid->psGroup == psGroup)
	{
		retval = true;
	}
	else
	{
		retval = false;
	}

	scrFunctionResult.v.bval = retval;
	if (!stackPushResult(VAL_BOOL, &scrFunctionResult))
	{
		return false;
	}

	return true;
}


// returns number of idle droids in a group.
bool scrIdleGroup()
{
	DROID_GROUP *psGroup;
	DROID		*psDroid;
	UDWORD		count = 0;

	if (!stackPopParams(1, ST_GROUP, &psGroup))
	{
		return false;
	}
	ASSERT_OR_RETURN(false, psGroup != nullptr, "Invalid group pointer");

	for (psDroid = psGroup->psList; psDroid; psDroid = psDroid->psGrpNext)
	{
		if (psDroid->order.type == DORDER_NONE || (psDroid->order.type == DORDER_GUARD && psDroid->order.psObj == nullptr))
		{
			count++;
		}
	}

	scrFunctionResult.v.ival = (SDWORD)count;
	if (!stackPushResult(VAL_INT, &scrFunctionResult))
	{
		return false;
	}
	return true;
}

// variables for the group iterator
static DROID_GROUP		*psScrIterateGroup;
static DROID			*psScrIterateGroupDroid;

// initialise iterating a groups members
bool scrInitIterateGroup()
{
	DROID_GROUP	*psGroup;

	if (!stackPopParams(1, ST_GROUP, &psGroup))
	{
		return false;
	}

	ASSERT_OR_RETURN(false, psGroup != nullptr, "Invalid group pointer");

	psScrIterateGroup = psGroup;
	psScrIterateGroupDroid = psGroup->psList;

	return true;
}


// iterate through a groups members
bool scrIterateGroup()
{
	DROID_GROUP	*psGroup;
	DROID		*psDroid;

	if (!stackPopParams(1, ST_GROUP, &psGroup))
	{
		return false;
	}

	if (psGroup != psScrIterateGroup)
	{
		debug(LOG_ERROR, "scrIterateGroup: invalid group, InitGroupIterate not called?");
		return false;
	}

	if (psScrIterateGroupDroid != nullptr)
	{
		psDroid = psScrIterateGroupDroid;
		psScrIterateGroupDroid = psScrIterateGroupDroid->psGrpNext;
	}
	else
	{
		psDroid = nullptr;
	}

	scrFunctionResult.v.oval = psDroid;
	if (!stackPushResult((INTERP_TYPE)ST_DROID, &scrFunctionResult))
	{
		return false;
	}

	return true;
}


// remove a droid from a group
bool scrDroidLeaveGroup()
{
	DROID			*psDroid;

	if (!stackPopParams(1, ST_DROID, &psDroid))
	{
		return false;
	}

	if (psDroid->psGroup != nullptr)
	{
		psDroid->psGroup->remove(psDroid);
	}

	return true;
}


// Give a group an order
bool scrOrderGroup()
{
	DROID_GROUP		*psGroup;
	DROID_ORDER		order;

	if (!stackPopParams(2, ST_GROUP, &psGroup, VAL_INT, &order))
	{
		return false;
	}

	ASSERT(psGroup != nullptr,
	       "scrOrderGroup: Invalid group pointer");

	if (order != DORDER_STOP &&
	    order != DORDER_RETREAT &&
	    order != DORDER_RTR &&
	    order != DORDER_RTB &&
	    order != DORDER_RUN)
	{
		ASSERT(false,
		       "scrOrderGroup: Invalid order");
		return false;
	}

	debug(LOG_NEVER, "group %p (%u) order %d", static_cast<void *>(psGroup), psGroup->getNumMembers(), order);
	psGroup->orderGroup(order);

	return true;
}


// Give a group an order to a location
bool scrOrderGroupLoc()
{
	DROID_GROUP		*psGroup;
	DROID_ORDER		order;
	SDWORD			x, y;

	if (!stackPopParams(4, ST_GROUP, &psGroup, VAL_INT, &order, VAL_INT, &x, VAL_INT, &y))
	{
		return false;
	}

	ASSERT(psGroup != nullptr,
	       "scrOrderGroupLoc: Invalid group pointer");

	if (order != DORDER_MOVE &&
	    order != DORDER_SCOUT)
	{
		ASSERT(false,
		       "scrOrderGroupLoc: Invalid order");
		return false;
	}
	if (x < 0
	    || x > world_coord(mapWidth)
	    || y < 0
	    || y > world_coord(mapHeight))
	{
		ASSERT(false, "Invalid map location (%d, %d), max is (%d, %d)", x, y, world_coord(mapWidth), world_coord(mapHeight));
		return false;
	}

	debug(LOG_NEVER, "group %p (%u) order %d (%d,%d)",
	      static_cast<void *>(psGroup), psGroup->getNumMembers(), order, x, y);
	psGroup->orderGroup(order, (UDWORD)x, (UDWORD)y);

	return true;
}


// Give a group an order to an object
bool scrOrderGroupObj()
{
	DROID_GROUP		*psGroup;
	DROID_ORDER		order;
	BASE_OBJECT		*psObj;

	if (!stackPopParams(3, ST_GROUP, &psGroup, VAL_INT, &order, ST_BASEOBJECT, &psObj))
	{
		return false;
	}

	ASSERT_OR_RETURN(false, psGroup != nullptr, "Invalid group pointer");
	ASSERT_OR_RETURN(false, psObj != nullptr, "Invalid object pointer");

	if (order != DORDER_ATTACK &&
	    order != DORDER_HELPBUILD &&
	    order != DORDER_DEMOLISH &&
	    order != DORDER_REPAIR &&
	    order != DORDER_OBSERVE &&
	    order != DORDER_EMBARK &&
	    order != DORDER_FIRESUPPORT &&
	    order != DORDER_DROIDREPAIR)
	{
		ASSERT(false, "Invalid order");
		return false;
	}

	debug(LOG_NEVER, "group %p (%u) order %d,  obj type %d player %d id %d",
	      static_cast<void *>(psGroup), psGroup->getNumMembers(), order, psObj->type, psObj->player, psObj->id);

	psGroup->orderGroup(order, psObj);

	return true;
}

// Give a droid an order
bool scrOrderDroid()
{
	DROID			*psDroid;
	DROID_ORDER		order;

	if (!stackPopParams(2, ST_DROID, &psDroid, VAL_INT, &order))
	{
		return false;
	}

	ASSERT(psDroid != nullptr,
	       "scrOrderUnit: Invalid unit pointer");
	if (psDroid == nullptr)
	{
		return false;
	}

	if (order != DORDER_STOP &&
	    order != DORDER_RETREAT &&
	    order != DORDER_RTR &&
	    order != DORDER_RTB &&
	    order != DORDER_RUN &&
	    order != DORDER_NONE)
	{
		ASSERT(false,
		       "scrOrderUnit: Invalid order %d", order);
		return false;
	}

	orderDroid(psDroid, order, ModeQueue);

	return true;
}


// Give a Droid an order to a location
bool scrOrderDroidLoc()
{
	DROID			*psDroid;
	DROID_ORDER		order;
	SDWORD			x, y;

	if (!stackPopParams(4, ST_DROID, &psDroid, VAL_INT, &order, VAL_INT, &x, VAL_INT, &y))
	{
		return false;
	}

	ASSERT(psDroid != nullptr,
	       "scrOrderUnitLoc: Invalid unit pointer");
	if (psDroid == nullptr)
	{
		return false;
	}

	if (order != DORDER_MOVE &&
	    order != DORDER_SCOUT)
	{
		ASSERT(false,
		       "scrOrderUnitLoc: Invalid order");
		return false;
	}
	if (x < 0
	    || x > world_coord(mapWidth)
	    || y < 0
	    || y > world_coord(mapHeight))
	{
		ASSERT(false,
		       "scrOrderUnitLoc: Invalid location");
		return false;
	}

	orderDroidLoc(psDroid, order, x, y, ModeQueue);

	return true;
}


// Give a Droid an order to an object
bool scrOrderDroidObj()
{
	DROID			*psDroid;
	DROID_ORDER		order;
	BASE_OBJECT		*psObj;

	if (!stackPopParams(3, ST_DROID, &psDroid, VAL_INT, &order, ST_BASEOBJECT, &psObj))
	{
		return false;
	}

	ASSERT(psDroid != nullptr,
	       "scrOrderUnitObj: Invalid unit pointer");
	ASSERT(psObj != nullptr,
	       "scrOrderUnitObj: Invalid object pointer");
	if (psDroid == nullptr || psObj == nullptr)
	{
		return false;
	}

	if (order != DORDER_ATTACK &&
	    order != DORDER_HELPBUILD &&
	    order != DORDER_DEMOLISH &&
	    order != DORDER_REPAIR &&
	    order != DORDER_OBSERVE &&
	    order != DORDER_EMBARK &&
	    order != DORDER_FIRESUPPORT &&
	    order != DORDER_DROIDREPAIR)
	{
		ASSERT(false,
		       "scrOrderUnitObj: Invalid order");
		return false;
	}

	orderDroidObj(psDroid, order, psObj, ModeQueue);

	return true;
}

// Give a Droid an order with a stat
bool scrOrderDroidStatsLoc()
{
	DROID			*psDroid;
	DROID_ORDER		order;
	SDWORD			x, y, statIndex;

	if (!stackPopParams(5, ST_DROID, &psDroid, VAL_INT, &order, ST_STRUCTURESTAT, &statIndex,
	                    VAL_INT, &x, VAL_INT, &y))
	{
		return false;
	}

	if (statIndex < 0 || statIndex >= (SDWORD)numStructureStats)
	{
		ASSERT(false, "Invalid structure stat");
		return false;
	}

	ASSERT_OR_RETURN(false, statIndex < numStructureStats, "Invalid range referenced for numStructureStats, %d > %d", statIndex, numStructureStats);
	STRUCTURE_STATS *psStats = asStructureStats + statIndex;

	ASSERT_OR_RETURN(false, psDroid != nullptr, "Invalid Unit pointer");
	ASSERT_OR_RETURN(false, psStats != nullptr, "Invalid object pointer");

	if ((x < 0) || (x > (SDWORD)mapWidth * TILE_UNITS) ||
	    (y < 0) || (y > (SDWORD)mapHeight * TILE_UNITS))
	{
		ASSERT(false, "Invalid location");
		return false;
	}

	if (order != DORDER_BUILD)
	{
		ASSERT(false, "Invalid order");
		return false;
	}
	// HACK: FIXME: Looks like a script error in the player*.slo files
	// buildOnExactLocation() which references previously destroyed buildings from
	// _stat = rebuildStructStat[_count]  causes this.
	if (psStats->id.compare("A0ADemolishStructure") == 0)
	{
		// I don't feel like spamming a ASSERT here, we *know* it is a issue.
		return true;
	}

	orderDroidStatsLocDir(psDroid, order, psStats, x, y, 0, ModeQueue);

	return true;
}


// set the secondary state for a droid
bool scrSetDroidSecondary()
{
	DROID		*psDroid;
	SECONDARY_ORDER	sec;
	SECONDARY_STATE	state;

	if (!stackPopParams(3, ST_DROID, &psDroid, VAL_INT, &sec, VAL_INT, &state))
	{
		return false;
	}

	ASSERT(psDroid != nullptr,
	       "scrSetUnitSecondary: invalid unit pointer");
	if (psDroid == nullptr)
	{
		return false;
	}

	secondarySetState(psDroid, sec, state);

	return true;
}

// set the secondary state for a droid
bool scrSetGroupSecondary()
{
	DROID_GROUP		*psGroup;
	SECONDARY_ORDER		sec;
	SECONDARY_STATE		state;

	if (!stackPopParams(3, ST_GROUP, &psGroup, VAL_INT, &sec, VAL_INT, &state))
	{
		return false;
	}

	ASSERT(psGroup != nullptr,
	       "scrSetGroupSecondary: invalid group pointer");

	psGroup->setSecondary(sec, state);

	return true;
}


// add a droid to a commander
bool scrCmdDroidAddDroid()
{
	DROID		*psDroid, *psCommander;

	if (!stackPopParams(2, ST_DROID, &psCommander, ST_DROID, &psDroid))
	{
		return false;
	}

	cmdDroidAddDroid(psCommander, psDroid);

	return true;
}

// returns max number of droids in a commander group
bool scrCmdDroidMaxGroup()
{
	DROID		*psCommander;

	if (!stackPopParams(1, ST_DROID, &psCommander))
	{
		return false;
	}

	ASSERT(psCommander != nullptr,
	       "scrCmdDroidMaxGroup: NULL pointer passed");

	scrFunctionResult.v.ival = cmdDroidMaxGroup(psCommander);
	if (!stackPushResult(VAL_INT, &scrFunctionResult))
	{
		return false;
	}

	return true;
}

// store the prefered and ignore targets for the target functions
UDWORD	scrStructPref, scrStructIgnore;
UDWORD	scrDroidPref, scrDroidIgnore;


// reset the structure preferences
bool scrResetStructTargets()
{
	scrStructPref = 0;
	scrStructIgnore = 0;

	return true;
}


// reset the droid preferences
bool scrResetDroidTargets()
{
	scrDroidPref = 0;
	scrDroidIgnore = 0;

	return true;
}


// set prefered structure target types
bool scrSetStructTarPref()
{
	UDWORD	pref;

	if (!stackPopParams(1, VAL_INT, &pref))
	{
		return false;
	}

	ASSERT((SCR_ST_HQ					== pref) ||
	       (SCR_ST_FACTORY			== pref) ||
	       (SCR_ST_POWER_GEN			== pref) ||
	       (SCR_ST_RESOURCE_EXTRACTOR	== pref) ||
	       (SCR_ST_WALL				== pref) ||
	       (SCR_ST_RESEARCH			== pref) ||
	       (SCR_ST_REPAIR_FACILITY	== pref) ||
	       (SCR_ST_COMMAND_CONTROL	== pref) ||
	       (SCR_ST_CYBORG_FACTORY		== pref) ||
	       (SCR_ST_VTOL_FACTORY		== pref) ||
	       (SCR_ST_REARM_PAD			== pref) ||
	       (SCR_ST_SENSOR				== pref) ||
	       (SCR_ST_DEF_GROUND			== pref) ||
	       (SCR_ST_DEF_AIR			== pref) ||
	       (SCR_ST_DEF_IDF			== pref) ||
	       (SCR_ST_DEF_ALL			== pref) ,
	       "scrSetStructTarPref: unknown target preference");

	scrStructPref |= pref;

	return true;
}


// set structure target ignore types
bool scrSetStructTarIgnore()
{
	UDWORD	pref;

	if (!stackPopParams(1, VAL_INT, &pref))
	{
		return false;
	}

	ASSERT((SCR_ST_HQ					== pref) ||
	       (SCR_ST_FACTORY			== pref) ||
	       (SCR_ST_POWER_GEN			== pref) ||
	       (SCR_ST_RESOURCE_EXTRACTOR	== pref) ||
	       (SCR_ST_WALL				== pref) ||
	       (SCR_ST_RESEARCH			== pref) ||
	       (SCR_ST_REPAIR_FACILITY	== pref) ||
	       (SCR_ST_COMMAND_CONTROL	== pref) ||
	       (SCR_ST_CYBORG_FACTORY		== pref) ||
	       (SCR_ST_VTOL_FACTORY		== pref) ||
	       (SCR_ST_REARM_PAD			== pref) ||
	       (SCR_ST_SENSOR				== pref) ||
	       (SCR_ST_DEF_GROUND			== pref) ||
	       (SCR_ST_DEF_AIR			== pref) ||
	       (SCR_ST_DEF_IDF			== pref) ||
	       (SCR_ST_DEF_ALL			== pref) ,
	       "scrSetStructTarIgnore: unknown ignore target");

	scrStructIgnore |= pref;

	return true;
}


// set prefered droid target types
bool scrSetDroidTarPref()
{
	UDWORD	pref;

	if (!stackPopParams(1, VAL_INT, &pref))
	{
		return false;
	}

	ASSERT((SCR_DT_COMMAND		== pref) ||
	       (SCR_DT_SENSOR			== pref) ||
	       (SCR_DT_CONSTRUCT		== pref) ||
	       (SCR_DT_REPAIR			== pref) ||
	       (SCR_DT_WEAP_GROUND	== pref) ||
	       (SCR_DT_WEAP_AIR		== pref) ||
	       (SCR_DT_WEAP_IDF		== pref) ||
	       (SCR_DT_WEAP_ALL		== pref) ||
	       (SCR_DT_LIGHT			== pref) ||
	       (SCR_DT_MEDIUM			== pref) ||
	       (SCR_DT_HEAVY			== pref) ||
	       (SCR_DT_SUPER_HEAVY	== pref) ||
	       (SCR_DT_TRACK			== pref) ||
	       (SCR_DT_HTRACK			== pref) ||
	       (SCR_DT_WHEEL			== pref) ||
	       (SCR_DT_LEGS			== pref) ||
	       (SCR_DT_GROUND			== pref) ||
	       (SCR_DT_VTOL			== pref) ||
	       (SCR_DT_HOVER			== pref) ,
	       "scrSetUnitTarPref: unknown target preference");


	scrDroidPref |= pref;

	return true;
}

// set droid target ignore types
bool scrSetDroidTarIgnore()
{
	UDWORD	pref;

	if (!stackPopParams(1, VAL_INT, &pref))
	{
		return false;
	}

	ASSERT((SCR_DT_COMMAND		== pref) ||
	       (SCR_DT_SENSOR			== pref) ||
	       (SCR_DT_CONSTRUCT		== pref) ||
	       (SCR_DT_REPAIR			== pref) ||
	       (SCR_DT_WEAP_GROUND	== pref) ||
	       (SCR_DT_WEAP_AIR		== pref) ||
	       (SCR_DT_WEAP_IDF		== pref) ||
	       (SCR_DT_WEAP_ALL		== pref) ||
	       (SCR_DT_LIGHT			== pref) ||
	       (SCR_DT_MEDIUM			== pref) ||
	       (SCR_DT_HEAVY			== pref) ||
	       (SCR_DT_SUPER_HEAVY	== pref) ||
	       (SCR_DT_TRACK			== pref) ||
	       (SCR_DT_HTRACK			== pref) ||
	       (SCR_DT_WHEEL			== pref) ||
	       (SCR_DT_LEGS			== pref) ||
	       (SCR_DT_GROUND			== pref) ||
	       (SCR_DT_VTOL			== pref) ||
	       (SCR_DT_HOVER			== pref) ,
	       "scrSetUnitTarIgnore: unknown ignore target");

	scrDroidIgnore |= pref;

	return true;
}


// get the correct type mask for a structure target
static UDWORD scrStructTargetMask(STRUCTURE *psStruct)
{
	UDWORD				mask = 0;
	STRUCTURE_STATS		*psStats;
	WEAPON_STATS		*psWStats;

	psStats = psStruct->pStructureType;
	switch (psStats->type)
	{
	case REF_HQ:
		mask = SCR_ST_HQ;
		break;
	case REF_FACTORY:
	case REF_FACTORY_MODULE:
		mask = SCR_ST_FACTORY;
		break;
	case REF_POWER_GEN:
	case REF_POWER_MODULE:
		mask = SCR_ST_POWER_GEN;
		break;
	case REF_RESOURCE_EXTRACTOR:
		mask = SCR_ST_RESOURCE_EXTRACTOR;
		break;
	case REF_DEFENSE:
		//if (psStats->numWeaps == 0 && psStats->pSensor != NULL)
		if (psStats->psWeapStat[0] == nullptr && psStats->pSensor != nullptr)
		{
			mask = SCR_ST_SENSOR;
		}
		//else if (psStats->numWeaps > 0)
		else if (psStats->psWeapStat[0] != nullptr)
		{
			psWStats = psStats->psWeapStat[0];
			if (!proj_Direct(psWStats))
			{
				mask = SCR_ST_DEF_IDF;
			}
			else if (psWStats->surfaceToAir & SHOOT_IN_AIR)
			{
				mask = SCR_ST_DEF_AIR;
			}
			else
			{
				mask = SCR_ST_DEF_GROUND;
			}
		}
		break;
	case REF_WALL:
	case REF_WALLCORNER:
		mask = 0;	// ignore walls for now
		break;
	case REF_RESEARCH:
	case REF_RESEARCH_MODULE:
		mask = SCR_ST_RESEARCH;
		break;
	case REF_REPAIR_FACILITY:
		mask = SCR_ST_REPAIR_FACILITY;
		break;
	case REF_COMMAND_CONTROL:
		mask = SCR_ST_COMMAND_CONTROL;
		break;
	case REF_CYBORG_FACTORY:
		mask = SCR_ST_CYBORG_FACTORY;
		break;
	case REF_VTOL_FACTORY:
		mask = SCR_ST_VTOL_FACTORY;
		break;
	case REF_REARM_PAD:
		mask = SCR_ST_REARM_PAD;
		break;
	case REF_MISSILE_SILO:
		//don't want the assert!
		mask = 0;
		break;
	case REF_LAB:
	case REF_BRIDGE:
	case REF_DEMOLISH:
	case REF_GENERIC:
	case REF_GATE:
	default:
		ASSERT(false,
		       "scrStructTargetMask: unknown or invalid target structure type");
		mask = 0;
		break;
	}

	return mask;
}


// prioritise structure targets
static void scrStructTargetPriority(STRUCTURE **ppsTarget, STRUCTURE *psCurr)
{
	// skip walls unless they are explicitly asked for
	if ((scrStructPref & SCR_ST_WALL) ||
	    ((psCurr->pStructureType->type != REF_WALL) &&
	     (psCurr->pStructureType->type != REF_WALLCORNER)))
	{
		*ppsTarget = psCurr;
	}
}


static UDWORD scrDroidTargetMask(DROID *psDroid)
{
	UDWORD				mask = 0;
	WEAPON_STATS		*psWStats;
	BODY_STATS			*psBStats;
	PROPULSION_STATS	*psPStats;

	// get the turret type
	switch (psDroid->droidType)
	{
	case DROID_PERSON:
	case DROID_CYBORG:
	case DROID_CYBORG_SUPER:
	case DROID_WEAPON:
		//if (psDroid->numWeaps > 0)
		if (psDroid->asWeaps[0].nStat > 0)
		{
			psWStats = asWeaponStats + psDroid->asWeaps[0].nStat;
			if (!proj_Direct(psWStats))
			{
				mask |= SCR_DT_WEAP_IDF;
			}
			else if (psWStats->surfaceToAir & SHOOT_IN_AIR)
			{
				mask |= SCR_DT_WEAP_AIR;
			}
			else
			{
				mask |= SCR_DT_WEAP_GROUND;
			}
		}
		else
		{
			mask |= SCR_DT_SENSOR;
		}
		break;
	case DROID_SENSOR:
	case DROID_ECM:
		mask |= SCR_DT_SENSOR;
		break;
	case DROID_CONSTRUCT:
	case DROID_CYBORG_CONSTRUCT:
		mask |= SCR_DT_CONSTRUCT;
		break;
	case DROID_COMMAND:
		mask |= SCR_DT_COMMAND;
		break;
	case DROID_REPAIR:
	case DROID_CYBORG_REPAIR:
		mask |= SCR_DT_REPAIR;
		break;
	case DROID_TRANSPORTER:
	case DROID_SUPERTRANSPORTER:
		break;
	case DROID_DEFAULT:
	case DROID_ANY:
	default:
		ASSERT(false,
		       "scrUnitTargetMask: unknown or invalid target unit type");
		break;
	}

	// get the body type
	psBStats = asBodyStats + psDroid->asBits[COMP_BODY];
	switch (psBStats->size)
	{
	case SIZE_LIGHT:
		mask |= SCR_DT_LIGHT;
		break;
	case SIZE_MEDIUM:
		mask |= SCR_DT_MEDIUM;
		break;
	case SIZE_HEAVY:
		mask |= SCR_DT_HEAVY;
		break;
	case SIZE_SUPER_HEAVY:
		mask |= SCR_DT_SUPER_HEAVY;
		break;
	default:
		ASSERT(false,
		       "scrUnitTargetMask: unknown or invalid target body size");
		break;
	}

	// get the propulsion type
	psPStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
	switch (psPStats->propulsionType)
	{
	case PROPULSION_TYPE_WHEELED:
		mask |= SCR_DT_WHEEL;
		break;
	case PROPULSION_TYPE_TRACKED:
		mask |= SCR_DT_TRACK;
		break;
	case PROPULSION_TYPE_LEGGED:
		mask |= SCR_DT_LEGS;
		break;
	case PROPULSION_TYPE_HOVER:
		mask |= SCR_DT_HOVER;
		break;
	case PROPULSION_TYPE_LIFT:
		mask |= SCR_DT_VTOL;
		break;
	case PROPULSION_TYPE_HALF_TRACKED:
		mask |= SCR_DT_HTRACK;
		break;
	case PROPULSION_TYPE_PROPELLOR:
		mask |= SCR_DT_PROPELLOR;
		break;
	default:
		ASSERT(false,
		       "scrUnitTargetMask: unknown or invalid target unit propulsion type");
		break;
	}


	return mask;
}

// prioritise droid targets
static void scrDroidTargetPriority(DROID **ppsTarget, DROID *psCurr)
{
	// priority to things with weapons
	if (((*ppsTarget) == nullptr) ||
	    ((*ppsTarget)->asWeaps[0].nStat == 0))
	{
		*ppsTarget = psCurr;
	}
}


// what type of object for scrTargetInArea
enum
{
	SCR_TAR_STRUCT,
	SCR_TAR_DROID,
};

// get target mask function for scrTargetInArea
typedef UDWORD(*TARGET_MASK)(BASE_OBJECT *);

// see if one target is preferable to another
typedef void (*TARGET_PREF)(BASE_OBJECT **, BASE_OBJECT *);

// generic find a target in an area given preference data
static BASE_OBJECT *scrTargetInArea(SDWORD tarPlayer, SDWORD visPlayer, SDWORD tarType, SDWORD cluster,
                                    SDWORD x1, SDWORD y1, SDWORD x2, SDWORD y2)
{
	BASE_OBJECT		*psTarget, *psCurr;
	SDWORD			temp;
	UDWORD			tarMask;
	TARGET_MASK		getTargetMask;
	TARGET_PREF		targetPriority;
	UDWORD			prefer, ignore;

	if (tarPlayer < 0 || tarPlayer >= MAX_PLAYERS)
	{
		ASSERT(false,
		       "scrTargetInArea: invalid target player number");
		return nullptr;
	}

	if (x1 > x2)
	{
		temp = x2;
		x2 = x1;
		x1 = temp;
	}
	if (y1 > y2)
	{
		temp = y2;
		y2 = y1;
		y1 = temp;
	}

	// see which target type to use
	switch (tarType)
	{
	case SCR_TAR_STRUCT:
		getTargetMask = (TARGET_MASK)scrStructTargetMask;
		targetPriority = (TARGET_PREF)scrStructTargetPriority;
		prefer = scrStructPref;
		ignore = scrStructIgnore;
		psCurr = apsStructLists[tarPlayer];
		break;
	case SCR_TAR_DROID:
		getTargetMask = (TARGET_MASK)scrDroidTargetMask;
		targetPriority = (TARGET_PREF)scrDroidTargetPriority;
		prefer = scrDroidPref;
		ignore = scrDroidIgnore;
		psCurr = apsDroidLists[tarPlayer];
		break;
	default:
		ASSERT(false, "scrTargetInArea: invalid target type");
		return nullptr;
	}

	psTarget = nullptr;
	for (; psCurr; psCurr = psCurr->psNext)
	{
		if (((SDWORD)psCurr->pos.x >= x1) &&
		    ((SDWORD)psCurr->pos.x <= x2) &&
		    ((SDWORD)psCurr->pos.y >= y1) &&
		    ((SDWORD)psCurr->pos.y <= y2))
		{
			tarMask = getTargetMask(psCurr);
			if (tarMask & ignore)
			{
				// skip any that match ignore
				continue;
			}
			else if (tarMask & prefer)
			{
				// got a prefered target - go with that
				psTarget = psCurr;
				break;
			}
			else
			{
				// see if the current target has priority over the previous one
				targetPriority(&psTarget, psCurr);
			}
		}
	}


	return psTarget;
}

// get a structure target in an area using the preferences
bool scrStructTargetInArea()
{
	SDWORD		x1, y1, x2, y2;
	SDWORD		tarPlayer, visPlayer;

	if (!stackPopParams(6, VAL_INT, &tarPlayer, VAL_INT, &visPlayer,
	                    VAL_INT, &x1, VAL_INT, &y1, VAL_INT, &x2, VAL_INT, &y2))
	{
		return false;
	}

	scrFunctionResult.v.oval = (STRUCTURE *)scrTargetInArea(tarPlayer, visPlayer, SCR_TAR_STRUCT, 0, x1, y1, x2, y2);

	if (!stackPushResult((INTERP_TYPE)ST_STRUCTURE, &scrFunctionResult))
	{
		return false;
	}

	return true;
}

// get a structure target on the map using the preferences
bool scrStructTargetOnMap()
{
	SDWORD		tarPlayer, visPlayer;
	STRUCTURE	*psTarget;

	if (!stackPopParams(2, VAL_INT, &tarPlayer, VAL_INT, &visPlayer))
	{
		return false;
	}

	psTarget = (STRUCTURE *)scrTargetInArea(tarPlayer, visPlayer, SCR_TAR_STRUCT, 0,
	                                        scrollMinX * TILE_UNITS, scrollMinY * TILE_UNITS,
	                                        scrollMaxX * TILE_UNITS, scrollMaxY * TILE_UNITS);

	scrFunctionResult.v.oval = psTarget;
	if (!stackPushResult((INTERP_TYPE)ST_STRUCTURE, &scrFunctionResult))
	{
		return false;
	}

	return true;
}

// get a droid target in an area using the preferences
bool scrDroidTargetInArea()
{
	SDWORD		x1, y1, x2, y2;
	SDWORD		tarPlayer, visPlayer;
	DROID		*psTarget;

	if (!stackPopParams(6, VAL_INT, &tarPlayer, VAL_INT, &visPlayer,
	                    VAL_INT, &x1, VAL_INT, &y1, VAL_INT, &x2, VAL_INT, &y2))
	{
		return false;
	}

	psTarget = (DROID *)scrTargetInArea(tarPlayer, visPlayer, SCR_TAR_DROID, 0, x1, y1, x2, y2);

	scrFunctionResult.v.oval = psTarget;
	if (!stackPushResult((INTERP_TYPE)ST_DROID, &scrFunctionResult))
	{
		return false;
	}

	return true;
}

// get a droid target on the map using the preferences
bool scrDroidTargetOnMap()
{
	SDWORD		tarPlayer, visPlayer;
	DROID		*psTarget;

	if (!stackPopParams(2, VAL_INT, &tarPlayer, VAL_INT, &visPlayer))
	{
		return false;
	}

	psTarget = (DROID *)scrTargetInArea(tarPlayer, visPlayer, SCR_TAR_DROID, 0,
	                                    scrollMinX * TILE_UNITS, scrollMinY * TILE_UNITS,
	                                    scrollMaxX * TILE_UNITS, scrollMaxY * TILE_UNITS);

	scrFunctionResult.v.oval = psTarget;
	if (!stackPushResult((INTERP_TYPE)ST_DROID, &scrFunctionResult))
	{
		return false;
	}

	return true;
}

// get a target from a cluster using the preferences
bool scrTargetInCluster()
{
	int visPlayer, clusterID;

	if (!stackPopParams(2, VAL_INT, &clusterID, VAL_INT, &visPlayer))
	{
		return false;
	}

	ASSERT(false, "unsupported function");

	scrFunctionResult.v.oval = nullptr;
	if (!stackPushResult((INTERP_TYPE)ST_BASEOBJECT, &scrFunctionResult))
	{
		return false;
	}

	return true;
}

// ********************************************************************************************
// ********************************************************************************************
// Additional Skirmish Code for superduper skirmish games may99
// ********************************************************************************************
// ********************************************************************************************

bool scrSkCanBuildTemplate()
{
	STRUCTURE *psStructure;
	DROID_TEMPLATE *psTempl;

	SDWORD player;

	if (!stackPopParams(3, VAL_INT, &player, ST_STRUCTURE, &psStructure, ST_TEMPLATE, &psTempl))
	{
		return false;
	}

	// is factory big enough?
	if (!validTemplateForFactory(psTempl, psStructure, false))
	{
		goto failTempl;
	}

	if ((asBodyStats + psTempl->asParts[COMP_BODY])->size > psStructure->capacity)
	{
		goto failTempl;
	}

	// is every component from template available?
	// body available.
	if (apCompLists[player][COMP_BODY][psTempl->asParts[COMP_BODY]] != AVAILABLE)
	{
		goto failTempl;
	}

	// propulsion method available.
	if (apCompLists[player][COMP_PROPULSION][psTempl->asParts[COMP_PROPULSION]] != AVAILABLE)
	{
		goto failTempl;
	}

	if (!researchedTemplate(psTempl, player, true, false))
	{
		goto failTempl;
	}

	scrFunctionResult.v.bval = true;
	if (!stackPushResult(VAL_BOOL, &scrFunctionResult))		// yes
	{
		return false;
	}
	return true;

failTempl:
	scrFunctionResult.v.bval = false;
	if (!stackPushResult(VAL_BOOL, &scrFunctionResult))		// no
	{
		return false;
	}
	return true;
}

// ********************************************************************************************
// locate the enemy
// gives a target location given a player to attack.
bool scrSkLocateEnemy()
{
	SDWORD		player;//,*x,*y;
	STRUCTURE	*psStruct;

	if (!stackPopParams(1, VAL_INT, &player))
	{
		return false;
	}

	// find where the player has some structures..	// factories or hq.
	for (psStruct = apsStructLists[player];
	     psStruct &&
	     !(psStruct->pStructureType->type == REF_HQ
	       || psStruct->pStructureType->type == REF_FACTORY
	       || psStruct->pStructureType->type == REF_CYBORG_FACTORY
	       || psStruct->pStructureType->type == REF_VTOL_FACTORY
	      );
	     psStruct = psStruct->psNext) {}

	// set the x and y accordingly..
	if (psStruct)
	{
		scrFunctionResult.v.oval = psStruct;
		if (!stackPushResult((INTERP_TYPE)ST_BASEOBJECT, &scrFunctionResult))		// success!
		{
			return false;
		}
	}
	else
	{
		scrFunctionResult.v.oval = nullptr;
		if (!stackPushResult((INTERP_TYPE)ST_BASEOBJECT, &scrFunctionResult))		// part success
		{
			return false;
		}
	}
	return true;
}

// ********************************************************************************************

bool skTopicAvail(UWORD inc, UDWORD player)
{
	UDWORD				incPR, incS;
	bool				bPRFound, bStructFound;


	//if the topic is possible and has not already been researched - add to list
	if ((IsResearchPossible(&asPlayerResList[player][inc])))
	{
		if (!IsResearchCompleted(&asPlayerResList[player][inc])
		    && !IsResearchStartedPending(&asPlayerResList[player][inc]))
		{
			return true;
		}
	}

	// make sure that the research is not completed  or started by another researchfac
	if (!IsResearchCompleted(&asPlayerResList[player][inc])
	    && !IsResearchStartedPending(&asPlayerResList[player][inc]))
	{
		// Research is not completed  ... also  it has not been started by another researchfac

		//if there aren't any PR's - go to next topic
		if (asResearch[inc].pPRList.empty())
		{
			return false;
		}

		//check for pre-requisites
		bPRFound = true;
		for (incPR = 0; incPR < asResearch[inc].pPRList.size(); incPR++)
		{
			if (IsResearchCompleted(&asPlayerResList[player][asResearch[inc].pPRList[incPR]]) == 0)
			{
				//if haven't pre-requisite - quit checking rest
				bPRFound = false;
				break;
			}
		}
		if (!bPRFound)
		{
			//if haven't pre-requisites, skip the rest of the checks
			return false;
		}

		//check for structure effects
		bStructFound = true;
		for (incS = 0; incS < asResearch[inc].pStructList.size(); incS++)
		{
			//if (!checkStructureStatus(asStructureStats + asResearch[inc].
			//	pStructList[incS], playerID, SS_BUILT))
			if (!checkSpecificStructExists(asResearch[inc].pStructList[incS],
			                               player))
			{
				//if not built, quit checking
				bStructFound = false;
				break;
			}
		}
		if (!bStructFound)
		{
			//if haven't all structs built, skip to next topic
			return false;
		}

		return true;
	}
	return false;
}

// ********************************************************************************************
bool scrSkDoResearch()
{
	SDWORD				player, bias;
	UWORD				i;
	STRUCTURE			*psBuilding;
	RESEARCH_FACILITY	*psResFacilty;

	if (!stackPopParams(3, ST_STRUCTURE, &psBuilding, VAL_INT, &player, VAL_INT, &bias))
	{
		return false;
	}

	psResFacilty =	(RESEARCH_FACILITY *)psBuilding->pFunctionality;

	if (psResFacilty->psSubject != nullptr)
	{
		// not finished yet..
		return true;
	}

	// choose a topic to complete.
	for (i = 0; i < asResearch.size(); i++)
	{
		if (skTopicAvail(i, player) && (!bMultiPlayer || !beingResearchedByAlly(i, player)))
		{
			break;
		}
	}

	if (i != asResearch.size())
	{
		sendResearchStatus(psBuilding, i, player, true);			// inform others, I'm researching this.
#if defined (DEBUG)
		{
			char	sTemp[128];
			sprintf(sTemp, "[debug]player:%d starts topic: %s", player, getName(&asResearch[i]));
			NETlogEntry(sTemp, SYNC_FLAG, 0);
		}
#endif
	}

	return true;
}

// ********************************************************************************************
bool scrSkVtolEnableCheck()
{
	SDWORD player;
	UDWORD i;

	if (!stackPopParams(1, VAL_INT, &player))
	{
		return false;
	}


	// vtol factory
	for (i = 0; (i < numStructureStats) && (asStructureStats[i].type != REF_VTOL_FACTORY); i++) {}
	if ((i < numStructureStats) && (apStructTypeLists[player][i] == AVAILABLE))
	{
		// vtol propulsion
		for (i = 0; (i < numPropulsionStats); i++)
		{
			if ((asPropulsionStats[i].propulsionType == PROPULSION_TYPE_LIFT)
			    && apCompLists[player][COMP_PROPULSION][i] == AVAILABLE)
			{
				scrFunctionResult.v.bval = true;
				if (!stackPushResult(VAL_BOOL, &scrFunctionResult))		// success!
				{
					return false;
				}
				return true;
			}
		}
	}

	scrFunctionResult.v.bval = false;
	if (!stackPushResult(VAL_BOOL, &scrFunctionResult))		// success!
	{
		return false;
	}
	return true;
}

// ********************************************************************************************
bool scrSkGetFactoryCapacity()
{
	SDWORD count = 0;
	STRUCTURE *psStructure;

	if (!stackPopParams(1, ST_STRUCTURE, &psStructure))
	{
		return false;
	}

	if (psStructure && StructIsFactory(psStructure))
	{
		count = psStructure->capacity;
	}

	scrFunctionResult.v.ival = count;
	if (!stackPushResult(VAL_INT, &scrFunctionResult))
	{
		return false;
	}
	return true;
}
// ********************************************************************************************
bool scrSkDifficultyModifier()
{
	int 			player;
	RESEARCH_FACILITY	*psResFacility;
	STRUCTURE		*psStr;
	PLAYER_RESEARCH		*pPlayerRes;

	if (!stackPopParams(1, VAL_INT, &player))
	{
		return false;
	}

	/* Skip cheats if difficulty modifier slider is set to minimum or this is a true multiplayer game.
	 * (0 - player disabled, 20 - max value, UBYTE_MAX - autogame)
	 */
	if (game.skDiff[player] <= 1 || game.skDiff[player] == UBYTE_MAX || NetPlay.bComms)
	{
		return true;
	}

	// power modifier, range: 0-1000
	giftPower(ANYPLAYER, player, game.skDiff[player] * 50, true);

	//research modifier
	for (psStr = apsStructLists[player]; psStr; psStr = psStr->psNext)
	{
		if (psStr->pStructureType->type == REF_RESEARCH)
		{
			psResFacility =	(RESEARCH_FACILITY *)psStr->pFunctionality;

			// subtract 0 - 80% off the time to research.
			if (psResFacility->psSubject)
			{
				RESEARCH	*psResearch = (RESEARCH *)psResFacility->psSubject;

				pPlayerRes = &asPlayerResList[player][psResearch->ref - REF_RESEARCH_START];
				pPlayerRes->currentPoints += (psResearch->researchPoints * 4 * game.skDiff[player]) / 100;
				syncDebug("AI %d is cheating with research time.", player);
			}
		}

	}

	return true;
}

// ********************************************************************************************

// not a direct script function but a helper for scrSkDefenseLocation and scrSkDefenseLocationB
static bool defenseLocation(bool variantB)
{
	SDWORD		*pX, *pY, statIndex, statIndex2;
	UDWORD		x, y, gX, gY, dist, player, nearestSoFar, count;
	GATEWAY		*psChosenGate;
	DROID		*psDroid;
	UDWORD		x1, x2, x3, x4, y1, y2, y3, y4;
	bool		noWater;
	UDWORD      minCount;
	UDWORD      offset;

	if (!stackPopParams(6,
	                    VAL_REF | VAL_INT, &pX,
	                    VAL_REF | VAL_INT, &pY,
	                    ST_STRUCTURESTAT, &statIndex,
	                    ST_STRUCTURESTAT, &statIndex2,
	                    ST_DROID, &psDroid,
	                    VAL_INT, &player))
	{
		debug(LOG_ERROR, "defenseLocation: failed to pop");
		return false;
	}

	if (player >= MAX_PLAYERS)
	{
		ASSERT(false, "defenseLocation:player number is too high");
		return false;
	}

	ASSERT_OR_RETURN(false, statIndex < numStructureStats, "Invalid range referenced for numStructureStats, %d > %d", statIndex, numStructureStats);

	ASSERT_OR_RETURN(false, statIndex2 < numStructureStats, "Invalid range referenced for numStructureStats, %d > %d", statIndex2, numStructureStats);
	STRUCTURE_STATS *psWStats = (asStructureStats + statIndex2);

	// check for wacky coords.
	if (*pX < 0
	    ||	*pX > world_coord(mapWidth)
	    ||	*pY < 0
	    ||	*pY > world_coord(mapHeight)
	   )
	{
		goto failed;
	}

	x = map_coord(*pX);					// change to tile coords.
	y = map_coord(*pY);

	// go down the gateways, find the nearest gateway with >1 empty tiles
	nearestSoFar = UDWORD_MAX;
	psChosenGate = nullptr;
	for (auto psGate : gwGetGateways())
	{
		if (auxTile(psGate->x1, psGate->y1, player) & AUXBITS_THREAT)
		{
			continue;	// enemy can shoot there, not safe to build
		}
		count = 0;
		noWater = true;
		// does it have >1 tile unoccupied.
		if (psGate->x1 == psGate->x2)
		{
			// vert
			//skip gates that are too short
			if (variantB && (psGate->y2 - psGate->y1) <= 2)
			{
				continue;
			}
			gX = psGate->x1;
			for (gY = psGate->y1; gY <= psGate->y2; gY++)
			{
				if (! TileIsOccupied(mapTile(gX, gY)))
				{
					count++;
				}
				if (terrainType(mapTile(gX, gY)) == TER_WATER)
				{
					noWater = false;
				}
			}
		}
		else
		{
			// horiz
			//skip gates that are too short
			if (variantB && (psGate->x2 - psGate->x1) <= 2)
			{
				continue;
			}
			gY = psGate->y1;
			for (gX = psGate->x1; gX <= psGate->x2; gX++)
			{
				if (! TileIsOccupied(mapTile(gX, gY)))
				{
					count++;
				}
				if (terrainType(mapTile(gX, gY)) == TER_WATER)
				{
					noWater = false;
				}
			}
		}
		if (variantB)
		{
			minCount = 2;
		}
		else
		{
			minCount = 1;
		}
		if (count > minCount && noWater)	//<NEW> min 2 tiles
		{
			// ok it's free. Is it the nearest one yet?
			/* Get gateway midpoint */
			gX = (psGate->x1 + psGate->x2) / 2;
			gY = (psGate->y1 + psGate->y2) / 2;
			/* Estimate the distance to it */
			dist = iHypot(x - gX, y - gY);
			/* Is it best we've found? */
			if (dist < nearestSoFar && dist < 30)
			{
				/* Yes, then keep a record of it */
				nearestSoFar = dist;
				psChosenGate = psGate;
			}
		}
	}

	if (!psChosenGate)	// we have a gateway.
	{
		goto failed;
	}

	// find an unnocupied tile on that gateway.
	if (psChosenGate->x1 == psChosenGate->x2)
	{
		// vert
		gX = psChosenGate->x1;
		for (gY = psChosenGate->y1; gY <= psChosenGate->y2; gY++)
		{
			if (! TileIsOccupied(mapTile(gX, gY)))
			{
				y = gY;
				x = gX;
				break;
			}
		}
	}
	else
	{
		// horiz
		gY = psChosenGate->y1;
		for (gX = psChosenGate->x1; gX <= psChosenGate->x2; gX++)
		{
			if (! TileIsOccupied(mapTile(gX, gY)))
			{
				y = gY;
				x = gX;
				break;
			}
		}
	}

	// back to world coords and store result.
	*pX = world_coord(x) + (TILE_UNITS / 2);		// return centre of tile.
	*pY = world_coord(y) + (TILE_UNITS / 2);

	scrFunctionResult.v.bval = true;
	if (!stackPushResult(VAL_BOOL, &scrFunctionResult))		// success
	{
		return false;
	}


	// order the droid to build two walls, one either side of the gateway.
	// or one in the case of a 2 size gateway.

	//find center of the gateway
	x = (psChosenGate->x1 + psChosenGate->x2) / 2;
	y = (psChosenGate->y1 + psChosenGate->y2) / 2;

	//find start pos of the gateway
	x1 = world_coord(psChosenGate->x1) + (TILE_UNITS / 2);
	y1 = world_coord(psChosenGate->y1) + (TILE_UNITS / 2);

	if (variantB)
	{
		offset = 2;
	}
	else
	{
		offset = 1;
	}
	if (psChosenGate->x1 == psChosenGate->x2)	//vert
	{
		x2 = x1;	//vert: end x pos of the first section = start x pos
		y2 = world_coord(y - 1) + TILE_UNITS / 2;	//start y loc of the first sec
		x3 = x1;
		y3 = world_coord(y + offset) + TILE_UNITS / 2;
	}
	else		//hor
	{
		x2 = world_coord(x - 1) + TILE_UNITS / 2;
		y2 = y1;
		x3 = world_coord(x + offset) + TILE_UNITS / 2;
		y3 = y1;

	}
	//end coords of the second section
	x4 = world_coord(psChosenGate->x2) + TILE_UNITS / 2;
	y4 = world_coord(psChosenGate->y2) + TILE_UNITS / 2;

	// first section.
	if (x1 == x2 && y1 == y2)	//first sec is 1 tile only: ((2 tile gate) or (3 tile gate and first sec))
	{
		orderDroidStatsLocDir(psDroid, DORDER_BUILD, psWStats, x1, y1, 0, ModeQueue);
	}
	else
	{
		orderDroidStatsTwoLocDir(psDroid, DORDER_LINEBUILD, psWStats,  x1, y1, x2, y2, 0, ModeQueue);
	}

	// second section
	if (x3 == x4 && y3 == y4)
	{
		orderDroidStatsLocDirAdd(psDroid, DORDER_BUILD, psWStats, x3, y3, 0);
	}
	else
	{
		orderDroidStatsTwoLocDirAdd(psDroid, DORDER_LINEBUILD, psWStats,  x3, y3, x4, y4, 0);
	}

	return true;

failed:
	scrFunctionResult.v.bval = false;
	if (!stackPushResult(VAL_BOOL, &scrFunctionResult))		// failed!
	{
		return false;
	}
	return true;
}


// return a good place to build a defence, given a starting point
bool scrSkDefenseLocation()
{
	return defenseLocation(false);
}

// return a good place to build a defence with a min number of clear tiles
bool scrSkDefenseLocationB()
{
	return defenseLocation(true);
}


bool scrSkFireLassat()
{
	SDWORD	player;
	BASE_OBJECT *psObj;

	if (!stackPopParams(2,  VAL_INT, &player, ST_BASEOBJECT, &psObj))
	{
		return false;
	}

	if (psObj)
	{
		orderStructureObj(player, psObj);
	}

	return true;
}

//-----------------------
// New functions
//-----------------------
bool scrActionDroidObj()
{
	DROID			*psDroid;
	DROID_ACTION		action;
	BASE_OBJECT		*psObj;

	if (!stackPopParams(3, ST_DROID, &psDroid, VAL_INT, &action, ST_BASEOBJECT, &psObj))
	{
		debug(LOG_ERROR, "scrActionDroidObj: failed to pop");
		return false;
	}

	ASSERT(psDroid != nullptr,
	       "scrOrderUnitObj: Invalid unit pointer");
	ASSERT(psObj != nullptr,
	       "scrOrderUnitObj: Invalid object pointer");

	if (psDroid == nullptr || psObj == nullptr)
	{
		return false;
	}

	if (action != DACTION_DROIDREPAIR)
	{
		debug(LOG_ERROR, "scrActionDroidObj: this action is not supported");
		return false;
	}

	syncDebug("TODO: Synchronise this!");
	actionDroid(psDroid, action, psObj);

	return true;
}

//<script function - improved version
// variables for the group iterator
static DROID_GROUP *psScrIterateGroupB[MAX_PLAYERS];
static DROID *psScrIterateGroupDroidB[MAX_PLAYERS];

// initialise iterating a groups members
bool scrInitIterateGroupB()
{
	DROID_GROUP	*psGroup;
	SDWORD		bucket;

	if (!stackPopParams(2, ST_GROUP, &psGroup, VAL_INT, &bucket))
	{
		debug(LOG_ERROR, "scrInitIterateGroupB: stackPopParams failed");
		return false;
	}

	ASSERT_OR_RETURN(false, psGroup != nullptr, "Invalid group pointer");
	ASSERT_OR_RETURN(false, bucket < MAX_PLAYERS, "Invalid bucket");

	psScrIterateGroupB[bucket] = psGroup;
	psScrIterateGroupDroidB[bucket] = psGroup->psList;

	return true;
}

//script function - improved version
// iterate through a groups members
bool scrIterateGroupB()
{
	DROID_GROUP	*psGroup;
	DROID		*psDroid;
	SDWORD		bucket;

	if (!stackPopParams(2, ST_GROUP, &psGroup, VAL_INT, &bucket))
	{
		debug(LOG_ERROR, "scrIterateGroupB: stackPopParams failed");
		return false;
	}

	ASSERT_OR_RETURN(false, bucket < MAX_PLAYERS, "Invalid bucket");

	if (psGroup != psScrIterateGroupB[bucket])
	{
		ASSERT(false, "scrIterateGroupB: invalid group, InitGroupIterateB not called?");
		return false;
	}

	if (psScrIterateGroupDroidB[bucket] != nullptr)
	{
		psDroid = psScrIterateGroupDroidB[bucket];
		psScrIterateGroupDroidB[bucket] = psScrIterateGroupDroidB[bucket]->psGrpNext;
	}
	else
	{
		psDroid = nullptr;
	}

	scrFunctionResult.v.oval = psDroid;
	if (!stackPushResult((INTERP_TYPE)ST_DROID, &scrFunctionResult))
	{
		debug(LOG_ERROR, "scrIterateGroupB: stackPushResult failed");
		return false;
	}

	return true;
}

bool scrDroidCanReach()
{
	DROID			*psDroid;
	int			x, y;

	if (!stackPopParams(3, ST_DROID, &psDroid, VAL_INT, &x, VAL_INT, &y))
	{
		debug(LOG_ERROR, "Failed to pop parameters");
		return false;
	}
	if (psDroid)
	{
		const PROPULSION_STATS *psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
		const Vector3i rPos(x, y, 0);

		scrFunctionResult.v.bval = fpathCheck(psDroid->pos, rPos, psPropStats->propulsionType);
		if (!stackPushResult(VAL_BOOL, &scrFunctionResult))
		{
			debug(LOG_ERROR, "stackPushResult failed");
			return false;
		}
		return true;
	}
	return false;
}

// ********************************************************************************************
// ********************************************************************************************
