nuclide/src/shared/NSNavAI.qc

648 lines
13 KiB
Plaintext

/*
* Copyright (c) 2016-2024 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void
NSNavAI::NSNavAI(void)
{
#ifdef SERVER
m_iNodes = 0i;
m_iCurNode = -1i;
m_pRoute = __NULL__;
m_vecLastNode = [0,0,0];
m_vecTurnAngle = [0,0,0];
m_flMoveSpeedKey = 0.0f;
_m_flRouteGiveUp = 0.0f;
#endif
for (int i = 0; i < MAX_AMMO_TYPES; i++)
m_iAmmoTypes[i] = 0;
}
bool
NSNavAI::IsCrouching(void)
{
return HasVFlags(VFL_CROUCHING);
}
bool
NSNavAI::IsProne(void)
{
return HasVFlags(VFL_PRONE);
}
bool
NSNavAI::IsStanding(void)
{
return !HasVFlags(VFL_PRONE | VFL_CROUCHING | VFL_SPRINTING);
}
bool
NSNavAI::IsSprinting(void)
{
return HasVFlags(VFL_SPRINTING);
}
bool
NSNavAI::IsLeaning(void)
{
return (false);
}
bool
NSNavAI::CanSprint(void)
{
return (false);
}
bool
NSNavAI::CanProne(void)
{
return (false);
}
bool
NSNavAI::CanLean(void)
{
return (false);
}
bool
NSNavAI::CanCrouch(void)
{
return (false);
}
/* filled these in with the (default) client side player movement values */
float
NSNavAI::GetForwardSpeed(void)
{
return (PMOVE_FORWARD_SPEED);
}
float
NSNavAI::GetSideSpeed(void)
{
return (PMOVE_SIDE_SPEED);
}
float
NSNavAI::GetBackSpeed(void)
{
return (PMOVE_BACK_SPEED);
}
#ifdef SERVER
void
NSNavAI::Save(float handle)
{
super::Save(handle);
SaveInt(handle, "m_iNodes", m_iNodes);
SaveInt(handle, "m_iCurNode", m_iCurNode);
SaveVector(handle, "m_vecLastNode", m_vecLastNode);
SaveVector(handle, "m_vecTurnAngle", m_vecTurnAngle);
SaveFloat(handle, "m_flMoveSpeedKey", m_flMoveSpeedKey);
}
void
NSNavAI::Restore(string strKey, string strValue)
{
switch (strKey) {
case "m_iNodes":
m_iNodes = ReadInt(strValue);
break;
case "m_iCurNode":
m_iCurNode = ReadInt(strValue);
break;
case "m_vecLastNode":
m_vecLastNode = ReadVector(strValue);
break;
case "m_vecTurnAngle":
m_vecTurnAngle = ReadVector(strValue);
break;
case "m_flMoveSpeedKey":
m_flMoveSpeedKey = ReadFloat(strValue);
break;
default:
super::Restore(strKey, strValue);
}
}
void
NSNavAI::RestoreComplete(void)
{
if (m_iNodes <= 0i)
return;
/* re-plot our route */
RouteToPosition(m_vecLastNode);
}
#endif
#ifdef SERVER
void
NSNavAI::RouteEnded(void)
{
}
void
NSNavAI::CheckRoute_Path(void)
{
float flDist = floor(vlen(m_pathEntity.GetOrigin() - GetOrigin()));
//print(sprintf("Check Path! %f\n", flDist));
/* close enough...? */
if (flDist < 80) {
NSNavAI_Log("Reached path node %S", m_pathTarget);
m_pathTarget = m_pathEntity.target;
m_pathEntity = (NSEntity)m_pathEntity.GetTargetEntity();
velocity = [0,0,0]; /* clamp friction */
}
}
void
NSNavAI::CheckRoute(void)
{
float flDist;
float flNodeRadius;
vector evenpos;
if (m_pathTarget) {
CheckRoute_Path();
return;
}
if (!m_iNodes)
return;
if (_m_flRouteGiveUp < time) {
float distanceToLastFrame = distanceSquared(_m_vecRoutePrev, origin);
printf("distanceToLastFrame: %f\n", distanceToLastFrame);
/* 50 units in 2 seconds is not good. */
if (distanceToLastFrame < 50.0f) {
/* HACK: for followers */
if (m_eFollowing) {
RouteToPosition(m_eFollowing.origin);
NSNavAI_Log("Giving up current route to follower, re-calculating");
} else {
RouteToPosition(m_vecLastNode);
NSNavAI_Log("Giving up current route, re-calculating.");
}
}
_m_flRouteGiveUp = time + 2.0f;
_m_vecRoutePrev = origin;
}
/* level out position/node stuff */
if (m_iCurNode < 0) {
evenpos = m_vecLastNode;
evenpos[2] = origin[2];
flNodeRadius = 8.0f;
} else {
evenpos = m_pRoute[m_iCurNode].dest;
evenpos[2] = origin[2];
flNodeRadius = m_pRoute[m_iCurNode].radius;
if (flNodeRadius <= 0.0)
flNodeRadius = 8.0f;
}
flDist = floor(vlen(evenpos - origin));
if (flDist < flNodeRadius) {
NSNavAI_Log("%S reached node", targetname);
m_iCurNode--;
velocity = [0,0,0]; /* clamp friction */
/* we've still traveling and from this node we may be able to walk
* directly to our end-destination */
if (m_iCurNode > -1) {
tracebox(origin, mins, maxs, m_vecLastNode, MOVE_NORMAL, this);
/* can we walk directly to our target destination? */
if (trace_fraction == 1.0) {
NSNavAI_Log("Walking directly to last node at '%v'", m_vecLastNode);
m_iCurNode = -1;
}
}
}
#if 1
/* check if we can reach the node after the current one */
if (m_iCurNode > 0 && m_iNodes > 3) { /* HACK: only bother when we have more than 3 nodes in the path... this works around an issue in c1a0d I'm unsure about */
int iNextNode = (m_iCurNode - 1);
vector vecNextNode = m_pRoute[iNextNode].dest;
other = world;
tracebox(origin, mins, maxs, vecNextNode, MOVE_OTHERONLY, this);
/* it's accessible */
if (!trace_startsolid && trace_fraction == 1.0f) {
evenpos = vecNextNode;
m_iCurNode = iNextNode;
NSNavAI_Log("Skipping to next node %i at '%v'", iNextNode, vecNextNode);
return;
}
}
#endif
/* reached the end of the line */
if (m_iCurNode < -1) {
RouteClear();
RouteEnded();
NSNavAI_Log("%S reached end", targetname);
}
/* crouch attempt */
if (CanCrouch()) {
vector src;
bool shouldcrouch = false;
/* test up */
src = origin + [0,0,24];
makevectors(angles);
traceline(src, src + anglesToForward(angles) * 128, MOVE_NORMAL, this);
/* we hit something */
if (trace_fraction < 1.0) {
src = origin + [0,0, -8];
traceline(src, src + anglesToForward(angles) * 128, MOVE_NORMAL, this);
/* we can crouch here, so let's do it */
if (trace_fraction >= 1.0)
shouldcrouch = true;
}
/* entire way-link needs to be crouched. that's the law of the land */
if (shouldcrouch || Route_GetNodeFlags(&m_pRoute[m_iCurNode]) & LF_CROUCH)
input_buttons |= INPUT_CROUCH;
}
/*if (flDist == m_flLastDist) {
m_flNodeGiveup += frametime;
} else {
m_flNodeGiveup = bound(0, m_flNodeGiveup - frametime, 1.0);
}
m_flLastDist = flDist;
if (m_flNodeGiveup >= 1.0f) {
print(sprintf("NSNavAI::CheckNode: %s gave up route\n",
this.netname));
RouteClear();
}*/
}
vector
NSNavAI::GetRouteMovevalues(void)
{
vector vecDirection;
vector fwdDir, rightDir, upDir;
fwdDir = anglesToForward(input_angles);
rightDir = anglesToRight(input_angles);
upDir = anglesToUp(input_angles);
if (m_pathTarget) {
vecDirection = vectorNormalize(m_pathEntity.GetOrigin() - GetOrigin());
} else {
if (!m_iNodes)
return m_vecLastNode;
if (m_iCurNode < 0)
vecDirection = vectorNormalize(m_vecLastNode - GetOrigin());
else
vecDirection = vectorNormalize(m_pRoute[m_iCurNode].dest - GetOrigin());
}
return [fwdDir * vecDirection, rightDir * vecDirection, upDir * vecDirection];
}
vector
NSNavAI::GetRouteDirection(void)
{
if (m_pathTarget) {
return vectorToAngles(m_pathEntity.GetOrigin() - GetOrigin());
} else {
if (!m_iNodes) {
return angles;
}
if (m_iCurNode < 0) {
return vectorToAngles(m_vecLastNode - origin);
} else {
return vectorToAngles(m_pRoute[m_iCurNode].dest - origin);
}
}
}
void
NSNavAI::RouteToPosition(vector destination)
{
RouteToPositionDenyFlags(destination, 0i);
}
void
NSNavAI::DebugDraw(void)
{
vector vecStart = GetOrigin();
vector vecEnd;
vector beamColor = [1,1,1];
float frac = 1.0 / m_iNodes;
for (int i = m_iCurNode; i < m_iNodes; i++) {
if (m_iCurNode < 0)
continue;
vecEnd = m_pRoute[i].dest;
R_BeginPolygon("", 0, 0);
R_PolygonVertex(vecStart, [1,1], beamColor, 1);
R_PolygonVertex(vecEnd, [0,1], beamColor, 1);
R_EndPolygon();
vecStart = vecEnd;
beamColor[0] -= frac;
beamColor[1] -= frac;
beamColor[2] -= frac;
}
}
void
NSNavAI::RouteToPositionDenyFlags(vector destination, int denylinkflags)
{
/* engine calls this upon successfully creating a route */
static void RouteToPosition_RouteCB(entity ent, vector dest, int numnodes, nodeslist_t *nodelist)
{
NSNavAI p = (NSNavAI)ent;
p.m_iNodes = numnodes;
p.m_iCurNode = numnodes - 1;
p.m_pRoute = nodelist;
p._m_flRouteGiveUp = time + 4.0f;
//traceline(p.origin, dest, MOVE_NORMAL, this);
tracebox(p.origin, [-16, -16, -16], [16, 16, 16], dest, MOVE_NORMAL, this);
/* can we walk directly to our target destination? */
if (trace_fraction == 1.0) {
NSNavAI_Log("Walking directly to last node");
p.m_iCurNode = -1;
} else {
NSNavAI_Log("Path obstructed, calculating route");
/* run through all nodes, mark the closest direct path possible */
for (int i = 0; i < p.m_iNodes; i++) {
vector vecDest = p.m_pRoute[i].dest;
tracebox(p.origin, p.mins, p.maxs, vecDest, TRUE, p);
//traceline(p.origin, vecDest, MOVE_NORMAL, this);
if (trace_fraction == 1.0) {
p.m_iCurNode = i;
break;
}
}
}
}
if (!g_nodes_present) {
Nodes_Init();
return;
}
/* still nothing... give up */
if (!g_nodes_present) {
return;
}
RouteClear();
if (!m_iNodes) {
route_calculate(this, destination, denylinkflags, RouteToPosition_RouteCB);
m_vecLastNode = destination;
}
}
void
NSNavAI::ChasePath(string startPath)
{
if (!startPath || startPath == "") {
m_pathTarget = __NULL__;
m_pathEntity = __NULL__;
return;
}
m_pathTarget = startPath;
m_pathEntity = (NSEntity)find(world, ::targetname, m_pathTarget);
NSNavAI_Log("Actor %S chase Path set to %S", netname, m_pathEntity.targetname);
}
void
NSNavAI::RouteClear(void)
{
if (!m_iNodes)
return;
m_iCurNode = BOTROUTE_END;
m_iNodes = 0;
memfree(m_pRoute);
NSNavAI_Log("Actor %S (%s) cleared their route.", netname, classname);
}
void
NSNavAI::SetMoveSpeedScale(float newValue)
{
m_flMoveSpeedKey = newValue;
}
float
NSNavAI::GetMoveSpeedScale(void)
{
return (m_flMoveSpeedKey == 0.0) ? 1.0f : m_flMoveSpeedKey;
}
void
NSNavAI::Physics_Run(void)
{
input_movevalues *= GetMoveSpeedScale();
if (CanCrouch())
PMoveCustom_RunCrouchPhysics(this);
else
PMoveCustom_RunPlayerPhysics(this);
SetOrigin(origin);
}
#endif
bool
NSNavAI::GiveAmmo(int ammoType, int ammoAmount)
{
/* bounds check */
if (ammoType < 0i || ammoType >= MAX_AMMO_TYPES)
return (false);
/* already at max-ammo? */
if (m_iAmmoTypes[ammoType] >= 255i)
return (false);
m_iAmmoTypes[ammoType] += ammoAmount;
if (m_iAmmoTypes[ammoType] >= 255i)
m_iAmmoTypes[ammoType] = 255i;
return (true);
}
bool
NSNavAI::UseAmmo(int ammoType, int ammoAmount)
{
/* bounds check */
if (ammoType < 0i || ammoType >= MAX_AMMO_TYPES)
return (false);
/* will underflow when used. */
if ((m_iAmmoTypes[ammoType] - ammoAmount) < 0i)
return (false);
m_iAmmoTypes[ammoType] -= ammoAmount;
return (true);
}
bool
NSNavAI::HasItem(string itemName)
{
NSItem linkedList = __NULL__;
/* we do not have an item. */
if (!m_itemList) {
return (false);
}
/* since we have something in the inventory, start there */
linkedList = m_itemList;
/* iterate through the inventory, then figure out if we already have it*/
while (linkedList) {
/* we already have the item. */
if (linkedList.classname == itemName) {
return (true);
}
linkedList = linkedList.chain;
}
return (false);
}
bool
NSNavAI::GiveItem(string itemName)
{
#ifdef SERVER
NSItem linkedList = __NULL__;
NSItem lastItem = __NULL__;
/* we do not have an item yet. */
if (!m_itemList) {
m_itemList = (NSItem)Entity_CreateClass(itemName);
return (true);
}
/* since we have something in the inventory, start there */
linkedList = m_itemList;
/* iterate through the inventory, then figure out if we already have it*/
while (linkedList) {
/* we already have the item. */
if (linkedList.classname == itemName) {
return (false);
}
lastItem = linkedList;
linkedList = linkedList.chain;
}
/* add it to the back of the chain, so it's part of our inventory. */
if (lastItem && linkedList == __NULL__) {
NSItem newItem = (NSItem)Entity_CreateClass(itemName);
lastItem.chain = newItem;
}
return (true);
#else
return (false);
#endif
}
bool
NSNavAI::RemoveItem(string itemName)
{
#ifdef SERVER
NSItem linkedList = __NULL__;
NSItem frontItem = __NULL__;
NSItem lastItem = __NULL__;
NSItem itemToRemove = __NULL__;
bool removeItem = false;
/* we posess nothing. auto return false. */
if (!m_itemList) {
return (false);
}
/* since we have something in the inventory, start there */
linkedList = m_itemList;
/* iterate through the inventory, then figure out if we already have it*/
while (linkedList) {
/* found the item, mark as needing to be removed */
if (linkedList.classname == itemName) {
removeItem = true;
frontItem = lastItem; /* the one before the current one */
itemToRemove = linkedList; /* the item to destroy */
}
lastItem = linkedList;
linkedList = linkedList.chain;
}
/* successfully remove the last item */
if (removeItem == true) {
/* we had an item in front, bridge across the removed item. */
if (frontItem) {
frontItem.chain = itemToRemove.chain;
} else {
/* this was the first item. set to chain (can be NULL) */
m_itemList = itemToRemove.chain;
}
itemToRemove.Destroy();
return (true);
}
#endif
return (false);
}
bool
NSNavAI::AddItem(NSItem theItem)
{
return (false);
}