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