4b2941648404fe976b7d83786a5980a1ff6312bc
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / machinegun.qc
1 #include "machinegun.qh"
2 #ifndef IMPLEMENTATION
3 CLASS(MachineGun, Weapon)
4 /* ammotype  */ ATTRIB(MachineGun, ammo_field, .int, ammo_nails);
5 /* impulse   */ ATTRIB(MachineGun, impulse, int, 3);
6 /* flags     */ ATTRIB(MachineGun, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN);
7 /* rating    */ ATTRIB(MachineGun, bot_pickupbasevalue, float, BOT_PICKUP_RATING_MID);
8 /* color     */ ATTRIB(MachineGun, wpcolor, vector, '1 1 0');
9 /* modelname */ ATTRIB(MachineGun, mdl, string, "uzi");
10 #ifdef GAMEQC
11 /* model     */ ATTRIB(MachineGun, m_model, Model, MDL_MACHINEGUN_ITEM);
12 #endif
13 /* crosshair */ ATTRIB(MachineGun, w_crosshair, string, "gfx/crosshairuzi");
14 /* crosshair */ ATTRIB(MachineGun, w_crosshair_size, float, 0.6);
15 /* wepimg    */ ATTRIB(MachineGun, model2, string, "weaponuzi");
16 /* refname   */ ATTRIB(MachineGun, netname, string, "machinegun");
17 /* wepname   */ ATTRIB(MachineGun, m_name, string, _("MachineGun"));
18
19 #define X(BEGIN, P, END, class, prefix) \
20         BEGIN(class) \
21                 P(class, prefix, burst, float, NONE) \
22                 P(class, prefix, burst_ammo, float, NONE) \
23                 P(class, prefix, burst_animtime, float, NONE) \
24                 P(class, prefix, burst_refire2, float, NONE) \
25                 P(class, prefix, burst_refire, float, NONE) \
26                 P(class, prefix, burst_speed, float, NONE) \
27                 P(class, prefix, first, float, NONE) \
28                 P(class, prefix, first_ammo, float, NONE) \
29                 P(class, prefix, first_damage, float, NONE) \
30                 P(class, prefix, first_force, float, NONE) \
31                 P(class, prefix, first_refire, float, NONE) \
32                 P(class, prefix, first_spread, float, NONE) \
33                 P(class, prefix, mode, float, NONE) \
34                 P(class, prefix, reload_ammo, float, NONE) \
35         P(class, prefix, reload_time, float, NONE) \
36                 P(class, prefix, solidpenetration, float, NONE) \
37                 P(class, prefix, spread_add, float, NONE) \
38                 P(class, prefix, spread_max, float, NONE) \
39                 P(class, prefix, spread_min, float, NONE) \
40                 P(class, prefix, sustained_ammo, float, NONE) \
41                 P(class, prefix, sustained_damage, float, NONE) \
42                 P(class, prefix, sustained_force, float, NONE) \
43                 P(class, prefix, sustained_refire, float, NONE) \
44                 P(class, prefix, sustained_spread, float, NONE) \
45         P(class, prefix, switchdelay_drop, float, NONE) \
46         P(class, prefix, switchdelay_raise, float, NONE) \
47         P(class, prefix, weaponreplace, string,NONE) \
48         P(class, prefix, weaponstartoverride, float, NONE) \
49         P(class, prefix, weaponstart, float, NONE) \
50         P(class, prefix, weaponthrowable, float, NONE) \
51         END()
52     W_PROPS(X, MachineGun, machinegun)
53 #undef X
54
55 ENDCLASS(MachineGun)
56 REGISTER_WEAPON(MACHINEGUN, machinegun, NEW(MachineGun));
57
58
59 #endif
60 #ifdef IMPLEMENTATION
61 #ifdef SVQC
62
63 spawnfunc(weapon_machinegun)
64 {
65         if(autocvar_sv_q3acompat_machineshotgunswap)
66         if(this.classname != "droppedweapon")
67         {
68                 weapon_defaultspawnfunc(this, WEP_SHOCKWAVE);
69                 return;
70         }
71         weapon_defaultspawnfunc(this, WEP_MACHINEGUN);
72 }
73 spawnfunc(weapon_uzi) { spawnfunc_weapon_machinegun(this); }
74
75 void W_MachineGun_MuzzleFlash_Think(entity this)
76 {
77         this.frame += 2;
78         this.scale *= 0.5;
79         this.alpha -= 0.25;
80         this.nextthink = time + 0.05;
81
82         if(this.alpha <= 0)
83         {
84                 setthink(this, SUB_Remove);
85                 this.nextthink = time;
86                 this.realowner.muzzle_flash = NULL;
87                 return;
88         }
89
90 }
91
92 void W_MachineGun_MuzzleFlash(entity actor, .entity weaponentity)
93 {
94         entity wepent = actor.(weaponentity);
95
96         if(wepent.muzzle_flash == NULL)
97                 wepent.muzzle_flash = spawn();
98
99         // muzzle flash for 1st person view
100         setmodel(wepent.muzzle_flash, MDL_MACHINEGUN_MUZZLEFLASH); // precision set below
101
102         wepent.muzzle_flash.scale = 0.75;
103         setthink(wepent.muzzle_flash, W_MachineGun_MuzzleFlash_Think);
104         wepent.muzzle_flash.nextthink = time + 0.02;
105         wepent.muzzle_flash.frame = 2;
106         wepent.muzzle_flash.alpha = 0.75;
107         wepent.muzzle_flash.angles_z = random() * 180;
108         wepent.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
109         wepent.muzzle_flash.owner = wepent.muzzle_flash.realowner = wepent;
110 }
111
112 void W_MachineGun_Attack(Weapon thiswep, int deathtype, entity actor, .entity weaponentity)
113 {
114         W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, ((actor.(weaponentity).misc_bulletcounter == 1) ? WEP_CVAR(machinegun, first_damage) : WEP_CVAR(machinegun, sustained_damage)));
115         if(!autocvar_g_norecoil)
116         {
117                 actor.punchangle_x = random() - 0.5;
118                 actor.punchangle_y = random() - 0.5;
119         }
120         int slot = weaponslot(weaponentity);
121         // this attack_finished just enforces a cooldown at the end of a burst
122         ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
123
124         if(actor.(weaponentity).misc_bulletcounter == 1)
125                 fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, 0);
126         else
127                 fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, 0);
128
129         Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
130
131         W_MachineGun_MuzzleFlash(actor, weaponentity);
132         W_AttachToShotorg(actor, weaponentity, actor.(weaponentity).muzzle_flash, '5 0 0');
133
134         // casing code
135         if(autocvar_g_casings >= 2)
136         {
137                 makevectors(actor.v_angle); // for some reason, this is lost
138                 SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
139         }
140
141         if(actor.(weaponentity).misc_bulletcounter == 1)
142                 W_DecreaseAmmo(thiswep, actor, WEP_CVAR(machinegun, first_ammo), weaponentity);
143         else
144                 W_DecreaseAmmo(thiswep, actor, WEP_CVAR(machinegun, sustained_ammo), weaponentity);
145 }
146
147 // weapon frames
148 void W_MachineGun_Attack_Frame(Weapon thiswep, entity actor, .entity weaponentity, int fire)
149 {
150         if(actor.(weaponentity).m_weapon != actor.(weaponentity).m_switchweapon) // abort immediately if switching
151         {
152                 w_ready(thiswep, actor, weaponentity, fire);
153                 return;
154         }
155         if(PHYS_INPUT_BUTTON_ATCK(actor))
156         {
157                 if(!thiswep.wr_checkammo2(thiswep, actor))
158                 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
159                 {
160                         W_SwitchWeapon_Force(actor, w_getbestweapon(actor), weaponentity);
161                         w_ready(thiswep, actor, weaponentity, fire);
162                         return;
163                 }
164                 actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
165                 W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id, actor, weaponentity);
166                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
167         }
168         else
169                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), w_ready);
170 }
171
172
173 void W_MachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
174 {
175         float machinegun_spread;
176
177         if(!(fire & 1))
178         {
179                 w_ready(thiswep, actor, weaponentity, fire);
180                 return;
181         }
182
183         if(!thiswep.wr_checkammo1(thiswep, actor))
184         if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
185         {
186                 W_SwitchWeapon_Force(actor, w_getbestweapon(actor), weaponentity);
187                 w_ready(thiswep, actor, weaponentity, fire);
188                 return;
189         }
190
191         W_DecreaseAmmo(WEP_MACHINEGUN, actor, WEP_CVAR(machinegun, sustained_ammo), weaponentity);
192
193         W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage));
194         if(!autocvar_g_norecoil)
195         {
196                 actor.punchangle_x = random() - 0.5;
197                 actor.punchangle_y = random() - 0.5;
198         }
199
200         machinegun_spread = bound(WEP_CVAR(machinegun, spread_min), WEP_CVAR(machinegun, spread_min) + (WEP_CVAR(machinegun, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR(machinegun, spread_max));
201         fireBullet(actor, weaponentity, w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
202
203         actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
204
205         Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
206
207         W_MachineGun_MuzzleFlash(actor, weaponentity);
208         W_AttachToShotorg(actor, weaponentity, actor.(weaponentity).muzzle_flash, '5 0 0');
209
210         if(autocvar_g_casings >= 2) // casing code
211         {
212                 makevectors(actor.v_angle); // for some reason, this is lost
213                 SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
214         }
215
216         int slot = weaponslot(weaponentity);
217         ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(actor);
218         weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Auto);
219 }
220
221 void W_MachineGun_Attack_Burst(Weapon thiswep, entity actor, .entity weaponentity, int fire)
222 {
223         W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage));
224         if(!autocvar_g_norecoil)
225         {
226                 actor.punchangle_x = random() - 0.5;
227                 actor.punchangle_y = random() - 0.5;
228         }
229
230         fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0);
231
232         Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
233
234         W_MachineGun_MuzzleFlash(actor, weaponentity);
235         W_AttachToShotorg(actor, weaponentity, actor.(weaponentity).muzzle_flash, '5 0 0');
236
237         if(autocvar_g_casings >= 2) // casing code
238         {
239                 makevectors(actor.v_angle); // for some reason, this is lost
240                 SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
241         }
242
243         actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
244         if(actor.(weaponentity).misc_bulletcounter == 0)
245         {
246                 int slot = weaponslot(weaponentity);
247                 ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor(actor);
248                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(machinegun, burst_animtime), w_ready);
249         }
250         else
251         {
252                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(machinegun, burst_refire), W_MachineGun_Attack_Burst);
253         }
254
255 }
256
257 METHOD(MachineGun, wr_aim, void(entity thiswep, entity actor))
258 {
259     if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200))
260         PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, 1000000, 0, 0.001, false);
261     else
262         PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, 1000000, 0, 0.001, false);
263 }
264 METHOD(MachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
265 {
266     if(WEP_CVAR(machinegun, reload_ammo) && actor.clip_load < min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo))) { // forced reload
267         thiswep.wr_reload(thiswep, actor, weaponentity);
268     } else
269     if(WEP_CVAR(machinegun, mode) == 1)
270     {
271         if(fire & 1)
272         if(weapon_prepareattack(thiswep, actor, weaponentity, false, 0))
273         {
274             actor.(weaponentity).misc_bulletcounter = 0;
275             W_MachineGun_Attack_Auto(thiswep, actor, weaponentity, fire);
276         }
277
278         if(fire & 2)
279         if(weapon_prepareattack(thiswep, actor, weaponentity, true, 0))
280         {
281             if(!thiswep.wr_checkammo2(thiswep, actor))
282             if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
283             {
284                 W_SwitchWeapon_Force(actor, w_getbestweapon(actor), weaponentity);
285                 w_ready(thiswep, actor, weaponentity, fire);
286                 return;
287             }
288
289             W_DecreaseAmmo(thiswep, actor, WEP_CVAR(machinegun, burst_ammo), weaponentity);
290
291             actor.(weaponentity).misc_bulletcounter = WEP_CVAR(machinegun, burst) * -1;
292             W_MachineGun_Attack_Burst(thiswep, actor, weaponentity, fire);
293         }
294     }
295     else
296     {
297
298         if(fire & 1)
299         if(weapon_prepareattack(thiswep, actor, weaponentity, false, 0))
300         {
301             actor.(weaponentity).misc_bulletcounter = 1;
302             W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id, actor, weaponentity); // sets attack_finished
303             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame);
304         }
305
306         if((fire & 2) && WEP_CVAR(machinegun, first))
307         if(weapon_prepareattack(thiswep, actor, weaponentity, true, 0))
308         {
309             actor.(weaponentity).misc_bulletcounter = 1;
310             W_MachineGun_Attack(WEP_MACHINEGUN, WEP_MACHINEGUN.m_id | HITTYPE_SECONDARY, actor, weaponentity); // sets attack_finished
311             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(machinegun, first_refire), w_ready);
312         }
313     }
314 }
315 METHOD(MachineGun, wr_checkammo1, bool(entity thiswep, entity actor))
316 {
317     float ammo_amount;
318     if(WEP_CVAR(machinegun, mode) == 1)
319         ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, sustained_ammo);
320     else
321         ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, first_ammo);
322
323     if(WEP_CVAR(machinegun, reload_ammo))
324     {
325         if(WEP_CVAR(machinegun, mode) == 1)
326             ammo_amount += actor.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, sustained_ammo);
327         else
328             ammo_amount += actor.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
329     }
330     return ammo_amount;
331 }
332 METHOD(MachineGun, wr_checkammo2, bool(entity thiswep, entity actor))
333 {
334     float ammo_amount;
335     if(WEP_CVAR(machinegun, mode) == 1)
336         ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, burst_ammo);
337     else
338         ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(machinegun, first_ammo);
339
340     if(WEP_CVAR(machinegun, reload_ammo))
341     {
342         if(WEP_CVAR(machinegun, mode) == 1)
343             ammo_amount += actor.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, burst_ammo);
344         else
345             ammo_amount += actor.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo);
346     }
347     return ammo_amount;
348 }
349 METHOD(MachineGun, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
350 {
351     W_Reload(actor, weaponentity, min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo)), SND_RELOAD);
352 }
353 METHOD(MachineGun, wr_suicidemessage, Notification(entity thiswep))
354 {
355     return WEAPON_THINKING_WITH_PORTALS;
356 }
357 METHOD(MachineGun, wr_killmessage, Notification(entity thiswep))
358 {
359     if(w_deathtype & HITTYPE_SECONDARY)
360         return WEAPON_MACHINEGUN_MURDER_SNIPE;
361     else
362         return WEAPON_MACHINEGUN_MURDER_SPRAY;
363 }
364
365 #endif
366 #ifdef CSQC
367
368 METHOD(MachineGun, wr_impacteffect, void(entity thiswep, entity actor))
369 {
370     vector org2;
371     org2 = w_org + w_backoff * 2;
372     pointparticles(EFFECT_MACHINEGUN_IMPACT, org2, w_backoff * 1000, 1);
373     if(!w_issilent)
374         sound(actor, CH_SHOTS, SND_RIC_RANDOM(), VOL_BASE, ATTN_NORM);
375 }
376
377 #endif
378 #endif