summaryrefslogtreecommitdiff
path: root/src/botlib
diff options
context:
space:
mode:
Diffstat (limited to 'src/botlib')
-rw-r--r--src/botlib/aasfile.h267
-rw-r--r--src/botlib/be_aas.h221
-rw-r--r--src/botlib/be_aas_bsp.h89
-rw-r--r--src/botlib/be_aas_bspq3.c487
-rw-r--r--src/botlib/be_aas_cluster.c1545
-rw-r--r--src/botlib/be_aas_cluster.h38
-rw-r--r--src/botlib/be_aas_debug.c777
-rw-r--r--src/botlib/be_aas_debug.h62
-rw-r--r--src/botlib/be_aas_def.h306
-rw-r--r--src/botlib/be_aas_entity.c437
-rw-r--r--src/botlib/be_aas_entity.h63
-rw-r--r--src/botlib/be_aas_file.c582
-rw-r--r--src/botlib/be_aas_file.h42
-rw-r--r--src/botlib/be_aas_funcs.h47
-rw-r--r--src/botlib/be_aas_main.c429
-rw-r--r--src/botlib/be_aas_main.h61
-rw-r--r--src/botlib/be_aas_move.c1101
-rw-r--r--src/botlib/be_aas_move.h71
-rw-r--r--src/botlib/be_aas_optimize.c312
-rw-r--r--src/botlib/be_aas_optimize.h33
-rw-r--r--src/botlib/be_aas_reach.c4538
-rw-r--r--src/botlib/be_aas_reach.h68
-rw-r--r--src/botlib/be_aas_route.c2210
-rw-r--r--src/botlib/be_aas_route.h67
-rw-r--r--src/botlib/be_aas_routealt.c240
-rw-r--r--src/botlib/be_aas_routealt.h40
-rw-r--r--src/botlib/be_aas_sample.c1394
-rw-r--r--src/botlib/be_aas_sample.h69
-rw-r--r--src/botlib/be_ai_char.c790
-rw-r--r--src/botlib/be_ai_char.h48
-rw-r--r--src/botlib/be_ai_chat.c3029
-rw-r--r--src/botlib/be_ai_chat.h113
-rw-r--r--src/botlib/be_ai_gen.c134
-rw-r--r--src/botlib/be_ai_gen.h33
-rw-r--r--src/botlib/be_ai_goal.c1821
-rw-r--r--src/botlib/be_ai_goal.h118
-rw-r--r--src/botlib/be_ai_move.c3572
-rw-r--r--src/botlib/be_ai_move.h144
-rw-r--r--src/botlib/be_ai_weap.c543
-rw-r--r--src/botlib/be_ai_weap.h104
-rw-r--r--src/botlib/be_ai_weight.c912
-rw-r--r--src/botlib/be_ai_weight.h83
-rw-r--r--src/botlib/be_ea.c508
-rw-r--r--src/botlib/be_ea.h66
-rw-r--r--src/botlib/be_interface.c881
-rw-r--r--src/botlib/be_interface.h57
-rw-r--r--src/botlib/botlib.h516
-rw-r--r--src/botlib/l_crc.c151
-rw-r--r--src/botlib/l_crc.h29
-rw-r--r--src/botlib/l_libvar.c294
-rw-r--r--src/botlib/l_libvar.h63
-rw-r--r--src/botlib/l_log.c169
-rw-r--r--src/botlib/l_log.h46
-rw-r--r--src/botlib/l_memory.c463
-rw-r--r--src/botlib/l_memory.h76
-rw-r--r--src/botlib/l_precomp.c3233
-rw-r--r--src/botlib/l_precomp.h180
-rw-r--r--src/botlib/l_script.c1433
-rw-r--r--src/botlib/l_script.h247
-rw-r--r--src/botlib/l_struct.c462
-rw-r--r--src/botlib/l_struct.h75
-rw-r--r--src/botlib/l_utils.h37
62 files changed, 36026 insertions, 0 deletions
diff --git a/src/botlib/aasfile.h b/src/botlib/aasfile.h
new file mode 100644
index 00000000..8f2fbf62
--- /dev/null
+++ b/src/botlib/aasfile.h
@@ -0,0 +1,267 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+//NOTE: int = default signed
+// default long
+
+#define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E')
+#define AASVERSION_OLD 4
+#define AASVERSION 5
+
+//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 //face seperating two areas with liquid
+#define FACE_LIQUIDSURFACE 32 //face seperating liquid and air
+#define FACE_BRIDGE 64 //can walk over this face if bridge is closed
+
+//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
+#define AREACONTENTS_MOVER 1024
+#define AREACONTENTS_NOTTEAM1 2048
+#define AREACONTENTS_NOTTEAM2 4096
+//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
+#define AREA_DISABLED 8 //area is disabled for routing when set
+#define AREA_BRIDGE 16 //area ontop of a bridge
+
+//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 area
+ int areaflags; //several area flags
+ int presencetype; //how a bot can be present in this 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
+} 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 an area, often it will also seperate two 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; //area at the front of this face
+ int backarea; //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;
+
+//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 area
+ int firstface; //first face in the face index used for the boundary of the area
+ vec3_t mins; //mins of the area
+ vec3_t maxs; //maxs of the area
+ vec3_t center; //'center' of the 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 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 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
+ the cluster number set to the negative portal number
+- 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.h b/src/botlib/be_aas.h
new file mode 100644
index 00000000..dbf24bc1
--- /dev/null
+++ b/src/botlib/be_aas.h
@@ -0,0 +1,221 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: be_aas.h
+ *
+ * desc: Area Awareness System, stuff exported to the AI
+ *
+ * $Archive: /source/code/botlib/be_aas.h $
+ *
+ *****************************************************************************/
+
+#ifndef MAX_STRINGFIELD
+#define MAX_STRINGFIELD 80
+#endif
+
+//travel flags
+#define TFL_INVALID 0x00000001 //traveling temporary not possible
+#define TFL_WALK 0x00000002 //walking
+#define TFL_CROUCH 0x00000004 //crouching
+#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier
+#define TFL_JUMP 0x00000010 //jumping
+#define TFL_LADDER 0x00000020 //climbing a ladder
+#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge
+#define TFL_SWIM 0x00000100 //swimming
+#define TFL_WATERJUMP 0x00000200 //jumping out of the water
+#define TFL_TELEPORT 0x00000400 //teleporting
+#define TFL_ELEVATOR 0x00000800 //elevator
+#define TFL_ROCKETJUMP 0x00001000 //rocket jumping
+#define TFL_BFGJUMP 0x00002000 //bfg jumping
+#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook
+#define TFL_DOUBLEJUMP 0x00008000 //double jump
+#define TFL_RAMPJUMP 0x00010000 //ramp jump
+#define TFL_STRAFEJUMP 0x00020000 //strafe jump
+#define TFL_JUMPPAD 0x00040000 //jump pad
+#define TFL_AIR 0x00080000 //travel through air
+#define TFL_WATER 0x00100000 //travel through water
+#define TFL_SLIME 0x00200000 //travel through slime
+#define TFL_LAVA 0x00400000 //travel through lava
+#define TFL_DONOTENTER 0x00800000 //travel through donotenter area
+#define TFL_FUNCBOB 0x01000000 //func bobbing
+#define TFL_FLIGHT 0x02000000 //flight
+#define TFL_BRIDGE 0x04000000 //move over a bridge
+//
+#define TFL_NOTTEAM1 0x08000000 //not team 1
+#define TFL_NOTTEAM2 0x10000000 //not team 2
+
+//default travel flags
+#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\
+ TFL_JUMP|TFL_LADDER|\
+ TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\
+ TFL_TELEPORT|TFL_ELEVATOR|\
+ TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB
+
+typedef enum
+{
+ SOLID_NOT, // no interaction with other objects
+ SOLID_TRIGGER, // only touch when inside, after moving
+ SOLID_BBOX, // touch on edge
+ SOLID_BSP // bsp clip, touch on edge
+} solid_t;
+
+//a trace is returned when a box is swept through the AAS world
+typedef struct aas_trace_s
+{
+ qboolean startsolid; // if true, the initial point was in a solid area
+ float fraction; // time completed, 1.0 = didn't hit anything
+ vec3_t endpos; // final position
+ int ent; // entity blocking the trace
+ int lastarea; // last area the trace was in (zero if none)
+ int area; // area blocking the trace (zero if none)
+ int planenum; // number of the plane that was hit
+} aas_trace_t;
+
+/* Defined in botlib.h
+
+//bsp_trace_t hit surface
+typedef struct bsp_surface_s
+{
+ char name[16];
+ int flags;
+ int value;
+} bsp_surface_t;
+
+//a trace is returned when a box is swept through the BSP world
+typedef struct bsp_trace_s
+{
+ qboolean allsolid; // if true, plane is not valid
+ qboolean startsolid; // if true, the initial point was in a solid area
+ float fraction; // time completed, 1.0 = didn't hit anything
+ vec3_t endpos; // final position
+ cplane_t plane; // surface normal at impact
+ float exp_dist; // expanded plane distance
+ int sidenum; // number of the brush side hit
+ bsp_surface_t surface; // hit surface
+ int contents; // contents on other side of surface hit
+ int ent; // number of entity hit
+} bsp_trace_t;
+//
+*/
+
+//entity info
+typedef struct aas_entityinfo_s
+{
+ int valid; // true if updated this frame
+ int type; // entity type
+ int flags; // entity flags
+ float ltime; // local time
+ float update_time; // time between last and current update
+ int number; // number of the entity
+ vec3_t origin; // origin of the entity
+ vec3_t angles; // angles of the model
+ vec3_t old_origin; // for lerping
+ vec3_t lastvisorigin; // last visible origin
+ vec3_t mins; // bounding box minimums
+ vec3_t maxs; // bounding box maximums
+ int groundent; // ground entity
+ int solid; // solid type
+ int modelindex; // model used
+ int modelindex2; // weapons, CTF flags, etc
+ int frame; // model frame number
+ int event; // impulse events -- muzzle flashes, footsteps, etc
+ int eventParm; // even parameter
+ int powerups; // bit flags
+ int weapon; // determines weapon and flash model, etc
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+} aas_entityinfo_t;
+
+// area info
+typedef struct aas_areainfo_s
+{
+ int contents;
+ int flags;
+ int presencetype;
+ int cluster;
+ vec3_t mins;
+ vec3_t maxs;
+ vec3_t center;
+} aas_areainfo_t;
+
+// client movement prediction stop events, stop as soon as:
+#define SE_NONE 0
+#define SE_HITGROUND 1 // the ground is hit
+#define SE_LEAVEGROUND 2 // there's no ground
+#define SE_ENTERWATER 4 // water is entered
+#define SE_ENTERSLIME 8 // slime is entered
+#define SE_ENTERLAVA 16 // lava is entered
+#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage
+#define SE_GAP 64 // there's a gap
+#define SE_TOUCHJUMPPAD 128 // touching a jump pad area
+#define SE_TOUCHTELEPORTER 256 // touching teleporter
+#define SE_ENTERAREA 512 // the given stoparea is entered
+#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit
+#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box
+#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal
+
+typedef struct aas_clientmove_s
+{
+ vec3_t endpos; //position at the end of movement prediction
+ int endarea; //area at end of movement prediction
+ vec3_t velocity; //velocity at the end of movement prediction
+ aas_trace_t trace; //last trace
+ int presencetype; //presence type at end of movement prediction
+ int stopevent; //event that made the prediction stop
+ int endcontents; //contents at the end of movement prediction
+ float time; //time predicted ahead
+ int frames; //number of frames predicted ahead
+} aas_clientmove_t;
+
+// alternate route goals
+#define ALTROUTEGOAL_ALL 1
+#define ALTROUTEGOAL_CLUSTERPORTALS 2
+#define ALTROUTEGOAL_VIEWPORTALS 4
+
+typedef struct aas_altroutegoal_s
+{
+ vec3_t origin;
+ int areanum;
+ unsigned short starttraveltime;
+ unsigned short goaltraveltime;
+ unsigned short extratraveltime;
+} aas_altroutegoal_t;
+
+// route prediction stop events
+#define RSE_NONE 0
+#define RSE_NOROUTE 1 //no route to goal
+#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used
+#define RSE_ENTERCONTENTS 4 //stop when entering the given contents
+#define RSE_ENTERAREA 8 //stop when entering the given area
+
+typedef struct aas_predictroute_s
+{
+ vec3_t endpos; //position at the end of movement prediction
+ int endarea; //area at end of movement prediction
+ int stopevent; //event that made the prediction stop
+ int endcontents; //contents at the end of movement prediction
+ int endtravelflags; //end travel flags
+ int numareas; //number of areas predicted ahead
+ int time; //time predicted ahead (in hundreth of a sec)
+} aas_predictroute_t;
diff --git a/src/botlib/be_aas_bsp.h b/src/botlib/be_aas_bsp.h
new file mode 100644
index 00000000..932874a1
--- /dev/null
+++ b/src/botlib/be_aas_bsp.h
@@ -0,0 +1,89 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_bsp.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/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 00000000..9bfc824f
--- /dev/null
+++ b/src/botlib/be_aas_bspq3.c
@@ -0,0 +1,487 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_bspq3.c
+ *
+ * desc: BSP, Environment Sampling
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_bspq3.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 "botlib.h"
+#include "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 2048
+
+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];
+} 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)
+ {
+ Com_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)
+{
+ 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
+ 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;
+
+ script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata");
+ SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES|SCFL_NOSTRINGESCAPECHARS);//SCFL_PRIMITIVE);
+
+ bspworld.numentities = 1;
+
+ 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
+ ent = &bspworld.entities[bspworld.numentities];
+ bspworld.numentities++;
+ ent->epairs = NULL;
+ while(PS_ReadToken(script, &token))
+ {
+ if (!strcmp(token.string, "}")) break;
+ epair = (bsp_epair_t *) GetClearedHunkMemory(sizeof(bsp_epair_t));
+ epair->next = ent->epairs;
+ ent->epairs = epair;
+ if (token.type != TT_STRING)
+ {
+ ScriptError(script, "invalid %s\n", token.string);
+ AAS_FreeBSPEntities();
+ FreeScript(script);
+ return;
+ } //end if
+ StripDoubleQuotes(token.string);
+ epair->key = (char *) GetHunkMemory(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 *) GetHunkMemory(strlen(token.string) + 1);
+ strcpy(epair->value, token.string);
+ } //end while
+ if (strcmp(token.string, "}"))
+ {
+ ScriptError(script, "missing }\n");
+ AAS_FreeBSPEntities();
+ FreeScript(script);
+ return;
+ } //end if
+ } //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;
+ Com_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);
+ Com_Memcpy(bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize);
+ AAS_ParseBSPEntities();
+ bspworld.loaded = qtrue;
+ return BLERR_NOERROR;
+} //end of the function AAS_LoadBSPFile
diff --git a/src/botlib/be_aas_cluster.c b/src/botlib/be_aas_cluster.c
new file mode 100644
index 00000000..6d577fa8
--- /dev/null
+++ b/src/botlib/be_aas_cluster.c
@@ -0,0 +1,1545 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_cluster.c
+ *
+ * desc: area clustering
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_cluster.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "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
+ {
+ //remove the cluster portal flag contents
+ aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL;
+ Log_Write("portal area %d is seperating more than two clusters\r\n", areanum);
+ 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
+ } //end if
+ //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;
+ 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;
+
+ Com_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;
+ //
+ Com_Memset(numareafrontfaces, 0, sizeof(numareafrontfaces));
+ Com_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 portal areas\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
+
+#if 0
+//===========================================================================
+//
+// 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
+
+#endif
+
+//===========================================================================
+//
+// 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)
+ {
+ Log_Write("area %d is a forced portal area\r\n", i);
+ num++;
+ } //end if
+ } //end for
+ botimport.Print(PRT_MESSAGE, "%6d forced portal areas\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 clustering isn't forced
+ if (!((int)LibVarGetValue("forceclustering")) &&
+ !((int)LibVarGetValue("forcereachability"))) return;
+#endif
+ } //end if
+ //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach)
+ AAS_SetViewPortalsAsClusterPortals();
+ //count the number of forced cluster portals
+ AAS_CountForcedClusterPortals();
+ //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));
+ //
+ 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;
+ //write the portal areas to the log file
+ for (i = 1; i < aasworld.numportals; i++)
+ {
+ Log_Write("portal %d: area %d\r\n", i, aasworld.portals[i].areanum);
+ } //end for
+ // 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 00000000..e36697d1
--- /dev/null
+++ b/src/botlib/be_aas_cluster.h
@@ -0,0 +1,38 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_cluster.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_cluster.h $
+ *
+ *****************************************************************************/
+
+#ifdef AASINTERN
+//initialize the AAS clustering
+void AAS_InitClustering(void);
+//
+void AAS_SetViewPortalsAsClusterPortals(void);
+#endif //AASINTERN
+
diff --git a/src/botlib/be_aas_debug.c b/src/botlib/be_aas_debug.c
new file mode 100644
index 00000000..ab44bc0b
--- /dev/null
+++ b/src/botlib/be_aas_debug.c
@@ -0,0 +1,777 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_debug.c
+ *
+ * desc: AAS debug code
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_debug.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_interface.h"
+#include "be_aas_funcs.h"
+#include "be_aas_def.h"
+
+#define MAX_DEBUGLINES 1024
+#define MAX_DEBUGPOLYGONS 8192
+
+int debuglines[MAX_DEBUGLINES];
+int debuglinevisible[MAX_DEBUGLINES];
+int numdebuglines;
+
+static int debugpolygons[MAX_DEBUGPOLYGONS];
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_ClearShownPolygons(void)
+{
+ int i;
+//*
+ for (i = 0; i < MAX_DEBUGPOLYGONS; i++)
+ {
+ if (debugpolygons[i]) botimport.DebugPolygonDelete(debugpolygons[i]);
+ debugpolygons[i] = 0;
+ } //end for
+//*/
+/*
+ for (i = 0; i < MAX_DEBUGPOLYGONS; i++)
+ {
+ botimport.DebugPolygonDelete(i);
+ debugpolygons[i] = 0;
+ } //end for
+*/
+} //end of the function AAS_ClearShownPolygons
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_ShowPolygon(int color, int numpoints, vec3_t *points)
+{
+ int i;
+
+ for (i = 0; i < MAX_DEBUGPOLYGONS; i++)
+ {
+ if (!debugpolygons[i])
+ {
+ debugpolygons[i] = botimport.DebugPolygonCreate(color, numpoints, points);
+ break;
+ } //end if
+ } //end for
+} //end of the function AAS_ShowPolygon
+//===========================================================================
+//
+// 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
+ Com_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, numpoints;
+ vec3_t points[128];
+ aas_edge_t *edge;
+ aas_face_t *face;
+
+ //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
+ numpoints = 0;
+ if (flip)
+ {
+ for (i = face->numedges-1; i >= 0; i--)
+ {
+ //edge number
+ edgenum = aasworld.edgeindex[face->firstedge + i];
+ edge = &aasworld.edges[abs(edgenum)];
+ VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]);
+ numpoints++;
+ } //end for
+ } //end if
+ else
+ {
+ for (i = 0; i < face->numedges; i++)
+ {
+ //edge number
+ edgenum = aasworld.edgeindex[face->firstedge + i];
+ edge = &aasworld.edges[abs(edgenum)];
+ VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]);
+ numpoints++;
+ } //end for
+ } //end else
+ AAS_ShowPolygon(color, numpoints, 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;
+ } //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
+ AAS_ShowFacePolygon(facenum, color, face->frontarea != areanum);
+ } //end for
+} //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 & TRAVELTYPE_MASK)
+ {
+ 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
+} //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 & TRAVELTYPE_MASK) == TRAVEL_JUMP ||
+ (reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE)
+ {
+ AAS_HorizontalVelocityForJump(aassettings.phys_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.phys_jumpvel;
+ //
+ AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue,
+ velocity, cmdmove, 3, 30, 0.1f,
+ SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|
+ SE_ENTERLAVA|SE_HITGROUNDDAMAGE, 0, qtrue);
+ //
+ if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP)
+ {
+ AAS_JumpReachRunStart(reach, dir);
+ AAS_DrawCross(dir, 4, LINECOLOR_BLUE);
+ } //end if
+ } //end if
+ else if ((reach->traveltype & TRAVELTYPE_MASK) == 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.1f,
+ SE_ENTERWATER|SE_ENTERSLIME|
+ SE_ENTERLAVA|SE_HITGROUNDDAMAGE|
+ SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue);
+ } //end else if
+ else if ((reach->traveltype & TRAVELTYPE_MASK) == 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.1f,
+ 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)
+ {
+ Com_Memcpy(&reach, &aasworld.reachability[settings->firstreachablearea + index], sizeof(aas_reachability_t));
+ index++;
+ lasttime = AAS_Time();
+ AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
+ botimport.Print(PRT_MESSAGE, "\n");
+ } //end if
+ AAS_ShowReachability(&reach);
+} //end of the function ShowReachableAreas
+
+void AAS_FloodAreas_r(int areanum, int cluster, int *done)
+{
+ int nextareanum, i, facenum;
+ aas_area_t *area;
+ aas_face_t *face;
+ aas_areasettings_t *settings;
+ aas_reachability_t *reach;
+
+ AAS_ShowAreaPolygons(areanum, 1, qtrue);
+ //pointer to the convex area
+ area = &aasworld.areas[areanum];
+ settings = &aasworld.areasettings[areanum];
+ //walk through the faces of the area
+ for (i = 0; i < area->numfaces; i++)
+ {
+ facenum = abs(aasworld.faceindex[area->firstface + i]);
+ face = &aasworld.faces[facenum];
+ if (face->frontarea == areanum)
+ nextareanum = face->backarea;
+ else
+ nextareanum = face->frontarea;
+ if (!nextareanum)
+ continue;
+ if (done[nextareanum])
+ continue;
+ done[nextareanum] = qtrue;
+ if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL)
+ continue;
+ if (AAS_AreaCluster(nextareanum) != cluster)
+ continue;
+ AAS_FloodAreas_r(nextareanum, cluster, done);
+ } //end for
+ //
+ for (i = 0; i < settings->numreachableareas; i++)
+ {
+ reach = &aasworld.reachability[settings->firstreachablearea + i];
+ nextareanum = reach->areanum;
+ if (!nextareanum)
+ continue;
+ if (done[nextareanum])
+ continue;
+ done[nextareanum] = qtrue;
+ if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL)
+ continue;
+ if (AAS_AreaCluster(nextareanum) != cluster)
+ continue;
+ /*
+ if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE)
+ {
+ AAS_DebugLine(reach->start, reach->end, 1);
+ }
+ */
+ AAS_FloodAreas_r(nextareanum, cluster, done);
+ }
+}
+
+void AAS_FloodAreas(vec3_t origin)
+{
+ int areanum, cluster, *done;
+
+ done = (int *) GetClearedMemory(aasworld.numareas * sizeof(int));
+ areanum = AAS_PointAreaNum(origin);
+ cluster = AAS_AreaCluster(areanum);
+ AAS_FloodAreas_r(areanum, cluster, done);
+}
diff --git a/src/botlib/be_aas_debug.h b/src/botlib/be_aas_debug.h
new file mode 100644
index 00000000..008eeba6
--- /dev/null
+++ b/src/botlib/be_aas_debug.h
@@ -0,0 +1,62 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_debug.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_debug.h $
+ *
+ *****************************************************************************/
+
+//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 00000000..eb995d6c
--- /dev/null
+++ b/src/botlib/be_aas_def.h
@@ -0,0 +1,306 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_def.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_def.h $
+ *
+ *****************************************************************************/
+
+//debugging on
+#define AAS_DEBUG
+
+#define MAX_CLIENTS 64
+#define MAX_MODELS 256 // these are sent over the net as 8 bits
+#define MAX_SOUNDS 256 // so they cannot be blindly increased
+#define MAX_CONFIGSTRINGS 1024
+
+#define CS_SCORES 32
+#define CS_MODELS (CS_SCORES+MAX_CLIENTS)
+#define CS_SOUNDS (CS_MODELS+MAX_MODELS)
+
+#define DF_AASENTNUMBER(x) (x - aasworld.entities)
+#define DF_NUMBERAASENT(x) (&aasworld.entities[x])
+#define DF_AASENTCLIENT(x) (x - aasworld.entities - 1)
+#define DF_CLIENTAASENT(x) (&aasworld.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
+{
+ vec3_t phys_gravitydirection;
+ float phys_friction;
+ float phys_stopspeed;
+ float phys_gravity;
+ float phys_waterfriction;
+ float phys_watergravity;
+ float phys_maxvelocity;
+ float phys_maxwalkvelocity;
+ float phys_maxcrouchvelocity;
+ float phys_maxswimvelocity;
+ float phys_walkaccelerate;
+ float phys_airaccelerate;
+ float phys_swimaccelerate;
+ float phys_maxstep;
+ float phys_maxsteepness;
+ float phys_maxwaterjump;
+ float phys_maxbarrier;
+ float phys_jumpvel;
+ float phys_falldelta5;
+ float phys_falldelta10;
+ float rs_waterjump;
+ float rs_teleport;
+ float rs_barrierjump;
+ float rs_startcrouch;
+ float rs_startgrapple;
+ float rs_startwalkoffledge;
+ float rs_startjump;
+ float rs_rocketjump;
+ float rs_bfgjump;
+ float rs_jumppad;
+ float rs_aircontrolledjumppad;
+ float rs_funcbob;
+ float rs_startelevator;
+ float rs_falldamage5;
+ float rs_falldamage10;
+ float rs_maxfallheight;
+ float rs_maxjumpfallheight;
+} aas_settings_t;
+
+#define CACHETYPE_PORTAL 0
+#define CACHETYPE_AREA 1
+
+//routing cache
+typedef struct aas_routingcache_s
+{
+ byte type; //portal or area cache
+ float time; //last time accessed or updated
+ int size; //size of the routing cache
+ 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;
+ struct aas_routingcache_s *time_prev, *time_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;
+
+//areas a reachability goes through
+typedef struct aas_reachabilityareas_s
+{
+ int firstarea, numareas;
+} aas_reachabilityareas_t;
+
+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 numreachabilityareas;
+ 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];
+ //travel flags for each area based on contents
+ int *areacontentstravelflags;
+ //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;
+ //cache list sorted on time
+ aas_routingcache_t *oldestcache; // start of cache list sorted on time
+ aas_routingcache_t *newestcache; // end of cache list sorted on time
+ //maximum travel time through portal areas
+ int *portalmaxtraveltimes;
+ //areas the reachabilities go through
+ int *reachabilityareaindex;
+ aas_reachabilityareas_t *reachabilityareas;
+} 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"
+
+#endif //BSPCINCLUDE
diff --git a/src/botlib/be_aas_entity.c b/src/botlib/be_aas_entity.c
new file mode 100644
index 00000000..02699bde
--- /dev/null
+++ b/src/botlib/be_aas_entity.c
@@ -0,0 +1,437 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_entity.c
+ *
+ * desc: AAS entities
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_entity.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "be_aas_def.h"
+
+#define MASK_SOLID CONTENTS_PLAYERCLIP
+
+//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 (!aasworld.loaded)
+ {
+ botimport.Print(PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n");
+ return BLERR_NOAASFILE;
+ } //end if
+
+ ent = &aasworld.entities[entnum];
+
+ if (!state) {
+ //unlink the entity
+ AAS_UnlinkFromAreas(ent->areas);
+ //unlink the entity from the BSP leaves
+ AAS_UnlinkFromBSPLeaves(ent->leaves);
+ //
+ ent->areas = NULL;
+ //
+ ent->leaves = NULL;
+ return BLERR_NOERROR;
+ }
+
+ 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;
+ //number of the entity
+ ent->i.number = entnum;
+ //updated so set valid flag
+ ent->i.valid = qtrue;
+ //link everything the first frame
+ if (aasworld.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
+ AAS_BSPModelMinsMaxsOrigin(ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL);
+ } //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
+ VectorCopy(state->angles, ent->i.angles);
+ } //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)
+{
+ if (!aasworld.initialized)
+ {
+ botimport.Print(PRT_FATAL, "AAS_EntityInfo: aasworld not initialized\n");
+ Com_Memset(info, 0, sizeof(aas_entityinfo_t));
+ return;
+ } //end if
+
+ if (entnum < 0 || entnum >= aasworld.maxentities)
+ {
+ botimport.Print(PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum);
+ Com_Memset(info, 0, sizeof(aas_entityinfo_t));
+ return;
+ } //end if
+
+ Com_Memcpy(info, &aasworld.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 >= aasworld.maxentities)
+ {
+ botimport.Print(PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum);
+ VectorClear(origin);
+ return;
+ } //end if
+
+ VectorCopy(aasworld.entities[entnum].i.origin, origin);
+} //end of the function AAS_EntityOrigin
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_EntityModelindex(int entnum)
+{
+ if (entnum < 0 || entnum >= aasworld.maxentities)
+ {
+ botimport.Print(PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum);
+ return 0;
+ } //end if
+ return aasworld.entities[entnum].i.modelindex;
+} //end of the function AAS_EntityModelindex
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_EntityType(int entnum)
+{
+ if (!aasworld.initialized) return 0;
+
+ if (entnum < 0 || entnum >= aasworld.maxentities)
+ {
+ botimport.Print(PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum);
+ return 0;
+ } //end if
+ return aasworld.entities[entnum].i.type;
+} //end of the AAS_EntityType
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_EntityModelNum(int entnum)
+{
+ if (!aasworld.initialized) return 0;
+
+ if (entnum < 0 || entnum >= aasworld.maxentities)
+ {
+ botimport.Print(PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum);
+ return 0;
+ } //end if
+ return aasworld.entities[entnum].i.modelindex;
+} //end of the function AAS_EntityModelNum
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin)
+{
+ int i;
+ aas_entity_t *ent;
+
+ for (i = 0; i < aasworld.maxentities; i++)
+ {
+ ent = &aasworld.entities[i];
+ if (ent->i.type == ET_MOVER)
+ {
+ if (ent->i.modelindex == modelnum)
+ {
+ VectorCopy(ent->i.origin, origin);
+ return qtrue;
+ } //end if
+ } //end if
+ } //end for
+ return qfalse;
+} //end of the function AAS_OriginOfMoverWithModelNum
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs)
+{
+ aas_entity_t *ent;
+
+ if (!aasworld.initialized) return;
+
+ if (entnum < 0 || entnum >= aasworld.maxentities)
+ {
+ botimport.Print(PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum);
+ return;
+ } //end if
+
+ ent = &aasworld.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 = &aasworld.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 < aasworld.maxentities; i++)
+ {
+ aasworld.entities[i].areas = NULL;
+ aasworld.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 < aasworld.maxentities; i++)
+ {
+ aasworld.entities[i].i.valid = qfalse;
+ aasworld.entities[i].i.number = i;
+ } //end for
+} //end of the function AAS_InvalidateEntities
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_UnlinkInvalidEntities(void)
+{
+ int i;
+ aas_entity_t *ent;
+
+ for (i = 0; i < aasworld.maxentities; i++)
+ {
+ ent = &aasworld.entities[i];
+ if (!ent->i.valid)
+ {
+ AAS_UnlinkFromAreas( ent->areas );
+ ent->areas = NULL;
+ AAS_UnlinkFromBSPLeaves( ent->leaves );
+ ent->leaves = NULL;
+ } //end for
+ } //end for
+} //end of the function AAS_UnlinkInvalidEntities
+//===========================================================================
+//
+// 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 < aasworld.maxentities; i++)
+ {
+ ent = &aasworld.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 = &aasworld.entities[entnum];
+ return AAS_BestReachableLinkArea(ent->areas);
+} //end of the function AAS_BestReachableEntityArea
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_NextEntity(int entnum)
+{
+ if (!aasworld.loaded) return 0;
+
+ if (entnum < 0) entnum = -1;
+ while(++entnum < aasworld.maxentities)
+ {
+ if (aasworld.entities[entnum].i.valid) return entnum;
+ } //end while
+ return 0;
+} //end of the function AAS_NextEntity
diff --git a/src/botlib/be_aas_entity.h b/src/botlib/be_aas_entity.h
new file mode 100644
index 00000000..01ec54c3
--- /dev/null
+++ b/src/botlib/be_aas_entity.h
@@ -0,0 +1,63 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_entity.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_entity.h $
+ *
+ *****************************************************************************/
+
+#ifdef AASINTERN
+//invalidates all entity infos
+void AAS_InvalidateEntities(void);
+//unlink not updated entities
+void AAS_UnlinkInvalidEntities(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_OriginOfMoverWithModelNum(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);
+
diff --git a/src/botlib/be_aas_file.c b/src/botlib/be_aas_file.c
new file mode 100644
index 00000000..9395b1d3
--- /dev/null
+++ b/src/botlib/be_aas_file.c
@@ -0,0 +1,582 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_file.c
+ *
+ * desc: AAS file loading/writing
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_file.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "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;
+ //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);
+ } //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, int size)
+{
+ char *buf;
+ //
+ if (!length)
+ {
+ //just alloc a dummy
+ return (char *) GetClearedHunkMemory(size+1);
+ } //end if
+ //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 NULL;
+ } //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;
+
+ 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_OLD && 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
+ //
+ if (header.version == AASVERSION)
+ {
+ AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8);
+ } //end if
+ //
+ aasworld.bspchecksum = atoi(LibVarGetString( "sv_mapChecksum"));
+ if (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, sizeof(aas_bbox_t));
+ 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, sizeof(aas_vertex_t));
+ 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, sizeof(aas_plane_t));
+ 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, sizeof(aas_edge_t));
+ 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, sizeof(aas_edgeindex_t));
+ 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, sizeof(aas_face_t));
+ 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, sizeof(aas_faceindex_t));
+ aasworld.faceindexsize = length / sizeof(aas_faceindex_t);
+ 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, sizeof(aas_area_t));
+ 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, sizeof(aas_areasettings_t));
+ 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, sizeof(aas_reachability_t));
+ 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, sizeof(aas_node_t));
+ 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, sizeof(aas_portal_t));
+ 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, sizeof(aas_portalindex_t));
+ 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, sizeof(aas_cluster_t));
+ 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
+ //
+ 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
+ Com_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);
+ AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8);
+ 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 00000000..c2271fcf
--- /dev/null
+++ b/src/botlib/be_aas_file.h
@@ -0,0 +1,42 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_file.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_file.h $
+ *
+ *****************************************************************************/
+
+#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 00000000..87c7636f
--- /dev/null
+++ b/src/botlib/be_aas_funcs.h
@@ -0,0 +1,47 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_funcs.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/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"
+
+#endif //BSPCINCLUDE
diff --git a/src/botlib/be_aas_main.c b/src/botlib/be_aas_main.c
new file mode 100644
index 00000000..c07eb886
--- /dev/null
+++ b/src/botlib/be_aas_main.c
@@ -0,0 +1,429 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_main.c
+ *
+ * desc: AAS
+ *
+ * $Archive: /MissionPack/code/botlib/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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "be_aas_def.h"
+
+aas_t aasworld;
+
+libvar_t *saveroutingcache;
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void QDECL AAS_Error(char *fmt, ...)
+{
+ char str[1024];
+ va_list arglist;
+
+ va_start(arglist, fmt);
+ vsprintf(str, fmt, arglist);
+ va_end(arglist);
+ botimport.Print(PRT_FATAL, str);
+} //end of the function AAS_Error
+//===========================================================================
+//
+// 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);
+} //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);
+} //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
+} //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("aasoptimize", "0")) AAS_Optimize();
+ //save the AAS file
+ if (AAS_WriteAASFile(aasworld.filename))
+ {
+ botimport.Print(PRT_MESSAGE, "%s written succesfully\n", aasworld.filename);
+ } //end if
+ 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)
+{
+ aasworld.time = time;
+ //unlink all entities that were not updated last frame
+ AAS_UnlinkInvalidEntities();
+ //invalidate the entities
+ AAS_InvalidateEntities();
+ //initialize AAS
+ AAS_ContinueInit(time);
+ //
+ aasworld.frameroutingupdates = 0;
+ //
+ if (bot_developer)
+ {
+ 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
+ //
+ if (saveroutingcache->value)
+ {
+ AAS_WriteRouteCache();
+ LibVarSet("saveroutingcache", "0");
+ } //end if
+ //
+ aasworld.numframes++;
+ return BLERR_NOERROR;
+} //end of the function AAS_StartFrame
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+float AAS_Time(void)
+{
+ return aasworld.time;
+} //end of the function AAS_Time
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj )
+{
+ vec3_t pVec, vec;
+
+ VectorSubtract( point, vStart, pVec );
+ VectorSubtract( vEnd, vStart, vec );
+ VectorNormalize( vec );
+ // project onto the directional vector for this segment
+ VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj );
+} //end of the function AAS_ProjectPointOntoVector
+//===========================================================================
+//
+// 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: -
+//===========================================================================
+int AAS_LoadMap(const char *mapname)
+{
+ int errnum;
+
+ //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(mapname);
+ if (errnum != BLERR_NOERROR)
+ {
+ aasworld.loaded = qfalse;
+ return errnum;
+ } //end if
+ //
+ 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();
+ //everything went ok
+ return 0;
+} //end of the function AAS_LoadMap
+//===========================================================================
+// called when the library is first loaded
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_Setup(void)
+{
+ aasworld.maxclients = (int) LibVarValue("maxclients", "128");
+ aasworld.maxentities = (int) LibVarValue("maxentities", "1024");
+ // as soon as it's set to 1 the routing cache will be saved
+ saveroutingcache = LibVar("saveroutingcache", "0");
+ //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)
+{
+ 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();
+ //free the entities
+ if (aasworld.entities) FreeMemory(aasworld.entities);
+ //clear the aasworld structure
+ Com_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 00000000..1d0bef10
--- /dev/null
+++ b/src/botlib/be_aas_main.h
@@ -0,0 +1,61 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_main.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/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);
+//
+void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj );
diff --git a/src/botlib/be_aas_move.c b/src/botlib/be_aas_move.c
new file mode 100644
index 00000000..fded262d
--- /dev/null
+++ b/src/botlib/be_aas_move.c
@@ -0,0 +1,1101 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_move.c
+ *
+ * desc: AAS
+ *
+ * $Archive: /MissionPack/code/botlib/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 "l_libvar.h"
+#include "aasfile.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_aas_def.h"
+
+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);
+ 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.phys_gravitydirection[0] = 0;
+ aassettings.phys_gravitydirection[1] = 0;
+ aassettings.phys_gravitydirection[2] = -1;
+ aassettings.phys_friction = LibVarValue("phys_friction", "6");
+ aassettings.phys_stopspeed = LibVarValue("phys_stopspeed", "100");
+ aassettings.phys_gravity = LibVarValue("phys_gravity", "800");
+ aassettings.phys_waterfriction = LibVarValue("phys_waterfriction", "1");
+ aassettings.phys_watergravity = LibVarValue("phys_watergravity", "400");
+ aassettings.phys_maxvelocity = LibVarValue("phys_maxvelocity", "320");
+ aassettings.phys_maxwalkvelocity = LibVarValue("phys_maxwalkvelocity", "320");
+ aassettings.phys_maxcrouchvelocity = LibVarValue("phys_maxcrouchvelocity", "100");
+ aassettings.phys_maxswimvelocity = LibVarValue("phys_maxswimvelocity", "150");
+ aassettings.phys_walkaccelerate = LibVarValue("phys_walkaccelerate", "10");
+ aassettings.phys_airaccelerate = LibVarValue("phys_airaccelerate", "1");
+ aassettings.phys_swimaccelerate = LibVarValue("phys_swimaccelerate", "4");
+ aassettings.phys_maxstep = LibVarValue("phys_maxstep", "19");
+ aassettings.phys_maxsteepness = LibVarValue("phys_maxsteepness", "0.7");
+ aassettings.phys_maxwaterjump = LibVarValue("phys_maxwaterjump", "18");
+ aassettings.phys_maxbarrier = LibVarValue("phys_maxbarrier", "33");
+ aassettings.phys_jumpvel = LibVarValue("phys_jumpvel", "270");
+ aassettings.phys_falldelta5 = LibVarValue("phys_falldelta5", "40");
+ aassettings.phys_falldelta10 = LibVarValue("phys_falldelta10", "60");
+ aassettings.rs_waterjump = LibVarValue("rs_waterjump", "400");
+ aassettings.rs_teleport = LibVarValue("rs_teleport", "50");
+ aassettings.rs_barrierjump = LibVarValue("rs_barrierjump", "100");
+ aassettings.rs_startcrouch = LibVarValue("rs_startcrouch", "300");
+ aassettings.rs_startgrapple = LibVarValue("rs_startgrapple", "500");
+ aassettings.rs_startwalkoffledge = LibVarValue("rs_startwalkoffledge", "70");
+ aassettings.rs_startjump = LibVarValue("rs_startjump", "300");
+ aassettings.rs_rocketjump = LibVarValue("rs_rocketjump", "500");
+ aassettings.rs_bfgjump = LibVarValue("rs_bfgjump", "500");
+ aassettings.rs_jumppad = LibVarValue("rs_jumppad", "250");
+ aassettings.rs_aircontrolledjumppad = LibVarValue("rs_aircontrolledjumppad", "300");
+ aassettings.rs_funcbob = LibVarValue("rs_funcbob", "300");
+ aassettings.rs_startelevator = LibVarValue("rs_startelevator", "50");
+ aassettings.rs_falldamage5 = LibVarValue("rs_falldamage5", "300");
+ aassettings.rs_falldamage10 = LibVarValue("rs_falldamage10", "500");
+ aassettings.rs_maxfallheight = LibVarValue("rs_maxfallheight", "0");
+ aassettings.rs_maxjumpfallheight = LibVarValue("rs_maxjumpfallheight", "450");
+} //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 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;
+ //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) < 3)
+ {
+ if (AAS_PointInsideFace(abs(facenum), origin, 0.1f)) 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;
+ vec3_t end, up = {0, 0, 1};
+ aas_plane_t *plane;
+
+ VectorCopy(origin, end);
+ end[2] -= 10;
+
+ trace = AAS_TraceClientBBox(origin, end, presencetype, passent);
+
+ //if in solid
+ if (trace.startsolid) return 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(plane->normal, up) < aassettings.phys_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: -
+//===========================================================================
+static vec3_t VEC_UP = {0, -1, 0};
+static vec3_t MOVEDIR_UP = {0, 0, 1};
+static vec3_t VEC_DOWN = {0, -2, 0};
+static 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.1f,
+ 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_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.phys_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
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_ClipToBBox(aas_trace_t *trace, vec3_t start, vec3_t end, int presencetype, vec3_t mins, vec3_t maxs)
+{
+ int i, j, side;
+ float front, back, frac, planedist;
+ vec3_t bboxmins, bboxmaxs, absmins, absmaxs, dir, mid;
+
+ AAS_PresenceTypeBoundingBox(presencetype, bboxmins, bboxmaxs);
+ VectorSubtract(mins, bboxmaxs, absmins);
+ VectorSubtract(maxs, bboxmins, absmaxs);
+ //
+ VectorCopy(end, trace->endpos);
+ trace->fraction = 1;
+ for (i = 0; i < 3; i++)
+ {
+ if (start[i] < absmins[i] && end[i] < absmins[i]) return qfalse;
+ if (start[i] > absmaxs[i] && end[i] > absmaxs[i]) return qfalse;
+ } //end for
+ //check bounding box collision
+ VectorSubtract(end, start, dir);
+ frac = 1;
+ for (i = 0; i < 3; i++)
+ {
+ //get plane to test collision with for the current axis direction
+ if (dir[i] > 0) planedist = absmins[i];
+ else planedist = absmaxs[i];
+ //calculate collision fraction
+ front = start[i] - planedist;
+ back = end[i] - planedist;
+ frac = front / (front-back);
+ //check if between bounding planes of next axis
+ side = i + 1;
+ if (side > 2) side = 0;
+ mid[side] = start[side] + dir[side] * frac;
+ if (mid[side] > absmins[side] && mid[side] < absmaxs[side])
+ {
+ //check if between bounding planes of next axis
+ side++;
+ if (side > 2) side = 0;
+ mid[side] = start[side] + dir[side] * frac;
+ if (mid[side] > absmins[side] && mid[side] < absmaxs[side])
+ {
+ mid[i] = planedist;
+ break;
+ } //end if
+ } //end if
+ } //end for
+ //if there was a collision
+ if (i != 3)
+ {
+ trace->startsolid = qfalse;
+ trace->fraction = frac;
+ trace->ent = 0;
+ trace->planenum = 0;
+ trace->area = 0;
+ trace->lastarea = 0;
+ //trace endpos
+ for (j = 0; j < 3; j++) trace->endpos[j] = start[j] + dir[j] * frac;
+ return qtrue;
+ } //end if
+ return qfalse;
+} //end of the function AAS_ClipToBBox
+//===========================================================================
+// 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_ClientMovementPrediction(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,
+ vec3_t mins, vec3_t maxs, int visualize)
+{
+ float phys_friction, phys_stopspeed, phys_gravity, phys_waterfriction;
+ float phys_watergravity;
+ float phys_walkaccelerate, phys_airaccelerate, phys_swimaccelerate;
+ float phys_maxwalkvelocity, phys_maxcrouchvelocity, phys_maxswimvelocity;
+ float phys_maxstep, phys_maxsteepness, phys_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];
+ vec3_t org, end, feet, start, stepend, lastorg, wishdir;
+ vec3_t frame_test_vel, old_frame_test_vel, left_test_vel;
+ vec3_t up = {0, 0, 1};
+ aas_plane_t *plane, *plane2;
+ aas_trace_t trace, steptrace;
+
+ if (frametime <= 0) frametime = 0.1f;
+ //
+ phys_friction = aassettings.phys_friction;
+ phys_stopspeed = aassettings.phys_stopspeed;
+ phys_gravity = aassettings.phys_gravity;
+ phys_waterfriction = aassettings.phys_waterfriction;
+ phys_watergravity = aassettings.phys_watergravity;
+ phys_maxwalkvelocity = aassettings.phys_maxwalkvelocity;// * frametime;
+ phys_maxcrouchvelocity = aassettings.phys_maxcrouchvelocity;// * frametime;
+ phys_maxswimvelocity = aassettings.phys_maxswimvelocity;// * frametime;
+ phys_walkaccelerate = aassettings.phys_walkaccelerate;
+ phys_airaccelerate = aassettings.phys_airaccelerate;
+ phys_swimaccelerate = aassettings.phys_swimaccelerate;
+ phys_maxstep = aassettings.phys_maxstep;
+ phys_maxsteepness = aassettings.phys_maxsteepness;
+ phys_jumpvel = aassettings.phys_jumpvel * frametime;
+ //
+ Com_Memset(move, 0, sizeof(aas_clientmove_t));
+ Com_Memset(&trace, 0, sizeof(aas_trace_t));
+ //start at the current origin
+ VectorCopy(origin, org);
+ org[2] += 0.25;
+ //velocity to test for the first frame
+ VectorScale(velocity, frametime, frame_test_vel);
+ //
+ jump_frame = -1;
+ //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 ? phys_watergravity : phys_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 ? phys_friction : phys_waterfriction;
+ //apply friction
+ VectorScale(frame_test_vel, 1/frametime, frame_test_vel);
+ AAS_ApplyFriction(frame_test_vel, friction, phys_stopspeed, frametime);
+ VectorScale(frame_test_vel, frametime, frame_test_vel);
+ } //end if
+ crouch = qfalse;
+ //apply command movement
+ if (n < cmdframes)
+ {
+ ax = 0;
+ maxvel = phys_maxwalkvelocity;
+ accelerate = phys_airaccelerate;
+ VectorCopy(cmdmove, wishdir);
+ if (onground)
+ {
+ if (cmdmove[2] < -300)
+ {
+ crouch = qtrue;
+ maxvel = phys_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] = phys_jumpvel - (gravity * 0.1 * frametime) + 5;
+ jump_frame = n;
+ //jumping so air accelerate
+ accelerate = phys_airaccelerate;
+ } //end if
+ else
+ {
+ accelerate = phys_walkaccelerate;
+ } //end else
+ ax = 2;
+ } //end if
+ if (swimming)
+ {
+ maxvel = phys_maxswimvelocity;
+ accelerate = phys_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 > phys_maxacceleration) velchange = phys_maxacceleration;
+ else if (velchange < -phys_maxacceleration) velchange = -phys_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, presencetype, entnum);
+ //
+//#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_ENTERAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_TOUCHCLUSTERPORTAL))
+ {
+ numareas = AAS_TraceAreas(org, trace.endpos, areas, points, 20);
+ for (i = 0; i < numareas; i++)
+ {
+ if (stopevent & SE_ENTERAREA)
+ {
+ if (areas[i] == stopareanum)
+ {
+ VectorCopy(points[i], move->endpos);
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->endarea = areas[i];
+ move->trace = trace;
+ move->stopevent = SE_ENTERAREA;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end if
+ } //end if
+ //NOTE: if not the first frame
+ if ((stopevent & SE_TOUCHJUMPPAD) && n)
+ {
+ if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_JUMPPAD)
+ {
+ VectorCopy(points[i], move->endpos);
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->endarea = areas[i];
+ move->trace = trace;
+ move->stopevent = SE_TOUCHJUMPPAD;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end if
+ } //end if
+ if (stopevent & SE_TOUCHTELEPORTER)
+ {
+ if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_TELEPORTER)
+ {
+ VectorCopy(points[i], move->endpos);
+ move->endarea = areas[i];
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_TOUCHTELEPORTER;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end if
+ } //end if
+ if (stopevent & SE_TOUCHCLUSTERPORTAL)
+ {
+ if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_CLUSTERPORTAL)
+ {
+ VectorCopy(points[i], move->endpos);
+ move->endarea = areas[i];
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_TOUCHCLUSTERPORTAL;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end if
+ } //end if
+ } //end for
+ } //end if
+ //
+ if (stopevent & SE_HITBOUNDINGBOX)
+ {
+ if (AAS_ClipToBBox(&trace, org, trace.endpos, presencetype, mins, maxs))
+ {
+ VectorCopy(trace.endpos, move->endpos);
+ move->endarea = AAS_PointAreaNum(move->endpos);
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_HITBOUNDINGBOX;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end if
+ } //end if
+ //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 = AAS_PlaneFromNum(trace.planenum);
+ //
+ if (stopevent & SE_HITGROUNDAREA)
+ {
+ if (DotProduct(plane->normal, up) > phys_maxsteepness)
+ {
+ VectorCopy(org, start);
+ start[2] += 0.5;
+ if (AAS_PointAreaNum(start) == stopareanum)
+ {
+ VectorCopy(start, move->endpos);
+ move->endarea = stopareanum;
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_HITGROUNDAREA;
+ move->presencetype = 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] += phys_maxstep;
+ steptrace = AAS_TraceClientBBox(start, stepend, presencetype, entnum);
+ //
+ if (!steptrace.startsolid)
+ {
+ plane2 = AAS_PlaneFromNum(steptrace.planenum);
+ if (DotProduct(plane2->normal, up) > phys_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);
+ //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) > phys_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);
+ move->endarea = AAS_PointAreaNum(org);
+ VectorCopy(frame_test_vel, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_HITGROUNDDAMAGE;
+ move->presencetype = 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);
+ move->endarea = areanum;
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->stopevent = event & stopevent;
+ move->presencetype = presencetype;
+ move->endcontents = pc;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end if
+ } //end if
+ //
+ onground = AAS_OnGround(org, presencetype, entnum);
+ //if onground and on the ground for at least one whole frame
+ if (onground)
+ {
+ if (stopevent & SE_HITGROUND)
+ {
+ VectorCopy(org, move->endpos);
+ move->endarea = AAS_PointAreaNum(org);
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_HITGROUND;
+ move->presencetype = 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);
+ move->endarea = AAS_PointAreaNum(org);
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_LEAVEGROUND;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end else if
+ else if (stopevent & SE_GAP)
+ {
+ aas_trace_t gaptrace;
+
+ VectorCopy(org, start);
+ VectorCopy(start, end);
+ end[2] -= 48 + aassettings.phys_maxbarrier;
+ gaptrace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1);
+ //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.phys_maxstep - 1)
+ {
+ if (!(AAS_PointContents(end) & CONTENTS_WATER))
+ {
+ VectorCopy(lastorg, move->endpos);
+ move->endarea = AAS_PointAreaNum(lastorg);
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->trace = trace;
+ move->stopevent = SE_GAP;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ return qtrue;
+ } //end if
+ } //end if
+ } //end if
+ } //end else if
+ } //end for
+ //
+ VectorCopy(org, move->endpos);
+ move->endarea = AAS_PointAreaNum(org);
+ VectorScale(frame_test_vel, 1/frametime, move->velocity);
+ move->stopevent = SE_NONE;
+ move->presencetype = presencetype;
+ move->endcontents = 0;
+ move->time = n * frametime;
+ move->frames = n;
+ //
+ return qtrue;
+} //end of the function AAS_ClientMovementPrediction
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+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)
+{
+ vec3_t mins, maxs;
+ return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground,
+ velocity, cmdmove, cmdframes, maxframes,
+ frametime, stopevent, stopareanum,
+ mins, maxs, visualize);
+} //end of the function AAS_PredictClientMovement
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_ClientMovementHitBBox(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,
+ vec3_t mins, vec3_t maxs, int visualize)
+{
+ return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground,
+ velocity, cmdmove, cmdframes, maxframes,
+ frametime, SE_HITBOUNDINGBOX, 0,
+ mins, maxs, visualize);
+} //end of the function AAS_ClientMovementHitBBox
+//===========================================================================
+//
+// 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.1f, 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 phys_gravity, phys_maxvelocity;
+ float maxjump, height2fall, t, top;
+ vec3_t dir;
+
+ phys_gravity = aassettings.phys_gravity;
+ phys_maxvelocity = aassettings.phys_maxvelocity;
+
+ //maximum height a player can jump with the given initial z velocity
+ maxjump = 0.5 * phys_gravity * (zvel / phys_gravity) * (zvel / phys_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 = phys_maxvelocity;
+ return 0;
+ } //end if
+ //time a player takes to fall the height
+ t = sqrt(height2fall / (0.5 * phys_gravity));
+ //direction from start to end
+ VectorSubtract(end, start, dir);
+ //
+ if ( (t + zvel / phys_gravity) == 0.0f ) {
+ *velocity = phys_maxvelocity;
+ return 0;
+ }
+ //calculate horizontal speed
+ *velocity = sqrt(dir[0]*dir[0] + dir[1]*dir[1]) / (t + zvel / phys_gravity);
+ //the horizontal speed must be lower than the max speed
+ if (*velocity > phys_maxvelocity)
+ {
+ *velocity = phys_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 00000000..b00e41ab
--- /dev/null
+++ b/src/botlib/be_aas_move.h
@@ -0,0 +1,71 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_move.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/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);
+//predict movement until bounding box is hit
+int AAS_ClientMovementHitBBox(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,
+ vec3_t mins, vec3_t maxs, 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);
+//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 00000000..ea0d2da6
--- /dev/null
+++ b/src/botlib/be_aas_optimize.c
@@ -0,0 +1,312 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_optimize.c
+ *
+ * desc: decreases the .aas file size after the reachabilities have
+ * been calculated, just dumps all the faces, edges and vertexes
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_optimize.c $
+ *
+ *****************************************************************************/
+
+#include "../qcommon/q_shared.h"
+#include "l_libvar.h"
+#include "l_memory.h"
+#include "l_script.h"
+#include "l_precomp.h"
+#include "l_struct.h"
+#include "aasfile.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "be_aas_def.h"
+
+typedef struct optimized_s
+{
+ //vertexes
+ 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;
+ //
+ int *vertexoptimizeindex;
+ int *edgeoptimizeindex;
+ int *faceoptimizeindex;
+} 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];
+ Com_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];
+ Com_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 & TRAVELTYPE_MASK) == 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 & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) continue;
+ //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information
+ if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == 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
diff --git a/src/botlib/be_aas_optimize.h b/src/botlib/be_aas_optimize.h
new file mode 100644
index 00000000..799c28a3
--- /dev/null
+++ b/src/botlib/be_aas_optimize.h
@@ -0,0 +1,33 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_optimize.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_optimize.h $
+ *
+ *****************************************************************************/
+
+void AAS_Optimize(void);
+
diff --git a/src/botlib/be_aas_reach.c b/src/botlib/be_aas_reach.c
new file mode 100644
index 00000000..95ead20c
--- /dev/null
+++ b/src/botlib/be_aas_reach.c
@@ -0,0 +1,4538 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_reach.c
+ *
+ * desc: reachability calculations
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_reach.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_aas_def.h"
+
+extern int Sys_MilliSeconds(void);
+
+
+extern botlib_import_t botimport;
+
+//#define REACH_DEBUG
+
+//NOTE: all travel times are in hundreth of a second
+//maximum number of reachability links
+#define AAS_MAX_REACHABILITYSIZE 65536
+//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
+#define INSIDEUNITS_WALKEND 5
+#define INSIDEUNITS_WALKSTART 0.1
+#define INSIDEUNITS_WATERJUMP 15
+//area flag used for weapon jumping
+#define AREA_WEAPONJUMP 8192 //valid area to weapon jump to
+//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;
+//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;
+
+//===========================================================================
+// 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, side;
+ 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];
+ side = face->backarea != areanum;
+ plane = &aasworld.planes[face->planenum ^ side];
+ 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
+//===========================================================================
+//
+// 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: this is a bad idea when the reachability is not yet
+ // 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_GetJumpPadInfo(int ent, vec3_t areastart, vec3_t absmins, vec3_t absmaxs, vec3_t velocity)
+{
+ int modelnum, ent2;
+ float speed, height, gravity, time, dist, forward;
+ vec3_t origin, angles, teststart, ent2origin;
+ aas_trace_t trace;
+ char model[MAX_EPAIRKEY];
+ char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY];
+
+ //
+ AAS_FloatForBSPEpairKey(ent, "speed", &speed);
+ if (!speed) speed = 1000;
+ 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);
+ 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);
+ return qfalse;
+ } //end if
+ AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin);
+ //
+ height = ent2origin[2] - origin[2];
+ gravity = aassettings.phys_gravity;
+ time = sqrt( height / ( 0.5 * gravity ) );
+ if (!time)
+ {
+ botimport.Print(PRT_MESSAGE, "trigger_push without time\n");
+ return qfalse;
+ } //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.1f;
+ VectorScale(velocity, forward, velocity);
+ velocity[2] = time * gravity;
+ return qtrue;
+} //end of the function AAS_GetJumpPadInfo
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs)
+{
+ int area2num, ent, bot_visualizejumppads, bestareanum;
+ float volume, bestareavolume;
+ vec3_t areastart, cmdmove, bboxmins, bboxmaxs;
+ vec3_t absmins, absmaxs, velocity;
+ aas_clientmove_t move;
+ aas_link_t *areas, *link;
+ char classname[MAX_EPAIRKEY];
+
+#ifdef BSPC
+ bot_visualizejumppads = 0;
+#else
+ bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0");
+#endif
+ VectorAdd(origin, mins, bboxmins);
+ VectorAdd(origin, maxs, bboxmaxs);
+ 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;
+ //
+ if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue;
+ //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 (AAS_AreaJumpPad(link->areanum)) break;
+ } //end for
+ if (!link)
+ {
+ botimport.Print(PRT_MESSAGE, "trigger_push 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]);
+ //
+ VectorSet(cmdmove, 0, 0, 0);
+ Com_Memset(&move, 0, sizeof(aas_clientmove_t));
+ area2num = 0;
+ AAS_ClientMovementHitBBox(&move, -1, areastart, PRESENCE_NORMAL, qfalse,
+ velocity, cmdmove, 0, 30, 0.1f, bboxmins, bboxmaxs, bot_visualizejumppads);
+ if (move.frames < 30)
+ {
+ bestareanum = 0;
+ bestareavolume = 0;
+ for (link = areas; link; link = link->next_area)
+ {
+ if (!AAS_AreaJumpPad(link->areanum)) continue;
+ volume = AAS_AreaVolume(link->areanum);
+ if (volume >= bestareavolume)
+ {
+ bestareanum = link->areanum;
+ bestareavolume = volume;
+ } //end if
+ } //end if
+ AAS_UnlinkFromAreas(areas);
+ return bestareanum;
+ } //end if
+ AAS_UnlinkFromAreas(areas);
+ } //end for
+ return 0;
+} //end of the function AAS_BestReachableFromJumpPadArea
+//===========================================================================
+//
+// 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 (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 0
+ 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");
+#endif
+ 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_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH);
+ //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)
+{
+ Com_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
+ return aasworld.areasettings[areanum].numreachableareas;
+} //end of the function AAS_AreaReachability
+//===========================================================================
+// 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.phys_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.phys_gravity;
+ t = sqrt(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 phys_jumpvel)
+{
+ float phys_gravity;
+
+ phys_gravity = aassettings.phys_gravity;
+ //maximum height a player can jump with the given initial z velocity
+ return 0.5 * phys_gravity * (phys_jumpvel / phys_gravity) * (phys_jumpvel / phys_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 phys_jumpvel)
+{
+ float phys_gravity, phys_maxvelocity, t;
+
+ phys_gravity = aassettings.phys_gravity;
+ phys_maxvelocity = aassettings.phys_maxvelocity;
+ //time a player takes to fall the height
+ t = sqrt(aassettings.rs_maxjumpfallheight / (0.5 * phys_gravity));
+ //maximum distance
+ return phys_maxvelocity * (t + phys_jumpvel / phys_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;
+} //end of the function AAS_AreaCrouch
+//===========================================================================
+// 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;
+} //end of the function AAS_AreaSwim
+//===========================================================================
+// 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_AreaClusterPortal(int areanum)
+{
+ return (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL);
+} //end of the function AAS_AreaClusterPortal
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_AreaDoNotEnter(int areanum)
+{
+ return (aasworld.areasettings[areanum].contents & AREACONTENTS_DONOTENTER);
+} //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.phys_jumpvel / (aassettings.phys_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;
+ Com_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 += aassettings.rs_startcrouch;
+ } //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, areas[10], numareas;
+ 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, 0, 0}, ground_bestend = {0, 0, 0}, ground_bestnormal = {0, 0, 0};
+ vec3_t water_beststart = {0, 0, 0}, water_bestend = {0, 0, 0}, water_bestnormal = {0, 0, 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.phys_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 += aassettings.rs_startcrouch;
+ } //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.phys_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.phys_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 = aassettings.rs_waterjump;
+ 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.phys_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 = aassettings.rs_barrierjump;//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.phys_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 = 1;
+ lreach->next = areareachability[area1num];
+ areareachability[area1num] = lreach;
+ //we've got another walk reachability
+ reach_walk++;
+ return qtrue;
+ } //end if
+ // if no maximum fall height set or less than the max
+ if (!aassettings.rs_maxfallheight || fabs(ground_bestdist) < aassettings.rs_maxfallheight) {
+ //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)
+ {
+ //if not going through a cluster portal
+ numareas = AAS_TraceAreas(start, end, areas, NULL, sizeof(areas) / sizeof(int));
+ for (i = 0; i < numareas; i++)
+ if (AAS_AreaClusterPortal(areas[i]))
+ break;
+ if (i >= numareas)
+ {
+ //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 = aassettings.rs_startwalkoffledge + fabs(ground_bestdist) * 50 / aassettings.phys_gravity;
+ //if falling from too high and not falling into water
+ if (!AAS_AreaSwim(area2num) && !AAS_AreaJumpPad(area2num))
+ {
+ if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta5)
+ {
+ lreach->traveltime += aassettings.rs_falldamage5;
+ } //end if
+ if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta10)
+ {
+ lreach->traveltime += aassettings.rs_falldamage10;
+ } //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 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: -
+//===========================================================================
+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;
+ int stopevent, areas[10], numareas;
+ float phys_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}, sidewards;
+ 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];
+ //
+ phys_jumpvel = aassettings.phys_jumpvel;
+ //maximum distance a player can jump
+ maxjumpdistance = 2 * AAS_MaxJumpDistance(phys_jumpvel);
+ //maximum height a player can jump with the given initial z velocity
+ maxjumpheight = AAS_MaxJumpHeight(phys_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 very close and almost no height difference then the bot can walk
+ if (bestdist <= 48 && fabs(beststart[2] - bestend[2]) < 8)
+ {
+ speed = 400;
+ traveltype = TRAVEL_WALKOFFLEDGE;
+ } //end if
+ else if (AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed))
+ {
+ //FIXME: why multiply with 1.2???
+ speed *= 1.2f;
+ 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(phys_jumpvel, beststart, bestend, &speed))
+ return qfalse;
+ speed *= 1.05f;
+ 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 the bot can stand on the surface
+ if (DotProduct(plane->normal, up) >= 0.7)
+ {
+ // if no lava or slime below
+ if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME)))
+ {
+ if (teststart[2] - trace.endpos[2] <= aassettings.phys_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 the bot can stand on the surface
+ if (DotProduct(plane->normal, up) >= 0.7)
+ {
+ // if no lava or slime below
+ if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME)))
+ {
+ if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier)
+ return qfalse;
+ } //end if
+ } //end if
+ } //end if
+ //
+ // get command movement
+ VectorClear(cmdmove);
+ if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP)
+ cmdmove[2] = aassettings.phys_jumpvel;
+ else
+ cmdmove[2] = 0;
+ //
+ VectorSubtract(bestend, beststart, dir);
+ dir[2] = 0;
+ VectorNormalize(dir);
+ CrossProduct(dir, up, sidewards);
+ //
+ stopevent = SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE;
+ if (!AAS_AreaClusterPortal(area1num) && !AAS_AreaClusterPortal(area2num))
+ stopevent |= SE_TOUCHCLUSTERPORTAL;
+ //
+ for (i = 0; i < 3; i++)
+ {
+ //
+ if (i == 1)
+ VectorAdd(testend, sidewards, testend);
+ else if (i == 2)
+ VectorSubtract(bestend, sidewards, testend);
+ else
+ VectorCopy(bestend, testend);
+ VectorSubtract(testend, beststart, dir);
+ dir[2] = 0;
+ VectorNormalize(dir);
+ VectorScale(dir, speed, velocity);
+ //
+ AAS_PredictClientMovement(&move, -1, beststart, PRESENCE_NORMAL, qtrue,
+ velocity, cmdmove, 3, 30, 0.1f,
+ stopevent, 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_ENTERSLIME|SE_ENTERLAVA))
+ return qfalse;
+ // never jump or fall through a cluster portal
+ if (move.stopevent & SE_TOUCHCLUSTERPORTAL)
+ 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
+ VectorMA(move.endpos, -64, dir, teststart);
+ teststart[2] += 1;
+ numareas = AAS_TraceAreas(move.endpos, teststart, areas, NULL, sizeof(areas) / sizeof(int));
+ for (j = 0; j < numareas; j++)
+ {
+ if (areas[j] == area2num)
+ break;
+ } //end for
+ if (j < numareas)
+ break;
+ }
+ if (i >= 3)
+ return qfalse;
+ //
+#ifdef REACH_DEBUG
+ //create the reachability
+ Log_Write("jump reachability between %d and %d\r\n", area1num, area2num);
+#endif //REACH_DEBUG
+ //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 & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE && height > VectorLength(dir))
+ {
+ lreach->traveltime = aassettings.rs_startwalkoffledge + height * 50 / aassettings.phys_gravity;
+ }
+ else
+ {
+ lreach->traveltime = aassettings.rs_startjump + VectorDistance(bestend, beststart) * 240 / aassettings.phys_maxwalkvelocity;
+ } //end if
+ //
+ if (!AAS_AreaJumpPad(area2num))
+ {
+ if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta5)
+ {
+ lreach->traveltime += aassettings.rs_falldamage5;
+ } //end if
+ else if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta10)
+ {
+ lreach->traveltime += aassettings.rs_falldamage10;
+ } //end if
+ } //end if
+ lreach->next = areareachability[area1num];
+ areareachability[area1num] = lreach;
+ //
+ if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP)
+ reach_jump++;
+ else
+ reach_walkoffledge++;
+ } //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 = 0, lowestedgenum = 0;
+ int face1num, face2num, ladderface1num = 0, ladderface2num = 0;
+ int ladderface1vertical, ladderface2vertical, firstv;
+ float face1area, face2area, bestface1area = -9999, bestface2area = -9999;
+ float phys_jumpvel, maxjumpheight;
+ vec3_t area1point, area2point, v1, v2, up = {0, 0, 1};
+ vec3_t mid, lowestpoint = {0, 0}, start, end, sharededgevec, dir;
+ aas_area_t *area1, *area2;
+ aas_face_t *face1, *face2, *ladderface1 = NULL, *ladderface2 = NULL;
+ 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;
+ //
+ phys_jumpvel = aassettings.phys_jumpvel;
+ //maximum height a player can jump with the given initial z velocity
+ maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel);
+
+ area1 = &aasworld.areas[area1num];
+ area2 = &aasworld.areas[area2num];
+
+ 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 = abs(DotProduct(plane1->normal, up)) < 0.1;
+ ladderface2vertical = abs(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
+ && abs(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, plane1->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
+ 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);
+ lreach->traveltype = TRAVEL_WALKOFFLEDGE;
+ lreach->traveltime = 10;
+ 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 REACH_DEBUG
+ if (trace.startsolid)
+ {
+ Log_Write("trace from area %d started in solid\r\n", area1num);
+ } //end if
+#endif //REACH_DEBUG
+ //
+ 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 (abs(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 REACH_DEBUG
+ Log_Write("jump up to ladder reach between %d and %d\r\n", area2num, area1num);
+#endif //REACH_DEBUG
+ } //end if
+#ifdef REACH_DEBUG
+ else Log_Write("jump too high between area %d and %d\r\n", area2num, area1num);
+#endif //REACH_DEBUG
+ } //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
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_TravelFlagsForTeam(int ent)
+{
+ int notteam;
+
+ if (!AAS_IntForBSPEpairKey(ent, "bot_notteam", &notteam))
+ return 0;
+ if (notteam == 1)
+ return TRAVELFLAG_NOTTEAM1;
+ if (notteam == 2)
+ return TRAVELFLAG_NOTTEAM2;
+ return 0;
+} //end of the function AAS_TravelFlagsForTeam
+//===========================================================================
+// 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;
+ float angle;
+ vec3_t origin, destorigin, mins, maxs, end, angles;
+ vec3_t mid, velocity, cmdmove;
+ aas_lreachability_t *lreach;
+ aas_clientmove_t move;
+ 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 REACH_DEBUG
+ botimport.Print(PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model);
+//#endif REACH_DEBUG
+ VectorClear(angles);
+ 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 REACH_DEBUG
+ botimport.Print(PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model);
+//#endif REACH_DEBUG
+ VectorClear(angles);
+ 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))
+ {
+ VectorCopy(destorigin, end);
+ end[2] -= 64;
+ 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
+ area2num = AAS_PointAreaNum(trace.endpos);
+ //
+ /*
+ if (!AAS_AreaTeleporter(area2num) &&
+ !AAS_AreaJumpPad(area2num) &&
+ !AAS_AreaGrounded(area2num))
+ {
+ VectorCopy(trace.endpos, destorigin);
+ }
+ else*/
+ {
+ //predict where you'll end up
+ AAS_FloatForBSPEpairKey(dest, "angle", &angle);
+ if (angle)
+ {
+ VectorSet(angles, 0, angle, 0);
+ AngleVectors(angles, velocity, NULL, NULL);
+ VectorScale(velocity, 400, velocity);
+ } //end if
+ else
+ {
+ VectorClear(velocity);
+ } //end else
+ VectorClear(cmdmove);
+ AAS_PredictClientMovement(&move, -1, destorigin, PRESENCE_NORMAL, qfalse,
+ velocity, cmdmove, 0, 30, 0.1f,
+ SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|
+ SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, qfalse); //qtrue);
+ area2num = AAS_PointAreaNum(move.endpos);
+ if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA))
+ {
+ botimport.Print(PRT_WARNING, "teleported into slime or lava at dest %s\n", target);
+ } //end if
+ VectorCopy(move.endpos, destorigin);
+ } //end else
+ } //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->traveltype |= AAS_TravelFlagsForTeam(ent);
+ lreach->traveltime = aassettings.rs_teleport;
+ 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: -
+//===========================================================================
+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 REACH_DEBUG
+ Log_Write("AAS_Reachability_Elevator\r\n");
+#endif //REACH_DEBUG
+ 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 REACH_DEBUG
+ Log_Write("found func plat\r\n");
+#endif //REACH_DEBUG
+ 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;
+ //
+ //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->traveltype |= AAS_TravelFlagsForTeam(ent);
+ lreach->traveltime = aassettings.rs_startelevator + height * 100 / speed;
+ lreach->next = areareachability[area1num];
+ areareachability[area1num] = lreach;
+ //don't go any further to the outside
+ n = 9999;
+ //
+#ifdef REACH_DEBUG
+ Log_Write("elevator reach from %d to %d\r\n", area1num, area2num);
+#endif //REACH_DEBUG
+ //
+ 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.phys_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.1f))
+ {
+ //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
+ //if the entity has an origin set then use it
+ if (!AAS_VectorForBSPEpairKey(ent, "origin", origin))
+ VectorSet(origin, 0, 0, 0);
+ //
+ AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL);
+ //
+ VectorAdd(mins, origin, mins);
+ VectorAdd(maxs, origin, maxs);
+ //
+ VectorAdd(mins, maxs, mid);
+ VectorScale(mid, 0.5, 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
+#if 0
+ 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
+#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->traveltype |= AAS_TravelFlagsForTeam(ent);
+ lreach->traveltime = aassettings.rs_funcbob;
+ 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, area2num, visualize, ent, bot_visualizejumppads;
+ //int modelnum, ent2;
+ //float dist, time, height, gravity, forward;
+ float speed, zvel, hordist;
+ aas_face_t *face2;
+ aas_area_t *area2;
+ aas_lreachability_t *lreach;
+ vec3_t areastart, facecenter, dir, cmdmove;
+ vec3_t velocity, absmins, absmaxs;
+ //vec3_t origin, ent2origin, angles, teststart;
+ aas_clientmove_t move;
+ //aas_trace_t trace;
+ aas_link_t *areas, *link;
+ //char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY], model[MAX_EPAIRKEY];
+ char classname[MAX_EPAIRKEY];
+
+#ifdef BSPC
+ bot_visualizejumppads = 0;
+#else
+ bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0");
+#endif
+ 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;
+ //
+ if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) 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 REACH_DEBUG
+ 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 REACH_DEBUG
+ 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 == 563)
+ {
+ 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_push 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;
+ Com_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.1f,
+ SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|
+ SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, bot_visualizejumppads);
+ area2num = move.endarea;
+ 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->traveltype |= AAS_TravelFlagsForTeam(ent);
+ lreach->traveltime = aassettings.rs_jumppad;
+ lreach->next = areareachability[link->areanum];
+ areareachability[link->areanum] = lreach;
+ //
+ reach_jumppad++;
+ } //end for
+ } //end if
+ } //end if
+ //
+ if (fabs(velocity[0]) > 100 || 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
+ 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.1f,
+ 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)))
+ {
+ //never go back to the same jumppad
+ for (link = areas; link; link = link->next_area)
+ {
+ if (link->areanum == move.endarea) break;
+ }
+ if (!link)
+ {
+ 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 = move.endarea;
+ //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->traveltype |= AAS_TravelFlagsForTeam(ent);
+ lreach->traveltime = aassettings.rs_aircontrolledjumppad;
+ 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_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 = aassettings.rs_startgrapple + 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, i;
+ vec3_t mins = {-15, -15, -15}, maxs = {15, 15, 15};
+ vec3_t origin;
+ int areanum, weaponjumpareas, spawnflags;
+ char classname[MAX_EPAIRKEY];
+
+ weaponjumpareas = 0;
+ for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent))
+ {
+ if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue;
+ 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_plasmagun") ||
+ !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, visualize;
+ 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;
+
+ visualize = qfalse;
+// if (area1num == 4436 && area2num == 4318)
+// {
+// visualize = qtrue;
+// }
+ 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 < 300)
+ {
+ //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.1f,
+ SE_ENTERWATER|SE_ENTERSLIME|
+ SE_ENTERLAVA|SE_HITGROUNDDAMAGE|
+ SE_TOUCHJUMPPAD|SE_HITGROUND|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)))
+ {
+ //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;
+ lreach->traveltime = aassettings.rs_bfgjump;
+ } //end if
+ else
+ {
+ lreach->traveltype = TRAVEL_ROCKETJUMP;
+ lreach->traveltime = aassettings.rs_rocketjump;
+ } //end else
+ 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, p, areas[10], numareas;
+ 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_SLIME
+ | AREACONTENTS_LAVA))
+ {
+ //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum);
+ break;
+ } //end if
+ //if not going through a cluster portal
+ numareas = AAS_TraceAreas(mid, testend, areas, NULL, sizeof(areas) / sizeof(int));
+ for (p = 0; p < numareas; p++)
+ if (AAS_AreaClusterPortal(areas[p]))
+ break;
+ if (p < numareas)
+ break;
+ // if a maximum fall height is set and the bot would fall down further
+ if (aassettings.rs_maxfallheight && fabs(mid[2] - trace.endpos[2]) > aassettings.rs_maxfallheight)
+ break;
+ //
+ 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 = aassettings.rs_startwalkoffledge + fabs(mid[2] - trace.endpos[2]) * 50 / aassettings.phys_gravity;
+ if (!AAS_AreaSwim(reachareanum) && !AAS_AreaJumpPad(reachareanum))
+ {
+ if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta5)
+ {
+ lreach->traveltime += aassettings.rs_falldamage5;
+ } //end if
+ else if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta10)
+ {
+ lreach->traveltime += aassettings.rs_falldamage10;
+ } //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;
+ //
+ 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.numreachabilityareas >= aasworld.numareas + 2) return qfalse;
+ //if starting with area 1 (area 0 is a dummy)
+ if (aasworld.numreachabilityareas == 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.numreachabilityareas + (int) framereachability;
+ start_time = Sys_MilliSeconds();
+ //loop over the areas
+ for (i = aasworld.numreachabilityareas; i < aasworld.numareas && i < todo; i++)
+ {
+ aasworld.numreachabilityareas++;
+ //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
+ if (calcgrapplereach) AAS_Reachability_Grapple(i, j);
+ //check for a weapon jump reachability
+ 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.numreachabilityareas * 1000 / aasworld.numareas > lastpercentage) break;
+ } //end for
+ //
+ if (aasworld.numreachabilityareas == aasworld.numareas)
+ {
+ botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) 100.0);
+ botimport.Print(PRT_MESSAGE, "\nplease wait while storing reachability...\n");
+ aasworld.numreachabilityareas++;
+ } //end if
+ //if this is the last step in the reachability calculations
+ else if (aasworld.numreachabilityareas == 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.numreachabilityareas++;
+ //
+ botimport.Print(PRT_MESSAGE, "calculating clusters...\n");
+ } //end if
+ else
+ {
+ lastpercentage = aasworld.numreachabilityareas * 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.numreachabilityareas = aasworld.numareas + 2;
+ return;
+ } //end if
+#else
+ aasworld.numreachabilityareas = aasworld.numareas + 2;
+ return;
+#endif //BSPC
+ } //end if
+#ifndef BSPC
+ calcgrapplereach = LibVarGetValue("grapplereach");
+#endif
+ aasworld.savefile = qtrue;
+ //start with area 1 because area zero is a dummy
+ aasworld.numreachabilityareas = 1;
+ ////aasworld.numreachabilityareas = aasworld.numareas + 1; //only calculate entity reachabilities
+ //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 00000000..0ab8740b
--- /dev/null
+++ b/src/botlib/be_aas_reach.h
@@ -0,0 +1,68 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_reach.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_reach.h $
+ *
+ *****************************************************************************/
+
+#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 best jumppad area from which the bbox at origin is reachable
+int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs);
+//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);
+//returns true if a player can swim in this area
+int AAS_AreaSwim(int areanum);
+//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);
diff --git a/src/botlib/be_aas_route.c b/src/botlib/be_aas_route.c
new file mode 100644
index 00000000..e31202b0
--- /dev/null
+++ b/src/botlib/be_aas_route.c
@@ -0,0 +1,2210 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_route.c
+ *
+ * desc: AAS
+ *
+ * $Archive: /MissionPack/code/botlib/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 "botlib.h"
+#include "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.3f //crouch speed = 100
+#define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150
+#define DISTANCEFACTOR_WALK 0.33f //walk speed = 300
+
+//cache refresh time
+#define CACHE_REFRESHTIME 15.0f //15 seconds refresh time
+
+//maximum number of routing updates each frame
+#define MAX_FRAMEROUTINGUPDATES 10
+
+
+/*
+
+ 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;
+
+//===========================================================================
+//
+// 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: -
+//===========================================================================
+ID_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: -
+//===========================================================================
+ID_INLINE int AAS_TravelFlagForType_inline(int traveltype)
+{
+ int tfl;
+
+ tfl = 0;
+ if (tfl & TRAVELFLAG_NOTTEAM1)
+ tfl |= TFL_NOTTEAM1;
+ if (tfl & TRAVELFLAG_NOTTEAM2)
+ tfl |= TFL_NOTTEAM2;
+ traveltype &= TRAVELTYPE_MASK;
+ if (traveltype < 0 || traveltype >= MAX_TRAVELTYPES)
+ return TFL_INVALID;
+ tfl |= aasworld.travelflagfortype[traveltype];
+ return tfl;
+} //end of the function AAS_TravelFlagForType_inline
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_TravelFlagForType(int traveltype)
+{
+ return AAS_TravelFlagForType_inline(traveltype);
+} //end of the function AAS_TravelFlagForType_inline
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_UnlinkCache(aas_routingcache_t *cache)
+{
+ if (cache->time_next) cache->time_next->time_prev = cache->time_prev;
+ else aasworld.newestcache = cache->time_prev;
+ if (cache->time_prev) cache->time_prev->time_next = cache->time_next;
+ else aasworld.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 (aasworld.newestcache)
+ {
+ aasworld.newestcache->time_next = cache;
+ cache->time_prev = aasworld.newestcache;
+ } //end if
+ else
+ {
+ aasworld.oldestcache = cache;
+ cache->time_prev = NULL;
+ } //end else
+ cache->time_next = NULL;
+ aasworld.newestcache = cache;
+} //end of the function AAS_LinkCache
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_FreeRoutingCache(aas_routingcache_t *cache)
+{
+ AAS_UnlinkCache(cache);
+ routingcachesize -= cache->size;
+ FreeMemory(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
+ 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_EnableRoutingArea(int areanum, int enable)
+{
+ int flags;
+
+ 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
+ flags = aasworld.areasettings[areanum].areaflags & AREA_DISABLED;
+ if (enable < 0)
+ return !flags;
+
+ if (enable)
+ aasworld.areasettings[areanum].areaflags &= ~AREA_DISABLED;
+ else
+ aasworld.areasettings[areanum].areaflags |= AREA_DISABLED;
+ // if the status of the area changed
+ if ( (flags & AREA_DISABLED) != (aasworld.areasettings[areanum].areaflags & AREA_DISABLED) )
+ {
+ //remove all routing cache involving this area
+ AAS_RemoveRoutingCacheUsingArea( areanum );
+ } //end if
+ return !flags;
+} //end of the function AAS_EnableRoutingArea
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+ID_INLINE float AAS_RoutingTime(void)
+{
+ return AAS_Time();
+} //end of the function AAS_RoutingTime
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_GetAreaContentsTravelFlags(int areanum)
+{
+ int contents, tfl;
+
+ contents = aasworld.areasettings[areanum].contents;
+ tfl = 0;
+ if (contents & AREACONTENTS_WATER)
+ tfl |= TFL_WATER;
+ else if (contents & AREACONTENTS_SLIME)
+ tfl |= TFL_SLIME;
+ else if (contents & AREACONTENTS_LAVA)
+ tfl |= TFL_LAVA;
+ else
+ tfl |= TFL_AIR;
+ if (contents & AREACONTENTS_DONOTENTER)
+ tfl |= TFL_DONOTENTER;
+ if (contents & AREACONTENTS_NOTTEAM1)
+ tfl |= TFL_NOTTEAM1;
+ if (contents & AREACONTENTS_NOTTEAM2)
+ tfl |= TFL_NOTTEAM2;
+ if (aasworld.areasettings[areanum].areaflags & AREA_BRIDGE)
+ tfl |= TFL_BRIDGE;
+ return tfl;
+} //end of the function AAS_GetAreaContentsTravelFlags
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+ID_INLINE int AAS_AreaContentsTravelFlags_inline(int areanum)
+{
+ return aasworld.areacontentstravelflags[areanum];
+} //end of the function AAS_AreaContentsTravelFlags
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_AreaContentsTravelFlags(int areanum)
+{
+ return aasworld.areacontentstravelflags[areanum];
+} //end of the function AAS_AreaContentsTravelFlags
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_InitAreaContentsTravelFlags(void)
+{
+ int i;
+
+ if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags);
+ aasworld.areacontentstravelflags = (int *) GetClearedMemory(aasworld.numareas * sizeof(int));
+ //
+ for (i = 0; i < aasworld.numareas; i++) {
+ aasworld.areacontentstravelflags[i] = AAS_GetAreaContentsTravelFlags(i);
+ }
+} //end of the function AAS_InitAreaContentsTravelFlags
+//===========================================================================
+//
+// 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) FreeMemory(aasworld.reversedreachability);
+ //allocate memory for the reversed reachability links
+ ptr = (char *) GetClearedMemory(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 reachabilities of all areas
+ for (i = 1; i < aasworld.numareas; i++)
+ {
+ //settings of the area
+ settings = &aasworld.areasettings[i];
+ //
+ if (settings->numreachableareas >= 128)
+ botimport.Print(PRT_WARNING, "area %d has more than 128 reachabilities\n", i);
+ //create reversed links for the reachabilities
+ for (n = 0; n < settings->numreachableareas && n < 128; n++)
+ {
+ //reachability link
+ reach = &aasworld.reachability[settings->firstreachablearea + n];
+ //
+ 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
+} //end of the function AAS_CreateReversedReachability
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end)
+{
+ int intdist;
+ float dist;
+ vec3_t dir;
+
+ VectorSubtract(start, end, dir);
+ dist = VectorLength(dir);
+ //if crouch only area
+ if (AAS_AreaCrouch(areanum)) dist *= DISTANCEFACTOR_CROUCH;
+ //if swim area
+ else if (AAS_AreaSwim(areanum)) dist *= DISTANCEFACTOR_SWIM;
+ //normal walk area
+ else dist *= DISTANCEFACTOR_WALK;
+ //
+ intdist = (int) dist;
+ //make sure the distance isn't zero
+ if (intdist <= 0) intdist = 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) FreeMemory(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 *
+ PAD(revreach->numlinks, sizeof(long)) * sizeof(unsigned short);
+ } //end for
+ //allocate memory for the area travel times
+ ptr = (char *) GetClearedMemory(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];
+ //
+ aasworld.areatraveltimes[i] = (unsigned short **) ptr;
+ ptr += settings->numreachableareas * sizeof(unsigned short *);
+ //
+ for (l = 0; l < settings->numreachableareas; l++)
+ {
+ aasworld.areatraveltimes[i][l] = (unsigned short *) ptr;
+ ptr += PAD(revreach->numlinks, sizeof(long)) * sizeof(unsigned short);
+ //reachability link
+ reach = &aasworld.reachability[settings->firstreachablearea + l];
+ //
+ 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
+} //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) FreeMemory(aasworld.portalmaxtraveltimes);
+
+ aasworld.portalmaxtraveltimes = (int *) GetClearedMemory(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: -
+//===========================================================================
+/*
+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: -
+//===========================================================================
+int AAS_FreeOldestCache(void)
+{
+ int clusterareanum;
+ aas_routingcache_t *cache;
+
+ for (cache = aasworld.oldestcache; cache; cache = cache->time_next) {
+ // never free area cache leading towards a portal
+ if (cache->type == CACHETYPE_AREA && aasworld.areasettings[cache->areanum].cluster < 0) {
+ continue;
+ }
+ break;
+ }
+ if (cache) {
+ // unlink the cache
+ if (cache->type == CACHETYPE_AREA) {
+ //number of the area in the cluster
+ clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum);
+ // unlink from cluster area cache
+ if (cache->prev) cache->prev->next = cache->next;
+ else aasworld.clusterareacache[cache->cluster][clusterareanum] = cache->next;
+ if (cache->next) cache->next->prev = cache->prev;
+ }
+ else {
+ // unlink from portal cache
+ if (cache->prev) cache->prev->next = cache->next;
+ else aasworld.portalcache[cache->areanum] = cache->next;
+ if (cache->next) cache->next->prev = cache->prev;
+ }
+ AAS_FreeRoutingCache(cache);
+ return qtrue;
+ }
+ return qfalse;
+} //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 *) GetClearedMemory(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
+ FreeMemory(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 *) GetClearedMemory(
+ 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
+ FreeMemory(aasworld.portalcache);
+ aasworld.portalcache = NULL;
+} //end of the function AAS_FreeAllPortalCache
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_InitPortalCache(void)
+{
+ //
+ aasworld.portalcache = (aas_routingcache_t **) GetClearedMemory(
+ aasworld.numareas * sizeof(aas_routingcache_t *));
+} //end of the function AAS_InitPortalCache
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_InitRoutingUpdate(void)
+{
+ int i, maxreachabilityareas;
+
+ //free routing update fields if already existing
+ if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate);
+ //
+ maxreachabilityareas = 0;
+ for (i = 0; i < aasworld.numclusters; i++)
+ {
+ if (aasworld.clusters[i].numreachabilityareas > maxreachabilityareas)
+ {
+ maxreachabilityareas = aasworld.clusters[i].numreachabilityareas;
+ } //end if
+ } //end for
+ //allocate memory for the routing update fields
+ aasworld.areaupdate = (aas_routingupdate_t *) GetClearedMemory(
+ maxreachabilityareas * sizeof(aas_routingupdate_t));
+ //
+ if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate);
+ //allocate memory for the portal update fields
+ aasworld.portalupdate = (aas_routingupdate_t *) GetClearedMemory(
+ (aasworld.numportals+1) * sizeof(aas_routingupdate_t));
+} //end of the function AAS_InitRoutingUpdate
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_CreateAllRoutingCache(void)
+{
+ int i, j, t;
+
+ aasworld.initialized = qtrue;
+ botimport.Print(PRT_MESSAGE, "AAS_CreateAllRoutingCache\n");
+ for (i = 1; i < aasworld.numareas; i++)
+ {
+ if (!AAS_AreaReachability(i)) continue;
+ for (j = 1; j < aasworld.numareas; j++)
+ {
+ if (i == j) continue;
+ if (!AAS_AreaReachability(j)) continue;
+ t = AAS_AreaTravelTimeToGoalArea(i, aasworld.areas[i].center, j, TFL_DEFAULT);
+ //Log_Write("traveltime from %d to %d is %d", i, j, t);
+ } //end for
+ } //end for
+ aasworld.initialized = qfalse;
+} //end of the function AAS_CreateAllRoutingCache
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+
+//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 numportalcache;
+ int numareacache;
+} routecacheheader_t;
+
+#define RCID (('C'<<24)+('R'<<16)+('E'<<8)+'M')
+#define RCVERSION 2
+
+//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, totalsize;
+ aas_routingcache_t *cache;
+ aas_cluster_t *cluster;
+ fileHandle_t fp;
+ char filename[MAX_QPATH];
+ routecacheheader_t routecacheheader;
+
+ 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.numportalcache = numportalcache;
+ routecacheheader.numareacache = numareacache;
+ //write the header
+ botimport.FS_Write(&routecacheheader, sizeof(routecacheheader_t), fp);
+ //
+ totalsize = 0;
+ //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);
+ totalsize += cache->size;
+ } //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);
+ totalsize += cache->size;
+ } //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, aasworld.decompressedvis );
+ botimport.FS_Write(&size, sizeof(int), fp);
+ botimport.FS_Write(aasworld.decompressedvis, size, fp);
+ }
+ */
+ //
+ botimport.FS_FCloseFile(fp);
+ botimport.Print(PRT_MESSAGE, "\nroute cache written to %s\n", filename);
+ botimport.Print(PRT_MESSAGE, "written %d bytes of routing cache\n", totalsize);
+} //end of the function AAS_WriteRouteCache
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+aas_routingcache_t *AAS_ReadCache(fileHandle_t fp)
+{
+ int size;
+ aas_routingcache_t *cache;
+
+ botimport.FS_Read(&size, sizeof(size), fp);
+ cache = (aas_routingcache_t *) GetMemory(size);
+ cache->size = size;
+ botimport.FS_Read((unsigned char *)cache + sizeof(size), size - sizeof(size), fp);
+ cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) +
+ (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2;
+ return cache;
+} //end of the function AAS_ReadCache
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_ReadRouteCache(void)
+{
+ int i, clusterareanum;//, size;
+ fileHandle_t fp;
+ 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)
+ {
+ AAS_Error("%s is not a route cache dump\n");
+ return qfalse;
+ } //end if
+ if (routecacheheader.version != RCVERSION)
+ {
+ AAS_Error("route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION);
+ return qfalse;
+ } //end if
+ if (routecacheheader.numareas != aasworld.numareas)
+ {
+ //AAS_Error("route cache dump has wrong number of areas\n");
+ return qfalse;
+ } //end if
+ if (routecacheheader.numclusters != aasworld.numclusters)
+ {
+ //AAS_Error("route cache dump has wrong number of clusters\n");
+ return qfalse;
+ } //end if
+ if (routecacheheader.areacrc !=
+ CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas ))
+ {
+ //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 ))
+ {
+ //AAS_Error("route cache dump cluster CRC incorrect\n");
+ return qfalse;
+ } //end if
+ //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 );
+ }
+ }
+ */
+ //
+ botimport.FS_FCloseFile(fp);
+ return qtrue;
+} //end of the function AAS_ReadRouteCache
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#define MAX_REACHABILITYPASSAREAS 32
+
+void AAS_InitReachabilityAreas(void)
+{
+ int i, j, numareas, areas[MAX_REACHABILITYPASSAREAS];
+ int numreachareas;
+ aas_reachability_t *reach;
+ vec3_t start, end;
+
+ if (aasworld.reachabilityareas)
+ FreeMemory(aasworld.reachabilityareas);
+ if (aasworld.reachabilityareaindex)
+ FreeMemory(aasworld.reachabilityareaindex);
+
+ aasworld.reachabilityareas = (aas_reachabilityareas_t *)
+ GetClearedMemory(aasworld.reachabilitysize * sizeof(aas_reachabilityareas_t));
+ aasworld.reachabilityareaindex = (int *)
+ GetClearedMemory(aasworld.reachabilitysize * MAX_REACHABILITYPASSAREAS * sizeof(int));
+ numreachareas = 0;
+ for (i = 0; i < aasworld.reachabilitysize; i++)
+ {
+ reach = &aasworld.reachability[i];
+ numareas = 0;
+ switch(reach->traveltype & TRAVELTYPE_MASK)
+ {
+ //trace areas from start to end
+ case TRAVEL_BARRIERJUMP:
+ case TRAVEL_WATERJUMP:
+ VectorCopy(reach->start, end);
+ end[2] = reach->end[2];
+ numareas = AAS_TraceAreas(reach->start, end, areas, NULL, MAX_REACHABILITYPASSAREAS);
+ break;
+ case TRAVEL_WALKOFFLEDGE:
+ VectorCopy(reach->end, start);
+ start[2] = reach->start[2];
+ numareas = AAS_TraceAreas(start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS);
+ break;
+ case TRAVEL_GRAPPLEHOOK:
+ numareas = AAS_TraceAreas(reach->start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS);
+ break;
+
+ //trace arch
+ case TRAVEL_JUMP: break;
+ case TRAVEL_ROCKETJUMP: break;
+ case TRAVEL_BFGJUMP: break;
+ case TRAVEL_JUMPPAD: break;
+
+ //trace from reach->start to entity center, along entity movement
+ //and from entity center to reach->end
+ case TRAVEL_ELEVATOR: break;
+ case TRAVEL_FUNCBOB: break;
+
+ //no areas in between
+ case TRAVEL_WALK: break;
+ case TRAVEL_CROUCH: break;
+ case TRAVEL_LADDER: break;
+ case TRAVEL_SWIM: break;
+ case TRAVEL_TELEPORT: break;
+ default: break;
+ } //end switch
+ aasworld.reachabilityareas[i].firstarea = numreachareas;
+ aasworld.reachabilityareas[i].numareas = numareas;
+ for (j = 0; j < numareas; j++)
+ {
+ aasworld.reachabilityareaindex[numreachareas++] = areas[j];
+ } //end for
+ } //end for
+} //end of the function AAS_InitReachabilityAreas
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_InitRouting(void)
+{
+ AAS_InitTravelFlagFromType();
+ //
+ AAS_InitAreaContentsTravelFlags();
+ //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();
+ //get the areas reachabilities go through
+ AAS_InitReachabilityAreas();
+ //
+#ifdef ROUTING_DEBUG
+ numareacacheupdates = 0;
+ numportalcacheupdates = 0;
+#endif //ROUTING_DEBUG
+ //
+ routingcachesize = 0;
+ max_routingcachesize = 1024 * (int) LibVarValue("max_routingcache", "4096");
+ // read any routing cache if available
+ AAS_ReadRouteCache();
+} //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 cached travel times within areas
+ if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes);
+ aasworld.areatraveltimes = NULL;
+ // free cached maximum travel time through cluster portals
+ if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes);
+ aasworld.portalmaxtraveltimes = NULL;
+ // free reversed reachability links
+ if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability);
+ aasworld.reversedreachability = NULL;
+ // free routing algorithm memory
+ if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate);
+ aasworld.areaupdate = NULL;
+ if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate);
+ aasworld.portalupdate = NULL;
+ // free lists with areas the reachabilities go through
+ if (aasworld.reachabilityareas) FreeMemory(aasworld.reachabilityareas);
+ aasworld.reachabilityareas = NULL;
+ // free the reachability area index
+ if (aasworld.reachabilityareaindex) FreeMemory(aasworld.reachabilityareaindex);
+ aasworld.reachabilityareaindex = NULL;
+ // free area contents travel flags look up table
+ if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags);
+ aasworld.areacontentstravelflags = NULL;
+} //end of the function AAS_FreeRoutingCaches
+//===========================================================================
+// 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]; //NOTE: not more than 128 reachabilities per area allowed
+ 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;
+ //
+ aasworld.frameroutingupdates++;
+ //clear the routing update fields
+// Com_Memset(aasworld.areaupdate, 0, aasworld.numareas * sizeof(aas_routingupdate_t));
+ //
+ badtravelflags = ~areacache->travelflags;
+ //
+ clusterareanum = AAS_ClusterAreaNum(areacache->cluster, areacache->areanum);
+ if (clusterareanum >= numreachabilityareas) return;
+ //
+ Com_Memset(startareatraveltimes, 0, sizeof(startareatraveltimes));
+ //
+ curupdate = &aasworld.areaupdate[clusterareanum];
+ curupdate->areanum = areacache->areanum;
+ //VectorCopy(areacache->origin, curupdate->start);
+ curupdate->areatraveltimes = startareatraveltimes;
+ 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
+ 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 (AAS_TravelFlagForType_inline(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_AreaContentsTravelFlags_inline(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 (!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)
+ {
+ // we add the update to the end of the list
+ // we could also use a B+ tree to have a real sorted list
+ // on travel time which makes for faster routing updates
+ 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)
+{
+ 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];
+ //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)
+ {
+ 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
+ else
+ {
+ AAS_UnlinkCache(cache);
+ } //end else
+ //the cache has been accessed
+ cache->time = AAS_RoutingTime();
+ cache->type = CACHETYPE_AREA;
+ AAS_LinkCache(cache);
+ 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
+// Com_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;
+ //if the start area is a cluster portal, store the travel time for that portal
+ clusternum = aasworld.areasettings[portalcache->areanum].cluster;
+ if (clusternum < 0)
+ {
+ portalcache->traveltimes[-clusternum] = portalcache->starttraveltime;
+ } //end if
+ //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
+ 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);
+ //take all portals of the cluster
+ for (i = 0; i < cluster->numportals; i++)
+ {
+ portalnum = aasworld.portalindex[cluster->firstportal + i];
+ portal = &aasworld.portals[portalnum];
+ //if this is the portal of the current update continue
+ if (portal->areanum == curupdate->areanum) continue;
+ //
+ 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;
+ 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 the actual portal area for the next update
+ nextupdate->tmptraveltime = t + aasworld.portalmaxtraveltimes[portalnum];
+ if (!nextupdate->inlist)
+ {
+ // we add the update to the end of the list
+ // we could also use a B+ tree to have a real sorted list
+ // on travel time which makes for faster routing updates
+ 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
+ else
+ {
+ AAS_UnlinkCache(cache);
+ } //end else
+ //the cache has been accessed
+ cache->time = AAS_RoutingTime();
+ cache->type = CACHETYPE_PORTAL;
+ AAS_LinkCache(cache);
+ 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;
+
+ if (!aasworld.initialized) return qfalse;
+
+ if (areanum == goalareanum)
+ {
+ *traveltime = 1;
+ *reachnum = 0;
+ return qtrue;
+ }
+ //
+ 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(AvailableMemory() < 1 * 1024 * 1024) {
+ if (!AAS_FreeOldestCache()) break;
+ }
+ //
+ if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goalareanum))
+ {
+ travelflags |= TFL_DONOTENTER;
+ } //end if
+ //NOTE: the number of routing updates is limited per frame
+ /*
+ if (aasworld.frameroutingupdates > MAX_FRAMEROUTINGUPDATES)
+ {
+#ifdef DEBUG
+ //Log_Write("WARNING: AAS_AreaTravelTimeToGoalArea: frame routing updates overflowed");
+#endif
+ return 0;
+ } //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;
+ } //end if
+ } //end if
+ //check if the goalarea is a portal of the area cluster
+ else if (clusternum > 0 && goalclusternum < 0)
+ {
+ portal = &aasworld.portals[-goalclusternum];
+ if (portal->frontcluster == clusternum ||
+ portal->backcluster == clusternum)
+ {
+ goalclusternum = clusternum;
+ } //end if
+ } //end if
+ //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);
+ //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 0;
+ //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;
+ } //end if
+ //
+ besttime = 0;
+ bestreachnum = -1;
+ //the cluster the area is in
+ cluster = &aasworld.clusters[clusternum];
+ //find the portal of the area cluster leading towards the goal area
+ for (i = 0; i < cluster->numportals; i++)
+ {
+ portalnum = aasworld.portalindex[cluster->firstportal + i];
+ //if the goal area isn't reachable from the portal
+ if (!portalcache->traveltimes[portalnum]) continue;
+ //
+ portal = &aasworld.portals[portalnum];
+ //get the cache of the portal area
+ areacache = AAS_GetAreaRoutingCache(clusternum, portal->areanum, travelflags);
+ //current area inside the current cluster
+ clusterareanum = AAS_ClusterAreaNum(clusternum, areanum);
+ //if the area is NOT a reachability area
+ if (clusterareanum >= cluster->numreachabilityareas) continue;
+ //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 portal area
+ // because we can't directly calculate the exact travel time
+ // to be more specific we don't know which reachability was used to travel
+ // into the portal area
+ t += aasworld.portalmaxtraveltimes[portalnum];
+ //
+ if (origin)
+ {
+ *reachnum = aasworld.areasettings[areanum].firstreachablearea +
+ areacache->reachabilities[clusterareanum];
+ 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
+ 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_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
+//===========================================================================
+// predict the route and stop on one of the stop events
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin,
+ int goalareanum, int travelflags, int maxareas, int maxtime,
+ int stopevent, int stopcontents, int stoptfl, int stopareanum)
+{
+ int curareanum, reachnum, i, j, testareanum;
+ vec3_t curorigin;
+ aas_reachability_t *reach;
+ aas_reachabilityareas_t *reachareas;
+
+ //init output
+ route->stopevent = RSE_NONE;
+ route->endarea = goalareanum;
+ route->endcontents = 0;
+ route->endtravelflags = 0;
+ VectorCopy(origin, route->endpos);
+ route->time = 0;
+
+ curareanum = areanum;
+ VectorCopy(origin, curorigin);
+
+ for (i = 0; curareanum != goalareanum && (!maxareas || i < maxareas) && i < aasworld.numareas; i++)
+ {
+ reachnum = AAS_AreaReachabilityToGoalArea(curareanum, curorigin, goalareanum, travelflags);
+ if (!reachnum)
+ {
+ route->stopevent = RSE_NOROUTE;
+ return qfalse;
+ } //end if
+ reach = &aasworld.reachability[reachnum];
+ //
+ if (stopevent & RSE_USETRAVELTYPE)
+ {
+ if (AAS_TravelFlagForType_inline(reach->traveltype) & stoptfl)
+ {
+ route->stopevent = RSE_USETRAVELTYPE;
+ route->endarea = curareanum;
+ route->endcontents = aasworld.areasettings[curareanum].contents;
+ route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype);
+ VectorCopy(reach->start, route->endpos);
+ return qtrue;
+ } //end if
+ if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & stoptfl)
+ {
+ route->stopevent = RSE_USETRAVELTYPE;
+ route->endarea = reach->areanum;
+ route->endcontents = aasworld.areasettings[reach->areanum].contents;
+ route->endtravelflags = AAS_AreaContentsTravelFlags_inline(reach->areanum);
+ VectorCopy(reach->end, route->endpos);
+ route->time += AAS_AreaTravelTime(areanum, origin, reach->start);
+ route->time += reach->traveltime;
+ return qtrue;
+ } //end if
+ } //end if
+ reachareas = &aasworld.reachabilityareas[reachnum];
+ for (j = 0; j < reachareas->numareas + 1; j++)
+ {
+ if (j >= reachareas->numareas)
+ testareanum = reach->areanum;
+ else
+ testareanum = aasworld.reachabilityareaindex[reachareas->firstarea + j];
+ if (stopevent & RSE_ENTERCONTENTS)
+ {
+ if (aasworld.areasettings[testareanum].contents & stopcontents)
+ {
+ route->stopevent = RSE_ENTERCONTENTS;
+ route->endarea = testareanum;
+ route->endcontents = aasworld.areasettings[testareanum].contents;
+ VectorCopy(reach->end, route->endpos);
+ route->time += AAS_AreaTravelTime(areanum, origin, reach->start);
+ route->time += reach->traveltime;
+ return qtrue;
+ } //end if
+ } //end if
+ if (stopevent & RSE_ENTERAREA)
+ {
+ if (testareanum == stopareanum)
+ {
+ route->stopevent = RSE_ENTERAREA;
+ route->endarea = testareanum;
+ route->endcontents = aasworld.areasettings[testareanum].contents;
+ VectorCopy(reach->start, route->endpos);
+ return qtrue;
+ } //end if
+ } //end if
+ } //end for
+
+ route->time += AAS_AreaTravelTime(areanum, origin, reach->start);
+ route->time += reach->traveltime;
+ route->endarea = reach->areanum;
+ route->endcontents = aasworld.areasettings[reach->areanum].contents;
+ route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype);
+ VectorCopy(reach->end, route->endpos);
+ //
+ curareanum = reach->areanum;
+ VectorCopy(reach->end, curorigin);
+ //
+ if (maxtime && route->time > maxtime)
+ break;
+ } //end while
+ if (curareanum != goalareanum)
+ return qfalse;
+ return qtrue;
+} //end of the function AAS_PredictRoute
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_BridgeWalkable(int areanum)
+{
+ return qfalse;
+} //end of the function AAS_BridgeWalkable
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach)
+{
+ if (!aasworld.initialized)
+ {
+ Com_Memset(reach, 0, sizeof(aas_reachability_t));
+ return;
+ } //end if
+ if (num < 0 || num >= aasworld.reachabilitysize)
+ {
+ Com_Memset(reach, 0, sizeof(aas_reachability_t));
+ return;
+ } //end if
+ Com_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 & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR)
+ {
+ if (aasworld.reachability[i].facenum == modelnum) return i;
+ } //end if
+ else if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == 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 && trace.fraction < 1 && 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
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_AreaVisible(int srcarea, int destarea)
+{
+ return qfalse;
+} //end of the function AAS_AreaVisible
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+float DistancePointToLine(vec3_t v1, vec3_t v2, vec3_t point)
+{
+ vec3_t vec, p2;
+
+ AAS_ProjectPointOntoVector(point, v1, v2, p2);
+ VectorSubtract(point, p2, vec);
+ return VectorLength(vec);
+} //end of the function DistancePointToLine
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags)
+{
+ int i, j, nextareanum, badtravelflags, numreach, bestarea;
+ unsigned short int t, besttraveltime;
+ static unsigned short int *hidetraveltimes;
+ aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate;
+ aas_reachability_t *reach;
+ float dist1, dist2;
+ vec3_t v1, v2, p;
+ qboolean startVisible;
+
+ //
+ if (!hidetraveltimes)
+ {
+ hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld.numareas * sizeof(unsigned short int));
+ } //end if
+ else
+ {
+ Com_Memset(hidetraveltimes, 0, aasworld.numareas * sizeof(unsigned short int));
+ } //end else
+ besttraveltime = 0;
+ bestarea = 0;
+ //assume visible
+ startVisible = qtrue;
+ //
+ badtravelflags = ~travelflags;
+ //
+ curupdate = &aasworld.areaupdate[areanum];
+ curupdate->areanum = areanum;
+ VectorCopy(origin, curupdate->start);
+ 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 list
+ 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 (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue;
+ //
+ if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) 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;
+ //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;
+
+ //avoid going near the enemy
+ AAS_ProjectPointOntoVector(enemyorigin, curupdate->start, reach->end, p);
+ for (j = 0; j < 3; j++)
+ if ((p[j] > curupdate->start[j] && p[j] > reach->end[j]) ||
+ (p[j] < curupdate->start[j] && p[j] < reach->end[j]))
+ 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 (dist2 < 40) continue;
+ //
+ VectorSubtract(enemyorigin, curupdate->start, v1);
+ dist1 = VectorLength(v1);
+ //
+ if (dist2 < dist1)
+ {
+ t += (dist1 - dist2) * 10;
+ }
+ // if we weren't visible when starting, make sure we don't move into their view
+ if (!startVisible && AAS_AreaVisible(enemyareanum, nextareanum)) {
+ continue;
+ }
+ //
+ if (besttraveltime && t >= besttraveltime) continue;
+ //
+ if (!hidetraveltimes[nextareanum] ||
+ hidetraveltimes[nextareanum] > t)
+ {
+ //if the nextarea is not visible from the enemy area
+ if (!AAS_AreaVisible(enemyareanum, nextareanum))
+ {
+ besttraveltime = t;
+ bestarea = nextareanum;
+ } //end if
+ 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
+ return bestarea;
+} //end of the function AAS_NearestHideArea
diff --git a/src/botlib/be_aas_route.h b/src/botlib/be_aas_route.h
new file mode 100644
index 00000000..8805c66f
--- /dev/null
+++ b/src/botlib/be_aas_route.h
@@ -0,0 +1,67 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_route.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/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_WriteRouteCache(void);
+//
+void AAS_RoutingInfo(void);
+#endif //AASINTERN
+
+//returns the travel flag for the given travel type
+int AAS_TravelFlagForType(int traveltype);
+//return the travel flag(s) for traveling through this area
+int AAS_AreaContentsTravelFlags(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);
+//enable or disable an area for routing
+int AAS_EnableRoutingArea(int areanum, int enable);
+//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);
+//predict a route up to a stop event
+int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin,
+ int goalareanum, int travelflags, int maxareas, int maxtime,
+ int stopevent, int stopcontents, int stoptfl, int stopareanum);
+
+
diff --git a/src/botlib/be_aas_routealt.c b/src/botlib/be_aas_routealt.c
new file mode 100644
index 00000000..e4f79eec
--- /dev/null
+++ b/src/botlib/be_aas_routealt.c
@@ -0,0 +1,240 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_routealt.c
+ *
+ * desc: AAS
+ *
+ * $Archive: /MissionPack/code/botlib/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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "be_aas_def.h"
+
+#define ENABLE_ALTROUTING
+//#define ALTROUTE_DEBUG
+
+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_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags,
+ aas_altroutegoal_t *altroutegoals, int maxaltroutegoals,
+ int type)
+{
+#ifndef ENABLE_ALTROUTING
+ return 0;
+#else
+ int i, j, bestareanum;
+ int numaltroutegoals, nummidrangeareas;
+ int starttime, goaltime, goaltraveltime;
+ float dist, bestdist;
+ vec3_t mid, dir;
+#ifdef ALTROUTE_DEBUG
+ int startmillisecs;
+
+ startmillisecs = Sys_MilliSeconds();
+#endif
+
+ if (!startareanum || !goalareanum)
+ return 0;
+ //travel time towards the goal area
+ goaltraveltime = AAS_AreaTravelTimeToGoalArea(startareanum, start, goalareanum, travelflags);
+ //clear the midrange areas
+ Com_Memset(midrangeareas, 0, aasworld.numareas * sizeof(midrangearea_t));
+ numaltroutegoals = 0;
+ //
+ nummidrangeareas = 0;
+ //
+ for (i = 1; i < aasworld.numareas; i++)
+ {
+ //
+ if (!(type & ALTROUTEGOAL_ALL))
+ {
+ if (!(type & ALTROUTEGOAL_CLUSTERPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)))
+ {
+ if (!(type & ALTROUTEGOAL_VIEWPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL)))
+ {
+ continue;
+ } //end if
+ } //end if
+ } //end if
+ //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 > (float) 1.1 * 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 > (float) 0.8 * 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
+ //now we've got an area for an alternative route
+ //FIXME: add alternative goal origin
+ VectorCopy(aasworld.areas[bestareanum].center, 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 ALTROUTE_DEBUG
+ AAS_ShowAreaPolygons(bestareanum, 1, qtrue);
+#endif
+ //don't return more than the maximum alternative route goals
+ if (numaltroutegoals >= maxaltroutegoals) break;
+ } //end for
+#ifdef ALTROUTE_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
+} //end of the function AAS_ShutdownAlternativeRouting
diff --git a/src/botlib/be_aas_routealt.h b/src/botlib/be_aas_routealt.h
new file mode 100644
index 00000000..160966a9
--- /dev/null
+++ b/src/botlib/be_aas_routealt.h
@@ -0,0 +1,40 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_routealt.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_routealt.h $
+ *
+ *****************************************************************************/
+
+#ifdef AASINTERN
+void AAS_InitAlternativeRouting(void);
+void AAS_ShutdownAlternativeRouting(void);
+#endif //AASINTERN
+
+
+int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags,
+ aas_altroutegoal_t *altroutegoals, int maxaltroutegoals,
+ int type);
diff --git a/src/botlib/be_aas_sample.c b/src/botlib/be_aas_sample.c
new file mode 100644
index 00000000..92b75174
--- /dev/null
+++ b/src/botlib/be_aas_sample.c
@@ -0,0 +1,1394 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_sample.c
+ *
+ * desc: AAS environment sampling
+ *
+ * $Archive: /MissionPack/code/botlib/be_aas_sample.c $
+ *
+ *****************************************************************************/
+
+#include "../qcommon/q_shared.h"
+#include "l_memory.h"
+#include "l_script.h"
+#include "l_precomp.h"
+#include "l_struct.h"
+#ifndef BSPC
+#include "l_libvar.h"
+#endif
+#include "aasfile.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_interface.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;
+
+int numaaslinks;
+
+//===========================================================================
+//
+// 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}, {-15, -15, -24}, {-15, -15, -24}};
+ vec3_t boxmaxs[3] = {{0, 0, 0}, { 15, 15, 32}, { 15, 15, 8}};
+
+ 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)
+ {
+#ifdef BSPC
+ max_aaslinks = 6144;
+#else
+ max_aaslinks = (int) LibVarValue("max_aaslinks", "6144");
+#endif
+ if (max_aaslinks < 0) max_aaslinks = 0;
+ aasworld.linkheapsize = max_aaslinks;
+ aasworld.linkheap = (aas_link_t *) GetHunkMemory(max_aaslinks * sizeof(aas_link_t));
+ } //end if
+ //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];
+ //
+ numaaslinks = max_aaslinks;
+} //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)
+ {
+#ifndef BSPC
+ if (bot_developer)
+#endif
+ {
+ botimport.Print(PRT_FATAL, "empty aas link heap\n");
+ } //end if
+ return NULL;
+ } //end if
+ if (aasworld.freelinks) aasworld.freelinks = aasworld.freelinks->next_ent;
+ if (aasworld.freelinks) aasworld.freelinks->prev_ent = NULL;
+ numaaslinks--;
+ 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;
+ numaaslinks++;
+} //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 point)
+{
+ int nodenum;
+ vec_t dist;
+ aas_node_t *node;
+ aas_plane_t *plane;
+
+ 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;
+ 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];
+ } //end while
+ if (!nodenum)
+ {
+#ifdef AAS_SAMPLE_DEBUG
+ botimport.Print(PRT_MESSAGE, "in solid\n");
+#endif //AAS_SAMPLE_DEBUG
+ return 0;
+ } //end if
+ return -nodenum;
+} //end of the function AAS_PointAreaNum
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_PointReachabilityAreaIndex( vec3_t origin )
+{
+ int areanum, cluster, i, index;
+
+ if (!aasworld.initialized)
+ return 0;
+
+ if ( !origin )
+ {
+ index = 0;
+ for (i = 0; i < aasworld.numclusters; i++)
+ {
+ index += aasworld.clusters[i].numreachabilityareas;
+ } //end for
+ return index;
+ } //end if
+
+ areanum = AAS_PointAreaNum( origin );
+ if ( !areanum || !AAS_AreaReachability(areanum) )
+ return 0;
+ cluster = aasworld.areasettings[areanum].cluster;
+ areanum = aasworld.areasettings[areanum].clusterareanum;
+ if (cluster < 0)
+ {
+ cluster = aasworld.portals[-cluster].frontcluster;
+ areanum = aasworld.portals[-cluster].clusterareanum[0];
+ } //end if
+
+ index = 0;
+ for (i = 0; i < cluster; i++)
+ {
+ index += aasworld.clusters[i].numreachabilityareas;
+ } //end for
+ index += areanum;
+ return index;
+} //end of the function AAS_PointReachabilityAreaIndex
+//===========================================================================
+//
+// 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
+//===========================================================================
+// calculates the minimum distance between the origin of the box and the
+// given plane when both will collide on the given side of the plane
+//
+// normal = normal vector of plane to calculate distance from
+// mins = minimums of box relative to origin
+// maxs = maximums of box relative to origin
+// side = side of the plane we want to calculate the distance from
+// 0 normal vector side
+// 1 not normal vector side
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+vec_t AAS_BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side)
+{
+ vec3_t v1, v2;
+ int i;
+
+ //swap maxs and mins when on the other side of the plane
+ if (side)
+ {
+ //get a point of the box that would be one of the first
+ //to collide with the plane
+ for (i = 0; i < 3; i++)
+ {
+ if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i];
+ else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i];
+ else v1[i] = 0;
+ } //end for
+ } //end if
+ else
+ {
+ //get a point of the box that would be one of the first
+ //to collide with the plane
+ for (i = 0; i < 3; i++)
+ {
+ if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i];
+ else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i];
+ else v1[i] = 0;
+ } //end for
+ } //end else
+ //
+ VectorCopy(normal, v2);
+ VectorInverse(v2);
+// VectorNegate(normal, v2);
+ return DotProduct(v1, v2);
+} //end of the function AAS_BoxOriginDistanceFromPlane
+//===========================================================================
+//
+// 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);
+
+ Com_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
+ Com_Memset(&trace, 0, sizeof(aas_trace_t));
+
+ 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 = 0;
+ 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;
+ VectorClear(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 = 0;
+ 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;
+ VectorClear(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 = 0;
+ 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
+ // bk010221 - old location of FPE hack and divide by zero expression
+ //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;
+ // bk010221 - new location of divide by zero (see above)
+ if ( front == back ) front -= 0.001f; // bk0101022 - hack/FPE
+ //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); // bk010221
+ //
+ if (frac < 0)
+ frac = 0.001f; //0
+ else if (frac > 1)
+ frac = 0.999f; //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.01f)) 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.01f)) 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;
+ } //end if
+
+ 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: -
+//===========================================================================
+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
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AAS_AreaInfo( int areanum, aas_areainfo_t *info )
+{
+ aas_areasettings_t *settings;
+ if (!info)
+ return 0;
+ if (areanum <= 0 || areanum >= aasworld.numareas)
+ {
+ botimport.Print(PRT_ERROR, "AAS_AreaInfo: areanum %d out of range\n", areanum);
+ return 0;
+ } //end if
+ settings = &aasworld.areasettings[areanum];
+ info->cluster = settings->cluster;
+ info->contents = settings->contents;
+ info->flags = settings->areaflags;
+ info->presencetype = settings->presencetype;
+ VectorCopy(aasworld.areas[areanum].mins, info->mins);
+ VectorCopy(aasworld.areas[areanum].maxs, info->maxs);
+ VectorCopy(aasworld.areas[areanum].center, info->center);
+ return sizeof(aas_areainfo_t);
+} //end of the function AAS_AreaInfo
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+aas_plane_t *AAS_PlaneFromNum(int planenum)
+{
+ if (!aasworld.loaded) return NULL;
+
+ return &aasworld.planes[planenum];
+} //end of the function AAS_PlaneFromNum
diff --git a/src/botlib/be_aas_sample.h b/src/botlib/be_aas_sample.h
new file mode 100644
index 00000000..ed6c2371
--- /dev/null
+++ b/src/botlib/be_aas_sample.h
@@ -0,0 +1,69 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_aas_sample.h
+ *
+ * desc: AAS
+ *
+ * $Archive: /source/code/botlib/be_aas_sample.h $
+ *
+ *****************************************************************************/
+
+#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 areas the bounding box is in
+int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas);
+//return area information
+int AAS_AreaInfo( int areanum, aas_areainfo_t *info );
+//returns the area the point is in
+int AAS_PointAreaNum(vec3_t point);
+//
+int AAS_PointReachabilityAreaIndex( vec3_t point );
+//returns the plane the given face is in
+void AAS_FacePlane(int facenum, vec3_t normal, float *dist);
+
diff --git a/src/botlib/be_ai_char.c b/src/botlib/be_ai_char.c
new file mode 100644
index 00000000..51520f71
--- /dev/null
+++ b/src/botlib/be_ai_char.c
@@ -0,0 +1,790 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_char.c
+ *
+ * desc: bot characters
+ *
+ * $Archive: /MissionPack/code/botlib/be_ai_char.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "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];
+ float 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
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ source = LoadSourceFile(charfile);
+ 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, float skill)
+{
+ int handle;
+
+ for (handle = 1; handle <= MAX_CLIENTS; handle++)
+ {
+ if ( !botcharacters[handle] ) continue;
+ if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 &&
+ (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) )
+ {
+ return handle;
+ } //end if
+ } //end for
+ return 0;
+} //end of the function BotFindCachedCharacter
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int BotLoadCachedCharacter(char *charfile, float skill, int reload)
+{
+ int handle, cachedhandle, intskill;
+ 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 %f from %s\n", skill, charfile);
+ return cachedhandle;
+ } //end if
+ } //end else
+ //
+ intskill = (int) (skill + 0.5);
+ //try to load the character with the given skill
+ ch = BotLoadCharacterFromFile(charfile, intskill);
+ if (ch)
+ {
+ botcharacters[handle] = ch;
+ //
+ botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile);
+#ifdef DEBUG
+ if (bot_developer)
+ {
+ botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile);
+ } //end if
+#endif //DEBUG
+ return handle;
+ } //end if
+ //
+ botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile);
+ //
+ if (!reload)
+ {
+ //try to load a cached default character with the given skill
+ cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill);
+ if (cachedhandle)
+ {
+ botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile);
+ return cachedhandle;
+ } //end if
+ } //end if
+ //try to load the default character with the given skill
+ ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill);
+ if (ch)
+ {
+ botcharacters[handle] = ch;
+ botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, 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 %f 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 %f 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 %f 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 %f 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, float 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, float 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 - ch1->skill) / (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, float skill)
+{
+ int firstskill, secondskill, handle;
+
+ //make sure the skill is in the valid range
+ if (skill < 1.0) skill = 1.0;
+ else if (skill > 5.0) skill = 5.0;
+ //skill 1, 4 and 5 should be available in the character files
+ if (skill == 1.0 || skill == 4.0 || skill == 5.0)
+ {
+ return BotLoadCharacterSkill(charfile, skill);
+ } //end if
+ //check if there's a cached skill
+ handle = BotFindCachedCharacter(charfile, skill);
+ if (handle)
+ {
+ botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile);
+ return handle;
+ } //end if
+ if (skill < 4.0)
+ {
+ //load skill 1 and 4
+ firstskill = BotLoadCharacterSkill(charfile, 1);
+ if (!firstskill) return 0;
+ secondskill = BotLoadCharacterSkill(charfile, 4);
+ if (!secondskill) return firstskill;
+ } //end if
+ else
+ {
+ //load skill 4 and 5
+ firstskill = BotLoadCharacterSkill(charfile, 4);
+ if (!firstskill) return 0;
+ secondskill = BotLoadCharacterSkill(charfile, 5);
+ if (!secondskill) return firstskill;
+ } //end else
+ //interpolate between the two skills
+ handle = BotInterpolateCharacters(firstskill, secondskill, 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_char.h b/src/botlib/be_ai_char.h
new file mode 100644
index 00000000..719d68f6
--- /dev/null
+++ b/src/botlib/be_ai_char.h
@@ -0,0 +1,48 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: be_ai_char.h
+ *
+ * desc: bot characters
+ *
+ * $Archive: /source/code/botlib/be_ai_char.h $
+ *
+ *****************************************************************************/
+
+//loads a bot character from a file
+int BotLoadCharacter(char *charfile, float skill);
+//frees a bot character
+void BotFreeCharacter(int character);
+//returns a float characteristic
+float Characteristic_Float(int character, int index);
+//returns a bounded float characteristic
+float Characteristic_BFloat(int character, int index, float min, float max);
+//returns an integer characteristic
+int Characteristic_Integer(int character, int index);
+//returns a bounded integer characteristic
+int Characteristic_BInteger(int character, int index, int min, int max);
+//returns a string characteristic
+void Characteristic_String(int character, int index, char *buf, int size);
+//free cached bot characters
+void BotShutdownCharacters(void);
diff --git a/src/botlib/be_ai_chat.c b/src/botlib/be_ai_chat.c
new file mode 100644
index 00000000..d61e11e7
--- /dev/null
+++ b/src/botlib/be_ai_chat.c
@@ -0,0 +1,3029 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_chat.c
+ *
+ * desc: bot chat AI
+ *
+ * $Archive: /MissionPack/code/botlib/be_ai_chat.c $
+ *
+ *****************************************************************************/
+
+#include "../qcommon/q_shared.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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "be_ea.h"
+#include "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
+ int client; //client number
+ 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;
+ 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)
+ {
+ Com_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 == '_'
+ || 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 != '.' && *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] == ' ' || str1[j] == '.' || 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
+ Com_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);
+ //
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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)
+ {
+ size_t len;
+ 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
+ len = strlen(token.string) + 1;
+ len = PAD(len, sizeof(long));
+ size += sizeof(bot_synonym_t) + len;
+ if (pass)
+ {
+ synonym = (bot_synonym_t *) ptr;
+ ptr += sizeof(bot_synonym_t);
+ synonym->string = ptr;
+ ptr += len;
+ 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
+ Com_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);
+ //
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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))
+ {
+ size_t len;
+ if (token.type != TT_NAME)
+ {
+ SourceError(source, "unknown random %s", token.string);
+ FreeSource(source);
+ return NULL;
+ } //end if
+ len = strlen(token.string) + 1;
+ len = PAD(len, sizeof(long));
+ size += sizeof(bot_randomlist_t) + len;
+ if (pass)
+ {
+ random = (bot_randomlist_t *) ptr;
+ ptr += sizeof(bot_randomlist_t);
+ random->string = ptr;
+ ptr += len;
+ 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, "}"))
+ {
+ size_t len;
+ if (!BotLoadChatMessage(source, chatmessagestring))
+ {
+ FreeSource(source);
+ return NULL;
+ } //end if
+ len = strlen(chatmessagestring) + 1;
+ len = PAD(len, sizeof(long));
+ size += sizeof(bot_randomstring_t) + len;
+ if (pass)
+ {
+ randomstring = (bot_randomstring_t *) ptr;
+ ptr += sizeof(bot_randomstring_t);
+ randomstring->string = ptr;
+ ptr += len;
+ 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 = random() * 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)
+ {
+ fprintf(fp, "{ " );
+ 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;
+
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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->string) - match->variables[lastvariable].offset;
+ //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].offset = strptr - match->string;
+ 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)
+ {
+ assert( match->variables[lastvariable].offset >= 0 ); // bk001204
+ match->variables[lastvariable].length =
+ strlen(&match->string[ (int) match->variables[lastvariable].offset]);
+ } //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 offsets
+ for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1;
+ //
+ 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].offset >= 0)
+ {
+ if (match->variables[variable].length < size)
+ size = match->variables[variable].length+1;
+ assert( match->variables[variable].offset >= 0 ); // bk001204
+ strncpy(buf, &match->string[ (int) match->variables[variable].offset], 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 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 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 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: -
+//===========================================================================
+void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys)
+{
+ int allprefixed, hasvariableskey, hasstringkey;
+ bot_matchpiece_t *m;
+ bot_matchstring_t *ms;
+ bot_replychatkey_t *key, *key2;
+
+ //
+ allprefixed = qtrue;
+ hasvariableskey = hasstringkey = qfalse;
+ for (key = keys; key; key = key->next)
+ {
+ if (!(key->flags & (RCKFL_AND|RCKFL_NOT)))
+ {
+ allprefixed = qfalse;
+ if (key->flags & RCKFL_VARIABLES)
+ {
+ for (m = key->match; m; m = m->next)
+ {
+ if (m->type == MT_VARIABLE) hasvariableskey = qtrue;
+ } //end for
+ } //end if
+ else if (key->flags & RCKFL_STRING)
+ {
+ hasstringkey = qtrue;
+ } //end else if
+ } //end if
+ else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING))
+ {
+ for (key2 = keys; key2; key2 = key2->next)
+ {
+ if (key2 == key) continue;
+ if (key2->flags & RCKFL_NOT) continue;
+ if (key2->flags & RCKFL_VARIABLES)
+ {
+ for (m = key2->match; m; m = m->next)
+ {
+ if (m->type == MT_STRING)
+ {
+ for (ms = m->firststring; ms; ms = ms->next)
+ {
+ if (StringContains(ms->string, key->string, qfalse) != -1)
+ {
+ break;
+ } //end if
+ } //end for
+ if (ms) break;
+ } //end if
+ else if (m->type == MT_VARIABLE)
+ {
+ break;
+ } //end if
+ } //end for
+ if (!m)
+ {
+ SourceWarning(source, "one of the match templates does not "
+ "leave space for the key %s with the & prefix", key->string);
+ } //end if
+ } //end if
+ } //end for
+ } //end else
+ if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING))
+ {
+ for (key2 = keys; key2; key2 = key2->next)
+ {
+ if (key2 == key) continue;
+ if (key2->flags & RCKFL_NOT) continue;
+ if (key2->flags & RCKFL_STRING)
+ {
+ if (StringContains(key2->string, key->string, qfalse) != -1)
+ {
+ SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string);
+ } //end if
+ } //end if
+ else if (key2->flags & RCKFL_VARIABLES)
+ {
+ for (m = key2->match; m; m = m->next)
+ {
+ if (m->type == MT_STRING)
+ {
+ for (ms = m->firststring; ms; ms = ms->next)
+ {
+ if (StringContains(ms->string, key->string, qfalse) != -1)
+ {
+ SourceWarning(source, "the key %s with prefix ! is inside "
+ "the match template string %s", key->string, ms->string);
+ } //end if
+ } //end for
+ } //end if
+ } //end for
+ } //end else if
+ } //end for
+ } //end if
+ } //end for
+ if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix");
+ if (hasvariableskey && hasstringkey)
+ {
+ SourceWarning(source, "variables from the match template(s) could be "
+ "invalid when outputting one of the chat messages");
+ } //end if
+} //end of the function BotCheckValidReplyChatKeySet
+//===========================================================================
+//
+// 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;
+
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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, "]"));
+ //
+ BotCheckValidReplyChatKeySet(source, replychat->keys);
+ //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
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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, "}"))
+ {
+ size_t len;
+ if (!BotLoadChatMessage(source, chatmessagestring))
+ {
+ FreeSource(source);
+ return NULL;
+ } //end if
+ len = strlen(chatmessagestring) + 1;
+ len = PAD(len, sizeof(long));
+ 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 += len;
+ //the number of chat messages increased
+ chattype->numchatmessages++;
+ } //end if
+ size += sizeof(bot_chatmessage_t) + len;
+ } //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
+ //
+ botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile);
+ //
+ //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] ) {
+ 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;
+ }
+ }
+
+ cs->chat = BotLoadInitialChat(chatfile, chatname);
+ 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] = GetClearedMemory( sizeof(bot_ichatdata_t) );
+ 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) );
+ } //end if
+
+ return BLERR_NOERROR;
+} //end of the function BotLoadChatFile
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext,
+ bot_match_t *match, 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
+ if (match->variables[num].offset >= 0)
+ {
+ assert( match->variables[num].offset >= 0 ); // bk001204
+ ptr = &match->string[ (int) match->variables[num].offset];
+ for (i = 0; i < match->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_match_t *match, 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, match, 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;
+ int index;
+ bot_match_t match;
+ 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
+ //
+ Com_Memset(&match, 0, sizeof(match));
+ index = 0;
+ if( var0 ) {
+ strcat(match.string, var0);
+ match.variables[0].offset = index;
+ match.variables[0].length = strlen(var0);
+ index += strlen(var0);
+ }
+ if( var1 ) {
+ strcat(match.string, var1);
+ match.variables[1].offset = index;
+ match.variables[1].length = strlen(var1);
+ index += strlen(var1);
+ }
+ if( var2 ) {
+ strcat(match.string, var2);
+ match.variables[2].offset = index;
+ match.variables[2].length = strlen(var2);
+ index += strlen(var2);
+ }
+ if( var3 ) {
+ strcat(match.string, var3);
+ match.variables[3].offset = index;
+ match.variables[3].length = strlen(var3);
+ index += strlen(var3);
+ }
+ if( var4 ) {
+ strcat(match.string, var4);
+ match.variables[4].offset = index;
+ match.variables[4].length = strlen(var4);
+ index += strlen(var4);
+ }
+ if( var5 ) {
+ strcat(match.string, var5);
+ match.variables[5].offset = index;
+ match.variables[5].length = strlen(var5);
+ index += strlen(var5);
+ }
+ if( var6 ) {
+ strcat(match.string, var6);
+ match.variables[6].offset = index;
+ match.variables[6].length = strlen(var6);
+ index += strlen(var6);
+ }
+ if( var7 ) {
+ strcat(match.string, var7);
+ match.variables[7].offset = index;
+ match.variables[7].length = strlen(var7);
+ index += strlen(var7);
+ }
+ //
+ BotConstructChatMessage(cs, message, mcontext, &match, 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, index;
+ bot_chatstate_t *cs;
+
+ cs = BotChatStateFromHandle(chatstate);
+ if (!cs) return qfalse;
+ Com_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
+ } //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)
+ {
+ Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t));
+ bestchatmessage = m;
+ bestrchat = rchat;
+ bestpriority = rchat->priority;
+ } //end if
+ } //end if
+ } //end if
+ } //end for
+ if (bestchatmessage)
+ {
+ index = strlen(bestmatch.string);
+ if( var0 ) {
+ strcat(bestmatch.string, var0);
+ bestmatch.variables[0].offset = index;
+ bestmatch.variables[0].length = strlen(var0);
+ index += strlen(var0);
+ }
+ if( var1 ) {
+ strcat(bestmatch.string, var1);
+ bestmatch.variables[1].offset = index;
+ bestmatch.variables[1].length = strlen(var1);
+ index += strlen(var1);
+ }
+ if( var2 ) {
+ strcat(bestmatch.string, var2);
+ bestmatch.variables[2].offset = index;
+ bestmatch.variables[2].length = strlen(var2);
+ index += strlen(var2);
+ }
+ if( var3 ) {
+ strcat(bestmatch.string, var3);
+ bestmatch.variables[3].offset = index;
+ bestmatch.variables[3].length = strlen(var3);
+ index += strlen(var3);
+ }
+ if( var4 ) {
+ strcat(bestmatch.string, var4);
+ bestmatch.variables[4].offset = index;
+ bestmatch.variables[4].length = strlen(var4);
+ index += strlen(var4);
+ }
+ if( var5 ) {
+ strcat(bestmatch.string, var5);
+ bestmatch.variables[5].offset = index;
+ bestmatch.variables[5].length = strlen(var5);
+ index += strlen(var5);
+ }
+ if( var6 ) {
+ strcat(bestmatch.string, var6);
+ bestmatch.variables[6].offset = index;
+ bestmatch.variables[6].length = strlen(var6);
+ index += strlen(var6);
+ }
+ if( var7 ) {
+ strcat(bestmatch.string, var7);
+ bestmatch.variables[7].offset = index;
+ bestmatch.variables[7].length = strlen(var7);
+ index += strlen(var7);
+ }
+ if (LibVarGetValue("bot_testrchat"))
+ {
+ for (m = bestrchat->firstchatmessage; m; m = m->next)
+ {
+ BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, 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, 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 clientto, 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 {
+ switch(sendto) {
+ case CHAT_TEAM:
+ EA_Command(cs->client, va("say_team %s", cs->chatmessage));
+ break;
+ case CHAT_TELL:
+ EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage));
+ break;
+ default: //CHAT_ALL
+ EA_Command(cs->client, va("say %s", cs->chatmessage));
+ break;
+ }
+ }
+ //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, int client)
+{
+ bot_chatstate_t *cs;
+
+ cs = BotChatStateFromHandle(chatstate);
+ if (!cs) return;
+ cs->client = client;
+ Com_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
+
+ 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
+
+ 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])
+ {
+ FreeMemory(ichatdata[i]->chat);
+ FreeMemory(ichatdata[i]);
+ ichatdata[i] = NULL;
+ } //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_chat.h b/src/botlib/be_ai_chat.h
new file mode 100644
index 00000000..53a56d7b
--- /dev/null
+++ b/src/botlib/be_ai_chat.h
@@ -0,0 +1,113 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+/*****************************************************************************
+ * name: be_ai_chat.h
+ *
+ * desc: char AI
+ *
+ * $Archive: /source/code/botlib/be_ai_chat.h $
+ *
+ *****************************************************************************/
+
+#define MAX_MESSAGE_SIZE 256
+#define MAX_CHATTYPE_NAME 32
+#define MAX_MATCHVARIABLES 8
+
+#define CHAT_GENDERLESS 0
+#define CHAT_GENDERFEMALE 1
+#define CHAT_GENDERMALE 2
+
+#define CHAT_ALL 0
+#define CHAT_TEAM 1
+#define CHAT_TELL 2
+
+//a console message
+typedef struct bot_consolemessage_s
+{
+ int handle;
+ float time; //message time
+ int type; //message type
+ char message[MAX_MESSAGE_SIZE]; //message
+ struct bot_consolemessage_s *prev, *next; //prev and next in list
+} bot_consolemessage_t;
+
+//match variable
+typedef struct bot_matchvariable_s
+{
+ char offset;
+ int length;
+} bot_matchvariable_t;
+//returned to AI when a match is found
+typedef struct bot_match_s
+{
+ char string[MAX_MESSAGE_SIZE];
+ int type;
+ int subtype;
+ bot_matchvariable_t variables[MAX_MATCHVARIABLES];
+} bot_match_t;
+
+//setup the chat AI
+int BotSetupChatAI(void);
+//shutdown the chat AI
+void BotShutdownChatAI(void);
+//returns the handle to a newly allocated chat state
+int BotAllocChatState(void);
+//frees the chatstate
+void BotFreeChatState(int handle);
+//adds a console message to the chat state
+void BotQueueConsoleMessage(int chatstate, int type, char *message);
+//removes the console message from the chat state
+void BotRemoveConsoleMessage(int chatstate, int handle);
+//returns the next console message from the state
+int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm);
+//returns the number of console messages currently stored in the state
+int BotNumConsoleMessages(int chatstate);
+//selects a chat message of the given type
+void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7);
+//returns the number of initial chat messages of the given type
+int BotNumInitialChats(int chatstate, char *type);
+//find and select a reply for the given message
+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);
+//returns the length of the currently selected chat message
+int BotChatLength(int chatstate);
+//enters the selected chat message
+void BotEnterChat(int chatstate, int clientto, int sendto);
+//get the chat message ready to be output
+void BotGetChatMessage(int chatstate, char *buf, int size);
+//checks if the first string contains the second one, returns index into first string or -1 if not found
+int StringContains(char *str1, char *str2, int casesensitive);
+//finds a match for the given string using the match templates
+int BotFindMatch(char *str, bot_match_t *match, unsigned long int context);
+//returns a variable from a match
+void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size);
+//unify all the white spaces in the string
+void UnifyWhiteSpaces(char *string);
+//replace all the context related synonyms in the string
+void BotReplaceSynonyms(char *string, unsigned long int context);
+//loads a chat file for the chat state
+int BotLoadChatFile(int chatstate, char *chatfile, char *chatname);
+//store the gender of the bot in the chat state
+void BotSetChatGender(int chatstate, int gender);
+//store the bot name in the chat state
+void BotSetChatName(int chatstate, char *name, int client);
+
diff --git a/src/botlib/be_ai_gen.c b/src/botlib/be_ai_gen.c
new file mode 100644
index 00000000..d7bb2214
--- /dev/null
+++ b/src/botlib/be_ai_gen.c
@@ -0,0 +1,134 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_gen.c
+ *
+ * desc: genetic selection
+ *
+ * $Archive: /MissionPack/code/botlib/be_ai_gen.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "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
+ Com_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_gen.h b/src/botlib/be_ai_gen.h
new file mode 100644
index 00000000..ce9ba92f
--- /dev/null
+++ b/src/botlib/be_ai_gen.h
@@ -0,0 +1,33 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: be_ai_gen.h
+ *
+ * desc: genetic selection
+ *
+ * $Archive: /source/code/botlib/be_ai_gen.h $
+ *
+ *****************************************************************************/
+
+int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child);
diff --git a/src/botlib/be_ai_goal.c b/src/botlib/be_ai_goal.c
new file mode 100644
index 00000000..e240c446
--- /dev/null
+++ b/src/botlib/be_ai_goal.c
@@ -0,0 +1,1821 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_goal.c
+ *
+ * desc: goal AI
+ *
+ * $Archive: /MissionPack/code/botlib/be_ai_goal.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "be_ai_weight.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+
+//#define DEBUG_AI_GOAL
+#ifdef RANDOMIZE
+#define UNDECIDEDFUZZY
+#endif //RANDOMIZE
+#define DROPPEDWEIGHT
+//minimum avoid goal time
+#define AVOID_MINIMUM_TIME 10
+//default avoid goal time
+#define AVOID_DEFAULT_TIME 30
+//avoid dropped goal time
+#define AVOID_DROPPED_TIME 10
+//
+#define TRAVELTIME_SCALE 0.01
+//item flags
+#define IFL_NOTFREE 1 //not in free for all
+#define IFL_NOTTEAM 2 //not in team play
+#define IFL_NOTSINGLE 4 //not in single player
+#define IFL_NOTBOT 8 //bot should never go for this
+#define IFL_ROAM 16 //bot roam goal
+
+//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;
+
+//FIXME: these are game specific
+typedef enum {
+ GT_FFA, // free for all
+ GT_TOURNAMENT, // one on one tournament
+ GT_SINGLE_PLAYER, // single player tournament
+
+ //-- team games go after this --
+
+ GT_TEAM, // team deathmatch
+ GT_CTF, // capture the flag
+#ifdef MISSIONPACK
+ GT_1FCTF,
+ GT_OBELISK,
+ GT_HARVESTER,
+#endif
+ GT_MAX_GAME_TYPE
+} gametype_t;
+
+typedef struct levelitem_s
+{
+ int number; //number of the level item
+ int iteminfo; //index into the item info
+ int flags; //item flags
+ float weight; //fixed roam weight
+ 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},
+{NULL, 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]; // bk001206 - FIXME: init?
+//item configuration
+itemconfig_t *itemconfig = NULL; // bk001206 - init
+//level items
+levelitem_t *levelitemheap = NULL; // bk001206 - init
+levelitem_t *freelevelitems = NULL; // bk001206 - init
+levelitem_t *levelitems = NULL; // bk001206 - init
+int numlevelitems = 0;
+//map locations
+maplocation_t *maplocations = NULL; // bk001206 - init
+//camp spots
+campspot_t *campspots = NULL; // bk001206 - init
+//the game type
+int g_gametype = 0; // bk001206 - init
+//additional dropped item weight
+libvar_t *droppedweight = NULL; // bk001206 - init
+
+//========================================================================
+//
+// 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 = 256;
+ LibVarSet( "max_iteminfo", "256" );
+ }
+
+ strncpy( path, filename, MAX_PATH );
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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];
+ Com_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 *) GetClearedMemory(max_levelitems * sizeof(levelitem_t));
+
+ for (i = 0; i < max_levelitems-1; 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;
+ Com_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, value;
+ char classname[MAX_EPAIRKEY];
+ vec3_t origin, end;
+ int ent, goalareanum;
+ itemconfig_t *ic;
+ levelitem_t *li;
+ bsp_trace_t trace;
+
+ //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);
+ //
+ for (i = 0; i < ic->numiteminfo; i++)
+ {
+ if (!strcmp(classname, ic->iteminfo[i].classname)) break;
+ } //end for
+ if (i >= ic->numiteminfo)
+ {
+ Log_Write("entity %s unknown item\r\n", classname);
+ continue;
+ } //end if
+ //get the origin of the item
+ if (!AAS_VectorForBSPEpairKey(ent, "origin", origin))
+ {
+ botimport.Print(PRT_ERROR, "item %s without origin\n", classname);
+ continue;
+ } //end else
+ //
+ goalareanum = 0;
+ //if it is a floating item
+ if (spawnflags & 1)
+ {
+ //if the item is not floating in water
+ if (!(AAS_PointContents(origin) & CONTENTS_WATER))
+ {
+ VectorCopy(origin, end);
+ end[2] -= 32;
+ trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
+ //if the item not near the ground
+ if (trace.fraction >= 1)
+ {
+ //if the item is not reachable from a jumppad
+ goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs);
+ Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum);
+ //botimport.Print(PRT_MESSAGE, "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum);
+ if (!goalareanum) continue;
+ } //end if
+ } //end if
+ } //end if
+
+ li = AllocLevelItem();
+ if (!li) return;
+ //
+ li->number = ++numlevelitems;
+ li->timeout = 0;
+ li->entitynum = 0;
+ //
+ li->flags = 0;
+ AAS_IntForBSPEpairKey(ent, "notfree", &value);
+ if (value) li->flags |= IFL_NOTFREE;
+ AAS_IntForBSPEpairKey(ent, "notteam", &value);
+ if (value) li->flags |= IFL_NOTTEAM;
+ AAS_IntForBSPEpairKey(ent, "notsingle", &value);
+ if (value) li->flags |= IFL_NOTSINGLE;
+ AAS_IntForBSPEpairKey(ent, "notbot", &value);
+ if (value) li->flags |= IFL_NOTBOT;
+ if (!strcmp(classname, "item_botroam"))
+ {
+ li->flags |= IFL_ROAM;
+ AAS_FloatForBSPEpairKey(ent, "weight", &li->weight);
+ } //end if
+ //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);
+ //
+ if (goalareanum)
+ {
+ li->goalareanum = goalareanum;
+ VectorCopy(origin, li->goalorigin);
+ } //end if
+ else
+ {
+ //get the item goal area and goal origin
+ li->goalareanum = AAS_BestReachableArea(origin,
+ ic->iteminfo[i].mins, ic->iteminfo[i].maxs,
+ li->goalorigin);
+ if (!li->goalareanum)
+ {
+ botimport.Print(PRT_MESSAGE, "%s not reachable for bots at (%1.1f %1.1f %1.1f)\n",
+ classname, origin[0], origin[1], origin[2]);
+ } //end if
+ } //end else
+ //
+ AddLevelItemToList(li);
+ } //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;
+ Com_Memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int));
+ Com_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 the avoid goal is already stored
+ if (gs->avoidgoals[i] == number)
+ {
+ gs->avoidgoals[i] = number;
+ gs->avoidgoaltimes[i] = AAS_Time() + avoidtime;
+ return;
+ } //end if
+ } //end for
+
+ 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: -
+//===========================================================================
+void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime)
+{
+ bot_goalstate_t *gs;
+ levelitem_t *li;
+
+ gs = BotGoalStateFromHandle(goalstate);
+ if (!gs)
+ return;
+ if (avoidtime < 0)
+ {
+ if (!itemconfig)
+ return;
+ //
+ for (li = levelitems; li; li = li->next)
+ {
+ if (li->number == number)
+ {
+ avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime;
+ if (!avoidtime)
+ avoidtime = AVOID_DEFAULT_TIME;
+ if (avoidtime < AVOID_MINIMUM_TIME)
+ avoidtime = AVOID_MINIMUM_TIME;
+ BotAddToAvoidGoals(gs, number, avoidtime);
+ return;
+ } //end for
+ } //end for
+ return;
+ } //end if
+ else
+ {
+ BotAddToAvoidGoals(gs, number, avoidtime);
+ } //end else
+} //end of the function BotSetAvoidGoalTime
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal)
+{
+ levelitem_t *li;
+
+ if (!itemconfig) return -1;
+ li = levelitems;
+ if (index >= 0)
+ {
+ for (; li; li = li->next)
+ {
+ if (li->number == index)
+ {
+ li = li->next;
+ break;
+ } //end if
+ } //end for
+ } //end for
+ for (; li; li = li->next)
+ {
+ //
+ if (g_gametype == GT_SINGLE_PLAYER) {
+ if (li->flags & IFL_NOTSINGLE) continue;
+ }
+ else if (g_gametype >= GT_TEAM) {
+ if (li->flags & IFL_NOTTEAM) continue;
+ }
+ else {
+ if (li->flags & IFL_NOTFREE) continue;
+ }
+ if (li->flags & IFL_NOTBOT) continue;
+ //
+ 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;
+ goal->flags = GFL_ITEM;
+ if (li->timeout) goal->flags |= GFL_DROPPED;
+ //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: -
+//===========================================================================
+void BotFindEntityForLevelItem(levelitem_t *li)
+{
+ int ent, modelindex;
+ itemconfig_t *ic;
+ aas_entityinfo_t entinfo;
+ vec3_t dir;
+
+ ic = itemconfig;
+ if (!itemconfig) return;
+ for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent))
+ {
+ //get the model index of the entity
+ modelindex = AAS_EntityModelindex(ent);
+ //
+ if (!modelindex) continue;
+ //get info about the entity
+ AAS_EntityInfo(ent, &entinfo);
+ //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;
+ //
+ if (ic->iteminfo[li->iteminfo].modelindex == modelindex)
+ {
+ //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;
+ } //end if
+ } //end if
+ } //end for
+} //end of the function BotFindEntityForLevelItem
+//===========================================================================
+//
+// 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 entity is already stored as a level item
+ for (li = levelitems; li; li = li->next)
+ {
+ //if the level item is linked to an entity
+ if (li->entitynum && li->entitynum == ent)
+ {
+ //the entity is re-used if the models are different
+ if (ic->iteminfo[li->iteminfo].modelindex != modelindex)
+ {
+ //remove this level item
+ RemoveLevelItemFromList(li);
+ FreeLevelItem(li);
+ li = NULL;
+ break;
+ } //end if
+ else
+ {
+ if (entinfo.origin[0] != li->origin[0] ||
+ entinfo.origin[1] != li->origin[1] ||
+ entinfo.origin[2] != li->origin[2])
+ {
+ 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);
+ } //end if
+ break;
+ } //end else
+ } //end if
+ } //end for
+ if (li) continue;
+ //try to link the entity to a level item
+ for (li = levelitems; li; li = li->next)
+ {
+ //if this level item is already linked
+ if (li->entitynum) continue;
+ //
+ if (g_gametype == GT_SINGLE_PLAYER) {
+ if (li->flags & IFL_NOTSINGLE) continue;
+ }
+ else if (g_gametype >= GT_TEAM) {
+ if (li->flags & IFL_NOTTEAM) continue;
+ }
+ else {
+ if (li->flags & IFL_NOTFREE) continue;
+ }
+ //if the model of the level item and the entity are the same
+ if (ic->iteminfo[li->iteminfo].modelindex == modelindex)
+ {
+ //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;
+ //if the origin is different
+ if (entinfo.origin[0] != li->origin[0] ||
+ entinfo.origin[1] != li->origin[1] ||
+ entinfo.origin[2] != li->origin[2])
+ {
+ //update the level item 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);
+ } //end if
+#ifdef DEBUG
+ Log_Write("linked item %s to an entity", ic->iteminfo[li->iteminfo].classname);
+#endif //DEBUG
+ break;
+ } //end if
+ } //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);
+ //never go for items dropped into jumppads
+ 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
+ /*
+ for (li = levelitems; li; li = li->next)
+ {
+ if (!li->entitynum)
+ {
+ BotFindEntityForLevelItem(li);
+ } //end if
+ } //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++;
+ Com_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;
+ Com_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;
+ Com_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;
+ Com_Memset(&goal, 0, sizeof(bot_goal_t));
+ //go through the items in the level
+ for (li = levelitems; li; li = li->next)
+ {
+ if (g_gametype == GT_SINGLE_PLAYER) {
+ if (li->flags & IFL_NOTSINGLE)
+ continue;
+ }
+ else if (g_gametype >= GT_TEAM) {
+ if (li->flags & IFL_NOTTEAM)
+ continue;
+ }
+ else {
+ if (li->flags & IFL_NOTFREE)
+ continue;
+ }
+ if (li->flags & IFL_NOTBOT)
+ continue;
+ //if the item is not in a possible goal area
+ if (!li->goalareanum)
+ continue;
+ //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk)
+ if (!li->entitynum && !(li->flags & IFL_ROAM))
+ continue;
+ //get the fuzzy weight function for this item
+ iteminfo = &ic->iteminfo[li->iteminfo];
+ weightnum = gs->itemweightindex[iteminfo->number];
+ if (weightnum < 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 += droppedweight->value;
+#endif //DROPPEDWEIGHT
+ //use weight scale for item_botroam
+ if (li->flags & IFL_ROAM) weight *= li->weight;
+ //
+ 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)
+ {
+ //if this item won't respawn before we get there
+ avoidtime = BotAvoidGoalTime(goalstate, li->number);
+ if (avoidtime - t * 0.009 > 0)
+ continue;
+ //
+ 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;
+ if (bestitem->timeout)
+ goal.flags |= GFL_DROPPED;
+ if (bestitem->flags & IFL_ROAM)
+ goal.flags |= GFL_ROAM;
+ goal.iteminfo = bestitem->iteminfo;
+ //if it's a dropped item
+ if (bestitem->timeout)
+ {
+ avoidtime = AVOID_DROPPED_TIME;
+ } //end if
+ else
+ {
+ avoidtime = iteminfo->respawntime;
+ if (!avoidtime)
+ avoidtime = AVOID_DEFAULT_TIME;
+ if (avoidtime < AVOID_MINIMUM_TIME)
+ avoidtime = AVOID_MINIMUM_TIME;
+ } //end else
+ //add the chosen goal to the goals to avoid for a while
+ BotAddToAvoidGoals(gs, bestitem->number, avoidtime);
+ //push the goal on the stack
+ BotPushGoal(goalstate, &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;
+ Com_Memset(&goal, 0, sizeof(bot_goal_t));
+ //go through the items in the level
+ for (li = levelitems; li; li = li->next)
+ {
+ if (g_gametype == GT_SINGLE_PLAYER) {
+ if (li->flags & IFL_NOTSINGLE)
+ continue;
+ }
+ else if (g_gametype >= GT_TEAM) {
+ if (li->flags & IFL_NOTTEAM)
+ continue;
+ }
+ else {
+ if (li->flags & IFL_NOTFREE)
+ continue;
+ }
+ if (li->flags & IFL_NOTBOT)
+ continue;
+ //if the item is in a possible goal area
+ if (!li->goalareanum)
+ continue;
+ //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk)
+ if (!li->entitynum && !(li->flags & IFL_ROAM))
+ continue;
+ //get the fuzzy weight function for this item
+ iteminfo = &ic->iteminfo[li->iteminfo];
+ weightnum = gs->itemweightindex[iteminfo->number];
+ if (weightnum < 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 += droppedweight->value;
+#endif //DROPPEDWEIGHT
+ //use weight scale for item_botroam
+ if (li->flags & IFL_ROAM) weight *= li->weight;
+ //
+ 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)
+ {
+ //if this item won't respawn before we get there
+ avoidtime = BotAvoidGoalTime(goalstate, li->number);
+ if (avoidtime - t * 0.009 > 0)
+ continue;
+ //
+ 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;
+ if (bestitem->timeout)
+ goal.flags |= GFL_DROPPED;
+ if (bestitem->flags & IFL_ROAM)
+ goal.flags |= GFL_ROAM;
+ goal.iteminfo = bestitem->iteminfo;
+ //if it's a dropped item
+ if (bestitem->timeout)
+ {
+ avoidtime = AVOID_DROPPED_TIME;
+ } //end if
+ else
+ {
+ avoidtime = iteminfo->respawntime;
+ if (!avoidtime)
+ avoidtime = AVOID_DEFAULT_TIME;
+ if (avoidtime < AVOID_MINIMUM_TIME)
+ avoidtime = AVOID_MINIMUM_TIME;
+ } //end else
+ //add the chosen goal to the goals to avoid for a while
+ BotAddToAvoidGoals(gs, bestitem->number, avoidtime);
+ //push the goal on the stack
+ BotPushGoal(goalstate, &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;
+ Com_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
+ gs->itemweightconfig = ReadWeightConfig(filename);
+ 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: -
+//===========================================================================
+int BotSetupGoalAI(void)
+{
+ char *filename;
+
+ //check if teamplay is on
+ g_gametype = LibVarValue("g_gametype", "0");
+ //item configuration file
+ filename = LibVarString("itemconfig", "items.c");
+ //load the item configuration
+ itemconfig = LoadItemConfig(filename);
+ if (!itemconfig)
+ {
+ botimport.Print(PRT_FATAL, "couldn't load item config\n");
+ return BLERR_CANNOTLOADITEMCONFIG;
+ } //end if
+ //
+ droppedweight = LibVar("droppedweight", "1000");
+ //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_goal.h b/src/botlib/be_ai_goal.h
new file mode 100644
index 00000000..80dad08d
--- /dev/null
+++ b/src/botlib/be_ai_goal.h
@@ -0,0 +1,118 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+/*****************************************************************************
+ * name: be_ai_goal.h
+ *
+ * desc: goal AI
+ *
+ * $Archive: /source/code/botlib/be_ai_goal.h $
+ *
+ *****************************************************************************/
+
+#define MAX_AVOIDGOALS 256
+#define MAX_GOALSTACK 8
+
+#define GFL_NONE 0
+#define GFL_ITEM 1
+#define GFL_ROAM 2
+#define GFL_DROPPED 4
+
+//a bot goal
+typedef struct bot_goal_s
+{
+ vec3_t origin; //origin of the goal
+ int areanum; //area number of the goal
+ vec3_t mins, maxs; //mins and maxs of the goal
+ int entitynum; //number of the goal entity
+ int number; //goal number
+ int flags; //goal flags
+ int iteminfo; //item information
+} bot_goal_t;
+
+//reset the whole goal state, but keep the item weights
+void BotResetGoalState(int goalstate);
+//reset avoid goals
+void BotResetAvoidGoals(int goalstate);
+//remove the goal with the given number from the avoid goals
+void BotRemoveFromAvoidGoals(int goalstate, int number);
+//push a goal onto the goal stack
+void BotPushGoal(int goalstate, bot_goal_t *goal);
+//pop a goal from the goal stack
+void BotPopGoal(int goalstate);
+//empty the bot's goal stack
+void BotEmptyGoalStack(int goalstate);
+//dump the avoid goals
+void BotDumpAvoidGoals(int goalstate);
+//dump the goal stack
+void BotDumpGoalStack(int goalstate);
+//get the name name of the goal with the given number
+void BotGoalName(int number, char *name, int size);
+//get the top goal from the stack
+int BotGetTopGoal(int goalstate, bot_goal_t *goal);
+//get the second goal on the stack
+int BotGetSecondGoal(int goalstate, bot_goal_t *goal);
+//choose the best long term goal item for the bot
+int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags);
+//choose the best nearby goal item for the bot
+//the item may not be further away from the current bot position than maxtime
+//also the travel time from the nearby goal towards the long term goal may not
+//be larger than the travel time towards the long term goal from the current bot position
+int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags,
+ bot_goal_t *ltg, float maxtime);
+//returns true if the bot touches the goal
+int BotTouchingGoal(vec3_t origin, bot_goal_t *goal);
+//returns true if the goal should be visible but isn't
+int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal);
+//search for a goal for the given classname, the index can be used
+//as a start point for the search when multiple goals are available with that same classname
+int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal);
+//get the next camp spot in the map
+int BotGetNextCampSpotGoal(int num, bot_goal_t *goal);
+//get the map location with the given name
+int BotGetMapLocationGoal(char *name, bot_goal_t *goal);
+//returns the avoid goal time
+float BotAvoidGoalTime(int goalstate, int number);
+//set the avoid goal time
+void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime);
+//initializes the items in the level
+void BotInitLevelItems(void);
+//regularly update dynamic entity items (dropped weapons, flags etc.)
+void BotUpdateEntityItems(void);
+//interbreed the goal fuzzy logic
+void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child);
+//save the goal fuzzy logic to disk
+void BotSaveGoalFuzzyLogic(int goalstate, char *filename);
+//mutate the goal fuzzy logic
+void BotMutateGoalFuzzyLogic(int goalstate, float range);
+//loads item weights for the bot
+int BotLoadItemWeights(int goalstate, char *filename);
+//frees the item weights of the bot
+void BotFreeItemWeights(int goalstate);
+//returns the handle of a newly allocated goal state
+int BotAllocGoalState(int client);
+//free the given goal state
+void BotFreeGoalState(int handle);
+//setup the goal AI
+int BotSetupGoalAI(void);
+//shut down the goal AI
+void BotShutdownGoalAI(void);
diff --git a/src/botlib/be_ai_move.c b/src/botlib/be_ai_move.c
new file mode 100644
index 00000000..332c2049
--- /dev/null
+++ b/src/botlib/be_ai_move.c
@@ -0,0 +1,3572 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_move.c
+ *
+ * desc: bot movement AI
+ *
+ * $Archive: /MissionPack/code/botlib/be_ai_move.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 "aasfile.h"
+#include "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+
+#include "be_ea.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+
+
+//#define DEBUG_AI_MOVE
+//#define DEBUG_ELEVATOR
+//#define DEBUG_GRAPPLE
+
+// bk001204 - redundant bot_avoidspot_t, see be_ai_move.h
+
+//movement state
+//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED, MFL_WATERJUMP and
+// MFL_GRAPPLEPULL 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
+ 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_avoidspot_t avoidspots[MAX_AVOIDSPOTS]; //spots to avoid
+ int numavoidspots;
+} bot_movestate_t;
+
+//used to avoid reachability links for some time after being used
+#define AVOIDREACH
+#define AVOIDREACH_TIME 6 //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
+//weapon indexes for weapon jumping
+#define WEAPONINDEX_ROCKET_LAUNCHER 5
+#define WEAPONINDEX_BFG 9
+
+#define MODELTYPE_FUNC_PLAT 1
+#define MODELTYPE_FUNC_BOB 2
+#define MODELTYPE_FUNC_DOOR 3
+#define MODELTYPE_FUNC_STATIC 4
+
+libvar_t *sv_maxstep;
+libvar_t *sv_maxbarrier;
+libvar_t *sv_gravity;
+libvar_t *weapindex_rocketlauncher;
+libvar_t *weapindex_bfg10k;
+libvar_t *weapindex_grapple;
+libvar_t *entitytypemissile;
+libvar_t *offhandgrapple;
+libvar_t *cmd_grappleoff;
+libvar_t *cmd_grappleon;
+//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
+//========================================================================
+//
+// 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;
+ 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;
+ ms->moveflags &= ~MFL_GRAPPLEPULL;
+ if (initmove->or_moveflags & MFL_GRAPPLEPULL) ms->moveflags |= MFL_GRAPPLEPULL;
+} //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
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int BotFuzzyPointReachabilityArea(vec3_t origin)
+{
+ 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);
+ end[0] += x * 8;
+ end[1] += y * 8;
+ end[2] += z * 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_OriginOfMoverWithModelNum(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_OriginOfMoverWithModelNum(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];
+
+ Com_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 (!Q_stricmp(classname, "func_bobbing"))
+ modeltypes[modelnum] = MODELTYPE_FUNC_BOB;
+ else if (!Q_stricmp(classname, "func_plat"))
+ modeltypes[modelnum] = MODELTYPE_FUNC_PLAT;
+ else if (!Q_stricmp(classname, "func_door"))
+ modeltypes[modelnum] = MODELTYPE_FUNC_DOOR;
+ else if (!Q_stricmp(classname, "func_static"))
+ modeltypes[modelnum] = MODELTYPE_FUNC_STATIC;
+ } //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_AreaContentsTravelFlags(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: -
+//===========================================================================
+float DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2)
+{
+ vec3_t proj, dir;
+ int j;
+
+ AAS_ProjectPointOntoVector(p, lp1, lp2, proj);
+ for (j = 0; j < 3; j++)
+ if ((proj[j] > lp1[j] && proj[j] > lp2[j]) ||
+ (proj[j] < lp1[j] && proj[j] < lp2[j]))
+ break;
+ if (j < 3) {
+ if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j]))
+ VectorSubtract(p, lp1, dir);
+ else
+ VectorSubtract(p, lp2, dir);
+ return VectorLengthSquared(dir);
+ }
+ VectorSubtract(p, proj, dir);
+ return VectorLengthSquared(dir);
+} //end of the function DistanceFromLineSquared
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+float VectorDistanceSquared(vec3_t p1, vec3_t p2)
+{
+ vec3_t dir;
+ VectorSubtract(p2, p1, dir);
+ return VectorLengthSquared(dir);
+} //end of the function VectorDistanceSquared
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int BotAvoidSpots(vec3_t origin, aas_reachability_t *reach, bot_avoidspot_t *avoidspots, int numavoidspots)
+{
+ int checkbetween, i, type;
+ float squareddist, squaredradius;
+
+ switch(reach->traveltype & TRAVELTYPE_MASK)
+ {
+ case TRAVEL_WALK: checkbetween = qtrue; break;
+ case TRAVEL_CROUCH: checkbetween = qtrue; break;
+ case TRAVEL_BARRIERJUMP: checkbetween = qtrue; break;
+ case TRAVEL_LADDER: checkbetween = qtrue; break;
+ case TRAVEL_WALKOFFLEDGE: checkbetween = qfalse; break;
+ case TRAVEL_JUMP: checkbetween = qfalse; break;
+ case TRAVEL_SWIM: checkbetween = qtrue; break;
+ case TRAVEL_WATERJUMP: checkbetween = qtrue; break;
+ case TRAVEL_TELEPORT: checkbetween = qfalse; break;
+ case TRAVEL_ELEVATOR: checkbetween = qfalse; break;
+ case TRAVEL_GRAPPLEHOOK: checkbetween = qfalse; break;
+ case TRAVEL_ROCKETJUMP: checkbetween = qfalse; break;
+ case TRAVEL_BFGJUMP: checkbetween = qfalse; break;
+ case TRAVEL_JUMPPAD: checkbetween = qfalse; break;
+ case TRAVEL_FUNCBOB: checkbetween = qfalse; break;
+ default: checkbetween = qtrue; break;
+ } //end switch
+
+ type = AVOID_CLEAR;
+ for (i = 0; i < numavoidspots; i++)
+ {
+ squaredradius = Square(avoidspots[i].radius);
+ squareddist = DistanceFromLineSquared(avoidspots[i].origin, origin, reach->start);
+ // if moving towards the avoid spot
+ if (squareddist < squaredradius &&
+ VectorDistanceSquared(avoidspots[i].origin, origin) > squareddist)
+ {
+ type = avoidspots[i].type;
+ } //end if
+ else if (checkbetween) {
+ squareddist = DistanceFromLineSquared(avoidspots[i].origin, reach->start, reach->end);
+ // if moving towards the avoid spot
+ if (squareddist < squaredradius &&
+ VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist)
+ {
+ type = avoidspots[i].type;
+ } //end if
+ } //end if
+ else
+ {
+ VectorDistanceSquared(avoidspots[i].origin, reach->end);
+ // if the reachability leads closer to the avoid spot
+ if (squareddist < squaredradius &&
+ VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist)
+ {
+ type = avoidspots[i].type;
+ } //end if
+ } //end else
+ if (type == AVOID_ALWAYS)
+ return type;
+ } //end for
+ return type;
+} //end of the function BotAvoidSpots
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type)
+{
+ bot_movestate_t *ms;
+
+ ms = BotMoveStateFromHandle(movestate);
+ if (!ms) return;
+ if (type == AVOID_CLEAR)
+ {
+ ms->numavoidspots = 0;
+ return;
+ } //end if
+
+ if (ms->numavoidspots >= MAX_AVOIDSPOTS)
+ return;
+ VectorCopy(origin, ms->avoidspots[ms->numavoidspots].origin);
+ ms->avoidspots[ms->numavoidspots].radius = radius;
+ ms->avoidspots[ms->numavoidspots].type = type;
+ ms->numavoidspots++;
+} //end of the function BotAddAvoidSpot
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int BotGetReachabilityToGoal(vec3_t origin, int areanum,
+ int lastgoalareanum, int lastareanum,
+ int *avoidreach, float *avoidreachtimes, int *avoidreachtries,
+ bot_goal_t *goal, int travelflags, int movetravelflags,
+ struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags)
+{
+ int i, t, besttime, bestreachnum, reachnum;
+ aas_reachability_t reach;
+
+ //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
+ //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
+ //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
+ if (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_AreaContentsTravelFlags(reach.areanum) & ~travelflags) continue;
+ //if the travel isn't valid
+ if (!BotValidTravel(origin, &reach, movetravelflags)) 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;
+ //if the bot should not use this reachability to avoid bad spots
+ if (BotAvoidSpots(origin, &reach, avoidspots, numavoidspots)) {
+ if (flags) {
+ *flags |= MOVERESULT_BLOCKEDBYAVOIDSPOT;
+ }
+ continue;
+ }
+ //add the travel time towards the area
+ t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start);
+ //if the travel time is better than the ones already found
+ if (!besttime || t < besttime)
+ {
+ besttime = t;
+ bestreachnum = reachnum;
+ } //end if
+ } //end for
+ //
+ 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 & TRAVELTYPE_MASK) == TRAVEL_TELEPORT) return qtrue;
+ //never look beyond the weapon jump point
+ if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) return qtrue;
+ if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_BFGJUMP) return qtrue;
+ //don't add jump pad distances
+ if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_JUMPPAD &&
+ (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR &&
+ (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB)
+ {
+ if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) return qtrue;
+ } //end if
+ reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum,
+ ms->lastgoalareanum, lastareanum,
+ ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
+ goal, travelflags, travelflags, NULL, 0, NULL);
+ 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;
+
+ Com_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,
+ lastgoalareanum, lastareanum,
+ avoidreach, avoidreachtimes, avoidreachtries,
+ goal, travelflags, travelflags, NULL, 0, NULL);
+ 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_OriginOfMoverWithModelNum(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->value;
+ 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->value - 8)
+ {
+ VectorCopy(trace.endpos, end);
+ end[2] -= 20;
+ 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->value;
+ //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->value) 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->value) 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 (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND;
+ //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.1f,
+ 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_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 (fabs(DotProduct(dir, up)) < 0.7)
+ {
+ mins[2] += sv_maxstep->value; //if the bot can step on
+ maxs[2] -= 10; //a little lower to avoid low ceiling
+ } //end if
+ VectorMA(ms->origin, 3, 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: -
+//===========================================================================
+bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach)
+{
+ float dist, speed;
+ vec3_t hordir;
+ bot_moveresult_t_cleared( 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);
+ //
+ if (dist < 10)
+ {
+ //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);
+ } //end if
+ //if going towards a crouch area
+ if (!(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);
+ //
+ 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_cleared( 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_cleared( 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_cleared( 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 < 9)
+ {
+ EA_Jump(ms->client);
+ } //end if
+ else
+ {
+ if (dist > 60) dist = 60;
+ speed = 360 - (360 - 6 * 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;
+ vec3_t hordir;
+ bot_moveresult_t_cleared( result );
+
+ //if near the top or going down
+ if (ms->velocity[2] < 250)
+ {
+ hordir[0] = reach->end[0] - ms->origin[0];
+ hordir[1] = reach->end[1] - ms->origin[1];
+ hordir[2] = 0;
+ dist = VectorNormalize(hordir);
+ //
+ BotCheckBlocked(ms, hordir, qtrue, &result);
+ //
+ EA_Move(ms->client, hordir, 400);
+ VectorCopy(hordir, result.movedir);
+ } //end if
+ //
+ 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_cleared( 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_cleared( 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_cleared( result );
+
+ //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n");
+ //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_cleared( 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
+ if (dist < 48)
+ {
+ hordir[0] = reach->end[0] - ms->origin[0];
+ hordir[1] = reach->end[1] - ms->origin[1];
+ hordir[2] = 0;
+ VectorNormalize(hordir);
+ //
+ if (reachhordist < 20)
+ {
+ speed = 100;
+ } //end if
+ else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed))
+ {
+ speed = 400;
+ } //end if
+ } //end if
+ else
+ {
+ if (reachhordist < 20)
+ {
+ if (dist > 64) dist = 64;
+ speed = 400 - (256 - 4 * dist);
+ } //end if
+ else
+ {
+ speed = 400;
+ } //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->value * 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_cleared( 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
+ //
+ 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_cleared( 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_cleared( result );
+ bsp_trace_t trace;
+
+ //
+ 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_cleared( 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_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_cleared( 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;
+ vec3_t origin = {0, 0, 0};
+// vec3_t up = {0, 0, 1};
+ bot_moveresult_t_cleared( result );
+
+ //
+// 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");
+ VectorSubtract(reach->end, ms->origin, dir);
+ VectorNormalize(dir);
+ //set the ideal view angles, facing the ladder up or down
+ viewdir[0] = dir[0];
+ viewdir[1] = dir[1];
+ viewdir[2] = 3 * dir[2];
+ Vector2Angles(viewdir, result.ideal_viewangles);
+ //elemantary action
+ EA_Move(ms->client, origin, 0);
+ EA_MoveForward(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\n");
+ VectorSubtract(reach->end, ms->origin, dir);
+ //make sure the horizontal movement is large anough
+ VectorCopy(dir, hordir);
+ hordir[2] = 0;
+ dist = VectorNormalize(hordir);
+ //
+ dir[0] = hordir[0];
+ dir[1] = hordir[1];
+ if (dir[2] > 0) dir[2] = 1;
+ else dir[2] = -1;
+ if (dist > 50) dist = 50;
+ speed = 400 - (200 - 4 * 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_cleared( 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_cleared( 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->value)
+ {
+#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.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_cleared( result );
+
+ //
+ MoverBottomCenter(reach, bottomcenter);
+ VectorSubtract(bottomcenter, ms->origin, bottomdir);
+ //
+ VectorSubtract(reach->end, ms->origin, topdir);
+ //
+ if (fabs(bottomdir[2]) < 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_OriginOfMoverWithModelNum(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_cleared( 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_WAITFORFUNCBOBBING;
+ 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_cleared( result );
+ float dist, speed;
+
+ //
+ 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)
+{
+ int i;
+ aas_entityinfo_t entinfo;
+
+ //if the grapple hook is pulling
+ if (ms->moveflags & MFL_GRAPPLEPULL)
+ return 2;
+ //check for a visible grapple missile entity
+ //or visible grapple entity
+ for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i))
+ {
+ if (AAS_EntityType(i) == (int) entitytypemissile->value)
+ {
+ AAS_EntityInfo(i, &entinfo);
+ if (entinfo.weapon == (int) weapindex_grapple->value)
+ {
+ return 1;
+ } //end if
+ } //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 & TRAVELTYPE_MASK) != TRAVEL_GRAPPLEHOOK)
+ {
+ if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time)
+ {
+ if (offhandgrapple->value)
+ EA_Command(ms->client, cmd_grappleoff->string);
+ 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_cleared( result );
+ float dist, speed;
+ vec3_t dir, viewdir, org;
+ int state, areanum;
+ bsp_trace_t trace;
+
+#ifdef DEBUG_GRAPPLE
+ static int debugline;
+ if (!debugline) debugline = botimport.DebugLineCreate();
+ botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE);
+#endif //DEBUG_GRAPPLE
+
+ //
+ if (ms->moveflags & MFL_GRAPPLERESET)
+ {
+ if (offhandgrapple->value)
+ EA_Command(ms->client, cmd_grappleoff->string);
+ ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
+ return result;
+ } //end if
+ //
+ if (!(int) offhandgrapple->value)
+ {
+ result.weapon = weapindex_grapple->value;
+ result.flags |= MOVERESULT_MOVEMENTWEAPON;
+ } //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)
+ {
+#ifdef DEBUG_GRAPPLE
+ botimport.Print(PRT_ERROR, "grapple normal end\n");
+#endif //DEBUG_GRAPPLE
+ if (offhandgrapple->value)
+ EA_Command(ms->client, cmd_grappleoff->string);
+ ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
+ ms->moveflags |= MFL_GRAPPLERESET;
+ ms->reachability_time = 0; //end the reachability
+ return result;
+ } //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
+ if (offhandgrapple->value)
+ EA_Command(ms->client, cmd_grappleoff->string);
+ ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
+ ms->moveflags |= MFL_GRAPPLERESET;
+ ms->reachability_time = 0; //end the reachability
+ return result;
+ } //end if
+ } //end if
+ else
+ {
+ ms->grapplevisible_time = AAS_Time();
+ } //end else
+ //
+ if (!(int) offhandgrapple->value)
+ {
+ EA_Attack(ms->client);
+ } //end if
+ //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 &&
+ fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 &&
+ 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
+ //check if the grapple missile path is clear
+ VectorAdd(ms->origin, ms->viewoffset, org);
+ trace = AAS_Trace(org, NULL, NULL, reach->end, ms->entitynum, CONTENTS_SOLID);
+ VectorSubtract(reach->end, trace.endpos, dir);
+ if (VectorLength(dir) > 16)
+ {
+ result.failure = qtrue;
+ return result;
+ } //end if
+ //activate the grapple
+ if (offhandgrapple->value)
+ {
+ EA_Command(ms->client, cmd_grappleon->string);
+ } //end if
+ else
+ {
+ EA_Attack(ms->client);
+ } //end else
+ 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_cleared( result );
+
+ //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n");
+ //
+ hordir[0] = reach->start[0] - ms->origin[0];
+ hordir[1] = reach->start[1] - ms->origin[1];
+ hordir[2] = 0;
+ //
+ dist = VectorNormalize(hordir);
+ //look in the movement direction
+ Vector2Angles(hordir, result.ideal_viewangles);
+ //look straight down
+ result.ideal_viewangles[PITCH] = 90;
+ //
+ if (dist < 5 &&
+ fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 &&
+ fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 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
+ //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, (int) weapindex_rocketlauncher->value);
+ //weapon is used for movement
+ result.weapon = (int) weapindex_rocketlauncher->value;
+ 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)
+{
+ vec3_t hordir;
+ float dist, speed;
+ bot_moveresult_t_cleared( result );
+
+ //botimport.Print(PRT_MESSAGE, "BotTravel_BFGJump: bah\n");
+ //
+ 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 &&
+ fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 &&
+ fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 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
+ //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, (int) weapindex_bfg10k->value);
+ //weapon is used for movement
+ result.weapon = (int) weapindex_bfg10k->value;
+ result.flags |= MOVERESULT_MOVEMENTWEAPON;
+ //
+ VectorCopy(hordir, result.movedir);
+ //
+ 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;
+ float speed;
+ bot_moveresult_t_cleared( 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);
+ */
+ //
+ if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed))
+ {
+ //go straight to the reachability end
+ VectorSubtract(reach->end, ms->origin, hordir);
+ hordir[2] = 0;
+ VectorNormalize(hordir);
+ speed = 400;
+ } //end if
+ //
+ EA_Move(ms->client, hordir, speed);
+ 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_cleared( 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_cleared( 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 & TRAVELTYPE_MASK)
+ {
+ 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_cleared( 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
+ //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) 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);
+ //
+ return result;
+} //end of the function BotMoveInGoalArea
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags)
+{
+ int reachnum, lastreachnum, foundjumppad, ent, resultflags;
+ aas_reachability_t reach, lastreach;
+ bot_movestate_t *ms;
+ //vec3_t mins, maxs, up = {0, 0, 1};
+ //bsp_trace_t trace;
+ //static int debugline;
+
+ result->failure = qfalse;
+ result->type = 0;
+ result->blocked = qfalse;
+ result->blockentity = 0;
+ result->traveltype = 0;
+ result->flags = 0;
+
+ //
+ 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 & TRAVELTYPE_MASK) != 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 & TRAVELTYPE_MASK) != 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
+ else if (modeltype == MODELTYPE_FUNC_STATIC || modeltype == MODELTYPE_FUNC_DOOR)
+ {
+ // check if ontop of a door bridge ?
+ ms->areanum = BotFuzzyPointReachabilityArea(ms->origin);
+ // if not in a reachability area
+ if (!AAS_AreaReachability(ms->areanum))
+ {
+ result->blocked = qtrue;
+ result->blockentity = ent;
+ result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
+ return;
+ } //end if
+ } //end else if
+ 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->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 & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR));
+ ms->areanum = BotFuzzyPointReachabilityArea(ms->origin);
+ //
+ if ( !ms->areanum )
+ {
+ result->failure = qtrue;
+ result->blocked = qtrue;
+ result->blockentity = 0;
+ result->type = RESULTTYPE_INSOLIDAREA;
+ return;
+ } //end if
+ //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 & TRAVELTYPE_MASK) == 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 & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR ||
+ (reach.traveltype & TRAVELTYPE_MASK) == 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
+ {
+#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 & TRAVELTYPE_MASK);
+ 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)
+ {
+ reachnum = 0;
+ //botimport.Print(PRT_MESSAGE, "area change or timeout\n");
+ } //end else if
+ } //end else
+ } //end if
+ resultflags = 0;
+ //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->lastgoalareanum, ms->lastareanum,
+ ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
+ goal, travelflags, travelflags,
+ ms->avoidspots, ms->numavoidspots, &resultflags);
+ //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() + BotReachabilityTime(&reach);
+ //
+#ifdef AVOIDREACH
+ //add the reachability to the reachabilities to avoid for a while
+ BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME);
+#endif //AVOIDREACH
+ } //end if
+#ifdef DEBUG
+
+ else if (bot_developer)
+ {
+ botimport.Print(PRT_MESSAGE, "goal not reachable\n");
+ Com_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;
+ //
+#ifdef DEBUG_AI_MOVE
+ AAS_ClearShownDebugLines();
+ AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
+ AAS_ShowReachability(&reach);
+#endif //DEBUG_AI_MOVE
+ //
+#ifdef DEBUG
+ //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client);
+ //AAS_PrintTravelType(reach.traveltype);
+ //botimport.Print(PRT_MESSAGE, "\n");
+#endif //DEBUG
+ switch(reach.traveltype & TRAVELTYPE_MASK)
+ {
+ 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: *result = BotTravel_BFGJump(ms, &reach); break;
+ 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 & TRAVELTYPE_MASK));
+ break;
+ } //end case
+ } //end switch
+ result->traveltype = reach.traveltype;
+ result->flags |= resultflags;
+ } //end if
+ else
+ {
+ result->failure = qtrue;
+ result->flags |= resultflags;
+ Com_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 & TRAVELTYPE_MASK);
+ 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->lastgoalareanum, ms->lastareanum,
+ ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
+ goal, travelflags, TFL_JUMPPAD, ms->avoidspots, ms->numavoidspots, NULL);
+ 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 & TRAVELTYPE_MASK) == 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 & TRAVELTYPE_MASK);
+ //botimport.Print(PRT_MESSAGE, "\n");
+#endif //DEBUG
+ //
+ switch(reach.traveltype & TRAVELTYPE_MASK)
+ {
+ 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:
+ case TRAVEL_BFGJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break;
+ 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 & TRAVELTYPE_MASK));
+ break;
+ } //end case
+ } //end switch
+ result->traveltype = reach.traveltype;
+#ifdef DEBUG
+ if (bot_developer)
+ {
+ if (result->failure)
+ {
+ botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client);
+ AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK);
+ 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);
+ //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;
+ Com_Memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int));
+ Com_Memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float));
+ Com_Memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int));
+} //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;
+ Com_Memset(ms, 0, sizeof(bot_movestate_t));
+} //end of the function BotResetMoveState
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int BotSetupMoveAI(void)
+{
+ BotSetBrushModelTypes();
+ sv_maxstep = LibVar("sv_step", "18");
+ sv_maxbarrier = LibVar("sv_maxbarrier", "32");
+ sv_gravity = LibVar("sv_gravity", "800");
+ weapindex_rocketlauncher = LibVar("weapindex_rocketlauncher", "5");
+ weapindex_bfg10k = LibVar("weapindex_bfg10k", "9");
+ weapindex_grapple = LibVar("weapindex_grapple", "10");
+ entitytypemissile = LibVar("entitytypemissile", "3");
+ offhandgrapple = LibVar("offhandgrapple", "0");
+ cmd_grappleon = LibVar("cmd_grappleon", "grappleon");
+ cmd_grappleoff = LibVar("cmd_grappleoff", "grappleoff");
+ 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_move.h b/src/botlib/be_ai_move.h
new file mode 100644
index 00000000..d960c3c8
--- /dev/null
+++ b/src/botlib/be_ai_move.h
@@ -0,0 +1,144 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: be_ai_move.h
+ *
+ * desc: movement AI
+ *
+ * $Archive: /source/code/botlib/be_ai_move.h $
+ *
+ *****************************************************************************/
+
+//movement types
+#define MOVE_WALK 1
+#define MOVE_CROUCH 2
+#define MOVE_JUMP 4
+#define MOVE_GRAPPLE 8
+#define MOVE_ROCKETJUMP 16
+#define MOVE_BFGJUMP 32
+//move flags
+#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump
+#define MFL_ONGROUND 2 //bot is in the ground
+#define MFL_SWIMMING 4 //bot is swimming
+#define MFL_AGAINSTLADDER 8 //bot is against a ladder
+#define MFL_WATERJUMP 16 //bot is waterjumping
+#define MFL_TELEPORTED 32 //bot is being teleported
+#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple
+#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook
+#define MFL_GRAPPLERESET 256 //bot has reset the grapple
+#define MFL_WALK 512 //bot should walk slowly
+// move result flags
+#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement
+#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming
+#define MOVERESULT_WAITING 4 //bot is waiting for something
+#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code
+#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement
+#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle
+#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing
+#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat)
+#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot
+//
+#define MAX_AVOIDREACH 1
+#define MAX_AVOIDSPOTS 32
+// avoid spot types
+#define AVOID_CLEAR 0 //clear all avoid spots
+#define AVOID_ALWAYS 1 //avoid always
+#define AVOID_DONTBLOCK 2 //never totally block
+// restult types
+#define RESULTTYPE_ELEVATORUP 1 //elevator is up
+#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive
+#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed
+#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad
+
+//structure used to initialize the movement state
+//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate
+typedef struct bot_initmove_s
+{
+ 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
+ int or_moveflags; //values ored to the movement flags
+} bot_initmove_t;
+
+//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set
+typedef struct bot_moveresult_s
+{
+ int failure; //true if movement failed all together
+ int type; //failure or blocked type
+ int blocked; //true if blocked by an entity
+ int blockentity; //entity blocking the bot
+ int traveltype; //last executed travel type
+ int flags; //result flags
+ int weapon; //weapon used for movement
+ vec3_t movedir; //movement direction
+ vec3_t ideal_viewangles; //ideal viewangles for the movement
+} bot_moveresult_t;
+
+#define bot_moveresult_t_cleared(x) bot_moveresult_t (x) = {0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, {0, 0, 0}}
+
+// bk001204: from code/botlib/be_ai_move.c
+// TTimo 04/12/2001 was moved here to avoid dup defines
+typedef struct bot_avoidspot_s
+{
+ vec3_t origin;
+ float radius;
+ int type;
+} bot_avoidspot_t;
+
+//resets the whole move state
+void BotResetMoveState(int movestate);
+//moves the bot to the given goal
+void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags);
+//moves the bot in the specified direction using the specified type of movement
+int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type);
+//reset avoid reachability
+void BotResetAvoidReach(int movestate);
+//resets the last avoid reachability
+void BotResetLastAvoidReach(int movestate);
+//returns a reachability area if the origin is in one
+int BotReachabilityArea(vec3_t origin, int client);
+//view target based on movement
+int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target);
+//predict the position of a player based on movement towards a goal
+int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target);
+//returns the handle of a newly allocated movestate
+int BotAllocMoveState(void);
+//frees the movestate with the given handle
+void BotFreeMoveState(int handle);
+//initialize movement state before performing any movement
+void BotInitMoveState(int handle, bot_initmove_t *initmove);
+//add a spot to avoid (if type == AVOID_CLEAR all spots are removed)
+void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type);
+//must be called every map change
+void BotSetBrushModelTypes(void);
+//setup movement AI
+int BotSetupMoveAI(void);
+//shutdown movement AI
+void BotShutdownMoveAI(void);
+
diff --git a/src/botlib/be_ai_weap.c b/src/botlib/be_ai_weap.c
new file mode 100644
index 00000000..56aabcc2
--- /dev/null
+++ b/src/botlib/be_ai_weap.c
@@ -0,0 +1,543 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_weap.c
+ *
+ * desc: weapon AI
+ *
+ * $Archive: /MissionPack/code/botlib/be_ai_weap.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "be_ai_weight.h" //fuzzy weights
+#include "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 // bk001212 - static
+static 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
+static 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}
+};
+
+static structdef_t weaponinfo_struct =
+{
+ sizeof(weaponinfo_t), weaponinfo_fields
+};
+static 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;
+
+static bot_weaponstate_t *botweaponstates[MAX_CLIENTS+1];
+static weaponconfig_t *weaponconfig;
+
+//========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//========================================================================
+int BotValidWeaponNumber(int weaponnum)
+{
+ if (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", "32");
+ if (max_weaponinfo < 0)
+ {
+ botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo);
+ max_weaponinfo = 32;
+ LibVarSet("max_weaponinfo", "32");
+ } //end if
+ max_projectileinfo = (int) LibVarValue("max_projectileinfo", "32");
+ if (max_projectileinfo < 0)
+ {
+ botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo);
+ max_projectileinfo = 32;
+ LibVarSet("max_projectileinfo", "32");
+ } //end if
+ strncpy(path, filename, MAX_PATH);
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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"))
+ {
+ Com_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
+ Com_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
+ Com_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))
+ {
+ Com_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);
+ //
+ ws->weaponweightconfig = ReadWeightConfig(filename);
+ 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;
+ Com_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;
+
+ //Com_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;
+
+ file = LibVarString("weaponconfig", "weapons.c");
+ weaponconfig = LoadWeaponConfig(file);
+ 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_weap.h b/src/botlib/be_ai_weap.h
new file mode 100644
index 00000000..59067fb0
--- /dev/null
+++ b/src/botlib/be_ai_weap.h
@@ -0,0 +1,104 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: be_ai_weap.h
+ *
+ * desc: weapon AI
+ *
+ * $Archive: /source/code/botlib/be_ai_weap.h $
+ *
+ *****************************************************************************/
+
+//projectile flags
+#define PFL_WINDOWDAMAGE 1 //projectile damages through window
+#define PFL_RETURN 2 //set when projectile returns to owner
+//weapon flags
+#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event
+//damage types
+#define DAMAGETYPE_IMPACT 1 //damage on impact
+#define DAMAGETYPE_RADIAL 2 //radial damage
+#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile
+
+typedef struct projectileinfo_s
+{
+ char name[MAX_STRINGFIELD];
+ char model[MAX_STRINGFIELD];
+ int flags;
+ float gravity;
+ int damage;
+ float radius;
+ int visdamage;
+ int damagetype;
+ int healthinc;
+ float push;
+ float detonation;
+ float bounce;
+ float bouncefric;
+ float bouncestop;
+} projectileinfo_t;
+
+typedef struct weaponinfo_s
+{
+ int valid; //true if the weapon info is valid
+ int number; //number of the weapon
+ char name[MAX_STRINGFIELD];
+ char model[MAX_STRINGFIELD];
+ int level;
+ int weaponindex;
+ int flags;
+ char projectile[MAX_STRINGFIELD];
+ int numprojectiles;
+ float hspread;
+ float vspread;
+ float speed;
+ float acceleration;
+ vec3_t recoil;
+ vec3_t offset;
+ vec3_t angleoffset;
+ float extrazvelocity;
+ int ammoamount;
+ int ammoindex;
+ float activate;
+ float reload;
+ float spinup;
+ float spindown;
+ projectileinfo_t proj; //pointer to the used projectile
+} weaponinfo_t;
+
+//setup the weapon AI
+int BotSetupWeaponAI(void);
+//shut down the weapon AI
+void BotShutdownWeaponAI(void);
+//returns the best weapon to fight with
+int BotChooseBestFightWeapon(int weaponstate, int *inventory);
+//returns the information of the current weapon
+void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo);
+//loads the weapon weights
+int BotLoadWeaponWeights(int weaponstate, char *filename);
+//returns a handle to a newly allocated weapon state
+int BotAllocWeaponState(void);
+//frees the weapon state
+void BotFreeWeaponState(int weaponstate);
+//resets the whole weapon state
+void BotResetWeaponState(int weaponstate);
diff --git a/src/botlib/be_ai_weight.c b/src/botlib/be_ai_weight.c
new file mode 100644
index 00000000..2324ed21
--- /dev/null
+++ b/src/botlib/be_ai_weight.c
@@ -0,0 +1,912 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_weight.c
+ *
+ * desc: fuzzy logic
+ *
+ * $Archive: /MissionPack/code/botlib/be_ai_weight.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "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
+
+ PC_SetBaseFolder(BOTFILESBASEFOLDER);
+ 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
+ botimport.Print(PRT_MESSAGE, "loaded %s\n", filename);
+#ifdef DEBUG
+ 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 00000000..fb1c8853
--- /dev/null
+++ b/src/botlib/be_ai_weight.h
@@ -0,0 +1,83 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ai_weight.h
+ *
+ * desc: fuzzy weights
+ *
+ * $Archive: /source/code/botlib/be_ai_weight.h $
+ *
+ *****************************************************************************/
+
+#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 00000000..3e284197
--- /dev/null
+++ b/src/botlib/be_ea.c
@@ -0,0 +1,508 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_ea.c
+ *
+ * desc: elementary actions
+ *
+ * $Archive: /MissionPack/code/botlib/be_ea.c $
+ *
+ *****************************************************************************/
+
+#include "../qcommon/q_shared.h"
+#include "l_memory.h"
+#include "l_script.h"
+#include "l_precomp.h"
+#include "l_struct.h"
+#include "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_Tell(int client, int clientto, char *str)
+{
+ botimport.BotClientCommand(client, va("tell %d, %s", clientto, 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_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_Action(int client, int action)
+{
+ bot_input_t *bi;
+
+ bi = &botinputs[client];
+
+ bi->actionflags |= action;
+} //end of function EA_Action
+//===========================================================================
+//
+// 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_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;
+ Com_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 *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;
+} //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_ea.h b/src/botlib/be_ea.h
new file mode 100644
index 00000000..4fb37046
--- /dev/null
+++ b/src/botlib/be_ea.h
@@ -0,0 +1,66 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: be_ea.h
+ *
+ * desc: elementary actions
+ *
+ * $Archive: /source/code/botlib/be_ea.h $
+ *
+ *****************************************************************************/
+
+//ClientCommand elementary actions
+void EA_Say(int client, char *str);
+void EA_SayTeam(int client, char *str);
+void EA_Command(int client, char *command );
+
+void EA_Action(int client, int action);
+void EA_Crouch(int client);
+void EA_Walk(int client);
+void EA_MoveUp(int client);
+void EA_MoveDown(int client);
+void EA_MoveForward(int client);
+void EA_MoveBack(int client);
+void EA_MoveLeft(int client);
+void EA_MoveRight(int client);
+void EA_Attack(int client);
+void EA_Respawn(int client);
+void EA_Talk(int client);
+void EA_Gesture(int client);
+void EA_Use(int client);
+
+//regular elementary actions
+void EA_SelectWeapon(int client, int weapon);
+void EA_Jump(int client);
+void EA_DelayedJump(int client);
+void EA_Move(int client, vec3_t dir, float speed);
+void EA_View(int client, vec3_t viewangles);
+
+//send regular input to the server
+void EA_EndRegular(int client, float thinktime);
+void EA_GetInput(int client, float thinktime, bot_input_t *input);
+void EA_ResetInput(int client);
+//setup and shutdown routines
+int EA_Setup(void);
+void EA_Shutdown(void);
diff --git a/src/botlib/be_interface.c b/src/botlib/be_interface.c
new file mode 100644
index 00000000..d0585731
--- /dev/null
+++ b/src/botlib/be_interface.c
@@ -0,0 +1,881 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_interface.c // bk010221 - FIXME - DEAD code elimination
+ *
+ * desc: bot library interface
+ *
+ * $Archive: /MissionPack/code/botlib/be_interface.c $
+ *
+ *****************************************************************************/
+
+#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 "botlib.h"
+#include "be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_aas_def.h"
+#include "be_interface.h"
+
+#include "be_ea.h"
+#include "be_ai_weight.h"
+#include "be_ai_goal.h"
+#include "be_ai_move.h"
+#include "be_ai_weap.h"
+#include "be_ai_chat.h"
+#include "be_ai_char.h"
+#include "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: -
+//===========================================================================
+int Sys_MilliSeconds(void)
+{
+ return clock() * 1000 / CLOCKS_PER_SEC;
+} //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)
+{
+ 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: -
+//===========================================================================
+int Export_BotLibSetup(void)
+{
+ int errnum;
+
+ bot_developer = LibVarGetValue("bot_developer");
+ memset( &botlibglobals, 0, sizeof(botlibglobals) ); // bk001207 - init
+ //initialize byte swapping (litte endian etc.)
+// Swap_Init();
+ 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;
+ errnum = BotSetupGoalAI(); //be_ai_goal.c
+ 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;
+
+ botlibsetup = qtrue;
+ botlibglobals.botlibsetup = qtrue;
+
+ return BLERR_NOERROR;
+} //end of the function Export_BotLibSetup
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int Export_BotLibShutdown(void)
+{
+ if (!BotLibSetup("BotLibShutdown")) return BLERR_LIBRARYNOTSETUP;
+#ifndef DEMO
+ //DumpFileCRCs();
+#endif //DEMO
+ //
+ 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
+ //shud down aas
+ AAS_Shutdown();
+ //shut down bot elemantary actions
+ EA_Shutdown();
+ //free all libvars
+ LibVarDeAllocAll();
+ //remove all global defines from the pre compiler
+ PC_RemoveAllGlobalDefines();
+
+ //dump all allocated memory
+// DumpMemory();
+#ifdef DEBUG
+ PrintMemoryLabels();
+#endif
+ //shut down library log file
+ Log_Shutdown();
+ //
+ botlibsetup = qfalse;
+ botlibglobals.botlibsetup = qfalse;
+ // print any files still open
+ PC_CheckOpenSourceHandles();
+ //
+ 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: -
+//===========================================================================
+int Export_BotLibLoadMap(const char *mapname)
+{
+#ifdef DEBUG
+ int starttime = Sys_MilliSeconds();
+#endif
+ int errnum;
+
+ if (!BotLibSetup("BotLoadMap")) return BLERR_LIBRARYNOTSETUP;
+ //
+ 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 lastgoalareanum, int lastareanum,
+ int *avoidreach, float *avoidreachtimes, int *avoidreachtries,
+ bot_goal_t *goal, int travelflags, int movetravelflags,
+ struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags);
+
+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);
+
+void AAS_FloodAreas(vec3_t origin);
+
+int BotExportTest(int parm0, char *parm1, vec3_t parm2, vec3_t parm3)
+{
+
+// return AAS_PointLight(parm2, NULL, NULL, NULL);
+
+#ifdef DEBUG
+ static int area = -1;
+ static int line[2];
+ int newarea, i, highlightarea, flood;
+// int reachnum;
+ vec3_t eye, forward, right, end, origin;
+// vec3_t bottomcenter;
+// aas_trace_t trace;
+// aas_face_t *face;
+// aas_entity_t *ent;
+// bsp_trace_t bsptrace;
+// aas_reachability_t reach;
+// bot_goal_t goal;
+
+ // clock_t start_time, end_time;
+ vec3_t mins = {-16, -16, -24};
+ vec3_t maxs = {16, 16, 32};
+
+// int areas[10], numareas;
+
+
+ //return 0;
+
+ if (!aasworld.loaded) return 0;
+
+ /*
+ if (parm0 & 1)
+ {
+ AAS_ClearShownPolygons();
+ AAS_FloodAreas(parm2);
+ } //end if
+ return 0;
+ */
+ for (i = 0; i < 2; i++) if (!line[i]) line[i] = botimport.DebugLineCreate();
+
+// AAS_ClearShownDebugLines();
+
+ //if (AAS_AgainstLadder(parm2)) botimport.Print(PRT_MESSAGE, "against ladder\n");
+ //BotOnGround(parm2, PRESENCE_NORMAL, 1, &newarea, &newarea);
+ //botimport.Print(PRT_MESSAGE, "%f %f %f\n", parm2[0], parm2[1], parm2[2]);
+ //*
+ highlightarea = LibVarGetValue("bot_highlightarea");
+ if (highlightarea > 0)
+ {
+ newarea = highlightarea;
+ } //end if
+ else
+ {
+ VectorCopy(parm2, origin);
+ origin[2] += 0.5;
+ //newarea = AAS_PointAreaNum(origin);
+ newarea = BotFuzzyPointReachabilityArea(origin);
+ } //end else
+
+ botimport.Print(PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum,
+ AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT));
+ //newarea = BotReachabilityArea(origin, qtrue);
+ 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));
+ botimport.Print(PRT_MESSAGE, "area contents: ");
+ 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, "slime &");
+ } //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_VIEWPORTAL)
+ {
+ botimport.Print(PRT_MESSAGE, "view 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_MOVER)
+ {
+ botimport.Print(PRT_MESSAGE, "mover &");
+ } //end if
+ if (!aasworld.areasettings[area].contents)
+ {
+ botimport.Print(PRT_MESSAGE, "empty");
+ } //end if
+ 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));
+ /*
+ VectorCopy(origin, end);
+ end[2] += 5;
+ numareas = AAS_TraceAreas(origin, end, areas, NULL, 10);
+ AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1);
+ botimport.Print(PRT_MESSAGE, "num areas = %d, area = %d\n", numareas, areas[0]);
+ */
+ /*
+ 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);
+ */
+ } //end if
+ //*
+ flood = LibVarGetValue("bot_flood");
+ if (parm0 & 1)
+ {
+ if (flood)
+ {
+ AAS_ClearShownPolygons();
+ AAS_ClearShownDebugLines();
+ AAS_FloodAreas(parm2);
+ }
+ else
+ {
+ 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);
+ }
+ } //end if*/
+ if (flood)
+ return 0;
+// if (parm0 & BUTTON_USE)
+// {
+// botlibglobals.runai = !botlibglobals.runai;
+// if (botlibglobals.runai) botimport.Print(PRT_MESSAGE, "started AI\n");
+// else botimport.Print(PRT_MESSAGE, "stopped AI\n");
+ //* /
+ /*
+ goal.areanum = botlibglobals.goalareanum;
+ reachnum = BotGetReachabilityToGoal(parm2, newarea, 1,
+ ms.avoidreach, ms.avoidreachtimes,
+ &goal, TFL_DEFAULT);
+ if (!reachnum)
+ {
+ botimport.Print(PRT_MESSAGE, "goal not reachable\n");
+ } //end if
+ else
+ {
+ AAS_ReachabilityFromNum(reachnum, &reach);
+ AAS_ClearShownDebugLines();
+ AAS_ShowArea(area, qtrue);
+ AAS_ShowArea(reach.areanum, qtrue);
+ AAS_DrawCross(reach.start, 6, LINECOLOR_BLUE);
+ AAS_DrawCross(reach.end, 6, LINECOLOR_RED);
+ //
+ if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR)
+ {
+ ElevatorBottomCenter(&reach, bottomcenter);
+ AAS_DrawCross(bottomcenter, 10, LINECOLOR_GREEN);
+ } //end if
+ } //end else*/
+// botimport.Print(PRT_MESSAGE, "travel time to goal = %d\n",
+// AAS_AreaTravelTimeToGoalArea(area, origin, botlibglobals.goalareanum, TFL_DEFAULT));
+// botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n");
+// AAS_Reachability_WeaponJump(703, 716);
+// } //end if*/
+
+/* face = AAS_AreaGroundFace(newarea, parm2);
+ if (face)
+ {
+ AAS_ShowFace(face - aasworld.faces);
+ } //end if*/
+ /*
+ AAS_ClearShownDebugLines();
+ AAS_ShowArea(newarea, parm0 & BUTTON_USE);
+ AAS_ShowReachableAreas(area);
+ */
+ AAS_ClearShownPolygons();
+ AAS_ClearShownDebugLines();
+ 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, resultFlags;
+ bot_goal_t goal;
+ aas_reachability_t reach;
+
+ /*
+ goal.areanum = botlibglobals.goalareanum;
+ VectorCopy(botlibglobals.goalorigin, goal.origin);
+ reachnum = BotGetReachabilityToGoal(origin, newarea,
+ lastgoalareanum, lastareanum,
+ avoidreach, avoidreachtimes, avoidreachtries,
+ &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP,
+ NULL, 0, &resultFlags);
+ AAS_ReachabilityFromNum(reachnum, &reach);
+ AAS_ShowReachability(&reach);
+ */
+ int curarea;
+ vec3_t curorigin;
+
+ goal.areanum = botlibglobals.goalareanum;
+ VectorCopy(botlibglobals.goalorigin, goal.origin);
+ VectorCopy(origin, curorigin);
+ curarea = newarea;
+ for ( i = 0; i < 100; i++ ) {
+ if ( curarea == goal.areanum ) {
+ break;
+ }
+ reachnum = BotGetReachabilityToGoal(curorigin, curarea,
+ lastgoalareanum, lastareanum,
+ avoidreach, avoidreachtimes, avoidreachtries,
+ &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP,
+ NULL, 0, &resultFlags);
+ AAS_ReachabilityFromNum(reachnum, &reach);
+ AAS_ShowReachability(&reach);
+ VectorCopy(reach.end, origin);
+ lastareanum = curarea;
+ curarea = reach.areanum;
+ }
+ } //end else
+ VectorClear(forward);
+ //BotGapDistance(origin, forward, 0);
+ /*
+ if (parm0 & BUTTON_USE)
+ {
+ botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n");
+ AAS_Reachability_WeaponJump(703, 716);
+ } //end if*/
+
+ AngleVectors(parm3, forward, right, NULL);
+ //get the eye 16 units to the right of the origin
+ VectorMA(parm2, 8, right, eye);
+ //get the eye 24 units up
+ eye[2] += 24;
+ //get the end point for the line to be traced
+ VectorMA(eye, 800, forward, end);
+
+// AAS_TestMovementPrediction(1, parm2, forward);
+/*
+ //trace the line to find the hit point
+ trace = AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1);
+ if (!line[0]) line[0] = botimport.DebugLineCreate();
+ botimport.DebugLineShow(line[0], eye, trace.endpos, LINECOLOR_BLUE);
+ //
+ AAS_ClearShownDebugLines();
+ if (trace.ent)
+ {
+ ent = &aasworld.entities[trace.ent];
+ AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs);
+ } //end if
+*/
+
+/*
+ start_time = clock();
+ for (i = 0; i < 2000; i++)
+ {
+ AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID);
+// AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1);
+ } //end for
+ end_time = clock();
+ botimport.Print(PRT_MESSAGE, "me %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC);
+ start_time = clock();
+ for (i = 0; i < 2000; i++)
+ {
+ AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID);
+ } //end for
+ end_time = clock();
+ botimport.Print(PRT_MESSAGE, "id %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC);
+*/
+
+ // TTimo: nested comments are BAD for gcc -Werror, use #if 0 instead..
+#if 0
+ AAS_ClearShownDebugLines();
+ //bsptrace = AAS_Trace(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID);
+ bsptrace = AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID);
+ if (!line[0]) line[0] = botimport.DebugLineCreate();
+ botimport.DebugLineShow(line[0], eye, bsptrace.endpos, LINECOLOR_YELLOW);
+ if (bsptrace.fraction < 1.0)
+ {
+ face = AAS_TraceEndFace(&trace);
+ if (face)
+ {
+ AAS_ShowFace(face - aasworld.faces);
+ } //end if
+
+ AAS_DrawPlaneCross(bsptrace.endpos,
+ bsptrace.plane.normal,
+ bsptrace.plane.dist + bsptrace.exp_dist,
+ bsptrace.plane.type, LINECOLOR_GREEN);
+ if (trace.ent)
+ {
+ ent = &aasworld.entities[trace.ent];
+ AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs);
+ } //end if
+ } //end if
+ //bsptrace = AAS_Trace2(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID);
+ bsptrace = AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID);
+ botimport.DebugLineShow(line[1], eye, bsptrace.endpos, LINECOLOR_BLUE);
+ if (bsptrace.fraction < 1.0)
+ {
+ AAS_DrawPlaneCross(bsptrace.endpos,
+ bsptrace.plane.normal,
+ bsptrace.plane.dist,// + bsptrace.exp_dist,
+ bsptrace.plane.type, LINECOLOR_RED);
+ if (bsptrace.ent)
+ {
+ ent = &aasworld.entities[bsptrace.ent];
+ AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs);
+ } //end if
+ } //end if
+#endif
+#endif
+ 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_PointReachabilityAreaIndex = AAS_PointReachabilityAreaIndex;
+ aas->AAS_TraceAreas = AAS_TraceAreas;
+ aas->AAS_BBoxAreas = AAS_BBoxAreas;
+ aas->AAS_AreaInfo = AAS_AreaInfo;
+ //--------------------------------------------
+ // 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;
+ //--------------------------------------------
+ // be_aas_route.c
+ //--------------------------------------------
+ aas->AAS_AreaTravelTimeToGoalArea = AAS_AreaTravelTimeToGoalArea;
+ aas->AAS_EnableRoutingArea = AAS_EnableRoutingArea;
+ aas->AAS_PredictRoute = AAS_PredictRoute;
+ //--------------------------------------------
+ // be_aas_altroute.c
+ //--------------------------------------------
+ aas->AAS_AlternativeRouteGoals = AAS_AlternativeRouteGoals;
+ //--------------------------------------------
+ // be_aas_move.c
+ //--------------------------------------------
+ aas->AAS_Swimming = AAS_Swimming;
+ aas->AAS_PredictClientMovement = AAS_PredictClientMovement;
+}
+
+
+/*
+============
+Init_EA_Export
+============
+*/
+static void Init_EA_Export( ea_export_t *ea ) {
+ //ClientCommand elementary actions
+ ea->EA_Command = EA_Command;
+ ea->EA_Say = EA_Say;
+ ea->EA_SayTeam = EA_SayTeam;
+
+ ea->EA_Action = EA_Action;
+ ea->EA_Gesture = EA_Gesture;
+ ea->EA_Talk = EA_Talk;
+ ea->EA_Attack = EA_Attack;
+ ea->EA_Use = EA_Use;
+ ea->EA_Respawn = EA_Respawn;
+ ea->EA_Crouch = EA_Crouch;
+ 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_SelectWeapon = EA_SelectWeapon;
+ ea->EA_Jump = EA_Jump;
+ ea->EA_DelayedJump = EA_DelayedJump;
+ 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;
+}
+
+
+/*
+============
+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->BotSetAvoidGoalTime = BotSetAvoidGoalTime;
+ 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;
+ ai->BotAddAvoidSpot = BotAddAvoidSpot;
+ //-----------------------------------
+ // 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) {
+ assert(import); // bk001129 - this wasn't set for baseq3/
+ botimport = *import;
+ assert(botimport.Print); // bk001129 - pars pro toto
+
+ Com_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_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.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/be_interface.h b/src/botlib/be_interface.h
new file mode 100644
index 00000000..956530b3
--- /dev/null
+++ b/src/botlib/be_interface.h
@@ -0,0 +1,57 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: be_interface.h
+ *
+ * desc: botlib interface
+ *
+ * $Archive: /source/code/botlib/be_interface.h $
+ *
+ *****************************************************************************/
+
+//#define DEBUG //debug code
+#define RANDOMIZE //randomize bot behaviour
+
+//FIXME: get rid of this global structure
+typedef struct botlib_globals_s
+{
+ int botlibsetup; //true when the bot library has been setup
+ int maxentities; //maximum number of entities
+ int maxclients; //maximum number of clients
+ float time; //the global time
+#ifdef DEBUG
+ qboolean debug; //true if debug is on
+ int goalareanum;
+ vec3_t goalorigin;
+ int runai;
+#endif
+} botlib_globals_t;
+
+
+extern botlib_globals_t botlibglobals;
+extern botlib_import_t botimport;
+extern int bot_developer; //true if developer is on
+
+//
+int Sys_MilliSeconds(void);
+
diff --git a/src/botlib/botlib.h b/src/botlib/botlib.h
new file mode 100644
index 00000000..a3f75161
--- /dev/null
+++ b/src/botlib/botlib.h
@@ -0,0 +1,516 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+/*****************************************************************************
+ * name: botlib.h
+ *
+ * desc: bot AI library
+ *
+ * $Archive: /source/code/game/botai.h $
+ *
+ *****************************************************************************/
+
+#define BOTLIB_API_VERSION 2
+
+struct aas_clientmove_s;
+struct aas_entityinfo_s;
+struct aas_areainfo_s;
+struct aas_altroutegoal_s;
+struct aas_predictroute_s;
+struct bot_consolemessage_s;
+struct bot_match_s;
+struct bot_goal_s;
+struct bot_moveresult_s;
+struct bot_initmove_s;
+struct weaponinfo_s;
+
+#define BOTFILESBASEFOLDER "botfiles"
+//debug line colors
+#define LINECOLOR_NONE -1
+#define LINECOLOR_RED 1//0xf2f2f0f0L
+#define LINECOLOR_GREEN 2//0xd0d1d2d3L
+#define LINECOLOR_BLUE 3//0xf3f3f1f1L
+#define LINECOLOR_YELLOW 4//0xdcdddedfL
+#define LINECOLOR_ORANGE 5//0xe0e1e2e3L
+
+//Print types
+#define PRT_MESSAGE 1
+#define PRT_WARNING 2
+#define PRT_ERROR 3
+#define PRT_FATAL 4
+#define PRT_EXIT 5
+
+//console message types
+#define CMS_NORMAL 0
+#define CMS_CHAT 1
+
+//botlib error codes
+#define BLERR_NOERROR 0 //no error
+#define BLERR_LIBRARYNOTSETUP 1 //library not setup
+#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number
+#define BLERR_NOAASFILE 3 //no AAS file available
+#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file
+#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id
+#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version
+#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump
+#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats
+#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights
+#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config
+#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights
+#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config
+
+//action flags
+#define ACTION_ATTACK 0x0000001
+#define ACTION_USE 0x0000002
+#define ACTION_RESPAWN 0x0000008
+#define ACTION_JUMP 0x0000010
+#define ACTION_MOVEUP 0x0000020
+#define ACTION_CROUCH 0x0000080
+#define ACTION_MOVEDOWN 0x0000100
+#define ACTION_MOVEFORWARD 0x0000200
+#define ACTION_MOVEBACK 0x0000800
+#define ACTION_MOVELEFT 0x0001000
+#define ACTION_MOVERIGHT 0x0002000
+#define ACTION_DELAYEDJUMP 0x0008000
+#define ACTION_TALK 0x0010000
+#define ACTION_GESTURE 0x0020000
+#define ACTION_WALK 0x0080000
+#define ACTION_AFFIRMATIVE 0x0100000
+#define ACTION_NEGATIVE 0x0200000
+#define ACTION_GETFLAG 0x0800000
+#define ACTION_GUARDBASE 0x1000000
+#define ACTION_PATROL 0x2000000
+#define ACTION_FOLLOWME 0x8000000
+
+//the bot input, will be converted to an usercmd_t
+typedef struct bot_input_s
+{
+ float thinktime; //time since last output (in seconds)
+ vec3_t dir; //movement direction
+ float speed; //speed in the range [0, 400]
+ vec3_t viewangles; //the view angles
+ int actionflags; //one of the ACTION_? flags
+ int weapon; //weapon to use
+} bot_input_t;
+
+#ifndef BSPTRACE
+
+#define BSPTRACE
+
+//bsp_trace_t hit surface
+typedef struct bsp_surface_s
+{
+ char name[16];
+ int flags;
+ int value;
+} bsp_surface_t;
+
+//remove the bsp_trace_s structure definition l8r on
+//a trace is returned when a box is swept through the world
+typedef struct bsp_trace_s
+{
+ qboolean allsolid; // if true, plane is not valid
+ qboolean startsolid; // if true, the initial point was in a solid area
+ float fraction; // time completed, 1.0 = didn't hit anything
+ vec3_t endpos; // final position
+ cplane_t plane; // surface normal at impact
+ float exp_dist; // expanded plane distance
+ int sidenum; // number of the brush side hit
+ bsp_surface_t surface; // the hit point surface
+ int contents; // contents on other side of surface hit
+ int ent; // number of entity hit
+} bsp_trace_t;
+
+#endif // BSPTRACE
+
+//entity state
+typedef struct bot_entitystate_s
+{
+ int type; // entity type
+ int flags; // entity flags
+ vec3_t origin; // origin of the entity
+ vec3_t angles; // angles of the model
+ vec3_t old_origin; // for lerping
+ vec3_t mins; // bounding box minimums
+ vec3_t maxs; // bounding box maximums
+ int groundent; // ground entity
+ int solid; // solid type
+ int modelindex; // model used
+ int modelindex2; // weapons, CTF flags, etc
+ int frame; // model frame number
+ int event; // impulse events -- muzzle flashes, footsteps, etc
+ int eventParm; // even parameter
+ int powerups; // bit flags
+ int weapon; // determines weapon and flash model, etc
+ int legsAnim; // mask off ANIM_TOGGLEBIT
+ int torsoAnim; // mask off ANIM_TOGGLEBIT
+} bot_entitystate_t;
+
+//bot AI library exported functions
+typedef struct botlib_import_s
+{
+ //print messages from the bot library
+ void (QDECL *Print)(int type, char *fmt, ...);
+ //trace a bbox through the world
+ void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask);
+ //trace a bbox against a specific entity
+ void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask);
+ //retrieve the contents at the given point
+ int (*PointContents)(vec3_t point);
+ //check if the point is in potential visible sight
+ int (*inPVS)(vec3_t p1, vec3_t p2);
+ //retrieve the BSP entity data lump
+ char *(*BSPEntityData)(void);
+ //
+ void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin);
+ //send a bot client command
+ void (*BotClientCommand)(int client, char *command);
+ //memory allocation
+ void *(*GetMemory)(int size); // allocate from Zone
+ void (*FreeMemory)(void *ptr); // free memory from Zone
+ int (*AvailableMemory)(void); // available Zone memory
+ void *(*HunkAlloc)(int size); // allocate from hunk
+ //file system access
+ int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode );
+ int (*FS_Read)( void *buffer, int len, fileHandle_t f );
+ int (*FS_Write)( const void *buffer, int len, fileHandle_t f );
+ void (*FS_FCloseFile)( fileHandle_t f );
+ int (*FS_Seek)( fileHandle_t f, long offset, int origin );
+ //debug visualisation stuff
+ int (*DebugLineCreate)(void);
+ void (*DebugLineDelete)(int line);
+ void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color);
+ //
+ int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points);
+ void (*DebugPolygonDelete)(int id);
+} botlib_import_t;
+
+typedef struct aas_export_s
+{
+ //-----------------------------------
+ // be_aas_entity.h
+ //-----------------------------------
+ void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info);
+ //-----------------------------------
+ // be_aas_main.h
+ //-----------------------------------
+ int (*AAS_Initialized)(void);
+ void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs);
+ float (*AAS_Time)(void);
+ //--------------------------------------------
+ // be_aas_sample.c
+ //--------------------------------------------
+ int (*AAS_PointAreaNum)(vec3_t point);
+ int (*AAS_PointReachabilityAreaIndex)( vec3_t point );
+ int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas);
+ int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas);
+ int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info );
+ //--------------------------------------------
+ // be_aas_bspq3.c
+ //--------------------------------------------
+ int (*AAS_PointContents)(vec3_t point);
+ int (*AAS_NextBSPEntity)(int ent);
+ int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size);
+ int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v);
+ int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value);
+ int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value);
+ //--------------------------------------------
+ // be_aas_reach.c
+ //--------------------------------------------
+ int (*AAS_AreaReachability)(int areanum);
+ //--------------------------------------------
+ // be_aas_route.c
+ //--------------------------------------------
+ int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags);
+ int (*AAS_EnableRoutingArea)(int areanum, int enable);
+ int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin,
+ int goalareanum, int travelflags, int maxareas, int maxtime,
+ int stopevent, int stopcontents, int stoptfl, int stopareanum);
+ //--------------------------------------------
+ // be_aas_altroute.c
+ //--------------------------------------------
+ int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags,
+ struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals,
+ int type);
+ //--------------------------------------------
+ // be_aas_move.c
+ //--------------------------------------------
+ int (*AAS_Swimming)(vec3_t origin);
+ 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);
+} aas_export_t;
+
+typedef struct ea_export_s
+{
+ //ClientCommand elementary actions
+ void (*EA_Command)(int client, char *command );
+ void (*EA_Say)(int client, char *str);
+ void (*EA_SayTeam)(int client, char *str);
+ //
+ void (*EA_Action)(int client, int action);
+ void (*EA_Gesture)(int client);
+ void (*EA_Talk)(int client);
+ void (*EA_Attack)(int client);
+ void (*EA_Use)(int client);
+ void (*EA_Respawn)(int client);
+ void (*EA_MoveUp)(int client);
+ void (*EA_MoveDown)(int client);
+ void (*EA_MoveForward)(int client);
+ void (*EA_MoveBack)(int client);
+ void (*EA_MoveLeft)(int client);
+ void (*EA_MoveRight)(int client);
+ void (*EA_Crouch)(int client);
+
+ void (*EA_SelectWeapon)(int client, int weapon);
+ void (*EA_Jump)(int client);
+ void (*EA_DelayedJump)(int client);
+ void (*EA_Move)(int client, vec3_t dir, float speed);
+ void (*EA_View)(int client, vec3_t viewangles);
+ //send regular input to the server
+ void (*EA_EndRegular)(int client, float thinktime);
+ void (*EA_GetInput)(int client, float thinktime, bot_input_t *input);
+ void (*EA_ResetInput)(int client);
+} ea_export_t;
+
+typedef struct ai_export_s
+{
+ //-----------------------------------
+ // be_ai_char.h
+ //-----------------------------------
+ int (*BotLoadCharacter)(char *charfile, float skill);
+ void (*BotFreeCharacter)(int character);
+ float (*Characteristic_Float)(int character, int index);
+ float (*Characteristic_BFloat)(int character, int index, float min, float max);
+ int (*Characteristic_Integer)(int character, int index);
+ int (*Characteristic_BInteger)(int character, int index, int min, int max);
+ void (*Characteristic_String)(int character, int index, char *buf, int size);
+ //-----------------------------------
+ // be_ai_chat.h
+ //-----------------------------------
+ int (*BotAllocChatState)(void);
+ void (*BotFreeChatState)(int handle);
+ void (*BotQueueConsoleMessage)(int chatstate, int type, char *message);
+ void (*BotRemoveConsoleMessage)(int chatstate, int handle);
+ int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm);
+ int (*BotNumConsoleMessages)(int chatstate);
+ void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7);
+ int (*BotNumInitialChats)(int chatstate, char *type);
+ 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);
+ int (*BotChatLength)(int chatstate);
+ void (*BotEnterChat)(int chatstate, int client, int sendto);
+ void (*BotGetChatMessage)(int chatstate, char *buf, int size);
+ int (*StringContains)(char *str1, char *str2, int casesensitive);
+ int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context);
+ void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size);
+ void (*UnifyWhiteSpaces)(char *string);
+ void (*BotReplaceSynonyms)(char *string, unsigned long int context);
+ int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname);
+ void (*BotSetChatGender)(int chatstate, int gender);
+ void (*BotSetChatName)(int chatstate, char *name, int client);
+ //-----------------------------------
+ // be_ai_goal.h
+ //-----------------------------------
+ void (*BotResetGoalState)(int goalstate);
+ void (*BotResetAvoidGoals)(int goalstate);
+ void (*BotRemoveFromAvoidGoals)(int goalstate, int number);
+ void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal);
+ void (*BotPopGoal)(int goalstate);
+ void (*BotEmptyGoalStack)(int goalstate);
+ void (*BotDumpAvoidGoals)(int goalstate);
+ void (*BotDumpGoalStack)(int goalstate);
+ void (*BotGoalName)(int number, char *name, int size);
+ int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal);
+ int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal);
+ int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags);
+ int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags,
+ struct bot_goal_s *ltg, float maxtime);
+ int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal);
+ int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal);
+ int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal);
+ int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal);
+ int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal);
+ float (*BotAvoidGoalTime)(int goalstate, int number);
+ void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime);
+ void (*BotInitLevelItems)(void);
+ void (*BotUpdateEntityItems)(void);
+ int (*BotLoadItemWeights)(int goalstate, char *filename);
+ void (*BotFreeItemWeights)(int goalstate);
+ void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child);
+ void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename);
+ void (*BotMutateGoalFuzzyLogic)(int goalstate, float range);
+ int (*BotAllocGoalState)(int client);
+ void (*BotFreeGoalState)(int handle);
+ //-----------------------------------
+ // be_ai_move.h
+ //-----------------------------------
+ void (*BotResetMoveState)(int movestate);
+ void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags);
+ int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type);
+ void (*BotResetAvoidReach)(int movestate);
+ void (*BotResetLastAvoidReach)(int movestate);
+ int (*BotReachabilityArea)(vec3_t origin, int testground);
+ int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target);
+ int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target);
+ int (*BotAllocMoveState)(void);
+ void (*BotFreeMoveState)(int handle);
+ void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove);
+ void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type);
+ //-----------------------------------
+ // be_ai_weap.h
+ //-----------------------------------
+ int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory);
+ void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo);
+ int (*BotLoadWeaponWeights)(int weaponstate, char *filename);
+ int (*BotAllocWeaponState)(void);
+ void (*BotFreeWeaponState)(int weaponstate);
+ void (*BotResetWeaponState)(int weaponstate);
+ //-----------------------------------
+ // be_ai_gen.h
+ //-----------------------------------
+ int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child);
+} ai_export_t;
+
+//bot AI library imported functions
+typedef struct botlib_export_s
+{
+ //Area Awareness System functions
+ aas_export_t aas;
+ //Elementary Action functions
+ ea_export_t ea;
+ //AI functions
+ ai_export_t ai;
+ //setup the bot library, returns BLERR_
+ int (*BotLibSetup)(void);
+ //shutdown the bot library, returns BLERR_
+ int (*BotLibShutdown)(void);
+ //sets a library variable returns BLERR_
+ int (*BotLibVarSet)(char *var_name, char *value);
+ //gets a library variable returns BLERR_
+ int (*BotLibVarGet)(char *var_name, char *value, int size);
+
+ //sets a C-like define returns BLERR_
+ int (*PC_AddGlobalDefine)(char *string);
+ int (*PC_LoadSourceHandle)(const char *filename);
+ int (*PC_FreeSourceHandle)(int handle);
+ int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token);
+ int (*PC_SourceFileAndLine)(int handle, char *filename, int *line);
+
+ //start a frame in the bot library
+ int (*BotLibStartFrame)(float time);
+ //load a new map in the bot library
+ int (*BotLibLoadMap)(const char *mapname);
+ //entity updates
+ int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state);
+ //just for testing
+ int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3);
+} botlib_export_t;
+
+//linking of bot library
+botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import );
+
+/* Library variables:
+
+name: default: module(s): description:
+
+"basedir" "" l_utils.c base directory
+"gamedir" "" l_utils.c game directory
+"cddir" "" l_utils.c CD directory
+
+"log" "0" l_log.c enable/disable creating a log file
+"maxclients" "4" be_interface.c maximum number of clients
+"maxentities" "1024" be_interface.c maximum number of entities
+"bot_developer" "0" be_interface.c bot developer mode
+
+"phys_friction" "6" be_aas_move.c ground friction
+"phys_stopspeed" "100" be_aas_move.c stop speed
+"phys_gravity" "800" be_aas_move.c gravity value
+"phys_waterfriction" "1" be_aas_move.c water friction
+"phys_watergravity" "400" be_aas_move.c gravity in water
+"phys_maxvelocity" "320" be_aas_move.c maximum velocity
+"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity
+"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity
+"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity
+"phys_walkaccelerate" "10" be_aas_move.c walk acceleration
+"phys_airaccelerate" "1" be_aas_move.c air acceleration
+"phys_swimaccelerate" "4" be_aas_move.c swim acceleration
+"phys_maxstep" "18" be_aas_move.c maximum step height
+"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness
+"phys_maxbarrier" "32" be_aas_move.c maximum barrier height
+"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height
+"phys_jumpvel" "270" be_aas_move.c jump z velocity
+"phys_falldelta5" "40" be_aas_move.c
+"phys_falldelta10" "60" be_aas_move.c
+"rs_waterjump" "400" be_aas_move.c
+"rs_teleport" "50" be_aas_move.c
+"rs_barrierjump" "100" be_aas_move.c
+"rs_startcrouch" "300" be_aas_move.c
+"rs_startgrapple" "500" be_aas_move.c
+"rs_startwalkoffledge" "70" be_aas_move.c
+"rs_startjump" "300" be_aas_move.c
+"rs_rocketjump" "500" be_aas_move.c
+"rs_bfgjump" "500" be_aas_move.c
+"rs_jumppad" "250" be_aas_move.c
+"rs_aircontrolledjumppad" "300" be_aas_move.c
+"rs_funcbob" "300" be_aas_move.c
+"rs_startelevator" "50" be_aas_move.c
+"rs_falldamage5" "300" be_aas_move.c
+"rs_falldamage10" "500" be_aas_move.c
+"rs_maxjumpfallheight" "450" be_aas_move.c
+
+"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS
+"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB
+"forceclustering" "0" be_aas_main.c force recalculation of clusters
+"forcereachability" "0" be_aas_main.c force recalculation of reachabilities
+"forcewrite" "0" be_aas_main.c force writing of aas file
+"aasoptimize" "0" be_aas_main.c enable aas optimization
+"sv_mapChecksum" "0" be_aas_main.c BSP file checksum
+"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads
+
+"bot_reloadcharacters" "0" - reload bot character files
+"ai_gametype" "0" be_ai_goal.c game type
+"droppedweight" "1000" be_ai_goal.c additional dropped item weight
+"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping
+"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping
+"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling
+"entitytypemissile" "3" be_ai_move.c ET_MISSILE
+"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook
+"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple
+"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple
+"itemconfig" "items.c" be_ai_goal.c item configuration file
+"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file
+"synfile" "syn.c" be_ai_chat.c file with synonyms
+"rndfile" "rnd.c" be_ai_chat.c file with random strings
+"matchfile" "match.c" be_ai_chat.c file with match strings
+"nochat" "0" be_ai_chat.c disable chats
+"max_messages" "1024" be_ai_chat.c console message heap size
+"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info
+"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info
+"max_iteminfo" "256" be_ai_goal.c maximum number of item info
+"max_levelitems" "256" be_ai_goal.c maximum number of level items
+
+*/
+
diff --git a/src/botlib/l_crc.c b/src/botlib/l_crc.c
new file mode 100644
index 00000000..e27b146e
--- /dev/null
+++ b/src/botlib/l_crc.c
@@ -0,0 +1,151 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_crc.c
+ *
+ * desc: CRC calculation
+ *
+ * $Archive: /MissionPack/CODE/botlib/l_crc.c $
+ *
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../qcommon/q_shared.h"
+#include "botlib.h"
+#include "be_interface.h" //for botimport.Print
+
+
+// FIXME: byte swap?
+
+// this is a 16 bit, non-reflected CRC using the polynomial 0x1021
+// and the initial and final xor values shown below... in other words, the
+// CCITT standard CRC used by XMODEM
+
+#define CRC_INIT_VALUE 0xffff
+#define CRC_XOR_VALUE 0x0000
+
+unsigned short crctable[257] =
+{
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
+ 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
+ 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
+ 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
+ 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
+ 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
+ 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
+ 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
+ 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
+ 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
+ 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
+ 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
+ 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
+ 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
+ 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+ 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
+ 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
+ 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
+ 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
+ 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
+ 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
+ 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
+ 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
+};
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void CRC_Init(unsigned short *crcvalue)
+{
+ *crcvalue = CRC_INIT_VALUE;
+} //end of the function CRC_Init
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void CRC_ProcessByte(unsigned short *crcvalue, byte data)
+{
+ *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data];
+} //end of the function CRC_ProcessByte
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+unsigned short CRC_Value(unsigned short crcvalue)
+{
+ return crcvalue ^ CRC_XOR_VALUE;
+} //end of the function CRC_Value
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+unsigned short CRC_ProcessString(unsigned char *data, int length)
+{
+ unsigned short crcvalue;
+ int i, ind;
+
+ CRC_Init(&crcvalue);
+
+ for (i = 0; i < length; i++)
+ {
+ ind = (crcvalue >> 8) ^ data[i];
+ if (ind < 0 || ind > 256) ind = 0;
+ crcvalue = (crcvalue << 8) ^ crctable[ind];
+ } //end for
+ return CRC_Value(crcvalue);
+} //end of the function CRC_ProcessString
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void CRC_ContinueProcessString(unsigned short *crc, char *data, int length)
+{
+ int i;
+
+ for (i = 0; i < length; i++)
+ {
+ *crc = (*crc << 8) ^ crctable[(*crc >> 8) ^ data[i]];
+ } //end for
+} //end of the function CRC_ProcessString
diff --git a/src/botlib/l_crc.h b/src/botlib/l_crc.h
new file mode 100644
index 00000000..f9c7e379
--- /dev/null
+++ b/src/botlib/l_crc.h
@@ -0,0 +1,29 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+typedef unsigned short crc_t;
+
+void CRC_Init(unsigned short *crcvalue);
+void CRC_ProcessByte(unsigned short *crcvalue, byte data);
+unsigned short CRC_Value(unsigned short crcvalue);
+unsigned short CRC_ProcessString(unsigned char *data, int length);
+void CRC_ContinueProcessString(unsigned short *crc, char *data, int length);
diff --git a/src/botlib/l_libvar.c b/src/botlib/l_libvar.c
new file mode 100644
index 00000000..6a063a93
--- /dev/null
+++ b/src/botlib/l_libvar.c
@@ -0,0 +1,294 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_libvar.c
+ *
+ * desc: bot library variables
+ *
+ * $Archive: /MissionPack/code/botlib/l_libvar.c $
+ *
+ *****************************************************************************/
+
+#include "../qcommon/q_shared.h"
+#include "l_memory.h"
+#include "l_libvar.h"
+
+//list with library variables
+libvar_t *libvarlist;
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+float LibVarStringValue(char *string)
+{
+ int dotfound = 0;
+ float value = 0;
+
+ while(*string)
+ {
+ if (*string < '0' || *string > '9')
+ {
+ if (dotfound || *string != '.')
+ {
+ return 0;
+ } //end if
+ else
+ {
+ dotfound = 10;
+ string++;
+ } //end if
+ } //end if
+ if (dotfound)
+ {
+ value = value + (float) (*string - '0') / (float) dotfound;
+ dotfound *= 10;
+ } //end if
+ else
+ {
+ value = value * 10.0 + (float) (*string - '0');
+ } //end else
+ string++;
+ } //end while
+ return value;
+} //end of the function LibVarStringValue
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+libvar_t *LibVarAlloc(char *var_name)
+{
+ libvar_t *v;
+
+ v = (libvar_t *) GetMemory(sizeof(libvar_t) + strlen(var_name) + 1);
+ Com_Memset(v, 0, sizeof(libvar_t));
+ v->name = (char *) v + sizeof(libvar_t);
+ strcpy(v->name, var_name);
+ //add the variable in the list
+ v->next = libvarlist;
+ libvarlist = v;
+ return v;
+} //end of the function LibVarAlloc
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void LibVarDeAlloc(libvar_t *v)
+{
+ if (v->string) FreeMemory(v->string);
+ FreeMemory(v);
+} //end of the function LibVarDeAlloc
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void LibVarDeAllocAll(void)
+{
+ libvar_t *v;
+
+ for (v = libvarlist; v; v = libvarlist)
+ {
+ libvarlist = libvarlist->next;
+ LibVarDeAlloc(v);
+ } //end for
+ libvarlist = NULL;
+} //end of the function LibVarDeAllocAll
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+libvar_t *LibVarGet(char *var_name)
+{
+ libvar_t *v;
+
+ for (v = libvarlist; v; v = v->next)
+ {
+ if (!Q_stricmp(v->name, var_name))
+ {
+ return v;
+ } //end if
+ } //end for
+ return NULL;
+} //end of the function LibVarGet
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+char *LibVarGetString(char *var_name)
+{
+ libvar_t *v;
+
+ v = LibVarGet(var_name);
+ if (v)
+ {
+ return v->string;
+ } //end if
+ else
+ {
+ return "";
+ } //end else
+} //end of the function LibVarGetString
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+float LibVarGetValue(char *var_name)
+{
+ libvar_t *v;
+
+ v = LibVarGet(var_name);
+ if (v)
+ {
+ return v->value;
+ } //end if
+ else
+ {
+ return 0;
+ } //end else
+} //end of the function LibVarGetValue
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+libvar_t *LibVar(char *var_name, char *value)
+{
+ libvar_t *v;
+ v = LibVarGet(var_name);
+ if (v) return v;
+ //create new variable
+ v = LibVarAlloc(var_name);
+ //variable string
+ v->string = (char *) GetMemory(strlen(value) + 1);
+ strcpy(v->string, value);
+ //the value
+ v->value = LibVarStringValue(v->string);
+ //variable is modified
+ v->modified = qtrue;
+ //
+ return v;
+} //end of the function LibVar
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+char *LibVarString(char *var_name, char *value)
+{
+ libvar_t *v;
+
+ v = LibVar(var_name, value);
+ return v->string;
+} //end of the function LibVarString
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+float LibVarValue(char *var_name, char *value)
+{
+ libvar_t *v;
+
+ v = LibVar(var_name, value);
+ return v->value;
+} //end of the function LibVarValue
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void LibVarSet(char *var_name, char *value)
+{
+ libvar_t *v;
+
+ v = LibVarGet(var_name);
+ if (v)
+ {
+ FreeMemory(v->string);
+ } //end if
+ else
+ {
+ v = LibVarAlloc(var_name);
+ } //end else
+ //variable string
+ v->string = (char *) GetMemory(strlen(value) + 1);
+ strcpy(v->string, value);
+ //the value
+ v->value = LibVarStringValue(v->string);
+ //variable is modified
+ v->modified = qtrue;
+} //end of the function LibVarSet
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+qboolean LibVarChanged(char *var_name)
+{
+ libvar_t *v;
+
+ v = LibVarGet(var_name);
+ if (v)
+ {
+ return v->modified;
+ } //end if
+ else
+ {
+ return qfalse;
+ } //end else
+} //end of the function LibVarChanged
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void LibVarSetNotModified(char *var_name)
+{
+ libvar_t *v;
+
+ v = LibVarGet(var_name);
+ if (v)
+ {
+ v->modified = qfalse;
+ } //end if
+} //end of the function LibVarSetNotModified
diff --git a/src/botlib/l_libvar.h b/src/botlib/l_libvar.h
new file mode 100644
index 00000000..d96685f4
--- /dev/null
+++ b/src/botlib/l_libvar.h
@@ -0,0 +1,63 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_libvar.h
+ *
+ * desc: botlib vars
+ *
+ * $Archive: /source/code/botlib/l_libvar.h $
+ *
+ *****************************************************************************/
+
+//library variable
+typedef struct libvar_s
+{
+ char *name;
+ char *string;
+ int flags;
+ qboolean modified; // set each time the cvar is changed
+ float value;
+ struct libvar_s *next;
+} libvar_t;
+
+//removes all library variables
+void LibVarDeAllocAll(void);
+//gets the library variable with the given name
+libvar_t *LibVarGet(char *var_name);
+//gets the string of the library variable with the given name
+char *LibVarGetString(char *var_name);
+//gets the value of the library variable with the given name
+float LibVarGetValue(char *var_name);
+//creates the library variable if not existing already and returns it
+libvar_t *LibVar(char *var_name, char *value);
+//creates the library variable if not existing already and returns the value
+float LibVarValue(char *var_name, char *value);
+//creates the library variable if not existing already and returns the value string
+char *LibVarString(char *var_name, char *value);
+//sets the library variable
+void LibVarSet(char *var_name, char *value);
+//returns true if the library variable has been modified
+qboolean LibVarChanged(char *var_name);
+//sets the library variable to unmodified
+void LibVarSetNotModified(char *var_name);
+
diff --git a/src/botlib/l_log.c b/src/botlib/l_log.c
new file mode 100644
index 00000000..1b41b500
--- /dev/null
+++ b/src/botlib/l_log.c
@@ -0,0 +1,169 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_log.c
+ *
+ * desc: log file
+ *
+ * $Archive: /MissionPack/CODE/botlib/l_log.c $
+ *
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../qcommon/q_shared.h"
+#include "botlib.h"
+#include "be_interface.h" //for botimport.Print
+#include "l_libvar.h"
+
+#define MAX_LOGFILENAMESIZE 1024
+
+typedef struct logfile_s
+{
+ char filename[MAX_LOGFILENAMESIZE];
+ FILE *fp;
+ int numwrites;
+} logfile_t;
+
+static logfile_t logfile;
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void Log_Open(char *filename)
+{
+ if (!LibVarValue("log", "0")) return;
+ if (!filename || !strlen(filename))
+ {
+ botimport.Print(PRT_MESSAGE, "openlog <filename>\n");
+ return;
+ } //end if
+ if (logfile.fp)
+ {
+ botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename);
+ return;
+ } //end if
+ logfile.fp = fopen(filename, "wb");
+ if (!logfile.fp)
+ {
+ botimport.Print(PRT_ERROR, "can't open the log file %s\n", filename);
+ return;
+ } //end if
+ strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE);
+ botimport.Print(PRT_MESSAGE, "Opened log %s\n", logfile.filename);
+} //end of the function Log_Create
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void Log_Close(void)
+{
+ if (!logfile.fp) return;
+ if (fclose(logfile.fp))
+ {
+ botimport.Print(PRT_ERROR, "can't close log file %s\n", logfile.filename);
+ return;
+ } //end if
+ logfile.fp = NULL;
+ botimport.Print(PRT_MESSAGE, "Closed log %s\n", logfile.filename);
+} //end of the function Log_Close
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void Log_Shutdown(void)
+{
+ if (logfile.fp) Log_Close();
+} //end of the function Log_Shutdown
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void QDECL Log_Write(char *fmt, ...)
+{
+ va_list ap;
+
+ if (!logfile.fp) return;
+ va_start(ap, fmt);
+ vfprintf(logfile.fp, fmt, ap);
+ va_end(ap);
+ //fprintf(logfile.fp, "\r\n");
+ fflush(logfile.fp);
+} //end of the function Log_Write
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void QDECL Log_WriteTimeStamped(char *fmt, ...)
+{
+ va_list ap;
+
+ if (!logfile.fp) 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);
+ va_start(ap, fmt);
+ vfprintf(logfile.fp, fmt, ap);
+ va_end(ap);
+ fprintf(logfile.fp, "\r\n");
+ logfile.numwrites++;
+ fflush(logfile.fp);
+} //end of the function Log_Write
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+FILE *Log_FilePointer(void)
+{
+ return logfile.fp;
+} //end of the function Log_FilePointer
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void Log_Flush(void)
+{
+ if (logfile.fp) fflush(logfile.fp);
+} //end of the function Log_Flush
+
diff --git a/src/botlib/l_log.h b/src/botlib/l_log.h
new file mode 100644
index 00000000..3ddb4641
--- /dev/null
+++ b/src/botlib/l_log.h
@@ -0,0 +1,46 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_log.h
+ *
+ * desc: log file
+ *
+ * $Archive: /source/code/botlib/l_log.h $
+ *
+ *****************************************************************************/
+
+//open a log file
+void Log_Open(char *filename);
+//close the current log file
+void Log_Close(void);
+//close log file if present
+void Log_Shutdown(void);
+//write to the current opened log file
+void QDECL Log_Write(char *fmt, ...);
+//write to the current opened log file with a time stamp
+void QDECL Log_WriteTimeStamped(char *fmt, ...);
+//returns a pointer to the log file
+FILE *Log_FilePointer(void);
+//flush log file
+void Log_Flush(void);
+
diff --git a/src/botlib/l_memory.c b/src/botlib/l_memory.c
new file mode 100644
index 00000000..0dad5b6a
--- /dev/null
+++ b/src/botlib/l_memory.c
@@ -0,0 +1,463 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_memory.c
+ *
+ * desc: memory allocation
+ *
+ * $Archive: /MissionPack/code/botlib/l_memory.c $
+ *
+ *****************************************************************************/
+
+#include "../qcommon/q_shared.h"
+#include "botlib.h"
+#include "l_log.h"
+#include "be_interface.h"
+
+//#define MEMDEBUG
+//#define MEMORYMANEGER
+
+#define MEM_ID 0x12345678l
+#define HUNK_ID 0x87654321l
+
+int allocatedmemory;
+int totalmemorysize;
+int numblocks;
+
+#ifdef MEMORYMANEGER
+
+typedef struct memoryblock_s
+{
+ unsigned long int id;
+ void *ptr;
+ int size;
+#ifdef MEMDEBUG
+ char *label;
+ char *file;
+ int line;
+#endif //MEMDEBUG
+ struct memoryblock_s *prev, *next;
+} memoryblock_t;
+
+memoryblock_t *memory;
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void LinkMemoryBlock(memoryblock_t *block)
+{
+ block->prev = NULL;
+ block->next = memory;
+ if (memory) memory->prev = block;
+ memory = block;
+} //end of the function LinkMemoryBlock
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void UnlinkMemoryBlock(memoryblock_t *block)
+{
+ if (block->prev) block->prev->next = block->next;
+ else memory = block->next;
+ if (block->next) block->next->prev = block->prev;
+} //end of the function UnlinkMemoryBlock
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+ memoryblock_t *block;
+ assert(botimport.GetMemory); // bk001129 - was NULL'ed
+ ptr = botimport.GetMemory(size + sizeof(memoryblock_t));
+ block = (memoryblock_t *) ptr;
+ block->id = MEM_ID;
+ block->ptr = (char *) ptr + sizeof(memoryblock_t);
+ block->size = size + sizeof(memoryblock_t);
+#ifdef MEMDEBUG
+ block->label = label;
+ block->file = file;
+ block->line = line;
+#endif //MEMDEBUG
+ LinkMemoryBlock(block);
+ allocatedmemory += block->size;
+ totalmemorysize += block->size + sizeof(memoryblock_t);
+ numblocks++;
+ return block->ptr;
+} //end of the function GetMemoryDebug
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetClearedMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+#ifdef MEMDEBUG
+ ptr = GetMemoryDebug(size, label, file, line);
+#else
+ ptr = GetMemory(size);
+#endif //MEMDEBUG
+ Com_Memset(ptr, 0, size);
+ return ptr;
+} //end of the function GetClearedMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetHunkMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+ memoryblock_t *block;
+
+ ptr = botimport.HunkAlloc(size + sizeof(memoryblock_t));
+ block = (memoryblock_t *) ptr;
+ block->id = HUNK_ID;
+ block->ptr = (char *) ptr + sizeof(memoryblock_t);
+ block->size = size + sizeof(memoryblock_t);
+#ifdef MEMDEBUG
+ block->label = label;
+ block->file = file;
+ block->line = line;
+#endif //MEMDEBUG
+ LinkMemoryBlock(block);
+ allocatedmemory += block->size;
+ totalmemorysize += block->size + sizeof(memoryblock_t);
+ numblocks++;
+ return block->ptr;
+} //end of the function GetHunkMemoryDebug
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetClearedHunkMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+#ifdef MEMDEBUG
+ ptr = GetHunkMemoryDebug(size, label, file, line);
+#else
+ ptr = GetHunkMemory(size);
+#endif //MEMDEBUG
+ Com_Memset(ptr, 0, size);
+ return ptr;
+} //end of the function GetClearedHunkMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+memoryblock_t *BlockFromPointer(void *ptr, char *str)
+{
+ memoryblock_t *block;
+
+ if (!ptr)
+ {
+#ifdef MEMDEBUG
+ //char *crash = (char *) NULL;
+ //crash[0] = 1;
+ botimport.Print(PRT_FATAL, "%s: NULL pointer\n", str);
+#endif // MEMDEBUG
+ return NULL;
+ } //end if
+ block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t));
+ if (block->id != MEM_ID && block->id != HUNK_ID)
+ {
+ botimport.Print(PRT_FATAL, "%s: invalid memory block\n", str);
+ return NULL;
+ } //end if
+ if (block->ptr != ptr)
+ {
+ botimport.Print(PRT_FATAL, "%s: memory block pointer invalid\n", str);
+ return NULL;
+ } //end if
+ return block;
+} //end of the function BlockFromPointer
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void FreeMemory(void *ptr)
+{
+ memoryblock_t *block;
+
+ block = BlockFromPointer(ptr, "FreeMemory");
+ if (!block) return;
+ UnlinkMemoryBlock(block);
+ allocatedmemory -= block->size;
+ totalmemorysize -= block->size + sizeof(memoryblock_t);
+ numblocks--;
+ //
+ if (block->id == MEM_ID)
+ {
+ botimport.FreeMemory(block);
+ } //end if
+} //end of the function FreeMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AvailableMemory(void)
+{
+ return botimport.AvailableMemory();
+} //end of the function AvailableMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int MemoryByteSize(void *ptr)
+{
+ memoryblock_t *block;
+
+ block = BlockFromPointer(ptr, "MemoryByteSize");
+ if (!block) return 0;
+ return block->size;
+} //end of the function MemoryByteSize
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void PrintUsedMemorySize(void)
+{
+ botimport.Print(PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10);
+ botimport.Print(PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10);
+ botimport.Print(PRT_MESSAGE, "total memory blocks: %d\n", numblocks);
+} //end of the function PrintUsedMemorySize
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void PrintMemoryLabels(void)
+{
+ memoryblock_t *block;
+ int i;
+
+ PrintUsedMemorySize();
+ i = 0;
+ Log_Write("============= Botlib memory log ==============\r\n");
+ Log_Write("\r\n");
+ for (block = memory; block; block = block->next)
+ {
+#ifdef MEMDEBUG
+ if (block->id == HUNK_ID)
+ {
+ Log_Write("%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label);
+ } //end if
+ else
+ {
+ Log_Write("%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label);
+ } //end else
+#endif //MEMDEBUG
+ i++;
+ } //end for
+} //end of the function PrintMemoryLabels
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void DumpMemory(void)
+{
+ memoryblock_t *block;
+
+ for (block = memory; block; block = memory)
+ {
+ FreeMemory(block->ptr);
+ } //end for
+ totalmemorysize = 0;
+ allocatedmemory = 0;
+} //end of the function DumpMemory
+
+#else
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+ unsigned long int *memid;
+
+ ptr = botimport.GetMemory(size + sizeof(unsigned long int));
+ if (!ptr) return NULL;
+ memid = (unsigned long int *) ptr;
+ *memid = MEM_ID;
+ return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int));
+} //end of the function GetMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetClearedMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+#ifdef MEMDEBUG
+ ptr = GetMemoryDebug(size, label, file, line);
+#else
+ ptr = GetMemory(size);
+#endif //MEMDEBUG
+ Com_Memset(ptr, 0, size);
+ return ptr;
+} //end of the function GetClearedMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetHunkMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+ unsigned long int *memid;
+
+ ptr = botimport.HunkAlloc(size + sizeof(unsigned long int));
+ if (!ptr) return NULL;
+ memid = (unsigned long int *) ptr;
+ *memid = HUNK_ID;
+ return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int));
+} //end of the function GetHunkMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+#ifdef MEMDEBUG
+void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line)
+#else
+void *GetClearedHunkMemory(unsigned long size)
+#endif //MEMDEBUG
+{
+ void *ptr;
+#ifdef MEMDEBUG
+ ptr = GetHunkMemoryDebug(size, label, file, line);
+#else
+ ptr = GetHunkMemory(size);
+#endif //MEMDEBUG
+ Com_Memset(ptr, 0, size);
+ return ptr;
+} //end of the function GetClearedHunkMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void FreeMemory(void *ptr)
+{
+ unsigned long int *memid;
+
+ memid = (unsigned long int *) ((char *) ptr - sizeof(unsigned long int));
+
+ if (*memid == MEM_ID)
+ {
+ botimport.FreeMemory(memid);
+ } //end if
+} //end of the function FreeMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int AvailableMemory(void)
+{
+ return botimport.AvailableMemory();
+} //end of the function AvailableMemory
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void PrintUsedMemorySize(void)
+{
+} //end of the function PrintUsedMemorySize
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void PrintMemoryLabels(void)
+{
+} //end of the function PrintMemoryLabels
+
+#endif
diff --git a/src/botlib/l_memory.h b/src/botlib/l_memory.h
new file mode 100644
index 00000000..17c89d5b
--- /dev/null
+++ b/src/botlib/l_memory.h
@@ -0,0 +1,76 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_memory.h
+ *
+ * desc: memory management
+ *
+ * $Archive: /source/code/botlib/l_memory.h $
+ *
+ *****************************************************************************/
+
+//#define MEMDEBUG
+
+#ifdef MEMDEBUG
+#define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__);
+#define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__);
+//allocate a memory block of the given size
+void *GetMemoryDebug(unsigned long size, char *label, char *file, int line);
+//allocate a memory block of the given size and clear it
+void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line);
+//
+#define GetHunkMemory(size) GetHunkMemoryDebug(size, #size, __FILE__, __LINE__);
+#define GetClearedHunkMemory(size) GetClearedHunkMemoryDebug(size, #size, __FILE__, __LINE__);
+//allocate a memory block of the given size
+void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line);
+//allocate a memory block of the given size and clear it
+void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line);
+#else
+//allocate a memory block of the given size
+void *GetMemory(unsigned long size);
+//allocate a memory block of the given size and clear it
+void *GetClearedMemory(unsigned long size);
+//
+#ifdef BSPC
+#define GetHunkMemory GetMemory
+#define GetClearedHunkMemory GetClearedMemory
+#else
+//allocate a memory block of the given size
+void *GetHunkMemory(unsigned long size);
+//allocate a memory block of the given size and clear it
+void *GetClearedHunkMemory(unsigned long size);
+#endif
+#endif
+
+//free the given memory block
+void FreeMemory(void *ptr);
+//returns the amount available memory
+int AvailableMemory(void);
+//prints the total used memory size
+void PrintUsedMemorySize(void);
+//print all memory blocks with label
+void PrintMemoryLabels(void);
+//returns the size of the memory block in bytes
+int MemoryByteSize(void *ptr);
+//free all allocated memory
+void DumpMemory(void);
diff --git a/src/botlib/l_precomp.c b/src/botlib/l_precomp.c
new file mode 100644
index 00000000..49f13ff0
--- /dev/null
+++ b/src/botlib/l_precomp.c
@@ -0,0 +1,3233 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+
+/*****************************************************************************
+ * name: l_precomp.c
+ *
+ * desc: pre compiler
+ *
+ * $Archive: /MissionPack/code/botlib/l_precomp.c $
+ *
+ *****************************************************************************/
+
+//Notes: fix: PC_StringizeTokens
+
+//#define SCREWUP
+//#define BOTLIB
+//#define QUAKE
+//#define QUAKEC
+//#define MEQCC
+
+#ifdef SCREWUP
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include "l_memory.h"
+#include "l_script.h"
+#include "l_precomp.h"
+
+typedef enum {qfalse, qtrue} qboolean;
+#endif //SCREWUP
+
+#ifdef BOTLIB
+#include "../qcommon/q_shared.h"
+#include "botlib.h"
+#include "be_interface.h"
+#include "l_memory.h"
+#include "l_script.h"
+#include "l_precomp.h"
+#include "l_log.h"
+#endif //BOTLIB
+
+#ifdef MEQCC
+#include "qcc.h"
+#include "time.h" //time & ctime
+#include "math.h" //fabs
+#include "l_memory.h"
+#include "l_script.h"
+#include "l_precomp.h"
+#include "l_log.h"
+
+#define qtrue true
+#define qfalse false
+#endif //MEQCC
+
+#ifdef BSPC
+//include files for usage in the BSP Converter
+#include "../bspc/qbsp.h"
+#include "../bspc/l_log.h"
+#include "../bspc/l_mem.h"
+#include "l_precomp.h"
+
+#define qtrue true
+#define qfalse false
+#define Q_stricmp stricmp
+
+#endif //BSPC
+
+#if defined(QUAKE) && !defined(BSPC)
+#include "l_utils.h"
+#endif //QUAKE
+
+//#define DEBUG_EVAL
+
+#define MAX_DEFINEPARMS 128
+
+#define DEFINEHASHING 1
+
+//directive name with parse function
+typedef struct directive_s
+{
+ char *name;
+ int (*func)(source_t *source);
+} directive_t;
+
+#define DEFINEHASHSIZE 1024
+
+#define TOKEN_HEAP_SIZE 4096
+
+int numtokens;
+/*
+int tokenheapinitialized; //true when the token heap is initialized
+token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens
+token_t *freetokens; //free tokens from the heap
+*/
+
+//list with global defines added to every source loaded
+define_t *globaldefines;
+
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void QDECL SourceError(source_t *source, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+#ifdef BOTLIB
+ botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+#endif //BOTLIB
+#ifdef MEQCC
+ printf("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+#endif //MEQCC
+#ifdef BSPC
+ Log_Print("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+#endif //BSPC
+} //end of the function SourceError
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void QDECL SourceWarning(source_t *source, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+#ifdef BOTLIB
+ botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+#endif //BOTLIB
+#ifdef MEQCC
+ printf("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+#endif //MEQCC
+#ifdef BSPC
+ Log_Print("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text);
+#endif //BSPC
+} //end of the function ScriptWarning
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_PushIndent(source_t *source, int type, int skip)
+{
+ indent_t *indent;
+
+ indent = (indent_t *) GetMemory(sizeof(indent_t));
+ indent->type = type;
+ indent->script = source->scriptstack;
+ indent->skip = (skip != 0);
+ source->skip += indent->skip;
+ indent->next = source->indentstack;
+ source->indentstack = indent;
+} //end of the function PC_PushIndent
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_PopIndent(source_t *source, int *type, int *skip)
+{
+ indent_t *indent;
+
+ *type = 0;
+ *skip = 0;
+
+ indent = source->indentstack;
+ if (!indent) return;
+
+ //must be an indent from the current script
+ if (source->indentstack->script != source->scriptstack) return;
+
+ *type = indent->type;
+ *skip = indent->skip;
+ source->indentstack = source->indentstack->next;
+ source->skip -= indent->skip;
+ FreeMemory(indent);
+} //end of the function PC_PopIndent
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_PushScript(source_t *source, script_t *script)
+{
+ script_t *s;
+
+ for (s = source->scriptstack; s; s = s->next)
+ {
+ if (!Q_stricmp(s->filename, script->filename))
+ {
+ SourceError(source, "%s recursively included", script->filename);
+ return;
+ } //end if
+ } //end for
+ //push the script on the script stack
+ script->next = source->scriptstack;
+ source->scriptstack = script;
+} //end of the function PC_PushScript
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_InitTokenHeap(void)
+{
+ /*
+ int i;
+
+ if (tokenheapinitialized) return;
+ freetokens = NULL;
+ for (i = 0; i < TOKEN_HEAP_SIZE; i++)
+ {
+ token_heap[i].next = freetokens;
+ freetokens = &token_heap[i];
+ } //end for
+ tokenheapinitialized = qtrue;
+ */
+} //end of the function PC_InitTokenHeap
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+token_t *PC_CopyToken(token_t *token)
+{
+ token_t *t;
+
+// t = (token_t *) malloc(sizeof(token_t));
+ t = (token_t *) GetMemory(sizeof(token_t));
+// t = freetokens;
+ if (!t)
+ {
+#ifdef BSPC
+ Error("out of token space\n");
+#else
+ Com_Error(ERR_FATAL, "out of token space\n");
+#endif
+ return NULL;
+ } //end if
+// freetokens = freetokens->next;
+ Com_Memcpy(t, token, sizeof(token_t));
+ t->next = NULL;
+ numtokens++;
+ return t;
+} //end of the function PC_CopyToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_FreeToken(token_t *token)
+{
+ //free(token);
+ FreeMemory(token);
+// token->next = freetokens;
+// freetokens = token;
+ numtokens--;
+} //end of the function PC_FreeToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ReadSourceToken(source_t *source, token_t *token)
+{
+ token_t *t;
+ script_t *script;
+ int type, skip;
+
+ //if there's no token already available
+ while(!source->tokens)
+ {
+ //if there's a token to read from the script
+ if (PS_ReadToken(source->scriptstack, token)) return qtrue;
+ //if at the end of the script
+ if (EndOfScript(source->scriptstack))
+ {
+ //remove all indents of the script
+ while(source->indentstack &&
+ source->indentstack->script == source->scriptstack)
+ {
+ SourceWarning(source, "missing #endif");
+ PC_PopIndent(source, &type, &skip);
+ } //end if
+ } //end if
+ //if this was the initial script
+ if (!source->scriptstack->next) return qfalse;
+ //remove the script and return to the last one
+ script = source->scriptstack;
+ source->scriptstack = source->scriptstack->next;
+ FreeScript(script);
+ } //end while
+ //copy the already available token
+ Com_Memcpy(token, source->tokens, sizeof(token_t));
+ //free the read token
+ t = source->tokens;
+ source->tokens = source->tokens->next;
+ PC_FreeToken(t);
+ return qtrue;
+} //end of the function PC_ReadSourceToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_UnreadSourceToken(source_t *source, token_t *token)
+{
+ token_t *t;
+
+ t = PC_CopyToken(token);
+ t->next = source->tokens;
+ source->tokens = t;
+ return qtrue;
+} //end of the function PC_UnreadSourceToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms)
+{
+ token_t token, *t, *last;
+ int i, done, lastcomma, numparms, indent;
+
+ if (!PC_ReadSourceToken(source, &token))
+ {
+ SourceError(source, "define %s missing parms", define->name);
+ return qfalse;
+ } //end if
+ //
+ if (define->numparms > maxparms)
+ {
+ SourceError(source, "define with more than %d parameters", maxparms);
+ return qfalse;
+ } //end if
+ //
+ for (i = 0; i < define->numparms; i++) parms[i] = NULL;
+ //if no leading "("
+ if (strcmp(token.string, "("))
+ {
+ PC_UnreadSourceToken(source, &token);
+ SourceError(source, "define %s missing parms", define->name);
+ return qfalse;
+ } //end if
+ //read the define parameters
+ for (done = 0, numparms = 0, indent = 0; !done;)
+ {
+ if (numparms >= maxparms)
+ {
+ SourceError(source, "define %s with too many parms", define->name);
+ return qfalse;
+ } //end if
+ if (numparms >= define->numparms)
+ {
+ SourceWarning(source, "define %s has too many parms", define->name);
+ return qfalse;
+ } //end if
+ parms[numparms] = NULL;
+ lastcomma = 1;
+ last = NULL;
+ while(!done)
+ {
+ //
+ if (!PC_ReadSourceToken(source, &token))
+ {
+ SourceError(source, "define %s incomplete", define->name);
+ return qfalse;
+ } //end if
+ //
+ if (!strcmp(token.string, ","))
+ {
+ if (indent <= 0)
+ {
+ if (lastcomma) SourceWarning(source, "too many comma's");
+ lastcomma = 1;
+ break;
+ } //end if
+ } //end if
+ lastcomma = 0;
+ //
+ if (!strcmp(token.string, "("))
+ {
+ indent++;
+ continue;
+ } //end if
+ else if (!strcmp(token.string, ")"))
+ {
+ if (--indent <= 0)
+ {
+ if (!parms[define->numparms-1])
+ {
+ SourceWarning(source, "too few define parms");
+ } //end if
+ done = 1;
+ break;
+ } //end if
+ } //end if
+ //
+ if (numparms < define->numparms)
+ {
+ //
+ t = PC_CopyToken(&token);
+ t->next = NULL;
+ if (last) last->next = t;
+ else parms[numparms] = t;
+ last = t;
+ } //end if
+ } //end while
+ numparms++;
+ } //end for
+ return qtrue;
+} //end of the function PC_ReadDefineParms
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_StringizeTokens(token_t *tokens, token_t *token)
+{
+ token_t *t;
+
+ token->type = TT_STRING;
+ token->whitespace_p = NULL;
+ token->endwhitespace_p = NULL;
+ token->string[0] = '\0';
+ strcat(token->string, "\"");
+ for (t = tokens; t; t = t->next)
+ {
+ strncat(token->string, t->string, MAX_TOKEN - strlen(token->string));
+ } //end for
+ strncat(token->string, "\"", MAX_TOKEN - strlen(token->string));
+ return qtrue;
+} //end of the function PC_StringizeTokens
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_MergeTokens(token_t *t1, token_t *t2)
+{
+ //merging of a name with a name or number
+ if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER))
+ {
+ strcat(t1->string, t2->string);
+ return qtrue;
+ } //end if
+ //merging of two strings
+ if (t1->type == TT_STRING && t2->type == TT_STRING)
+ {
+ //remove trailing double quote
+ t1->string[strlen(t1->string)-1] = '\0';
+ //concat without leading double quote
+ strcat(t1->string, &t2->string[1]);
+ return qtrue;
+ } //end if
+ //FIXME: merging of two number of the same sub type
+ return qfalse;
+} //end of the function PC_MergeTokens
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+/*
+void PC_PrintDefine(define_t *define)
+{
+ printf("define->name = %s\n", define->name);
+ printf("define->flags = %d\n", define->flags);
+ printf("define->builtin = %d\n", define->builtin);
+ printf("define->numparms = %d\n", define->numparms);
+// token_t *parms; //define parameters
+// token_t *tokens; //macro tokens (possibly containing parm tokens)
+// struct define_s *next; //next defined macro in a list
+} //end of the function PC_PrintDefine*/
+#if DEFINEHASHING
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_PrintDefineHashTable(define_t **definehash)
+{
+ int i;
+ define_t *d;
+
+ for (i = 0; i < DEFINEHASHSIZE; i++)
+ {
+ Log_Write("%4d:", i);
+ for (d = definehash[i]; d; d = d->hashnext)
+ {
+ Log_Write(" %s", d->name);
+ } //end for
+ Log_Write("\n");
+ } //end for
+} //end of the function PC_PrintDefineHashTable
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47};
+
+int PC_NameHash(char *name)
+{
+ int register hash, i;
+
+ hash = 0;
+ for (i = 0; name[i] != '\0'; i++)
+ {
+ hash += name[i] * (119 + i);
+ //hash += (name[i] << 7) + i;
+ //hash += (name[i] << (i&15));
+ } //end while
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1);
+ return hash;
+} //end of the function PC_NameHash
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_AddDefineToHash(define_t *define, define_t **definehash)
+{
+ int hash;
+
+ hash = PC_NameHash(define->name);
+ define->hashnext = definehash[hash];
+ definehash[hash] = define;
+} //end of the function PC_AddDefineToHash
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+define_t *PC_FindHashedDefine(define_t **definehash, char *name)
+{
+ define_t *d;
+ int hash;
+
+ hash = PC_NameHash(name);
+ for (d = definehash[hash]; d; d = d->hashnext)
+ {
+ if (!strcmp(d->name, name)) return d;
+ } //end for
+ return NULL;
+} //end of the function PC_FindHashedDefine
+#endif //DEFINEHASHING
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+define_t *PC_FindDefine(define_t *defines, char *name)
+{
+ define_t *d;
+
+ for (d = defines; d; d = d->next)
+ {
+ if (!strcmp(d->name, name)) return d;
+ } //end for
+ return NULL;
+} //end of the function PC_FindDefine
+//============================================================================
+//
+// Parameter: -
+// Returns: number of the parm
+// if no parm found with the given name -1 is returned
+// Changes Globals: -
+//============================================================================
+int PC_FindDefineParm(define_t *define, char *name)
+{
+ token_t *p;
+ int i;
+
+ i = 0;
+ for (p = define->parms; p; p = p->next)
+ {
+ if (!strcmp(p->string, name)) return i;
+ i++;
+ } //end for
+ return -1;
+} //end of the function PC_FindDefineParm
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_FreeDefine(define_t *define)
+{
+ token_t *t, *next;
+
+ //free the define parameters
+ for (t = define->parms; t; t = next)
+ {
+ next = t->next;
+ PC_FreeToken(t);
+ } //end for
+ //free the define tokens
+ for (t = define->tokens; t; t = next)
+ {
+ next = t->next;
+ PC_FreeToken(t);
+ } //end for
+ //free the define
+ FreeMemory(define);
+} //end of the function PC_FreeDefine
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_AddBuiltinDefines(source_t *source)
+{
+ int i;
+ define_t *define;
+ struct builtin
+ {
+ char *string;
+ int builtin;
+ } builtin[] = { // bk001204 - brackets
+ { "__LINE__", BUILTIN_LINE },
+ { "__FILE__", BUILTIN_FILE },
+ { "__DATE__", BUILTIN_DATE },
+ { "__TIME__", BUILTIN_TIME },
+// { "__STDC__", BUILTIN_STDC },
+ { NULL, 0 }
+ };
+
+ for (i = 0; builtin[i].string; i++)
+ {
+ define = (define_t *) GetMemory(sizeof(define_t) + strlen(builtin[i].string) + 1);
+ Com_Memset(define, 0, sizeof(define_t));
+ define->name = (char *) define + sizeof(define_t);
+ strcpy(define->name, builtin[i].string);
+ define->flags |= DEFINE_FIXED;
+ define->builtin = builtin[i].builtin;
+ //add the define to the source
+#if DEFINEHASHING
+ PC_AddDefineToHash(define, source->definehash);
+#else
+ define->next = source->defines;
+ source->defines = define;
+#endif //DEFINEHASHING
+ } //end for
+} //end of the function PC_AddBuiltinDefines
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define,
+ token_t **firsttoken, token_t **lasttoken)
+{
+ token_t *token;
+#ifdef _WIN32
+ unsigned long t; // time_t t; //to prevent LCC warning
+#else
+ time_t t;
+#endif
+
+ char *curtime;
+
+ token = PC_CopyToken(deftoken);
+ switch(define->builtin)
+ {
+ case BUILTIN_LINE:
+ {
+ sprintf(token->string, "%d", deftoken->line);
+#ifdef NUMBERVALUE
+ token->intvalue = deftoken->line;
+ token->floatvalue = deftoken->line;
+#endif //NUMBERVALUE
+ token->type = TT_NUMBER;
+ token->subtype = TT_DECIMAL | TT_INTEGER;
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ } //end case
+ case BUILTIN_FILE:
+ {
+ strcpy(token->string, source->scriptstack->filename);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ } //end case
+ case BUILTIN_DATE:
+ {
+ t = time(NULL);
+ curtime = ctime(&t);
+ strcpy(token->string, "\"");
+ strncat(token->string, curtime+4, 7);
+ strncat(token->string+7, curtime+20, 4);
+ strcat(token->string, "\"");
+ free(curtime);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ } //end case
+ case BUILTIN_TIME:
+ {
+ t = time(NULL);
+ curtime = ctime(&t);
+ strcpy(token->string, "\"");
+ strncat(token->string, curtime+11, 8);
+ strcat(token->string, "\"");
+ free(curtime);
+ token->type = TT_NAME;
+ token->subtype = strlen(token->string);
+ *firsttoken = token;
+ *lasttoken = token;
+ break;
+ } //end case
+ case BUILTIN_STDC:
+ default:
+ {
+ *firsttoken = NULL;
+ *lasttoken = NULL;
+ break;
+ } //end case
+ } //end switch
+ return qtrue;
+} //end of the function PC_ExpandBuiltinDefine
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ExpandDefine(source_t *source, token_t *deftoken, define_t *define,
+ token_t **firsttoken, token_t **lasttoken)
+{
+ token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t;
+ token_t *t1, *t2, *first, *last, *nextpt, token;
+ int parmnum, i;
+
+ //if it is a builtin define
+ if (define->builtin)
+ {
+ return PC_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken);
+ } //end if
+ //if the define has parameters
+ if (define->numparms)
+ {
+ if (!PC_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse;
+#ifdef DEBUG_EVAL
+ for (i = 0; i < define->numparms; i++)
+ {
+ Log_Write("define parms %d:", i);
+ for (pt = parms[i]; pt; pt = pt->next)
+ {
+ Log_Write("%s", pt->string);
+ } //end for
+ } //end for
+#endif //DEBUG_EVAL
+ } //end if
+ //empty list at first
+ first = NULL;
+ last = NULL;
+ //create a list with tokens of the expanded define
+ for (dt = define->tokens; dt; dt = dt->next)
+ {
+ parmnum = -1;
+ //if the token is a name, it could be a define parameter
+ if (dt->type == TT_NAME)
+ {
+ parmnum = PC_FindDefineParm(define, dt->string);
+ } //end if
+ //if it is a define parameter
+ if (parmnum >= 0)
+ {
+ for (pt = parms[parmnum]; pt; pt = pt->next)
+ {
+ t = PC_CopyToken(pt);
+ //add the token to the list
+ t->next = NULL;
+ if (last) last->next = t;
+ else first = t;
+ last = t;
+ } //end for
+ } //end if
+ else
+ {
+ //if stringizing operator
+ if (dt->string[0] == '#' && dt->string[1] == '\0')
+ {
+ //the stringizing operator must be followed by a define parameter
+ if (dt->next) parmnum = PC_FindDefineParm(define, dt->next->string);
+ else parmnum = -1;
+ //
+ if (parmnum >= 0)
+ {
+ //step over the stringizing operator
+ dt = dt->next;
+ //stringize the define parameter tokens
+ if (!PC_StringizeTokens(parms[parmnum], &token))
+ {
+ SourceError(source, "can't stringize tokens");
+ return qfalse;
+ } //end if
+ t = PC_CopyToken(&token);
+ } //end if
+ else
+ {
+ SourceWarning(source, "stringizing operator without define parameter");
+ continue;
+ } //end if
+ } //end if
+ else
+ {
+ t = PC_CopyToken(dt);
+ } //end else
+ //add the token to the list
+ t->next = NULL;
+ if (last) last->next = t;
+ else first = t;
+ last = t;
+ } //end else
+ } //end for
+ //check for the merging operator
+ for (t = first; t; )
+ {
+ if (t->next)
+ {
+ //if the merging operator
+ if (t->next->string[0] == '#' && t->next->string[1] == '#')
+ {
+ t1 = t;
+ t2 = t->next->next;
+ if (t2)
+ {
+ if (!PC_MergeTokens(t1, t2))
+ {
+ SourceError(source, "can't merge %s with %s", t1->string, t2->string);
+ return qfalse;
+ } //end if
+ PC_FreeToken(t1->next);
+ t1->next = t2->next;
+ if (t2 == last) last = t1;
+ PC_FreeToken(t2);
+ continue;
+ } //end if
+ } //end if
+ } //end if
+ t = t->next;
+ } //end for
+ //store the first and last token of the list
+ *firsttoken = first;
+ *lasttoken = last;
+ //free all the parameter tokens
+ for (i = 0; i < define->numparms; i++)
+ {
+ for (pt = parms[i]; pt; pt = nextpt)
+ {
+ nextpt = pt->next;
+ PC_FreeToken(pt);
+ } //end for
+ } //end for
+ //
+ return qtrue;
+} //end of the function PC_ExpandDefine
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define)
+{
+ token_t *firsttoken, *lasttoken;
+
+ if (!PC_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse;
+
+ if (firsttoken && lasttoken)
+ {
+ lasttoken->next = source->tokens;
+ source->tokens = firsttoken;
+ return qtrue;
+ } //end if
+ return qfalse;
+} //end of the function PC_ExpandDefineIntoSource
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_ConvertPath(char *path)
+{
+ char *ptr;
+
+ //remove double path seperators
+ for (ptr = path; *ptr;)
+ {
+ if ((*ptr == '\\' || *ptr == '/') &&
+ (*(ptr+1) == '\\' || *(ptr+1) == '/'))
+ {
+ strcpy(ptr, ptr+1);
+ } //end if
+ else
+ {
+ ptr++;
+ } //end else
+ } //end while
+ //set OS dependent path seperators
+ for (ptr = path; *ptr;)
+ {
+ if (*ptr == '/' || *ptr == '\\') *ptr = PATHSEPERATOR_CHAR;
+ ptr++;
+ } //end while
+} //end of the function PC_ConvertPath
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_include(source_t *source)
+{
+ script_t *script;
+ token_t token;
+ char path[MAX_PATH];
+#ifdef QUAKE
+ foundfile_t file;
+#endif //QUAKE
+
+ if (source->skip > 0) return qtrue;
+ //
+ if (!PC_ReadSourceToken(source, &token))
+ {
+ SourceError(source, "#include without file name");
+ return qfalse;
+ } //end if
+ if (token.linescrossed > 0)
+ {
+ SourceError(source, "#include without file name");
+ return qfalse;
+ } //end if
+ if (token.type == TT_STRING)
+ {
+ StripDoubleQuotes(token.string);
+ PC_ConvertPath(token.string);
+ script = LoadScriptFile(token.string);
+ if (!script)
+ {
+ strcpy(path, source->includepath);
+ strcat(path, token.string);
+ script = LoadScriptFile(path);
+ } //end if
+ } //end if
+ else if (token.type == TT_PUNCTUATION && *token.string == '<')
+ {
+ strcpy(path, source->includepath);
+ while(PC_ReadSourceToken(source, &token))
+ {
+ if (token.linescrossed > 0)
+ {
+ PC_UnreadSourceToken(source, &token);
+ break;
+ } //end if
+ if (token.type == TT_PUNCTUATION && *token.string == '>') break;
+ strncat(path, token.string, MAX_PATH);
+ } //end while
+ if (*token.string != '>')
+ {
+ SourceWarning(source, "#include missing trailing >");
+ } //end if
+ if (!strlen(path))
+ {
+ SourceError(source, "#include without file name between < >");
+ return qfalse;
+ } //end if
+ PC_ConvertPath(path);
+ script = LoadScriptFile(path);
+ } //end if
+ else
+ {
+ SourceError(source, "#include without file name");
+ return qfalse;
+ } //end else
+#ifdef QUAKE
+ if (!script)
+ {
+ Com_Memset(&file, 0, sizeof(foundfile_t));
+ script = LoadScriptFile(path);
+ if (script) strncpy(script->filename, path, MAX_PATH);
+ } //end if
+#endif //QUAKE
+ if (!script)
+ {
+#ifdef SCREWUP
+ SourceWarning(source, "file %s not found", path);
+ return qtrue;
+#else
+ SourceError(source, "file %s not found", path);
+ return qfalse;
+#endif //SCREWUP
+ } //end if
+ PC_PushScript(source, script);
+ return qtrue;
+} //end of the function PC_Directive_include
+//============================================================================
+// reads a token from the current line, continues reading on the next
+// line only if a backslash '\' is encountered.
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ReadLine(source_t *source, token_t *token)
+{
+ int crossline;
+
+ crossline = 0;
+ do
+ {
+ if (!PC_ReadSourceToken(source, token)) return qfalse;
+
+ if (token->linescrossed > crossline)
+ {
+ PC_UnreadSourceToken(source, token);
+ return qfalse;
+ } //end if
+ crossline = 1;
+ } while(!strcmp(token->string, "\\"));
+ return qtrue;
+} //end of the function PC_ReadLine
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_WhiteSpaceBeforeToken(token_t *token)
+{
+ return token->endwhitespace_p - token->whitespace_p > 0;
+} //end of the function PC_WhiteSpaceBeforeToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_ClearTokenWhiteSpace(token_t *token)
+{
+ token->whitespace_p = NULL;
+ token->endwhitespace_p = NULL;
+ token->linescrossed = 0;
+} //end of the function PC_ClearTokenWhiteSpace
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_undef(source_t *source)
+{
+ token_t token;
+ define_t *define, *lastdefine;
+ int hash;
+
+ if (source->skip > 0) return qtrue;
+ //
+ if (!PC_ReadLine(source, &token))
+ {
+ SourceError(source, "undef without name");
+ return qfalse;
+ } //end if
+ if (token.type != TT_NAME)
+ {
+ PC_UnreadSourceToken(source, &token);
+ SourceError(source, "expected name, found %s", token.string);
+ return qfalse;
+ } //end if
+#if DEFINEHASHING
+
+ hash = PC_NameHash(token.string);
+ for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext)
+ {
+ if (!strcmp(define->name, token.string))
+ {
+ if (define->flags & DEFINE_FIXED)
+ {
+ SourceWarning(source, "can't undef %s", token.string);
+ } //end if
+ else
+ {
+ if (lastdefine) lastdefine->hashnext = define->hashnext;
+ else source->definehash[hash] = define->hashnext;
+ PC_FreeDefine(define);
+ } //end else
+ break;
+ } //end if
+ lastdefine = define;
+ } //end for
+#else //DEFINEHASHING
+ for (lastdefine = NULL, define = source->defines; define; define = define->next)
+ {
+ if (!strcmp(define->name, token.string))
+ {
+ if (define->flags & DEFINE_FIXED)
+ {
+ SourceWarning(source, "can't undef %s", token.string);
+ } //end if
+ else
+ {
+ if (lastdefine) lastdefine->next = define->next;
+ else source->defines = define->next;
+ PC_FreeDefine(define);
+ } //end else
+ break;
+ } //end if
+ lastdefine = define;
+ } //end for
+#endif //DEFINEHASHING
+ return qtrue;
+} //end of the function PC_Directive_undef
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_define(source_t *source)
+{
+ token_t token, *t, *last;
+ define_t *define;
+
+ if (source->skip > 0) return qtrue;
+ //
+ if (!PC_ReadLine(source, &token))
+ {
+ SourceError(source, "#define without name");
+ return qfalse;
+ } //end if
+ if (token.type != TT_NAME)
+ {
+ PC_UnreadSourceToken(source, &token);
+ SourceError(source, "expected name after #define, found %s", token.string);
+ return qfalse;
+ } //end if
+ //check if the define already exists
+#if DEFINEHASHING
+ define = PC_FindHashedDefine(source->definehash, token.string);
+#else
+ define = PC_FindDefine(source->defines, token.string);
+#endif //DEFINEHASHING
+ if (define)
+ {
+ if (define->flags & DEFINE_FIXED)
+ {
+ SourceError(source, "can't redefine %s", token.string);
+ return qfalse;
+ } //end if
+ SourceWarning(source, "redefinition of %s", token.string);
+ //unread the define name before executing the #undef directive
+ PC_UnreadSourceToken(source, &token);
+ if (!PC_Directive_undef(source)) return qfalse;
+ //if the define was not removed (define->flags & DEFINE_FIXED)
+#if DEFINEHASHING
+ define = PC_FindHashedDefine(source->definehash, token.string);
+#else
+ define = PC_FindDefine(source->defines, token.string);
+#endif //DEFINEHASHING
+ } //end if
+ //allocate define
+ define = (define_t *) GetMemory(sizeof(define_t) + strlen(token.string) + 1);
+ Com_Memset(define, 0, sizeof(define_t));
+ define->name = (char *) define + sizeof(define_t);
+ strcpy(define->name, token.string);
+ //add the define to the source
+#if DEFINEHASHING
+ PC_AddDefineToHash(define, source->definehash);
+#else //DEFINEHASHING
+ define->next = source->defines;
+ source->defines = define;
+#endif //DEFINEHASHING
+ //if nothing is defined, just return
+ if (!PC_ReadLine(source, &token)) return qtrue;
+ //if it is a define with parameters
+ if (!PC_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "("))
+ {
+ //read the define parameters
+ last = NULL;
+ if (!PC_CheckTokenString(source, ")"))
+ {
+ while(1)
+ {
+ if (!PC_ReadLine(source, &token))
+ {
+ SourceError(source, "expected define parameter");
+ return qfalse;
+ } //end if
+ //if it isn't a name
+ if (token.type != TT_NAME)
+ {
+ SourceError(source, "invalid define parameter");
+ return qfalse;
+ } //end if
+ //
+ if (PC_FindDefineParm(define, token.string) >= 0)
+ {
+ SourceError(source, "two the same define parameters");
+ return qfalse;
+ } //end if
+ //add the define parm
+ t = PC_CopyToken(&token);
+ PC_ClearTokenWhiteSpace(t);
+ t->next = NULL;
+ if (last) last->next = t;
+ else define->parms = t;
+ last = t;
+ define->numparms++;
+ //read next token
+ if (!PC_ReadLine(source, &token))
+ {
+ SourceError(source, "define parameters not terminated");
+ return qfalse;
+ } //end if
+ //
+ if (!strcmp(token.string, ")")) break;
+ //then it must be a comma
+ if (strcmp(token.string, ","))
+ {
+ SourceError(source, "define not terminated");
+ return qfalse;
+ } //end if
+ } //end while
+ } //end if
+ if (!PC_ReadLine(source, &token)) return qtrue;
+ } //end if
+ //read the defined stuff
+ last = NULL;
+ do
+ {
+ t = PC_CopyToken(&token);
+ if (t->type == TT_NAME && !strcmp(t->string, define->name))
+ {
+ SourceError(source, "recursive define (removed recursion)");
+ continue;
+ } //end if
+ PC_ClearTokenWhiteSpace(t);
+ t->next = NULL;
+ if (last) last->next = t;
+ else define->tokens = t;
+ last = t;
+ } while(PC_ReadLine(source, &token));
+ //
+ if (last)
+ {
+ //check for merge operators at the beginning or end
+ if (!strcmp(define->tokens->string, "##") ||
+ !strcmp(last->string, "##"))
+ {
+ SourceError(source, "define with misplaced ##");
+ return qfalse;
+ } //end if
+ } //end if
+ return qtrue;
+} //end of the function PC_Directive_define
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+define_t *PC_DefineFromString(char *string)
+{
+ script_t *script;
+ source_t src;
+ token_t *t;
+ int res, i;
+ define_t *def;
+
+ PC_InitTokenHeap();
+
+ script = LoadScriptMemory(string, strlen(string), "*extern");
+ //create a new source
+ Com_Memset(&src, 0, sizeof(source_t));
+ strncpy(src.filename, "*extern", MAX_PATH);
+ src.scriptstack = script;
+#if DEFINEHASHING
+ src.definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *));
+#endif //DEFINEHASHING
+ //create a define from the source
+ res = PC_Directive_define(&src);
+ //free any tokens if left
+ for (t = src.tokens; t; t = src.tokens)
+ {
+ src.tokens = src.tokens->next;
+ PC_FreeToken(t);
+ } //end for
+#ifdef DEFINEHASHING
+ def = NULL;
+ for (i = 0; i < DEFINEHASHSIZE; i++)
+ {
+ if (src.definehash[i])
+ {
+ def = src.definehash[i];
+ break;
+ } //end if
+ } //end for
+#else
+ def = src.defines;
+#endif //DEFINEHASHING
+ //
+#if DEFINEHASHING
+ FreeMemory(src.definehash);
+#endif //DEFINEHASHING
+ //
+ FreeScript(script);
+ //if the define was created succesfully
+ if (res > 0) return def;
+ //free the define is created
+ if (src.defines) PC_FreeDefine(def);
+ //
+ return NULL;
+} //end of the function PC_DefineFromString
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_AddDefine(source_t *source, char *string)
+{
+ define_t *define;
+
+ define = PC_DefineFromString(string);
+ if (!define) return qfalse;
+#if DEFINEHASHING
+ PC_AddDefineToHash(define, source->definehash);
+#else //DEFINEHASHING
+ define->next = source->defines;
+ source->defines = define;
+#endif //DEFINEHASHING
+ return qtrue;
+} //end of the function PC_AddDefine
+//============================================================================
+// add a globals define that will be added to all opened sources
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_AddGlobalDefine(char *string)
+{
+ define_t *define;
+
+ define = PC_DefineFromString(string);
+ if (!define) return qfalse;
+ define->next = globaldefines;
+ globaldefines = define;
+ return qtrue;
+} //end of the function PC_AddGlobalDefine
+//============================================================================
+// remove the given global define
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_RemoveGlobalDefine(char *name)
+{
+ define_t *define;
+
+ define = PC_FindDefine(globaldefines, name);
+ if (define)
+ {
+ PC_FreeDefine(define);
+ return qtrue;
+ } //end if
+ return qfalse;
+} //end of the function PC_RemoveGlobalDefine
+//============================================================================
+// remove all globals defines
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_RemoveAllGlobalDefines(void)
+{
+ define_t *define;
+
+ for (define = globaldefines; define; define = globaldefines)
+ {
+ globaldefines = globaldefines->next;
+ PC_FreeDefine(define);
+ } //end for
+} //end of the function PC_RemoveAllGlobalDefines
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+define_t *PC_CopyDefine(source_t *source, define_t *define)
+{
+ define_t *newdefine;
+ token_t *token, *newtoken, *lasttoken;
+
+ newdefine = (define_t *) GetMemory(sizeof(define_t) + strlen(define->name) + 1);
+ //copy the define name
+ newdefine->name = (char *) newdefine + sizeof(define_t);
+ strcpy(newdefine->name, define->name);
+ newdefine->flags = define->flags;
+ newdefine->builtin = define->builtin;
+ newdefine->numparms = define->numparms;
+ //the define is not linked
+ newdefine->next = NULL;
+ newdefine->hashnext = NULL;
+ //copy the define tokens
+ newdefine->tokens = NULL;
+ for (lasttoken = NULL, token = define->tokens; token; token = token->next)
+ {
+ newtoken = PC_CopyToken(token);
+ newtoken->next = NULL;
+ if (lasttoken) lasttoken->next = newtoken;
+ else newdefine->tokens = newtoken;
+ lasttoken = newtoken;
+ } //end for
+ //copy the define parameters
+ newdefine->parms = NULL;
+ for (lasttoken = NULL, token = define->parms; token; token = token->next)
+ {
+ newtoken = PC_CopyToken(token);
+ newtoken->next = NULL;
+ if (lasttoken) lasttoken->next = newtoken;
+ else newdefine->parms = newtoken;
+ lasttoken = newtoken;
+ } //end for
+ return newdefine;
+} //end of the function PC_CopyDefine
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_AddGlobalDefinesToSource(source_t *source)
+{
+ define_t *define, *newdefine;
+
+ for (define = globaldefines; define; define = define->next)
+ {
+ newdefine = PC_CopyDefine(source, define);
+#if DEFINEHASHING
+ PC_AddDefineToHash(newdefine, source->definehash);
+#else //DEFINEHASHING
+ newdefine->next = source->defines;
+ source->defines = newdefine;
+#endif //DEFINEHASHING
+ } //end for
+} //end of the function PC_AddGlobalDefinesToSource
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_if_def(source_t *source, int type)
+{
+ token_t token;
+ define_t *d;
+ int skip;
+
+ if (!PC_ReadLine(source, &token))
+ {
+ SourceError(source, "#ifdef without name");
+ return qfalse;
+ } //end if
+ if (token.type != TT_NAME)
+ {
+ PC_UnreadSourceToken(source, &token);
+ SourceError(source, "expected name after #ifdef, found %s", token.string);
+ return qfalse;
+ } //end if
+#if DEFINEHASHING
+ d = PC_FindHashedDefine(source->definehash, token.string);
+#else
+ d = PC_FindDefine(source->defines, token.string);
+#endif //DEFINEHASHING
+ skip = (type == INDENT_IFDEF) == (d == NULL);
+ PC_PushIndent(source, type, skip);
+ return qtrue;
+} //end of the function PC_Directiveif_def
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_ifdef(source_t *source)
+{
+ return PC_Directive_if_def(source, INDENT_IFDEF);
+} //end of the function PC_Directive_ifdef
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_ifndef(source_t *source)
+{
+ return PC_Directive_if_def(source, INDENT_IFNDEF);
+} //end of the function PC_Directive_ifndef
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_else(source_t *source)
+{
+ int type, skip;
+
+ PC_PopIndent(source, &type, &skip);
+ if (!type)
+ {
+ SourceError(source, "misplaced #else");
+ return qfalse;
+ } //end if
+ if (type == INDENT_ELSE)
+ {
+ SourceError(source, "#else after #else");
+ return qfalse;
+ } //end if
+ PC_PushIndent(source, INDENT_ELSE, !skip);
+ return qtrue;
+} //end of the function PC_Directive_else
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_endif(source_t *source)
+{
+ int type, skip;
+
+ PC_PopIndent(source, &type, &skip);
+ if (!type)
+ {
+ SourceError(source, "misplaced #endif");
+ return qfalse;
+ } //end if
+ return qtrue;
+} //end of the function PC_Directive_endif
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+typedef struct operator_s
+{
+ int operator;
+ int priority;
+ int parentheses;
+ struct operator_s *prev, *next;
+} operator_t;
+
+typedef struct value_s
+{
+ signed long int intvalue;
+ double floatvalue;
+ int parentheses;
+ struct value_s *prev, *next;
+} value_t;
+
+int PC_OperatorPriority(int op)
+{
+ switch(op)
+ {
+ case P_MUL: return 15;
+ case P_DIV: return 15;
+ case P_MOD: return 15;
+ case P_ADD: return 14;
+ case P_SUB: return 14;
+
+ case P_LOGIC_AND: return 7;
+ case P_LOGIC_OR: return 6;
+ case P_LOGIC_GEQ: return 12;
+ case P_LOGIC_LEQ: return 12;
+ case P_LOGIC_EQ: return 11;
+ case P_LOGIC_UNEQ: return 11;
+
+ case P_LOGIC_NOT: return 16;
+ case P_LOGIC_GREATER: return 12;
+ case P_LOGIC_LESS: return 12;
+
+ case P_RSHIFT: return 13;
+ case P_LSHIFT: return 13;
+
+ case P_BIN_AND: return 10;
+ case P_BIN_OR: return 8;
+ case P_BIN_XOR: return 9;
+ case P_BIN_NOT: return 16;
+
+ case P_COLON: return 5;
+ case P_QUESTIONMARK: return 5;
+ } //end switch
+ return qfalse;
+} //end of the function PC_OperatorPriority
+
+//#define AllocValue() GetClearedMemory(sizeof(value_t));
+//#define FreeValue(val) FreeMemory(val)
+//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t));
+//#define FreeOperator(op) FreeMemory(op);
+
+#define MAX_VALUES 64
+#define MAX_OPERATORS 64
+#define AllocValue(val) \
+ if (numvalues >= MAX_VALUES) { \
+ SourceError(source, "out of value space\n"); \
+ error = 1; \
+ break; \
+ } \
+ else \
+ val = &value_heap[numvalues++];
+#define FreeValue(val)
+//
+#define AllocOperator(op) \
+ if (numoperators >= MAX_OPERATORS) { \
+ SourceError(source, "out of operator space\n"); \
+ error = 1; \
+ break; \
+ } \
+ else \
+ op = &operator_heap[numoperators++];
+#define FreeOperator(op)
+
+int PC_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue,
+ double *floatvalue, int integer)
+{
+ operator_t *o, *firstoperator, *lastoperator;
+ value_t *v, *firstvalue, *lastvalue, *v1, *v2;
+ token_t *t;
+ int brace = 0;
+ int parentheses = 0;
+ int error = 0;
+ int lastwasvalue = 0;
+ int negativevalue = 0;
+ int questmarkintvalue = 0;
+ double questmarkfloatvalue = 0;
+ int gotquestmarkvalue = qfalse;
+ int lastoperatortype = 0;
+ //
+ operator_t operator_heap[MAX_OPERATORS];
+ int numoperators = 0;
+ value_t value_heap[MAX_VALUES];
+ int numvalues = 0;
+
+ firstoperator = lastoperator = NULL;
+ firstvalue = lastvalue = NULL;
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ for (t = tokens; t; t = t->next)
+ {
+ switch(t->type)
+ {
+ case TT_NAME:
+ {
+ if (lastwasvalue || negativevalue)
+ {
+ SourceError(source, "syntax error in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ if (strcmp(t->string, "defined"))
+ {
+ SourceError(source, "undefined name %s in #if/#elif", t->string);
+ error = 1;
+ break;
+ } //end if
+ t = t->next;
+ if (!strcmp(t->string, "("))
+ {
+ brace = qtrue;
+ t = t->next;
+ } //end if
+ if (!t || t->type != TT_NAME)
+ {
+ SourceError(source, "defined without name in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ //v = (value_t *) GetClearedMemory(sizeof(value_t));
+ AllocValue(v);
+#if DEFINEHASHING
+ if (PC_FindHashedDefine(source->definehash, t->string))
+#else
+ if (PC_FindDefine(source->defines, t->string))
+#endif //DEFINEHASHING
+ {
+ v->intvalue = 1;
+ v->floatvalue = 1;
+ } //end if
+ else
+ {
+ v->intvalue = 0;
+ v->floatvalue = 0;
+ } //end else
+ v->parentheses = parentheses;
+ v->next = NULL;
+ v->prev = lastvalue;
+ if (lastvalue) lastvalue->next = v;
+ else firstvalue = v;
+ lastvalue = v;
+ if (brace)
+ {
+ t = t->next;
+ if (!t || strcmp(t->string, ")"))
+ {
+ SourceError(source, "defined without ) in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ } //end if
+ brace = qfalse;
+ // defined() creates a value
+ lastwasvalue = 1;
+ break;
+ } //end case
+ case TT_NUMBER:
+ {
+ if (lastwasvalue)
+ {
+ SourceError(source, "syntax error in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ //v = (value_t *) GetClearedMemory(sizeof(value_t));
+ AllocValue(v);
+ if (negativevalue)
+ {
+ v->intvalue = - (signed int) t->intvalue;
+ v->floatvalue = - t->floatvalue;
+ } //end if
+ else
+ {
+ v->intvalue = t->intvalue;
+ v->floatvalue = t->floatvalue;
+ } //end else
+ v->parentheses = parentheses;
+ v->next = NULL;
+ v->prev = lastvalue;
+ if (lastvalue) lastvalue->next = v;
+ else firstvalue = v;
+ lastvalue = v;
+ //last token was a value
+ lastwasvalue = 1;
+ //
+ negativevalue = 0;
+ break;
+ } //end case
+ case TT_PUNCTUATION:
+ {
+ if (negativevalue)
+ {
+ SourceError(source, "misplaced minus sign in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ if (t->subtype == P_PARENTHESESOPEN)
+ {
+ parentheses++;
+ break;
+ } //end if
+ else if (t->subtype == P_PARENTHESESCLOSE)
+ {
+ parentheses--;
+ if (parentheses < 0)
+ {
+ SourceError(source, "too many ) in #if/#elsif");
+ error = 1;
+ } //end if
+ break;
+ } //end else if
+ //check for invalid operators on floating point values
+ if (!integer)
+ {
+ if (t->subtype == P_BIN_NOT || t->subtype == P_MOD ||
+ t->subtype == P_RSHIFT || t->subtype == P_LSHIFT ||
+ t->subtype == P_BIN_AND || t->subtype == P_BIN_OR ||
+ t->subtype == P_BIN_XOR)
+ {
+ SourceError(source, "illigal operator %s on floating point operands\n", t->string);
+ error = 1;
+ break;
+ } //end if
+ } //end if
+ switch(t->subtype)
+ {
+ case P_LOGIC_NOT:
+ case P_BIN_NOT:
+ {
+ if (lastwasvalue)
+ {
+ SourceError(source, "! or ~ after value in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ break;
+ } //end case
+ case P_INC:
+ case P_DEC:
+ {
+ SourceError(source, "++ or -- used in #if/#elif");
+ break;
+ } //end case
+ case P_SUB:
+ {
+ if (!lastwasvalue)
+ {
+ negativevalue = 1;
+ break;
+ } //end if
+ } //end case
+
+ case P_MUL:
+ case P_DIV:
+ case P_MOD:
+ case P_ADD:
+
+ case P_LOGIC_AND:
+ case P_LOGIC_OR:
+ case P_LOGIC_GEQ:
+ case P_LOGIC_LEQ:
+ case P_LOGIC_EQ:
+ case P_LOGIC_UNEQ:
+
+ case P_LOGIC_GREATER:
+ case P_LOGIC_LESS:
+
+ case P_RSHIFT:
+ case P_LSHIFT:
+
+ case P_BIN_AND:
+ case P_BIN_OR:
+ case P_BIN_XOR:
+
+ case P_COLON:
+ case P_QUESTIONMARK:
+ {
+ if (!lastwasvalue)
+ {
+ SourceError(source, "operator %s after operator in #if/#elif", t->string);
+ error = 1;
+ break;
+ } //end if
+ break;
+ } //end case
+ default:
+ {
+ SourceError(source, "invalid operator %s in #if/#elif", t->string);
+ error = 1;
+ break;
+ } //end default
+ } //end switch
+ if (!error && !negativevalue)
+ {
+ //o = (operator_t *) GetClearedMemory(sizeof(operator_t));
+ AllocOperator(o);
+ o->operator = t->subtype;
+ o->priority = PC_OperatorPriority(t->subtype);
+ o->parentheses = parentheses;
+ o->next = NULL;
+ o->prev = lastoperator;
+ if (lastoperator) lastoperator->next = o;
+ else firstoperator = o;
+ lastoperator = o;
+ lastwasvalue = 0;
+ } //end if
+ break;
+ } //end case
+ default:
+ {
+ SourceError(source, "unknown %s in #if/#elif", t->string);
+ error = 1;
+ break;
+ } //end default
+ } //end switch
+ if (error) break;
+ } //end for
+ if (!error)
+ {
+ if (!lastwasvalue)
+ {
+ SourceError(source, "trailing operator in #if/#elif");
+ error = 1;
+ } //end if
+ else if (parentheses)
+ {
+ SourceError(source, "too many ( in #if/#elif");
+ error = 1;
+ } //end else if
+ } //end if
+ //
+ gotquestmarkvalue = qfalse;
+ questmarkintvalue = 0;
+ questmarkfloatvalue = 0;
+ //while there are operators
+ while(!error && firstoperator)
+ {
+ v = firstvalue;
+ for (o = firstoperator; o->next; o = o->next)
+ {
+ //if the current operator is nested deeper in parentheses
+ //than the next operator
+ if (o->parentheses > o->next->parentheses) break;
+ //if the current and next operator are nested equally deep in parentheses
+ if (o->parentheses == o->next->parentheses)
+ {
+ //if the priority of the current operator is equal or higher
+ //than the priority of the next operator
+ if (o->priority >= o->next->priority) break;
+ } //end if
+ //if the arity of the operator isn't equal to 1
+ if (o->operator != P_LOGIC_NOT
+ && o->operator != P_BIN_NOT) v = v->next;
+ //if there's no value or no next value
+ if (!v)
+ {
+ SourceError(source, "mising values in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ } //end for
+ if (error) break;
+ v1 = v;
+ v2 = v->next;
+#ifdef DEBUG_EVAL
+ if (integer)
+ {
+ Log_Write("operator %s, value1 = %d", PunctuationFromNum(source->scriptstack, o->operator), v1->intvalue);
+ if (v2) Log_Write("value2 = %d", v2->intvalue);
+ } //end if
+ else
+ {
+ Log_Write("operator %s, value1 = %f", PunctuationFromNum(source->scriptstack, o->operator), v1->floatvalue);
+ if (v2) Log_Write("value2 = %f", v2->floatvalue);
+ } //end else
+#endif //DEBUG_EVAL
+ switch(o->operator)
+ {
+ case P_LOGIC_NOT: v1->intvalue = !v1->intvalue;
+ v1->floatvalue = !v1->floatvalue; break;
+ case P_BIN_NOT: v1->intvalue = ~v1->intvalue;
+ break;
+ case P_MUL: v1->intvalue *= v2->intvalue;
+ v1->floatvalue *= v2->floatvalue; break;
+ case P_DIV: if (!v2->intvalue || !v2->floatvalue)
+ {
+ SourceError(source, "divide by zero in #if/#elif\n");
+ error = 1;
+ break;
+ }
+ v1->intvalue /= v2->intvalue;
+ v1->floatvalue /= v2->floatvalue; break;
+ case P_MOD: if (!v2->intvalue)
+ {
+ SourceError(source, "divide by zero in #if/#elif\n");
+ error = 1;
+ break;
+ }
+ v1->intvalue %= v2->intvalue; break;
+ case P_ADD: v1->intvalue += v2->intvalue;
+ v1->floatvalue += v2->floatvalue; break;
+ case P_SUB: v1->intvalue -= v2->intvalue;
+ v1->floatvalue -= v2->floatvalue; break;
+ case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue;
+ v1->floatvalue = v1->floatvalue && v2->floatvalue; break;
+ case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue;
+ v1->floatvalue = v1->floatvalue || v2->floatvalue; break;
+ case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue;
+ v1->floatvalue = v1->floatvalue >= v2->floatvalue; break;
+ case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue;
+ v1->floatvalue = v1->floatvalue <= v2->floatvalue; break;
+ case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue;
+ v1->floatvalue = v1->floatvalue == v2->floatvalue; break;
+ case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue;
+ v1->floatvalue = v1->floatvalue != v2->floatvalue; break;
+ case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue;
+ v1->floatvalue = v1->floatvalue > v2->floatvalue; break;
+ case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue;
+ v1->floatvalue = v1->floatvalue < v2->floatvalue; break;
+ case P_RSHIFT: v1->intvalue >>= v2->intvalue;
+ break;
+ case P_LSHIFT: v1->intvalue <<= v2->intvalue;
+ break;
+ case P_BIN_AND: v1->intvalue &= v2->intvalue;
+ break;
+ case P_BIN_OR: v1->intvalue |= v2->intvalue;
+ break;
+ case P_BIN_XOR: v1->intvalue ^= v2->intvalue;
+ break;
+ case P_COLON:
+ {
+ if (!gotquestmarkvalue)
+ {
+ SourceError(source, ": without ? in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ if (integer)
+ {
+ if (!questmarkintvalue) v1->intvalue = v2->intvalue;
+ } //end if
+ else
+ {
+ if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue;
+ } //end else
+ gotquestmarkvalue = qfalse;
+ break;
+ } //end case
+ case P_QUESTIONMARK:
+ {
+ if (gotquestmarkvalue)
+ {
+ SourceError(source, "? after ? in #if/#elif");
+ error = 1;
+ break;
+ } //end if
+ questmarkintvalue = v1->intvalue;
+ questmarkfloatvalue = v1->floatvalue;
+ gotquestmarkvalue = qtrue;
+ break;
+ } //end if
+ } //end switch
+#ifdef DEBUG_EVAL
+ if (integer) Log_Write("result value = %d", v1->intvalue);
+ else Log_Write("result value = %f", v1->floatvalue);
+#endif //DEBUG_EVAL
+ if (error) break;
+ lastoperatortype = o->operator;
+ //if not an operator with arity 1
+ if (o->operator != P_LOGIC_NOT
+ && o->operator != P_BIN_NOT)
+ {
+ //remove the second value if not question mark operator
+ if (o->operator != P_QUESTIONMARK) v = v->next;
+ //
+ if (v->prev) v->prev->next = v->next;
+ else firstvalue = v->next;
+ if (v->next) v->next->prev = v->prev;
+ else lastvalue = v->prev;
+ //FreeMemory(v);
+ FreeValue(v);
+ } //end if
+ //remove the operator
+ if (o->prev) o->prev->next = o->next;
+ else firstoperator = o->next;
+ if (o->next) o->next->prev = o->prev;
+ else lastoperator = o->prev;
+ //FreeMemory(o);
+ FreeOperator(o);
+ } //end while
+ if (firstvalue)
+ {
+ if (intvalue) *intvalue = firstvalue->intvalue;
+ if (floatvalue) *floatvalue = firstvalue->floatvalue;
+ } //end if
+ for (o = firstoperator; o; o = lastoperator)
+ {
+ lastoperator = o->next;
+ //FreeMemory(o);
+ FreeOperator(o);
+ } //end for
+ for (v = firstvalue; v; v = lastvalue)
+ {
+ lastvalue = v->next;
+ //FreeMemory(v);
+ FreeValue(v);
+ } //end for
+ if (!error) return qtrue;
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ return qfalse;
+} //end of the function PC_EvaluateTokens
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Evaluate(source_t *source, signed long int *intvalue,
+ double *floatvalue, int integer)
+{
+ token_t token, *firsttoken, *lasttoken;
+ token_t *t, *nexttoken;
+ define_t *define;
+ int defined = qfalse;
+
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ //
+ if (!PC_ReadLine(source, &token))
+ {
+ SourceError(source, "no value after #if/#elif");
+ return qfalse;
+ } //end if
+ firsttoken = NULL;
+ lasttoken = NULL;
+ do
+ {
+ //if the token is a name
+ if (token.type == TT_NAME)
+ {
+ if (defined)
+ {
+ defined = qfalse;
+ t = PC_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ } //end if
+ else if (!strcmp(token.string, "defined"))
+ {
+ defined = qtrue;
+ t = PC_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ } //end if
+ else
+ {
+ //then it must be a define
+#if DEFINEHASHING
+ define = PC_FindHashedDefine(source->definehash, token.string);
+#else
+ define = PC_FindDefine(source->defines, token.string);
+#endif //DEFINEHASHING
+ if (!define)
+ {
+ SourceError(source, "can't evaluate %s, not defined", token.string);
+ return qfalse;
+ } //end if
+ if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse;
+ } //end else
+ } //end if
+ //if the token is a number or a punctuation
+ else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION)
+ {
+ t = PC_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ } //end else
+ else //can't evaluate the token
+ {
+ SourceError(source, "can't evaluate %s", token.string);
+ return qfalse;
+ } //end else
+ } while(PC_ReadLine(source, &token));
+ //
+ if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse;
+ //
+#ifdef DEBUG_EVAL
+ Log_Write("eval:");
+#endif //DEBUG_EVAL
+ for (t = firsttoken; t; t = nexttoken)
+ {
+#ifdef DEBUG_EVAL
+ Log_Write(" %s", t->string);
+#endif //DEBUG_EVAL
+ nexttoken = t->next;
+ PC_FreeToken(t);
+ } //end for
+#ifdef DEBUG_EVAL
+ if (integer) Log_Write("eval result: %d", *intvalue);
+ else Log_Write("eval result: %f", *floatvalue);
+#endif //DEBUG_EVAL
+ //
+ return qtrue;
+} //end of the function PC_Evaluate
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_DollarEvaluate(source_t *source, signed long int *intvalue,
+ double *floatvalue, int integer)
+{
+ int indent, defined = qfalse;
+ token_t token, *firsttoken, *lasttoken;
+ token_t *t, *nexttoken;
+ define_t *define;
+
+ if (intvalue) *intvalue = 0;
+ if (floatvalue) *floatvalue = 0;
+ //
+ if (!PC_ReadSourceToken(source, &token))
+ {
+ SourceError(source, "no leading ( after $evalint/$evalfloat");
+ return qfalse;
+ } //end if
+ if (!PC_ReadSourceToken(source, &token))
+ {
+ SourceError(source, "nothing to evaluate");
+ return qfalse;
+ } //end if
+ indent = 1;
+ firsttoken = NULL;
+ lasttoken = NULL;
+ do
+ {
+ //if the token is a name
+ if (token.type == TT_NAME)
+ {
+ if (defined)
+ {
+ defined = qfalse;
+ t = PC_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ } //end if
+ else if (!strcmp(token.string, "defined"))
+ {
+ defined = qtrue;
+ t = PC_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ } //end if
+ else
+ {
+ //then it must be a define
+#if DEFINEHASHING
+ define = PC_FindHashedDefine(source->definehash, token.string);
+#else
+ define = PC_FindDefine(source->defines, token.string);
+#endif //DEFINEHASHING
+ if (!define)
+ {
+ SourceError(source, "can't evaluate %s, not defined", token.string);
+ return qfalse;
+ } //end if
+ if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse;
+ } //end else
+ } //end if
+ //if the token is a number or a punctuation
+ else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION)
+ {
+ if (*token.string == '(') indent++;
+ else if (*token.string == ')') indent--;
+ if (indent <= 0) break;
+ t = PC_CopyToken(&token);
+ t->next = NULL;
+ if (lasttoken) lasttoken->next = t;
+ else firsttoken = t;
+ lasttoken = t;
+ } //end else
+ else //can't evaluate the token
+ {
+ SourceError(source, "can't evaluate %s", token.string);
+ return qfalse;
+ } //end else
+ } while(PC_ReadSourceToken(source, &token));
+ //
+ if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse;
+ //
+#ifdef DEBUG_EVAL
+ Log_Write("$eval:");
+#endif //DEBUG_EVAL
+ for (t = firsttoken; t; t = nexttoken)
+ {
+#ifdef DEBUG_EVAL
+ Log_Write(" %s", t->string);
+#endif //DEBUG_EVAL
+ nexttoken = t->next;
+ PC_FreeToken(t);
+ } //end for
+#ifdef DEBUG_EVAL
+ if (integer) Log_Write("$eval result: %d", *intvalue);
+ else Log_Write("$eval result: %f", *floatvalue);
+#endif //DEBUG_EVAL
+ //
+ return qtrue;
+} //end of the function PC_DollarEvaluate
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_elif(source_t *source)
+{
+ signed long int value;
+ int type, skip;
+
+ PC_PopIndent(source, &type, &skip);
+ if (!type || type == INDENT_ELSE)
+ {
+ SourceError(source, "misplaced #elif");
+ return qfalse;
+ } //end if
+ if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse;
+ skip = (value == 0);
+ PC_PushIndent(source, INDENT_ELIF, skip);
+ return qtrue;
+} //end of the function PC_Directive_elif
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_if(source_t *source)
+{
+ signed long int value;
+ int skip;
+
+ if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse;
+ skip = (value == 0);
+ PC_PushIndent(source, INDENT_IF, skip);
+ return qtrue;
+} //end of the function PC_Directive
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_line(source_t *source)
+{
+ SourceError(source, "#line directive not supported");
+ return qfalse;
+} //end of the function PC_Directive_line
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_error(source_t *source)
+{
+ token_t token;
+
+ strcpy(token.string, "");
+ PC_ReadSourceToken(source, &token);
+ SourceError(source, "#error directive: %s", token.string);
+ return qfalse;
+} //end of the function PC_Directive_error
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_pragma(source_t *source)
+{
+ token_t token;
+
+ SourceWarning(source, "#pragma directive not supported");
+ while(PC_ReadLine(source, &token)) ;
+ return qtrue;
+} //end of the function PC_Directive_pragma
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void UnreadSignToken(source_t *source)
+{
+ token_t token;
+
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ strcpy(token.string, "-");
+ token.type = TT_PUNCTUATION;
+ token.subtype = P_SUB;
+ PC_UnreadSourceToken(source, &token);
+} //end of the function UnreadSignToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_eval(source_t *source)
+{
+ signed long int value;
+ token_t token;
+
+ if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse;
+ //
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%d", abs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL;
+ PC_UnreadSourceToken(source, &token);
+ if (value < 0) UnreadSignToken(source);
+ return qtrue;
+} //end of the function PC_Directive_eval
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_Directive_evalfloat(source_t *source)
+{
+ double value;
+ token_t token;
+
+ if (!PC_Evaluate(source, NULL, &value, qfalse)) return qfalse;
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%1.2f", fabs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL;
+ PC_UnreadSourceToken(source, &token);
+ if (value < 0) UnreadSignToken(source);
+ return qtrue;
+} //end of the function PC_Directive_evalfloat
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+directive_t directives[20] =
+{
+ {"if", PC_Directive_if},
+ {"ifdef", PC_Directive_ifdef},
+ {"ifndef", PC_Directive_ifndef},
+ {"elif", PC_Directive_elif},
+ {"else", PC_Directive_else},
+ {"endif", PC_Directive_endif},
+ {"include", PC_Directive_include},
+ {"define", PC_Directive_define},
+ {"undef", PC_Directive_undef},
+ {"line", PC_Directive_line},
+ {"error", PC_Directive_error},
+ {"pragma", PC_Directive_pragma},
+ {"eval", PC_Directive_eval},
+ {"evalfloat", PC_Directive_evalfloat},
+ {NULL, NULL}
+};
+
+int PC_ReadDirective(source_t *source)
+{
+ token_t token;
+ int i;
+
+ //read the directive name
+ if (!PC_ReadSourceToken(source, &token))
+ {
+ SourceError(source, "found # without name");
+ return qfalse;
+ } //end if
+ //directive name must be on the same line
+ if (token.linescrossed > 0)
+ {
+ PC_UnreadSourceToken(source, &token);
+ SourceError(source, "found # at end of line");
+ return qfalse;
+ } //end if
+ //if if is a name
+ if (token.type == TT_NAME)
+ {
+ //find the precompiler directive
+ for (i = 0; directives[i].name; i++)
+ {
+ if (!strcmp(directives[i].name, token.string))
+ {
+ return directives[i].func(source);
+ } //end if
+ } //end for
+ } //end if
+ SourceError(source, "unknown precompiler directive %s", token.string);
+ return qfalse;
+} //end of the function PC_ReadDirective
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_DollarDirective_evalint(source_t *source)
+{
+ signed long int value;
+ token_t token;
+
+ if (!PC_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse;
+ //
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%d", abs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL;
+#ifdef NUMBERVALUE
+ token.intvalue = value;
+ token.floatvalue = value;
+#endif //NUMBERVALUE
+ PC_UnreadSourceToken(source, &token);
+ if (value < 0) UnreadSignToken(source);
+ return qtrue;
+} //end of the function PC_DollarDirective_evalint
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_DollarDirective_evalfloat(source_t *source)
+{
+ double value;
+ token_t token;
+
+ if (!PC_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse;
+ token.line = source->scriptstack->line;
+ token.whitespace_p = source->scriptstack->script_p;
+ token.endwhitespace_p = source->scriptstack->script_p;
+ token.linescrossed = 0;
+ sprintf(token.string, "%1.2f", fabs(value));
+ token.type = TT_NUMBER;
+ token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL;
+#ifdef NUMBERVALUE
+ token.intvalue = (unsigned long) value;
+ token.floatvalue = value;
+#endif //NUMBERVALUE
+ PC_UnreadSourceToken(source, &token);
+ if (value < 0) UnreadSignToken(source);
+ return qtrue;
+} //end of the function PC_DollarDirective_evalfloat
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+directive_t dollardirectives[20] =
+{
+ {"evalint", PC_DollarDirective_evalint},
+ {"evalfloat", PC_DollarDirective_evalfloat},
+ {NULL, NULL}
+};
+
+int PC_ReadDollarDirective(source_t *source)
+{
+ token_t token;
+ int i;
+
+ //read the directive name
+ if (!PC_ReadSourceToken(source, &token))
+ {
+ SourceError(source, "found $ without name");
+ return qfalse;
+ } //end if
+ //directive name must be on the same line
+ if (token.linescrossed > 0)
+ {
+ PC_UnreadSourceToken(source, &token);
+ SourceError(source, "found $ at end of line");
+ return qfalse;
+ } //end if
+ //if if is a name
+ if (token.type == TT_NAME)
+ {
+ //find the precompiler directive
+ for (i = 0; dollardirectives[i].name; i++)
+ {
+ if (!strcmp(dollardirectives[i].name, token.string))
+ {
+ return dollardirectives[i].func(source);
+ } //end if
+ } //end for
+ } //end if
+ PC_UnreadSourceToken(source, &token);
+ SourceError(source, "unknown precompiler directive %s", token.string);
+ return qfalse;
+} //end of the function PC_ReadDirective
+
+#ifdef QUAKEC
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int BuiltinFunction(source_t *source)
+{
+ token_t token;
+
+ if (!PC_ReadSourceToken(source, &token)) return qfalse;
+ if (token.type == TT_NUMBER)
+ {
+ PC_UnreadSourceToken(source, &token);
+ return qtrue;
+ } //end if
+ else
+ {
+ PC_UnreadSourceToken(source, &token);
+ return qfalse;
+ } //end else
+} //end of the function BuiltinFunction
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int QuakeCMacro(source_t *source)
+{
+ int i;
+ token_t token;
+
+ if (!PC_ReadSourceToken(source, &token)) return qtrue;
+ if (token.type != TT_NAME)
+ {
+ PC_UnreadSourceToken(source, &token);
+ return qtrue;
+ } //end if
+ //find the precompiler directive
+ for (i = 0; dollardirectives[i].name; i++)
+ {
+ if (!strcmp(dollardirectives[i].name, token.string))
+ {
+ PC_UnreadSourceToken(source, &token);
+ return qfalse;
+ } //end if
+ } //end for
+ PC_UnreadSourceToken(source, &token);
+ return qtrue;
+} //end of the function QuakeCMacro
+#endif //QUAKEC
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ReadToken(source_t *source, token_t *token)
+{
+ define_t *define;
+
+ while(1)
+ {
+ if (!PC_ReadSourceToken(source, token)) return qfalse;
+ //check for precompiler directives
+ if (token->type == TT_PUNCTUATION && *token->string == '#')
+ {
+#ifdef QUAKEC
+ if (!BuiltinFunction(source))
+#endif //QUAKC
+ {
+ //read the precompiler directive
+ if (!PC_ReadDirective(source)) return qfalse;
+ continue;
+ } //end if
+ } //end if
+ if (token->type == TT_PUNCTUATION && *token->string == '$')
+ {
+#ifdef QUAKEC
+ if (!QuakeCMacro(source))
+#endif //QUAKEC
+ {
+ //read the precompiler directive
+ if (!PC_ReadDollarDirective(source)) return qfalse;
+ continue;
+ } //end if
+ } //end if
+ // recursively concatenate strings that are behind each other still resolving defines
+ if (token->type == TT_STRING)
+ {
+ token_t newtoken;
+ if (PC_ReadToken(source, &newtoken))
+ {
+ if (newtoken.type == TT_STRING)
+ {
+ token->string[strlen(token->string)-1] = '\0';
+ if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN)
+ {
+ SourceError(source, "string longer than MAX_TOKEN %d\n", MAX_TOKEN);
+ return qfalse;
+ }
+ strcat(token->string, newtoken.string+1);
+ }
+ else
+ {
+ PC_UnreadToken(source, &newtoken);
+ }
+ }
+ } //end if
+ //if skipping source because of conditional compilation
+ if (source->skip) continue;
+ //if the token is a name
+ if (token->type == TT_NAME)
+ {
+ //check if the name is a define macro
+#if DEFINEHASHING
+ define = PC_FindHashedDefine(source->definehash, token->string);
+#else
+ define = PC_FindDefine(source->defines, token->string);
+#endif //DEFINEHASHING
+ //if it is a define macro
+ if (define)
+ {
+ //expand the defined macro
+ if (!PC_ExpandDefineIntoSource(source, token, define)) return qfalse;
+ continue;
+ } //end if
+ } //end if
+ //copy token for unreading
+ Com_Memcpy(&source->token, token, sizeof(token_t));
+ //found a token
+ return qtrue;
+ } //end while
+} //end of the function PC_ReadToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ExpectTokenString(source_t *source, char *string)
+{
+ token_t token;
+
+ if (!PC_ReadToken(source, &token))
+ {
+ SourceError(source, "couldn't find expected %s", string);
+ return qfalse;
+ } //end if
+
+ if (strcmp(token.string, string))
+ {
+ SourceError(source, "expected %s, found %s", string, token.string);
+ return qfalse;
+ } //end if
+ return qtrue;
+} //end of the function PC_ExpectTokenString
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token)
+{
+ char str[MAX_TOKEN];
+
+ if (!PC_ReadToken(source, token))
+ {
+ SourceError(source, "couldn't read expected token");
+ return qfalse;
+ } //end if
+
+ if (token->type != type)
+ {
+ strcpy(str, "");
+ if (type == TT_STRING) strcpy(str, "string");
+ if (type == TT_LITERAL) strcpy(str, "literal");
+ if (type == TT_NUMBER) strcpy(str, "number");
+ if (type == TT_NAME) strcpy(str, "name");
+ if (type == TT_PUNCTUATION) strcpy(str, "punctuation");
+ SourceError(source, "expected a %s, found %s", str, token->string);
+ return qfalse;
+ } //end if
+ if (token->type == TT_NUMBER)
+ {
+ if ((token->subtype & subtype) != subtype)
+ {
+ if (subtype & TT_DECIMAL) strcpy(str, "decimal");
+ if (subtype & TT_HEX) strcpy(str, "hex");
+ if (subtype & TT_OCTAL) strcpy(str, "octal");
+ if (subtype & TT_BINARY) strcpy(str, "binary");
+ if (subtype & TT_LONG) strcat(str, " long");
+ if (subtype & TT_UNSIGNED) strcat(str, " unsigned");
+ if (subtype & TT_FLOAT) strcat(str, " float");
+ if (subtype & TT_INTEGER) strcat(str, " integer");
+ SourceError(source, "expected %s, found %s", str, token->string);
+ return qfalse;
+ } //end if
+ } //end if
+ else if (token->type == TT_PUNCTUATION)
+ {
+ if (token->subtype != subtype)
+ {
+ SourceError(source, "found %s", token->string);
+ return qfalse;
+ } //end if
+ } //end else if
+ return qtrue;
+} //end of the function PC_ExpectTokenType
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_ExpectAnyToken(source_t *source, token_t *token)
+{
+ if (!PC_ReadToken(source, token))
+ {
+ SourceError(source, "couldn't read expected token");
+ return qfalse;
+ } //end if
+ else
+ {
+ return qtrue;
+ } //end else
+} //end of the function PC_ExpectAnyToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_CheckTokenString(source_t *source, char *string)
+{
+ token_t tok;
+
+ if (!PC_ReadToken(source, &tok)) return qfalse;
+ //if the token is available
+ if (!strcmp(tok.string, string)) return qtrue;
+ //
+ PC_UnreadSourceToken(source, &tok);
+ return qfalse;
+} //end of the function PC_CheckTokenString
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token)
+{
+ token_t tok;
+
+ if (!PC_ReadToken(source, &tok)) return qfalse;
+ //if the type matches
+ if (tok.type == type &&
+ (tok.subtype & subtype) == subtype)
+ {
+ Com_Memcpy(token, &tok, sizeof(token_t));
+ return qtrue;
+ } //end if
+ //
+ PC_UnreadSourceToken(source, &tok);
+ return qfalse;
+} //end of the function PC_CheckTokenType
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PC_SkipUntilString(source_t *source, char *string)
+{
+ token_t token;
+
+ while(PC_ReadToken(source, &token))
+ {
+ if (!strcmp(token.string, string)) return qtrue;
+ } //end while
+ return qfalse;
+} //end of the function PC_SkipUntilString
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_UnreadLastToken(source_t *source)
+{
+ PC_UnreadSourceToken(source, &source->token);
+} //end of the function PC_UnreadLastToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_UnreadToken(source_t *source, token_t *token)
+{
+ PC_UnreadSourceToken(source, token);
+} //end of the function PC_UnreadToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_SetIncludePath(source_t *source, char *path)
+{
+ strncpy(source->includepath, path, MAX_PATH);
+ //add trailing path seperator
+ if (source->includepath[strlen(source->includepath)-1] != '\\' &&
+ source->includepath[strlen(source->includepath)-1] != '/')
+ {
+ strcat(source->includepath, PATHSEPERATOR_STR);
+ } //end if
+} //end of the function PC_SetIncludePath
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_SetPunctuations(source_t *source, punctuation_t *p)
+{
+ source->punctuations = p;
+} //end of the function PC_SetPunctuations
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+source_t *LoadSourceFile(const char *filename)
+{
+ source_t *source;
+ script_t *script;
+
+ PC_InitTokenHeap();
+
+ script = LoadScriptFile(filename);
+ if (!script) return NULL;
+
+ script->next = NULL;
+
+ source = (source_t *) GetMemory(sizeof(source_t));
+ Com_Memset(source, 0, sizeof(source_t));
+
+ strncpy(source->filename, filename, MAX_PATH);
+ source->scriptstack = script;
+ source->tokens = NULL;
+ source->defines = NULL;
+ source->indentstack = NULL;
+ source->skip = 0;
+
+#if DEFINEHASHING
+ source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *));
+#endif //DEFINEHASHING
+ PC_AddGlobalDefinesToSource(source);
+ return source;
+} //end of the function LoadSourceFile
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+source_t *LoadSourceMemory(char *ptr, int length, char *name)
+{
+ source_t *source;
+ script_t *script;
+
+ PC_InitTokenHeap();
+
+ script = LoadScriptMemory(ptr, length, name);
+ if (!script) return NULL;
+ script->next = NULL;
+
+ source = (source_t *) GetMemory(sizeof(source_t));
+ Com_Memset(source, 0, sizeof(source_t));
+
+ strncpy(source->filename, name, MAX_PATH);
+ source->scriptstack = script;
+ source->tokens = NULL;
+ source->defines = NULL;
+ source->indentstack = NULL;
+ source->skip = 0;
+
+#if DEFINEHASHING
+ source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *));
+#endif //DEFINEHASHING
+ PC_AddGlobalDefinesToSource(source);
+ return source;
+} //end of the function LoadSourceMemory
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void FreeSource(source_t *source)
+{
+ script_t *script;
+ token_t *token;
+ define_t *define;
+ indent_t *indent;
+ int i;
+
+ //PC_PrintDefineHashTable(source->definehash);
+ //free all the scripts
+ while(source->scriptstack)
+ {
+ script = source->scriptstack;
+ source->scriptstack = source->scriptstack->next;
+ FreeScript(script);
+ } //end for
+ //free all the tokens
+ while(source->tokens)
+ {
+ token = source->tokens;
+ source->tokens = source->tokens->next;
+ PC_FreeToken(token);
+ } //end for
+#if DEFINEHASHING
+ for (i = 0; i < DEFINEHASHSIZE; i++)
+ {
+ while(source->definehash[i])
+ {
+ define = source->definehash[i];
+ source->definehash[i] = source->definehash[i]->hashnext;
+ PC_FreeDefine(define);
+ } //end while
+ } //end for
+#else //DEFINEHASHING
+ //free all defines
+ while(source->defines)
+ {
+ define = source->defines;
+ source->defines = source->defines->next;
+ PC_FreeDefine(define);
+ } //end for
+#endif //DEFINEHASHING
+ //free all indents
+ while(source->indentstack)
+ {
+ indent = source->indentstack;
+ source->indentstack = source->indentstack->next;
+ FreeMemory(indent);
+ } //end for
+#if DEFINEHASHING
+ //
+ if (source->definehash) FreeMemory(source->definehash);
+#endif //DEFINEHASHING
+ //free the source itself
+ FreeMemory(source);
+} //end of the function FreeSource
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+
+#define MAX_SOURCEFILES 64
+
+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;
+ if (pc_token->type == TT_STRING)
+ StripDoubleQuotes(pc_token->string);
+ return ret;
+} //end of the function PC_ReadTokenHandle
+//============================================================================
+//
+// 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: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_SetBaseFolder(char *path)
+{
+ PS_SetBaseFolder(path);
+} //end of the function PC_SetBaseFolder
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PC_CheckOpenSourceHandles(void)
+{
+ int i;
+
+ for (i = 1; i < MAX_SOURCEFILES; i++)
+ {
+ if (sourceFiles[i])
+ {
+#ifdef BOTLIB
+ botimport.Print(PRT_ERROR, "file %s still open in precompiler\n", sourceFiles[i]->scriptstack->filename);
+#endif //BOTLIB
+ } //end if
+ } //end for
+} //end of the function PC_CheckOpenSourceHandles
+
diff --git a/src/botlib/l_precomp.h b/src/botlib/l_precomp.h
new file mode 100644
index 00000000..fcc0e8a3
--- /dev/null
+++ b/src/botlib/l_precomp.h
@@ -0,0 +1,180 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_precomp.h
+ *
+ * desc: pre compiler
+ *
+ * $Archive: /source/code/botlib/l_precomp.h $
+ *
+ *****************************************************************************/
+
+#ifndef MAX_PATH
+ #define MAX_PATH MAX_QPATH
+#endif
+
+#ifndef PATH_SEPERATORSTR
+ #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__)
+ #define PATHSEPERATOR_STR "\\"
+ #else
+ #define PATHSEPERATOR_STR "/"
+ #endif
+#endif
+#ifndef PATH_SEPERATORCHAR
+ #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__)
+ #define PATHSEPERATOR_CHAR '\\'
+ #else
+ #define PATHSEPERATOR_CHAR '/'
+ #endif
+#endif
+
+#if defined(BSPC) && !defined(QDECL)
+#define QDECL
+#endif
+
+
+#define DEFINE_FIXED 0x0001
+
+#define BUILTIN_LINE 1
+#define BUILTIN_FILE 2
+#define BUILTIN_DATE 3
+#define BUILTIN_TIME 4
+#define BUILTIN_STDC 5
+
+#define INDENT_IF 0x0001
+#define INDENT_ELSE 0x0002
+#define INDENT_ELIF 0x0004
+#define INDENT_IFDEF 0x0008
+#define INDENT_IFNDEF 0x0010
+
+//macro definitions
+typedef struct define_s
+{
+ char *name; //define name
+ int flags; //define flags
+ int builtin; // > 0 if builtin define
+ int numparms; //number of define parameters
+ token_t *parms; //define parameters
+ token_t *tokens; //macro tokens (possibly containing parm tokens)
+ struct define_s *next; //next defined macro in a list
+ struct define_s *hashnext; //next define in the hash chain
+} define_t;
+
+//indents
+//used for conditional compilation directives:
+//#if, #else, #elif, #ifdef, #ifndef
+typedef struct indent_s
+{
+ int type; //indent type
+ int skip; //true if skipping current indent
+ script_t *script; //script the indent was in
+ struct indent_s *next; //next indent on the indent stack
+} indent_t;
+
+//source file
+typedef struct source_s
+{
+ char filename[1024]; //file name of the script
+ char includepath[1024]; //path to include files
+ punctuation_t *punctuations; //punctuations to use
+ script_t *scriptstack; //stack with scripts of the source
+ token_t *tokens; //tokens to read first
+ define_t *defines; //list with macro definitions
+ define_t **definehash; //hash chain with defines
+ indent_t *indentstack; //stack with indents
+ int skip; // > 0 if skipping conditional code
+ token_t token; //last read token
+} source_t;
+
+
+//read a token from the source
+int PC_ReadToken(source_t *source, token_t *token);
+//expect a certain token
+int PC_ExpectTokenString(source_t *source, char *string);
+//expect a certain token type
+int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token);
+//expect a token
+int PC_ExpectAnyToken(source_t *source, token_t *token);
+//returns true when the token is available
+int PC_CheckTokenString(source_t *source, char *string);
+//returns true an reads the token when a token with the given type is available
+int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token);
+//skip tokens until the given token string is read
+int PC_SkipUntilString(source_t *source, char *string);
+//unread the last token read from the script
+void PC_UnreadLastToken(source_t *source);
+//unread the given token
+void PC_UnreadToken(source_t *source, token_t *token);
+//read a token only if on the same line, lines are concatenated with a slash
+int PC_ReadLine(source_t *source, token_t *token);
+//returns true if there was a white space in front of the token
+int PC_WhiteSpaceBeforeToken(token_t *token);
+//add a define to the source
+int PC_AddDefine(source_t *source, char *string);
+//add a globals define that will be added to all opened sources
+int PC_AddGlobalDefine(char *string);
+//remove the given global define
+int PC_RemoveGlobalDefine(char *name);
+//remove all globals defines
+void PC_RemoveAllGlobalDefines(void);
+//add builtin defines
+void PC_AddBuiltinDefines(source_t *source);
+//set the source include path
+void PC_SetIncludePath(source_t *source, char *path);
+//set the punction set
+void PC_SetPunctuations(source_t *source, punctuation_t *p);
+//set the base folder to load files from
+void PC_SetBaseFolder(char *path);
+//load a source file
+source_t *LoadSourceFile(const char *filename);
+//load a source from memory
+source_t *LoadSourceMemory(char *ptr, int length, char *name);
+//free the given source
+void FreeSource(source_t *source);
+//print a source error
+void QDECL SourceError(source_t *source, char *str, ...);
+//print a source warning
+void QDECL SourceWarning(source_t *source, char *str, ...);
+
+#ifdef BSPC
+// some of BSPC source does include game/q_shared.h and some does not
+// we define pc_token_s pc_token_t if needed (yes, it's ugly)
+#ifndef __Q_SHARED_H
+#define MAX_TOKENLENGTH 1024
+typedef struct pc_token_s
+{
+ int type;
+ int subtype;
+ int intvalue;
+ float floatvalue;
+ char string[MAX_TOKENLENGTH];
+} pc_token_t;
+#endif //!_Q_SHARED_H
+#endif //BSPC
+
+//
+int PC_LoadSourceHandle(const char *filename);
+int PC_FreeSourceHandle(int handle);
+int PC_ReadTokenHandle(int handle, pc_token_t *pc_token);
+int PC_SourceFileAndLine(int handle, char *filename, int *line);
+void PC_CheckOpenSourceHandles(void);
diff --git a/src/botlib/l_script.c b/src/botlib/l_script.c
new file mode 100644
index 00000000..e721891c
--- /dev/null
+++ b/src/botlib/l_script.c
@@ -0,0 +1,1433 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_script.c
+ *
+ * desc: lexicographical parser
+ *
+ * $Archive: /MissionPack/code/botlib/l_script.c $
+ *
+ *****************************************************************************/
+
+//#define SCREWUP
+//#define BOTLIB
+//#define MEQCC
+//#define BSPC
+
+#ifdef SCREWUP
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <stdarg.h>
+#include "l_memory.h"
+#include "l_script.h"
+
+typedef enum {qfalse, qtrue} qboolean;
+
+#endif //SCREWUP
+
+#ifdef BOTLIB
+//include files for usage in the bot library
+#include "../qcommon/q_shared.h"
+#include "botlib.h"
+#include "be_interface.h"
+#include "l_script.h"
+#include "l_memory.h"
+#include "l_log.h"
+#include "l_libvar.h"
+#endif //BOTLIB
+
+#ifdef MEQCC
+//include files for usage in MrElusive's QuakeC Compiler
+#include "qcc.h"
+#include "l_script.h"
+#include "l_memory.h"
+#include "l_log.h"
+
+#define qtrue true
+#define qfalse false
+#endif //MEQCC
+
+#ifdef BSPC
+//include files for usage in the BSP Converter
+#include "../bspc/qbsp.h"
+#include "../bspc/l_log.h"
+#include "../bspc/l_mem.h"
+
+#define qtrue true
+#define qfalse false
+#endif //BSPC
+
+
+#define PUNCTABLE
+
+//longer punctuations first
+punctuation_t default_punctuations[] =
+{
+ //binary operators
+ {">>=",P_RSHIFT_ASSIGN, NULL},
+ {"<<=",P_LSHIFT_ASSIGN, NULL},
+ //
+ {"...",P_PARMS, NULL},
+ //define merge operator
+ {"##",P_PRECOMPMERGE, NULL},
+ //logic operators
+ {"&&",P_LOGIC_AND, NULL},
+ {"||",P_LOGIC_OR, NULL},
+ {">=",P_LOGIC_GEQ, NULL},
+ {"<=",P_LOGIC_LEQ, NULL},
+ {"==",P_LOGIC_EQ, NULL},
+ {"!=",P_LOGIC_UNEQ, NULL},
+ //arithmatic operators
+ {"*=",P_MUL_ASSIGN, NULL},
+ {"/=",P_DIV_ASSIGN, NULL},
+ {"%=",P_MOD_ASSIGN, NULL},
+ {"+=",P_ADD_ASSIGN, NULL},
+ {"-=",P_SUB_ASSIGN, NULL},
+ {"++",P_INC, NULL},
+ {"--",P_DEC, NULL},
+ //binary operators
+ {"&=",P_BIN_AND_ASSIGN, NULL},
+ {"|=",P_BIN_OR_ASSIGN, NULL},
+ {"^=",P_BIN_XOR_ASSIGN, NULL},
+ {">>",P_RSHIFT, NULL},
+ {"<<",P_LSHIFT, NULL},
+ //reference operators
+ {"->",P_POINTERREF, NULL},
+ //C++
+ {"::",P_CPP1, NULL},
+ {".*",P_CPP2, NULL},
+ //arithmatic operators
+ {"*",P_MUL, NULL},
+ {"/",P_DIV, NULL},
+ {"%",P_MOD, NULL},
+ {"+",P_ADD, NULL},
+ {"-",P_SUB, NULL},
+ {"=",P_ASSIGN, NULL},
+ //binary operators
+ {"&",P_BIN_AND, NULL},
+ {"|",P_BIN_OR, NULL},
+ {"^",P_BIN_XOR, NULL},
+ {"~",P_BIN_NOT, NULL},
+ //logic operators
+ {"!",P_LOGIC_NOT, NULL},
+ {">",P_LOGIC_GREATER, NULL},
+ {"<",P_LOGIC_LESS, NULL},
+ //reference operator
+ {".",P_REF, NULL},
+ //seperators
+ {",",P_COMMA, NULL},
+ {";",P_SEMICOLON, NULL},
+ //label indication
+ {":",P_COLON, NULL},
+ //if statement
+ {"?",P_QUESTIONMARK, NULL},
+ //embracements
+ {"(",P_PARENTHESESOPEN, NULL},
+ {")",P_PARENTHESESCLOSE, NULL},
+ {"{",P_BRACEOPEN, NULL},
+ {"}",P_BRACECLOSE, NULL},
+ {"[",P_SQBRACKETOPEN, NULL},
+ {"]",P_SQBRACKETCLOSE, NULL},
+ //
+ {"\\",P_BACKSLASH, NULL},
+ //precompiler operator
+ {"#",P_PRECOMP, NULL},
+#ifdef DOLLAR
+ {"$",P_DOLLAR, NULL},
+#endif //DOLLAR
+ {NULL, 0}
+};
+
+#ifdef BSPC
+char basefolder[MAX_PATH];
+#else
+char basefolder[MAX_QPATH];
+#endif
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void PS_CreatePunctuationTable(script_t *script, punctuation_t *punctuations)
+{
+ int i;
+ punctuation_t *p, *lastp, *newp;
+
+ //get memory for the table
+ if (!script->punctuationtable) script->punctuationtable = (punctuation_t **)
+ GetMemory(256 * sizeof(punctuation_t *));
+ Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *));
+ //add the punctuations in the list to the punctuation table
+ for (i = 0; punctuations[i].p; i++)
+ {
+ newp = &punctuations[i];
+ lastp = NULL;
+ //sort the punctuations in this table entry on length (longer punctuations first)
+ for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next)
+ {
+ if (strlen(p->p) < strlen(newp->p))
+ {
+ newp->next = p;
+ if (lastp) lastp->next = newp;
+ else script->punctuationtable[(unsigned int) newp->p[0]] = newp;
+ break;
+ } //end if
+ lastp = p;
+ } //end for
+ if (!p)
+ {
+ newp->next = NULL;
+ if (lastp) lastp->next = newp;
+ else script->punctuationtable[(unsigned int) newp->p[0]] = newp;
+ } //end if
+ } //end for
+} //end of the function PS_CreatePunctuationTable
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+char *PunctuationFromNum(script_t *script, int num)
+{
+ int i;
+
+ for (i = 0; script->punctuations[i].p; i++)
+ {
+ if (script->punctuations[i].n == num) return script->punctuations[i].p;
+ } //end for
+ return "unkown punctuation";
+} //end of the function PunctuationFromNum
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void QDECL ScriptError(script_t *script, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ if (script->flags & SCFL_NOERRORS) return;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+#ifdef BOTLIB
+ botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text);
+#endif //BOTLIB
+#ifdef MEQCC
+ printf("error: file %s, line %d: %s\n", script->filename, script->line, text);
+#endif //MEQCC
+#ifdef BSPC
+ Log_Print("error: file %s, line %d: %s\n", script->filename, script->line, text);
+#endif //BSPC
+} //end of the function ScriptError
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void QDECL ScriptWarning(script_t *script, char *str, ...)
+{
+ char text[1024];
+ va_list ap;
+
+ if (script->flags & SCFL_NOWARNINGS) return;
+
+ va_start(ap, str);
+ vsprintf(text, str, ap);
+ va_end(ap);
+#ifdef BOTLIB
+ botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text);
+#endif //BOTLIB
+#ifdef MEQCC
+ printf("warning: file %s, line %d: %s\n", script->filename, script->line, text);
+#endif //MEQCC
+#ifdef BSPC
+ Log_Print("warning: file %s, line %d: %s\n", script->filename, script->line, text);
+#endif //BSPC
+} //end of the function ScriptWarning
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+void SetScriptPunctuations(script_t *script, punctuation_t *p)
+{
+#ifdef PUNCTABLE
+ if (p) PS_CreatePunctuationTable(script, p);
+ else PS_CreatePunctuationTable(script, default_punctuations);
+#endif //PUNCTABLE
+ if (p) script->punctuations = p;
+ else script->punctuations = default_punctuations;
+} //end of the function SetScriptPunctuations
+//============================================================================
+// Reads spaces, tabs, C-like comments etc.
+// When a newline character is found the scripts line counter is increased.
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadWhiteSpace(script_t *script)
+{
+ while(1)
+ {
+ //skip white space
+ while(*script->script_p <= ' ')
+ {
+ if (!*script->script_p) return 0;
+ if (*script->script_p == '\n') script->line++;
+ script->script_p++;
+ } //end while
+ //skip comments
+ if (*script->script_p == '/')
+ {
+ //comments //
+ if (*(script->script_p+1) == '/')
+ {
+ script->script_p++;
+ do
+ {
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ } //end do
+ while(*script->script_p != '\n');
+ script->line++;
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ continue;
+ } //end if
+ //comments /* */
+ else if (*(script->script_p+1) == '*')
+ {
+ script->script_p++;
+ do
+ {
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ if (*script->script_p == '\n') script->line++;
+ } //end do
+ while(!(*script->script_p == '*' && *(script->script_p+1) == '/'));
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ script->script_p++;
+ if (!*script->script_p) return 0;
+ continue;
+ } //end if
+ } //end if
+ break;
+ } //end while
+ return 1;
+} //end of the function PS_ReadWhiteSpace
+//============================================================================
+// Reads an escape character.
+//
+// Parameter: script : script to read from
+// ch : place to store the read escape character
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadEscapeCharacter(script_t *script, char *ch)
+{
+ int c, val, i;
+
+ //step over the leading '\\'
+ script->script_p++;
+ //determine the escape character
+ switch(*script->script_p)
+ {
+ case '\\': c = '\\'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'a': c = '\a'; break;
+ case '\'': c = '\''; break;
+ case '\"': c = '\"'; break;
+ case '\?': c = '\?'; break;
+ case 'x':
+ {
+ script->script_p++;
+ for (i = 0, val = 0; ; i++, script->script_p++)
+ {
+ c = *script->script_p;
+ if (c >= '0' && c <= '9') c = c - '0';
+ else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10;
+ else if (c >= 'a' && c <= 'z') c = c - 'a' + 10;
+ else break;
+ val = (val << 4) + c;
+ } //end for
+ script->script_p--;
+ if (val > 0xFF)
+ {
+ ScriptWarning(script, "too large value in escape character");
+ val = 0xFF;
+ } //end if
+ c = val;
+ break;
+ } //end case
+ default: //NOTE: decimal ASCII code, NOT octal
+ {
+ if (*script->script_p < '0' || *script->script_p > '9') ScriptError(script, "unknown escape char");
+ for (i = 0, val = 0; ; i++, script->script_p++)
+ {
+ c = *script->script_p;
+ if (c >= '0' && c <= '9') c = c - '0';
+ else break;
+ val = val * 10 + c;
+ } //end for
+ script->script_p--;
+ if (val > 0xFF)
+ {
+ ScriptWarning(script, "too large value in escape character");
+ val = 0xFF;
+ } //end if
+ c = val;
+ break;
+ } //end default
+ } //end switch
+ //step over the escape character or the last digit of the number
+ script->script_p++;
+ //store the escape character
+ *ch = c;
+ //succesfully read escape character
+ return 1;
+} //end of the function PS_ReadEscapeCharacter
+//============================================================================
+// Reads C-like string. Escape characters are interpretted.
+// Quotes are included with the string.
+// Reads two strings with a white space between them as one string.
+//
+// Parameter: script : script to read from
+// token : buffer to store the string
+// Returns: qtrue when a string was read succesfully
+// Changes Globals: -
+//============================================================================
+int PS_ReadString(script_t *script, token_t *token, int quote)
+{
+ int len, tmpline;
+ char *tmpscript_p;
+
+ if (quote == '\"') token->type = TT_STRING;
+ else token->type = TT_LITERAL;
+
+ len = 0;
+ //leading quote
+ token->string[len++] = *script->script_p++;
+ //
+ while(1)
+ {
+ //minus 2 because trailing double quote and zero have to be appended
+ if (len >= MAX_TOKEN - 2)
+ {
+ ScriptError(script, "string longer than MAX_TOKEN = %d", MAX_TOKEN);
+ return 0;
+ } //end if
+ //if there is an escape character and
+ //if escape characters inside a string are allowed
+ if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS))
+ {
+ if (!PS_ReadEscapeCharacter(script, &token->string[len]))
+ {
+ token->string[len] = 0;
+ return 0;
+ } //end if
+ len++;
+ } //end if
+ //if a trailing quote
+ else if (*script->script_p == quote)
+ {
+ //step over the double quote
+ script->script_p++;
+ //if white spaces in a string are not allowed
+ if (script->flags & SCFL_NOSTRINGWHITESPACES) break;
+ //
+ tmpscript_p = script->script_p;
+ tmpline = script->line;
+ //read unusefull stuff between possible two following strings
+ if (!PS_ReadWhiteSpace(script))
+ {
+ script->script_p = tmpscript_p;
+ script->line = tmpline;
+ break;
+ } //end if
+ //if there's no leading double qoute
+ if (*script->script_p != quote)
+ {
+ script->script_p = tmpscript_p;
+ script->line = tmpline;
+ break;
+ } //end if
+ //step over the new leading double quote
+ script->script_p++;
+ } //end if
+ else
+ {
+ if (*script->script_p == '\0')
+ {
+ token->string[len] = 0;
+ ScriptError(script, "missing trailing quote");
+ return 0;
+ } //end if
+ if (*script->script_p == '\n')
+ {
+ token->string[len] = 0;
+ ScriptError(script, "newline inside string %s", token->string);
+ return 0;
+ } //end if
+ token->string[len++] = *script->script_p++;
+ } //end else
+ } //end while
+ //trailing quote
+ token->string[len++] = quote;
+ //end string with a zero
+ token->string[len] = '\0';
+ //the sub type is the length of the string
+ token->subtype = len;
+ return 1;
+} //end of the function PS_ReadString
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadName(script_t *script, token_t *token)
+{
+ int len = 0;
+ char c;
+
+ token->type = TT_NAME;
+ do
+ {
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN)
+ {
+ ScriptError(script, "name longer than MAX_TOKEN = %d", MAX_TOKEN);
+ return 0;
+ } //end if
+ c = *script->script_p;
+ } while ((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '_');
+ token->string[len] = '\0';
+ //the sub type is the length of the name
+ token->subtype = len;
+ return 1;
+} //end of the function PS_ReadName
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void NumberValue(char *string, int subtype, unsigned long int *intvalue,
+ double *floatvalue)
+{
+ unsigned long int dotfound = 0;
+
+ *intvalue = 0;
+ *floatvalue = 0;
+ //floating point number
+ if (subtype & TT_FLOAT)
+ {
+ while(*string)
+ {
+ if (*string == '.')
+ {
+ if (dotfound) return;
+ dotfound = 10;
+ string++;
+ } //end if
+ if (dotfound)
+ {
+ *floatvalue = *floatvalue + (double) (*string - '0') /
+ (double) dotfound;
+ dotfound *= 10;
+ } //end if
+ else
+ {
+ *floatvalue = *floatvalue * 10.0 + (double) (*string - '0');
+ } //end else
+ string++;
+ } //end while
+ *intvalue = (unsigned long) *floatvalue;
+ } //end if
+ else if (subtype & TT_DECIMAL)
+ {
+ while(*string) *intvalue = *intvalue * 10 + (*string++ - '0');
+ *floatvalue = *intvalue;
+ } //end else if
+ else if (subtype & TT_HEX)
+ {
+ //step over the leading 0x or 0X
+ string += 2;
+ while(*string)
+ {
+ *intvalue <<= 4;
+ if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10;
+ else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10;
+ else *intvalue += *string - '0';
+ string++;
+ } //end while
+ *floatvalue = *intvalue;
+ } //end else if
+ else if (subtype & TT_OCTAL)
+ {
+ //step over the first zero
+ string += 1;
+ while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0');
+ *floatvalue = *intvalue;
+ } //end else if
+ else if (subtype & TT_BINARY)
+ {
+ //step over the leading 0b or 0B
+ string += 2;
+ while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0');
+ *floatvalue = *intvalue;
+ } //end else if
+} //end of the function NumberValue
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadNumber(script_t *script, token_t *token)
+{
+ int len = 0, i;
+ int octal, dot;
+ char c;
+// unsigned long int intvalue = 0;
+// double floatvalue = 0;
+
+ token->type = TT_NUMBER;
+ //check for a hexadecimal number
+ if (*script->script_p == '0' &&
+ (*(script->script_p + 1) == 'x' ||
+ *(script->script_p + 1) == 'X'))
+ {
+ token->string[len++] = *script->script_p++;
+ token->string[len++] = *script->script_p++;
+ c = *script->script_p;
+ //hexadecimal
+ while((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'A'))
+ {
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN)
+ {
+ ScriptError(script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN);
+ return 0;
+ } //end if
+ c = *script->script_p;
+ } //end while
+ token->subtype |= TT_HEX;
+ } //end if
+#ifdef BINARYNUMBERS
+ //check for a binary number
+ else if (*script->script_p == '0' &&
+ (*(script->script_p + 1) == 'b' ||
+ *(script->script_p + 1) == 'B'))
+ {
+ token->string[len++] = *script->script_p++;
+ token->string[len++] = *script->script_p++;
+ c = *script->script_p;
+ //binary
+ while(c == '0' || c == '1')
+ {
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN)
+ {
+ ScriptError(script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN);
+ return 0;
+ } //end if
+ c = *script->script_p;
+ } //end while
+ token->subtype |= TT_BINARY;
+ } //end if
+#endif //BINARYNUMBERS
+ else //decimal or octal integer or floating point number
+ {
+ octal = qfalse;
+ dot = qfalse;
+ if (*script->script_p == '0') octal = qtrue;
+ while(1)
+ {
+ c = *script->script_p;
+ if (c == '.') dot = qtrue;
+ else if (c == '8' || c == '9') octal = qfalse;
+ else if (c < '0' || c > '9') break;
+ token->string[len++] = *script->script_p++;
+ if (len >= MAX_TOKEN - 1)
+ {
+ ScriptError(script, "number longer than MAX_TOKEN = %d", MAX_TOKEN);
+ return 0;
+ } //end if
+ } //end while
+ if (octal) token->subtype |= TT_OCTAL;
+ else token->subtype |= TT_DECIMAL;
+ if (dot) token->subtype |= TT_FLOAT;
+ } //end else
+ for (i = 0; i < 2; i++)
+ {
+ c = *script->script_p;
+ //check for a LONG number
+ if ( (c == 'l' || c == 'L') // bk001204 - brackets
+ && !(token->subtype & TT_LONG))
+ {
+ script->script_p++;
+ token->subtype |= TT_LONG;
+ } //end if
+ //check for an UNSIGNED number
+ else if ( (c == 'u' || c == 'U') // bk001204 - brackets
+ && !(token->subtype & (TT_UNSIGNED | TT_FLOAT)))
+ {
+ script->script_p++;
+ token->subtype |= TT_UNSIGNED;
+ } //end if
+ } //end for
+ token->string[len] = '\0';
+#ifdef NUMBERVALUE
+ NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue);
+#endif //NUMBERVALUE
+ if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER;
+ return 1;
+} //end of the function PS_ReadNumber
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadLiteral(script_t *script, token_t *token)
+{
+ token->type = TT_LITERAL;
+ //first quote
+ token->string[0] = *script->script_p++;
+ //check for end of file
+ if (!*script->script_p)
+ {
+ ScriptError(script, "end of file before trailing \'");
+ return 0;
+ } //end if
+ //if it is an escape character
+ if (*script->script_p == '\\')
+ {
+ if (!PS_ReadEscapeCharacter(script, &token->string[1])) return 0;
+ } //end if
+ else
+ {
+ token->string[1] = *script->script_p++;
+ } //end else
+ //check for trailing quote
+ if (*script->script_p != '\'')
+ {
+ ScriptWarning(script, "too many characters in literal, ignored");
+ while(*script->script_p &&
+ *script->script_p != '\'' &&
+ *script->script_p != '\n')
+ {
+ script->script_p++;
+ } //end while
+ if (*script->script_p == '\'') script->script_p++;
+ } //end if
+ //store the trailing quote
+ token->string[2] = *script->script_p++;
+ //store trailing zero to end the string
+ token->string[3] = '\0';
+ //the sub type is the integer literal value
+ token->subtype = token->string[1];
+ //
+ return 1;
+} //end of the function PS_ReadLiteral
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadPunctuation(script_t *script, token_t *token)
+{
+ int len;
+ char *p;
+ punctuation_t *punc;
+
+#ifdef PUNCTABLE
+ for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next)
+ {
+#else
+ int i;
+
+ for (i = 0; script->punctuations[i].p; i++)
+ {
+ punc = &script->punctuations[i];
+#endif //PUNCTABLE
+ p = punc->p;
+ len = strlen(p);
+ //if the script contains at least as much characters as the punctuation
+ if (script->script_p + len <= script->end_p)
+ {
+ //if the script contains the punctuation
+ if (!strncmp(script->script_p, p, len))
+ {
+ strncpy(token->string, p, MAX_TOKEN);
+ script->script_p += len;
+ token->type = TT_PUNCTUATION;
+ //sub type is the number of the punctuation
+ token->subtype = punc->n;
+ return 1;
+ } //end if
+ } //end if
+ } //end for
+ return 0;
+} //end of the function PS_ReadPunctuation
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadPrimitive(script_t *script, token_t *token)
+{
+ int len;
+
+ len = 0;
+ while(*script->script_p > ' ' && *script->script_p != ';')
+ {
+ if (len >= MAX_TOKEN)
+ {
+ ScriptError(script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN);
+ return 0;
+ } //end if
+ token->string[len++] = *script->script_p++;
+ } //end while
+ token->string[len] = 0;
+ //copy the token into the script structure
+ Com_Memcpy(&script->token, token, sizeof(token_t));
+ //primitive reading successfull
+ return 1;
+} //end of the function PS_ReadPrimitive
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ReadToken(script_t *script, token_t *token)
+{
+ //if there is a token available (from UnreadToken)
+ if (script->tokenavailable)
+ {
+ script->tokenavailable = 0;
+ Com_Memcpy(token, &script->token, sizeof(token_t));
+ return 1;
+ } //end if
+ //save script pointer
+ script->lastscript_p = script->script_p;
+ //save line counter
+ script->lastline = script->line;
+ //clear the token stuff
+ Com_Memset(token, 0, sizeof(token_t));
+ //start of the white space
+ script->whitespace_p = script->script_p;
+ token->whitespace_p = script->script_p;
+ //read unusefull stuff
+ if (!PS_ReadWhiteSpace(script)) return 0;
+ //end of the white space
+ script->endwhitespace_p = script->script_p;
+ token->endwhitespace_p = script->script_p;
+ //line the token is on
+ token->line = script->line;
+ //number of lines crossed before token
+ token->linescrossed = script->line - script->lastline;
+ //if there is a leading double quote
+ if (*script->script_p == '\"')
+ {
+ if (!PS_ReadString(script, token, '\"')) return 0;
+ } //end if
+ //if an literal
+ else if (*script->script_p == '\'')
+ {
+ //if (!PS_ReadLiteral(script, token)) return 0;
+ if (!PS_ReadString(script, token, '\'')) return 0;
+ } //end if
+ //if there is a number
+ else if ((*script->script_p >= '0' && *script->script_p <= '9') ||
+ (*script->script_p == '.' &&
+ (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9')))
+ {
+ if (!PS_ReadNumber(script, token)) return 0;
+ } //end if
+ //if this is a primitive script
+ else if (script->flags & SCFL_PRIMITIVE)
+ {
+ return PS_ReadPrimitive(script, token);
+ } //end else if
+ //if there is a name
+ else if ((*script->script_p >= 'a' && *script->script_p <= 'z') ||
+ (*script->script_p >= 'A' && *script->script_p <= 'Z') ||
+ *script->script_p == '_')
+ {
+ if (!PS_ReadName(script, token)) return 0;
+ } //end if
+ //check for punctuations
+ else if (!PS_ReadPunctuation(script, token))
+ {
+ ScriptError(script, "can't read token");
+ return 0;
+ } //end if
+ //copy the token into the script structure
+ Com_Memcpy(&script->token, token, sizeof(token_t));
+ //succesfully read a token
+ return 1;
+} //end of the function PS_ReadToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ExpectTokenString(script_t *script, char *string)
+{
+ token_t token;
+
+ if (!PS_ReadToken(script, &token))
+ {
+ ScriptError(script, "couldn't find expected %s", string);
+ return 0;
+ } //end if
+
+ if (strcmp(token.string, string))
+ {
+ ScriptError(script, "expected %s, found %s", string, token.string);
+ return 0;
+ } //end if
+ return 1;
+} //end of the function PS_ExpectToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token)
+{
+ char str[MAX_TOKEN];
+
+ if (!PS_ReadToken(script, token))
+ {
+ ScriptError(script, "couldn't read expected token");
+ return 0;
+ } //end if
+
+ if (token->type != type)
+ {
+ if (type == TT_STRING) strcpy(str, "string");
+ if (type == TT_LITERAL) strcpy(str, "literal");
+ if (type == TT_NUMBER) strcpy(str, "number");
+ if (type == TT_NAME) strcpy(str, "name");
+ if (type == TT_PUNCTUATION) strcpy(str, "punctuation");
+ ScriptError(script, "expected a %s, found %s", str, token->string);
+ return 0;
+ } //end if
+ if (token->type == TT_NUMBER)
+ {
+ if ((token->subtype & subtype) != subtype)
+ {
+ if (subtype & TT_DECIMAL) strcpy(str, "decimal");
+ if (subtype & TT_HEX) strcpy(str, "hex");
+ if (subtype & TT_OCTAL) strcpy(str, "octal");
+ if (subtype & TT_BINARY) strcpy(str, "binary");
+ if (subtype & TT_LONG) strcat(str, " long");
+ if (subtype & TT_UNSIGNED) strcat(str, " unsigned");
+ if (subtype & TT_FLOAT) strcat(str, " float");
+ if (subtype & TT_INTEGER) strcat(str, " integer");
+ ScriptError(script, "expected %s, found %s", str, token->string);
+ return 0;
+ } //end if
+ } //end if
+ else if (token->type == TT_PUNCTUATION)
+ {
+ if (subtype < 0)
+ {
+ ScriptError(script, "BUG: wrong punctuation subtype");
+ return 0;
+ } //end if
+ if (token->subtype != subtype)
+ {
+ ScriptError(script, "expected %s, found %s",
+ script->punctuations[subtype], token->string);
+ return 0;
+ } //end if
+ } //end else if
+ return 1;
+} //end of the function PS_ExpectTokenType
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_ExpectAnyToken(script_t *script, token_t *token)
+{
+ if (!PS_ReadToken(script, token))
+ {
+ ScriptError(script, "couldn't read expected token");
+ return 0;
+ } //end if
+ else
+ {
+ return 1;
+ } //end else
+} //end of the function PS_ExpectAnyToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_CheckTokenString(script_t *script, char *string)
+{
+ token_t tok;
+
+ if (!PS_ReadToken(script, &tok)) return 0;
+ //if the token is available
+ if (!strcmp(tok.string, string)) return 1;
+ //token not available
+ script->script_p = script->lastscript_p;
+ return 0;
+} //end of the function PS_CheckTokenString
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token)
+{
+ token_t tok;
+
+ if (!PS_ReadToken(script, &tok)) return 0;
+ //if the type matches
+ if (tok.type == type &&
+ (tok.subtype & subtype) == subtype)
+ {
+ Com_Memcpy(token, &tok, sizeof(token_t));
+ return 1;
+ } //end if
+ //token is not available
+ script->script_p = script->lastscript_p;
+ return 0;
+} //end of the function PS_CheckTokenType
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int PS_SkipUntilString(script_t *script, char *string)
+{
+ token_t token;
+
+ while(PS_ReadToken(script, &token))
+ {
+ if (!strcmp(token.string, string)) return 1;
+ } //end while
+ return 0;
+} //end of the function PS_SkipUntilString
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PS_UnreadLastToken(script_t *script)
+{
+ script->tokenavailable = 1;
+} //end of the function UnreadLastToken
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PS_UnreadToken(script_t *script, token_t *token)
+{
+ Com_Memcpy(&script->token, token, sizeof(token_t));
+ script->tokenavailable = 1;
+} //end of the function UnreadToken
+//============================================================================
+// returns the next character of the read white space, returns NULL if none
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+char PS_NextWhiteSpaceChar(script_t *script)
+{
+ if (script->whitespace_p != script->endwhitespace_p)
+ {
+ return *script->whitespace_p++;
+ } //end if
+ else
+ {
+ return 0;
+ } //end else
+} //end of the function PS_NextWhiteSpaceChar
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void StripDoubleQuotes(char *string)
+{
+ if (*string == '\"')
+ {
+ strcpy(string, string+1);
+ } //end if
+ if (string[strlen(string)-1] == '\"')
+ {
+ string[strlen(string)-1] = '\0';
+ } //end if
+} //end of the function StripDoubleQuotes
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void StripSingleQuotes(char *string)
+{
+ if (*string == '\'')
+ {
+ strcpy(string, string+1);
+ } //end if
+ if (string[strlen(string)-1] == '\'')
+ {
+ string[strlen(string)-1] = '\0';
+ } //end if
+} //end of the function StripSingleQuotes
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+double ReadSignedFloat(script_t *script)
+{
+ token_t token;
+ double sign = 1.0;
+
+ PS_ExpectAnyToken(script, &token);
+ if (!strcmp(token.string, "-"))
+ {
+ sign = -1.0;
+ PS_ExpectTokenType(script, TT_NUMBER, 0, &token);
+ } //end if
+ else if (token.type != TT_NUMBER)
+ {
+ ScriptError(script, "expected float value, found %s\n", token.string);
+ } //end else if
+ return sign * token.floatvalue;
+} //end of the function ReadSignedFloat
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+signed long int ReadSignedInt(script_t *script)
+{
+ token_t token;
+ signed long int sign = 1;
+
+ PS_ExpectAnyToken(script, &token);
+ if (!strcmp(token.string, "-"))
+ {
+ sign = -1;
+ PS_ExpectTokenType(script, TT_NUMBER, TT_INTEGER, &token);
+ } //end if
+ else if (token.type != TT_NUMBER || token.subtype == TT_FLOAT)
+ {
+ ScriptError(script, "expected integer value, found %s\n", token.string);
+ } //end else if
+ return sign * token.intvalue;
+} //end of the function ReadSignedInt
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void SetScriptFlags(script_t *script, int flags)
+{
+ script->flags = flags;
+} //end of the function SetScriptFlags
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int GetScriptFlags(script_t *script)
+{
+ return script->flags;
+} //end of the function GetScriptFlags
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void ResetScript(script_t *script)
+{
+ //pointer in script buffer
+ script->script_p = script->buffer;
+ //pointer in script buffer before reading token
+ script->lastscript_p = script->buffer;
+ //begin of white space
+ script->whitespace_p = NULL;
+ //end of white space
+ script->endwhitespace_p = NULL;
+ //set if there's a token available in script->token
+ script->tokenavailable = 0;
+ //
+ script->line = 1;
+ script->lastline = 1;
+ //clear the saved token
+ Com_Memset(&script->token, 0, sizeof(token_t));
+} //end of the function ResetScript
+//============================================================================
+// returns true if at the end of the script
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int EndOfScript(script_t *script)
+{
+ return script->script_p >= script->end_p;
+} //end of the function EndOfScript
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int NumLinesCrossed(script_t *script)
+{
+ return script->line - script->lastline;
+} //end of the function NumLinesCrossed
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int ScriptSkipTo(script_t *script, char *value)
+{
+ int len;
+ char firstchar;
+
+ firstchar = *value;
+ len = strlen(value);
+ do
+ {
+ if (!PS_ReadWhiteSpace(script)) return 0;
+ if (*script->script_p == firstchar)
+ {
+ if (!strncmp(script->script_p, value, len))
+ {
+ return 1;
+ } //end if
+ } //end if
+ script->script_p++;
+ } while(1);
+} //end of the function ScriptSkipTo
+#ifndef BOTLIB
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+int FileLength(FILE *fp)
+{
+ int pos;
+ int end;
+
+ pos = ftell(fp);
+ fseek(fp, 0, SEEK_END);
+ end = ftell(fp);
+ fseek(fp, pos, SEEK_SET);
+
+ return end;
+} //end of the function FileLength
+#endif
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+script_t *LoadScriptFile(const char *filename)
+{
+#ifdef BOTLIB
+ fileHandle_t fp;
+ char pathname[MAX_QPATH];
+#else
+ FILE *fp;
+#endif
+ int length;
+ void *buffer;
+ script_t *script;
+
+#ifdef BOTLIB
+ if (strlen(basefolder))
+ Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename);
+ else
+ Com_sprintf(pathname, sizeof(pathname), "%s", filename);
+ length = botimport.FS_FOpenFile( pathname, &fp, FS_READ );
+ if (!fp) return NULL;
+#else
+ fp = fopen(filename, "rb");
+ if (!fp) return NULL;
+
+ length = FileLength(fp);
+#endif
+
+ buffer = GetClearedMemory(sizeof(script_t) + length + 1);
+ script = (script_t *) buffer;
+ Com_Memset(script, 0, sizeof(script_t));
+ strcpy(script->filename, filename);
+ script->buffer = (char *) buffer + sizeof(script_t);
+ script->buffer[length] = 0;
+ script->length = length;
+ //pointer in script buffer
+ script->script_p = script->buffer;
+ //pointer in script buffer before reading token
+ script->lastscript_p = script->buffer;
+ //pointer to end of script buffer
+ script->end_p = &script->buffer[length];
+ //set if there's a token available in script->token
+ script->tokenavailable = 0;
+ //
+ script->line = 1;
+ script->lastline = 1;
+ //
+ SetScriptPunctuations(script, NULL);
+ //
+#ifdef BOTLIB
+ botimport.FS_Read(script->buffer, length, fp);
+ botimport.FS_FCloseFile(fp);
+#else
+ if (fread(script->buffer, length, 1, fp) != 1)
+ {
+ FreeMemory(buffer);
+ script = NULL;
+ } //end if
+ fclose(fp);
+#endif
+ //
+ script->length = COM_Compress(script->buffer);
+
+ return script;
+} //end of the function LoadScriptFile
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+script_t *LoadScriptMemory(char *ptr, int length, char *name)
+{
+ void *buffer;
+ script_t *script;
+
+ buffer = GetClearedMemory(sizeof(script_t) + length + 1);
+ script = (script_t *) buffer;
+ Com_Memset(script, 0, sizeof(script_t));
+ strcpy(script->filename, name);
+ script->buffer = (char *) buffer + sizeof(script_t);
+ script->buffer[length] = 0;
+ script->length = length;
+ //pointer in script buffer
+ script->script_p = script->buffer;
+ //pointer in script buffer before reading token
+ script->lastscript_p = script->buffer;
+ //pointer to end of script buffer
+ script->end_p = &script->buffer[length];
+ //set if there's a token available in script->token
+ script->tokenavailable = 0;
+ //
+ script->line = 1;
+ script->lastline = 1;
+ //
+ SetScriptPunctuations(script, NULL);
+ //
+ Com_Memcpy(script->buffer, ptr, length);
+ //
+ return script;
+} //end of the function LoadScriptMemory
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void FreeScript(script_t *script)
+{
+#ifdef PUNCTABLE
+ if (script->punctuationtable) FreeMemory(script->punctuationtable);
+#endif //PUNCTABLE
+ FreeMemory(script);
+} //end of the function FreeScript
+//============================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//============================================================================
+void PS_SetBaseFolder(char *path)
+{
+#ifdef BSPC
+ sprintf(basefolder, path);
+#else
+ Com_sprintf(basefolder, sizeof(basefolder), path);
+#endif
+} //end of the function PS_SetBaseFolder
diff --git a/src/botlib/l_script.h b/src/botlib/l_script.h
new file mode 100644
index 00000000..7fe4ac96
--- /dev/null
+++ b/src/botlib/l_script.h
@@ -0,0 +1,247 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_script.h
+ *
+ * desc: lexicographical parser
+ *
+ * $Archive: /source/code/botlib/l_script.h $
+ *
+ *****************************************************************************/
+
+//undef if binary numbers of the form 0b... or 0B... are not allowed
+#define BINARYNUMBERS
+//undef if not using the token.intvalue and token.floatvalue
+#define NUMBERVALUE
+//use dollar sign also as punctuation
+#define DOLLAR
+
+//maximum token length
+#define MAX_TOKEN 1024
+
+#if defined(BSPC) && !defined(QDECL)
+#define QDECL
+#endif
+
+
+//script flags
+#define SCFL_NOERRORS 0x0001
+#define SCFL_NOWARNINGS 0x0002
+#define SCFL_NOSTRINGWHITESPACES 0x0004
+#define SCFL_NOSTRINGESCAPECHARS 0x0008
+#define SCFL_PRIMITIVE 0x0010
+#define SCFL_NOBINARYNUMBERS 0x0020
+#define SCFL_NONUMBERVALUES 0x0040
+
+//token types
+#define TT_STRING 1 // string
+#define TT_LITERAL 2 // literal
+#define TT_NUMBER 3 // number
+#define TT_NAME 4 // name
+#define TT_PUNCTUATION 5 // punctuation
+
+//string sub type
+//---------------
+// the length of the string
+//literal sub type
+//----------------
+// the ASCII code of the literal
+//number sub type
+//---------------
+#define TT_DECIMAL 0x0008 // decimal number
+#define TT_HEX 0x0100 // hexadecimal number
+#define TT_OCTAL 0x0200 // octal number
+#ifdef BINARYNUMBERS
+#define TT_BINARY 0x0400 // binary number
+#endif //BINARYNUMBERS
+#define TT_FLOAT 0x0800 // floating point number
+#define TT_INTEGER 0x1000 // integer number
+#define TT_LONG 0x2000 // long number
+#define TT_UNSIGNED 0x4000 // unsigned number
+//punctuation sub type
+//--------------------
+#define P_RSHIFT_ASSIGN 1
+#define P_LSHIFT_ASSIGN 2
+#define P_PARMS 3
+#define P_PRECOMPMERGE 4
+
+#define P_LOGIC_AND 5
+#define P_LOGIC_OR 6
+#define P_LOGIC_GEQ 7
+#define P_LOGIC_LEQ 8
+#define P_LOGIC_EQ 9
+#define P_LOGIC_UNEQ 10
+
+#define P_MUL_ASSIGN 11
+#define P_DIV_ASSIGN 12
+#define P_MOD_ASSIGN 13
+#define P_ADD_ASSIGN 14
+#define P_SUB_ASSIGN 15
+#define P_INC 16
+#define P_DEC 17
+
+#define P_BIN_AND_ASSIGN 18
+#define P_BIN_OR_ASSIGN 19
+#define P_BIN_XOR_ASSIGN 20
+#define P_RSHIFT 21
+#define P_LSHIFT 22
+
+#define P_POINTERREF 23
+#define P_CPP1 24
+#define P_CPP2 25
+#define P_MUL 26
+#define P_DIV 27
+#define P_MOD 28
+#define P_ADD 29
+#define P_SUB 30
+#define P_ASSIGN 31
+
+#define P_BIN_AND 32
+#define P_BIN_OR 33
+#define P_BIN_XOR 34
+#define P_BIN_NOT 35
+
+#define P_LOGIC_NOT 36
+#define P_LOGIC_GREATER 37
+#define P_LOGIC_LESS 38
+
+#define P_REF 39
+#define P_COMMA 40
+#define P_SEMICOLON 41
+#define P_COLON 42
+#define P_QUESTIONMARK 43
+
+#define P_PARENTHESESOPEN 44
+#define P_PARENTHESESCLOSE 45
+#define P_BRACEOPEN 46
+#define P_BRACECLOSE 47
+#define P_SQBRACKETOPEN 48
+#define P_SQBRACKETCLOSE 49
+#define P_BACKSLASH 50
+
+#define P_PRECOMP 51
+#define P_DOLLAR 52
+//name sub type
+//-------------
+// the length of the name
+
+//punctuation
+typedef struct punctuation_s
+{
+ char *p; //punctuation character(s)
+ int n; //punctuation indication
+ struct punctuation_s *next; //next punctuation
+} punctuation_t;
+
+//token
+typedef struct token_s
+{
+ char string[MAX_TOKEN]; //available token
+ int type; //last read token type
+ int subtype; //last read token sub type
+#ifdef NUMBERVALUE
+ unsigned long int intvalue; //integer value
+ double floatvalue; //floating point value
+#endif //NUMBERVALUE
+ char *whitespace_p; //start of white space before token
+ char *endwhitespace_p; //start of white space before token
+ int line; //line the token was on
+ int linescrossed; //lines crossed in white space
+ struct token_s *next; //next token in chain
+} token_t;
+
+//script file
+typedef struct script_s
+{
+ char filename[1024]; //file name of the script
+ char *buffer; //buffer containing the script
+ char *script_p; //current pointer in the script
+ char *end_p; //pointer to the end of the script
+ char *lastscript_p; //script pointer before reading token
+ char *whitespace_p; //begin of the white space
+ char *endwhitespace_p; //end of the white space
+ int length; //length of the script in bytes
+ int line; //current line in script
+ int lastline; //line before reading token
+ int tokenavailable; //set by UnreadLastToken
+ int flags; //several script flags
+ punctuation_t *punctuations; //the punctuations used in the script
+ punctuation_t **punctuationtable;
+ token_t token; //available token
+ struct script_s *next; //next script in a chain
+} script_t;
+
+//read a token from the script
+int PS_ReadToken(script_t *script, token_t *token);
+//expect a certain token
+int PS_ExpectTokenString(script_t *script, char *string);
+//expect a certain token type
+int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token);
+//expect a token
+int PS_ExpectAnyToken(script_t *script, token_t *token);
+//returns true when the token is available
+int PS_CheckTokenString(script_t *script, char *string);
+//returns true an reads the token when a token with the given type is available
+int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token);
+//skip tokens until the given token string is read
+int PS_SkipUntilString(script_t *script, char *string);
+//unread the last token read from the script
+void PS_UnreadLastToken(script_t *script);
+//unread the given token
+void PS_UnreadToken(script_t *script, token_t *token);
+//returns the next character of the read white space, returns NULL if none
+char PS_NextWhiteSpaceChar(script_t *script);
+//remove any leading and trailing double quotes from the token
+void StripDoubleQuotes(char *string);
+//remove any leading and trailing single quotes from the token
+void StripSingleQuotes(char *string);
+//read a possible signed integer
+signed long int ReadSignedInt(script_t *script);
+//read a possible signed floating point number
+double ReadSignedFloat(script_t *script);
+//set an array with punctuations, NULL restores default C/C++ set
+void SetScriptPunctuations(script_t *script, punctuation_t *p);
+//set script flags
+void SetScriptFlags(script_t *script, int flags);
+//get script flags
+int GetScriptFlags(script_t *script);
+//reset a script
+void ResetScript(script_t *script);
+//returns true if at the end of the script
+int EndOfScript(script_t *script);
+//returns a pointer to the punctuation with the given number
+char *PunctuationFromNum(script_t *script, int num);
+//load a script from the given file at the given offset with the given length
+script_t *LoadScriptFile(const char *filename);
+//load a script from the given memory with the given length
+script_t *LoadScriptMemory(char *ptr, int length, char *name);
+//free a script
+void FreeScript(script_t *script);
+//set the base folder to load files from
+void PS_SetBaseFolder(char *path);
+//print a script error with filename and line number
+void QDECL ScriptError(script_t *script, char *str, ...);
+//print a script warning with filename and line number
+void QDECL ScriptWarning(script_t *script, char *str, ...);
+
+
diff --git a/src/botlib/l_struct.c b/src/botlib/l_struct.c
new file mode 100644
index 00000000..303ecaa8
--- /dev/null
+++ b/src/botlib/l_struct.c
@@ -0,0 +1,462 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_struct.c
+ *
+ * desc: structure reading / writing
+ *
+ * $Archive: /MissionPack/CODE/botlib/l_struct.c $
+ *
+ *****************************************************************************/
+
+#ifdef BOTLIB
+#include "../qcommon/q_shared.h"
+#include "botlib.h" //for the include of be_interface.h
+#include "l_script.h"
+#include "l_precomp.h"
+#include "l_struct.h"
+#include "l_utils.h"
+#include "be_interface.h"
+#endif //BOTLIB
+
+#ifdef BSPC
+//include files for usage in the BSP Converter
+#include "../bspc/qbsp.h"
+#include "../bspc/l_log.h"
+#include "../bspc/l_mem.h"
+#include "l_precomp.h"
+#include "l_struct.h"
+
+#define qtrue true
+#define qfalse false
+#endif //BSPC
+
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+fielddef_t *FindField(fielddef_t *defs, char *name)
+{
+ int i;
+
+ for (i = 0; defs[i].name; i++)
+ {
+ if (!strcmp(defs[i].name, name)) return &defs[i];
+ } //end for
+ return NULL;
+} //end of the function FindField
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+qboolean ReadNumber(source_t *source, fielddef_t *fd, void *p)
+{
+ token_t token;
+ int negative = qfalse;
+ long int intval, intmin = 0, intmax = 0;
+ double floatval;
+
+ if (!PC_ExpectAnyToken(source, &token)) return 0;
+
+ //check for minus sign
+ if (token.type == TT_PUNCTUATION)
+ {
+ if (fd->type & FT_UNSIGNED)
+ {
+ SourceError(source, "expected unsigned value, found %s", token.string);
+ return 0;
+ } //end if
+ //if not a minus sign
+ if (strcmp(token.string, "-"))
+ {
+ SourceError(source, "unexpected punctuation %s", token.string);
+ return 0;
+ } //end if
+ negative = qtrue;
+ //read the number
+ if (!PC_ExpectAnyToken(source, &token)) return 0;
+ } //end if
+ //check if it is a number
+ if (token.type != TT_NUMBER)
+ {
+ SourceError(source, "expected number, found %s", token.string);
+ return 0;
+ } //end if
+ //check for a float value
+ if (token.subtype & TT_FLOAT)
+ {
+ if ((fd->type & FT_TYPE) != FT_FLOAT)
+ {
+ SourceError(source, "unexpected float");
+ return 0;
+ } //end if
+ floatval = token.floatvalue;
+ if (negative) floatval = -floatval;
+ if (fd->type & FT_BOUNDED)
+ {
+ if (floatval < fd->floatmin || floatval > fd->floatmax)
+ {
+ SourceError(source, "float out of range [%f, %f]", fd->floatmin, fd->floatmax);
+ return 0;
+ } //end if
+ } //end if
+ *(float *) p = (float) floatval;
+ return 1;
+ } //end if
+ //
+ intval = token.intvalue;
+ if (negative) intval = -intval;
+ //check bounds
+ if ((fd->type & FT_TYPE) == FT_CHAR)
+ {
+ if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 255;}
+ else {intmin = -128; intmax = 127;}
+ } //end if
+ if ((fd->type & FT_TYPE) == FT_INT)
+ {
+ if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 65535;}
+ else {intmin = -32768; intmax = 32767;}
+ } //end else if
+ if ((fd->type & FT_TYPE) == FT_CHAR || (fd->type & FT_TYPE) == FT_INT)
+ {
+ if (fd->type & FT_BOUNDED)
+ {
+ intmin = Maximum(intmin, fd->floatmin);
+ intmax = Minimum(intmax, fd->floatmax);
+ } //end if
+ if (intval < intmin || intval > intmax)
+ {
+ SourceError(source, "value %d out of range [%d, %d]", intval, intmin, intmax);
+ return 0;
+ } //end if
+ } //end if
+ else if ((fd->type & FT_TYPE) == FT_FLOAT)
+ {
+ if (fd->type & FT_BOUNDED)
+ {
+ if (intval < fd->floatmin || intval > fd->floatmax)
+ {
+ SourceError(source, "value %d out of range [%f, %f]", intval, fd->floatmin, fd->floatmax);
+ return 0;
+ } //end if
+ } //end if
+ } //end else if
+ //store the value
+ if ((fd->type & FT_TYPE) == FT_CHAR)
+ {
+ if (fd->type & FT_UNSIGNED) *(unsigned char *) p = (unsigned char) intval;
+ else *(char *) p = (char) intval;
+ } //end if
+ else if ((fd->type & FT_TYPE) == FT_INT)
+ {
+ if (fd->type & FT_UNSIGNED) *(unsigned int *) p = (unsigned int) intval;
+ else *(int *) p = (int) intval;
+ } //end else
+ else if ((fd->type & FT_TYPE) == FT_FLOAT)
+ {
+ *(float *) p = (float) intval;
+ } //end else
+ return 1;
+} //end of the function ReadNumber
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+qboolean ReadChar(source_t *source, fielddef_t *fd, void *p)
+{
+ token_t token;
+
+ if (!PC_ExpectAnyToken(source, &token)) return 0;
+
+ //take literals into account
+ if (token.type == TT_LITERAL)
+ {
+ StripSingleQuotes(token.string);
+ *(char *) p = token.string[0];
+ } //end if
+ else
+ {
+ PC_UnreadLastToken(source);
+ if (!ReadNumber(source, fd, p)) return 0;
+ } //end if
+ return 1;
+} //end of the function ReadChar
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int ReadString(source_t *source, fielddef_t *fd, void *p)
+{
+ token_t token;
+
+ if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) return 0;
+ //remove the double quotes
+ StripDoubleQuotes(token.string);
+ //copy the string
+ strncpy((char *) p, token.string, MAX_STRINGFIELD);
+ //make sure the string is closed with a zero
+ ((char *)p)[MAX_STRINGFIELD-1] = '\0';
+ //
+ return 1;
+} //end of the function ReadString
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int ReadStructure(source_t *source, structdef_t *def, char *structure)
+{
+ token_t token;
+ fielddef_t *fd;
+ void *p;
+ int num;
+
+ if (!PC_ExpectTokenString(source, "{")) return 0;
+ while(1)
+ {
+ if (!PC_ExpectAnyToken(source, &token)) return qfalse;
+ //if end of structure
+ if (!strcmp(token.string, "}")) break;
+ //find the field with the name
+ fd = FindField(def->fields, token.string);
+ if (!fd)
+ {
+ SourceError(source, "unknown structure field %s", token.string);
+ return qfalse;
+ } //end if
+ if (fd->type & FT_ARRAY)
+ {
+ num = fd->maxarray;
+ if (!PC_ExpectTokenString(source, "{")) return qfalse;
+ } //end if
+ else
+ {
+ num = 1;
+ } //end else
+ p = (void *)(structure + fd->offset);
+ while (num-- > 0)
+ {
+ if (fd->type & FT_ARRAY)
+ {
+ if (PC_CheckTokenString(source, "}")) break;
+ } //end if
+ switch(fd->type & FT_TYPE)
+ {
+ case FT_CHAR:
+ {
+ if (!ReadChar(source, fd, p)) return qfalse;
+ p = (char *) p + sizeof(char);
+ break;
+ } //end case
+ case FT_INT:
+ {
+ if (!ReadNumber(source, fd, p)) return qfalse;
+ p = (char *) p + sizeof(int);
+ break;
+ } //end case
+ case FT_FLOAT:
+ {
+ if (!ReadNumber(source, fd, p)) return qfalse;
+ p = (char *) p + sizeof(float);
+ break;
+ } //end case
+ case FT_STRING:
+ {
+ if (!ReadString(source, fd, p)) return qfalse;
+ p = (char *) p + MAX_STRINGFIELD;
+ break;
+ } //end case
+ case FT_STRUCT:
+ {
+ if (!fd->substruct)
+ {
+ SourceError(source, "BUG: no sub structure defined");
+ return qfalse;
+ } //end if
+ ReadStructure(source, fd->substruct, (char *) p);
+ p = (char *) p + fd->substruct->size;
+ break;
+ } //end case
+ } //end switch
+ if (fd->type & FT_ARRAY)
+ {
+ if (!PC_ExpectAnyToken(source, &token)) return qfalse;
+ if (!strcmp(token.string, "}")) break;
+ if (strcmp(token.string, ","))
+ {
+ SourceError(source, "expected a comma, found %s", token.string);
+ return qfalse;
+ } //end if
+ } //end if
+ } //end while
+ } //end while
+ return qtrue;
+} //end of the function ReadStructure
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int WriteIndent(FILE *fp, int indent)
+{
+ while(indent-- > 0)
+ {
+ if (fprintf(fp, "\t") < 0) return qfalse;
+ } //end while
+ return qtrue;
+} //end of the function WriteIndent
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int WriteFloat(FILE *fp, float value)
+{
+ char buf[128];
+ int l;
+
+ sprintf(buf, "%f", value);
+ l = strlen(buf);
+ //strip any trailing zeros
+ while(l-- > 1)
+ {
+ if (buf[l] != '0' && buf[l] != '.') break;
+ if (buf[l] == '.')
+ {
+ buf[l] = 0;
+ break;
+ } //end if
+ buf[l] = 0;
+ } //end while
+ //write the float to file
+ if (fprintf(fp, "%s", buf) < 0) return 0;
+ return 1;
+} //end of the function WriteFloat
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int WriteStructWithIndent(FILE *fp, structdef_t *def, char *structure, int indent)
+{
+ int i, num;
+ void *p;
+ fielddef_t *fd;
+
+ if (!WriteIndent(fp, indent)) return qfalse;
+ if (fprintf(fp, "{\r\n") < 0) return qfalse;
+
+ indent++;
+ for (i = 0; def->fields[i].name; i++)
+ {
+ fd = &def->fields[i];
+ if (!WriteIndent(fp, indent)) return qfalse;
+ if (fprintf(fp, "%s\t", fd->name) < 0) return qfalse;
+ p = (void *)(structure + fd->offset);
+ if (fd->type & FT_ARRAY)
+ {
+ num = fd->maxarray;
+ if (fprintf(fp, "{") < 0) return qfalse;
+ } //end if
+ else
+ {
+ num = 1;
+ } //end else
+ while(num-- > 0)
+ {
+ switch(fd->type & FT_TYPE)
+ {
+ case FT_CHAR:
+ {
+ if (fprintf(fp, "%d", *(char *) p) < 0) return qfalse;
+ p = (char *) p + sizeof(char);
+ break;
+ } //end case
+ case FT_INT:
+ {
+ if (fprintf(fp, "%d", *(int *) p) < 0) return qfalse;
+ p = (char *) p + sizeof(int);
+ break;
+ } //end case
+ case FT_FLOAT:
+ {
+ if (!WriteFloat(fp, *(float *)p)) return qfalse;
+ p = (char *) p + sizeof(float);
+ break;
+ } //end case
+ case FT_STRING:
+ {
+ if (fprintf(fp, "\"%s\"", (char *) p) < 0) return qfalse;
+ p = (char *) p + MAX_STRINGFIELD;
+ break;
+ } //end case
+ case FT_STRUCT:
+ {
+ if (!WriteStructWithIndent(fp, fd->substruct, structure, indent)) return qfalse;
+ p = (char *) p + fd->substruct->size;
+ break;
+ } //end case
+ } //end switch
+ if (fd->type & FT_ARRAY)
+ {
+ if (num > 0)
+ {
+ if (fprintf(fp, ",") < 0) return qfalse;
+ } //end if
+ else
+ {
+ if (fprintf(fp, "}") < 0) return qfalse;
+ } //end else
+ } //end if
+ } //end while
+ if (fprintf(fp, "\r\n") < 0) return qfalse;
+ } //end for
+ indent--;
+
+ if (!WriteIndent(fp, indent)) return qfalse;
+ if (fprintf(fp, "}\r\n") < 0) return qfalse;
+ return qtrue;
+} //end of the function WriteStructWithIndent
+//===========================================================================
+//
+// Parameter: -
+// Returns: -
+// Changes Globals: -
+//===========================================================================
+int WriteStructure(FILE *fp, structdef_t *def, char *structure)
+{
+ return WriteStructWithIndent(fp, def, structure, 0);
+} //end of the function WriteStructure
+
diff --git a/src/botlib/l_struct.h b/src/botlib/l_struct.h
new file mode 100644
index 00000000..e2c6b032
--- /dev/null
+++ b/src/botlib/l_struct.h
@@ -0,0 +1,75 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_struct.h
+ *
+ * desc: structure reading/writing
+ *
+ * $Archive: /source/code/botlib/l_struct.h $
+ *
+ *****************************************************************************/
+
+
+#define MAX_STRINGFIELD 80
+//field types
+#define FT_CHAR 1 // char
+#define FT_INT 2 // int
+#define FT_FLOAT 3 // float
+#define FT_STRING 4 // char [MAX_STRINGFIELD]
+#define FT_STRUCT 6 // struct (sub structure)
+//type only mask
+#define FT_TYPE 0x00FF // only type, clear subtype
+//sub types
+#define FT_ARRAY 0x0100 // array of type
+#define FT_BOUNDED 0x0200 // bounded value
+#define FT_UNSIGNED 0x0400
+
+//structure field definition
+typedef struct fielddef_s
+{
+ char *name; //name of the field
+ int offset; //offset in the structure
+ int type; //type of the field
+ //type specific fields
+ int maxarray; //maximum array size
+ float floatmin, floatmax; //float min and max
+ struct structdef_s *substruct; //sub structure
+} fielddef_t;
+
+//structure definition
+typedef struct structdef_s
+{
+ int size;
+ fielddef_t *fields;
+} structdef_t;
+
+//read a structure from a script
+int ReadStructure(source_t *source, structdef_t *def, char *structure);
+//write a structure to a file
+int WriteStructure(FILE *fp, structdef_t *def, char *structure);
+//writes indents
+int WriteIndent(FILE *fp, int indent);
+//writes a float without traling zeros
+int WriteFloat(FILE *fp, float value);
+
+
diff --git a/src/botlib/l_utils.h b/src/botlib/l_utils.h
new file mode 100644
index 00000000..6944d06f
--- /dev/null
+++ b/src/botlib/l_utils.h
@@ -0,0 +1,37 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*****************************************************************************
+ * name: l_util.h
+ *
+ * desc: utils
+ *
+ * $Archive: /source/code/botlib/l_util.h $
+ *
+ *****************************************************************************/
+
+#define Vector2Angles(v,a) vectoangles(v,a)
+#ifndef MAX_PATH
+#define MAX_PATH MAX_QPATH
+#endif
+#define Maximum(x,y) (x > y ? x : y)
+#define Minimum(x,y) (x < y ? x : y)