]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/monster/marine.qc
Rename grunt to marine
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / monster / marine.qc
1 // size
2 const vector MARINE_MIN = '-16 -16 -30';
3 const vector MARINE_MAX = '16 16 32';
4
5 // model
6 string MARINE_MODEL = "models/monsters/marine.zym";
7
8 #ifdef SVQC
9 // cvars
10 float autocvar_g_monster_marine;
11 float autocvar_g_monster_marine_health;
12 float autocvar_g_monster_marine_melee_damage;
13 float autocvar_g_monster_marine_speed_walk;
14 float autocvar_g_monster_marine_speed_run;
15 float autocvar_g_monster_marine_ammo;
16 float autocvar_g_monster_marine_weapon_laser_chance;
17 float autocvar_g_monster_marine_weapon_shotgun_chance;
18 float autocvar_g_monster_marine_weapon_machinegun_chance;
19 float autocvar_g_monster_marine_weapon_rocketlauncher_chance;
20 float autocvar_g_monster_marine_attack_uzi_bullets;
21 float autocvar_g_monster_marine_attack_uzi_damage;
22 float autocvar_g_monster_marine_attack_uzi_force;
23 float autocvar_g_monster_marine_attack_shotgun_damage;
24 float autocvar_g_monster_marine_attack_shotgun_force;
25 float autocvar_g_monster_marine_attack_shotgun_spread;
26 float autocvar_g_monster_marine_attack_shotgun_bullets;
27 float autocvar_g_monster_marine_attack_rocket_damage;
28 float autocvar_g_monster_marine_attack_rocket_edgedamage;
29 float autocvar_g_monster_marine_attack_rocket_radius;
30 float autocvar_g_monster_marine_attack_rocket_force;
31 float autocvar_g_monster_marine_attack_rocket_lifetime;
32 float autocvar_g_monster_marine_attack_rocket_speed;
33 float autocvar_g_monster_marine_attack_laser_damage;
34 float autocvar_g_monster_marine_attack_laser_edgedamage;
35 float autocvar_g_monster_marine_attack_laser_radius;
36 float autocvar_g_monster_marine_attack_laser_force;
37
38 // animations
39 const float marine_anim_die1                    = 0;
40 const float marine_anim_die2                    = 1;
41 const float marine_anim_draw                    = 2;
42 const float marine_anim_duck                    = 3;
43 const float marine_anim_duckwalk                = 4;
44 const float marine_anim_duckjump                = 5;
45 const float marine_anim_duckidle                = 6;
46 const float marine_anim_idle                    = 7;
47 const float marine_anim_jump                    = 8;
48 const float marine_anim_pain1                   = 9;
49 const float marine_anim_pain2                   = 10;
50 const float marine_anim_shoot                   = 11;
51 const float marine_anim_taunt                   = 12;
52 const float marine_anim_run                     = 13;
53 const float marine_anim_runbackwards    = 14;
54 const float marine_anim_strafeleft      = 15;
55 const float marine_anim_straferight     = 16;
56 const float marine_anim_dead1                   = 17;
57 const float marine_anim_dead2                   = 18;
58 const float marine_anim_forwardright    = 19;
59 const float marine_anim_forwardleft     = 20;
60 const float marine_anim_backright               = 21;
61 const float marine_anim_backleft                = 22;
62
63 .float marine_cycles;
64
65 void marine_think()
66 {
67         self.think = marine_think;
68         self.nextthink = time + self.ticrate;
69         
70         if(time < self.attack_finished_single)
71                 monster_move(0, 0, 0, marine_anim_shoot, marine_anim_shoot, marine_anim_shoot);
72         else
73                 monster_move(autocvar_g_monster_marine_speed_run, autocvar_g_monster_marine_speed_walk, 50, marine_anim_run, marine_anim_run, marine_anim_idle);
74 }
75
76 void marine_reload()
77 {
78         self.monster_delayedattack = func_null; // out of ammo, don't keep attacking
79         self.delay = -1;
80         monsters_setframe(marine_anim_draw);
81         self.attack_finished_single = time + 2;
82         self.currentammo = autocvar_g_monster_marine_ammo;
83         sound (self, CH_SHOTS, "weapons/reload.wav", VOL_BASE, ATTN_LARGE);
84 }
85
86 void marine_uzi()
87 {
88         self.currentammo -= 1;
89         if(self.currentammo <= 0)
90         {
91                 marine_reload();
92                 return;
93         }
94                 
95         self.marine_cycles += 1;
96         
97         if(self.marine_cycles > autocvar_g_monster_marine_attack_uzi_bullets)
98         {
99                 self.monster_delayedattack = func_null;
100                 self.delay = -1;
101                 return;
102         }
103         
104         monster_makevectors(self.enemy);
105         
106         W_SetupShot(self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_monster_marine_attack_uzi_damage);
107         fireBallisticBullet(w_shotorg, w_shotdir, 0.02, 18000, 5, autocvar_g_monster_marine_attack_uzi_damage, autocvar_g_monster_marine_attack_uzi_force, DEATH_MONSTER_MARINE, 0, 1, 115);
108         endFireBallisticBullet();
109         
110         self.delay = time + 0.1;
111         self.monster_delayedattack = marine_uzi;
112 }
113
114 void marine_rocket_explode()
115 {
116         self.event_damage = func_null;
117         self.takedamage = DAMAGE_NO;
118         
119         pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1);
120         sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
121
122         RadiusDamage(self, self.realowner, autocvar_g_monster_marine_attack_rocket_damage, autocvar_g_monster_marine_attack_rocket_edgedamage, autocvar_g_monster_marine_attack_rocket_radius, world, autocvar_g_monster_marine_attack_rocket_force, self.projectiledeathtype, other);
123
124         remove(self);
125 }
126
127 void marine_rocket_touch()
128 {
129         PROJECTILE_TOUCH;
130         
131         marine_rocket_explode();
132 }
133
134 void marine_rocket_think()
135 {
136         self.nextthink = time;
137         if(time >= self.cnt)
138         {
139                 marine_rocket_explode();
140                 return;
141         }
142 }
143
144 void marine_rocket_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
145 {
146         if(self.health <= 0)
147                 return;
148         
149         if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
150                 return; // g_projectiles_damage says to halt
151                 
152         self.health -= damage;
153         self.angles = vectoangles(self.velocity);
154         
155         if(self.health <= 0)
156                 W_PrepareExplosionByDamage(attacker, marine_rocket_explode);
157 }
158
159 void marine_rocket()
160 {
161         entity missile;
162         
163         W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_monster_marine_attack_rocket_damage);
164
165         missile = spawn();
166         missile.owner = missile.realowner = self;
167         missile.classname = "rocket";
168         missile.bot_dodge = TRUE;
169         missile.bot_dodgerating = autocvar_g_monster_marine_attack_rocket_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
170
171         missile.takedamage = DAMAGE_YES;
172         missile.health = 50;
173         missile.event_damage = marine_rocket_damage;
174         missile.damagedbycontents = TRUE;
175
176         missile.movetype = MOVETYPE_FLY;
177         PROJECTILE_MAKETRIGGER(missile);
178         missile.projectiledeathtype = DEATH_MONSTER_MARINE;
179         setsize(missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
180
181         setorigin(missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
182         W_SetupProjectileVelocity(missile, autocvar_g_monster_marine_attack_rocket_speed, 0);
183         missile.angles = vectoangles(missile.velocity);
184
185         missile.touch = marine_rocket_touch;
186         missile.think = marine_rocket_think;
187         missile.nextthink = time;
188         missile.cnt = time + autocvar_g_monster_marine_attack_rocket_lifetime;
189         missile.flags = FL_PROJECTILE;
190         missile.missile_flags = MIF_SPLASH; 
191
192         CSQCProjectile(missile, TRUE, PROJECTILE_ROCKET, FALSE);
193 }
194
195 void marine_shotgun()
196 {
197         float sc;
198         W_SetupShot(self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, autocvar_g_monster_marine_attack_shotgun_damage * autocvar_g_monster_marine_attack_shotgun_bullets);
199         for(sc = 0;sc < autocvar_g_monster_marine_attack_shotgun_bullets;sc = sc + 1)
200                 fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_monster_marine_attack_shotgun_spread, 18000, 5, autocvar_g_monster_marine_attack_shotgun_damage, autocvar_g_monster_marine_attack_shotgun_force, DEATH_MONSTER_MARINE, 0, 1, 115);
201         endFireBallisticBullet();
202 }
203
204 void marine_laser_touch()
205 {
206         PROJECTILE_TOUCH;
207
208         self.event_damage = func_null;
209         RadiusDamage(self, self.realowner, autocvar_g_monster_marine_attack_laser_damage, autocvar_g_monster_marine_attack_laser_edgedamage, autocvar_g_monster_marine_attack_laser_radius, world, autocvar_g_monster_marine_attack_laser_force, self.projectiledeathtype, other);
210
211         remove(self);
212 }
213
214 void marine_laser()
215 {
216         entity missile;
217         
218         W_SetupShot_Dir(self, v_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_monster_marine_attack_laser_damage);
219         
220         missile = spawn();
221         missile.owner = missile.realowner = self;
222         missile.classname = "laserbolt";
223         PROJECTILE_MAKETRIGGER(missile);
224         missile.projectiledeathtype = DEATH_MONSTER_MARINE;
225
226         setorigin(missile, w_shotorg);
227         setsize(missile, '0 0 0', '0 0 0');
228
229         W_SETUPPROJECTILEVELOCITY(missile, g_monster_marine_attack_laser);
230         missile.angles = vectoangles(missile.velocity);
231         missile.touch = marine_laser_touch;
232
233         missile.flags = FL_PROJECTILE;
234         missile.missile_flags = MIF_SPLASH;
235         missile.movetype = MOVETYPE_FLY;
236         
237         missile.think = SUB_Remove;
238         missile.nextthink = time + 5;
239         
240         CSQCProjectile(missile, TRUE, PROJECTILE_LASER, TRUE);
241 }
242
243 float marine_attack(float attack_type)
244 {
245         switch(attack_type)
246         {
247                 case MONSTER_ATTACK_MELEE:
248                 {
249                         monsters_setframe(marine_anim_shoot);
250                         self.attack_finished_single = time + 0.8;
251                         monster_melee(self.enemy, autocvar_g_monster_marine_melee_damage, 0.3, DEATH_MONSTER_MARINE_SLAP, TRUE);
252                         
253                         return TRUE;
254                 }
255                 case MONSTER_ATTACK_RANGED:
256                 {
257                         if(self.currentammo <= 0)
258                         {
259                                 marine_reload();
260                                 
261                                 return FALSE;
262                         }
263                         
264                         monsters_setframe(marine_anim_shoot);
265                         monster_makevectors(self.enemy);
266                         self.marine_cycles = 0;
267         
268                         switch(self.weapon)
269                         {
270                                 case WEP_ROCKET_LAUNCHER:
271                                 {
272                                         self.currentammo -= 1;
273                                         self.attack_finished_single = time + 0.8;
274                                         marine_rocket();
275                                         
276                                         return TRUE;
277                                 }
278                                 case WEP_SHOTGUN:
279                                 {
280                                         self.currentammo -= 1;
281                                         self.attack_finished_single = time + 0.8;
282                                         marine_shotgun();
283                                         
284                                         return TRUE;
285                                 }
286                                 case WEP_UZI:
287                                 {
288                                         self.attack_finished_single = time + 0.8;
289                                         self.delay = time + 0.1;
290                                         self.monster_delayedattack = marine_uzi;
291                                         
292                                         return TRUE;
293                                 }
294                                 case WEP_LASER:
295                                 {
296                                         self.attack_finished_single = time + 0.8;
297                                         marine_laser();
298                                         
299                                         return TRUE;
300                                 }
301                         }
302                         
303                         return FALSE;
304                 }
305         }
306         
307         return FALSE;
308 }
309
310 void marine_die()
311 {
312         Monster_CheckDropCvars ("marine");
313         
314         self.think = monster_dead_think;
315         self.nextthink = time + self.ticrate;
316         self.ltime = time + 5;
317         monsters_setframe((random() > 0.5) ? marine_anim_die1 : marine_anim_die2);
318                 
319         monster_hook_death(); // for post-death mods
320 }
321
322 void marine_spawn()
323 {
324         if not(self.health)
325                 self.health = autocvar_g_monster_marine_health;
326
327         self.damageforcescale   = 0.003;
328         self.classname                  = "monster_marine";
329         self.monster_attackfunc = marine_attack;
330         self.nextthink                  = time + random() * 0.5 + 0.1;
331         self.think                              = marine_think;
332         self.currentammo                = 3;
333         self.items                              = (IT_SHELLS | IT_ROCKETS | IT_NAILS);
334         
335         monsters_setframe(marine_anim_draw);
336         
337         monster_setupsounds("marine");
338         
339         setmodel(self, MARINE_MODEL);
340         
341         RandomSelection_Init();
342         RandomSelection_Add(world, WEP_LASER, string_null, autocvar_g_monster_marine_weapon_laser_chance, 1);
343         RandomSelection_Add(world, WEP_SHOTGUN, string_null, autocvar_g_monster_marine_weapon_shotgun_chance, 1);
344         RandomSelection_Add(world, WEP_UZI, string_null, autocvar_g_monster_marine_weapon_machinegun_chance, 1);
345         RandomSelection_Add(world, WEP_ROCKET_LAUNCHER, string_null, autocvar_g_monster_marine_weapon_rocketlauncher_chance, 1);
346         
347         self.weaponentity = spawn();
348         self.weaponentity.movetype = MOVETYPE_NOCLIP;
349         self.weaponentity.team = self.team;
350         self.weaponentity.solid = SOLID_NOT;
351         self.weaponentity.owner = self.weaponentity.realowner = self;
352         setmodel(self.weaponentity, "models/weapons/v_seeker.md3");
353         setattachment(self.weaponentity, self, "bip01 r hand");
354         
355         self.armorvalue = bound(0.5, random(), 1);
356         self.weapon = RandomSelection_chosen_float;
357
358         monster_hook_spawn(); // for post-spawn mods
359 }
360
361 void spawnfunc_monster_marine()
362 {       
363         if not(autocvar_g_monster_marine) { remove(self); return; }
364         
365         self.monster_spawnfunc = spawnfunc_monster_marine;
366         
367         if(Monster_CheckAppearFlags(self))
368                 return;
369                 
370         precache_model("models/weapons/v_seeker.md3");
371         precache_model(MARINE_MODEL);
372         
373         if not (monster_initialize(
374                          "Marine", MONSTER_MARINE,
375                          MARINE_MIN, MARINE_MAX,
376                          FALSE,
377                          marine_die, marine_spawn))
378         {
379                 remove(self);
380                 return;
381         }
382 }
383
384 // compatibility with old spawns
385 void spawnfunc_monster_army() { spawnfunc_monster_marine(); }
386
387 #endif // SVQC