]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/monster/soldier.qc
Replace cloaked soldier with a familiar marine
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / monster / soldier.qc
1 // size
2 const vector SOLDIER_MIN = '-16 -16 -30';
3 const vector SOLDIER_MAX = '16 16 32';
4
5 // cvars
6 float autocvar_g_monster_soldier;
7 float autocvar_g_monster_soldier_health;
8 float autocvar_g_monster_soldier_melee_damage;
9 float autocvar_g_monster_soldier_speed_walk;
10 float autocvar_g_monster_soldier_speed_run;
11 float autocvar_g_monster_soldier_ammo;
12 float autocvar_g_monster_soldier_weapon_laser_chance;
13 float autocvar_g_monster_soldier_weapon_shotgun_chance;
14 float autocvar_g_monster_soldier_weapon_machinegun_chance;
15 float autocvar_g_monster_soldier_weapon_rocketlauncher_chance;
16 float autocvar_g_monster_soldier_attack_uzi_bullets;
17
18 // animations
19 #define soldier_anim_die1                       0
20 #define soldier_anim_die2                       1
21 #define soldier_anim_draw                       2
22 #define soldier_anim_duck                       3
23 #define soldier_anim_duckwalk           4
24 #define soldier_anim_duckjump           5
25 #define soldier_anim_duckidle           6
26 #define soldier_anim_idle                       7
27 #define soldier_anim_jump                       8
28 #define soldier_anim_pain1                      9
29 #define soldier_anim_pain2                      10
30 #define soldier_anim_shoot                      11
31 #define soldier_anim_taunt                      12
32 #define soldier_anim_run                        13
33 #define soldier_anim_runbackwards       14
34 #define soldier_anim_strafeleft         15
35 #define soldier_anim_straferight        16
36 #define soldier_anim_dead1                      17
37 #define soldier_anim_dead2                      18
38 #define soldier_anim_forwardright       19
39 #define soldier_anim_forwardleft        20
40 #define soldier_anim_backright          21
41 #define soldier_anim_backleft           22
42
43 //#define soldier_anim_stand    0
44 //#define soldier_anim_death1 1
45 //#define soldier_anim_death2 2
46 //#define soldier_anim_reload 3
47 //#define soldier_anim_pain1    4
48 //#define soldier_anim_pain2    5
49 //#define soldier_anim_pain3    6
50 //#define soldier_anim_run      7
51 //#define soldier_anim_shoot    8
52 //#define soldier_anim_prowl    9
53
54 void soldier_think ()
55 {
56         self.think = soldier_think;
57         self.nextthink = time + 0.1;
58         
59         if(self.delay != -1)
60                 self.nextthink = self.delay;
61         
62         if(time < self.attack_finished_single)
63                 monster_move(0, 0, 0, soldier_anim_shoot, soldier_anim_shoot, soldier_anim_shoot);
64         else
65                 monster_move(autocvar_g_monster_soldier_speed_run, autocvar_g_monster_soldier_speed_walk, 50, soldier_anim_run, soldier_anim_run, soldier_anim_idle);
66 }
67
68 void soldier_reload ()
69 {
70         self.frame = soldier_anim_draw;
71         self.attack_finished_single = time + 2;
72         self.currentammo = autocvar_g_monster_soldier_ammo;
73         sound (self, CH_SHOTS, "weapons/reload.wav", VOL_BASE, ATTN_LARGE);
74 }
75
76 float SoldierCheckAttack ()
77 {
78         local vector spot1 = '0 0 0', spot2 = '0 0 0';
79         local entity targ = self.enemy;
80         local float chance = 0;
81
82         if (self.health <= 0 || targ.health < 1 || targ == world)
83                 return FALSE;
84
85         if (vlen(targ.origin - self.origin) > 2000) // long traces are slow
86                 return FALSE;
87
88         // see if any entities are in the way of the shot
89         spot1 = self.origin + self.view_ofs;
90         spot2 = targ.origin + targ.view_ofs;
91
92         traceline (spot1, spot2, FALSE, self);
93
94         if (trace_ent != targ)
95                 return FALSE; // don't have a clear shot
96
97         if (trace_inwater)
98         if (trace_inopen)
99                 return FALSE; // sight line crossed contents
100                 
101         if(self.monster_delayedattack && self.delay != -1)
102         {
103                 if(time < self.delay)
104                         return FALSE;
105                         
106                 self.monster_delayedattack();
107         }
108
109         // missile attack
110         if (time < self.attack_finished_single)
111                 return FALSE;
112
113         if (enemy_range() >= 2000)
114                 return FALSE;
115
116         if (enemy_range() <= 120)
117                 chance = 0.9;
118         else if (enemy_range() <= 500)
119                 chance = 0.6; // was 0.4
120         else if (enemy_range() <= 1000)
121                 chance = 0.3; // was 0.05
122         else
123                 chance = 0;
124
125         if (chance > 0)
126         if (chance > random())
127                 return FALSE;
128                 
129         if(self.currentammo <= 0 && enemy_range() <= 120)
130         {
131                 self.attack_melee();
132                 return TRUE;
133         }
134         
135         if(self.currentammo <= 0)
136         {
137                 soldier_reload();
138                 return FALSE;
139         }
140
141         if (self.attack_ranged())
142                 return TRUE;
143
144         return FALSE;
145 }
146
147 void soldier_laser ()
148 {
149         self.frame = soldier_anim_shoot;
150         self.attack_finished_single = time + 0.8;
151         W_Laser_Attack(0);
152 }
153
154 float soldier_missile_laser ()
155 {
156         // FIXME: check if it would hit
157         soldier_laser();
158         return TRUE;
159 }
160
161 .float grunt_cycles;
162 void soldier_uzi_fire ()
163 {
164         self.currentammo -= 1;
165         if(self.currentammo <= 0)
166                 return;
167                 
168         self.grunt_cycles += 1;
169         
170         if(self.grunt_cycles > autocvar_g_monster_soldier_attack_uzi_bullets)
171         {
172                 self.monster_delayedattack = func_null;
173                 self.delay = -1;
174                 return;
175         }
176         W_UZI_Attack(DEATH_MONSTER_SOLDIER_NAIL);
177         self.delay = time + 0.1;
178         self.monster_delayedattack = soldier_uzi_fire;
179 }
180
181 void soldier_uzi ()
182 {
183         if(self.currentammo <= 0)
184                 return;
185                 
186         self.frame = soldier_anim_shoot;
187         self.attack_finished_single = time + 0.8;
188         self.delay = time + 0.1;
189         self.monster_delayedattack = soldier_uzi_fire;
190 }
191
192 float soldier_missile_uzi ()
193 {
194         self.grunt_cycles = 0;
195         // FIXME: check if it would hit
196         soldier_uzi();
197         return TRUE;
198 }
199
200 void soldier_shotgun ()
201 {
202         self.currentammo -= 1;
203         if(self.currentammo <= 0)
204                 return;
205                 
206         self.frame = soldier_anim_shoot;
207         self.attack_finished_single = time + 0.8;
208         W_Shotgun_Attack();
209 }
210
211 float soldier_missile_shotgun ()
212 {
213         // FIXME: check if it would hit
214         self.grunt_cycles = 0;
215         soldier_shotgun();
216         return TRUE;
217 }
218
219 void soldier_rl ()
220 {
221         self.currentammo -= 1;
222         if(self.currentammo <= 0)
223                 return;
224                 
225         self.frame = soldier_anim_shoot;
226         self.attack_finished_single = time + 0.8;
227         W_Rocket_Attack();
228 }
229
230 float soldier_missile_rl ()
231 {
232         // FIXME: check if it would hit
233         soldier_rl();
234         return TRUE;
235 }
236
237 void soldier_bash ()
238 {
239         self.frame = soldier_anim_shoot;
240         self.attack_finished_single = time + 0.8;
241         monster_melee(self.enemy, autocvar_g_monster_soldier_melee_damage, 70, DEATH_MONSTER_SOLDIER_NAIL);
242 }
243
244 void soldier_die()
245 {
246         Monster_CheckDropCvars ("soldier");
247         
248         remove(self.weaponentity);
249         
250         self.solid                      = SOLID_NOT;
251         self.takedamage         = DAMAGE_NO;
252         self.event_damage   = func_null;
253         self.enemy                      = world;
254         self.movetype           = MOVETYPE_TOSS;
255         self.think                      = Monster_Fade;
256         self.nextthink          = time + 2.1;
257         self.pain_finished  = self.nextthink;
258         self.weaponentity       = world;
259         
260         if (self.attack_ranged == soldier_missile_uzi)
261                 W_ThrowNewWeapon(self, WEP_UZI, 0, self.origin, self.velocity);    
262         else if (self.attack_ranged == soldier_missile_shotgun)
263                 W_ThrowNewWeapon(self, WEP_SHOTGUN, 0, self.origin, self.velocity);
264         else if (self.attack_ranged == soldier_missile_rl)
265                 W_ThrowNewWeapon(self, WEP_ROCKET_LAUNCHER, 0, self.origin, self.velocity);
266         else
267                 W_ThrowNewWeapon(self, WEP_LASER, 0, self.origin, self.velocity);
268
269         if (random() < 0.5)
270                 self.frame = soldier_anim_die1;
271         else
272                 self.frame = soldier_anim_die2;
273                 
274         monster_hook_death(); // for post-death mods
275 }
276
277 void soldier_spawn ()
278 {
279         if not(self.health)
280                 self.health = autocvar_g_monster_soldier_health * self.scale;
281
282         self.damageforcescale   = 0.003;
283         self.classname                  = "monster_soldier";
284         self.checkattack                = SoldierCheckAttack;
285         self.attack_melee               = soldier_bash;
286         self.frame                              = soldier_anim_draw;
287         self.nextthink                  = time + random() * 0.5 + 0.1;
288         self.think                              = soldier_think;
289         self.sprite_height              = 45 * self.scale;
290         self.items                              = (IT_SHELLS | IT_ROCKETS | IT_NAILS);
291         
292         RandomSelection_Init();
293         RandomSelection_Add(world, WEP_LASER, string_null, autocvar_g_monster_soldier_weapon_laser_chance, 1);
294         RandomSelection_Add(world, WEP_SHOTGUN, string_null, autocvar_g_monster_soldier_weapon_shotgun_chance, 1);
295         RandomSelection_Add(world, WEP_UZI, string_null, autocvar_g_monster_soldier_weapon_machinegun_chance, 1);
296         RandomSelection_Add(world, WEP_ROCKET_LAUNCHER, string_null, autocvar_g_monster_soldier_weapon_rocketlauncher_chance, 1);
297         
298         self.weaponentity = spawn();
299         self.weaponentity.movetype = MOVETYPE_NOCLIP;
300         self.weaponentity.owner = self;
301         setmodel(self.weaponentity, "models/weapons/v_seeker.md3");
302         setattachment(self.weaponentity, self, "bip01 r hand");
303         
304         if (RandomSelection_chosen_float == WEP_ROCKET_LAUNCHER)
305         {
306                 self.weapon = WEP_ROCKET_LAUNCHER;
307                 self.currentammo = self.ammo_rockets;
308                 self.armorvalue = 10;
309                 self.attack_ranged = soldier_missile_rl;
310         }
311         else if (RandomSelection_chosen_float == WEP_UZI)
312         {
313                 self.weapon = WEP_UZI;
314                 self.currentammo = self.ammo_nails;
315                 self.armorvalue = 100;
316                 self.attack_ranged = soldier_missile_uzi;
317         }
318         else if (RandomSelection_chosen_float == WEP_SHOTGUN)
319         {
320                 self.weapon = WEP_SHOTGUN;
321                 self.currentammo = self.ammo_shells;
322                 self.armorvalue = 25;
323                 self.attack_ranged = soldier_missile_shotgun;
324         }
325         else
326         {
327                 self.weapon = WEP_LASER;
328                 self.armorvalue = 60;
329                 self.currentammo = self.ammo_none;
330                 self.attack_ranged = soldier_missile_laser;
331         }
332
333         monster_hook_spawn(); // for post-spawn mods
334 }
335
336 void spawnfunc_monster_soldier ()
337 {       
338         if not(autocvar_g_monster_soldier)
339         {
340                 remove(self);
341                 return;
342         }
343         
344         self.monster_spawnfunc = spawnfunc_monster_soldier;
345         
346         if(self.spawnflags & MONSTERFLAG_APPEAR)
347         {
348                 self.think = func_null;
349                 self.nextthink = -1;
350                 self.use = Monster_Appear;
351                 return;
352         }
353         
354         if not (monster_initialize(
355                          "Grunt",
356                          "models/monsters/soldier.zym",
357                          SOLDIER_MIN, SOLDIER_MAX,
358                          FALSE,
359                          soldier_die, soldier_spawn))
360         {
361                 remove(self);
362                 return;
363         }
364         
365         precache_sound ("weapons/shotgun_fire.wav");
366         precache_sound ("weapons/uzi_fire.wav");
367         precache_sound ("weapons/laser_fire.wav");
368         precache_sound ("weapons/reload.wav");
369 }
370
371 // compatibility with old spawns
372 void spawnfunc_monster_army () { spawnfunc_monster_soldier(); }