]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/monster/soldier.qc
Fix all soldier attacks
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / monster / soldier.qc
1 #ifndef MENUQC
2 // size
3 const vector SOLDIER_MIN = '-16 -16 -30';
4 const vector SOLDIER_MAX = '16 16 32';
5
6 // model
7 string SOLDIER_MODEL = "models/monsters/soldier.zym";
8
9 #endif
10
11 #ifdef SVQC
12 // cvars
13 float autocvar_g_monster_soldier;
14 float autocvar_g_monster_soldier_health;
15 float autocvar_g_monster_soldier_melee_damage;
16 float autocvar_g_monster_soldier_speed_walk;
17 float autocvar_g_monster_soldier_speed_run;
18 float autocvar_g_monster_soldier_ammo;
19 float autocvar_g_monster_soldier_weapon_laser_chance;
20 float autocvar_g_monster_soldier_weapon_shotgun_chance;
21 float autocvar_g_monster_soldier_weapon_machinegun_chance;
22 float autocvar_g_monster_soldier_weapon_rocketlauncher_chance;
23 float autocvar_g_monster_soldier_attack_uzi_bullets;
24 float autocvar_g_monster_soldier_attack_uzi_damage;
25 float autocvar_g_monster_soldier_attack_uzi_force;
26 float autocvar_g_monster_soldier_attack_shotgun_damage;
27 float autocvar_g_monster_soldier_attack_shotgun_force;
28 float autocvar_g_monster_soldier_attack_shotgun_spread;
29 float autocvar_g_monster_soldier_attack_shotgun_bullets;
30 float autocvar_g_monster_soldier_attack_rocket_damage;
31 float autocvar_g_monster_soldier_attack_rocket_edgedamage;
32 float autocvar_g_monster_soldier_attack_rocket_radius;
33 float autocvar_g_monster_soldier_attack_rocket_force;
34 float autocvar_g_monster_soldier_attack_rocket_lifetime;
35 float autocvar_g_monster_soldier_attack_rocket_speed;
36 float autocvar_g_monster_soldier_attack_laser_damage;
37 float autocvar_g_monster_soldier_attack_laser_edgedamage;
38 float autocvar_g_monster_soldier_attack_laser_radius;
39 float autocvar_g_monster_soldier_attack_laser_force;
40
41 // animations
42 const float soldier_anim_die1                   = 0;
43 const float soldier_anim_die2                   = 1;
44 const float soldier_anim_draw                   = 2;
45 const float soldier_anim_duck                   = 3;
46 const float soldier_anim_duckwalk               = 4;
47 const float soldier_anim_duckjump               = 5;
48 const float soldier_anim_duckidle               = 6;
49 const float soldier_anim_idle                   = 7;
50 const float soldier_anim_jump                   = 8;
51 const float soldier_anim_pain1                  = 9;
52 const float soldier_anim_pain2                  = 10;
53 const float soldier_anim_shoot                  = 11;
54 const float soldier_anim_taunt                  = 12;
55 const float soldier_anim_run                    = 13;
56 const float soldier_anim_runbackwards   = 14;
57 const float soldier_anim_strafeleft     = 15;
58 const float soldier_anim_straferight    = 16;
59 const float soldier_anim_dead1                  = 17;
60 const float soldier_anim_dead2                  = 18;
61 const float soldier_anim_forwardright   = 19;
62 const float soldier_anim_forwardleft    = 20;
63 const float soldier_anim_backright              = 21;
64 const float soldier_anim_backleft               = 22;
65
66 void soldier_think ()
67 {
68         self.think = soldier_think;
69         self.nextthink = time + self.ticrate;
70         
71         if(self.delay != -1)
72                 self.nextthink = self.delay;
73         
74         if(time < self.attack_finished_single)
75                 monster_move(0, 0, 0, soldier_anim_shoot, soldier_anim_shoot, soldier_anim_shoot);
76         else
77                 monster_move(autocvar_g_monster_soldier_speed_run, autocvar_g_monster_soldier_speed_walk, 50, soldier_anim_run, soldier_anim_run, soldier_anim_idle);
78 }
79
80 void soldier_reload ()
81 {
82         self.monster_delayedattack = func_null; // out of ammo, don't keep attacking
83         self.delay = -1;
84         monsters_setframe(soldier_anim_draw);
85         self.attack_finished_single = time + 2;
86         self.currentammo = autocvar_g_monster_soldier_ammo;
87         sound (self, CH_SHOTS, "weapons/reload.wav", VOL_BASE, ATTN_LARGE);
88 }
89
90 .float grunt_cycles;
91 void soldier_uzi_fire ()
92 {
93         self.currentammo -= 1;
94         if(self.currentammo <= 0)
95         {
96                 soldier_reload();
97                 return;
98         }
99                 
100         self.grunt_cycles += 1;
101         
102         if(self.grunt_cycles > autocvar_g_monster_soldier_attack_uzi_bullets)
103         {
104                 self.monster_delayedattack = func_null;
105                 self.delay = -1;
106                 return;
107         }
108         
109         W_SetupShot (self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_monster_soldier_attack_uzi_damage);
110         fireBallisticBullet(w_shotorg, w_shotdir, 0.02, 18000, 5, autocvar_g_monster_soldier_attack_uzi_damage, autocvar_g_monster_soldier_attack_uzi_force, DEATH_MONSTER_MARINE, 0, 1, 115);
111         endFireBallisticBullet();
112         
113         self.delay = time + 0.1;
114         self.monster_delayedattack = soldier_uzi_fire;
115 }
116
117 void soldier_rocket_explode()
118 {
119         self.event_damage = func_null;
120         self.takedamage = DAMAGE_NO;
121         
122         pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1);
123         sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
124
125         RadiusDamage (self, self.realowner, autocvar_g_monster_soldier_attack_rocket_damage, autocvar_g_monster_soldier_attack_rocket_edgedamage, autocvar_g_monster_soldier_attack_rocket_radius, world, autocvar_g_monster_soldier_attack_rocket_force, self.projectiledeathtype, other);
126
127         remove (self);
128 }
129
130 void soldier_rocket_touch()
131 {
132         PROJECTILE_TOUCH;
133         
134         soldier_rocket_explode();
135 }
136
137 void soldier_rocket_think()
138 {
139         self.nextthink = time;
140         if (time > self.cnt)
141         {
142                 soldier_rocket_explode();
143                 return;
144         }
145 }
146
147 void soldier_rocket_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
148 {
149         if (self.health <= 0)
150                 return;
151         
152         if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
153                 return; // g_projectiles_damage says to halt
154                 
155         self.health -= damage;
156         self.angles = vectoangles(self.velocity);
157         
158         if (self.health <= 0)
159                 W_PrepareExplosionByDamage(attacker, soldier_rocket_explode);
160 }
161
162 void soldier_rocket_fire()
163 {
164         entity missile;
165         
166         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_monster_soldier_attack_rocket_damage);
167
168         missile = spawn();
169         missile.owner = missile.realowner = self;
170         missile.classname = "rocket";
171         missile.bot_dodge = TRUE;
172         missile.bot_dodgerating = autocvar_g_monster_soldier_attack_rocket_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
173
174         missile.takedamage = DAMAGE_YES;
175         missile.health = 50;
176         missile.event_damage = soldier_rocket_damage;
177         missile.damagedbycontents = TRUE;
178
179         missile.movetype = MOVETYPE_FLY;
180         PROJECTILE_MAKETRIGGER(missile);
181         missile.projectiledeathtype = DEATH_MONSTER_MARINE;
182         setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
183
184         setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
185         W_SetupProjectileVelocity(missile, autocvar_g_monster_soldier_attack_rocket_speed, 0);
186         missile.angles = vectoangles (missile.velocity);
187
188         missile.touch = soldier_rocket_touch;
189         missile.think = soldier_rocket_think;
190         missile.nextthink = time;
191         missile.cnt = time + autocvar_g_monster_soldier_attack_rocket_lifetime;
192         missile.flags = FL_PROJECTILE;
193         missile.missile_flags = MIF_SPLASH; 
194
195         CSQCProjectile(missile, TRUE, PROJECTILE_ROCKET, FALSE);
196 }
197
198 void soldier_shotgun_fire()
199 {
200         float sc;
201         W_SetupShot (self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, autocvar_g_monster_soldier_attack_shotgun_damage * autocvar_g_monster_soldier_attack_shotgun_bullets);
202         for (sc = 0;sc < autocvar_g_monster_soldier_attack_shotgun_bullets;sc = sc + 1)
203                 fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_monster_soldier_attack_shotgun_spread, 18000, 5, autocvar_g_monster_soldier_attack_shotgun_damage, autocvar_g_monster_soldier_attack_shotgun_force, DEATH_MONSTER_MARINE, 0, 1, 115);
204         endFireBallisticBullet();
205 }
206
207 void soldier_laser_touch()
208 {
209         PROJECTILE_TOUCH;
210
211         self.event_damage = func_null;
212         RadiusDamage (self, self.realowner, autocvar_g_monster_soldier_attack_laser_damage, autocvar_g_monster_soldier_attack_laser_edgedamage, autocvar_g_monster_soldier_attack_laser_radius, world, autocvar_g_monster_soldier_attack_laser_force, self.projectiledeathtype, other);
213
214         remove (self);
215 }
216
217 void soldier_laser_fire()
218 {
219         entity missile;
220         
221         W_SetupShot_Dir(self, v_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_monster_soldier_attack_laser_damage);
222         
223         missile = spawn ();
224         missile.owner = missile.realowner = self;
225         missile.classname = "laserbolt";
226         PROJECTILE_MAKETRIGGER(missile);
227         missile.projectiledeathtype = DEATH_MONSTER_MARINE;
228
229         setorigin (missile, w_shotorg);
230         setsize(missile, '0 0 0', '0 0 0');
231
232         W_SETUPPROJECTILEVELOCITY(missile, g_monster_soldier_attack_laser);
233         missile.angles = vectoangles(missile.velocity);
234         missile.touch = soldier_laser_touch;
235
236         missile.flags = FL_PROJECTILE;
237         missile.missile_flags = MIF_SPLASH;
238         missile.movetype = MOVETYPE_FLY;
239         
240         missile.think = SUB_Remove;
241         missile.nextthink = time + 5;
242         
243         CSQCProjectile(missile, TRUE, PROJECTILE_LASER, TRUE);
244 }
245
246 float soldier_attack()
247 {
248         monsters_setframe(soldier_anim_shoot);
249         makevectors(self.angles);
250         
251         if(self.currentammo <= 0)
252         {
253                 soldier_reload();
254                 return FALSE;
255         }
256         
257         self.grunt_cycles = 0;
258         
259         switch(self.weapon)
260         {
261                 case WEP_ROCKET_LAUNCHER:
262                 {
263                         self.currentammo -= 1;
264                         self.attack_finished_single = time + 0.8;
265                         soldier_rocket_fire();
266                         return TRUE;
267                 }
268                 case WEP_SHOTGUN:
269                 {
270                         self.currentammo -= 1;
271                         self.attack_finished_single = time + 0.8;
272                         soldier_shotgun_fire();
273                         return TRUE;
274                 }
275                 case WEP_UZI:
276                 {
277                         self.attack_finished_single = time + 0.8;
278                         self.delay = time + 0.1;
279                         self.monster_delayedattack = soldier_uzi_fire;
280                         return TRUE;
281                 }
282                 case WEP_LASER:
283                 {
284                         self.attack_finished_single = time + 0.8;
285                         soldier_laser_fire();
286                         return TRUE;
287                 }
288                 default:
289                         return FALSE; // no weapon?
290         }
291 }
292
293 void soldier_melee ()
294 {
295         monsters_setframe(soldier_anim_shoot);
296         self.attack_finished_single = time + 0.8;
297         monster_melee(self.enemy, autocvar_g_monster_soldier_melee_damage, 0.3, DEATH_MONSTER_MARINE_SLAP, TRUE);
298 }
299
300 void soldier_die()
301 {
302         Monster_CheckDropCvars ("soldier");
303         
304         self.think = Monster_Fade;
305         self.nextthink = time + 5;
306         monsters_setframe((random() > 0.5) ? soldier_anim_die1 : soldier_anim_die2);
307                 
308         monster_hook_death(); // for post-death mods
309 }
310
311 void soldier_spawn ()
312 {
313         if not(self.health)
314                 self.health = autocvar_g_monster_soldier_health * self.scale;
315
316         self.damageforcescale   = 0.003;
317         self.classname                  = "monster_soldier";
318         self.checkattack                = GenericCheckAttack;
319         self.attack_melee               = soldier_melee;
320         self.attack_ranged              = soldier_attack;
321         self.nextthink                  = time + random() * 0.5 + 0.1;
322         self.think                              = soldier_think;
323         self.currentammo                = 3;
324         self.sprite_height              = 45;
325         self.items                              = (IT_SHELLS | IT_ROCKETS | IT_NAILS);
326         
327         monsters_setframe(soldier_anim_draw);
328         
329         monster_setupsounds("soldier");
330         
331         setmodel(self, SOLDIER_MODEL);
332         
333         RandomSelection_Init();
334         RandomSelection_Add(world, WEP_LASER, string_null, autocvar_g_monster_soldier_weapon_laser_chance, 1);
335         RandomSelection_Add(world, WEP_SHOTGUN, string_null, autocvar_g_monster_soldier_weapon_shotgun_chance, 1);
336         RandomSelection_Add(world, WEP_UZI, string_null, autocvar_g_monster_soldier_weapon_machinegun_chance, 1);
337         RandomSelection_Add(world, WEP_ROCKET_LAUNCHER, string_null, autocvar_g_monster_soldier_weapon_rocketlauncher_chance, 1);
338         
339         self.weaponentity = spawn();
340         self.weaponentity.movetype = MOVETYPE_NOCLIP;
341         self.weaponentity.team = self.team;
342         self.weaponentity.solid = SOLID_NOT;
343         self.weaponentity.owner = self.weaponentity.realowner = self;
344         setmodel(self.weaponentity, "models/weapons/v_seeker.md3");
345         setattachment(self.weaponentity, self, "bip01 r hand");
346         
347         self.armorvalue = bound(0.5, random(), 1);
348         self.weapon = RandomSelection_chosen_float;
349
350         monster_hook_spawn(); // for post-spawn mods
351 }
352
353 void spawnfunc_monster_soldier ()
354 {       
355         if not(autocvar_g_monster_soldier) { remove(self); return; }
356         
357         self.monster_spawnfunc = spawnfunc_monster_soldier;
358         
359         if(Monster_CheckAppearFlags(self))
360                 return;
361                 
362         precache_model("models/weapons/v_seeker.md3");
363         precache_model(SOLDIER_MODEL);
364         
365         if not (monster_initialize(
366                          "Marine", MONSTER_MARINE,
367                          SOLDIER_MIN, SOLDIER_MAX,
368                          FALSE,
369                          soldier_die, soldier_spawn))
370         {
371                 remove(self);
372                 return;
373         }
374 }
375
376 // compatibility with old spawns
377 void spawnfunc_monster_army () { spawnfunc_monster_soldier(); }
378
379 #endif // SVQC