centerprints now are managed independently per line
[divverent/nexuiz.git] / data / qcsrc / server / cl_weaponsystem.qc
1 /*
2 ===========================================================================
3
4   CLIENT WEAPONSYSTEM CODE
5   Bring back W_Weaponframe
6
7 ===========================================================================
8 */
9
10 // VorteX: static frame globals
11 float WFRAME_FIRE1 = 0;
12 float WFRAME_FIRE2 = 1;
13 float WFRAME_IDLE = 2;
14
15 void(float fr, float t, void() func) weapon_thinkf;
16
17 vector w_shotorg;
18 vector w_shotdir;
19
20 // this function calculates w_shotorg and w_shotdir based on the weapon model
21 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.
22 // make sure you call makevectors first (FIXME?)
23 void(entity ent, vector vecs, float antilag, float recoil, string snd) W_SetupShot =
24 {
25         local vector trueaimpoint;
26
27         traceline_hitcorpse(self, self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 8192, MOVE_NOMONSTERS, self);
28         trueaimpoint = trace_endpos;
29
30         // if aiming at a player and the original trace won't hit that player
31         // anymore, try aiming at the player's new position
32         if (antilag)
33         if (self.cursor_trace_ent)
34         if (self.cursor_trace_ent.takedamage)
35         if (trace_ent != self.cursor_trace_ent)
36         if (cvar("g_antilag"))
37                 trueaimpoint = self.cursor_trace_ent.origin;
38
39         /*
40         // don't allow the shot to start inside a wall
41         local vector startorg;
42         local vector idealorg;
43         startorg = ent.origin + ent.view_ofs;// - '0 0 8';
44         idealorg = ent.origin + ent.view_ofs + v_forward * vecs_x + v_right * vecs_y + v_up * vecs_z;
45         traceline_hitcorpse (ent, startorg, idealorg, MOVE_NOMONSTERS, ent);
46         w_shotorg = trace_endpos;
47         */
48         // all positions in the code are now required to be inside the player box
49         w_shotorg = ent.origin + ent.view_ofs + v_forward * vecs_x + v_right * vecs_y + v_up * vecs_z;
50
51         w_shotdir = normalize(trueaimpoint - w_shotorg);
52
53         if (!cvar("g_norecoil"))
54                 self.punchangle_x = recoil * -1;
55
56         if (snd != "")
57                 sound (self, CHAN_WEAPON, snd, 1, ATTN_NORM);
58
59         if (self.items & IT_STRENGTH)
60         if (!cvar("g_minstagib"))
61                 sound (self, CHAN_AUTO, "weapons/strength_fire.ogg", 1, ATTN_NORM);
62 };
63
64 void LaserTarget_Think()
65 {
66         entity e;
67         vector offset;
68         float uselaser;
69         uselaser = 0;
70
71         // list of weapons that will use the laser, and the options that enable it
72         if(self.owner.laser_on && self.owner.weapon == WEP_ROCKET_LAUNCHER && cvar("g_laserguided_missile"))
73                 uselaser = 1;
74         // example
75         //if(self.owner.weapon == WEP_ELECTRO && cvar("g_laserguided_electro"))
76         //      uselaser = 1;
77
78
79
80         // if a laser-enabled weapon isn't selected, delete any existing laser and quit
81         if(!uselaser)
82         {
83                 // rocket launcher isn't selected, so no laser target.
84                 if(self.lasertarget != world)
85                 {
86                         remove(self.lasertarget);
87                         self.lasertarget = world;
88                 }
89                 return;
90         }
91
92         if(!self.lasertarget)
93         {
94                 // we don't have a lasertarget entity, so spawn one
95                 //bprint("create laser target\n");
96                 e = self.lasertarget = spawn();
97                 e.owner = self.owner;                   // Its owner is my owner
98                 e.classname = "laser_target";
99                 e.movetype = MOVETYPE_NOCLIP;   // don't touch things
100                 setmodel(e, "models/laser_dot.mdl");    // what it looks like
101                 e.scale = 1.25;                         // make it larger
102                 e.alpha = 0.25;                         // transparency
103                 e.colormod = '255 0 0' * (1/255) * 8;   // change colors
104                 e.effects = EF_FULLBRIGHT;
105                 // make it dynamically glow
106                 // you should avoid over-using this, as it can slow down the player's computer.
107                 e.glow_color = 251; // red color
108                 e.glow_size = 12;
109         }
110         else
111                 e = self.lasertarget;
112
113         // move the laser dot to where the player is looking
114
115         makevectors(self.owner.v_angle); // set v_forward etc to the direction the player is looking
116         offset = '0 0 26' + v_right*3;
117         traceline(self.owner.origin + offset, self.owner.origin + offset + v_forward * 8192, FALSE, self); // trace forward until you hit something, like a player or wall
118         setorigin(e, trace_endpos + v_forward*8); // move me to where the traceline ended
119         if(trace_plane_normal != '0 0 0')
120                 e.angles = vectoangles(trace_plane_normal);
121         else
122                 e.angles = vectoangles(v_forward);
123 }
124
125 .string weaponname;
126 void() CL_Weaponentity_Think =
127 {
128         self.nextthink = time;
129         if (intermission_running)
130                 self.frame = WFRAME_IDLE;
131         if (self.owner.weaponentity != self)
132         {
133                 remove(self);
134                 return;
135         }
136         if (self.owner.deadflag != DEAD_NO)
137         {
138                 self.model = "";
139                 return;
140         }
141         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
142         {
143                 self.cnt = self.owner.weapon;
144                 self.dmg = self.owner.modelindex;
145                 self.deadflag = self.owner.deadflag;
146                 if (self.owner.weaponname != "")
147                         setmodel(self, strcat("models/weapons/w_", self.owner.weaponname, ".zym"));
148                 else
149                         self.model = "";
150         }
151         self.effects = self.owner.effects;
152
153         if (self.flags & FL_FLY)
154                 // owner is currently being teleported, so don't apply EF_NODRAW otherwise the viewmodel would "blink"
155                 self.effects = self.effects - (self.effects & EF_NODRAW);
156
157         self.alpha = self.owner.alpha;
158         self.colormap = self.owner.colormap;
159
160         self.angles = '0 0 0';
161         local float f;
162         f = 0;
163         if (self.state == WS_RAISE)
164                 f = (self.owner.weapon_nextthink - time) / cvar("g_balance_weaponswitchdelay");
165         else if (self.state == WS_DROP)
166                 f = 1 - (self.owner.weapon_nextthink - time) / cvar("g_balance_weaponswitchdelay");
167         else if (self.state == WS_CLEAR)
168                 f = 1;
169         self.angles_x = -90 * f * f;
170
171         // create or update the lasertarget entity
172         LaserTarget_Think();
173 };
174
175 void() CL_ExteriorWeaponentity_Think =
176 {
177         self.nextthink = time;
178         if (self.owner.exteriorweaponentity != self)
179         {
180                 remove(self);
181                 return;
182         }
183         if (self.owner.deadflag != DEAD_NO)
184         {
185                 self.model = "";
186                 return;
187         }
188         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
189         {
190                 self.cnt = self.owner.weapon;
191                 self.dmg = self.owner.modelindex;
192                 self.deadflag = self.owner.deadflag;
193                 if (self.owner.weaponname != "")
194                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3"));
195                 else
196                         self.model = "";
197                 setattachment(self, self.owner, "bip01 r hand");
198                 // if that didn't find a tag, hide the exterior weapon model
199                 if (!self.tag_index)
200                         self.model = "";
201         }
202         self.effects = self.owner.effects;
203         self.colormap = self.owner.colormap;
204 };
205
206 // spawning weaponentity for client
207 void() CL_SpawnWeaponentity =
208 {
209         self.weaponentity = spawn();
210         self.weaponentity.solid = SOLID_NOT;
211         self.weaponentity.owner = self;
212         self.weaponentity.weaponentity = self.weaponentity;
213         setmodel(self.weaponentity, "");
214         self.weaponentity.origin = '0 0 0';
215         self.weaponentity.angles = '0 0 0';
216         self.weaponentity.viewmodelforclient = self;
217         self.weaponentity.flags = 0;
218         self.weaponentity.think = CL_Weaponentity_Think;
219         self.weaponentity.nextthink = time;
220         self.weaponentity.scale = 0.61;
221
222         self.exteriorweaponentity = spawn();
223         self.exteriorweaponentity.solid = SOLID_NOT;
224         self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
225         self.exteriorweaponentity.owner = self;
226         self.exteriorweaponentity.origin = '0 0 0';
227         self.exteriorweaponentity.angles = '0 0 0';
228         self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
229         self.exteriorweaponentity.nextthink = time;
230 };
231
232 // convertion from index (= impulse) to flag in .items
233 float(float index) weapon_translateindextoflag =
234 {
235         if (index == WEP_LASER)
236                 return IT_LASER;
237         else if (index == WEP_SHOTGUN)
238                 return IT_SHOTGUN;
239         else if (index == WEP_UZI)
240                 return IT_UZI;
241         else if (index == WEP_GRENADE_LAUNCHER)
242                 return IT_GRENADE_LAUNCHER;
243         else if (index == WEP_ELECTRO)
244                 return IT_ELECTRO;
245         else if (index == WEP_CRYLINK)
246                 return IT_CRYLINK;
247         else if (index == WEP_NEX)
248                 return IT_NEX;
249         else if (index == WEP_HAGAR)
250                 return IT_HAGAR;
251         else if (index == WEP_ROCKET_LAUNCHER)
252                 return IT_ROCKET_LAUNCHER;
253         else if (index == WEP_LASER)
254                 return IT_LASER;
255         else
256                 return 0;
257 };
258
259 float(entity cl, float wpn, float andammo, float complain) client_hasweapon =
260 {
261         local float itemcode, f;
262         local entity oldself;
263
264         if (wpn < WEP_FIRST || wpn > WEP_LAST)
265         {
266                 if (complain)
267                         sprint(self, "Invalid weapon\n");
268                 return FALSE;
269         }
270         itemcode = weapon_translateindextoflag(wpn);
271         if (cl.items & itemcode)
272         {
273                 if (andammo)
274                 {
275                         oldself = self;
276                         self = cl;
277                         f = weapon_action(wpn, WR_CHECKAMMO1);
278                         f = f + weapon_action(wpn, WR_CHECKAMMO2);
279                         self = oldself;
280                         if (!f)
281                         {
282                                 if (complain)
283                                         sprint(self, "You don't have any ammo for that weapon\n");
284                                 return FALSE;
285                         }
286                 }
287                 return TRUE;
288         }
289         if (complain)
290                 sprint(self, "You don't own that weapon\n");
291         return FALSE;
292 };
293
294 // Weapon subs
295 void() w_clear =
296 {
297         if (self.weapon != -1)
298                 self.weapon = 0;
299         if (self.weaponentity)
300         {
301                 self.weaponentity.state = WS_CLEAR;
302                 setmodel(self.weaponentity, "");
303                 self.weaponentity.effects = 0;
304         }
305 };
306
307 void() w_ready =
308 {
309         if (self.weaponentity)
310         {
311                 self.weaponentity.state = WS_READY;
312                 weapon_thinkf(WFRAME_IDLE, 0.1, w_ready);
313         }
314 };
315
316 // FIXME: add qw-style client-custom weaponrating (cl_weaponrating)?
317 float(entity e) w_getbestweapon
318 {// add new weapons here
319         if (client_hasweapon(e, WEP_ROCKET_LAUNCHER, TRUE, FALSE))
320                 return WEP_ROCKET_LAUNCHER;
321         else if (client_hasweapon(e, WEP_NEX, TRUE, FALSE))
322                 return WEP_NEX;
323         else if (client_hasweapon(e, WEP_HAGAR, TRUE, FALSE))
324                 return WEP_HAGAR;
325         else if (client_hasweapon(e, WEP_GRENADE_LAUNCHER, TRUE, FALSE))
326                 return WEP_GRENADE_LAUNCHER;
327         else if (client_hasweapon(e, WEP_ELECTRO, TRUE, FALSE))
328                 return WEP_ELECTRO;
329         else if (client_hasweapon(e, WEP_CRYLINK, TRUE, FALSE))
330                 return WEP_CRYLINK;
331         else if (client_hasweapon(e, WEP_UZI, TRUE, FALSE))
332                 return WEP_UZI;
333         else if (client_hasweapon(e, WEP_SHOTGUN, TRUE, FALSE))
334                 return WEP_SHOTGUN;
335         else if (client_hasweapon(e, WEP_LASER, FALSE, FALSE))
336                 return WEP_LASER;
337         else
338                 return 0;
339 };
340
341 // Setup weapon for client (after this raise frame will be launched)
342 void(float windex, string wname, float hudammo) weapon_setup =
343 {
344         self.items = self.items - (self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS));
345         self.items = self.items | hudammo;
346
347         // the two weapon entities will notice this has changed and update their models
348         self.weapon = windex;
349         self.weaponname = wname;
350 };
351
352 // perform weapon to attack (weaponstate and attack_finished check is here)
353 float(float secondary, float attacktime) weapon_prepareattack =
354 {
355         if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))
356         {
357                 self.switchweapon = w_getbestweapon(self);
358                 if (self.switchweapon != self.weapon)
359                         self.cnt = self.weapon;
360                 return FALSE;
361         }
362         // don't fire if previous attack is not finished
363         if (self.attack_finished > time)
364                 return FALSE;
365         // don't fire while changing weapon
366         if (self.weaponentity.state != WS_READY)
367                 return FALSE;
368         self.weaponentity.state = WS_INUSE;
369         self.attack_finished = max(time, self.attack_finished + attacktime);
370         return TRUE;
371 };
372
373 void(float fr, float t, void() func) weapon_thinkf =
374 {
375         if (fr >= 0)
376         {
377                 if (self.weaponentity != world)
378                         self.weaponentity.frame = fr;
379         }
380
381         if(cvar("g_runematch"))
382         {
383                 if(self.runes & RUNE_SPEED)
384                 {
385                         if(self.runes & CURSE_SLOW)
386                                 t = t * cvar("g_balance_rune_speed_combo_atkrate");
387                         else
388                                 t = t * cvar("g_balance_rune_speed_atkrate");
389                 }
390                 else if(self.runes & CURSE_SLOW)
391                 {
392                         t = t * cvar("g_balance_curse_slow_atkrate");
393                 }
394         }
395
396         // VorteX: haste can be added here
397         self.weapon_nextthink = max(time, self.weapon_nextthink + t);
398         self.weapon_think = func;
399 };
400
401 void(float spd, vector org) weapon_boblayer1 =
402 {
403         // VorteX: haste can be added here
404 };
405