1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6 
7 This file is part of the OpenJK source code.
8 
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22 
23 #include "g_headers.h"
24 
25 #include "b_local.h"
26 
27 
28 #define	MIN_MELEE_RANGE		640
29 #define	MIN_MELEE_RANGE_SQR	( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
30 
31 #define MIN_DISTANCE		128
32 #define MIN_DISTANCE_SQR	( MIN_DISTANCE * MIN_DISTANCE )
33 
34 #define TURN_OFF			0x00000100//G2SURFACEFLAG_NODESCENDANTS
35 
36 #define LEFT_ARM_HEALTH 40
37 #define RIGHT_ARM_HEALTH 40
38 
39 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
40 /*
41 -------------------------
42 NPC_ATST_Precache
43 -------------------------
44 */
NPC_ATST_Precache(void)45 void NPC_ATST_Precache(void)
46 {
47 	G_SoundIndex( "sound/chars/atst/atst_damaged1" );
48 	G_SoundIndex( "sound/chars/atst/atst_damaged2" );
49 
50 	RegisterItem( FindItemForWeapon( WP_ATST_MAIN ));	//precache the weapon
51 	RegisterItem( FindItemForWeapon( WP_BOWCASTER ));	//precache the weapon
52 	RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER ));	//precache the weapon
53 
54 	G_EffectIndex( "env/med_explode2" );
55 //	G_EffectIndex( "smaller_chunks" );
56 	G_EffectIndex( "blaster/smoke_bolton" );
57 	G_EffectIndex( "droidexplosion1" );
58 }
59 
60 //-----------------------------------------------------------------
ATST_PlayEffect(gentity_t * self,const int boltID,const char * fx)61 static void ATST_PlayEffect( gentity_t *self, const int boltID, const char *fx )
62 {
63 	if ( boltID >=0 && fx && fx[0] )
64 	{
65 		mdxaBone_t	boltMatrix;
66 		vec3_t		org, dir;
67 
68 		gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
69 					boltID,
70 					&boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time),
71 					NULL, self->s.modelScale );
72 
73 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
74 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
75 
76 		G_PlayEffect( fx, org, dir );
77 	}
78 }
79 
80 /*
81 -------------------------
82 G_ATSTCheckPain
83 
84 Called by NPC's and player in an ATST
85 -------------------------
86 */
87 
G_ATSTCheckPain(gentity_t * self,gentity_t * other,vec3_t point,int damage,int mod,int hitLoc)88 void G_ATSTCheckPain( gentity_t *self, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc )
89 {
90 	int newBolt;
91 
92 	if ( rand() & 1 )
93 	{
94 		G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged1" );
95 	}
96 	else
97 	{
98 		G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged2" );
99 	}
100 
101 	if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH))
102 	{
103 		if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH)	// Blow it up?
104 		{
105 			newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash3" );
106 			if ( newBolt != -1 )
107 			{
108 //				G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt1, self->s.number);
109 				ATST_PlayEffect( self, self->genericBolt1, "env/med_explode2" );
110 				G_PlayEffect( "blaster/smoke_bolton", self->playerModel, newBolt, self->s.number);
111 			}
112 
113 			gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_light_blaster_cann", TURN_OFF );
114 		}
115 	}
116 	else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH))	// Blow it up?
117 	{
118 		if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH)
119 		{
120 			newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash4" );
121 			if ( newBolt != -1 )
122 			{
123 //				G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number);
124 				ATST_PlayEffect( self, self->genericBolt2, "env/med_explode2" );
125 				G_PlayEffect( "blaster/smoke_bolton", self->playerModel, newBolt, self->s.number);
126 			}
127 
128 			gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_concussion_charger", TURN_OFF );
129 		}
130 	}
131 }
132 /*
133 -------------------------
134 NPC_ATST_Pain
135 -------------------------
136 */
NPC_ATST_Pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,vec3_t point,int damage,int mod,int hitLoc)137 void NPC_ATST_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc )
138 {
139 	G_ATSTCheckPain( self, other, point, damage, mod, hitLoc );
140 	NPC_Pain( self, inflictor, other, point, damage, mod );
141 }
142 
143 /*
144 -------------------------
145 ATST_Hunt
146 -------------------------`
147 */
ATST_Hunt(qboolean visible,qboolean advance)148 void ATST_Hunt( qboolean visible, qboolean advance )
149 {
150 
151 	if ( NPCInfo->goalEntity == NULL )
152 	{//hunt
153 		NPCInfo->goalEntity = NPC->enemy;
154 	}
155 
156 	NPCInfo->combatMove = qtrue;
157 
158 	NPC_MoveToGoal( qtrue );
159 
160 }
161 
162 /*
163 -------------------------
164 ATST_Ranged
165 -------------------------
166 */
ATST_Ranged(qboolean visible,qboolean advance,qboolean altAttack)167 void ATST_Ranged( qboolean visible, qboolean advance, qboolean altAttack )
168 {
169 
170 	if ( TIMER_Done( NPC, "atkDelay" ) && visible )	// Attack?
171 	{
172 		TIMER_Set( NPC, "atkDelay", Q_irand( 500, 3000 ) );
173 
174 		if (altAttack)
175 		{
176 			ucmd.buttons |= BUTTON_ATTACK|BUTTON_ALT_ATTACK;
177 		}
178 		else
179 		{
180 			ucmd.buttons |= BUTTON_ATTACK;
181 		}
182 	}
183 
184 	if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
185 	{
186 		ATST_Hunt( visible, advance );
187 	}
188 }
189 
190 /*
191 -------------------------
192 ATST_Attack
193 -------------------------
194 */
ATST_Attack(void)195 void ATST_Attack( void )
196 {
197 	qboolean	altAttack=qfalse;
198 	int			blasterTest,chargerTest,weapon;
199 
200 	if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )//
201 	{
202 		NPC->enemy = NULL;
203 		return;
204 	}
205 
206 	NPC_FaceEnemy( qtrue );
207 
208 	// Rate our distance to the target, and our visibilty
209 	float		distance	= (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
210 	distance_e	distRate	= ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE;
211 	qboolean	visible		= NPC_ClearLOS( NPC->enemy );
212 	qboolean	advance		= (qboolean)(distance > MIN_DISTANCE_SQR);
213 
214 	// If we cannot see our target, move to see it
215 	if ( visible == qfalse )
216 	{
217 		if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
218 		{
219 			ATST_Hunt( visible, advance );
220 			return;
221 		}
222 	}
223 
224 	// Decide what type of attack to do
225 	switch ( distRate )
226 	{
227 	case DIST_MELEE:
228 		NPC_ChangeWeapon( WP_ATST_MAIN );
229 		break;
230 
231 	case DIST_LONG:
232 
233 		NPC_ChangeWeapon( WP_ATST_SIDE );
234 
235 		// See if the side weapons are there
236 		blasterTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "head_light_blaster_cann" );
237 		chargerTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "head_concussion_charger" );
238 
239 		// It has both side weapons
240 		if (!(blasterTest & TURN_OFF)  && !(chargerTest & TURN_OFF))
241 		{
242 			weapon = Q_irand( 0, 1);	// 0 is blaster, 1 is charger (ALT SIDE)
243 
244 			if (weapon)				// Fire charger
245 			{
246 				altAttack = qtrue;
247 			}
248 			else
249 			{
250 				altAttack = qfalse;
251 			}
252 
253 		}
254 		else if (!(blasterTest & TURN_OFF))	// Blaster is on
255 		{
256 			altAttack = qfalse;
257 		}
258 		else if (!(chargerTest & TURN_OFF))	// Blaster is on
259 		{
260 			altAttack = qtrue;
261 		}
262 		else
263 		{
264 			NPC_ChangeWeapon( WP_NONE );
265 		}
266 		break;
267 	}
268 
269 	NPC_FaceEnemy( qtrue );
270 
271 	ATST_Ranged( visible, advance,altAttack );
272 }
273 
274 /*
275 -------------------------
276 ATST_Patrol
277 -------------------------
278 */
ATST_Patrol(void)279 void ATST_Patrol( void )
280 {
281 	if ( NPC_CheckPlayerTeamStealth() )
282 	{
283 		NPC_UpdateAngles( qtrue, qtrue );
284 		return;
285 	}
286 
287 	//If we have somewhere to go, then do that
288 	if (!NPC->enemy)
289 	{
290 		if ( UpdateGoal() )
291 		{
292 			ucmd.buttons |= BUTTON_WALKING;
293 			NPC_MoveToGoal( qtrue );
294 			NPC_UpdateAngles( qtrue, qtrue );
295 		}
296 	}
297 
298 }
299 
300 /*
301 -------------------------
302 ATST_Idle
303 -------------------------
304 */
ATST_Idle(void)305 void ATST_Idle( void )
306 {
307 
308 	NPC_BSIdle();
309 
310 	NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL );
311 }
312 
313 /*
314 -------------------------
315 NPC_BSDroid_Default
316 -------------------------
317 */
NPC_BSATST_Default(void)318 void NPC_BSATST_Default( void )
319 {
320 	if ( NPC->enemy )
321 	{
322 		if( (NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )
323 		{
324 			NPCInfo->goalEntity = NPC->enemy;
325 		}
326 		ATST_Attack();
327 	}
328 	else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
329 	{
330 		ATST_Patrol();
331 	}
332 	else
333 	{
334 		ATST_Idle();
335 	}
336 }
337