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