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