/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.  
COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/
/*
 * $Source: f:/miner/source/main/rcs/ai.c $
 * $Revision: 2.11 $
 * $Author: john $
 * $Date: 1995/07/09 11:15:48 $
 *
 * Autonomous Individual movement.
 *
 * $Log: ai.c $
 * Revision 2.11  1995/07/09  11:15:48  john
 * Put in Mike's code to fix bug where bosses don't gate in bots after
 * 32767 seconds of playing.
 * 
 * Revision 2.10  1995/06/15  12:31:08  john
 * Fixed bug with cheats getting enabled when you type
 * the whole alphabet.
 * 
 * Revision 2.9  1995/05/26  16:16:18  john
 * Split SATURN into define's for requiring cd, using cd, etc.
 * Also started adding all the Rockwell stuff.
 * 
 * Revision 2.8  1995/04/06  15:12:27  john
 * Fixed bug with insane not working.
 * 
 * Revision 2.7  1995/03/30  16:36:44  mike
 * text localization.
 * 
 * Revision 2.6  1995/03/28  11:22:24  john
 * Added cheats to save file. Changed lunacy text.
 * 
 * Revision 2.5  1995/03/27  16:45:07  john
 * Fixed some cheat bugs.  Added astral cheat.
 * 
 * Revision 2.4  1995/03/24  15:29:17  mike
 * add new cheats.
 * 
 * Revision 2.3  1995/03/21  14:39:45  john
 * Ifdef'd out the NETWORK code.
 * 
 * Revision 2.2  1995/03/14  18:24:39  john
 * Force Destination Saturn to use CD-ROM drive.
 * 
 * Revision 2.1  1995/03/06  16:47:14  mike
 * destination saturn
 * 
 * Revision 2.0  1995/02/27  11:30:01  john
 * New version 2.0, which has no anonymous unions, builds with
 * Watcom 10.0, and doesn't require parsing BITMAPS.TBL.
 * 
 * Revision 1.295  1995/02/22  13:23:04  allender
 * remove anonymous unions from object structure
 * 
 * Revision 1.294  1995/02/13  11:00:43  rob
 * Make brain guys high enough to get an open slot.
 * 
 * Revision 1.293  1995/02/13  10:31:55  mike
 * Make brains understand they can't open locked doors.
 * 
 * Revision 1.292  1995/02/13  10:18:01  rob
 * Reduced brain guy's level of awareness to keep him from hogging slots.
 * 
 * Revision 1.291  1995/02/11  12:27:12  mike
 * fix path-to-exit cheat.
 * 
 * Revision 1.290  1995/02/11  01:56:30  mike
 * robots don't fire cheat.
 * 
 * Revision 1.289  1995/02/10  17:15:09  rob
 * Fixed some stuff with 64 awareness stuff.
 * 
 * Revision 1.288  1995/02/10  16:31:32  mike
 * oops.
 * 
 * Revision 1.287  1995/02/10  16:24:45  mike
 * fix the network follow path fix.
 * 
 * Revision 1.286  1995/02/10  16:11:40  mike
 * in serial or modem games, follow path guys don't move if far away and
 * can't see player.
 * 
 * Revision 1.285  1995/02/09  13:11:35  mike
 * comment out a bunch of mprintfs.
 * add toaster (drops prox bombs, runs away) to boss gate list.
 * 
 * Revision 1.284  1995/02/08  22:44:53  rob
 * Lowerd anger level for follow path of any sort.
 * 
 * Revision 1.283  1995/02/08  22:30:43  mike
 * lower awareness on station guys if they are returning home (multiplayer).
 * 
 * Revision 1.282  1995/02/08  17:01:06  rob
 * Fixed problem with toasters dropping of proximity bombs.
 * 
 * Revision 1.281  1995/02/08  11:49:35  rob
 * Reduce Green-guy attack awareness level so we don't let him attack us too.
 * 
 * Revision 1.280  1995/02/08  11:37:52  mike
 * Check for failures in call to obj_create.
 * 
 * Revision 1.279  1995/02/07  20:38:46  mike
 * fix toasters in multiplayer
 * 
 * 
 * Revision 1.278  1995/02/07  16:51:07  mike
 * fix sound time play bug.
 * 
 * Revision 1.277  1995/02/06  22:33:04  mike
 * make robots follow path better in cooperative/roboarchy.
 * 
 * Revision 1.276  1995/02/06  18:15:42  rob
 * Added forced sends for evasion movemnet.
 * 
 * Revision 1.275  1995/02/06  16:41:22  rob
 * Change some positioning calls.
 * 
 * Revision 1.274  1995/02/06  11:40:33  mike
 * replace some lint-related hacks with clean, proper code.
 * 
 * Revision 1.273  1995/02/04  17:28:19  mike
 * make station guys return better.
 * 
 * Revision 1.272  1995/02/03  17:40:55  mike
 * fix problem with robots falling asleep if you sit in game overnight, not in pause...bah.
 * 
 * Revision 1.271  1995/02/02  21:11:25  rob
 * Tweaking stuff for multiplayer ai.
 * 
 * Revision 1.270  1995/02/02  17:32:06  john
 * Added Hack for Assert that Mike put in after using Lint to find
 * uninitialized variables.
 * 
 * Revision 1.269  1995/02/02  16:46:31  mike
 * fix boss gating.
 * 
 * Revision 1.268  1995/02/02  16:27:29  mike
 * make boss not put out infinite robots.
 * 
 * Revision 1.267  1995/02/01  21:10:02  mike
 * lint found bug! player_visibility not initialized!
 * 
 * Revision 1.266  1995/02/01  20:51:27  john
 * Lintized
 * 
 * Revision 1.265  1995/02/01  17:14:05  mike
 * fix robot sounds.
 * 
 * Revision 1.264  1995/01/31  16:16:40  mike
 * Comment out "Darn you, John" Int3().
 * 
 * Revision 1.263  1995/01/30  20:55:04  mike
 * fix nonsense in robot firing when a player is cloaked.
 * 
 * Revision 1.262  1995/01/30  17:15:10  rob
 * Fixed problems with bigboss eclip messages.
 * Tweaked robot position sending for modem purposes.
 * 
 * Revision 1.261  1995/01/30  15:30:31  rob
 * Prevent non-master players from gating in robots.
 * 
 * Revision 1.260  1995/01/30  13:30:55  mike
 * new cases for firing at other players were bogus, could send position
 * without permission.
 * 
 * Revision 1.259  1995/01/30  13:01:17  mike
 * Make robots fire at player other than one they are controlled by sometimes.
 * 
 * Revision 1.258  1995/01/29  16:09:17  rob
 * Trying to get robots to shoot at non-controlling players.
 * 
 * Revision 1.257  1995/01/29  13:47:05  mike
 * Make boss have more fireballs on death, have until end (though silent at end).
 * Fix bug which was preventing him from teleporting until hit, so he'd always
 * be in the same place when the player enters the room.
 * 
 * Revision 1.256  1995/01/28  17:40:18  mike
 * make boss teleport & gate before you see him.
 * 
 * Revision 1.255  1995/01/27  17:02:08  mike
 * move code around, was sending one frame (or worse!) old robot information.
 * 
 * Revision 1.254  1995/01/26  17:02:43  mike
 * make fusion cannon have more chrome, make fusion, mega rock you!
 * 
 * Revision 1.253  1995/01/26  15:11:17  rob
 * Shutup!  I fixed it!
 * 
 * Revision 1.252  1995/01/26  15:08:55  rob
 * Changed robot gating to accomodate multiplayer.
 * 
 * Revision 1.251  1995/01/26  14:49:04  rob
 * Increase awareness level for firing to 94.
 * 
 * Revision 1.250  1995/01/26  12:41:20  mike
 * fix bogus multiplayer code, would send permission without getting permission.
 * 
 * Revision 1.249  1995/01/26  12:23:23  rob
 * Removed defines that were moved to ai.h
 * 
 * Revision 1.248  1995/01/25  23:38:48  mike
 * modify list of robots gated in by super boss.
 * 
 * Revision 1.247  1995/01/25  21:21:13  rob
 * Trying to let robots fire at a player even if they're not in control.
 * 
 * Revision 1.246  1995/01/25  13:50:37  mike
 * Robots make angry sounds.
 * 
 * Revision 1.245  1995/01/25  10:53:47  mike
 * better handling of robots which poke out of mine and try to recover.
 * 
 * Revision 1.244  1995/01/24  22:03:02  mike
 * Tricky code to move a robot to a legal position if he is poking out of
 * the mine, even if it means moving him to another segment.
 * 
 * Revision 1.243  1995/01/24  20:12:06  rob
 * Changed robot fire awareness level from 74 to 94.
 * 
 * Revision 1.242  1995/01/24  13:22:32  mike
 * make robots accelerate faster, and Difficulty_level dependent.
 * 
 * Revision 1.241  1995/01/24  12:09:39  mike
 * make robots animate in multiplayer.
 * 
 * Revision 1.240  1995/01/21  21:21:10  mike
 * Make boss only gate robots into specified segments.
 * 
 * Revision 1.239  1995/01/20  20:21:26  mike
 * prevent unnecessary boss cloaking.
 * 
 */

#pragma off (unreferenced)
static char rcsid[] = "$Id: ai.c 2.11 1995/07/09 11:15:48 john Exp $";
#pragma on (unreferenced)

#include <stdio.h>
#include <stdlib.h>

#include "inferno.h"
#include "game.h"
#include "mono.h"
#include "3d.h"

#include "object.h"
#include "render.h"
#include "error.h"
#include "ai.h"
#include "laser.h"
#include "fvi.h"
#include "polyobj.h"
#include "bm.h"
#include "weapon.h"
#include "physics.h"
#include "collide.h"
#include "fuelcen.h"
#include "player.h"
#include "wall.h"
#include "vclip.h"
#include "digi.h"
#include "fireball.h"
#include "morph.h"
#include "effects.h"
#include "timer.h"
#include "sounds.h"
#include "cntrlcen.h"
#include "multibot.h"
#include "multi.h"
#include "network.h"
#include "gameseq.h"
#include "key.h"
#include "powerup.h"
#include "gauges.h"
#include "text.h"

#ifdef EDITOR
#include "editor\editor.h"
#endif

#ifndef NDEBUG
#include "string.h"
#include <time.h>
#endif

#define	JOHN_CHEATS_SIZE_1	6
#define	JOHN_CHEATS_SIZE_2	6
#define	JOHN_CHEATS_SIZE_3	6

ubyte	john_cheats_1[JOHN_CHEATS_SIZE_1] = { 	KEY_P ^ 0x00 ^ 0x34, 
															KEY_O ^ 0x10 ^ 0x34, 
															KEY_B ^ 0x20 ^ 0x34, 
															KEY_O ^ 0x30 ^ 0x34, 
															KEY_Y ^ 0x40 ^ 0x34, 
															KEY_S ^ 0x50 ^ 0x34 };

#define	PARALLAX	0		//	If !0, then special debugging info for Parallax eyes only enabled.

#define MIN_D 0x100

int	Flinch_scale = 4;
int	john_cheats_index_1;		//	POBOYS		detonate reactor
int	Attack_scale = 24;
#define	ANIM_RATE		(F1_0/16)
#define	DELTA_ANG_SCALE	16

byte Mike_to_matt_xlate[] = {AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST};
int	john_cheats_index_2;		//	PORGYS		high speed weapon firing

// int	No_ai_flag=0;

#define	OVERALL_AGITATION_MAX	100

#define		MAX_AI_CLOAK_INFO	8	//	Must be a power of 2!

typedef struct {
	fix			last_time;
	vms_vector	last_position;
} ai_cloak_info;

#define	BOSS_CLOAK_DURATION	(F1_0*7)
#define	BOSS_DEATH_DURATION	(F1_0*6)
#define	BOSS_DEATH_SOUND_DURATION	0x2ae14		//	2.68 seconds

//	Amount of time since the current robot was last processed for things such as movement.
//	It is not valid to use FrameTime because robots do not get moved every frame.
//fix	AI_proc_time;

int	Num_boss_teleport_segs;
short	Boss_teleport_segs[MAX_BOSS_TELEPORT_SEGS];
#ifndef SHAREWARE
int	Num_boss_gate_segs;
short	Boss_gate_segs[MAX_BOSS_TELEPORT_SEGS];
#endif

int	john_cheats_index_3;		//	LUNACY		lunacy (insane behavior, rookie firing)

//	---------- John: These variables must be saved as part of gamesave. ----------
int				Ai_initialized = 0;
int				Overall_agitation;
ai_local			Ai_local_info[MAX_OBJECTS];
point_seg		Point_segs[MAX_POINT_SEGS];
point_seg		*Point_segs_free_ptr = Point_segs;
ai_cloak_info	Ai_cloak_info[MAX_AI_CLOAK_INFO];
fix				Boss_cloak_start_time = 0;
fix				Boss_cloak_end_time = 0;
fix				Last_teleport_time = 0;
fix				Boss_teleport_interval = F1_0*8;
fix				Boss_cloak_interval = F1_0*10;					//	Time between cloaks
fix				Boss_cloak_duration = BOSS_CLOAK_DURATION;
fix				Last_gate_time = 0;
fix				Gate_interval = F1_0*6;
fix				Boss_dying_start_time;
int				Boss_dying, Boss_dying_sound_playing, Boss_hit_this_frame;
int				Boss_been_hit=0;


//	---------- John: End of variables which must be saved as part of gamesave. ----------

int	john_cheats_index_4;		//	PLETCHnnn	paint robots
int				ai_evaded=0;

#ifndef SHAREWARE
//	0	mech
//	1	green claw
//	2	spider
//	3	josh
//	4	violet
//	5	cloak vulcan
//	6	cloak mech
//	7	brain
//	8	onearm
//	9	plasma
//	10	toaster
//	11	bird
//	12	missile bird
//	13	polyhedron
//	14	baby spider
//	15	mini boss
//	16	super mech
//	17	shareware boss
//	18	cloak-green	; note, gating in this guy benefits player, cloak objects
//	19	vulcan
//	20	toad
//	21	4-claw
//	22	quad-laser
// 23 super boss

// byte	Super_boss_gate_list[] = {0, 1, 2, 9, 11, 16, 18, 19, 21, 22, 0, 9, 9, 16, 16, 18, 19, 19, 22, 22};
byte	Super_boss_gate_list[] = {0, 1, 8, 9, 10, 11, 12, 15, 16, 18, 19, 20, 22, 0, 8, 11, 19, 20, 8, 20, 8};
#define	MAX_GATE_INDEX	( sizeof(Super_boss_gate_list) / sizeof(Super_boss_gate_list[0]) )
#endif

int	Ai_info_enabled=0;
int	Robot_firing_enabled = 1;

extern	int	Ugly_robot_cheat, Ugly_robot_texture, Laser_rapid_fire;
extern	byte	Enable_john_cheat_1, Enable_john_cheat_2, Enable_john_cheat_3, Enable_john_cheat_4;

ubyte	john_cheats_3[2*JOHN_CHEATS_SIZE_3+1] = { KEY_Y ^ 0x67, 
																KEY_E ^ 0x66, 
																KEY_C ^ 0x65, 
																KEY_A ^ 0x64, 
																KEY_N ^ 0x63, 
																KEY_U ^ 0x62, 
																KEY_L ^ 0x61 };


#define	MAX_AWARENESS_EVENTS	64
typedef struct awareness_event {
	short 		segnum;				// segment the event occurred in
	short			type;					// type of event, defines behavior
	vms_vector	pos;					// absolute 3 space location of event
} awareness_event;


//	These globals are set by a call to find_vector_intersection, which is a slow routine,
//	so we don't want to call it again (for this object) unless we have to.
vms_vector	Hit_pos;
int			Hit_type, Hit_seg;
fvi_info		Hit_data;

int					Num_awareness_events = 0;
awareness_event	Awareness_events[MAX_AWARENESS_EVENTS];

vms_vector		Believed_player_pos;

#define	AIS_MAX	8
#define	AIE_MAX	4

//--unused-- int	Processed_this_frame, LastFrameCount;
#ifndef NDEBUG
//	Index into this array with ailp->mode
char	mode_text[8][9] = {
	"STILL   ",
	"WANDER  ",
	"FOL_PATH",
	"CHASE_OB",
	"RUN_FROM",
	"HIDE    ",
	"FOL_PAT2",
	"OPENDOR2"
};

//	Index into this array with aip->behavior
char	behavior_text[6][9] = {
	"STILL   ",
	"NORMAL  ",
	"HIDE    ",
	"RUN_FROM",
	"FOLPATH ",
	"STATION "
};

//	Index into this array with aip->GOAL_STATE or aip->CURRENT_STATE
char	state_text[8][5] = {
	"NONE",
	"REST",
	"SRCH",
	"LOCK",
	"FLIN",
	"FIRE",
	"RECO",
	"ERR_",
};


int	Ai_animation_test=0;
#endif

// Current state indicates where the robot current is, or has just done.
//	Transition table between states for an AI object.
//	 First dimension is trigger event.
//	Second dimension is current state.
//	 Third dimension is goal state.
//	Result is new goal state.
//	ERR_ means something impossible has happened.
byte Ai_transition_table[AI_MAX_EVENT][AI_MAX_STATE][AI_MAX_STATE] = {
	{
	//	Event = AIE_FIRE, a nearby object fired
	//	none			rest			srch			lock			flin			fire			reco				// CURRENT is rows, GOAL is columns
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},		//	none
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},		//	rest
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},		//	search
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},		//	lock
	{	AIS_ERR_,	AIS_REST,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FIRE,	AIS_RECO},		//	flinch
	{	AIS_ERR_,	AIS_FIRE,	AIS_FIRE,	AIS_FIRE,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},		//	fire
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_FIRE}		//	recoil
	},

	//	Event = AIE_HITT, a nearby object was hit (or a wall was hit)
	{
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_REST,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_FIRE}
	},

	//	Event = AIE_COLL, player collided with robot
	{
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_LOCK,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_REST,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FIRE,	AIS_RECO},
	{	AIS_ERR_,	AIS_LOCK,	AIS_LOCK,	AIS_LOCK,	AIS_FLIN,	AIS_FIRE,	AIS_FIRE}
	},

	//	Event = AIE_HURT, player hurt robot (by firing at and hitting it)
	//	Note, this doesn't necessarily mean the robot JUST got hit, only that that is the most recent thing that happened.
	{
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN},
	{	AIS_ERR_,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN,	AIS_FLIN}
	}
};

ubyte	john_cheats_2[2*JOHN_CHEATS_SIZE_2] = { 	KEY_P ^ 0x00 ^ 0x43, 0x66, 
																KEY_O ^ 0x10 ^ 0x43, 0x11, 
																KEY_R ^ 0x20 ^ 0x43, 0x8, 
																KEY_G ^ 0x30 ^ 0x43, 0x2, 
																KEY_Y ^ 0x40 ^ 0x43, 0x0, 
																KEY_S ^ 0x50 ^ 0x43 };

// ---------------------------------------------------------
//	On entry, N_robot_types had darn sure better be set.
//	Mallocs N_robot_types robot_info structs into global Robot_info.
void init_ai_system(void)
{
#if 0
	int	i;

	mprintf((0, "Trying to malloc %i bytes for Robot_info.\n", N_robot_types * sizeof(*Robot_info)));
	Robot_info = (robot_info *) malloc( N_robot_types * sizeof(*Robot_info) );
	mprintf((0, "Robot_info = %i\n", Robot_info));

	for (i=0; i<N_robot_types; i++) {
		Robot_info[i].field_of_view = F1_0/2;
		Robot_info[i].firing_wait = F1_0;
		Robot_info[i].turn_time = F1_0*2;
		Robot_info[i].fire_power = F1_0;
		Robot_info[i].shield = F1_0/2;
		Robot_info[i].max_speed = F1_0*10;
		Robot_info[i].always_0xabcd = 0xabcd;
	}
#endif

}

void john_cheat_func_1(int key)
{
	if (!Cheats_enabled)
		return;

	if (key == (john_cheats_1[john_cheats_index_1] ^ (john_cheats_index_1 << 4) ^ 0x34)) {
		john_cheats_index_1++;
		if (john_cheats_index_1 == JOHN_CHEATS_SIZE_1)	{
			do_controlcen_destroyed_stuff(NULL);
			john_cheats_index_1 = 0;
			digi_play_sample( SOUND_CHEATER, F1_0);
		}
	} else
		john_cheats_index_1 = 0;
}

// ---------------------------------------------------------------------------------------------------------------------
//	Given a behavior, set initial mode.
int ai_behavior_to_mode(int behavior)
{
	switch (behavior) {
		case AIB_STILL:			return AIM_STILL;
		case AIB_NORMAL:			return AIM_CHASE_OBJECT;
		case AIB_HIDE:				return AIM_HIDE;
		case AIB_RUN_FROM:		return AIM_RUN_FROM_OBJECT;
		case AIB_FOLLOW_PATH:	return AIM_FOLLOW_PATH;
		case AIB_STATION:			return AIM_STILL;
		default:	Int3();	//	Contact Mike: Error, illegal behavior type
	}

	return AIM_STILL;
}

// ---------------------------------------------------------------------------------------------------------------------
//	Call every time the player starts a new ship.
void ai_init_boss_for_ship(void)
{
	Boss_been_hit = 0;
}

// ---------------------------------------------------------------------------------------------------------------------
//	initial_mode == -1 means leave mode unchanged.
void init_ai_object(int objnum, int behavior, int hide_segment)
{
	object	*objp = &Objects[objnum];
	ai_static	*aip = &objp->ctype.ai_info;
	ai_local		*ailp = &Ai_local_info[objnum];

#ifdef DEST_SAT
		if (!(Game_mode & GM_MULTI) && Robot_info[objp->id].boss_flag) {
			mprintf((0, "Current_level_num = %i, Last_level = %i\n", Current_level_num, Last_level));
			if (Current_level_num != Last_level) {
				mprintf((0, "Removing boss, object num = %i\n", objnum));
				objp->id = 0;
				objp->flags |= OF_SHOULD_BE_DEAD;
			}
		}
#endif

	if (behavior == 0) {
		// mprintf((0, "Behavior of 0 for object #%i, bashing to AIB_NORMAL.\n", objnum));
		behavior = AIB_NORMAL;
		objp->ctype.ai_info.behavior = behavior;
	}
	// mprintf((0, "Initializing object #%i\n", objnum));

	//	mode is now set from the Robot dialog, so this should get overwritten.
	ailp->mode = AIM_STILL;

	ailp->previous_visibility = 0;

	if (behavior != -1) {
		aip->behavior = behavior;
		ailp->mode = ai_behavior_to_mode(aip->behavior);
	} else if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) {
		mprintf((0, "[obj %i -> normal] ", objnum));
		aip->behavior = AIB_NORMAL;
	}

	// This is astonishingly stupid!  This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;

	vm_vec_zero(&objp->mtype.phys_info.velocity);
	// -- ailp->wait_time = F1_0*5;
	ailp->player_awareness_time = 0;
	ailp->player_awareness_type = 0;
	aip->GOAL_STATE = AIS_SRCH;
	aip->CURRENT_STATE = AIS_REST;
	ailp->time_player_seen = GameTime;
	ailp->next_misc_sound_time = GameTime;
	ailp->time_player_sound_attacked = GameTime;

	if ((behavior == AIB_HIDE) || (behavior == AIB_FOLLOW_PATH) || (behavior == AIB_STATION) || (behavior == AIB_RUN_FROM)) {
		aip->hide_segment = hide_segment;
		ailp->goal_segment = hide_segment;
		aip->hide_index = -1;			// This means the path has not yet been created.
		aip->cur_path_index = 0;
	}

	aip->SKIP_AI_COUNT = 0;

	if (Robot_info[objp->id].cloak_type == RI_CLOAKED_ALWAYS)
		aip->CLOAKED = 1;
	else
		aip->CLOAKED = 0;

	objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL);
	
	aip->REMOTE_OWNER = -1;
}

void john_cheat_func_2(int key)
{
	if (!Cheats_enabled)
		return;

	if (key == (john_cheats_2[2*john_cheats_index_2] ^ (john_cheats_index_2 << 4) ^ 0x43)) {
		john_cheats_index_2++;
		if (john_cheats_index_2 == JOHN_CHEATS_SIZE_2) {
			Laser_rapid_fire = 0xBADA55;
			do_megawow_powerup(200);
			john_cheats_index_2 = 0;
			digi_play_sample( SOUND_CHEATER, F1_0);
		}
	} else
		john_cheats_index_2 = 0;
}

// ---------------------------------------------------------------------------------------------------------------------
void init_ai_objects(void)
{
	int	i;

	Point_segs_free_ptr = Point_segs;

	for (i=0; i<MAX_OBJECTS; i++) {
		object *objp = &Objects[i];

		if (objp->control_type == CT_AI)
			init_ai_object(i, objp->ctype.ai_info.behavior, objp->ctype.ai_info.hide_segment);
	}

	init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1);

	#ifndef SHAREWARE
		init_boss_segments(Boss_gate_segs, &Num_boss_gate_segs, 0);
	#endif

	Boss_dying_sound_playing = 0;
	Boss_dying = 0;
	Boss_been_hit = 0;
	#ifndef SHAREWARE
	Gate_interval = F1_0*5 - Difficulty_level*F1_0/2;
	#endif

	Ai_initialized = 1;
}

int	Lunacy = 0;
int	Diff_save = 1;

fix	Firing_wait_copy[MAX_ROBOT_TYPES];
byte	Rapidfire_count_copy[MAX_ROBOT_TYPES];

void do_lunacy_on(void)
{
	int	i;

	if ( !Lunacy )	{
		Lunacy = 1;
		Diff_save = Difficulty_level;
		Difficulty_level = NDL-1;

		for (i=0; i<MAX_ROBOT_TYPES; i++) {
			Firing_wait_copy[i] = Robot_info[i].firing_wait[NDL-1];
			Rapidfire_count_copy[i] = Robot_info[i].rapidfire_count[NDL-1];
	
			Robot_info[i].firing_wait[NDL-1] = Robot_info[i].firing_wait[1];
			Robot_info[i].rapidfire_count[NDL-1] = Robot_info[i].rapidfire_count[1];
		}
	}
}

void do_lunacy_off(void)
{
	int	i;

	if ( Lunacy )	{
		Lunacy = 0;
		for (i=0; i<MAX_ROBOT_TYPES; i++) {
			Robot_info[i].firing_wait[NDL-1] = Firing_wait_copy[i];
			Robot_info[i].rapidfire_count[NDL-1] = Rapidfire_count_copy[i];
		}
		Difficulty_level = Diff_save;
	}
}

void john_cheat_func_3(int key)
{
	if (!Cheats_enabled)
		return;

	if (key == (john_cheats_3[JOHN_CHEATS_SIZE_3 - john_cheats_index_3] ^ (0x61 + john_cheats_index_3))) {
		if (john_cheats_index_3 == 4)
			john_cheats_index_3++;
		john_cheats_index_3++;
		if (john_cheats_index_3 == JOHN_CHEATS_SIZE_3+1) {
			if (Lunacy) {
				do_lunacy_off();
				HUD_init_message( TXT_NO_LUNACY );
			} else {
				do_lunacy_on();
				HUD_init_message( TXT_LUNACY );
				digi_play_sample( SOUND_CHEATER, F1_0);
			}
			john_cheats_index_3 = 0;
		}
	} else
		john_cheats_index_3 = 0;
}

//	----------------------------------------------------------------
//	Do *dest = *delta unless:
//				*delta is pretty small
//		and	they are of different signs.
void set_rotvel_and_saturate(fix *dest, fix delta)
{
	if ((delta ^ *dest) < 0) {
		if (abs(delta) < F1_0/8) {
			// mprintf((0, "D"));
			*dest = delta/4;
		} else
			// mprintf((0, "d"));
			*dest = delta;
	} else {
		// mprintf((0, "!"));
		*dest = delta;
	}
}

//--debug-- #ifndef NDEBUG
//--debug-- int	Total_turns=0;
//--debug-- int	Prevented_turns=0;
//--debug-- #endif

#define	AI_TURN_SCALE	1
#define	BABY_SPIDER_ID	14

extern void physics_turn_towards_vector(vms_vector *goal_vector, object *obj, fix rate);

//-------------------------------------------------------------------------------------------
void ai_turn_towards_vector(vms_vector *goal_vector, object *objp, fix rate)
{
	vms_vector	new_fvec;
	fix			dot;

	if ((objp->id == BABY_SPIDER_ID) && (objp->type == OBJ_ROBOT)) {
		physics_turn_towards_vector(goal_vector, objp, rate);
		return;
	}

	new_fvec = *goal_vector;

	dot = vm_vec_dot(goal_vector, &objp->orient.fvec);

	if (dot < (F1_0 - FrameTime/2)) {
		fix	mag;
		fix	new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate);
		vm_vec_scale(&new_fvec, new_scale);
		vm_vec_add2(&new_fvec, &objp->orient.fvec);
		mag = vm_vec_normalize_quick(&new_fvec);
		if (mag < F1_0/256) {
			mprintf((1, "Degenerate vector in ai_turn_towards_vector (mag = %7.3f)\n", f2fl(mag)));
			new_fvec = *goal_vector;		//	if degenerate vector, go right to goal
		}
	}

//	//	Every 8th time, do a correct matrix create, 7/8 time, do a quick one.
//	if (rand() < 0x1000)
		vm_vector_2_matrix(&objp->orient, &new_fvec, NULL, &objp->orient.rvec);
//	else
//		vm_vector_2_matrix_norm(&objp->orient, &new_fvec, NULL, &objp->orient.rvec);

//--{
//--vms_vector tvec;
//--fix mag;
//--tvec = objp->orient.fvec;
//--mag = vm_vec_mag(&tvec);
//--mprintf((0, "mags = %7.3f ", f2fl(mag)));
//--
//--tvec = objp->orient.uvec;
//--mag = vm_vec_mag(&tvec);
//--mprintf((0, "%7.3f ", f2fl(mag)));
//--
//--tvec = objp->orient.rvec;
//--mag = vm_vec_mag(&tvec);
//--mprintf((0, "%7.3f\n", f2fl(mag)));
//--}
//--simpler, but buggy:	//	The cross product of the forward vector with the right vector is the up vector
//--simpler, but buggy:	vm_vec_cross(&new_uvec, &new_fvec, &objp->orient.rvec);
//--simpler, but buggy:	vm_vec_cross(&new_rvec, &new_uvec, &new_fvec);
//--simpler, but buggy:
//--simpler, but buggy:	objp->orient.fvec = new_fvec;
//--simpler, but buggy:	objp->orient.rvec = new_rvec;
//--simpler, but buggy:	objp->orient.uvec = new_uvec;
}

// --------------------------------------------------------------------------------------------------------------------
void ai_turn_randomly(vms_vector *vec_to_player, object *obj, fix rate, int previous_visibility)
{
	vms_vector	curvec;

	//	Random turning looks too stupid, so 1/4 of time, cheat.
	if (previous_visibility)
		if (rand() > 0x7400) {
			ai_turn_towards_vector(vec_to_player, obj, rate);
			return;
		}
//--debug-- 	if (rand() > 0x6000)
//--debug-- 		Prevented_turns++;

	curvec = obj->mtype.phys_info.rotvel;

	curvec.y += F1_0/64;

	curvec.x += curvec.y/6;
	curvec.y += curvec.z/4;
	curvec.z += curvec.x/10;

	if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
	if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
	if (abs(curvec.z) > F1_0/8) curvec.z /= 4;

	obj->mtype.phys_info.rotvel = curvec;

}

//	Overall_agitation affects:
//		Widens field of view.  Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
//			Overall_agitation/128 subtracted from field of view, making robots see wider.
//		Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
//		Decreases wait between fire times by Overall_agitation/64 seconds.

void john_cheat_func_4(int key)
{
	if (!Cheats_enabled)
		return;

	switch (john_cheats_index_4) {
		case 3:
			if (key == KEY_T)
				john_cheats_index_4++;
			else
				john_cheats_index_4 = 0;
			break;

		case 1:
			if (key == KEY_L)
				john_cheats_index_4++;
			else
				john_cheats_index_4 = 0;
			break;
	
		case 2:
			if (key == KEY_E)
				john_cheats_index_4++;
			else
				john_cheats_index_4 = 0;
			break;
	
		case 0:
			if (key == KEY_P)
				john_cheats_index_4++;
			break;


		case 4:
			if (key == KEY_C)
				john_cheats_index_4++;
			else
				john_cheats_index_4 = 0;
			break;
	
		case 5:
			if (key == KEY_H)
				john_cheats_index_4++;
			else
				john_cheats_index_4 = 0;
			break;
	
		case 6:
			Ugly_robot_texture = 0;
		case 7:
		case 8:
			if ((key >= KEY_1) && (key <= KEY_0)) {
				john_cheats_index_4++;
				Ugly_robot_texture *= 10;
				if (key != KEY_0)
					Ugly_robot_texture += key - 1;
				if (john_cheats_index_4 == 9) {
					if (Ugly_robot_texture == 999)	{
						Ugly_robot_cheat = 0;
						HUD_init_message( TXT_ROBOT_PAINTING_OFF );
					} else {
						HUD_init_message( TXT_ROBOT_PAINTING_ON, Ugly_robot_texture );
						Ugly_robot_cheat = 0xBADA55;
					}
					mprintf((0, "Paint value = %i\n", Ugly_robot_texture));
					john_cheats_index_4 = 0;
				}
			} else
				john_cheats_index_4 = 0;
		
			break;
		default:
			john_cheats_index_4 = 0;
	}
}

// --------------------------------------------------------------------------------------------------------------------
//	Returns:
//		0		Player is not visible from object, obstruction or something.
//		1		Player is visible, but not in field of view.
//		2		Player is visible and in field of view.
//	Note: Uses Believed_player_pos as player's position for cloak effect.
//	NOTE: Will destructively modify *pos if *pos is outside the mine.
int player_is_visible_from_object(object *objp, vms_vector *pos, fix field_of_view, vms_vector *vec_to_player)
{
	fix			dot;
	fvi_query	fq;

	fq.p0						= pos;
	if ((pos->x != objp->pos.x) || (pos->y != objp->pos.y) || (pos->z != objp->pos.z)) {
		int	segnum = find_point_seg(pos, objp->segnum);
		if (segnum == -1) {
			fq.startseg = objp->segnum;
			*pos = objp->pos;
			mprintf((1, "Object %i, gun is outside mine, moving towards center.\n", objp-Objects));
			move_towards_segment_center(objp);
		} else
			fq.startseg = segnum;
	} else
		fq.startseg			= objp->segnum;
	fq.p1						= &Believed_player_pos;
	fq.rad					= F1_0/4;
	fq.thisobjnum			= objp-Objects;
	fq.ignore_obj_list	= NULL;
	fq.flags					= FQ_TRANSWALL | FQ_CHECK_OBJS;		//what about trans walls???

	Hit_type = find_vector_intersection(&fq,&Hit_data);

	Hit_pos = Hit_data.hit_pnt;
	Hit_seg = Hit_data.hit_seg;

	if ((Hit_type == HIT_NONE) || ((Hit_type == HIT_OBJECT) && (Hit_data.hit_object == Players[Player_num].objnum))) {
		dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
		// mprintf((0, "Fvec = [%5.2f %5.2f %5.2f], vec_to_player = [%5.2f %5.2f %5.2f], dot = %7.3f\n", f2fl(objp->orient.fvec.x), f2fl(objp->orient.fvec.y), f2fl(objp->orient.fvec.z), f2fl(vec_to_player->x), f2fl(vec_to_player->y), f2fl(vec_to_player->z), f2fl(dot)));
		if (dot > field_of_view - (Overall_agitation << 9)) {
			// mprintf((0, "I can see you!\n"));
			return 2;
		} else {
			// mprintf((0, "Damn, I could see you if I were looking...\n"));
			return 1;
		}
	} else {
		// mprintf((0, " ** Where are you? **\n"));
		return 0;
	}
}

// ------------------------------------------------------------------------------------------------------------------
//	Return 1 if animates, else return 0
int do_silly_animation(object *objp)
{
	int				objnum = objp-Objects;
	jointpos 		*jp_list;
	int				robot_type, gun_num, robot_state, num_joint_positions;
	polyobj_info	*pobj_info = &objp->rtype.pobj_info;
	ai_static		*aip = &objp->ctype.ai_info;
	// ai_local			*ailp = &Ai_local_info[objnum];
	int				num_guns, at_goal;
	int				attack_type;
	int				flinch_attack_scale = 1;

	robot_type = objp->id;
	num_guns = Robot_info[robot_type].n_guns;
	attack_type = Robot_info[robot_type].attack_type;

	if (num_guns == 0) {
		// mprintf((0, "Object #%i of type #%i has 0 guns.\n", objp-Objects, robot_type));
		return 0;
	}

	//	This is a hack.  All positions should be based on goal_state, not GOAL_STATE.
	robot_state = Mike_to_matt_xlate[aip->GOAL_STATE];
	// previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];

	if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
		flinch_attack_scale = Attack_scale;
	else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL))
		flinch_attack_scale = Flinch_scale;

	at_goal = 1;
	for (gun_num=0; gun_num <= num_guns; gun_num++) {
		int	joint;

		num_joint_positions = robot_get_anim_state(&jp_list, robot_type, gun_num, robot_state);

		for (joint=0; joint<num_joint_positions; joint++) {
			fix			delta_angle, delta_2;
			int			jointnum = jp_list[joint].jointnum;
			vms_angvec	*jp = &jp_list[joint].angles;
			vms_angvec	*pobjp = &pobj_info->anim_angles[jointnum];

			if (jointnum >= Polygon_models[objp->rtype.pobj_info.model_num].n_models) {
				Int3();		// Contact Mike: incompatible data, illegal jointnum, problem in pof file?
				continue;
			}
			if (jp->p != pobjp->p) {
				if (gun_num == 0)
					at_goal = 0;
				Ai_local_info[objnum].goal_angles[jointnum].p = jp->p;

				delta_angle = jp->p - pobjp->p;
				if (delta_angle >= F1_0/2)
					delta_2 = -ANIM_RATE;
				else if (delta_angle >= 0)
					delta_2 = ANIM_RATE;
				else if (delta_angle >= -F1_0/2)
					delta_2 = -ANIM_RATE;
				else
					delta_2 = ANIM_RATE;

				if (flinch_attack_scale != 1)
					delta_2 *= flinch_attack_scale;

				Ai_local_info[objnum].delta_angles[jointnum].p = delta_2/DELTA_ANG_SCALE;		// complete revolutions per second
			}

			if (jp->b != pobjp->b) {
				if (gun_num == 0)
					at_goal = 0;
				Ai_local_info[objnum].goal_angles[jointnum].b = jp->b;

				delta_angle = jp->b - pobjp->b;
				if (delta_angle >= F1_0/2)
					delta_2 = -ANIM_RATE;
				else if (delta_angle >= 0)
					delta_2 = ANIM_RATE;
				else if (delta_angle >= -F1_0/2)
					delta_2 = -ANIM_RATE;
				else
					delta_2 = ANIM_RATE;

				if (flinch_attack_scale != 1)
					delta_2 *= flinch_attack_scale;

				Ai_local_info[objnum].delta_angles[jointnum].b = delta_2/DELTA_ANG_SCALE;		// complete revolutions per second
			}

			if (jp->h != pobjp->h) {
				if (gun_num == 0)
					at_goal = 0;
				Ai_local_info[objnum].goal_angles[jointnum].h = jp->h;

				delta_angle = jp->h - pobjp->h;
				if (delta_angle >= F1_0/2)
					delta_2 = -ANIM_RATE;
				else if (delta_angle >= 0)
					delta_2 = ANIM_RATE;
				else if (delta_angle >= -F1_0/2)
					delta_2 = -ANIM_RATE;
				else
					delta_2 = ANIM_RATE;

				if (flinch_attack_scale != 1)
					delta_2 *= flinch_attack_scale;

				Ai_local_info[objnum].delta_angles[jointnum].h = delta_2/DELTA_ANG_SCALE;		// complete revolutions per second
			}
		}

		if (at_goal) {
			//ai_static	*aip = &objp->ctype.ai_info;
			ai_local		*ailp = &Ai_local_info[objp-Objects];
			ailp->achieved_state[gun_num] = ailp->goal_state[gun_num];
			if (ailp->achieved_state[gun_num] == AIS_RECO)
				ailp->goal_state[gun_num] = AIS_FIRE;

			if (ailp->achieved_state[gun_num] == AIS_FLIN)
				ailp->goal_state[gun_num] = AIS_LOCK;

		}
	}

	if (at_goal == 1) //num_guns)
		aip->CURRENT_STATE = aip->GOAL_STATE;

	return 1;
}

//	------------------------------------------------------------------------------------------
//	Move all sub-objects in an object towards their goals.
//	Current orientation of object is at:	pobj_info.anim_angles
//	Goal orientation of object is at:		ai_info.goal_angles
//	Delta orientation of object is at:		ai_info.delta_angles
void ai_frame_animation(object *objp)
{
	int	objnum = objp-Objects;
	int	joint;
	int	num_joints;

	num_joints = Polygon_models[objp->rtype.pobj_info.model_num].n_models;

	for (joint=1; joint<num_joints; joint++) {
		fix			delta_to_goal;
		fix			scaled_delta_angle;
		vms_angvec	*curangp = &objp->rtype.pobj_info.anim_angles[joint];
		vms_angvec	*goalangp = &Ai_local_info[objnum].goal_angles[joint];
		vms_angvec	*deltaangp = &Ai_local_info[objnum].delta_angles[joint];

#ifndef NDEBUG
if (Ai_animation_test) {
	printf("%i: [%7.3f %7.3f %7.3f]  [%7.3f %7.3f %7.3f]\n", joint, f2fl(curangp->p), f2fl(curangp->b), f2fl(curangp->h), f2fl(goalangp->p), f2fl(goalangp->b), f2fl(goalangp->h), f2fl(curangp->p), f2fl(curangp->b), f2fl(curangp->h));
}
#endif
		delta_to_goal = goalangp->p - curangp->p;
		if (delta_to_goal > 32767)
			delta_to_goal = delta_to_goal - 65536;
		else if (delta_to_goal < -32767)
			delta_to_goal = 65536 + delta_to_goal;

		if (delta_to_goal) {
			scaled_delta_angle = fixmul(deltaangp->p, FrameTime) * DELTA_ANG_SCALE;
			curangp->p += scaled_delta_angle;
			if (abs(delta_to_goal) < abs(scaled_delta_angle))
				curangp->p = goalangp->p;
		}

		delta_to_goal = goalangp->b - curangp->b;
		if (delta_to_goal > 32767)
			delta_to_goal = delta_to_goal - 65536;
		else if (delta_to_goal < -32767)
			delta_to_goal = 65536 + delta_to_goal;

		if (delta_to_goal) {
			scaled_delta_angle = fixmul(deltaangp->b, FrameTime) * DELTA_ANG_SCALE;
			curangp->b += scaled_delta_angle;
			if (abs(delta_to_goal) < abs(scaled_delta_angle))
				curangp->b = goalangp->b;
		}

		delta_to_goal = goalangp->h - curangp->h;
		if (delta_to_goal > 32767)
			delta_to_goal = delta_to_goal - 65536;
		else if (delta_to_goal < -32767)
			delta_to_goal = 65536 + delta_to_goal;

		if (delta_to_goal) {
			scaled_delta_angle = fixmul(deltaangp->h, FrameTime) * DELTA_ANG_SCALE;
			curangp->h += scaled_delta_angle;
			if (abs(delta_to_goal) < abs(scaled_delta_angle))
				curangp->h = goalangp->h;
		}

	}

}

// ----------------------------------------------------------------------------------
void set_next_fire_time(ai_local *ailp, robot_info *robptr)
{
	ailp->rapidfire_count++;

	if (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level]) {
		ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
	} else {
		ailp->rapidfire_count = 0;
		ailp->next_fire = robptr->firing_wait[Difficulty_level];
	}
}

// ----------------------------------------------------------------------------------
//	When some robots collide with the player, they attack.
//	If player is cloaked, then robot probably didn't actually collide, deal with that here.
void do_ai_robot_hit_attack(object *robot, object *player, vms_vector *collision_point)
{
	ai_local		*ailp = &Ai_local_info[robot-Objects];
	robot_info *robptr = &Robot_info[robot->id];

//#ifndef NDEBUG
	if (!Robot_firing_enabled)
		return;
//#endif

	//	If player is dead, stop firing.
	if (Objects[Players[Player_num].objnum].type == OBJ_GHOST)
		return;

	if (robptr->attack_type == 1) {
		if (ailp->next_fire <= 0) {
			if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED))
				if (vm_vec_dist_quick(&ConsoleObject->pos, &robot->pos) < robot->size + ConsoleObject->size + F1_0*2)
					collide_player_and_nasty_robot( player, robot, collision_point );

			robot->ctype.ai_info.GOAL_STATE = AIS_RECO;
			set_next_fire_time(ailp, robptr);
		}
	}

}

extern int Player_exploded;

// --------------------------------------------------------------------------------------------------------------------
//	Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
//	center of the robot will not fire right at the player.  We need to aim the guns at the player.  Barring that, we cheat.
//	When this routine is complete, the parameter vec_to_player should not be necessary.
void ai_fire_laser_at_player(object *obj, vms_vector *fire_point)
{
	int			objnum = obj-Objects;
	ai_local		*ailp = &Ai_local_info[objnum];
	robot_info	*robptr = &Robot_info[obj->id];
	vms_vector	fire_vec;
	vms_vector	bpp_diff;

	if (!Robot_firing_enabled)
		return;

#ifndef NDEBUG
	//	We should never be coming here for the green guy, as he has no laser!
	if (robptr->attack_type == 1)
		Int3();	// Contact Mike: This is impossible.
#endif

	if (obj->control_type == CT_MORPH)
		return;

	//	If player is exploded, stop firing.
	if (Player_exploded)
		return;

	//	If player is cloaked, maybe don't fire based on how long cloaked and randomness.
	if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
		fix	cloak_time = Ai_cloak_info[objnum % MAX_AI_CLOAK_INFO].last_time;

		if (GameTime - cloak_time > CLOAK_TIME_MAX/4)
			if (rand() > fixdiv(GameTime - cloak_time, CLOAK_TIME_MAX)/2) {
				set_next_fire_time(ailp, robptr);
				return;
			}
	}

//--	//	Find segment containing laser fire position.  If the robot is straddling a segment, the position from
//--	//	which it fires may be in a different segment, which is bad news for find_vector_intersection.  So, cast
//--	//	a ray from the object center (whose segment we know) to the laser position.  Then, in the call to Laser_create_new
//--	//	use the data returned from this call to find_vector_intersection.
//--	//	Note that while find_vector_intersection is pretty slow, it is not terribly slow if the destination point is
//--	//	in the same segment as the source point.
//--
//--	fq.p0						= &obj->pos;
//--	fq.startseg				= obj->segnum;
//--	fq.p1						= fire_point;
//--	fq.rad					= 0;
//--	fq.thisobjnum			= obj-Objects;
//--	fq.ignore_obj_list	= NULL;
//--	fq.flags					= FQ_TRANSWALL | FQ_CHECK_OBJS;		//what about trans walls???
//--
//--	fate = find_vector_intersection(&fq, &hit_data);
//--	if (fate != HIT_NONE)
//--		return;

	//	Set position to fire at based on difficulty level.
	bpp_diff.x = Believed_player_pos.x + (rand()-16384) * (NDL-Difficulty_level-1) * 4;
	bpp_diff.y = Believed_player_pos.y + (rand()-16384) * (NDL-Difficulty_level-1) * 4;
	bpp_diff.z = Believed_player_pos.z + (rand()-16384) * (NDL-Difficulty_level-1) * 4;

	//	Half the time fire at the player, half the time lead the player.
	if (rand() > 16384) {

		vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point);

	} else {
		vms_vector	player_direction_vector;

		vm_vec_sub(&player_direction_vector, &bpp_diff, &bpp_diff);

		// If player is not moving, fire right at him!
		//	Note: If the robot fires in the direction of its forward vector, this is bad because the weapon does not
		//	come out from the center of the robot; it comes out from the side.  So it is common for the weapon to miss
		//	its target.  Ideally, we want to point the guns at the player.  For now, just fire right at the player.
		if ((abs(player_direction_vector.x < 0x10000)) && (abs(player_direction_vector.y < 0x10000)) && (abs(player_direction_vector.z < 0x10000))) {

			vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point);

		// Player is moving.  Determine where the player will be at the end of the next frame if he doesn't change his
		//	behavior.  Fire at exactly that point.  This isn't exactly what you want because it will probably take the laser
		//	a different amount of time to get there, since it will probably be a different distance from the player.
		//	So, that's why we write games, instead of guiding missiles...
		} else {
			vm_vec_sub(&fire_vec, &bpp_diff, fire_point);
			vm_vec_scale(&fire_vec,fixmul(Weapon_info[Robot_info[obj->id].weapon_type].speed[Difficulty_level], FrameTime));

			vm_vec_add2(&fire_vec, &player_direction_vector);
			vm_vec_normalize_quick(&fire_vec);

		}
	}

//#ifndef NDEBUG
//	if (robptr->boss_flag)
//		mprintf((0, "Boss (%i) fires!\n", obj-Objects));
//#endif

	Laser_create_new_easy( &fire_vec, fire_point, obj-Objects, robptr->weapon_type, 1);

#ifndef SHAREWARE
#ifdef NETWORK
	if (Game_mode & GM_MULTI)
	{
		ai_multi_send_robot_position(objnum, -1);
		multi_send_robot_fire(objnum, obj->ctype.ai_info.CURRENT_GUN, &fire_vec);
	}
#endif
#endif

	create_awareness_event(obj, PA_NEARBY_ROBOT_FIRED);

	set_next_fire_time(ailp, robptr);

	//	If the boss fired, allow him to teleport very soon (right after firing, cool!), pending other factors.
	if (robptr->boss_flag)
		Last_teleport_time -= Boss_teleport_interval/2;
}

// --------------------------------------------------------------------------------------------------------------------
//	vec_goal must be normalized, or close to it.
void move_towards_vector(object *objp, vms_vector *vec_goal)
{
	physics_info	*pptr = &objp->mtype.phys_info;
	fix				speed, dot, max_speed;
	robot_info		*robptr = &Robot_info[objp->id];
	vms_vector		vel;

	//	Trying to move towards player.  If forward vector much different than velocity vector,
	//	bash velocity vector twice as much towards player as usual.

	vel = pptr->velocity;
	vm_vec_normalize_quick(&vel);
	dot = vm_vec_dot(&vel, &objp->orient.fvec);

	if (dot < 3*F1_0/4) {
		//	This funny code is supposed to slow down the robot and move his velocity towards his direction
		//	more quickly than the general code
		//-! mprintf((0, "Th  "));
		pptr->velocity.x = pptr->velocity.x/2 + fixmul(vec_goal->x, FrameTime*32);
		pptr->velocity.y = pptr->velocity.y/2 + fixmul(vec_goal->y, FrameTime*32);
		pptr->velocity.z = pptr->velocity.z/2 + fixmul(vec_goal->z, FrameTime*32);
	} else {
		//-! mprintf((0, "Tn  "));
		pptr->velocity.x += fixmul(vec_goal->x, FrameTime*64) * (Difficulty_level+5)/4;
		pptr->velocity.y += fixmul(vec_goal->y, FrameTime*64) * (Difficulty_level+5)/4;
		pptr->velocity.z += fixmul(vec_goal->z, FrameTime*64) * (Difficulty_level+5)/4;
	}

	speed = vm_vec_mag_quick(&pptr->velocity);
	max_speed = robptr->max_speed[Difficulty_level];

	//	Green guy attacks twice as fast as he moves away.
	if (robptr->attack_type == 1)
		max_speed *= 2;

	if (speed > max_speed) {
		pptr->velocity.x = (pptr->velocity.x*3)/4;
		pptr->velocity.y = (pptr->velocity.y*3)/4;
		pptr->velocity.z = (pptr->velocity.z*3)/4;
	}
}

// --------------------------------------------------------------------------------------------------------------------
void move_towards_player(object *objp, vms_vector *vec_to_player)
//	vec_to_player must be normalized, or close to it.
{
	move_towards_vector(objp, vec_to_player);
}

// --------------------------------------------------------------------------------------------------------------------
//	I am ashamed of this: fast_flag == -1 means normal slide about.  fast_flag = 0 means no evasion.
void move_around_player(object *objp, vms_vector *vec_to_player, int fast_flag)
{
	physics_info	*pptr = &objp->mtype.phys_info;
	fix				speed;
	robot_info		*robptr = &Robot_info[objp->id];
	int				objnum = objp-Objects;
	int				dir;
	int				dir_change;
	fix				ft;
	vms_vector		evade_vector;
	int				count=0;

	if (fast_flag == 0)
		return;

	dir_change = 48;
	ft = FrameTime;
	if (ft < F1_0/32) {
		dir_change *= 8;
		count += 3;
	} else
		while (ft < F1_0/4) {
			dir_change *= 2;
			ft *= 2;
			count++;
		}

	dir = (FrameCount + (count+1) * (objnum*8 + objnum*4 + objnum)) & dir_change;
	dir >>= (4+count);

	Assert((dir >= 0) && (dir <= 3));

	switch (dir) {
		case 0:
			evade_vector.x = fixmul(vec_to_player->z, FrameTime*32);
			evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
			evade_vector.z = fixmul(-vec_to_player->x, FrameTime*32);
			break;
		case 1:
			evade_vector.x = fixmul(-vec_to_player->z, FrameTime*32);
			evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
			evade_vector.z = fixmul(vec_to_player->x, FrameTime*32);
			break;
		case 2:
			evade_vector.x = fixmul(-vec_to_player->y, FrameTime*32);
			evade_vector.y = fixmul(vec_to_player->x, FrameTime*32);
			evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
			break;
		case 3:
			evade_vector.x = fixmul(vec_to_player->y, FrameTime*32);
			evade_vector.y = fixmul(-vec_to_player->x, FrameTime*32);
			evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
			break;
	}

	//	Note: -1 means normal circling about the player.  > 0 means fast evasion.
	if (fast_flag > 0) {
		fix	dot;

		//	Only take evasive action if looking at player.
		//	Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.

		dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
		if ((dot > robptr->field_of_view[Difficulty_level]) && !(ConsoleObject->flags & PLAYER_FLAGS_CLOAKED)) {
			fix	damage_scale;

			damage_scale = fixdiv(objp->shields, robptr->strength);
			if (damage_scale > F1_0)
				damage_scale = F1_0;		//	Just in case...
			else if (damage_scale < 0)
				damage_scale = 0;			//	Just in case...

			vm_vec_scale(&evade_vector, i2f(fast_flag) + damage_scale);
		}
	}

	pptr->velocity.x += evade_vector.x;
	pptr->velocity.y += evade_vector.y;
	pptr->velocity.z += evade_vector.z;

	speed = vm_vec_mag_quick(&pptr->velocity);
	if (speed > robptr->max_speed[Difficulty_level]) {
		pptr->velocity.x = (pptr->velocity.x*3)/4;
		pptr->velocity.y = (pptr->velocity.y*3)/4;
		pptr->velocity.z = (pptr->velocity.z*3)/4;
	}

}

// --------------------------------------------------------------------------------------------------------------------
void move_away_from_player(object *objp, vms_vector *vec_to_player, int attack_type)
{
	fix				speed;
	physics_info	*pptr = &objp->mtype.phys_info;
	robot_info		*robptr = &Robot_info[objp->id];
	int				objref;

	pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16);
	pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16);
	pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16);

	if (attack_type) {
		//	Get value in 0..3 to choose evasion direction.
		objref = ((objp-Objects) ^ ((FrameCount + 3*(objp-Objects)) >> 5)) & 3;

		switch (objref) {
			case 0:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, FrameTime << 5);	break;
			case 1:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, -FrameTime << 5);	break;
			case 2:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, FrameTime << 5);	break;
			case 3:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, -FrameTime << 5);	break;
			default:	Int3();	//	Impossible, bogus value on objref, must be in 0..3
		}
	}


	speed = vm_vec_mag_quick(&pptr->velocity);

	if (speed > robptr->max_speed[Difficulty_level]) {
		pptr->velocity.x = (pptr->velocity.x*3)/4;
		pptr->velocity.y = (pptr->velocity.y*3)/4;
		pptr->velocity.z = (pptr->velocity.z*3)/4;
	}

//--old--	fix				speed, dot;
//--old--	physics_info	*pptr = &objp->mtype.phys_info;
//--old--	robot_info		*robptr = &Robot_info[objp->id];
//--old--
//--old--	//	Trying to move away from player.  If forward vector much different than velocity vector,
//--old--	//	bash velocity vector twice as much away from player as usual.
//--old--	dot = vm_vec_dot(&pptr->velocity, &objp->orient.fvec);
//--old--	if (dot > -3*F1_0/4) {
//--old--		//	This funny code is supposed to slow down the robot and move his velocity towards his direction
//--old--		//	more quickly than the general code
//--old--		pptr->velocity.x = pptr->velocity.x/2 - fixmul(vec_to_player->x, FrameTime*16);
//--old--		pptr->velocity.y = pptr->velocity.y/2 - fixmul(vec_to_player->y, FrameTime*16);
//--old--		pptr->velocity.z = pptr->velocity.z/2 - fixmul(vec_to_player->z, FrameTime*16);
//--old--	} else {
//--old--		pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16);
//--old--		pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16);
//--old--		pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16);
//--old--	}
//--old--
//--old--	speed = vm_vec_mag_quick(&pptr->velocity);
//--old--
//--old--	if (speed > robptr->max_speed[Difficulty_level]) {
//--old--		pptr->velocity.x = (pptr->velocity.x*3)/4;
//--old--		pptr->velocity.y = (pptr->velocity.y*3)/4;
//--old--		pptr->velocity.z = (pptr->velocity.z*3)/4;
//--old--	}
}

// --------------------------------------------------------------------------------------------------------------------
//	Move towards, away_from or around player.
//	Also deals with evasion.
//	If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
void ai_move_relative_to_player(object *objp, ai_local *ailp, fix dist_to_player, vms_vector *vec_to_player, fix circle_distance, int evade_only)
{
	object		*dobjp;
	robot_info	*robptr = &Robot_info[objp->id];

	//	See if should take avoidance.

// New way, green guys don't evade:	if ((robptr->attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1)) {
	if (objp->ctype.ai_info.danger_laser_num != -1) {
		dobjp = &Objects[objp->ctype.ai_info.danger_laser_num];

		if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) {
			fix			dot, dist_to_laser, field_of_view;
			vms_vector	vec_to_laser, laser_fvec;

			field_of_view = Robot_info[objp->id].field_of_view[Difficulty_level];

			vm_vec_sub(&vec_to_laser, &dobjp->pos, &objp->pos);
			dist_to_laser = vm_vec_normalize_quick(&vec_to_laser);
			dot = vm_vec_dot(&vec_to_laser, &objp->orient.fvec);

			if (dot > field_of_view) {
				fix			laser_robot_dot;
				vms_vector	laser_vec_to_robot;

				//	The laser is seen by the robot, see if it might hit the robot.
				//	Get the laser's direction.  If it's a polyobj, it can be gotten cheaply from the orientation matrix.
				if (dobjp->render_type == RT_POLYOBJ)
					laser_fvec = dobjp->orient.fvec;
				else {		//	Not a polyobj, get velocity and normalize.
					laser_fvec = dobjp->mtype.phys_info.velocity;	//dobjp->orient.fvec;
					vm_vec_normalize_quick(&laser_fvec);
				}
				vm_vec_sub(&laser_vec_to_robot, &objp->pos, &dobjp->pos);
				vm_vec_normalize_quick(&laser_vec_to_robot);
				laser_robot_dot = vm_vec_dot(&laser_fvec, &laser_vec_to_robot);

				if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) {
					int	evade_speed;

					ai_evaded = 1;
					evade_speed = Robot_info[objp->id].evade_speed[Difficulty_level];

					move_around_player(objp, vec_to_player, evade_speed);
				}
			}
			return;
		}
	}

	//	If only allowed to do evade code, then done.
	//	Hmm, perhaps brilliant insight.  If want claw-type guys to keep coming, don't return here after evasion.
	if ((!robptr->attack_type) && evade_only)
		return;

	//	If we fall out of above, then no object to be avoided.
	objp->ctype.ai_info.danger_laser_num = -1;

	//	Green guy selects move around/towards/away based on firing time, not distance.
	if (robptr->attack_type == 1) {
		if (((ailp->next_fire > robptr->firing_wait[Difficulty_level]/4) && (dist_to_player < F1_0*30)) || Player_is_dead) {
			//	1/4 of time, move around player, 3/4 of time, move away from player
			if (rand() < 8192) {
				move_around_player(objp, vec_to_player, -1);
			} else {
				move_away_from_player(objp, vec_to_player, 1);
			}
		} else {
			move_towards_player(objp, vec_to_player);
		}
	} else {
		if (dist_to_player < circle_distance)
			move_away_from_player(objp, vec_to_player, 0);
		else if (dist_to_player < circle_distance*2)
			move_around_player(objp, vec_to_player, -1);
		else
			move_towards_player(objp, vec_to_player);
	}

}

// --------------------------------------------------------------------------------------------------------------------
//	Compute a somewhat random, normalized vector.
void make_random_vector(vms_vector *vec)
{
	vec->x = (rand() - 16384) | 1;	// make sure we don't create null vector
	vec->y = rand() - 16384;
	vec->z = rand() - 16384;

	vm_vec_normalize_quick(vec);
}

#ifndef NDEBUG
void mprintf_animation_info(object *objp)
{
	ai_static	*aip = &objp->ctype.ai_info;
	ai_local		*ailp = &Ai_local_info[objp-Objects];

	if (!Ai_info_enabled)
		return;

	mprintf((0, "Goal = "));

	switch (aip->GOAL_STATE) {
		case AIS_NONE:	mprintf((0, "NONE "));	break;
		case AIS_REST:	mprintf((0, "REST "));	break;
		case AIS_SRCH:	mprintf((0, "SRCH "));	break;
		case AIS_LOCK:	mprintf((0, "LOCK "));	break;
		case AIS_FLIN:	mprintf((0, "FLIN "));	break;
		case AIS_FIRE:	mprintf((0, "FIRE "));	break;
		case AIS_RECO:	mprintf((0, "RECO "));	break;
		case AIS_ERR_:	mprintf((0, "ERR_ "));	break;
	}

	mprintf((0, " Cur = "));

	switch (aip->CURRENT_STATE) {
		case AIS_NONE:	mprintf((0, "NONE "));	break;
		case AIS_REST:	mprintf((0, "REST "));	break;
		case AIS_SRCH:	mprintf((0, "SRCH "));	break;
		case AIS_LOCK:	mprintf((0, "LOCK "));	break;
		case AIS_FLIN:	mprintf((0, "FLIN "));	break;
		case AIS_FIRE:	mprintf((0, "FIRE "));	break;
		case AIS_RECO:	mprintf((0, "RECO "));	break;
		case AIS_ERR_:	mprintf((0, "ERR_ "));	break;
	}

	mprintf((0, " Aware = "));

	switch (ailp->player_awareness_type) {
		case AIE_FIRE: mprintf((0, "FIRE ")); break;
		case AIE_HITT: mprintf((0, "HITT ")); break;
		case AIE_COLL: mprintf((0, "COLL ")); break;
		case AIE_HURT: mprintf((0, "HURT ")); break;
	}

	mprintf((0, "Next fire = %6.3f, Time = %6.3f\n", f2fl(ailp->next_fire), f2fl(ailp->player_awareness_time)));

}
#endif

//	-------------------------------------------------------------------------------------------------------------------
int	Break_on_object = -1;

void do_firing_stuff(object *obj, int player_visibility, vms_vector *vec_to_player)
{
//mprintf((0, "!"));
	if (player_visibility >= 1) {
		//	Now, if in robot's field of view, lock onto player
		fix	dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
//mprintf((0, "dot = %8x ", dot));
		if ((dot >= 7*F1_0/8) || (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) {
			ai_static	*aip = &obj->ctype.ai_info;
			ai_local		*ailp = &Ai_local_info[obj-Objects];

			switch (aip->GOAL_STATE) {
				case AIS_NONE:
				case AIS_REST:
				case AIS_SRCH:
				case AIS_LOCK:
					aip->GOAL_STATE = AIS_FIRE;
					if (ailp->player_awareness_type <= PA_NEARBY_ROBOT_FIRED) {
						ailp->player_awareness_type = PA_NEARBY_ROBOT_FIRED;
						ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
					}
					break;
			}
		} else if (dot >= F1_0/2) {
			ai_static	*aip = &obj->ctype.ai_info;
			switch (aip->GOAL_STATE) {
				case AIS_NONE:
				case AIS_REST:
				case AIS_SRCH:
					aip->GOAL_STATE = AIS_LOCK;
					break;
			}
		}
	}
}

// --------------------------------------------------------------------------------------------------------------------
//	If a hiding robot gets bumped or hit, he decides to find another hiding place.
void do_ai_robot_hit(object *objp, int type)
{
	if (objp->control_type == CT_AI) {
		if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_PLAYER_COLLISION))
			switch (objp->ctype.ai_info.behavior) {
				case AIM_HIDE:
					objp->ctype.ai_info.SUBMODE = AISM_GOHIDE;
					break;
				case AIM_STILL:
					Ai_local_info[objp-Objects].mode = AIM_CHASE_OBJECT;
					break;
			}
	}

}
#ifndef NDEBUG
int	Do_ai_flag=1;
int	Cvv_test=0;
int	Cvv_last_time[MAX_OBJECTS];
int	Gun_point_hack=0;
#endif

#define	CHASE_TIME_LENGTH		(F1_0*8)
#define	DEFAULT_ROBOT_SOUND_VOLUME		F1_0
int		Robot_sound_volume=DEFAULT_ROBOT_SOUND_VOLUME;

// --------------------------------------------------------------------------------------------------------------------
//	Note: This function could be optimized.  Surely player_is_visible_from_object would benefit from the
//	information of a normalized vec_to_player.
//	Return player visibility:
//		0		not visible
//		1		visible, but robot not looking at player (ie, on an unobstructed vector)
//		2		visible and in robot's field of view
//		-1		player is cloaked
//	If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
//	Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
//	and is copied to player_visibility
void compute_vis_and_vec(object *objp, vms_vector *pos, ai_local *ailp, vms_vector *vec_to_player, int *player_visibility, robot_info *robptr, int *flag)
{
	if (!*flag) {
		if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
			fix			delta_time, dist;
			int			cloak_index = (objp-Objects) % MAX_AI_CLOAK_INFO;

			delta_time = GameTime - Ai_cloak_info[cloak_index].last_time;
			if (delta_time > F1_0*2) {
				vms_vector	randvec;

				Ai_cloak_info[cloak_index].last_time = GameTime;
				make_random_vector(&randvec);
				vm_vec_scale_add2(&Ai_cloak_info[cloak_index].last_position, &randvec, 8*delta_time );
			}

			dist = vm_vec_normalized_dir_quick(vec_to_player, &Ai_cloak_info[cloak_index].last_position, pos);
			*player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
			// *player_visibility = 2;

			if ((ailp->next_misc_sound_time < GameTime) && (ailp->next_fire < F1_0) && (dist < F1_0*20)) {
				mprintf((0, "ANGRY! "));
				ailp->next_misc_sound_time = GameTime + (rand() + F1_0) * (7 - Difficulty_level) / 1;
				digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
			}
		} else {
			//	Compute expensive stuff -- vec_to_player and player_visibility
			vm_vec_normalized_dir_quick(vec_to_player, &Believed_player_pos, pos);
			if ((vec_to_player->x == 0) && (vec_to_player->y == 0) && (vec_to_player->z == 0)) {
				mprintf((0, "Warning: Player and robot at exactly the same location.\n"));
				vec_to_player->x = F1_0;
			}
			*player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);

			//	This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
			//	see you without killing frame rate.
			{
				ai_static	*aip = &objp->ctype.ai_info;
			if ((*player_visibility == 2) && (ailp->previous_visibility != 2))
				if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) {
					aip->GOAL_STATE = AIS_FIRE;
					aip->CURRENT_STATE = AIS_FIRE;
				}
			}

			if (!Player_exploded && (ailp->previous_visibility != *player_visibility) && (*player_visibility == 2)) {
				if (ailp->previous_visibility == 0) {
					if (ailp->time_player_seen + F1_0/2 < GameTime) {
						// mprintf((0, "SEE! "));
						digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
						ailp->time_player_sound_attacked = GameTime;
						ailp->next_misc_sound_time = GameTime + F1_0 + rand()*4;
					}
				} else if (ailp->time_player_sound_attacked + F1_0/4 < GameTime) {
					// mprintf((0, "ANGRY! "));
					digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
					ailp->time_player_sound_attacked = GameTime;
				}
			} 

			if ((*player_visibility == 2) && (ailp->next_misc_sound_time < GameTime)) {
				// mprintf((0, "ATTACK! "));
				ailp->next_misc_sound_time = GameTime + (rand() + F1_0) * (7 - Difficulty_level) / 2;
				digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
			}
			ailp->previous_visibility = *player_visibility;
		}

		*flag = 1;

		if (*player_visibility) {
			ailp->time_player_seen = GameTime;
		}
	}

}

// --------------------------------------------------------------------------------------------------------------------
//	Move the object objp to a spot in which it doesn't intersect a wall.
//	It might mean moving it outside its current segment.
void move_object_to_legal_spot(object *objp)
{
	vms_vector	original_pos = objp->pos;
	int		i;
	segment	*segp = &Segments[objp->segnum];

	for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
		if (WALL_IS_DOORWAY(segp, i) & WID_FLY_FLAG) {
			vms_vector	segment_center, goal_dir;
			fix			dist_to_center;

			compute_segment_center(&segment_center, &Segments[segp->children[i]]);
			vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
			dist_to_center = vm_vec_normalize_quick(&goal_dir);
			vm_vec_scale(&goal_dir, objp->size);
			vm_vec_add2(&objp->pos, &goal_dir);
			if (!object_intersects_wall(objp)) {
				int	new_segnum = find_point_seg(&objp->pos, objp->segnum);

				if (new_segnum != -1) {
					obj_relink(objp-Objects, new_segnum);
					return;
				}
			} else
				objp->pos = original_pos;
		}
	}

	// Int3();		//	Darn you John, you done it again!  (But contact Mike)
	mprintf((0, "Note: Killing robot #%i because he's badly stuck outside the mine.\n", objp-Objects));

	apply_damage_to_robot(objp, objp->shields*2, objp-Objects);
}

// --------------------------------------------------------------------------------------------------------------------
//	Move object one object radii from current position towards segment center.
//	If segment center is nearer than 2 radii, move it to center.
void move_towards_segment_center(object *objp)
{
	int			segnum = objp->segnum;
	fix			dist_to_center;
	vms_vector	segment_center, goal_dir;

	compute_segment_center(&segment_center, &Segments[segnum]);

	vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
	dist_to_center = vm_vec_normalize_quick(&goal_dir);

	if (dist_to_center < objp->size) {
		//	Center is nearer than the distance we want to move, so move to center.
		objp->pos = segment_center;
		mprintf((0, "Object #%i moved to center of segment #%i (%7.3f %7.3f %7.3f)\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z)));
		if (object_intersects_wall(objp)) {
			mprintf((0, "Object #%i still illegal, trying trickier move.\n"));
			move_object_to_legal_spot(objp);
		}
	} else {
		int	new_segnum;
		//	Move one radii towards center.
		vm_vec_scale(&goal_dir, objp->size);
		vm_vec_add2(&objp->pos, &goal_dir);
		new_segnum = find_point_seg(&objp->pos, objp->segnum);
		if (new_segnum == -1) {
			objp->pos = segment_center;
			move_object_to_legal_spot(objp);
		}
		mprintf((0, "Obj %i moved twrds seg %i (%6.2f %6.2f %6.2f), dists: [%6.2f %6.2f]\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center)), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center))));
	}

}

//	-----------------------------------------------------------------------------------------------------------
//	Return true if door can be flown through by a suitable type robot.
//	Only brains and avoid robots can open doors.
int ai_door_is_openable(object *objp, segment *segp, int sidenum)
{
	int	wall_num;

	//	The mighty console object can open all doors (for purposes of determining paths).
	if (objp == ConsoleObject) {
		int	wall_num = segp->sides[sidenum].wall_num;

		if (Walls[wall_num].type == WALL_DOOR)
			return 1;
	}

	if ((objp->id == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == AIB_RUN_FROM)) {
		wall_num = segp->sides[sidenum].wall_num;

		if (wall_num != -1)
			if ((Walls[wall_num].type == WALL_DOOR) && (Walls[wall_num].keys == KEY_NONE) && !(Walls[wall_num].flags & WALL_DOOR_LOCKED))
				return 1;
	}

	return 0;
}

//--//	-----------------------------------------------------------------------------------------------------------
//--//	Return true if object *objp is allowed to open door at wall_num
//--int door_openable_by_robot(object *objp, int wall_num)
//--{
//--	if (objp->id == ROBOT_BRAIN)
//--		if (Walls[wall_num].keys == KEY_NONE)
//--			return 1;
//--
//--	return 0;
//--}

//	-----------------------------------------------------------------------------------------------------------
//	Return side of openable door in segment, if any.  If none, return -1.
int openable_doors_in_segment(object *objp)
{
	int	i;
	int	segnum = objp->segnum;

	for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
		if (Segments[segnum].sides[i].wall_num != -1) {
			int	wall_num = Segments[segnum].sides[i].wall_num;
			if ((Walls[wall_num].type == WALL_DOOR) && (Walls[wall_num].keys == KEY_NONE) && (Walls[wall_num].state == WALL_DOOR_CLOSED) && !(Walls[wall_num].flags & WALL_DOOR_LOCKED))
				return i;
		}
	}

	return -1;

}

//--unused-- //	-----------------------------------------------------------------------------------------------------------
//--unused-- //	For all doors this guy can open in his segment, open them.
//--unused-- void ai_open_doors_in_segment(object *objp)
//--unused-- {
//--unused-- 	int	i;
//--unused-- 	int	segnum = objp->segnum;
//--unused-- 
//--unused-- 	for (i=0; i<MAX_SIDES_PER_SEGMENT; i++)
//--unused-- 		if (Segments[segnum].sides[i].wall_num != -1) {
//--unused-- 			int	wall_num = Segments[segnum].sides[i].wall_num;
//--unused-- 			if ((Walls[wall_num].type == WALL_DOOR) && (Walls[wall_num].keys == KEY_NONE) && (Walls[wall_num].state == WALL_DOOR_CLOSED))
//--unused-- 				if (door_openable_by_robot(objp, wall_num)) {
//--unused-- 					mprintf((0, "Trying to open door at segment %i, side %i\n", segnum, i));
//--unused-- 					wall_open_door(&Segments[segnum], i);
//--unused-- 				}
//--unused-- 		}
//--unused-- }

// --------------------------------------------------------------------------------------------------------------------
//	Return true if a special object (player or control center) is in this segment.
int special_object_in_seg(int segnum)
{
	int	objnum;

	objnum = Segments[segnum].objects;

	while (objnum != -1) {
		if ((Objects[objnum].type == OBJ_PLAYER) || (Objects[objnum].type == OBJ_CNTRLCEN)) {
			mprintf((0, "Special object of type %i in segment %i\n", Objects[objnum].type, segnum));
			return 1;
		} else
			objnum = Objects[objnum].next;
	}

	return 0;
}

// --------------------------------------------------------------------------------------------------------------------
//	Randomly select a segment attached to *segp, reachable by flying.
int get_random_child(int segnum)
{
	int	sidenum;
	segment	*segp = &Segments[segnum];

	sidenum = (rand() * 6) >> 15;

	while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG))
		sidenum = (rand() * 6) >> 15;

	segnum = segp->children[sidenum];

	return segnum;
}

// --------------------------------------------------------------------------------------------------------------------
//	Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
int check_object_object_intersection(vms_vector *pos, fix size, segment *segp)
{
	int		curobjnum;

	//	If this would intersect with another object (only check those in this segment), then try to move.
	curobjnum = segp->objects;
	while (curobjnum != -1) {
		object *curobjp = &Objects[curobjnum];
		if ((curobjp->type == OBJ_PLAYER) || (curobjp->type == OBJ_ROBOT) || (curobjp->type == OBJ_CNTRLCEN)) {
			if (vm_vec_dist_quick(pos, &curobjp->pos) < size + curobjp->size)
				return 1;
		}
		curobjnum = curobjp->next;
	}

	return 0;

}

#ifndef SHAREWARE

// --------------------------------------------------------------------------------------------------------------------
//	Return true if object created, else return false.
int create_gated_robot( int segnum, int object_id)
{
	int		objnum;
	object	*objp;
	segment	*segp = &Segments[segnum];
	vms_vector	object_pos;
	robot_info	*robptr = &Robot_info[object_id];
	int		i, count=0;
	fix		objsize = Polygon_models[robptr->model_num].rad;
	int		default_behavior;

	for (i=0; i<=Highest_object_index; i++)
		if (Objects[i].type == OBJ_ROBOT)
			if (Objects[i].matcen_creator == BOSS_GATE_MATCEN_NUM)
				count++;

	if (count > 2*Difficulty_level + 3) {
		// mprintf((0, "Cannot gate in a robot until you kill one.\n"));
		Last_gate_time = GameTime - 3*Gate_interval/4;
		return 0;
	}

	compute_segment_center(&object_pos, segp);
	pick_random_point_in_seg(&object_pos, segp-Segments);

	//	See if legal to place object here.  If not, move about in segment and try again.
	if (check_object_object_intersection(&object_pos, objsize, segp)) {
		// mprintf((0, "Can't get in because object collides with something.\n"));
		Last_gate_time = GameTime - 3*Gate_interval/4;
		return 0;
	}

	objnum = obj_create(OBJ_ROBOT, object_id, segnum, &object_pos, &vmd_identity_matrix, objsize, CT_AI, MT_PHYSICS, RT_POLYOBJ);

	if ( objnum < 0 ) {
		// mprintf((1, "Can't get object to gate in robot.  Not gating in.\n"));
		Last_gate_time = GameTime - 3*Gate_interval/4;
		return 0;
	}

	mprintf((0, "Gating in object %i in segment %i\n", objnum, segp-Segments));

	#ifdef NETWORK
	Net_create_objnums[0] = objnum; // A convenient global to get objnum back to caller for multiplayer
	#endif

	objp = &Objects[objnum];

	//Set polygon-object-specific data

	objp->rtype.pobj_info.model_num = robptr->model_num;
	objp->rtype.pobj_info.subobj_flags = 0;

	//set Physics info

	objp->mtype.phys_info.mass = robptr->mass;
	objp->mtype.phys_info.drag = robptr->drag;

	objp->mtype.phys_info.flags |= (PF_LEVELLING);

	objp->shields = robptr->strength;
	objp->matcen_creator = BOSS_GATE_MATCEN_NUM;	//	flag this robot as having been created by the boss.

	default_behavior = AIB_NORMAL;
	if (object_id == 10)						//	This is a toaster guy!
		default_behavior = AIB_RUN_FROM;

	init_ai_object(objp-Objects, default_behavior, -1 );		//	Note, -1 = segment this robot goes to to hide, should probably be something useful

	object_create_explosion(segnum, &object_pos, i2f(10), VCLIP_MORPHING_ROBOT );
	digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segnum, 0, &object_pos, 0 , F1_0);
	morph_start(objp);

	Last_gate_time = GameTime;

	Players[Player_num].num_robots_level++;
	Players[Player_num].num_robots_total++;

	return 1;
}

// --------------------------------------------------------------------------------------------------------------------
//	Make object objp gate in a robot.
//	The process of him bringing in a robot takes one second.
//	Then a robot appears somewhere near the player.
//	Return true if robot successfully created, else return false
int gate_in_robot(int type, int segnum)
{
	if (segnum < 0)
		segnum = Boss_gate_segs[(rand() * Num_boss_gate_segs) >> 15];

	Assert((segnum >= 0) && (segnum <= Highest_segment_index));

	return create_gated_robot(segnum, type);
}

#endif

//// --------------------------------------------------------------------------------------------------------------------
////	Return true if some distance function of vertices implies segment is too small for object.
//int segment_too_small_for_object(object *objp, segment *segp)
//{
//	int	i;
//	fix	threshold_distance;
//
//	threshold_distance = objp->size*2;
//
//	for (i=1; i<MAX_VERTICES_PER_SEGMENT; i++)
//		if (vm_vec_dist_quick(&Vertices[segp->verts[i]], &Vertices[segp->verts[i-1]]) < threshold_distance) {
//#ifndef NDEBUG
//			fix dist = vm_vec_dist_quick(&Vertices[segp->verts[i]], &Vertices[segp->verts[i-1]]);
//#endif
//			mprintf((0, "Seg %i too small for obj %i (sz=%7.3f), verts %i, %i only %7.3f apart\n", segp-Segments, objp-Objects, f2fl(objp->size), segp->verts[i], segp->verts[i-1], f2fl(dist)));
//			return 1;
//		}
//
//	return 0;
//}

//--unused-- int	Shown_all_segments=0;

// --------------------------------------------------------------------------------------------------------------------
int boss_fits_in_seg(object *boss_objp, int segnum)
{
	vms_vector	segcenter;
	int			boss_objnum = boss_objp-Objects;
	int			posnum;

	compute_segment_center(&segcenter, &Segments[segnum]);

	for (posnum=0; posnum<9; posnum++) {
		if (posnum > 0) {
			vms_vector	vertex_pos;

			Assert((posnum-1 >= 0) && (posnum-1 < 8));
			vertex_pos = Vertices[Segments[segnum].verts[posnum-1]];
			vm_vec_avg(&boss_objp->pos, &vertex_pos, &segcenter);
		} else
			boss_objp->pos = segcenter;

		obj_relink(boss_objnum, segnum);
		if (!object_intersects_wall(boss_objp))
			return 1;
	}

	return 0;
}

#define	QUEUE_SIZE	256

// --------------------------------------------------------------------------------------------------------------------
//	Create list of segments boss is allowed to teleport to at segptr.
//	Set *num_segs.
//	Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
//	he can reach from his initial position (calls find_connected_distance).
//	If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
void init_boss_segments(short segptr[], int *num_segs, int size_check)
{
	int			boss_objnum=-1;
	int			i;

	*num_segs = 0;
#ifdef EDITOR
	N_selected_segs = 0;
#endif


	//	See if there is a boss.  If not, quick out.
	for (i=0; i<=Highest_object_index; i++)
		if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].boss_flag)) {
			Assert(boss_objnum == -1);		//	There are two bosses in this mine!  i and boss_objnum!
			boss_objnum = i;
		}

	if (boss_objnum != -1) {
		int			original_boss_seg;
		vms_vector	original_boss_pos;
		object		*boss_objp = &Objects[boss_objnum];
		int			head, tail;
		int			seg_queue[QUEUE_SIZE];
//ALREADY IN RENDER.H		byte			visited[MAX_SEGMENTS];
		fix			boss_size_save;

		boss_size_save = boss_objp->size;
		boss_objp->size = fixmul((F1_0/4)*3, boss_objp->size);
		original_boss_seg = boss_objp->segnum;
		original_boss_pos = boss_objp->pos;
		head = 0;
		tail = 0;
		seg_queue[head++] = original_boss_seg;

		segptr[(*num_segs)++] = original_boss_seg;
		#ifdef EDITOR
		Selected_segs[N_selected_segs++] = original_boss_seg;
		#endif

		for (i=0; i<=Highest_segment_index; i++)
			visited[i] = 0;

		while (tail != head) {
			int		sidenum;
			segment	*segp = &Segments[seg_queue[tail++]];

			tail &= QUEUE_SIZE-1;

			for (sidenum=0; sidenum<MAX_SIDES_PER_SEGMENT; sidenum++) {
				if (WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG) {
					if (visited[segp->children[sidenum]] == 0) {
						seg_queue[head++] = segp->children[sidenum];
						visited[segp->children[sidenum]] = 1;
						head &= QUEUE_SIZE-1;
						if (head > tail) {
							if (head == tail + QUEUE_SIZE-1)
								Int3();	//	queue overflow.  Make it bigger!
						} else
							if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1)
								Int3();	//	queue overflow.  Make it bigger!
	
						if ((!size_check) || boss_fits_in_seg(boss_objp, segp->children[sidenum])) {
							segptr[(*num_segs)++] = segp->children[sidenum];
							#ifdef EDITOR
							Selected_segs[N_selected_segs++] = segp->children[sidenum];
							#endif
							if (*num_segs >= MAX_BOSS_TELEPORT_SEGS) {
								mprintf((1, "Warning: Too many boss teleport segments.  Found %i after searching %i/%i segments.\n", MAX_BOSS_TELEPORT_SEGS, segp->children[sidenum], Highest_segment_index+1));
								tail = head;
							}
						}
					}
				}
			}

		}

		boss_objp->size = boss_size_save;
		boss_objp->pos = original_boss_pos;
		obj_relink(boss_objnum, original_boss_seg);

	}

}

// --------------------------------------------------------------------------------------------------------------------
void teleport_boss(object *objp)
{
	int			rand_segnum;
	vms_vector	boss_dir;
	int			rand_seg;
	Assert(Num_boss_teleport_segs > 0);

	//	Pick a random segment from the list of boss-teleportable-to segments.
	rand_seg = (rand() * Num_boss_teleport_segs) >> 15;	
	rand_segnum = Boss_teleport_segs[rand_seg];
	Assert((rand_segnum >= 0) && (rand_segnum <= Highest_segment_index));

#ifndef SHAREWARE
#ifdef NETWORK
	if (Game_mode & GM_MULTI)
		multi_send_boss_actions(objp-Objects, 1, rand_seg, 0);
#endif
#endif

	compute_segment_center(&objp->pos, &Segments[rand_segnum]);
	obj_relink(objp-Objects, rand_segnum);

	Last_teleport_time = GameTime;

	//	make boss point right at player
	vm_vec_sub(&boss_dir, &Objects[Players[Player_num].objnum].pos, &objp->pos);
	vm_vector_2_matrix(&objp->orient, &boss_dir, NULL, NULL);

	digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segnum, 0, &objp->pos, 0 , F1_0);
	digi_kill_sound_linked_to_object( objp-Objects);
	digi_link_sound_to_object2( SOUND_BOSS_SHARE_SEE, objp-Objects, 1, F1_0, F1_0*512 );	//	F1_0*512 means play twice as loud
	#ifndef NDEBUG
	mprintf((0, "Boss teleported to segment %i\n", rand_segnum));
	#endif

	//	After a teleport, boss can fire right away.
	Ai_local_info[objp-Objects].next_fire = 0;

}

//	----------------------------------------------------------------------
void start_boss_death_sequence(object *objp)
{
	if (Robot_info[objp->id].boss_flag) {
		Boss_dying = 1;
		Boss_dying_start_time = GameTime;
	}

}

//	----------------------------------------------------------------------
void do_boss_dying_frame(object *objp)
{
	fix	boss_roll_val, temp;

	boss_roll_val = fixdiv(GameTime - Boss_dying_start_time, BOSS_DEATH_DURATION);

	fix_sincos(fixmul(boss_roll_val, boss_roll_val), &temp, &objp->mtype.phys_info.rotvel.x);
	fix_sincos(boss_roll_val, &temp, &objp->mtype.phys_info.rotvel.y);
	fix_sincos(boss_roll_val-F1_0/8, &temp, &objp->mtype.phys_info.rotvel.z);

	objp->mtype.phys_info.rotvel.x = (GameTime - Boss_dying_start_time)/9;
	objp->mtype.phys_info.rotvel.y = (GameTime - Boss_dying_start_time)/5;
	objp->mtype.phys_info.rotvel.z = (GameTime - Boss_dying_start_time)/7;

	if (Boss_dying_start_time + BOSS_DEATH_DURATION - BOSS_DEATH_SOUND_DURATION < GameTime) {
		if (!Boss_dying_sound_playing) {
			mprintf((0, "Starting boss death sound!\n"));
			Boss_dying_sound_playing = 1;
			digi_link_sound_to_object2( SOUND_BOSS_SHARE_DIE, objp-Objects, 0, F1_0*4, F1_0*1024 );	//	F1_0*512 means play twice as loud
		} else if (rand() < FrameTime*16)
			create_small_fireball_on_object(objp, (F1_0 + rand()) * 8, 0);
	} else if (rand() < FrameTime*8)
		create_small_fireball_on_object(objp, (F1_0/2 + rand()) * 8, 1);

	if (Boss_dying_start_time + BOSS_DEATH_DURATION < GameTime) {
		do_controlcen_destroyed_stuff(NULL);
		explode_object(objp, F1_0/4);
		digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp-Objects, 0, F2_0, F1_0*512);
	}
}

#ifndef SHAREWARE 
#ifdef NETWORK
// --------------------------------------------------------------------------------------------------------------------
//	Called for an AI object if it is fairly aware of the player.
//	awareness_level is in 0..100.  Larger numbers indicate greater awareness (eg, 99 if firing at player).
//	In a given frame, might not get called for an object, or might be called more than once.
//	The fact that this routine is not called for a given object does not mean that object is not interested in the player.
//	Objects are moved by physics, so they can move even if not interested in a player.  However, if their velocity or
//	orientation is changing, this routine will be called.
//	Return value:
//		0	this player IS NOT allowed to move this robot.
//		1	this player IS allowed to move this robot.
int ai_multiplayer_awareness(object *objp, int awareness_level)
{
	int	rval=1;

#ifndef SHAREWARE
	if (Game_mode & GM_MULTI) {
		if (awareness_level == 0)
			return 0;
		rval = multi_can_move_robot(objp-Objects, awareness_level);
	}
#endif

	return rval;

}
#else
#define ai_multiplayer_awareness(a, b) 1
#endif
#else
#define ai_multiplayer_awareness(a, b) 1
#endif

#ifndef NDEBUG
fix	Prev_boss_shields = -1;
#endif

// --------------------------------------------------------------------------------------------------------------------
//	Do special stuff for a boss.
void do_boss_stuff(object *objp)
{
    //  New code, fixes stupid bug which meant boss never gated in robots if > 32767 seconds played.
    if (Last_teleport_time > GameTime)
        Last_teleport_time = GameTime;

    if (Last_gate_time > GameTime)
        Last_gate_time = GameTime;

#ifndef NDEBUG
	if (objp->shields != Prev_boss_shields) {
		mprintf((0, "Boss shields = %7.3f, object %i\n", f2fl(objp->shields), objp-Objects));
		Prev_boss_shields = objp->shields;
	}
#endif

	if (!Boss_dying) {
		if (objp->ctype.ai_info.CLOAKED == 1) {
			if ((GameTime - Boss_cloak_start_time > BOSS_CLOAK_DURATION/3) && (Boss_cloak_end_time - GameTime > BOSS_CLOAK_DURATION/3) && (GameTime - Last_teleport_time > Boss_teleport_interval)) {
				if (ai_multiplayer_awareness(objp, 98))
					teleport_boss(objp);
			} else if (Boss_hit_this_frame) {
				Boss_hit_this_frame = 0;
				Last_teleport_time -= Boss_teleport_interval/4;
			}

			if (GameTime > Boss_cloak_end_time)
				objp->ctype.ai_info.CLOAKED = 0;
		} else {
			if ((GameTime - Boss_cloak_end_time > Boss_cloak_interval) || Boss_hit_this_frame) {
				if (ai_multiplayer_awareness(objp, 95))
				{
					Boss_hit_this_frame = 0;
					Boss_cloak_start_time = GameTime;
					Boss_cloak_end_time = GameTime+Boss_cloak_duration;
					objp->ctype.ai_info.CLOAKED = 1;
#ifndef SHAREWARE
#ifdef NETWORK
					if (Game_mode & GM_MULTI)
						multi_send_boss_actions(objp-Objects, 2, 0, 0);
#endif
#endif
				}
			}
		}
	} else
		do_boss_dying_frame(objp);

}

#define	BOSS_TO_PLAYER_GATE_DISTANCE	(F1_0*150)

#ifndef SHAREWARE

// --------------------------------------------------------------------------------------------------------------------
//	Do special stuff for a boss.
void do_super_boss_stuff(object *objp, fix dist_to_player, int player_visibility)
{
	static int eclip_state = 0;
	do_boss_stuff(objp);

	// Only master player can cause gating to occur.
	#ifdef NETWORK
	if ((Game_mode & GM_MULTI) && !network_i_am_master())
		return; 
	#endif

	if ((dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE) || player_visibility || (Game_mode & GM_MULTI)) {
		if (GameTime - Last_gate_time > Gate_interval/2) {
			restart_effect(BOSS_ECLIP_NUM);
#ifndef SHAREWARE
#ifdef NETWORK
			if (eclip_state == 0) {
				multi_send_boss_actions(objp-Objects, 4, 0, 0);
				eclip_state = 1;
			}
#endif
#endif
		}
		else {
			stop_effect(BOSS_ECLIP_NUM);
#ifndef SHAREWARE
#ifdef NETWORK
			if (eclip_state == 1) {
				multi_send_boss_actions(objp-Objects, 5, 0, 0);
				eclip_state = 0;
			}
#endif
#endif
		}

		if (GameTime - Last_gate_time > Gate_interval)
			if (ai_multiplayer_awareness(objp, 99)) {
				int	rtval;
				int	randtype = (rand() * MAX_GATE_INDEX) >> 15;

				Assert(randtype < MAX_GATE_INDEX);
				randtype = Super_boss_gate_list[randtype];
				Assert(randtype < N_robot_types);

				rtval = gate_in_robot(randtype, -1);
#ifndef SHAREWARE
#ifdef NETWORK
				if (rtval && (Game_mode & GM_MULTI))
				{
					multi_send_boss_actions(objp-Objects, 3, randtype, Net_create_objnums[0]);
					map_objnum_local_to_local(Net_create_objnums[0]);
				}
#endif
#endif
			}	
	}
}

#endif

//int multi_can_move_robot(object *objp, int awareness_level)
//{
//	return 0;
//}

#ifndef SHAREWARE
void ai_multi_send_robot_position(int objnum, int force)
{
#ifndef SHAREWARE
#ifdef NETWORK
	if (Game_mode & GM_MULTI) 
	{
		if (force != -1)
			multi_send_robot_position(objnum, 1);
		else
			multi_send_robot_position(objnum, 0);
	}
#endif
#endif
	return;
}
#else
#define ai_multi_send_robot_position(a, b)
#endif

// --------------------------------------------------------------------------------------------------------------------
//	Returns true if this object should be allowed to fire at the player.
int maybe_ai_do_actual_firing_stuff(object *obj, ai_static *aip)
{
	if (Game_mode & GM_MULTI)
		if ((aip->GOAL_STATE != AIS_FLIN) && (obj->id != ROBOT_BRAIN))
			if (aip->CURRENT_STATE == AIS_FIRE)
				return 1;

	return 0;
}

// --------------------------------------------------------------------------------------------------------------------
void ai_do_actual_firing_stuff(object *obj, ai_static *aip, ai_local *ailp, robot_info *robptr, vms_vector *vec_to_player, fix dist_to_player, vms_vector *gun_point, int player_visibility, int object_animates)
{
	fix	dot;

	if (player_visibility == 2) {
		//	Changed by mk, 01/04/94, onearm would take about 9 seconds until he can fire at you.
		// if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE)) && (ailp->next_fire <= 0)) {
		if (!object_animates || (ailp->next_fire <= 0)) {
			dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
			if (dot >= 7*F1_0/8) {

				if (aip->CURRENT_GUN < Robot_info[obj->id].n_guns) {
					if (robptr->attack_type == 1) {
						if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) {		// robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
							if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
								return;
							do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
						} else {
							// mprintf((0, "Green won't fire: Too far: dist = %7.3f, threshold = %7.3f\n", f2fl(dist_to_player), f2fl(obj->size + ConsoleObject->size + F1_0*2)));
							return;
						}
					} else {
						if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
							; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
						} else {
							if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
								return;
							ai_fire_laser_at_player(obj, gun_point);
						}
					}

					//	Wants to fire, so should go into chase mode, probably.
					if ( (aip->behavior != AIB_RUN_FROM) && (aip->behavior != AIB_STILL) && (aip->behavior != AIB_FOLLOW_PATH) && ((ailp->mode == AIM_FOLLOW_PATH) || (ailp->mode == AIM_STILL)))
						ailp->mode = AIM_CHASE_OBJECT;
				}

				aip->GOAL_STATE = AIS_RECO;
				ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;

				// Switch to next gun for next fire.
				aip->CURRENT_GUN++;
				if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
					aip->CURRENT_GUN = 0;
			}
		}
	} else if (Weapon_info[Robot_info[obj->id].weapon_type].homing_flag == 1) {
		//	Robots which fire homing weapons might fire even if they don't have a bead on the player.
		if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE)) && (ailp->next_fire <= 0) && (vm_vec_dist_quick(&Hit_pos, &obj->pos) > F1_0*40)) {
			if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
				return;
			ai_fire_laser_at_player(obj, gun_point);

			aip->GOAL_STATE = AIS_RECO;
			ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;

			// Switch to next gun for next fire.
			aip->CURRENT_GUN++;
			if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
				aip->CURRENT_GUN = 0;
		} else {
			// Switch to next gun for next fire.
			aip->CURRENT_GUN++;
			if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
				aip->CURRENT_GUN = 0;
		}
	}
}

// --------------------------------------------------------------------------------------------------------------------
void do_ai_frame(object *obj)
{
	int			objnum = obj-Objects;
	ai_static	*aip = &obj->ctype.ai_info;
	ai_local		*ailp = &Ai_local_info[objnum];
	fix			dist_to_player;
	vms_vector	vec_to_player;
	fix			dot;
	robot_info	*robptr;
	int			player_visibility=-1;
	int			obj_ref;
	int			object_animates;
	int			new_goal_state;
	int			visibility_and_vec_computed = 0;
	int			previous_visibility;
	vms_vector	gun_point;
	vms_vector	vis_vec_pos;

	if (aip->SKIP_AI_COUNT) {
		aip->SKIP_AI_COUNT--;
		return;
	}

	//	Kind of a hack.  If a robot is flinching, but it is time for it to fire, unflinch it.
	//	Else, you can turn a big nasty robot into a wimp by firing flares at it.
	//	This also allows the player to see the cool flinch effect for mechs without unbalancing the game.
	if ((aip->GOAL_STATE == AIS_FLIN) && (ailp->next_fire < 0)) {
		aip->GOAL_STATE = AIS_FIRE;
	}

#ifndef NDEBUG
	if ((aip->behavior == AIB_RUN_FROM) && (ailp->mode != AIM_RUN_FROM_OBJECT))
		Int3();	//	This is peculiar.  Behavior is run from, but mode is not.  Contact Mike.

	mprintf_animation_info((obj));

	if (Ai_animation_test) {
		if (aip->GOAL_STATE == aip->CURRENT_STATE) {
			aip->GOAL_STATE++;
			if (aip->GOAL_STATE > AIS_RECO)
				aip->GOAL_STATE = AIS_REST;
		}

		mprintf((0, "Frame %4i, current = %i, goal = %i\n", FrameCount, aip->CURRENT_STATE, aip->GOAL_STATE));
		object_animates = do_silly_animation(obj);
		if (object_animates)
			ai_frame_animation(obj);
		return;
	}

	if (!Do_ai_flag)
		return;

	if (Break_on_object != -1)
		if ((obj-Objects) == Break_on_object)
			Int3();	//	Contact Mike: This is a debug break
#endif

	Believed_player_pos = Ai_cloak_info[objnum & (MAX_AI_CLOAK_INFO-1)].last_position;

	// mprintf((0, "Object %i: behavior = %02x, mode = %i, awareness = %i, time = %7.3f\n", obj-Objects, aip->behavior, ailp->mode, ailp->player_awareness_type, f2fl(ailp->player_awareness_time)));
	// mprintf((0, "Object %i: behavior = %02x, mode = %i, awareness = %i, cur=%i, goal=%i\n", obj-Objects, aip->behavior, ailp->mode, ailp->player_awareness_type, aip->CURRENT_STATE, aip->GOAL_STATE));

//	Assert((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR));
	if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) {
		// mprintf((0, "Object %i behavior is %i, setting to AIB_NORMAL, fix in editor!\n", objnum, aip->behavior));
		aip->behavior = AIB_NORMAL;
	}

	Assert(obj->segnum != -1);
	Assert(obj->id < N_robot_types);

	robptr = &Robot_info[obj->id];
	Assert(robptr->always_0xabcd == 0xabcd);
	obj_ref = objnum ^ FrameCount;
	// -- if (ailp->wait_time > -F1_0*8)
	// -- 	ailp->wait_time -= FrameTime;
	if (ailp->next_fire > -F1_0*8)
		ailp->next_fire -= FrameTime;
	if (ailp->time_since_processed < F1_0*256)
		ailp->time_since_processed += FrameTime;
	previous_visibility = ailp->previous_visibility;	//	Must get this before we toast the master copy!

	//	Deal with cloaking for robots which are cloaked except just before firing.
	if (robptr->cloak_type == RI_CLOAKED_EXCEPT_FIRING)
		if (ailp->next_fire < F1_0/2)
			aip->CLOAKED = 1;
		else
			aip->CLOAKED = 0;

	if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED))
		Believed_player_pos = ConsoleObject->pos;

	dist_to_player = vm_vec_dist_quick(&Believed_player_pos, &obj->pos);

//--!-- 	mprintf((0, "%2i: %s, [vel = %5.1f %5.1f %5.1f] spd = %4.1f dtp=%5.1f ", objnum, mode_text[ailp->mode], f2fl(obj->mtype.phys_info.velocity.x), f2fl(obj->mtype.phys_info.velocity.y), f2fl(obj->mtype.phys_info.velocity.z), f2fl(vm_vec_mag(&obj->mtype.phys_info.velocity)), f2fl(dist_to_player)));
//--!-- 	if (ailp->mode == AIM_FOLLOW_PATH) {
//--!-- 		mprintf((0, "gseg = %i\n", Point_segs[aip->hide_index+aip->cur_path_index].segnum));
//--!-- 	} else
//--!-- 		mprintf((0, "\n"));

	//	If this robot can fire, compute visibility from gun position.
	//	Don't want to compute visibility twice, as it is expensive.  (So is call to calc_gun_point).
	if ((ailp->next_fire <= 0) && (dist_to_player < F1_0*200) && (robptr->n_guns) && !(robptr->attack_type)) {
		calc_gun_point(&gun_point, obj, aip->CURRENT_GUN);
		vis_vec_pos = gun_point;
		// mprintf((0, "Visibility = %i, computed from gun #%i\n", player_visibility, aip->CURRENT_GUN));
	} else {
		vis_vec_pos = obj->pos;
		vm_vec_zero(&gun_point);
		// mprintf((0, "Visibility = %i, computed from center.\n", player_visibility));
	}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	Occasionally make non-still robots make a path to the player.  Based on agitation and distance from player.
	if ((aip->behavior != AIB_RUN_FROM) && (aip->behavior != AIB_STILL) && !(Game_mode & GM_MULTI))
		if (Overall_agitation > 70) {
			if ((dist_to_player < F1_0*200) && (rand() < FrameTime/4)) {
				if (rand() * (Overall_agitation - 40) > F1_0*5) {
					// -- mprintf((0, "(1) Object #%i going from still to path in frame %i.\n", objnum, FrameCount));
					create_path_to_player(obj, 4 + Overall_agitation/8 + Difficulty_level, 1);
					// -- show_path_and_other(obj);
					return;
				}
			}
		}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	If retry count not 0, then add it into consecutive_retries.
	//	If it is 0, cut down consecutive_retries.
	//	This is largely a hack to speed up physics and deal with stupid AI.  This is low level
	//	communication between systems of a sort that should not be done.
	if ((ailp->retry_count) && !(Game_mode & GM_MULTI)) {
		ailp->consecutive_retries += ailp->retry_count;
		ailp->retry_count = 0;
		if (ailp->consecutive_retries > 3) {
			switch (ailp->mode) {
				case AIM_CHASE_OBJECT:
					// -- mprintf((0, "(2) Object #%i, retries while chasing, creating path to player in frame %i\n", objnum, FrameCount));
					create_path_to_player(obj, 4 + Overall_agitation/8 + Difficulty_level, 1);
					break;
				case AIM_STILL:
					if (!((aip->behavior == AIB_STILL) || (aip->behavior == AIB_STATION)))	//	Behavior is still, so don't follow path.
						attempt_to_resume_path(obj);
					break;	
				case AIM_FOLLOW_PATH:
						// mprintf((0, "Object %i following path got %i retries in frame %i\n", obj-Objects, ailp->consecutive_retries, FrameCount));
					if (Game_mode & GM_MULTI)
						ailp->mode = AIM_STILL;
					else
						attempt_to_resume_path(obj);
					break;
				case AIM_RUN_FROM_OBJECT:
					move_towards_segment_center(obj);
					obj->mtype.phys_info.velocity.x = 0;
					obj->mtype.phys_info.velocity.y = 0;
					obj->mtype.phys_info.velocity.z = 0;
					create_n_segment_path(obj, 5, -1);
					ailp->mode = AIM_RUN_FROM_OBJECT;
					break;
				case AIM_HIDE:
					move_towards_segment_center(obj);
					obj->mtype.phys_info.velocity.x = 0;
					obj->mtype.phys_info.velocity.y = 0;
					obj->mtype.phys_info.velocity.z = 0;
					// -- mprintf((0, "Hiding, yet creating path to player.\n"));
					if (Overall_agitation > (50 - Difficulty_level*4))
						create_path_to_player(obj, 4 + Overall_agitation/8, 1);
					else {
						create_n_segment_path(obj, 5, -1);
					}
					break;
				case AIM_OPEN_DOOR:
					create_n_segment_path_to_door(obj, 5, -1);
					break;
				#ifndef NDEBUG
				case AIM_FOLLOW_PATH_2:
					Int3();	//	Should never happen!
					break;
				#endif
			}
			ailp->consecutive_retries = 0;
		}
	} else
		ailp->consecutive_retries /= 2;

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
 	//	If in materialization center, exit
 	if (!(Game_mode & GM_MULTI) && (Segments[obj->segnum].special == SEGMENT_IS_ROBOTMAKER)) {
 		ai_follow_path(obj, 1);		// 1 = player is visible, which might be a lie, but it works.
 		return;
 	}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	Decrease player awareness due to the passage of time.
//-----old, faster way from about 11/06/94-----	if (ailp->player_awareness_type) {
//-----old, faster way from about 11/06/94-----		if (ailp->player_awareness_time > 0) {
//-----old, faster way from about 11/06/94-----			ailp->player_awareness_time -= FrameTime;
//-----old, faster way from about 11/06/94-----			if (ailp->player_awareness_time <= 0) {
//-----old, faster way from about 11/06/94----- 				ailp->player_awareness_time = F1_0*2;
//-----old, faster way from about 11/06/94----- 				ailp->player_awareness_type--;
//-----old, faster way from about 11/06/94-----				if (ailp->player_awareness_type < 0) {
//-----old, faster way from about 11/06/94-----	 				aip->GOAL_STATE = AIS_REST;
//-----old, faster way from about 11/06/94-----	 				ailp->player_awareness_type = 0;
//-----old, faster way from about 11/06/94-----				}
//-----old, faster way from about 11/06/94-----
//-----old, faster way from about 11/06/94-----			}
//-----old, faster way from about 11/06/94-----		} else {
//-----old, faster way from about 11/06/94----- 			ailp->player_awareness_type = 0;
//-----old, faster way from about 11/06/94----- 			aip->GOAL_STATE = AIS_REST;
//-----old, faster way from about 11/06/94-----		}
//-----old, faster way from about 11/06/94-----	} else
//-----old, faster way from about 11/06/94-----		aip->GOAL_STATE = AIS_REST;


	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	Decrease player awareness due to the passage of time.
	if (ailp->player_awareness_type) {
		if (ailp->player_awareness_time > 0) {
			ailp->player_awareness_time -= FrameTime;
			if (ailp->player_awareness_time <= 0) {
 				ailp->player_awareness_time = F1_0*2;	//new: 11/05/94
 				ailp->player_awareness_type--;	//new: 11/05/94
			}
		} else {
			ailp->player_awareness_type--;
			ailp->player_awareness_time = F1_0*2;
			// aip->GOAL_STATE = AIS_REST;
		}
	} else
		aip->GOAL_STATE = AIS_REST;							//new: 12/13/94


	if (Player_is_dead && (ailp->player_awareness_type == 0))
		if ((dist_to_player < F1_0*200) && (rand() < FrameTime/8)) {
			if ((aip->behavior != AIB_STILL) && (aip->behavior != AIB_RUN_FROM)) {
				if (!ai_multiplayer_awareness(obj, 30))
					return;
				ai_multi_send_robot_position(objnum, -1);

				if (!((ailp->mode == AIM_FOLLOW_PATH) && (aip->cur_path_index < aip->path_length-1)))
					if (dist_to_player < F1_0*30)
						create_n_segment_path(obj, 5, 1);
					else
						create_path_to_player(obj, 20, 1);
			}
		}

	//	Make sure that if this guy got hit or bumped, then he's chasing player.
	if ((ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
		if ((aip->behavior != AIB_STILL) && (aip->behavior != AIB_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM) && (obj->id != ROBOT_BRAIN))
			ailp->mode = AIM_CHASE_OBJECT;
	}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	if ((aip->GOAL_STATE == AIS_FLIN) && (aip->CURRENT_STATE == AIS_FLIN))
		aip->GOAL_STATE = AIS_LOCK;

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	Note: Should only do these two function calls for objects which animate
	if ((dist_to_player < F1_0*100)) { // && !(Game_mode & GM_MULTI)) {
		object_animates = do_silly_animation(obj);
		if (object_animates)
			ai_frame_animation(obj);
		//mprintf((0, "Object %i: goal=%i, current=%i\n", obj-Objects, obj->ctype.ai_info.GOAL_STATE, obj->ctype.ai_info.CURRENT_STATE));
	} else {
		//	If Object is supposed to animate, but we don't let it animate due to distance, then
		//	we must change its state, else it will never update.
		aip->CURRENT_STATE = aip->GOAL_STATE;
		object_animates = 0;		//	If we're not doing the animation, then should pretend it doesn't animate.
	}

//Processed_this_frame++;
//if (FrameCount != LastFrameCount) {
//	LastFrameCount = FrameCount;
//	mprintf((0, "Processed in frame %i = %i robots\n", FrameCount-1, Processed_this_frame));
//	Processed_this_frame = 0;
//}

	switch (Robot_info[obj->id].boss_flag) {
		case 0:
			break;
		case 1:
			if (aip->GOAL_STATE == AIS_FLIN)
				aip->GOAL_STATE = AIS_FIRE;
			if (aip->CURRENT_STATE == AIS_FLIN)
				aip->CURRENT_STATE = AIS_FIRE;
			dist_to_player /= 4;

			do_boss_stuff(obj);
			dist_to_player *= 4;
			break;
#ifndef SHAREWARE
		case 2:
			if (aip->GOAL_STATE == AIS_FLIN)
				aip->GOAL_STATE = AIS_FIRE;
			if (aip->CURRENT_STATE == AIS_FLIN)
				aip->CURRENT_STATE = AIS_FIRE;
			compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

			{	int pv = player_visibility;
				fix	dtp = dist_to_player/4;

			//	If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to.
			if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
				pv = 0;
				dtp = vm_vec_dist_quick(&ConsoleObject->pos, &obj->pos)/4;
			}

			do_super_boss_stuff(obj, dtp, pv);
			}
			break;
#endif
		default:
			Int3();	//	Bogus boss flag value.
	}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	Time-slice, don't process all the time, purely an efficiency hack.
	//	Guys whose behavior is station and are not at their hide segment get processed anyway.
	if (ailp->player_awareness_type < PA_WEAPON_ROBOT_COLLISION-1) { // If robot got hit, he gets to attack player always!
		#ifndef NDEBUG
		if (Break_on_object != objnum) {	//	don't time slice if we're interested in this object.
		#endif
			if ((dist_to_player > F1_0*250) && (ailp->time_since_processed <= F1_0*2))
				return;
			else if (!((aip->behavior == AIB_STATION) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->hide_segment != obj->segnum))) {
				if ((dist_to_player > F1_0*150) && (ailp->time_since_processed <= F1_0))
					return;
				else if ((dist_to_player > F1_0*100) && (ailp->time_since_processed <= F1_0/2))
					return;
			}
		#ifndef NDEBUG
		}
		#endif
	}

// -- 	if ((aip->behavior == AIB_STATION) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->hide_segment != obj->segnum))
// -- 		mprintf((0, "[%i] ", obj-Objects));

	//	Reset time since processed, but skew objects so not everything processed synchronously, else
	//	we get fast frames with the occasional very slow frame.
	// AI_proc_time = ailp->time_since_processed;
	ailp->time_since_processed = - ((objnum & 0x03) * FrameTime ) / 2;

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	Perform special ability
	switch (obj->id) {
		case ROBOT_BRAIN:
			//	Robots function nicely if behavior is Station.  This means they won't move until they
			//	can see the player, at which time they will start wandering about opening doors.
			if (ConsoleObject->segnum == obj->segnum) {
				if (!ai_multiplayer_awareness(obj, 97))
					return;
				compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
				move_away_from_player(obj, &vec_to_player, 0);
				ai_multi_send_robot_position(objnum, -1);
			} else if (ailp->mode != AIM_STILL) {
				int	r;

				r = openable_doors_in_segment(obj);
				if (r != -1) {
					ailp->mode = AIM_OPEN_DOOR;
					aip->GOALSIDE = r;
				} else if (ailp->mode != AIM_FOLLOW_PATH) {
					if (!ai_multiplayer_awareness(obj, 50))
						return;
					create_n_segment_path_to_door(obj, 8+Difficulty_level, -1);		//	third parameter is avoid_seg, -1 means avoid nothing.
					ai_multi_send_robot_position(objnum, -1);
				}
			} else {
				compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
				if (player_visibility) {
					if (!ai_multiplayer_awareness(obj, 50))
						return;
					create_n_segment_path_to_door(obj, 8+Difficulty_level, -1);		//	third parameter is avoid_seg, -1 means avoid nothing.
					ai_multi_send_robot_position(objnum, -1);
				}
			}
			break;
		default:
			break;
	}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	switch (ailp->mode) {
		case AIM_CHASE_OBJECT: {		// chasing player, sort of, chase if far, back off if close, circle in between
			fix	circle_distance;

			circle_distance = robptr->circle_distance[Difficulty_level] + ConsoleObject->size;
			//	Green guy doesn't get his circle distance boosted, else he might never attack.
			if (robptr->attack_type != 1)
				circle_distance += (objnum&0xf) * F1_0/2;

			compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

			//	@mk, 12/27/94, structure here was strange.  Would do both clauses of what are now this if/then/else.  Used to be if/then, if/then.
			if ((player_visibility < 2) && (previous_visibility == 2)) { // this is redundant: mk, 01/15/95: && (ailp->mode == AIM_CHASE_OBJECT)) {
				// mprintf((0, "I used to be able to see the player!\n"));
				if (!ai_multiplayer_awareness(obj, 53)) {
					if (maybe_ai_do_actual_firing_stuff(obj, aip))
						ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
					return;
				}
				// -- mprintf((0, "(3) Object #%i going from chase to player path in frame %i.\n", objnum, FrameCount));
				create_path_to_player(obj, 8, 1);
				// -- show_path_and_other(obj);
				ai_multi_send_robot_position(objnum, -1);
			} else if ((player_visibility == 0) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
				//	If pretty far from the player, player cannot be seen (obstructed) and in chase mode, switch to follow path mode.
				//	This has one desirable benefit of avoiding physics retries.
				if (aip->behavior == AIB_STATION) {
					ailp->goal_segment = aip->hide_segment;
					// -- mprintf((0, "(1) Object #%i going from chase to STATION in frame %i.\n", objnum, FrameCount));
					create_path_to_station(obj, 15);
					// -- show_path_and_other(obj);
				} else
					create_n_segment_path(obj, 5, -1);
				break;
			}

			if ((aip->CURRENT_STATE == AIS_REST) && (aip->GOAL_STATE == AIS_REST)) {
				if (player_visibility) {
					if (rand() < FrameTime*player_visibility) {
						if (dist_to_player/256 < rand()*player_visibility) {
							// mprintf((0, "Object %i searching for player.\n", obj-Objects));
							aip->GOAL_STATE = AIS_SRCH;
							aip->CURRENT_STATE = AIS_SRCH;
						}
					}
				}
			}

			if (GameTime - ailp->time_player_seen > CHASE_TIME_LENGTH) {

				if (Game_mode & GM_MULTI)
					if (!player_visibility && (dist_to_player > F1_0*70)) {
						ailp->mode = AIM_STILL;
						return;
					}

				if (!ai_multiplayer_awareness(obj, 64)) {
					if (maybe_ai_do_actual_firing_stuff(obj, aip))
						ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
					return;
				}
				// -- mprintf((0, "(4) Object #%i going from chase to player path in frame %i.\n", objnum, FrameCount));
				create_path_to_player(obj, 10, 1);
				// -- show_path_and_other(obj);
				ai_multi_send_robot_position(objnum, -1);
			} else if ((aip->CURRENT_STATE != AIS_REST) && (aip->GOAL_STATE != AIS_REST)) {
				if (!ai_multiplayer_awareness(obj, 70)) {
					if (maybe_ai_do_actual_firing_stuff(obj, aip))
						ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
					return;
				}
				ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, circle_distance, 0);

				if ((obj_ref & 1) && ((aip->GOAL_STATE == AIS_SRCH) || (aip->GOAL_STATE == AIS_LOCK))) {
					if (player_visibility) // == 2)
						ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
					else
						ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility);
				}

				if (ai_evaded) {
					ai_multi_send_robot_position(objnum, 1);
					ai_evaded = 0;
				}
				else 
					ai_multi_send_robot_position(objnum, -1);
				
				do_firing_stuff(obj, player_visibility, &vec_to_player);
			}
			break;
		}

		case AIM_RUN_FROM_OBJECT:
			compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

			if (player_visibility) {
				if (ailp->player_awareness_type == 0)
					ailp->player_awareness_type = PA_WEAPON_ROBOT_COLLISION;

			}

			//	If in multiplayer, only do if player visible.  If not multiplayer, do always.
			if (!(Game_mode & GM_MULTI) || player_visibility)
				if (ai_multiplayer_awareness(obj, 75)) {
					ai_follow_path(obj, player_visibility);
					ai_multi_send_robot_position(objnum, -1);
				}

			if (aip->GOAL_STATE != AIS_FLIN)
				aip->GOAL_STATE = AIS_LOCK;
			else if (aip->CURRENT_STATE == AIS_FLIN)
				aip->GOAL_STATE = AIS_LOCK;

			//	Bad to let run_from robot fire at player because it will cause a war in which it turns towards the
			//	player to fire and then towards its goal to move.
			// do_firing_stuff(obj, player_visibility, &vec_to_player);
			//	Instead, do this:
			//	(Note, only drop if player is visible.  This prevents the bombs from being a giveaway, and
			//	also ensures that the robot is moving while it is dropping.  Also means fewer will be dropped.)
			if ((ailp->next_fire <= 0) && (player_visibility)) {
				vms_vector	fire_vec, fire_pos;

				if (!ai_multiplayer_awareness(obj, 75))
					return;

				fire_vec = obj->orient.fvec;
				vm_vec_negate(&fire_vec);
				vm_vec_add(&fire_pos, &obj->pos, &fire_vec);

				Laser_create_new_easy( &fire_vec, &fire_pos, obj-Objects, PROXIMITY_ID, 1);
				ailp->next_fire = F1_0*5;		//	Drop a proximity bomb every 5 seconds.
				
				#ifdef NETWORK
				if (Game_mode & GM_MULTI)
				{
					ai_multi_send_robot_position(obj-Objects, -1);
					multi_send_robot_fire(obj-Objects, -1, &fire_vec);
				}				  
				#endif	
			}
			break;

		case AIM_FOLLOW_PATH: {
			int	anger_level = 65;

			if (aip->behavior == AIB_STATION)
				if (Point_segs[aip->hide_index + aip->path_length - 1].segnum == aip->hide_segment) {
					anger_level = 64;
					// mprintf((0, "Object %i, station, lowering anger to 64.\n"));
				}

			compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

			if (Game_mode & (GM_MODEM | GM_SERIAL))
				if (!player_visibility && (dist_to_player > F1_0*70)) {
					ailp->mode = AIM_STILL;
					return;
				}

			if (!ai_multiplayer_awareness(obj, anger_level)) {
				if (maybe_ai_do_actual_firing_stuff(obj, aip)) {
					compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
					ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
				}
				return;
			}

			ai_follow_path(obj, player_visibility);

			if (aip->GOAL_STATE != AIS_FLIN)
				aip->GOAL_STATE = AIS_LOCK;
			else if (aip->CURRENT_STATE == AIS_FLIN)
				aip->GOAL_STATE = AIS_LOCK;

			if ((aip->behavior != AIB_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM))
				do_firing_stuff(obj, player_visibility, &vec_to_player);

			if ((player_visibility == 2) && (aip->behavior != AIB_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM) && (obj->id != ROBOT_BRAIN)) {
				if (robptr->attack_type == 0)
					ailp->mode = AIM_CHASE_OBJECT;
			} else if ((player_visibility == 0) && (aip->behavior == AIB_NORMAL) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM)) {
				ailp->mode = AIM_STILL;
				aip->hide_index = -1;
				aip->path_length = 0;
			}

			ai_multi_send_robot_position(objnum, -1);

			break;
		}

		case AIM_HIDE:
			if (!ai_multiplayer_awareness(obj, 71)) {
				if (maybe_ai_do_actual_firing_stuff(obj, aip)) {
					compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
					ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
				}
				return;
			}

			compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

 			ai_follow_path(obj, player_visibility);

			if (aip->GOAL_STATE != AIS_FLIN)
				aip->GOAL_STATE = AIS_LOCK;
			else if (aip->CURRENT_STATE == AIS_FLIN)
				aip->GOAL_STATE = AIS_LOCK;

			ai_multi_send_robot_position(objnum, -1);
			break;

		case AIM_STILL:
			if ((dist_to_player < F1_0*120+Difficulty_level*F1_0*20) || (ailp->player_awareness_type >= PA_WEAPON_ROBOT_COLLISION-1)) {
				compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

				//	turn towards vector if visible this time or last time, or rand
				// new!
				if ((player_visibility) || (previous_visibility) || ((rand() > 0x4000) && !(Game_mode & GM_MULTI))) {
					if (!ai_multiplayer_awareness(obj, 71)) {
						if (maybe_ai_do_actual_firing_stuff(obj, aip))
							ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
						return;
					}
					ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
					ai_multi_send_robot_position(objnum, -1);
				}

				do_firing_stuff(obj, player_visibility, &vec_to_player);
				//	This is debugging code!  Remove it!  It's to make the green guy attack without doing other kinds of movement.
				if (player_visibility) {		//	Change, MK, 01/03/94 for Multiplayer reasons.  If robots can't see you (even with eyes on back of head), then don't do evasion.
					if (robptr->attack_type == 1) {
						aip->behavior = AIB_NORMAL;
						if (!ai_multiplayer_awareness(obj, 80)) {
							if (maybe_ai_do_actual_firing_stuff(obj, aip))
								ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
							return;
						}
						ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 0);
						if (ai_evaded) {
							ai_multi_send_robot_position(objnum, 1);
							ai_evaded = 0;
						}
						else
							ai_multi_send_robot_position(objnum, -1);
					} else {
						//	Robots in hover mode are allowed to evade at half normal speed.
						if (!ai_multiplayer_awareness(obj, 81)) {
							if (maybe_ai_do_actual_firing_stuff(obj, aip))
								ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
							return;
						}
						ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 1);
						if (ai_evaded) {
							ai_multi_send_robot_position(objnum, -1);
							ai_evaded = 0;
						}
						else				
							ai_multi_send_robot_position(objnum, -1);
					}
				} else if ((obj->segnum != aip->hide_segment) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
					//	If pretty far from the player, player cannot be seen (obstructed) and in chase mode, switch to follow path mode.
					//	This has one desirable benefit of avoiding physics retries.
					if (aip->behavior == AIB_STATION) {
						ailp->goal_segment = aip->hide_segment;
						// -- mprintf((0, "(2) Object #%i going from STILL to STATION in frame %i.\n", objnum, FrameCount));
						create_path_to_station(obj, 15);
						// -- show_path_and_other(obj);
					}
					break;
				}
			}

			break;
		case AIM_OPEN_DOOR: {		// trying to open a door.
			vms_vector	center_point, goal_vector;
			Assert(obj->id == ROBOT_BRAIN);		//	Make sure this guy is allowed to be in this mode.

			if (!ai_multiplayer_awareness(obj, 62))
				return;
			compute_center_point_on_side(&center_point, &Segments[obj->segnum], aip->GOALSIDE);
			vm_vec_sub(&goal_vector, &center_point, &obj->pos);
			vm_vec_normalize_quick(&goal_vector);
			ai_turn_towards_vector(&goal_vector, obj, robptr->turn_time[Difficulty_level]);
			move_towards_vector(obj, &goal_vector);
			ai_multi_send_robot_position(objnum, -1);

			break;
		}

		default:
			mprintf((0, "Unknown mode = %i in robot %i, behavior = %i\n", ailp->mode, obj-Objects, aip->behavior));
			ailp->mode = AIM_CHASE_OBJECT;
			break;
	}		// end:	switch (ailp->mode) {

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	If the robot can see you, increase his awareness of you.
	//	This prevents the problem of a robot looking right at you but doing nothing.
	// Assert(player_visibility != -1);	//	Means it didn't get initialized!
	compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
	if (player_visibility == 2)
		if (ailp->player_awareness_type == 0)
			ailp->player_awareness_type = PA_PLAYER_COLLISION;

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	if (!object_animates) {
		aip->CURRENT_STATE = aip->GOAL_STATE;
		// mprintf((0, "Setting current to goal (%i) because object doesn't animate.\n", aip->GOAL_STATE));
	}

	Assert(ailp->player_awareness_type <= AIE_MAX);
	Assert(aip->CURRENT_STATE < AIS_MAX);
	Assert(aip->GOAL_STATE < AIS_MAX);

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	if (ailp->player_awareness_type) {
		new_goal_state = Ai_transition_table[ailp->player_awareness_type-1][aip->CURRENT_STATE][aip->GOAL_STATE];
		if (ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) {
			//	Decrease awareness, else this robot will flinch every frame.
			ailp->player_awareness_type--;
			ailp->player_awareness_time = F1_0*3;
		}

		if (new_goal_state == AIS_ERR_)
			new_goal_state = AIS_REST;

		if (aip->CURRENT_STATE == AIS_NONE)
			aip->CURRENT_STATE = AIS_REST;

		aip->GOAL_STATE = new_goal_state;

	}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	If new state = fire, then set all gun states to fire.
	if ((aip->GOAL_STATE == AIS_FIRE) ) {
		int	i,num_guns;
		num_guns = Robot_info[obj->id].n_guns;
		for (i=0; i<num_guns; i++)
			ailp->goal_state[i] = AIS_FIRE;
	}

	//	- -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
	//	Hack by mk on 01/04/94, if a guy hasn't animated to the firing state, but his next_fire says ok to fire, bash him there
	if ((ailp->next_fire < 0) && (aip->GOAL_STATE == AIS_FIRE))
		aip->CURRENT_STATE = AIS_FIRE;

	if ((aip->GOAL_STATE != AIS_FLIN)  && (obj->id != ROBOT_BRAIN)) {
		switch (aip->CURRENT_STATE) {
			case	AIS_NONE:
				compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

				dot = vm_vec_dot(&obj->orient.fvec, &vec_to_player);
				if (dot >= F1_0/2)
					if (aip->GOAL_STATE == AIS_REST)
						aip->GOAL_STATE = AIS_SRCH;
				//mprintf((0, "State = none, goal = %i.\n", aip->GOAL_STATE));
				break;
			case	AIS_REST:
				if (aip->GOAL_STATE == AIS_REST) {
					compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
					if ((ailp->next_fire <= 0) && (player_visibility)) {
						// mprintf((0, "Setting goal state to fire from rest.\n"));
						aip->GOAL_STATE = AIS_FIRE;
					}
					//mprintf((0, "State = rest, goal = %i.\n", aip->GOAL_STATE));
				}
				break;
			case	AIS_SRCH:
				if (!ai_multiplayer_awareness(obj, 60))
					return;

				compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

				if (player_visibility) {
					ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
					ai_multi_send_robot_position(objnum, -1);
				} else if (!(Game_mode & GM_MULTI))
					ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility);
				break;
			case	AIS_LOCK:
				compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

				if (!(Game_mode & GM_MULTI) || (player_visibility)) {
					if (!ai_multiplayer_awareness(obj, 68))
						return;

					if (player_visibility) {
						ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
						ai_multi_send_robot_position(objnum, -1);
					} else if (!(Game_mode & GM_MULTI))
						ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility);
				}
				break;
			case	AIS_FIRE:
				compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);

				if (player_visibility) {
					if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1))) 
					{
						if (Game_mode & GM_MULTI) {
							ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);
							return;
						}
					}
					ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
					ai_multi_send_robot_position(objnum, -1);
				} else if (!(Game_mode & GM_MULTI)) {
					ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility);
				}

				//	Fire at player, if appropriate.
				ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates);

				break;
			case	AIS_RECO:
				if (!(obj_ref & 3)) {
					compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
					if (player_visibility) {
						if (!ai_multiplayer_awareness(obj, 69))
							return;
						ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
						ai_multi_send_robot_position(objnum, -1);
					} else if (!(Game_mode & GM_MULTI)) {
						ai_turn_randomly(&vec_to_player, obj, robptr->turn_time[Difficulty_level], previous_visibility);
					}
				}
				break;
			case	AIS_FLIN:
				// mprintf((0, "State = flinch, goal = %i.\n", aip->GOAL_STATE));
				break;
			default:
				mprintf((1, "Unknown mode for AI object #%i\n", objnum));
				aip->GOAL_STATE = AIS_REST;
				aip->CURRENT_STATE = AIS_REST;
				break;
		}
	} // end of: if (aip->GOAL_STATE != AIS_FLIN) {

	// Switch to next gun for next fire.
	if (player_visibility == 0) {
		aip->CURRENT_GUN++;
		if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
			aip->CURRENT_GUN = 0;
	}

}

//--mk, 121094 -- // ----------------------------------------------------------------------------------
//--mk, 121094 -- void spin_robot(object *robot, vms_vector *collision_point)
//--mk, 121094 -- {
//--mk, 121094 -- 	if (collision_point->x != 3) {
//--mk, 121094 -- 		robot->phys_info.rotvel.x = 0x1235;
//--mk, 121094 -- 		robot->phys_info.rotvel.y = 0x2336;
//--mk, 121094 -- 		robot->phys_info.rotvel.z = 0x3737;
//--mk, 121094 -- 	}
//--mk, 121094 -- 
//--mk, 121094 -- }

//	-----------------------------------------------------------------------------------
void ai_do_cloak_stuff(void)
{
	int	i;

	for (i=0; i<MAX_AI_CLOAK_INFO; i++) {
		Ai_cloak_info[i].last_position = ConsoleObject->pos;
		Ai_cloak_info[i].last_time = GameTime;
	}

	//	Make work for control centers.
	Believed_player_pos = Ai_cloak_info[0].last_position;

}

//	-----------------------------------------------------------------------------------
//	Returns false if awareness is considered too puny to add, else returns true.
int add_awareness_event(object *objp, int type)
{
	//	If player cloaked and hit a robot, then increase awareness
	if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_WEAPON_WALL_COLLISION) || (type == PA_PLAYER_COLLISION))
		ai_do_cloak_stuff();

	if (Num_awareness_events < MAX_AWARENESS_EVENTS) {
		if ((type == PA_WEAPON_WALL_COLLISION) || (type == PA_WEAPON_ROBOT_COLLISION))
			if (objp->id == VULCAN_ID)
				if (rand() > 3276)
					return 0;		//	For vulcan cannon, only about 1/10 actually cause awareness

		Awareness_events[Num_awareness_events].segnum = objp->segnum;
		Awareness_events[Num_awareness_events].pos = objp->pos;
		Awareness_events[Num_awareness_events].type = type;
		Num_awareness_events++;
	} else
		Assert(0);		// Hey -- Overflowed Awareness_events, make more or something
							// This just gets ignored, so you can just continue.
	return 1;

}

// ----------------------------------------------------------------------------------
//	Robots will become aware of the player based on something that occurred.
//	The object (probably player or weapon) which created the awareness is objp.
void create_awareness_event(object *objp, int type)
{
	if (add_awareness_event(objp, type)) {
		if (((rand() * (type+4)) >> 15) > 4)
			Overall_agitation++;
		if (Overall_agitation > OVERALL_AGITATION_MAX)
			Overall_agitation = OVERALL_AGITATION_MAX;
	}
}

byte	New_awareness[MAX_SEGMENTS];

// ----------------------------------------------------------------------------------
void pae_aux(int segnum, int type, int level)
{
	int	j;

	if (New_awareness[segnum] < type)
		New_awareness[segnum] = type;

	// Process children.
	if (level <= 4)
		for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
			if (IS_CHILD(Segments[segnum].children[j]))
				if (type == 4)
					pae_aux(Segments[segnum].children[j], type-1, level+1);
				else
					pae_aux(Segments[segnum].children[j], type, level+1);
}


// ----------------------------------------------------------------------------------
void process_awareness_events(void)
{
	int	i;

	for (i=0; i<=Highest_segment_index; i++)
		New_awareness[i] = 0;

	for (i=0; i<Num_awareness_events; i++)
		pae_aux(Awareness_events[i].segnum, Awareness_events[i].type, 1);

	Num_awareness_events = 0;
}

// ----------------------------------------------------------------------------------
void set_player_awareness_all(void)
{
	int	i;

	process_awareness_events();

	for (i=0; i<=Highest_object_index; i++)
		if (Objects[i].control_type == CT_AI)
			if (New_awareness[Objects[i].segnum] > Ai_local_info[i].player_awareness_type) {
				Ai_local_info[i].player_awareness_type = New_awareness[Objects[i].segnum];
				Ai_local_info[i].player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
			}
}

#ifndef NDEBUG
int	Ai_dump_enable = 0;

FILE *Ai_dump_file = NULL;

char	Ai_error_message[128] = "";

// ----------------------------------------------------------------------------------
void dump_ai_objects_all()
{
#if PARALLAX
	int	objnum;
	int	total=0;
	time_t	time_of_day;

	time_of_day = time(NULL);

	if (!Ai_dump_enable)
		return;

	if (Ai_dump_file == NULL)
		Ai_dump_file = fopen("ai.out","a+t");

	fprintf(Ai_dump_file, "\nnum: seg distance __mode__ behav.    [velx vely velz] (Frame = %i)\n", FrameCount);
	fprintf(Ai_dump_file, "Date & Time = %s\n", ctime(&time_of_day));

	if (Ai_error_message[0])
		fprintf(Ai_dump_file, "Error message: %s\n", Ai_error_message);

	for (objnum=0; objnum <= Highest_object_index; objnum++) {
		object		*objp = &Objects[objnum];
		ai_static	*aip = &objp->ctype.ai_info;
		ai_local		*ailp = &Ai_local_info[objnum];
		fix			dist_to_player;

		dist_to_player = vm_vec_dist(&objp->pos, &ConsoleObject->pos);

		if (objp->control_type == CT_AI) {
			fprintf(Ai_dump_file, "%3i: %3i %8.3f %8s %8s [%3i %4i]\n",
				objnum, objp->segnum, f2fl(dist_to_player), mode_text[ailp->mode], behavior_text[aip->behavior-0x80], aip->hide_index, aip->path_length);
			if (aip->path_length)
				total += aip->path_length;
		}
	}

	fprintf(Ai_dump_file, "Total path length = %4i\n", total);
#endif

}

// ----------------------------------------------------------------------------------
void force_dump_ai_objects_all(char *msg)
{
	int	tsave;

	tsave = Ai_dump_enable;

	Ai_dump_enable = 1;

	sprintf(Ai_error_message, "%s\n", msg);
	dump_ai_objects_all();
	Ai_error_message[0] = 0;

	Ai_dump_enable = tsave;
}

// ----------------------------------------------------------------------------------
void turn_off_ai_dump(void)
{
	if (Ai_dump_file != NULL)
		fclose(Ai_dump_file);

	Ai_dump_file = NULL;
}

#endif

// ----------------------------------------------------------------------------------
//	Do things which need to get done for all AI objects each frame.
//	This includes:
//		Setting player_awareness (a fix, time in seconds which object is aware of player)
void do_ai_frame_all(void)
{
#ifndef NDEBUG
	dump_ai_objects_all();
#endif

	set_player_awareness_all();

//	if ((FrameCount & 0x07) == 0)
//		mprintf((0, "[%i] ", Overall_agitation));
}

//--unused-- //	----------------------------------------------------------------------------------
//--unused-- //	Reset various state information for a new life.
//--unused-- //	Probably not going to be called for a new game because for that an entire object
//--unused-- //	gets reloaded.
//--unused-- void reset_ai_states(object *objp)
//--unused-- {
//--unused-- 	int		i;
//--unused-- 	//ai_static	*aip = &objp->ctype.ai_info;
//--unused-- 	ai_local		*ailp = &Ai_local_info[objp-Objects];
//--unused-- 
//--unused-- 	ailp->wait_time = 0;
//--unused-- 	ailp->next_fire = 0;
//--unused-- 	ailp->player_awareness_type = 0;
//--unused-- 	for (i=0; i<MAX_SUBMODELS; i++) {
//--unused-- 		ailp->goal_angles[i].p = 0;		ailp->goal_angles[i].b = 0;		ailp->goal_angles[i].h = 0;
//--unused-- 		ailp->delta_angles[i].p = 0;	ailp->delta_angles[i].b = 0;	ailp->delta_angles[i].h = 0;
//--unused-- 		ailp->goal_state[i] = 0;
//--unused-- 		ailp->achieved_state[i] = 0;
//--unused-- 	}
//--unused-- }

//	Initializations to be performed for all robots for a new level.
void init_robots_for_level(void)
{
	Overall_agitation = 0;
}

int ai_save_state( FILE * fp )
{
	fwrite( &Ai_initialized, sizeof(int), 1, fp );
	fwrite( &Overall_agitation, sizeof(int), 1, fp );
	fwrite( Ai_local_info, sizeof(ai_local)*MAX_OBJECTS, 1, fp );
	fwrite( Point_segs, sizeof(point_seg)*MAX_POINT_SEGS, 1, fp );
	fwrite( Ai_cloak_info, sizeof(ai_cloak_info)*MAX_AI_CLOAK_INFO, 1, fp );
	fwrite( &Boss_cloak_start_time, sizeof(fix), 1, fp );
	fwrite( &Boss_cloak_end_time , sizeof(fix), 1, fp );
	fwrite( &Last_teleport_time , sizeof(fix), 1, fp );
	fwrite( &Boss_teleport_interval, sizeof(fix), 1, fp );
	fwrite( &Boss_cloak_interval, sizeof(fix), 1, fp );
	fwrite( &Boss_cloak_duration, sizeof(fix), 1, fp );
	fwrite( &Last_gate_time, sizeof(fix), 1, fp );
	fwrite( &Gate_interval, sizeof(fix), 1, fp );
	fwrite( &Boss_dying_start_time, sizeof(fix), 1, fp );
	fwrite( &Boss_dying, sizeof(int), 1, fp );
	fwrite( &Boss_dying_sound_playing, sizeof(int), 1, fp );
	fwrite( &Boss_hit_this_frame, sizeof(int), 1, fp );
	fwrite( &Boss_been_hit, sizeof(int), 1, fp );
	return 1;
}

int ai_restore_state( FILE * fp )
{
	fread( &Ai_initialized, sizeof(int), 1, fp );
	fread( &Overall_agitation, sizeof(int), 1, fp );
	fread( Ai_local_info, sizeof(ai_local)*MAX_OBJECTS, 1, fp );
	fread( Point_segs, sizeof(point_seg)*MAX_POINT_SEGS, 1, fp );
	fread( Ai_cloak_info, sizeof(ai_cloak_info)*MAX_AI_CLOAK_INFO, 1, fp );
	fread( &Boss_cloak_start_time, sizeof(fix), 1, fp );
	fread( &Boss_cloak_end_time , sizeof(fix), 1, fp );
	fread( &Last_teleport_time , sizeof(fix), 1, fp );
	fread( &Boss_teleport_interval, sizeof(fix), 1, fp );
	fread( &Boss_cloak_interval, sizeof(fix), 1, fp );
	fread( &Boss_cloak_duration, sizeof(fix), 1, fp );
	fread( &Last_gate_time, sizeof(fix), 1, fp );
	fread( &Gate_interval, sizeof(fix), 1, fp );
	fread( &Boss_dying_start_time, sizeof(fix), 1, fp );
	fread( &Boss_dying, sizeof(int), 1, fp );
	fread( &Boss_dying_sound_playing, sizeof(int), 1, fp );
	fread( &Boss_hit_this_frame, sizeof(int), 1, fp );
	fread( &Boss_been_hit, sizeof(int), 1, fp );
	return 1;
}

// -- void show_path_and_other(object *objp )
// -- {
// -- 	int			i;
// -- 	ai_static	*aip = &objp->ctype.ai_info;

// -- 	for (i=0; i<aip->path_length; i++)
// -- 		mprintf((0, "%2i ", Point_segs[aip->hide_index+i].segnum));
// -- 	mprintf((0, "[pl: %i cur: st: %i]\n", ConsoleObject->segnum, objp->segnum, aip->hide_segment));

// -- }


