3902 lines
133 KiB
C
3902 lines
133 KiB
C
![]() |
/*
|
||
|
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: /cvsroot/dxx-rebirth/d1x-rebirth/main/ai.c,v $
|
||
|
* $Revision: 1.1.1.1 $
|
||
|
* $Author: zicodxx $
|
||
|
* $Date: 2006/03/17 19:41:40 $
|
||
|
*
|
||
|
* Autonomous Individual movement.
|
||
|
*
|
||
|
* $Log: ai.c,v $
|
||
|
* Revision 1.1.1.1 2006/03/17 19:41:40 zicodxx
|
||
|
* initial import
|
||
|
*
|
||
|
* Revision 1.1.1.1 1999/06/14 22:05:03 donut
|
||
|
* Import of d1x 1.37 source.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#ifdef RCS
|
||
|
static char rcsid[] = "$Id: ai.c,v 1.1.1.1 2006/03/17 19:41:40 zicodxx Exp $";
|
||
|
#endif
|
||
|
|
||
|
#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"
|
||
|
#ifdef __MSDOS__
|
||
|
#include <time.h>
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#include "d_gamecv.h"
|
||
|
//added 05/17/99 Matt Mueller
|
||
|
#include "u_mem.h"
|
||
|
//end addition -MM
|
||
|
|
||
|
void init_boss_segments(short segptr[], int *num_segs, int size_check);
|
||
|
void ai_multi_send_robot_position(int objnum, int force);
|
||
|
|
||
|
#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_message(MSGC_GAME_FEEDBACK, TXT_NO_LUNACY );
|
||
|
} else {
|
||
|
do_lunacy_on();
|
||
|
hud_message(MSGC_GAME_FEEDBACK, 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 (d_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 (d_rand() > 0x7400) {
|
||
|
ai_turn_towards_vector(vec_to_player, obj, rate);
|
||
|
return;
|
||
|
}
|
||
|
//--debug-- if (d_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_message(MSGC_GAME_FEEDBACK, TXT_ROBOT_PAINTING_OFF );
|
||
|
} else {
|
||
|
hud_message(MSGC_GAME_FEEDBACK, 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));
|
||
|
}
|
||
|
#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 (d_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 + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
|
||
|
bpp_diff.y = Believed_player_pos.y + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
|
||
|
bpp_diff.z = Believed_player_pos.z + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
|
||
|
|
||
|
// Half the time fire at the player, half the time lead the player.
|
||
|
if (d_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 (d_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 = (d_rand() - 16384) | 1; // make sure we don't create null vector
|
||
|
vec->y = d_rand() - 16384;
|
||
|
vec->z = d_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 + (d_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 + d_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 + (d_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 = (d_rand() * 6) >> 15;
|
||
|
|
||
|
while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG))
|
||
|
sidenum = (d_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[(d_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 = (d_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 (d_rand() < FrameTime*16)
|
||
|
create_small_fireball_on_object(objp, (F1_0 + d_rand()) * 8, 0);
|
||
|
} else if (d_rand() < FrameTime*8)
|
||
|
create_small_fireball_on_object(objp, (F1_0/2 + d_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)
|
||
|
{
|
||
|
#ifdef NETWORK
|
||
|
static int eclip_state = 0;
|
||
|
#endif
|
||
|
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 = (d_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) && (d_rand() < FrameTime/4)) {
|
||
|
if (d_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) && (d_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 (d_rand() < FrameTime*player_visibility) {
|
||
|
if (dist_to_player/256 < d_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
|
||
|
#ifndef SHAREWARE
|
||
|
if (Game_mode & GM_MULTI)
|
||
|
{
|
||
|
ai_multi_send_robot_position(obj-Objects, -1);
|
||
|
multi_send_robot_fire(obj-Objects, -1, &fire_vec);
|
||
|
}
|
||
|
#endif
|
||
|
#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) || ((d_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(¢er_point, &Segments[obj->segnum], aip->GOALSIDE);
|
||
|
vm_vec_sub(&goal_vector, ¢er_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 (d_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 (((d_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 )
|
||
|
{
|
||
|
char buffer[1024]; // That should be enough for us...
|
||
|
char *ptr=buffer;
|
||
|
int i;
|
||
|
|
||
|
d_export_int(buffer, Ai_initialized);
|
||
|
d_export_int(buffer+SIZEOF_INT, Overall_agitation);
|
||
|
fwrite(buffer, SIZEOF_INT, 2, fp);
|
||
|
|
||
|
for (i=0; i<MAX_OBJECTS; i++)
|
||
|
{
|
||
|
d_export_ai_local(buffer, &Ai_local_info[i]);
|
||
|
fwrite(buffer, SIZEOF_AI_LOCAL, 1, fp);
|
||
|
}
|
||
|
for (i=0; i<MAX_POINT_SEGS; i++)
|
||
|
{
|
||
|
d_export_point_seg(buffer, &Point_segs[i]);
|
||
|
fwrite(buffer, SIZEOF_POINT_SEG, 1, fp);
|
||
|
}
|
||
|
for (i=0; i<MAX_AI_CLOAK_INFO; i++)
|
||
|
{
|
||
|
d_export_fix(buffer,Ai_cloak_info[i].last_time);
|
||
|
d_export_vms_vector(buffer + SIZEOF_FIX, &Ai_cloak_info[i].last_position);
|
||
|
fwrite(buffer, SIZEOF_FIX + SIZEOF_VMS_VECTOR, 1, fp);
|
||
|
}
|
||
|
d_export_fix(ptr, Boss_cloak_start_time); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Boss_cloak_end_time); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Last_teleport_time); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Boss_teleport_interval); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Boss_cloak_interval); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Boss_cloak_duration); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Last_gate_time); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Gate_interval); ptr+=SIZEOF_FIX;
|
||
|
d_export_fix(ptr, Boss_dying_start_time); ptr+=SIZEOF_FIX;
|
||
|
d_export_int(ptr, Boss_dying); ptr+=SIZEOF_INT;
|
||
|
d_export_int(ptr, Boss_dying_sound_playing); ptr+=SIZEOF_INT;
|
||
|
d_export_int(ptr, Boss_hit_this_frame); ptr+=SIZEOF_INT;
|
||
|
d_export_int(ptr, Boss_been_hit);
|
||
|
fwrite(buffer, (SIZEOF_FIX * 9) + (SIZEOF_INT * 4), 1, fp);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int ai_restore_state( FILE * fp )
|
||
|
{
|
||
|
// Enough storage space to last us.
|
||
|
char buffer[1024];
|
||
|
char *ptr=buffer;
|
||
|
int i;
|
||
|
|
||
|
fread(buffer,SIZEOF_INT,2,fp);
|
||
|
Ai_initialized = d_import_int(buffer);
|
||
|
Overall_agitation = d_import_int(buffer+SIZEOF_INT);
|
||
|
|
||
|
for (i=0; i<MAX_OBJECTS; i++)
|
||
|
{
|
||
|
fread(buffer, SIZEOF_AI_LOCAL, 1, fp);
|
||
|
d_import_ai_local(&Ai_local_info[i], buffer);
|
||
|
}
|
||
|
|
||
|
for (i=0; i<MAX_POINT_SEGS; i++)
|
||
|
{
|
||
|
fread(buffer, SIZEOF_POINT_SEG, 1, fp);
|
||
|
d_import_point_seg(&Point_segs[i], buffer);
|
||
|
}
|
||
|
for (i=0; i<MAX_AI_CLOAK_INFO; i++)
|
||
|
{
|
||
|
// This struct is local to ai.c, so we handle it internally here.
|
||
|
// It is only ever read here, and only ever stored above.
|
||
|
fread(buffer, SIZEOF_FIX + SIZEOF_VMS_VECTOR, 1, fp);
|
||
|
Ai_cloak_info[i].last_time = d_import_fix(buffer);
|
||
|
d_import_vms_vector(&Ai_cloak_info[i].last_position, buffer + SIZEOF_FIX);
|
||
|
}
|
||
|
|
||
|
fread(buffer,SIZEOF_FIX,9,fp);
|
||
|
Boss_cloak_start_time = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Boss_cloak_end_time = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Last_teleport_time = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Boss_teleport_interval = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Boss_cloak_interval = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Boss_cloak_duration = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Last_gate_time = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Gate_interval = d_import_fix(ptr); ptr += SIZEOF_FIX;
|
||
|
Boss_dying_start_time = d_import_fix(ptr); ptr = buffer;
|
||
|
|
||
|
fread(buffer,SIZEOF_INT,4,fp);
|
||
|
Boss_dying = d_import_int(ptr); ptr+=SIZEOF_INT;
|
||
|
Boss_dying_sound_playing = d_import_int(ptr); ptr+=SIZEOF_INT;
|
||
|
Boss_hit_this_frame = d_import_int(ptr); ptr+=SIZEOF_INT;
|
||
|
Boss_been_hit = d_import_int(ptr);
|
||
|
|
||
|
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));
|
||
|
|
||
|
// -- }
|
||
|
|