2015-10-07 01:43:52 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
2015-06-26 21:02:55 +00:00
|
|
|
// helpers.cs
|
2015-10-07 01:43:52 +00:00
|
|
|
// Helper functions used in the experimental DXAI system.
|
|
|
|
|
// https://github.com/Ragora/T2-DXAI.git
|
|
|
|
|
//
|
2015-10-10 20:08:43 +00:00
|
|
|
// Copyright (c) 2015 Robert MacGregor
|
2016-01-14 18:53:48 +00:00
|
|
|
// This software is licensed under the MIT license.
|
2015-10-07 01:43:52 +00:00
|
|
|
// Refer to LICENSE.txt for more information.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
|
|
|
|
|
function sameSide(%p1, %p2, %a, %b)
|
|
|
|
|
{
|
|
|
|
|
%cp1 = vectorCross(vectorSub(%b, %a), vectorSub(%p1, %a));
|
|
|
|
|
%cp2 = vectorCross(vectorSub(%b, %a), vectorSub(%p2, %a));
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
if (vectorDot(%cp1, %cp2) >= 0)
|
|
|
|
|
return true;
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 23:10:22 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Returns whether or not the given point resides inside of the triangle
|
|
|
|
|
// denoted by points %a, %b and %c.
|
|
|
|
|
// Param %point: The point to test.
|
|
|
|
|
// Param %a: One point of the triangle.
|
|
|
|
|
// Param %b: One point of the triangle.
|
|
|
|
|
// Param %c: One point of the triangle.
|
2016-01-14 18:53:48 +00:00
|
|
|
// Return: A boolean representing whether or not the given point resides inside of the
|
2015-10-07 23:10:22 +00:00
|
|
|
// triangle.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function pointInTriangle(%point, %a, %b, %c)
|
|
|
|
|
{
|
|
|
|
|
if (sameSide(%point, %a, %b, %c) && sameSide(%point, %b, %a, %c) && sameSide(%point, %c, %a, %b))
|
|
|
|
|
return true;
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-14 18:53:48 +00:00
|
|
|
function Player::getXFacing(%this)
|
|
|
|
|
{
|
|
|
|
|
%forward = %this.getForwardVector();
|
|
|
|
|
return mAtan(getWord(%forward, 1), getWord(%forward, 0));
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 23:10:22 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Calculates all the points of the given client's view cone given a maximum
|
|
|
|
|
// view distance and returns them in a long string.
|
|
|
|
|
// Param %distance: The distance of their view cone.
|
|
|
|
|
// Return: A string in the following format:
|
|
|
|
|
// "OriginX OriginY OriginZ Outer1X Outer1Y Outer1Z Outer2X Outer2Y Outer2Z UpperX UpperY UpperZ
|
|
|
|
|
// LowerX LowerY LowerZ"
|
|
|
|
|
//
|
2014-11-20 05:12:25 +00:00
|
|
|
// TODO: Return in a faster-to-read format: Could try as static GVar names
|
|
|
|
|
// as the game's scripting environment for the gameplay is single threaded
|
|
|
|
|
// and it probably does a hash to store the values.
|
2016-01-14 18:53:48 +00:00
|
|
|
// FIXME: The horizontal view cones may be all that's necessary. A player
|
|
|
|
|
// height check could be used to help alleviate computational complexity.
|
2015-10-07 23:10:22 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function GameConnection::calculateViewCone(%this, %distance)
|
|
|
|
|
{
|
2015-10-07 23:10:22 +00:00
|
|
|
if (!isObject(%this.player) || %this.player.getState() !$= "Move")
|
|
|
|
|
return -1;
|
2016-01-14 18:53:48 +00:00
|
|
|
|
|
|
|
|
%xFacing = %this.player.getXFacing();
|
2014-11-20 05:12:25 +00:00
|
|
|
%coneOrigin = %this.player.getMuzzlePoint($WeaponSlot);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
|
|
|
|
%halfView = %this.fieldOfView / 2;
|
|
|
|
|
%cos = mCos(%halfView);
|
|
|
|
|
%sin = mSin(%halfView);
|
|
|
|
|
|
|
|
|
|
// Translate the horizontal points
|
|
|
|
|
%viewConeClockwisePoint = vectorAdd(%coneOrigin, vectorScale(%cos SPC -%sin SPC "0", %this.viewDistance));
|
|
|
|
|
%viewConeCounterClockwisePoint = vectorAdd(%coneOrigin, vectorScale(%cos SPC -%sin SPC "0", %this.viewDistance));
|
|
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
// Translate the upper and lower points
|
2016-01-14 18:53:48 +00:00
|
|
|
%halfDistance = vectorDist(%viewConeCounterClockwisePoint, %viewConeClockwisePoint) / 2;
|
|
|
|
|
|
|
|
|
|
%viewConeUpperPoint = vectorAdd(%coneOrigin, "0 0" SPC %halfDistance);
|
|
|
|
|
%viewConeLowerPoint = vectorAdd(%coneOrigin, "0 0" SPC -%halfDistance);
|
2014-11-20 05:12:25 +00:00
|
|
|
|
|
|
|
|
return %coneOrigin SPC %viewConeClockwisePoint SPC %viewConeCounterClockwisePoint SPC %viewConeUpperPoint SPC %viewConeLowerPoint;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 22:45:33 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Returns a SimSet of all contained object ID's inside of the given SimSet,
|
|
|
|
|
// including those in child SimGroup and SimSet instances.
|
2015-10-07 23:10:22 +00:00
|
|
|
// Return: The ID of the SimSet that contains all child objects that are not containers
|
|
|
|
|
// themselves.
|
2015-10-07 22:45:33 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
2015-10-05 09:03:45 +00:00
|
|
|
function SimSet::recurse(%this, %result)
|
|
|
|
|
{
|
|
|
|
|
if (!isObject(%result))
|
|
|
|
|
%result = new SimSet();
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
for (%iteration = 0; %iteration < %this.getCount(); %iteration++)
|
|
|
|
|
{
|
|
|
|
|
%current = %this.getObject(%iteration);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
if (%current.getClassName() $= "SimGroup" || %current.getClassName() $= "SimSet")
|
|
|
|
|
%current.recurse(%result);
|
|
|
|
|
else
|
|
|
|
|
%result.add(%current);
|
|
|
|
|
}
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
return %result;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 22:45:33 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Returns the closest friendly inventory station to the given client.
|
|
|
|
|
// Return: The object ID of the inventory station determined to be the closest to this
|
|
|
|
|
// client.
|
2015-10-07 23:10:22 +00:00
|
|
|
//
|
2015-10-05 09:03:45 +00:00
|
|
|
// TODO: Use the nav graph to estimate an actual distance?
|
2015-10-07 22:45:33 +00:00
|
|
|
// FIXME: Return *working* stations only.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2015-10-05 09:03:45 +00:00
|
|
|
function GameConnection::getClosestInventory(%this)
|
|
|
|
|
{
|
|
|
|
|
if (!isObject(%this.player))
|
|
|
|
|
return -1;
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
%group = nameToID("Team" @ %this.team);
|
|
|
|
|
if (!isObject(%group))
|
|
|
|
|
return -1;
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
%teamObjects = %group.recurse();
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
%closestInventory = -1;
|
|
|
|
|
%closestInventoryDistance = 9999;
|
|
|
|
|
for (%iteration = 0; %iteration < %teamObjects.getCount(); %iteration++)
|
|
|
|
|
{
|
|
|
|
|
%current = %teamObjects.getObject(%iteration);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
if (%current.getClassName() $= "StaticShape" && %current.getDatablock().getName() $= "StationInventory")
|
|
|
|
|
{
|
|
|
|
|
%inventoryDistance = vectorDist(%current.getPosition(), %this.player.getPosition());
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
if (%inventoryDistance < %closestInventoryDistance)
|
|
|
|
|
{
|
|
|
|
|
%closestInventoryDistance = %inventoryDistance;
|
|
|
|
|
%closestInventory = %current;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
%teamObjects.delete();
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
return %closestInventory;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 22:45:33 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Calculates a list of objects that can be seen by the given client using
|
|
|
|
|
// distance & field of view values passed in for evaluation.
|
|
|
|
|
// Param %typeMask: The typemask of all objects to consider.
|
|
|
|
|
// Param %distance: The maximum distance to project our view cone checks out to.
|
|
|
|
|
// Param %performLOSTest: A boolean representing whether or not found objects should be
|
|
|
|
|
// verified using a raycast test. If you cannot draw a line from the player to the potential
|
|
|
|
|
// target, then it the potential target is discarded.
|
|
|
|
|
// Return: A SimSet of objects that can be seen by the given client.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function GameConnection::getObjectsInViewcone(%this, %typeMask, %distance, %performLOSTest)
|
|
|
|
|
{
|
|
|
|
|
// FIXME: Radians
|
|
|
|
|
if (%this.fieldOfView < 0 || %this.fieldOfView > 3.14)
|
|
|
|
|
{
|
|
|
|
|
%this.fieldOfView = $DXAPI::Bot::DefaultFieldOfView;
|
|
|
|
|
error("DXAI: Bad field of view value! (" @ %this @ ".fieldOfView > 3.14 || " @ %this @ ".fieldOfView < 0)");
|
|
|
|
|
}
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
if (%this.viewDistance <= 0)
|
|
|
|
|
{
|
|
|
|
|
%this.viewDistance = $DXAPI::Bot::DefaultViewDistance;
|
|
|
|
|
error("DXAI: Bad view distance value! (" @ %this @ ".viewDistance <= 0)");
|
|
|
|
|
}
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
if (%distance $= "")
|
|
|
|
|
%distance = %this.viewDistance;
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
%viewCone = %this.calculateViewCone(%distance);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
// Extract the results: See TODO above ::calculateViewCone implementation
|
|
|
|
|
%coneOrigin = getWords(%viewCone, 0, 2);
|
|
|
|
|
%viewConeClockwiseVector = getWords(%viewCone, 3, 5);
|
|
|
|
|
%viewConeCounterClockwiseVector = getWords(%viewCone, 6, 8);
|
|
|
|
|
%viewConeUpperVector = getWords(%viewCone, 9, 11);
|
|
|
|
|
%viewConeLowerVector = getWords(%viewCone, 12, 14);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
%result = new SimSet();
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
// Doing a radius search should hopefully be faster than iterating over all objects in MissionCleanup.
|
|
|
|
|
// Even if the game did that internally it's definitely faster than doing it in TS
|
|
|
|
|
InitContainerRadiusSearch(%coneOrigin, %distance, %typeMask);
|
|
|
|
|
while((%currentObject = containerSearchNext()) != 0)
|
|
|
|
|
{
|
|
|
|
|
if (%currentObject == %this || !isObject(%currentObject) || containerSearchCurrRadDamageDist() > %distance)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Check if the object is within both the horizontal and vertical triangles representing our view cone
|
|
|
|
|
if (%currentObject.getType() & %typeMask && pointInTriangle(%currentObject.getPosition(), %viewConeClockwiseVector, %viewConeCounterClockwiseVector, %coneOrigin) && pointInTriangle(%currentObject.getPosition(), %viewConeLowerVector, %viewConeUpperVector, %coneOrigin))
|
|
|
|
|
{
|
|
|
|
|
if (!%performLOSTest)
|
|
|
|
|
%result.add(%currentObject);
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-10-07 07:16:32 +00:00
|
|
|
%rayCast = containerRayCast(%coneOrigin, %currentObject.getWorldBoxCenter(), $TypeMasks::AllObjectType, %this.player);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
%hitObject = getWord(%raycast, 0);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-11 03:55:09 +00:00
|
|
|
// Since the engine doesn't do raycasts against projectiles & items correctly, we just check if the bot
|
2014-11-20 05:12:25 +00:00
|
|
|
// hit -nothing- when doing the raycast rather than checking for a hit against the object
|
2015-10-11 03:55:09 +00:00
|
|
|
%workaroundTypes = $TypeMasks::ProjectileObjectType | $TypeMasks::ItemObjectType;
|
|
|
|
|
if (%hitObject == %currentObject || (%currentObject.getType() & %workaroundTypes && !isObject(%hitObject)))
|
2014-11-20 05:12:25 +00:00
|
|
|
%result.add(%currentObject);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
return %result;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 23:10:22 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Gets a random position somewhere within %distance of the given position.
|
2015-10-07 23:11:01 +00:00
|
|
|
// Param %position: The position to generate a new position around.
|
2016-01-14 18:53:48 +00:00
|
|
|
// Param %distance: The maximum distance the new position may be
|
2015-10-10 20:08:43 +00:00
|
|
|
// Param %raycast: A boolean representing whether or not a raycast should be made from
|
|
|
|
|
// %position to the randomly chosen location to stop on objects that may be in the way.
|
|
|
|
|
// This is useful for grabbing positions indoors.
|
2015-10-07 23:10:22 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
2015-10-07 22:45:33 +00:00
|
|
|
function getRandomPosition(%position, %distance, %raycast)
|
2015-06-26 17:18:13 +00:00
|
|
|
{
|
|
|
|
|
// First, we determine a random direction vector
|
|
|
|
|
%direction = vectorNormalize(getRandom(0, 10000) SPC getRandom(0, 10000) SPC getRandom(0, 10000));
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-06-26 17:18:13 +00:00
|
|
|
// Return the scaled result
|
2015-10-07 22:45:33 +00:00
|
|
|
%result = vectorAdd(%position, vectorScale(%direction, getRandom(0, %distance)));
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-07 22:45:33 +00:00
|
|
|
if (!%raycast)
|
|
|
|
|
return %result;
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-07 22:45:33 +00:00
|
|
|
%rayCast = containerRayCast(%position, %result, $TypeMasks::AllObjectType, 0);
|
|
|
|
|
%result = getWords(%raycast, 1, 3);
|
2016-01-14 18:53:48 +00:00
|
|
|
|
2015-10-07 22:45:33 +00:00
|
|
|
return %result;
|
2015-06-26 17:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Gets a random position somewhere within %distance of the given position
|
|
|
|
|
// relative to the terrain object using getTerrainHeight. This is faster to use than
|
|
|
|
|
// getRandomPosition with the raycast setting if all that is necessary is generating a
|
|
|
|
|
// position relative to the terrain object.
|
|
|
|
|
// Param %position: The position to generate a new position around.
|
2016-01-14 18:53:48 +00:00
|
|
|
// Param %distance: The maximum distance the new position may be
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
2015-06-26 21:02:55 +00:00
|
|
|
function getRandomPositionOnTerrain(%position, %distance)
|
|
|
|
|
{
|
|
|
|
|
%result = getRandomPosition(%position, %distance);
|
|
|
|
|
return setWord(%result, 2, getTerrainHeight(%result));
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Multiplies two vectors together and returns the result.
|
|
|
|
|
// Param %vec1: The first vector to multiply.
|
|
|
|
|
// Param %vec2: The second vector to multiply.
|
|
|
|
|
// Return: The product of the multiplication.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
function vectorMult(%vec1, %vec2)
|
2015-06-25 01:03:57 +00:00
|
|
|
{
|
2016-01-14 18:53:48 +00:00
|
|
|
return (getWord(%vec1, 0) * getWord(%vec2, 0)) SPC
|
|
|
|
|
(getWord(%vec1, 1) * getWord(%vec2, 1)) SPC
|
2015-06-25 01:03:57 +00:00
|
|
|
(getWord(%vec1, 2) * getWord(%vec2, 2));
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 07:16:32 +00:00
|
|
|
function listStuckBots()
|
|
|
|
|
{
|
|
|
|
|
for (%iteration = 0; %iteration < ClientGroup.getCount(); %iteration++)
|
|
|
|
|
{
|
|
|
|
|
%client = ClientGroup.getObject(%iteration);
|
|
|
|
|
if (%client.isAIControlled() && %client.isPathCorrecting)
|
|
|
|
|
error(%client);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
// If the map editor was instantiated, this will prevent a little bit
|
|
|
|
|
// of console warnings
|
|
|
|
|
function Terraformer::getType(%this) { return 0; }
|
|
|
|
|
|
|
|
|
|
// Dummy ScriptObject methods to silence console warnings when testing the runtime
|
|
|
|
|
// environment; this may not silence for all mods but it should help.
|
|
|
|
|
$DXAI::System::RuntimeDummy = new ScriptObject(RuntimeDummy) { class = "RuntimeDummy"; };
|
|
|
|
|
|
|
|
|
|
function RuntimeDummy::addTask() { }
|
|
|
|
|
function RuntimeDummy::reset() { }
|
2015-10-05 09:03:45 +00:00
|
|
|
|
|
|
|
|
$TypeMasks::InteractiveObjectType = $TypeMasks::PlayerObjectType | $TypeMasks::VehicleObjectType | $TypeMasks::WaterObjectType | $TypeMasks::ProjectileObjectType | $TypeMasks::ItemObjectType | $TypeMasks::CorpseObjectType;
|
|
|
|
|
$TypeMasks::UnInteractiveObjectType = $TypeMasks::StaticObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::StaticTSObjectType | $TypeMasks::StaticRenderedObjectType;
|
|
|
|
|
$TypeMasks::BaseAssetObjectType = $TypeMasks::ForceFieldObjectType | $TypeMasks::TurretObjectType | $TypeMasks::SensorObjectType | $TypeMasks::StationObjectType | $TypeMasks::GeneratorObjectType;
|
|
|
|
|
$TypeMasks::GameSupportObjectType = $TypeMasks::TriggerObjectType | $TypeMasks::MarkerObjectType | $TypeMasks::CameraObjectType | $TypeMasks::VehicleBlockerObjectType | $TypeMasks::PhysicalZoneObjectType;
|
|
|
|
|
$TypeMasks::GameContentObjectType = $TypeMasks::ExplosionObjectType | $TypeMasks::CorpseObjectType | $TypeMasks::DebrisObjectType;
|
|
|
|
|
$TypeMasks::DefaultLOSObjectType = $TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType | $TypeMasks::StaticObjectType;
|
2015-10-07 01:43:52 +00:00
|
|
|
$TypeMasks::AllObjectType = -1;
|