From 8558c910cd80e21cba47014fa235614ca89d8d73 Mon Sep 17 00:00:00 2001 From: Radegast Date: Sat, 17 Mar 2012 13:52:35 +0000 Subject: [PATCH] Revert "partially removed botlib, fixes #3" This reverts commit 1e50d88359c65b6310532728013c10d53ea175fa. --- premake4.lua | 4 +- src/botlib/aasfile.h | 288 ++ src/botlib/be_aas_bsp.h | 90 + src/botlib/be_aas_bspq3.c | 570 ++++ src/botlib/be_aas_cluster.c | 1738 +++++++++++ src/botlib/be_aas_cluster.h | 38 + src/botlib/be_aas_debug.c | 802 +++++ src/botlib/be_aas_debug.h | 64 + src/botlib/be_aas_def.h | 300 ++ src/botlib/be_aas_entity.c | 593 ++++ src/botlib/be_aas_entity.h | 65 + src/botlib/be_aas_file.c | 743 +++++ src/botlib/be_aas_file.h | 44 + src/botlib/be_aas_funcs.h | 52 + src/botlib/be_aas_main.c | 528 ++++ src/botlib/be_aas_main.h | 65 + src/botlib/be_aas_move.c | 1133 +++++++ src/botlib/be_aas_move.h | 65 + src/botlib/be_aas_optimize.c | 742 +++++ src/botlib/be_aas_optimize.h | 38 + src/botlib/be_aas_reach.c | 5183 ++++++++++++++++++++++++++++++++ src/botlib/be_aas_reach.h | 77 + src/botlib/be_aas_route.c | 4212 ++++++++++++++++++++++++++ src/botlib/be_aas_route.h | 64 + src/botlib/be_aas_routealt.c | 320 ++ src/botlib/be_aas_routealt.h | 42 + src/botlib/be_aas_routetable.c | 1240 ++++++++ src/botlib/be_aas_routetable.h | 169 ++ src/botlib/be_aas_sample.c | 1655 ++++++++++ src/botlib/be_aas_sample.h | 69 + src/botlib/be_ai_char.c | 866 ++++++ src/botlib/be_ai_chat.c | 3346 +++++++++++++++++++++ src/botlib/be_ai_gen.c | 161 + src/botlib/be_ai_goal.c | 1813 +++++++++++ src/botlib/be_ai_move.c | 4383 +++++++++++++++++++++++++++ src/botlib/be_ai_weap.c | 606 ++++ src/botlib/be_ai_weight.c | 1204 ++++++++ src/botlib/be_ai_weight.h | 86 + src/botlib/be_ea.c | 529 ++++ src/botlib/be_interface.c | 953 ++++++ src/botlib/l_log.c | 14 +- src/botlib/l_precomp.c | 254 +- 42 files changed, 35073 insertions(+), 135 deletions(-) create mode 100644 src/botlib/aasfile.h create mode 100644 src/botlib/be_aas_bsp.h create mode 100644 src/botlib/be_aas_bspq3.c create mode 100644 src/botlib/be_aas_cluster.c create mode 100644 src/botlib/be_aas_cluster.h create mode 100644 src/botlib/be_aas_debug.c create mode 100644 src/botlib/be_aas_debug.h create mode 100644 src/botlib/be_aas_def.h create mode 100644 src/botlib/be_aas_entity.c create mode 100644 src/botlib/be_aas_entity.h create mode 100644 src/botlib/be_aas_file.c create mode 100644 src/botlib/be_aas_file.h create mode 100644 src/botlib/be_aas_funcs.h create mode 100644 src/botlib/be_aas_main.c create mode 100644 src/botlib/be_aas_main.h create mode 100644 src/botlib/be_aas_move.c create mode 100644 src/botlib/be_aas_move.h create mode 100644 src/botlib/be_aas_optimize.c create mode 100644 src/botlib/be_aas_optimize.h create mode 100644 src/botlib/be_aas_reach.c create mode 100644 src/botlib/be_aas_reach.h create mode 100644 src/botlib/be_aas_route.c create mode 100644 src/botlib/be_aas_route.h create mode 100644 src/botlib/be_aas_routealt.c create mode 100644 src/botlib/be_aas_routealt.h create mode 100644 src/botlib/be_aas_routetable.c create mode 100644 src/botlib/be_aas_routetable.h create mode 100644 src/botlib/be_aas_sample.c create mode 100644 src/botlib/be_aas_sample.h create mode 100644 src/botlib/be_ai_char.c create mode 100644 src/botlib/be_ai_chat.c create mode 100644 src/botlib/be_ai_gen.c create mode 100644 src/botlib/be_ai_goal.c create mode 100644 src/botlib/be_ai_move.c create mode 100644 src/botlib/be_ai_weap.c create mode 100644 src/botlib/be_ai_weight.c create mode 100644 src/botlib/be_ai_weight.h create mode 100644 src/botlib/be_ea.c create mode 100644 src/botlib/be_interface.c diff --git a/premake4.lua b/premake4.lua index d7fa0abf8..7911f4cc2 100644 --- a/premake4.lua +++ b/premake4.lua @@ -61,6 +61,8 @@ project "etlegacy" } excludes { + "src/botlib/botlib_stub.c", + -- Premake will support configuration-dependent files in the next version. -- Force cURL until then. "src/qcommon/dl_main_stubs.c", @@ -199,7 +201,6 @@ project "etlegacy-dedicated" "src/qcommon/**.c", "src/qcommon/**.h", "src/botlib/**.c", "src/botlib/**.h", - "src/server/**.c", "src/server/**.h", "src/null/null_client.c", @@ -218,6 +219,7 @@ project "etlegacy-dedicated" } excludes { + "src/botlib/botlib_stub.c", "src/qcommon/dl_main_curl.c", } diff --git a/src/botlib/aasfile.h b/src/botlib/aasfile.h new file mode 100644 index 000000000..56dbf66bc --- /dev/null +++ b/src/botlib/aasfile.h @@ -0,0 +1,288 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file aasfile.h + */ + +//NOTE: int = default signed +// default long + +#define AASID (('S' << 24) + ('A' << 16) + ('A' << 8) + 'E') +#define AASVERSION 8 + +//presence types +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 + +//travel types +#define MAX_TRAVELTYPES 32 +#define TRAVEL_INVALID 1 //temporary not possible +#define TRAVEL_WALK 2 //walking +#define TRAVEL_CROUCH 3 //crouching +#define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier +#define TRAVEL_JUMP 5 //jumping +#define TRAVEL_LADDER 6 //climbing a ladder +#define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge +#define TRAVEL_SWIM 8 //swimming +#define TRAVEL_WATERJUMP 9 //jump out of the water +#define TRAVEL_TELEPORT 10 //teleportation +#define TRAVEL_ELEVATOR 11 //travel by elevator +#define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel +#define TRAVEL_BFGJUMP 13 //bfg jumping required for travel +#define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel +#define TRAVEL_DOUBLEJUMP 15 //double jump +#define TRAVEL_RAMPJUMP 16 //ramp jump +#define TRAVEL_STRAFEJUMP 17 //strafe jump +#define TRAVEL_JUMPPAD 18 //jump pad +#define TRAVEL_FUNCBOB 19 //func bob + +//additional travel flags +#define TRAVELTYPE_MASK 0xFFFFFF +#define TRAVELFLAG_NOTTEAM1 (1 << 24) +#define TRAVELFLAG_NOTTEAM2 (2 << 24) + +//face flags +#define FACE_SOLID 1 //just solid at the other side +#define FACE_LADDER 2 //ladder +#define FACE_GROUND 4 //standing on ground when in this face +#define FACE_GAP 8 //gap in the ground +#define FACE_LIQUID 16 +#define FACE_LIQUIDSURFACE 32 + +//area contents +#define AREACONTENTS_WATER 1 +#define AREACONTENTS_LAVA 2 +#define AREACONTENTS_SLIME 4 +#define AREACONTENTS_CLUSTERPORTAL 8 +#define AREACONTENTS_TELEPORTAL 16 +#define AREACONTENTS_ROUTEPORTAL 32 +#define AREACONTENTS_TELEPORTER 64 +#define AREACONTENTS_JUMPPAD 128 +#define AREACONTENTS_DONOTENTER 256 +#define AREACONTENTS_VIEWPORTAL 512 +// Rafael - nopass +#define AREACONTENTS_DONOTENTER_LARGE 1024 +#define AREACONTENTS_MOVER 2048 + +//number of model of the mover inside this area +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) + +//area flags +#define AREA_GROUNDED 1 //bot can stand on the ground +#define AREA_LADDER 2 //area contains one or more ladder faces +#define AREA_LIQUID 4 //area contains a liquid +// Ridah +#define AREA_DISABLED 8 +#define AREA_AVOID 16 +#define AREA_TEAM_AXIS 32 +#define AREA_TEAM_ALLIES 64 +#define AREA_TEAM_AXIS_DISGUISED 128 +#define AREA_TEAM_ALLIES_DISGUISED 256 +#define AREA_USEFORROUTING 1024 +#define AREA_AVOID_AXIS 2048 // death area +#define AREA_AVOID_ALLIES 4096 // death area + +#define AREA_TEAM_FLAGS (AREA_TEAM_AXIS | AREA_TEAM_ALLIES | AREA_TEAM_AXIS_DISGUISED | AREA_TEAM_ALLIES_DISGUISED | AREA_AVOID_AXIS | AREA_AVOID_ALLIES) + +//aas file header lumps +#define AAS_LUMPS 14 +#define AASLUMP_BBOXES 0 +#define AASLUMP_VERTEXES 1 +#define AASLUMP_PLANES 2 +#define AASLUMP_EDGES 3 +#define AASLUMP_EDGEINDEX 4 +#define AASLUMP_FACES 5 +#define AASLUMP_FACEINDEX 6 +#define AASLUMP_AREAS 7 +#define AASLUMP_AREASETTINGS 8 +#define AASLUMP_REACHABILITY 9 +#define AASLUMP_NODES 10 +#define AASLUMP_PORTALS 11 +#define AASLUMP_PORTALINDEX 12 +#define AASLUMP_CLUSTERS 13 + +//========== bounding box ========= + +//bounding box +typedef struct aas_bbox_s +{ + int presencetype; + int flags; + vec3_t mins, maxs; +} aas_bbox_t; + +//============ settings =========== + +//reachability to another area +typedef struct aas_reachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime; //travel time of the inter area movement +} aas_reachability_t; + +//area settings +typedef struct aas_areasettings_s +{ + //could also add all kind of statistic fields + int contents; //contents of the convex area + int areaflags; //several area flags + int presencetype; //how a bot can be present in this convex area + int cluster; //cluster the area belongs to, if negative it's a portal + int clusterareanum; //number of the area in the cluster + int numreachableareas; //number of reachable areas from this one + int firstreachablearea; //first reachable area in the reachable area index + // Ridah, add a ground steepness stat, so we can avoid terrain when we can take a close-by flat route + float groundsteepness; // 0 = flat, 1 = steep +} aas_areasettings_t; + +//cluster portal +typedef struct aas_portal_s +{ + int areanum; //area that is the actual portal + int frontcluster; //cluster at front of portal + int backcluster; //cluster at back of portal + int clusterareanum[2]; //number of the area in the front and back cluster +} aas_portal_t; + +//cluster portal index +typedef int aas_portalindex_t; + +//cluster +typedef struct aas_cluster_s +{ + int numareas; //number of areas in the cluster + int numreachabilityareas; //number of areas with reachabilities + int numportals; //number of cluster portals + int firstportal; //first cluster portal in the index +} aas_cluster_t; + +//============ 3d definition ============ + +typedef vec3_t aas_vertex_t; + +//just a plane in the third dimension +typedef struct aas_plane_s +{ + vec3_t normal; //normal vector of the plane + float dist; //distance of the plane (normal vector * distance = point in plane) + int type; +} aas_plane_t; + +//edge +typedef struct aas_edge_s +{ + int v[2]; //numbers of the vertexes of this edge +} aas_edge_t; + +//edge index, negative if vertexes are reversed +typedef int aas_edgeindex_t; + +//a face bounds a convex area, often it will also seperate two convex areas +typedef struct aas_face_s +{ + int planenum; //number of the plane this face is in + int faceflags; //face flags (no use to create face settings for just this field) + int numedges; //number of edges in the boundary of the face + int firstedge; //first edge in the edge index + int frontarea; //convex area at the front of this face + int backarea; //convex area at the back of this face +} aas_face_t; + +//face index, stores a negative index if backside of face +typedef int aas_faceindex_t; + +//convex area with a boundary of faces +typedef struct aas_area_s +{ + int areanum; //number of this area + //3d definition + int numfaces; //number of faces used for the boundary of the convex area + int firstface; //first face in the face index used for the boundary of the convex area + vec3_t mins; //mins of the convex area + vec3_t maxs; //maxs of the convex area + vec3_t center; //'center' of the convex area +} aas_area_t; + +//nodes of the bsp tree +typedef struct aas_node_s +{ + int planenum; + int children[2]; //child nodes of this node, or convex areas as leaves when negative + //when a child is zero it's a solid leaf +} aas_node_t; + +//=========== aas file =============== + +//header lump +typedef struct +{ + int fileofs; + int filelen; +} aas_lump_t; + +//aas file header +typedef struct aas_header_s +{ + int ident; + int version; + int bspchecksum; + //data entries + aas_lump_t lumps[AAS_LUMPS]; +} aas_header_t; + + +//====== additional information ====== +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the convex areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + cluster number zero +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +*/ diff --git a/src/botlib/be_aas_bsp.h b/src/botlib/be_aas_bsp.h new file mode 100644 index 000000000..24eb0c986 --- /dev/null +++ b/src/botlib/be_aas_bsp.h @@ -0,0 +1,90 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_bsp.h + */ + +#ifdef AASINTERN +//loads the given BSP file +int AAS_LoadBSPFile(void); +//dump the loaded BSP data +void AAS_DumpBSPData(void); +//unlink the given entity from the bsp tree leaves +void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves); +//link the given entity to the bsp tree leaves of the given model +bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, + vec3_t absmaxs, + int entnum, + int modelnum); + +//calculates collision with given entity +qboolean AAS_EntityCollision(int entnum, + vec3_t start, + vec3_t boxmins, + vec3_t boxmaxs, + vec3_t end, + int contentmask, + bsp_trace_t *trace); +//for debugging +void AAS_PrintFreeBSPLinks(char *str); +// +#endif //AASINTERN + +#define MAX_EPAIRKEY 128 + +//trace through the world +bsp_trace_t AAS_Trace(vec3_t start, + vec3_t mins, + vec3_t maxs, + vec3_t end, + int passent, + int contentmask); +//returns the contents at the given point +int AAS_PointContents(vec3_t point); +//returns true when p2 is in the PVS of p1 +qboolean AAS_inPVS(vec3_t p1, vec3_t p2); +//returns true when p2 is in the PHS of p1 +qboolean AAS_inPHS(vec3_t p1, vec3_t p2); +//returns true if the given areas are connected +qboolean AAS_AreasConnected(int area1, int area2); +//creates a list with entities totally or partly within the given box +int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount); +//gets the mins, maxs and origin of a BSP model +void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); +//handle to the next bsp entity +int AAS_NextBSPEntity(int ent); +//return the value of the BSP epair key +int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size); +//get a vector for the BSP epair key +int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v); +//get a float for the BSP epair key +int AAS_FloatForBSPEpairKey(int ent, char *key, float *value); +//get an integer for the BSP epair key +int AAS_IntForBSPEpairKey(int ent, char *key, int *value); diff --git a/src/botlib/be_aas_bspq3.c b/src/botlib/be_aas_bspq3.c new file mode 100644 index 000000000..c45589614 --- /dev/null +++ b/src/botlib/be_aas_bspq3.c @@ -0,0 +1,570 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_bspq3.c + * @brief BSP, Environment Sampling + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define TRACE_DEBUG + +#define ON_EPSILON 0.005 +//#define DEG2RAD( a ) (( a * M_PI ) / 180.0F) + +#define MAX_BSPENTITIES 4096 + +typedef struct rgb_s +{ + int red; + int green; + int blue; +} rgb_t; + +//bsp entity epair +typedef struct bsp_epair_s +{ + char *key; + char *value; + struct bsp_epair_s *next; +} bsp_epair_t; + +//bsp data entity +typedef struct bsp_entity_s +{ + bsp_epair_t *epairs; +} bsp_entity_t; + +//id Sofware BSP data +typedef struct bsp_s +{ + //true when bsp file is loaded + int loaded; + //entity data + int entdatasize; + char *dentdata; + //bsp entities + int numentities; + bsp_entity_t entities[MAX_BSPENTITIES]; + //memory used for strings and epairs + byte *ebuffer; +} bsp_t; + +//global bsp +bsp_t bspworld; + + +#ifdef BSP_DEBUG +typedef struct cname_s +{ + int value; + char *name; +} cname_t; + +cname_t contentnames[] = +{ + { CONTENTS_SOLID, "CONTENTS_SOLID" }, + { CONTENTS_WINDOW, "CONTENTS_WINDOW" }, + { CONTENTS_AUX, "CONTENTS_AUX" }, + { CONTENTS_LAVA, "CONTENTS_LAVA" }, + { CONTENTS_SLIME, "CONTENTS_SLIME" }, + { CONTENTS_WATER, "CONTENTS_WATER" }, + { CONTENTS_MIST, "CONTENTS_MIST" }, + { LAST_VISIBLE_CONTENTS, "LAST_VISIBLE_CONTENTS" }, + + { CONTENTS_AREAPORTAL, "CONTENTS_AREAPORTAL" }, + { CONTENTS_PLAYERCLIP, "CONTENTS_PLAYERCLIP" }, + { CONTENTS_MONSTERCLIP, "CONTENTS_MONSTERCLIP" }, + { CONTENTS_CURRENT_0, "CONTENTS_CURRENT_0" }, + { CONTENTS_CURRENT_90, "CONTENTS_CURRENT_90" }, + { CONTENTS_CURRENT_180, "CONTENTS_CURRENT_180" }, + { CONTENTS_CURRENT_270, "CONTENTS_CURRENT_270" }, + { CONTENTS_CURRENT_UP, "CONTENTS_CURRENT_UP" }, + { CONTENTS_CURRENT_DOWN, "CONTENTS_CURRENT_DOWN" }, + { CONTENTS_ORIGIN, "CONTENTS_ORIGIN" }, + { CONTENTS_MONSTER, "CONTENTS_MONSTER" }, + { CONTENTS_DEADMONSTER, "CONTENTS_DEADMONSTER" }, + { CONTENTS_DETAIL, "CONTENTS_DETAIL" }, + { CONTENTS_TRANSLUCENT, "CONTENTS_TRANSLUCENT" }, + { CONTENTS_LADDER, "CONTENTS_LADDER" }, + { 0, 0 } +}; + +void PrintContents(int contents) +{ + int i; + + for (i = 0; contentnames[i].value; i++) + { + if (contents & contentnames[i].value) + { + botimport.Print(PRT_MESSAGE, "%s\n", contentnames[i].name); + } //end if + } //end for +} //end of the function PrintContents + +#endif //BSP_DEBUG +//=========================================================================== +// traces axial boxes of any size through the world +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_trace_t AAS_Trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) +{ + bsp_trace_t bsptrace; + botimport.Trace(&bsptrace, start, mins, maxs, end, passent, contentmask); + return bsptrace; +} //end of the function AAS_Trace +//=========================================================================== +// returns the contents at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointContents(vec3_t point) +{ + return botimport.PointContents(point); +} //end of the function AAS_PointContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_EntityCollision(int entnum, + vec3_t start, vec3_t boxmins, vec3_t boxmaxs, vec3_t end, + int contentmask, bsp_trace_t *trace) +{ + bsp_trace_t enttrace; + + botimport.EntityTrace(&enttrace, start, boxmins, boxmaxs, end, entnum, contentmask); + if (enttrace.fraction < trace->fraction) + { + memcpy(trace, &enttrace, sizeof(bsp_trace_t)); + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_EntityCollision +//=========================================================================== +// returns true if in Potentially Hearable Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPVS(vec3_t p1, vec3_t p2) +{ + return botimport.inPVS(p1, p2); +} //end of the function AAS_InPVS +//=========================================================================== +// returns true if in Potentially Visible Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPHS(vec3_t p1, vec3_t p2) +{ + return qtrue; +} //end of the function AAS_inPHS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin) +{ + botimport.BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); +} //end of the function AAS_BSPModelMinsMaxs +//=========================================================================== +// unlinks the entity from all leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves) +{ +} //end of the function AAS_UnlinkFromBSPLeaves +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum) +{ + return NULL; +} //end of the function AAS_BSPLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount) +{ + return 0; +} //end of the function AAS_BoxEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextBSPEntity(int ent) +{ + ent++; + if (ent >= 1 && ent < bspworld.numentities) + { + return ent; + } + return 0; +} //end of the function AAS_NextBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPEntityInRange(int ent) +{ + if (ent <= 0 || ent >= bspworld.numentities) + { + botimport.Print(PRT_MESSAGE, "bsp entity out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function AAS_BSPEntityInRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size) +{ + bsp_epair_t *epair; + + value[0] = '\0'; + if (!AAS_BSPEntityInRange(ent)) + { + return qfalse; + } + for (epair = bspworld.entities[ent].epairs; epair; epair = epair->next) + { + if (!strcmp(epair->key, key)) + { + strncpy(value, epair->value, size - 1); + value[size - 1] = '\0'; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_FindBSPEpair +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v) +{ + char buf[MAX_EPAIRKEY]; + double v1, v2, v3; + + VectorClear(v); + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) + { + return qfalse; + } + //scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf(buf, "%lf %lf %lf", &v1, &v2, &v3); + v[0] = v1; + v[1] = v2; + v[2] = v3; + return qtrue; +} //end of the function AAS_VectorForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloatForBSPEpairKey(int ent, char *key, float *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) + { + return qfalse; + } + *value = atof(buf); + return qtrue; +} //end of the function AAS_FloatForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IntForBSPEpairKey(int ent, char *key, int *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) + { + return qfalse; + } + *value = atoi(buf); + return qtrue; +} //end of the function AAS_IntForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeBSPEntities(void) +{ +// RF, optimized memory allocation + /* + int i; + bsp_entity_t *ent; + bsp_epair_t *epair, *nextepair; + + for (i = 1; i < bspworld.numentities; i++) + { + ent = &bspworld.entities[i]; + for (epair = ent->epairs; epair; epair = nextepair) + { + nextepair = epair->next; + // + if (epair->key) FreeMemory(epair->key); + if (epair->value) FreeMemory(epair->value); + FreeMemory(epair); + } //end for + } //end for + */ + if (bspworld.ebuffer) + { + FreeMemory(bspworld.ebuffer); + } + bspworld.numentities = 0; +} //end of the function AAS_FreeBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ParseBSPEntities(void) +{ + script_t *script; + token_t token; + bsp_entity_t *ent; + bsp_epair_t *epair; + byte *buffer, *buftrav; + int bufsize; + + // RF, modified this, so that it first gathers up memory requirements, then allocates a single chunk, + // and places the strings all in there + + bspworld.ebuffer = NULL; + + script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS); //SCFL_PRIMITIVE); + + bufsize = 0; + + while (PS_ReadToken(script, &token)) + { + if (strcmp(token.string, "{")) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + if (bspworld.numentities >= MAX_BSPENTITIES) + { + botimport.Print(PRT_MESSAGE, "too many entities in BSP file\n"); + break; + } //end if + while (PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, "}")) + { + break; + } + bufsize += sizeof(bsp_epair_t); + if (token.type != TT_STRING) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + bufsize += strlen(token.string) + 1; + if (!PS_ExpectTokenType(script, TT_STRING, 0, &token)) + { + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + bufsize += strlen(token.string) + 1; + } //end while + if (strcmp(token.string, "}")) + { + ScriptError(script, "missing }\n"); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + } //end while + FreeScript(script); + + buffer = (byte *)GetClearedHunkMemory(bufsize); + buftrav = buffer; + bspworld.ebuffer = buffer; + + // RF, now parse the entities into memory + // RF, NOTE: removed error checks for speed, no need to do them twice + + script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS); //SCFL_PRIMITIVE); + + bspworld.numentities = 1; + + while (PS_ReadToken(script, &token)) + { + ent = &bspworld.entities[bspworld.numentities]; + bspworld.numentities++; + ent->epairs = NULL; + while (PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, "}")) + { + break; + } + epair = (bsp_epair_t *) buftrav; + buftrav += sizeof(bsp_epair_t); + epair->next = ent->epairs; + ent->epairs = epair; + StripDoubleQuotes(token.string); + epair->key = (char *) buftrav; + buftrav += (strlen(token.string) + 1); + strcpy(epair->key, token.string); + if (!PS_ExpectTokenType(script, TT_STRING, 0, &token)) + { + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + epair->value = (char *) buftrav; + buftrav += (strlen(token.string) + 1); + strcpy(epair->value, token.string); + } //end while + } //end while + FreeScript(script); +} //end of the function AAS_ParseBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPTraceLight(vec3_t start, vec3_t end, vec3_t endpos, int *red, int *green, int *blue) +{ + return 0; +} //end of the function AAS_BSPTraceLight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpBSPData(void) +{ + AAS_FreeBSPEntities(); + + if (bspworld.dentdata) + { + FreeMemory(bspworld.dentdata); + } + bspworld.dentdata = NULL; + bspworld.entdatasize = 0; + // + bspworld.loaded = qfalse; + memset(&bspworld, 0, sizeof(bspworld)); +} //end of the function AAS_DumpBSPData +//=========================================================================== +// load an bsp file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadBSPFile(void) +{ + AAS_DumpBSPData(); + bspworld.entdatasize = strlen(botimport.BSPEntityData()) + 1; + bspworld.dentdata = (char *) GetClearedHunkMemory(bspworld.entdatasize); + memcpy(bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize); + AAS_ParseBSPEntities(); + bspworld.loaded = qtrue; + return BLERR_NOERROR; +} //end of the function AAS_LoadBSPFile + +void AAS_InitBSP(void) +{ + memset(&bspworld, 0, sizeof(bspworld)); +} diff --git a/src/botlib/be_aas_cluster.c b/src/botlib/be_aas_cluster.c new file mode 100644 index 000000000..ac7067316 --- /dev/null +++ b/src/botlib/be_aas_cluster.c @@ -0,0 +1,1738 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_bspq3.c + * @brief area clustering + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +#define AAS_MAX_PORTALS 65536 +#define AAS_MAX_PORTALINDEXSIZE 65536 +#define AAS_MAX_CLUSTERS 65536 +// +#define MAX_PORTALAREAS 1024 + +// do not flood through area faces, only use reachabilities +int nofaceflood = qtrue; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveClusterAreas(void) +{ + int i; + + for (i = 1; i < (*aasworld).numareas; i++) + { + (*aasworld).areasettings[i].cluster = 0; + } //end for +} //end of the function AAS_RemoveClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearCluster(int clusternum) +{ + int i; + + for (i = 1; i < (*aasworld).numareas; i++) + { + if ((*aasworld).areasettings[i].cluster == clusternum) + { + (*aasworld).areasettings[i].cluster = 0; + } //end if + } //end for +} //end of the function AAS_ClearCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemovePortalsClusterReference(int clusternum) +{ + int portalnum; + + for (portalnum = 1; portalnum < (*aasworld).numportals; portalnum++) + { + if ((*aasworld).portals[portalnum].frontcluster == clusternum) + { + (*aasworld).portals[portalnum].frontcluster = 0; + } //end if + if ((*aasworld).portals[portalnum].backcluster == clusternum) + { + (*aasworld).portals[portalnum].backcluster = 0; + } //end if + } //end for +} //end of the function AAS_RemovePortalsClusterReference +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdatePortal(int areanum, int clusternum) +{ + int portalnum; + aas_portal_t *portal; + aas_cluster_t *cluster; + + //find the portal of the area + for (portalnum = 1; portalnum < (*aasworld).numportals; portalnum++) + { + if ((*aasworld).portals[portalnum].areanum == areanum) + { + break; + } + } //end for + // + if (portalnum == (*aasworld).numportals) + { + AAS_Error("no portal of area %d", areanum); + return qtrue; + } //end if + // + portal = &(*aasworld).portals[portalnum]; + //if the portal is already fully updated + if (portal->frontcluster == clusternum) + { + return qtrue; + } + if (portal->backcluster == clusternum) + { + return qtrue; + } + //if the portal has no front cluster yet + if (!portal->frontcluster) + { + portal->frontcluster = clusternum; + } //end if + //if the portal has no back cluster yet + else if (!portal->backcluster) + { + portal->backcluster = clusternum; + } //end else if + else + { + Log_Write("portal using area %d is seperating more than two clusters\r\n", areanum); + //remove the cluster portal flag contents + (*aasworld).areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + return qfalse; + } //end else + if ((*aasworld).portalindexsize >= AAS_MAX_PORTALINDEXSIZE) + { + AAS_Error("AAS_MAX_PORTALINDEXSIZE"); + return qtrue; + } //end if + //set the area cluster number to the negative portal number + (*aasworld).areasettings[areanum].cluster = -portalnum; + //add the portal to the cluster using the portal index + cluster = &(*aasworld).clusters[clusternum]; + (*aasworld).portalindex[cluster->firstportal + cluster->numportals] = portalnum; + (*aasworld).portalindexsize++; + cluster->numportals++; + return qtrue; +} //end of the function AAS_UpdatePortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreas_r(int areanum, int clusternum) +{ + aas_area_t *area; + aas_face_t *face; + int facenum, i; + + // + if (areanum <= 0 || areanum >= (*aasworld).numareas) + { + AAS_Error("AAS_FloodClusterAreas_r: areanum out of range"); + return qfalse; + } //end if + //if the area is already part of a cluster + if ((*aasworld).areasettings[areanum].cluster > 0) + { + if ((*aasworld).areasettings[areanum].cluster == clusternum) + { + return qtrue; + } + // + //there's a reachability going from one cluster to another only in one direction + // + AAS_Error("cluster %d touched cluster %d at area %d\r\n", + clusternum, (*aasworld).areasettings[areanum].cluster, areanum); + return qfalse; + } //end if + //don't add the cluster portal areas to the clusters + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + return AAS_UpdatePortal(areanum, clusternum); + } //end if + //set the area cluster number + (*aasworld).areasettings[areanum].cluster = clusternum; + (*aasworld).areasettings[areanum].clusterareanum = + (*aasworld).clusters[clusternum].numareas; + //the cluster has an extra area + (*aasworld).clusters[clusternum].numareas++; + + area = &(*aasworld).areas[areanum]; + //use area faces to flood into adjacent areas + if (!nofaceflood) + { + for (i = 0; i < area->numfaces; i++) + { + facenum = abs((*aasworld).faceindex[area->firstface + i]); + face = &(*aasworld).faces[facenum]; + if (face->frontarea == areanum) + { + if (face->backarea) + { + if (!AAS_FloodClusterAreas_r(face->backarea, clusternum)) + { + return qfalse; + } + } + } //end if + else + { + if (face->frontarea) + { + if (!AAS_FloodClusterAreas_r(face->frontarea, clusternum)) + { + return qfalse; + } + } + } //end else + } //end for + } + //use the reachabilities to flood into other areas + for (i = 0; i < (*aasworld).areasettings[areanum].numreachableareas; i++) + { + if (!(*aasworld).reachability[ + (*aasworld).areasettings[areanum].firstreachablearea + i].areanum) + { + continue; + } //end if + if (!AAS_FloodClusterAreas_r((*aasworld).reachability[ + (*aasworld).areasettings[areanum].firstreachablearea + i].areanum, clusternum)) + { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreas_r +//=========================================================================== +// try to flood from all areas without cluster into areas with a cluster set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreasUsingReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < (*aasworld).numareas; i++) + { + //if this area already has a cluster set + if ((*aasworld).areasettings[i].cluster) + { + continue; + } + //if this area is a cluster portal + if ((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } + //loop over the reachable areas from this area + for (j = 0; j < (*aasworld).areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = (*aasworld).reachability[(*aasworld).areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } + //if this area has a cluster set + if ((*aasworld).areasettings[areanum].cluster) + { + if (!AAS_FloodClusterAreas_r(i, clusternum)) + { + return qfalse; + } + i = 0; + break; + } //end if + } //end for + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreasUsingReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterPortals(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + cluster = &(*aasworld).clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = (*aasworld).portalindex[cluster->firstportal + i]; + portal = &(*aasworld).portals[portalnum]; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterAreas(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + (*aasworld).clusters[clusternum].numareas = 0; + (*aasworld).clusters[clusternum].numreachabilityareas = 0; + //number all areas in this cluster WITH reachabilities + for (i = 1; i < (*aasworld).numareas; i++) + { + // + if ((*aasworld).areasettings[i].cluster != clusternum) + { + continue; + } + // + if (!AAS_AreaReachability(i)) + { + continue; + } + // + (*aasworld).areasettings[i].clusterareanum = (*aasworld).clusters[clusternum].numareas; + //the cluster has an extra area + (*aasworld).clusters[clusternum].numareas++; + (*aasworld).clusters[clusternum].numreachabilityareas++; + } //end for + //number all portals in this cluster WITH reachabilities + cluster = &(*aasworld).clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = (*aasworld).portalindex[cluster->firstportal + i]; + portal = &(*aasworld).portals[portalnum]; + if (!AAS_AreaReachability(portal->areanum)) + { + continue; + } + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + (*aasworld).clusters[clusternum].numreachabilityareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + (*aasworld).clusters[clusternum].numreachabilityareas++; + } //end else + } //end for + //number all areas in this cluster WITHOUT reachabilities + for (i = 1; i < (*aasworld).numareas; i++) + { + // + if ((*aasworld).areasettings[i].cluster != clusternum) + { + continue; + } + // + if (AAS_AreaReachability(i)) + { + continue; + } + // + (*aasworld).areasettings[i].clusterareanum = (*aasworld).clusters[clusternum].numareas; + //the cluster has an extra area + (*aasworld).clusters[clusternum].numareas++; + } //end for + //number all portals in this cluster WITHOUT reachabilities + cluster = &(*aasworld).clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = (*aasworld).portalindex[cluster->firstportal + i]; + portal = &(*aasworld).portals[portalnum]; + if (AAS_AreaReachability(portal->areanum)) + { + continue; + } + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindClusters(void) +{ + int i; + aas_cluster_t *cluster; + + AAS_RemoveClusterAreas(); + // + for (i = 1; i < (*aasworld).numareas; i++) + { + //if the area is already part of a cluster + if ((*aasworld).areasettings[i].cluster) + { + continue; + } + // if not flooding through faces only use areas that have reachabilities + if (nofaceflood) + { + if (!(*aasworld).areasettings[i].numreachableareas) + { + continue; + } + } //end if + //if the area is a cluster portal + if ((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } + if ((*aasworld).numclusters >= AAS_MAX_CLUSTERS) + { + AAS_Error("AAS_MAX_CLUSTERS"); + return qfalse; + } //end if + cluster = &(*aasworld).clusters[(*aasworld).numclusters]; + cluster->numareas = 0; + cluster->numreachabilityareas = 0; + cluster->firstportal = (*aasworld).portalindexsize; + cluster->numportals = 0; + //flood the areas in this cluster + if (!AAS_FloodClusterAreas_r(i, (*aasworld).numclusters)) + { + return qfalse; + } + if (!AAS_FloodClusterAreasUsingReachabilities((*aasworld).numclusters)) + { + return qfalse; + } + //number the cluster areas + //AAS_NumberClusterPortals((*aasworld).numclusters); + AAS_NumberClusterAreas((*aasworld).numclusters); + //Log_Write("cluster %d has %d areas\r\n", (*aasworld).numclusters, cluster->numareas); + (*aasworld).numclusters++; + } //end for + return qtrue; +} //end of the function AAS_FindClusters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreatePortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < (*aasworld).numareas; i++) + { + //if the area is a cluster portal + if ((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + if ((*aasworld).numportals >= AAS_MAX_PORTALS) + { + AAS_Error("AAS_MAX_PORTALS"); + return; + } //end if + portal = &(*aasworld).portals[(*aasworld).numportals]; + portal->areanum = i; + portal->frontcluster = 0; + portal->backcluster = 0; + Log_Write("portal %d: area %d\r\n", (*aasworld).numportals, portal->areanum); + (*aasworld).numportals++; + } //end if + } //end for +} //end of the function AAS_CreatePortals +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MapContainsTeleporters(void) +{ + bsp_entity_t *entities, *ent; + char *classname; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + AAS_FreeBSPEntities(entities); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_MapContainsTeleporters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NonConvexFaces(aas_face_t *face1, aas_face_t *face2, int side1, int side2) +{ + int i, j, edgenum; + aas_plane_t *plane1, *plane2; + aas_edge_t *edge; + + + plane1 = &(*aasworld).planes[face1->planenum ^ side1]; + plane2 = &(*aasworld).planes[face2->planenum ^ side2]; + + //check if one of the points of face1 is at the back of the plane of face2 + for (i = 0; i < face1->numedges; i++) + { + edgenum = abs((*aasworld).edgeindex[face1->firstedge + i]); + edge = &(*aasworld).edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane2->normal, (*aasworld).vertexes[edge->v[j]]) - + plane2->dist < -0.01) return qtrue; + } //end for + } //end for + for (i = 0; i < face2->numedges; i++) + { + edgenum = abs((*aasworld).edgeindex[face2->firstedge + i]); + edge = &(*aasworld).edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane1->normal, (*aasworld).vertexes[edge->v[j]]) - + plane1->dist < -0.01) return qtrue; + } //end for + } //end for + + return qfalse; +} //end of the function AAS_NonConvexFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeAreas(int *areanums, int numareas) +{ + int i, j, s, face1num, face2num, side1, side2, fn1, fn2; + aas_face_t *face1, *face2; + aas_area_t *area1, *area2; + + for (i = 0; i < numareas; i++) + { + area1 = &(*aasworld).areas[areanums[i]]; + for (fn1 = 0; fn1 < area1->numfaces; fn1++) + { + face1num = abs((*aasworld).faceindex[area1->firstface + fn1]); + face1 = &(*aasworld).faces[face1num]; + side1 = face1->frontarea != areanums[i]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == i) continue; + if (face1->frontarea == s || face1->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + for (j = 0; j < numareas; j++) + { + if (j == i) continue; + area2 = &(*aasworld).areas[areanums[j]]; + for (fn2 = 0; fn2 < area2->numfaces; fn2++) + { + face2num = abs((*aasworld).faceindex[area2->firstface + fn2]); + face2 = &(*aasworld).faces[face2num]; + side2 = face2->frontarea != areanums[j]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == j) continue; + if (face2->frontarea == s || face2->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + if (AAS_NonConvexFaces(face1, face2, side1, side2)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_NonConvexEdges(aas_edge_t *edge1, aas_edge_t *edge2, int side1, int side2, int planenum) +{ + int i; + vec3_t edgevec1, edgevec2, normal1, normal2; + float dist1, dist2; + aas_plane_t *plane; + + plane = &(*aasworld).planes[planenum]; + VectorSubtract((*aasworld).vertexes[edge1->v[1]], (*aasworld).vertexes[edge1->v[0]], edgevec1); + VectorSubtract((*aasworld).vertexes[edge2->v[1]], (*aasworld).vertexes[edge2->v[0]], edgevec2); + if (side1) VectorInverse(edgevec1); + if (side2) VectorInverse(edgevec2); + // + CrossProduct(edgevec1, plane->normal, normal1); + dist1 = DotProduct(normal1, (*aasworld).vertexes[edge1->v[0]]); + CrossProduct(edgevec2, plane->normal, normal2); + dist2 = DotProduct(normal2, (*aasworld).vertexes[edge2->v[0]]); + + for (i = 0; i < 2; i++) + { + if (DotProduct((*aasworld).vertexes[edge1->v[i]], normal2) - dist2 < -0.01) return qfalse; + } //end for + for (i = 0; i < 2; i++) + { + if (DotProduct((*aasworld).vertexes[edge2->v[i]], normal1) - dist1 < -0.01) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_NonConvexEdges +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeFaces(int *facenums, int numfaces, int planenum) +{ + int i, j, s, edgenum1, edgenum2, side1, side2, en1, en2, ens; + aas_face_t *face1, *face2, *otherface; + aas_edge_t *edge1, *edge2; + + for (i = 0; i < numfaces; i++) + { + face1 = &(*aasworld).faces[facenums[i]]; + for (en1 = 0; en1 < face1->numedges; en1++) + { + edgenum1 = (*aasworld).edgeindex[face1->firstedge + en1]; + side1 = (edgenum1 < 0) ^ (face1->planenum != planenum); + edgenum1 = abs(edgenum1); + edge1 = &(*aasworld).edges[edgenum1]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &(*aasworld).faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum1 == abs((*aasworld).edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + for (j = 0; j < numfaces; j++) + { + if (j == i) continue; + face2 = &(*aasworld).faces[facenums[j]]; + for (en2 = 0; en2 < face2->numedges; en2++) + { + edgenum2 = (*aasworld).edgeindex[face2->firstedge + en2]; + side2 = (edgenum2 < 0) ^ (face2->planenum != planenum); + edgenum2 = abs(edgenum2); + edge2 = &(*aasworld).edges[edgenum2]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &(*aasworld).faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum2 == abs((*aasworld).edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + if (AAS_NonConvexEdges(edge1, edge2, side1, side2, planenum)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeFaces*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ConnectedAreas_r(int *areanums, int numareas, int *connectedareas, int curarea) +{ + int i, j, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + connectedareas[curarea] = qtrue; + area = &(*aasworld).areas[areanums[curarea]]; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs((*aasworld).faceindex[area->firstface + i]); + face = &(*aasworld).faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) + { + continue; + } + //get the area at the other side of the face + if (face->frontarea != areanums[curarea]) + { + otherareanum = face->frontarea; + } + else + { + otherareanum = face->backarea; + } + //check if the face is leading to one of the other areas + for (j = 0; j < numareas; j++) + { + if (areanums[j] == otherareanum) + { + break; + } + } //end for + //if the face isn't leading to one of the other areas + if (j == numareas) + { + continue; + } + //if the other area is already connected + if (connectedareas[j]) + { + continue; + } + //recursively proceed with the other area + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, j); + } //end for +} //end of the function AAS_ConnectedAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ConnectedAreas(int *areanums, int numareas) +{ + int connectedareas[MAX_PORTALAREAS], i; + + memset(connectedareas, 0, sizeof(connectedareas)); + if (numareas < 1) + { + return qfalse; + } + if (numareas == 1) + { + return qtrue; + } + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, 0); + for (i = 0; i < numareas; i++) + { + if (!connectedareas[i]) + { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_ConnectedAreas +//=========================================================================== +// gets adjacent areas with less presence types recursively +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAdjacentAreasWithLessPresenceTypes_r(int *areanums, int numareas, int curareanum) +{ + int i, j, presencetype, otherpresencetype, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + areanums[numareas++] = curareanum; + area = &(*aasworld).areas[curareanum]; + presencetype = (*aasworld).areasettings[curareanum].presencetype; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs((*aasworld).faceindex[area->firstface + i]); + face = &(*aasworld).faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) + { + continue; + } + //the area at the other side of the face + if (face->frontarea != curareanum) + { + otherareanum = face->frontarea; + } + else + { + otherareanum = face->backarea; + } + // + otherpresencetype = (*aasworld).areasettings[otherareanum].presencetype; + //if the other area has less presence types + if ((presencetype & ~otherpresencetype) && + !(otherpresencetype & ~presencetype)) + { + //check if the other area isn't already in the list + for (j = 0; j < numareas; j++) + { + if (otherareanum == areanums[j]) + { + break; + } + } //end for + //if the other area isn't already in the list + if (j == numareas) + { + if (numareas >= MAX_PORTALAREAS) + { + AAS_Error("MAX_PORTALAREAS"); + return numareas; + } //end if + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, numareas, otherareanum); + } //end if + } //end if + } //end for + return numareas; +} //end of the function AAS_GetAdjacentAreasWithLessPresenceTypes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CheckAreaForPossiblePortals(int areanum) +{ + int i, j, k, fen, ben, frontedgenum, backedgenum, facenum; + int areanums[MAX_PORTALAREAS], numareas, otherareanum; + int numareafrontfaces[MAX_PORTALAREAS], numareabackfaces[MAX_PORTALAREAS]; + int frontfacenums[MAX_PORTALAREAS], backfacenums[MAX_PORTALAREAS]; + int numfrontfaces, numbackfaces; + int frontareanums[MAX_PORTALAREAS], backareanums[MAX_PORTALAREAS]; + int numfrontareas, numbackareas; + int frontplanenum, backplanenum, faceplanenum; + aas_area_t *area; + aas_face_t *frontface, *backface, *face; + + //if it isn't already a portal + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + return 0; + } + //it must be a grounded area + if (!((*aasworld).areasettings[areanum].areaflags & AREA_GROUNDED)) + { + return 0; + } + // + memset(numareafrontfaces, 0, sizeof(numareafrontfaces)); + memset(numareabackfaces, 0, sizeof(numareabackfaces)); + numareas = numfrontfaces = numbackfaces = 0; + numfrontareas = numbackareas = 0; + frontplanenum = backplanenum = -1; + //add any adjacent areas with less presence types + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, 0, areanum); + // + for (i = 0; i < numareas; i++) + { + area = &(*aasworld).areas[areanums[i]]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs((*aasworld).faceindex[area->firstface + j]); + face = &(*aasworld).faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) + { + continue; + } + //check if the face is shared with one of the other areas + for (k = 0; k < numareas; k++) + { + if (k == i) + { + continue; + } + if (face->frontarea == areanums[k] || face->backarea == areanums[k]) + { + break; + } + } //end for + //if the face is shared + if (k != numareas) + { + continue; + } + //the number of the area at the other side of the face + if (face->frontarea == areanums[i]) + { + otherareanum = face->backarea; + } + else + { + otherareanum = face->frontarea; + } + //if the other area already is a cluter portal + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + return 0; + } + //number of the plane of the area + faceplanenum = face->planenum & ~1; + // + if (frontplanenum < 0 || faceplanenum == frontplanenum) + { + frontplanenum = faceplanenum; + frontfacenums[numfrontfaces++] = facenum; + for (k = 0; k < numfrontareas; k++) + { + if (frontareanums[k] == otherareanum) + { + break; + } + } //end for + if (k == numfrontareas) + { + frontareanums[numfrontareas++] = otherareanum; + } + numareafrontfaces[i]++; + } //end if + else if (backplanenum < 0 || faceplanenum == backplanenum) + { + backplanenum = faceplanenum; + backfacenums[numbackfaces++] = facenum; + for (k = 0; k < numbackareas; k++) + { + if (backareanums[k] == otherareanum) + { + break; + } + } //end for + if (k == numbackareas) + { + backareanums[numbackareas++] = otherareanum; + } + numareabackfaces[i]++; + } //end else + else + { + return 0; + } //end else + } //end for + } //end for + //every area should have at least one front face and one back face + for (i = 0; i < numareas; i++) + { + if (!numareafrontfaces[i] || !numareabackfaces[i]) + { + return 0; + } + } //end for + //the front areas should all be connected + if (!AAS_ConnectedAreas(frontareanums, numfrontareas)) + { + return 0; + } + //the back areas should all be connected + if (!AAS_ConnectedAreas(backareanums, numbackareas)) + { + return 0; + } + //none of the front faces should have a shared edge with a back face + for (i = 0; i < numfrontfaces; i++) + { + frontface = &(*aasworld).faces[frontfacenums[i]]; + for (fen = 0; fen < frontface->numedges; fen++) + { + frontedgenum = abs((*aasworld).edgeindex[frontface->firstedge + fen]); + for (j = 0; j < numbackfaces; j++) + { + backface = &(*aasworld).faces[backfacenums[j]]; + for (ben = 0; ben < backface->numedges; ben++) + { + backedgenum = abs((*aasworld).edgeindex[backface->firstedge + ben]); + if (frontedgenum == backedgenum) + { + break; + } + } //end for + if (ben != backface->numedges) + { + break; + } + } //end for + if (j != numbackfaces) + { + break; + } + } //end for + if (fen != frontface->numedges) + { + break; + } + } //end for + if (i != numfrontfaces) + { + return 0; + } + //set the cluster portal contents + for (i = 0; i < numareas; i++) + { + (*aasworld).areasettings[areanums[i]].contents |= AREACONTENTS_CLUSTERPORTAL; + //this area can be used as a route portal + (*aasworld).areasettings[areanums[i]].contents |= AREACONTENTS_ROUTEPORTAL; + Log_Write("possible portal: %d\r\n", areanums[i]); + } //end for + // + return numareas; +} //end of the function AAS_CheckAreaForPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FindPossiblePortals(void) +{ + int i, numpossibleportals; + + numpossibleportals = 0; + for (i = 1; i < (*aasworld).numareas; i++) + { + numpossibleportals += AAS_CheckAreaForPossiblePortals(i); + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d possible portals\n", numpossibleportals); +} //end of the function AAS_FindPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveAllPortals(void) +{ + int i; + + for (i = 1; i < (*aasworld).numareas; i++) + { + (*aasworld).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + } //end for +} //end of the function AAS_RemoveAllPortals +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodCluster_r(int areanum, int clusternum) +{ + int i, otherareanum; + aas_face_t *face; + aas_area_t *area; + + //set cluster mark + (*aasworld).areasettings[areanum].cluster = clusternum; + //if the area is a portal + //if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return; + // + area = &(*aasworld).areas[areanum]; + //use area faces to flood into adjacent areas + for (i = 0; i < area->numfaces; i++) + { + face = &(*aasworld).faces[abs((*aasworld).faceindex[area->firstface + i])]; + // + if (face->frontarea != areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if there's no area at the other side + if (!otherareanum) continue; + //if the area is a portal + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if ((*aasworld).areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for + //use the reachabilities to flood into other areas + for (i = 0; i < (*aasworld).areasettings[areanum].numreachableareas; i++) + { + otherareanum = (*aasworld).reachability[ + (*aasworld).areasettings[areanum].firstreachablearea + i].areanum; + if (!otherareanum) + { + continue; + AAS_Error("reachability %d has zero area\n", (*aasworld).areasettings[areanum].firstreachablearea + i); + } //end if + //if the area is a portal + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if ((*aasworld).areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for +} //end of the function AAS_FloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < (*aasworld).numareas; i++) + { + for (j = 0; j < (*aasworld).areasettings[i].numreachableareas; j++) + { + areanum = (*aasworld).reachability[(*aasworld).areasettings[i].firstreachablearea + j].areanum; + if ((*aasworld).reachability[(*aasworld).areasettings[i].firstreachablearea + j].traveltype == TRAVEL_TELEPORT) + { + (*aasworld).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + (*aasworld).areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_RemoveTeleporterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodClusterReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < (*aasworld).numareas; i++) + { + //if this area already has a cluster set + if ((*aasworld).areasettings[i].cluster) continue; + //if this area is a cluster portal + if ((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //loop over the reachable areas from this area + for (j = 0; j < (*aasworld).areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = (*aasworld).reachability[(*aasworld).areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if this area has a cluster set + if ((*aasworld).areasettings[areanum].cluster == clusternum) + { + AAS_FloodCluster_r(i, clusternum); + i = 0; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_FloodClusterReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, k, facenum, otherareanum, nonclosingportals; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < (*aasworld).numareas; i++) + { + if (!((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &(*aasworld).areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs((*aasworld).faceindex[area->firstface + j]); + face = &(*aasworld).faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + //reset all cluster fields + AAS_RemoveClusterAreas(); + // + AAS_FloodCluster_r(otherareanum, 1); + AAS_FloodClusterReachabilities(1); + //check if all adjacent non-portal areas have a cluster set + for (k = 0; k < area->numfaces; k++) + { + facenum = abs((*aasworld).faceindex[area->firstface + k]); + face = &(*aasworld).faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + if (!(*aasworld).areasettings[otherareanum].cluster) break; + } //end for + //if all adjacent non-portal areas have a cluster set then the portal + //didn't seal a cluster + if (k >= area->numfaces) + { + (*aasworld).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + break; + } //end if + } //end for + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, facenum, otherareanum, nonclosingportals, numseperatedclusters; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < (*aasworld).numareas; i++) + { + if (!((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + // + numseperatedclusters = 0; + //reset all cluster fields + AAS_RemoveClusterAreas(); + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &(*aasworld).areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs((*aasworld).faceindex[area->firstface + j]); + face = &(*aasworld).faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if not solid at the other side of the face + if (!otherareanum) continue; + //don't flood into other portals + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if ((*aasworld).areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //use the reachabilities to flood into other areas + for (j = 0; j < (*aasworld).areasettings[i].numreachableareas; j++) + { + otherareanum = (*aasworld).reachability[ + (*aasworld).areasettings[i].firstreachablearea + j].areanum; + //this should never be qtrue but we check anyway + if (!otherareanum) continue; + //don't flood into other portals + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if ((*aasworld).areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //a portal must seperate no more and no less than 2 clusters + if (numseperatedclusters != 2) + { + (*aasworld).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void AAS_AddTeleporterPortals(void) +{ + int j, area2num, facenum, otherareanum; + char *target, *targetname, *classname; + bsp_entity_t *entities, *ent, *dest; + vec3_t origin, destorigin, mins, maxs, end; + vec3_t bbmins, bbmaxs; + aas_area_t *area; + aas_face_t *face; + aas_trace_t trace; + aas_link_t *areas, *link; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without origin\n", target); + continue; + } //end if + // + target = AAS_ValueForBSPEpairKey(ent, "target"); + if (!target) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without target\n", target); + continue; + } //end if + for (dest = entities; dest; dest = dest->next) + { + classname = AAS_ValueForBSPEpairKey(dest, "classname"); + if (classname && !strcmp(classname, "misc_teleporter_dest")) + { + targetname = AAS_ValueForBSPEpairKey(dest, "targetname"); + if (targetname && !strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without destination (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground + VectorCopy(destorigin, end); + end[2] -= 100; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + VectorCopy(trace.endpos, destorigin); + area2num = AAS_PointAreaNum(destorigin); + //reset all cluster fields + for (j = 0; j < (*aasworld).numareas; j++) + { + (*aasworld).areasettings[j].cluster = 0; + } //end for + // + VectorSet(mins, -8, -8, 8); + VectorSet(maxs, 8, 8, 24); + // + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + // + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + //add bounding box size + VectorSubtract(mins, bbmaxs, mins); + VectorSubtract(maxs, bbmins, maxs); + //link an invalid (-1) entity + areas = AAS_AASLinkEntity(mins, maxs, -1); + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + (*aasworld).areasettings[link->areanum].contents |= AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL; + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &(*aasworld).areas[link->areanum]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs((*aasworld).faceindex[area->firstface + j]); + face = &(*aasworld).faces[facenum]; + // + if (face->frontarea != link->areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if ((*aasworld).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + AAS_FloodCluster_r(otherareanum, 1); + } //end for + } //end for + //if the teleport destination IS in the same cluster + if ((*aasworld).areasettings[area2num].cluster) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + (*aasworld).areasettings[link->areanum].contents &= ~(AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL); + } //end for + } //end if + } //end if + } //end for + AAS_FreeBSPEntities(entities); +} //end of the function AAS_AddTeleporterPortals*/ +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < (*aasworld).numareas; i++) + { + for (j = 0; j < (*aasworld).areasettings[i].numreachableareas; j++) + { + if ((*aasworld).reachability[(*aasworld).areasettings[i].firstreachablearea + j].traveltype != TRAVEL_TELEPORT) continue; + areanum = (*aasworld).reachability[(*aasworld).areasettings[i].firstreachablearea + j].areanum; + (*aasworld).areasettings[areanum].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end for + } //end for +} //end of the function AAS_AddTeleporterPortals*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TestPortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < (*aasworld).numportals; i++) + { + portal = &(*aasworld).portals[i]; + if (!portal->frontcluster) + { + (*aasworld).areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no front cluster\r\n", portal->areanum); + return qfalse; + } //end if + if (!portal->backcluster) + { + (*aasworld).areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no back cluster\r\n", portal->areanum); + return qfalse; + } //end if + } //end for + return qtrue; +} //end of the function +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CountForcedClusterPortals(void) +{ + int num, i; + + num = 0; + for (i = 1; i < (*aasworld).numareas; i++) + { + if ((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + num++; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "%6d forced portals\n", num); +} //end of the function AAS_CountForcedClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateViewPortals(void) +{ + int i; + + for (i = 1; i < (*aasworld).numareas; i++) + { + if ((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + (*aasworld).areasettings[i].contents |= AREACONTENTS_VIEWPORTAL; + } //end if + } //end for +} //end of the function AAS_CreateViewPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetViewPortalsAsClusterPortals(void) +{ + int i; + + for (i = 1; i < (*aasworld).numareas; i++) + { + if ((*aasworld).areasettings[i].contents & AREACONTENTS_VIEWPORTAL) + { + (*aasworld).areasettings[i].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end if + } //end for +} //end of the function AAS_SetViewPortalsAsClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClustering(void) +{ + int i, removedPortalAreas; + int n, total, numreachabilityareas; + + if (!(*aasworld).loaded) + { + return; + } + //if there are clusters + if ((*aasworld).numclusters >= 1) + { +#ifndef BSPC + + if ((*aasworld).clusterTeamTravelFlags) + { + FreeMemory((*aasworld).clusterTeamTravelFlags); + } + (*aasworld).clusterTeamTravelFlags = (int *) GetClearedMemory((*aasworld).numclusters * sizeof(int)); + + //if clustering isn't forced + if (!((int)LibVarGetValue("forceclustering")) && + !((int)LibVarGetValue("forcereachability"))) + { + return; + } +#else + return; +#endif + } //end if + // + AAS_CountForcedClusterPortals(); + //remove all the existing portals + //AAS_RemoveAllPortals(); + //remove all area cluster marks + AAS_RemoveClusterAreas(); + //find possible cluster portals + AAS_FindPossiblePortals(); + //craete portals to for the bot view + AAS_CreateViewPortals(); + //remove all portals that are not closing a cluster + //AAS_RemoveNotClusterClosingPortals(); + //initialize portal memory + if ((*aasworld).portals) + { + FreeMemory((*aasworld).portals); + } + (*aasworld).portals = (aas_portal_t *) GetClearedMemory(AAS_MAX_PORTALS * sizeof(aas_portal_t)); + //initialize portal index memory + if ((*aasworld).portalindex) + { + FreeMemory((*aasworld).portalindex); + } + (*aasworld).portalindex = (aas_portalindex_t *) GetClearedMemory(AAS_MAX_PORTALINDEXSIZE * sizeof(aas_portalindex_t)); + //initialize cluster memory + if ((*aasworld).clusters) + { + FreeMemory((*aasworld).clusters); + } + (*aasworld).clusters = (aas_cluster_t *) GetClearedMemory(AAS_MAX_CLUSTERS * sizeof(aas_cluster_t)); + // + if ((*aasworld).clusterTeamTravelFlags) + { + FreeMemory((*aasworld).clusterTeamTravelFlags); + } + (*aasworld).clusterTeamTravelFlags = (int *) GetClearedMemory(AAS_MAX_CLUSTERS * sizeof(int)); + // + removedPortalAreas = 0; + botimport.Print(PRT_MESSAGE, "\r%6d removed portal areas", removedPortalAreas); + while (1) + { + botimport.Print(PRT_MESSAGE, "\r%6d", removedPortalAreas); + //initialize the number of portals and clusters + (*aasworld).numportals = 1; //portal 0 is a dummy + (*aasworld).portalindexsize = 0; + (*aasworld).numclusters = 1; //cluster 0 is a dummy + //create the portals from the portal areas + AAS_CreatePortals(); + // + removedPortalAreas++; + //find the clusters + if (!AAS_FindClusters()) + { + continue; + } + //test the portals + if (!AAS_TestPortals()) + { + continue; + } + // + break; + } //end while + botimport.Print(PRT_MESSAGE, "\n"); + //the AAS file should be saved + (*aasworld).savefile = qtrue; + // report cluster info + botimport.Print(PRT_MESSAGE, "%6d portals created\n", (*aasworld).numportals); + botimport.Print(PRT_MESSAGE, "%6d clusters created\n", (*aasworld).numclusters); + for (i = 1; i < (*aasworld).numclusters; i++) + { + botimport.Print(PRT_MESSAGE, "cluster %d has %d reachability areas\n", i, + (*aasworld).clusters[i].numreachabilityareas); + } //end for + // report AAS file efficiency + numreachabilityareas = 0; + total = 0; + for (i = 0; i < (*aasworld).numclusters; i++) + { + n = (*aasworld).clusters[i].numreachabilityareas; + numreachabilityareas += n; + total += n * n; + } + total += numreachabilityareas * (*aasworld).numportals; + // + botimport.Print(PRT_MESSAGE, "%6i total reachability areas\n", numreachabilityareas); + botimport.Print(PRT_MESSAGE, "%6i AAS memory/CPU usage (the lower the better)\n", total * 3); +} //end of the function AAS_InitClustering diff --git a/src/botlib/be_aas_cluster.h b/src/botlib/be_aas_cluster.h new file mode 100644 index 000000000..b3b005e0d --- /dev/null +++ b/src/botlib/be_aas_cluster.h @@ -0,0 +1,38 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_cluster.h + * @brief AAS + */ + +#ifdef AASINTERN +//initialize the AAS clustering +void AAS_InitClustering(void); +#endif //AASINTERN diff --git a/src/botlib/be_aas_debug.c b/src/botlib/be_aas_debug.c new file mode 100644 index 000000000..34f932467 --- /dev/null +++ b/src/botlib/be_aas_debug.c @@ -0,0 +1,802 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_debug.c + * @brief AAS debug code + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +#define MAX_DEBUGLINES 1024 + +int debuglines[MAX_DEBUGLINES]; +int debuglinevisible[MAX_DEBUGLINES]; +int numdebuglines; + +static bot_debugpoly_t *debugpolygons[MAX_DEBUGPOLYS]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownPolygons(void) +{ + int i; + for (i = 0; i < MAX_DEBUGPOLYS; i++) + { + if (debugpolygons[i]) + { + botimport.DebugPolygonDeletePointer(debugpolygons[i]); + } + debugpolygons[i] = NULL; + } +} //end of the function AAS_ClearShownPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_debugpoly_t *AAS_GetDebugPolygon(void) +{ + int i; + + for (i = 0; i < MAX_DEBUGPOLYS; i++) + { + if (!debugpolygons[i]) + { + debugpolygons[i] = botimport.DebugPolygonGetFree(); + + return debugpolygons[i]; + } + } + + return NULL; +} //end of the function AAS_GetDebugPolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownDebugLines(void) +{ + int i; + + //make all lines invisible + for (i = 0; i < MAX_DEBUGLINES; i++) + { + if (debuglines[i]) + { + //botimport.DebugLineShow(debuglines[i], NULL, NULL, LINECOLOR_NONE); + botimport.DebugLineDelete(debuglines[i]); + debuglines[i] = 0; + debuglinevisible[i] = qfalse; + } //end if + } //end for +} //end of the function AAS_ClearShownDebugLines +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DebugLine(vec3_t start, vec3_t end, int color) +{ + int line; + + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + botimport.DebugLineShow(debuglines[line], start, end, color); + debuglinevisible[line] = qtrue; + return; + } //end else + } //end for +} //end of the function AAS_DebugLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PermanentLine(vec3_t start, vec3_t end, int color) +{ + int line; + + line = botimport.DebugLineCreate(); + botimport.DebugLineShow(line, start, end, color); +} //end of the function AAS_PermenentLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPermanentCross(vec3_t origin, float size, int color) +{ + int i, debugline; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow(debugline, start, end, color); + } //end for +} //end of the function AAS_DrawPermanentCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color) +{ + int n0, n1, n2, j, line, lines[2]; + vec3_t start1, end1, start2, end2; + + //make a cross in the hit plane at the hit point + VectorCopy(point, start1); + VectorCopy(point, end1); + VectorCopy(point, start2); + VectorCopy(point, end2); + + n0 = type % 3; + n1 = (type + 1) % 3; + n2 = (type + 2) % 3; + start1[n1] -= 6; + start1[n2] -= 6; + end1[n1] += 6; + end1[n2] += 6; + start2[n1] += 6; + start2[n2] -= 6; + end2[n1] -= 6; + end2[n2] += 6; + + start1[n0] = (dist - (start1[n1] * normal[n1] + + start1[n2] * normal[n2])) / normal[n0]; + end1[n0] = (dist - (end1[n1] * normal[n1] + + end1[n2] * normal[n2])) / normal[n0]; + start2[n0] = (dist - (start2[n1] * normal[n1] + + start2[n2] * normal[n2])) / normal[n0]; + end2[n0] = (dist - (end2[n1] * normal[n1] + + end2[n2] * normal[n2])) / normal[n0]; + + for (j = 0, line = 0; j < 2 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + botimport.DebugLineShow(lines[0], start1, end1, color); + botimport.DebugLineShow(lines[1], start2, end2, color); +} //end of the function AAS_DrawPlaneCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t bboxcorners[8]; + int lines[3]; + int i, j, line; + + //upper corners + bboxcorners[0][0] = origin[0] + maxs[0]; + bboxcorners[0][1] = origin[1] + maxs[1]; + bboxcorners[0][2] = origin[2] + maxs[2]; + // + bboxcorners[1][0] = origin[0] + mins[0]; + bboxcorners[1][1] = origin[1] + maxs[1]; + bboxcorners[1][2] = origin[2] + maxs[2]; + // + bboxcorners[2][0] = origin[0] + mins[0]; + bboxcorners[2][1] = origin[1] + mins[1]; + bboxcorners[2][2] = origin[2] + maxs[2]; + // + bboxcorners[3][0] = origin[0] + maxs[0]; + bboxcorners[3][1] = origin[1] + mins[1]; + bboxcorners[3][2] = origin[2] + maxs[2]; + //lower corners + memcpy(bboxcorners[4], bboxcorners[0], sizeof(vec3_t) * 4); + for (i = 0; i < 4; i++) + bboxcorners[4 + i][2] = origin[2] + mins[2]; + //draw bounding box + for (i = 0; i < 4; i++) + { + for (j = 0, line = 0; j < 3 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + //top plane + botimport.DebugLineShow(lines[0], bboxcorners[i], + bboxcorners[(i + 1) & 3], LINECOLOR_RED); + //bottom plane + botimport.DebugLineShow(lines[1], bboxcorners[4 + i], + bboxcorners[4 + ((i + 1) & 3)], LINECOLOR_RED); + //vertical lines + botimport.DebugLineShow(lines[2], bboxcorners[i], + bboxcorners[4 + i], LINECOLOR_RED); + } //end for +} //end of the function AAS_ShowBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFace(int facenum) +{ + int i, color, edgenum; + aas_edge_t *edge; + aas_face_t *face; + aas_plane_t *plane; + vec3_t start, end; + + color = LINECOLOR_YELLOW; + //check if face number is in range + if (facenum >= (*aasworld).numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &(*aasworld).faces[facenum]; + //walk through the edges of the face + for (i = 0; i < face->numedges; i++) + { + //edge number + edgenum = abs((*aasworld).edgeindex[face->firstedge + i]); + //check if edge number is in range + if (edgenum >= (*aasworld).numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + edge = &(*aasworld).edges[edgenum]; + if (color == LINECOLOR_RED) + { + color = LINECOLOR_GREEN; + } + else if (color == LINECOLOR_GREEN) + { + color = LINECOLOR_BLUE; + } + else if (color == LINECOLOR_BLUE) + { + color = LINECOLOR_YELLOW; + } + else + { + color = LINECOLOR_RED; + } + AAS_DebugLine((*aasworld).vertexes[edge->v[0]], + (*aasworld).vertexes[edge->v[1]], + color); + } //end for + plane = &(*aasworld).planes[face->planenum]; + edgenum = abs((*aasworld).edgeindex[face->firstedge]); + edge = &(*aasworld).edges[edgenum]; + VectorCopy((*aasworld).vertexes[edge->v[0]], start); + VectorMA(start, 20, plane->normal, end); + AAS_DebugLine(start, end, LINECOLOR_RED); +} //end of the function AAS_ShowFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFacePolygon(int facenum, int color, int flip) +{ + int i, edgenum; + aas_edge_t *edge; + aas_face_t *face; + + vec3_t points[128]; + int numpoints = 0; + + //check if face number is in range + if (facenum >= aasworld->numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } + + //walk through the edges of the face + face = &(aasworld->faces[facenum]); + + if (flip) + { + for (i = face->numedges - 1; i >= 0; i--) + { + edgenum = aasworld->edgeindex[face->firstedge + i]; + edge = &(aasworld->edges[abs(edgenum)]); + + VectorCopy(aasworld->vertexes[edge->v[edgenum < 0]], points[numpoints]); + + numpoints++; + } //end for + } + else + { + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld->edgeindex[face->firstedge + i]; + edge = &(aasworld->edges[abs(edgenum)]); + + VectorCopy(aasworld->vertexes[edge->v[edgenum < 0]], points[numpoints]); + + numpoints++; + } + } + + botimport.BotDrawPolygon(color, numpoints, (float *) points); +} //end of the function AAS_ShowFacePolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowArea(int areanum, int groundfacesonly) +{ + int areaedges[MAX_DEBUGLINES]; + int numareaedges, i, j, n, color = 0, line; + int facenum, edgenum; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + + // + numareaedges = 0; + // + if (areanum < 0 || areanum >= (*aasworld).numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, (*aasworld).numareas); + return; + } //end if + //pointer to the convex area + area = &(*aasworld).areas[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs((*aasworld).faceindex[area->firstface + i]); + //check if face number is in range + if (facenum >= (*aasworld).numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &(*aasworld).faces[facenum]; + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) + { + continue; + } + } //end if + //walk through the edges of the face + for (j = 0; j < face->numedges; j++) + { + //edge number + edgenum = abs((*aasworld).edgeindex[face->firstedge + j]); + //check if edge number is in range + if (edgenum >= (*aasworld).numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + //check if the edge is stored already + for (n = 0; n < numareaedges; n++) + { + if (areaedges[n] == edgenum) + { + break; + } + } //end for + if (n == numareaedges && numareaedges < MAX_DEBUGLINES) + { + areaedges[numareaedges++] = edgenum; + } //end if + } //end for + //AAS_ShowFace(facenum); + } //end for + //draw all the edges + for (n = 0; n < numareaedges; n++) + { + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + break; + } //end else + } //end for + if (line >= MAX_DEBUGLINES) + { + return; + } + edge = &(*aasworld).edges[areaedges[n]]; + if (color == LINECOLOR_RED) + { + color = LINECOLOR_BLUE; + } + else if (color == LINECOLOR_BLUE) + { + color = LINECOLOR_GREEN; + } + else if (color == LINECOLOR_GREEN) + { + color = LINECOLOR_YELLOW; + } + else + { + color = LINECOLOR_RED; + } + botimport.DebugLineShow(debuglines[line], + (*aasworld).vertexes[edge->v[0]], + (*aasworld).vertexes[edge->v[1]], + color); + debuglinevisible[line] = qtrue; + } //end for*/ +} //end of the function AAS_ShowArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face; + + if (areanum < 0 || areanum >= aasworld->numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", areanum, aasworld->numareas); + return; + } + + //pointer to the convex area + area = &(aasworld->areas[areanum]); + + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld->faceindex[area->firstface + i]); + + //check if face number is in range + if (facenum >= aasworld->numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } + + face = &(aasworld->faces[facenum]); + + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) + { + continue; + } + } + + AAS_ShowFacePolygon(facenum, color, face->frontarea != areanum); + } +} //end of the function AAS_ShowAreaPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawCross(vec3_t origin, float size, int color) +{ + int i; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + } //end for +} //end of the function AAS_DrawCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PrintTravelType(int traveltype) +{ +#ifdef DEBUG + char *str; + // + switch (traveltype) + { + case TRAVEL_INVALID: + str = "TRAVEL_INVALID"; + break; + case TRAVEL_WALK: + str = "TRAVEL_WALK"; + break; + case TRAVEL_CROUCH: + str = "TRAVEL_CROUCH"; + break; + case TRAVEL_BARRIERJUMP: + str = "TRAVEL_BARRIERJUMP"; + break; + case TRAVEL_JUMP: + str = "TRAVEL_JUMP"; + break; + case TRAVEL_LADDER: + str = "TRAVEL_LADDER"; + break; + case TRAVEL_WALKOFFLEDGE: + str = "TRAVEL_WALKOFFLEDGE"; + break; + case TRAVEL_SWIM: + str = "TRAVEL_SWIM"; + break; + case TRAVEL_WATERJUMP: + str = "TRAVEL_WATERJUMP"; + break; + case TRAVEL_TELEPORT: + str = "TRAVEL_TELEPORT"; + break; + case TRAVEL_ELEVATOR: + str = "TRAVEL_ELEVATOR"; + break; + case TRAVEL_ROCKETJUMP: + str = "TRAVEL_ROCKETJUMP"; + break; + case TRAVEL_BFGJUMP: + str = "TRAVEL_BFGJUMP"; + break; + case TRAVEL_GRAPPLEHOOK: + str = "TRAVEL_GRAPPLEHOOK"; + break; + case TRAVEL_JUMPPAD: + str = "TRAVEL_JUMPPAD"; + break; + case TRAVEL_FUNCBOB: + str = "TRAVEL_FUNCBOB"; + break; + default: + str = "UNKNOWN TRAVEL TYPE"; + break; + } //end switch + botimport.Print(PRT_MESSAGE, "%s", str); +#endif //DEBUG +} //end of the function AAS_PrintTravelType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor) +{ + vec3_t dir, cross, p1, p2, up = { 0, 0, 1 }; + float dot; + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + dot = DotProduct(dir, up); + if (dot > 0.99 || dot < -0.99) + { + VectorSet(cross, 1, 0, 0); + } + else + { + CrossProduct(dir, up, cross); + } + + VectorMA(end, -6, dir, p1); + VectorCopy(p1, p2); + VectorMA(p1, 6, cross, p1); + VectorMA(p2, -6, cross, p2); + + AAS_DebugLine(start, end, linecolor); + AAS_DebugLine(p1, end, arrowcolor); + AAS_DebugLine(p2, end, arrowcolor); +} //end of the function AAS_DrawArrow +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachability(aas_reachability_t *reach) +{ + vec3_t dir, cmdmove, velocity; + float speed, zvel; + aas_clientmove_t move; + + AAS_ShowAreaPolygons(reach->areanum, 5, qtrue); + //AAS_ShowArea(reach->areanum, qtrue); + AAS_DrawArrow(reach->start, reach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); + // + if (reach->traveltype == TRAVEL_JUMP || reach->traveltype == TRAVEL_WALKOFFLEDGE) + { + AAS_HorizontalVelocityForJump(aassettings.sv_jumpvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + VectorScale(dir, speed, velocity); + //set the command movement + VectorClear(cmdmove); + cmdmove[2] = aassettings.sv_jumpvel; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1, + SE_HITGROUND | SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE, 0, qtrue); + // + if (reach->traveltype == TRAVEL_JUMP) + { + AAS_JumpReachRunStart(reach, dir); + AAS_DrawCross(dir, 4, LINECOLOR_BLUE); + } //end if + } //end if + else if (reach->traveltype == TRAVEL_ROCKETJUMP) + { + zvel = AAS_RocketJumpZVelocity(reach->start); + AAS_HorizontalVelocityForJump(zvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if + else if (reach->traveltype == TRAVEL_JUMPPAD) + { + VectorSet(cmdmove, 0, 0, 0); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + //NOTE: the edgenum is the horizontal velocity + VectorScale(dir, reach->edgenum, velocity); + //NOTE: the facenum is the Z velocity + velocity[2] = reach->facenum; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if +} //end of the function AAS_ShowReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachableAreas(int areanum) +{ + aas_areasettings_t *settings; + static aas_reachability_t reach; + static int index, lastareanum; + static float lasttime; + + if (areanum != lastareanum) + { + index = 0; + lastareanum = areanum; + } //end if + settings = &(*aasworld).areasettings[areanum]; + // + if (!settings->numreachableareas) + { + return; + } + // + if (index >= settings->numreachableareas) + { + index = 0; + } + // + if (AAS_Time() - lasttime > 1.5) + { + memcpy(&reach, &(*aasworld).reachability[settings->firstreachablearea + index], sizeof(aas_reachability_t)); + index++; + lasttime = AAS_Time(); + AAS_PrintTravelType(reach.traveltype); + botimport.Print(PRT_MESSAGE, "(traveltime: %i)\n", reach.traveltime); + } //end if + AAS_ShowReachability(&reach); +} //end of the function ShowReachableAreas diff --git a/src/botlib/be_aas_debug.h b/src/botlib/be_aas_debug.h new file mode 100644 index 000000000..e64cd9f56 --- /dev/null +++ b/src/botlib/be_aas_debug.h @@ -0,0 +1,64 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_debug.h + * @brief AAS debug code + */ + +//clear the shown debug lines +void AAS_ClearShownDebugLines(void); +// +void AAS_ClearShownPolygons(void); +//show a debug line +void AAS_DebugLine(vec3_t start, vec3_t end, int color); +//show a permenent line +void AAS_PermanentLine(vec3_t start, vec3_t end, int color); +//show a permanent cross +void AAS_DrawPermanentCross(vec3_t origin, float size, int color); +//draw a cross in the plane +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color); +//show a bounding box +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs); +//show a face +void AAS_ShowFace(int facenum); +//show an area +void AAS_ShowArea(int areanum, int groundfacesonly); +// +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly); +//draw a cros +void AAS_DrawCross(vec3_t origin, float size, int color); +//print the travel type +void AAS_PrintTravelType(int traveltype); +//draw an arrow +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor); +//visualize the given reachability +void AAS_ShowReachability(struct aas_reachability_s *reach); +//show the reachable areas from the given area +void AAS_ShowReachableAreas(int areanum); diff --git a/src/botlib/be_aas_def.h b/src/botlib/be_aas_def.h new file mode 100644 index 000000000..7d2e260e9 --- /dev/null +++ b/src/botlib/be_aas_def.h @@ -0,0 +1,300 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_def.h + * @brief AAS + */ + +//debugging on +#define AAS_DEBUG + +#define DF_AASENTNUMBER(x) (x - (*aasworlds).entities) +#define DF_NUMBERAASENT(x) (&(*aasworlds).entities[x]) +#define DF_AASENTCLIENT(x) (x - (*aasworlds).entities - 1) +#define DF_CLIENTAASENT(x) (&(*aasworlds).entities[x + 1]) + +#ifndef MAX_PATH +#define MAX_PATH MAX_QPATH +#endif + +//string index (for model, sound and image index) +typedef struct aas_stringindex_s +{ + int numindexes; + char **index; +} aas_stringindex_t; + +//structure to link entities to areas and areas to entities +typedef struct aas_link_s +{ + int entnum; + int areanum; + struct aas_link_s *next_ent, *prev_ent; + struct aas_link_s *next_area, *prev_area; +} aas_link_t; + +//structure to link entities to leaves and leaves to entities +typedef struct bsp_link_s +{ + int entnum; + int leafnum; + struct bsp_link_s *next_ent, *prev_ent; + struct bsp_link_s *next_leaf, *prev_leaf; +} bsp_link_t; + +typedef struct bsp_entdata_s +{ + vec3_t origin; + vec3_t angles; + vec3_t absmins; + vec3_t absmaxs; + int solid; + int modelnum; +} bsp_entdata_t; + +//entity +typedef struct aas_entity_s +{ + //entity info + aas_entityinfo_t i; + //links into the AAS areas + aas_link_t *areas; + //links into the BSP leaves + bsp_link_t *leaves; +} aas_entity_t; + +typedef struct aas_settings_s +{ + float sv_friction; + float sv_stopspeed; + float sv_gravity; + float sv_waterfriction; + float sv_watergravity; + float sv_maxvelocity; + float sv_maxwalkvelocity; + float sv_maxcrouchvelocity; + float sv_maxswimvelocity; + float sv_walkaccelerate; + float sv_airaccelerate; + float sv_swimaccelerate; + float sv_maxstep; + float sv_maxsteepness; + float sv_maxwaterjump; + float sv_maxbarrier; + float sv_jumpvel; + qboolean sv_allowladders; +} aas_settings_t; + +//routing cache +typedef struct aas_routingcache_s +{ + int size; //size of the routing cache + float time; //last time accessed or updated + int cluster; //cluster the cache is for + int areanum; //area the cache is created for + vec3_t origin; //origin within the area + float starttraveltime; //travel time to start with + int travelflags; //combinations of the travel flags + struct aas_routingcache_s *prev, *next; + unsigned char *reachabilities; //reachabilities used for routing + unsigned short int traveltimes[1]; //travel time for every area (variable sized) +} aas_routingcache_t; + +//fields for the routing algorithm +typedef struct aas_routingupdate_s +{ + int cluster; + int areanum; //area number of the update + vec3_t start; //start point the area was entered + unsigned short int tmptraveltime; //temporary travel time + unsigned short int *areatraveltimes; //travel times within the area + qboolean inlist; //true if the update is in the list + struct aas_routingupdate_s *next; + struct aas_routingupdate_s *prev; +} aas_routingupdate_t; + +//reversed reachability link +typedef struct aas_reversedlink_s +{ + int linknum; //the aas_areareachability_t + int areanum; //reachable from this area + struct aas_reversedlink_s *next; //next link +} aas_reversedlink_t; + +//reversed area reachability +typedef struct aas_reversedreachability_s +{ + int numlinks; + aas_reversedlink_t *first; +} aas_reversedreachability_t; + +// Ridah, route-tables +#include "be_aas_routetable.h" +// done. + +typedef struct aas_s +{ + int loaded; //true when an AAS file is loaded + int initialized; //true when AAS has been initialized + int savefile; //set true when file should be saved + int bspchecksum; + //current time + float time; + int numframes; + //name of the aas file + char filename[MAX_PATH]; + char mapname[MAX_PATH]; + //bounding boxes + int numbboxes; + aas_bbox_t *bboxes; + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //planes + int numplanes; + aas_plane_t *planes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; + //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //cluster portal index + int portalindexsize; + aas_portalindex_t *portalindex; + //clusters + int numclusters; + aas_cluster_t *clusters; + // + int reachabilityareas; + float reachabilitytime; + //enities linked in the areas + aas_link_t *linkheap; //heap with link structures + int linkheapsize; //size of the link heap + aas_link_t *freelinks; //first free link + aas_link_t **arealinkedentities; //entities linked into areas + //entities + int maxentities; + int maxclients; + aas_entity_t *entities; + //string indexes + char *configstrings[MAX_CONFIGSTRINGS]; + int indexessetup; + //index to retrieve travel flag for a travel type + int travelflagfortype[MAX_TRAVELTYPES]; + //routing update + aas_routingupdate_t *areaupdate; + aas_routingupdate_t *portalupdate; + //number of routing updates during a frame (reset every frame) + int frameroutingupdates; + //reversed reachability links + aas_reversedreachability_t *reversedreachability; + //travel times within the areas + unsigned short ***areatraveltimes; + //array of size numclusters with cluster cache + aas_routingcache_t ***clusterareacache; + aas_routingcache_t **portalcache; + //maximum travel time through portals + int *portalmaxtraveltimes; + // Ridah, pointer to Route-Table information + aas_rt_t *routetable; + //hide travel times + unsigned short int *hidetraveltimes; + + // Distance from Dangerous areas + unsigned short int *distanceFromDanger; + + // Priority Queue Implementation + unsigned short int *PQ_accumulator; + + // How many items are in the PQ + int PQ_size; + + //vis data + byte *decompressedvis; + int decompressedvisarea; + byte **areavisibility; + // done. + // Ridah, store the area's waypoint for hidepos calculations (center traced downwards) + vec3_t *areawaypoints; + // Ridah, so we can cache the areas that have already been tested for visibility/attackability + byte *visCache; + // RF, cluster team flags (-1 means not calculated) + int *clusterTeamTravelFlags; + // RF, last time a death influenced this area. Seperate lists for axis/allies + int *teamDeathTime; + // RF, number of deaths accumulated before the time expired + byte *teamDeathCount; + // RF, areas that are influenced by a death count + byte *teamDeathAvoid; +} aas_t; + +#define AASINTERN + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +// Ridah, route-tables +#include "be_aas_routetable.h" + +#endif //BSPCINCLUDE diff --git a/src/botlib/be_aas_entity.c b/src/botlib/be_aas_entity.c new file mode 100644 index 000000000..821bf7301 --- /dev/null +++ b/src/botlib/be_aas_entity.c @@ -0,0 +1,593 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_entity.c + * @brief AAS entities + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define MASK_SOLID CONTENTS_PLAYERCLIP + +// Ridah, always use the default world for entities +extern aas_t aasworlds[MAX_AAS_WORLDS]; + +aas_t *defaultaasworld = aasworlds; + +//FIXME: these might change +/*enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER +};*/ + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdateEntity(int entnum, bot_entitystate_t *state) +{ + int relink; + aas_entity_t *ent; + vec3_t absmins, absmaxs; + + if (!(*defaultaasworld).loaded) + { + botimport.Print(PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n"); + return BLERR_NOAASFILE; + } //end if + + ent = &(*defaultaasworld).entities[entnum]; + + ent->i.update_time = AAS_Time() - ent->i.ltime; + ent->i.type = state->type; + ent->i.flags = state->flags; + ent->i.ltime = AAS_Time(); + VectorCopy(ent->i.origin, ent->i.lastvisorigin); + VectorCopy(state->old_origin, ent->i.old_origin); + ent->i.solid = state->solid; + ent->i.groundent = state->groundent; + ent->i.modelindex = state->modelindex; + ent->i.modelindex2 = state->modelindex2; + ent->i.frame = state->frame; + //ent->i.event = state->event; + ent->i.eventParm = state->eventParm; + ent->i.powerups = state->powerups; + ent->i.weapon = state->weapon; + ent->i.legsAnim = state->legsAnim; + ent->i.torsoAnim = state->torsoAnim; + +// ent->i.weapAnim = state->weapAnim; //----(SA) +//----(SA) didn't want to comment in as I wasn't sure of any implications of changing the aas_entityinfo_t and bot_entitystate_t structures. + + //number of the entity + ent->i.number = entnum; + //updated so set valid flag + ent->i.valid = qtrue; + //link everything the first frame + + if ((*defaultaasworld).numframes == 1) + { + relink = qtrue; + } + else + { + relink = qfalse; + } + + // + if (ent->i.solid == SOLID_BSP) + { + //if the angles of the model changed + if (!VectorCompare(state->angles, ent->i.angles)) + { + VectorCopy(state->angles, ent->i.angles); + relink = qtrue; + } //end if + //get the mins and maxs of the model + //FIXME: rotate mins and maxs + + // RF, this is broken, just use the state bounds + //AAS_BSPModelMinsMaxsOrigin(ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL); + VectorCopy(state->mins, ent->i.mins); + VectorCopy(state->maxs, ent->i.maxs); + } //end if + else if (ent->i.solid == SOLID_BBOX) + { + //if the bounding box size changed + if (!VectorCompare(state->mins, ent->i.mins) || + !VectorCompare(state->maxs, ent->i.maxs)) + { + VectorCopy(state->mins, ent->i.mins); + VectorCopy(state->maxs, ent->i.maxs); + relink = qtrue; + } //end if + } //end if + //if the origin changed + if (!VectorCompare(state->origin, ent->i.origin)) + { + VectorCopy(state->origin, ent->i.origin); + relink = qtrue; + } //end if + //if the entity should be relinked + if (relink) + { + //don't link the world model + if (entnum != ENTITYNUM_WORLD) + { + //absolute mins and maxs + VectorAdd(ent->i.mins, ent->i.origin, absmins); + VectorAdd(ent->i.maxs, ent->i.origin, absmaxs); + + //unlink the entity + AAS_UnlinkFromAreas(ent->areas); + //relink the entity to the AAS areas (use the larges bbox) + ent->areas = AAS_LinkEntityClientBBox(absmins, absmaxs, entnum, PRESENCE_NORMAL); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves(ent->leaves); + //link the entity to the world BSP tree + ent->leaves = AAS_BSPLinkEntity(absmins, absmaxs, entnum, 0); + } //end if + } //end if + return BLERR_NOERROR; +} //end of the function AAS_UpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info) +{ + // Gordon: lets not spam this message making it impossible to see anything on the console + static qboolean debug_msg_done = qfalse; + + if (!(*defaultaasworld).initialized) + { + if (!debug_msg_done) + { + debug_msg_done = qtrue; + botimport.Print(PRT_FATAL, "AAS_EntityInfo: (*defaultaasworld) not initialized\n"); + memset(info, 0, sizeof(aas_entityinfo_t)); + } + return; + } //end if + + if (entnum < 0 || entnum >= (*defaultaasworld).maxentities) + { + // if it's not a bot game entity, then report it + if (!(entnum >= (*defaultaasworld).maxentities && entnum < (*defaultaasworld).maxentities + NUM_BOTGAMEENTITIES)) + { + botimport.Print(PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum); + } + memset(info, 0, sizeof(aas_entityinfo_t)); + return; + } //end if + + memcpy(info, &(*defaultaasworld).entities[entnum].i, sizeof(aas_entityinfo_t)); +} //end of the function AAS_EntityInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityOrigin(int entnum, vec3_t origin) +{ + if (entnum < 0 || entnum >= (*defaultaasworld).maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum); + VectorClear(origin); + return; + } //end if + + VectorCopy((*defaultaasworld).entities[entnum].i.origin, origin); +} //end of the function AAS_EntityOrigin +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelindex(int entnum) +{ + if (entnum < 0 || entnum >= (*defaultaasworld).maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum); + return 0; + } //end if + return (*defaultaasworld).entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelindex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityType(int entnum) +{ + if (!(*defaultaasworld).initialized) + { + return 0; + } + + if (entnum < 0 || entnum >= (*defaultaasworld).maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum); + return 0; + } //end if + return (*defaultaasworld).entities[entnum].i.type; +} //end of the AAS_EntityType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelNum(int entnum) +{ + if (!(*defaultaasworld).initialized) + { + return 0; + } + + if (entnum < 0 || entnum >= (*defaultaasworld).maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum); + return 0; + } //end if + return (*defaultaasworld).entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OriginOfEntityWithModelNum(int modelnum, vec3_t origin) +{ + int i; + aas_entity_t *ent; + + for (i = 0; i < (*defaultaasworld).maxentities; i++) + { + ent = &(*defaultaasworld).entities[i]; + if (ent->i.type == ET_MOVER) + { + if (ent->i.modelindex == modelnum) + { + VectorCopy(ent->i.origin, origin); + return qtrue; + } //end if + } + } //end for + return qfalse; +} //end of the function AAS_OriginOfEntityWithModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs) +{ + aas_entity_t *ent; + + if (!(*defaultaasworld).initialized) + { + return; + } + + if (entnum < 0 || entnum >= (*defaultaasworld).maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum); + return; + } //end if + + ent = &(*defaultaasworld).entities[entnum]; + VectorCopy(ent->i.mins, mins); + VectorCopy(ent->i.maxs, maxs); +} //end of the function AAS_EntitySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata) +{ + aas_entity_t *ent; + + ent = &(*defaultaasworld).entities[entnum]; + VectorCopy(ent->i.origin, entdata->origin); + VectorCopy(ent->i.angles, entdata->angles); + VectorAdd(ent->i.origin, ent->i.mins, entdata->absmins); + VectorAdd(ent->i.origin, ent->i.maxs, entdata->absmaxs); + entdata->solid = ent->i.solid; + entdata->modelnum = ent->i.modelindex - 1; +} //end of the function AAS_EntityBSPData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ResetEntityLinks(void) +{ + int i; + for (i = 0; i < (*defaultaasworld).maxentities; i++) + { + (*defaultaasworld).entities[i].areas = NULL; + (*defaultaasworld).entities[i].leaves = NULL; + } //end for +} //end of the function AAS_ResetEntityLinks +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InvalidateEntities(void) +{ + int i; + for (i = 0; i < (*defaultaasworld).maxentities; i++) + { + (*defaultaasworld).entities[i].i.valid = qfalse; + (*defaultaasworld).entities[i].i.number = i; + } //end for +} //end of the function AAS_InvalidateEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestEntity(vec3_t origin, int modelindex) +{ + int i, bestentnum; + float dist, bestdist; + aas_entity_t *ent; + vec3_t dir; + + bestentnum = 0; + bestdist = 99999; + for (i = 0; i < (*defaultaasworld).maxentities; i++) + { + ent = &(*defaultaasworld).entities[i]; + if (ent->i.modelindex != modelindex) + { + continue; + } + VectorSubtract(ent->i.origin, origin, dir); + if (abs(dir[0]) < 40) + { + if (abs(dir[1]) < 40) + { + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestentnum = i; + } //end if + } //end if + } //end if + } //end for + return bestentnum; +} //end of the function AAS_NearestEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableEntityArea(int entnum) +{ + aas_entity_t *ent; + + ent = &(*defaultaasworld).entities[entnum]; + return AAS_BestReachableLinkArea(ent->areas); +} //end of the function AAS_BestReachableEntityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextEntity(int entnum) +{ + if (!(*defaultaasworld).loaded) + { + return 0; + } + + if (entnum < 0) + { + entnum = -1; + } + while (++entnum < (*defaultaasworld).maxentities) + { + if ((*defaultaasworld).entities[entnum].i.valid) + { + return entnum; + } + } //end while + return 0; +} //end of the function AAS_NextEntity + +// Ridah, used to find out if there is an entity touching the given area, if so, try and avoid it +/* +============ +AAS_EntityInArea +============ +*/ +int AAS_IsEntityInArea(int entnumIgnore, int entnumIgnore2, int areanum) +{ + aas_link_t *link; + aas_entity_t *ent; + + for (link = (*aasworld).arealinkedentities[areanum]; link; link = link->next_ent) + { + //ignore the pass entity + if (link->entnum == entnumIgnore) + { + continue; + } + if (link->entnum == entnumIgnore2) + { + continue; + } + // + ent = &(*defaultaasworld).entities[link->entnum]; + if (!ent->i.valid) + { + continue; + } + if (!ent->i.solid) + { + continue; + } + return qtrue; + } + /* + ent = (*defaultaasworld).entities; + for (i = 0; i < (*defaultaasworld).maxclients; i++, ent++) + { + if (!ent->i.valid) + continue; + if (!ent->i.solid) + continue; + if (i == entnumIgnore) + continue; + if (i == entnumIgnore2) + continue; + for (link = ent->areas; link; link = link->next_area) + { + if (link->areanum == areanum) + { + return qtrue; + } //end if + } //end for + } + */ + return qfalse; +} + +/* +============= +AAS_SetAASBlockingEntity +============= +*/ +int AAS_EnableRoutingArea(int areanum, int enable); +void AAS_SetAASBlockingEntity(vec3_t absmin, vec3_t absmax, int blocking) +{ + int areas[1024]; + int numareas, i, w; + qboolean mover, changed = qfalse; + // + // check for resetting AAS blocking + if (VectorCompare(absmin, absmax) && blocking < 0) + { + for (w = 0; w < MAX_AAS_WORLDS; w++) + { + AAS_SetCurrentWorld(w); + // + if (!(*aasworld).loaded) + { + continue; + } + // now clear blocking status + for (i = 1; i < (*aasworld).numareas; i++) + { + AAS_EnableRoutingArea(i, qtrue); + } + } + // + return; + } + // + if (blocking & BLOCKINGFLAG_MOVER) + { + mover = qtrue; + blocking &= ~BLOCKINGFLAG_MOVER; + } + else + { + mover = qfalse; + } + // +areas_again: + // + for (w = 0; w < MAX_AAS_WORLDS; w++) + { + AAS_SetCurrentWorld(w); + // + if (!(*aasworld).loaded) + { + continue; + } + // grab the list of areas + numareas = AAS_BBoxAreas(absmin, absmax, areas, 1024); + // now set their blocking status + for (i = 0; i < numareas; i++) + { + if (mover) + { + if (!(aasworld->areasettings[areas[i]].contents & AREACONTENTS_MOVER)) + { + continue; // this isn't a mover area, so ignore it + } + } + AAS_EnableRoutingArea(areas[i], (blocking & ~0x1) | !(blocking & 1)); + changed = qtrue; + } + } + // + if (mover && !changed) // map must not be compiled with MOVER flags enabled, so redo the old way + { + mover = qfalse; + goto areas_again; + } +} diff --git a/src/botlib/be_aas_entity.h b/src/botlib/be_aas_entity.h new file mode 100644 index 000000000..7bf3afbaf --- /dev/null +++ b/src/botlib/be_aas_entity.h @@ -0,0 +1,65 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_entity.h + * @brief AAS entities + */ + +#ifdef AASINTERN +//invalidates all entity infos +void AAS_InvalidateEntities(void); +//resets the entity AAS and BSP links (sets areas and leaves pointers to NULL) +void AAS_ResetEntityLinks(void); +//updates an entity +int AAS_UpdateEntity(int ent, bot_entitystate_t *state); +//gives the entity data used for collision detection +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata); +#endif //AASINTERN + +//returns the size of the entity bounding box in mins and maxs +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs); +//returns the BSP model number of the entity +int AAS_EntityModelNum(int entnum); +//returns the origin of an entity with the given model number +int AAS_OriginOfEntityWithModelNum(int modelnum, vec3_t origin); +//returns the best reachable area the entity is situated in +int AAS_BestReachableEntityArea(int entnum); +//returns the info of the given entity +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info); +//returns the next entity +int AAS_NextEntity(int entnum); +//returns the origin of the entity +void AAS_EntityOrigin(int entnum, vec3_t origin); +//returns the entity type +int AAS_EntityType(int entnum); +//returns the model index of the entity +int AAS_EntityModelindex(int entnum); +// Ridah +int AAS_IsEntityInArea(int entnumIgnore, int entnumIgnore2, int areanum); diff --git a/src/botlib/be_aas_file.c b/src/botlib/be_aas_file.c new file mode 100644 index 000000000..ae87c1ba4 --- /dev/null +++ b/src/botlib/be_aas_file.c @@ -0,0 +1,743 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_file.c + * @brief AAS file loading/writing + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +//#define AASFILEDEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SwapAASData(void) +{ + int i, j; + + // Ridah, no need to do anything if this OS doesn't need byte swapping + if (LittleLong(1) == 1) + { + return; + } + // done. + + //bounding boxes + for (i = 0; i < (*aasworld).numbboxes; i++) + { + (*aasworld).bboxes[i].presencetype = LittleLong((*aasworld).bboxes[i].presencetype); + (*aasworld).bboxes[i].flags = LittleLong((*aasworld).bboxes[i].flags); + for (j = 0; j < 3; j++) + { + (*aasworld).bboxes[i].mins[j] = LittleLong((*aasworld).bboxes[i].mins[j]); + (*aasworld).bboxes[i].maxs[j] = LittleLong((*aasworld).bboxes[i].maxs[j]); + } //end for + } //end for + //vertexes + for (i = 0; i < (*aasworld).numvertexes; i++) + { + for (j = 0; j < 3; j++) + (*aasworld).vertexes[i][j] = LittleFloat((*aasworld).vertexes[i][j]); + } //end for + //planes + for (i = 0; i < (*aasworld).numplanes; i++) + { + for (j = 0; j < 3; j++) + (*aasworld).planes[i].normal[j] = LittleFloat((*aasworld).planes[i].normal[j]); + (*aasworld).planes[i].dist = LittleFloat((*aasworld).planes[i].dist); + (*aasworld).planes[i].type = LittleLong((*aasworld).planes[i].type); + } //end for + //edges + for (i = 0; i < (*aasworld).numedges; i++) + { + (*aasworld).edges[i].v[0] = LittleLong((*aasworld).edges[i].v[0]); + (*aasworld).edges[i].v[1] = LittleLong((*aasworld).edges[i].v[1]); + } //end for + //edgeindex + for (i = 0; i < (*aasworld).edgeindexsize; i++) + { + (*aasworld).edgeindex[i] = LittleLong((*aasworld).edgeindex[i]); + } //end for + //faces + for (i = 0; i < (*aasworld).numfaces; i++) + { + (*aasworld).faces[i].planenum = LittleLong((*aasworld).faces[i].planenum); + (*aasworld).faces[i].faceflags = LittleLong((*aasworld).faces[i].faceflags); + (*aasworld).faces[i].numedges = LittleLong((*aasworld).faces[i].numedges); + (*aasworld).faces[i].firstedge = LittleLong((*aasworld).faces[i].firstedge); + (*aasworld).faces[i].frontarea = LittleLong((*aasworld).faces[i].frontarea); + (*aasworld).faces[i].backarea = LittleLong((*aasworld).faces[i].backarea); + } //end for + //face index + for (i = 0; i < (*aasworld).faceindexsize; i++) + { + (*aasworld).faceindex[i] = LittleLong((*aasworld).faceindex[i]); + } //end for + //convex areas + for (i = 0; i < (*aasworld).numareas; i++) + { + (*aasworld).areas[i].areanum = LittleLong((*aasworld).areas[i].areanum); + (*aasworld).areas[i].numfaces = LittleLong((*aasworld).areas[i].numfaces); + (*aasworld).areas[i].firstface = LittleLong((*aasworld).areas[i].firstface); + for (j = 0; j < 3; j++) + { + (*aasworld).areas[i].mins[j] = LittleFloat((*aasworld).areas[i].mins[j]); + (*aasworld).areas[i].maxs[j] = LittleFloat((*aasworld).areas[i].maxs[j]); + (*aasworld).areas[i].center[j] = LittleFloat((*aasworld).areas[i].center[j]); + } //end for + } //end for + //area settings + for (i = 0; i < (*aasworld).numareasettings; i++) + { + (*aasworld).areasettings[i].contents = LittleLong((*aasworld).areasettings[i].contents); + (*aasworld).areasettings[i].areaflags = LittleLong((*aasworld).areasettings[i].areaflags); + (*aasworld).areasettings[i].presencetype = LittleLong((*aasworld).areasettings[i].presencetype); + (*aasworld).areasettings[i].cluster = LittleLong((*aasworld).areasettings[i].cluster); + (*aasworld).areasettings[i].clusterareanum = LittleLong((*aasworld).areasettings[i].clusterareanum); + (*aasworld).areasettings[i].numreachableareas = LittleLong((*aasworld).areasettings[i].numreachableareas); + (*aasworld).areasettings[i].firstreachablearea = LittleLong((*aasworld).areasettings[i].firstreachablearea); + (*aasworld).areasettings[i].groundsteepness = LittleFloat((*aasworld).areasettings[i].groundsteepness); + } //end for + //area reachability + for (i = 0; i < (*aasworld).reachabilitysize; i++) + { + (*aasworld).reachability[i].areanum = LittleLong((*aasworld).reachability[i].areanum); + (*aasworld).reachability[i].facenum = LittleLong((*aasworld).reachability[i].facenum); + (*aasworld).reachability[i].edgenum = LittleLong((*aasworld).reachability[i].edgenum); + for (j = 0; j < 3; j++) + { + (*aasworld).reachability[i].start[j] = LittleFloat((*aasworld).reachability[i].start[j]); + (*aasworld).reachability[i].end[j] = LittleFloat((*aasworld).reachability[i].end[j]); + } //end for + (*aasworld).reachability[i].traveltype = LittleLong((*aasworld).reachability[i].traveltype); + (*aasworld).reachability[i].traveltime = LittleShort((*aasworld).reachability[i].traveltime); + } //end for + //nodes + for (i = 0; i < (*aasworld).numnodes; i++) + { + (*aasworld).nodes[i].planenum = LittleLong((*aasworld).nodes[i].planenum); + (*aasworld).nodes[i].children[0] = LittleLong((*aasworld).nodes[i].children[0]); + (*aasworld).nodes[i].children[1] = LittleLong((*aasworld).nodes[i].children[1]); + } //end for + //cluster portals + for (i = 0; i < (*aasworld).numportals; i++) + { + (*aasworld).portals[i].areanum = LittleLong((*aasworld).portals[i].areanum); + (*aasworld).portals[i].frontcluster = LittleLong((*aasworld).portals[i].frontcluster); + (*aasworld).portals[i].backcluster = LittleLong((*aasworld).portals[i].backcluster); + (*aasworld).portals[i].clusterareanum[0] = LittleLong((*aasworld).portals[i].clusterareanum[0]); + (*aasworld).portals[i].clusterareanum[1] = LittleLong((*aasworld).portals[i].clusterareanum[1]); + } //end for + //cluster portal index + for (i = 0; i < (*aasworld).portalindexsize; i++) + { + (*aasworld).portalindex[i] = LittleLong((*aasworld).portalindex[i]); + } //end for + //cluster + for (i = 0; i < (*aasworld).numclusters; i++) + { + (*aasworld).clusters[i].numareas = LittleLong((*aasworld).clusters[i].numareas); + (*aasworld).clusters[i].numreachabilityareas = LittleLong((*aasworld).clusters[i].numreachabilityareas); + (*aasworld).clusters[i].numportals = LittleLong((*aasworld).clusters[i].numportals); + (*aasworld).clusters[i].firstportal = LittleLong((*aasworld).clusters[i].firstportal); + } //end for +} //end of the function AAS_SwapAASData +//=========================================================================== +// dump the current loaded aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpAASData(void) +{ + (*aasworld).numbboxes = 0; + if ((*aasworld).bboxes) + { + FreeMemory((*aasworld).bboxes); + } + (*aasworld).bboxes = NULL; + (*aasworld).numvertexes = 0; + if ((*aasworld).vertexes) + { + FreeMemory((*aasworld).vertexes); + } + (*aasworld).vertexes = NULL; + (*aasworld).numplanes = 0; + if ((*aasworld).planes) + { + FreeMemory((*aasworld).planes); + } + (*aasworld).planes = NULL; + (*aasworld).numedges = 0; + if ((*aasworld).edges) + { + FreeMemory((*aasworld).edges); + } + (*aasworld).edges = NULL; + (*aasworld).edgeindexsize = 0; + if ((*aasworld).edgeindex) + { + FreeMemory((*aasworld).edgeindex); + } + (*aasworld).edgeindex = NULL; + (*aasworld).numfaces = 0; + if ((*aasworld).faces) + { + FreeMemory((*aasworld).faces); + } + (*aasworld).faces = NULL; + (*aasworld).faceindexsize = 0; + if ((*aasworld).faceindex) + { + FreeMemory((*aasworld).faceindex); + } + (*aasworld).faceindex = NULL; + (*aasworld).numareas = 0; + if ((*aasworld).areas) + { + FreeMemory((*aasworld).areas); + } + (*aasworld).areas = NULL; + (*aasworld).numareasettings = 0; + if ((*aasworld).areasettings) + { + FreeMemory((*aasworld).areasettings); + } + (*aasworld).areasettings = NULL; + (*aasworld).reachabilitysize = 0; + if ((*aasworld).reachability) + { + FreeMemory((*aasworld).reachability); + } + (*aasworld).reachability = NULL; + (*aasworld).numnodes = 0; + if ((*aasworld).nodes) + { + FreeMemory((*aasworld).nodes); + } + (*aasworld).nodes = NULL; + (*aasworld).numportals = 0; + if ((*aasworld).portals) + { + FreeMemory((*aasworld).portals); + } + (*aasworld).portals = NULL; + (*aasworld).numportals = 0; + if ((*aasworld).portalindex) + { + FreeMemory((*aasworld).portalindex); + } + (*aasworld).portalindex = NULL; + (*aasworld).portalindexsize = 0; + if ((*aasworld).clusters) + { + FreeMemory((*aasworld).clusters); + } + (*aasworld).clusters = NULL; + (*aasworld).numclusters = 0; + // + (*aasworld).loaded = qfalse; + (*aasworld).initialized = qfalse; + (*aasworld).savefile = qfalse; +} //end of the function AAS_DumpAASData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef AASFILEDEBUG +void AAS_FileInfo(void) +{ + int i, n, optimized; + + botimport.Print(PRT_MESSAGE, "version = %d\n", AASVERSION); + botimport.Print(PRT_MESSAGE, "numvertexes = %d\n", (*aasworld).numvertexes); + botimport.Print(PRT_MESSAGE, "numplanes = %d\n", (*aasworld).numplanes); + botimport.Print(PRT_MESSAGE, "numedges = %d\n", (*aasworld).numedges); + botimport.Print(PRT_MESSAGE, "edgeindexsize = %d\n", (*aasworld).edgeindexsize); + botimport.Print(PRT_MESSAGE, "numfaces = %d\n", (*aasworld).numfaces); + botimport.Print(PRT_MESSAGE, "faceindexsize = %d\n", (*aasworld).faceindexsize); + botimport.Print(PRT_MESSAGE, "numareas = %d\n", (*aasworld).numareas); + botimport.Print(PRT_MESSAGE, "numareasettings = %d\n", (*aasworld).numareasettings); + botimport.Print(PRT_MESSAGE, "reachabilitysize = %d\n", (*aasworld).reachabilitysize); + botimport.Print(PRT_MESSAGE, "numnodes = %d\n", (*aasworld).numnodes); + botimport.Print(PRT_MESSAGE, "numportals = %d\n", (*aasworld).numportals); + botimport.Print(PRT_MESSAGE, "portalindexsize = %d\n", (*aasworld).portalindexsize); + botimport.Print(PRT_MESSAGE, "numclusters = %d\n", (*aasworld).numclusters); + // + for (n = 0, i = 0; i < (*aasworld).numareasettings; i++) + { + if ((*aasworld).areasettings[i].areaflags & AREA_GROUNDED) + { + n++; + } + } //end for + botimport.Print(PRT_MESSAGE, "num grounded areas = %d\n", n); + // + botimport.Print(PRT_MESSAGE, "planes size %d bytes\n", (*aasworld).numplanes * sizeof(aas_plane_t)); + botimport.Print(PRT_MESSAGE, "areas size %d bytes\n", (*aasworld).numareas * sizeof(aas_area_t)); + botimport.Print(PRT_MESSAGE, "areasettings size %d bytes\n", (*aasworld).numareasettings * sizeof(aas_areasettings_t)); + botimport.Print(PRT_MESSAGE, "nodes size %d bytes\n", (*aasworld).numnodes * sizeof(aas_node_t)); + botimport.Print(PRT_MESSAGE, "reachability size %d bytes\n", (*aasworld).reachabilitysize * sizeof(aas_reachability_t)); + botimport.Print(PRT_MESSAGE, "portals size %d bytes\n", (*aasworld).numportals * sizeof(aas_portal_t)); + botimport.Print(PRT_MESSAGE, "clusters size %d bytes\n", (*aasworld).numclusters * sizeof(aas_cluster_t)); + + optimized = (*aasworld).numplanes * sizeof(aas_plane_t) + + (*aasworld).numareas * sizeof(aas_area_t) + + (*aasworld).numareasettings * sizeof(aas_areasettings_t) + + (*aasworld).numnodes * sizeof(aas_node_t) + + (*aasworld).reachabilitysize * sizeof(aas_reachability_t) + + (*aasworld).numportals * sizeof(aas_portal_t) + + (*aasworld).numclusters * sizeof(aas_cluster_t); + botimport.Print(PRT_MESSAGE, "optimzed size %d KB\n", optimized >> 10); +} //end of the function AAS_FileInfo +#endif //AASFILEDEBUG +//=========================================================================== +// allocate memory and read a lump of a AAS file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_LoadAASLump(fileHandle_t fp, int offset, int length, int *lastoffset) +{ + char *buf; + // + if (!length) + { + return NULL; + } + //seek to the data + if (offset != *lastoffset) + { + botimport.Print(PRT_WARNING, "AAS file not sequentially read\n"); + if (botimport.FS_Seek(fp, offset, FS_SEEK_SET)) + { + AAS_Error("can't seek to aas lump\n"); + AAS_DumpAASData(); + botimport.FS_FCloseFile(fp); + return 0; + } //end if + } //end if + //allocate memory + buf = (char *) GetClearedHunkMemory(length + 1); + //read the data + if (length) + { + botimport.FS_Read(buf, length, fp); + *lastoffset += length; + } //end if + return buf; +} //end of the function AAS_LoadAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DData(unsigned char *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + { + data[i] ^= (unsigned char) i * 119; + } //end for +} //end of the function AAS_DData +//=========================================================================== +// load an aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadAASFile(char *filename) +{ + fileHandle_t fp; + aas_header_t header; + int offset, length, lastoffset; + int nocrc; + + botimport.Print(PRT_MESSAGE, "trying to load %s\n", filename); + //dump current loaded aas file + AAS_DumpAASData(); + //open the file + botimport.FS_FOpenFile(filename, &fp, FS_READ); + if (!fp) + { + AAS_Error("can't open %s\n", filename); + return BLERR_CANNOTOPENAASFILE; + } //end if + //read the header + botimport.FS_Read(&header, sizeof(aas_header_t), fp); + lastoffset = sizeof(aas_header_t); + //check header identification + header.ident = LittleLong(header.ident); + if (header.ident != AASID) + { + AAS_Error("%s is not an AAS file\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEID; + } //end if + //check the version + header.version = LittleLong(header.version); + // + if (header.version != AASVERSION) + { + AAS_Error("aas file %s is version %i, not %i\n", filename, header.version, AASVERSION); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + // + //RF, checksum of -1 always passes, hack to fix commercial maps without having to distribute new bsps + nocrc = 0; + if (LittleLong(header.bspchecksum) == -1) + { + nocrc = 1; + } + // + if (header.version == AASVERSION) + { + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + } //end if + // + (*aasworld).bspchecksum = atoi(LibVarGetString("sv_mapChecksum")); + if (!nocrc && LittleLong(header.bspchecksum) != (*aasworld).bspchecksum) + { + AAS_Error("aas file %s is out of date\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + //load the lumps: + //bounding boxes + offset = LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); + (*aasworld).bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numbboxes = length / sizeof(aas_bbox_t); + if ((*aasworld).numbboxes && !(*aasworld).bboxes) + { + return BLERR_CANNOTREADAASLUMP; + } + //vertexes + offset = LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); + (*aasworld).vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numvertexes = length / sizeof(aas_vertex_t); + if ((*aasworld).numvertexes && !(*aasworld).vertexes) + { + return BLERR_CANNOTREADAASLUMP; + } + //planes + offset = LittleLong(header.lumps[AASLUMP_PLANES].fileofs); + length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); + (*aasworld).planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numplanes = length / sizeof(aas_plane_t); + if ((*aasworld).numplanes && !(*aasworld).planes) + { + return BLERR_CANNOTREADAASLUMP; + } + //edges + offset = LittleLong(header.lumps[AASLUMP_EDGES].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); + (*aasworld).edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numedges = length / sizeof(aas_edge_t); + if ((*aasworld).numedges && !(*aasworld).edges) + { + return BLERR_CANNOTREADAASLUMP; + } + //edgeindex + offset = LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); + (*aasworld).edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).edgeindexsize = length / sizeof(aas_edgeindex_t); + if ((*aasworld).edgeindexsize && !(*aasworld).edgeindex) + { + return BLERR_CANNOTREADAASLUMP; + } + //faces + offset = LittleLong(header.lumps[AASLUMP_FACES].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACES].filelen); + (*aasworld).faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numfaces = length / sizeof(aas_face_t); + if ((*aasworld).numfaces && !(*aasworld).faces) + { + return BLERR_CANNOTREADAASLUMP; + } + //faceindex + offset = LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); + (*aasworld).faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).faceindexsize = length / sizeof(int); + if ((*aasworld).faceindexsize && !(*aasworld).faceindex) + { + return BLERR_CANNOTREADAASLUMP; + } + //convex areas + offset = LittleLong(header.lumps[AASLUMP_AREAS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); + (*aasworld).areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numareas = length / sizeof(aas_area_t); + if ((*aasworld).numareas && !(*aasworld).areas) + { + return BLERR_CANNOTREADAASLUMP; + } + //area settings + offset = LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); + (*aasworld).areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numareasettings = length / sizeof(aas_areasettings_t); + if ((*aasworld).numareasettings && !(*aasworld).areasettings) + { + return BLERR_CANNOTREADAASLUMP; + } + //reachability list + offset = LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); + length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); + (*aasworld).reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).reachabilitysize = length / sizeof(aas_reachability_t); + if ((*aasworld).reachabilitysize && !(*aasworld).reachability) + { + return BLERR_CANNOTREADAASLUMP; + } + //nodes + offset = LittleLong(header.lumps[AASLUMP_NODES].fileofs); + length = LittleLong(header.lumps[AASLUMP_NODES].filelen); + (*aasworld).nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numnodes = length / sizeof(aas_node_t); + if ((*aasworld).numnodes && !(*aasworld).nodes) + { + return BLERR_CANNOTREADAASLUMP; + } + //cluster portals + offset = LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); + (*aasworld).portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numportals = length / sizeof(aas_portal_t); + if ((*aasworld).numportals && !(*aasworld).portals) + { + return BLERR_CANNOTREADAASLUMP; + } + //cluster portal index + offset = LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); + (*aasworld).portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).portalindexsize = length / sizeof(aas_portalindex_t); + if ((*aasworld).portalindexsize && !(*aasworld).portalindex) + { + return BLERR_CANNOTREADAASLUMP; + } + //clusters + offset = LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); + length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); + (*aasworld).clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset); + (*aasworld).numclusters = length / sizeof(aas_cluster_t); + if ((*aasworld).numclusters && !(*aasworld).clusters) + { + return BLERR_CANNOTREADAASLUMP; + } + //swap everything + AAS_SwapAASData(); + //aas file is loaded + (*aasworld).loaded = qtrue; + //close the file + botimport.FS_FCloseFile(fp); + // +#ifdef AASFILEDEBUG + AAS_FileInfo(); +#endif //AASFILEDEBUG + // + + { + int j = 0; + int i; + for (i = 1; i < aasworld->numareas; i++) + { + j += aasworld->areasettings[i].numreachableareas; + } + if (j > aasworld->reachabilitysize) + { + Com_Error(ERR_DROP, "aas reachabilitysize incorrect\n"); + } + } + + return BLERR_NOERROR; +} //end of the function AAS_LoadAASFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int AAS_WriteAASLump_offset; + +int AAS_WriteAASLump(fileHandle_t fp, aas_header_t *h, int lumpnum, void *data, int length) +{ + aas_lump_t *lump; + + lump = &h->lumps[lumpnum]; + + lump->fileofs = LittleLong(AAS_WriteAASLump_offset); //LittleLong(ftell(fp)); + lump->filelen = LittleLong(length); + + if (length > 0) + { + botimport.FS_Write(data, length, fp); + } //end if + + AAS_WriteAASLump_offset += length; + + return qtrue; +} //end of the function AAS_WriteAASLump +//=========================================================================== +// aas data is useless after writing to file because it is byte swapped +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_WriteAASFile(char *filename) +{ + aas_header_t header; + fileHandle_t fp; + + botimport.Print(PRT_MESSAGE, "writing %s\n", filename); + //swap the aas data + AAS_SwapAASData(); + //initialize the file header + memset(&header, 0, sizeof(aas_header_t)); + header.ident = LittleLong(AASID); + header.version = LittleLong(AASVERSION); + header.bspchecksum = LittleLong((*aasworld).bspchecksum); + //open a new file + botimport.FS_FOpenFile(filename, &fp, FS_WRITE); + if (!fp) + { + botimport.Print(PRT_ERROR, "error opening %s\n", filename); + return qfalse; + } //end if + //write the header + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + AAS_WriteAASLump_offset = sizeof(aas_header_t); + //add the data lumps to the file + if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, (*aasworld).bboxes, + (*aasworld).numbboxes * sizeof(aas_bbox_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, (*aasworld).vertexes, + (*aasworld).numvertexes * sizeof(aas_vertex_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, (*aasworld).planes, + (*aasworld).numplanes * sizeof(aas_plane_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, (*aasworld).edges, + (*aasworld).numedges * sizeof(aas_edge_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, (*aasworld).edgeindex, + (*aasworld).edgeindexsize * sizeof(aas_edgeindex_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, (*aasworld).faces, + (*aasworld).numfaces * sizeof(aas_face_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, (*aasworld).faceindex, + (*aasworld).faceindexsize * sizeof(aas_faceindex_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, (*aasworld).areas, + (*aasworld).numareas * sizeof(aas_area_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, (*aasworld).areasettings, + (*aasworld).numareasettings * sizeof(aas_areasettings_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, (*aasworld).reachability, + (*aasworld).reachabilitysize * sizeof(aas_reachability_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, (*aasworld).nodes, + (*aasworld).numnodes * sizeof(aas_node_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, (*aasworld).portals, + (*aasworld).numportals * sizeof(aas_portal_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, (*aasworld).portalindex, + (*aasworld).portalindexsize * sizeof(aas_portalindex_t))) + { + return qfalse; + } + if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, (*aasworld).clusters, + (*aasworld).numclusters * sizeof(aas_cluster_t))) + { + return qfalse; + } + //rewrite the header with the added lumps + botimport.FS_Seek(fp, 0, FS_SEEK_SET); + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + //close the file + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_WriteAASFile diff --git a/src/botlib/be_aas_file.h b/src/botlib/be_aas_file.h new file mode 100644 index 000000000..dcce7587e --- /dev/null +++ b/src/botlib/be_aas_file.h @@ -0,0 +1,44 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_file.h + * @brief AAS file loading/writing + */ + +#ifdef AASINTERN +//loads the AAS file with the given name +int AAS_LoadAASFile(char *filename); +//writes an AAS file with the given name +qboolean AAS_WriteAASFile(char *filename); +//dumps the loaded AAS data +void AAS_DumpAASData(void); +//print AAS file information +void AAS_FileInfo(void); +#endif //AASINTERN diff --git a/src/botlib/be_aas_funcs.h b/src/botlib/be_aas_funcs.h new file mode 100644 index 000000000..0e937b7f2 --- /dev/null +++ b/src/botlib/be_aas_funcs.h @@ -0,0 +1,52 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_funcs.h + */ + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +// Ridah, route-tables +#include "be_aas_routetable.h" + +#endif //BSPCINCLUDE diff --git a/src/botlib/be_aas_main.c b/src/botlib/be_aas_main.c new file mode 100644 index 000000000..54fe49a6c --- /dev/null +++ b/src/botlib/be_aas_main.c @@ -0,0 +1,528 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_main.c + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +aas_t aasworlds[MAX_AAS_WORLDS]; + +aas_t *aasworld; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL AAS_Error(char *fmt, ...) +{ + char str[1024]; + va_list arglist; + + va_start(arglist, fmt); + Q_vsnprintf(str, sizeof(str), fmt, arglist); + va_end(arglist); + botimport.Print(PRT_FATAL, str); +} //end of the function AAS_Error + +// Ridah, multiple AAS worlds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetCurrentWorld(int index) +{ + if (index >= MAX_AAS_WORLDS || index < 0) + { + AAS_Error("AAS_SetCurrentWorld: index out of range\n"); + return; + } + + // set the current world pointer + aasworld = &aasworlds[index]; +} +// done. + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_StringFromIndex(char *indexname, char *stringindex[], int numindexes, int index) +{ + if (!(*aasworld).indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index %d not setup\n", indexname, index); + return ""; + } //end if + if (index < 0 || index >= numindexes) + { + botimport.Print(PRT_ERROR, "%s: index %d out of range\n", indexname, index); + return ""; + } //end if + if (!stringindex[index]) + { + if (index) + { + botimport.Print(PRT_ERROR, "%s: reference to unused index %d\n", indexname, index); + } //end if + return ""; + } //end if + return stringindex[index]; +} //end of the function AAS_StringFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromString(char *indexname, char *stringindex[], int numindexes, char *string) +{ + int i; + if (!(*aasworld).indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index not setup \"%s\"\n", indexname, string); + return 0; + } //end if + for (i = 0; i < numindexes; i++) + { + if (!stringindex[i]) + { + continue; + } + if (!Q_stricmp(stringindex[i], string)) + { + return i; + } + } //end for + return 0; +} //end of the function AAS_IndexFromString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_ModelFromIndex(int index) +{ +// return AAS_StringFromIndex("ModelFromIndex", &(*aasworld).configstrings[CS_MODELS], MAX_MODELS, index); + return 0; // removed so the CS_ defines could be removed from be_aas_def.h +} //end of the function AAS_ModelFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromModel(char *modelname) +{ +// return AAS_IndexFromString("IndexFromModel", &(*aasworld).configstrings[CS_MODELS], MAX_MODELS, modelname); + return 0; // removed so the CS_ defines could be removed from be_aas_def.h +} //end of the function AAS_IndexFromModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateStringIndexes(int numconfigstrings, char *configstrings[]) +{ + int i; + //set string pointers and copy the strings + for (i = 0; i < numconfigstrings; i++) + { + if (configstrings[i]) + { + //if ((*aasworld).configstrings[i]) FreeMemory((*aasworld).configstrings[i]); + (*aasworld).configstrings[i] = (char *) GetMemory(strlen(configstrings[i]) + 1); + strcpy((*aasworld).configstrings[i], configstrings[i]); + } //end if + } //end for + (*aasworld).indexessetup = qtrue; +} //end of the function AAS_UpdateStringIndexes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Loaded(void) +{ + return (*aasworld).loaded; +} //end of the function AAS_Loaded +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Initialized(void) +{ + return (*aasworld).initialized; +} //end of the function AAS_Initialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetInitialized(void) +{ + (*aasworld).initialized = qtrue; + botimport.Print(PRT_MESSAGE, "AAS initialized.\n"); +#ifdef DEBUG + //create all the routing cache + //AAS_CreateAllRoutingCache(); + // + //AAS_RoutingInfo(); +#endif + + // Ridah, build/load the route-table + AAS_RT_BuildRouteTable(); + // done. + + AAS_InitTeamDeath(); + +} //end of the function AAS_SetInitialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ContinueInit(float time) +{ + //if no AAS file loaded + if (!(*aasworld).loaded) + { + return; + } + //if AAS is already initialized + if ((*aasworld).initialized) + { + return; + } + //calculate reachability, if not finished return + if (AAS_ContinueInitReachability(time)) + { + return; + } + //initialize clustering for the new map + AAS_InitClustering(); + + //if reachability has been calculated and an AAS file should be written + //or there is a forced data optimization + if ((*aasworld).savefile || ((int)LibVarGetValue("forcewrite"))) + { + //optimize the AAS data + if (!((int)LibVarValue("nooptimize", "1"))) + { + AAS_Optimize(); + } + //save the AAS file + if (AAS_WriteAASFile((*aasworld).filename)) + { + botimport.Print(PRT_MESSAGE, "%s written succesfully\n", (*aasworld).filename); + } + else + { + botimport.Print(PRT_ERROR, "couldn't write %s\n", (*aasworld).filename); + } //end else + } //end if + //initialize the routing + AAS_InitRouting(); + //at this point AAS is initialized + AAS_SetInitialized(); +} //end of the function AAS_ContinueInit +//=========================================================================== +// called at the start of every frame +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StartFrame(float time) +{ + // Ridah, do each of the aasworlds + int i; + + for (i = 0; i < MAX_AAS_WORLDS; i++) + { + AAS_SetCurrentWorld(i); + + (*aasworld).time = time; + //invalidate the entities + + AAS_InvalidateEntities(); + //initialize AAS + AAS_ContinueInit(time); + //update team deaths + AAS_UpdateTeamDeath(); + // + (*aasworld).frameroutingupdates = 0; + // + /* Ridah, disabled for speed + if (LibVarGetValue("showcacheupdates")) + { + AAS_RoutingInfo(); + LibVarSet("showcacheupdates", "0"); + } //end if + if (LibVarGetValue("showmemoryusage")) + { + PrintUsedMemorySize(); + LibVarSet("showmemoryusage", "0"); + } //end if + if (LibVarGetValue("memorydump")) + { + PrintMemoryLabels(); + LibVarSet("memorydump", "0"); + } //end if + */ + } //end if + (*aasworld).numframes++; + + return BLERR_NOERROR; +} //end of the function AAS_StartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_Time(void) +{ + return aasworld->time; +} +//=========================================================================== +// basedir = Quake2 console basedir +// gamedir = Quake2 console gamedir +// mapname = name of the map without extension (.bsp) +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadFiles(const char *mapname) +{ + int errnum; + char aasfile[MAX_PATH]; +// char bspfile[MAX_PATH]; + + strcpy((*aasworld).mapname, mapname); + //NOTE: first reset the entity links into the AAS areas and BSP leaves + // the AAS link heap and BSP link heap are reset after respectively the + // AAS file and BSP file are loaded + AAS_ResetEntityLinks(); + // + + // load bsp info + AAS_LoadBSPFile(); + + //load the aas file + Com_sprintf(aasfile, MAX_PATH, "maps/%s.aas", mapname); + errnum = AAS_LoadAASFile(aasfile); + if (errnum != BLERR_NOERROR) + { + return errnum; + } + + botimport.Print(PRT_MESSAGE, "loaded %s\n", aasfile); + strncpy((*aasworld).filename, aasfile, MAX_PATH); + return BLERR_NOERROR; +} //end of the function AAS_LoadFiles +//=========================================================================== +// called everytime a map changes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +// Ridah, modified this for multiple AAS files + +int AAS_LoadMap(const char *mapname) +{ + int errnum; + int i; + char this_mapname[256]; //, intstr[4]; + qboolean loaded = qfalse; + int missingErrNum = 0; + + for (i = 0; i < MAX_AAS_WORLDS; i++) + { + AAS_SetCurrentWorld(i); + + strncpy(this_mapname, mapname, 256); + //strncat( this_mapname, "_b", 256 ); + //sprintf( intstr, "%i", i ); + //strncat( this_mapname, intstr, 256 ); + + //if no mapname is provided then the string indexes are updated + if (!mapname) + { + return 0; + } //end if + // + (*aasworld).initialized = qfalse; + //NOTE: free the routing caches before loading a new map because + // to free the caches the old number of areas, number of clusters + // and number of areas in a clusters must be available + AAS_FreeRoutingCaches(); + //load the map + errnum = AAS_LoadFiles(this_mapname); + if (errnum != BLERR_NOERROR) + { + (*aasworld).loaded = qfalse; + // RF, we are allowed to skip one of the files, but not both + //return errnum; + missingErrNum = errnum; + continue; + } //end if + // + loaded = qtrue; + // + AAS_InitSettings(); + //initialize the AAS link heap for the new map + AAS_InitAASLinkHeap(); + //initialize the AAS linked entities for the new map + AAS_InitAASLinkedEntities(); + //initialize reachability for the new map + AAS_InitReachability(); + //initialize the alternative routing + AAS_InitAlternativeRouting(); + } + + if (!loaded) + { + return missingErrNum; + } + + //everything went ok + return 0; +} //end of the function AAS_LoadMap + +// done. + +//=========================================================================== +// called when the library is first loaded +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Setup(void) +{ + // Ridah, just use the default world for entities + AAS_SetCurrentWorld(0); + + (*aasworlds).maxclients = (int) LibVarValue("maxclients", "128"); + (*aasworlds).maxentities = (int) LibVarValue("maxentities", "1024"); + //allocate memory for the entities + if ((*aasworld).entities) + { + FreeMemory((*aasworld).entities); + } + (*aasworld).entities = (aas_entity_t *) GetClearedHunkMemory((*aasworld).maxentities * sizeof(aas_entity_t)); + //invalidate all the entities + AAS_InvalidateEntities(); + + //force some recalculations + //LibVarSet("forceclustering", "1"); //force clustering calculation + //LibVarSet("forcereachability", "1"); //force reachability calculation + (*aasworld).numframes = 0; + return BLERR_NOERROR; +} //end of the function AAS_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Shutdown(void) +{ + // Ridah, do each of the worlds + int i; + + for (i = 0; i < MAX_AAS_WORLDS; i++) + { + AAS_SetCurrentWorld(i); + + // Ridah, kill the route-table data + AAS_RT_ShutdownRouteTable(); + + AAS_ShutdownAlternativeRouting(); + AAS_DumpBSPData(); + //free routing caches + AAS_FreeRoutingCaches(); + //free aas link heap + AAS_FreeAASLinkHeap(); + //free aas linked entities + AAS_FreeAASLinkedEntities(); + //free the aas data + AAS_DumpAASData(); + + if (i == 0) + { + //free the entities + if ((*aasworld).entities) + { + FreeMemory((*aasworld).entities); + } + } + + //clear the (*aasworld) structure + memset(&(*aasworld), 0, sizeof(aas_t)); + //aas has not been initialized + (*aasworld).initialized = qfalse; + } + + //NOTE: as soon as a new .bsp file is loaded the .bsp file memory is + // freed an reallocated, so there's no need to free that memory here + //print shutdown + botimport.Print(PRT_MESSAGE, "AAS shutdown.\n"); +} //end of the function AAS_Shutdown diff --git a/src/botlib/be_aas_main.h b/src/botlib/be_aas_main.h new file mode 100644 index 000000000..5e8017be8 --- /dev/null +++ b/src/botlib/be_aas_main.h @@ -0,0 +1,65 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_main.h + */ + +#ifdef AASINTERN + +extern aas_t(*aasworld); + +//AAS error message +void QDECL AAS_Error(char *fmt, ...); +//set AAS initialized +void AAS_SetInitialized(void); +//setup AAS with the given number of entities and clients +int AAS_Setup(void); +//shutdown AAS +void AAS_Shutdown(void); +//start a new map +int AAS_LoadMap(const char *mapname); +//start a new time frame +int AAS_StartFrame(float time); +#endif //AASINTERN + +//returns true if AAS is initialized +int AAS_Initialized(void); +//returns true if the AAS file is loaded +int AAS_Loaded(void); +//returns the model name from the given index +char *AAS_ModelFromIndex(int index); +//returns the index from the given model name +int AAS_IndexFromModel(char *modelname); +//returns the current time +float AAS_Time(void); + +// Ridah +void AAS_SetCurrentWorld(int index); +// done. diff --git a/src/botlib/be_aas_move.c b/src/botlib/be_aas_move.c new file mode 100644 index 000000000..98fce016f --- /dev/null +++ b/src/botlib/be_aas_move.c @@ -0,0 +1,1133 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_move.c + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +//#define BSPC + +extern botlib_import_t botimport; + +aas_settings_t aassettings; + +//#define AAS_MOVE_DEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t end; + bsp_trace_t trace; + + VectorCopy(origin, end); + end[2] -= 100; + trace = AAS_Trace(origin, mins, maxs, end, 0, (CONTENTS_SOLID | CONTENTS_PLAYERCLIP) & ~CONTENTS_BODY); + if (trace.startsolid) + { + return qfalse; + } + VectorCopy(trace.endpos, origin); + return qtrue; +} //end of the function AAS_DropToFloor +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitSettings(void) +{ + aassettings.sv_friction = 6; + aassettings.sv_stopspeed = 100; + aassettings.sv_gravity = 800; + aassettings.sv_waterfriction = 1; + aassettings.sv_watergravity = 400; + aassettings.sv_maxvelocity = 320; + aassettings.sv_maxwalkvelocity = 300; + aassettings.sv_maxcrouchvelocity = 100; + aassettings.sv_maxswimvelocity = 150; + aassettings.sv_walkaccelerate = 10; + aassettings.sv_airaccelerate = 1; + aassettings.sv_swimaccelerate = 4; + aassettings.sv_maxstep = 18; + aassettings.sv_maxsteepness = 0.7; + aassettings.sv_maxwaterjump = 17; + // Ridah, calculate maxbarrier according to jumpvel and gravity + aassettings.sv_jumpvel = 270; + aassettings.sv_maxbarrier = 49; //-0.8 + (0.5 * aassettings.sv_gravity * (aassettings.sv_jumpvel / aassettings.sv_gravity) * (aassettings.sv_jumpvel / aassettings.sv_gravity)); + // done. + +} //end of the function AAS_InitSettings +//=========================================================================== +// returns qtrue if the bot is against a ladder +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AgainstLadder(vec3_t origin, int ms_areanum) +{ + int areanum, i, facenum, side; + vec3_t org; + aas_plane_t *plane; + aas_face_t *face; + aas_area_t *area; + + VectorCopy(origin, org); + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] -= 2; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] -= 2; + areanum = AAS_PointAreaNum(org); + } //end if + } //end if + } //end if + } //end if + //if in solid... wrrr shouldn't happen + //if (!areanum) return qfalse; + // RF, it does if they're in a monsterclip brush + if (!areanum) + { + areanum = ms_areanum; + } + //if not in a ladder area + if (!((*aasworld).areasettings[areanum].areaflags & AREA_LADDER)) + { + return qfalse; + } + //if a crouch only area + if (!((*aasworld).areasettings[areanum].presencetype & PRESENCE_NORMAL)) + { + return qfalse; + } + // + area = &(*aasworld).areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = (*aasworld).faceindex[area->firstface + i]; + side = facenum < 0; + face = &(*aasworld).faces[abs(facenum)]; + //if the face isn't a ladder face + if (!(face->faceflags & FACE_LADDER)) + { + continue; + } + //get the plane the face is in + plane = &(*aasworld).planes[face->planenum ^ side]; + //if the origin is pretty close to the plane + if (abs(DotProduct(plane->normal, origin) - plane->dist) < 7) + { + // RF, if hanging on to the edge of a ladder, we have to account for bounding box touching + //if (AAS_PointInsideFace(abs(facenum), origin, 0.1)) return qtrue; + if (AAS_PointInsideFace(abs(facenum), origin, 2.0)) + { + return qtrue; + } + } //end if + } //end for + return qfalse; +} //end of the function AAS_AgainstLadder +//=========================================================================== +// returns qtrue if the bot is on the ground +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OnGround(vec3_t origin, int presencetype, int passent) +{ + //aas_trace_t trace; + bsp_trace_t trace; + vec3_t end, up = { 0, 0, 1 }; + //aas_plane_t *plane; + vec3_t mins, maxs; + + VectorCopy(origin, end); + end[2] -= 10; + + //trace = AAS_TraceClientBBox(origin, end, presencetype, passent); + AAS_PresenceTypeBoundingBox(presencetype, mins, maxs); + trace = AAS_Trace(origin, mins, maxs, end, passent, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + + //if in solid + if (trace.startsolid) + { + return qtrue; //qfalse; + } + //if nothing hit at all + if (trace.fraction >= 1.0) + { + return qfalse; + } + //if too far from the hit plane + if (origin[2] - trace.endpos[2] > 10) + { + return qfalse; + } + //check if the plane isn't too steep + //plane = AAS_PlaneFromNum(trace.planenum); + if (DotProduct(trace.plane.normal, up) < aassettings.sv_maxsteepness) + { + return qfalse; + } + //the bot is on the ground + return qtrue; +} //end of the function AAS_OnGround +//=========================================================================== +// returns qtrue if a bot at the given position is swimming +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Swimming(vec3_t origin) +{ + vec3_t testorg; + + VectorCopy(origin, testorg); + testorg[2] -= 2; + if (AAS_PointContents(testorg) & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) + { + return qtrue; + } + return qfalse; +} //end of the function AAS_Swimming +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec3_t VEC_UP = { 0, -1, 0 }; +vec3_t MOVEDIR_UP = { 0, 0, 1 }; +vec3_t VEC_DOWN = { 0, -2, 0 }; +vec3_t MOVEDIR_DOWN = { 0, 0, -1 }; + +void AAS_SetMovedir(vec3_t angles, vec3_t movedir) +{ + if (VectorCompare(angles, VEC_UP)) + { + VectorCopy(MOVEDIR_UP, movedir); + } //end if + else if (VectorCompare(angles, VEC_DOWN)) + { + VectorCopy(MOVEDIR_DOWN, movedir); + } //end else if + else + { + AngleVectors(angles, movedir, NULL, NULL); + } //end else +} //end of the function AAS_SetMovedir +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_JumpReachRunStart(aas_reachability_t *reach, vec3_t runstart) +{ + vec3_t hordir, start, cmdmove; + aas_clientmove_t move; + + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //start point + VectorCopy(reach->start, start); + start[2] += 1; + //get command movement + VectorScale(hordir, 400, cmdmove); + // + AAS_PredictClientMovement(&move, -1, start, PRESENCE_NORMAL, qtrue, + vec3_origin, cmdmove, 1, 2, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA | + SE_HITGROUNDDAMAGE | SE_GAP, 0, qfalse); + VectorCopy(move.endpos, runstart); + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERLAVA | SE_HITGROUNDDAMAGE)) //----(SA) modified since slime is no longer deadly + { // if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + VectorCopy(start, runstart); + } //end if +} //end of the function AAS_JumpReachRunStart +//=========================================================================== +// returns the Z velocity when rocket jumping at the origin +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_WeaponJumpZVelocity(vec3_t origin, float radiusdamage) +{ + vec3_t kvel, v, start, end, forward, right, viewangles, dir; + float mass, knockback, points; + vec3_t rocketoffset = { 8, 8, -8 }; + vec3_t botmins = { -16, -16, -24 }; + vec3_t botmaxs = { 16, 16, 32 }; + bsp_trace_t bsptrace; + + //look down (90 degrees) + viewangles[PITCH] = 90; + viewangles[YAW] = 0; + viewangles[ROLL] = 0; + //get the start point shooting from + VectorCopy(origin, start); + start[2] += 8; //view offset Z + AngleVectors(viewangles, forward, right, NULL); + start[0] += forward[0] * rocketoffset[0] + right[0] * rocketoffset[1]; + start[1] += forward[1] * rocketoffset[0] + right[1] * rocketoffset[1]; + start[2] += forward[2] * rocketoffset[0] + right[2] * rocketoffset[1] + rocketoffset[2]; + //end point of the trace + VectorMA(start, 500, forward, end); + //trace a line to get the impact point + bsptrace = AAS_Trace(start, NULL, NULL, end, 1, CONTENTS_SOLID); + //calculate the damage the bot will get from the rocket impact + VectorAdd(botmins, botmaxs, v); + VectorMA(origin, 0.5, v, v); + VectorSubtract(bsptrace.endpos, v, v); + // + points = radiusdamage - 0.5 * VectorLength(v); + if (points < 0) + { + points = 0; + } + //the owner of the rocket gets half the damage + points *= 0.5; + //mass of the bot (p_client.c: PutClientInServer) + mass = 200; + //knockback is the same as the damage points + knockback = points; + //direction of the damage (from trace.endpos to bot origin) + VectorSubtract(origin, bsptrace.endpos, dir); + VectorNormalize(dir); + //damage velocity + VectorScale(dir, 1600.0 * (float)knockback / mass, kvel); //the rocket jump hack... + //rocket impact velocity + jump velocity + return kvel[2] + aassettings.sv_jumpvel; +} //end of the function AAS_WeaponJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_RocketJumpZVelocity(vec3_t origin) +{ + //rocket radius damage is 120 (p_weapon.c: Weapon_RocketLauncher_Fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_RocketJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_BFGJumpZVelocity(vec3_t origin) +{ + //bfg radius damage is 1000 (p_weapon.c: weapon_bfg_fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_BFGJumpZVelocity +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Accelerate(vec3_t velocity, float frametime, vec3_t wishdir, float wishspeed, float accel) +{ + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct(velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + { + return; + } + accelspeed = accel * frametime * wishspeed; + if (accelspeed > addspeed) + { + accelspeed = addspeed; + } + + for (i = 0 ; i < 3 ; i++) + { + velocity[i] += accelspeed * wishdir[i]; + } +} //end of the function AAS_Accelerate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AirControl(vec3_t start, vec3_t end, vec3_t velocity, vec3_t cmdmove) +{ + vec3_t dir; + + VectorSubtract(end, start, dir); +} //end of the function AAS_AirControl +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ApplyFriction(vec3_t vel, float friction, float stopspeed, + float frametime) +{ + float speed, control, newspeed; + + //horizontal speed + speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); + if (speed) + { + control = speed < stopspeed ? stopspeed : speed; + newspeed = speed - frametime * control * friction; + if (newspeed < 0) + { + newspeed = 0; + } + newspeed /= speed; + vel[0] *= newspeed; + vel[1] *= newspeed; + } //end if +} //end of the function AAS_ApplyFriction +//=========================================================================== +// predicts the movement +// assumes regular bounding box sizes +// NOTE: out of water jumping is not included +// NOTE: grappling hook is not included +// +// Parameter: origin : origin to start with +// presencetype : presence type to start with +// velocity : velocity to start with +// cmdmove : client command movement +// cmdframes : number of frame cmdmove is valid +// maxframes : maximum number of predicted frames +// frametime : duration of one predicted frame +// stopevent : events that stop the prediction +// stopareanum : stop as soon as entered this area +// Returns: aas_clientmove_t +// Changes Globals: - +//=========================================================================== +int AAS_PredictClientMovement(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int hitent, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize) +{ + float sv_friction, sv_stopspeed, sv_gravity, sv_waterfriction; + float sv_watergravity; + float sv_walkaccelerate, sv_airaccelerate, sv_swimaccelerate; + float sv_maxwalkvelocity, sv_maxcrouchvelocity, sv_maxswimvelocity; + float sv_maxstep, sv_maxsteepness, sv_jumpvel, friction; + float gravity, delta, maxvel, wishspeed, accelerate; + //float velchange, newvel; + int n, i, j, pc, step, swimming, ax, crouch, event, jump_frame, areanum; + int areas[20], numareas; + vec3_t points[20], mins, maxs; + vec3_t org, end, feet, start, stepend, lastorg, wishdir; + vec3_t frame_test_vel, old_frame_test_vel, left_test_vel, savevel; + vec3_t up = { 0, 0, 1 }; + cplane_t *plane, *plane2, *lplane; + //aas_trace_t trace, steptrace; + bsp_trace_t trace, steptrace; + + if (visualize) + { + +// These debugging tools are not currently available in bspc. Mad Doctor I, 1/27/2003. +#ifndef BSPC + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); +#endif + + } + + // don't let us succeed on interaction with area 0 + if (stopareanum == 0) + { + stopevent &= ~(SE_ENTERAREA | SE_HITGROUNDAREA); + } + + if (frametime <= 0) + { + frametime = 0.1; + } + // + sv_friction = aassettings.sv_friction; + sv_stopspeed = aassettings.sv_stopspeed; + sv_gravity = aassettings.sv_gravity; + sv_waterfriction = aassettings.sv_waterfriction; + sv_watergravity = aassettings.sv_watergravity; + sv_maxwalkvelocity = aassettings.sv_maxwalkvelocity; // * frametime; + sv_maxcrouchvelocity = aassettings.sv_maxcrouchvelocity; // * frametime; + sv_maxswimvelocity = aassettings.sv_maxswimvelocity; // * frametime; + sv_walkaccelerate = aassettings.sv_walkaccelerate; + sv_airaccelerate = aassettings.sv_airaccelerate; + sv_swimaccelerate = aassettings.sv_swimaccelerate; + sv_maxstep = aassettings.sv_maxstep; + sv_maxsteepness = aassettings.sv_maxsteepness; + sv_jumpvel = aassettings.sv_jumpvel * frametime; + // + memset(move, 0, sizeof(aas_clientmove_t)); + memset(&trace, 0, sizeof(bsp_trace_t)); + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + //start at the current origin + VectorCopy(origin, org); + org[2] += 0.25; + // test this position, if it's in solid, move it up to adjust for capsules + //trace = AAS_TraceClientBBox(org, org, PRESENCE_NORMAL, entnum); + trace = AAS_Trace(org, mins, maxs, org, entnum, (CONTENTS_SOLID | CONTENTS_PLAYERCLIP) & ~CONTENTS_BODY); + while (trace.startsolid) + { + org[2] += 8; + //trace = AAS_TraceClientBBox(org, org, PRESENCE_NORMAL, entnum); + trace = AAS_Trace(org, mins, maxs, org, entnum, (CONTENTS_SOLID | CONTENTS_PLAYERCLIP) & ~CONTENTS_BODY); + if (trace.startsolid && (org[2] - origin[2] > 16)) + { + move->stopevent = SE_NONE; + return qfalse; + } + } + //velocity to test for the first frame + VectorScale(velocity, frametime, frame_test_vel); + // + jump_frame = -1; + lplane = NULL; + //predict a maximum of 'maxframes' ahead + for (n = 0; n < maxframes; n++) + { + swimming = AAS_Swimming(org); + //get gravity depending on swimming or not + gravity = swimming ? sv_watergravity : sv_gravity; + //apply gravity at the START of the frame + frame_test_vel[2] = frame_test_vel[2] - (gravity * 0.1 * frametime); + //if on the ground or swimming + if (onground || swimming) + { + friction = swimming ? sv_friction : sv_waterfriction; + //apply friction + VectorScale(frame_test_vel, 1 / frametime, frame_test_vel); + AAS_ApplyFriction(frame_test_vel, friction, sv_stopspeed, frametime); + VectorScale(frame_test_vel, frametime, frame_test_vel); + } //end if + crouch = qfalse; + //apply command movement + if (cmdframes < 0) + { + // cmdmove is the destination, we should keep moving towards it + VectorSubtract(cmdmove, org, wishdir); + VectorNormalize(wishdir); + VectorScale(wishdir, sv_maxwalkvelocity, wishdir); + VectorCopy(frame_test_vel, savevel); + VectorScale(wishdir, frametime, frame_test_vel); + if (!swimming) + { + frame_test_vel[2] = savevel[2]; + } + } + else if (n < cmdframes) + { + ax = 0; + maxvel = sv_maxwalkvelocity; + accelerate = sv_airaccelerate; + VectorCopy(cmdmove, wishdir); + if (onground) + { + if (cmdmove[2] < -300) + { + crouch = qtrue; + maxvel = sv_maxcrouchvelocity; + } //end if + //if not swimming and upmove is positive then jump + if (!swimming && cmdmove[2] > 1) + { + //jump velocity minus the gravity for one frame + 5 for safety + frame_test_vel[2] = sv_jumpvel - (gravity * 0.1 * frametime) + 5; + jump_frame = n; + //jumping so air accelerate + accelerate = sv_airaccelerate; + } //end if + else + { + accelerate = sv_walkaccelerate; + } //end else + ax = 2; + } //end if + if (swimming) + { + maxvel = sv_maxswimvelocity; + accelerate = sv_swimaccelerate; + ax = 3; + } //end if + else + { + wishdir[2] = 0; + } //end else + // + wishspeed = VectorNormalize(wishdir); + if (wishspeed > maxvel) + { + wishspeed = maxvel; + } + VectorScale(frame_test_vel, 1 / frametime, frame_test_vel); + AAS_Accelerate(frame_test_vel, frametime, wishdir, wishspeed, accelerate); + VectorScale(frame_test_vel, frametime, frame_test_vel); + /* + for (i = 0; i < ax; i++) + { + velchange = (cmdmove[i] * frametime) - frame_test_vel[i]; + if (velchange > sv_maxacceleration) velchange = sv_maxacceleration; + else if (velchange < -sv_maxacceleration) velchange = -sv_maxacceleration; + newvel = frame_test_vel[i] + velchange; + // + if (frame_test_vel[i] <= maxvel && newvel > maxvel) frame_test_vel[i] = maxvel; + else if (frame_test_vel[i] >= -maxvel && newvel < -maxvel) frame_test_vel[i] = -maxvel; + else frame_test_vel[i] = newvel; + } //end for + */ + } //end if + //if (crouch) + //{ + // presencetype = PRESENCE_CROUCH; + //} //end if + //else if (presencetype == PRESENCE_CROUCH) + //{ + // if (AAS_PointPresenceType(org) & PRESENCE_NORMAL) + // { + // presencetype = PRESENCE_NORMAL; + // } //end if + //} //end else + //save the current origin + VectorCopy(org, lastorg); + //move linear during one frame + VectorCopy(frame_test_vel, left_test_vel); + j = 0; + do + { + VectorAdd(org, left_test_vel, end); + //trace a bounding box + //trace = AAS_TraceClientBBox(org, end, PRESENCE_NORMAL, entnum); + trace = AAS_Trace(org, mins, maxs, end, entnum, (CONTENTS_SOLID | CONTENTS_PLAYERCLIP) & ~CONTENTS_BODY); + // +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + //if (trace.startsolid) + //botimport.Print(PRT_MESSAGE, "PredictMovement: start solid\n"); + AAS_DebugLine(org, trace.endpos, LINECOLOR_RED); + } //end if +//#endif //AAS_MOVE_DEBUG + // + if (stopevent & SE_HITENT) + { + if (trace.fraction < 1.0 && trace.ent == hitent) + { + areanum = AAS_PointAreaNum(org); + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITENT; + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } + } + + if (stopevent & SE_ENTERAREA) + { + numareas = AAS_TraceAreas(org, trace.endpos, areas, points, 20); + for (i = 0; i < numareas; i++) + { + if (areas[i] == stopareanum) + { + VectorCopy(points[i], move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_ENTERAREA; + move->presencetype = (*aasworld).areasettings[areas[i]].presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end for + } //end if + + if (stopevent & SE_STUCK) + { + if (trace.fraction < 1.0) + { + plane = &trace.plane; + //if (Q_fabs(plane->normal[2]) <= sv_maxsteepness) { + VectorNormalize2(frame_test_vel, wishdir); + if (DotProduct(plane->normal, wishdir) < -0.8) + { + areanum = AAS_PointAreaNum(org); + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_STUCK; + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } + } + } + + //move the entity to the trace end point + VectorCopy(trace.endpos, org); + //if there was a collision + if (trace.fraction < 1.0) + { + //get the plane the bounding box collided with + plane = &trace.plane; + // + if (stopevent & SE_HITGROUNDAREA) + { + if (DotProduct(plane->normal, up) > sv_maxsteepness) + { + VectorCopy(org, start); + start[2] += 0.5; + if ((stopareanum < 0 && AAS_PointAreaNum(start)) || (AAS_PointAreaNum(start) == stopareanum)) + { + VectorCopy(start, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDAREA; + move->presencetype = (*aasworld).areasettings[stopareanum].presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + //assume there's no step + step = qfalse; + //if it is a vertical plane and the bot didn't jump recently + if (plane->normal[2] == 0 && (jump_frame < 0 || n - jump_frame > 2)) + { + //check for a step + VectorMA(org, -0.25, plane->normal, start); + VectorCopy(start, stepend); + start[2] += sv_maxstep; + //steptrace = AAS_TraceClientBBox(start, stepend, PRESENCE_NORMAL, entnum); + steptrace = AAS_Trace(start, mins, maxs, stepend, entnum, (CONTENTS_SOLID | CONTENTS_PLAYERCLIP) & ~CONTENTS_BODY); + // + if (!steptrace.startsolid) + { + plane2 = &steptrace.plane; + if (DotProduct(plane2->normal, up) > sv_maxsteepness) + { + VectorSubtract(end, steptrace.endpos, left_test_vel); + left_test_vel[2] = 0; + frame_test_vel[2] = 0; +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + if (steptrace.endpos[2] - org[2] > 0.125) + { + VectorCopy(org, start); + start[2] = steptrace.endpos[2]; + AAS_DebugLine(org, start, LINECOLOR_BLUE); + } //end if + } //end if +//#endif //AAS_MOVE_DEBUG + org[2] = steptrace.endpos[2]; + step = qtrue; + } //end if + } //end if + } //end if + // + if (!step) + { + //velocity left to test for this frame is the projection + //of the current test velocity into the hit plane + VectorMA(left_test_vel, -DotProduct(left_test_vel, plane->normal), + plane->normal, left_test_vel); + // RF: from PM_SlideMove() + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + if (lplane && DotProduct(lplane->normal, plane->normal) > 0.99) + { + VectorAdd(plane->normal, left_test_vel, left_test_vel); + } + lplane = plane; + //store the old velocity for landing check + VectorCopy(frame_test_vel, old_frame_test_vel); + //test velocity for the next frame is the projection + //of the velocity of the current frame into the hit plane + VectorMA(frame_test_vel, -DotProduct(frame_test_vel, plane->normal), + plane->normal, frame_test_vel); + //check for a landing on an almost horizontal floor + if (DotProduct(plane->normal, up) > sv_maxsteepness) + { + onground = qtrue; + } //end if + if (stopevent & SE_HITGROUNDDAMAGE) + { + delta = 0; + if (old_frame_test_vel[2] < 0 && + frame_test_vel[2] > old_frame_test_vel[2] && + !onground) + { + delta = old_frame_test_vel[2]; + } //end if + else if (onground) + { + delta = frame_test_vel[2] - old_frame_test_vel[2]; + } //end else + if (delta) + { + delta = delta * 10; + delta = delta * delta * 0.0001; + if (swimming) + { + delta = 0; + } + // never take falling damage if completely underwater + /* + if (ent->waterlevel == 3) return; + if (ent->waterlevel == 2) delta *= 0.25; + if (ent->waterlevel == 1) delta *= 0.5; + */ + if (delta > 40) + { + VectorCopy(org, move->endpos); + VectorCopy(frame_test_vel, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDDAMAGE; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + } + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end if + //extra check to prevent endless loop + if (++j > 20) + { + return qfalse; + } + //while there is a plane hit + } + while (trace.fraction < 1.0); + //if going down + if (frame_test_vel[2] <= 10) + { + //check for a liquid at the feet of the bot + VectorCopy(org, feet); + feet[2] -= 22; + pc = AAS_PointContents(feet); + //get event from pc + event = SE_NONE; + if (pc & CONTENTS_LAVA) + { + event |= SE_ENTERLAVA; + } + if (pc & CONTENTS_SLIME) + { + event |= SE_ENTERSLIME; + } + if (pc & CONTENTS_WATER) + { + event |= SE_ENTERWATER; + } + // + areanum = AAS_PointAreaNum(org); + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_LAVA) + { + event |= SE_ENTERLAVA; + } + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_SLIME) + { + event |= SE_ENTERSLIME; + } + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_WATER) + { + event |= SE_ENTERWATER; + } + //if in lava or slime + if (event & stopevent) + { + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->stopevent = event & stopevent; + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + move->endcontents = pc; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + // + onground = AAS_OnGround(org, PRESENCE_NORMAL, entnum); + //if onground and on the ground for at least one whole frame + if (onground) + { + if (stopevent & SE_HITGROUND) + { + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUND; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + } + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + else if (stopevent & SE_LEAVEGROUND) + { + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_LEAVEGROUND; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + } + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end else if + else if (stopevent & SE_GAP) + { + bsp_trace_t gaptrace; + + VectorCopy(org, start); + VectorCopy(start, end); + end[2] -= 48 + aassettings.sv_maxbarrier; + //gaptrace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + gaptrace = AAS_Trace(start, mins, maxs, end, -1, (CONTENTS_SOLID | CONTENTS_PLAYERCLIP) & ~CONTENTS_BODY); + //if solid is found the bot cannot walk any further and will not fall into a gap + if (!gaptrace.startsolid) + { + //if it is a gap (lower than one step height) + if (gaptrace.endpos[2] < org[2] - aassettings.sv_maxstep - 1) + { + if (!(AAS_PointContents(end) & (CONTENTS_WATER | CONTENTS_SLIME))) //----(SA) modified since slime is no longer deadly + { // if (!(AAS_PointContents(end) & CONTENTS_WATER)) + VectorCopy(lastorg, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_GAP; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + } + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end else if + if (stopevent & SE_TOUCHJUMPPAD) + { + if ((*aasworld).areasettings[AAS_PointAreaNum(org)].contents & AREACONTENTS_JUMPPAD) + { + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHJUMPPAD; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + } + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if (stopevent & SE_TOUCHTELEPORTER) + { + if ((*aasworld).areasettings[AAS_PointAreaNum(org)].contents & AREACONTENTS_TELEPORTER) + { + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHTELEPORTER; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + move->presencetype = (*aasworld).areasettings[areanum].presencetype; + } + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end for + // + areanum = AAS_PointAreaNum(org); + VectorCopy(org, move->endpos); + VectorScale(frame_test_vel, 1 / frametime, move->velocity); + move->stopevent = SE_NONE; + move->presencetype = aasworld->areasettings ? aasworld->areasettings[areanum].presencetype : 0; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + // + return qtrue; +} //end of the function AAS_PredictClientMovement +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir) +{ + vec3_t velocity, cmdmove; + aas_clientmove_t move; + + VectorClear(velocity); + if (!AAS_Swimming(origin)) + { + dir[2] = 0; + } + VectorNormalize(dir); + VectorScale(dir, 400, cmdmove); + cmdmove[2] = 224; + AAS_ClearShownDebugLines(); + AAS_PredictClientMovement(&move, entnum, origin, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 13, 13, 0.1, SE_HITGROUND, 0, qtrue); //SE_LEAVEGROUND); + if (move.stopevent & SE_LEAVEGROUND) + { + botimport.Print(PRT_MESSAGE, "leave ground\n"); + } //end if +} //end of the function TestMovementPrediction +//=========================================================================== +// calculates the horizontal velocity needed to perform a jump from start +// to end +// +// Parameter: zvel : z velocity for jump +// start : start position of jump +// end : end position of jump +// *speed : returned speed for jump +// Returns: qfalse if too high or too far from start to end +// Changes Globals: - +//=========================================================================== +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity) +{ + float sv_gravity, sv_maxvelocity; + float maxjump, height2fall, t, top; + vec3_t dir; + + sv_gravity = aassettings.sv_gravity; + sv_maxvelocity = aassettings.sv_maxvelocity; + + //maximum height a player can jump with the given initial z velocity + maxjump = 0.5 * sv_gravity * (zvel / sv_gravity) * (zvel / sv_gravity); + //top of the parabolic jump + top = start[2] + maxjump; + //height the bot will fall from the top + height2fall = top - end[2]; + //if the goal is to high to jump to + if (height2fall < 0) + { + *velocity = sv_maxvelocity; + return 0; + } //end if + //time a player takes to fall the height + t = sqrt(height2fall / (0.5 * sv_gravity)); + //direction from start to end + VectorSubtract(end, start, dir); + //calculate horizontal speed + *velocity = sqrt(dir[0] * dir[0] + dir[1] * dir[1]) / (t + zvel / sv_gravity); + //the horizontal speed must be lower than the max speed + if (*velocity > sv_maxvelocity) + { + *velocity = sv_maxvelocity; + return 0; + } //end if + return 1; +} //end of the function AAS_HorizontalVelocityForJump diff --git a/src/botlib/be_aas_move.h b/src/botlib/be_aas_move.h new file mode 100644 index 000000000..6ca5c1c84 --- /dev/null +++ b/src/botlib/be_aas_move.h @@ -0,0 +1,65 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_move.h + */ + +#ifdef AASINTERN +extern aas_settings_t aassettings; +#endif //AASINTERN + +//movement prediction +int AAS_PredictClientMovement(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +//returns true if on the ground at the given origin +int AAS_OnGround(vec3_t origin, int presencetype, int passent); +//returns true if swimming at the given origin +int AAS_Swimming(vec3_t origin); +//returns the jump reachability run start point +void AAS_JumpReachRunStart(struct aas_reachability_s *reach, vec3_t runstart); +//returns true if against a ladder at the given origin +int AAS_AgainstLadder(vec3_t origin, int ms_areanum); +//rocket jump Z velocity when rocket-jumping at origin +float AAS_RocketJumpZVelocity(vec3_t origin); +//bfg jump Z velocity when bfg-jumping at origin +float AAS_BFGJumpZVelocity(vec3_t origin); +//calculates the horizontal velocity needed for a jump and returns true this velocity could be calculated +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity); +// +void AAS_SetMovedir(vec3_t angles, vec3_t movedir); +// +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs); +// +void AAS_InitSettings(void); diff --git a/src/botlib/be_aas_optimize.c b/src/botlib/be_aas_optimize.c new file mode 100644 index 000000000..f0a6a8aea --- /dev/null +++ b/src/botlib/be_aas_optimize.c @@ -0,0 +1,742 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_optimize.c + * @brief decreases the .aas file size after the reachabilities have been + * calculated, just dumps all the faces, edges and vertexes + */ + +#include "../qcommon/q_shared.h" +#include "l_libvar.h" +//#include "l_utils.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +typedef struct optimized_s +{ + //vertixes + int numvertexes; + aas_vertex_t *vertexes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + + /// + // RF, addition of removal of non-reachability areas + // + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; +/* //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //clusters + int numclusters; + aas_cluster_t *clusters; +*/ // + int *vertexoptimizeindex; + int *edgeoptimizeindex; + int *faceoptimizeindex; + // + int *areakeep; + int *arearemap; + int *removedareas; + int *reachabilityremap; +} optimized_t; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepEdge(aas_edge_t *edge) +{ + return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeEdge(optimized_t *optimized, int edgenum) +{ + int i, optedgenum; + aas_edge_t *edge, *optedge; + + edge = &(*aasworld).edges[abs(edgenum)]; + if (!AAS_KeepEdge(edge)) + { + return 0; + } + + optedgenum = optimized->edgeoptimizeindex[abs(edgenum)]; + if (optedgenum) + { + //keep the edge reversed sign + if (edgenum > 0) + { + return optedgenum; + } + else + { + return -optedgenum; + } + } //end if + + optedge = &optimized->edges[optimized->numedges]; + + for (i = 0; i < 2; i++) + { + if (optimized->vertexoptimizeindex[edge->v[i]]) + { + optedge->v[i] = optimized->vertexoptimizeindex[edge->v[i]]; + } //end if + else + { + VectorCopy((*aasworld).vertexes[edge->v[i]], optimized->vertexes[optimized->numvertexes]); + optedge->v[i] = optimized->numvertexes; + optimized->vertexoptimizeindex[edge->v[i]] = optimized->numvertexes; + optimized->numvertexes++; + } //end else + } //end for + optimized->edgeoptimizeindex[abs(edgenum)] = optimized->numedges; + optedgenum = optimized->numedges; + optimized->numedges++; + //keep the edge reversed sign + if (edgenum > 0) + { + return optedgenum; + } + else + { + return -optedgenum; + } +} //end of the function AAS_OptimizeEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepFace(aas_face_t *face) +{ + if (!(face->faceflags & FACE_LADDER)) + { + return 0; + } + else + { + return 1; + } +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeFace(optimized_t *optimized, int facenum) +{ + int i, edgenum, optedgenum, optfacenum; + aas_face_t *face, *optface; + + face = &(*aasworld).faces[abs(facenum)]; + if (!AAS_KeepFace(face)) + { + return 0; + } + + optfacenum = optimized->faceoptimizeindex[abs(facenum)]; + if (optfacenum) + { + //keep the face side sign + if (facenum > 0) + { + return optfacenum; + } + else + { + return -optfacenum; + } + } //end if + + optface = &optimized->faces[optimized->numfaces]; + memcpy(optface, face, sizeof(aas_face_t)); + + optface->numedges = 0; + optface->firstedge = optimized->edgeindexsize; + for (i = 0; i < face->numedges; i++) + { + edgenum = (*aasworld).edgeindex[face->firstedge + i]; + optedgenum = AAS_OptimizeEdge(optimized, edgenum); + if (optedgenum) + { + optimized->edgeindex[optface->firstedge + optface->numedges] = optedgenum; + optface->numedges++; + optimized->edgeindexsize++; + } //end if + } //end for + optimized->faceoptimizeindex[abs(facenum)] = optimized->numfaces; + optfacenum = optimized->numfaces; + optimized->numfaces++; + //keep the face side sign + if (facenum > 0) + { + return optfacenum; + } + else + { + return -optfacenum; + } +} //end of the function AAS_OptimizeFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeArea(optimized_t *optimized, int areanum) +{ + int i, facenum, optfacenum; + aas_area_t *area, *optarea; + + area = &(*aasworld).areas[areanum]; + optarea = &optimized->areas[areanum]; + memcpy(optarea, area, sizeof(aas_area_t)); + + optarea->numfaces = 0; + optarea->firstface = optimized->faceindexsize; + for (i = 0; i < area->numfaces; i++) + { + facenum = (*aasworld).faceindex[area->firstface + i]; + optfacenum = AAS_OptimizeFace(optimized, facenum); + if (optfacenum) + { + optimized->faceindex[optarea->firstface + optarea->numfaces] = optfacenum; + optarea->numfaces++; + optimized->faceindexsize++; + } //end if + } //end for +} //end of the function AAS_OptimizeArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeAlloc(optimized_t *optimized) +{ + optimized->vertexes = (aas_vertex_t *) GetClearedMemory((*aasworld).numvertexes * sizeof(aas_vertex_t)); + optimized->numvertexes = 0; + optimized->edges = (aas_edge_t *) GetClearedMemory((*aasworld).numedges * sizeof(aas_edge_t)); + optimized->numedges = 1; //edge zero is a dummy + optimized->edgeindex = (aas_edgeindex_t *) GetClearedMemory((*aasworld).edgeindexsize * sizeof(aas_edgeindex_t)); + optimized->edgeindexsize = 0; + optimized->faces = (aas_face_t *) GetClearedMemory((*aasworld).numfaces * sizeof(aas_face_t)); + optimized->numfaces = 1; //face zero is a dummy + optimized->faceindex = (aas_faceindex_t *) GetClearedMemory((*aasworld).faceindexsize * sizeof(aas_faceindex_t)); + optimized->faceindexsize = 0; + optimized->areas = (aas_area_t *) GetClearedMemory((*aasworld).numareas * sizeof(aas_area_t)); + optimized->numareas = (*aasworld).numareas; + // + optimized->vertexoptimizeindex = (int *) GetClearedMemory((*aasworld).numvertexes * sizeof(int)); + optimized->edgeoptimizeindex = (int *) GetClearedMemory((*aasworld).numedges * sizeof(int)); + optimized->faceoptimizeindex = (int *) GetClearedMemory((*aasworld).numfaces * sizeof(int)); +} //end of the function AAS_OptimizeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeStore(optimized_t *optimized) +{ + //store the optimized vertexes + if ((*aasworld).vertexes) + { + FreeMemory((*aasworld).vertexes); + } + (*aasworld).vertexes = optimized->vertexes; + (*aasworld).numvertexes = optimized->numvertexes; + //store the optimized edges + if ((*aasworld).edges) + { + FreeMemory((*aasworld).edges); + } + (*aasworld).edges = optimized->edges; + (*aasworld).numedges = optimized->numedges; + //store the optimized edge index + if ((*aasworld).edgeindex) + { + FreeMemory((*aasworld).edgeindex); + } + (*aasworld).edgeindex = optimized->edgeindex; + (*aasworld).edgeindexsize = optimized->edgeindexsize; + //store the optimized faces + if ((*aasworld).faces) + { + FreeMemory((*aasworld).faces); + } + (*aasworld).faces = optimized->faces; + (*aasworld).numfaces = optimized->numfaces; + //store the optimized face index + if ((*aasworld).faceindex) + { + FreeMemory((*aasworld).faceindex); + } + (*aasworld).faceindex = optimized->faceindex; + (*aasworld).faceindexsize = optimized->faceindexsize; + //store the optimized areas + if ((*aasworld).areas) + { + FreeMemory((*aasworld).areas); + } + (*aasworld).areas = optimized->areas; + (*aasworld).numareas = optimized->numareas; + //free optimize indexes + FreeMemory(optimized->vertexoptimizeindex); + FreeMemory(optimized->edgeoptimizeindex); + FreeMemory(optimized->faceoptimizeindex); +} //end of the function AAS_OptimizeStore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Optimize(void) +{ + int i, sign; + optimized_t optimized; + + AAS_OptimizeAlloc(&optimized); + for (i = 1; i < (*aasworld).numareas; i++) + { + AAS_OptimizeArea(&optimized, i); + } //end for + //reset the reachability face pointers + for (i = 0; i < (*aasworld).reachabilitysize; i++) + { + //NOTE: for TRAVEL_ELEVATOR the facenum is the model number of + // the elevator + if ((*aasworld).reachability[i].traveltype == TRAVEL_ELEVATOR) + { + continue; + } + //NOTE: for TRAVEL_JUMPPAD the facenum is the Z velocity and the edgenum is the hor velocity + if ((*aasworld).reachability[i].traveltype == TRAVEL_JUMPPAD) + { + continue; + } + //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information + if ((*aasworld).reachability[i].traveltype == TRAVEL_FUNCBOB) + { + continue; + } + // + sign = (*aasworld).reachability[i].facenum; + (*aasworld).reachability[i].facenum = optimized.faceoptimizeindex[abs((*aasworld).reachability[i].facenum)]; + if (sign < 0) + { + (*aasworld).reachability[i].facenum = -(*aasworld).reachability[i].facenum; + } + sign = (*aasworld).reachability[i].edgenum; + (*aasworld).reachability[i].edgenum = optimized.edgeoptimizeindex[abs((*aasworld).reachability[i].edgenum)]; + if (sign < 0) + { + (*aasworld).reachability[i].edgenum = -(*aasworld).reachability[i].edgenum; + } + } //end for + //store the optimized AAS data into (*aasworld) + AAS_OptimizeStore(&optimized); + //print some nice stuff :) + botimport.Print(PRT_MESSAGE, "AAS data optimized.\n"); +} //end of the function AAS_Optimize + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveNonReachabilityAlloc(optimized_t *optimized) +{ + optimized->areas = (aas_area_t *) GetClearedMemory((*aasworld).numareas * sizeof(aas_area_t)); + // + optimized->areasettings = (aas_areasettings_t *) GetClearedMemory((*aasworld).numareas * sizeof(aas_areasettings_t)); + // + optimized->reachability = (aas_reachability_t *) GetClearedMemory((*aasworld).reachabilitysize * sizeof(aas_reachability_t)); + optimized->reachabilitysize = (*aasworld).reachabilitysize; +/* // + optimized->nodes = (aas_node_t *) GetClearedMemory((*aasworld).numnodes * sizeof(aas_node_t)); + optimized->numnodes = (*aasworld).numnodes; + // + optimized->portals = (aas_portals_t *) GetClearedMemory((*aasworld).numportals * sizeof(aas_portal_t)); + optimized->numportals = (*aasworld).numportals; + // + optimized->clusters = (aas_cluster_t *) GetClearedMemory((*aasworld).numclusters * sizeof(aas_cluster_t)); + optimized->numclusters = (*aasworld).numclusters; +*/ // + optimized->areakeep = (int *) GetClearedMemory((*aasworld).numareas * sizeof(int)); + optimized->arearemap = (int *) GetClearedMemory((*aasworld).numareas * sizeof(int)); + optimized->removedareas = (int *) GetClearedMemory((*aasworld).numareas * sizeof(int)); + optimized->reachabilityremap = (int *) GetClearedMemory((*aasworld).reachabilitysize * sizeof(int)); +} //end of the function AAS_OptimizeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterAreas(int clusternum); +void AAS_RemoveNonReachability(void) +{ + int i, j; + optimized_t optimized; + int removed = 0, valid = 0, numoriginalareas; + int validreach = 0; + aas_face_t *face; + + AAS_RemoveNonReachabilityAlloc(&optimized); + // mark portal areas as non-removable + if (aasworld->numportals) + { + for (i = 0; i < aasworld->numportals; i++) + { + optimized.areakeep[aasworld->portals[i].areanum] = qtrue; + } + } + // remove non-reachability areas + numoriginalareas = aasworld->numareas; + for (i = 1; i < (*aasworld).numareas; i++) + { + // is this a reachability area? + if (optimized.areakeep[i] || aasworld->areasettings[i].numreachableareas) + { + optimized.arearemap[i] = ++valid; + // copy it to the optimized areas + optimized.areas[valid] = (*aasworld).areas[i]; + optimized.areas[valid].areanum = valid; + continue; + } + // yes it is if it made it to here + removed++; + optimized.removedareas[i] = qtrue; + } + optimized.numareas = valid + 1; + // store the new areas + if ((*aasworld).areas) + { + FreeMemory((*aasworld).areas); + } + (*aasworld).areas = optimized.areas; + (*aasworld).numareas = optimized.numareas; + // + // remove reachabilities that are no longer required + validreach = 1; + for (i = 1; i < aasworld->reachabilitysize; i++) + { + optimized.reachabilityremap[i] = validreach; + if (optimized.removedareas[aasworld->reachability[i].areanum]) + { + continue; + } + // save this reachability + optimized.reachability[validreach] = aasworld->reachability[i]; + optimized.reachability[validreach].areanum = optimized.arearemap[optimized.reachability[validreach].areanum]; + // + validreach++; + } + optimized.reachabilitysize = validreach; + // store the reachabilities + if ((*aasworld).reachability) + { + FreeMemory((*aasworld).reachability); + } + (*aasworld).reachability = optimized.reachability; + (*aasworld).reachabilitysize = optimized.reachabilitysize; + // + // remove and update areasettings + for (i = 1; i < numoriginalareas; i++) + { + if (optimized.removedareas[i]) + { + continue; + } + j = optimized.arearemap[i]; + optimized.areasettings[j] = aasworld->areasettings[i]; + optimized.areasettings[j].firstreachablearea = optimized.reachabilityremap[aasworld->areasettings[i].firstreachablearea]; + optimized.areasettings[j].numreachableareas = 1 + optimized.reachabilityremap[aasworld->areasettings[i].firstreachablearea + aasworld->areasettings[i].numreachableareas - 1] - optimized.areasettings[j].firstreachablearea; + } + // + // update faces (TODO: remove unused) + for (i = 1, face = &aasworld->faces[1]; i < aasworld->numfaces; i++, face++) + { + if (!optimized.removedareas[face->backarea]) + { + face->backarea = optimized.arearemap[face->backarea]; + } + else // now points to a void + { + face->backarea = 0; + } + if (!optimized.removedareas[face->frontarea]) + { + face->frontarea = optimized.arearemap[face->frontarea]; + } + else + { + face->frontarea = 0; + } + } + // store the areasettings + if ((*aasworld).areasettings) + { + FreeMemory((*aasworld).areasettings); + } + (*aasworld).areasettings = optimized.areasettings; + (*aasworld).numareasettings = optimized.numareas; + // + // update nodes + for (i = 1; i < (*aasworld).numnodes; i++) + { + for (j = 0; j < 2; j++) + { + if (aasworld->nodes[i].children[j] < 0) + { + if (optimized.removedareas[-aasworld->nodes[i].children[j]]) + { + aasworld->nodes[i].children[j] = 0; //make it solid + } + else // remap + { + aasworld->nodes[i].children[j] = -optimized.arearemap[-aasworld->nodes[i].children[j]]; + } + } + } + } + // + // update portal areanums + for (i = 0; i < aasworld->numportals; i++) + { + aasworld->portals[i].areanum = optimized.arearemap[aasworld->portals[i].areanum]; + } + // update clusters and portals + for (i = 0; i < (*aasworld).numclusters; i++) + { + AAS_NumberClusterAreas(i); + } + // free temporary memory + FreeMemory(optimized.areakeep); + FreeMemory(optimized.arearemap); + FreeMemory(optimized.removedareas); + FreeMemory(optimized.reachabilityremap); + //print some nice stuff :) + botimport.Print(PRT_MESSAGE, "%i non-reachability areas removed, %i remain.\n", removed, valid); +} //end of the function AAS_Optimize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveNonGrounded(void) +{ + int i, j; + optimized_t optimized; + int removed = 0, valid = 0, numoriginalareas; + int validreach = 0; + aas_face_t *face; + + AAS_RemoveNonReachabilityAlloc(&optimized); + // mark portal areas as non-removable + if (aasworld->numportals) + { + for (i = 0; i < aasworld->numportals; i++) + { + optimized.areakeep[aasworld->portals[i].areanum] = qtrue; + } + } + // remove non-reachability areas + numoriginalareas = aasworld->numareas; + for (i = 1; i < (*aasworld).numareas; i++) + { + // is this a grounded area? + if (optimized.areakeep[i] || (aasworld->areasettings[i].areaflags & (AREA_GROUNDED | AREA_LADDER))) + { + optimized.arearemap[i] = ++valid; + // copy it to the optimized areas + optimized.areas[valid] = (*aasworld).areas[i]; + optimized.areas[valid].areanum = valid; + continue; + } + // yes it is if it made it to here + removed++; + optimized.removedareas[i] = qtrue; + } + optimized.numareas = valid + 1; + // store the new areas + if ((*aasworld).areas) + { + FreeMemory((*aasworld).areas); + } + (*aasworld).areas = optimized.areas; + (*aasworld).numareas = optimized.numareas; + // + // remove reachabilities that are no longer required + validreach = 1; + for (i = 1; i < aasworld->reachabilitysize; i++) + { + optimized.reachabilityremap[i] = validreach; + if (optimized.removedareas[aasworld->reachability[i].areanum]) + { + continue; + } + // save this reachability + optimized.reachability[validreach] = aasworld->reachability[i]; + optimized.reachability[validreach].areanum = optimized.arearemap[optimized.reachability[validreach].areanum]; + // + validreach++; + } + optimized.reachabilitysize = validreach; + // store the reachabilities + if ((*aasworld).reachability) + { + FreeMemory((*aasworld).reachability); + } + (*aasworld).reachability = optimized.reachability; + (*aasworld).reachabilitysize = optimized.reachabilitysize; + // + // remove and update areasettings + for (i = 1; i < numoriginalareas; i++) + { + if (optimized.removedareas[i]) + { + continue; + } + j = optimized.arearemap[i]; + optimized.areasettings[j] = aasworld->areasettings[i]; + optimized.areasettings[j].firstreachablearea = optimized.reachabilityremap[aasworld->areasettings[i].firstreachablearea]; + optimized.areasettings[j].numreachableareas = 1 + optimized.reachabilityremap[aasworld->areasettings[i].firstreachablearea + aasworld->areasettings[i].numreachableareas - 1] - optimized.areasettings[j].firstreachablearea; + } + // + // update faces (TODO: remove unused) + for (i = 1, face = &aasworld->faces[1]; i < aasworld->numfaces; i++, face++) + { + if (!optimized.removedareas[face->backarea]) + { + face->backarea = optimized.arearemap[face->backarea]; + } + else // now points to a void + { + face->backarea = 0; + } + if (!optimized.removedareas[face->frontarea]) + { + face->frontarea = optimized.arearemap[face->frontarea]; + } + else + { + face->frontarea = 0; + } + } + // store the areasettings + if ((*aasworld).areasettings) + { + FreeMemory((*aasworld).areasettings); + } + (*aasworld).areasettings = optimized.areasettings; + (*aasworld).numareasettings = optimized.numareas; + // + // update nodes + for (i = 1; i < (*aasworld).numnodes; i++) + { + for (j = 0; j < 2; j++) + { + if (aasworld->nodes[i].children[j] < 0) + { + if (optimized.removedareas[-aasworld->nodes[i].children[j]]) + { + aasworld->nodes[i].children[j] = 0; //make it solid + } + else // remap + { + aasworld->nodes[i].children[j] = -optimized.arearemap[-aasworld->nodes[i].children[j]]; + } + } + } + } + // + // update portal areanums + for (i = 0; i < aasworld->numportals; i++) + { + aasworld->portals[i].areanum = optimized.arearemap[aasworld->portals[i].areanum]; + } + // update clusters and portals + for (i = 0; i < (*aasworld).numclusters; i++) + { + AAS_NumberClusterAreas(i); + } + // free temporary memory + FreeMemory(optimized.areakeep); + FreeMemory(optimized.arearemap); + FreeMemory(optimized.removedareas); + FreeMemory(optimized.reachabilityremap); + //print some nice stuff :) + botimport.Print(PRT_MESSAGE, "%i non-grounded areas removed, %i remain.\n", removed, valid); +} //end of the function AAS_Optimize diff --git a/src/botlib/be_aas_optimize.h b/src/botlib/be_aas_optimize.h new file mode 100644 index 000000000..34e6b128d --- /dev/null +++ b/src/botlib/be_aas_optimize.h @@ -0,0 +1,38 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_optimize.h + * @brief decreases the .aas file size after the reachabilities have been + * calculated, just dumps all the faces, edges and vertexes + */ + +void AAS_Optimize(void); +void AAS_RemoveNonReachability(void); +void AAS_RemoveNonGrounded(void); diff --git a/src/botlib/be_aas_reach.c b/src/botlib/be_aas_reach.c new file mode 100644 index 000000000..3844b8fab --- /dev/null +++ b/src/botlib/be_aas_reach.c @@ -0,0 +1,5183 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_reach.c + * @brief reachability calculations + */ + +#include "../qcommon/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_libvar.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern int Sys_MilliSeconds(void); + +//#include "../../../gladiator/bspc/aas_store.h" + +extern botlib_import_t botimport; + +//#define REACHDEBUG + +//NOTE: all travel times are in hundreth of a second +//maximum fall delta before getting damaged + +// Ridah, tweaked for Wolf AI +#define FALLDELTA_5DAMAGE 25 //40 +#define FALLDELTA_10DAMAGE 40 //60 +// done. + +//maximum number of reachability links +#define AAS_MAX_REACHABILITYSIZE 128000 +//number of areas reachability is calculated for each frame +#define REACHABILITYAREASPERCYCLE 15 +//number of units reachability points are placed inside the areas +#define INSIDEUNITS 2 + +// Ridah, tweaked this, routing issues around small areas +#define INSIDEUNITS_WALKEND 5 // original +//#define INSIDEUNITS_WALKEND 0.2 // new + +// Ridah, added this for better walking off ledges +#define INSIDEUNITS_WALKOFFLEDGEEND 15 + +#define INSIDEUNITS_WALKSTART 0.1 +#define INSIDEUNITS_WATERJUMP 15 +//travel times in hundreth of a second + +// Ridah, tweaked these for Wolf AI +#define REACH_MIN_TIME 4 // always at least this much time for a reachability +#define WATERJUMP_TIME 700 //7 seconds +#define TELEPORT_TIME 50 //0.5 seconds +#define BARRIERJUMP_TIME 900 //fixed value? +#define STARTCROUCH_TIME 300 //3 sec to start crouching +#define STARTGRAPPLE_TIME 500 //using the grapple costs a lot of time +#define STARTWALKOFFLEDGE_TIME 300 //3 seconds +#define STARTJUMP_TIME 500 //3 seconds for jumping + +#define FALLDAMAGE_5_TIME 400 //extra travel time when falling hurts +#define FALLDAMAGE_10_TIME 900 //extra travel time when falling hurts +// done. + +//maximum height the bot may fall down when jumping +#define MAX_JUMPFALLHEIGHT 450 +//area flag used for weapon jumping +#define AREA_WEAPONJUMP 8192 //valid area to weapon jump to +#define AREA_JUMPSRC 16384 //valid area to JUMP FROM +//number of reachabilities of each type +int reach_swim; //swim +int reach_equalfloor; //walk on floors with equal height +int reach_step; //step up +int reach_walk; //walk of step +int reach_barrier; //jump up to a barrier +int reach_waterjump; //jump out of water +int reach_walkoffledge; //walk of a ledge +int reach_jump; //jump +int reach_ladder; //climb or descent a ladder +int reach_teleport; //teleport +int reach_elevator; //use an elevator +int reach_funcbob; //use a func bob +int reach_grapple; //grapple hook +int reach_doublejump; //double jump +int reach_rampjump; //ramp jump +int reach_strafejump; //strafe jump (just normal jump but further) +int reach_rocketjump; //rocket jump +int reach_bfgjump; //bfg jump +int reach_jumppad; //jump pads +//if true grapple reachabilities are skipped +int calcgrapplereach = qfalse; +//linked reachability +typedef struct aas_lreachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime; //travel time of the inter area movement + // + struct aas_lreachability_s *next; +} aas_lreachability_t; +//temporary reachabilities +aas_lreachability_t *reachabilityheap; //heap with reachabilities +aas_lreachability_t *nextreachability; //next free reachability from the heap +aas_lreachability_t **areareachability; //reachability links for every area +int numlreachabilities; + +typedef struct +{ + int destarea; + vec3_t srcpos; + vec3_t destpos; +} aas_jumplink_t; + +static aas_jumplink_t *jumplinks; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableLinkArea(aas_link_t *areas) +{ + aas_link_t *link; + + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaGrounded(link->areanum) || AAS_AreaSwim(link->areanum)) + { + return link->areanum; + } //end if + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (link->areanum) + { + return link->areanum; + } + //FIXME: cannot enable next line right now because the reachability + // does not have to be calculated when the level items are loaded + //if (AAS_AreaReachability(link->areanum)) return link->areanum; + } //end for + return 0; +} //end of the function AAS_BestReachableLinkArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin) +{ + int areanum, i, j, k, l; + aas_link_t *areas; + vec3_t absmins, absmaxs; + //vec3_t bbmins, bbmaxs; + vec3_t start, end; + aas_trace_t trace; + + if (!(*aasworld).loaded) + { + botimport.Print(PRT_ERROR, "AAS_BestReachableArea: aas not loaded\n"); + return 0; + } //end if + //find a point in an area + VectorCopy(origin, start); + areanum = AAS_PointAreaNum(start); + //while no area found fudge around a little + for (i = 0; i < 5 && !areanum; i++) + { + for (j = 0; j < 5 && !areanum; j++) + { + for (k = -1; k <= 1 && !areanum; k++) + { + for (l = -1; l <= 1 && !areanum; l++) + { + VectorCopy(origin, start); + start[0] += (float) j * 4 * k; + start[1] += (float) j * 4 * l; + start[2] += (float) i * 4; + areanum = AAS_PointAreaNum(start); + } //end for + } //end for + } //end for + } //end for + //if an area was found + if (areanum) + { + //drop client bbox down and try again + VectorCopy(start, end); + start[2] += 0.25; + end[2] -= 50; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + areanum = AAS_PointAreaNum(trace.endpos); + VectorCopy(trace.endpos, goalorigin); + //FIXME: cannot enable next line right now because the reachability + // does not have to be calculated when the level items are loaded + //if the origin is in an area with reachability + //if (AAS_AreaReachability(areanum)) return areanum; + if (AAS_AreaGrounded(areanum)) + { + return areanum; + } + } //end if + else + { + //it can very well happen that the AAS_PointAreaNum function tells that + //a point is in an area and that starting a AAS_TraceClientBBox from that + //point will return trace.startsolid qtrue + /* + if (AAS_PointAreaNum(start)) + { + Log_Write("point %f %f %f in area %d but trace startsolid", start[0], start[1], start[2], areanum); + AAS_DrawPermanentCross(start, 4, LINECOLOR_RED); + } //end if + botimport.Print(PRT_MESSAGE, "AAS_BestReachableArea: start solid\n"); + */ + VectorCopy(start, goalorigin); + return areanum; + } //end else + } //end if + // + //AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + //NOTE: the goal origin does not have to be in the goal area + // because the bot will have to move towards the item origin anyway + VectorCopy(origin, goalorigin); + // + VectorAdd(origin, mins, absmins); + VectorAdd(origin, maxs, absmaxs); + //add bounding box size + //VectorSubtract(absmins, bbmaxs, absmins); + //VectorSubtract(absmaxs, bbmins, absmaxs); + //link an invalid (-1) entity + areas = AAS_AASLinkEntity(absmins, absmaxs, -1); + //get the reachable link arae + areanum = AAS_BestReachableLinkArea(areas); + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + // + return areanum; +} //end of the function AAS_BestReachableArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetupReachabilityHeap(void) +{ + int i; + + reachabilityheap = (aas_lreachability_t *) GetClearedMemory( + AAS_MAX_REACHABILITYSIZE * sizeof(aas_lreachability_t)); + for (i = 0; i < AAS_MAX_REACHABILITYSIZE - 1; i++) + { + reachabilityheap[i].next = &reachabilityheap[i + 1]; + } //end for + reachabilityheap[AAS_MAX_REACHABILITYSIZE - 1].next = NULL; + nextreachability = reachabilityheap; + numlreachabilities = 0; +} //end of the function AAS_InitReachabilityHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutDownReachabilityHeap(void) +{ + FreeMemory(reachabilityheap); + numlreachabilities = 0; +} //end of the function AAS_ShutDownReachabilityHeap +//=========================================================================== +// returns a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_AllocReachability(void) +{ + aas_lreachability_t *r; + + if (!nextreachability) + { + return NULL; + } + //make sure the error message only shows up once + if (!nextreachability->next) + { + AAS_Error("AAS_MAX_REACHABILITYSIZE"); + } + // + r = nextreachability; + nextreachability = nextreachability->next; + numlreachabilities++; + return r; +} //end of the function AAS_AllocReachability +//=========================================================================== +// frees a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeReachability(aas_lreachability_t *lreach) +{ + memset(lreach, 0, sizeof(aas_lreachability_t)); + + lreach->next = nextreachability; + nextreachability = lreach; + numlreachabilities--; +} //end of the function AAS_FreeReachability +//=========================================================================== +// returns qtrue if the area has reachability links +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachability(int areanum) +{ + if (areanum < 0 || areanum >= (*aasworld).numareas) + { + AAS_Error("AAS_AreaReachability: areanum %d out of range", areanum); + return 0; + } //end if + // RF, if this area is disabled, then fail + if ((*aasworld).areasettings[areanum].areaflags & AREA_DISABLED) + { + return 0; + } + return (*aasworld).areasettings[areanum].numreachableareas; +} //end of the function AAS_AreaReachability +//=========================================================================== +// returns the surface area of the given face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FaceArea(aas_face_t *face) +{ + int i, edgenum, side; + float total; + vec_t *v; + vec3_t d1, d2, cross; + aas_edge_t *edge; + + edgenum = (*aasworld).edgeindex[face->firstedge]; + side = edgenum < 0; + edge = &(*aasworld).edges[abs(edgenum)]; + v = (*aasworld).vertexes[edge->v[side]]; + + total = 0; + for (i = 1; i < face->numedges - 1; i++) + { + edgenum = (*aasworld).edgeindex[face->firstedge + i]; + side = edgenum < 0; + edge = &(*aasworld).edges[abs(edgenum)]; + VectorSubtract((*aasworld).vertexes[edge->v[side]], v, d1); + VectorSubtract((*aasworld).vertexes[edge->v[!side]], v, d2); + CrossProduct(d1, d2, cross); + total += 0.5 * VectorLength(cross); + } //end for + return total; +} //end of the function AAS_FaceArea +//=========================================================================== +// returns the volume of an area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaVolume(int areanum) +{ + int i, edgenum, facenum; + vec_t d, a, volume; + vec3_t corner; + aas_plane_t *plane; + aas_edge_t *edge; + aas_face_t *face; + aas_area_t *area; + + area = &(*aasworld).areas[areanum]; + facenum = (*aasworld).faceindex[area->firstface]; + face = &(*aasworld).faces[abs(facenum)]; + edgenum = (*aasworld).edgeindex[face->firstedge]; + edge = &(*aasworld).edges[abs(edgenum)]; + // + VectorCopy((*aasworld).vertexes[edge->v[0]], corner); + + //make tetrahedrons to all other faces + volume = 0; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs((*aasworld).faceindex[area->firstface + i]); + face = &(*aasworld).faces[facenum]; + plane = &(*aasworld).planes[face->planenum]; + d = -(DotProduct(corner, plane->normal) - plane->dist); + a = AAS_FaceArea(face); + volume += d * a; + } //end for + + volume /= 3; + return volume; +} //end of the function AAS_AreaVolume +//=========================================================================== +// returns the surface area of all ground faces together of the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaGroundFaceArea(int areanum) +{ + int i; + float total; + aas_area_t *area; + aas_face_t *face; + + total = 0; + area = &(*aasworld).areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &(*aasworld).faces[abs((*aasworld).faceindex[area->firstface + i])]; + if (!(face->faceflags & FACE_GROUND)) + { + continue; + } + // + total += AAS_FaceArea(face); + } //end for + return total; +} //end of the function AAS_AreaGroundFaceArea +//=========================================================================== +// returns the center of a face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FaceCenter(int facenum, vec3_t center) +{ + int i; + float scale; + aas_face_t *face; + aas_edge_t *edge; + + face = &(*aasworld).faces[facenum]; + + VectorClear(center); + for (i = 0; i < face->numedges; i++) + { + edge = &(*aasworld).edges[abs((*aasworld).edgeindex[face->firstedge + i])]; + VectorAdd(center, (*aasworld).vertexes[edge->v[0]], center); + VectorAdd(center, (*aasworld).vertexes[edge->v[1]], center); + } //end for + scale = 0.5 / face->numedges; + VectorScale(center, scale, center); +} //end of the function AAS_FaceCenter +//=========================================================================== +// returns the maximum distance a player can fall before being damaged +// damage = deltavelocity*deltavelocity * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FallDamageDistance(void) +{ + float maxzvelocity, gravity, t; + + maxzvelocity = sqrt(30 * 10000); + gravity = aassettings.sv_gravity; + t = maxzvelocity / gravity; + return 0.5 * gravity * t * t; +} //end of the function AAS_FallDamageDistance +//=========================================================================== +// distance = 0.5 * gravity * t * t +// vel = t * gravity +// damage = vel * vel * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FallDelta(float distance) +{ + float t, delta, gravity; + + gravity = aassettings.sv_gravity; + t = sqrt(Q_fabs(distance) * 2 / gravity); + delta = t * gravity; + return delta * delta * 0.0001; +} //end of the function AAS_FallDelta +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpHeight(float sv_jumpvel) +{ + float sv_gravity; + + sv_gravity = aassettings.sv_gravity; + //maximum height a player can jump with the given initial z velocity + return 0.5 * sv_gravity * (sv_jumpvel / sv_gravity) * (sv_jumpvel / sv_gravity); +} //end of the function MaxJumpHeight +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpDistance(float sv_jumpvel) +{ + float sv_gravity, sv_maxvelocity, t; + + sv_gravity = aassettings.sv_gravity; + sv_maxvelocity = aassettings.sv_maxvelocity; + //time a player takes to fall the height + t = sqrt(MAX_JUMPFALLHEIGHT / (0.5 * sv_gravity)); + //maximum distance + return sv_maxvelocity * (t + sv_jumpvel / sv_gravity); +} //end of the function AAS_MaxJumpDistance +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/*int AAS_AreaCrouch(int areanum) { + if (!(aasworld->areasettings[areanum].presencetype & PRESENCE_NORMAL)) { + return qtrue; + } else { + return qfalse; + } +}*/ +//=========================================================================== +// returns qtrue if it is possible to swim in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/*int AAS_AreaSwim(int areanum) { + if(aasworld->areasettings[areanum].areaflags & AREA_LIQUID) { + return qtrue; + } else { + return qfalse; + } +}*/ +//=========================================================================== +// returns qtrue if the area contains a liquid +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLiquid(int areanum) +{ + if ((*aasworld).areasettings[areanum].areaflags & AREA_LIQUID) + { + return qtrue; + } + else + { + return qfalse; + } +} //end of the function AAS_AreaLiquid +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLava(int areanum) +{ + return ((*aasworld).areasettings[areanum].contents & AREACONTENTS_LAVA); +} //end of the function AAS_AreaLava +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSlime(int areanum) +{ + return ((*aasworld).areasettings[areanum].contents & AREACONTENTS_SLIME); +} //end of the function AAS_AreaSlime +//=========================================================================== +// returns qtrue if the area contains ground faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaGrounded(int areanum) +{ + return ((*aasworld).areasettings[areanum].areaflags & AREA_GROUNDED); +} //end of the function AAS_AreaGround +//=========================================================================== +// returns true if the area contains ladder faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLadder(int areanum) +{ + return ((*aasworld).areasettings[areanum].areaflags & AREA_LADDER); +} //end of the function AAS_AreaLadder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaJumpPad(int areanum) +{ + return ((*aasworld).areasettings[areanum].contents & AREACONTENTS_JUMPPAD); +} //end of the function AAS_AreaJumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTeleporter(int areanum) +{ + return ((*aasworld).areasettings[areanum].contents & AREACONTENTS_TELEPORTER); +} //end of the function AAS_AreaTeleporter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaDoNotEnter(int areanum) +{ + return ((*aasworld).areasettings[areanum].contents & AREACONTENTS_DONOTENTER); +} //end of the function AAS_AreaDoNotEnter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaDoNotEnterLarge(int areanum) +{ + return ((*aasworld).areasettings[areanum].contents & AREACONTENTS_DONOTENTER_LARGE); +} //end of the function AAS_AreaDoNotEnter +//=========================================================================== +// returns the time it takes perform a barrier jump +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_BarrierJumpTravelTime(void) +{ + return aassettings.sv_jumpvel / (aassettings.sv_gravity * 0.1); +} //end op the function AAS_BarrierJumpTravelTime +//=========================================================================== +// returns true if there already exists a reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ReachabilityExists(int area1num, int area2num) +{ + aas_lreachability_t *r; + + for (r = areareachability[area1num]; r; r = r->next) + { + if (r->areanum == area2num) + { + return qtrue; + } + } //end for + return qfalse; +} //end of the function AAS_ReachabilityExists +//=========================================================================== +// returns true if there is a solid just after the end point when going +// from start to end +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearbySolidOrGap(vec3_t start, vec3_t end) +{ + vec3_t dir, testpoint; + int areanum; + + VectorSubtract(end, start, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorMA(end, 48, dir, testpoint); + + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) + { + testpoint[2] += 16; + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) + { + return qtrue; + } + } //end if + VectorMA(end, 64, dir, testpoint); + areanum = AAS_PointAreaNum(testpoint); + if (areanum) + { + if (!AAS_AreaSwim(areanum) && !AAS_AreaGrounded(areanum)) + { + return qtrue; + } + } //end if + return qfalse; +} //end of the function AAS_SolidGapTime +//=========================================================================== +// searches for swim reachabilities between adjacent areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Swim(int area1num, int area2num) +{ + int i, j, face1num, face2num, side1; + aas_area_t *area1, *area2; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_face_t *face1; + aas_plane_t *plane; + vec3_t start; + + if (!AAS_AreaSwim(area1num) || !AAS_AreaSwim(area2num)) + { + return qfalse; + } + //if the second area is crouch only + if (!((*aasworld).areasettings[area2num].presencetype & PRESENCE_NORMAL)) + { + return qfalse; + } + + area1 = &(*aasworld).areas[area1num]; + area2 = &(*aasworld).areas[area2num]; + + //if the areas are not near anough + for (i = 0; i < 3; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) + { + return qfalse; + } + if (area1->maxs[i] < area2->mins[i] - 10) + { + return qfalse; + } + } //end for + //find a shared face and create a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1num = (*aasworld).faceindex[area1->firstface + i]; + side1 = face1num < 0; + face1num = abs(face1num); + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = abs((*aasworld).faceindex[area2->firstface + j]); + // + if (face1num == face2num) + { + AAS_FaceCenter(face1num, start); + // + if (AAS_PointContents(start) & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) + { + // + face1 = &(*aasworld).faces[face1num]; + areasettings = &(*aasworld).areasettings[area1num]; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = face1num; + lreach->edgenum = 0; + VectorCopy(start, lreach->start); + plane = &(*aasworld).planes[face1->planenum ^ side1]; + VectorMA(lreach->start, INSIDEUNITS, plane->normal, lreach->end); + lreach->traveltype = TRAVEL_SWIM; + lreach->traveltime = 1; + //if the volume of the area is rather small + if (AAS_AreaVolume(area2num) < 800) + { + lreach->traveltime += 200; + } + //if (!(AAS_PointContents(start) & MASK_WATER)) lreach->traveltime += 500; + //link the reachability + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + reach_swim++; + return qtrue; + } //end if + } //end if + } //end for + } //end for + return qfalse; +} //end of the function AAS_Reachability_Swim +//=========================================================================== +// searches for reachabilities between adjacent areas with equal floor +// heights +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_EqualFloorHeight(int area1num, int area2num) +{ + int i, j, edgenum, edgenum1, edgenum2, foundreach, side; + float height, bestheight, length, bestlength; + vec3_t dir, start, end, normal, invgravity, gravitydirection = { 0, 0, -1 }; + vec3_t edgevec; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge; + aas_plane_t *plane2; + aas_lreachability_t lr, *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) + { + return qfalse; + } + + area1 = &(*aasworld).areas[area1num]; + area2 = &(*aasworld).areas[area2num]; + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) + { + return qfalse; + } + if (area1->maxs[i] < area2->mins[i] - 10) + { + return qfalse; + } + } //end for + //if area 2 is too high above area 1 + if (area2->mins[2] > area1->maxs[2]) + { + return qfalse; + } + // + VectorCopy(gravitydirection, invgravity); + VectorInverse(invgravity); + // + bestheight = 99999; + bestlength = 0; + foundreach = qfalse; + memset(&lr, 0, sizeof(aas_lreachability_t)); //make the compiler happy + // + //check if the areas have ground faces with a common edge + //if existing use the lowest common edge for a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1 = &(*aasworld).faces[abs((*aasworld).faceindex[area1->firstface + i])]; + if (!(face1->faceflags & FACE_GROUND)) + { + continue; + } + // + for (j = 0; j < area2->numfaces; j++) + { + face2 = &(*aasworld).faces[abs((*aasworld).faceindex[area2->firstface + j])]; + if (!(face2->faceflags & FACE_GROUND)) + { + continue; + } + //if there is a common edge + for (edgenum1 = 0; edgenum1 < face1->numedges; edgenum1++) + { + for (edgenum2 = 0; edgenum2 < face2->numedges; edgenum2++) + { + if (abs((*aasworld).edgeindex[face1->firstedge + edgenum1]) != + abs((*aasworld).edgeindex[face2->firstedge + edgenum2])) + { + continue; + } + edgenum = (*aasworld).edgeindex[face1->firstedge + edgenum1]; + side = edgenum < 0; + edge = &(*aasworld).edges[abs(edgenum)]; + //get the length of the edge + VectorSubtract((*aasworld).vertexes[edge->v[1]], + (*aasworld).vertexes[edge->v[0]], dir); + length = VectorLength(dir); + //get the start point + VectorAdd((*aasworld).vertexes[edge->v[0]], + (*aasworld).vertexes[edge->v[1]], start); + VectorScale(start, 0.5, start); + VectorCopy(start, end); + //get the end point several units inside area2 + //and the start point several units inside area1 + //NOTE: normal is pointing into area2 because the + //face edges are stored counter clockwise + VectorSubtract((*aasworld).vertexes[edge->v[side]], + (*aasworld).vertexes[edge->v[!side]], edgevec); + plane2 = &(*aasworld).planes[face2->planenum]; + CrossProduct(edgevec, plane2->normal, normal); + VectorNormalize(normal); + // + //VectorMA(start, -1, normal, start); + VectorMA(end, INSIDEUNITS_WALKEND, normal, end); + VectorMA(start, INSIDEUNITS_WALKSTART, normal, start); + end[2] += 0.125; + // + height = DotProduct(invgravity, start); + //NOTE: if there's nearby solid or a gap area after this area + //disabled this crap + //if (AAS_NearbySolidOrGap(start, end)) height += 200; + //NOTE: disabled because it disables reachabilities to very small areas + //if (AAS_PointAreaNum(end) != area2num) continue; + //get the longest lowest edge + if (height < bestheight || + (height < bestheight + 1 && length > bestlength)) + { + bestheight = height; + bestlength = length; + //create a new reachability link + lr.areanum = area2num; + lr.facenum = 0; + lr.edgenum = edgenum; + VectorCopy(start, lr.start); + VectorCopy(end, lr.end); + lr.traveltype = TRAVEL_WALK; + lr.traveltime = 1; + foundreach = qtrue; + } //end if + } //end for + } //end for + } //end for + } //end for + if (foundreach) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = lr.areanum; + lreach->facenum = lr.facenum; + lreach->edgenum = lr.edgenum; + VectorCopy(lr.start, lreach->start); + VectorCopy(lr.end, lreach->end); + lreach->traveltype = lr.traveltype; + lreach->traveltime = lr.traveltime; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += STARTCROUCH_TIME; + } //end if + /* + //NOTE: if there's nearby solid or a gap area after this area + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_equalfloor++; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_Reachability_EqualFloorHeight +//=========================================================================== +// searches step, barrier, waterjump and walk off ledge reachabilities +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num; + int ground_bestarea2groundedgenum, ground_foundreach; + int water_bestarea2groundedgenum, water_foundreach; + int side1, area1swim, faceside1, groundface1num; + float dist, dist1, dist2, diff, invgravitydot, ortdot; + float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; + float length, ground_bestlength, water_bestlength, ground_bestdist, water_bestdist; + vec3_t v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; + vec3_t normal, ort, edgevec, start, end, dir; + vec3_t ground_beststart = { 0 }, ground_bestend = { 0 }, ground_bestnormal = { 0 }; + vec3_t water_beststart = { 0 }, water_bestend = { 0 }, water_bestnormal = { 0 }; + vec3_t invgravity = { 0, 0, 1 }; + vec3_t testpoint; + aas_plane_t *plane; + aas_area_t *area1, *area2; + aas_face_t *groundface1, *groundface2, *ground_bestface1, *water_bestface1; + aas_edge_t *edge1, *edge2; + aas_lreachability_t *lreach; + aas_trace_t trace; + + //must be able to walk or swim in the first area + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) + { + return qfalse; + } + // + if (!AAS_AreaGrounded(area2num) && !AAS_AreaSwim(area2num)) + { + return qfalse; + } + // + area1 = &(*aasworld).areas[area1num]; + area2 = &(*aasworld).areas[area2num]; + //if the first area contains a liquid + area1swim = AAS_AreaSwim(area1num); + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) + { + return qfalse; + } + if (area1->maxs[i] < area2->mins[i] - 10) + { + return qfalse; + } + } //end for + // + ground_foundreach = qfalse; + ground_bestdist = 99999; + ground_bestlength = 0; + ground_bestarea2groundedgenum = 0; + // + water_foundreach = qfalse; + water_bestdist = 99999; + water_bestlength = 0; + water_bestarea2groundedgenum = 0; + // + for (i = 0; i < area1->numfaces; i++) + { + groundface1num = (*aasworld).faceindex[area1->firstface + i]; + faceside1 = groundface1num < 0; + groundface1 = &(*aasworld).faces[abs(groundface1num)]; + //if this isn't a ground face + if (!(groundface1->faceflags & FACE_GROUND)) + { + //if we can swim in the first area + if (area1swim) + { + //face plane must be more or less horizontal + plane = &(*aasworld).planes[groundface1->planenum ^ (!faceside1)]; + if (DotProduct(plane->normal, invgravity) < 0.7) + { + continue; + } + } //end if + else + { + //if we can't swim in the area it must be a ground face + continue; + } //end else + } //end if + // + for (k = 0; k < groundface1->numedges; k++) + { + edge1num = (*aasworld).edgeindex[groundface1->firstedge + k]; + side1 = (edge1num < 0); + //NOTE: for water faces we must take the side area 1 is + // on into account because the face is shared and doesn't + // have to be oriented correctly + if (!(groundface1->faceflags & FACE_GROUND)) + { + side1 = (side1 == faceside1); + } + edge1num = abs(edge1num); + edge1 = &(*aasworld).edges[edge1num]; + //vertexes of the edge + VectorCopy((*aasworld).vertexes[edge1->v[!side1]], v1); + VectorCopy((*aasworld).vertexes[edge1->v[side1]], v2); + //get a vertical plane through the edge + //NOTE: normal is pointing into area 2 because the + //face edges are stored counter clockwise + VectorSubtract(v2, v1, edgevec); + CrossProduct(edgevec, invgravity, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + //check the faces from the second area + for (j = 0; j < area2->numfaces; j++) + { + groundface2 = &(*aasworld).faces[abs((*aasworld).faceindex[area2->firstface + j])]; + //must be a ground face + if (!(groundface2->faceflags & FACE_GROUND)) + { + continue; + } + //check the edges of this ground face + for (l = 0; l < groundface2->numedges; l++) + { + edge2num = abs((*aasworld).edgeindex[groundface2->firstedge + l]); + edge2 = &(*aasworld).edges[edge2num]; + //vertexes of the edge + VectorCopy((*aasworld).vertexes[edge2->v[0]], v3); + VectorCopy((*aasworld).vertexes[edge2->v[1]], v4); + //check the distance between the two points and the vertical plane + //through the edge of area1 + diff = DotProduct(normal, v3) - dist; + if (diff < -0.1 || diff > 0.1) + { + continue; + } + diff = DotProduct(normal, v4) - dist; + if (diff < -0.1 || diff > 0.1) + { + continue; + } + // + //project the two ground edges into the step side plane + //and calculate the shortest distance between the two + //edges if they overlap in the direction orthogonal to + //the gravity direction + CrossProduct(invgravity, normal, ort); + invgravitydot = DotProduct(invgravity, invgravity); + ortdot = DotProduct(ort, ort); + //projection into the step plane + //NOTE: since gravity is vertical this is just the z coordinate + y1 = v1[2]; //DotProduct(v1, invgravity) / invgravitydot; + y2 = v2[2]; //DotProduct(v2, invgravity) / invgravitydot; + y3 = v3[2]; //DotProduct(v3, invgravity) / invgravitydot; + y4 = v4[2]; //DotProduct(v4, invgravity) / invgravitydot; + // + x1 = DotProduct(v1, ort) / ortdot; + x2 = DotProduct(v2, ort) / ortdot; + x3 = DotProduct(v3, ort) / ortdot; + x4 = DotProduct(v4, ort) / ortdot; + // + if (x1 > x2) + { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + VectorCopy(v1, tmpv); + VectorCopy(v2, v1); + VectorCopy(tmpv, v2); + } //end if + if (x3 > x4) + { + tmp = x3; + x3 = x4; + x4 = tmp; + tmp = y3; + y3 = y4; + y4 = tmp; + VectorCopy(v3, tmpv); + VectorCopy(v4, v3); + VectorCopy(tmpv, v4); + } //end if + //if the two projected edge lines have no overlap + if (x2 <= x3 || x4 <= x1) + { +// Log_Write("lines no overlap: from area %d to %d\r\n", area1num, area2num); + continue; + } //end if + //if the two lines fully overlap + if ((x1 - 0.5 < x3 && x4 < x2 + 0.5) && + (x3 - 0.5 < x1 && x2 < x4 + 0.5)) + { + dist1 = y3 - y1; + dist2 = y4 - y2; + VectorCopy(v1, p1area1); + VectorCopy(v2, p2area1); + VectorCopy(v3, p1area2); + VectorCopy(v4, p2area2); + } //end if + else + { + //if the points are equal + if (x1 > x3 - 0.1 && x1 < x3 + 0.1) + { + dist1 = y3 - y1; + VectorCopy(v1, p1area1); + VectorCopy(v3, p1area2); + } //end if + else if (x1 < x3) + { + y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1); + dist1 = y3 - y; + VectorCopy(v3, p1area1); + p1area1[2] = y; + VectorCopy(v3, p1area2); + } //end if + else + { + y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3); + dist1 = y - y1; + VectorCopy(v1, p1area1); + VectorCopy(v1, p1area2); + p1area2[2] = y; + } //end if + //if the points are equal + if (x2 > x4 - 0.1 && x2 < x4 + 0.1) + { + dist2 = y4 - y2; + VectorCopy(v2, p2area1); + VectorCopy(v4, p2area2); + } //end if + else if (x2 < x4) + { + y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3); + dist2 = y - y2; + VectorCopy(v2, p2area1); + VectorCopy(v2, p2area2); + p2area2[2] = y; + } //end if + else + { + y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1); + dist2 = y4 - y; + VectorCopy(v4, p2area1); + p2area1[2] = y; + VectorCopy(v4, p2area2); + } //end else + } //end else + //if both distances are pretty much equal + //then we take the middle of the points + if (dist1 > dist2 - 1 && dist1 < dist2 + 1) + { + dist = dist1; + VectorAdd(p1area1, p2area1, start); + VectorScale(start, 0.5, start); + VectorAdd(p1area2, p2area2, end); + VectorScale(end, 0.5, end); + } //end if + else if (dist1 < dist2) + { + dist = dist1; + VectorCopy(p1area1, start); + VectorCopy(p1area2, end); + } //end else if + else + { + dist = dist2; + VectorCopy(p2area1, start); + VectorCopy(p2area2, end); + } //end else + //get the length of the overlapping part of the edges of the two areas + VectorSubtract(p2area2, p1area2, dir); + length = VectorLength(dir); + // + if (groundface1->faceflags & FACE_GROUND) + { + //if the vertical distance is smaller + if (dist < ground_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < ground_bestdist + 1 && length > ground_bestlength)) + { + ground_bestdist = dist; + ground_bestlength = length; + ground_foundreach = qtrue; + ground_bestarea2groundedgenum = edge1num; + ground_bestface1 = groundface1; + //best point towards area1 + VectorCopy(start, ground_beststart); + //normal is pointing into area2 + VectorCopy(normal, ground_bestnormal); + //best point towards area2 + VectorCopy(end, ground_bestend); + } //end if + } //end if + else + { + //if the vertical distance is smaller + if (dist < water_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < water_bestdist + 1 && length > water_bestlength)) + { + water_bestdist = dist; + water_bestlength = length; + water_foundreach = qtrue; + water_bestarea2groundedgenum = edge1num; + water_bestface1 = groundface1; + //best point towards area1 + VectorCopy(start, water_beststart); + //normal is pointing into area2 + VectorCopy(normal, water_bestnormal); + //best point towards area2 + VectorCopy(end, water_bestend); + } //end if + } //end else + } //end for + } //end for + } //end for + } //end for + // + // NOTE: swim reachabilities are already filtered out + // + // Steps + // + // --------- + // | step height -> TRAVEL_WALK + //--------| + // + // --------- + //~~~~~~~~| step height and low water -> TRAVEL_WALK + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | step height and low water up to the step -> TRAVEL_WALK + //--------| + // + //check for a step reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum step height + //NOTE: ground_bestdist >= 0 also catches equal floor reachabilities + if (ground_bestdist >= 0 && ground_bestdist < aassettings.sv_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 0; //1; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += STARTCROUCH_TIME; + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //NOTE: if there's nearby solid or a gap area after this area + /* + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_step++; + return qtrue; + } //end if + } //end if + // + // Water Jumps + // + // --------- + // | + //~~~~~~~~| + // | + // | higher than step height and water up to waterjump height -> TRAVEL_WATERJUMP + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | + // | + // | + // | higher than step height and low water up to the step -> TRAVEL_WATERJUMP + //--------| + // + //check for a waterjump reachability + if (water_foundreach) + { + //get a test point a little bit towards area1 + VectorMA(water_bestend, -INSIDEUNITS, water_bestnormal, testpoint); + //go down the maximum waterjump height + testpoint[2] -= aassettings.sv_maxwaterjump; + //if there IS water the sv_maxwaterjump height below the bestend point + if ((*aasworld).areasettings[AAS_PointAreaNum(testpoint)].areaflags & AREA_LIQUID) + { + //don't create rediculous water jump reachabilities from areas very far below + //the water surface + if (water_bestdist < aassettings.sv_maxwaterjump + 24) + { + //waterjumping from or towards a crouch only area is not possible in Quake2 + if (((*aasworld).areasettings[area1num].presencetype & PRESENCE_NORMAL) && + ((*aasworld).areasettings[area2num].presencetype & PRESENCE_NORMAL)) + { + //create water jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = water_bestarea2groundedgenum; + VectorCopy(water_beststart, lreach->start); + VectorMA(water_bestend, INSIDEUNITS_WATERJUMP, water_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WATERJUMP; + lreach->traveltime = WATERJUMP_TIME; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another waterjump reachability + reach_waterjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Barrier Jumps + // + // --------- + // | + // | + // | + // | higher than step height lower than barrier height -> TRAVEL_BARRIERJUMP + //--------| + // + // --------- + // | + // | + // | + //~~~~~~~~| higher than step height lower than barrier height + //--------| and a thin layer of water in the area to jump from -> TRAVEL_BARRIERJUMP + // + //check for a barrier jump reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum barrier jump height + if (ground_bestdist > 0 && ground_bestdist < aassettings.sv_maxbarrier) + { + //if no water in area1 or a very thin layer of water on the ground + if (!water_foundreach || (ground_bestdist - water_bestdist < 16)) + { + //cannot perform a barrier jump towards or from a crouch area in Quake2 + if (!AAS_AreaCrouch(area1num) && !AAS_AreaCrouch(area2num)) + { + //create barrier jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_BARRIERJUMP; + lreach->traveltime = BARRIERJUMP_TIME; //AAS_BarrierJumpTravelTime(); + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another barrierjump reachability + reach_barrier++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Walk and Walk Off Ledge + // + //--------| + // | can walk or step back -> TRAVEL_WALK + // --------- + // + //--------| + // | + // | + // | + // | cannot walk/step back -> TRAVEL_WALKOFFLEDGE + // --------- + // + //--------| + // | + // |~~~~~~~~ + // | + // | cannot step back but can waterjump back -> TRAVEL_WALKOFFLEDGE + // --------- FIXME: create TRAVEL_WALK reach?? + // + //check for a walk or walk off ledge reachability + if (ground_foundreach) + { + if (ground_bestdist < 0) + { + if (ground_bestdist > -aassettings.sv_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + + // Ridah +// VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + VectorMA(ground_bestend, INSIDEUNITS_WALKOFFLEDGEEND, ground_bestnormal, lreach->end); + + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 1; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another walk reachability + reach_walk++; + return qtrue; + } //end if + //trace a bounding box vertically to check for solids + VectorMA(ground_bestend, INSIDEUNITS, ground_bestnormal, ground_bestend); + VectorCopy(ground_bestend, start); + start[2] = ground_beststart[2]; + VectorCopy(ground_bestend, end); + end[2] += 4; + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + //if no solids were found + if (!trace.startsolid && trace.fraction >= 1.0) + { + //the trace end point must be in the goal area + trace.endpos[2] += 1; + if (AAS_PointAreaNum(trace.endpos) == area2num) + { + //create a walk off ledge reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorCopy(ground_beststart, lreach->start); + VectorCopy(ground_bestend, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = STARTWALKOFFLEDGE_TIME + Q_fabs(ground_bestdist) * 50 / aassettings.sv_gravity; + //if falling from too high and not falling into water + if (!AAS_AreaSwim(area2num) && !AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(ground_bestdist) > FALLDELTA_5DAMAGE) + { + lreach->traveltime += FALLDAMAGE_5_TIME; + } //end if + if (AAS_FallDelta(ground_bestdist) > FALLDELTA_10DAMAGE) + { + lreach->traveltime += FALLDAMAGE_10_TIME; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_walkoffledge++; + //NOTE: don't create a weapon (rl, bfg) jump reachability here + //because it interferes with other reachabilities + //like the ladder reachability + return qtrue; + } //end if + } //end if + } //end else + } //end if + return qfalse; +} //end of the function AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge +//=========================================================================== +// returns the distance between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* Ridah, moved to q_math.c +float VectorDistance(vec3_t v1, vec3_t v2) +{ + vec3_t dir; + + VectorSubtract(v2, v1, dir); + return VectorLength(dir); +} //end of the function VectorDistance +*/ +//=========================================================================== +// returns true if the first vector is between the last two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int VectorBetweenVectors(vec3_t v, vec3_t v1, vec3_t v2) +{ + vec3_t dir1, dir2; + + VectorSubtract(v, v1, dir1); + VectorSubtract(v, v2, dir2); + return (DotProduct(dir1, dir2) <= 0); +} //end of the function VectorBetweenVectors +//=========================================================================== +// returns the mid point between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void VectorMiddle(vec3_t v1, vec3_t v2, vec3_t middle) +{ + VectorAdd(v1, v2, middle); + VectorScale(middle, 0.5, middle); +} //end of the function VectorMiddle +//=========================================================================== +// calculate a range of points closest to each other on both edges +// +// Parameter: beststart1 start of the range of points on edge v1-v2 +// beststart2 end of the range of points on edge v1-v2 +// bestend1 start of the range of points on edge v3-v4 +// bestend2 end of the range of points on edge v3-v4 +// bestdist best distance so far +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart, vec3_t bestend, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v1, beststart); + VectorMiddle(bestend, p1, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(p1, bestend); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v2, beststart); + VectorMiddle(bestend, p2, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(p2, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p3, beststart); + VectorMiddle(bestend, v3, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart); + VectorCopy(v3, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p4, beststart); + VectorMiddle(bestend, v4, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart); + VectorCopy(v4, bestend); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v4, bestend); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v4, bestend); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints*/ + +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart1, vec3_t bestend1, + vec3_t beststart2, vec3_t bestend2, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist, dist1, dist2; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v1); + dist2 = VectorDistance(beststart2, v1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(v1, beststart2); + } + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(v1, beststart1); + } + } //end else + dist1 = VectorDistance(bestend1, p1); + dist2 = VectorDistance(bestend2, p1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(p1, bestend2); + } + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(p1, bestend1); + } + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(p1, bestend1); + VectorCopy(p1, bestend2); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v2); + dist2 = VectorDistance(beststart2, v2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(v2, beststart2); + } + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(v2, beststart1); + } + } //end else + dist1 = VectorDistance(bestend1, p2); + dist2 = VectorDistance(bestend2, p2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(p2, bestend2); + } + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(p2, bestend1); + } + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(p2, bestend1); + VectorCopy(p2, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p3); + dist2 = VectorDistance(beststart2, p3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(p3, beststart2); + } + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(p3, beststart1); + } + } //end else + dist1 = VectorDistance(bestend1, v3); + dist2 = VectorDistance(bestend2, v3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(v3, bestend2); + } + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(v3, bestend1); + } + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart1); + VectorCopy(p3, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p4); + dist2 = VectorDistance(beststart2, p4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(p4, beststart2); + } + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) + { + VectorCopy(p4, beststart1); + } + } //end else + dist1 = VectorDistance(bestend1, v4); + dist2 = VectorDistance(bestend2, v4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(v4, bestend2); + } + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) + { + VectorCopy(v4, bestend1); + } + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart1); + VectorCopy(p4, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints +//=========================================================================== +// creates possible jump reachabilities between the areas +// +// The two closest points on the ground of the areas are calculated +// One of the points will be on an edge of a ground face of area1 and +// one on an edge of a ground face of area2. +// If there is a range of closest points the point in the middle of this range +// is selected. +// Between these two points there must be one or more gaps. +// If the gaps exist a potential jump is predicted. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Jump(int area1num, int area2num) +{ + int i, j, k, l, face1num, face2num, edge1num, edge2num, traveltype; + float sv_jumpvel, maxjumpdistance, maxjumpheight, height, bestdist, speed; + vec_t *v1, *v2, *v3, *v4; + vec3_t beststart, beststart2, bestend, bestend2; + vec3_t teststart, testend, dir, velocity, cmdmove, up = { 0, 0, 1 }; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge1, *edge2; + aas_plane_t *plane1, *plane2, *plane; + aas_trace_t trace; + aas_clientmove_t move; + aas_lreachability_t *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) + { + return qfalse; + } + //cannot jump from or to a crouch area + if (AAS_AreaCrouch(area1num) || AAS_AreaCrouch(area2num)) + { + return qfalse; + } + // + area1 = &(*aasworld).areas[area1num]; + area2 = &(*aasworld).areas[area2num]; + // + // RF, check for a forced jump reachability + if (aasworld->areasettings[area1num].areaflags & AREA_JUMPSRC) + { + if (jumplinks[area1num].destarea == area2num) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(jumplinks[area1num].srcpos, lreach->start); + VectorCopy(jumplinks[area1num].destpos, lreach->end); + lreach->traveltype = TRAVEL_JUMP; + + VectorCopy(jumplinks[area1num].srcpos, beststart); + VectorCopy(jumplinks[area1num].destpos, bestend); + VectorSubtract(bestend, beststart, dir); + height = dir[2]; + dir[2] = 0; + lreach->traveltime = STARTJUMP_TIME + VectorDistance(beststart, bestend) * 240 / aassettings.sv_maxwalkvelocity; + // + if (AAS_FallDelta(beststart[2] - bestend[2]) > FALLDELTA_5DAMAGE) + { + lreach->traveltime += FALLDAMAGE_5_TIME; + } //end if + else if (AAS_FallDelta(beststart[2] - bestend[2]) > FALLDELTA_10DAMAGE) + { + lreach->traveltime += FALLDAMAGE_10_TIME; + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_jump++; + return qtrue; + } + } + // + sv_jumpvel = aassettings.sv_jumpvel; + //maximum distance a player can jump + maxjumpdistance = 2 * AAS_MaxJumpDistance(sv_jumpvel); + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(sv_jumpvel); + + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + maxjumpdistance) + { + return qfalse; + } + if (area1->maxs[i] < area2->mins[i] - maxjumpdistance) + { + return qfalse; + } + } //end for + //if area2 is way to high to jump up to + if (area2->mins[2] > area1->maxs[2] + maxjumpheight) + { + return qfalse; + } + // + bestdist = 999999; + // + for (i = 0; i < area1->numfaces; i++) + { + face1num = (*aasworld).faceindex[area1->firstface + i]; + face1 = &(*aasworld).faces[abs(face1num)]; + //if not a ground face + if (!(face1->faceflags & FACE_GROUND)) + { + continue; + } + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = (*aasworld).faceindex[area2->firstface + j]; + face2 = &(*aasworld).faces[abs(face2num)]; + //if not a ground face + if (!(face2->faceflags & FACE_GROUND)) + { + continue; + } + // + for (k = 0; k < face1->numedges; k++) + { + edge1num = abs((*aasworld).edgeindex[face1->firstedge + k]); + edge1 = &(*aasworld).edges[edge1num]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = abs((*aasworld).edgeindex[face2->firstedge + l]); + edge2 = &(*aasworld).edges[edge2num]; + //calculate the minimum distance between the two edges + v1 = (*aasworld).vertexes[edge1->v[0]]; + v2 = (*aasworld).vertexes[edge1->v[1]]; + v3 = (*aasworld).vertexes[edge2->v[0]]; + v4 = (*aasworld).vertexes[edge2->v[1]]; + //get the ground planes + plane1 = &(*aasworld).planes[face1->planenum]; + plane2 = &(*aasworld).planes[face2->planenum]; + // + bestdist = AAS_ClosestEdgePoints(v1, v2, v3, v4, plane1, plane2, + beststart, bestend, + beststart2, bestend2, bestdist); + } //end for + } //end for + } //end for + } //end for + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + if (bestdist > 4 && bestdist < maxjumpdistance) + { +// Log_Write("shortest distance between %d and %d is %f\r\n", area1num, area2num, bestdist); + //if the fall would damage the bot + // + if (AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) + { + //FIXME: why multiply with 1.2??? + speed *= 1.2; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end if + else if (bestdist <= 48 && Q_fabs(beststart[2] - bestend[2]) < 8) + { + speed = 400; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end else if + else + { + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + if (!AAS_HorizontalVelocityForJump(sv_jumpvel, beststart, bestend, &speed)) + { + return qfalse; + } + traveltype = TRAVEL_JUMP; + // + //NOTE: test if the horizontal distance isn't too small + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + if (VectorLength(dir) < 10) + { + return qfalse; + } + } //end if + // + VectorSubtract(bestend, beststart, dir); + VectorNormalize(dir); + VectorMA(beststart, 1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + { + return qfalse; + } + if (trace.fraction < 1) + { + plane = &(*aasworld).planes[trace.planenum]; + if (DotProduct(plane->normal, up) >= 0.7) + { + if (!(AAS_PointContents(trace.endpos) & CONTENTS_LAVA)) //----(SA) modified since slime is no longer deadly + { // if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + if (teststart[2] - trace.endpos[2] <= aassettings.sv_maxbarrier) + { + return qfalse; + } + } //end if + } //end if + } //end if + // + VectorMA(bestend, -1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + { + return qfalse; + } + if (trace.fraction < 1) + { + plane = &(*aasworld).planes[trace.planenum]; + if (DotProduct(plane->normal, up) >= 0.7) + { + if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA /*|CONTENTS_SLIME*/))) + { + if (teststart[2] - trace.endpos[2] <= aassettings.sv_maxbarrier) + { + return qfalse; + } + } //end if + } //end if + } //end if + // + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + VectorNormalize(dir); + // + VectorScale(dir, speed, velocity); + //get command movement + VectorClear(cmdmove); + if (traveltype == TRAVEL_JUMP) + { + cmdmove[2] = aassettings.sv_jumpvel; + } + else + { + cmdmove[2] = 0; + } + // + AAS_PredictClientMovement(&move, -1, beststart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1, + SE_HITGROUND | SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE, 0, qfalse); + //if prediction time wasn't enough to fully predict the movement + if (move.frames >= 30) + { + return qfalse; + } + //don't enter slime or lava and don't fall from too high + if (move.stopevent & SE_ENTERLAVA) + { + return qfalse; //----(SA) modified since slime is no longer deadly + } +// if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) return qfalse; + //the end position should be in area2, also test a little bit back + //because the predicted jump could have rushed through the area + for (i = 0; i <= 32; i += 8) + { + VectorMA(move.endpos, -i, dir, teststart); + teststart[2] += 0.125; + if (AAS_PointAreaNum(teststart) == area2num) + { + break; + } + } //end for + if (i > 32) + { + return qfalse; + } + // +#ifdef REACHDEBUG + //create the reachability + Log_Write("jump reachability between %d and %d\r\n", area1num, area2num); +#endif //REACHDEBUG + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = traveltype; + + VectorSubtract(bestend, beststart, dir); + height = dir[2]; + dir[2] = 0; + if (traveltype == TRAVEL_WALKOFFLEDGE && height > VectorLength(dir)) + { + lreach->traveltime = STARTWALKOFFLEDGE_TIME + height * 50 / aassettings.sv_gravity; + } + else + { + lreach->traveltime = STARTJUMP_TIME + VectorDistance(bestend, beststart) * 240 / aassettings.sv_maxwalkvelocity; + } //end if + // + if (!AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(beststart[2] - bestend[2]) > FALLDELTA_5DAMAGE) + { + lreach->traveltime += FALLDAMAGE_5_TIME; + } //end if + else if (AAS_FallDelta(beststart[2] - bestend[2]) > FALLDELTA_10DAMAGE) + { + lreach->traveltime += FALLDAMAGE_10_TIME; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + if (traveltype == TRAVEL_JUMP) + { + reach_jump++; + } + else + { + reach_walkoffledge++; + } + // + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_Reachability_Jump +//=========================================================================== +// create a possible ladder reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Ladder(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num, sharededgenum, lowestedgenum; + int face1num, face2num, ladderface1num, ladderface2num; + int ladderface1vertical, ladderface2vertical, firstv; + float face1area, face2area, bestface1area, bestface2area; + float sv_jumpvel, maxjumpheight; + vec3_t area1point, area2point, v1, v2, up = { 0, 0, 1 }; + vec3_t mid, lowestpoint = { 0 }, start, end, sharededgevec, dir = { 0 }; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2, *ladderface1, *ladderface2; + aas_plane_t *plane1, *plane2; + aas_edge_t *sharededge, *edge1; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaLadder(area1num) || !AAS_AreaLadder(area2num)) + { + return qfalse; + } + // + sv_jumpvel = aassettings.sv_jumpvel; + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(sv_jumpvel); + + area1 = &(*aasworld).areas[area1num]; + area2 = &(*aasworld).areas[area2num]; + // + ladderface1 = NULL; + ladderface2 = NULL; + ladderface1num = 0; //make compiler happy + ladderface2num = 0; //make compiler happy + bestface1area = -9999; + bestface2area = -9999; + sharededgenum = 0; //make compiler happy + lowestedgenum = 0; //make compiler happy + // + for (i = 0; i < area1->numfaces; i++) + { + face1num = (*aasworld).faceindex[area1->firstface + i]; + face1 = &(*aasworld).faces[abs(face1num)]; + //if not a ladder face + if (!(face1->faceflags & FACE_LADDER)) + { + continue; + } + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = (*aasworld).faceindex[area2->firstface + j]; + face2 = &(*aasworld).faces[abs(face2num)]; + //if not a ladder face + if (!(face2->faceflags & FACE_LADDER)) + { + continue; + } + //check if the faces share an edge + for (k = 0; k < face1->numedges; k++) + { + edge1num = (*aasworld).edgeindex[face1->firstedge + k]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = (*aasworld).edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the face with the largest area + face1area = AAS_FaceArea(face1); + face2area = AAS_FaceArea(face2); + if (face1area > bestface1area && face2area > bestface2area) + { + bestface1area = face1area; + bestface2area = face2area; + ladderface1 = face1; + ladderface2 = face2; + ladderface1num = face1num; + ladderface2num = face2num; + sharededgenum = edge1num; + } //end if + break; + } //end if + } //end for + if (l != face2->numedges) + { + break; + } + } //end for + } //end for + } //end for + // + if (ladderface1 && ladderface2) + { + //get the middle of the shared edge + sharededge = &(*aasworld).edges[abs(sharededgenum)]; + firstv = sharededgenum < 0; + // + VectorCopy((*aasworld).vertexes[sharededge->v[firstv]], v1); + VectorCopy((*aasworld).vertexes[sharededge->v[!firstv]], v2); + VectorAdd(v1, v2, area1point); + VectorScale(area1point, 0.5, area1point); + VectorCopy(area1point, area2point); + // + //if the face plane in area 1 is pretty much vertical + plane1 = &(*aasworld).planes[ladderface1->planenum ^ (ladderface1num < 0)]; + plane2 = &(*aasworld).planes[ladderface2->planenum ^ (ladderface2num < 0)]; + // + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane1->normal, sharededgevec, dir); + VectorNormalize(dir); + //NOTE: 32 because that's larger than 16 (bot bbox x,y) + VectorMA(area1point, -32, dir, area1point); + VectorMA(area2point, 32, dir, area2point); + // + ladderface1vertical = Q_fabs(DotProduct(plane1->normal, up)) < 0.1; + ladderface2vertical = Q_fabs(DotProduct(plane2->normal, up)) < 0.1; + //there's only reachability between vertical ladder faces + if (!ladderface1vertical && !ladderface2vertical) + { + return qfalse; + } + //if both vertical ladder faces + if (ladderface1vertical && ladderface2vertical + //and the ladder faces do not make a sharp corner + && DotProduct(plane1->normal, plane2->normal) > 0.7 + //and the shared edge is not too vertical + && Q_fabs(DotProduct(sharededgevec, up)) < 0.7) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + //VectorCopy(area2point, lreach->end); + VectorMA(area2point, -3, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + //VectorCopy(area1point, lreach->end); + VectorMA(area1point, -3, plane2->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_ladder++; + // + return qtrue; + } //end if + //if the second ladder face is also a ground face + //create ladder end (just ladder) reachability and + //walk off a ladder (ledge) reachability + if (ladderface1vertical && (ladderface2->faceflags & FACE_GROUND)) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + VectorCopy(area2point, lreach->end); + lreach->end[2] += 16; + VectorMA(lreach->end, -15, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + // RF, this should be a ladder reachability, since we usually climb down ladders in Wolf (falling is possibly dangerous) + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + VectorCopy(area1point, lreach->end); +#if 1 // testing ladder descend instead of walk off ledge + lreach->end[2] -= 16; + VectorMA(lreach->start, -15, plane1->normal, lreach->start); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 100; // have to turn around +#else + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = 10; +#endif + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_walkoffledge++; + // + return qtrue; + } //end if + // + if (ladderface1vertical) + { + //find lowest edge of the ladder face + lowestpoint[2] = 99999; + for (i = 0; i < ladderface1->numedges; i++) + { + edge1num = abs((*aasworld).edgeindex[ladderface1->firstedge + i]); + edge1 = &(*aasworld).edges[edge1num]; + // + VectorCopy((*aasworld).vertexes[edge1->v[0]], v1); + VectorCopy((*aasworld).vertexes[edge1->v[1]], v2); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + // + if (mid[2] < lowestpoint[2]) + { + VectorCopy(mid, lowestpoint); + lowestedgenum = edge1num; + } //end if + } //end for + // + plane1 = &(*aasworld).planes[ladderface1->planenum]; + //trace down in the middle of this edge + VectorMA(lowestpoint, 5, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + // +#ifdef REACHDEBUG + if (trace.startsolid) + { + Log_Write("trace from area %d started in solid\r\n", area1num); + } //end if +#endif //REACHDEBUG + // + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + // + area2 = &(*aasworld).areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = (*aasworld).faceindex[area2->firstface + i]; + face2 = &(*aasworld).faces[abs(face2num)]; + // + if (face2->faceflags & FACE_LADDER) + { + plane2 = &(*aasworld).planes[face2->planenum]; + if (Q_fabs(DotProduct(plane2->normal, up)) < 0.1) + { + break; + } + } //end if + } //end for + //if from another area without vertical ladder faces + if (i >= area2->numfaces && area2num != area1num && + //the reachabilities shouldn't exist already + !AAS_ReachabilityExists(area1num, area2num) && + !AAS_ReachabilityExists(area2num, area1num)) + { + //if the height is jumpable + if (start[2] - trace.endpos[2] < maxjumpheight) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(lowestpoint, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + //get the end point a little bit into the ladder + VectorMA(lowestpoint, -5, plane1->normal, lreach->end); + //get the end point a little higher + lreach->end[2] += 10; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + return qtrue; +#ifdef REACHDEBUG + Log_Write("jump up to ladder reach between %d and %d\r\n", area2num, area1num); +#endif //REACHDEBUG + } //end if +#ifdef REACHDEBUG + else + { + Log_Write("jump too high between area %d and %d\r\n", area2num, area1num); + } +#endif //REACHDEBUG + } //end if + /* // if slime or lava below the ladder + //try jump reachability from far towards the ladder + if ((*aasworld).areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + for (i = 20; i <= 120; i += 20) + { + //trace down in the middle of this edge + VectorMA(lowestpoint, i, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + { + break; + } + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + if (area2num == area1num) + { + continue; + } + // + if (start[2] - trace.endpos[2] > maxjumpheight) + { + continue; + } + if ((*aasworld).areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + continue; + } + // + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + VectorCopy(lowestpoint, lreach->end); + lreach->end[2] += 5; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + Log_Write("jump far to ladder reach between %d and %d\r\n", area2num, area1num); + // + break; + } //end for + } //end if*/ + } //end if + } //end if + return qfalse; +} //end of the function AAS_Reachability_Ladder +//=========================================================================== +// create possible teleporter reachabilities +// this is very game dependent.... :( +// +// classname = trigger_multiple or trigger_teleport +// target = "t1" +// +// classname = target_teleporter +// targetname = "t1" +// target = "t2" +// +// classname = misc_teleporter_dest +// targetname = "t2" +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Teleport(void) +{ + int area1num, area2num; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + int ent, dest; + vec3_t origin, destorigin, mins, maxs, end, angles = { 0, 0, 0 }; + vec3_t mid; + aas_lreachability_t *lreach; + aas_trace_t trace; + aas_link_t *areas, *link; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (!strcmp(classname, "trigger_multiple")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACHDEBUG + botimport.Print(PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model); +//#endif REACHDEBUG + AAS_BSPModelMinsMaxsOrigin(atoi(model + 1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_multiple at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + if (!AAS_ValueForBSPEpairKey(dest, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (!strcmp(classname, "target_teleporter")) + { + if (!AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) + { + continue; + } + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + continue; + } //end if + if (!AAS_ValueForBSPEpairKey(dest, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "target_teleporter without target\n"); + continue; + } //end if + } //end else + else if (!strcmp(classname, "trigger_teleport")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACHDEBUG + botimport.Print(PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model); +//#endif REACHDEBUG + AAS_BSPModelMinsMaxsOrigin(atoi(model + 1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_teleport at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + } //end if + else + { + continue; + } //end else + // + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + //classname should be misc_teleporter_dest + //but I've also seen target_position and actually any + //entity could be used... burp + if (AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) + { + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without misc_teleporter_dest (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + // + area2num = AAS_PointAreaNum(destorigin); + //if not teleported into a teleporter or into a jumppad + if (!AAS_AreaTeleporter(area2num) && !AAS_AreaJumpPad(area2num)) + { + destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground + VectorCopy(destorigin, end); + end[2] -= 100; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + VectorCopy(trace.endpos, destorigin); + area2num = AAS_PointAreaNum(destorigin); + } //end if + // + //botimport.Print(PRT_MESSAGE, "teleporter brush origin at %f %f %f\n", origin[0], origin[1], origin[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush mins = %f %f %f\n", mins[0], mins[1], mins[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush maxs = %f %f %f\n", maxs[0], maxs[1], maxs[2]); + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox(mins, maxs, -1, PRESENCE_CROUCH); + if (!areas) + { + botimport.Print(PRT_MESSAGE, "trigger_multiple not in any area\n"); + } + // + for (link = areas; link; link = link->next_area) + { + //if (!AAS_AreaGrounded(link->areanum)) continue; + if (!AAS_AreaTeleporter(link->areanum)) + { + continue; + } + // + area1num = link->areanum; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + break; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(mid, lreach->start); + VectorCopy(destorigin, lreach->end); + lreach->traveltype = TRAVEL_TELEPORT; + lreach->traveltime = TELEPORT_TIME; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_teleport++; + } //end for + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_Teleport +//=========================================================================== +// create possible elevator (func_plat) reachabilities +// this is very game dependent.... :( +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define REACHDEBUG +void AAS_Reachability_Elevator(void) +{ + int area1num, area2num, modelnum, i, j, k, l, n, p; + float lip, height, speed; + char model[MAX_EPAIRKEY], classname[MAX_EPAIRKEY]; + int ent; + vec3_t mins, maxs, origin, angles = { 0, 0, 0 }; + vec3_t pos1, pos2, mids, platbottom, plattop; + vec3_t bottomorg, toporg, start, end, dir; + vec_t xvals[8], yvals[8], xvals_top[8], yvals_top[8]; + aas_lreachability_t *lreach; + aas_trace_t trace; + +#ifdef REACHDEBUG + Log_Write("AAS_Reachability_Elevator\r\n"); +#endif //REACHDEBUG + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (!strcmp(classname, "func_plat")) + { +#ifdef REACHDEBUG + Log_Write("found func plat\r\n"); +#endif //REACHDEBUG + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_plat without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model + 1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_plat with invalid model number\n"); + continue; + } //end if + //get the mins, maxs and origin of the model + //NOTE: the origin is usually (0,0,0) and the mins and maxs + // are the absolute mins and maxs + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + AAS_VectorForBSPEpairKey(ent, "origin", origin); + //pos1 is the top position, pos2 is the bottom + VectorCopy(origin, pos1); + VectorCopy(origin, pos2); + //get the lip of the plat + AAS_FloatForBSPEpairKey(ent, "lip", &lip); + if (!lip) + { + lip = 8; + } + //get the movement height of the plat + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) + { + height = (maxs[2] - mins[2]) - lip; + } + //get the speed of the plat + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) + { + speed = 200; + } + //get bottom position below pos1 + pos2[2] -= height; + // + botimport.Print(PRT_MESSAGE, "pos2[2] = %1.1f pos1[2] = %1.1f\n", pos2[2], pos1[2]); + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, platbottom); + platbottom[2] = maxs[2] - (pos1[2] - pos2[2]) + 2; + //get a point just above the plat in the top position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, plattop); + plattop[2] = maxs[2] + 2; + // + /*if (!area1num) + { + Log_Write("no grounded area near plat bottom\r\n"); + continue; + } //end if*/ + //get the mins and maxs a little larger + for (i = 0; i < 3; i++) + { + mins[i] -= 1; + maxs[i] += 1; + } //end for + // + botimport.Print(PRT_MESSAGE, "platbottom[2] = %1.1f plattop[2] = %1.1f\n", platbottom[2], plattop[2]); + // + VectorAdd(mins, maxs, mids); + VectorScale(mids, 0.5, mids); + // + xvals[0] = mins[0]; + xvals[1] = mids[0]; + xvals[2] = maxs[0]; + xvals[3] = mids[0]; + yvals[0] = mids[1]; + yvals[1] = maxs[1]; + yvals[2] = mids[1]; + yvals[3] = mins[1]; + // + xvals[4] = mins[0]; + xvals[5] = maxs[0]; + xvals[6] = maxs[0]; + xvals[7] = mins[0]; + yvals[4] = maxs[1]; + yvals[5] = maxs[1]; + yvals[6] = mins[1]; + yvals[7] = mins[1]; + //find adjacent areas around the bottom of the plat + for (i = 0; i < 9; i++) + { + if (i < 8) //check at the sides of the plat + { + bottomorg[0] = origin[0] + xvals[i]; + bottomorg[1] = origin[1] + yvals[i]; + bottomorg[2] = platbottom[2] + 16; + //get a grounded or swim area near the plat in the bottom position + area1num = AAS_PointAreaNum(bottomorg); + for (k = 0; k < 16; k++) + { + if (area1num) + { + if (AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) + { + break; + } + } //end if + bottomorg[2] += 4; + area1num = AAS_PointAreaNum(bottomorg); + } //end if + //if in solid + if (k >= 16) + { + continue; + } //end if + } //end if + else //at the middle of the plat + { + VectorCopy(plattop, bottomorg); + bottomorg[2] += 24; + area1num = AAS_PointAreaNum(bottomorg); + if (!area1num) + { + continue; + } + VectorCopy(platbottom, bottomorg); + bottomorg[2] += 24; + } //end else + //look at adjacent areas around the top of the plat + //make larger steps to outside the plat everytime + for (n = 0; n < 3; n++) + { + for (k = 0; k < 3; k++) + { + mins[k] -= 4; + maxs[k] += 4; + } //end for + xvals_top[0] = mins[0]; + xvals_top[1] = mids[0]; + xvals_top[2] = maxs[0]; + xvals_top[3] = mids[0]; + yvals_top[0] = mids[1]; + yvals_top[1] = maxs[1]; + yvals_top[2] = mids[1]; + yvals_top[3] = mins[1]; + // + xvals_top[4] = mins[0]; + xvals_top[5] = maxs[0]; + xvals_top[6] = maxs[0]; + xvals_top[7] = mins[0]; + yvals_top[4] = maxs[1]; + yvals_top[5] = maxs[1]; + yvals_top[6] = mins[1]; + yvals_top[7] = mins[1]; + // + for (j = 0; j < 8; j++) + { + toporg[0] = origin[0] + xvals_top[j]; + toporg[1] = origin[1] + yvals_top[j]; + toporg[2] = plattop[2] + 16; + //get a grounded or swim area near the plat in the top position + area2num = AAS_PointAreaNum(toporg); + for (l = 0; l < 16; l++) + { + if (area2num) + { + if (AAS_AreaGrounded(area2num) || AAS_AreaSwim(area2num)) + { + VectorCopy(plattop, start); + start[2] += 32; + VectorCopy(toporg, end); + end[2] += 1; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.fraction >= 1) + { + break; + } + } //end if + } //end if + toporg[2] += 4; + area2num = AAS_PointAreaNum(toporg); + } //end if + //if in solid + if (l >= 16) + { + continue; + } + //never create a reachability in the same area + if (area2num == area1num) + { + continue; + } + //if the area isn't grounded + if (!AAS_AreaGrounded(area2num)) + { + continue; + } + //if there already exists reachability between the areas + if (AAS_ReachabilityExists(area1num, area2num)) + { + continue; + } + //if the reachability start is within the elevator bounding box + VectorSubtract(bottomorg, platbottom, dir); + VectorNormalize(dir); + dir[0] = bottomorg[0] + 24 * dir[0]; + dir[1] = bottomorg[1] + 24 * dir[1]; + dir[2] = bottomorg[2]; + // + for (p = 0; p < 3; p++) + if (dir[p] < origin[p] + mins[p] || dir[p] > origin[p] + maxs[p]) + { + break; + } + if (p >= 3) + { + continue; + } + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + continue; + } + lreach->areanum = area2num; + //the facenum is the model number + lreach->facenum = modelnum; + //the edgenum is the height + lreach->edgenum = (int) height; + // + VectorCopy(dir, lreach->start); + VectorCopy(toporg, lreach->end); + lreach->traveltype = TRAVEL_ELEVATOR; + lreach->traveltime = height * 100 / speed; + if (!lreach->traveltime) + { + lreach->traveltime = 50; + } + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //don't go any further to the outside + n = 9999; + // +#ifdef REACHDEBUG + Log_Write("elevator reach from %d to %d\r\n", area1num, area2num); +#endif //REACHDEBUG + // + reach_elevator++; + } //end for + } //end for + } //end for + } //end if + } //end for +} //end of the function AAS_Reachability_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_FindFaceReachabilities(vec3_t *facepoints, int numpoints, aas_plane_t *plane, int towardsface) +{ + int i, j, k, l; + int facenum, edgenum, bestfacenum; + float *v1, *v2, *v3, *v4; + float bestdist, speed, hordist, dist; + vec3_t beststart, beststart2, bestend, bestend2, tmp, hordir, testpoint; + aas_lreachability_t *lreach, *lreachabilities; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + aas_plane_t *faceplane, *bestfaceplane; + + // + lreachabilities = NULL; + bestfacenum = 0; + bestfaceplane = NULL; + // + for (i = 1; i < (*aasworld).numareas; i++) + { + area = &(*aasworld).areas[i]; + // get the shortest distance between one of the func_bob start edges and + // one of the face edges of area1 + bestdist = 999999; + for (j = 0; j < area->numfaces; j++) + { + facenum = (*aasworld).faceindex[area->firstface + j]; + face = &(*aasworld).faces[abs(facenum)]; + //if not a ground face + if (!(face->faceflags & FACE_GROUND)) + { + continue; + } + //get the ground planes + faceplane = &(*aasworld).planes[face->planenum]; + // + for (k = 0; k < face->numedges; k++) + { + edgenum = abs((*aasworld).edgeindex[face->firstedge + k]); + edge = &(*aasworld).edges[edgenum]; + //calculate the minimum distance between the two edges + v1 = (*aasworld).vertexes[edge->v[0]]; + v2 = (*aasworld).vertexes[edge->v[1]]; + // + for (l = 0; l < numpoints; l++) + { + v3 = facepoints[l]; + v4 = facepoints[(l + 1) % numpoints]; + dist = AAS_ClosestEdgePoints(v1, v2, v3, v4, faceplane, plane, + beststart, bestend, + beststart2, bestend2, bestdist); + if (dist < bestdist) + { + bestfacenum = facenum; + bestfaceplane = faceplane; + bestdist = dist; + } //end if + } //end for + } //end for + } //end for + // + if (bestdist > 192) + { + continue; + } + // + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + // + if (!towardsface) + { + VectorCopy(beststart, tmp); + VectorCopy(bestend, beststart); + VectorCopy(tmp, bestend); + } //end if + // + VectorSubtract(bestend, beststart, hordir); + hordir[2] = 0; + hordist = VectorLength(hordir); + // + if (hordist > 2 * AAS_MaxJumpDistance(aassettings.sv_jumpvel)) + { + continue; + } + //the end point should not be significantly higher than the start point + if (bestend[2] - 32 > beststart[2]) + { + continue; + } + //don't fall down too far + if (bestend[2] < beststart[2] - 128) + { + continue; + } + //the distance should not be too far + if (hordist > 32) + { + //check for walk off ledge + if (!AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) + { + continue; + } + } //end if + // + beststart[2] += 1; + bestend[2] += 1; + // + if (towardsface) + { + VectorCopy(bestend, testpoint); + } + else + { + VectorCopy(beststart, testpoint); + } + testpoint[2] = 0; + testpoint[2] = (bestfaceplane->dist - DotProduct(bestfaceplane->normal, testpoint)) / bestfaceplane->normal[2]; + // + if (!AAS_PointInsideFace(bestfacenum, testpoint, 0.1)) + { + //if the faces are not overlapping then only go down + if (bestend[2] - 16 > beststart[2]) + { + continue; + } + } //end if + lreach = AAS_AllocReachability(); + if (!lreach) + { + return lreachabilities; + } + lreach->areanum = i; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = 0; + lreach->traveltime = 0; + lreach->next = lreachabilities; + lreachabilities = lreach; +#ifndef BSPC + if (towardsface) + { + AAS_PermanentLine(lreach->start, lreach->end, 1); + } + else + { + AAS_PermanentLine(lreach->start, lreach->end, 2); + } +#endif + } //end for + return lreachabilities; +} //end of the function AAS_FindFaceReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_FuncBobbing(void) +{ + int ent, spawnflags, modelnum, axis; + int i, numareas, areas[10]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + vec3_t origin, move_end, move_start, move_start_top, move_end_top; + vec3_t mins, maxs, angles = { 0, 0, 0 }; + vec3_t start_edgeverts[4], end_edgeverts[4], mid; + vec3_t org, start, end, dir, points[10]; + float height; + aas_plane_t start_plane, end_plane; + aas_lreachability_t *startreach, *endreach, *nextstartreach, *nextendreach, *lreach; + aas_lreachability_t *firststartreach, *firstendreach; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (strcmp(classname, "func_bobbing")) + { + continue; + } + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) + { + height = 32; + } + // + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_bobbing without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model + 1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_bobbing with invalid model number\n"); + continue; + } //end if + // + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + //VectorAdd(mid, origin, mid); + VectorCopy(mid, origin); + // + VectorCopy(origin, move_end); + VectorCopy(origin, move_start); + // + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // set the axis of bobbing + if (spawnflags & 1) + { + axis = 0; + } + else if (spawnflags & 2) + { + axis = 1; + } + else + { + axis = 2; + } + // + move_start[axis] -= height; + move_end[axis] += height; + // + Log_Write("funcbob model %d, start = {%1.1f, %1.1f, %1.1f} end = {%1.1f, %1.1f, %1.1f}\n", + modelnum, move_start[0], move_start[1], move_start[2], move_end[0], move_end[1], move_end[2]); + // +#ifndef BSPC + /* + AAS_DrawPermanentCross(move_start, 4, 1); + AAS_DrawPermanentCross(move_end, 4, 2); + */ +#endif + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_start, start_edgeverts[i]); + start_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + start_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + start_edgeverts[0][0] += maxs[0] - mid[0]; + start_edgeverts[0][1] += maxs[1] - mid[1]; + start_edgeverts[1][0] += maxs[0] - mid[0]; + start_edgeverts[1][1] += mins[1] - mid[1]; + start_edgeverts[2][0] += mins[0] - mid[0]; + start_edgeverts[2][1] += mins[1] - mid[1]; + start_edgeverts[3][0] += mins[0] - mid[0]; + start_edgeverts[3][1] += maxs[1] - mid[1]; + // + start_plane.dist = start_edgeverts[0][2]; + VectorSet(start_plane.normal, 0, 0, 1); + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_end, end_edgeverts[i]); + end_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + end_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + end_edgeverts[0][0] += maxs[0] - mid[0]; + end_edgeverts[0][1] += maxs[1] - mid[1]; + end_edgeverts[1][0] += maxs[0] - mid[0]; + end_edgeverts[1][1] += mins[1] - mid[1]; + end_edgeverts[2][0] += mins[0] - mid[0]; + end_edgeverts[2][1] += mins[1] - mid[1]; + end_edgeverts[3][0] += mins[0] - mid[0]; + end_edgeverts[3][1] += maxs[1] - mid[1]; + // + end_plane.dist = end_edgeverts[0][2]; + VectorSet(end_plane.normal, 0, 0, 1); + // +#ifndef BSPC + /* + for (i = 0; i < 4; i++) + { + AAS_PermanentLine(start_edgeverts[i], start_edgeverts[(i+1)%4], 1); + AAS_PermanentLine(end_edgeverts[i], end_edgeverts[(i+1)%4], 1); + } //end for + */ +#endif + VectorCopy(move_start, move_start_top); + move_start_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + VectorCopy(move_end, move_end_top); + move_end_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + // + if (!AAS_PointAreaNum(move_start_top)) + { + continue; + } + if (!AAS_PointAreaNum(move_end_top)) + { + continue; + } + // + for (i = 0; i < 2; i++) + { + firststartreach = firstendreach = NULL; + // + if (i == 0) + { + firststartreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qfalse); + } //end if + else + { + firststartreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qfalse); + } //end else + // + //create reachabilities from start to end + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + // + //trace = AAS_TraceClientBBox(startreach->start, move_start_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + // + //trace = AAS_TraceClientBBox(endreach->end, move_end_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + Log_Write("funcbob reach from area %d to %d\n", startreach->areanum, endreach->areanum); + // + // + if (i == 0) + { + VectorCopy(move_start_top, org); + } + else + { + VectorCopy(move_end_top, org); + } + VectorSubtract(startreach->start, org, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorCopy(startreach->start, start); + VectorMA(startreach->start, 1, dir, start); + start[2] += 1; + VectorMA(startreach->start, 16, dir, end); + end[2] += 1; + // + numareas = AAS_TraceAreas(start, end, areas, points, 10); + if (numareas <= 0) + { + continue; + } + if (numareas > 1) + { + VectorCopy(points[1], startreach->start); + } + else + { + VectorCopy(end, startreach->start); + } + // + if (!AAS_PointAreaNum(startreach->start)) + { + continue; + } + if (!AAS_PointAreaNum(endreach->end)) + { + continue; + } + // + lreach = AAS_AllocReachability(); + lreach->areanum = endreach->areanum; + if (i == 0) + { + lreach->edgenum = ((int)move_start[axis] << 16) | ((int) move_end[axis] & 0x0000ffff); + } + else + { + lreach->edgenum = ((int)move_end[axis] << 16) | ((int) move_start[axis] & 0x0000ffff); + } + lreach->facenum = (spawnflags << 16) | modelnum; + VectorCopy(startreach->start, lreach->start); + VectorCopy(endreach->end, lreach->end); +#ifndef BSPC +// AAS_DrawArrow(lreach->start, lreach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); +// AAS_PermanentLine(lreach->start, lreach->end, 1); +#endif + lreach->traveltype = TRAVEL_FUNCBOB; + lreach->traveltime = 300; + reach_funcbob++; + lreach->next = areareachability[startreach->areanum]; + areareachability[startreach->areanum] = lreach; + // + } //end for + } //end for + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + AAS_FreeReachability(startreach); + } //end for + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + AAS_FreeReachability(endreach); + } //end for + //only go up with func_bobbing entities that go up and down + if (!(spawnflags & 1) && !(spawnflags & 2)) + { + break; + } + } //end for + } //end for +} //end of the function AAS_Reachability_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_JumpPad(void) +{ + int face2num, i, ret, modelnum, area2num, visualize; + float speed, zvel, hordist, dist, time, height, gravity, forward; + aas_face_t *face2; + aas_area_t *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, dir, cmdmove, teststart; + vec3_t velocity, origin, ent2origin, angles, absmins, absmaxs; + aas_clientmove_t move; + aas_trace_t trace; + int ent, ent2; + aas_link_t *areas, *link; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (strcmp(classname, "trigger_push")) + { + continue; + } + // + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) + { + speed = 1000; + } +// AAS_VectorForBSPEpairKey(ent, "angles", angles); +// AAS_SetMovedir(angles, velocity); +// VectorScale(velocity, speed, velocity); + VectorClear(angles); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); + if (model[0]) + { + modelnum = atoi(model + 1); + } + else + { + modelnum = 0; + } + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); + VectorAdd(origin, absmins, absmins); + VectorAdd(origin, absmaxs, absmaxs); + // +#ifdef REACHDEBUG + botimport.Print(PRT_MESSAGE, "absmins = %f %f %f\n", absmins[0], absmins[1], absmins[2]); + botimport.Print(PRT_MESSAGE, "absmaxs = %f %f %f\n", absmaxs[0], absmaxs[1], absmaxs[2]); +#endif // REACHDEBUG + VectorAdd(absmins, absmaxs, origin); + VectorScale(origin, 0.5, origin); + + //get the start areas + VectorCopy(origin, teststart); + teststart[2] += 64; + trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); + VectorCopy(origin, areastart); + } //end if + else + { + VectorCopy(trace.endpos, areastart); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); + for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) + { + if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) + { + continue; + } + if (!strcmp(targetname, target)) + { + break; + } + } //end for + if (!ent2) + { + botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); + continue; + } //end if + AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.sv_gravity; + time = sqrt(height / (0.5 * gravity)); + if (!time) + { + botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); + continue; + } //end if + // set s.origin2 to the push velocity + VectorSubtract(ent2origin, origin, velocity); + dist = VectorNormalize(velocity); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1; + VectorScale(velocity, forward, velocity); + velocity[2] = time * gravity; + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + //* + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 5772) + { + ret = qfalse; + } + } //*/ + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaJumpPad(link->areanum)) + { + break; + } + } //end for + if (!link) + { + botimport.Print(PRT_MESSAGE, "trigger_multiple not in any jump pad area\n"); + AAS_UnlinkFromAreas(areas); + continue; + } //end if + // + botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); + //if there is a horizontal velocity check for a reachability without air control + if (velocity[0] || velocity[1]) + { + VectorSet(cmdmove, 0, 0, 0); + //VectorCopy(velocity, cmdmove); + //cmdmove[2] = 0; + memset(&move, 0, sizeof(aas_clientmove_t)); + area2num = 0; + for (i = 0; i < 20; i++) + { + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1, + SE_HITGROUND | SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | SE_TOUCHJUMPPAD | SE_TOUCHTELEPORTER, 0, qfalse); //qtrue); + area2num = AAS_PointAreaNum(move.endpos); + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) + { + continue; + } + if (link->areanum == area2num) + { + break; + } + } //end if + if (!link) + { + break; + } + VectorCopy(move.endpos, areastart); + VectorCopy(move.velocity, velocity); + } //end for + if (area2num && i < 20) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) + { + continue; + } + if (AAS_ReachabilityExists(link->areanum, area2num)) + { + continue; + } + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = area2num; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(move.endpos, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltime = 200; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } //end if + } //end if + // + if (Q_fabs(velocity[0]) > 100 || Q_fabs(velocity[1]) > 100) + { + continue; + } + //check for areas we can reach with air control + for (area2num = 1; area2num < (*aasworld).numareas; area2num++) + { + visualize = qfalse; + /* + if (area2num == 3568) + { + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 3380) + { + visualize = qtrue; + botimport.Print(PRT_MESSAGE, "bah\n"); + } //end if + } //end for + } //end if*/ + //never try to go back to one of the original jumppad areas + //and don't create reachabilities if they already exist + for (link = areas; link; link = link->next_area) + { + if (AAS_ReachabilityExists(link->areanum, area2num)) + { + break; + } + if (AAS_AreaJumpPad(link->areanum)) + { + if (link->areanum == area2num) + { + break; + } + } //end if + } //end if + if (link) + { + continue; + } + // + area2 = &(*aasworld).areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = (*aasworld).faceindex[area2->firstface + i]; + face2 = &(*aasworld).faces[abs(face2num)]; + //if it is not a ground face + if (!(face2->faceflags & FACE_GROUND)) + { + continue; + } + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up + if (facecenter[2] < areastart[2]) + { + continue; + } + //get the jumppad jump z velocity + zvel = velocity[2]; + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 150) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + hordist = VectorNormalize(dir); + //if (hordist < 1.6 * facecenter[2] - areastart[2]) + { + //get command movement + VectorScale(dir, speed, cmdmove); + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_TOUCHTELEPORTER | SE_HITGROUNDAREA, area2num, visualize); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME | SE_ENTERLAVA | SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA | SE_TOUCHJUMPPAD | SE_TOUCHTELEPORTER))) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) + { + continue; + } + if (AAS_ReachabilityExists(link->areanum, area2num)) + { + continue; + } + //create a jumppad reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = area2num; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(cmdmove[0] * cmdmove[0] + cmdmove[1] * cmdmove[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltime = 250; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } //end if + } //end if + } //end for + } //end for + } //end for + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_JumpPad +//=========================================================================== +// never point at ground faces +// always a higher and pretty far area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Grapple(int area1num, int area2num) +{ + int face2num, i, j, areanum, numareas, areas[20]; + float mingrappleangle, z, hordist; + bsp_trace_t bsptrace; + aas_trace_t trace; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, down = { 0, 0, -1 }; + vec_t *v; + + //only grapple when on the ground or swimming + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) + { + return qfalse; + } + //don't grapple from a crouch area + if (!(AAS_AreaPresenceType(area1num) & PRESENCE_NORMAL)) + { + return qfalse; + } + //NOTE: disabled area swim it doesn't work right + if (AAS_AreaSwim(area1num)) + { + return qfalse; + } + // + area1 = &(*aasworld).areas[area1num]; + area2 = &(*aasworld).areas[area2num]; + //don't grapple towards way lower areas + if (area2->maxs[2] < area1->mins[2]) + { + return qfalse; + } + // + VectorCopy((*aasworld).areas[area1num].center, start); + //if not a swim area + if (!AAS_AreaSwim(area1num)) + { + if (!AAS_PointAreaNum(start)) + { + Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + } + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + return qfalse; + } + VectorCopy(trace.endpos, areastart); + } //end if + else + { + if (!(AAS_PointContents(start) & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER))) + { + return qfalse; + } + } //end else + // + //start is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = (*aasworld).faceindex[area2->firstface + i]; + face2 = &(*aasworld).faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_SOLID)) + { + continue; + } + //direction towards the first vertex of the face + v = (*aasworld).vertexes[(*aasworld).edges[abs((*aasworld).edgeindex[face2->firstedge])].v[0]]; + VectorSubtract(v, areastart, dir); + //if the face plane is facing away + if (DotProduct((*aasworld).planes[face2->planenum].normal, dir) > 0) + { + continue; + } + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with the grapple + if (facecenter[2] < areastart[2] + 64) + { + continue; + } + //only use vertical faces or downward facing faces + if (DotProduct((*aasworld).planes[face2->planenum].normal, down) < 0) + { + continue; + } + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + // + z = dir[2]; + dir[2] = 0; + hordist = VectorLength(dir); + if (!hordist) + { + continue; + } + //if too far + if (hordist > 2000) + { + continue; + } + //check the minimal angle of the movement + mingrappleangle = 15; //15 degrees + if (z / hordist < tan(2 * M_PI * mingrappleangle / 360)) + { + continue; + } + // + VectorCopy(facecenter, start); + VectorMA(facecenter, -500, (*aasworld).planes[face2->planenum].normal, end); + // + bsptrace = AAS_Trace(start, NULL, NULL, end, 0, CONTENTS_SOLID); + //the grapple won't stick to the sky and the grapple point should be near the AAS wall + if ((bsptrace.surface.flags & SURF_SKY) || (bsptrace.fraction * 500 > 32)) + { + continue; + } + //trace a full bounding box from the area center on the ground to + //the center of the face + VectorSubtract(facecenter, areastart, dir); + VectorNormalize(dir); + VectorMA(areastart, 4, dir, start); + VectorCopy(bsptrace.endpos, end); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + VectorSubtract(trace.endpos, facecenter, dir); + if (VectorLength(dir) > 24) + { + continue; + } + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] -= AAS_FallDamageDistance(); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + if (trace.fraction >= 1) + { + continue; + } + //area to end in + areanum = AAS_PointAreaNum(trace.endpos); + //if not in lava or slime + if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_LAVA) //----(SA) modified since slime is no longer deadly + { // if ((*aasworld).areasettings[areanum].contents & (AREACONTENTS_SLIME|AREACONTENTS_LAVA)) + continue; + } //end if + //do not go the the source area + if (areanum == area1num) + { + continue; + } + //don't create reachabilities if they already exist + if (AAS_ReachabilityExists(area1num, areanum)) + { + continue; + } + //only end in areas we can stand + if (!AAS_AreaGrounded(areanum)) + { + continue; + } + //never go through cluster portals!! + numareas = AAS_TraceAreas(areastart, bsptrace.endpos, areas, NULL, 20); + if (numareas >= 20) + { + continue; + } + for (j = 0; j < numareas; j++) + { + if ((*aasworld).areasettings[areas[j]].contents & AREACONTENTS_CLUSTERPORTAL) + { + break; + } + } //end for + if (j < numareas) + { + continue; + } + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = areanum; + lreach->facenum = face2num; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + //VectorCopy(facecenter, lreach->end); + VectorCopy(bsptrace.endpos, lreach->end); + lreach->traveltype = TRAVEL_GRAPPLEHOOK; + VectorSubtract(lreach->end, lreach->start, dir); + lreach->traveltime = STARTGRAPPLE_TIME + VectorLength(dir) * 0.25; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_grapple++; + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetWeaponJumpAreaFlags(void) +{ + int ent, dent; + vec3_t mins = { -18, -18, -24 }, maxs = { 18, 18, 40 }; + vec3_t origin, destorigin; + int areanum, weaponjumpareas, spawnflags, destareanum; + char classname[MAX_EPAIRKEY]; + char target[MAX_EPAIRKEY]; + char targetname[MAX_EPAIRKEY]; + // + weaponjumpareas = 0; + jumplinks = (aas_jumplink_t *)GetClearedMemory(aasworld->numareas * sizeof(aas_jumplink_t)); + // + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (!strcmp(classname, "bot_jump_source")) + { + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + continue; + } + // find the destination + for (dent = AAS_NextBSPEntity(0); dent; dent = AAS_NextBSPEntity(dent)) + { + if (!AAS_ValueForBSPEpairKey(dent, "targetname", targetname, MAX_EPAIRKEY)) + { + continue; + } + if (!strcmp(target, targetname)) + { + // match found + break; + } + } + // if it failed, ignore and print message + if (!dent) + { + botimport.Print(PRT_MESSAGE, "WARNING: %s doesn't have a matching bot_jump_dest (target = %s)\n", + classname, target); + continue; + } + // success + // find source area + if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, mins, maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //areanum = AAS_PointAreaNum(origin); + areanum = AAS_BestReachableArea(origin, mins, maxs, origin); + if (!areanum) + { + botimport.Print(PRT_MESSAGE, "%s in void area at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + continue; + } + } + else + { + botimport.Print(PRT_MESSAGE, "%s has no origin (%s)\n", + classname, target); + continue; + } + // find dest area + if (!AAS_ValueForBSPEpairKey(dent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (AAS_VectorForBSPEpairKey(dent, "origin", destorigin)) + { + spawnflags = 0; + AAS_IntForBSPEpairKey(dent, "spawnflags", &spawnflags); + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(destorigin, mins, maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, destorigin[0], destorigin[1], destorigin[2]); + } //end if + } //end if + //areanum = AAS_PointAreaNum(origin); + destareanum = AAS_BestReachableArea(destorigin, mins, maxs, destorigin); + if (!destareanum) + { + botimport.Print(PRT_MESSAGE, "%s in void area at (%1.1f %1.1f %1.1f)\n", + classname, destorigin[0], destorigin[1], destorigin[2]); + continue; + } + } + else + { + botimport.Print(PRT_MESSAGE, "%s has no origin (%s)\n", + classname, targetname); + continue; + } + + //the bot may jump between these areas + (*aasworld).areasettings[areanum].areaflags |= AREA_JUMPSRC; + jumplinks[areanum].destarea = destareanum; + VectorCopy(origin, jumplinks[areanum].srcpos); + VectorCopy(destorigin, jumplinks[areanum].destpos); + // + if (!AAS_AreaGrounded(areanum)) + { + botimport.Print(PRT_MESSAGE, "area not grounded\n"); + } + // + weaponjumpareas++; + } //end if + /* + if ( + !strcmp(classname, "item_armor_body") || + !strcmp(classname, "item_armor_combat") || + !strcmp(classname, "item_health_mega") || + !strcmp(classname, "weapon_grenadelauncher") || + !strcmp(classname, "weapon_rocketlauncher") || + !strcmp(classname, "weapon_lightning") || + !strcmp(classname, "weapon_sp5") || + !strcmp(classname, "weapon_railgun") || + !strcmp(classname, "weapon_bfg") || + !strcmp(classname, "item_quad") || + !strcmp(classname, "item_regen") || + !strcmp(classname, "item_invulnerability")) + { + if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, mins, maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //areanum = AAS_PointAreaNum(origin); + areanum = AAS_BestReachableArea(origin, mins, maxs, origin); + //the bot may rocket jump towards this area + (*aasworld).areasettings[areanum].areaflags |= AREA_WEAPONJUMP; + // + if (!AAS_AreaGrounded(areanum)) botimport.Print(PRT_MESSAGE, "area not grounded\n"); + // + weaponjumpareas++; + } //end if + } //end if + */ + } //end for + /* + for (i = 1; i < (*aasworld).numareas; i++) + { + if ((*aasworld).areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + (*aasworld).areasettings[i].areaflags |= AREA_WEAPONJUMP; + weaponjumpareas++; + } //end if + } //end for + */ + botimport.Print(PRT_MESSAGE, "%d weapon jump areas\n", weaponjumpareas); +} //end of the function AAS_SetWeaponJumpAreaFlags +//=========================================================================== +// create a possible weapon jump reachability from area1 to area2 +// +// check if there's a cool item in the second area +// check if area1 is lower than area2 +// check if the bot can rocketjump from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_WeaponJump(int area1num, int area2num) +{ + int face2num, i, n, ret; + float speed, zvel, hordist; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, cmdmove; // teststart; + vec3_t velocity; + aas_clientmove_t move; + aas_trace_t trace; + + if (!AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) + { + return qfalse; + } + if (!AAS_AreaGrounded(area2num)) + { + return qfalse; + } + //NOTE: only weapon jump towards areas with an interesting item in it?? + if (!((*aasworld).areasettings[area2num].areaflags & AREA_WEAPONJUMP)) + { + return qfalse; + } + // + area1 = &(*aasworld).areas[area1num]; + area2 = &(*aasworld).areas[area2num]; + //don't weapon jump towards way lower areas + if (area2->maxs[2] < area1->mins[2]) + { + return qfalse; + } + // + VectorCopy((*aasworld).areas[area1num].center, start); + //if not a swim area + if (!AAS_PointAreaNum(start)) + { + Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + } + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + return qfalse; + } + VectorCopy(trace.endpos, areastart); + // + //areastart is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = (*aasworld).faceindex[area2->firstface + i]; + face2 = &(*aasworld).faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_GROUND)) + { + continue; + } + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with weapon jumps + if (facecenter[2] < areastart[2] + 64) + { + continue; + } + //NOTE: set to 2 to allow bfg jump reachabilities + for (n = 0; n < 1 /*2*/; n++) + { + //get the rocket jump z velocity + if (n) + { + zvel = AAS_BFGJumpZVelocity(areastart); + } + else + { + zvel = AAS_RocketJumpZVelocity(areastart); + } + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 270) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + hordist = VectorNormalize(dir); + //if (hordist < 1.6 * (facecenter[2] - areastart[2])) + { + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + /* + //get command movement + VectorScale(dir, speed, velocity); + velocity[2] = zvel; + VectorSet(cmdmove, 0, 0, 0); + */ + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_HITGROUNDAREA, area2num, qfalse); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME | SE_ENTERLAVA | SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA | SE_TOUCHJUMPPAD))) + { + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + if (n) + { + lreach->traveltype = TRAVEL_BFGJUMP; + } + else + { + lreach->traveltype = TRAVEL_ROCKETJUMP; + } + lreach->traveltime = 300; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_rocketjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end for + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_WeaponJump +//=========================================================================== +// calculates additional walk off ledge reachabilities for the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_WalkOffLedge(int areanum) +{ + int i, j, k, l, m, n; + int face1num, face2num, face3num, edge1num, edge2num, edge3num; + int otherareanum, gap, reachareanum, side; + aas_area_t *area, *area2; + aas_face_t *face1, *face2, *face3; + aas_edge_t *edge; + aas_plane_t *plane; + vec_t *v1, *v2; + vec3_t sharededgevec, mid, dir, testend; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaGrounded(areanum) || AAS_AreaSwim(areanum)) + { + return; + } + // + area = &(*aasworld).areas[areanum]; + // + for (i = 0; i < area->numfaces; i++) + { + face1num = (*aasworld).faceindex[area->firstface + i]; + face1 = &(*aasworld).faces[abs(face1num)]; + //face 1 must be a ground face + if (!(face1->faceflags & FACE_GROUND)) + { + continue; + } + //go through all the edges of this ground face + for (k = 0; k < face1->numedges; k++) + { + edge1num = (*aasworld).edgeindex[face1->firstedge + k]; + //find another not ground face using this same edge + for (j = 0; j < area->numfaces; j++) + { + face2num = (*aasworld).faceindex[area->firstface + j]; + face2 = &(*aasworld).faces[abs(face2num)]; + //face 2 may not be a ground face + if (face2->faceflags & FACE_GROUND) + { + continue; + } + //compare all the edges + for (l = 0; l < face2->numedges; l++) + { + edge2num = (*aasworld).edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the area at the other side of the face + if (face2->frontarea == areanum) + { + otherareanum = face2->backarea; + } + else + { + otherareanum = face2->frontarea; + } + // + area2 = &(*aasworld).areas[otherareanum]; + //if the other area is grounded! + if ((*aasworld).areasettings[otherareanum].areaflags & AREA_GROUNDED) + { + //check for a possible gap + gap = qfalse; + for (n = 0; n < area2->numfaces; n++) + { + face3num = (*aasworld).faceindex[area2->firstface + n]; + //may not be the shared face of the two areas + if (abs(face3num) == abs(face2num)) + { + continue; + } + // + face3 = &(*aasworld).faces[abs(face3num)]; + //find an edge shared by all three faces + for (m = 0; m < face3->numedges; m++) + { + edge3num = (*aasworld).edgeindex[face3->firstedge + m]; + //but the edge should be shared by all three faces + if (abs(edge3num) == abs(edge1num)) + { + if (!(face3->faceflags & FACE_SOLID)) + { + gap = qtrue; + break; + } //end if + // + if (face3->faceflags & FACE_GROUND) + { + gap = qfalse; + break; + } //end if + //FIXME: there are more situations to be handled + gap = qtrue; + break; + } //end if + } //end for + if (m < face3->numedges) + { + break; + } + } //end for + if (!gap) + { + break; + } + } //end if + //check for a walk off ledge reachability + edge = &(*aasworld).edges[abs(edge1num)]; + side = edge1num < 0; + // + v1 = (*aasworld).vertexes[edge->v[side]]; + v2 = (*aasworld).vertexes[edge->v[!side]]; + // + plane = &(*aasworld).planes[face1->planenum]; + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane->normal, sharededgevec, dir); + VectorNormalize(dir); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + VectorMA(mid, 8, dir, mid); + // + VectorCopy(mid, testend); + testend[2] -= 1000; + trace = AAS_TraceClientBBox(mid, testend, PRESENCE_CROUCH, -1); + // + if (trace.startsolid) + { + //Log_Write("area %d: trace.startsolid\r\n", areanum); + break; + } //end if + reachareanum = AAS_PointAreaNum(trace.endpos); + if (reachareanum == areanum) + { + //Log_Write("area %d: same area\r\n", areanum); + break; + } //end if + if (AAS_ReachabilityExists(areanum, reachareanum)) + { + //Log_Write("area %d: reachability already exists\r\n", areanum); + break; + } //end if + if (!AAS_AreaGrounded(reachareanum) && !AAS_AreaSwim(reachareanum)) + { + //Log_Write("area %d, reach area %d: not grounded and not swim\r\n", areanum, reachareanum); + break; + } //end if + // + if ((*aasworld).areasettings[reachareanum].contents & AREACONTENTS_LAVA) //----(SA) modified since slime is no longer deadly + { // if ((*aasworld).areasettings[reachareanum].contents & (AREACONTENTS_SLIME | AREACONTENTS_LAVA)) + //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum); + break; + } //end if + lreach = AAS_AllocReachability(); + if (!lreach) + { + break; + } + lreach->areanum = reachareanum; + lreach->facenum = 0; + lreach->edgenum = edge1num; + VectorCopy(mid, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = STARTWALKOFFLEDGE_TIME + Q_fabs(mid[2] - trace.endpos[2]) * 50 / aassettings.sv_gravity; + if (!AAS_AreaSwim(reachareanum) && !AAS_AreaJumpPad(reachareanum)) + { + if (AAS_FallDelta(mid[2] - trace.endpos[2]) > FALLDELTA_5DAMAGE) + { + lreach->traveltime += FALLDAMAGE_5_TIME; + } //end if + else if (AAS_FallDelta(mid[2] - trace.endpos[2]) > FALLDELTA_10DAMAGE) + { + lreach->traveltime += FALLDAMAGE_10_TIME; + } //end if + } //end if + lreach->next = areareachability[areanum]; + areareachability[areanum] = lreach; + //we've got another walk off ledge reachability + reach_walkoffledge++; + } //end if + } //end for + } //end for + } //end for + } //end for +} //end of the function AAS_Reachability_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreReachability(void) +{ + int i; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_reachability_t *reach; + + if ((*aasworld).reachability) + { + FreeMemory((*aasworld).reachability); + } + (*aasworld).reachability = (aas_reachability_t *) GetClearedMemory((numlreachabilities + 10) * sizeof(aas_reachability_t)); + (*aasworld).reachabilitysize = 1; + for (i = 0; i < (*aasworld).numareas; i++) + { + areasettings = &(*aasworld).areasettings[i]; + areasettings->firstreachablearea = (*aasworld).reachabilitysize; + areasettings->numreachableareas = 0; + for (lreach = areareachability[i]; lreach; lreach = lreach->next) + { + reach = &(*aasworld).reachability[areasettings->firstreachablearea + + areasettings->numreachableareas]; + reach->areanum = lreach->areanum; + reach->facenum = lreach->facenum; + reach->edgenum = lreach->edgenum; + VectorCopy(lreach->start, reach->start); + VectorCopy(lreach->end, reach->end); + reach->traveltype = lreach->traveltype; + reach->traveltime = lreach->traveltime; + // RF, enforce the min reach time + if (reach->traveltime < REACH_MIN_TIME) + { + reach->traveltime = REACH_MIN_TIME; + } + // + areasettings->numreachableareas++; + } //end for + (*aasworld).reachabilitysize += areasettings->numreachableareas; + } //end for +} //end of the function AAS_StoreReachability +//=========================================================================== +// +// TRAVEL_WALK 100% equal floor height + steps +// TRAVEL_CROUCH 100% +// TRAVEL_BARRIERJUMP 100% +// TRAVEL_JUMP 80% +// TRAVEL_LADDER 100% + fall down from ladder + jump up to ladder +// TRAVEL_WALKOFFLEDGE 90% walk off very steep walls? +// TRAVEL_SWIM 100% +// TRAVEL_WATERJUMP 100% +// TRAVEL_TELEPORT 100% +// TRAVEL_ELEVATOR 100% +// TRAVEL_GRAPPLEHOOK 100% +// TRAVEL_DOUBLEJUMP 0% +// TRAVEL_RAMPJUMP 0% +// TRAVEL_STRAFEJUMP 0% +// TRAVEL_ROCKETJUMP 100% (currently limited towards areas with items) +// TRAVEL_BFGJUMP 0% (currently disabled) +// TRAVEL_JUMPPAD 100% +// TRAVEL_FUNCBOB 100% +// +// Parameter: - +// Returns: true if NOT finished +// Changes Globals: - +//=========================================================================== +int AAS_ContinueInitReachability(float time) +{ + int i, j, todo, start_time; + static float framereachability, reachability_delay; + static int lastpercentage; + + if (!(*aasworld).loaded) + { + return qfalse; + } + //if reachability is calculated for all areas + if ((*aasworld).reachabilityareas >= (*aasworld).numareas + 2) + { + return qfalse; + } + //if starting with area 1 (area 0 is a dummy) + if ((*aasworld).reachabilityareas == 1) + { + botimport.Print(PRT_MESSAGE, "calculating reachability...\n"); + lastpercentage = 0; + framereachability = 2000; + reachability_delay = 1000; + } //end if + //number of areas to calculate reachability for this cycle + todo = (*aasworld).reachabilityareas + (int) framereachability; + start_time = Sys_MilliSeconds(); + //loop over the areas + for (i = (*aasworld).reachabilityareas; i < (*aasworld).numareas && i < todo; i++) + { + (*aasworld).reachabilityareas++; + //only create jumppad reachabilities from jumppad areas + if ((*aasworld).areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < (*aasworld).numareas; j++) + { + if (i == j) + { + continue; + } + //never create reachabilities from teleporter or jumppad areas to regular areas + if ((*aasworld).areasettings[i].contents & (AREACONTENTS_TELEPORTER | AREACONTENTS_JUMPPAD)) + { + if (!((*aasworld).areasettings[j].contents & (AREACONTENTS_TELEPORTER | AREACONTENTS_JUMPPAD))) + { + continue; + } //end if + } //end if + //if there already is a reachability link from area i to j + if (AAS_ReachabilityExists(i, j)) + { + continue; + } + //check for a swim reachability + if (AAS_Reachability_Swim(i, j)) + { + continue; + } + //check for a simple walk on equal floor height reachability + if (AAS_Reachability_EqualFloorHeight(i, j)) + { + continue; + } + //check for step, barrier, waterjump and walk off ledge reachabilities + if (AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(i, j)) + { + continue; + } + //check for ladder reachabilities + if (AAS_Reachability_Ladder(i, j)) + { + continue; + } + //check for a jump reachability + if (AAS_Reachability_Jump(i, j)) + { + continue; + } + } //end for + //never create these reachabilities from teleporter or jumppad areas + if ((*aasworld).areasettings[i].contents & (AREACONTENTS_TELEPORTER | AREACONTENTS_JUMPPAD)) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < (*aasworld).numareas; j++) + { + if (i == j) + { + continue; + } + // + if (AAS_ReachabilityExists(i, j)) + { + continue; + } + //check for a grapple hook reachability +// Ridah, no grapple +// AAS_Reachability_Grapple(i, j); + //check for a weapon jump reachability +// Ridah, no weapon jumping +// AAS_Reachability_WeaponJump(i, j); + } //end for + //if the calculation took more time than the max reachability delay + if (Sys_MilliSeconds() - start_time > (int) reachability_delay) + { + break; + } + // + if ((*aasworld).reachabilityareas * 1000 / (*aasworld).numareas > lastpercentage) + { + break; + } + } //end for + // + if ((*aasworld).reachabilityareas == (*aasworld).numareas) + { + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) 100.0); + botimport.Print(PRT_MESSAGE, "\nplease wait while storing reachability...\n"); + (*aasworld).reachabilityareas++; + } //end if + //if this is the last step in the reachability calculations + else if ((*aasworld).reachabilityareas == (*aasworld).numareas + 1) + { + //create additional walk off ledge reachabilities for every area + for (i = 1; i < (*aasworld).numareas; i++) + { + //only create jumppad reachabilities from jumppad areas + if ((*aasworld).areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + AAS_Reachability_WalkOffLedge(i); + } //end for + //create jump pad reachabilities + AAS_Reachability_JumpPad(); + //create teleporter reachabilities + AAS_Reachability_Teleport(); + //create elevator (func_plat) reachabilities + AAS_Reachability_Elevator(); + //create func_bobbing reachabilities + AAS_Reachability_FuncBobbing(); + // +//#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "%6d reach swim\n", reach_swim); + botimport.Print(PRT_MESSAGE, "%6d reach equal floor\n", reach_equalfloor); + botimport.Print(PRT_MESSAGE, "%6d reach step\n", reach_step); + botimport.Print(PRT_MESSAGE, "%6d reach barrier\n", reach_barrier); + botimport.Print(PRT_MESSAGE, "%6d reach waterjump\n", reach_waterjump); + botimport.Print(PRT_MESSAGE, "%6d reach walkoffledge\n", reach_walkoffledge); + botimport.Print(PRT_MESSAGE, "%6d reach jump\n", reach_jump); + botimport.Print(PRT_MESSAGE, "%6d reach ladder\n", reach_ladder); + botimport.Print(PRT_MESSAGE, "%6d reach walk\n", reach_walk); + botimport.Print(PRT_MESSAGE, "%6d reach teleport\n", reach_teleport); + botimport.Print(PRT_MESSAGE, "%6d reach funcbob\n", reach_funcbob); + botimport.Print(PRT_MESSAGE, "%6d reach elevator\n", reach_elevator); + botimport.Print(PRT_MESSAGE, "%6d reach grapple\n", reach_grapple); + botimport.Print(PRT_MESSAGE, "%6d reach rocketjump\n", reach_rocketjump); + botimport.Print(PRT_MESSAGE, "%6d reach jumppad\n", reach_jumppad); +//#endif + //*/ + //store all the reachabilities + AAS_StoreReachability(); + //free the reachability link heap + AAS_ShutDownReachabilityHeap(); + // + FreeMemory(areareachability); + // + (*aasworld).reachabilityareas++; + // + botimport.Print(PRT_MESSAGE, "calculating clusters...\n"); + } //end if + else + { + lastpercentage = (*aasworld).reachabilityareas * 1000 / (*aasworld).numareas; + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) lastpercentage / 10); + } //end else + //not yet finished + return qtrue; +} //end of the function AAS_ContinueInitReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitReachability(void) +{ + if (!(*aasworld).loaded) + { + return; + } + + if ((*aasworld).reachabilitysize) + { +#ifndef BSPC + if (!((int)LibVarGetValue("forcereachability"))) + { + (*aasworld).reachabilityareas = (*aasworld).numareas + 2; + return; + } //end if +#else + (*aasworld).reachabilityareas = (*aasworld).numareas + 2; + return; +#endif //BSPC + } //end if + (*aasworld).savefile = qtrue; + //start with area 1 because area zero is a dummy + (*aasworld).reachabilityareas = 1; + //setup the heap with reachability links + AAS_SetupReachabilityHeap(); + //allocate area reachability link array + areareachability = (aas_lreachability_t **) GetClearedMemory( + (*aasworld).numareas * sizeof(aas_lreachability_t *)); + // + AAS_SetWeaponJumpAreaFlags(); +} //end of the function AAS_InitReachable diff --git a/src/botlib/be_aas_reach.h b/src/botlib/be_aas_reach.h new file mode 100644 index 000000000..b42c9d863 --- /dev/null +++ b/src/botlib/be_aas_reach.h @@ -0,0 +1,77 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_reach.h + * @brief reachability calculations + */ + +#ifdef AASINTERN +//initialize calculating the reachabilities +void AAS_InitReachability(void); +//continue calculating the reachabilities +int AAS_ContinueInitReachability(float time); +// +int AAS_BestReachableLinkArea(aas_link_t *areas); +#endif //AASINTERN + +//returns true if the are has reachabilities to other areas +int AAS_AreaReachability(int areanum); +//returns the best reachable area and goal origin for a bounding box at the given origin +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin); +//returns the next reachability using the given model +int AAS_NextModelReachability(int num, int modelnum); +//returns the total area of the ground faces of the given area +float AAS_AreaGroundFaceArea(int areanum); + + +//returns true if the area is crouch only +//int AAS_AreaCrouch(int areanum); +#define AAS_AreaCrouch(areanum) ((!(aasworld->areasettings[areanum].presencetype & PRESENCE_NORMAL)) ? qtrue : qfalse) + +//returns true if a player can swim in this area +//int AAS_AreaSwim(int areanum); +#define AAS_AreaSwim(areanum) ((aasworld->areasettings[areanum].areaflags & AREA_LIQUID) ? qtrue : qfalse) + +//returns true if the area is filled with a liquid +int AAS_AreaLiquid(int areanum); +//returns true if the area contains lava +int AAS_AreaLava(int areanum); +//returns true if the area contains slime +int AAS_AreaSlime(int areanum); +//returns true if the area has one or more ground faces +int AAS_AreaGrounded(int areanum); +//returns true if the area has one or more ladder faces +int AAS_AreaLadder(int areanum); +//returns true if the area is a jump pad +int AAS_AreaJumpPad(int areanum); +//returns true if the area is donotenter +int AAS_AreaDoNotEnter(int areanum); +//returns true if the area is donotenterlarge +int AAS_AreaDoNotEnterLarge(int areanum); diff --git a/src/botlib/be_aas_route.c b/src/botlib/be_aas_route.c new file mode 100644 index 000000000..a6dc65c7a --- /dev/null +++ b/src/botlib/be_aas_route.c @@ -0,0 +1,4212 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_route.c + */ + +#include "../qcommon/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_crc.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ROUTING_DEBUG + +//travel time in hundreths of a second = distance * 100 / speed +#define DISTANCEFACTOR_CROUCH 1.3 //crouch speed = 100 +#define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150 +#define DISTANCEFACTOR_WALK 0.33 //walk speed = 300 + +// Ridah, scale traveltimes with ground steepness of area +#define GROUNDSTEEPNESS_TIMESCALE 1 // this is the maximum scale, 1 being the usual for a flat ground + +//cache refresh time +#define CACHE_REFRESHTIME 15.0 //15 seconds refresh time + +#define DEFAULT_MAX_ROUTINGCACHESIZE "16384" + +extern aas_t aasworlds[MAX_AAS_WORLDS]; + + +/* + + area routing cache: + stores the distances within one cluster to a specific goal area + this goal area is in this same cluster and could be a cluster portal + for every cluster there's a list with routing cache for every area + in that cluster (including the portals of that cluster) + area cache stores aasworld->clusters[?].numreachabilityareas travel times + + portal routing cache: + stores the distances of all portals to a specific goal area + this goal area could be in any cluster and could also be a cluster portal + for every area (aasworld->numareas) the portal cache stores + aasworld->numportals travel times + +*/ + +#ifdef ROUTING_DEBUG +int numareacacheupdates; +int numportalcacheupdates; +#endif //ROUTING_DEBUG + +int routingcachesize; +int max_routingcachesize; +int max_frameroutingupdates; + +// Ridah, routing memory calls go here, so we can change between Hunk/Zone easily +void *AAS_RoutingGetMemory(int size) +{ + return GetClearedMemory(size); +} + +void AAS_RoutingFreeMemory(void *ptr) +{ + FreeMemory(ptr); +} +// done. + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef ROUTING_DEBUG +void AAS_RoutingInfo(void) +{ + botimport.Print(PRT_MESSAGE, "%d area cache updates\n", numareacacheupdates); + botimport.Print(PRT_MESSAGE, "%d portal cache updates\n", numportalcacheupdates); + botimport.Print(PRT_MESSAGE, "%d bytes routing cache\n", routingcachesize); +} //end of the function AAS_RoutingInfo +#endif //ROUTING_DEBUG +//=========================================================================== +// returns the number of the area in the cluster +// assumes the given area is in the given cluster or a portal of the cluster +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_ClusterAreaNum(int cluster, int areanum) +{ + int side, areacluster; + + areacluster = aasworld->areasettings[areanum].cluster; + if (areacluster > 0) + { + return aasworld->areasettings[areanum].clusterareanum; + } + else + { +/*#ifdef ROUTING_DEBUG + if (aasworld->portals[-areacluster].frontcluster != cluster && + aasworld->portals[-areacluster].backcluster != cluster) + { + botimport.Print(PRT_ERROR, "portal %d: does not belong to cluster %d\n" + , -areacluster, cluster); + } //end if +#endif //ROUTING_DEBUG*/ + side = aasworld->portals[-areacluster].frontcluster != cluster; + return aasworld->portals[-areacluster].clusterareanum[side]; + } //end else +} //end of the function AAS_ClusterAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitTravelFlagFromType(void) +{ + int i; + + for (i = 0; i < MAX_TRAVELTYPES; i++) + { + aasworld->travelflagfortype[i] = TFL_INVALID; + } //end for + aasworld->travelflagfortype[TRAVEL_INVALID] = TFL_INVALID; + aasworld->travelflagfortype[TRAVEL_WALK] = TFL_WALK; + aasworld->travelflagfortype[TRAVEL_CROUCH] = TFL_CROUCH; + aasworld->travelflagfortype[TRAVEL_BARRIERJUMP] = TFL_BARRIERJUMP; + aasworld->travelflagfortype[TRAVEL_JUMP] = TFL_JUMP; + aasworld->travelflagfortype[TRAVEL_LADDER] = TFL_LADDER; + aasworld->travelflagfortype[TRAVEL_WALKOFFLEDGE] = TFL_WALKOFFLEDGE; + aasworld->travelflagfortype[TRAVEL_SWIM] = TFL_SWIM; + aasworld->travelflagfortype[TRAVEL_WATERJUMP] = TFL_WATERJUMP; + aasworld->travelflagfortype[TRAVEL_TELEPORT] = TFL_TELEPORT; + aasworld->travelflagfortype[TRAVEL_ELEVATOR] = TFL_ELEVATOR; + aasworld->travelflagfortype[TRAVEL_ROCKETJUMP] = TFL_ROCKETJUMP; + aasworld->travelflagfortype[TRAVEL_BFGJUMP] = TFL_BFGJUMP; + aasworld->travelflagfortype[TRAVEL_GRAPPLEHOOK] = TFL_GRAPPLEHOOK; + aasworld->travelflagfortype[TRAVEL_DOUBLEJUMP] = TFL_DOUBLEJUMP; + aasworld->travelflagfortype[TRAVEL_RAMPJUMP] = TFL_RAMPJUMP; + aasworld->travelflagfortype[TRAVEL_STRAFEJUMP] = TFL_STRAFEJUMP; + aasworld->travelflagfortype[TRAVEL_JUMPPAD] = TFL_JUMPPAD; + aasworld->travelflagfortype[TRAVEL_FUNCBOB] = TFL_FUNCBOB; +} //end of the function AAS_InitTravelFlagFromType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagForType(int traveltype) +{ + if (traveltype < 0 || traveltype >= MAX_TRAVELTYPES) + { + return TFL_INVALID; + } + return aasworld->travelflagfortype[traveltype]; +} //end of the function AAS_TravelFlagForType + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline float AAS_RoutingTime(void) +{ + return AAS_Time(); +} //end of the function AAS_RoutingTime + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCache(aas_routingcache_t *cache) +{ + routingcachesize -= cache->size; + AAS_RoutingFreeMemory(cache); +} //end of the function AAS_FreeRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheInCluster(int clusternum) +{ + int i; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + if (!aasworld->clusterareacache) + { + return; + } + cluster = &aasworld->clusters[clusternum]; + for (i = 0; i < cluster->numareas; i++) + { + for (cache = aasworld->clusterareacache[clusternum][i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld->clusterareacache[clusternum][i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheInCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheUsingArea(int areanum) +{ + int i, clusternum; + aas_routingcache_t *cache, *nextcache; + + clusternum = aasworld->areasettings[areanum].cluster; + if (clusternum > 0) + { + //remove all the cache in the cluster the area is in + AAS_RemoveRoutingCacheInCluster(clusternum); + } //end if + else + { + // if this is a portal remove all cache in both the front and back cluster + AAS_RemoveRoutingCacheInCluster(aasworld->portals[-clusternum].frontcluster); + AAS_RemoveRoutingCacheInCluster(aasworld->portals[-clusternum].backcluster); + } //end else + // remove all portal cache + if (aasworld->portalcache) + { + for (i = 0; i < aasworld->numareas; i++) + { + //refresh portal cache + for (cache = aasworld->portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld->portalcache[i] = NULL; + } //end for + } +} //end of the function AAS_RemoveRoutingCacheUsingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TeamTravelFlagsForAreaFlags(int areaflags) +{ + int travelflags = 0; + // + if (areaflags & AREA_TEAM_FLAGS) + { + if (areaflags & AREA_TEAM_AXIS) + { + travelflags |= TFL_TEAM_AXIS; + } + if (areaflags & AREA_TEAM_ALLIES) + { + travelflags |= TFL_TEAM_ALLIES; + } + if (areaflags & AREA_TEAM_AXIS_DISGUISED) + { + travelflags |= TFL_TEAM_AXIS_DISGUISED; + } + if (areaflags & AREA_TEAM_ALLIES_DISGUISED) + { + travelflags |= TFL_TEAM_AXIS_DISGUISED; + } + if (areaflags & AREA_AVOID_AXIS) + { + travelflags |= TFL_TEAM_AXIS; + } + if (areaflags & AREA_AVOID_ALLIES) + { + travelflags |= TFL_TEAM_ALLIES; + } + } + // + return travelflags; +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearClusterTeamFlags(int areanum) +{ + int clusternum; + // + clusternum = aasworld->areasettings[areanum].cluster; + if (clusternum > 0) + { + aasworld->clusterTeamTravelFlags[clusternum] = -1; // recalculate + } +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CalculateClusterTeamFlags(int clusternum) +{ + int i; + // + if (clusternum < 0) + { + return; + } + // + aasworld->clusterTeamTravelFlags[clusternum] = 0; + for (i = 1; i < aasworld->numareas; i++) + { + if (aasworld->areasettings[i].cluster == clusternum) + { + aasworld->clusterTeamTravelFlags[clusternum] |= AAS_TeamTravelFlagsForAreaFlags(aasworld->areasettings[i].areaflags); + } + } +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EnableRoutingArea(int areanum, int enable) +{ + int flags; + int bitflag; // flag to set or clear + + if (areanum <= 0 || areanum >= aasworld->numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_EnableRoutingArea: areanum %d out of range\n", areanum); + } //end if + return 0; + } //end if + + if ((enable & 1) || (enable < 0)) + { + // clear all flags + bitflag = AREA_AVOID | AREA_DISABLED | AREA_TEAM_AXIS | AREA_TEAM_ALLIES | AREA_TEAM_AXIS_DISGUISED | AREA_TEAM_ALLIES_DISGUISED; + } + else if (enable & 0x10) + { + bitflag = AREA_AVOID; + } + else if (enable & 0x20) + { + bitflag = AREA_TEAM_AXIS; + } + else if (enable & 0x40) + { + bitflag = AREA_TEAM_ALLIES; + } + else if (enable & 0x80) + { + bitflag = AREA_TEAM_AXIS_DISGUISED; + } + else if (enable & 0x100) + { + bitflag = AREA_TEAM_ALLIES_DISGUISED; + } + else + { + bitflag = AREA_DISABLED; + } + + // remove avoidance flag + enable &= 1; + + flags = aasworld->areasettings[areanum].areaflags & bitflag; + if (enable < 0) + { + return !flags; + } + + if (enable) + { + aasworld->areasettings[areanum].areaflags &= ~bitflag; + } + else + { + aasworld->areasettings[areanum].areaflags |= bitflag; + } + + // if the status of the area changed + if ((flags & bitflag) != (aasworld->areasettings[areanum].areaflags & bitflag)) + { + //remove all routing cache involving this area + AAS_RemoveRoutingCacheUsingArea(areanum); + // recalculate the team flags that are used in this cluster + AAS_ClearClusterTeamFlags(areanum); + } //end if + return !flags; +} //end of the function AAS_EnableRoutingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EnableAllAreas(void) +{ + int i; + for (i = 0; i < (*aasworld).numareas; i++) + { + if ((*aasworld).areasettings[i].areaflags & AREA_DISABLED) + { + AAS_EnableRoutingArea(i, qtrue); + } + } +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateReversedReachability(void) +{ + int i, n; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + char *ptr; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif + //free reversed links that have already been created + if (aasworld->reversedreachability) + { + AAS_RoutingFreeMemory(aasworld->reversedreachability); + } + //allocate memory for the reversed reachability links + ptr = (char *) AAS_RoutingGetMemory(aasworld->numareas * sizeof(aas_reversedreachability_t) + aasworld->reachabilitysize * sizeof(aas_reversedlink_t)); + + aasworld->reversedreachability = (aas_reversedreachability_t *) ptr; + //pointer to the memory for the reversed links + ptr += aasworld->numareas * sizeof(aas_reversedreachability_t); + //check all other areas for reachability links to the area + for (i = 1; i < aasworld->numareas; i++) + { + //settings of the area + settings = &(aasworld->areasettings[i]); + //check the reachability links + for (n = 0; n < settings->numreachableareas; n++) + { + // Gordon: Temp hack for b0rked last area in + if (settings->firstreachablearea < 0 || settings->firstreachablearea >= (*aasworld).reachabilitysize) + { + Com_Printf("^1WARNING: settings->firstreachablearea out of range\n"); + continue; + } + + //reachability link + reach = &aasworld->reachability[settings->firstreachablearea + n]; + + if ((reach->areanum < 0 || (reach->areanum >= aasworld->reachabilitysize))) + { + Com_Printf("^1WARNING: reach->areanum out of range\n"); + continue; + } + + revlink = (aas_reversedlink_t *) ptr; + ptr += sizeof(aas_reversedlink_t); + // + revlink->areanum = i; + revlink->linknum = settings->firstreachablearea + n; + revlink->next = aasworld->reversedreachability[reach->areanum].first; + aasworld->reversedreachability[reach->areanum].first = revlink; + aasworld->reversedreachability[reach->areanum].numlinks++; + } //end for + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "reversed reachability %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG +} //end of the function AAS_CreateReversedReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// Gordon: always returns 1, so er?... +float AAS_AreaGroundSteepnessScale(int areanum) +{ + return (1.0 + aasworld->areasettings[areanum].groundsteepness * (float)(GROUNDSTEEPNESS_TIMESCALE - 1)); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end) +{ + int intdist; + float dist; + + // Ridah, factor in the groundsteepness now + dist = VectorDistance(start, end); // * AAS_AreaGroundSteepnessScale(areanum); // Gordon: useless as it returns 1 all the time... + + if (AAS_AreaCrouch(areanum)) + { + dist *= DISTANCEFACTOR_CROUCH; //if crouch only area +/* } else if( AAS_AreaSwim(areanum)) { // Gordon: again, uselss as it's a multiply by 1 + dist *= DISTANCEFACTOR_SWIM; //if swim area */ + } + else + { + dist *= DISTANCEFACTOR_WALK; //normal walk area + } + + intdist = (int)(dist); + + //make sure the distance isn't zero + if (intdist <= 0) + { + return 1; + } + + return intdist; +} //end of the function AAS_AreaTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CalculateAreaTravelTimes(void) +{ + int i, l, n, size; + char *ptr; + vec3_t end; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + int starttime; + + starttime = Sys_MilliSeconds(); + //if there are still area travel times, free the memory + if (aasworld->areatraveltimes) + { + AAS_RoutingFreeMemory(aasworld->areatraveltimes); + } + //get the total size of all the area travel times + size = aasworld->numareas * sizeof(unsigned short **); + for (i = 0; i < aasworld->numareas; i++) + { + revreach = &aasworld->reversedreachability[i]; + //settings of the area + settings = &aasworld->areasettings[i]; + // + size += settings->numreachableareas * sizeof(unsigned short *); + // + size += settings->numreachableareas * revreach->numlinks * sizeof(unsigned short); + } //end for + //allocate memory for the area travel times + ptr = (char *) AAS_RoutingGetMemory(size); + aasworld->areatraveltimes = (unsigned short ***) ptr; + ptr += aasworld->numareas * sizeof(unsigned short **); + //calcluate the travel times for all the areas + for (i = 0; i < aasworld->numareas; i++) + { + //reversed reachabilities of this area + revreach = &aasworld->reversedreachability[i]; + //settings of the area + settings = &aasworld->areasettings[i]; + // + if (settings->numreachableareas) + { + aasworld->areatraveltimes[i] = (unsigned short **) ptr; + ptr += settings->numreachableareas * sizeof(unsigned short *); + // + reach = &aasworld->reachability[settings->firstreachablearea]; + for (l = 0; l < settings->numreachableareas; l++, reach++) + { + aasworld->areatraveltimes[i][l] = (unsigned short *) ptr; + ptr += revreach->numlinks * sizeof(unsigned short); + //reachability link + // + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + VectorCopy(aasworld->reachability[revlink->linknum].end, end); + // + aasworld->areatraveltimes[i][l][n] = AAS_AreaTravelTime(i, end, reach->start); + } //end for + } //end for + } + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "area travel times %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG +} //end of the function AAS_CalculateAreaTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PortalMaxTravelTime(int portalnum) +{ + int l, n, t, maxt; + aas_portal_t *portal; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_areasettings_t *settings; + + portal = &aasworld->portals[portalnum]; + //reversed reachabilities of this portal area + revreach = &aasworld->reversedreachability[portal->areanum]; + //settings of the portal area + settings = &aasworld->areasettings[portal->areanum]; + // + maxt = 0; + for (l = 0; l < settings->numreachableareas; l++) + { + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + t = aasworld->areatraveltimes[portal->areanum][l][n]; + if (t > maxt) + { + maxt = t; + } //end if + } //end for + } //end for + return maxt; +} //end of the function AAS_PortalMaxTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalMaxTravelTimes(void) +{ + int i; + + if (aasworld->portalmaxtraveltimes) + { + AAS_RoutingFreeMemory(aasworld->portalmaxtraveltimes); + } + + aasworld->portalmaxtraveltimes = (int *) AAS_RoutingGetMemory(aasworld->numportals * sizeof(int)); + + for (i = 0; i < aasworld->numportals; i++) + { + aasworld->portalmaxtraveltimes[i] = AAS_PortalMaxTravelTime(i); + //botimport.Print(PRT_MESSAGE, "portal %d max tt = %d\n", i, aasworld->portalmaxtraveltimes[i]); + } //end for +} //end of the function AAS_InitPortalMaxTravelTimes +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkCache(aas_routingcache_t *cache) +{ + if (cache->time_next) cache->time_next->time_prev = cache->time_prev; + else newestcache = cache->time_prev; + if (cache->time_prev) cache->time_prev->time_next = cache->time_next; + else oldestcache = cache->time_next; + cache->time_next = NULL; + cache->time_prev = NULL; +} //end of the function AAS_UnlinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_LinkCache(aas_routingcache_t *cache) +{ + if (newestcache) + { + newestcache->time_next = cache; + cache->time_prev = cache; + } //end if + else + { + oldestcache = cache; + cache->time_prev = NULL; + } //end else + cache->time_next = NULL; + newestcache = cache; +} //end of the function AAS_LinkCache*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FreeOldestCache(void) +{ + int i, j, bestcluster, bestarea, freed; + float besttime; + aas_routingcache_t *cache, *bestcache; + + freed = qfalse; + besttime = 999999999; + bestcache = NULL; + bestcluster = 0; + bestarea = 0; + //refresh cluster cache + for (i = 0; i < aasworld->numclusters; i++) + { + for (j = 0; j < aasworld->clusters[i].numareas; j++) + { + for (cache = aasworld->clusterareacache[i][j]; cache; cache = cache->next) + { + //never remove cache leading towards a portal + if (aasworld->areasettings[cache->areanum].cluster < 0) + { + continue; + } + //if this cache is older than the cache we found so far + if (cache->time < besttime) + { + bestcache = cache; + bestcluster = i; + bestarea = j; + besttime = cache->time; + } //end if + } //end for + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) + { + cache->prev->next = cache->next; + } + else + { + aasworld->clusterareacache[bestcluster][bestarea] = cache->next; + } + if (cache->next) + { + cache->next->prev = cache->prev; + } + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + besttime = 999999999; + bestcache = NULL; + bestarea = 0; + for (i = 0; i < aasworld->numareas; i++) + { + //refresh portal cache + for (cache = aasworld->portalcache[i]; cache; cache = cache->next) + { + if (cache->time < besttime) + { + bestcache = cache; + bestarea = i; + besttime = cache->time; + } //end if + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) + { + cache->prev->next = cache->next; + } + else + { + aasworld->portalcache[bestarea] = cache->next; + } + if (cache->next) + { + cache->next->prev = cache->prev; + } + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + return freed; +} //end of the function AAS_FreeOldestCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_AllocRoutingCache(int numtraveltimes) +{ + aas_routingcache_t *cache; + int size; + + size = sizeof(aas_routingcache_t) + numtraveltimes * sizeof(unsigned short int) + numtraveltimes * sizeof(unsigned char); + + routingcachesize += size; + + cache = (aas_routingcache_t *) AAS_RoutingGetMemory(size); + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) + numtraveltimes * sizeof(unsigned short int); + cache->size = size; + return cache; +} //end of the function AAS_AllocRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllClusterAreaCache(void) +{ + int i, j; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + //free all cluster cache if existing + if (!aasworld->clusterareacache) + { + return; + } + //free caches + for (i = 0; i < aasworld->numclusters; i++) + { + cluster = &aasworld->clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld->clusterareacache[i][j]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld->clusterareacache[i][j] = NULL; + } //end for + } //end for + //free the cluster cache array + AAS_RoutingFreeMemory(aasworld->clusterareacache); + aasworld->clusterareacache = NULL; +} //end of the function AAS_FreeAllClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClusterAreaCache(void) +{ + int i, size; + char *ptr; + + // + for (size = 0, i = 0; i < aasworld->numclusters; i++) + { + size += aasworld->clusters[i].numareas; + } //end for + //two dimensional array with pointers for every cluster to routing cache + //for every area in that cluster + ptr = (char *) AAS_RoutingGetMemory( + aasworld->numclusters * sizeof(aas_routingcache_t * *) + + size * sizeof(aas_routingcache_t *)); + aasworld->clusterareacache = (aas_routingcache_t ***) ptr; + ptr += aasworld->numclusters * sizeof(aas_routingcache_t * *); + for (i = 0; i < aasworld->numclusters; i++) + { + aasworld->clusterareacache[i] = (aas_routingcache_t **) ptr; + ptr += aasworld->clusters[i].numareas * sizeof(aas_routingcache_t *); + } //end for +} //end of the function AAS_InitClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllPortalCache(void) +{ + int i; + aas_routingcache_t *cache, *nextcache; + + //free all portal cache if existing + if (!aasworld->portalcache) + { + return; + } + //free portal caches + for (i = 0; i < aasworld->numareas; i++) + { + for (cache = aasworld->portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld->portalcache[i] = NULL; + } //end for + AAS_RoutingFreeMemory(aasworld->portalcache); + aasworld->portalcache = NULL; +} //end of the function AAS_FreeAllPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalCache(void) +{ + // + aasworld->portalcache = (aas_routingcache_t **) AAS_RoutingGetMemory( + aasworld->numareas * sizeof(aas_routingcache_t *)); +} //end of the function AAS_InitPortalCache +// +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAreaVisibility(void) +{ + int i; + + if (aasworld->areavisibility) + { + for (i = 0; i < aasworld->numareas; i++) + { + if (aasworld->areavisibility[i]) + { + FreeMemory(aasworld->areavisibility[i]); + } + } + } + if (aasworld->areavisibility) + { + FreeMemory(aasworld->areavisibility); + } + aasworld->areavisibility = NULL; + if (aasworld->decompressedvis) + { + FreeMemory(aasworld->decompressedvis); + } + aasworld->decompressedvis = NULL; +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRoutingUpdate(void) +{ +// int i, maxreachabilityareas; + + //free routing update fields if already existing + if (aasworld->areaupdate) + { + AAS_RoutingFreeMemory(aasworld->areaupdate); + } + + aasworld->areaupdate = (aas_routingupdate_t *)AAS_RoutingGetMemory(aasworld->numareas * sizeof(aas_routingupdate_t)); + + if (aasworld->portalupdate) + { + AAS_RoutingFreeMemory(aasworld->portalupdate); + } + //allocate memory for the portal update fields + aasworld->portalupdate = (aas_routingupdate_t *) AAS_RoutingGetMemory((aasworld->numportals + 1) * sizeof(aas_routingupdate_t)); +} //end of the function AAS_InitRoutingUpdate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_CreateAllRoutingCache(void) +{ + int i, j, k, t, tfl, numroutingareas; + aas_areasettings_t *areasettings; + aas_reachability_t *reach; + + numroutingareas = 0; + tfl = TFL_DEFAULT & ~(TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA); //----(SA) modified since slime is no longer deadly +// tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); + botimport.Print(PRT_MESSAGE, "AAS_CreateAllRoutingCache\n"); + // + for (i = 1; i < aasworld->numareas; i++) + { + if (!AAS_AreaReachability(i)) + { + continue; + } + areasettings = &aasworld->areasettings[i]; + for (k = 0; k < areasettings->numreachableareas; k++) + { + reach = &aasworld->reachability[areasettings->firstreachablearea + k]; + if (aasworld->travelflagfortype[reach->traveltype] & tfl) + { + break; + } + } + if (k >= areasettings->numreachableareas) + { + continue; + } + aasworld->areasettings[i].areaflags |= AREA_USEFORROUTING; + numroutingareas++; + } + for (i = 1; i < aasworld->numareas; i++) + { + if (!(aasworld->areasettings[i].areaflags & AREA_USEFORROUTING)) + { + continue; + } + for (j = 1; j < aasworld->numareas; j++) + { + if (i == j) + { + continue; + } + if (!(aasworld->areasettings[j].areaflags & AREA_USEFORROUTING)) + { + continue; + } + t = AAS_AreaTravelTimeToGoalArea(j, aasworld->areawaypoints[j], i, tfl); + aasworld->frameroutingupdates = 0; + //if (t) break; + //Log_Write("traveltime from %d to %d is %d", i, j, t); + } //end for + } //end for +} //end of the function AAS_CreateAllRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_ProcessString(unsigned char *data, int length); + +//the route cache header +//this header is followed by numportalcache + numareacache aas_routingcache_t +//structures that store routing cache +typedef struct routecacheheader_s +{ + int ident; + int version; + int numareas; + int numclusters; + int areacrc; + int clustercrc; + int reachcrc; + int numportalcache; + int numareacache; +} routecacheheader_t; + +#define RCID (('C' << 24) + ('R' << 16) + ('E' << 8) + 'M') +#define RCVERSION 16 + +void AAS_DecompressVis(byte *in, int numareas, byte *decompressed); +int AAS_CompressVis(byte *vis, int numareas, byte *dest); + +void AAS_WriteRouteCache(void) +{ + int i, j, numportalcache, numareacache, size; + aas_routingcache_t *cache; + aas_cluster_t *cluster; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + byte *buf; + + buf = (byte *) GetClearedMemory(aasworld->numareas * 2 * sizeof(byte)); // in case it ends up bigger than the decompressedvis, which is rare but possible + + numportalcache = 0; + for (i = 0; i < aasworld->numareas; i++) + { + for (cache = aasworld->portalcache[i]; cache; cache = cache->next) + { + numportalcache++; + } //end for + } //end for + numareacache = 0; + for (i = 0; i < aasworld->numclusters; i++) + { + cluster = &aasworld->clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld->clusterareacache[i][j]; cache; cache = cache->next) + { + numareacache++; + } //end for + } //end for + } //end for + // open the file for writing + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld->mapname); + botimport.FS_FOpenFile(filename, &fp, FS_WRITE); + if (!fp) + { + AAS_Error("Unable to open file: %s\n", filename); + return; + } //end if + //create the header + routecacheheader.ident = RCID; + routecacheheader.version = RCVERSION; + routecacheheader.numareas = aasworld->numareas; + routecacheheader.numclusters = aasworld->numclusters; + routecacheheader.areacrc = CRC_ProcessString((unsigned char *)aasworld->areas, sizeof(aas_area_t) * aasworld->numareas); + routecacheheader.clustercrc = CRC_ProcessString((unsigned char *)aasworld->clusters, sizeof(aas_cluster_t) * aasworld->numclusters); + routecacheheader.reachcrc = CRC_ProcessString((unsigned char *)aasworld->reachability, sizeof(aas_reachability_t) * aasworld->reachabilitysize); + routecacheheader.numportalcache = numportalcache; + routecacheheader.numareacache = numareacache; + //write the header + botimport.FS_Write(&routecacheheader, sizeof(routecacheheader_t), fp); + //write all the cache + for (i = 0; i < aasworld->numareas; i++) + { + for (cache = aasworld->portalcache[i]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + } //end for + } //end for + for (i = 0; i < aasworld->numclusters; i++) + { + cluster = &aasworld->clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld->clusterareacache[i][j]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + } //end for + } //end for + } //end for + // write the visareas + for (i = 0; i < aasworld->numareas; i++) + { + if (!aasworld->areavisibility[i]) + { + size = 0; + botimport.FS_Write(&size, sizeof(int), fp); + continue; + } + AAS_DecompressVis(aasworld->areavisibility[i], aasworld->numareas, aasworld->decompressedvis); + size = AAS_CompressVis(aasworld->decompressedvis, aasworld->numareas, buf); + botimport.FS_Write(&size, sizeof(int), fp); + botimport.FS_Write(buf, size, fp); + } + // write the waypoints + botimport.FS_Write(aasworld->areawaypoints, sizeof(vec3_t) * aasworld->numareas, fp); + // + botimport.FS_FCloseFile(fp); + botimport.Print(PRT_MESSAGE, "\nroute cache written to %s\n", filename); +} //end of the function AAS_WriteRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_ReadCache(fileHandle_t fp) +{ + int size, i; + aas_routingcache_t *cache; + + botimport.FS_Read(&size, sizeof(size), fp); + size = LittleLong(size); + cache = (aas_routingcache_t *) AAS_RoutingGetMemory(size); + cache->size = size; + botimport.FS_Read((unsigned char *)cache + sizeof(size), size - sizeof(size), fp); + + if (1 != LittleLong(1)) + { + cache->time = LittleFloat(cache->time); + cache->cluster = LittleLong(cache->cluster); + cache->areanum = LittleLong(cache->areanum); + cache->origin[0] = LittleFloat(cache->origin[0]); + cache->origin[1] = LittleFloat(cache->origin[1]); + cache->origin[2] = LittleFloat(cache->origin[2]); + cache->starttraveltime = LittleFloat(cache->starttraveltime); + cache->travelflags = LittleLong(cache->travelflags); + } + +// cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) + +// (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2; + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) + + ((size - sizeof(aas_routingcache_t)) / 3) * 2; + + //DAJ BUGFIX for missing byteswaps for traveltimes + size = (size - sizeof(aas_routingcache_t)) / 3 + 1; + for (i = 0; i < size; i++) + { + cache->traveltimes[i] = LittleShort(cache->traveltimes[i]); + } + return cache; +} //end of the function AAS_ReadCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ReadRouteCache(void) +{ + int i, clusterareanum, size; + fileHandle_t fp = 0; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + aas_routingcache_t *cache; + + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld->mapname); + botimport.FS_FOpenFile(filename, &fp, FS_READ); + if (!fp) + { + return qfalse; + } //end if + botimport.FS_Read(&routecacheheader, sizeof(routecacheheader_t), fp); + if (routecacheheader.ident != RCID) + { + botimport.FS_FCloseFile(fp); + AAS_Error("%s is not a route cache dump\n"); + return qfalse; + } //end if + if (routecacheheader.version != RCVERSION) + { + botimport.FS_FCloseFile(fp); + AAS_Error("route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION); + return qfalse; + } //end if + if (routecacheheader.numareas != aasworld->numareas) + { + botimport.FS_FCloseFile(fp); + //AAS_Error("route cache dump has wrong number of areas\n"); + return qfalse; + } //end if + if (routecacheheader.numclusters != aasworld->numclusters) + { + botimport.FS_FCloseFile(fp); + //AAS_Error("route cache dump has wrong number of clusters\n"); + return qfalse; + } //end if +#if defined(MACOSX) + // the crc table stuff is endian orientated.... +#else + if (routecacheheader.areacrc != + CRC_ProcessString((unsigned char *)aasworld->areas, sizeof(aas_area_t) * aasworld->numareas)) + { + botimport.FS_FCloseFile(fp); + //AAS_Error("route cache dump area CRC incorrect\n"); + return qfalse; + } //end if + if (routecacheheader.clustercrc != + CRC_ProcessString((unsigned char *)aasworld->clusters, sizeof(aas_cluster_t) * aasworld->numclusters)) + { + botimport.FS_FCloseFile(fp); + //AAS_Error("route cache dump cluster CRC incorrect\n"); + return qfalse; + } //end if + if (routecacheheader.reachcrc != + CRC_ProcessString((unsigned char *)aasworld->reachability, sizeof(aas_reachability_t) * aasworld->reachabilitysize)) + { + botimport.FS_FCloseFile(fp); + //AAS_Error("route cache dump reachability CRC incorrect\n"); + return qfalse; + } //end if +#endif + //read all the portal cache + for (i = 0; i < routecacheheader.numportalcache; i++) + { + cache = AAS_ReadCache(fp); + cache->next = aasworld->portalcache[cache->areanum]; + cache->prev = NULL; + if (aasworld->portalcache[cache->areanum]) + { + aasworld->portalcache[cache->areanum]->prev = cache; + } + aasworld->portalcache[cache->areanum] = cache; + } //end for + //read all the cluster area cache + for (i = 0; i < routecacheheader.numareacache; i++) + { + cache = AAS_ReadCache(fp); + clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); + cache->next = aasworld->clusterareacache[cache->cluster][clusterareanum]; + cache->prev = NULL; + if (aasworld->clusterareacache[cache->cluster][clusterareanum]) + { + aasworld->clusterareacache[cache->cluster][clusterareanum]->prev = cache; + } + aasworld->clusterareacache[cache->cluster][clusterareanum] = cache; + } //end for + // read the visareas + aasworld->areavisibility = (byte **) GetClearedMemory(aasworld->numareas * sizeof(byte *)); + aasworld->decompressedvis = (byte *) GetClearedMemory(aasworld->numareas * sizeof(byte)); + for (i = 0; i < aasworld->numareas; i++) + { + botimport.FS_Read(&size, sizeof(size), fp); + if (size) + { + aasworld->areavisibility[i] = (byte *) GetMemory(size); + botimport.FS_Read(aasworld->areavisibility[i], size, fp); + } + } + // read the area waypoints + aasworld->areawaypoints = (vec3_t *) GetClearedMemory(aasworld->numareas * sizeof(vec3_t)); + botimport.FS_Read(aasworld->areawaypoints, aasworld->numareas * sizeof(vec3_t), fp); + // + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_ReadRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateVisibility(qboolean waypointsOnly); +void AAS_InitRouting(void) +{ + AAS_InitTravelFlagFromType(); + //initialize the routing update fields + AAS_InitRoutingUpdate(); + //create reversed reachability links used by the routing update algorithm + AAS_CreateReversedReachability(); + //initialize the cluster cache + AAS_InitClusterAreaCache(); + //initialize portal cache + AAS_InitPortalCache(); + //initialize the area travel times + AAS_CalculateAreaTravelTimes(); + //calculate the maximum travel times through portals + AAS_InitPortalMaxTravelTimes(); + // +#ifdef ROUTING_DEBUG + numareacacheupdates = 0; + numportalcacheupdates = 0; +#endif //ROUTING_DEBUG + // + routingcachesize = 0; + max_routingcachesize = 1024 * (int) LibVarValue("max_routingcache", DEFAULT_MAX_ROUTINGCACHESIZE); + max_frameroutingupdates = (int) LibVarGetValue("bot_frameroutingupdates"); + // + // enable this for quick testing of maps without enemies + if (LibVarGetValue("bot_norcd")) + { + // RF, create the waypoints for each area + AAS_CreateVisibility(qtrue); + } + else + { + // Ridah, load or create the routing cache + if (!AAS_ReadRouteCache()) + { + aasworld->initialized = qtrue; // Hack, so routing can compute traveltimes + AAS_CreateVisibility(qfalse); + // RF, removed, going back to dynamic routes + //AAS_CreateAllRoutingCache(); + aasworld->initialized = qfalse; + + AAS_WriteRouteCache(); // save it so we don't have to create it again + } + // done. + } +} //end of the function AAS_InitRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCaches(void) +{ + // free all the existing cluster area cache + AAS_FreeAllClusterAreaCache(); + // free all the existing portal cache + AAS_FreeAllPortalCache(); + // free all the existing area visibility data + AAS_FreeAreaVisibility(); + // free cached travel times within areas + if (aasworld->areatraveltimes) + { + AAS_RoutingFreeMemory(aasworld->areatraveltimes); + } + aasworld->areatraveltimes = NULL; + // free cached maximum travel time through cluster portals + if (aasworld->portalmaxtraveltimes) + { + AAS_RoutingFreeMemory(aasworld->portalmaxtraveltimes); + } + aasworld->portalmaxtraveltimes = NULL; + // free reversed reachability links + if (aasworld->reversedreachability) + { + AAS_RoutingFreeMemory(aasworld->reversedreachability); + } + aasworld->reversedreachability = NULL; + // free routing algorithm memory + if (aasworld->areaupdate) + { + AAS_RoutingFreeMemory(aasworld->areaupdate); + } + aasworld->areaupdate = NULL; + if (aasworld->portalupdate) + { + AAS_RoutingFreeMemory(aasworld->portalupdate); + } + aasworld->portalupdate = NULL; + // free area waypoints + if (aasworld->areawaypoints) + { + FreeMemory(aasworld->areawaypoints); + } + aasworld->areawaypoints = NULL; +} //end of the function AAS_FreeRoutingCaches +//=========================================================================== +// this function could be replaced by a bubble sort or for even faster +// routing by a B+ tree +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline void AAS_AddUpdateToList(aas_routingupdate_t **updateliststart, + aas_routingupdate_t **updatelistend, + aas_routingupdate_t *update) +{ + if (!update->inlist) + { + if (*updatelistend) + { + (*updatelistend)->next = update; + } + else + { + *updateliststart = update; + } + update->prev = *updatelistend; + update->next = NULL; + *updatelistend = update; + update->inlist = qtrue; + } //end if +} //end of the function AAS_AddUpdateToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaContentsTravelFlag(int areanum) +{ + int contents, tfl; + + contents = aasworld->areasettings[areanum].contents; + tfl = 0; + if (contents & AREACONTENTS_WATER) + { + return tfl |= TFL_WATER; + } + else if (contents & AREACONTENTS_SLIME) + { + return tfl |= TFL_SLIME; + } + else if (contents & AREACONTENTS_LAVA) + { + return tfl |= TFL_LAVA; + } + else + { + tfl |= TFL_AIR; + } + if (contents & AREACONTENTS_DONOTENTER_LARGE) + { + tfl |= TFL_DONOTENTER_LARGE; + } + if (contents & AREACONTENTS_DONOTENTER) + { + return tfl |= TFL_DONOTENTER; + } + return tfl; +} //end of the function AAS_AreaContentsTravelFlag +//=========================================================================== +// update the given routing cache +// +// Parameter: areacache : routing cache to update +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateAreaRoutingCache(aas_routingcache_t *areacache) +{ + int i, nextareanum, cluster, badtravelflags, clusterareanum, linknum; + int numreachabilityareas; + unsigned short int t, startareatraveltimes[128]; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + +#ifdef ROUTING_DEBUG + numareacacheupdates++; +#endif //ROUTING_DEBUG + //number of reachability areas within this cluster + numreachabilityareas = aasworld->clusters[areacache->cluster].numreachabilityareas; + // + //clear the routing update fields +// memset(aasworld->areaupdate, 0, aasworld->numareas * sizeof(aas_routingupdate_t)); + // + badtravelflags = ~areacache->travelflags; + // + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, areacache->areanum); + if (clusterareanum >= numreachabilityareas) + { + return; + } + // + memset(startareatraveltimes, 0, sizeof(startareatraveltimes)); + // + curupdate = &aasworld->areaupdate[clusterareanum]; + curupdate->areanum = areacache->areanum; + //VectorCopy(areacache->origin, curupdate->start); + curupdate->areatraveltimes = aasworld->areatraveltimes[areacache->areanum][0]; + curupdate->tmptraveltime = areacache->starttraveltime; + // + areacache->traveltimes[clusterareanum] = areacache->starttraveltime; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + revreach = &aasworld->reversedreachability[curupdate->areanum]; + // + for (i = 0, revlink = revreach->first; revlink; revlink = revlink->next, i++) + { + linknum = revlink->linknum; + reach = &aasworld->reachability[linknum]; + //if there is used an undesired travel type + if (aasworld->travelflagfortype[reach->traveltype] & badtravelflags) + { + continue; + } + //if not allowed to enter the next area + if (aasworld->areasettings[reach->areanum].areaflags & AREA_DISABLED) + { + continue; + } + //if the next area has a not allowed travel flag + if (AAS_AreaContentsTravelFlag(reach->areanum) & badtravelflags) + { + continue; + } + //number of the area the reversed reachability leads to + nextareanum = revlink->areanum; + //get the cluster number of the area + cluster = aasworld->areasettings[nextareanum].cluster; + //don't leave the cluster + if (cluster > 0 && cluster != areacache->cluster) + { + continue; + } + //get the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, nextareanum); + if (clusterareanum >= numreachabilityareas) + { + continue; + } + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + //AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->end) + + curupdate->areatraveltimes[i] + + reach->traveltime; + //if trying to avoid this area + if (aasworld->areasettings[reach->areanum].areaflags & AREA_AVOID) + { + t += 1000; + } + else if ((aasworld->areasettings[reach->areanum].areaflags & AREA_AVOID_AXIS) && (areacache->travelflags & TFL_TEAM_AXIS)) + { + t += 200; // + (curupdate->areatraveltimes[i] + reach->traveltime) * 30; + } + else if ((aasworld->areasettings[reach->areanum].areaflags & AREA_AVOID_ALLIES) && (areacache->travelflags & TFL_TEAM_ALLIES)) + { + t += 200; // + (curupdate->areatraveltimes[i] + reach->traveltime) * 30; + } + // + aasworld->frameroutingupdates++; + // + if (aasworld->areatraveltimes[nextareanum] && + (!areacache->traveltimes[clusterareanum] || + areacache->traveltimes[clusterareanum] > t)) + { + areacache->traveltimes[clusterareanum] = t; + areacache->reachabilities[clusterareanum] = linknum - aasworld->areasettings[nextareanum].firstreachablearea; + nextupdate = &aasworld->areaupdate[clusterareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //VectorCopy(reach->start, nextupdate->start); + nextupdate->areatraveltimes = aasworld->areatraveltimes[nextareanum][linknum - + aasworld->areasettings[nextareanum].firstreachablearea]; + if (!nextupdate->inlist) + { + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdateAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetAreaRoutingCache(int clusternum, int areanum, int travelflags, qboolean forceUpdate) +{ + int clusterareanum; + aas_routingcache_t *cache, *clustercache; + + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //pointer to the cache for the area in the cluster + clustercache = aasworld->clusterareacache[clusternum][clusterareanum]; + + // RF, remove team-specific flags which don't exist in this cluster + travelflags &= ~TFL_TEAM_FLAGS | aasworld->clusterTeamTravelFlags[clusternum]; + + //find the cache without undesired travel flags + for (cache = clustercache; cache; cache = cache->next) + { + //if there aren't used any undesired travel types for the cache + if (cache->travelflags == travelflags) + { + break; + } + } //end for + + //if there was no cache + if (!cache) + { + //NOTE: the number of routing updates is limited per frame + if (!forceUpdate && (aasworld->frameroutingupdates > max_frameroutingupdates)) + { + return NULL; + } //end if + cache = AAS_AllocRoutingCache(aasworld->clusters[clusternum].numreachabilityareas); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld->areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + cache->prev = NULL; + cache->next = clustercache; + if (clustercache) + { + clustercache->prev = cache; + } + aasworld->clusterareacache[clusternum][clusterareanum] = cache; + AAS_UpdateAreaRoutingCache(cache); + } //end if + //the cache has been accessed + cache->time = AAS_RoutingTime(); + return cache; +} //end of the function AAS_GetAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdatePortalRoutingCache(aas_routingcache_t *portalcache) +{ + int i, portalnum, clusterareanum; //, clusternum; + unsigned short int t; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *cache; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + +#ifdef ROUTING_DEBUG + numportalcacheupdates++; +#endif //ROUTING_DEBUG + //clear the routing update fields +// memset(aasworld->portalupdate, 0, (aasworld->numportals+1) * sizeof(aas_routingupdate_t)); + // + curupdate = &aasworld->portalupdate[aasworld->numportals]; + curupdate->cluster = portalcache->cluster; + curupdate->areanum = portalcache->areanum; + curupdate->tmptraveltime = portalcache->starttraveltime; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + //remove the current update from the list + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + //current update is removed from the list + curupdate->inlist = qfalse; + // + cluster = &aasworld->clusters[curupdate->cluster]; + // + cache = AAS_GetAreaRoutingCache(curupdate->cluster, + curupdate->areanum, portalcache->travelflags, qtrue); + //take all portals of the cluster + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld->portalindex[cluster->firstportal + i]; + portal = &aasworld->portals[portalnum]; + // + clusterareanum = AAS_ClusterAreaNum(curupdate->cluster, portal->areanum); + if (clusterareanum >= cluster->numreachabilityareas) + { + continue; + } + // + t = cache->traveltimes[clusterareanum]; + if (!t) + { + continue; + } + t += curupdate->tmptraveltime; + // + if (!portalcache->traveltimes[portalnum] || + portalcache->traveltimes[portalnum] > t) + { + portalcache->traveltimes[portalnum] = t; + portalcache->reachabilities[portalnum] = cache->reachabilities[clusterareanum]; + nextupdate = &aasworld->portalupdate[portalnum]; + if (portal->frontcluster == curupdate->cluster) + { + nextupdate->cluster = portal->backcluster; + } //end if + else + { + nextupdate->cluster = portal->frontcluster; + } //end else + nextupdate->areanum = portal->areanum; + //add travel time through actual portal area for the next update + nextupdate->tmptraveltime = t + aasworld->portalmaxtraveltimes[portalnum]; + if (!nextupdate->inlist) + { + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdatePortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetPortalRoutingCache(int clusternum, int areanum, int travelflags) +{ + aas_routingcache_t *cache; + + //find the cached portal routing if existing + for (cache = aasworld->portalcache[areanum]; cache; cache = cache->next) + { + if (cache->travelflags == travelflags) + { + break; + } + } //end for + //if the portal routing isn't cached + if (!cache) + { + cache = AAS_AllocRoutingCache(aasworld->numportals); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld->areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + //add the cache to the cache list + cache->prev = NULL; + cache->next = aasworld->portalcache[areanum]; + if (aasworld->portalcache[areanum]) + { + aasworld->portalcache[areanum]->prev = cache; + } + aasworld->portalcache[areanum] = cache; + //update the cache + AAS_UpdatePortalRoutingCache(cache); + } //end if + //the cache has been accessed + cache->time = AAS_RoutingTime(); + return cache; +} //end of the function AAS_GetPortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum) +{ + int clusternum, goalclusternum, portalnum, i, clusterareanum, bestreachnum; + unsigned short int t, besttime; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *areacache, *portalcache; + aas_reachability_t *reach; + aas_portalindex_t *pPortalnum; + + if (!aasworld->initialized) + { + return qfalse; + } + + if (areanum == goalareanum) + { + *traveltime = 1; + *reachnum = 0; + return qtrue; + } //end if + // + if (areanum <= 0 || areanum >= aasworld->numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: areanum %d out of range\n", areanum); + } //end if + return qfalse; + } //end if + if (goalareanum <= 0 || goalareanum >= aasworld->numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: goalareanum %d out of range\n", goalareanum); + } //end if + return qfalse; + } //end if + + //make sure the routing cache doesn't grow to large + while (routingcachesize > max_routingcachesize) + { + if (!AAS_FreeOldestCache()) + { + break; + } + } + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goalareanum)) + { + travelflags |= TFL_DONOTENTER; + } //end if + if (AAS_AreaDoNotEnterLarge(areanum) || AAS_AreaDoNotEnterLarge(goalareanum)) + { + travelflags |= TFL_DONOTENTER_LARGE; + } //end if + // + clusternum = aasworld->areasettings[areanum].cluster; + goalclusternum = aasworld->areasettings[goalareanum].cluster; + + //check if the area is a portal of the goal area cluster + if (clusternum < 0 && goalclusternum > 0) + { + portal = &aasworld->portals[-clusternum]; + if (portal->frontcluster == goalclusternum || portal->backcluster == goalclusternum) + { + clusternum = goalclusternum; + } + } + else if (clusternum > 0 && goalclusternum < 0) //check if the goalarea is a portal of the area cluster + { + portal = &aasworld->portals[-goalclusternum]; + if (portal->frontcluster == clusternum || portal->backcluster == clusternum) + { + goalclusternum = clusternum; + } + } + + //if both areas are in the same cluster + //NOTE: there might be a shorter route via another cluster!!! but we don't care + if (clusternum > 0 && goalclusternum > 0 && clusternum == goalclusternum) + { + areacache = AAS_GetAreaRoutingCache(clusternum, goalareanum, travelflags, qfalse); + // RF, note that the routing cache might be NULL now since we are restricting + // the updates per frame, hopefully rejected cache's will be requested again + // when things have settled down + if (!areacache) + { + return qfalse; + } + //the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //the cluster the area is in + cluster = &aasworld->clusters[clusternum]; + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) + { + return qfalse; + } + //if it is possible to travel to the goal area through this cluster + if (areacache->traveltimes[clusterareanum] != 0) + { + *reachnum = aasworld->areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + // + if (!origin) + { + *traveltime = areacache->traveltimes[clusterareanum]; + return qtrue; + } + // + reach = &aasworld->reachability[*reachnum]; + *traveltime = areacache->traveltimes[clusterareanum] + + AAS_AreaTravelTime(areanum, origin, reach->start); + return qtrue; + } //end if + } //end if + // + clusternum = aasworld->areasettings[areanum].cluster; + goalclusternum = aasworld->areasettings[goalareanum].cluster; + //if the goal area is a portal + if (goalclusternum < 0) + { + //just assume the goal area is part of the front cluster + portal = &aasworld->portals[-goalclusternum]; + goalclusternum = portal->frontcluster; + } //end if + //get the portal routing cache + portalcache = AAS_GetPortalRoutingCache(goalclusternum, goalareanum, travelflags); + //if the area is a cluster portal, read directly from the portal cache + if (clusternum < 0) + { + *traveltime = portalcache->traveltimes[-clusternum]; + *reachnum = aasworld->areasettings[areanum].firstreachablearea + + portalcache->reachabilities[-clusternum]; + return qtrue; + } + // + besttime = 0; + bestreachnum = -1; + //the cluster the area is in + cluster = &aasworld->clusters[clusternum]; + //current area inside the current cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) + { + return qfalse; + } + // + pPortalnum = aasworld->portalindex + cluster->firstportal; + //find the portal of the area cluster leading towards the goal area + for (i = 0; i < cluster->numportals; i++, pPortalnum++) + { + portalnum = *pPortalnum; + //if the goal area isn't reachable from the portal + if (!portalcache->traveltimes[portalnum]) + { + continue; + } + // + portal = aasworld->portals + portalnum; + // if the area in disabled + if (aasworld->areasettings[portal->areanum].areaflags & AREA_DISABLED) + { + continue; + } + // if there is no reachability out of the area + if (!aasworld->areasettings[portal->areanum].numreachableareas) + { + continue; + } + //get the cache of the portal area + areacache = AAS_GetAreaRoutingCache(clusternum, portal->areanum, travelflags, qfalse); + // RF, this may be NULL if we were unable to calculate the cache this frame + if (!areacache) + { + return qfalse; + } + //if the portal is NOT reachable from this area + if (!areacache->traveltimes[clusterareanum]) + { + continue; + } + //total travel time is the travel time the portal area is from + //the goal area plus the travel time towards the portal area + t = portalcache->traveltimes[portalnum] + areacache->traveltimes[clusterareanum]; + //FIXME: add the exact travel time through the actual portal area + //NOTE: for now we just add the largest travel time through the area portal + // because we can't directly calculate the exact travel time + // to be more specific we don't know which reachability is used to travel + // into the portal area when coming from the current area + t += aasworld->portalmaxtraveltimes[portalnum]; + // + // Ridah, needs to be up here + *reachnum = aasworld->areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + +//botimport.Print(PRT_MESSAGE, "portal reachability: %i\n", (int)areacache->reachabilities[clusterareanum] ); + + if (origin) + { + reach = aasworld->reachability + *reachnum; + t += AAS_AreaTravelTime(areanum, origin, reach->start); + } //end if + //if the time is better than the one already found + if (!besttime || t < besttime) + { + bestreachnum = *reachnum; + besttime = t; + } //end if + } //end for + // Ridah, check a route was found + if (bestreachnum < 0) + { + return qfalse; + } + *reachnum = bestreachnum; + *traveltime = besttime; + return qtrue; +} //end of the function AAS_AreaRouteToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return traveltime; + } + return 0; +} //end of the function AAS_AreaTravelTimeToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTravelTimeToGoalAreaCheckLoop(int areanum, vec3_t origin, int goalareanum, int travelflags, int loopareanum) +{ + int traveltime, reachnum; + aas_reachability_t *reach; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + reach = &aasworld->reachability[reachnum]; + if (loopareanum && reach->areanum == loopareanum) + { + return 0; // going here will cause a looped route + } + return traveltime; + } + return 0; +} //end of the function AAS_AreaTravelTimeToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachabilityToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return reachnum; + } + return 0; +} //end of the function AAS_AreaReachabilityToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach) +{ + if (!aasworld->initialized) + { + memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + if (num < 0 || num >= aasworld->reachabilitysize) + { + memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + memcpy(reach, &aasworld->reachability[num], sizeof(aas_reachability_t));; +} //end of the function AAS_ReachabilityFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextAreaReachability(int areanum, int reachnum) +{ + aas_areasettings_t *settings; + + if (!aasworld->initialized) + { + return 0; + } + + if (areanum <= 0 || areanum >= aasworld->numareas) + { + botimport.Print(PRT_ERROR, "AAS_NextAreaReachability: areanum %d out of range\n", areanum); + return 0; + } //end if + + settings = &aasworld->areasettings[areanum]; + if (!reachnum) + { + return settings->firstreachablearea; + } //end if + if (reachnum < settings->firstreachablearea) + { + botimport.Print(PRT_FATAL, "AAS_NextAreaReachability: reachnum < settings->firstreachableara"); + return 0; + } //end if + reachnum++; + if (reachnum >= settings->firstreachablearea + settings->numreachableareas) + { + return 0; + } //end if + return reachnum; +} //end of the function AAS_NextAreaReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextModelReachability(int num, int modelnum) +{ + int i; + + if (num <= 0) + { + num = 1; + } + else if (num >= aasworld->reachabilitysize) + { + return 0; + } + else + { + num++; + } + // + for (i = num; i < aasworld->reachabilitysize; i++) + { + if (aasworld->reachability[i].traveltype == TRAVEL_ELEVATOR) + { + if (aasworld->reachability[i].facenum == modelnum) + { + return i; + } + } //end if + else if (aasworld->reachability[i].traveltype == TRAVEL_FUNCBOB) + { + if ((aasworld->reachability[i].facenum & 0x0000FFFF) == modelnum) + { + return i; + } + } //end if + } //end for + return 0; +} //end of the function AAS_NextModelReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin) +{ + int i, n, t; + vec3_t start, end; + aas_trace_t trace; + + //if the area has no reachabilities + if (!AAS_AreaReachability(areanum)) + { + return qfalse; + } + // + n = aasworld->numareas * random(); + for (i = 0; i < aasworld->numareas; i++) + { + if (n <= 0) + { + n = 1; + } + if (n >= aasworld->numareas) + { + n = 1; + } + if (AAS_AreaReachability(n)) + { + t = AAS_AreaTravelTimeToGoalArea(areanum, aasworld->areas[areanum].center, n, travelflags); + //if the goal is reachable + if (t > 0) + { + if (AAS_AreaSwim(n)) + { + *goalareanum = n; + VectorCopy(aasworld->areas[n].center, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + VectorCopy(aasworld->areas[n].center, start); + if (!AAS_PointAreaNum(start)) + { + Log_Write("area %d center %f %f %f in solid?", n, + start[0], start[1], start[2]); + } + VectorCopy(start, end); + end[2] -= 300; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid && AAS_PointAreaNum(trace.endpos) == n) + { + if (AAS_AreaGroundFaceArea(n) > 300) + { + *goalareanum = n; + VectorCopy(trace.endpos, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + } //end if + } //end if + } //end if + n++; + } //end for + return qfalse; +} //end of the function AAS_RandomGoalArea +//=========================================================================== +// run-length compression on zeros +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CompressVis(byte *vis, int numareas, byte *dest) +{ + int j; + int rep; + //int visrow; + byte *dest_p; + byte check; + + // + dest_p = dest; + //visrow = (numareas + 7)>>3; + + for (j = 0 ; j < numareas /*visrow*/ ; j++) + { + *dest_p++ = vis[j]; + check = vis[j]; + //if (vis[j]) + // continue; + + rep = 1; + for (j++; j < numareas /*visrow*/ ; j++) + if (vis[j] != check || rep == 255) + { + break; + } + else + { + rep++; + } + *dest_p++ = rep; + j--; + } + return dest_p - dest; +} //end of the function AAS_CompressVis +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DecompressVis(byte *in, int numareas, byte *decompressed) +{ + byte c; + byte *out; + //int row; + byte *end; + + // initialize the vis data, only set those that are visible + memset(decompressed, 0, numareas); + + //row = (numareas+7)>>3; + out = decompressed; + end = ( byte * )((int)decompressed + numareas); + + do + { + /* + if (*in) + { + *out++ = *in++; + continue; + } + */ + + c = in[1]; + if (!c) + { + AAS_Error("DecompressVis: 0 repeat"); + } + if (*in) // we need to set these bits + { + memset(out, 1, c); + } + in += 2; + /* + while (c) + { + *out++ = 0; + c--; + } + */ + out += c; + } + while (out < end); +} //end of the function AAS_DecompressVis +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaVisible(int srcarea, int destarea) +{ + if (!aasworld->areavisibility) + { +// botimport.Print(PRT_MESSAGE, "AAS_AreaVisible: no vis data available, returning qtrue\n" ); + return qtrue; + } + if (srcarea != aasworld->decompressedvisarea) + { + if (!aasworld->areavisibility[srcarea]) + { + return qfalse; + } + AAS_DecompressVis(aasworld->areavisibility[srcarea], + aasworld->numareas, aasworld->decompressedvis); + aasworld->decompressedvisarea = srcarea; + } + return aasworld->decompressedvis[destarea]; +} //end of the function AAS_AreaVisible +//=========================================================================== +// just center to center visibility checking... +// FIXME: implement a correct full vis +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateVisibility(qboolean waypointsOnly) +{ + int i, j, size, totalsize; + vec3_t endpos, mins, maxs; + bsp_trace_t trace; + byte *buf; + byte *validareas = NULL; + int numvalid = 0; + byte *areaTable = NULL; + int numAreas, numAreaBits; + + numAreas = aasworld->numareas; + numAreaBits = ((numAreas + 8) >> 3); + + if (!waypointsOnly) + { + validareas = (byte *) GetClearedMemory(numAreas * sizeof(byte)); + } + + aasworld->areawaypoints = (vec3_t *) GetClearedMemory(numAreas * sizeof(vec3_t)); + totalsize = numAreas * sizeof(byte *); + + for (i = 1; i < numAreas; i++) + { + if (!AAS_AreaReachability(i)) + { + continue; + } + + // find the waypoint + VectorCopy(aasworld->areas[i].center, endpos); + endpos[2] -= 256; + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + trace = AAS_Trace(aasworld->areas[i].center, mins, maxs, endpos, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP); + if (trace.startsolid && trace.ent < ENTITYNUM_WORLD) + { + trace = AAS_Trace(aasworld->areas[i].center, mins, maxs, endpos, trace.ent, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP); + } + if (!trace.startsolid && trace.fraction < 1 && AAS_PointAreaNum(trace.endpos) == i) + { + VectorCopy(trace.endpos, aasworld->areawaypoints[i]); + + if (!waypointsOnly) + { + validareas[i] = 1; + } + + numvalid++; + } + else + { + VectorClear(aasworld->areawaypoints[i]); + } + } + + if (waypointsOnly) + { + return; + } + + aasworld->areavisibility = (byte **) GetClearedMemory(numAreas * sizeof(byte *)); + + aasworld->decompressedvis = (byte *) GetClearedMemory(numAreas * sizeof(byte)); + + areaTable = (byte *) GetClearedMemory(numAreas * numAreaBits * sizeof(byte)); + + buf = (byte *) GetClearedMemory(numAreas * 2 * sizeof(byte)); // in case it ends up bigger than the decompressedvis, which is rare but possible + + for (i = 1; i < numAreas; i++) + { + if (!validareas[i]) + { + continue; + } + + for (j = 1; j < numAreas; j++) + { + aasworld->decompressedvis[j] = 0; + if (i == j) + { + aasworld->decompressedvis[j] = 1; + if (areaTable) + { + areaTable[(i * numAreaBits) + (j >> 3)] |= (1 << (j & 7)); + } + continue; + } + + if (!validareas[j]) + { + continue; + } + + // if we have already checked this combination, copy the result + if (areaTable && (i > j)) + { + // use the reverse result stored in the table + if (areaTable[(j * numAreaBits) + (i >> 3)] & (1 << (i & 7))) + { + aasworld->decompressedvis[j] = 1; + } + // done, move to the next area + continue; + } + + // RF, check PVS first, since it's much faster + if (!AAS_inPVS(aasworld->areawaypoints[i], aasworld->areawaypoints[j])) + { + continue; + } + + trace = AAS_Trace(aasworld->areawaypoints[i], NULL, NULL, aasworld->areawaypoints[j], -1, CONTENTS_SOLID); + if (trace.startsolid && trace.ent < ENTITYNUM_WORLD) + { + trace = AAS_Trace(aasworld->areas[i].center, mins, maxs, endpos, trace.ent, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP); + } + + if (trace.fraction >= 1) + { + if (areaTable) + { + areaTable[(i * numAreaBits) + (j >> 3)] |= (1 << (j & 7)); + } + aasworld->decompressedvis[j] = 1; + } + } + + size = AAS_CompressVis(aasworld->decompressedvis, numAreas, buf); + aasworld->areavisibility[i] = (byte *) GetMemory(size); + memcpy(aasworld->areavisibility[i], buf, size); + totalsize += size; + } + + if (areaTable) + { + FreeMemory(areaTable); + } + + botimport.Print(PRT_MESSAGE, "AAS_CreateVisibility: compressed vis size = %i\n", totalsize); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistance(vec3_t v1, vec3_t v2); +extern void ProjectPointOntoVector(vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj) ; +int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags, float maxdist, vec3_t distpos) +{ + int i, j, nextareanum, badtravelflags, numreach, bestarea; + unsigned short int t, besttraveltime, enemytraveltime; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + float dist1, dist2; + float enemytraveldist; + vec3_t enemyVec; + qboolean startVisible; + vec3_t v1, v2, p; + #define MAX_HIDEAREA_LOOPS 3000 + static float lastTime; + static int loopCount; + // + if (!aasworld->areavisibility) + { + return 0; + } + // + if (srcnum < 0) // hack to force run this call + { + srcnum = -srcnum - 1; + lastTime = 0; + } + // don't run this more than once per frame + if (lastTime == AAS_Time() && loopCount >= MAX_HIDEAREA_LOOPS) + { + return 0; + } + if (lastTime != AAS_Time()) + { + loopCount = 0; + } + lastTime = AAS_Time(); + // + if (!aasworld->hidetraveltimes) + { + aasworld->hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld->numareas * sizeof(unsigned short int)); + } + else + { + memset(aasworld->hidetraveltimes, 0, aasworld->numareas * sizeof(unsigned short int)); + } //end else + // + if (!aasworld->visCache) + { + aasworld->visCache = (byte *) GetClearedMemory(aasworld->numareas * sizeof(byte)); + } + else + { + memset(aasworld->visCache, 0, aasworld->numareas * sizeof(byte)); + } //end else + besttraveltime = 0; + bestarea = 0; + if (enemyareanum) + { + enemytraveltime = AAS_AreaTravelTimeToGoalArea(areanum, origin, enemyareanum, travelflags); + } + VectorSubtract(enemyorigin, origin, enemyVec); + enemytraveldist = VectorNormalize(enemyVec); + startVisible = botimport.BotVisibleFromPos(enemyorigin, enemynum, origin, srcnum, qfalse); + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld->areaupdate[areanum]; + curupdate->areanum = areanum; + VectorCopy(origin, curupdate->start); + // GORDON: TEMP: FIXME: just avoiding a crash for the moment + if (areanum == 0) + { + return 0; + } + curupdate->areatraveltimes = aasworld->areatraveltimes[areanum][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld->areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld->reachability[aasworld->areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (aasworld->travelflagfortype[reach->traveltype] & badtravelflags) + { + continue; + } + // + if (AAS_AreaContentsTravelFlag(reach->areanum) & badtravelflags) + { + continue; + } + // dont pass through ladder areas + if (aasworld->areasettings[reach->areanum].areaflags & AREA_LADDER) + { + continue; + } + // + if (aasworld->areasettings[reach->areanum].areaflags & AREA_DISABLED) + { + continue; + } + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if this moves us into the enemies area, skip it + if (nextareanum == enemyareanum) + { + continue; + } + // if this moves us outside maxdist + if (distpos && (VectorDistance(reach->end, distpos) > maxdist)) + { + continue; + } + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + + reach->traveltime; + // inc the loopCount, we are starting to use a bit of cpu time + loopCount++; + // if this isn't the fastest route to this area, ignore + if (aasworld->hidetraveltimes[nextareanum] && aasworld->hidetraveltimes[nextareanum] < t) + { + continue; + } + aasworld->hidetraveltimes[nextareanum] = t; + // if the bestarea is this area, then it must be a longer route, so ignore it + if (bestarea == nextareanum) + { + bestarea = 0; + besttraveltime = 0; + } + // do this test now, so we can reject the route if it starts out too long + if (besttraveltime && t >= besttraveltime) + { + continue; + } + // + //avoid going near the enemy + ProjectPointOntoVector(enemyorigin, curupdate->start, reach->end, p); + for (j = 0; j < 3; j++) + { + if ((p[j] > curupdate->start[j] + 0.1 && p[j] > reach->end[j] + 0.1) || + (p[j] < curupdate->start[j] - 0.1 && p[j] < reach->end[j] - 0.1)) + { + break; + } + } + if (j < 3) + { + VectorSubtract(enemyorigin, reach->end, v2); + } //end if + else + { + VectorSubtract(enemyorigin, p, v2); + } //end else + dist2 = VectorLength(v2); + //never go through the enemy + if (enemytraveldist > 32 && dist2 < enemytraveldist && dist2 < 256) + { + continue; + } + // + VectorSubtract(reach->end, origin, v2); + if (enemytraveldist > 32 && DotProduct(v2, enemyVec) > enemytraveldist / 2) + { + continue; + } + // + VectorSubtract(enemyorigin, curupdate->start, v1); + dist1 = VectorLength(v1); + // + if (enemytraveldist > 32 && dist2 < dist1) + { + t += (dist1 - dist2) * 10; + // test it again after modifying it + if (besttraveltime && t >= besttraveltime) + { + continue; + } + } + // make sure the hide area doesn't have anyone else in it + if (AAS_IsEntityInArea(srcnum, -1, nextareanum)) + { + t += 1000; // avoid this path/area + //continue; + } + // + // if we weren't visible when starting, make sure we don't move into their view + if (enemyareanum && !startVisible && AAS_AreaVisible(enemyareanum, nextareanum)) + { + continue; + //t += 1000; + } + // + if (!besttraveltime || besttraveltime > t) + { + // + // if this area doesn't have a vis list, ignore it + if (aasworld->areavisibility[nextareanum]) + { + //if the nextarea is not visible from the enemy area + if (!AAS_AreaVisible(enemyareanum, nextareanum)) // now last of all, check that this area is a safe hiding spot + { + if ((aasworld->visCache[nextareanum] == 2) || + (!aasworld->visCache[nextareanum] && !botimport.BotVisibleFromPos(enemyorigin, enemynum, aasworld->areawaypoints[nextareanum], srcnum, qfalse))) + { + aasworld->visCache[nextareanum] = 2; + besttraveltime = t; + bestarea = nextareanum; + } + else + { + aasworld->visCache[nextareanum] = 1; + } + } //end if + } + // + // getting down to here is bad for cpu usage + if (loopCount++ > MAX_HIDEAREA_LOOPS) + { + //botimport.Print(PRT_MESSAGE, "AAS_NearestHideArea: exceeded max loops, aborting\n" ); + continue; + } + // + // otherwise, add this to the list so we check is reachables + // disabled, this should only store the raw traveltime, not the adjusted time + //aasworld->hidetraveltimes[nextareanum] = t; + nextupdate = &aasworld->areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while + //botimport.Print(PRT_MESSAGE, "AAS_NearestHideArea: hidearea: %i, %i loops\n", bestarea, count ); + return bestarea; +} //end of the function AAS_NearestHideArea + +int BotFuzzyPointReachabilityArea(vec3_t origin); +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindAttackSpotWithinRange(int srcnum, int rangenum, int enemynum, float rangedist, int travelflags, float *outpos) +{ + int i, nextareanum, badtravelflags, numreach, bestarea; + unsigned short int t, besttraveltime, enemytraveltime; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + vec3_t srcorg, rangeorg, enemyorg; + int srcarea, rangearea, enemyarea; + unsigned short int srctraveltime; + int count = 0; + #define MAX_ATTACKAREA_LOOPS 200 + static float lastTime; + // + // RF, currently doesn't work with multiple AAS worlds, so only enable for the default world + //if (aasworld != aasworlds) return 0; + // + // don't run this more than once per frame + if (lastTime == AAS_Time()) + { + return 0; + } + lastTime = AAS_Time(); + // + if (!aasworld->hidetraveltimes) + { + aasworld->hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld->numareas * sizeof(unsigned short int)); + } + else + { + memset(aasworld->hidetraveltimes, 0, aasworld->numareas * sizeof(unsigned short int)); + } //end else + // + if (!aasworld->visCache) + { + aasworld->visCache = (byte *) GetClearedMemory(aasworld->numareas * sizeof(byte)); + } + else + { + memset(aasworld->visCache, 0, aasworld->numareas * sizeof(byte)); + } //end else + // + AAS_EntityOrigin(srcnum, srcorg); + AAS_EntityOrigin(rangenum, rangeorg); + AAS_EntityOrigin(enemynum, enemyorg); + // + srcarea = BotFuzzyPointReachabilityArea(srcorg); + rangearea = BotFuzzyPointReachabilityArea(rangeorg); + enemyarea = BotFuzzyPointReachabilityArea(enemyorg); + // + besttraveltime = 0; + bestarea = 0; + enemytraveltime = AAS_AreaTravelTimeToGoalArea(srcarea, srcorg, enemyarea, travelflags); + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld->areaupdate[rangearea]; + curupdate->areanum = rangearea; + VectorCopy(rangeorg, curupdate->start); + curupdate->areatraveltimes = aasworld->areatraveltimes[srcarea][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld->areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld->reachability[aasworld->areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (aasworld->travelflagfortype[reach->traveltype] & badtravelflags) + { + continue; + } + // + if (AAS_AreaContentsTravelFlag(reach->areanum) & badtravelflags) + { + continue; + } + // dont pass through ladder areas + if (aasworld->areasettings[reach->areanum].areaflags & AREA_LADDER) + { + continue; + } + // + if (aasworld->areasettings[reach->areanum].areaflags & AREA_DISABLED) + { + continue; + } + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if this moves us into the enemies area, skip it + if (nextareanum == enemyarea) + { + continue; + } + // if we've already been to this area + if (aasworld->hidetraveltimes[nextareanum]) + { + continue; + } + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + if (count++ > MAX_ATTACKAREA_LOOPS) + { + //botimport.Print(PRT_MESSAGE, "AAS_FindAttackSpotWithinRange: exceeded max loops, aborting\n" ); + if (bestarea) + { + VectorCopy(aasworld->areawaypoints[bestarea], outpos); + } + return bestarea; + } + t = curupdate->tmptraveltime + + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + + reach->traveltime; + // + // if it's too far from rangenum, ignore + if (Distance(rangeorg, aasworld->areawaypoints[nextareanum]) > rangedist) + { + continue; + } + // + // find the traveltime from srcnum + srctraveltime = AAS_AreaTravelTimeToGoalArea(srcarea, srcorg, nextareanum, travelflags); + // do this test now, so we can reject the route if it starts out too long + if (besttraveltime && srctraveltime >= besttraveltime) + { + continue; + } + // + // if this area doesn't have a vis list, ignore it + if (aasworld->areavisibility[nextareanum]) + { + //if the nextarea can see the enemy area + if (AAS_AreaVisible(enemyarea, nextareanum)) // now last of all, check that this area is a good attacking spot + { + if ((aasworld->visCache[nextareanum] == 2) || + (!aasworld->visCache[nextareanum] && + (count += 10) && // we are about to use lots of CPU time + botimport.BotCheckAttackAtPos(srcnum, enemynum, aasworld->areawaypoints[nextareanum], qfalse, qfalse))) + { + aasworld->visCache[nextareanum] = 2; + besttraveltime = srctraveltime; + bestarea = nextareanum; + } + else + { + aasworld->visCache[nextareanum] = 1; + } + } //end if + } + aasworld->hidetraveltimes[nextareanum] = t; + nextupdate = &aasworld->areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end for + } //end while +//botimport.Print(PRT_MESSAGE, "AAS_NearestHideArea: hidearea: %i, %i loops\n", bestarea, count ); + if (bestarea) + { + VectorCopy(aasworld->areawaypoints[bestarea], outpos); + } + return bestarea; +} //end of the function AAS_NearestHideArea + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetRouteFirstVisPos(vec3_t srcpos, vec3_t destpos, int travelflags, vec3_t retpos) +{ + int srcarea, destarea, travarea; + vec3_t travpos; + int ftraveltime, freachnum, lasttraveltime; + aas_reachability_t reach; + int loops = 0; +#define MAX_GETROUTE_VISPOS_LOOPS 200 + // + // SRCPOS: enemy + // DESTPOS: self + // RETPOS: first area that is visible from destpos, in route from srcpos to destpos + srcarea = BotFuzzyPointReachabilityArea(srcpos); + if (!srcarea) + { + return qfalse; + } + destarea = BotFuzzyPointReachabilityArea(destpos); + if (!destarea) + { + return qfalse; + } + if (destarea == srcarea) + { + VectorCopy(srcpos, retpos); + return qtrue; + } + // + //if the srcarea can see the destarea + if (AAS_AreaVisible(srcarea, destarea)) + { + VectorCopy(srcpos, retpos); + return qtrue; + } + // if this area doesn't have a vis list, ignore it + if (!aasworld->areavisibility[destarea]) + { + return qfalse; + } + // + travarea = srcarea; + VectorCopy(srcpos, travpos); + lasttraveltime = -1; + while ((loops++ < MAX_GETROUTE_VISPOS_LOOPS) && AAS_AreaRouteToGoalArea(travarea, travpos, destarea, travelflags, &ftraveltime, &freachnum)) + { + if (lasttraveltime >= 0 && ftraveltime >= lasttraveltime) + { + return qfalse; // we may be in a loop + } + lasttraveltime = ftraveltime; + // + AAS_ReachabilityFromNum(freachnum, &reach); + if (reach.areanum == destarea) + { + VectorCopy(travpos, retpos); + return qtrue; + } + //if the reach area can see the destarea + if (AAS_AreaVisible(reach.areanum, destarea)) + { + VectorCopy(reach.end, retpos); + return qtrue; + } + // + travarea = reach.areanum; + VectorCopy(reach.end, travpos); + } + // + // unsuccessful + return qfalse; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ListAreasInRange(vec3_t srcpos, int srcarea, float range, int travelflags, vec3_t *outareas, int maxareas) +{ + int i, nextareanum, badtravelflags, numreach; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + int count = 0; + // + if (!aasworld->hidetraveltimes) + { + aasworld->hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld->numareas * sizeof(unsigned short int)); + } + else + { + memset(aasworld->hidetraveltimes, 0, aasworld->numareas * sizeof(unsigned short int)); + } //end else + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld->areaupdate[srcarea]; + curupdate->areanum = srcarea; + VectorCopy(srcpos, curupdate->start); + // GORDON: TEMP: FIXME: temp to stop crash + if (srcarea == 0) + { + return 0; + } + curupdate->areatraveltimes = aasworld->areatraveltimes[srcarea][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld->areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld->reachability[aasworld->areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (aasworld->travelflagfortype[reach->traveltype] & badtravelflags) + { + continue; + } + // + if (AAS_AreaContentsTravelFlag(reach->areanum) & badtravelflags) + { + continue; + } + // dont pass through ladder areas + //if (aasworld->areasettings[reach->areanum].areaflags & AREA_LADDER) continue; + // + //if (aasworld->areasettings[reach->areanum].areaflags & AREA_DISABLED) continue; + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if we've already been to this area + if (aasworld->hidetraveltimes[nextareanum]) + { + continue; + } + aasworld->hidetraveltimes[nextareanum] = 1; + // if it's too far from srcpos, ignore + if (Distance(srcpos, aasworld->areawaypoints[nextareanum]) > range) + { + continue; + } + // + // if visible from srcarea + if (!(aasworld->areasettings[reach->areanum].areaflags & AREA_LADDER) && + !(aasworld->areasettings[reach->areanum].areaflags & AREA_DISABLED) && + (AAS_AreaVisible(srcarea, nextareanum))) + { + VectorCopy(aasworld->areawaypoints[nextareanum], outareas[count]); + count++; + if (count >= maxareas) + { + break; + } + } + // + nextupdate = &aasworld->areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end for + } //end while + return count; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AvoidDangerArea(vec3_t srcpos, int srcarea, vec3_t dangerpos, int dangerarea, float range, int travelflags) +{ + int i, nextareanum, badtravelflags, numreach, bestarea = 0; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + int bestTravel = 999999, t; + const int maxTime = 5000; + const int goodTime = 1000; + vec_t dangerDistance = 0; + + // + if (!aasworld->areavisibility) + { + return 0; + } + if (!srcarea) + { + return 0; + } + // + if (!aasworld->hidetraveltimes) + { + aasworld->hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld->numareas * sizeof(unsigned short int)); + } + else + { + memset(aasworld->hidetraveltimes, 0, aasworld->numareas * sizeof(unsigned short int)); + } //end else + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld->areaupdate[srcarea]; + curupdate->areanum = srcarea; + VectorCopy(srcpos, curupdate->start); + curupdate->areatraveltimes = aasworld->areatraveltimes[srcarea][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + + // Mad Doctor I, 11/3/2002. The source area never needs to be expanded + // again, so mark it as cut off + aasworld->hidetraveltimes[srcarea] = 1; + + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld->areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld->reachability[aasworld->areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (aasworld->travelflagfortype[reach->traveltype] & badtravelflags) + { + continue; + } + // + if (AAS_AreaContentsTravelFlag(reach->areanum) & badtravelflags) + { + continue; + } + // dont pass through ladder areas + if (aasworld->areasettings[reach->areanum].areaflags & AREA_LADDER) + { + continue; + } + // + if (aasworld->areasettings[reach->areanum].areaflags & AREA_DISABLED) + { + continue; + } + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if we've already been to this area + if (aasworld->hidetraveltimes[nextareanum]) + { + continue; + } + aasworld->hidetraveltimes[nextareanum] = 1; + // calc traveltime from srcpos + t = curupdate->tmptraveltime + + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + + reach->traveltime; + if (t > maxTime) + { + continue; + } + if (t > bestTravel) + { + continue; + } + + // How far is it + dangerDistance = Distance(dangerpos, aasworld->areawaypoints[nextareanum]); + + // if it's safe from dangerpos + if (aasworld->areavisibility[nextareanum] && (dangerDistance > range)) + { + if (t < goodTime) + { + return nextareanum; + } + if (t < bestTravel) + { + bestTravel = t; + bestarea = nextareanum; + } + } + // + nextupdate = &aasworld->areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + +// Mad Doctor I, 11/3/2002. The inlist field seems to not be inited properly for this function. +// It causes certain routes to be excluded unnecessarily, so I'm trying to do without it. +// Note that the hidetraveltimes array seems to cut off duplicates already. +// if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end for + } //end while + return bestarea; +} + + +// +// AAS_DangerPQInit() +// +// Description: Init the priority queue +// Written: 12/12/2002 +// +void AAS_DangerPQInit(void) +{ + // Local Variables //////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + +//@TEST. To test that the retreat algorithm is going to work, I've implemented +// a quicky, slow-ass PQ. This needs to be replaced with a heap implementation. + + // Init the distanceFromDanger array if needed + if (!aasworld->PQ_accumulator) + { + // Get memory for this array the safe way. + aasworld->PQ_accumulator = (unsigned short int *) GetClearedMemory(aasworld->numareas * sizeof(unsigned short int)); + + } // if (!aasworld->distanceFromDanger) ... + + // There are no items in the PQ right now + aasworld->PQ_size = 0; + +} +// +// AAS_DangerPQInit() +// + + + +// +// AAS_DangerPQInsert +// +// Description: Put an area into the PQ. ASSUMES the dangerdistance for the +// area is set ahead of time. +// Written: 12/11/2002 +// +void AAS_DangerPQInsert +( + // The area to insert + int areaNum +) +{ + // Local Variables //////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + +//@TEST. To test that the retreat algorithm is going to work, I've implemented +// a quicky, slow-ass PQ. This needs to be replaced with a heap implementation. + + // Increment the count in the accum + aasworld->PQ_size++; + + // Put this one at the end + aasworld->PQ_accumulator[aasworld->PQ_size] = areaNum; + +} +// +// AAS_DangerPQInsert +// + + + +// +// AAS_DangerPQEmpty +// +// Description: Is the Danger Priority Queue empty? +// Written: 12/11/2002 +// +qboolean AAS_DangerPQEmpty(void) +{ + // Local Variables //////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + +//@TEST. To test that the retreat algorithm is going to work, I've implemented +// a quicky, slow-ass PQ. This needs to be replaced with a heap implementation. + + // It's not empty if anything is in the accumulator + if (aasworld->PQ_size > 0) + { + return qfalse; + } + + return qtrue; +} +// +// AAS_DangerPQEmpty +// + + +// +// AAS_DangerPQRemove +// +// Description: Pull the smallest distance area off of the danger Priority +// Queue. +// Written: 12/11/2002 +// +int AAS_DangerPQRemove(void) +{ + // Local Variables //////////////////////////////////////////////////////// + int j; + int nearest = 1; + int nearestArea = aasworld->PQ_accumulator[nearest]; + int nearestDistance = aasworld->distanceFromDanger[nearestArea]; + int distance; + int temp; + int currentArea; + /////////////////////////////////////////////////////////////////////////// + +//@TEST. To test that the retreat algorithm is going to work, I've implemented +// a quicky, slow-ass PQ. This needs to be replaced with a heap implementation. + + // Just loop through the items in the PQ + for (j = 2; j <= aasworld->PQ_size; j++) + { + // What's the next area? + currentArea = aasworld->PQ_accumulator[j]; + + // What's the danerg distance of it + distance = aasworld->distanceFromDanger[currentArea]; + + // Is this element the best one? Top of the heap, so to speak + if (distance < nearestDistance) + { + // Save this one + nearest = j; + + // This has the best distance + nearestDistance = distance; + + // This one is the nearest region so far + nearestArea = currentArea; + + } // if (distance < nearestDistance)... + + } // for (j = 2; j <= aasworld->PQ_size; j++)... + + // Save out the old end of list + temp = aasworld->PQ_accumulator[aasworld->PQ_size]; + + // Put this where the old one was + aasworld->PQ_accumulator[nearest] = temp; + + // Decrement the count + aasworld->PQ_size--; + + return nearestArea; +} +// +// AAS_DangerPQRemove +// + + +// +// AAS_DangerPQChange +// +// Description: We've changed the danger distance for this node, so change +// its place in the PQ if needed. +// Written: 12/11/2002 +// +void AAS_DangerPQChange +( + // The area to change in the PQ + int areaNum +) +{ + // Local Variables //////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + +//@TEST. To test that the retreat algorithm is going to work, I've implemented +// a quicky, slow-ass PQ. This needs to be replaced with a heap implementation. +// NOTHING NEEDS TO BE DONE HERE FOR THE SLOW-ASS PQ. + +} +// +// AAS_DangerPQChange +// + + + + + + +//=========================================================================== +// +// AAS_CalculateDangerZones +// +// Description: +// Written: 12/11/2002 +// +//=========================================================================== +void AAS_CalculateDangerZones +( + // Locations of the danger spots + int *dangerSpots, + + // The number of danger spots + int dangerSpotCount, + + // What is the furthest danger range we care about? (Everything further is safe) + float dangerRange, + + // A safe distance to init distanceFromDanger to + unsigned short int definitelySafe +) +{ + // Local Variables //////////////////////////////////////////////////////// + // Generic index used to loop through danger zones (and later, the reachabilities) + int i; + + // The area number of a danger spot + int dangerAreaNum; + + // What's the current AAS area we're measuring distance from danger to + int currentArea; + + // How many links come out of the current AAS area? + int numreach; + + // Number of the area the reachability leads to + int nextareanum; + + // Distance from current node to next node + float distanceFromCurrentNode; + + // Total distance from danger to next node + int dangerDistance; + + // The previous distance for this node + int oldDistance; + + // A link from the current node + aas_reachability_t *reach = NULL; + + /////////////////////////////////////////////////////////////////////////// + + // Initialize all of the starting danger zones. + for (i = 0; i < dangerSpotCount; i++) + { + // Get the area number of this danger spot + dangerAreaNum = dangerSpots[i]; + + // Set it's distance to 0, meaning it's 0 units to danger + aasworld->distanceFromDanger[dangerAreaNum] = 0; + + // Add the zone to the PQ. + AAS_DangerPQInsert(dangerAreaNum); + + } // for (i = 0; i < dangerSpotCount; i++)... + + // Go through the Priority Queue, pop off the smallest distance, and expand + // to the neighboring nodes. Stop when the PQ is empty. + while (!AAS_DangerPQEmpty()) + { + // Get the smallest distance in the PQ. + currentArea = AAS_DangerPQRemove(); + + // Check all reversed reachability links + numreach = aasworld->areasettings[currentArea].numreachableareas; + reach = &aasworld->reachability[aasworld->areasettings[currentArea].firstreachablearea]; + + // Loop through the neighbors to this node. + for (i = 0; i < numreach; i++, reach++) + { + // Number of the area the reachability leads to + nextareanum = reach->areanum; + + // How far was it from the last node to this one? + distanceFromCurrentNode = Distance(aasworld->areawaypoints[currentArea], + aasworld->areawaypoints[nextareanum]); + + // Calculate the distance from danger to this neighbor along the + // current route. + dangerDistance = aasworld->distanceFromDanger[currentArea] + + (int) distanceFromCurrentNode; + + // Skip this neighbor if the distance is bigger than we care about. + if (dangerDistance > dangerRange) + { + continue; + } + + // Store the distance from danger if it's smaller than any previous + // distance to this node (note that unvisited nodes are inited with + // a big distance, so this check will be satisfied). + if (dangerDistance < aasworld->distanceFromDanger[nextareanum]) + { + // How far was this node from danger before this visit? + oldDistance = aasworld->distanceFromDanger[nextareanum]; + + // Store the new value + aasworld->distanceFromDanger[nextareanum] = dangerDistance; + + // If the neighbor has been calculated already, see if we need to + // update the priority. + if (oldDistance != definitelySafe) + { + // We need to update the priority queue's position for this node + AAS_DangerPQChange(nextareanum); + + } // if (aasworld->distanceFromDanger[nextareanum] == definitelySafe)... + // Otherwise, insert the neighbor into the PQ. + else + { + // Insert this node into the PQ + AAS_DangerPQInsert(nextareanum); + + } // else ... + + } // if (dangerDistance < aasworld->distanceFromDanger[nextareanum])... + + } // for (i = 0; i < numreach; i++, reach++)... + + } // while (!AAS_DangerPQEmpty())... + + // At this point, all of the nodes within our danger range have their + // distance from danger calculated. + +} +// +// AAS_CalculateDangerZones +// + + + +//=========================================================================== +// +// AAS_Retreat +// +// Use this to find a safe spot away from a dangerous situation/enemy. +// This differs from AAS_AvoidDangerArea in the following ways: +// * AAS_Retreat will return the farthest location found even if it does not +// exceed the desired minimum distance. +// * AAS_Retreat will give preference to nodes on the "safe" side of the danger +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Retreat +( + // Locations of the danger spots (AAS area numbers) + int *dangerSpots, + + // The number of danger spots + int dangerSpotCount, + + vec3_t srcpos, + int srcarea, + vec3_t dangerpos, + int dangerarea, + // Min range from startpos + float range, + // Min range from danger + float dangerRange, + int travelflags +) +{ + int i, nextareanum, badtravelflags, numreach, bestarea = 0; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + int t; + const int maxTime = 5000; +// const int goodTime = 1000; + vec_t dangerDistance = 0; + vec_t sourceDistance = 0; + float bestDistanceSoFar = 0; + + // Choose a safe distance to init distanceFromDanger to +// int definitelySafe = 1 + (int) range; + unsigned short int definitelySafe = 0xFFFF; + + // + if (!aasworld->areavisibility) + { + return 0; + } + + // Init the hide travel time if needed + if (!aasworld->hidetraveltimes) + { + aasworld->hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld->numareas * sizeof(unsigned short int)); + } + // Otherwise, it exists already, so just reset the values + else + { + memset(aasworld->hidetraveltimes, 0, aasworld->numareas * sizeof(unsigned short int)); + } //end else + + // Init the distanceFromDanger array if needed + if (!aasworld->distanceFromDanger) + { + // Get memory for this array the safe way. + aasworld->distanceFromDanger = (unsigned short int *) GetClearedMemory(aasworld->numareas * sizeof(unsigned short int)); + + } // if (!aasworld->distanceFromDanger) ... + + // Set all the values in the distanceFromDanger array to a safe value + memset(aasworld->distanceFromDanger, 0xFF, aasworld->numareas * sizeof(unsigned short int)); + + // Init the priority queue + AAS_DangerPQInit(); + + // Set up the distanceFromDanger array + AAS_CalculateDangerZones(dangerSpots, dangerSpotCount, dangerRange, definitelySafe); + + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld->areaupdate[srcarea]; + curupdate->areanum = srcarea; + VectorCopy(srcpos, curupdate->start); + curupdate->areatraveltimes = aasworld->areatraveltimes[srcarea][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + + // Mad Doctor I, 11/3/2002. The source area never needs to be expanded + // again, so mark it as cut off + aasworld->hidetraveltimes[srcarea] = 1; + + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld->areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld->reachability[aasworld->areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (aasworld->travelflagfortype[reach->traveltype] & badtravelflags) + { + continue; + } + // + if (AAS_AreaContentsTravelFlag(reach->areanum) & badtravelflags) + { + continue; + } + // dont pass through ladder areas + if (aasworld->areasettings[reach->areanum].areaflags & AREA_LADDER) + { + continue; + } + // + if (aasworld->areasettings[reach->areanum].areaflags & AREA_DISABLED) + { + continue; + } + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if we've already been to this area + if (aasworld->hidetraveltimes[nextareanum]) + { + continue; + } + aasworld->hidetraveltimes[nextareanum] = 1; + // calc traveltime from srcpos + t = curupdate->tmptraveltime + + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + + reach->traveltime; + if (t > maxTime) + { + continue; + } + + // How far is it from a danger area? + dangerDistance = aasworld->distanceFromDanger[nextareanum]; + + // How far is it from our starting position? + sourceDistance = Distance(srcpos, aasworld->areawaypoints[nextareanum]); + + // If it's safe from dangerpos + if (aasworld->areavisibility[nextareanum] + && (sourceDistance > range) + && ((dangerDistance > dangerRange) + || (dangerDistance == definitelySafe) + ) + ) + { + // Just use this area + return nextareanum; + } + + // In case we don't find a perfect one, save the best + if (dangerDistance > bestDistanceSoFar) + { + bestarea = nextareanum; + bestDistanceSoFar = dangerDistance; + + } // if (dangerDistance > bestDistanceSoFar)... + + // + nextupdate = &aasworld->areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + + } //end for + } //end while + + return bestarea; +} + +/* +=================== +AAS_InitTeamDeath +=================== +*/ +void AAS_InitTeamDeath(void) +{ + if (aasworld->teamDeathTime) + { + FreeMemory(aasworld->teamDeathTime); + } + aasworld->teamDeathTime = GetClearedMemory(sizeof(int) * aasworld->numareas * 2); + if (aasworld->teamDeathCount) + { + FreeMemory(aasworld->teamDeathCount); + } + aasworld->teamDeathCount = GetClearedMemory(sizeof(byte) * aasworld->numareas * 2); + if (aasworld->teamDeathAvoid) + { + FreeMemory(aasworld->teamDeathAvoid); + } + aasworld->teamDeathAvoid = GetClearedMemory(sizeof(byte) * aasworld->numareas * 2); +} + +#define TEAM_DEATH_TIMEOUT 120.0 +#define TEAM_DEATH_AVOID_COUNT_SCALE 0.5 // so if there are 12 players, then if count reaches (12 * scale) then AVOID +#define TEAM_DEATH_RANGE 512.0 + +/* +=================== +AAS_UpdateTeamDeath +=================== +*/ +void AAS_UpdateTeamDeath(void) +{ + int i, j, k; + + // check for areas which have timed out, so we can stop avoiding them + + // for each area + for (i = 0; i < aasworld->numareas; i++) + { + // for each team + for (j = 0; j < 2; j++) + { + k = (aasworld->numareas * j) + i; + if (aasworld->teamDeathTime[k]) + { + if (aasworld->teamDeathTime[k] < AAS_Time() - TEAM_DEATH_TIMEOUT) + { + // this area has timed out + aasworld->teamDeathAvoid[k] = 0; + aasworld->teamDeathTime[k] = 0; + if (aasworld->teamDeathAvoid[k]) + { + // unmark this area + if (j == 0) + { + aasworld->areasettings[i].areaflags &= ~AREA_AVOID_AXIS; + } + else + { + aasworld->areasettings[i].areaflags &= ~AREA_AVOID_ALLIES; + } + //remove all routing cache involving this area + AAS_RemoveRoutingCacheUsingArea(i); + // recalculate the team flags that are used in this cluster + AAS_ClearClusterTeamFlags(i); + } + } + } + } + } +} + +/* +=================== +AAS_RecordTeamDeathArea +=================== +*/ +void AAS_RecordTeamDeathArea(vec3_t srcpos, int srcarea, int team, int teamCount, int travelflags) +{ + int i, nextareanum, badtravelflags, numreach, k; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; +// int count=0; +// + return; + // + badtravelflags = ~travelflags; + k = (aasworld->numareas * team) + srcarea; + // + curupdate = &aasworld->areaupdate[srcarea]; + curupdate->areanum = srcarea; + VectorCopy(srcpos, curupdate->start); + // + if (srcarea == 0) + { + return; + } + curupdate->areatraveltimes = aasworld->areatraveltimes[srcarea][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) + { + curupdate->next->prev = NULL; + } + else + { + updatelistend = NULL; + } + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld->areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld->reachability[aasworld->areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + // if tihs area has already been done + if (aasworld->teamDeathTime[reach->areanum] >= AAS_Time()) + { + continue; + } + aasworld->teamDeathTime[reach->areanum] = AAS_Time(); + // + //if an undesired travel type is used + if (aasworld->travelflagfortype[reach->traveltype] & badtravelflags) + { + continue; + } + // + if (AAS_AreaContentsTravelFlag(reach->areanum) & badtravelflags) + { + continue; + } + //number of the area the reachability leads to + nextareanum = reach->areanum; + // + // if it's too far from srcpos, ignore + if (Distance(curupdate->start, reach->end) + (float)curupdate->tmptraveltime > TEAM_DEATH_RANGE) + { + continue; + } + // + k = reach->areanum; + // mark this area + aasworld->teamDeathCount[k]++; + if (aasworld->teamDeathCount[k] > 100) + { + aasworld->teamDeathCount[k] = 100; // make sure it doesnt loop around + } + // + // see if this area is now to be avoided + if (!aasworld->teamDeathAvoid[k]) + { + if (aasworld->teamDeathCount[k] > (int)(TEAM_DEATH_AVOID_COUNT_SCALE * teamCount)) + { + // avoid this area + aasworld->teamDeathAvoid[k] = 1; + // mark this area + if (team == 0) + { + aasworld->areasettings[k].areaflags |= AREA_AVOID_AXIS; + } + else + { + aasworld->areasettings[k].areaflags |= AREA_AVOID_ALLIES; + } + //remove all routing cache involving this area + AAS_RemoveRoutingCacheUsingArea(k); + // recalculate the team flags that are used in this cluster + AAS_ClearClusterTeamFlags(k); + } + } + // + nextupdate = &aasworld->areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + // calc the distance + nextupdate->tmptraveltime = (float)curupdate->tmptraveltime + Distance(curupdate->start, reach->end); + //if this update is not in the list yet + if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) + { + updatelistend->next = nextupdate; + } + else + { + updateliststart = nextupdate; + } + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end for + } //end while + // + return; +} diff --git a/src/botlib/be_aas_route.h b/src/botlib/be_aas_route.h new file mode 100644 index 000000000..9ded951db --- /dev/null +++ b/src/botlib/be_aas_route.h @@ -0,0 +1,64 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_route.h + */ + +#ifdef AASINTERN +//initialize the AAS routing +void AAS_InitRouting(void); +//free the AAS routing caches +void AAS_FreeRoutingCaches(void); +//returns the travel time from start to end in the given area +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +// +void AAS_CreateAllRoutingCache(void); +// +void AAS_RoutingInfo(void); +#endif //AASINTERN + +//returns the travel flag for the given travel type +int AAS_TravelFlagForType(int traveltype); +// +int AAS_AreaContentsTravelFlag(int areanum); +//returns the index of the next reachability for the given area +int AAS_NextAreaReachability(int areanum, int reachnum); +//returns the reachability with the given index +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach); +//returns a random goal area and goal origin +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin); +//returns the travel time within the given area from start to end +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +//returns the travel time from the area to the goal area using the given travel flags +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags); + +void AAS_InitTeamDeath(void); +void AAS_RecordTeamDeathArea(vec3_t srcpos, int srcarea, int team, int teamCount, int travelflags); +void AAS_UpdateTeamDeath(void); diff --git a/src/botlib/be_aas_routealt.c b/src/botlib/be_aas_routealt.c new file mode 100644 index 000000000..6e9a70866 --- /dev/null +++ b/src/botlib/be_aas_routealt.c @@ -0,0 +1,320 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_routealt.c + */ + +#include "../qcommon/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ENABLE_ALTROUTING + +typedef struct midrangearea_s +{ + int valid; + unsigned short starttime; + unsigned short goaltime; +} midrangearea_t; + +midrangearea_t *midrangeareas; +int *clusterareas; +int numclusterareas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AltRoutingFloodCluster_r(int areanum) +{ + int i, otherareanum; + aas_area_t *area; + aas_face_t *face; + + //add the current area to the areas of the current cluster + clusterareas[numclusterareas] = areanum; + numclusterareas++; + //remove the area from the mid range areas + midrangeareas[areanum].valid = qfalse; + //flood to other areas through the faces of this area + area = &(*aasworld).areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &(*aasworld).faces[abs((*aasworld).faceindex[area->firstface + i])]; + //get the area at the other side of the face + if (face->frontarea == areanum) + { + otherareanum = face->backarea; + } + else + { + otherareanum = face->frontarea; + } + //if there is an area at the other side of this face + if (!otherareanum) + { + continue; + } + //if the other area is not a midrange area + if (!midrangeareas[otherareanum].valid) + { + continue; + } + // + AAS_AltRoutingFloodCluster_r(otherareanum); + } //end for +} //end of the function AAS_AltRoutingFloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum); + +int AAS_AlternativeRouteGoals(vec3_t start, vec3_t goal, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int color) +{ +#ifndef ENABLE_ALTROUTING + return 0; +#else + int i, j, startareanum, goalareanum, bestareanum; + int numaltroutegoals, nummidrangeareas; + int starttime, goaltime, goaltraveltime; + float dist, bestdist; + vec3_t mid, dir; + int reachnum, time; + int a1, a2; + /*#ifdef DEBUG + int startmillisecs; + + startmillisecs = Sys_MilliSeconds(); + #endif*/ + + startareanum = AAS_PointAreaNum(start); + if (!startareanum) + { + return 0; + } + goalareanum = AAS_PointAreaNum(goal); + if (!goalareanum) + { + VectorCopy(goal, dir); + dir[2] += 30; + goalareanum = AAS_PointAreaNum(dir); + if (!goalareanum) + { + return 0; + } + } + //travel time towards the goal area + goaltraveltime = AAS_AreaTravelTimeToGoalArea(startareanum, start, goalareanum, travelflags); + //clear the midrange areas + memset(midrangeareas, 0, (*aasworld).numareas * sizeof(midrangearea_t)); + numaltroutegoals = 0; + // + nummidrangeareas = 0; + // + for (i = 1; i < (*aasworld).numareas; i++) + { + // + if (!((*aasworld).areasettings[i].contents & AREACONTENTS_ROUTEPORTAL) && + !((*aasworld).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) + { + continue; + } + //if the area has no reachabilities + if (!AAS_AreaReachability(i)) + { + continue; + } + //tavel time from the area to the start area + starttime = AAS_AreaTravelTimeToGoalArea(startareanum, start, i, travelflags); + if (!starttime) + { + continue; + } + //if the travel time from the start to the area is greater than the shortest goal travel time + if (starttime > 500 + 3.0 * goaltraveltime) + { + continue; + } + //travel time from the area to the goal area + goaltime = AAS_AreaTravelTimeToGoalArea(i, NULL, goalareanum, travelflags); + if (!goaltime) + { + continue; + } + //if the travel time from the area to the goal is greater than the shortest goal travel time + if (goaltime > 500 + 3.0 * goaltraveltime) + { + continue; + } + //this is a mid range area + midrangeareas[i].valid = qtrue; + midrangeareas[i].starttime = starttime; + midrangeareas[i].goaltime = goaltime; + Log_Write("%d midrange area %d", nummidrangeareas, i); + nummidrangeareas++; + } //end for + // + for (i = 1; i < (*aasworld).numareas; i++) + { + if (!midrangeareas[i].valid) + { + continue; + } + //get the areas in one cluster + numclusterareas = 0; + AAS_AltRoutingFloodCluster_r(i); + //now we've got a cluster with areas through which an alternative route could go + //get the 'center' of the cluster + VectorClear(mid); + for (j = 0; j < numclusterareas; j++) + { + VectorAdd(mid, (*aasworld).areas[clusterareas[j]].center, mid); + } //end for + VectorScale(mid, 1.0 / numclusterareas, mid); + //get the area closest to the center of the cluster + bestdist = 999999; + bestareanum = 0; + for (j = 0; j < numclusterareas; j++) + { + VectorSubtract(mid, (*aasworld).areas[clusterareas[j]].center, dir); + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestareanum = clusterareas[j]; + } //end if + } //end for + // make sure the route to the destination isn't in the same direction as the route to the source + if (!AAS_AreaRouteToGoalArea(bestareanum, (*aasworld).areawaypoints[bestareanum], goalareanum, travelflags, &time, &reachnum)) + { + continue; + } + a1 = (*aasworld).reachability[reachnum].areanum; + if (!AAS_AreaRouteToGoalArea(bestareanum, (*aasworld).areawaypoints[bestareanum], startareanum, travelflags, &time, &reachnum)) + { + continue; + } + a2 = (*aasworld).reachability[reachnum].areanum; + if (a1 == a2) + { + continue; + } + //now we've got an area for an alternative route + //FIXME: add alternative goal origin + VectorCopy((*aasworld).areawaypoints[bestareanum], altroutegoals[numaltroutegoals].origin); + altroutegoals[numaltroutegoals].areanum = bestareanum; + altroutegoals[numaltroutegoals].starttraveltime = midrangeareas[bestareanum].starttime; + altroutegoals[numaltroutegoals].goaltraveltime = midrangeareas[bestareanum].goaltime; + altroutegoals[numaltroutegoals].extratraveltime = + (midrangeareas[bestareanum].starttime + midrangeareas[bestareanum].goaltime) - + goaltraveltime; + numaltroutegoals++; + // + /*#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "alternative route goal area %d, numclusterareas = %d\n", bestareanum, numclusterareas); + if (color) + { + AAS_DrawPermanentCross((*aasworld).areas[bestareanum].center, 10, color); + } //end if + //AAS_ShowArea(bestarea, qtrue); + #endif*/ + //don't return more than the maximum alternative route goals + if (numaltroutegoals >= maxaltroutegoals) + { + break; + } + } //end for + //botimport.Print(PRT_MESSAGE, "%d alternative route goals\n", numaltroutegoals); +#ifdef DEBUG +// botimport.Print(PRT_MESSAGE, "alternative route goals in %d msec\n", Sys_MilliSeconds() - startmillisecs); +#endif + return numaltroutegoals; +#endif +} //end of the function AAS_AlternativeRouteGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) + { + FreeMemory(midrangeareas); + } + midrangeareas = (midrangearea_t *) GetMemory((*aasworld).numareas * sizeof(midrangearea_t)); + if (clusterareas) + { + FreeMemory(clusterareas); + } + clusterareas = (int *) GetMemory((*aasworld).numareas * sizeof(int)); +#endif +} //end of the function AAS_InitAlternativeRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutdownAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) + { + FreeMemory(midrangeareas); + } + midrangeareas = NULL; + if (clusterareas) + { + FreeMemory(clusterareas); + } + clusterareas = NULL; + numclusterareas = 0; +#endif +} diff --git a/src/botlib/be_aas_routealt.h b/src/botlib/be_aas_routealt.h new file mode 100644 index 000000000..74e6ed141 --- /dev/null +++ b/src/botlib/be_aas_routealt.h @@ -0,0 +1,42 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_routealt.h + */ + +#ifdef AASINTERN +void AAS_InitAlternativeRouting(void); +void AAS_ShutdownAlternativeRouting(void); +#endif //AASINTERN + + +int AAS_AlternativeRouteGoals(vec3_t start, vec3_t goal, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int color); diff --git a/src/botlib/be_aas_routetable.c b/src/botlib/be_aas_routetable.c new file mode 100644 index 000000000..a3028cc40 --- /dev/null +++ b/src/botlib/be_aas_routetable.c @@ -0,0 +1,1240 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_routetable.c + * @author Ridah + * @brief Area Awareness System, Route-table defines + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_def.h" + +// ugly hack to turn off route-tables, can't find a way to check cvar's +int disable_routetable = 0; + +// this must be enabled for the route-tables to work, but it's not fully operational yet +#define CHECK_TRAVEL_TIMES +//#define DEBUG_ROUTETABLE +#define FILTERAREAS + +// enable this to use the built-in route-cache system to find the routes +#define USE_ROUTECACHE + +// enable this to disable Rocket/BFG Jumping, Grapple Hook +#define FILTER_TRAVEL + +// hmm, is there a cleaner way of finding out memory usage? +extern int totalmemorysize; +static int memorycount, cachememory; + +// globals to reduce function parameters +static unsigned short int *filtered_areas, childcount, num_parents; +static unsigned short int *rev_filtered_areas; + +// misc defines +unsigned short CRC_ProcessString(unsigned char *data, int length); + + +//=========================================================================== +// Memory debugging/optimization + +void *AAS_RT_GetClearedMemory(unsigned long size) +{ + void *ptr; + + memorycount += size; + + // ptr = GetClearedMemory(size); + ptr = GetClearedHunkMemory(size); + // Ryan - 01102k, need to use this, since the routetable calculations use up a lot of memory + // this will be a non-issue once we transfer the remnants of the routetable over to the aasworld + //ptr = malloc (size); + //memset (ptr, 0, size); + + return ptr; +} + +void AAS_RT_FreeMemory(void *ptr) +{ + int before; + + before = totalmemorysize; + + // FreeMemory( ptr ); + // Ryan - 01102k + free(ptr); + + memorycount -= before - totalmemorysize; +} + +void AAS_RT_PrintMemoryUsage(void) +{ +#ifdef AAS_RT_MEMORY_USAGE + + botimport.Print(PRT_MESSAGE, "\n"); + + // TODO: print the usage from each of the aas_rt_t lumps + +#endif +} +//=========================================================================== + + +//=========================================================================== +// return the number of unassigned areas that are in the given area's visible list +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_RT_GetValidVisibleAreasCount(aas_area_buildlocalinfo_t *localinfo, aas_area_childlocaldata_t **childlocaldata) +{ + int i, cnt; + + cnt = 1; // assume it can reach itself + + for (i = 0; i < localinfo->numvisible; i++) + { + if (childlocaldata[localinfo->visible[i]]) + { + continue; + } + + cnt++; + } + + return cnt; +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static aas_rt_route_t **routetable; + +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RT_CalcTravelTimesToGoalArea(int goalarea) +{ + int i; + // TTimo: unused +// static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_LAVA); //----(SA) modified since slime is no longer deadly +// static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); + aas_rt_route_t *rt; + int reach, travel; + + for (i = 0; i < childcount; i++) + { + rt = &routetable[i][-1 + rev_filtered_areas[goalarea]]; + if (AAS_AreaRouteToGoalArea(filtered_areas[i], (*aasworld).areas[filtered_areas[i]].center, goalarea, ~RTB_BADTRAVELFLAGS, &travel, &reach)) + { + rt->reachable_index = reach; + rt->travel_time = travel; + } + else + { + //rt->reachable_index = -1; + rt->travel_time = 0; + } + } +} +//=========================================================================== +// calculate the initial route-table for each filtered area to all other areas +// +// FIXME: this isn't fully operational yet, for some reason not all routes are found +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_CalculateRouteTable(aas_rt_route_t **parmroutetable) +{ + int i; + + routetable = parmroutetable; + + for (i = 0; i < childcount; i++) + { + AAS_RT_CalcTravelTimesToGoalArea(filtered_areas[i]); + } +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_AddParentLink(aas_area_childlocaldata_t *child, int parentindex, int childindex) +{ + aas_parent_link_t *oldparentlink; + + oldparentlink = child->parentlink; + + child->parentlink = (aas_parent_link_t *) AAS_RT_GetClearedMemory(sizeof(aas_parent_link_t)); + + child->parentlink->childindex = (unsigned short int)childindex; + child->parentlink->parent = (unsigned short int)parentindex; + child->parentlink->next = oldparentlink; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_WriteShort(unsigned short int si, fileHandle_t fp) +{ + unsigned short int lsi; + + lsi = LittleShort(si); + botimport.FS_Write(&lsi, sizeof(lsi), fp); +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_WriteByte(int si, fileHandle_t fp) +{ + unsigned char uc; + + uc = si; + botimport.FS_Write(&uc, sizeof(uc), fp); +} + +//=========================================================================== +// writes the current route-table data to a .rtb file in tne maps folder +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_WriteRouteTable(void) +{ + int ident, version; + unsigned short crc_aas; + fileHandle_t fp; + char filename[MAX_QPATH]; + + // open the file for writing + Com_sprintf(filename, MAX_QPATH, "maps/%s.rtb", (*aasworld).mapname); + botimport.Print(PRT_MESSAGE, "\nsaving route-table to %s\n", filename); + botimport.FS_FOpenFile(filename, &fp, FS_WRITE); + if (!fp) + { + AAS_Error("Unable to open file: %s\n", filename); + return; + } + + // ident + ident = LittleLong(RTBID); + botimport.FS_Write(&ident, sizeof(ident), fp); + + // version + version = LittleLong(RTBVERSION); + botimport.FS_Write(&version, sizeof(version), fp); + + // crc + crc_aas = CRC_ProcessString((unsigned char *)(*aasworld).areas, sizeof(aas_area_t) * (*aasworld).numareas); + botimport.FS_Write(&crc_aas, sizeof(crc_aas), fp); + + // save the table data + + // children + botimport.FS_Write(&(*aasworld).routetable->numChildren, sizeof(int), fp); + botimport.FS_Write((*aasworld).routetable->children, (*aasworld).routetable->numChildren * sizeof(aas_rt_child_t), fp); + + // parents + botimport.FS_Write(&(*aasworld).routetable->numParents, sizeof(int), fp); + botimport.FS_Write((*aasworld).routetable->parents, (*aasworld).routetable->numParents * sizeof(aas_rt_parent_t), fp); + + // parentChildren + botimport.FS_Write(&(*aasworld).routetable->numParentChildren, sizeof(int), fp); + botimport.FS_Write((*aasworld).routetable->parentChildren, (*aasworld).routetable->numParentChildren * sizeof(unsigned short int), fp); + + // visibleParents + botimport.FS_Write(&(*aasworld).routetable->numVisibleParents, sizeof(int), fp); + botimport.FS_Write((*aasworld).routetable->visibleParents, (*aasworld).routetable->numVisibleParents * sizeof(unsigned short int), fp); + + // parentLinks + botimport.FS_Write(&(*aasworld).routetable->numParentLinks, sizeof(int), fp); + botimport.FS_Write((*aasworld).routetable->parentLinks, (*aasworld).routetable->numParentLinks * sizeof(aas_rt_parent_link_t), fp); + + botimport.FS_FCloseFile(fp); + return; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_DBG_Read(void *buf, int size, int fp) +{ + botimport.FS_Read(buf, size, fp); +} + +//=========================================================================== +// reads the given file, and creates the structures required for the route-table system +// +// Parameter: - +// Returns: qtrue if succesful, qfalse if not +// Changes Globals: - +//=========================================================================== +#define DEBUG_READING_TIME +qboolean AAS_RT_ReadRouteTable(fileHandle_t fp) +{ + int ident, version, i; + unsigned short int crc, crc_aas; + aas_rt_t *routetable; + aas_rt_child_t *child; + aas_rt_parent_t *parent; + aas_rt_parent_link_t *plink; + unsigned short int *psi; + + qboolean doswap; + +#ifdef DEBUG_READING_TIME + int pretime; + + pretime = Sys_MilliSeconds(); +#endif + + routetable = (*aasworld).routetable; + + doswap = (LittleLong(1) != 1); + + // check ident + AAS_RT_DBG_Read(&ident, sizeof(ident), fp); + ident = LittleLong(ident); + + if (ident != RTBID) + { + AAS_Error("File is not an RTB file\n"); + botimport.FS_FCloseFile(fp); + return qfalse; + } + + // check version + AAS_RT_DBG_Read(&version, sizeof(version), fp); + version = LittleLong(version); + + if (version != RTBVERSION) + { + AAS_Error("File is version %i not %i\n", version, RTBVERSION); + botimport.FS_FCloseFile(fp); + return qfalse; + } + + // read the CRC check on the AAS data + AAS_RT_DBG_Read(&crc, sizeof(crc), fp); + crc = LittleShort(crc); + + // calculate a CRC on the AAS areas + crc_aas = CRC_ProcessString((unsigned char *)(*aasworld).areas, sizeof(aas_area_t) * (*aasworld).numareas); + + if (crc != crc_aas) + { + AAS_Error("Route-table is from different AAS file, ignoring.\n"); + botimport.FS_FCloseFile(fp); + return qfalse; + } + + // read the route-table + + // children + botimport.FS_Read(&routetable->numChildren, sizeof(int), fp); + routetable->numChildren = LittleLong(routetable->numChildren); + routetable->children = (aas_rt_child_t *) AAS_RT_GetClearedMemory(routetable->numChildren * sizeof(aas_rt_child_t)); + botimport.FS_Read(routetable->children, routetable->numChildren * sizeof(aas_rt_child_t), fp); + child = &routetable->children[0]; + if (doswap) + { + for (i = 0; i < routetable->numChildren; i++, child++) + { + child->areanum = LittleShort(child->areanum); + child->numParentLinks = LittleLong(child->numParentLinks); + child->startParentLinks = LittleLong(child->startParentLinks); + } + } + + // parents + botimport.FS_Read(&routetable->numParents, sizeof(int), fp); + routetable->numParents = LittleLong(routetable->numParents); + routetable->parents = (aas_rt_parent_t *) AAS_RT_GetClearedMemory(routetable->numParents * sizeof(aas_rt_parent_t)); + botimport.FS_Read(routetable->parents, routetable->numParents * sizeof(aas_rt_parent_t), fp); + parent = &routetable->parents[0]; + if (doswap) + { + for (i = 0; i < routetable->numParents; i++, parent++) + { + parent->areanum = LittleShort(parent->areanum); + parent->numParentChildren = LittleLong(parent->numParentChildren); + parent->startParentChildren = LittleLong(parent->startParentChildren); + parent->numVisibleParents = LittleLong(parent->numVisibleParents); + parent->startVisibleParents = LittleLong(parent->startVisibleParents); + } + } + + // parentChildren + botimport.FS_Read(&routetable->numParentChildren, sizeof(int), fp); + routetable->numParentChildren = LittleLong(routetable->numParentChildren); + routetable->parentChildren = (unsigned short int *) AAS_RT_GetClearedMemory(routetable->numParentChildren * sizeof(unsigned short int)); + botimport.FS_Read(routetable->parentChildren, routetable->numParentChildren * sizeof(unsigned short int), fp); + psi = &routetable->parentChildren[0]; + if (doswap) + { + for (i = 0; i < routetable->numParentChildren; i++, psi++) + { + *psi = LittleShort(*psi); + } + } + + // visibleParents + botimport.FS_Read(&routetable->numVisibleParents, sizeof(int), fp); + routetable->numVisibleParents = LittleLong(routetable->numVisibleParents); + routetable->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory(routetable->numVisibleParents * sizeof(unsigned short int)); + botimport.FS_Read(routetable->visibleParents, routetable->numVisibleParents * sizeof(unsigned short int), fp); + psi = &routetable->visibleParents[0]; + if (doswap) + { + for (i = 0; i < routetable->numVisibleParents; i++, psi++) + { + *psi = LittleShort(*psi); + } + } + + // parentLinks + botimport.FS_Read(&routetable->numParentLinks, sizeof(int), fp); + routetable->numParentLinks = LittleLong(routetable->numParentLinks); + routetable->parentLinks = (aas_rt_parent_link_t *) AAS_RT_GetClearedMemory(routetable->numParentLinks * sizeof(aas_rt_parent_link_t)); + botimport.FS_Read(routetable->parentLinks, routetable->numParentLinks * sizeof(aas_parent_link_t), fp); + plink = &routetable->parentLinks[0]; + if (doswap) + { + for (i = 0; i < routetable->numParentLinks; i++, plink++) + { + plink->childIndex = LittleShort(plink->childIndex); + plink->parent = LittleShort(plink->parent); + } + } + + // build the areaChildIndexes + routetable->areaChildIndexes = (unsigned short int *) AAS_RT_GetClearedMemory((*aasworld).numareas * sizeof(unsigned short int)); + child = routetable->children; + for (i = 0; i < routetable->numChildren; i++, child++) + { + routetable->areaChildIndexes[child->areanum] = i + 1; + } + + botimport.Print(PRT_MESSAGE, "Total Parents: %d\n", routetable->numParents); + botimport.Print(PRT_MESSAGE, "Total Children: %d\n", routetable->numChildren); + botimport.Print(PRT_MESSAGE, "Total Memory Used: %d\n", memorycount); + +#ifdef DEBUG_READING_TIME + botimport.Print(PRT_MESSAGE, "Route-Table read time: %i\n", Sys_MilliSeconds() - pretime); +#endif + + botimport.FS_FCloseFile(fp); + return qtrue; +} + +int AAS_RT_NumParentLinks(aas_area_childlocaldata_t *child) +{ + aas_parent_link_t *plink; + int i; + + i = 0; + plink = child->parentlink; + while (plink) + { + i++; + plink = plink->next; + } + + return i; +} + +//=========================================================================== +// main routine to build the route-table +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAllRoutingCache(void); + +void AAS_RT_BuildRouteTable(void) +{ + int i, j, k; + aas_area_t *srcarea; + aas_areasettings_t *srcsettings; +// vec3_t vec; + unsigned int totalcount; + unsigned int noroutecount; + + aas_area_buildlocalinfo_t **area_localinfos; + aas_area_buildlocalinfo_t *localinfo; + + aas_area_childlocaldata_t **area_childlocaldata; + aas_area_childlocaldata_t *child; + + aas_area_parent_t *area_parents[MAX_PARENTS]; + aas_area_parent_t *thisparent; + + int bestchild, bestcount, bestparent, cnt; + + int memoryend; + + unsigned short int *visibleParents; + +#ifdef CHECK_TRAVEL_TIMES + aas_rt_route_t **filteredroutetable; + unsigned short int traveltime; +#endif + + fileHandle_t fp; + char filename[MAX_QPATH]; + +// not used anymore + return; + + // create the routetable in this aasworld + aasworld->routetable = (aas_rt_t *) AAS_RT_GetClearedMemory(sizeof(aas_rt_t)); + + // Try to load in a prepared route-table + Com_sprintf(filename, MAX_QPATH, "maps/%s.rtb", (*aasworld).mapname); + botimport.Print(PRT_MESSAGE, "\n---------------------------------\n"); + botimport.Print(PRT_MESSAGE, "\ntrying to load %s\n", filename); + botimport.FS_FOpenFile(filename, &fp, FS_READ); + if (fp) + { + // read in the table.. + if (AAS_RT_ReadRouteTable(fp)) + { + AAS_RT_PrintMemoryUsage(); + + botimport.Print(PRT_MESSAGE, "\nAAS Route-Table loaded.\n"); + botimport.Print(PRT_MESSAGE, "---------------------------------\n\n"); + return; + } + else + { + botimport.Print(PRT_MESSAGE, "\nUnable to load %s, building route-table..\n", filename); + } + } + else + { + botimport.Print(PRT_MESSAGE, "file not found, building route-table\n\n"); + } + + + botimport.Print(PRT_MESSAGE, "\n-------------------------------------\nRoute-table memory usage figures..\n\n"); + + totalcount = 0; + childcount = 0; + noroutecount = 0; + childcount = 0; + num_parents = 0; + + memorycount = 0; + cachememory = 0; + + filtered_areas = (unsigned short int *) AAS_RT_GetClearedMemory((*aasworld).numareas * sizeof(unsigned short int)); + rev_filtered_areas = (unsigned short int *) AAS_RT_GetClearedMemory((*aasworld).numareas * sizeof(unsigned short int)); + + // to speed things up, build a list of FILTERED areas first + // do this so we can check for filtered areas + AAS_CreateAllRoutingCache(); + for (i = 0; i < (*aasworld).numareas; i++) + { + srcarea = &(*aasworld).areas[i]; + srcsettings = &(*aasworld).areasettings[i]; + +#ifdef FILTERAREAS + if (!(srcsettings->areaflags & (AREA_USEFORROUTING))) + { + continue; + } + if (!(srcsettings->areaflags & (AREA_GROUNDED | AREA_LIQUID | AREA_LADDER))) + { + continue; + } +#endif + + rev_filtered_areas[i] = childcount + 1; + filtered_areas[childcount++] = (unsigned short int)i; + } + +#ifdef CHECK_TRAVEL_TIMES + // allocate and calculate the travel times + filteredroutetable = (aas_rt_route_t **) AAS_RT_GetClearedMemory(childcount * sizeof(aas_rt_route_t *)); + for (i = 0; i < childcount; i++) + filteredroutetable[i] = (aas_rt_route_t *) AAS_RT_GetClearedMemory(childcount * sizeof(aas_rt_route_t)); + + AAS_RT_CalculateRouteTable(filteredroutetable); + +#endif // CHECK_TRAVEL_TIMES + + // allocate for the temporary build local data + area_localinfos = (aas_area_buildlocalinfo_t **) AAS_RT_GetClearedMemory(childcount * sizeof(aas_area_buildlocalinfo_t *)); + + for (i = 0; i < childcount; i++) + { + srcarea = &(*aasworld).areas[filtered_areas[i]]; + srcsettings = &(*aasworld).areasettings[filtered_areas[i]]; + + // allocate memory for this area + area_localinfos[i] = (aas_area_buildlocalinfo_t *) AAS_RT_GetClearedMemory(sizeof(aas_area_buildlocalinfo_t)); + localinfo = area_localinfos[i]; + + for (j = 0; j < childcount; j++) + { + if (i == j) + { + continue; + } + +#ifdef CHECK_TRAVEL_TIMES + + // make sure travel time is reasonable + // Get the travel time from i to j + traveltime = (int)filteredroutetable[i][j].travel_time; + + if (!traveltime) + { + noroutecount++; + continue; + } + if (traveltime > MAX_LOCALTRAVELTIME) + { + continue; + } + +#endif // CHECK_TRAVEL_TIMES + + // Add it to the list + localinfo->visible[localinfo->numvisible++] = j; + totalcount++; + + if (localinfo->numvisible >= MAX_VISIBLE_AREAS) + { + botimport.Print(PRT_MESSAGE, "MAX_VISIBLE_AREAS exceeded, lower MAX_VISIBLE_RANGE\n"); + break; + } + } + } + + // now calculate the best list of locale's + + // allocate for the long-term child data + area_childlocaldata = (aas_area_childlocaldata_t **) AAS_RT_GetClearedMemory(childcount * sizeof(aas_area_childlocaldata_t *)); + + for (i = 0; i < childcount; i++) + { + area_childlocaldata[i] = (aas_area_childlocaldata_t *) AAS_RT_GetClearedMemory(sizeof(aas_area_childlocaldata_t)); + area_childlocaldata[i]->areanum = filtered_areas[i]; + } + + while (1) + { + bestchild = -1; + bestcount = 99999; + + // find the area with the least number of visible areas + for (i = 0; i < childcount; i++) + { + if (area_childlocaldata[i]->parentlink) + { + continue; // already has been allocated to a parent + + } + cnt = AAS_RT_GetValidVisibleAreasCount(area_localinfos[i], area_childlocaldata); + + if (cnt < bestcount) + { + bestcount = area_localinfos[i]->numvisible; + bestchild = i; + } + } + + if (bestchild < 0) + { + break; // our job is done + + + } + localinfo = area_localinfos[bestchild]; + + + // look through this area's list of visible areas, and pick the one with the most VALID visible areas + bestparent = bestchild; + + for (i = 0; i < localinfo->numvisible; i++) + { + if (area_childlocaldata[localinfo->visible[i]]->parentlink) + { + continue; // already has been allocated to a parent + + } + // calculate how many of children are valid + cnt = AAS_RT_GetValidVisibleAreasCount(area_localinfos[localinfo->visible[i]], area_childlocaldata); + + if (cnt > bestcount) + { + bestcount = cnt; + bestparent = localinfo->visible[i]; + } + } + + // now setup this parent, and assign all it's children + localinfo = area_localinfos[bestparent]; + + // we use all children now, not just valid ones + bestcount = localinfo->numvisible; + + area_parents[num_parents] = (aas_area_parent_t *) AAS_RT_GetClearedMemory(sizeof(aas_area_parent_t)); + thisparent = area_parents[num_parents]; + + thisparent->areanum = filtered_areas[bestparent]; + thisparent->children = (unsigned short int *) AAS_RT_GetClearedMemory((localinfo->numvisible + 1) * sizeof(unsigned short int)); + + // first, add itself to the list (yes, a parent is a child of itself) + child = area_childlocaldata[bestparent]; + AAS_RT_AddParentLink(child, num_parents, thisparent->numchildren); + thisparent->children[thisparent->numchildren++] = filtered_areas[bestparent]; + + // loop around all the parent's visible list, and make them children if they're aren't already assigned to a parent + for (i = 0; i < localinfo->numvisible; i++) + { + // create the childlocaldata + child = area_childlocaldata[localinfo->visible[i]]; + + // Ridah, only one parent per child in the new system + if (child->parentlink) + { + continue; // already has been allocated to a parent + + } + if (child->areanum != thisparent->areanum) + { + AAS_RT_AddParentLink(child, num_parents, thisparent->numchildren); + thisparent->children[thisparent->numchildren++] = filtered_areas[localinfo->visible[i]]; + } + } + + // now setup the list of children and the route-tables + for (i = 0; i < thisparent->numchildren; i++) + { + child = area_childlocaldata[-1 + rev_filtered_areas[thisparent->children[i]]]; + localinfo = area_localinfos[-1 + rev_filtered_areas[thisparent->children[i]]]; + + child->parentlink->routeindexes = (unsigned short int *) AAS_RT_GetClearedMemory(thisparent->numchildren * sizeof(unsigned short int)); + + // now setup the indexes + for (j = 0; j < thisparent->numchildren; j++) + { + // find this child in our list of visibles + if (j == child->parentlink->childindex) + { + continue; + } + + for (k = 0; k < localinfo->numvisible; k++) + { + if (thisparent->children[j] == filtered_areas[localinfo->visible[k]]) // found a match + { + child->parentlink->routeindexes[j] = (unsigned short int)k; + break; + } + } + + if (k == localinfo->numvisible) // didn't find it, so add it to our list + { + if (localinfo->numvisible >= MAX_VISIBLE_AREAS) + { + botimport.Print(PRT_MESSAGE, "MAX_VISIBLE_AREAS exceeded, lower MAX_VISIBLE_RANGE\n"); + } + else + { + localinfo->visible[localinfo->numvisible] = -1 + rev_filtered_areas[thisparent->children[j]]; + child->parentlink->routeindexes[j] = (unsigned short int)localinfo->numvisible; + localinfo->numvisible++; + } + } + } + } + + num_parents++; + } + + // place all the visible areas from each child, into their childlocaldata route-table + for (i = 0; i < childcount; i++) + { + localinfo = area_localinfos[i]; + child = area_childlocaldata[i]; + + child->numlocal = localinfo->numvisible; + child->localroutes = (aas_rt_route_t *) AAS_RT_GetClearedMemory(localinfo->numvisible * sizeof(aas_rt_route_t)); + + for (j = 0; j < localinfo->numvisible; j++) + { + child->localroutes[j] = filteredroutetable[i][localinfo->visible[j]]; + } + + child->parentroutes = (aas_rt_route_t *) AAS_RT_GetClearedMemory(num_parents * sizeof(aas_rt_route_t)); + + for (j = 0; j < num_parents; j++) + { + child->parentroutes[j] = filteredroutetable[i][-1 + rev_filtered_areas[area_parents[j]->areanum]]; + } + } + + // build the visibleParents lists + visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory(num_parents * sizeof(unsigned short int)); + for (i = 0; i < num_parents; i++) + { + area_parents[i]->numVisibleParents = 0; + + for (j = 0; j < num_parents; j++) + { + if (i == j) + { + continue; + } + + if (!AAS_inPVS((*aasworld).areas[area_parents[i]->areanum].center, (*aasworld).areas[area_parents[j]->areanum].center)) + { + continue; + } + + visibleParents[area_parents[i]->numVisibleParents] = j; + area_parents[i]->numVisibleParents++; + } + + // now copy the list over to the current src area + area_parents[i]->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory(area_parents[i]->numVisibleParents * sizeof(unsigned short int)); + memcpy(area_parents[i]->visibleParents, visibleParents, area_parents[i]->numVisibleParents * sizeof(unsigned short int)); + + } + AAS_RT_FreeMemory(visibleParents); + + // before we free the main childlocaldata, go through and assign the aas_area's to their appropriate childlocaldata + // this would require modification of the aas_area_t structure, so for now, we'll just place them in a global array, for external reference + +// aasworld->routetable->area_childlocaldata_list = (aas_area_childlocaldata_t **) AAS_RT_GetClearedMemory( (*aasworld).numareas * sizeof(aas_area_childlocaldata_t *) ); +// for (i=0; iroutetable->area_childlocaldata_list[filtered_areas[i]] = area_childlocaldata[i]; +// } + + // copy the list of parents to a global structure for now (should eventually go into the (*aasworld) structure +// aasworld->routetable->area_parents_global = (aas_area_parent_t **) AAS_RT_GetClearedMemory( num_parents * sizeof(aas_area_parent_t *) ); +// memcpy( aasworld->routetable->area_parents_global, area_parents, num_parents * sizeof(aas_area_parent_t *) ); + + // ................................................ + // Convert the data into the correct format + { + aas_rt_t *rt; + aas_rt_child_t *child; + aas_rt_parent_t *parent; + aas_rt_parent_link_t *plink; + unsigned short int *psi; + + aas_area_childlocaldata_t *chloc; + aas_area_parent_t *apar; + aas_parent_link_t *oplink; + + int localRoutesCount, parentRoutesCount, parentChildrenCount, visibleParentsCount, parentLinkCount, routeIndexesCount; + + rt = (*aasworld).routetable; + localRoutesCount = 0; + parentRoutesCount = 0; + parentChildrenCount = 0; + visibleParentsCount = 0; + parentLinkCount = 0; + routeIndexesCount = 0; + + // areaChildIndexes + rt->areaChildIndexes = (unsigned short int *) AAS_RT_GetClearedMemory((*aasworld).numareas * sizeof(unsigned short int)); + for (i = 0; i < childcount; i++) + { + rt->areaChildIndexes[filtered_areas[i]] = i + 1; + } + + // children + rt->numChildren = childcount; + rt->children = (aas_rt_child_t *) AAS_RT_GetClearedMemory(rt->numChildren * sizeof(aas_rt_child_t)); + child = rt->children; + for (i = 0; i < childcount; i++, child++) + { + chloc = area_childlocaldata[i]; + + child->areanum = chloc->areanum; + child->numParentLinks = AAS_RT_NumParentLinks(chloc); + + child->startParentLinks = parentLinkCount; + + parentLinkCount += child->numParentLinks; + } + + // parents + rt->numParents = num_parents; + rt->parents = (aas_rt_parent_t *) AAS_RT_GetClearedMemory(rt->numParents * sizeof(aas_rt_parent_t)); + parent = rt->parents; + for (i = 0; i < num_parents; i++, parent++) + { + apar = area_parents[i]; + + parent->areanum = apar->areanum; + parent->numParentChildren = apar->numchildren; + parent->numVisibleParents = apar->numVisibleParents; + + parent->startParentChildren = parentChildrenCount; + parent->startVisibleParents = visibleParentsCount; + + parentChildrenCount += parent->numParentChildren; + visibleParentsCount += parent->numVisibleParents; + } + + // parentChildren + rt->numParentChildren = parentChildrenCount; + rt->parentChildren = (unsigned short int *) AAS_RT_GetClearedMemory(parentChildrenCount * sizeof(unsigned short int)); + psi = rt->parentChildren; + for (i = 0; i < num_parents; i++) + { + apar = area_parents[i]; + for (j = 0; j < apar->numchildren; j++, psi++) + { + *psi = apar->children[j]; + } + } + + // visibleParents + rt->numVisibleParents = visibleParentsCount; + rt->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory(rt->numVisibleParents * sizeof(unsigned short int)); + psi = rt->visibleParents; + for (i = 0; i < num_parents; i++) + { + apar = area_parents[i]; + for (j = 0; j < apar->numVisibleParents; j++, psi++) + { + *psi = apar->visibleParents[j]; + } + } + + // parentLinks + rt->numParentLinks = parentLinkCount; + rt->parentLinks = (aas_rt_parent_link_t *) AAS_RT_GetClearedMemory(parentLinkCount * sizeof(aas_rt_parent_link_t)); + plink = rt->parentLinks; + for (i = 0; i < childcount; i++) + { + chloc = area_childlocaldata[i]; + for (oplink = chloc->parentlink; oplink; plink++, oplink = oplink->next) + { + plink->childIndex = oplink->childindex; + plink->parent = oplink->parent; + } + } + + } + // ................................................ + + // write the newly created table + AAS_RT_WriteRouteTable(); + + + botimport.Print(PRT_MESSAGE, "Child Areas: %i\nTotal Parents: %i\nAverage VisAreas: %i\n", (int)childcount, num_parents, (int)(childcount / num_parents)); + botimport.Print(PRT_MESSAGE, "NoRoute Ratio: %i%%\n", (int)((100.0 * noroutecount) / (1.0 * childcount * childcount))); + + memoryend = memorycount; + + // clear allocated memory + +// causes crashes in route-caching +//#ifdef USE_ROUTECACHE +// AAS_FreeRoutingCaches(); +//#endif + + for (i = 0; i < childcount; i++) + { + AAS_RT_FreeMemory(area_localinfos[i]); +#ifdef CHECK_TRAVEL_TIMES + AAS_RT_FreeMemory(filteredroutetable[i]); +#endif + } + + { + aas_parent_link_t *next, *trav; + + // kill the client areas + for (i = 0; i < childcount; i++) + { + // kill the parent links + next = area_childlocaldata[i]->parentlink; + // TTimo gcc: suggests () around assignment used as truth value + while ((trav = next)) + { + next = next->next; + + AAS_RT_FreeMemory(trav->routeindexes); + AAS_RT_FreeMemory(trav); + + } + + AAS_RT_FreeMemory(area_childlocaldata[i]->localroutes); + AAS_RT_FreeMemory(area_childlocaldata[i]->parentroutes); + AAS_RT_FreeMemory(area_childlocaldata[i]); + } + + // kill the parents + for (i = 0; i < num_parents; i++) + { + AAS_RT_FreeMemory(area_parents[i]->children); + AAS_RT_FreeMemory(area_parents[i]->visibleParents); + AAS_RT_FreeMemory(area_parents[i]); + } + } + + AAS_RT_FreeMemory(area_localinfos); + AAS_RT_FreeMemory(area_childlocaldata); + AAS_RT_FreeMemory(filtered_areas); + AAS_RT_FreeMemory(rev_filtered_areas); +#ifdef CHECK_TRAVEL_TIMES + AAS_RT_FreeMemory(filteredroutetable); +#endif + + // check how much memory we've used, and intend to keep + AAS_RT_PrintMemoryUsage(); + + botimport.Print(PRT_MESSAGE, "Route-Table Permanent Memory Usage: %i\n", memorycount); + botimport.Print(PRT_MESSAGE, "Route-Table Calculation Usage: %i\n", memoryend + cachememory); + botimport.Print(PRT_MESSAGE, "---------------------------------\n"); +} + +//=========================================================================== +// free permanent memory used by route-table system +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_ShutdownRouteTable(void) +{ + if (!aasworld->routetable) + { + return; + } + + // free the dynamic lists + AAS_RT_FreeMemory(aasworld->routetable->areaChildIndexes); + AAS_RT_FreeMemory(aasworld->routetable->children); + AAS_RT_FreeMemory(aasworld->routetable->parents); + AAS_RT_FreeMemory(aasworld->routetable->parentChildren); + AAS_RT_FreeMemory(aasworld->routetable->visibleParents); +// AAS_RT_FreeMemory( aasworld->routetable->localRoutes ); +// AAS_RT_FreeMemory( aasworld->routetable->parentRoutes ); + AAS_RT_FreeMemory(aasworld->routetable->parentLinks); +// AAS_RT_FreeMemory( aasworld->routetable->routeIndexes ); +// AAS_RT_FreeMemory( aasworld->routetable->parentTravelTimes ); + + // kill the table + AAS_RT_FreeMemory(aasworld->routetable); + aasworld->routetable = NULL; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_rt_parent_link_t *AAS_RT_GetFirstParentLink(aas_rt_child_t *child) +{ + return &aasworld->routetable->parentLinks[child->startParentLinks]; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_rt_child_t *AAS_RT_GetChild(int areanum) +{ + int i; + + i = (int)aasworld->routetable->areaChildIndexes[areanum] - 1; + + if (i >= 0) + { + return &aasworld->routetable->children[i]; + } + else + { + return NULL; + } +} + +//=========================================================================== +// returns a route between the areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum); +aas_rt_route_t *AAS_RT_GetRoute(int srcnum, vec3_t origin, int destnum) +{ + #define GETROUTE_NUMROUTES 64 + static aas_rt_route_t routes[GETROUTE_NUMROUTES]; // cycle through these, so we don't overlap + static int routeIndex = 0; + aas_rt_route_t *thisroute; + int reach, traveltime; + aas_rt_t *rt; + static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA); //----(SA) modified since slime is no longer deadly +// static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); + + if (!(rt = aasworld->routetable)) // no route table present + { + return NULL; + } + + if (disable_routetable) + { + return NULL; + } + + if (++routeIndex >= GETROUTE_NUMROUTES) + { + routeIndex = 0; + } + + thisroute = &routes[routeIndex]; + + if (AAS_AreaRouteToGoalArea(srcnum, origin, destnum, tfl, &traveltime, &reach)) + { + thisroute->reachable_index = reach; + thisroute->travel_time = traveltime; + return thisroute; + } + else + { + return NULL; + } +} + +//=========================================================================== +// draws the route-table from src to dest +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#include "../game/be_ai_goal.h" +int BotGetReachabilityToGoal(vec3_t origin, int areanum, int entnum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags); + +void AAS_RT_ShowRoute(vec3_t srcpos, int srcnum, int destnum) +{ +#ifdef DEBUG +#define MAX_RT_AVOID_REACH 1 + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_ShowAreaPolygons(srcnum, 1, qtrue); + AAS_ShowAreaPolygons(destnum, 4, qtrue); + { + static int lastgoalareanum, lastareanum; + static int avoidreach[MAX_RT_AVOID_REACH]; + static float avoidreachtimes[MAX_RT_AVOID_REACH]; + static int avoidreachtries[MAX_RT_AVOID_REACH]; + int reachnum; + bot_goal_t goal; + aas_reachability_t reach; + + goal.areanum = destnum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + reachnum = BotGetReachabilityToGoal(srcpos, srcnum, -1, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT | TFL_FUNCBOB, TFL_DEFAULT | TFL_FUNCBOB); + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ShowReachability(&reach); + } +#endif +} + +/* +================= +AAS_RT_GetHidePos + + "src" is hiding ent, "dest" is the enemy +================= +*/ +qboolean AAS_RT_GetHidePos(vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos) +{ + return 0; +} + +/* +================= +AAS_RT_GetReachabilityIndex +================= +*/ +int AAS_RT_GetReachabilityIndex(int areanum, int reachIndex) +{ +// return (*aasworld).areasettings[areanum].firstreachablearea + reachIndex; + return reachIndex; +} diff --git a/src/botlib/be_aas_routetable.h b/src/botlib/be_aas_routetable.h new file mode 100644 index 000000000..c7aeef9fc --- /dev/null +++ b/src/botlib/be_aas_routetable.h @@ -0,0 +1,169 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_routetable.h + * @author Ridah + * @brief Area Awareness System, Route-table defines + */ + +#ifndef RT_DEFINED + +#define RT_DEFINED + +#define RTBID (('B' << 24) + ('T' << 16) + ('R' << 8) + 'X') +#define RTBVERSION 17 + +#define RTB_BADTRAVELFLAGS (TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA) //----(SA) modified since slime is no longer deadly +//#define RTB_BADTRAVELFLAGS (TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA) + +#define MAX_VISIBLE_AREAS 1024 // going over this limit will result in excessive memory usage, try and keep RANGE low enough so this limit won't be reached +#define MAX_LOCALTRAVELTIME 60 // use this to tweak memory usage (reduces parent count, increases local count (and cpu usage) - find a balance) +#define MAX_PARENTS 8192 + +extern int disable_routetable; + +//.................................................................... +// Permanent structures (in order of highest to lowest count) +typedef struct +{ + unsigned short int reachable_index; // reachability index (from this area's first reachability link in the world) to head for to get to the destination + unsigned short int travel_time; // travel time (!) +} aas_rt_route_t; + +typedef struct +{ + unsigned short int parent; // parent we belong to + unsigned short int childIndex; // our index in the parent's list of children +// unsigned short int numRouteIndexes; +// int startRouteIndexes; +} aas_rt_parent_link_t; + +typedef struct +{ + unsigned short int areanum; +// int numLocalRoutes; +// int startLocalRoutes; +// int numParentRoutes; +// int startParentRoutes; + int numParentLinks; + int startParentLinks; +} aas_rt_child_t; + +typedef struct +{ + unsigned short int areanum; // out area number in the global list + int numParentChildren; + int startParentChildren; + int numVisibleParents; + int startVisibleParents; // list of other parents that we can see (used for fast hide/retreat checks) +// int startParentTravelTimes; +} aas_rt_parent_t; + +// this is what each aasworld attaches itself to +typedef struct +{ + unsigned short int *areaChildIndexes; // each aas area that is part of the Route-Table has a pointer here to their position in the list of children + + int numChildren; + aas_rt_child_t *children; + + int numParents; + aas_rt_parent_t *parents; + + int numParentChildren; + unsigned short int *parentChildren; + + int numVisibleParents; + unsigned short int *visibleParents; + +// int numLocalRoutes; +// aas_rt_route_t *localRoutes; // the list of routes to all other local areas + +// int numParentRoutes; +// unsigned char *parentRoutes; // reachability to each other parent, as an offset from our first reachability + + int numParentLinks; + aas_rt_parent_link_t *parentLinks; // links from each child to the parent's it belongs to + +// int numParentTravelTimes; +// unsigned short int *parentTravelTimes; // travel times between all parent areas + +// int numRouteIndexes; +// unsigned short int *routeIndexes; // each parentLink has a list within here, which + // contains the local indexes of each child that + // belongs to the parent, within the source child's + // localroutes +} aas_rt_t; + +//.................................................................... +// Temp structures used only during route-table contruction +typedef struct +{ + unsigned short int numvisible; // number of areas that are visible and within range + unsigned short int + visible[MAX_VISIBLE_AREAS]; // list of area indexes of visible and within range areas +} aas_area_buildlocalinfo_t; + +typedef struct aas_parent_link_s +{ + unsigned short int parent; // parent we belong to + unsigned short int childindex; // our index in the parent's list of children + unsigned short int *routeindexes; // for this parent link, list the children that fall under that parent, and their associated indexes in our localroutes table + struct aas_parent_link_s *next; +} aas_parent_link_t; + +typedef struct +{ + unsigned short int areanum; + unsigned short int numlocal; + aas_parent_link_t *parentlink; // linked list of parents that we belong to + aas_rt_route_t *localroutes; // the list of routes to all other local areas + aas_rt_route_t *parentroutes; // the list of routes to all other parent areas +} aas_area_childlocaldata_t; + +typedef struct +{ + unsigned short int areanum; // out area number in the global list + unsigned short int numchildren; + unsigned short int *children; + unsigned short int numVisibleParents; + unsigned short int *visibleParents; // list of other parents that we can see (used for fast hide/retreat checks) +} aas_area_parent_t; + +#endif // RT_DEFINED + +//.................................................................... + +void AAS_RT_BuildRouteTable(void); +void AAS_RT_ShowRoute(vec3_t srcpos, int srcnum, int destnum); +aas_rt_route_t *AAS_RT_GetRoute(int srcnum, vec3_t origin, int destnum); +void AAS_RT_ShutdownRouteTable(void); +qboolean AAS_RT_GetHidePos(vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos); +int AAS_RT_GetReachabilityIndex(int areanum, int reachIndex); diff --git a/src/botlib/be_aas_sample.c b/src/botlib/be_aas_sample.c new file mode 100644 index 000000000..9d8af4346 --- /dev/null +++ b/src/botlib/be_aas_sample.c @@ -0,0 +1,1655 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_sample.c + * @brief AAS environment sampling + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define AAS_SAMPLE_DEBUG + +#define BBOX_NORMAL_EPSILON 0.001 + +#define ON_EPSILON 0 //0.0005 + +#define TRACEPLANE_EPSILON 0.125 + +typedef struct aas_tracestack_s +{ + vec3_t start; //start point of the piece of line to trace + vec3_t end; //end point of the piece of line to trace + int planenum; //last plane used as splitter + int nodenum; //node found after splitting with planenum +} aas_tracestack_t; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs) +{ + int index; + //bounding box size for each presence type + vec3_t boxmins[3] = { { 0, 0, 0 }, { -18, -18, -24 }, { -18, -18, -24 } }; + vec3_t boxmaxs[3] = { { 0, 0, 0 }, { 18, 18, 48 }, { 18, 18, 24 } }; + + if (presencetype == PRESENCE_NORMAL) + { + index = 1; + } + else if (presencetype == PRESENCE_CROUCH) + { + index = 2; + } + else + { + botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n"); + index = 2; + } //end if + VectorCopy(boxmins[index], mins); + VectorCopy(boxmaxs[index], maxs); +} //end of the function AAS_PresenceTypeBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkHeap(void) +{ + int i, max_aaslinks; + + max_aaslinks = (*aasworld).linkheapsize; + //if there's no link heap present + if (!(*aasworld).linkheap) + { + max_aaslinks = (int) 4096; //LibVarValue("max_aaslinks", "4096"); + if (max_aaslinks < 0) + { + max_aaslinks = 0; + } + (*aasworld).linkheapsize = max_aaslinks; + (*aasworld).linkheap = (aas_link_t *) GetHunkMemory(max_aaslinks * sizeof(aas_link_t)); + } + else + { + // just clear the memory + memset((*aasworld).linkheap, 0, (*aasworld).linkheapsize * sizeof(aas_link_t)); + } + //link the links on the heap + (*aasworld).linkheap[0].prev_ent = NULL; + (*aasworld).linkheap[0].next_ent = &(*aasworld).linkheap[1]; + for (i = 1; i < max_aaslinks - 1; i++) + { + (*aasworld).linkheap[i].prev_ent = &(*aasworld).linkheap[i - 1]; + (*aasworld).linkheap[i].next_ent = &(*aasworld).linkheap[i + 1]; + } //end for + (*aasworld).linkheap[max_aaslinks - 1].prev_ent = &(*aasworld).linkheap[max_aaslinks - 2]; + (*aasworld).linkheap[max_aaslinks - 1].next_ent = NULL; + //pointer to the first free link + (*aasworld).freelinks = &(*aasworld).linkheap[0]; +} //end of the function AAS_InitAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkHeap(void) +{ + if ((*aasworld).linkheap) + { + FreeMemory((*aasworld).linkheap); + } + (*aasworld).linkheap = NULL; + (*aasworld).linkheapsize = 0; +} //end of the function AAS_FreeAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_AllocAASLink(void) +{ + aas_link_t *link; + + link = (*aasworld).freelinks; + if (!link) + { + botimport.Print(PRT_FATAL, "empty aas link heap\n"); + return NULL; + } //end if + if ((*aasworld).freelinks) + { + (*aasworld).freelinks = (*aasworld).freelinks->next_ent; + } + if ((*aasworld).freelinks) + { + (*aasworld).freelinks->prev_ent = NULL; + } + return link; +} //end of the function AAS_AllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DeAllocAASLink(aas_link_t *link) +{ + if ((*aasworld).freelinks) + { + (*aasworld).freelinks->prev_ent = link; + } + link->prev_ent = NULL; + link->next_ent = (*aasworld).freelinks; + link->prev_area = NULL; + link->next_area = NULL; + (*aasworld).freelinks = link; +} //end of the function AAS_DeAllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkedEntities(void) +{ + if (!(*aasworld).loaded) + { + return; + } + if ((*aasworld).arealinkedentities) + { + FreeMemory((*aasworld).arealinkedentities); + } + (*aasworld).arealinkedentities = (aas_link_t **) GetClearedHunkMemory( + (*aasworld).numareas * sizeof(aas_link_t *)); +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkedEntities(void) +{ + if ((*aasworld).arealinkedentities) + { + FreeMemory((*aasworld).arealinkedentities); + } + (*aasworld).arealinkedentities = NULL; +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// returns the AAS area the point is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointAreaNum(vec3_t inPoint) +{ + int nodenum; + vec_t dist; + aas_node_t *node; + aas_plane_t *plane = NULL; + vec3_t point; +// aas_plane_t *closestPlane; +// aas_node_t *closestNode; +// float closestDist; +// static int recursion = 0; + + VectorCopy(inPoint, point); + + if (!(*aasworld).loaded) + { + botimport.Print(PRT_ERROR, "AAS_PointAreaNum: aas not loaded\n"); + return 0; + } //end if + + //start with node 1 because node zero is a dummy used for solid leafs + nodenum = 1; + +//nodesearch: + +// recursion++; +// closestDist = 128.0f; +// closestPlane = NULL; + while (nodenum > 0) + { +// botimport.Print(PRT_MESSAGE, "[%d]", nodenum); +#ifdef AAS_SAMPLE_DEBUG + if (nodenum >= (*aasworld).numnodes) + { + botimport.Print(PRT_ERROR, "nodenum = %d >= (*aasworld).numnodes = %d\n", nodenum, (*aasworld).numnodes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + node = &(*aasworld).nodes[nodenum]; +#ifdef AAS_SAMPLE_DEBUG + if (node->planenum < 0 || node->planenum >= (*aasworld).numplanes) + { + botimport.Print(PRT_ERROR, "node->planenum = %d >= (*aasworld).numplanes = %d\n", node->planenum, (*aasworld).numplanes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + plane = &(*aasworld).planes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + // + if (dist > 0) + { + nodenum = node->children[0]; + } + else + { + nodenum = node->children[1]; + } +/* // check for closest plane + if (dist > 0 && Q_fabs(dist) < Q_fabs(closestDist)) { + closestPlane = plane; + closestDist = dist; + closestNode = node; + } +*/ } //end while + if (!nodenum) + { +#ifdef AAS_SAMPLE_DEBUG + botimport.Print(PRT_MESSAGE, "in solid\n"); +#endif //AAS_SAMPLE_DEBUG +/* + // RF (HACK), if we failed, move us to the other side of the closest plane + if ((recursion < 10) && closestPlane) { + dist = closestDist; + node = closestNode; + plane = closestPlane; + if (dist > 0) { + VectorMA( point, -(dist+1), plane->normal, point ); + } else { + VectorMA( point, -(dist-1), plane->normal, point ); + } + // take the opposite side since we have moved the point there now + if (dist <= 0) nodenum = node->children[0]; + else nodenum = node->children[1]; + // + goto nodesearch; + //return AAS_PointAreaNum( point ); + } +*/ +// recursion = 0; + return 0; + } //end if +// recursion = 0; + return -nodenum; +} //end of the function AAS_PointAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCluster(int areanum) +{ + if (areanum <= 0 || areanum >= (*aasworld).numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaCluster: invalid area number\n"); + return 0; + } //end if + return (*aasworld).areasettings[areanum].cluster; +} //end of the function AAS_AreaCluster +//=========================================================================== +// returns the presence types of the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaPresenceType(int areanum) +{ + if (!(*aasworld).loaded) + { + return 0; + } + if (areanum <= 0 || areanum >= (*aasworld).numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaPresenceType: invalid area number\n"); + return 0; + } //end if + return (*aasworld).areasettings[areanum].presencetype; +} //end of the function AAS_AreaPresenceType +//=========================================================================== +// returns the presence type at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointPresenceType(vec3_t point) +{ + int areanum; + + if (!(*aasworld).loaded) + { + return 0; + } + + areanum = AAS_PointAreaNum(point); + if (!areanum) + { + return PRESENCE_NONE; + } + return (*aasworld).areasettings[areanum].presencetype; +} //end of the function AAS_PointPresenceType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_AreaEntityCollision(int areanum, vec3_t start, vec3_t end, + int presencetype, int passent, aas_trace_t *trace) +{ + int collision; + vec3_t boxmins, boxmaxs; + aas_link_t *link; + bsp_trace_t bsptrace; + + AAS_PresenceTypeBoundingBox(presencetype, boxmins, boxmaxs); + + memset(&bsptrace, 0, sizeof(bsp_trace_t)); //make compiler happy + //assume no collision + bsptrace.fraction = 1; + collision = qfalse; + for (link = (*aasworld).arealinkedentities[areanum]; link; link = link->next_ent) + { + //ignore the pass entity + if (link->entnum == passent) + { + continue; + } + // + if (AAS_EntityCollision(link->entnum, start, boxmins, boxmaxs, end, + CONTENTS_SOLID | CONTENTS_PLAYERCLIP, &bsptrace)) + { + collision = qtrue; + } //end if + } //end for + if (collision) + { + trace->startsolid = bsptrace.startsolid; + trace->ent = bsptrace.ent; + VectorCopy(bsptrace.endpos, trace->endpos); + trace->area = 0; + trace->planenum = 0; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_AreaEntityCollision +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, + int passent) +{ + int side, nodenum, tmpplanenum; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid, v1, v2; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_trace_t trace; + + //clear the trace structure + memset(&trace, 0, sizeof(aas_trace_t)); + trace.ent = ENTITYNUM_NONE; + + if (!(*aasworld).loaded) + { + return trace; + } + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + tstack_p++; + //nothing was hit + trace.startsolid = qfalse; + trace.fraction = 1.0; + //endpos is the end of the line + VectorCopy(end, trace.endpos); + //nothing hit + trace.ent = ENTITYNUM_NONE; + trace.area = 0; + trace.planenum = 0; + return trace; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > (*aasworld).numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: -nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); +/* //if can't enter the area because it hasn't got the right presence type + if (!((*aasworld).areasettings[-nodenum].presencetype & presencetype)) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + // Gordon: NOTE, uninitialized var: v1 + VectorSubtract(end, start, v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = ENTITYNUM_NONE; + trace.area = -nodenum; +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &(*aasworld).planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; + return trace; + } //end if + else +*/ { + if (passent >= 0) + { + if (AAS_AreaEntityCollision(-nodenum, tstack_p->start, + tstack_p->end, presencetype, passent, + &trace)) + { + if (!trace.startsolid) + { + VectorSubtract(end, start, v1); + VectorSubtract(trace.endpos, start, v2); + trace.fraction = VectorLength(v2) / VectorLength(v1); + } //end if + return trace; + } //end if + } //end if + } //end else + trace.lastarea = -nodenum; + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + + // Gordon: NOTE, uninitialized var: v1 + VectorSubtract(end, start, v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = ENTITYNUM_NONE; + trace.area = 0; //hit solid leaf +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &(*aasworld).planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) + { + trace.planenum ^= 1; + } + return trace; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > (*aasworld).numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &(*aasworld).nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &(*aasworld).planes[aasnode->planenum]; + + switch (plane->type) /*FIXME: wtf doesn't this work? obviously the axial node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + { + default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; + break; + } //end default + } //end switch + + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) + { + frac = (front + TRACEPLANE_EPSILON) / (front - back); + } + else + { + frac = (front - TRACEPLANE_EPSILON) / (front - back); + } + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if ((front >= -ON_EPSILON && back >= -ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if ((front < ON_EPSILON && back < ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + // + if (frac < 0) + { + frac = 0.001; //0 + } + else if (frac > 1) + { + frac = 0.999; //1 + } + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end else + } //end while +// return trace; +} //end of the function AAS_TraceClientBBox +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas) +{ + int side, nodenum, tmpplanenum; + int numareas; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + + numareas = 0; + areas[0] = 0; + if (!(*aasworld).loaded) + { + return numareas; + } + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + return numareas; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > (*aasworld).numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: -nodenum = %d out of range\n", -nodenum); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + areas[numareas] = -nodenum; + if (points) + { + VectorCopy(tstack_p->start, points[numareas]); + } + numareas++; + if (numareas >= maxareas) + { + return numareas; + } + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + continue; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > (*aasworld).numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: nodenum out of range\n"); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &(*aasworld).nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &(*aasworld).planes[aasnode->planenum]; + + switch (plane->type) /*FIXME: wtf doesn't this work? obviously the node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + { + default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; + break; + } //end default + } //end switch + + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if (front > 0 && back > 0) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if (front <= 0 && back <= 0) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) + { + frac = (front) / (front - back); + } + else + { + frac = (front) / (front - back); + } + if (frac < 0) + { + frac = 0; + } + else if (frac > 1) + { + frac = 1; + } + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end else + } //end while +// return numareas; +} //end of the function AAS_TraceAreas +//=========================================================================== +// a simple cross product +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// void AAS_OrthogonalToVectors(vec3_t v1, vec3_t v2, vec3_t res) +#define AAS_OrthogonalToVectors(v1, v2, res) \ + (res)[0] = ((v1)[1] * (v2)[2]) - ((v1)[2] * (v2)[1]); \ + (res)[1] = ((v1)[2] * (v2)[0]) - ((v1)[0] * (v2)[2]); \ + (res)[2] = ((v1)[0] * (v2)[1]) - ((v1)[1] * (v2)[0]); +//=========================================================================== +// tests if the given point is within the face boundaries +// +// Parameter: face : face to test if the point is in it +// pnormal : normal of the plane to use for the face +// point : point to test if inside face boundaries +// Returns: qtrue if the point is within the face boundaries +// Changes Globals: - +//=========================================================================== +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec3_t v0; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; +#ifdef AAS_SAMPLE_DEBUG + int lastvertex = 0; +#endif //AAS_SAMPLE_DEBUG + + if (!(*aasworld).loaded) + { + return qfalse; + } + + for (i = 0; i < face->numedges; i++) + { + edgenum = (*aasworld).edgeindex[face->firstedge + i]; + edge = &(*aasworld).edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + VectorCopy((*aasworld).vertexes[edge->v[firstvertex]], v0); + //edge vector + VectorSubtract((*aasworld).vertexes[edge->v[!firstvertex]], v0, edgevec); + // +#ifdef AAS_SAMPLE_DEBUG + if (lastvertex && lastvertex != edge->v[firstvertex]) + { + botimport.Print(PRT_MESSAGE, "winding not counter clockwise\n"); + } //end if + lastvertex = edge->v[!firstvertex]; +#endif //AAS_SAMPLE_DEBUG + //vector from first edge point to point possible in face + VectorSubtract(point, v0, pointvec); + //get a vector pointing inside the face orthogonal to both the + //edge vector and the normal vector of the plane the face is in + //this vector defines a plane through the origin (first vertex of + //edge) and through both the edge vector and the normal vector + //of the plane + AAS_OrthogonalToVectors(edgevec, pnormal, sepnormal); + //check on wich side of the above plane the point is + //this is done by checking the sign of the dot product of the + //vector orthogonal vector from above and the vector from the + //origin (first vertex of edge) to the point + //if the dotproduct is smaller than zero the point is outside the face + if (DotProduct(pointvec, sepnormal) < -epsilon) + { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_InsideFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec_t *v1, *v2; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; + aas_plane_t *plane; + aas_face_t *face; + + if (!(*aasworld).loaded) + { + return qfalse; + } + + face = &(*aasworld).faces[facenum]; + plane = &(*aasworld).planes[face->planenum]; + // + for (i = 0; i < face->numedges; i++) + { + edgenum = (*aasworld).edgeindex[face->firstedge + i]; + edge = &(*aasworld).edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + v1 = (*aasworld).vertexes[edge->v[firstvertex]]; + v2 = (*aasworld).vertexes[edge->v[!firstvertex]]; + //edge vector + VectorSubtract(v2, v1, edgevec); + //vector from first edge point to point possible in face + VectorSubtract(point, v1, pointvec); + // + CrossProduct(edgevec, plane->normal, sepnormal); + // + if (DotProduct(pointvec, sepnormal) < -epsilon) + { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_PointInsideFace +//=========================================================================== +// returns the ground face the given point is above in the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point) +{ + int i, facenum; + vec3_t up = { 0, 0, 1 }; + vec3_t normal; + aas_area_t *area; + aas_face_t *face; + + if (!(*aasworld).loaded) + { + return NULL; + } + + area = &(*aasworld).areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = (*aasworld).faceindex[area->firstface + i]; + face = &(*aasworld).faces[abs(facenum)]; + //if this is a ground face + if (face->faceflags & FACE_GROUND) + { + //get the up or down normal + if ((*aasworld).planes[face->planenum].normal[2] < 0) + { + VectorNegate(up, normal); + } + else + { + VectorCopy(up, normal); + } + //check if the point is in the face + if (AAS_InsideFace(face, normal, point, 0.01)) + { + return face; + } + } //end if + } //end for + return NULL; +} //end of the function AAS_AreaGroundFace +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FacePlane(int facenum, vec3_t normal, float *dist) +{ + aas_plane_t *plane; + + plane = &(*aasworld).planes[(*aasworld).faces[facenum].planenum]; + VectorCopy(plane->normal, normal); + *dist = plane->dist; +} //end of the function AAS_FacePlane +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face, *firstface = NULL; + + if (!(*aasworld).loaded) + { + return NULL; + } + + //if started in solid no face was hit + if (trace->startsolid) + { + return NULL; + } + //trace->lastarea is the last area the trace was in + area = &(*aasworld).areas[trace->lastarea]; + //check which face the trace.endpos was in + for (i = 0; i < area->numfaces; i++) + { + facenum = (*aasworld).faceindex[area->firstface + i]; + face = &(*aasworld).faces[abs(facenum)]; + //if the face is in the same plane as the trace end point + if ((face->planenum & ~1) == (trace->planenum & ~1)) + { + //firstface is used for optimization, if theres only one + //face in the plane then it has to be the good one + //if there are more faces in the same plane then always + //check the one with the fewest edges first +/* if (firstface) + { + if (firstface->numedges < face->numedges) + { + if (AAS_InsideFace(firstface, + (*aasworld).planes[face->planenum].normal, trace->endpos)) + { + return firstface; + } //end if + firstface = face; + } //end if + else + { + if (AAS_InsideFace(face, + (*aasworld).planes[face->planenum].normal, trace->endpos)) + { + return face; + } //end if + } //end else + } //end if + else + { + firstface = face; + } //end else*/ + if (AAS_InsideFace(face, + (*aasworld).planes[face->planenum].normal, trace->endpos, 0.01)) + { + return face; + } + } //end if + } //end for + return firstface; +} //end of the function AAS_TraceEndFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxOnPlaneSide2(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +{ + int i, sides; + float dist1, dist2; + vec3_t corners[2]; + + for (i = 0; i < 3; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = absmins[i]; + corners[1][i] = absmaxs[i]; + } //end if + else + { + corners[1][i] = absmins[i]; + corners[0][i] = absmaxs[i]; + } //end else + } //end for + dist1 = DotProduct(p->normal, corners[0]) - p->dist; + dist2 = DotProduct(p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + { + sides = 1; + } + if (dist2 < 0) + { + sides |= 2; + } + + return sides; +} //end of the function AAS_BoxOnPlaneSide2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//int AAS_BoxOnPlaneSide(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +#define AAS_BoxOnPlaneSide(absmins, absmaxs, p) ( \ + ((p)->type < 3) ? \ + ( \ + ((p)->dist <= (absmins)[(p)->type]) ? \ + ( \ + 1 \ + ) \ + : \ + ( \ + ((p)->dist >= (absmaxs)[(p)->type]) ? \ + ( \ + 2 \ + ) \ + : \ + ( \ + 3 \ + ) \ + ) \ + ) \ + : \ + ( \ + AAS_BoxOnPlaneSide2((absmins), (absmaxs), (p)) \ + ) \ + ) //end of the function AAS_BoxOnPlaneSide +//=========================================================================== +// remove the links to this entity from all areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromAreas(aas_link_t *areas) +{ + aas_link_t *link, *nextlink; + + for (link = areas; link; link = nextlink) + { + //next area the entity is linked in + nextlink = link->next_area; + //remove the entity from the linked list of this area + if (link->prev_ent) + { + link->prev_ent->next_ent = link->next_ent; + } + else + { + (*aasworld).arealinkedentities[link->areanum] = link->next_ent; + } + if (link->next_ent) + { + link->next_ent->prev_ent = link->prev_ent; + } + //deallocate the link structure + AAS_DeAllocAASLink(link); + } //end for +} //end of the function AAS_UnlinkFromAreas +//=========================================================================== +// link the entity to the areas the bounding box is totally or partly +// situated in. This is done with recursion down the tree using the +// bounding box to test for plane sides +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +typedef struct +{ + int nodenum; //node found after splitting +} aas_linkstack_t; + +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum) +{ + int side, nodenum; + aas_linkstack_t linkstack[128]; + aas_linkstack_t *lstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_link_t *link, *areas; + + if (!aasworld->loaded) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: aas not loaded\n"); + return NULL; + } + + areas = NULL; + + lstack_p = linkstack; + //we start with the whole line on the stack + //start with node 1 because node zero is a dummy used for solid leafs + lstack_p->nodenum = 1; //starting at the root of the tree + lstack_p++; + + while (1) + { + //pop up the stack + lstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (lstack_p < linkstack) + { + break; + } + //number of the current node to test the line against + nodenum = lstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { + //NOTE: the entity might have already been linked into this area + // because several node children can point to the same area + for (link = (*aasworld).arealinkedentities[-nodenum]; link; link = link->next_ent) + { + if (link->entnum == entnum) + { + break; + } + } //end for + if (link) + { + continue; + } + // + link = AAS_AllocAASLink(); + if (!link) + { + return areas; + } + link->entnum = entnum; + link->areanum = -nodenum; + //put the link into the double linked area list of the entity + link->prev_area = NULL; + link->next_area = areas; + if (areas) + { + areas->prev_area = link; + } + areas = link; + //put the link into the double linked entity list of the area + link->prev_ent = NULL; + link->next_ent = (*aasworld).arealinkedentities[-nodenum]; + if ((*aasworld).arealinkedentities[-nodenum]) + { + (*aasworld).arealinkedentities[-nodenum]->prev_ent = link; + } + (*aasworld).arealinkedentities[-nodenum] = link; + // + continue; + } //end if + //if solid leaf + if (!nodenum) + { + continue; + } + //the node to test against + aasnode = &(*aasworld).nodes[nodenum]; + //the current node plane + plane = &(*aasworld).planes[aasnode->planenum]; + //get the side(s) the box is situated relative to the plane + side = AAS_BoxOnPlaneSide2(absmins, absmaxs, plane); + //if on the front side of the node + if (side & 1) + { + lstack_p->nodenum = aasnode->children[0]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + //if on the back side of the node + if (side & 2) + { + lstack_p->nodenum = aasnode->children[1]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + } //end while + return areas; +} //end of the function AAS_AASLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype) +{ + vec3_t mins, maxs; + vec3_t newabsmins, newabsmaxs; + + AAS_PresenceTypeBoundingBox(presencetype, mins, maxs); + VectorSubtract(absmins, maxs, newabsmins); + VectorSubtract(absmaxs, mins, newabsmaxs); + //relink the entity + return AAS_AASLinkEntity(newabsmins, newabsmaxs, entnum); +} //end of the function AAS_LinkEntityClientBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_plane_t *AAS_PlaneFromNum(int planenum) +{ + if (!(*aasworld).loaded) + { + return 0; + } + + return &(*aasworld).planes[planenum]; +} //end of the function AAS_PlaneFromNum + + +#ifndef BSPC + +/* +============= +AAS_BBoxAreas +============= +*/ +#define NUM_BBOXAREASCACHE 128 +#define BBOXAREASCACHE_MAXAREAS 128 + +typedef struct +{ + float lastUsedTime; + int numUsed; + vec3_t absmins, absmaxs; +#if AAS_MAX_AREAS <= 65536 + unsigned short areas[BBOXAREASCACHE_MAXAREAS]; // we can used shorts since AAS_MAX_AREAS < 65536 +#else + int areas[BBOXAREASCACHE_MAXAREAS]; +#endif + int numAreas; +} bboxAreasCache_t; + +bboxAreasCache_t bboxAreasCache[NUM_BBOXAREASCACHE]; + +int AAS_BBoxAreasCheckCache(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) +{ + int i; + bboxAreasCache_t *cache; + + // is this absmins/absmax in the cache? + for (i = 0, cache = bboxAreasCache; i < NUM_BBOXAREASCACHE; i++, cache++) + { + if (VectorCompare(absmins, cache->absmins) && VectorCompare(absmaxs, cache->absmaxs)) + { + // found a match + break; + } + } + + if (i == NUM_BBOXAREASCACHE) + { + return 0; + } + + // use this cache + cache->lastUsedTime = AAS_Time(); + cache->numUsed++; + if (cache->numUsed > 99999) + { + cache->numUsed = 99999; // cap it so it doesn't loop back to 0 + } + if (cache->numAreas > maxareas) + { + for (i = 0; i < maxareas; i++) + areas[i] = (int)cache->areas[i]; + return maxareas; + } + else + { + memcpy(areas, cache->areas, sizeof(int) * cache->numAreas); + return cache->numAreas; + } +} + +void AAS_BBoxAreasAddToCache(vec3_t absmins, vec3_t absmaxs, int *areas, int numareas) +{ + int i; + bboxAreasCache_t *cache, *weakestLink = NULL; + + // find a free cache slot + for (i = 0, cache = bboxAreasCache; i < NUM_BBOXAREASCACHE; i++, cache++) + { + if (!cache->lastUsedTime) + { + break; + } + if (cache->lastUsedTime < AAS_Time() - 2.0) + { + break; // too old + } + + if (!weakestLink) + { + weakestLink = cache; + } + else + { + if (cache->numUsed < weakestLink->numUsed) + { + weakestLink = cache; + } + } + } + + if (i == NUM_BBOXAREASCACHE) + { + // overwrite the weakest link + cache = weakestLink; + } + + cache->lastUsedTime = AAS_Time(); + cache->numUsed = 1; + VectorCopy(absmins, cache->absmins); + VectorCopy(absmaxs, cache->absmaxs); + + if (numareas > BBOXAREASCACHE_MAXAREAS) + { + numareas = BBOXAREASCACHE_MAXAREAS; + } + + for (i = 0; i < numareas; i++) + { + cache->areas[i] = (unsigned short) areas[i]; + } +} + +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) +{ + aas_link_t *linkedareas, *link; + int num; + + if ((num = AAS_BBoxAreasCheckCache(absmins, absmaxs, areas, maxareas))) + { + return num; + } + + linkedareas = AAS_AASLinkEntity(absmins, absmaxs, -1); + + num = 0; + for (link = linkedareas; link; link = link->next_area) + { + areas[num] = link->areanum; + num++; + if (num >= maxareas) + { + break; + } + } + + AAS_UnlinkFromAreas(linkedareas); + + //record this result in the cache + AAS_BBoxAreasAddToCache(absmins, absmaxs, areas, num); + + return num; +} //end of the function AAS_BBoxAreas + +#else + +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) +{ + aas_link_t *linkedareas, *link; + int num; + + linkedareas = AAS_AASLinkEntity(absmins, absmaxs, -1); + num = 0; + for (link = linkedareas; link; link = link->next_area) + { + areas[num] = link->areanum; + num++; + if (num >= maxareas) + { + break; + } + } //end for + AAS_UnlinkFromAreas(linkedareas); + return num; +} //end of the function AAS_BBoxAreas + +#endif + +/* +============= +AAS_AreaCenter +============= +*/ +void AAS_AreaCenter(int areanum, vec3_t center) +{ + if (areanum < 0 || areanum >= (*aasworld).numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaCenter: invalid areanum\n"); + return; + } + VectorCopy((*aasworld).areas[areanum].center, center); + return; +} //end of the function AAS_AreaCenter + +/* +============= +AAS_AreaWaypoint +============= +*/ +qboolean AAS_AreaWaypoint(int areanum, vec3_t center) +{ + if (areanum < 0 || areanum >= (*aasworld).numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaWaypoint: invalid areanum\n"); + return qfalse; + } + if (!(*aasworld).areawaypoints) + { + return qfalse; + } + if (VectorCompare((*aasworld).areawaypoints[areanum], vec3_origin)) + { + return qfalse; + } + VectorCopy((*aasworld).areawaypoints[areanum], center); + return qtrue; +} //end of the function AAS_AreaCenter diff --git a/src/botlib/be_aas_sample.h b/src/botlib/be_aas_sample.h new file mode 100644 index 000000000..4ae43be64 --- /dev/null +++ b/src/botlib/be_aas_sample.h @@ -0,0 +1,69 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_aas_sample.h + * @brief AAS environment sampling + */ + +#ifdef AASINTERN +void AAS_InitAASLinkHeap(void); +void AAS_InitAASLinkedEntities(void); +void AAS_FreeAASLinkHeap(void); +void AAS_FreeAASLinkedEntities(void); +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point); +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace); +aas_plane_t *AAS_PlaneFromNum(int planenum); +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum); +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype); +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon); +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon); +void AAS_UnlinkFromAreas(aas_link_t *areas); +#endif //AASINTERN + +//returns the mins and maxs of the bounding box for the given presence type +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs); +//returns the cluster the area is in (negative portal number if the area is a portal) +int AAS_AreaCluster(int areanum); +//returns the presence type(s) of the area +int AAS_AreaPresenceType(int areanum); +//returns the presence type(s) at the given point +int AAS_PointPresenceType(vec3_t point); +//returns the result of the trace of a client bbox +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, int passent); +//stores the areas the trace went through and returns the number of passed areas +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); +//returns the area the point is in +int AAS_PointAreaNum(vec3_t point); +//returns the plane the given face is in +void AAS_FacePlane(int facenum, vec3_t normal, float *dist); + +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); +void AAS_AreaCenter(int areanum, vec3_t center); +qboolean AAS_AreaWaypoint(int areanum, vec3_t center); diff --git a/src/botlib/be_ai_char.c b/src/botlib/be_ai_char.c new file mode 100644 index 000000000..1b4433510 --- /dev/null +++ b/src/botlib/be_ai_char.c @@ -0,0 +1,866 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_char.c + * @brief bot characters + */ + +#include "../qcommon/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_char.h" + +#define MAX_CHARACTERISTICS 80 + +#define CT_INTEGER 1 +#define CT_FLOAT 2 +#define CT_STRING 3 + +#define DEFAULT_CHARACTER "bots/default_c.c" + +//characteristic value +union cvalue +{ + int integer; + float _float; + char *string; +}; +//a characteristic +typedef struct bot_characteristic_s +{ + char type; //characteristic type + union cvalue value; //characteristic value +} bot_characteristic_t; + +//a bot character +typedef struct bot_character_s +{ + char filename[MAX_QPATH]; + int skill; + bot_characteristic_t c[1]; //variable sized +} bot_character_t; + +bot_character_t *botcharacters[MAX_CLIENTS + 1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_character_t *BotCharacterFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return NULL; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return NULL; + } //end if + return botcharacters[handle]; +} //end of the function BotCharacterFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpCharacter(bot_character_t *ch) +{ + int i; + + Log_Write("%s", ch->filename); + Log_Write("skill %d\n", ch->skill); + Log_Write("{\n"); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + switch (ch->c[i].type) + { + case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break; + case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break; + case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break; + } //end case + } //end for + Log_Write("}\n"); +} //end of the function BotDumpCharacter +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacterStrings(bot_character_t *ch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type == CT_STRING) + { + FreeMemory(ch->c[i].value.string); + } //end if + } //end for +} //end of the function BotFreeCharacterStrings +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter2(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return; + } //end if + BotFreeCharacterStrings(botcharacters[handle]); + FreeMemory(botcharacters[handle]); + botcharacters[handle] = NULL; +} //end of the function BotFreeCharacter2 +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter(int handle) +{ + if (!LibVarGetValue("bot_reloadcharacters")) + { + return; + } + BotFreeCharacter2(handle); +} //end of the function BotFreeCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type) + { + continue; + } + // + if (defaultch->c[i].type == CT_FLOAT) + { + ch->c[i].type = CT_FLOAT; + ch->c[i].value._float = defaultch->c[i].value._float; + } //end if + else if (defaultch->c[i].type == CT_INTEGER) + { + ch->c[i].type = CT_INTEGER; + ch->c[i].value.integer = defaultch->c[i].value.integer; + } //end else if + else if (defaultch->c[i].type == CT_STRING) + { + ch->c[i].type = CT_STRING; + ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string) + 1); + strcpy(ch->c[i].value.string, defaultch->c[i].value.string); + } //end else if + } //end for +} //end of the function BotDefaultCharacteristics +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill) +{ + int indent, index, foundcharacter; + bot_character_t *ch; + source_t *source; + token_t token; + + foundcharacter = qfalse; + //a bot character is parsed in two phases + PS_SetBaseFolder("botfiles"); + source = LoadSourceFile(charfile); + PS_SetBaseFolder(""); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile); + return NULL; + } //end if + ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + strcpy(ch->filename, charfile); + while (PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "skill")) + { + if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + //if it's the correct skill + if (skill < 0 || token.intvalue == skill) + { + foundcharacter = qtrue; + ch->skill = token.intvalue; + while (PC_ExpectAnyToken(source, &token)) + { + if (!strcmp(token.string, "}")) + { + break; + } + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer index, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + index = token.intvalue; + if (index < 0 || index > MAX_CHARACTERISTICS) + { + SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (ch->c[index].type) + { + SourceError(source, "characteristic %d already initialized\n", index); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (token.type == TT_NUMBER) + { + if (token.subtype & TT_FLOAT) + { + ch->c[index].value._float = token.floatvalue; + ch->c[index].type = CT_FLOAT; + } //end if + else + { + ch->c[index].value.integer = token.intvalue; + ch->c[index].type = CT_INTEGER; + } //end else + } //end if + else if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + ch->c[index].value.string = GetMemory(strlen(token.string) + 1); + strcpy(ch->c[index].value.string, token.string); + ch->c[index].type = CT_STRING; + } //end else if + else + { + SourceError(source, "expected integer, float or string, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end if + break; + } //end if + else + { + indent = 1; + while (indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!strcmp(token.string, "{")) + { + indent++; + } + else if (!strcmp(token.string, "}")) + { + indent--; + } + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!foundcharacter) + { + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + return ch; +} //end of the function BotLoadCharacterFromFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindCachedCharacter(char *charfile, int skill) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) + { + continue; + } + if (strcmp(botcharacters[handle]->filename, charfile) == 0 && + (skill < 0 || botcharacters[handle]->skill == skill)) + { + return handle; + } //end if + } //end for + return 0; +} //end of the function BotFindCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCachedCharacter(char *charfile, int skill, int reload) +{ + int handle, cachedhandle; + bot_character_t *ch = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) + { + break; + } + } //end for + if (handle > MAX_CLIENTS) + { + return 0; + } + //try to load a cached character with the given skill + if (!reload) + { + cachedhandle = BotFindCachedCharacter(charfile, skill); + if (cachedhandle) + { + //botimport.Print(PRT_MESSAGE, "loaded cached skill %d from %s\n", skill, charfile); + return cachedhandle; + } //end if + } //end else + //try to load the character with the given skill + ch = BotLoadCharacterFromFile(charfile, skill); + if (ch) + { + botcharacters[handle] = ch; + // + //botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", skill, charfile); +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", skill, Sys_MilliSeconds() - starttime, charfile); + } //end if +#endif //DEBUG + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", skill, charfile); + // + if (!reload) + { + //try to load a cached default character with the given skill + cachedhandle = BotFindCachedCharacter("bots/default_c.c", skill); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load the default character with the given skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, skill); + if (ch) + { + botcharacters[handle] = ch; + //botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", skill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(charfile, -1); + if (cachedhandle) + { + //botimport.Print(PRT_MESSAGE, "loaded cached skill %d from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(charfile, -1); + if (ch) + { + botcharacters[handle] = ch; + //botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", ch->skill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", ch->skill, charfile); + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile); + //couldn't load any character + return 0; +} //end of the function BotLoadCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacterSkill(char *charfile, int skill) +{ + int ch, defaultch; + + defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse); + ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters")); + + if (defaultch && ch) + { + BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]); + } //end if + + return ch; +} //end of the function BotLoadCharacterSkill +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotInterpolateCharacters(int handle1, int handle2, int desiredskill) +{ + bot_character_t *ch1, *ch2, *out; + int i, handle; + float scale; + + ch1 = BotCharacterFromHandle(handle1); + ch2 = BotCharacterFromHandle(handle2); + if (!ch1 || !ch2) + { + return 0; + } + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) + { + break; + } + } //end for + if (handle > MAX_CLIENTS) + { + return 0; + } + out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + out->skill = desiredskill; + strcpy(out->filename, ch1->filename); + botcharacters[handle] = out; + + scale = (float) (desiredskill - 1) / (ch2->skill - ch1->skill); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + // + if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT) + { + out->c[i].type = CT_FLOAT; + out->c[i].value._float = ch1->c[i].value._float + + (ch2->c[i].value._float - ch1->c[i].value._float) * scale; + } //end if + else if (ch1->c[i].type == CT_INTEGER) + { + out->c[i].type = CT_INTEGER; + out->c[i].value.integer = ch1->c[i].value.integer; + } //end else if + else if (ch1->c[i].type == CT_STRING) + { + out->c[i].type = CT_STRING; + out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string) + 1); + strcpy(out->c[i].value.string, ch1->c[i].value.string); + } //end else if + } //end for + return handle; +} //end of the function BotInterpolateCharacters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacter(char *charfile, int skill) +{ + int skill1, skill4, handle; + + //make sure the skill is in the valid range + if (skill < 1) + { + skill = 1; + } + else if (skill > 5) + { + skill = 5; + } + //skill 1, 4 and 5 should be available in the character files + if (skill == 1 || skill == 4 || skill == 5) + { + return BotLoadCharacterSkill(charfile, skill); + } //end if + //check if there's a cached skill 2 or 3 + handle = BotFindCachedCharacter(charfile, skill); + if (handle) + { + //botimport.Print(PRT_MESSAGE, "loaded cached skill %d from %s\n", skill, charfile); + return handle; + } //end if + //load skill 1 and 4 + skill1 = BotLoadCharacterSkill(charfile, 1); + if (!skill1) + { + return 0; + } + skill4 = BotLoadCharacterSkill(charfile, 4); + if (!skill4) + { + return skill1; + } + //interpolate between 1 and 4 to create skill 2 or 3 + handle = BotInterpolateCharacters(skill1, skill4, skill); + if (!handle) + { + return 0; + } + //write the character to the log file + BotDumpCharacter(botcharacters[handle]); + // + return handle; +} //end of the function BotLoadCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CheckCharacteristicIndex(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) + { + return qfalse; + } + if (index < 0 || index >= MAX_CHARACTERISTICS) + { + botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index); + return qfalse; + } //end if + if (!ch->c[index].type) + { + botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index); + return qfalse; + } //end if + return qtrue; +} //end of the function CheckCharacteristicIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_Float(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) + { + return 0; + } + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) + { + return 0; + } + //an integer will be converted to a float + if (ch->c[index].type == CT_INTEGER) + { + return (float) ch->c[index].value.integer; + } //end if + //floats are just returned + else if (ch->c[index].type == CT_FLOAT) + { + return ch->c[index].value._float; + } //end else if + //cannot convert a string pointer to a float + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Float +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_BFloat(int character, int index, float min, float max) +{ + float value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) + { + return 0; + } + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max); + return 0; + } //end if + value = Characteristic_Float(character, index); + if (value < min) + { + return min; + } + if (value > max) + { + return max; + } + return value; +} //end of the function Characteristic_BFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_Integer(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) + { + return 0; + } + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) + { + return 0; + } + //an integer will just be returned + if (ch->c[index].type == CT_INTEGER) + { + return ch->c[index].value.integer; + } //end if + //floats are casted to integers + else if (ch->c[index].type == CT_FLOAT) + { + return (int) ch->c[index].value._float; + } //end else if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Integer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_BInteger(int character, int index, int min, int max) +{ + int value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) + { + return 0; + } + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); + return 0; + } //end if + value = Characteristic_Integer(character, index); + if (value < min) + { + return min; + } + if (value > max) + { + return max; + } + return value; +} //end of the function Characteristic_BInteger +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Characteristic_String(int character, int index, char *buf, int size) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) + { + return; + } + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) + { + return; + } + //an integer will be converted to a float + if (ch->c[index].type == CT_STRING) + { + strncpy(buf, ch->c[index].value.string, size - 1); + buf[size - 1] = '\0'; + return; + } //end if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index); + return; + } //end else if + return; +} //end of the function Characteristic_String +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownCharacters(void) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (botcharacters[handle]) + { + BotFreeCharacter2(handle); + } //end if + } //end for +} //end of the function BotShutdownCharacters diff --git a/src/botlib/be_ai_chat.c b/src/botlib/be_ai_chat.c new file mode 100644 index 000000000..6d9d91884 --- /dev/null +++ b/src/botlib/be_ai_chat.c @@ -0,0 +1,3346 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_chat.c + * @brief bot chat AI + */ + +#include "../qcommon/q_shared.h" +//#include "../server/server.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ea.h" +#include "../game/be_ai_chat.h" + + +//escape character +#define ESCAPE_CHAR 0x01 //'_' +// +// "hi ", people, " ", 0, " entered the game" +//becomes: +// "hi _rpeople_ _v0_ entered the game" +// + +//match piece types +#define MT_VARIABLE 1 //variable match piece +#define MT_STRING 2 //string match piece +//reply chat key flags +#define RCKFL_AND 1 //key must be present +#define RCKFL_NOT 2 //key must be absent +#define RCKFL_NAME 4 //name of bot must be present +#define RCKFL_STRING 8 //key is a string +#define RCKFL_VARIABLES 16 //key is a match template +#define RCKFL_BOTNAMES 32 //key is a series of botnames +#define RCKFL_GENDERFEMALE 64 //bot must be female +#define RCKFL_GENDERMALE 128 //bot must be male +#define RCKFL_GENDERLESS 256 //bot must be genderless +//time to ignore a chat message after using it +#define CHATMESSAGE_RECENTTIME 20 + +//the actuall chat messages +typedef struct bot_chatmessage_s +{ + char *chatmessage; //chat message string + float time; //last time used + struct bot_chatmessage_s *next; //next chat message in a list +} bot_chatmessage_t; +//bot chat type with chat lines +typedef struct bot_chattype_s +{ + char name[MAX_CHATTYPE_NAME]; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_chattype_s *next; +} bot_chattype_t; +//bot chat lines +typedef struct bot_chat_s +{ + bot_chattype_t *types; +} bot_chat_t; + +//random string +typedef struct bot_randomstring_s +{ + char *string; + struct bot_randomstring_s *next; +} bot_randomstring_t; +//list with random strings +typedef struct bot_randomlist_s +{ + char *string; + int numstrings; + bot_randomstring_t *firstrandomstring; + struct bot_randomlist_s *next; +} bot_randomlist_t; + +//synonym +typedef struct bot_synonym_s +{ + char *string; + float weight; + struct bot_synonym_s *next; +} bot_synonym_t; +//list with synonyms +typedef struct bot_synonymlist_s +{ + unsigned long int context; + float totalweight; + bot_synonym_t *firstsynonym; + struct bot_synonymlist_s *next; +} bot_synonymlist_t; + +//fixed match string +typedef struct bot_matchstring_s +{ + char *string; + struct bot_matchstring_s *next; +} bot_matchstring_t; + +//piece of a match template +typedef struct bot_matchpiece_s +{ + int type; + bot_matchstring_t *firststring; + int variable; + struct bot_matchpiece_s *next; +} bot_matchpiece_t; +//match template +typedef struct bot_matchtemplate_s +{ + unsigned long int context; + int type; + int subtype; + bot_matchpiece_t *first; + struct bot_matchtemplate_s *next; +} bot_matchtemplate_t; + +//reply chat key +typedef struct bot_replychatkey_s +{ + int flags; + char *string; + bot_matchpiece_t *match; + struct bot_replychatkey_s *next; +} bot_replychatkey_t; +//reply chat +typedef struct bot_replychat_s +{ + bot_replychatkey_t *keys; + float priority; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_replychat_s *next; +} bot_replychat_t; + +//string list +typedef struct bot_stringlist_s +{ + char *string; + struct bot_stringlist_s *next; +} bot_stringlist_t; + +//chat state of a bot +typedef struct bot_chatstate_s +{ + int gender; //0=it, 1=female, 2=male + char name[32]; //name of the bot + char chatmessage[MAX_MESSAGE_SIZE]; + int handle; + //the console messages visible to the bot + bot_consolemessage_t *firstmessage; //first message is the first typed message + bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console + //number of console messages stored in the state + int numconsolemessages; + //the bot chat lines + bot_chat_t *chat; +} bot_chatstate_t; + +typedef struct +{ + bot_chat_t *chat; + int inuse; + char filename[MAX_QPATH]; + char chatname[MAX_QPATH]; +} bot_ichatdata_t; + +bot_ichatdata_t ichatdata[MAX_CLIENTS]; + +bot_chatstate_t *botchatstates[MAX_CLIENTS + 1]; +//console message heap +bot_consolemessage_t *consolemessageheap = NULL; +bot_consolemessage_t *freeconsolemessages = NULL; +//list with match strings +bot_matchtemplate_t *matchtemplates = NULL; +//list with synonyms +bot_synonymlist_t *synonyms = NULL; +//list with random strings +bot_randomlist_t *randomstrings = NULL; +//reply chats +bot_replychat_t *replychats = NULL; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_chatstate_t *BotChatStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return NULL; + } //end if + return botchatstates[handle]; +} //end of the function BotChatStateFromHandle +//=========================================================================== +// initialize the heap with unused console messages +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitConsoleMessageHeap(void) +{ + int i, max_messages; + + if (consolemessageheap) + { + FreeMemory(consolemessageheap); + } + // + max_messages = (int) LibVarValue("max_messages", "1024"); + consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages * + sizeof(bot_consolemessage_t)); + consolemessageheap[0].prev = NULL; + consolemessageheap[0].next = &consolemessageheap[1]; + for (i = 1; i < max_messages - 1; i++) + { + consolemessageheap[i].prev = &consolemessageheap[i - 1]; + consolemessageheap[i].next = &consolemessageheap[i + 1]; + } //end for + consolemessageheap[max_messages - 1].prev = &consolemessageheap[max_messages - 2]; + consolemessageheap[max_messages - 1].next = NULL; + //pointer to the free console messages + freeconsolemessages = consolemessageheap; +} //end of the function InitConsoleMessageHeap +//=========================================================================== +// allocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_consolemessage_t *AllocConsoleMessage(void) +{ + bot_consolemessage_t *message; + message = freeconsolemessages; + if (freeconsolemessages) + { + freeconsolemessages = freeconsolemessages->next; + } + if (freeconsolemessages) + { + freeconsolemessages->prev = NULL; + } + return message; +} //end of the function AllocConsoleMessage +//=========================================================================== +// deallocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeConsoleMessage(bot_consolemessage_t *message) +{ + if (freeconsolemessages) + { + freeconsolemessages->prev = message; + } + message->prev = NULL; + message->next = freeconsolemessages; + freeconsolemessages = message; +} //end of the function FreeConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveConsoleMessage(int chatstate, int handle) +{ + bot_consolemessage_t *m, *nextm; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + + for (m = cs->firstmessage; m; m = nextm) + { + nextm = m->next; + if (m->handle == handle) + { + if (m->next) + { + m->next->prev = m->prev; + } + else + { + cs->lastmessage = m->prev; + } + if (m->prev) + { + m->prev->next = m->next; + } + else + { + cs->firstmessage = m->next; + } + + FreeConsoleMessage(m); + cs->numconsolemessages--; + break; + } //end if + } //end for +} //end of the function BotRemoveConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotQueueConsoleMessage(int chatstate, int type, char *message) +{ + bot_consolemessage_t *m; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + + m = AllocConsoleMessage(); + if (!m) + { + botimport.Print(PRT_ERROR, "empty console message heap\n"); + return; + } //end if + cs->handle++; + if (cs->handle <= 0 || cs->handle > 8192) + { + cs->handle = 1; + } + m->handle = cs->handle; + m->time = AAS_Time(); + m->type = type; + strncpy(m->message, message, MAX_MESSAGE_SIZE); + m->next = NULL; + if (cs->lastmessage) + { + cs->lastmessage->next = m; + m->prev = cs->lastmessage; + cs->lastmessage = m; + } //end if + else + { + cs->lastmessage = m; + cs->firstmessage = m; + m->prev = NULL; + } //end if + cs->numconsolemessages++; +} //end of the function BotQueueConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return 0; + } + if (cs->firstmessage) + { + memcpy(cm, cs->firstmessage, sizeof(bot_consolemessage_t)); + cm->next = cm->prev = NULL; + return cm->handle; + } //end if + return 0; +} //end of the function BotConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumConsoleMessages(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return 0; + } + return cs->numconsolemessages; +} //end of the function BotNumConsoleMessages +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int IsWhiteSpace(char c) +{ + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '(' || c == ')' + || c == '?' || c == '\'' + || c == ':' || c == ',' + || c == '[' || c == ']' + || c == '-' || c == '_' + || c == '+' || c == '=') + { + return qfalse; + } + return qtrue; +} //end of the function IsWhiteSpace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveTildes(char *message) +{ + int i; + + //remove all tildes from the chat message + for (i = 0; message[i]; i++) + { + if (message[i] == '~') + { + memmove(&message[i], &message[i + 1], strlen(&message[i + 1]) + 1); + } //end if + } //end for +} //end of the function BotRemoveTildes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnifyWhiteSpaces(char *string) +{ + char *ptr, *oldptr; + + for (ptr = oldptr = string; *ptr; oldptr = ptr) + { + while (*ptr && IsWhiteSpace(*ptr)) + ptr++; + if (ptr > oldptr) + { + //if not at the start and not at the end of the string + //write only one space + if (oldptr > string && *ptr) + { + *oldptr++ = ' '; + } + //remove all other white spaces + if (ptr > oldptr) + { + memmove(oldptr, ptr, strlen(ptr) + 1); + } + } //end if + while (*ptr && !IsWhiteSpace(*ptr)) + ptr++; + } //end while +} //end of the function UnifyWhiteSpaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringContains(char *str1, char *str2, int casesensitive) +{ + int len, i, j, index; + + if (str1 == NULL || str2 == NULL) + { + return -1; + } + + len = strlen(str1) - strlen(str2); + index = 0; + for (i = 0; i <= len; i++, str1++, index++) + { + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) + { + break; + } + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) + { + break; + } + } //end else + } //end for + if (!str2[j]) + { + return index; + } + } //end for + return -1; +} //end of the function StringContains +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *StringContainsWord(char *str1, char *str2, int casesensitive) +{ + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) + { + //if not at the start of the string + if (i) + { + //skip to the start of the next word + while (*str1 && *str1 != ' ') + str1++; + if (!*str1) + { + break; + } + str1++; + } //end for + //compare the word + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) + { + break; + } + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) + { + break; + } + } //end else + } //end for + //if there was a word match + if (!str2[j]) + { + //if the first string has an end of word + if (!str1[j] || str1[j] == ' ') + { + return str1; + } + } //end if + } //end for + return NULL; +} //end of the function StringContainsWord +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void StringReplaceWords(char *string, char *synonym, char *replacement) +{ + char *str, *str2; + + //find the synonym in the string + str = StringContainsWord(string, synonym, qfalse); + //if the synonym occured in the string + while (str) + { + //if the synonym isn't part of the replacement which is already in the string + //usefull for abreviations + str2 = StringContainsWord(string, replacement, qfalse); + while (str2) + { + if (str2 <= str && str < str2 + strlen(replacement)) + { + break; + } + str2 = StringContainsWord(str2 + 1, replacement, qfalse); + } //end while + if (!str2) + { + memmove(str + strlen(replacement), str + strlen(synonym), strlen(str + strlen(synonym)) + 1); + //append the synonum replacement + memcpy(str, replacement, strlen(replacement)); + } //end if + //find the next synonym in the string + str = StringContainsWord(str + strlen(replacement), synonym, qfalse); + } //end if +} //end of the function StringReplaceWords +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpSynonymList(bot_synonymlist_t *synlist) +{ + FILE *fp; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + fp = Log_FilePointer(); + if (!fp) + { + return; + } + for (syn = synlist; syn; syn = syn->next) + { + fprintf(fp, "%ld : [", syn->context); + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight); + if (synonym->next) + { + fprintf(fp, ", "); + } + } //end for + fprintf(fp, "]\n"); + } //end for +} //end of the function BotDumpSynonymList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_synonymlist_t *BotLoadSynonyms(char *filename) +{ + int pass, size, contextlevel, numsynonyms; + unsigned long int context, contextstack[32]; + char *ptr = NULL; + source_t *source; + token_t token; + bot_synonymlist_t *synlist, *lastsyn, *syn; + bot_synonym_t *synonym, *lastsynonym; + + size = 0; + synlist = NULL; //make compiler happy + syn = NULL; //make compiler happy + synonym = NULL; //make compiler happy + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) + { + ptr = (char *) GetClearedHunkMemory(size); + } + // + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + context = 0; + contextlevel = 0; + synlist = NULL; //list synonyms + lastsyn = NULL; //last synonym in the list + // + while (PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER) + { + context |= token.intvalue; + contextstack[contextlevel] = token.intvalue; + contextlevel++; + if (contextlevel >= 32) + { + SourceError(source, "more than 32 context levels"); + FreeSource(source); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + } //end if + else if (token.type == TT_PUNCTUATION) + { + if (!strcmp(token.string, "}")) + { + contextlevel--; + if (contextlevel < 0) + { + SourceError(source, "too many }"); + FreeSource(source); + return NULL; + } //end if + context &= ~contextstack[contextlevel]; + } //end if + else if (!strcmp(token.string, "[")) + { + size += sizeof(bot_synonymlist_t); + if (pass) + { + syn = (bot_synonymlist_t *) ptr; + ptr += sizeof(bot_synonymlist_t); + syn->context = context; + syn->firstsynonym = NULL; + syn->next = NULL; + if (lastsyn) + { + lastsyn->next = syn; + } + else + { + synlist = syn; + } + lastsyn = syn; + } //end if + numsynonyms = 0; + lastsynonym = NULL; + while (1) + { + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(token.string) <= 0) + { + SourceError(source, "empty string", token.string); + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_synonym_t) + strlen(token.string) + 1; + if (pass) + { + synonym = (bot_synonym_t *) ptr; + ptr += sizeof(bot_synonym_t); + synonym->string = ptr; + ptr += strlen(token.string) + 1; + strcpy(synonym->string, token.string); + // + if (lastsynonym) + { + lastsynonym->next = synonym; + } + else + { + syn->firstsynonym = synonym; + } + lastsynonym = synonym; + } //end if + numsynonyms++; + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) || + !PC_ExpectTokenString(source, ")")) + { + FreeSource(source); + return NULL; + } //end if + if (pass) + { + synonym->weight = token.floatvalue; + syn->totalweight += synonym->weight; + } //end if + if (PC_CheckTokenString(source, "]")) + { + break; + } + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + return NULL; + } //end if + } //end while + if (numsynonyms < 2) + { + SourceError(source, "synonym must have at least two entries\n"); + FreeSource(source); + return NULL; + } //end if + } //end else + else + { + SourceError(source, "unexpected %s", token.string); + FreeSource(source); + return NULL; + } //end if + } //end else if + } //end while + // + FreeSource(source); + // + if (contextlevel > 0) + { + SourceError(source, "missing }"); + return NULL; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpSynonymList(synlist); + // + return synlist; +} //end of the function BotLoadSynonyms +//=========================================================================== +// replace all the synonyms in the string +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) + { + continue; + } + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + StringReplaceWords(string, synonym->string, syn->firstsynonym->string); + } //end for + } //end for +} //end of the function BotReplaceSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceWeightedSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym, *replacement; + float weight, curweight; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) + { + continue; + } + //choose a weighted random replacement synonym + weight = random() * syn->totalweight; + if (!weight) + { + continue; + } + curweight = 0; + for (replacement = syn->firstsynonym; replacement; replacement = replacement->next) + { + curweight += replacement->weight; + if (weight < curweight) + { + break; + } + } //end for + if (!replacement) + { + continue; + } + //replace all synonyms with the replacement + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + if (synonym == replacement) + { + continue; + } + StringReplaceWords(string, synonym->string, replacement->string); + } //end for + } //end for +} //end of the function BotReplaceWeightedSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceReplySynonyms(char *string, unsigned long int context) +{ + char *str1, *str2, *replacement; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (str1 = string; *str1; ) + { + //go to the start of the next word + while (*str1 && *str1 <= ' ') + str1++; + if (!*str1) + { + break; + } + // + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) + { + continue; + } + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + str2 = synonym->string; + //if the synonym is not at the front of the string continue + str2 = StringContainsWord(str1, synonym->string, qfalse); + if (!str2 || str2 != str1) + { + continue; + } + // + replacement = syn->firstsynonym->string; + //if the replacement IS in front of the string continue + str2 = StringContainsWord(str1, replacement, qfalse); + if (str2 && str2 == str1) + { + continue; + } + // + memmove(str1 + strlen(replacement), str1 + strlen(synonym->string), + strlen(str1 + strlen(synonym->string)) + 1); + //append the synonum replacement + memcpy(str1, replacement, strlen(replacement)); + // + break; + } //end for + //if a synonym has been replaced + if (synonym) + { + break; + } + } //end for + //skip over this word + while (*str1 && *str1 > ' ') + str1++; + if (!*str1) + { + break; + } + } //end while +} //end of the function BotReplaceReplySynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatMessage(source_t *source, char *chatmessagestring) +{ + char *ptr; + token_t token; + + ptr = chatmessagestring; + *ptr = 0; + // + while (1) + { + if (!PC_ExpectAnyToken(source, &token)) + { + return qfalse; + } + //fixed string + if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + if (strlen(ptr) + strlen(token.string) + 1 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + strcat(ptr, token.string); + } //end else if + //variable string + else if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR); + } //end if + //random string + else if (token.type == TT_NAME) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR); + } //end else if + else + { + SourceError(source, "unknown message component %s\n", token.string); + return qfalse; + } //end else + if (PC_CheckTokenString(source, ";")) + { + break; + } + if (!PC_ExpectTokenString(source, ",")) + { + return qfalse; + } + } //end while + // + return qtrue; +} //end of the function BotLoadChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpRandomStringList(bot_randomlist_t *randomlist) +{ + FILE *fp; + bot_randomlist_t *random; + bot_randomstring_t *rs; + + fp = Log_FilePointer(); + if (!fp) + { + return; + } + for (random = randomlist; random; random = random->next) + { + fprintf(fp, "%s = {", random->string); + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + fprintf(fp, "\"%s\"", rs->string); + if (rs->next) + { + fprintf(fp, ", "); + } + else + { + fprintf(fp, "}\n"); + } + } //end for + } //end for +} //end of the function BotDumpRandomStringList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_randomlist_t *BotLoadRandomStrings(char *filename) +{ + int pass, size; + char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_randomlist_t *randomlist, *lastrandom, *random; + bot_randomstring_t *randomstring; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + size = 0; + randomlist = NULL; + random = NULL; + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) + { + ptr = (char *) GetClearedHunkMemory(size); + } + // + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + randomlist = NULL; //list + lastrandom = NULL; //last + // + while (PC_ReadToken(source, &token)) + { + if (token.type != TT_NAME) + { + SourceError(source, "unknown random %s", token.string); + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_randomlist_t) + strlen(token.string) + 1; + if (pass) + { + random = (bot_randomlist_t *) ptr; + ptr += sizeof(bot_randomlist_t); + random->string = ptr; + ptr += strlen(token.string) + 1; + strcpy(random->string, token.string); + random->firstrandomstring = NULL; + random->numstrings = 0; + // + if (lastrandom) + { + lastrandom->next = random; + } + else + { + randomlist = random; + } + lastrandom = random; + } //end if + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + while (!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_randomstring_t) + strlen(chatmessagestring) + 1; + if (pass) + { + randomstring = (bot_randomstring_t *) ptr; + ptr += sizeof(bot_randomstring_t); + randomstring->string = ptr; + ptr += strlen(chatmessagestring) + 1; + strcpy(randomstring->string, chatmessagestring); + // + random->numstrings++; + randomstring->next = random->firstrandomstring; + random->firstrandomstring = randomstring; + } //end if + } //end while + } //end while + //free the source after one pass + FreeSource(source); + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime); + //BotDumpRandomStringList(randomlist); +#endif //DEBUG + // + return randomlist; +} //end of the function BotLoadRandomStrings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *RandomString(char *name) +{ + bot_randomlist_t *random; + bot_randomstring_t *rs; + int i; + + for (random = randomstrings; random; random = random->next) + { + if (!strcmp(random->string, name)) + { + i = rand() % random->numstrings; + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + if (--i < 0) + { + break; + } + } //end for + if (rs) + { + return rs->string; + } //end if + } //end for + } //end for + return NULL; +} //end of the function RandomString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpMatchTemplates(bot_matchtemplate_t *matches) +{ + FILE *fp; + bot_matchtemplate_t *mt; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + fp = Log_FilePointer(); + if (!fp) + { + return; + } + for (mt = matches; mt; mt = mt->next) + { + // TTimo ? + // fprintf(fp, "%8d { "); + for (mp = mt->first; mp; mp = mp->next) + { + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = ms->next) + { + fprintf(fp, "\"%s\"", ms->string); + if (ms->next) + { + fprintf(fp, "|"); + } + } //end for + } //end if + else if (mp->type == MT_VARIABLE) + { + fprintf(fp, "%d", mp->variable); + } //end else if + if (mp->next) + { + fprintf(fp, ", "); + } + } //end for + fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype); + } //end for +} //end of the function BotDumpMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchPieces(bot_matchpiece_t *matchpieces) +{ + bot_matchpiece_t *mp, *nextmp; + bot_matchstring_t *ms, *nextms; + + for (mp = matchpieces; mp; mp = nextmp) + { + nextmp = mp->next; + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = nextms) + { + nextms = ms->next; + FreeMemory(ms); + } //end for + } //end if + FreeMemory(mp); + } //end for +} //end of the function BotFreeMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken) +{ + int lastwasvariable, emptystring; + token_t token; + bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; + bot_matchstring_t *matchstring, *lastmatchstring; + + firstpiece = NULL; + lastpiece = NULL; + // + lastwasvariable = qfalse; + // + while (PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES) + { + SourceError(source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + if (lastwasvariable) + { + SourceError(source, "not allowed to have adjacent variables\n"); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + lastwasvariable = qtrue; + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->type = MT_VARIABLE; + matchpiece->variable = token.intvalue; + matchpiece->next = NULL; + if (lastpiece) + { + lastpiece->next = matchpiece; + } + else + { + firstpiece = matchpiece; + } + lastpiece = matchpiece; + } //end if + else if (token.type == TT_STRING) + { + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->firststring = NULL; + matchpiece->type = MT_STRING; + matchpiece->variable = 0; + matchpiece->next = NULL; + if (lastpiece) + { + lastpiece->next = matchpiece; + } + else + { + firstpiece = matchpiece; + } + lastpiece = matchpiece; + // + lastmatchstring = NULL; + emptystring = qfalse; + // + do + { + if (matchpiece->firststring) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end if + StripDoubleQuotes(token.string); + matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1); + matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t); + strcpy(matchstring->string, token.string); + if (!strlen(token.string)) + { + emptystring = qtrue; + } + matchstring->next = NULL; + if (lastmatchstring) + { + lastmatchstring->next = matchstring; + } + else + { + matchpiece->firststring = matchstring; + } + lastmatchstring = matchstring; + } + while (PC_CheckTokenString(source, "|")); + //if there was no empty string found + if (!emptystring) + { + lastwasvariable = qfalse; + } + } //end if + else + { + SourceError(source, "invalid token %s\n", token.string); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end else + if (PC_CheckTokenString(source, endtoken)) + { + break; + } + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end while + return firstpiece; +} //end of the function BotLoadMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchTemplates(bot_matchtemplate_t *mt) +{ + bot_matchtemplate_t *nextmt; + + for (; mt; mt = nextmt) + { + nextmt = mt->next; + BotFreeMatchPieces(mt->first); + FreeMemory(mt); + } //end for +} //end of the function BotFreeMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchtemplate_t *BotLoadMatchTemplates(char *matchfile) +{ + source_t *source; + token_t token; + bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; + unsigned long int context; + + source = LoadSourceFile(matchfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", matchfile); + return NULL; + } //end if + // + matches = NULL; //list with matches + lastmatch = NULL; //last match in the list + + while (PC_ReadToken(source, &token)) + { + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer, found %s\n", token.string); + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + //the context + context = token.intvalue; + // + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + // + while (PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "}")) + { + break; + } + // + PC_UnreadLastToken(source); + // + matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t)); + matchtemplate->context = context; + matchtemplate->next = NULL; + //add the match template to the list + if (lastmatch) + { + lastmatch->next = matchtemplate; + } + else + { + matches = matchtemplate; + } + lastmatch = matchtemplate; + //load the match template + matchtemplate->first = BotLoadMatchPieces(source, "="); + if (!matchtemplate->first) + { + BotFreeMatchTemplates(matches); + return NULL; + } //end if + //read the match type + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->type = token.intvalue; + //read the match subtype + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->subtype = token.intvalue; + //read trailing punctuations + if (!PC_ExpectTokenString(source, ")") || + !PC_ExpectTokenString(source, ";")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + } //end while + } //end while + //free the source + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile); + // + //BotDumpMatchTemplates(matches); + // + return matches; +} //end of the function BotLoadMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match) +{ + int lastvariable, index; + char *strptr, *newstrptr; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + //no last variable + lastvariable = -1; + //pointer to the string to compare the match string with + strptr = match->string; + //Log_Write("match: %s", strptr); + //compare the string with the current match string + for (mp = pieces; mp; mp = mp->next) + { + //if it is a piece of string + if (mp->type == MT_STRING) + { + newstrptr = NULL; + for (ms = mp->firststring; ms; ms = ms->next) + { + if (!strlen(ms->string)) + { + newstrptr = strptr; + break; + } //end if + //Log_Write("MT_STRING: %s", mp->string); + index = StringContains(strptr, ms->string, qfalse); + if (index >= 0) + { + newstrptr = strptr + index; + if (lastvariable >= 0) + { + match->variables[lastvariable].length = + newstrptr - match->variables[lastvariable].ptr; + lastvariable = -1; + break; + } //end if + else if (index == 0) + { + break; + } //end else + newstrptr = NULL; + } //end if + } //end for + if (!newstrptr) + { + return qfalse; + } + strptr = newstrptr + strlen(ms->string); + } //end if + //if it is a variable piece of string + else if (mp->type == MT_VARIABLE) + { + //Log_Write("MT_VARIABLE"); + match->variables[mp->variable].ptr = strptr; + lastvariable = mp->variable; + } //end else if + } //end for + //if a match was found + if (!mp && (lastvariable >= 0 || !strlen(strptr))) + { + //if the last piece was a variable string + if (lastvariable >= 0) + { + match->variables[lastvariable].length = strlen(match->variables[lastvariable].ptr); + } //end if + return qtrue; + } //end if + return qfalse; +} //end of the function StringsMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context) +{ + int i; + bot_matchtemplate_t *ms; + + strncpy(match->string, str, MAX_MESSAGE_SIZE); + //remove any trailing enters + while (strlen(match->string) && + match->string[strlen(match->string) - 1] == '\n') + { + match->string[strlen(match->string) - 1] = '\0'; + } //end while + //compare the string with all the match strings + for (ms = matchtemplates; ms; ms = ms->next) + { + if (!(ms->context & context)) + { + continue; + } + //reset the match variable pointers + for (i = 0; i < MAX_MATCHVARIABLES; i++) + match->variables[i].ptr = NULL; + // + if (StringsMatch(ms->first, match)) + { + match->type = ms->type; + match->subtype = ms->subtype; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotFindMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size) +{ + if (variable < 0 || variable >= MAX_MATCHVARIABLES) + { + botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n"); + strcpy(buf, ""); + return; + } //end if + + if (match->variables[variable].ptr) + { + if (match->variables[variable].length < size) + { + size = match->variables[variable].length + 1; + } + strncpy(buf, match->variables[variable].ptr, size - 1); + buf[size - 1] = '\0'; + } //end if + else + { + strcpy(buf, ""); + } //end else + return; +} //end of the function BotMatchVariable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, char *string) +{ + bot_stringlist_t *s; + + for (s = list; s; s = s->next) + { + if (!strcmp(s->string, string)) + { + return s; + } + } //end for + return NULL; +} //end of the function BotFindStringInList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist) +{ + int i; + char *msgptr; + char temp[MAX_MESSAGE_SIZE]; + bot_stringlist_t *s; + + msgptr = message; + // + while (*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch (*msgptr) + { + case 'v': //variable + { + //step over the 'v' + msgptr++; + while (*msgptr && *msgptr != ESCAPE_CHAR) + msgptr++; + //step over the trailing escape char + if (*msgptr) + { + msgptr++; + } + break; + } //end case + case 'r': //random + { + //step over the 'r' + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) + { + msgptr++; + } + //find the random keyword + if (!RandomString(temp)) + { + if (!BotFindStringInList(stringlist, temp)) + { + Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp); + s = GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1); + s->string = (char *) s + sizeof(bot_stringlist_t); + strcpy(s->string, temp); + s->next = stringlist; + stringlist = s; + } //end if + } //end if + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + msgptr++; + } //end else + } //end while + return stringlist; +} //end of the function BotCheckChatMessageIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckReplyChatIntegrety(bot_replychat_t *replychat) +{ + bot_replychat_t *rp; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (rp = replychat; rp; rp = rp->next) + { + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckReplyChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckInitialChatIntegrety(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (t = chat->types; t; t = t->next) + { + for (cm = t->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckInitialChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpReplyChat(bot_replychat_t *replychat) +{ + FILE *fp; + bot_replychat_t *rp; + bot_replychatkey_t *key; + bot_chatmessage_t *cm; + bot_matchpiece_t *mp; + + fp = Log_FilePointer(); + if (!fp) + { + return; + } + fprintf(fp, "BotDumpReplyChat:\n"); + for (rp = replychat; rp; rp = rp->next) + { + fprintf(fp, "["); + for (key = rp->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) + { + fprintf(fp, "&"); + } + else if (key->flags & RCKFL_NOT) + { + fprintf(fp, "!"); + } + // + if (key->flags & RCKFL_NAME) + { + fprintf(fp, "name"); + } + else if (key->flags & RCKFL_GENDERFEMALE) + { + fprintf(fp, "female"); + } + else if (key->flags & RCKFL_GENDERMALE) + { + fprintf(fp, "male"); + } + else if (key->flags & RCKFL_GENDERLESS) + { + fprintf(fp, "it"); + } + else if (key->flags & RCKFL_VARIABLES) + { + fprintf(fp, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) + { + fprintf(fp, "\"%s\"", mp->firststring->string); + } + else + { + fprintf(fp, "%d", mp->variable); + } + if (mp->next) + { + fprintf(fp, ", "); + } + } //end for + fprintf(fp, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + fprintf(fp, "\"%s\"", key->string); + } //end if + if (key->next) + { + fprintf(fp, ", "); + } + else + { + fprintf(fp, "] = %1.0f\n", rp->priority); + } + } //end for + fprintf(fp, "{\n"); + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + fprintf(fp, "\t\"%s\";\n", cm->chatmessage); + } //end for + fprintf(fp, "}\n"); + } //end for +} //end of the function BotDumpReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeReplyChat(bot_replychat_t *replychat) +{ + bot_replychat_t *rp, *nextrp; + bot_replychatkey_t *key, *nextkey; + bot_chatmessage_t *cm, *nextcm; + + for (rp = replychat; rp; rp = nextrp) + { + nextrp = rp->next; + for (key = rp->keys; key; key = nextkey) + { + nextkey = key->next; + if (key->match) + { + BotFreeMatchPieces(key->match); + } + if (key->string) + { + FreeMemory(key->string); + } + FreeMemory(key); + } //end for + for (cm = rp->firstchatmessage; cm; cm = nextcm) + { + nextcm = cm->next; + FreeMemory(cm); + } //end for + FreeMemory(rp); + } //end for +} //end of the function BotFreeReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_replychat_t *BotLoadReplyChat(char *filename) +{ + char chatmessagestring[MAX_MESSAGE_SIZE]; + char namebuffer[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chatmessage_t *chatmessage = NULL; + bot_replychat_t *replychat, *replychatlist; + bot_replychatkey_t *key; + + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + replychatlist = NULL; + // + while (PC_ReadToken(source, &token)) + { + if (strcmp(token.string, "[")) + { + SourceError(source, "expected [, found %s", token.string); + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + // + replychat = GetClearedHunkMemory(sizeof(bot_replychat_t)); + replychat->keys = NULL; + replychat->next = replychatlist; + replychatlist = replychat; + //read the keys, there must be at least one key + do + { + //allocate a key + key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t)); + key->flags = 0; + key->string = NULL; + key->match = NULL; + key->next = replychat->keys; + replychat->keys = key; + //check for MUST BE PRESENT and MUST BE ABSENT keys + if (PC_CheckTokenString(source, "&")) + { + key->flags |= RCKFL_AND; + } + else if (PC_CheckTokenString(source, "!")) + { + key->flags |= RCKFL_NOT; + } + //special keys + if (PC_CheckTokenString(source, "name")) + { + key->flags |= RCKFL_NAME; + } + else if (PC_CheckTokenString(source, "female")) + { + key->flags |= RCKFL_GENDERFEMALE; + } + else if (PC_CheckTokenString(source, "male")) + { + key->flags |= RCKFL_GENDERMALE; + } + else if (PC_CheckTokenString(source, "it")) + { + key->flags |= RCKFL_GENDERLESS; + } + else if (PC_CheckTokenString(source, "(")) //match key + { + key->flags |= RCKFL_VARIABLES; + key->match = BotLoadMatchPieces(source, ")"); + if (!key->match) + { + BotFreeReplyChat(replychatlist); + return NULL; + } //end if + } //end else if + else if (PC_CheckTokenString(source, "<")) //bot names + { + key->flags |= RCKFL_BOTNAMES; + strcpy(namebuffer, ""); + do + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(namebuffer)) + { + strcat(namebuffer, "\\"); + } + strcat(namebuffer, token.string); + } + while (PC_CheckTokenString(source, ",")); + if (!PC_ExpectTokenString(source, ">")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1); + strcpy(key->string, namebuffer); + } //end else if + else //normal string key + { + key->flags |= RCKFL_STRING; + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1); + strcpy(key->string, token.string); + } //end else + // + PC_CheckTokenString(source, ","); + } + while (!PC_CheckTokenString(source, "]")); + //read the = sign and the priority + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->priority = token.floatvalue; + //read the leading { + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->numchatmessages = 0; + //while the trailing } is not found + while (!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1); + chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t); + strcpy(chatmessage->chatmessage, chatmessagestring); + chatmessage->time = -2 * CHATMESSAGE_RECENTTIME; + chatmessage->next = replychat->firstchatmessage; + //add the chat message to the reply chat + replychat->firstchatmessage = chatmessage; + replychat->numchatmessages++; + } //end while + } //end while + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpReplyChat(replychatlist); + if (bot_developer) + { + BotCheckReplyChatIntegrety(replychatlist); + } //end if + // + if (!replychatlist) + { + botimport.Print(PRT_MESSAGE, "no rchats\n"); + } + // + return replychatlist; +} //end of the function BotLoadReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpInitialChat(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *m; + + Log_Write("{"); + for (t = chat->types; t; t = t->next) + { + Log_Write(" type \"%s\"", t->name); + Log_Write(" {"); + Log_Write(" numchatmessages = %d", t->numchatmessages); + for (m = t->firstchatmessage; m; m = m->next) + { + Log_Write(" \"%s\"", m->chatmessage); + } //end for + Log_Write(" }"); + } //end for + Log_Write("}"); +} //end of the function BotDumpInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname) +{ + int pass, foundchat, indent, size; + char *ptr = NULL; + char chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chat_t *chat = NULL; + bot_chattype_t *chattype = NULL; + bot_chatmessage_t *chatmessage = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + // + size = 0; + foundchat = qfalse; + //a bot chat is parsed in two phases + for (pass = 0; pass < 2; pass++) + { + //allocate memory + if (pass && size) + { + ptr = (char *) GetClearedMemory(size); + } + //load the source file + source = LoadSourceFile(chatfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", chatfile); + return NULL; + } //end if + //chat structure + if (pass) + { + chat = (bot_chat_t *) ptr; + ptr += sizeof(bot_chat_t); + } //end if + size = sizeof(bot_chat_t); + // + while (PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "chat")) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + //after the chat name we expect a opening brace + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + //if the chat name is found + if (!Q_stricmp(token.string, chatname)) + { + foundchat = qtrue; + //read the chat types + while (1) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "}")) + { + break; + } + if (strcmp(token.string, "type")) + { + SourceError(source, "expected type found %s\n", token.string); + FreeSource(source); + return NULL; + } //end if + //expect the chat type name + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (pass) + { + chattype = (bot_chattype_t *) ptr; + strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME); + chattype->firstchatmessage = NULL; + //add the chat type to the chat + chattype->next = chat->types; + chat->types = chattype; + // + ptr += sizeof(bot_chattype_t); + } //end if + size += sizeof(bot_chattype_t); + //read the chat messages + while (!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + if (pass) + { + chatmessage = (bot_chatmessage_t *) ptr; + chatmessage->time = -2 * CHATMESSAGE_RECENTTIME; + //put the chat message in the list + chatmessage->next = chattype->firstchatmessage; + chattype->firstchatmessage = chatmessage; + //store the chat message + ptr += sizeof(bot_chatmessage_t); + chatmessage->chatmessage = ptr; + strcpy(chatmessage->chatmessage, chatmessagestring); + ptr += strlen(chatmessagestring) + 1; + //the number of chat messages increased + chattype->numchatmessages++; + } //end if + size += sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1; + } //end if + } //end while + } //end if + else //skip the bot chat + { + indent = 1; + while (indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "{")) + { + indent++; + } + else if (!strcmp(token.string, "}")) + { + indent--; + } + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source + FreeSource(source); + //if the requested character is not found + if (!foundchat) + { + botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile); + return NULL; + } //end if + } //end for + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile); +#endif + // + //BotDumpInitialChat(chat); + if (bot_developer) + { + BotCheckInitialChatIntegrety(chat); + } //end if +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + //character was read succesfully + return chat; +} //end of the function BotLoadInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeChatFile(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + if (cs->chat) + { + FreeMemory(cs->chat); + } + cs->chat = NULL; +} //end of the function BotFreeChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname) +{ + bot_chatstate_t *cs; + int n, avail = 0; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return BLERR_CANNOTLOADICHAT; + } + BotFreeChatFile(chatstate); + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for (n = 0; n < MAX_CLIENTS; n++) + { + if (!ichatdata[n].inuse) + { + if (avail == -1) + { + avail = n; + } + continue; + } + if (strcmp(chatfile, ichatdata[n].filename) != 0) + { + continue; + } + if (strcmp(chatname, ichatdata[n].chatname) != 0) + { + continue; + } + cs->chat = ichatdata[n].chat; + // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); + return BLERR_NOERROR; + } + + if (avail == -1) + { + botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } + } + + PS_SetBaseFolder("botfiles"); + cs->chat = BotLoadInitialChat(chatfile, chatname); + PS_SetBaseFolder(""); + if (!cs->chat) + { + botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } //end if + if (!LibVarGetValue("bot_reloadcharacters")) + { + ichatdata[avail].chat = cs->chat; + Q_strncpyz(ichatdata[avail].chatname, chatname, sizeof(ichatdata[avail].chatname)); + Q_strncpyz(ichatdata[avail].filename, chatfile, sizeof(ichatdata[avail].filename)); + ichatdata[avail].inuse = qtrue; + } //end if + + return BLERR_NOERROR; +} //end of the function BotLoadChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext, + bot_matchvariable_t *variables, unsigned long vcontext, int reply) +{ + int num, len, i, expansion; + char *outputbuf, *ptr, *msgptr; + char temp[MAX_MESSAGE_SIZE]; + + expansion = qfalse; + msgptr = message; + outputbuf = outmessage; + len = 0; + // + while (*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch (*msgptr) + { + case 'v': //variable + { + msgptr++; + num = 0; + while (*msgptr && *msgptr != ESCAPE_CHAR) + { + num = num * 10 + (*msgptr++) - '0'; + } //end while + //step over the trailing escape char + if (*msgptr) + { + msgptr++; + } + if (num > MAX_MATCHVARIABLES) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num); + return qfalse; + } //end if + ptr = variables[num].ptr; + if (ptr) + { + for (i = 0; i < variables[num].length; i++) + { + temp[i] = ptr[i]; + } //end for + temp[i] = 0; + //if it's a reply message + if (reply) + { + //replace the reply synonyms in the variables + BotReplaceReplySynonyms(temp, vcontext); + } //end if + else + { + //replace synonyms in the variable context + BotReplaceSynonyms(temp, vcontext); + } //end else + // + if (len + strlen(temp) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], temp); + len += strlen(temp); + } //end if + break; + } //end case + case 'r': //random + { + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) + { + msgptr++; + } + //find the random keyword + ptr = RandomString(temp); + if (!ptr) + { + botimport.Print(PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp); + return qfalse; + } //end if + if (len + strlen(ptr) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], ptr); + len += strlen(ptr); + expansion = qtrue; + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + outputbuf[len++] = *msgptr++; + if (len >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + break; + } //end if + } //end else + } //end while + outputbuf[len] = '\0'; + //replace synonyms weighted in the message context + BotReplaceWeightedSynonyms(outputbuf, mcontext); + //return true if a random was expanded + return expansion; +} //end of the function BotExpandChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotConstructChatMessage(bot_chatstate_t *chatstate, char *message, unsigned long mcontext, + bot_matchvariable_t *variables, unsigned long vcontext, int reply) +{ + int i; + char srcmessage[MAX_MESSAGE_SIZE]; + + strcpy(srcmessage, message); + for (i = 0; i < 10; i++) + { + if (!BotExpandChatMessage(chatstate->chatmessage, srcmessage, mcontext, variables, vcontext, reply)) + { + break; + } //end if + strcpy(srcmessage, chatstate->chatmessage); + } //end for + if (i >= 10) + { + botimport.Print(PRT_WARNING, "too many expansions in chat message\n"); + botimport.Print(PRT_WARNING, "%s\n", chatstate->chatmessage); + } //end if +} //end of the function BotConstructChatMessage +//=========================================================================== +// randomly chooses one of the chat message of the given type +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *BotChooseInitialChatMessage(bot_chatstate_t *cs, char *type) +{ + int n, numchatmessages; + float besttime; + bot_chattype_t *t; + bot_chatmessage_t *m, *bestchatmessage; + bot_chat_t *chat; + + chat = cs->chat; + for (t = chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + numchatmessages = 0; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) + { + continue; + } + numchatmessages++; + } //end if + //if all chat messages have been used recently + if (numchatmessages <= 0) + { + besttime = 0; + bestchatmessage = NULL; + for (m = t->firstchatmessage; m; m = m->next) + { + if (!besttime || m->time < besttime) + { + bestchatmessage = m; + besttime = m->time; + } //end if + } //end for + if (bestchatmessage) + { + return bestchatmessage->chatmessage; + } + } //end if + else //choose a chat message randomly + { + n = random() * numchatmessages; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) + { + continue; + } + if (--n < 0) + { + m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + return m->chatmessage; + } //end if + } //end for + } //end else + return NULL; + } //end if + } //end for + return NULL; +} //end of the function BotChooseInitialChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumInitialChats(int chatstate, char *type) +{ + bot_chatstate_t *cs; + bot_chattype_t *t; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return 0; + } + + for (t = cs->chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + if (LibVarGetValue("bot_testichat")) + { + botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages); + botimport.Print(PRT_MESSAGE, "-------------------\n"); + } + return t->numchatmessages; + } //end if + } //end for + return 0; +} //end of the function BotNumInitialChats +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) +{ + char *message; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + //if no chat file is loaded + if (!cs->chat) + { + return; + } + //choose a chat message randomly of the given type + message = BotChooseInitialChatMessage(cs, type); + //if there's no message of the given type + if (!message) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type); +#endif //DEBUG + return; + } //end if + // + memset(variables, 0, sizeof(variables)); + if (var0) + { + variables[0].ptr = var0; + variables[0].length = strlen(var0); + } + if (var1) + { + variables[1].ptr = var1; + variables[1].length = strlen(var1); + } + if (var2) + { + variables[2].ptr = var2; + variables[2].length = strlen(var2); + } + if (var3) + { + variables[3].ptr = var3; + variables[3].length = strlen(var3); + } + if (var4) + { + variables[4].ptr = var4; + variables[4].length = strlen(var4); + } + if (var5) + { + variables[5].ptr = var5; + variables[5].length = strlen(var5); + } + if (var6) + { + variables[6].ptr = var6; + variables[6].length = strlen(var6); + } + if (var7) + { + variables[7].ptr = var7; + variables[7].length = strlen(var7); + } + // + BotConstructChatMessage(cs, message, mcontext, variables, 0, qfalse); +} //end of the function BotInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPrintReplyChatKeys(bot_replychat_t *replychat) +{ + bot_replychatkey_t *key; + bot_matchpiece_t *mp; + + botimport.Print(PRT_MESSAGE, "["); + for (key = replychat->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) + { + botimport.Print(PRT_MESSAGE, "&"); + } + else if (key->flags & RCKFL_NOT) + { + botimport.Print(PRT_MESSAGE, "!"); + } + // + if (key->flags & RCKFL_NAME) + { + botimport.Print(PRT_MESSAGE, "name"); + } + else if (key->flags & RCKFL_GENDERFEMALE) + { + botimport.Print(PRT_MESSAGE, "female"); + } + else if (key->flags & RCKFL_GENDERMALE) + { + botimport.Print(PRT_MESSAGE, "male"); + } + else if (key->flags & RCKFL_GENDERLESS) + { + botimport.Print(PRT_MESSAGE, "it"); + } + else if (key->flags & RCKFL_VARIABLES) + { + botimport.Print(PRT_MESSAGE, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) + { + botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string); + } + else + { + botimport.Print(PRT_MESSAGE, "%d", mp->variable); + } + if (mp->next) + { + botimport.Print(PRT_MESSAGE, ", "); + } + } //end for + botimport.Print(PRT_MESSAGE, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + botimport.Print(PRT_MESSAGE, "\"%s\"", key->string); + } //end if + if (key->next) + { + botimport.Print(PRT_MESSAGE, ", "); + } + else + { + botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority); + } + } //end for + botimport.Print(PRT_MESSAGE, "{\n"); +} //end of the function BotPrintReplyChatKeys +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) +{ + bot_replychat_t *rchat, *bestrchat; + bot_replychatkey_t *key; + bot_chatmessage_t *m, *bestchatmessage; + bot_match_t match, bestmatch; + int bestpriority, num, found, res, numchatmessages; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return qfalse; + } + memset(&match, 0, sizeof(bot_match_t)); + strcpy(match.string, message); + bestpriority = -1; + bestchatmessage = NULL; + bestrchat = NULL; + //go through all the reply chats + for (rchat = replychats; rchat; rchat = rchat->next) + { + found = qfalse; + for (key = rchat->keys; key; key = key->next) + { + res = qfalse; + //get the match result + if (key->flags & RCKFL_NAME) + { + res = (StringContains(message, cs->name, qfalse) != -1); + } + else if (key->flags & RCKFL_BOTNAMES) + { + res = (StringContains(key->string, cs->name, qfalse) != -1); + } + else if (key->flags & RCKFL_GENDERFEMALE) + { + res = (cs->gender == CHAT_GENDERFEMALE); + } + else if (key->flags & RCKFL_GENDERMALE) + { + res = (cs->gender == CHAT_GENDERMALE); + } + else if (key->flags & RCKFL_GENDERLESS) + { + res = (cs->gender == CHAT_GENDERLESS); + } + else if (key->flags & RCKFL_VARIABLES) + { + res = StringsMatch(key->match, &match); + } + else if (key->flags & RCKFL_STRING) + { + res = (StringContainsWord(message, key->string, qfalse) != NULL); + } + //if the key must be present + if (key->flags & RCKFL_AND) + { + if (!res) + { + found = qfalse; + break; + } //end if + //botnames is an exception + //if (!(key->flags & RCKFL_BOTNAMES)) found = qtrue; + } //end else if + //if the key must be absent + else if (key->flags & RCKFL_NOT) + { + if (res) + { + found = qfalse; + break; + } //end if + } //end if + else if (res) + { + found = qtrue; + } //end else + } //end for + // + if (found) + { + if (rchat->priority > bestpriority) + { + numchatmessages = 0; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) + { + continue; + } + numchatmessages++; + } //end if + num = random() * numchatmessages; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (--num < 0) + { + break; + } + if (m->time > AAS_Time()) + { + continue; + } + } //end for + //if the reply chat has a message + if (m) + { + memcpy(&bestmatch, &match, sizeof(bot_match_t)); + bestchatmessage = m; + bestrchat = rchat; + bestpriority = rchat->priority; + } //end if + } //end if + } //end if + } //end for + if (bestchatmessage) + { + if (var0) + { + bestmatch.variables[0].ptr = var0; + bestmatch.variables[0].length = strlen(var0); + } + if (var1) + { + bestmatch.variables[1].ptr = var1; + bestmatch.variables[1].length = strlen(var1); + } + if (var2) + { + bestmatch.variables[2].ptr = var2; + bestmatch.variables[2].length = strlen(var2); + } + if (var3) + { + bestmatch.variables[3].ptr = var3; + bestmatch.variables[3].length = strlen(var3); + } + if (var4) + { + bestmatch.variables[4].ptr = var4; + bestmatch.variables[4].length = strlen(var4); + } + if (var5) + { + bestmatch.variables[5].ptr = var5; + bestmatch.variables[5].length = strlen(var5); + } + if (var6) + { + bestmatch.variables[6].ptr = var6; + bestmatch.variables[6].length = strlen(var6); + } + if (var7) + { + bestmatch.variables[7].ptr = var7; + bestmatch.variables[7].length = strlen(var7); + } + if (LibVarGetValue("bot_testrchat")) + { + for (m = bestrchat->firstchatmessage; m; m = m->next) + { + BotConstructChatMessage(cs, m->chatmessage, mcontext, bestmatch.variables, vcontext, qtrue); + BotRemoveTildes(cs->chatmessage); + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } //end if + } //end if + else + { + bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, bestmatch.variables, vcontext, qtrue); + } //end else + return qtrue; + } //end if + return qfalse; +} //end of the function BotReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChatLength(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return 0; + } + return strlen(cs->chatmessage); +} //end of the function BotChatLength +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEnterChat(int chatstate, int client, int sendto) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + + if (strlen(cs->chatmessage)) + { + BotRemoveTildes(cs->chatmessage); + if (LibVarGetValue("bot_testichat")) + { + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } + else + { + if (sendto == CHAT_TEAM) + { + EA_SayTeam(client, cs->chatmessage); + } + else + { + EA_Say(client, cs->chatmessage); + } + } + //clear the chat message from the state + strcpy(cs->chatmessage, ""); + } //end if +} //end of the function BotEnterChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetChatMessage(int chatstate, char *buf, int size) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + + BotRemoveTildes(cs->chatmessage); + strncpy(buf, cs->chatmessage, size - 1); + buf[size - 1] = '\0'; + //clear the chat message from the state + strcpy(cs->chatmessage, ""); +} //end of the function BotGetChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatGender(int chatstate, int gender) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + switch (gender) + { + case CHAT_GENDERFEMALE: + cs->gender = CHAT_GENDERFEMALE; + break; + case CHAT_GENDERMALE: + cs->gender = CHAT_GENDERMALE; + break; + default: + cs->gender = CHAT_GENDERLESS; + break; + } //end switch +} //end of the function BotSetChatGender +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatName(int chatstate, char *name) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) + { + return; + } + memset(cs->name, 0, sizeof(cs->name)); + strncpy(cs->name, name, sizeof(cs->name)); + cs->name[sizeof(cs->name) - 1] = '\0'; +} //end of the function BotSetChatName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetChatAI(void) +{ + bot_replychat_t *rchat; + bot_chatmessage_t *m; + + for (rchat = replychats; rchat; rchat = rchat->next) + { + for (m = rchat->firstchatmessage; m; m = m->next) + { + m->time = 0; + } //end for + } //end for +} //end of the function BotResetChatAI +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocChatState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botchatstates[i]) + { + botchatstates[i] = GetClearedMemory(sizeof(bot_chatstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocChatState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeChatState(int handle) +{ + bot_chatstate_t *cs; + bot_consolemessage_t m; + int h; + + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return; + } //end if + cs = botchatstates[handle]; + if (LibVarGetValue("bot_reloadcharacters")) + { + BotFreeChatFile(handle); + } //end if + //free all the console messages left in the chat state + for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m)) + { + //remove the console message + BotRemoveConsoleMessage(handle, h); + } //end for + FreeMemory(botchatstates[handle]); + botchatstates[handle] = NULL; +} //end of the function BotFreeChatState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupChatAI(void) +{ + char *file; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + PS_SetBaseFolder("botfiles"); + file = LibVarString("synfile", "syn.c"); + synonyms = BotLoadSynonyms(file); + file = LibVarString("rndfile", "rnd.c"); + randomstrings = BotLoadRandomStrings(file); + file = LibVarString("matchfile", "match.c"); + matchtemplates = BotLoadMatchTemplates(file); + // + if (!LibVarValue("nochat", "0")) + { + file = LibVarString("rchatfile", "rchat.c"); + replychats = BotLoadReplyChat(file); + } //end if + PS_SetBaseFolder(""); + + InitConsoleMessageHeap(); + +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + return BLERR_NOERROR; +} //end of the function BotSetupChatAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownChatAI(void) +{ + int i; + + //free all remaining chat states + for (i = 0; i < MAX_CLIENTS; i++) + { + if (botchatstates[i]) + { + BotFreeChatState(i); + } //end if + } //end for + //free all cached chats + for (i = 0; i < MAX_CLIENTS; i++) + { + if (ichatdata[i].inuse) + { + FreeMemory(ichatdata[i].chat); + ichatdata[i].inuse = qfalse; + } //end if + } //end for + if (consolemessageheap) + { + FreeMemory(consolemessageheap); + } + consolemessageheap = NULL; + if (matchtemplates) + { + BotFreeMatchTemplates(matchtemplates); + } + matchtemplates = NULL; + if (randomstrings) + { + FreeMemory(randomstrings); + } + randomstrings = NULL; + if (synonyms) + { + FreeMemory(synonyms); + } + synonyms = NULL; + if (replychats) + { + BotFreeReplyChat(replychats); + } + replychats = NULL; +} //end of the function BotShutdownChatAI diff --git a/src/botlib/be_ai_gen.c b/src/botlib/be_ai_gen.c new file mode 100644 index 000000000..3ac15f57d --- /dev/null +++ b/src/botlib/be_ai_gen.c @@ -0,0 +1,161 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_gen.c + * @brief genetic selection + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_gen.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticSelection(int numranks, float *rankings) +{ + float sum, select; + int i, index; + + sum = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) + { + continue; + } + sum += rankings[i]; + } //end for + if (sum > 0) + { + //select a bot where the ones with the higest rankings have + //the highest chance of being selected + select = random() * sum; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) + { + continue; + } + sum -= rankings[i]; + if (sum <= 0) + { + return i; + } + } //end for + } //end if + //select a bot randomly + index = random() * numranks; + for (i = 0; i < numranks; i++) + { + if (rankings[index] >= 0) + { + return index; + } + index = (index + 1) % numranks; + } //end for + return 0; +} //end of the function GeneticSelection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child) +{ + float rankings[256], max; + int i; + + if (numranks > 256) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too many bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + for (max = 0, i = 0; i < numranks; i++) + { + if (ranks[i] < 0) + { + continue; + } + max++; + } //end for + if (max < 3) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too few valid bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + memcpy(rankings, ranks, sizeof(float) * numranks); + //select first parent + *parent1 = GeneticSelection(numranks, rankings); + rankings[*parent1] = -1; + //select second parent + *parent2 = GeneticSelection(numranks, rankings); + rankings[*parent2] = -1; + //reverse the rankings + max = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) + { + continue; + } + if (rankings[i] > max) + { + max = rankings[i]; + } + } //end for + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) + { + continue; + } + rankings[i] = max - rankings[i]; + } //end for + //select child + *child = GeneticSelection(numranks, rankings); + return qtrue; +} //end of the function GeneticParentsAndChildSelection diff --git a/src/botlib/be_ai_goal.c b/src/botlib/be_ai_goal.c new file mode 100644 index 000000000..7997bf90f --- /dev/null +++ b/src/botlib/be_ai_goal.c @@ -0,0 +1,1813 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_goal.c + * @brief goal AI + */ + +#include "../qcommon/q_shared.h" +#include "l_utils.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + +//#define DEBUG_AI_GOAL +#ifdef RANDOMIZE +#define UNDECIDEDFUZZY +#endif //RANDOMIZE +#define DROPPEDWEIGHT +//avoid goal time +#define AVOID_TIME 30 +//avoid dropped goal time +#define AVOIDDROPPED_TIME 5 +// +#define TRAVELTIME_SCALE 0.01 + +//location in the map "target_location" +typedef struct maplocation_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + struct maplocation_s *next; +} maplocation_t; + +//camp spots "info_camp" +typedef struct campspot_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + float range; + float weight; + float wait; + float random; + struct campspot_s *next; +} campspot_t; + +typedef struct levelitem_s +{ + int number; //number of the level item + int iteminfo; //index into the item info + int notteam; //true if not in teamplay + int notfree; //true if not in ffa + int notsingle; //true if not in single + vec3_t origin; //origin of the item + int goalareanum; //area the item is in + vec3_t goalorigin; //goal origin within the area + int entitynum; //entity number + float timeout; //item is removed after this time + struct levelitem_s *prev, *next; +} levelitem_t; + +typedef struct iteminfo_s +{ + char classname[32]; //classname of the item + char name[MAX_STRINGFIELD]; //name of the item + char model[MAX_STRINGFIELD]; //model of the item + int modelindex; //model index + int type; //item type + int index; //index in the inventory + float respawntime; //respawn time + vec3_t mins; //mins of the item + vec3_t maxs; //maxs of the item + int number; //number of the item info +} iteminfo_t; + +#define ITEMINFO_OFS(x) (int)&(((iteminfo_t *)0)->x) + +fielddef_t iteminfo_fields[] = +{ + { "name", ITEMINFO_OFS(name), FT_STRING }, + { "model", ITEMINFO_OFS(model), FT_STRING }, + { "modelindex", ITEMINFO_OFS(modelindex), FT_INT }, + { "type", ITEMINFO_OFS(type), FT_INT }, + { "index", ITEMINFO_OFS(index), FT_INT }, + { "respawntime", ITEMINFO_OFS(respawntime), FT_FLOAT }, + { "mins", ITEMINFO_OFS(mins), FT_FLOAT | FT_ARRAY, 3}, + { "maxs", ITEMINFO_OFS(maxs), FT_FLOAT | FT_ARRAY, 3}, + { 0, 0, 0 } +}; + +structdef_t iteminfo_struct = +{ + sizeof(iteminfo_t), iteminfo_fields +}; + +typedef struct itemconfig_s +{ + int numiteminfo; + iteminfo_t *iteminfo; +} itemconfig_t; + +//goal state +typedef struct bot_goalstate_s +{ + struct weightconfig_s *itemweightconfig; //weight config + int *itemweightindex; //index from item to weight + // + int client; //client using this goal state + int lastreachabilityarea; //last area with reachabilities the bot was in + // + bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack + int goalstacktop; //the top of the goal stack + // + int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid + float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals +} bot_goalstate_t; + +bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; +//item configuration +itemconfig_t *itemconfig = NULL; +//level items +levelitem_t *levelitemheap = NULL; +levelitem_t *freelevelitems = NULL; +levelitem_t *levelitems = NULL; +int numlevelitems = 0; +//map locations +maplocation_t *maplocations = NULL; +//camp spots +campspot_t *campspots = NULL; +//the game type +// START Arnout changes, 28-08-2002. +// removed gametype, added single player +//int g_gametype; +qboolean g_singleplayer; +// END Arnout changes, 28-08-2002. + +// Rafael gameskill +int g_gameskill; +// done + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_goalstate_t *BotGoalStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state %d\n", handle); + return NULL; + } //end if + return botgoalstates[handle]; +} //end of the function BotGoalStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child) +{ + bot_goalstate_t *p1, *p2, *c; + + p1 = BotGoalStateFromHandle(parent1); + p2 = BotGoalStateFromHandle(parent2); + c = BotGoalStateFromHandle(child); + + InterbreedWeightConfigs(p1->itemweightconfig, p2->itemweightconfig, + c->itemweightconfig); +} //end of the function BotInterbreedingGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSaveGoalFuzzyLogic(int goalstate, char *filename) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + + //WriteWeightConfig(filename, gs->itemweightconfig); +} //end of the function BotSaveGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMutateGoalFuzzyLogic(int goalstate, float range) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + + EvolveWeightConfig(gs->itemweightconfig); +} //end of the function BotMutateGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +itemconfig_t *LoadItemConfig(char *filename) +{ + int max_iteminfo; + token_t token; + char path[MAX_PATH]; + source_t *source; + itemconfig_t *ic; + iteminfo_t *ii; + + max_iteminfo = (int) LibVarValue("max_iteminfo", "256"); + if (max_iteminfo < 0) + { + botimport.Print(PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo); + max_iteminfo = 128; + LibVarSet("max_iteminfo", "128"); + } + + strncpy(path, filename, MAX_PATH); + source = LoadSourceFile(path); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", path); + return NULL; + } //end if + //initialize item config + ic = (itemconfig_t *) GetClearedHunkMemory(sizeof(itemconfig_t) + + max_iteminfo * sizeof(iteminfo_t)); + ic->iteminfo = ( iteminfo_t * )((char *) ic + sizeof(itemconfig_t)); + ic->numiteminfo = 0; + //parse the item config file + while (PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "iteminfo")) + { + if (ic->numiteminfo >= max_iteminfo) + { + SourceError(source, "more than %d item info defined\n", max_iteminfo); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii = &ic->iteminfo[ic->numiteminfo]; + memset(ii, 0, sizeof(iteminfo_t)); + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeMemory(ic); + FreeMemory(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + strncpy(ii->classname, token.string, sizeof(ii->classname) - 1); + if (!ReadStructure(source, &iteminfo_struct, (char *) ii)) + { + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii->number = ic->numiteminfo; + ic->numiteminfo++; + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!ic->numiteminfo) + { + botimport.Print(PRT_WARNING, "no item info loaded\n"); + } + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return ic; +} //end of the function LoadItemConfig +//=========================================================================== +// index to find the weight function of an iteminfo +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *ItemWeightIndex(weightconfig_t *iwc, itemconfig_t *ic) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * ic->numiteminfo); + + for (i = 0; i < ic->numiteminfo; i++) + { + index[i] = FindFuzzyWeight(iwc, ic->iteminfo[i].classname); + if (index[i] < 0) + { + Log_Write("item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname); + } //end if + } //end for + return index; +} //end of the function ItemWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitLevelItemHeap(void) +{ + int i, max_levelitems; + + if (levelitemheap) + { + FreeMemory(levelitemheap); + } + + max_levelitems = (int) LibVarValue("max_levelitems", "256"); + levelitemheap = (levelitem_t *) GetMemory(max_levelitems * sizeof(levelitem_t)); + + for (i = 0; i < max_levelitems - 2; i++) + { + levelitemheap[i].next = &levelitemheap[i + 1]; + } //end for + levelitemheap[max_levelitems - 1].next = NULL; + // + freelevelitems = levelitemheap; +} //end of the function InitLevelItemHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +levelitem_t *AllocLevelItem(void) +{ + levelitem_t *li; + + li = freelevelitems; + if (!li) + { + botimport.Print(PRT_FATAL, "out of level items\n"); + return NULL; + } //end if + // + freelevelitems = freelevelitems->next; + memset(li, 0, sizeof(levelitem_t)); + return li; +} //end of the function AllocLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeLevelItem(levelitem_t *li) +{ + li->next = freelevelitems; + freelevelitems = li; +} //end of the function FreeLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddLevelItemToList(levelitem_t *li) +{ + if (levelitems) + { + levelitems->prev = li; + } + li->prev = NULL; + li->next = levelitems; + levelitems = li; +} //end of the function AddLevelItemToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveLevelItemFromList(levelitem_t *li) +{ + if (li->prev) + { + li->prev->next = li->next; + } + else + { + levelitems = li->next; + } + if (li->next) + { + li->next->prev = li->prev; + } +} //end of the function RemoveLevelItemFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeInfoEntities(void) +{ + maplocation_t *ml, *nextml; + campspot_t *cs, *nextcs; + + for (ml = maplocations; ml; ml = nextml) + { + nextml = ml->next; + FreeMemory(ml); + } //end for + maplocations = NULL; + for (cs = campspots; cs; cs = nextcs) + { + nextcs = cs->next; + FreeMemory(cs); + } //end for + campspots = NULL; +} //end of the function BotFreeInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitInfoEntities(void) +{ + char classname[MAX_EPAIRKEY]; + maplocation_t *ml; + campspot_t *cs; + int ent, numlocations, numcampspots; + + BotFreeInfoEntities(); + // + numlocations = 0; + numcampspots = 0; + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + + //map locations + if (!strcmp(classname, "target_location")) + { + ml = (maplocation_t *) GetClearedMemory(sizeof(maplocation_t)); + AAS_VectorForBSPEpairKey(ent, "origin", ml->origin); + AAS_ValueForBSPEpairKey(ent, "message", ml->name, sizeof(ml->name)); + ml->areanum = AAS_PointAreaNum(ml->origin); + ml->next = maplocations; + maplocations = ml; + numlocations++; + } //end if + //camp spots + else if (!strcmp(classname, "info_camp")) + { + cs = (campspot_t *) GetClearedMemory(sizeof(campspot_t)); + AAS_VectorForBSPEpairKey(ent, "origin", cs->origin); + //cs->origin[2] += 16; + AAS_ValueForBSPEpairKey(ent, "message", cs->name, sizeof(cs->name)); + AAS_FloatForBSPEpairKey(ent, "range", &cs->range); + AAS_FloatForBSPEpairKey(ent, "weight", &cs->weight); + AAS_FloatForBSPEpairKey(ent, "wait", &cs->wait); + AAS_FloatForBSPEpairKey(ent, "random", &cs->random); + cs->areanum = AAS_PointAreaNum(cs->origin); + if (!cs->areanum) + { + botimport.Print(PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2]); + FreeMemory(cs); + continue; + } //end if + cs->next = campspots; + campspots = cs; + //AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW); + numcampspots++; + } //end else if + } //end for + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "%d map locations\n", numlocations); + botimport.Print(PRT_MESSAGE, "%d camp spots\n", numcampspots); + } //end if +} //end of the function BotInitInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitLevelItems(void) +{ + int i, spawnflags; + char classname[MAX_EPAIRKEY]; + vec3_t origin; + int ent; + itemconfig_t *ic; + levelitem_t *li; + + //initialize the map locations and camp spots + BotInitInfoEntities(); + + //initialize the level item heap + InitLevelItemHeap(); + levelitems = NULL; + numlevelitems = 0; + // + ic = itemconfig; + if (!ic) + { + return; + } + + //if there's no AAS file loaded + if (!AAS_Loaded()) + { + return; + } + + //update the modelindexes of the item info + for (i = 0; i < ic->numiteminfo; i++) + { + //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); + if (!ic->iteminfo[i].modelindex) + { + Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname); + } //end if + } //end for + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + // + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + //FIXME: don't do this + // for now skip all floating entities + if (spawnflags & 1) + { + continue; + } + // + for (i = 0; i < ic->numiteminfo; i++) + { + if (!strcmp(classname, ic->iteminfo[i].classname)) + { + //get the origin of the item + if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + li = AllocLevelItem(); + if (!li) + { + return; + } + // + li->number = ++numlevelitems; + li->timeout = 0; + li->entitynum = 0; + // + AAS_IntForBSPEpairKey(ent, "notfree", &li->notfree); + AAS_IntForBSPEpairKey(ent, "notteam", &li->notteam); + AAS_IntForBSPEpairKey(ent, "notsingle", &li->notsingle); + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //item info of the level item + li->iteminfo = i; + //origin of the item + VectorCopy(origin, li->origin); + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + // + AddLevelItemToList(li); + } //end if + else + { + botimport.Print(PRT_ERROR, "item %s without origin\n", classname); + } //end else + break; + } //end if + } //end for + if (i >= ic->numiteminfo) + { + Log_Write("entity %s unknown item\r\n", classname); + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "found %d level items\n", numlevelitems); +} //end of the function BotInitLevelItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGoalName(int number, char *name, int size) +{ + levelitem_t *li; + + if (!itemconfig) + { + return; + } + // + for (li = levelitems; li; li = li->next) + { + if (li->number == number) + { + strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size - 1); + name[size - 1] = '\0'; + return; + } //end for + } //end for + strcpy(name, ""); + return; +} //end of the function BotGoalName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidGoals(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int)); + memset(gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof(float)); +} //end of the function BotResetAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpAvoidGoals(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoaltimes[i] >= AAS_Time()) + { + BotGoalName(gs->avoidgoals[i], name, 32); + Log_Write("avoid goal %s, number %d for %f seconds", name, + gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time()); + } //end if + } //end for +} //end of the function BotDumpAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidGoals(bot_goalstate_t *gs, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + //if this avoid goal has expired + if (gs->avoidgoaltimes[i] < AAS_Time()) + { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveFromAvoidGoals(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + gs->avoidgoaltimes[i] = 0; + return; + } //end if + } //end for +} //end of the function BotRemoveFromAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotAvoidGoalTime(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return 0; + } + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + return gs->avoidgoaltimes[i] - AAS_Time(); + } //end if + } //end for + return 0; +} //end of the function BotAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal) +{ + levelitem_t *li; + + if (!itemconfig) + { + return -1; + } + for (li = levelitems; li; li = li->next) + { + if (li->number <= index) + { + continue; + } + // +// START Arnout changes, 28-08-2002. +// removed gametype, added single player + //if (g_gametype == GT_SINGLE_PLAYER) { + if (g_singleplayer) + { + if (li->notsingle) + { + continue; + } + } + // Gordon: GT_TEAM no longer exists, switching for GT_WOLF + /* else + if (g_gametype >= GT_WOLF) { + if (li->notteam) continue; + } + else { + if (li->notfree) continue; + }*/ +// END Arnout changes, 28-08-2002. + // + if (!Q_stricmp(name, itemconfig->iteminfo[li->iteminfo].name)) + { + goal->areanum = li->goalareanum; + VectorCopy(li->goalorigin, goal->origin); + goal->entitynum = li->entitynum; + VectorCopy(itemconfig->iteminfo[li->iteminfo].mins, goal->mins); + VectorCopy(itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs); + goal->number = li->number; + //botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); + return li->number; + } //end if + } //end for + return -1; +} //end of the function BotGetLevelItemGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetMapLocationGoal(char *name, bot_goal_t *goal) +{ + maplocation_t *ml; + vec3_t mins = { -8, -8, -8 }, maxs = { 8, 8, 8 }; + + for (ml = maplocations; ml; ml = ml->next) + { + if (!Q_stricmp(ml->name, name)) + { + goal->areanum = ml->areanum; + VectorCopy(ml->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotGetMapLocationGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal) +{ + int i; + campspot_t *cs; + vec3_t mins = { -8, -8, -8 }, maxs = { 8, 8, 8 }; + + if (num < 0) + { + num = 0; + } + i = num; + for (cs = campspots; cs; cs = cs->next) + { + if (--i < 0) + { + goal->areanum = cs->areanum; + VectorCopy(cs->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return num + 1; + } //end if + } //end for + return 0; +} //end of the function BotGetNextCampSpotGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//NOTE: enum entityType_t in bg_public.h +#define ET_ITEM 2 + +void BotUpdateEntityItems(void) +{ + int ent, i, modelindex; + vec3_t dir; + levelitem_t *li, *nextli; + aas_entityinfo_t entinfo; + itemconfig_t *ic; + + //timeout current entity items if necessary + for (li = levelitems; li; li = nextli) + { + nextli = li->next; + //if it is a item that will time out + if (li->timeout) + { + //timeout the item + if (li->timeout < AAS_Time()) + { + RemoveLevelItemFromList(li); + FreeLevelItem(li); + } //end if + } //end if + } //end for + //find new entity items + ic = itemconfig; + if (!itemconfig) + { + return; + } + // + for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) + { + if (AAS_EntityType(ent) != ET_ITEM) + { + continue; + } + //get the model index of the entity + modelindex = AAS_EntityModelindex(ent); + // + if (!modelindex) + { + continue; + } + //get info about the entity + AAS_EntityInfo(ent, &entinfo); + //FIXME: don't do this + //skip all floating items for now + if (entinfo.groundent != ENTITYNUM_WORLD) + { + continue; + } + //if the entity is still moving + if (entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2]) + { + continue; + } + //check if the level item isn't already stored + for (li = levelitems; li; li = li->next) + { + //if the model of the level item and the entity are different + if (ic->iteminfo[li->iteminfo].modelindex != modelindex) + { + continue; + } + //if the level item is linked to an entity + if (li->entitynum) + { + if (li->entitynum == ent) + { + VectorCopy(entinfo.origin, li->origin); + break; + } //end if + } //end if + else + { + //check if the entity is very close + VectorSubtract(li->origin, entinfo.origin, dir); + if (VectorLength(dir) < 30) + { + //found an entity for this level item + li->entitynum = ent; + //keep updating the entity origin + VectorCopy(entinfo.origin, li->origin); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin); + //Log_Write("found item %s entity", ic->iteminfo[li->iteminfo].classname); + break; + } //end if + //else botimport.Print(PRT_MESSAGE, "item %s has no attached entity\n", + // ic->iteminfo[li->iteminfo].name); + } //end else + } //end for + if (li) + { + continue; + } + //check if the model is from a known item + for (i = 0; i < ic->numiteminfo; i++) + { + if (ic->iteminfo[i].modelindex == modelindex) + { + break; + } //end if + } //end for + //if the model is not from a known item + if (i >= ic->numiteminfo) + { + continue; + } + //allocate a new level item + li = AllocLevelItem(); + // + if (!li) + { + continue; + } + //entity number of the level item + li->entitynum = ent; + //number for the level item + li->number = numlevelitems + ent; + //set the item info index for the level item + li->iteminfo = i; + //origin of the item + VectorCopy(entinfo.origin, li->origin); + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + // + if (AAS_AreaJumpPad(li->goalareanum)) + { + FreeLevelItem(li); + continue; + } //end if + //time this item out after 30 seconds + //dropped items disappear after 30 seconds + li->timeout = AAS_Time() + 30; + //add the level item to the list + AddLevelItemToList(li); + //botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname); + } //end for +} //end of the function BotUpdateEntityItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpGoalStack(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + for (i = 1; i <= gs->goalstacktop; i++) + { + BotGoalName(gs->goalstack[i].number, name, 32); + Log_Write("%d: %s", i, name); + } //end for +} //end of the function BotDumpGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPushGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + if (gs->goalstacktop >= MAX_GOALSTACK - 1) + { + botimport.Print(PRT_ERROR, "goal heap overflow\n"); + BotDumpGoalStack(goalstate); + return; + } //end if + gs->goalstacktop++; + memcpy(&gs->goalstack[gs->goalstacktop], goal, sizeof(bot_goal_t)); +} //end of the function BotPushGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPopGoal(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + if (gs->goalstacktop > 0) + { + gs->goalstacktop--; + } +} //end of the function BotPopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEmptyGoalStack(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + gs->goalstacktop = 0; +} //end of the function BotEmptyGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetTopGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return qfalse; + } + if (!gs->goalstacktop) + { + return qfalse; + } + memcpy(goal, &gs->goalstack[gs->goalstacktop], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetTopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetSecondGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return qfalse; + } + if (gs->goalstacktop <= 1) + { + return qfalse; + } + memcpy(goal, &gs->goalstack[gs->goalstacktop - 1], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetSecondGoal +//=========================================================================== +// pops a new long term goal on the goal stack in the goalstate +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags) +{ + int areanum, t, weightnum; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return qfalse; + } + if (!gs->itemweightconfig) + { + return qfalse; + } + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + { + return qfalse; + } + //the item configuration + ic = itemconfig; + if (!itemconfig) + { + return qfalse; + } + //best weight and item so far + bestweight = 0; + bestitem = NULL; + memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { +// START Arnout changes, 28-08-2002. +// removed gametype, added single player + //if (g_gametype == GT_SINGLE_PLAYER) { + if (g_singleplayer) + { + if (li->notsingle) + { + continue; + } + } + // Gordon: GT_TEAM no longer exists, switching for GT_WOLF + /* else + if (g_gametype >= GT_WOLF) { + if (li->notteam) continue; + } + else { + if (li->notfree) continue; + }*/ +// END Arnout changes, 28-08-2002. + //if the item is not in a possible goal area + if (!li->goalareanum) + { + continue; + } + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + { + continue; + } + //if this goal is in the avoid goals + if (BotAvoidGoalTime(goalstate, li->number) > 0) + { + continue; + } + +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + { + weight += 1000; + } +#endif //DROPPEDWEIGHT + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0) + { + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + { + /* + //if not in lava or slime + if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum)) + { + if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin)) + { + VectorSet(goal.mins, -15, -15, -15); + VectorSet(goal.maxs, 15, 15, 15); + goal.entitynum = 0; + goal.number = 0; + goal.flags = GFL_ROAM; + goal.iteminfo = 0; + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // + #ifdef DEBUG + botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum); + #endif //DEBUG + return qtrue; + } //end if + } //end if + */ + return qfalse; + } //end if + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + goal.iteminfo = bestitem->iteminfo; + //add the chosen goal to the goals to avoid for a while + avoidtime = iteminfo->respawntime * 0.5; + if (avoidtime < 10) + { + avoidtime = AVOID_TIME; + } + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOIDDROPPED_TIME; + } + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // +#ifdef DEBUG_AI_GOAL + if (bestitem->timeout) + { + botimport.Print(PRT_MESSAGE, "new ltg dropped item %s\n", ic->iteminfo[bestitem->iteminfo].classname); + } //end if + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + botimport.Print(PRT_MESSAGE, "new ltg \"%s\"\n", iteminfo->classname); +#endif //DEBUG_AI_GOAL + return qtrue; +} //end of the function BotChooseLTGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime) +{ + int areanum, t, weightnum, ltg_time; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return qfalse; + } + if (!gs->itemweightconfig) + { + return qfalse; + } + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + { + return qfalse; + } + // + if (ltg) + { + ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags); + } + else + { + ltg_time = 99999; + } + //the item configuration + ic = itemconfig; + if (!itemconfig) + { + return qfalse; + } + //best weight and item so far + bestweight = 0; + bestitem = NULL; + memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { +// START Arnout changes, 28-08-2002. +// removed gametype, added single player + if (g_singleplayer) + { + if (li->notsingle) + { + continue; + } + } + // Gordon: GT_TEAM no longer exists, switching for GT_WOLF + /* else + if (g_gametype >= GT_WOLF) { + if (li->notteam) continue; + } + else { + if (li->notfree) continue; + }*/ +// END Arnout changes, 28-08-2002. + //if the item is in a possible goal area + if (!li->goalareanum) + { + continue; + } + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + { + continue; + } + //if this goal is in the avoid goals + if (BotAvoidGoalTime(goalstate, li->number) > 0) + { + continue; + } + // +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + { + weight += 1000; + } +#endif //DROPPEDWEIGHT + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0 && t < maxtime) + { + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + t = 0; + if (ltg && !li->timeout) + { + //get the travel time from the goal to the long term goal + t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags); + } //end if + //if the travel back is possible and doesn't take too long + if (t <= ltg_time) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + { + return qfalse; + } + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + goal.iteminfo = bestitem->iteminfo; + //add the chosen goal to the goals to avoid for a while + avoidtime = iteminfo->respawntime * 0.5; + if (avoidtime < 10) + { + avoidtime = AVOID_TIME; + } + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOIDDROPPED_TIME; + } + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // +#ifdef DEBUG_AI_GOAL + if (bestitem->timeout) + { + botimport.Print(PRT_MESSAGE, "new nbg dropped item %s\n", ic->iteminfo[bestitem->iteminfo].classname); + } //end if + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + botimport.Print(PRT_MESSAGE, "new nbg \"%s\"\n", iteminfo->classname); +#endif //DEBUG_AI_GOAL + return qtrue; +} //end of the function BotChooseNBGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal) +{ + int i; + vec3_t boxmins, boxmaxs; + vec3_t absmins, absmaxs; + vec3_t safety_maxs = { 0, 0, 0 }; //{4, 4, 10}; + vec3_t safety_mins = { 0, 0, 0 }; //{-4, -4, 0}; + + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, boxmins, boxmaxs); + VectorSubtract(goal->mins, boxmaxs, absmins); + VectorSubtract(goal->maxs, boxmins, absmaxs); + VectorAdd(absmins, goal->origin, absmins); + VectorAdd(absmaxs, goal->origin, absmaxs); + //make the box a little smaller for safety + VectorSubtract(absmaxs, safety_maxs, absmaxs); + VectorSubtract(absmins, safety_mins, absmins); + + for (i = 0; i < 3; i++) + { + if (origin[i] < absmins[i] || origin[i] > absmaxs[i]) + { + return qfalse; + } + } //end for + return qtrue; +} //end of the function BotTouchingGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal) +{ + aas_entityinfo_t entinfo; + bsp_trace_t trace; + vec3_t middle; + + if (!(goal->flags & GFL_ITEM)) + { + return qfalse; + } + // + VectorAdd(goal->mins, goal->mins, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(goal->origin, middle, middle); + // + trace = AAS_Trace(eye, NULL, NULL, middle, viewer, CONTENTS_SOLID); + //if the goal middle point is visible + if (trace.fraction >= 1) + { + //the goal entity number doesn't have to be valid + //just assume it's valid + if (goal->entitynum <= 0) + { + return qfalse; + } + // + //if the entity data isn't valid + AAS_EntityInfo(goal->entitynum, &entinfo); + //NOTE: for some wacko reason entities are sometimes + // not updated + //if (!entinfo.valid) return qtrue; + if (entinfo.ltime < AAS_Time() - 0.5) + { + return qtrue; + } + } //end if + return qfalse; +} //end of the function BotItemGoalInVisButNotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGoalState(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t)); + gs->goalstacktop = 0; + BotResetAvoidGoals(goalstate); +} //end of the function BotResetGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadItemWeights(int goalstate, char *filename) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return BLERR_CANNOTLOADITEMWEIGHTS; + } + //load the weight configuration + PS_SetBaseFolder("botfiles"); + gs->itemweightconfig = ReadWeightConfig(filename); + PS_SetBaseFolder(""); + if (!gs->itemweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weights\n"); + return BLERR_CANNOTLOADITEMWEIGHTS; + } //end if + //if there's no item configuration + if (!itemconfig) + { + return BLERR_CANNOTLOADITEMWEIGHTS; + } + //create the item weight index + gs->itemweightindex = ItemWeightIndex(gs->itemweightconfig, itemconfig); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotLoadItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeItemWeights(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + { + return; + } + if (gs->itemweightconfig) + { + FreeWeightConfig(gs->itemweightconfig); + } + if (gs->itemweightindex) + { + FreeMemory(gs->itemweightindex); + } +} //end of the function BotFreeItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAllocGoalState(int client) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botgoalstates[i]) + { + botgoalstates[i] = GetClearedMemory(sizeof(bot_goalstate_t)); + botgoalstates[i]->client = client; + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocGoalState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeGoalState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state handle %d\n", handle); + return; + } //end if + BotFreeItemWeights(handle); + FreeMemory(botgoalstates[handle]); + botgoalstates[handle] = NULL; +} //end of the function BotFreeGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// START Arnout changes, 28-08-2002. +// removed gametype, added single player +int BotSetupGoalAI(qboolean singleplayer) +{ +// END Arnout changes, 28-08-2002. + char *filename; + + //check if teamplay is on +// START Arnout changes, 28-08-2002. +// removed gametype, added single player +// g_gametype = LibVarValue("g_gametype", "0"); + g_singleplayer = singleplayer; +// END Arnout changes, 28-08-2002. + //item configuration file + PS_SetBaseFolder("botfiles"); + filename = LibVarString("itemconfig", "items.c"); + //load the item configuration + itemconfig = LoadItemConfig(filename); + PS_SetBaseFolder(""); + if (!itemconfig) + { + botimport.Print(PRT_FATAL, "couldn't load item config\n"); + return BLERR_CANNOTLOADITEMCONFIG; + } //end if + //everything went ok + return BLERR_NOERROR; +} //end of the function BotSetupGoalAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownGoalAI(void) +{ + int i; + + if (itemconfig) + { + FreeMemory(itemconfig); + } + itemconfig = NULL; + if (levelitemheap) + { + FreeMemory(levelitemheap); + } + levelitemheap = NULL; + freelevelitems = NULL; + levelitems = NULL; + numlevelitems = 0; + + BotFreeInfoEntities(); + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botgoalstates[i]) + { + BotFreeGoalState(i); + } //end if + } //end for +} //end of the function BotShutdownGoalAI diff --git a/src/botlib/be_ai_move.c b/src/botlib/be_ai_move.c new file mode 100644 index 000000000..a61f18e8e --- /dev/null +++ b/src/botlib/be_ai_move.c @@ -0,0 +1,4383 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_move.c + * @brief bot movement AI + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + + +//#define DEBUG_AI_MOVE +//#define DEBUG_ELEVATOR +//#define DEBUG_GRAPPLE +//movement state + +//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP must be set outside the movement code +typedef struct bot_movestate_s +{ + //input vars (all set outside the movement code) + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + //state vars + int areanum; //area the bot is in + int lastareanum; //last area the bot was in + int lastgoalareanum; //last goal area number + int lastreachnum; //last reachability number + vec3_t lastorigin; //origin previous cycle + float lasttime; + int reachareanum; //area number of the reachabilty + int moveflags; //movement flags + int jumpreach; //set when jumped + float grapplevisible_time; //last time the grapple was visible + float lastgrappledist; //last distance to the grapple end + float reachability_time; //time to use current reachability + int avoidreach[MAX_AVOIDREACH]; //reachabilities to avoid + float avoidreachtimes[MAX_AVOIDREACH]; //times to avoid the reachabilities + int avoidreachtries[MAX_AVOIDREACH]; //number of tries before avoiding +} bot_movestate_t; + +//used to avoid reachability links for some time after being used +#define AVOIDREACH +#define AVOIDREACH_TIME 4 //avoid links for 6 seconds after use +#define AVOIDREACH_TRIES 4 +//prediction times +#define PREDICTIONTIME_JUMP 3 //in seconds +#define PREDICTIONTIME_MOVE 2 //in seconds +//hook commands +#define CMD_HOOKOFF "hookoff" +#define CMD_HOOKON "hookon" +//weapon indexes for weapon jumping +#define WEAPONINDEX_ROCKET_LAUNCHER 5 +#define WEAPONINDEX_BFG 9 + +#define MODELTYPE_FUNC_PLAT 1 +#define MODELTYPE_FUNC_BOB 2 + +float sv_maxstep; +float sv_maxbarrier; +float sv_gravity; +//type of model, func_plat or func_bobbing +int modeltypes[MAX_MODELS]; + +bot_movestate_t *botmovestates[MAX_CLIENTS + 1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocMoveState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botmovestates[i]) + { + botmovestates[i] = GetClearedMemory(sizeof(bot_movestate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeMoveState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + FreeMemory(botmovestates[handle]); + botmovestates[handle] = NULL; +} //end of the function BotFreeMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_movestate_t *BotMoveStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botmovestates[handle]; +} //end of the function BotMoveStateFromHandle + +// Ridah, provide a means of resetting the avoidreach, so if a bot stops moving, they don't avoid the area they were heading for +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotInitAvoidReach(int handle) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(handle); + if (!ms) + { + return; + } + + memset(ms->avoidreach, 0, sizeof(ms->avoidreach)); + memset(ms->avoidreachtries, 0, sizeof(ms->avoidreachtries)); + memset(ms->avoidreachtimes, 0, sizeof(ms->avoidreachtimes)); +} +// done. + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotInitMoveState(int handle, bot_initmove_t *initmove) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(handle); + if (!ms) + { + return; + } + VectorCopy(initmove->origin, ms->origin); + VectorCopy(initmove->velocity, ms->velocity); + VectorCopy(initmove->viewoffset, ms->viewoffset); + ms->entitynum = initmove->entitynum; + ms->client = initmove->client; + ms->thinktime = initmove->thinktime; + ms->presencetype = initmove->presencetype; + ms->areanum = initmove->areanum; + VectorCopy(initmove->viewangles, ms->viewangles); + // + ms->moveflags &= ~MFL_ONGROUND; + if (initmove->or_moveflags & MFL_ONGROUND) + { + ms->moveflags |= MFL_ONGROUND; + } + ms->moveflags &= ~MFL_TELEPORTED; + if (initmove->or_moveflags & MFL_TELEPORTED) + { + ms->moveflags |= MFL_TELEPORTED; + } + ms->moveflags &= ~MFL_WATERJUMP; + if (initmove->or_moveflags & MFL_WATERJUMP) + { + ms->moveflags |= MFL_WATERJUMP; + } + ms->moveflags &= ~MFL_WALK; + if (initmove->or_moveflags & MFL_WALK) + { + ms->moveflags |= MFL_WALK; + } +} //end of the function BotInitMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +float AngleDiff(float ang1, float ang2) +{ + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) + { + if (diff > 180.0) + { + diff -= 360.0; + } + } //end if + else + { + if (diff < -180.0) + { + diff += 360.0; + } + } //end else + return diff; +} //end of the function AngleDiff + +/* +================== +BotFirstReachabilityArea +================== +*/ +int BotFirstReachabilityArea(vec3_t origin, int *areas, int numareas, qboolean distCheck) +{ + int i, best = 0; + vec3_t center; + float bestDist, dist; + bsp_trace_t tr; + // + bestDist = 999999; + for (i = 0; i < numareas; i++) + { + if (AAS_AreaReachability(areas[i])) + { + // make sure this area is visible + if (!AAS_AreaWaypoint(areas[i], center)) + { + AAS_AreaCenter(areas[i], center); + } + if (distCheck) + { + dist = VectorDistance(center, origin); + if (center[2] > origin[2]) + { + dist += 32 * (center[2] - origin[2]); + } + if (dist < bestDist) + { + tr = AAS_Trace(origin, NULL, NULL, center, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + if (tr.fraction == 1.0 && !tr.startsolid && !tr.allsolid) + { + best = areas[i]; + bestDist = dist; + //if (dist < 128) { + // return best; + //} + } + } + } + else + { + tr = AAS_Trace(origin, NULL, NULL, center, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + if (tr.fraction == 1.0 && !tr.startsolid && !tr.allsolid) + { + best = areas[i]; + break; + } + } + } + } + // + return best; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFuzzyPointReachabilityArea(vec3_t origin) +{ + int areanum, numareas, areas[100], bestarea = 0; //, i; + vec3_t end, start /*, ofs*/, mins, maxs; + //float f; + #define BOTAREA_JIGGLE_DIST 256 // 32 // OUCH, hack for MP_BEACH which has lots of voids (mostly in water) + + areanum = AAS_PointAreaNum(origin); + if (!AAS_AreaReachability(areanum)) + { + areanum = 0; + } + if (areanum) + { + return areanum; + } + + // try a line trace from beneath to above + VectorCopy(origin, start); + VectorCopy(origin, end); + start[2] -= 30; + end[2] += 40; + numareas = AAS_TraceAreas(start, end, areas, NULL, 100); + if (numareas > 0) + { + bestarea = BotFirstReachabilityArea(origin, areas, numareas, qfalse); + } + if (bestarea) + { + return bestarea; + } + + // try a small box around the origin + maxs[0] = 4; + maxs[1] = 4; + maxs[2] = 4; + VectorSubtract(origin, maxs, mins); + VectorAdd(origin, maxs, maxs); + numareas = AAS_BBoxAreas(mins, maxs, areas, 100); + if (numareas > 0) + { + bestarea = BotFirstReachabilityArea(origin, areas, numareas, qtrue); + } + if (bestarea) + { + return bestarea; + } + + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + VectorAdd(mins, origin, mins); + VectorAdd(maxs, origin, maxs); + numareas = AAS_BBoxAreas(mins, maxs, areas, 100); + if (numareas > 0) + { + bestarea = BotFirstReachabilityArea(origin, areas, numareas, qtrue); + } + if (bestarea) + { + return bestarea; + } +/* + // try half size first + for (f=0.1; f<=1.0; f+=0.45) { + VectorCopy( origin, end ); + end[2]+=80; + VectorCopy( origin, ofs ); + ofs[2]-=60; + for (i=0;i<2;i++) end[i]+=BOTAREA_JIGGLE_DIST*f; + for (i=0;i<2;i++) ofs[i]-=BOTAREA_JIGGLE_DIST*f; + + numareas = AAS_BBoxAreas(ofs, end, areas, 100); + if (numareas > 0) bestarea = BotFirstReachabilityArea(origin, areas, numareas); + if (bestarea) return bestarea; + } +*/ + return 0; +/* + int firstareanum, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t points[10], v, end; + + firstareanum = 0; + areanum = AAS_PointAreaNum(origin); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + VectorCopy(origin, end); + end[2] += 4; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) return areas[j]; + } //end for + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(origin, end); + // Ridah, increased this for Wolf larger bounding boxes + end[0] += x * 16;//8; + end[1] += y * 16;//8; + end[2] += z * 24;//12; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], origin, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + if (!firstareanum) firstareanum = areas[j]; + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + return firstareanum; +*/ +} //end of the function BotFuzzyPointReachabilityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityArea(vec3_t origin, int client) +{ + int modelnum, modeltype, reachnum, areanum; + aas_reachability_t reach; + vec3_t org, end, mins, maxs, up = { 0, 0, 1 }; + bsp_trace_t bsptrace; + aas_trace_t trace; + + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + VectorMA(origin, -3, up, end); + bsptrace = AAS_Trace(origin, mins, maxs, end, client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + if (!bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE) + { + //if standing on the world the bot should be in a valid area + if (bsptrace.ent == ENTITYNUM_WORLD) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + + modelnum = AAS_EntityModelindex(bsptrace.ent); + modeltype = modeltypes[modelnum]; + + //if standing on a func_plat or func_bobbing then the bot is assumed to be + //in the area the reachability points to + if (modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + return reach.areanum; + } //end if + } //end else if + + //if the bot is swimming the bot should be in a valid area + if (AAS_Swimming(origin)) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + // + areanum = BotFuzzyPointReachabilityArea(origin); + //if the bot is in an area with reachabilities + if (areanum && AAS_AreaReachability(areanum)) + { + return areanum; + } + //trace down till the ground is hit because the bot is standing on some other entity + VectorCopy(origin, org); + VectorCopy(org, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(org, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + // + return BotFuzzyPointReachabilityArea(org); + } //end if + // + return BotFuzzyPointReachabilityArea(origin); +} //end of the function BotReachabilityArea +//=========================================================================== +// returns the reachability area the bot is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int BotReachabilityArea(vec3_t origin, int testground) +{ + int firstareanum, i, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t org, end, points[10], v; + aas_trace_t trace; + + firstareanum = 0; + for (i = 0; i < 2; i++) + { + VectorCopy(origin, org); + //if test at the ground (used when bot is standing on an entity) + if (i > 0) + { + VectorCopy(origin, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + } //end if + + firstareanum = 0; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(org, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(org, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], org, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + if (!testground) break; + } //end for +//#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "no reachability area\n"); +//#endif //DEBUG + return firstareanum; +} //end of the function BotReachabilityArea*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnMover(vec3_t origin, int entnum, aas_reachability_t *reach) +{ + int i, modelnum; + vec3_t mins, maxs, modelorigin, org, end; + vec3_t angles = { 0, 0, 0 }; + vec3_t boxmins = { -16, -16, -8 }, boxmaxs = { 16, 16, 8 }; + bsp_trace_t trace; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + if (!AAS_OriginOfEntityWithModelNum(modelnum, modelorigin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + // + for (i = 0; i < 2; i++) + { + if (origin[i] > modelorigin[i] + maxs[i] + 16) + { + return qfalse; + } + if (origin[i] < modelorigin[i] + mins[i] - 16) + { + return qfalse; + } + } //end for + // + VectorCopy(origin, org); + org[2] += 24; + VectorCopy(origin, end); + end[2] -= 48; + // + trace = AAS_Trace(org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + if (!trace.startsolid && !trace.allsolid) + { + //NOTE: the reachability face number is the model number of the elevator + if (trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum(trace.ent) == modelnum) + { + return qtrue; + } //end if + } //end if + return qfalse; +} //end of the function BotOnMover +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MoverDown(aas_reachability_t *reach) +{ + int modelnum; + vec3_t mins, maxs, origin; + vec3_t angles = { 0, 0, 0 }; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfEntityWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + //if the top of the plat is below the reachability start point + if (origin[2] + maxs[2] < reach->start[2]) + { + return qtrue; + } + return qfalse; +} //end of the function MoverDown +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotSetBrushModelTypes(void) +{ + int ent, modelnum; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + + memset(modeltypes, 0, MAX_MODELS * sizeof(int)); + // + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) + { + continue; + } + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + continue; + } + if (model[0]) + { + modelnum = atoi(model + 1); + } + else + { + modelnum = 0; + } + + if (modelnum < 0 || modelnum > MAX_MODELS) + { + botimport.Print(PRT_MESSAGE, "entity %s model number out of range\n", classname); + continue; + } //end if + + if (!strcmp(classname, "func_bobbing")) + { + modeltypes[modelnum] = MODELTYPE_FUNC_BOB; + } + else if (!strcmp(classname, "func_plat")) + { + modeltypes[modelnum] = MODELTYPE_FUNC_PLAT; + } + } //end for +} //end of the function BotSetBrushModelTypes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnTopOfEntity(bot_movestate_t *ms) +{ + vec3_t mins, maxs, end, up = { 0, 0, 1 }; + bsp_trace_t trace; + + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE)) + { + return trace.ent; + } //end if + return -1; +} //end of the function BotOnTopOfEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotValidTravel(vec3_t origin, aas_reachability_t *reach, int travelflags) +{ + //if the reachability uses an unwanted travel type + if (AAS_TravelFlagForType(reach->traveltype) & ~travelflags) + { + return qfalse; + } + //don't go into areas with bad travel types + if (AAS_AreaContentsTravelFlag(reach->areanum) & ~travelflags) + { + return qfalse; + } + return qtrue; +} //end of the function BotValidTravel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidReach(bot_movestate_t *ms, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreach[i] == number) + { + if (ms->avoidreachtimes[i] > AAS_Time()) + { + ms->avoidreachtries[i]++; + } + else + { + ms->avoidreachtries[i] = 1; + } + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + //add the reachability to the reachabilities to avoid for a while + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] < AAS_Time()) + { + ms->avoidreach[i] = number; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + ms->avoidreachtries[i] = 1; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//__inline int AAS_AreaContentsTravelFlag(int areanum); + +int BotGetReachabilityToGoal(vec3_t origin, int areanum, int entnum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags) +{ + int t, besttime, bestreachnum, reachnum; + aas_reachability_t reach; + qboolean useAvoidPass = qfalse; + +again: + + //if not in a valid area + if (!areanum) + { + return 0; + } + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goal->areanum)) + { + travelflags |= TFL_DONOTENTER; + movetravelflags |= TFL_DONOTENTER; + } //end if + if (AAS_AreaDoNotEnterLarge(areanum) || AAS_AreaDoNotEnterLarge(goal->areanum)) + { + travelflags |= TFL_DONOTENTER_LARGE; + movetravelflags |= TFL_DONOTENTER_LARGE; + } //end if + //use the routing to find the next area to go to + besttime = 0; + bestreachnum = 0; + // + for (reachnum = AAS_NextAreaReachability(areanum, 0); reachnum; + reachnum = AAS_NextAreaReachability(areanum, reachnum)) + { +#ifdef AVOIDREACH + int i; + //check if it isn't an reachability to avoid + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time()) + { + break; + } + } //end for + // RF, if this is a "useAvoidPass" then we should only accept avoidreach reachabilities + if ((!useAvoidPass && i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES) + || (useAvoidPass && !(i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES))) + { +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i]); + } //end if +#endif //DEBUG + continue; + } //end if +#endif //AVOIDREACH + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + //NOTE: do not go back to the previous area if the goal didn't change + //NOTE: is this actually avoidance of local routing minima between two areas??? + if (lastgoalareanum == goal->areanum && reach.areanum == lastareanum) + { + continue; + } + //if (AAS_AreaContentsTravelFlag(reach.areanum) & ~travelflags) continue; + //if the travel isn't valid + if (!BotValidTravel(origin, &reach, movetravelflags)) + { + continue; + } + // if the area is disabled + if (!AAS_AreaReachability(reach.areanum)) + { + continue; + } + //get the travel time + t = AAS_AreaTravelTimeToGoalArea(reach.areanum, reach.end, goal->areanum, travelflags); + //if the goal area isn't reachable from the reachable area + if (!t) + { + continue; + } + + // Ridah, if this sends us to a looped route, ignore it +// if (AAS_AreaTravelTimeToGoalArea(areanum, reach.start, goal->areanum, travelflags) + reach.traveltime == t) +// continue; + + //add the travel time towards the area + // Ridah, not sure why this was disabled, but it causes looped links in the route-cache + //t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start); + t += reach.traveltime + AAS_AreaTravelTime(areanum, origin, reach.start); + + // Ridah, if there exists other entities in this area, avoid it +// if (reach.areanum != goal->areanum && AAS_IsEntityInArea( entnum, goal->entitynum, reach.areanum )) { +// t += 50; +// } + + //if the travel time is better than the ones already found + if (!besttime || t < besttime) + { + besttime = t; + bestreachnum = reachnum; + } //end if + } //end for + // + // RF, if we didnt find a route, then try again only looking through avoidreach reachabilities + if (!bestreachnum && !useAvoidPass) + { + useAvoidPass = qtrue; + goto again; + } + // + return bestreachnum; +} //end of the function BotGetReachabilityToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAddToTarget(vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target) +{ + vec3_t dir; + float curdist; + + VectorSubtract(end, start, dir); + curdist = VectorNormalize(dir); + if (*dist + curdist < maxdist) + { + VectorCopy(end, target); + *dist += curdist; + return qfalse; + } //end if + else + { + VectorMA(start, maxdist - *dist, dir, target); + *dist = maxdist; + return qtrue; + } //end else +} //end of the function BotAddToTarget + +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastareanum; + bot_movestate_t *ms; + vec3_t end; + float dist; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) + { + return qfalse; + } + reachnum = 0; + //if the bot has no goal or no last reachability + if (!ms->lastreachnum || !goal) + { + return qfalse; + } + + reachnum = ms->lastreachnum; + VectorCopy(ms->origin, end); + lastareanum = ms->lastareanum; + dist = 0; + while (reachnum && dist < lookahead) + { + AAS_ReachabilityFromNum(reachnum, &reach); + if (BotAddToTarget(end, reach.start, lookahead, &dist, target)) + { + return qtrue; + } + //never look beyond teleporters + if (reach.traveltype == TRAVEL_TELEPORT) + { + return qtrue; + } + //don't add jump pad distances + if (reach.traveltype != TRAVEL_JUMPPAD && + reach.traveltype != TRAVEL_ELEVATOR && + reach.traveltype != TRAVEL_FUNCBOB) + { + if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) + { + return qtrue; + } + } //end if + reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum, -1, + ms->lastgoalareanum, lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags); + VectorCopy(reach.end, end); + lastareanum = reach.areanum; + if (lastareanum == goal->areanum) + { + BotAddToTarget(reach.end, goal->origin, lookahead, &dist, target); + return qtrue; + } //end if + } //end while + // + return qfalse; +} //end of the function BotMovementViewTarget +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotVisible(int ent, vec3_t eye, vec3_t target) +{ + bsp_trace_t trace; + + trace = AAS_Trace(eye, NULL, NULL, target, ent, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1) + { + return qtrue; + } + return qfalse; +} //end of the function BotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastgoalareanum, lastareanum, i; + int avoidreach[MAX_AVOIDREACH]; + float avoidreachtimes[MAX_AVOIDREACH]; + int avoidreachtries[MAX_AVOIDREACH]; + vec3_t end; + + //if the bot has no goal or no last reachability + if (!goal) + { + return qfalse; + } + //if the areanum is not valid + if (!areanum) + { + return qfalse; + } + //if the goal areanum is not valid + if (!goal->areanum) + { + return qfalse; + } + + memset(avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + lastgoalareanum = goal->areanum; + lastareanum = areanum; + VectorCopy(origin, end); + //only do 20 hops + for (i = 0; i < 20 && (areanum != goal->areanum); i++) + { + // + reachnum = BotGetReachabilityToGoal(end, areanum, -1, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + goal, travelflags, travelflags); + if (!reachnum) + { + return qfalse; + } + AAS_ReachabilityFromNum(reachnum, &reach); + // + if (BotVisible(goal->entitynum, goal->origin, reach.start)) + { + VectorCopy(reach.start, target); + return qtrue; + } //end if + // + if (BotVisible(goal->entitynum, goal->origin, reach.end)) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + if (reach.areanum == goal->areanum) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + lastareanum = areanum; + areanum = reach.areanum; + VectorCopy(reach.end, end); + // + } //end while + // + return qfalse; +} //end of the function BotPredictVisiblePosition +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MoverBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter) +{ + int modelnum; + vec3_t mins, maxs, origin, mids; + vec3_t angles = { 0, 0, 0 }; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfEntityWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + } //end if + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(origin, 0.5, mids, bottomcenter); + bottomcenter[2] = reach->start[2]; +} //end of the function MoverBottomCenter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum) +{ + float dist, startz; + vec3_t start, end; + aas_trace_t trace; + + //do gap checking + startz = origin[2]; + //this enables walking down stairs more fluidly + { + VectorCopy(origin, start); + VectorCopy(origin, end); + end[2] -= 60; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + if (trace.fraction >= 1) + { + return 1; + } + startz = trace.endpos[2] + 1; + } + // + for (dist = 8; dist <= 100; dist += 8) + { + VectorMA(origin, dist, hordir, start); + start[2] = startz + 24; + VectorCopy(start, end); + end[2] -= 48 + sv_maxbarrier; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + //if solid is found the bot can't walk any further and fall into a gap + if (!trace.startsolid) + { + //if it is a gap + if (trace.endpos[2] < startz - sv_maxstep - 8) + { + VectorCopy(trace.endpos, end); + end[2] -= 20; + if (AAS_PointContents(end) & (CONTENTS_WATER | CONTENTS_SLIME)) + { + break; //----(SA) modified since slime is no longer deadly + } +// if (AAS_PointContents(end) & CONTENTS_WATER) break; + //if a gap is found slow down + //botimport.Print(PRT_MESSAGE, "gap at %f\n", dist); + return dist; + } //end if + startz = trace.endpos[2]; + } //end if + } //end for + return 0; +} //end of the function BotGapDistance +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotCheckBarrierJump(bot_movestate_t *ms, vec3_t dir, float speed) +{ + vec3_t start, hordir, end; + aas_trace_t trace; + + VectorCopy(ms->origin, end); + end[2] += sv_maxbarrier; + //trace right up + trace = AAS_TraceClientBBox(ms->origin, end, PRESENCE_NORMAL, ms->entitynum); + //this shouldn't happen... but we check anyway + if (trace.startsolid) + { + return qfalse; + } + //if very low ceiling it isn't possible to jump up to a barrier + if (trace.endpos[2] - ms->origin[2] < sv_maxstep) + { + return qfalse; + } + // + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + VectorMA(ms->origin, ms->thinktime * speed * 0.5, hordir, end); + VectorCopy(trace.endpos, start); + end[2] = trace.endpos[2]; + //trace from previous trace end pos horizontally in the move direction + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //again this shouldn't happen + if (trace.startsolid) + { + return qfalse; + } + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] = ms->origin[2]; + //trace down from the previous trace end pos + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //if solid + if (trace.startsolid) + { + return qfalse; + } + //if no obstacle at all + if (trace.fraction >= 1.0) + { + return qfalse; + } + //if less than the maximum step height + if (trace.endpos[2] - ms->origin[2] < sv_maxstep) + { + return qfalse; + } + // + EA_Jump(ms->client); + EA_Move(ms->client, hordir, speed); + ms->moveflags |= MFL_BARRIERJUMP; + //there is a barrier + return qtrue; +} //end of the function BotCheckBarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSwimInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t normdir; + + VectorCopy(dir, normdir); + VectorNormalize(normdir); + EA_Move(ms->client, normdir, speed); + return qtrue; +} //end of the function BotSwimInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotWalkInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t hordir, cmdmove, velocity, tmpdir, origin; + int presencetype, maxframes, cmdframes, stopevent; + aas_clientmove_t move; + float dist; + + //if the bot is on the ground + if (ms->moveflags & MFL_ONGROUND) + { + //if there is a barrier the bot can jump on + if (BotCheckBarrierJump(ms, dir, speed)) + { + return qtrue; + } + //remove barrier jump flag + ms->moveflags &= ~MFL_BARRIERJUMP; + //get the presence type for the movement + if ((type & MOVE_CROUCH) && !(type & MOVE_JUMP)) + { + presencetype = PRESENCE_CROUCH; + } + else + { + presencetype = PRESENCE_NORMAL; + } + //horizontal direction + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //if the bot is not supposed to jump + if (!(type & MOVE_JUMP)) + { + //if there is a gap, try to jump over it + if (BotGapDistance(ms->origin, hordir, ms->entitynum) > 0) + { + type |= MOVE_JUMP; + } + } //end if + //get command movement + VectorScale(hordir, speed, cmdmove); + VectorCopy(ms->velocity, velocity); + // + if (type & MOVE_JUMP) + { + //botimport.Print(PRT_MESSAGE, "trying jump\n"); + cmdmove[2] = 400; + maxframes = PREDICTIONTIME_JUMP / 0.1; + cmdframes = 1; + stopevent = SE_HITGROUND | SE_HITGROUNDDAMAGE | + SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA; + } //end if + else + { + maxframes = 2; + cmdframes = 2; + stopevent = SE_HITGROUNDDAMAGE | + SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA; + } //end else + //AAS_ClearShownDebugLines(); + // + VectorCopy(ms->origin, origin); + origin[2] += 0.5; + AAS_PredictClientMovement(&move, ms->entitynum, origin, presencetype, qtrue, + velocity, cmdmove, cmdframes, maxframes, 0.1, + stopevent, 0, qfalse); //qtrue); + //if prediction time wasn't enough to fully predict the movement + if (move.frames >= maxframes && (type & MOVE_JUMP)) + { + //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client); + return qfalse; + } //end if + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERLAVA | SE_HITGROUNDDAMAGE)) //----(SA) modified since slime is no longer deadly + { // if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client); + //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n"); + //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n"); + //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n"); + return qfalse; + } //end if + //if ground was hit + if (move.stopevent & SE_HITGROUND) + { + //check for nearby gap + VectorNormalize2(move.velocity, tmpdir); + dist = BotGapDistance(move.endpos, tmpdir, ms->entitynum); + if (dist > 0) + { + return qfalse; + } + // + dist = BotGapDistance(move.endpos, hordir, ms->entitynum); + if (dist > 0) + { + return qfalse; + } + } //end if + //get horizontal movement + tmpdir[0] = move.endpos[0] - ms->origin[0]; + tmpdir[1] = move.endpos[1] - ms->origin[1]; + tmpdir[2] = 0; + // + //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE); + //the bot is blocked by something + if (VectorLength(tmpdir) < speed * ms->thinktime * 0.5) + { + return qfalse; + } + //perform the movement + if (type & MOVE_JUMP) + { + EA_Jump(ms->client); + } + if (type & MOVE_CROUCH) + { + EA_Crouch(ms->client); + } + EA_Move(ms->client, hordir, speed); + //movement was succesfull + return qtrue; + } //end if + else + { + if (ms->moveflags & MFL_BARRIERJUMP) + { + //if near the top or going down + if (ms->velocity[2] < 50) + { + EA_Move(ms->client, dir, speed); + } //end if + } //end if + //FIXME: do air control to avoid hazards + return qtrue; + } //end else +} //end of the function BotWalkInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) + { + return qfalse; + } + //if swimming + if (AAS_Swimming(ms->origin)) + { + return BotSwimInDirection(ms, dir, speed, type); + } //end if + else + { + return BotWalkInDirection(ms, dir, speed, type); + } //end else +} //end of the function BotMoveInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Intersection(vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out) +{ + float x1, dx1, dy1, x2, dx2, dy2, d; + + dx1 = p2[0] - p1[0]; + dy1 = p2[1] - p1[1]; + dx2 = p4[0] - p3[0]; + dy2 = p4[1] - p3[1]; + + d = dy1 * dx2 - dx1 * dy2; + if (d != 0) + { + x1 = p1[1] * dx1 - p1[0] * dy1; + x2 = p3[1] * dx2 - p3[0] * dy2; + out[0] = (int) ((dx1 * x2 - dx2 * x1) / d); + out[1] = (int) ((dy1 * x2 - dy2 * x1) / d); + return qtrue; + } //end if + else + { + return qfalse; + } //end else +} //end of the function Intersection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckBlocked(bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result) +{ + vec3_t mins, maxs, end, up = { 0, 0, 1 }; + bsp_trace_t trace; + + //test for entities obstructing the bot's path + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + // + if (Q_fabs(DotProduct(dir, up)) < 0.7) + { + mins[2] += sv_maxstep; //if the bot can step on + maxs[2] -= 10; //a little lower to avoid low ceiling + } //end if + VectorMA(ms->origin, 16, dir, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY); + //if not started in solid and not hitting the world entity + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE)) + { + result->blocked = qtrue; + result->blockentity = trace.ent; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + //if not in an area with reachability + else if (checkbottom && !AAS_AreaReachability(ms->areanum)) + { + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE)) + { + result->blocked = qtrue; + result->blockentity = trace.ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + } //end else +} //end of the function BotCheckBlocked +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotClearMoveResult(bot_moveresult_t *moveresult) +{ + moveresult->failure = qfalse; + moveresult->type = 0; + moveresult->blocked = qfalse; + moveresult->blockentity = -1; + moveresult->traveltype = 0; + moveresult->flags = 0; +} //end of the function BotClearMoveResult + + +char *vtosf(const vec3_t v) +{ + static int index; + static char str[8][64]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1) & 7; + + Com_sprintf(s, 64, "(%f %f %f)", v[0], v[1], v[2]); + + return s; +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; //, v1, v2, p; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + // Ridah, tweaked this +// if (dist < 10) + if (dist < 32) + { + //walk straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); +/* + // if we are really close to the line, then move towards the center of the reach area + VectorCopy( reach->start, v1 ); + VectorCopy( reach->end, v2 ); + VectorCopy( ms->origin, p ); + v1[2] = 0; + v2[2] = 0; + p[2] = 0; + if (DistanceFromVectorSquared( p, v1, v2 ) < 4) { + if (!AAS_AreaWaypoint( reach->areanum, p )) + AAS_AreaCenter( reach->areanum, p ); + if (VectorDistance( ms->origin, p ) > 32) { + VectorSubtract( p, ms->origin, hordir ); + hordir[2] = 0; + dist = VectorNormalize(hordir); + } + } +*/ + } + else + { +//botimport.Print(PRT_MESSAGE, "BotTravel_Walk: NOT in range of reach (%i)\n", reach->areanum); + } //end if + //if going towards a crouch area + + // Ridah, some areas have a 0 presence (!?!) +// if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) + if ((AAS_AreaPresenceType(reach->areanum) & PRESENCE_CROUCH) && + !(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) + { + //if pretty close to the reachable area + if (dist < 20) + { + EA_Crouch(ms->client); + } + } //end if + // + dist = BotGapDistance(ms->origin, hordir, ms->entitynum); + // + if (ms->moveflags & MFL_WALK) + { + if (dist > 0) + { + speed = 200 - (180 - 1 * dist); + } + else + { + speed = 200; + } + EA_Walk(ms->client); + } //end if + else + { + if (dist > 0) + { + speed = 400 - (360 - 2 * dist); + } + else + { + speed = 400; + } + } //end else + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); +// +//botimport.Print(PRT_MESSAGE, "\nBotTravel_Walk: srcarea %i, rcharea %i, org %s, reachorg %s\n", ms->areanum, reach->areanum, vtosf(ms->origin), vtosf(reach->start) ); + // + return result; +} //end of the function BotTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not on the ground and changed areas... don't walk back!! + //(doesn't seem to help) + /* + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + if (ms->areanum == reach->areanum) + { + #ifdef DEBUG + botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n"); + #endif //DEBUG + return result; + } //end if*/ + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 100) + { + dist = 100; + } + speed = 400 - (400 - 3 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Crouch(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + speed = 400; + //walk straight to reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary actions + EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //walk straight to reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //if pretty close to the barrier + if (dist < 12) + { + EA_Jump(ms->client); + + // Ridah, do the movement also, so we have momentum to get onto the barrier + hordir[0] = reach->end[0] - reach->start[0]; + hordir[1] = reach->end[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize(hordir); + + dist = 90; + speed = 400 - (360 - 4 * dist); + EA_Move(ms->client, hordir, speed); + // done. + } //end if + else + { + if (dist > 90) + { + dist = 90; + } + speed = 400 - (360 - 4 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if near the top or going down + if (ms->velocity[2] < 250) + { + // Ridah, extend the end point a bit, so we strive to get over the ledge more + vec3_t end; + + VectorSubtract(reach->end, reach->start, end); + end[2] = 0; + VectorNormalize(end); + VectorMA(reach->end, 32, end, end); + hordir[0] = end[0] - ms->origin[0]; + hordir[1] = end[1] - ms->origin[1]; +// hordir[0] = reach->end[0] - ms->origin[0]; +// hordir[1] = reach->end[1] - ms->origin[1]; + // done. + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + if (dist > 60) + { + dist = 60; + } + speed = 400 - (400 - 6 * dist); + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } + else + { + // hold crouch in case we are going for a crouch area + if (AAS_AreaPresenceType(reach->areanum) & PRESENCE_CROUCH) + { + EA_Crouch(ms->client); + } + } + // + return result; +} //end of the function BotFinishTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Swim(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //swim straight to reachability end + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary actions + EA_Move(ms->client, dir, 400); + // + VectorCopy(dir, result.movedir); + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + // + return result; +} //end of the function BotTravel_Swim +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + VectorCopy(dir, hordir); + hordir[2] = 0; + dir[2] += 15 + crandom() * 40; + //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]); + VectorNormalize(dir); + dist = VectorNormalize(hordir); + //elemantary actions + //EA_Move(ms->client, dir, 400); + EA_MoveForward(ms->client); + //move up if close to the actual out of water jump spot + if (dist < 40) + { + EA_MoveUp(ms->client); + } + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, pnt; + float dist; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n"); + BotClearMoveResult(&result); + //if waterjumping there's nothing to do + if (ms->moveflags & MFL_WATERJUMP) + { + return result; + } + //if not touching any water anymore don't do anything + //otherwise the bot sometimes keeps jumping? + VectorCopy(ms->origin, pnt); + pnt[2] -= 32; //extra for q2dm4 near red armor/mega health + if (!(AAS_PointContents(pnt) & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER))) + { + return result; + } + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + dir[0] += crandom() * 10; + dir[1] += crandom() * 10; + dir[2] += 70 + crandom() * 10; + dist = VectorNormalize(dir); + //elemantary actions + EA_Move(ms->client, dir, 400); + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir; + float dist, speed, reachhordist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //check if the bot is blocked by anything + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + BotCheckBlocked(ms, dir, qtrue, &result); + //if the reachability start and end are practially above each other + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + reachhordist = VectorLength(dir); + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + //if pretty close to the start focus on the reachability end + + // Ridah, tweaked this +#if 0 + if (dist < 48) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; +#else + if ((dist < 72) && (DotProduct(dir, hordir) < 0)) // walk in the direction of start -> end + { //hordir[0] = reach->end[0] - ms->origin[0]; + //hordir[1] = reach->end[1] - ms->origin[1]; + //VectorNormalize( dir ); + //VectorMA( hordir, 48, dir, hordir ); + //hordir[2] = 0; + + VectorCopy(dir, hordir); +#endif + VectorNormalize(hordir); + // + if (reachhordist < 20) + { + speed = 100; + } //end if + else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed)) + { + speed = 400; + } //end if + // looks better crouching off a ledge + EA_Crouch(ms->client); + } //end if + else + { + if (reachhordist < 20) + { + if (dist > 64) + { + dist = 64; + } + speed = 400 - (256 - 4 * dist); + } //end if + else + { + speed = 400; + // Ridah, tweaked this + if (dist < 128) + { + speed *= (dist / 128); + } + } //end else + } //end else + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAirControl(vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed) +{ + vec3_t org, vel; + float dist; + int i; + + VectorCopy(origin, org); + VectorScale(velocity, 0.1, vel); + for (i = 0; i < 50; i++) + { + vel[2] -= sv_gravity * 0.01; + //if going down and next position would be below the goal + if (vel[2] < 0 && org[2] + vel[2] < goal[2]) + { + VectorScale(vel, (goal[2] - org[2]) / vel[2], vel); + VectorAdd(org, vel, org); + VectorSubtract(goal, org, dir); + dist = VectorNormalize(dir); + if (dist > 32) + { + dist = 32; + } + *speed = 400 - (400 - 13 * dist); + return qtrue; + } //end if + else + { + VectorAdd(org, vel, org); + } //end else + } //end for + VectorSet(dir, 0, 0, 0); + *speed = 400; + return qfalse; +} //end of the function BotAirControl +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir, end, v; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + VectorSubtract(reach->end, ms->origin, dir); + BotCheckBlocked(ms, dir, qtrue, &result); + // + VectorSubtract(reach->end, ms->origin, v); + v[2] = 0; + dist = VectorNormalize(v); + if (dist > 16) + { + VectorMA(reach->end, 16, v, end); + } + else + { + VectorCopy(reach->end, end); + } + // + if (!BotAirControl(ms->origin, ms->velocity, end, hordir, &speed)) + { + //go straight to the reachability end + VectorCopy(dir, hordir); + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + speed = 400; + } //end if + // + // looks better crouching off a ledge + EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, gapdist, speed, horspeed, sv_jumpvel; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + sv_jumpvel = botlibglobals.sv_jumpvel->value; + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + speed = 350; + // + gapdist = BotGapDistance(ms, hordir, ms->entitynum); + //if pretty close to the start focus on the reachability end + if (dist < 50 || (gapdist && gapdist < 50)) + { + //NOTE: using max speed (400) works best + //if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed)) + //{ + // speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + //} //end if + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + // + ms->jumpreach = ms->lastreachnum; + speed = 600; + } //end if + else + { + if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed)) + { + speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + } //end if + } //end else + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, mins, maxs, start, end; + float dist1, dist2, speed; + bot_moveresult_t result; + bsp_trace_t trace; + + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + //minus back the bouding box size plus 16 + VectorMA(reach->start, 80, hordir, end); + // + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + //check for solids + trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID); + if (trace.startsolid) VectorCopy(start, trace.endpos); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, trace.endpos); +// dist1 = BotGapDistance(start, hordir, ms->entitynum); +// if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, trace.endpos, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run to point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "going towards run to point\n"); + hordir[0] = trace.endpos[0] - ms->origin[0]; + hordir[1] = trace.endpos[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, start, end, runstart; +// vec3_t runstart, dir1, dir2, hordir; + float dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + AAS_JumpReachRunStart(reach, runstart); + //* + hordir[0] = runstart[0] - reach->start[0]; + hordir[1] = runstart[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + VectorMA(reach->start, 80, hordir, runstart); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1 + 10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) + { + break; + } + } //end for + if (dist1 < 80) + { + VectorMA(reach->start, dist1, hordir, runstart); + } + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, runstart, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 12) + { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) + { + EA_Jump(ms->client); + } + else if (dist1 < 32) + { + EA_DelayedJump(ms->client); + } + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { +// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); + hordir[0] = runstart[0] - ms->origin[0]; + hordir[1] = runstart[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) + { + dist2 = 80; + } + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, hordir2; + float speed, dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not jumped yet + if (!ms->jumpreach) + { + return result; + } + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + hordir2[0] = reach->end[0] - reach->start[0]; + hordir2[1] = reach->end[1] - reach->start[1]; + hordir2[2] = 0; + VectorNormalize(hordir2); + // + if (DotProduct(hordir, hordir2) < -0.5 && dist < 24) + { + return result; + } + //always use max speed when traveling through the air + speed = 800; + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Ladder(bot_movestate_t *ms, aas_reachability_t *reach) +{ + //float dist, speed; + vec3_t dir, viewdir, hordir, pos, p, v1, v2, vec, right; + vec3_t origin = { 0, 0, 0 }; +// vec3_t up = {0, 0, 1}; + bot_moveresult_t result; + float dist, speed; + + // RF, heavily modified, wolf has different ladder movement + + BotClearMoveResult(&result); + // + // if it's a descend reachability + if (reach->start[2] > reach->end[2]) + { + if (!(ms->moveflags & MFL_ONGROUND)) + { + //botimport.Print(PRT_MESSAGE, "not on ground\n"); + // RF, wolf has different ladder movement + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + dist = VectorNormalize(dir); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = 0; // straight forward goes up + if (dist >= 3.1) // vertical ladder -> ladder reachability has length 3, dont inverse in this case + { + VectorInverse(viewdir); + } + VectorNormalize(viewdir); + Vector2Angles(viewdir, result.ideal_viewangles); + //elemantary action + EA_Move(ms->client, origin, 0); + // RF, disabled this check, if we're not on ground, then it shouldn't be a problem, and the ladder flag is unreliable + //if (ms->moveflags & MFL_AGAINSTLADDER) { + //botimport.Print(PRT_MESSAGE, "against ladder, descending\n"); + EA_MoveBack(ms->client); // only move back if we are touching the ladder brush + // check for sideways adjustments to stay on the center of the ladder + VectorMA(ms->origin, 18, viewdir, p); + VectorCopy(reach->start, v1); + v1[2] = ms->origin[2]; + VectorCopy(reach->end, v2); + v2[2] = ms->origin[2]; + VectorSubtract(v2, v1, vec); + VectorNormalize(vec); + VectorMA(v1, -32, vec, v1); + VectorMA(v2, 32, vec, v2); + ProjectPointOntoVector(p, v1, v2, pos); + VectorSubtract(pos, p, vec); + if (VectorLength(vec) > 2) + { + AngleVectors(result.ideal_viewangles, NULL, right, NULL); + if (DotProduct(vec, right) > 0) + { + EA_MoveRight(ms->client); + } + else + { + EA_MoveLeft(ms->client); + } + } + //} + //set movement view flag so the AI can see the view is focussed + result.flags |= MOVERESULT_MOVEMENTVIEW; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "moving towards ladder top\n"); + // find a postion back away from the edge of the ladder + VectorSubtract(reach->end, reach->start, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + VectorMA(reach->start, -24, hordir, pos); + VectorCopy(reach->end, v1); + v1[2] = pos[2]; + // project our position onto the vector + ProjectPointOntoVectorBounded(ms->origin, pos, v1, p); + VectorSubtract(p, ms->origin, dir); + //make sure the horizontal movement is large anough + VectorCopy(dir, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + if (dist < 64) // within range, go for the end + { //botimport.Print(PRT_MESSAGE, "found top, moving towards ladder edge\n"); + VectorSubtract(reach->end, reach->start, dir); + //make sure the horizontal movement is large anough + dir[2] = 0; + VectorNormalize(dir); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = 0; + VectorInverse(viewdir); + VectorNormalize(viewdir); + Vector2Angles(viewdir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // if we are still on ground, then start moving backwards until we are in air + if ((dist < 4) && (ms->moveflags & MFL_ONGROUND)) + { + //botimport.Print(PRT_MESSAGE, "close to edge, backing in slowly..\n"); + VectorSubtract(reach->end, ms->origin, vec); + vec[2] = 0; + VectorNormalize(vec); + EA_Move(ms->client, vec, 100); + VectorCopy(vec, result.movedir); + result.ideal_viewangles[PITCH] = 45; // face downwards + return result; + } + } + // + dir[0] = hordir[0]; + dir[1] = hordir[1]; + dir[2] = 0; + if (dist > 150) + { + dist = 150; + } + speed = 400 - (300 - 2 * dist); + //botimport.Print(PRT_MESSAGE, "speed = %.0f\n", speed); + EA_Move(ms->client, dir, speed); + } //end else + } + else + { + if ((ms->moveflags & MFL_AGAINSTLADDER) + //NOTE: not a good idea for ladders starting in water + || !(ms->moveflags & MFL_ONGROUND)) + { + //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n"); + // RF, wolf has different ladder movement + VectorSubtract(reach->end, reach->start, dir); + VectorNormalize(dir); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = dir[2]; + if (dir[2] < 0) // going down, so face the other way (towards ladder) + { + VectorInverse(viewdir); + } + viewdir[2] = 0; // straight forward goes up + VectorNormalize(viewdir); + Vector2Angles(viewdir, result.ideal_viewangles); + //elemantary action + EA_Move(ms->client, origin, 0); + if (dir[2] < 0) // going down, so face the other way + { + EA_MoveBack(ms->client); + } + else + { + EA_MoveForward(ms->client); + } + // RF, against ladder code isn't completely accurate + //if (ms->moveflags & MFL_AGAINSTLADDER) { + // check for sideways adjustments to stay on the center of the ladder + VectorMA(ms->origin, 18, viewdir, p); + VectorCopy(reach->start, v1); + v1[2] = ms->origin[2]; + VectorCopy(reach->end, v2); + v2[2] = ms->origin[2]; + VectorSubtract(v2, v1, vec); + VectorNormalize(vec); + VectorMA(v1, -32, vec, v1); + VectorMA(v2, 32, vec, v2); + ProjectPointOntoVectorBounded(p, v1, v2, pos); + VectorSubtract(pos, p, vec); + if (VectorLength(vec) > 2) + { + AngleVectors(result.ideal_viewangles, NULL, right, NULL); + if (DotProduct(vec, right) > 0) + { + EA_MoveRight(ms->client); + } + else + { + EA_MoveLeft(ms->client); + } + } + //} + //set movement view flag so the AI can see the view is focussed + result.flags |= MOVERESULT_MOVEMENTVIEW; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "moving towards ladder base\n"); + // find a postion back away from the base of the ladder + VectorSubtract(reach->end, reach->start, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + VectorMA(reach->start, -24, hordir, pos); + VectorCopy(reach->end, v1); + v1[2] = pos[2]; + // project our position onto the vector + ProjectPointOntoVectorBounded(ms->origin, pos, v1, p); + VectorSubtract(p, ms->origin, dir); + //make sure the horizontal movement is large anough + VectorCopy(dir, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + if (dist < 16) // within range, go for the end + { //botimport.Print(PRT_MESSAGE, "found base, moving towards ladder top\n"); + VectorSubtract(reach->end, ms->origin, dir); + //make sure the horizontal movement is large anough + VectorCopy(dir, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = 0; + VectorNormalize(viewdir); + Vector2Angles(viewdir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + } + // + dir[0] = hordir[0]; + dir[1] = hordir[1]; + // if (dist < 48) { + // if (dir[2] > 0) dir[2] = 1; + // else dir[2] = -1; + // } else { + dir[2] = 0; + // } + if (dist > 50) + { + dist = 50; + } + speed = 400 - (300 - 6 * dist); + EA_Move(ms->client, dir, speed); + } //end else + } + //save the movement direction + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Teleport(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if the bot is being teleported + if (ms->moveflags & MFL_TELEPORTED) + { + return result; + } + + //walk straight to center of the teleporter + VectorSubtract(reach->start, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) + { + hordir[2] = 0; + } + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + + if (dist < 30) + { + EA_Move(ms->client, hordir, 200); + } + else + { + EA_Move(ms->client, hordir, 400); + } + + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + + VectorCopy(hordir, result.movedir); + return result; +} //end of the function BotTravel_Teleport +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if standing on the plat + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot on elevator\n"); +#endif //DEBUG_ELEVATOR + //if vertically not too far from the end point + if (abs(ms->origin[2] - reach->end[2]) < sv_maxbarrier) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif //DEBUG_ELEVATOR + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + //move to the center of the plat + if (dist > 100) + { + dist = 100; + } + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot not on elevator\n"); +#endif //DEBUG_ELEVATOR + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { + if (dist > 60) + { + dist = 60; + } + speed = 360 - (360 - 6 * dist); + // + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) + { + EA_Move(ms->client, dir, speed); + } + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) + { + dir1[2] = 0; + } + dist1 = VectorNormalize(dir1); + //if the elevator isn't down + if (!MoverDown(reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "elevator not down\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) + { + dist = 60; + } + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) + { + EA_Move(ms->client, dir, speed); + } + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + //this isn't a failure... just wait till the elevator comes down + //result.failure = qtrue; + result.type = RESULTTYPE_ELEVATORUP; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to elevator bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) + { + dir2[2] = 0; + } + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and elevator center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to start\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) + { + dist = 60; + } + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + } //end else + return result; +} //end of the function BotTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bottomcenter, bottomdir, topdir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, bottomdir); + // + VectorSubtract(reach->end, ms->origin, topdir); + // + if (Q_fabs(bottomdir[2]) < Q_fabs(topdir[2])) + { + VectorNormalize(bottomdir); + EA_Move(ms->client, bottomdir, 300); + } //end if + else + { + VectorNormalize(topdir); + EA_Move(ms->client, topdir, 300); + } //end else + return result; +} //end of the function BotFinishTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFuncBobStartEnd(aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin) +{ + int spawnflags, modelnum; + vec3_t mins, maxs, mid, angles = { 0, 0, 0 }; + int num0, num1; + + modelnum = reach->facenum & 0x0000FFFF; + if (!AAS_OriginOfEntityWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum); + VectorSet(start, 0, 0, 0); + VectorSet(end, 0, 0, 0); + return; + } //end if + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, start); + VectorCopy(mid, end); + spawnflags = reach->facenum >> 16; + num0 = reach->edgenum >> 16; + if (num0 > 0x00007FFF) + { + num0 |= 0xFFFF0000; + } + num1 = reach->edgenum & 0x0000FFFF; + if (num1 > 0x00007FFF) + { + num1 |= 0xFFFF0000; + } + if (spawnflags & 1) + { + start[0] = num0; + end[0] = num1; + // + origin[0] += mid[0]; + origin[1] = mid[1]; + origin[2] = mid[2]; + } //end if + else if (spawnflags & 2) + { + start[1] = num0; + end[1] = num1; + // + origin[0] = mid[0]; + origin[1] += mid[1]; + origin[2] = mid[2]; + } //end else if + else + { + start[2] = num0; + end[2] = num1; + // + origin[0] = mid[0]; + origin[1] = mid[1]; + origin[2] += mid[2]; + } //end else +} //end of the function BotFuncBobStartEnd +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + //if standing ontop of the func_bobbing + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot on func_bobbing\n"); +#endif + //if near end point of reachability + VectorSubtract(bob_origin, bob_end, dir); + if (VectorLength(dir) < 24) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability end\n"); +#endif + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + //move to the center of the plat + if (dist > 100) + { + dist = 100; + } + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot not ontop of func_bobbing\n"); +#endif + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif + if (dist > 60) + { + dist = 60; + } + speed = 360 - (360 - 6 * dist); + //if swimming or no barrier jump + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) + { + EA_Move(ms->client, dir, speed); + } + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) + { + dir1[2] = 0; + } + dist1 = VectorNormalize(dir1); + //if func_bobbing is Not it's start position + VectorSubtract(bob_origin, bob_start, dir); + if (VectorLength(dir) > 16) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "func_bobbing not at start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) + { + dist = 60; + } + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) + { + EA_Move(ms->client, dir, speed); + } + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + //this isn't a failure... just wait till the func_bobbing arrives + result.type = RESULTTYPE_ELEVATORUP; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to func_bob bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) + { + dir2[2] = 0; + } + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and func_bobbing center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) + { + dist = 60; + } + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + } //end else + return result; +} //end of the function BotTravel_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter; + bot_moveresult_t result; + float dist, speed; + + BotClearMoveResult(&result); + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + // + VectorSubtract(bob_origin, bob_end, dir); + dist = VectorLength(dir); + //if the func_bobbing is near the end + if (dist < 16) + { + VectorSubtract(reach->end, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) + { + hordir[2] = 0; + } + dist = VectorNormalize(hordir); + // + if (dist > 60) + { + dist = 60; + } + speed = 360 - (360 - 6 * dist); + // + if (speed > 5) + { + EA_Move(ms->client, dir, speed); + } + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + result.flags |= MOVERESULT_SWIMVIEW; + } + } //end if + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) + { + hordir[2] = 0; + } + dist = VectorNormalize(hordir); + // + if (dist > 5) + { + //move to the center of the plat + if (dist > 100) + { + dist = 100; + } + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + return result; +} //end of the function BotFinishTravel_FuncBobbing +//=========================================================================== +// 0 no valid grapple hook visible +// 1 the grapple hook is still flying +// 2 the grapple hooked into a wall +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) +{ + static int grapplemodelindex; + int i; + vec3_t dir; + aas_entityinfo_t entinfo; + + if (!grapplemodelindex) + { + grapplemodelindex = AAS_IndexFromModel("models/weapons/grapple/hook/tris.md2"); + } //end if + for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) + { + if (AAS_EntityModelindex(i) == grapplemodelindex) + { + AAS_EntityInfo(i, &entinfo); + if (VectorCompare(entinfo.origin, entinfo.old_origin)) + { + VectorSubtract(entinfo.origin, reach->end, dir); + //if hooked near the reachability end + if (VectorLength(dir) < 32) return 2; + } //end if + else + { + //still shooting hook + return 1; + } //end else + } //end if + } //end if + //no valid grapple at all + return 0; +} //end of the function GrappleState*/ +//=========================================================================== +// 0 no valid grapple hook visible +// 1 the grapple hook is still flying +// 2 the grapple hooked into a wall +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) +{ + static int grapplemodelindex; + static libvar_t *laserhook; + int i; + vec3_t dir; + aas_entityinfo_t entinfo; + + if (!laserhook) + { + laserhook = LibVar("laserhook", "0"); + } + if (!laserhook->value && !grapplemodelindex) + { + grapplemodelindex = AAS_IndexFromModel("models/weapons/grapple/hook/tris.md2"); + } //end if + for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) + { + if ((!laserhook->value && AAS_EntityModelindex(i) == grapplemodelindex) +// || (laserhook->value && (AAS_EntityRenderFX(i) & RF_BEAM)) + ) + { + AAS_EntityInfo(i, &entinfo); + //if the origin is equal to the last visible origin + if (VectorCompare(entinfo.origin, entinfo.lastvisorigin)) + { + VectorSubtract(entinfo.origin, reach->end, dir); + //if hooked near the reachability end + if (VectorLength(dir) < 32) + { + return 2; + } + } //end if + else + { + //still shooting hook + return 1; + } //end else + } //end if + } //end for + //no valid grapple at all + return 0; +} //end of the function GrappleState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGrapple(bot_movestate_t *ms) +{ + aas_reachability_t reach; + + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if not using the grapple hook reachability anymore + if (reach.traveltype != TRAVEL_GRAPPLEHOOK) + { + if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time) + { + EA_Command(ms->client, CMD_HOOKOFF); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->grapplevisible_time = 0; +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "reset grapple\n"); +#endif //DEBUG_GRAPPLE + } //end if + } //end if +} //end of the function BotResetGrapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Grapple(bot_movestate_t *ms, aas_reachability_t *reach) +{ + bot_moveresult_t result; + float dist, speed; + vec3_t dir, viewdir, org; + int state, areanum; + +#ifdef DEBUG_GRAPPLE + static int debugline; + if (!debugline) + { + debugline = botimport.DebugLineCreate(); + } + botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE); +#endif //DEBUG_GRAPPLE + + BotClearMoveResult(&result); + // + if (ms->moveflags & MFL_GRAPPLERESET) + { + EA_Command(ms->client, CMD_HOOKOFF); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + return result; + } //end if + // + if (ms->moveflags & MFL_ACTIVEGRAPPLE) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: active grapple\n"); +#endif //DEBUG_GRAPPLE + // + state = GrappleState(ms, reach); + // + VectorSubtract(reach->end, ms->origin, dir); + dir[2] = 0; + dist = VectorLength(dir); + //if very close to the grapple end or + //the grappled is hooked and the bot doesn't get any closer + if (state && dist < 48) + { + if (ms->lastgrappledist - dist < 1) + { + EA_Command(ms->client, CMD_HOOKOFF); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple normal end\n"); +#endif //DEBUG_GRAPPLE + } //end if + } //end if + //if no valid grapple at all, or the grapple hooked and the bot + //isn't moving anymore + else if (!state || (state == 2 && dist > ms->lastgrappledist - 2)) + { + if (ms->grapplevisible_time < AAS_Time() - 0.4) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple not visible\n"); +#endif //DEBUG_GRAPPLE + EA_Command(ms->client, CMD_HOOKOFF); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + //result.failure = qtrue; + //result.type = RESULTTYPE_INVISIBLEGRAPPLE; + return result; + } //end if + } //end if + else + { + ms->grapplevisible_time = AAS_Time(); + } //end else + //remember the current grapple distance + ms->lastgrappledist = dist; + } //end if + else + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n"); +#endif //DEBUG_GRAPPLE + // + ms->grapplevisible_time = AAS_Time(); + // + VectorSubtract(reach->start, ms->origin, dir); + if (!(ms->moveflags & MFL_SWIMMING)) + { + dir[2] = 0; + } + VectorAdd(ms->origin, ms->viewoffset, org); + VectorSubtract(reach->end, org, viewdir); + // + dist = VectorNormalize(dir); + Vector2Angles(viewdir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + if (dist < 5 && + Q_fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 && + Q_fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 2) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n"); +#endif //DEBUG_GRAPPLE + EA_Command(ms->client, CMD_HOOKON); + ms->moveflags |= MFL_ACTIVEGRAPPLE; + ms->lastgrappledist = 999999; + } //end if + else + { + if (dist < 70) + { + speed = 300 - (300 - 4 * dist); + } + else + { + speed = 400; + } + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + } //end else + //if in another area before actually grappling + areanum = AAS_PointAreaNum(ms->origin); + if (areanum && areanum != ms->reachareanum) + { + ms->reachability_time = 0; + } + } //end else + return result; +} //end of the function BotTravel_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_RocketJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + // + if (dist < 5) + { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if (dist > 80) + { + dist = 80; + } + speed = 400 - (400 - 5 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + // +/* + vec3_t hordir, dir1, dir2, start, end, runstart; + float dist1, dist2, speed; + bot_moveresult_t result; + + botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); + BotClearMoveResult(&result); + AAS_JumpReachRunStart(reach, runstart); + // + hordir[0] = runstart[0] - reach->start[0]; + hordir[1] = runstart[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + VectorMA(reach->start, 80, hordir, runstart); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, runstart, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { +// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); + hordir[0] = runstart[0] - ms->origin[0]; + hordir[1] = runstart[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + */ + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View(ms->client, result.ideal_viewangles); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon(ms->client, WEAPONINDEX_ROCKET_LAUNCHER); + //weapon is used for movement + result.weapon = WEAPONINDEX_ROCKET_LAUNCHER; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_RocketJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BFGJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + return result; +} //end of the function BotTravel_BFGJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WeaponJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not jumped yet + if (!ms->jumpreach) + { + return result; + } + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //always use max speed when traveling through the air + EA_Move(ms->client, hordir, 800); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WeaponJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + speed = 400; + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_JumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + speed = 400; + } //end if + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_JumpPad +//=========================================================================== +// time before the reachability times out +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityTime(aas_reachability_t *reach) +{ + switch (reach->traveltype) + { + case TRAVEL_WALK: return 5; + case TRAVEL_CROUCH: return 5; + case TRAVEL_BARRIERJUMP: return 5; + case TRAVEL_LADDER: return 6; + case TRAVEL_WALKOFFLEDGE: return 5; + case TRAVEL_JUMP: return 5; + case TRAVEL_SWIM: return 5; + case TRAVEL_WATERJUMP: return 5; + case TRAVEL_TELEPORT: return 5; + case TRAVEL_ELEVATOR: return 10; + case TRAVEL_GRAPPLEHOOK: return 8; + case TRAVEL_ROCKETJUMP: return 6; + //case TRAVEL_BFGJUMP: return 6; + case TRAVEL_JUMPPAD: return 10; + case TRAVEL_FUNCBOB: return 10; + default: + { + botimport.Print(PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype); + return 8; + } //end case + } //end switch +} //end of the function BotReachabilityTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotMoveInGoalArea(bot_movestate_t *ms, bot_goal_t *goal) +{ + bot_moveresult_t result; + vec3_t dir; + float dist, speed; + +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1)); + //AAS_ClearShownDebugLines(); + //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); +#endif //DEBUG + BotClearMoveResult(&result); + //walk straight to the goal origin + dir[0] = goal->origin[0] - ms->origin[0]; + dir[1] = goal->origin[1] - ms->origin[1]; + if (ms->moveflags & MFL_SWIMMING) + { + dir[2] = goal->origin[2] - ms->origin[2]; + result.traveltype = TRAVEL_SWIM; + } //end if + else + { + dir[2] = 0; + result.traveltype = TRAVEL_WALK; + } //endif + // + dist = VectorNormalize(dir); + if (dist > 100 || (goal->flags & GFL_NOSLOWAPPROACH)) + { + dist = 100; + } + speed = 400 - (400 - 4 * dist); + if (speed < 10) + { + speed = 0; + } + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + } //end if + //if (!debugline) debugline = botimport.DebugLineCreate(); + //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE); + // + ms->lastreachnum = 0; + ms->lastareanum = 0; + ms->lastgoalareanum = goal->areanum; + VectorCopy(ms->origin, ms->lastorigin); + ms->lasttime = AAS_Time(); + // + return result; +} //end of the function BotMoveInGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum); +extern float VectorDistance(vec3_t v1, vec3_t v2); +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags) +{ + int reachnum = 0; // TTimo (might be used uninitialized in this function) + int lastreachnum, foundjumppad, ent; + aas_reachability_t reach, lastreach; + bot_movestate_t *ms; + //vec3_t mins, maxs, up = {0, 0, 1}; + //bsp_trace_t trace; + //static int debugline; + + // + BotClearMoveResult(result); + // + ms = BotMoveStateFromHandle(movestate); + if (!ms) + { + return; + } + //reset the grapple before testing if the bot has a valid goal + //because the bot could loose all it's goals when stuck to a wall + BotResetGrapple(ms); + // + if (!goal) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client); +#endif //DEBUG + result->failure = qtrue; + return; + } //end if + //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach); + //remove some of the move flags + ms->moveflags &= ~(MFL_SWIMMING | MFL_AGAINSTLADDER); + //set some of the move flags + //NOTE: the MFL_ONGROUND flag is also set in the higher AI + if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) + { + ms->moveflags |= MFL_ONGROUND; + } + // + + if (ms->moveflags & MFL_ONGROUND) + { + int modeltype, modelnum; + + ent = BotOnTopOfEntity(ms); + + if (ent != -1) + { + modelnum = AAS_EntityModelindex(ent); + if (modelnum >= 0 && modelnum < MAX_MODELS) + { + modeltype = modeltypes[modelnum]; + + if (modeltype == MODELTYPE_FUNC_PLAT) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the elevator + if (reach.traveltype != TRAVEL_ELEVATOR || + //NOTE: the face number is the plat model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_ELEVATOR; + } //end if + else if (modeltype == MODELTYPE_FUNC_BOB) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the func bobbing + if (reach.traveltype != TRAVEL_FUNCBOB || + //NOTE: the face number is the func_bobbing model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_FUNCBOB; + } //end if + /* Ridah, disabled this, or standing on little fragments causes problems + else + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + */ + } //end if + } //end if + } //end if + //if swimming + if (AAS_Swimming(ms->origin)) + { + ms->moveflags |= MFL_SWIMMING; + } + //if against a ladder + if (AAS_AgainstLadder(ms->origin, ms->areanum)) + { + ms->moveflags |= MFL_AGAINSTLADDER; + } + //if the bot is on the ground, swimming or against a ladder + if (ms->moveflags & (MFL_ONGROUND | MFL_SWIMMING | MFL_AGAINSTLADDER)) + { + //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + // + AAS_ReachabilityFromNum(ms->lastreachnum, &lastreach); + //reachability area the bot is in + //ms->areanum = BotReachabilityArea(ms->origin, (lastreach.traveltype != TRAVEL_ELEVATOR)); + if (!ms->areanum) + { + result->failure = qtrue; + return; + } + //ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + //if the bot is in the goal area + if (ms->areanum == goal->areanum) + { + *result = BotMoveInGoalArea(ms, goal); + return; + } //end if + //assume we can use the reachability from the last frame + reachnum = ms->lastreachnum; + //if there is a last reachability + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //check if the reachability is still valid + if (!(AAS_TravelFlagForType(reach.traveltype) & travelflags)) + { + reachnum = 0; + } //end if + //special grapple hook case + else if (reach.traveltype == TRAVEL_GRAPPLEHOOK) + { + if (ms->reachability_time < AAS_Time() || + (ms->moveflags & MFL_GRAPPLERESET)) + { + reachnum = 0; + } //end if + } //end if + //special elevator case + else if (reach.traveltype == TRAVEL_ELEVATOR || reach.traveltype == TRAVEL_FUNCBOB) + { + if ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || + (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) + { + ms->reachability_time = AAS_Time() + 5; + } //end if + //if the bot was going for an elevator and reached the reachability area + if (ms->areanum == reach.areanum || + ms->reachability_time < AAS_Time()) + { + reachnum = 0; + } //end if + } //end if + else + { + if (ms->reachability_time < AAS_Time()) + { + // the reachability timed out, add it to the ignore list +#ifdef AVOIDREACH + BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME + 4); +#endif //AVOIDREACH + } +#ifdef DEBUG + if (bot_developer) + { + if (ms->reachability_time < AAS_Time()) + { + botimport.Print(PRT_MESSAGE, "client %d: reachability timeout in ", ms->client); + AAS_PrintTravelType(reach.traveltype); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + /* + if (ms->lastareanum != ms->areanum) + { + botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum); + } //end if*/ + } //end if +#endif //DEBUG + //if the goal area changed or the reachability timed out + //or the area changed + if (ms->lastgoalareanum != goal->areanum || + ms->reachability_time < AAS_Time() || + ms->lastareanum != ms->areanum || + + //@TODO. The hardcoded distance here should actually be tied to speed. As it was, 20 was too big for + // slow moving walking Nazis. +// ((ms->lasttime > (AAS_Time()-0.5)) && (VectorDistance(ms->origin, ms->lastorigin) < 20*(AAS_Time()-ms->lasttime)))) + ((ms->lasttime > (AAS_Time() - 0.5)) && (VectorDistance(ms->origin, ms->lastorigin) < 5 * (AAS_Time() - ms->lasttime)))) + { + reachnum = 0; + //botimport.Print(PRT_MESSAGE, "area change or timeout\n"); + } //end else if + } //end else + } //end if + //if the bot needs a new reachability + if (!reachnum) + { + //if the area has no reachability links + if (!AAS_AreaReachability(ms->areanum)) + { +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "area %d no reachability\n", ms->areanum); + } //end if +#endif //DEBUG + } //end if + //get a new reachability leading towards the goal + reachnum = BotGetReachabilityToGoal(ms->origin, ms->areanum, ms->entitynum, + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags); + //the area number the reachability starts in + ms->reachareanum = ms->areanum; + //reset some state variables + ms->jumpreach = 0; //for TRAVEL_JUMP + ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK + //if there is a reachability to the goal + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //set a timeout for this reachability + ms->reachability_time = AAS_Time() + (float)(0.01 * (AAS_AreaTravelTime(ms->areanum, ms->origin, reach.start) * 2) + BotReachabilityTime(&reach) + 100); + if (reach.traveltype == TRAVEL_LADDER) + { + ms->reachability_time += 4.0; // allow more time to navigate ladders + } + // +#ifdef AVOIDREACH + if (reach.traveltype != TRAVEL_LADDER) // don't avoid ladders unless we were unable to reach them in time + { + BotAddToAvoidReach(ms, reachnum, 3); // add a short avoid reach time + } +#endif //AVOIDREACH + } //end if +#ifdef DEBUG + + else if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + memset(&reach, 0, sizeof(aas_reachability_t)); //make compiler happy + } //end else + if (bot_developer) + { + //if still going for the same goal + if (ms->lastgoalareanum == goal->areanum) + { + if (ms->lastareanum == reach.areanum) + { + botimport.Print(PRT_MESSAGE, "same goal, going back to previous area\n"); + } //end if + } //end if + } //end if +#endif //DEBUG + } //end else + // + ms->lastreachnum = reachnum; + ms->lastgoalareanum = goal->areanum; + ms->lastareanum = ms->areanum; + //if the bot has a reachability + if (reachnum) + { + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + result->traveltype = reach.traveltype; + // + if (goal->flags & GFL_DEBUGPATH) + { + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_PrintTravelType(reach.traveltype); + // src area + AAS_ShowAreaPolygons(ms->areanum, 1, qtrue); + // dest area + AAS_ShowAreaPolygons(goal->areanum, 3, qtrue); + // reachability + AAS_ShowReachability(&reach); + AAS_ShowAreaPolygons(reach.areanum, 2, qtrue); + } + // +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client); + //AAS_PrintTravelType(reach.traveltype); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + switch (reach.traveltype) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: *result = BotTravel_Crouch(ms, &reach); break; + case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: *result = BotTravel_Teleport(ms, &reach); break; + case TRAVEL_ELEVATOR: *result = BotTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump(ms, &reach); break; + //case TRAVEL_BFGJUMP: + case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "travel type %d not implemented yet\n", reach.traveltype); + break; + } //end case + } //end switch + } //end if + else + { + result->failure = qtrue; + memset(&reach, 0, sizeof(aas_reachability_t)); + } //end else +#ifdef DEBUG + if (bot_developer) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in ", ms->client); + AAS_PrintTravelType(reach.traveltype); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG + } //end if + else + { + int i, numareas, areas[16]; + vec3_t end; + + //special handling of jump pads when the bot uses a jump pad without knowing it + foundjumppad = qfalse; + VectorMA(ms->origin, -2 * ms->thinktime, ms->velocity, end); + numareas = AAS_TraceAreas(ms->origin, end, areas, NULL, 16); + for (i = numareas - 1; i >= 0; i--) + { + if (AAS_AreaJumpPad(areas[i])) + { + //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]); + foundjumppad = qtrue; + lastreachnum = BotGetReachabilityToGoal(end, areas[i], ms->entitynum, + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, TFL_JUMPPAD); + if (lastreachnum) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n"); + break; + } //end if + else + { + for (lastreachnum = AAS_NextAreaReachability(areas[i], 0); lastreachnum; + lastreachnum = AAS_NextAreaReachability(areas[i], lastreachnum)) + { + //get the reachability from the number + AAS_ReachabilityFromNum(lastreachnum, &reach); + if (reach.traveltype == TRAVEL_JUMPPAD) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n"); + break; + } //end if + } //end for + if (lastreachnum) + { + break; + } + } //end else + } //end if + } //end for + if (bot_developer) + { + //if a jumppad is found with the trace but no reachability is found + if (foundjumppad && !ms->lastreachnum) + { + botimport.Print(PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client); + } //end if + } //end if + // + if (ms->lastreachnum) + { + //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + result->traveltype = reach.traveltype; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client); + //AAS_PrintTravelType(reach.traveltype); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + // + switch (reach.traveltype) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break; //BotFinishTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: /*do nothing*/ break; + case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotFinishTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: /*do nothing*/ break; + case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break; + //case TRAVEL_BFGJUMP: + case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "(last) travel type %d not implemented yet\n", reach.traveltype); + break; + } //end case + } //end switch +#ifdef DEBUG + if (bot_developer) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client); + AAS_PrintTravelType(reach.traveltype); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG + } //end if + } //end else + //FIXME: is it right to do this here? + if (result->blocked) + { + ms->reachability_time -= 10 * ms->thinktime; + } + //copy the last origin + VectorCopy(ms->origin, ms->lastorigin); + ms->lasttime = AAS_Time(); +/* + // RF, try to look in the direction we will be moving ahead of time + if (reachnum > 0 && !(result->flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW))) { + vec3_t dir; + int ftraveltime, freachnum; + + AAS_ReachabilityFromNum( reachnum, &reach); + if (reach.areanum != goal->areanum) { + if (AAS_AreaRouteToGoalArea( reach.areanum, reach.end, goal->areanum, travelflags, &ftraveltime, &freachnum )) { + AAS_ReachabilityFromNum( freachnum, &reach); + VectorSubtract( reach.end, ms->origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, result->ideal_viewangles ); + result->flags |= MOVERESULT_FUTUREVIEW; + } + } else { + VectorSubtract( goal->origin, ms->origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, result->ideal_viewangles ); + result->flags |= MOVERESULT_FUTUREVIEW; + } + } +*/ + //return the movement result + return; +} //end of the function BotMoveToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidReach(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) + { + return; + } + memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float)); + memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int)); + + // RF, also clear movestate stuff + ms->lastareanum = 0; + ms->lastgoalareanum = 0; + ms->lastreachnum = 0; +} //end of the function BotResetAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetLastAvoidReach(int movestate) +{ + int i, latest; + float latesttime; + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) + { + return; + } + latesttime = 0; + latest = 0; + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] > latesttime) + { + latesttime = ms->avoidreachtimes[i]; + latest = i; + } //end if + } //end for + if (latesttime) + { + ms->avoidreachtimes[latest] = 0; + if (ms->avoidreachtries[i] > 0) + { + ms->avoidreachtries[latest]--; + } + } //end if +} //end of the function BotResetLastAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetMoveState(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) + { + return; + } + memset(ms, 0, sizeof(bot_movestate_t)); +} //end of the function BotResetMoveState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupMoveAI(void) +{ + BotSetBrushModelTypes(); + sv_maxstep = LibVarValue("sv_step", "18"); + sv_maxbarrier = LibVarValue("sv_maxbarrier", "32"); + sv_gravity = LibVarValue("sv_gravity", "800"); + return BLERR_NOERROR; +} //end of the function BotSetupMoveAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownMoveAI(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botmovestates[i]) + { + FreeMemory(botmovestates[i]); + botmovestates[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownMoveAI diff --git a/src/botlib/be_ai_weap.c b/src/botlib/be_ai_weap.c new file mode 100644 index 000000000..3930b9398 --- /dev/null +++ b/src/botlib/be_ai_weap.c @@ -0,0 +1,606 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_weap.c + * @brief weapon AI + */ + +#include "../qcommon/q_shared.h" +#include "l_libvar.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" //fuzzy weights +#include "../game/be_ai_weap.h" + +//#define DEBUG_AI_WEAP + +//structure field offsets +#define WEAPON_OFS(x) (int)&(((weaponinfo_t *)0)->x) +#define PROJECTILE_OFS(x) (int)&(((projectileinfo_t *)0)->x) + +//weapon definition +fielddef_t weaponinfo_fields[] = +{ + { "number", WEAPON_OFS(number), FT_INT }, //weapon number + { "name", WEAPON_OFS(name), FT_STRING }, //name of the weapon + { "level", WEAPON_OFS(level), FT_INT }, + { "model", WEAPON_OFS(model), FT_STRING }, //model of the weapon + { "weaponindex", WEAPON_OFS(weaponindex), FT_INT }, //index of weapon in inventory + { "flags", WEAPON_OFS(flags), FT_INT }, //special flags + { "projectile", WEAPON_OFS(projectile), FT_STRING }, //projectile used by the weapon + { "numprojectiles", WEAPON_OFS(numprojectiles), FT_INT }, //number of projectiles + { "hspread", WEAPON_OFS(hspread), FT_FLOAT }, //horizontal spread of projectiles (degrees from middle) + { "vspread", WEAPON_OFS(vspread), FT_FLOAT }, //vertical spread of projectiles (degrees from middle) + { "speed", WEAPON_OFS(speed), FT_FLOAT }, //speed of the projectile (0 = instant hit) + { "acceleration", WEAPON_OFS(acceleration), FT_FLOAT }, //"acceleration" * time (in seconds) + "speed" = projectile speed + { "recoil", WEAPON_OFS(recoil), FT_FLOAT | FT_ARRAY, 3}, //amount of recoil the player gets from the weapon + { "offset", WEAPON_OFS(offset), FT_FLOAT | FT_ARRAY, 3}, //projectile start offset relative to eye and view angles + { "angleoffset", WEAPON_OFS(angleoffset), FT_FLOAT | FT_ARRAY, 3}, //offset of the shoot angles relative to the view angles + { "extrazvelocity", WEAPON_OFS(extrazvelocity), FT_FLOAT }, //extra z velocity the projectile gets + { "ammoamount", WEAPON_OFS(ammoamount), FT_INT }, //ammo amount used per shot + { "ammoindex", WEAPON_OFS(ammoindex), FT_INT }, //index of ammo in inventory + { "activate", WEAPON_OFS(activate), FT_FLOAT }, //time it takes to select the weapon + { "reload", WEAPON_OFS(reload), FT_FLOAT }, //time it takes to reload the weapon + { "spinup", WEAPON_OFS(spinup), FT_FLOAT }, //time it takes before first shot + { "spindown", WEAPON_OFS(spindown), FT_FLOAT }, //time it takes before weapon stops firing + { NULL, 0, 0, 0 } +}; + +//projectile definition +fielddef_t projectileinfo_fields[] = +{ + { "name", PROJECTILE_OFS(name), FT_STRING }, //name of the projectile + { "model", WEAPON_OFS(model), FT_STRING }, //model of the projectile + { "flags", PROJECTILE_OFS(flags), FT_INT }, //special flags + { "gravity", PROJECTILE_OFS(gravity), FT_FLOAT }, //amount of gravity applied to the projectile [0,1] + { "damage", PROJECTILE_OFS(damage), FT_INT }, //damage of the projectile + { "radius", PROJECTILE_OFS(radius), FT_FLOAT }, //radius of damage + { "visdamage", PROJECTILE_OFS(visdamage), FT_INT }, //damage of the projectile to visible entities + { "damagetype", PROJECTILE_OFS(damagetype), FT_INT }, //type of damage (combination of the DAMAGETYPE_? flags) + { "healthinc", PROJECTILE_OFS(healthinc), FT_INT }, //health increase the owner gets + { "push", PROJECTILE_OFS(push), FT_FLOAT }, //amount a player is pushed away from the projectile impact + { "detonation", PROJECTILE_OFS(detonation), FT_FLOAT }, //time before projectile explodes after fire pressed + { "bounce", PROJECTILE_OFS(bounce), FT_FLOAT }, //amount the projectile bounces + { "bouncefric", PROJECTILE_OFS(bouncefric), FT_FLOAT }, //amount the bounce decreases per bounce + { "bouncestop", PROJECTILE_OFS(bouncestop), FT_FLOAT }, //minimum bounce value before bouncing stops +//recurive projectile definition?? + { NULL, 0, 0, 0 } +}; + +structdef_t weaponinfo_struct = +{ + sizeof(weaponinfo_t), weaponinfo_fields +}; +structdef_t projectileinfo_struct = +{ + sizeof(projectileinfo_t), projectileinfo_fields +}; + +//weapon configuration: set of weapons with projectiles +typedef struct weaponconfig_s +{ + int numweapons; + int numprojectiles; + projectileinfo_t *projectileinfo; + weaponinfo_t *weaponinfo; +} weaponconfig_t; + +//the bot weapon state +typedef struct bot_weaponstate_s +{ + struct weightconfig_s *weaponweightconfig; //weapon weight configuration + int *weaponweightindex; //weapon weight index +} bot_weaponstate_t; + +bot_weaponstate_t *botweaponstates[MAX_CLIENTS + 1]; +weaponconfig_t *weaponconfig; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +extern qboolean g_singleplayer; + +int BotValidWeaponNumber(int weaponnum) +{ + // Thanks Arnout! TDF + // Gordon: 0 is valid in mp now too, for mr. pow + if ((g_singleplayer && weaponnum < 0) || (!g_singleplayer && weaponnum < 0) || weaponnum > weaponconfig->numweapons) + { + botimport.Print(PRT_ERROR, "weapon number out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidWeaponNumber +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_weaponstate_t *BotWeaponStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botweaponstates[handle]; +} //end of the function BotWeaponStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef DEBUG_AI_WEAP +void DumpWeaponConfig(weaponconfig_t *wc) +{ + FILE *fp; + int i; + + fp = Log_FileStruct(); + if (!fp) + { + return; + } + for (i = 0; i < wc->numprojectiles; i++) + { + WriteStructure(fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i]); + Log_Flush(); + } //end for + for (i = 0; i < wc->numweapons; i++) + { + WriteStructure(fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i]); + Log_Flush(); + } //end for +} //end of the function DumpWeaponConfig +#endif //DEBUG_AI_WEAP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weaponconfig_t *LoadWeaponConfig(char *filename) +{ + int max_weaponinfo, max_projectileinfo; + token_t token; + char path[MAX_PATH]; + int i, j; + source_t *source; + weaponconfig_t *wc; + weaponinfo_t weaponinfo; + + max_weaponinfo = (int) LibVarValue("max_weaponinfo", "64"); + if (max_weaponinfo < 0) + { + botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo); + max_weaponinfo = 64; + LibVarSet("max_weaponinfo", "64"); + } //end if + max_projectileinfo = (int) LibVarValue("max_projectileinfo", "64"); + if (max_projectileinfo < 0) + { + botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo); + max_projectileinfo = 64; + LibVarSet("max_projectileinfo", "64"); + } //end if + strncpy(path, filename, MAX_PATH); + source = LoadSourceFile(path); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", path); + return NULL; + } //end if + //initialize weapon config + wc = (weaponconfig_t *) GetClearedHunkMemory(sizeof(weaponconfig_t) + + max_weaponinfo * sizeof(weaponinfo_t) + + max_projectileinfo * sizeof(projectileinfo_t)); + wc->weaponinfo = ( weaponinfo_t * )((char *) wc + sizeof(weaponconfig_t)); + wc->projectileinfo = ( projectileinfo_t * )((char *) wc->weaponinfo + + max_weaponinfo * sizeof(weaponinfo_t)); + wc->numweapons = max_weaponinfo; + wc->numprojectiles = 0; + //parse the source file + while (PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weaponinfo")) + { + memset(&weaponinfo, 0, sizeof(weaponinfo_t)); + if (!ReadStructure(source, &weaponinfo_struct, (char *) &weaponinfo)) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + if (weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo) + { + botimport.Print(PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + memcpy(&wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof(weaponinfo_t)); + wc->weaponinfo[weaponinfo.number].valid = qtrue; + } //end if + else if (!strcmp(token.string, "projectileinfo")) + { + if (wc->numprojectiles >= max_projectileinfo) + { + botimport.Print(PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + memset(&wc->projectileinfo[wc->numprojectiles], 0, sizeof(projectileinfo_t)); + if (!ReadStructure(source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles])) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + wc->numprojectiles++; + } //end if + else + { + botimport.Print(PRT_ERROR, "unknown definition %s in %s\n", token.string, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + //fix up weapons + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) + { + continue; + } + if (!wc->weaponinfo[i].name[0]) + { + botimport.Print(PRT_ERROR, "weapon %d has no name in %s\n", i, path); + FreeMemory(wc); + return NULL; + } //end if + if (!wc->weaponinfo[i].projectile[0]) + { + botimport.Print(PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + //find the projectile info and copy it to the weapon info + for (j = 0; j < wc->numprojectiles; j++) + { + if (!strcmp(wc->projectileinfo[j].name, wc->weaponinfo[i].projectile)) + { + memcpy(&wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof(projectileinfo_t)); + break; + } //end if + } //end for + if (j == wc->numprojectiles) + { + botimport.Print(PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + } //end for + if (!wc->numweapons) + { + botimport.Print(PRT_WARNING, "no weapon info loaded\n"); + } + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return wc; +} //end of the function LoadWeaponConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *WeaponWeightIndex(weightconfig_t *wwc, weaponconfig_t *wc) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * wc->numweapons); + + for (i = 0; i < wc->numweapons; i++) + { + index[i] = FindFuzzyWeight(wwc, wc->weaponinfo[i].name); + } //end for + return index; +} //end of the function WeaponWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeWeaponWeights(int weaponstate) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) + { + return; + } + if (ws->weaponweightconfig) + { + FreeWeightConfig(ws->weaponweightconfig); + } + if (ws->weaponweightindex) + { + FreeMemory(ws->weaponweightindex); + } +} //end of the function BotFreeWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadWeaponWeights(int weaponstate, char *filename) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) + { + return BLERR_CANNOTLOADWEAPONWEIGHTS; + } + BotFreeWeaponWeights(weaponstate); + // + PS_SetBaseFolder("botfiles"); + ws->weaponweightconfig = ReadWeightConfig(filename); + PS_SetBaseFolder(""); + if (!ws->weaponweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weapon config %s\n", filename); + return BLERR_CANNOTLOADWEAPONWEIGHTS; + } //end if + if (!weaponconfig) + { + return BLERR_CANNOTLOADWEAPONCONFIG; + } + ws->weaponweightindex = WeaponWeightIndex(ws->weaponweightconfig, weaponconfig); + return BLERR_NOERROR; +} //end of the function BotLoadWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo) +{ + bot_weaponstate_t *ws; + + if (!BotValidWeaponNumber(weapon)) + { + return; + } + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) + { + return; + } + if (!weaponconfig) + { + return; + } + memcpy(weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof(weaponinfo_t)); +} //end of the function BotGetWeaponInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseBestFightWeapon(int weaponstate, int *inventory) +{ + int i, index, bestweapon; + float weight, bestweight; + weaponconfig_t *wc; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) + { + return 0; + } + wc = weaponconfig; + if (!weaponconfig) + { + return 0; + } + + //if the bot has no weapon weight configuration + if (!ws->weaponweightconfig) + { + return 0; + } + + bestweight = 0; + bestweapon = 0; + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) + { + continue; + } + index = ws->weaponweightindex[i]; + if (index < 0) + { + continue; + } + weight = FuzzyWeight(inventory, ws->weaponweightconfig, index); + if (weight > bestweight) + { + bestweight = weight; + bestweapon = i; + } //end if + } //end for + return bestweapon; +} //end of the function BotChooseBestFightWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetWeaponState(int weaponstate) +{ + struct weightconfig_s *weaponweightconfig; + int *weaponweightindex; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) + { + return; + } + weaponweightconfig = ws->weaponweightconfig; + weaponweightindex = ws->weaponweightindex; + + //memset(ws, 0, sizeof(bot_weaponstate_t)); + ws->weaponweightconfig = weaponweightconfig; + ws->weaponweightindex = weaponweightindex; +} //end of the function BotResetWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocWeaponState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botweaponstates[i]) + { + botweaponstates[i] = GetClearedMemory(sizeof(bot_weaponstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeWeaponState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + BotFreeWeaponWeights(handle); + FreeMemory(botweaponstates[handle]); + botweaponstates[handle] = NULL; +} //end of the function BotFreeWeaponState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupWeaponAI(void) +{ + char *file; + + PS_SetBaseFolder("botfiles"); + file = LibVarString("weaponconfig", "weapons.c"); + weaponconfig = LoadWeaponConfig(file); + PS_SetBaseFolder(""); + if (!weaponconfig) + { + botimport.Print(PRT_FATAL, "couldn't load the weapon config\n"); + return BLERR_CANNOTLOADWEAPONCONFIG; + } //end if + +#ifdef DEBUG_AI_WEAP + DumpWeaponConfig(weaponconfig); +#endif //DEBUG_AI_WEAP + // + return BLERR_NOERROR; +} //end of the function BotSetupWeaponAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeaponAI(void) +{ + int i; + + if (weaponconfig) + { + FreeMemory(weaponconfig); + } + weaponconfig = NULL; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botweaponstates[i]) + { + BotFreeWeaponState(i); + } //end if + } //end for +} //end of the function BotShutdownWeaponAI diff --git a/src/botlib/be_ai_weight.c b/src/botlib/be_ai_weight.c new file mode 100644 index 000000000..8212194bb --- /dev/null +++ b/src/botlib/be_ai_weight.c @@ -0,0 +1,1204 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_weight.c + * @brief fuzzy logic + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" + +#define MAX_INVENTORYVALUE 999999 +#define EVALUATERECURSIVELY + +#define MAX_WEIGHT_FILES 128 +weightconfig_t *weightFileList[MAX_WEIGHT_FILES]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadValue(source_t *source, float *value) +{ + token_t token; + + if (!PC_ExpectAnyToken(source, &token)) + { + return qfalse; + } + if (!strcmp(token.string, "-")) + { + SourceWarning(source, "negative value set to zero\n"); + if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + return qfalse; + } + } //end if + if (token.type != TT_NUMBER) + { + SourceError(source, "invalid return value %s\n", token.string); + return qfalse; + } //end if + *value = token.floatvalue; + return qtrue; +} //end of the function ReadValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadFuzzyWeight(source_t *source, fuzzyseperator_t *fs) +{ + if (PC_CheckTokenString(source, "balance")) + { + fs->type = WT_BALANCE; + if (!PC_ExpectTokenString(source, "(")) + { + return qfalse; + } + if (!ReadValue(source, &fs->weight)) + { + return qfalse; + } + if (!PC_ExpectTokenString(source, ",")) + { + return qfalse; + } + if (!ReadValue(source, &fs->minweight)) + { + return qfalse; + } + if (!PC_ExpectTokenString(source, ",")) + { + return qfalse; + } + if (!ReadValue(source, &fs->maxweight)) + { + return qfalse; + } + if (!PC_ExpectTokenString(source, ")")) + { + return qfalse; + } + } //end if + else + { + fs->type = 0; + if (!ReadValue(source, &fs->weight)) + { + return qfalse; + } + fs->minweight = fs->weight; + fs->maxweight = fs->weight; + } //end if + if (!PC_ExpectTokenString(source, ";")) + { + return qfalse; + } + return qtrue; +} //end of the function ReadFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeFuzzySeperators_r(fuzzyseperator_t *fs) +{ + if (!fs) + { + return; + } + if (fs->child) + { + FreeFuzzySeperators_r(fs->child); + } + if (fs->next) + { + FreeFuzzySeperators_r(fs->next); + } + FreeMemory(fs); +} //end of the function FreeFuzzySeperators +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig2(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + FreeFuzzySeperators_r(config->weights[i].firstseperator); + if (config->weights[i].name) + { + FreeMemory(config->weights[i].name); + } + } //end for + FreeMemory(config); +} //end of the function FreeWeightConfig2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig(weightconfig_t *config) +{ + if (!LibVarGetValue("bot_reloadcharacters")) + { + return; + } + FreeWeightConfig2(config); +} //end of the function FreeWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fuzzyseperator_t *ReadFuzzySeperators_r(source_t *source) +{ + int newindent, index, def, founddefault; + token_t token; + fuzzyseperator_t *fs, *lastfs, *firstfs; + + founddefault = qfalse; + firstfs = NULL; + lastfs = NULL; + if (!PC_ExpectTokenString(source, "(")) + { + return NULL; + } + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + return NULL; + } + index = token.intvalue; + if (!PC_ExpectTokenString(source, ")")) + { + return NULL; + } + if (!PC_ExpectTokenString(source, "{")) + { + return NULL; + } + if (!PC_ExpectAnyToken(source, &token)) + { + return NULL; + } + do + { + def = !strcmp(token.string, "default"); + if (def || !strcmp(token.string, "case")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + if (lastfs) + { + lastfs->next = fs; + } + else + { + firstfs = fs; + } + lastfs = fs; + if (def) + { + if (founddefault) + { + SourceError(source, "switch already has a default\n"); + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = MAX_INVENTORYVALUE; + founddefault = qtrue; + } //end if + else + { + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = token.intvalue; + } //end else + if (!PC_ExpectTokenString(source, ":") || !PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "return")) + { + if (!ReadFuzzyWeight(source, fs)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + else if (!strcmp(token.string, "switch")) + { + fs->child = ReadFuzzySeperators_r(source); + if (!fs->child) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + } //end if + else + { + FreeFuzzySeperators_r(firstfs); + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } + while (strcmp(token.string, "}")); + // + if (!founddefault) + { + SourceWarning(source, "switch without default\n"); + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + fs->value = MAX_INVENTORYVALUE; + fs->weight = 0; + fs->next = NULL; + fs->child = NULL; + if (lastfs) + { + lastfs->next = fs; + } + else + { + firstfs = fs; + } + lastfs = fs; + } //end if + // + return firstfs; +} //end of the function ReadFuzzySeperators_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weightconfig_t *ReadWeightConfig(char *filename) +{ + int newindent, avail = 0, n; + token_t token; + source_t *source; + fuzzyseperator_t *fs; + weightconfig_t *config = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for (n = 0; n < MAX_WEIGHT_FILES; n++) + { + config = weightFileList[n]; + if (!config) + { + if (avail == -1) + { + avail = n; + } //end if + continue; + } //end if + if (strcmp(filename, config->filename) == 0) + { + //botimport.Print( PRT_MESSAGE, "retained %s\n", filename ); + return config; + } //end if + } //end for + + if (avail == -1) + { + botimport.Print(PRT_ERROR, "weightFileList was full trying to load %s\n", filename); + return NULL; + } //end if + } //end if + + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + config = (weightconfig_t *) GetClearedMemory(sizeof(weightconfig_t)); + config->numweights = 0; + Q_strncpyz(config->filename, filename, sizeof(config->filename)); + //parse the item config file + while (PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weight")) + { + if (config->numweights >= MAX_WEIGHTS) + { + SourceWarning(source, "too many fuzzy weights\n"); + break; + } //end if + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + config->weights[config->numweights].name = (char *) GetClearedMemory(strlen(token.string) + 1); + strcpy(config->weights[config->numweights].name, token.string); + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "switch")) + { + fs = ReadFuzzySeperators_r(source); + if (!fs) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end if + else if (!strcmp(token.string, "return")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = 0; + fs->value = MAX_INVENTORYVALUE; + fs->next = NULL; + fs->child = NULL; + if (!ReadFuzzyWeight(source, fs)) + { + FreeMemory(fs); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + config->numweights++; + } //end if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source at the end of a pass + FreeSource(source); + //if the file was located in a pak file +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "weights loaded in %d msec\n", Sys_MilliSeconds() - starttime); + } //end if +#endif //DEBUG + // + if (!LibVarGetValue("bot_reloadcharacters")) + { + weightFileList[avail] = config; + } //end if + // + return config; +} //end of the function ReadWeightConfig +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzyWeight(FILE *fp, fuzzyseperator_t *fs) +{ + if (fs->type == WT_BALANCE) + { + if (fprintf(fp, " return balance(") < 0) + { + return qfalse; + } + if (!WriteFloat(fp, fs->weight)) + { + return qfalse; + } + if (fprintf(fp, ",") < 0) + { + return qfalse; + } + if (!WriteFloat(fp, fs->minweight)) + { + return qfalse; + } + if (fprintf(fp, ",") < 0) + { + return qfalse; + } + if (!WriteFloat(fp, fs->maxweight)) + { + return qfalse; + } + if (fprintf(fp, ");\n") < 0) + { + return qfalse; + } + } //end if + else + { + if (fprintf(fp, " return ") < 0) + { + return qfalse; + } + if (!WriteFloat(fp, fs->weight)) + { + return qfalse; + } + if (fprintf(fp, ";\n") < 0) + { + return qfalse; + } + } //end else + return qtrue; +} //end of the function WriteFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzySeperators_r(FILE *fp, fuzzyseperator_t *fs, int indent) +{ + if (!WriteIndent(fp, indent)) + { + return qfalse; + } + if (fprintf(fp, "switch(%d)\n", fs->index) < 0) + { + return qfalse; + } + if (!WriteIndent(fp, indent)) + { + return qfalse; + } + if (fprintf(fp, "{\n") < 0) + { + return qfalse; + } + indent++; + do + { + if (!WriteIndent(fp, indent)) + { + return qfalse; + } + if (fs->next) + { + if (fprintf(fp, "case %d:", fs->value) < 0) + { + return qfalse; + } + } //end if + else + { + if (fprintf(fp, "default:") < 0) + { + return qfalse; + } + } //end else + if (fs->child) + { + if (fprintf(fp, "\n") < 0) + { + return qfalse; + } + if (!WriteIndent(fp, indent)) + { + return qfalse; + } + if (fprintf(fp, "{\n") < 0) + { + return qfalse; + } + if (!WriteFuzzySeperators_r(fp, fs->child, indent + 1)) + { + return qfalse; + } + if (!WriteIndent(fp, indent)) + { + return qfalse; + } + if (fs->next) + { + if (fprintf(fp, "} //end case\n") < 0) + { + return qfalse; + } + } //end if + else + { + if (fprintf(fp, "} //end default\n") < 0) + { + return qfalse; + } + } //end else + } //end if + else + { + if (!WriteFuzzyWeight(fp, fs)) + { + return qfalse; + } + } //end else + fs = fs->next; + } + while (fs); + indent--; + if (!WriteIndent(fp, indent)) + { + return qfalse; + } + if (fprintf(fp, "} //end switch\n") < 0) + { + return qfalse; + } + return qtrue; +} //end of the function WriteItemFuzzyWeights_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteWeightConfig(char *filename, weightconfig_t *config) +{ + int i; + FILE *fp; + weight_t *ifw; + + fp = fopen(filename, "wb"); + if (!fp) + { + return qfalse; + } + + for (i = 0; i < config->numweights; i++) + { + ifw = &config->weights[i]; + if (fprintf(fp, "\nweight \"%s\"\n", ifw->name) < 0) + { + return qfalse; + } + if (fprintf(fp, "{\n") < 0) + { + return qfalse; + } + if (ifw->firstseperator->index > 0) + { + if (!WriteFuzzySeperators_r(fp, ifw->firstseperator, 1)) + { + return qfalse; + } + } //end if + else + { + if (!WriteIndent(fp, 1)) + { + return qfalse; + } + if (!WriteFuzzyWeight(fp, ifw->firstseperator)) + { + return qfalse; + } + } //end else + if (fprintf(fp, "} //end weight\n") < 0) + { + return qfalse; + } + } //end for + fclose(fp); + return qtrue; +} //end of the function WriteWeightConfig +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindFuzzyWeight(weightconfig_t *wc, char *name) +{ + int i; + + for (i = 0; i < wc->numweights; i++) + { + if (!strcmp(wc->weights[i].name, name)) + { + return i; + } //end if + } //end if + return -1; +} //end of the function FindFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) + { + return FuzzyWeight_r(inventory, fs->child); + } + else + { + return fs->weight; + } + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) + { + w1 = FuzzyWeight_r(inventory, fs->child); + } + else + { + w1 = fs->weight; + } + //second weight + if (fs->next->child) + { + w2 = FuzzyWeight_r(inventory, fs->next->child); + } + else + { + w2 = fs->next->weight; + } + //the scale factor + scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return scale * w1 + (1 - scale) * w2; + } //end if + return FuzzyWeight_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeight_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) + { + return FuzzyWeightUndecided_r(inventory, fs->child); + } + else + { + return fs->minweight + random() * (fs->maxweight - fs->minweight); + } + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) + { + w1 = FuzzyWeightUndecided_r(inventory, fs->child); + } + else + { + w1 = fs->minweight + random() * (fs->maxweight - fs->minweight); + } + //second weight + if (fs->next->child) + { + w2 = FuzzyWeight_r(inventory, fs->next->child); + } + else + { + w2 = fs->next->minweight + random() * (fs->next->maxweight - fs->next->minweight); + } + //the scale factor + scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return scale * w1 + (1 - scale) * w2; + } //end if + return FuzzyWeightUndecided_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeightUndecided_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeight_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) + { + return 0; + } + while (1) + { + if (inventory[s->index] < s->value) + { + if (s->child) + { + s = s->child; + } + else + { + return s->weight; + } + } //end if + else + { + if (s->next) + { + s = s->next; + } + else + { + return s->weight; + } + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeightUndecided_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) + { + return 0; + } + while (1) + { + if (inventory[s->index] < s->value) + { + if (s->child) + { + s = s->child; + } + else + { + return s->minweight + random() * (s->maxweight - s->minweight); + } + } //end if + else + { + if (s->next) + { + s = s->next; + } + else + { + return s->minweight + random() * (s->maxweight - s->minweight); + } + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeightUndecided +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveFuzzySeperator_r(fuzzyseperator_t *fs) +{ + if (fs->child) + { + EvolveFuzzySeperator_r(fs->child); + } //end if + else if (fs->type == WT_BALANCE) + { + //every once in a while an evolution leap occurs, mutation + if (random() < 0.01) + { + fs->weight += crandom() * (fs->maxweight - fs->minweight); + } + else + { + fs->weight += crandom() * (fs->maxweight - fs->minweight) * 0.5; + } + //modify bounds if necesary because of mutation + if (fs->weight < fs->minweight) + { + fs->minweight = fs->weight; + } + else if (fs->weight > fs->maxweight) + { + fs->maxweight = fs->weight; + } + } //end else if + if (fs->next) + { + EvolveFuzzySeperator_r(fs->next); + } +} //end of the function EvolveFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveWeightConfig(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + EvolveFuzzySeperator_r(config->weights[i].firstseperator); + } //end for +} //end of the function EvolveWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperator_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperator_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + // + fs->weight = (fs->maxweight + fs->minweight) * scale; + //get the weight between bounds + if (fs->weight < fs->minweight) + { + fs->weight = fs->minweight; + } + else if (fs->weight > fs->maxweight) + { + fs->weight = fs->maxweight; + } + } //end else if + if (fs->next) + { + ScaleFuzzySeperator_r(fs->next, scale); + } +} //end of the function ScaleFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleWeight(weightconfig_t *config, char *name, float scale) +{ + int i; + + if (scale < 0) + { + scale = 0; + } + else if (scale > 1) + { + scale = 1; + } + for (i = 0; i < config->numweights; i++) + { + if (!strcmp(name, config->weights[i].name)) + { + ScaleFuzzySeperator_r(config->weights[i].firstseperator, scale); + break; + } //end if + } //end for +} //end of the function ScaleWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperatorBalanceRange_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperatorBalanceRange_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + float mid = (fs->minweight + fs->maxweight) * 0.5; + //get the weight between bounds + fs->maxweight = mid + (fs->maxweight - mid) * scale; + fs->minweight = mid + (fs->minweight - mid) * scale; + if (fs->maxweight < fs->minweight) + { + fs->maxweight = fs->minweight; + } //end if + } //end else if + if (fs->next) + { + ScaleFuzzySeperatorBalanceRange_r(fs->next, scale); + } +} //end of the function ScaleFuzzySeperatorBalanceRange_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzyBalanceRange(weightconfig_t *config, float scale) +{ + int i; + + if (scale < 0) + { + scale = 0; + } + else if (scale > 100) + { + scale = 100; + } + for (i = 0; i < config->numweights; i++) + { + ScaleFuzzySeperatorBalanceRange_r(config->weights[i].firstseperator, scale); + } //end for +} //end of the function ScaleFuzzyBalanceRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int InterbreedFuzzySeperator_r(fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, + fuzzyseperator_t *fsout) +{ + if (fs1->child) + { + if (!fs2->child || !fsout->child) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal child\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs2->child, fs2->child, fsout->child)) + { + return qfalse; + } //end if + } //end if + else if (fs1->type == WT_BALANCE) + { + if (fs2->type != WT_BALANCE || fsout->type != WT_BALANCE) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal balance\n"); + return qfalse; + } //end if + fsout->weight = (fs1->weight + fs2->weight) / 2; + if (fsout->weight > fsout->maxweight) + { + fsout->maxweight = fsout->weight; + } + if (fsout->weight > fsout->minweight) + { + fsout->minweight = fsout->weight; + } + } //end else if + if (fs1->next) + { + if (!fs2->next || !fsout->next) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal next\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs1->next, fs2->next, fsout->next)) + { + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function InterbreedFuzzySeperator_r +//=========================================================================== +// config1 and config2 are interbreeded and stored in configout +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, + weightconfig_t *configout) +{ + int i; + + if (config1->numweights != config2->numweights || + config1->numweights != configout->numweights) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n"); + return; + } //end if + for (i = 0; i < config1->numweights; i++) + { + InterbreedFuzzySeperator_r(config1->weights[i].firstseperator, + config2->weights[i].firstseperator, + configout->weights[i].firstseperator); + } //end for +} //end of the function InterbreedWeightConfigs +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeights(void) +{ + int i; + + for (i = 0; i < MAX_WEIGHT_FILES; i++) + { + if (weightFileList[i]) + { + FreeWeightConfig2(weightFileList[i]); + weightFileList[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownWeights diff --git a/src/botlib/be_ai_weight.h b/src/botlib/be_ai_weight.h new file mode 100644 index 000000000..c61605dc4 --- /dev/null +++ b/src/botlib/be_ai_weight.h @@ -0,0 +1,86 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ai_weight.h + * @brief fuzzy weights + */ + +#define WT_BALANCE 1 +#define MAX_WEIGHTS 128 + +//fuzzy seperator +typedef struct fuzzyseperator_s +{ + int index; + int value; + int type; + float weight; + float minweight; + float maxweight; + struct fuzzyseperator_s *child; + struct fuzzyseperator_s *next; +} fuzzyseperator_t; + +//fuzzy weight +typedef struct weight_s +{ + char *name; + struct fuzzyseperator_s *firstseperator; +} weight_t; + +//weight configuration +typedef struct weightconfig_s +{ + int numweights; + weight_t weights[MAX_WEIGHTS]; + char filename[MAX_QPATH]; +} weightconfig_t; + +//reads a weight configuration +weightconfig_t *ReadWeightConfig(char *filename); +//free a weight configuration +void FreeWeightConfig(weightconfig_t *config); +//writes a weight configuration, returns true if successfull +qboolean WriteWeightConfig(char *filename, weightconfig_t *config); +//find the fuzzy weight with the given name +int FindFuzzyWeight(weightconfig_t *wc, char *name); +//returns the fuzzy weight for the given inventory and weight +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum); +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum); +//scales the weight with the given name +void ScaleWeight(weightconfig_t *config, char *name, float scale); +//scale the balance range +void ScaleBalanceRange(weightconfig_t *config, float scale); +//evolves the weight configuration +void EvolveWeightConfig(weightconfig_t *config); +//interbreed the weight configurations and stores the interbreeded one in configout +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout); +//frees cached weight configurations +void BotShutdownWeights(void); diff --git a/src/botlib/be_ea.c b/src/botlib/be_ea.c new file mode 100644 index 000000000..d0bd4c667 --- /dev/null +++ b/src/botlib/be_ea.c @@ -0,0 +1,529 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_ea.c + * @brief elementary actions + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "../game/botlib.h" +#include "be_interface.h" + +#define MAX_USERMOVE 400 +#define MAX_COMMANDARGUMENTS 10 +#define ACTION_JUMPEDLASTFRAME 128 + +bot_input_t *botinputs; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Say(int client, char *str) +{ + botimport.BotClientCommand(client, va("say %s", str)); +} //end of the function EA_Say +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SayTeam(int client, char *str) +{ + botimport.BotClientCommand(client, va("say_team %s", str)); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("use %s", it)); +} //end of the function EA_UseItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("drop %s", it)); +} //end of the function EA_DropItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invuse %s", inv)); +} //end of the function EA_UseInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invdrop %s", inv)); +} //end of the function EA_DropInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Gesture(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_GESTURE; +} //end of the function EA_Gesture +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Command(int client, char *command) +{ + botimport.BotClientCommand(client, command); +} //end of the function EA_Command +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SelectWeapon(int client, int weapon) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->weapon = weapon; +} //end of the function EA_SelectWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Attack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_ATTACK; +} //end of the function EA_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Reload(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_RELOAD; +} //end of the function EA_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Talk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_TALK; +} //end of the function EA_Talk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Use(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_USE; +} //end of the function EA_Use +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Respawn(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_RESPAWN; +} //end of the function EA_Respawn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Jump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_JUMP; + } //end if + else + { + bi->actionflags |= ACTION_JUMP; + } //end if +} //end of the function EA_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DelayedJump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } //end if + else + { + bi->actionflags |= ACTION_DELAYEDJUMP; + } //end if +} //end of the function EA_DelayedJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Crouch(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_CROUCH; +} //end of the function EA_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Walk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_WALK; +} //end of the function EA_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveUp(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEUP; +} //end of the function EA_MoveUp +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveDown(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEDOWN; +} //end of the function EA_MoveDown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveForward(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEFORWARD; +} //end of the function EA_MoveForward +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveBack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEBACK; +} //end of the function EA_MoveBack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveLeft(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVELEFT; +} //end of the function EA_MoveLeft +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveRight(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVERIGHT; +} //end of the function EA_MoveRight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Move(int client, vec3_t dir, float speed) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(dir, bi->dir); + //cap speed + if (speed > MAX_USERMOVE) + { + speed = MAX_USERMOVE; + } + else if (speed < -MAX_USERMOVE) + { + speed = -MAX_USERMOVE; + } + bi->speed = speed; +} //end of the function EA_Move +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_View(int client, vec3_t viewangles) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(viewangles, bi->viewangles); +} //end of the function EA_View +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Prone(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_PRONE; +} //end of the function EA_Prone +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_EndRegular(int client, float thinktime) +{ + /* + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + botimport.BotInput(client, bi); + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; + */ +} //end of the function EA_EndRegular +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_GetInput(int client, float thinktime, bot_input_t *input) +{ + bot_input_t *bi; +// int jumped = qfalse; + + bi = &botinputs[client]; + +// bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + memcpy(input, bi, sizeof(bot_input_t)); + + /* + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; + */ +} //end of the function EA_GetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_ResetInput(int client, bot_input_t *init) +{ + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) + { + bi->actionflags |= ACTION_JUMPEDLASTFRAME; + } + + if (init) + { + memcpy(bi, init, sizeof(bot_input_t)); + } +} //end of the function EA_ResetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int EA_Setup(void) +{ + //initialize the bot inputs + botinputs = (bot_input_t *) GetClearedHunkMemory( + botlibglobals.maxclients * sizeof(bot_input_t)); + return BLERR_NOERROR; +} //end of the function EA_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Shutdown(void) +{ + FreeMemory(botinputs); + botinputs = NULL; +} //end of the function EA_Shutdown diff --git a/src/botlib/be_interface.c b/src/botlib/be_interface.c new file mode 100644 index 000000000..418f14044 --- /dev/null +++ b/src/botlib/be_interface.c @@ -0,0 +1,953 @@ +/* + * Wolfenstein: Enemy Territory GPL Source Code + * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + * + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy 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 ET: Legacy. If not, see . + * + * In addition, Wolfenstein: Enemy Territory GPL Source Code is also + * subject to certain additional terms. You should have received a copy + * of these additional terms immediately following the terms and conditions + * of the GNU General Public License which accompanied the source code. + * If not, please request a copy in writing from id Software at the address below. + * + * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + * + * @file be_interface.c + * @brief bot library interface + */ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_gen.h" + +//library globals in a structure +botlib_globals_t botlibglobals; + +botlib_export_t be_botlib_export; +botlib_import_t botimport; +// +int bot_developer; +//qtrue if the library is setup +int botlibsetup = qfalse; + +//=========================================================================== +// +// several functions used by the exported functions +// +//=========================================================================== + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// Ridah, faster Win32 code +#ifdef _WIN32 +#undef MAX_PATH // this is an ugly hack, to temporarily ignore the current definition, since it's also defined in windows.h +#include +#undef MAX_PATH +#define MAX_PATH MAX_QPATH +#endif + +int Sys_MilliSeconds(void) +{ +// Ridah, faster Win32 code +#ifdef _WIN32 + int sys_curtime; + static qboolean initialized = qfalse; + static int sys_timeBase; + + if (!initialized) + { + sys_timeBase = timeGetTime(); + initialized = qtrue; + } + sys_curtime = timeGetTime() - sys_timeBase; + + return sys_curtime; +#else + return clock() * 1000 / CLOCKS_PER_SEC; +#endif +} //end of the function Sys_MilliSeconds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidClientNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxclients) + { + //weird: the disabled stuff results in a crash + botimport.Print(PRT_ERROR, "%s: invalid client number %d, [0, %d]\n", + str, num, botlibglobals.maxclients); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidEntityNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxentities) + { + botimport.Print(PRT_ERROR, "%s: invalid entity number %d, [0, %d]\n", + str, num, botlibglobals.maxentities); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BotLibSetup(char *str) +{ +// return qtrue; + + if (!botlibglobals.botlibsetup) + { + botimport.Print(PRT_ERROR, "%s: bot library used before being setup\n", str); + return qfalse; + } //end if + return qtrue; +} //end of the function BotLibSetup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +extern define_t *globaldefines; +int Export_BotLibSetup(qboolean singleplayer) +{ + int errnum; + + bot_developer = LibVarGetValue("bot_developer"); + //initialize byte swapping (litte endian etc.) + Log_Open("botlib.log"); + // + botimport.Print(PRT_MESSAGE, "------- BotLib Initialization -------\n"); + // + botlibglobals.maxclients = (int) LibVarValue("maxclients", "128"); + botlibglobals.maxentities = (int) LibVarValue("maxentities", "1024"); + + errnum = AAS_Setup(); //be_aas_main.c + if (errnum != BLERR_NOERROR) + { + return errnum; + } + errnum = EA_Setup(); //be_ea.c + if (errnum != BLERR_NOERROR) + { + return errnum; + } + errnum = BotSetupWeaponAI(); //be_ai_weap.c + if (errnum != BLERR_NOERROR) + { + return errnum; + } +// START Arnout changes, 28-08-2002. +// added single player + errnum = BotSetupGoalAI(singleplayer); //be_ai_goal.c +// END Arnout changes, 28-08-2002. + if (errnum != BLERR_NOERROR) + { + return errnum; + } + errnum = BotSetupChatAI(); //be_ai_chat.c + if (errnum != BLERR_NOERROR) + { + return errnum; + } + errnum = BotSetupMoveAI(); //be_ai_move.c + if (errnum != BLERR_NOERROR) + { + return errnum; + } + + globaldefines = NULL; + + botlibsetup = qtrue; + botlibglobals.botlibsetup = qtrue; + + return BLERR_NOERROR; +} //end of the function Export_BotLibSetup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibShutdown(void) +{ + static int recursive = 0; + + if (!BotLibSetup("BotLibShutdown")) + { + return BLERR_LIBRARYNOTSETUP; + } + // + if (recursive) + { + return BLERR_NOERROR; + } + recursive = 1; + // shutdown all AI subsystems + BotShutdownChatAI(); //be_ai_chat.c + BotShutdownMoveAI(); //be_ai_move.c + BotShutdownGoalAI(); //be_ai_goal.c + BotShutdownWeaponAI(); //be_ai_weap.c + BotShutdownWeights(); //be_ai_weight.c + BotShutdownCharacters(); //be_ai_char.c + // shutdown AAS + AAS_Shutdown(); + // shutdown bot elemantary actions + EA_Shutdown(); + // free all libvars + LibVarDeAllocAll(); + // remove all global defines from the pre compiler + PC_RemoveAllGlobalDefines(); + // shut down library log file + Log_Shutdown(); + // + botlibsetup = qfalse; + botlibglobals.botlibsetup = qfalse; + recursive = 0; + // print any files still open + PC_CheckOpenSourceHandles(); + // +#ifdef _DEBUG + Log_AlwaysOpen("memory.log"); + PrintMemoryLabels(); + Log_Shutdown(); +#endif + return BLERR_NOERROR; +} //end of the function Export_BotLibShutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarSet(char *var_name, char *value) +{ + LibVarSet(var_name, value); + return BLERR_NOERROR; +} //end of the function Export_BotLibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarGet(char *var_name, char *value, int size) +{ + char *varvalue; + + varvalue = LibVarGetString(var_name); + strncpy(value, varvalue, size - 1); + value[size - 1] = '\0'; + return BLERR_NOERROR; +} //end of the function Export_BotLibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibStartFrame(float time) +{ + if (!BotLibSetup("BotStartFrame")) + { + return BLERR_LIBRARYNOTSETUP; + } + return AAS_StartFrame(time); +} //end of the function Export_BotLibStartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EnableAllAreas(void); + +int Export_BotLibLoadMap(const char *mapname) +{ +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif + int errnum; + + if (!BotLibSetup("BotLoadMap")) + { + return BLERR_LIBRARYNOTSETUP; + } + // + // if the mapname is NULL, then this is a restart + if (!mapname) + { + // START Arnout changes, 29-08-2002. + // don't init the heap if no aas loaded, causes "SV_Bot_HunkAlloc: Alloc with marks already set" + if ((*aasworld).loaded) + { + AAS_InitAASLinkHeap(); + AAS_EnableAllAreas(); + } + // END Arnout changes, 29-08-2002. + (*aasworld).numframes = 0; + memset((*aasworld).arealinkedentities, 0, (*aasworld).numareas * sizeof(aas_link_t *)); + memset((*aasworld).entities, 0, (*aasworld).maxentities * sizeof(aas_entity_t)); + return BLERR_NOERROR; + } + // + botimport.Print(PRT_MESSAGE, "------------ Map Loading ------------\n"); + //startup AAS for the current map, model and sound index + errnum = AAS_LoadMap(mapname); + if (errnum != BLERR_NOERROR) + { + return errnum; + } + //initialize the items in the level + BotInitLevelItems(); //be_ai_goal.h + BotSetBrushModelTypes(); //be_ai_move.h + // + botimport.Print(PRT_MESSAGE, "-------------------------------------\n"); +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "map loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif + // + return BLERR_NOERROR; +} //end of the function Export_BotLibLoadMap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibUpdateEntity(int ent, bot_entitystate_t *state) +{ + if (!BotLibSetup("BotUpdateEntity")) + { + return BLERR_LIBRARYNOTSETUP; + } + if (!ValidEntityNumber(ent, "BotUpdateEntity")) + { + return BLERR_INVALIDENTITYNUMBER; + } + + return AAS_UpdateEntity(ent, state); +} //end of the function Export_BotLibUpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir); +void ElevatorBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter); +int BotGetReachabilityToGoal(vec3_t origin, int areanum, int entnum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags); + +int AAS_PointLight(vec3_t origin, int *red, int *green, int *blue); + +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + +int AAS_Reachability_WeaponJump(int area1num, int area2num); + +int BotFuzzyPointReachabilityArea(vec3_t origin); + +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum); + +int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags, float maxdist, vec3_t distpos); + +int AAS_FindAttackSpotWithinRange(int srcnum, int rangenum, int enemynum, float rangedist, int travelflags, float *outpos); + +int AAS_ListAreasInRange(vec3_t srcpos, int srcarea, float range, int travelflags, vec3_t *outareas, int maxareas); + +int AAS_AvoidDangerArea(vec3_t srcpos, int srcarea, vec3_t dangerpos, int dangerarea, float range, int travelflags); + +int AAS_Retreat(int *dangerSpots, int dangerSpotCount, vec3_t srcpos, int srcarea, vec3_t dangerpos, int dangerarea, float range, float dangerRange, int travelflags); + +int AAS_AlternativeRouteGoals(vec3_t start, vec3_t goal, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int color); + +void AAS_SetAASBlockingEntity(vec3_t absmin, vec3_t absmax, int blocking); + +void AAS_RecordTeamDeathArea(vec3_t srcpos, int srcarea, int team, int teamCount, int travelflags); + +int BotExportTest(int parm0, char *parm1, vec3_t parm2, vec3_t parm3) +{ + static int area = -1; + static int line[2]; + int newarea, i, highlightarea, bot_testhidepos, hideposarea, bot_debug; + vec3_t forward, origin; + +// vec3_t mins = {-16, -16, -24}; +// vec3_t maxs = {16, 16, 32}; + + if (!aasworld->loaded) + { + return 0; + } + + AAS_SetCurrentWorld(0); + + for (i = 0; i < 2; i++) + { + if (!line[i]) + { + line[i] = botimport.DebugLineCreate(); + } + } + +// AAS_ClearShownDebugLines(); + bot_testhidepos = LibVarGetValue("bot_testhidepos"); + if (bot_testhidepos) + { + VectorCopy(parm2, origin); + newarea = BotFuzzyPointReachabilityArea(origin); + + if (parm0 & 1) + { + botlibglobals.goalareanum = newarea; + VectorCopy(origin, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new enemy position %2.1f %2.1f %2.1f area %d\n", origin[0], origin[1], origin[2], newarea); + } //end if + + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + hideposarea = AAS_NearestHideArea(0, origin, AAS_PointAreaNum(origin), 0, + botlibglobals.goalorigin, botlibglobals.goalareanum, TFL_DEFAULT, 99999, NULL); + + //area we are currently in + AAS_ShowAreaPolygons(newarea, 1, qtrue); + + //enemy position + AAS_ShowAreaPolygons(botlibglobals.goalareanum, 2, qtrue); + + //area we should go hide + AAS_ShowAreaPolygons(hideposarea, 4, qtrue); + + return 0; + } + + highlightarea = LibVarGetValue("bot_highlightarea"); + if (highlightarea > 0) + { + newarea = highlightarea; + } + else + { + VectorCopy(parm2, origin); + + //origin[2] += 0.5; + newarea = BotFuzzyPointReachabilityArea(origin); + } //end else + + bot_debug = LibVarGetValue("bot_debug"); + if (bot_debug == 9) + { + aas_clientmove_t move; + vec3_t dest; + qboolean this_success; + + if (parm0 & 1) + { + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", origin[0], origin[1], origin[2], newarea); + } + + VectorCopy(parm2, origin); + VectorCopy(botlibglobals.goalorigin, dest); + + // debug direct movement + VectorSubtract(dest, origin, forward); + VectorNormalize(forward); + VectorScale(forward, 300, forward); + + this_success = AAS_PredictClientMovement(&move, 0, origin, + -1, qfalse, + forward, dest, -1, + 40, 0.05, SE_ENTERAREA | SE_HITGROUNDDAMAGE | SE_HITENT | SE_HITGROUNDAREA | SE_STUCK | SE_GAP, botlibglobals.goalareanum, + qtrue); + + if (this_success) + { + switch (move.stopevent) + { + case SE_ENTERAREA: + case SE_HITENT: + case SE_HITGROUNDAREA: + break; + default: + this_success = qfalse; + } + } + + if (this_success != botlibglobals.lastsuccess) + { + botimport.Print(PRT_MESSAGE, "DirectMove: %s\n", this_success ? "SUCCESS" : "FAILURE"); + botlibglobals.lastsuccess = this_success; + } + + return 0; + } + + botimport.Print(PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum, AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT)); + if (newarea != area) + { + botimport.Print(PRT_MESSAGE, "origin = %f, %f, %f\n", origin[0], origin[1], origin[2]); + area = newarea; + botimport.Print(PRT_MESSAGE, "new area %d, cluster %d, presence type %d\n", area, AAS_AreaCluster(area), AAS_PointPresenceType(origin)); + + if (aasworld->areasettings[area].areaflags & AREA_LIQUID) + { + botimport.Print(PRT_MESSAGE, "liquid area\n"); + } //end if + + botimport.Print(PRT_MESSAGE, "area contents: "); + if (aasworld->areasettings[area].contents & AREACONTENTS_MOVER) + { + botimport.Print(PRT_MESSAGE, "mover "); + } //end if + if (aasworld->areasettings[area].contents & AREACONTENTS_WATER) + { + botimport.Print(PRT_MESSAGE, "water "); + } //end if + if (aasworld->areasettings[area].contents & AREACONTENTS_LAVA) + { + botimport.Print(PRT_MESSAGE, "lava "); + } //end if + if (aasworld->areasettings[area].contents & AREACONTENTS_SLIME) + { + botimport.Print(PRT_MESSAGE, "slag "); + } //end if + + if (aasworld->areasettings[area].contents & AREACONTENTS_JUMPPAD) + { + botimport.Print(PRT_MESSAGE, "jump pad "); + } //end if + if (aasworld->areasettings[area].contents & AREACONTENTS_CLUSTERPORTAL) + { + botimport.Print(PRT_MESSAGE, "cluster portal "); + } //end if + if (aasworld->areasettings[area].contents & AREACONTENTS_DONOTENTER) + { + botimport.Print(PRT_MESSAGE, "do not enter "); + } //end if + if (aasworld->areasettings[area].contents & AREACONTENTS_DONOTENTER_LARGE) + { + botimport.Print(PRT_MESSAGE, "do not enter large "); + } //end if + if (!aasworld->areasettings[area].contents) + { + botimport.Print(PRT_MESSAGE, "empty "); + } //end if + + botimport.Print(PRT_MESSAGE, "\n"); + botimport.Print(PRT_MESSAGE, "area flags: "); + + if (aasworld->areasettings[area].areaflags & AREA_LADDER) + { + botimport.Print(PRT_MESSAGE, "ladder "); + } + if (aasworld->areasettings[area].areaflags & AREA_GROUNDED) + { + botimport.Print(PRT_MESSAGE, "grounded "); + } + if (aasworld->areasettings[area].areaflags & AREA_LIQUID) + { + botimport.Print(PRT_MESSAGE, "liquid "); + } + if (aasworld->areasettings[area].areaflags & AREA_DISABLED) + { + botimport.Print(PRT_MESSAGE, "DISABLED "); + } + if (aasworld->areasettings[area].areaflags & AREA_AVOID) + { + botimport.Print(PRT_MESSAGE, "AVOID "); + } + + botimport.Print(PRT_MESSAGE, "\n"); + botimport.Print(PRT_MESSAGE, "travel time to goal (%d) = %d\n", botlibglobals.goalareanum, AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT | TFL_ROCKETJUMP)); + } + + if (parm0 & 1) + { + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", origin[0], origin[1], origin[2], newarea); + } + + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + + if (parm0 & 8) + { + int jk = 0; + if (parm0 & 16) + { + for ( ; jk < aasworld->numareas; jk++) + { + if (!(aasworld->areasettings[jk].areaflags & AREA_DISABLED)) + { + AAS_ShowAreaPolygons(jk, 1, parm0 & 4); + } + } + } + else + { + for ( ; jk < aasworld->numareas; jk++) + { + AAS_ShowAreaPolygons(jk, 1, parm0 & 4); + } + } + } + else + { + AAS_ShowAreaPolygons(newarea, 1, parm0 & 4); + } + + if (parm0 & 2) + { + AAS_ShowReachableAreas(area); + } + else + { + static int lastgoalareanum, lastareanum; + static int avoidreach[MAX_AVOIDREACH]; + static float avoidreachtimes[MAX_AVOIDREACH]; + static int avoidreachtries[MAX_AVOIDREACH]; + + int reachnum; + bot_goal_t goal; + aas_reachability_t reach; + static int lastreach; + + goal.areanum = botlibglobals.goalareanum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + reachnum = BotGetReachabilityToGoal(origin, newarea, -1, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT | TFL_FUNCBOB, TFL_DEFAULT); + AAS_ReachabilityFromNum(reachnum, &reach); + if (lastreach != reachnum) + { + botimport.Print(PRT_MESSAGE, "Travel Type: "); + AAS_PrintTravelType(reach.traveltype); + botimport.Print(PRT_MESSAGE, "\n"); + } + lastreach = reachnum; + AAS_ShowReachability(&reach); + } //end else + VectorClear(forward); + + return 0; +} //end of the function BotExportTest + + +/* +============ +Init_AAS_Export +============ +*/ +static void Init_AAS_Export(aas_export_t *aas) +{ + //-------------------------------------------- + // be_aas_entity.c + //-------------------------------------------- + aas->AAS_EntityInfo = AAS_EntityInfo; + //-------------------------------------------- + // be_aas_main.c + //-------------------------------------------- + aas->AAS_Initialized = AAS_Initialized; + aas->AAS_PresenceTypeBoundingBox = AAS_PresenceTypeBoundingBox; + aas->AAS_Time = AAS_Time; + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + aas->AAS_PointAreaNum = AAS_PointAreaNum; + aas->AAS_TraceAreas = AAS_TraceAreas; + aas->AAS_BBoxAreas = AAS_BBoxAreas; + aas->AAS_AreaCenter = AAS_AreaCenter; + aas->AAS_AreaWaypoint = AAS_AreaWaypoint; + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + aas->AAS_PointContents = AAS_PointContents; + aas->AAS_NextBSPEntity = AAS_NextBSPEntity; + aas->AAS_ValueForBSPEpairKey = AAS_ValueForBSPEpairKey; + aas->AAS_VectorForBSPEpairKey = AAS_VectorForBSPEpairKey; + aas->AAS_FloatForBSPEpairKey = AAS_FloatForBSPEpairKey; + aas->AAS_IntForBSPEpairKey = AAS_IntForBSPEpairKey; + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + aas->AAS_AreaReachability = AAS_AreaReachability; + aas->AAS_AreaLadder = AAS_AreaLadder; + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + aas->AAS_AreaTravelTimeToGoalArea = AAS_AreaTravelTimeToGoalArea; + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + aas->AAS_Swimming = AAS_Swimming; + aas->AAS_PredictClientMovement = AAS_PredictClientMovement; + + // Ridah, route-tables + //-------------------------------------------- + // be_aas_routetable.c + //-------------------------------------------- + aas->AAS_RT_ShowRoute = AAS_RT_ShowRoute; + aas->AAS_RT_GetHidePos = AAS_RT_GetHidePos; + aas->AAS_FindAttackSpotWithinRange = AAS_FindAttackSpotWithinRange; + aas->AAS_NearestHideArea = AAS_NearestHideArea; + aas->AAS_ListAreasInRange = AAS_ListAreasInRange; + aas->AAS_AvoidDangerArea = AAS_AvoidDangerArea; + aas->AAS_Retreat = AAS_Retreat; + aas->AAS_AlternativeRouteGoals = AAS_AlternativeRouteGoals; + aas->AAS_SetAASBlockingEntity = AAS_SetAASBlockingEntity; + aas->AAS_RecordTeamDeathArea = AAS_RecordTeamDeathArea; + // done. + + // Ridah, multiple AAS files + aas->AAS_SetCurrentWorld = AAS_SetCurrentWorld; + // done. + +} + + +/* +============ +Init_EA_Export +============ +*/ +static void Init_EA_Export(ea_export_t *ea) +{ + //ClientCommand elementary actions + ea->EA_Say = EA_Say; + ea->EA_SayTeam = EA_SayTeam; + ea->EA_UseItem = EA_UseItem; + ea->EA_DropItem = EA_DropItem; + ea->EA_UseInv = EA_UseInv; + ea->EA_DropInv = EA_DropInv; + ea->EA_Gesture = EA_Gesture; + ea->EA_Command = EA_Command; + ea->EA_SelectWeapon = EA_SelectWeapon; + ea->EA_Talk = EA_Talk; + ea->EA_Attack = EA_Attack; + ea->EA_Reload = EA_Reload; + ea->EA_Use = EA_Use; + ea->EA_Respawn = EA_Respawn; + ea->EA_Jump = EA_Jump; + ea->EA_DelayedJump = EA_DelayedJump; + ea->EA_Crouch = EA_Crouch; + ea->EA_Walk = EA_Walk; + ea->EA_MoveUp = EA_MoveUp; + ea->EA_MoveDown = EA_MoveDown; + ea->EA_MoveForward = EA_MoveForward; + ea->EA_MoveBack = EA_MoveBack; + ea->EA_MoveLeft = EA_MoveLeft; + ea->EA_MoveRight = EA_MoveRight; + ea->EA_Move = EA_Move; + ea->EA_View = EA_View; + ea->EA_GetInput = EA_GetInput; + ea->EA_EndRegular = EA_EndRegular; + ea->EA_ResetInput = EA_ResetInput; + ea->EA_Prone = EA_Prone; +} + + +/* +============ +Init_AI_Export +============ +*/ +static void Init_AI_Export(ai_export_t *ai) +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + ai->BotLoadCharacter = BotLoadCharacter; + ai->BotFreeCharacter = BotFreeCharacter; + ai->Characteristic_Float = Characteristic_Float; + ai->Characteristic_BFloat = Characteristic_BFloat; + ai->Characteristic_Integer = Characteristic_Integer; + ai->Characteristic_BInteger = Characteristic_BInteger; + ai->Characteristic_String = Characteristic_String; + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + ai->BotAllocChatState = BotAllocChatState; + ai->BotFreeChatState = BotFreeChatState; + ai->BotQueueConsoleMessage = BotQueueConsoleMessage; + ai->BotRemoveConsoleMessage = BotRemoveConsoleMessage; + ai->BotNextConsoleMessage = BotNextConsoleMessage; + ai->BotNumConsoleMessages = BotNumConsoleMessages; + ai->BotInitialChat = BotInitialChat; + ai->BotNumInitialChats = BotNumInitialChats; + ai->BotReplyChat = BotReplyChat; + ai->BotChatLength = BotChatLength; + ai->BotEnterChat = BotEnterChat; + ai->BotGetChatMessage = BotGetChatMessage; + ai->StringContains = StringContains; + ai->BotFindMatch = BotFindMatch; + ai->BotMatchVariable = BotMatchVariable; + ai->UnifyWhiteSpaces = UnifyWhiteSpaces; + ai->BotReplaceSynonyms = BotReplaceSynonyms; + ai->BotLoadChatFile = BotLoadChatFile; + ai->BotSetChatGender = BotSetChatGender; + ai->BotSetChatName = BotSetChatName; + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + ai->BotResetGoalState = BotResetGoalState; + ai->BotResetAvoidGoals = BotResetAvoidGoals; + ai->BotRemoveFromAvoidGoals = BotRemoveFromAvoidGoals; + ai->BotPushGoal = BotPushGoal; + ai->BotPopGoal = BotPopGoal; + ai->BotEmptyGoalStack = BotEmptyGoalStack; + ai->BotDumpAvoidGoals = BotDumpAvoidGoals; + ai->BotDumpGoalStack = BotDumpGoalStack; + ai->BotGoalName = BotGoalName; + ai->BotGetTopGoal = BotGetTopGoal; + ai->BotGetSecondGoal = BotGetSecondGoal; + ai->BotChooseLTGItem = BotChooseLTGItem; + ai->BotChooseNBGItem = BotChooseNBGItem; + ai->BotTouchingGoal = BotTouchingGoal; + ai->BotItemGoalInVisButNotVisible = BotItemGoalInVisButNotVisible; + ai->BotGetLevelItemGoal = BotGetLevelItemGoal; + ai->BotGetNextCampSpotGoal = BotGetNextCampSpotGoal; + ai->BotGetMapLocationGoal = BotGetMapLocationGoal; + ai->BotAvoidGoalTime = BotAvoidGoalTime; + ai->BotInitLevelItems = BotInitLevelItems; + ai->BotUpdateEntityItems = BotUpdateEntityItems; + ai->BotLoadItemWeights = BotLoadItemWeights; + ai->BotFreeItemWeights = BotFreeItemWeights; + ai->BotInterbreedGoalFuzzyLogic = BotInterbreedGoalFuzzyLogic; + ai->BotSaveGoalFuzzyLogic = BotSaveGoalFuzzyLogic; + ai->BotMutateGoalFuzzyLogic = BotMutateGoalFuzzyLogic; + ai->BotAllocGoalState = BotAllocGoalState; + ai->BotFreeGoalState = BotFreeGoalState; + //----------------------------------- + // be_ai_move.h + //----------------------------------- + ai->BotResetMoveState = BotResetMoveState; + ai->BotMoveToGoal = BotMoveToGoal; + ai->BotMoveInDirection = BotMoveInDirection; + ai->BotResetAvoidReach = BotResetAvoidReach; + ai->BotResetLastAvoidReach = BotResetLastAvoidReach; + ai->BotReachabilityArea = BotReachabilityArea; + ai->BotMovementViewTarget = BotMovementViewTarget; + ai->BotPredictVisiblePosition = BotPredictVisiblePosition; + ai->BotAllocMoveState = BotAllocMoveState; + ai->BotFreeMoveState = BotFreeMoveState; + ai->BotInitMoveState = BotInitMoveState; + // Ridah + ai->BotInitAvoidReach = BotInitAvoidReach; + // done. + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + ai->BotChooseBestFightWeapon = BotChooseBestFightWeapon; + ai->BotGetWeaponInfo = BotGetWeaponInfo; + ai->BotLoadWeaponWeights = BotLoadWeaponWeights; + ai->BotAllocWeaponState = BotAllocWeaponState; + ai->BotFreeWeaponState = BotFreeWeaponState; + ai->BotResetWeaponState = BotResetWeaponState; + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + ai->GeneticParentsAndChildSelection = GeneticParentsAndChildSelection; +} + + +/* +============ +GetBotLibAPI +============ +*/ +botlib_export_t *GetBotLibAPI(int apiVersion, botlib_import_t *import) +{ + botimport = *import; + + memset(&be_botlib_export, 0, sizeof(be_botlib_export)); + + if (apiVersion != BOTLIB_API_VERSION) + { + botimport.Print(PRT_ERROR, "Mismatched BOTLIB_API_VERSION: expected %i, got %i\n", BOTLIB_API_VERSION, apiVersion); + return NULL; + } + + Init_AAS_Export(&be_botlib_export.aas); + Init_EA_Export(&be_botlib_export.ea); + Init_AI_Export(&be_botlib_export.ai); + + be_botlib_export.BotLibSetup = Export_BotLibSetup; + be_botlib_export.BotLibShutdown = Export_BotLibShutdown; + be_botlib_export.BotLibVarSet = Export_BotLibVarSet; + be_botlib_export.BotLibVarGet = Export_BotLibVarGet; + be_botlib_export.PC_AddGlobalDefine = PC_AddGlobalDefine; + be_botlib_export.PC_RemoveAllGlobalDefines = PC_RemoveAllGlobalDefines; + be_botlib_export.PC_LoadSourceHandle = PC_LoadSourceHandle; + be_botlib_export.PC_FreeSourceHandle = PC_FreeSourceHandle; + be_botlib_export.PC_ReadTokenHandle = PC_ReadTokenHandle; + be_botlib_export.PC_SourceFileAndLine = PC_SourceFileAndLine; + be_botlib_export.PC_UnreadLastTokenHandle = PC_UnreadLastTokenHandle; + + be_botlib_export.BotLibStartFrame = Export_BotLibStartFrame; + be_botlib_export.BotLibLoadMap = Export_BotLibLoadMap; + be_botlib_export.BotLibUpdateEntity = Export_BotLibUpdateEntity; + be_botlib_export.Test = BotExportTest; + + return &be_botlib_export; +} diff --git a/src/botlib/l_log.c b/src/botlib/l_log.c index 26014d7e4..76703110d 100644 --- a/src/botlib/l_log.c +++ b/src/botlib/l_log.c @@ -161,13 +161,13 @@ void QDECL Log_WriteTimeStamped(char *fmt, ...) { return; } -// fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", -// logfile.numwrites, -// (int) (botlibglobals.time / 60 / 60), -// (int) (botlibglobals.time / 60), -// (int) (botlibglobals.time), -// (int) ((int) (botlibglobals.time * 100)) - -// ((int) botlibglobals.time) * 100); + fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) (botlibglobals.time / 60 / 60), + (int) (botlibglobals.time / 60), + (int) (botlibglobals.time), + (int) ((int) (botlibglobals.time * 100)) - + ((int) botlibglobals.time) * 100); va_start(ap, fmt); vfprintf(logfile.fp, fmt, ap); va_end(ap); diff --git a/src/botlib/l_precomp.c b/src/botlib/l_precomp.c index 54a326afc..12992bcfa 100644 --- a/src/botlib/l_precomp.c +++ b/src/botlib/l_precomp.c @@ -3633,133 +3633,133 @@ void FreeSource(source_t *source) source_t *sourceFiles[MAX_SOURCEFILES]; -// int PC_LoadSourceHandle(const char *filename) -// { -// source_t *source; -// int i; -// -// for (i = 1; i < MAX_SOURCEFILES; i++) -// { -// if (!sourceFiles[i]) -// { -// break; -// } -// } //end for -// if (i >= MAX_SOURCEFILES) -// { -// return 0; -// } -// PS_SetBaseFolder(""); -// source = LoadSourceFile(filename); -// if (!source) -// { -// return 0; -// } -// sourceFiles[i] = source; -// return i; -// } //end of the function PC_LoadSourceHandle -// //============================================================================ -// // -// // Parameter: - -// // Returns: - -// // Changes Globals: - -// //============================================================================ -// int PC_FreeSourceHandle(int handle) -// { -// if (handle < 1 || handle >= MAX_SOURCEFILES) -// { -// return qfalse; -// } -// if (!sourceFiles[handle]) -// { -// return qfalse; -// } -// -// FreeSource(sourceFiles[handle]); -// sourceFiles[handle] = NULL; -// return qtrue; -// } //end of the function PC_FreeSourceHandle -// //============================================================================ -// // -// // Parameter: - -// // Returns: - -// // Changes Globals: - -// //============================================================================ -// int PC_ReadTokenHandle(int handle, pc_token_t *pc_token) -// { -// token_t token; -// int ret; -// -// if (handle < 1 || handle >= MAX_SOURCEFILES) -// { -// return 0; -// } -// if (!sourceFiles[handle]) -// { -// return 0; -// } -// -// ret = PC_ReadToken(sourceFiles[handle], &token); -// strcpy(pc_token->string, token.string); -// pc_token->type = token.type; -// pc_token->subtype = token.subtype; -// pc_token->intvalue = token.intvalue; -// pc_token->floatvalue = token.floatvalue; -// pc_token->line = token.line; -// pc_token->linescrossed = token.linescrossed; -// if (pc_token->type == TT_STRING) -// { -// StripDoubleQuotes(pc_token->string); -// } -// return ret; -// } //end of the function PC_ReadTokenHandle -// //============================================================================ -// // -// // Parameter: - -// // Returns: - -// // Changes Globals: - -// //============================================================================ -// void PC_UnreadLastTokenHandle(int handle) -// { -// if (handle < 1 || handle >= MAX_SOURCEFILES) -// { -// return; -// } -// if (!sourceFiles[handle]) -// { -// return; -// } -// -// PC_UnreadSourceToken(sourceFiles[handle], &sourceFiles[handle]->token); -// } //end of the function PC_UnreadLastTokenHandle -// //============================================================================ -// // -// // Parameter: - -// // Returns: - -// // Changes Globals: - -// //============================================================================ -// int PC_SourceFileAndLine(int handle, char *filename, int *line) -// { -// if (handle < 1 || handle >= MAX_SOURCEFILES) -// { -// return qfalse; -// } -// if (!sourceFiles[handle]) -// { -// return qfalse; -// } -// -// strcpy(filename, sourceFiles[handle]->filename); -// if (sourceFiles[handle]->scriptstack) -// { -// *line = sourceFiles[handle]->scriptstack->line; -// } -// else -// { -// *line = 0; -// } -// return qtrue; -// } //end of the function PC_SourceFileAndLine +int PC_LoadSourceHandle(const char *filename) +{ + source_t *source; + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (!sourceFiles[i]) + { + break; + } + } //end for + if (i >= MAX_SOURCEFILES) + { + return 0; + } + PS_SetBaseFolder(""); + source = LoadSourceFile(filename); + if (!source) + { + return 0; + } + sourceFiles[i] = source; + return i; +} //end of the function PC_LoadSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_FreeSourceHandle(int handle) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + { + return qfalse; + } + if (!sourceFiles[handle]) + { + return qfalse; + } + + FreeSource(sourceFiles[handle]); + sourceFiles[handle] = NULL; + return qtrue; +} //end of the function PC_FreeSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadTokenHandle(int handle, pc_token_t *pc_token) +{ + token_t token; + int ret; + + if (handle < 1 || handle >= MAX_SOURCEFILES) + { + return 0; + } + if (!sourceFiles[handle]) + { + return 0; + } + + ret = PC_ReadToken(sourceFiles[handle], &token); + strcpy(pc_token->string, token.string); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = token.floatvalue; + pc_token->line = token.line; + pc_token->linescrossed = token.linescrossed; + if (pc_token->type == TT_STRING) + { + StripDoubleQuotes(pc_token->string); + } + return ret; +} //end of the function PC_ReadTokenHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadLastTokenHandle(int handle) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + { + return; + } + if (!sourceFiles[handle]) + { + return; + } + + PC_UnreadSourceToken(sourceFiles[handle], &sourceFiles[handle]->token); +} //end of the function PC_UnreadLastTokenHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SourceFileAndLine(int handle, char *filename, int *line) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + { + return qfalse; + } + if (!sourceFiles[handle]) + { + return qfalse; + } + + strcpy(filename, sourceFiles[handle]->filename); + if (sourceFiles[handle]->scriptstack) + { + *line = sourceFiles[handle]->scriptstack->line; + } + else + { + *line = 0; + } + return qtrue; +} //end of the function PC_SourceFileAndLine //============================================================================ // // Parameter: -