]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/all.qc
Viewmodels: CSQC rendering
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / all.qc
1 #ifndef WEAPONS_ALL_C
2 #define WEAPONS_ALL_C
3
4 #include "all.qh"
5
6 #if defined(CSQC)
7         #include "../../client/defs.qh"
8         #include "../constants.qh"
9         #include "../stats.qh"
10         #include "../../lib/warpzone/anglestransform.qh"
11         #include "../../lib/warpzone/common.qh"
12         #include "../../lib/warpzone/client.qh"
13         #include "../util.qh"
14         #include "../buffs/all.qh"
15         #include "../../client/autocvars.qh"
16         #include "../deathtypes/all.qh"
17         #include "../../lib/csqcmodel/interpolate.qh"
18         #include "../movetypes/movetypes.qh"
19         #include "../../client/main.qh"
20         #include "../../lib/csqcmodel/cl_model.qh"
21 #elif defined(MENUQC)
22 #elif defined(SVQC)
23         #include "../../lib/warpzone/anglestransform.qh"
24         #include "../../lib/warpzone/common.qh"
25         #include "../../lib/warpzone/util_server.qh"
26         #include "../../lib/warpzone/server.qh"
27         #include "../constants.qh"
28         #include "../stats.qh"
29         #include "../teams.qh"
30         #include "../util.qh"
31         #include "../buffs/all.qh"
32         #include "../monsters/all.qh"
33         #include "config.qh"
34         #include "../../server/weapons/csqcprojectile.qh"
35         #include "../../server/weapons/tracing.qh"
36         #include "../../server/t_items.qh"
37         #include "../../server/autocvars.qh"
38         #include "../../server/constants.qh"
39         #include "../../server/defs.qh"
40         #include "../notifications.qh"
41         #include "../deathtypes/all.qh"
42         #include "../../server/mutators/all.qh"
43         #include "../mapinfo.qh"
44         #include "../../server/command/common.qh"
45         #include "../../lib/csqcmodel/sv_model.qh"
46         #include "../../server/portals.qh"
47         #include "../../server/g_hook.qh"
48 #endif
49 #ifndef MENUQC
50         #include "calculations.qc"
51 #endif
52 #define IMPLEMENTATION
53 #include "all.inc"
54 #undef IMPLEMENTATION
55
56 // WEAPON PLUGIN SYSTEM
57
58 WepSet WepSet_FromWeapon(int a)
59 {
60         a -= WEP_FIRST;
61         if (Weapons_MAX > 24)
62                 if (a >= 24)
63                 {
64                         a -= 24;
65                         if (Weapons_MAX > 48)
66                                 if (a >= 24)
67                                 {
68                                         a -= 24;
69                                         return '0 0 1' * power2of(a);
70                                 }
71                         return '0 1 0' * power2of(a);
72                 }
73         return '1 0 0' * power2of(a);
74 }
75 #ifdef SVQC
76         void WepSet_AddStat()
77         {
78                 addstat(STAT_WEAPONS, AS_INT, weapons_x);
79                 if (Weapons_MAX > 24) addstat(STAT_WEAPONS2, AS_INT, weapons_y);
80                 if (Weapons_MAX > 48) addstat(STAT_WEAPONS3, AS_INT, weapons_z);
81         }
82         void WepSet_AddStat_InMap()
83         {
84                 addstat(STAT_WEAPONSINMAP, AS_INT, weaponsinmap_x);
85                 if (Weapons_MAX > 24) addstat(STAT_WEAPONSINMAP2, AS_INT, weaponsinmap_y);
86                 if (Weapons_MAX > 48) addstat(STAT_WEAPONSINMAP3, AS_INT, weaponsinmap_z);
87         }
88         void WriteWepSet(float dst, WepSet w)
89         {
90                 if (Weapons_MAX > 48) WriteInt72_t(dst, w);
91                 else if (Weapons_MAX > 24) WriteInt48_t(dst, w);
92                 else WriteInt24_t(dst, w.x);
93         }
94 #endif
95 #ifdef CSQC
96         WepSet WepSet_GetFromStat()
97         {
98                 WepSet w = '0 0 0';
99                 w.x = getstati(STAT_WEAPONS);
100                 if (Weapons_MAX > 24) w.y = getstati(STAT_WEAPONS2);
101                 if (Weapons_MAX > 48) w.z = getstati(STAT_WEAPONS3);
102                 return w;
103         }
104         WepSet WepSet_GetFromStat_InMap()
105         {
106                 WepSet w = '0 0 0';
107                 w_x = getstati(STAT_WEAPONSINMAP);
108                 if (Weapons_MAX > 24) w_y = getstati(STAT_WEAPONSINMAP2);
109                 if (Weapons_MAX > 48) w_z = getstati(STAT_WEAPONSINMAP3);
110                 return w;
111         }
112         WepSet ReadWepSet()
113         {
114                 if (Weapons_MAX > 48) return ReadInt72_t();
115                 if (Weapons_MAX > 24) return ReadInt48_t();
116                 return ReadInt24_t() * '1 0 0';
117         }
118 #endif
119
120 string W_FixWeaponOrder(string order, float complete)
121 {
122         return fixPriorityList(order, WEP_FIRST, WEP_LAST, WEP_IMPULSE_BEGIN - WEP_FIRST, complete);
123 }
124 string W_NameWeaponOrder_MapFunc(string s)
125 {
126         entity wi;
127         if (s == "0" || stof(s))
128         {
129                 wi = get_weaponinfo(stof(s));
130                 if (wi != WEP_Null) return wi.netname;
131         }
132         return s;
133 }
134
135 string W_UndeprecateName(string s)
136 {
137         switch (s)
138         {
139                 case "nex": return "vortex";
140                 case "rocketlauncher": return "devastator";
141                 case "laser": return "blaster";
142                 case "minstanex": return "vaporizer";
143                 case "grenadelauncher": return "mortar";
144                 case "uzi": return "machinegun";
145                 default: return s;
146         }
147 }
148 string W_NameWeaponOrder(string order)
149 {
150         return mapPriorityList(order, W_NameWeaponOrder_MapFunc);
151 }
152 string W_NumberWeaponOrder_MapFunc(string s)
153 {
154         int i;
155         if (s == "0" || stof(s)) return s;
156         s = W_UndeprecateName(s);
157         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
158                 if (s == get_weaponinfo(i).netname) return ftos(i);
159         return s;
160 }
161 string W_NumberWeaponOrder(string order)
162 {
163         return mapPriorityList(order, W_NumberWeaponOrder_MapFunc);
164 }
165
166 float W_FixWeaponOrder_BuildImpulseList_buf[Weapons_MAX];
167 string W_FixWeaponOrder_BuildImpulseList_order;
168 void W_FixWeaponOrder_BuildImpulseList_swap(int i, int j, entity pass)
169 {
170         float h;
171         h = W_FixWeaponOrder_BuildImpulseList_buf[i];
172         W_FixWeaponOrder_BuildImpulseList_buf[i] = W_FixWeaponOrder_BuildImpulseList_buf[j];
173         W_FixWeaponOrder_BuildImpulseList_buf[j] = h;
174 }
175 float W_FixWeaponOrder_BuildImpulseList_cmp(int i, int j, entity pass)
176 {
177         entity e1, e2;
178         float d;
179         e1 = get_weaponinfo(W_FixWeaponOrder_BuildImpulseList_buf[i]);
180         e2 = get_weaponinfo(W_FixWeaponOrder_BuildImpulseList_buf[j]);
181         d = (e1.impulse + 9) % 10 - (e2.impulse + 9) % 10;
182         if (d != 0) return -d;  // high impulse first!
183         return strstrofs(strcat(" ", W_FixWeaponOrder_BuildImpulseList_order, " "),
184                 sprintf(" %d ", W_FixWeaponOrder_BuildImpulseList_buf[i]), 0)
185                -
186                strstrofs(strcat(" ", W_FixWeaponOrder_BuildImpulseList_order, " "),
187                 sprintf(" %d ", W_FixWeaponOrder_BuildImpulseList_buf[j]), 0)
188         ;  // low char index first!
189 }
190 string W_FixWeaponOrder_BuildImpulseList(string o)
191 {
192         int i;
193         W_FixWeaponOrder_BuildImpulseList_order = o;
194         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
195                 W_FixWeaponOrder_BuildImpulseList_buf[i - WEP_FIRST] = i;
196         heapsort(WEP_LAST - WEP_FIRST + 1, W_FixWeaponOrder_BuildImpulseList_swap, W_FixWeaponOrder_BuildImpulseList_cmp,
197                 world);
198         o = "";
199         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
200                 o = strcat(o, " ", ftos(W_FixWeaponOrder_BuildImpulseList_buf[i - WEP_FIRST]));
201         W_FixWeaponOrder_BuildImpulseList_order = string_null;
202         return substring(o, 1, -1);
203 }
204
205 string W_FixWeaponOrder_AllowIncomplete(string order)
206 {
207         return W_FixWeaponOrder(order, 0);
208 }
209
210 string W_FixWeaponOrder_ForceComplete(string order)
211 {
212         if (order == "") order = W_NumberWeaponOrder(cvar_defstring("cl_weaponpriority"));
213         return W_FixWeaponOrder(order, 1);
214 }
215
216 void W_RandomWeapons(entity e, float n)
217 {
218         int i, j;
219         WepSet remaining;
220         WepSet result;
221         remaining = e.weapons;
222         result = '0 0 0';
223         for (i = 0; i < n; ++i)
224         {
225                 RandomSelection_Init();
226                 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
227                         if (remaining & WepSet_FromWeapon(j)) RandomSelection_Add(world, j, string_null, 1, 1);
228                 result |= WepSet_FromWeapon(RandomSelection_chosen_float);
229                 remaining &= ~WepSet_FromWeapon(RandomSelection_chosen_float);
230         }
231         e.weapons = result;
232 }
233
234 string GetAmmoPicture(.int ammotype)
235 {
236         switch (ammotype)
237         {
238                 case ammo_shells:  return ITEM_Shells.m_icon;
239                 case ammo_nails:   return ITEM_Bullets.m_icon;
240                 case ammo_rockets: return ITEM_Rockets.m_icon;
241                 case ammo_cells:   return ITEM_Cells.m_icon;
242                 case ammo_plasma:  return ITEM_Plasma.m_icon;
243                 case ammo_fuel:    return ITEM_JetpackFuel.m_icon;
244                 default: return "";  // wtf, no ammo type?
245         }
246 }
247
248 #ifdef CSQC
249         .int GetAmmoFieldFromNum(int i)
250         {
251                 switch (i)
252                 {
253                         case 0: return ammo_shells;
254                         case 1: return ammo_nails;
255                         case 2: return ammo_rockets;
256                         case 3: return ammo_cells;
257                         case 4: return ammo_plasma;
258                         case 5: return ammo_fuel;
259                         default: return ammo_none;
260                 }
261         }
262
263         int GetAmmoStat(.int ammotype)
264         {
265                 switch (ammotype)
266                 {
267                         case ammo_shells: return STAT_SHELLS;
268                         case ammo_nails: return STAT_NAILS;
269                         case ammo_rockets: return STAT_ROCKETS;
270                         case ammo_cells: return STAT_CELLS;
271                         case ammo_plasma: return STAT_PLASMA;
272                         case ammo_fuel: return STAT_FUEL;
273                         default: return -1;
274                 }
275         }
276 #endif
277
278 string W_Sound(string w_snd)
279 {
280         #define extensions(X) X(wav) X(ogg)
281         #define tryext(ext) { if (fexists(strcat("sound/", output = strcat("weapons/", w_snd, "." #ext)))) break; }
282         string output;
283         do
284         {
285                 extensions(tryext);
286 #undef tryext
287 #undef extensions
288                 output = strcat("weapons/", w_snd);
289         }
290         while (0);
291
292 #ifdef SVQC
293                 MUTATOR_CALLHOOK(WeaponSound, w_snd, output);
294                 return weapon_sound_output;
295 #else
296                 return output;
297 #endif
298 }
299
300 string W_Model(string w_mdl)
301 {
302         string output = strcat("models/weapons/", w_mdl);
303 #ifdef SVQC
304                 MUTATOR_CALLHOOK(WeaponModel, w_mdl, output);
305                 return weapon_model_output;
306 #else
307                 return output;
308 #endif
309 }
310
311 #ifndef MENUQC
312 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float algn)
313 {
314         switch (algn)
315         {
316                 default:
317                 case 3:
318                         // right alignment
319                         break;
320                 case 4:
321                         // left
322                         vecs.y = -vecs.y;
323                         break;
324                 case 1:
325                 case 2:
326                         // center
327                         vecs.y = 0;
328                         vecs.z -= 2;
329                         break;
330         }
331         return vecs;
332 }
333
334 vector shotorg_adjust_values(vector vecs, bool y_is_right, bool visual, int algn)
335 {
336 #ifdef SVQC
337         string s;
338 #endif
339         if (visual)
340         {
341                 vecs = shotorg_adjustfromclient(vecs, y_is_right, algn);
342         }
343 #ifdef SVQC
344         else if (autocvar_g_shootfromeye)
345         {
346                 vecs.y = vecs.z = 0;
347         }
348         else if (autocvar_g_shootfromcenter)
349         {
350                 vecs.y = 0;
351                 vecs.z -= 2;
352         }
353         else if ((s = autocvar_g_shootfromfixedorigin) != "")
354         {
355                 vector v = stov(s);
356                 if (y_is_right) v.y = -v.y;
357                 if (v.x != 0) vecs.x = v.x;
358                 vecs.y = v.y;
359                 vecs.z = v.z;
360         }
361 #endif
362         else  // just do the same as top
363         {
364                 vecs = shotorg_adjustfromclient(vecs, y_is_right, algn);
365         }
366
367         return vecs;
368 }
369
370 #define shotorg_adjust shotorg_adjust_values
371
372 /**
373  * supported formats:
374  *
375  * 1. simple animated model, muzzle flash handling on h_ model:
376  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
377  *      tags:
378  *        shot = muzzle end (shot origin, also used for muzzle flashes)
379  *        shell = casings ejection point (must be on the right hand side of the gun)
380  *        weapon = attachment for v_tuba.md3
381  *    v_tuba.md3 - first and third person model
382  *    g_tuba.md3 - pickup model
383  *
384  * 2. simple animated model, muzzle flash handling on v_ model:
385  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
386  *      tags:
387  *        weapon = attachment for v_tuba.md3
388  *    v_tuba.md3 - first and third person model
389  *      tags:
390  *        shot = muzzle end (shot origin, also used for muzzle flashes)
391  *        shell = casings ejection point (must be on the right hand side of the gun)
392  *    g_tuba.md3 - pickup model
393  *
394  * 3. fully animated model, muzzle flash handling on h_ model:
395  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
396  *      tags:
397  *        shot = muzzle end (shot origin, also used for muzzle flashes)
398  *        shell = casings ejection point (must be on the right hand side of the gun)
399  *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
400  *    v_tuba.md3 - third person model
401  *    g_tuba.md3 - pickup model
402  *
403  * 4. fully animated model, muzzle flash handling on v_ model:
404  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
405  *      tags:
406  *        shot = muzzle end (shot origin)
407  *        shell = casings ejection point (must be on the right hand side of the gun)
408  *    v_tuba.md3 - third person model
409  *      tags:
410  *        shot = muzzle end (for muzzle flashes)
411  *    g_tuba.md3 - pickup model
412  *
413  * writes:
414  *   this.origin, this.angles
415  *   this.weaponchild
416  *   this.movedir, this.view_ofs
417  *   attachment stuff
418  *   anim stuff
419  * to free:
420  *   call again with ""
421  *   remove the ent
422  */
423 void CL_WeaponEntity_SetModel(entity this, string name)
424 {
425         if (name == "")
426         {
427                 this.model = "";
428                 if (this.weaponchild) remove(this.weaponchild);
429                 this.weaponchild = NULL;
430                 this.movedir = '0 0 0';
431                 this.spawnorigin = '0 0 0';
432                 this.oldorigin = '0 0 0';
433                 this.anim_fire1  = '0 1 0.01';
434                 this.anim_fire2  = '0 1 0.01';
435                 this.anim_idle   = '0 1 0.01';
436                 this.anim_reload = '0 1 0.01';
437         }
438         else
439         {
440                 // if there is a child entity, hide it until we're sure we use it
441                 if (this.weaponchild) this.weaponchild.model = "";
442                 _setmodel(this, W_Model(strcat("v_", name, ".md3")));
443                 int v_shot_idx;  // used later
444                 (v_shot_idx = gettagindex(this, "shot")) || (v_shot_idx = gettagindex(this, "tag_shot"));
445
446                 _setmodel(this, W_Model(strcat("h_", name, ".iqm")));
447                 // preset some defaults that work great for renamed zym files (which don't need an animinfo)
448                 this.anim_fire1  = animfixfps(this, '0 1 0.01', '0 0 0');
449                 this.anim_fire2  = animfixfps(this, '1 1 0.01', '0 0 0');
450                 this.anim_idle   = animfixfps(this, '2 1 0.01', '0 0 0');
451                 this.anim_reload = animfixfps(this, '3 1 0.01', '0 0 0');
452
453                 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
454                 // if we don't, this is a "real" animated model
455                 string t;
456                 if ((t = "weapon", gettagindex(this, t)) || (t = "tag_weapon", gettagindex(this, t)))
457                 {
458                         if (!this.weaponchild)
459                         {
460                                 this.weaponchild = new(weaponchild);
461 #ifdef CSQC
462                                 this.weaponchild.drawmask = MASK_NORMAL;
463 #endif
464                         }
465                         _setmodel(this.weaponchild, W_Model(strcat("v_", name, ".md3")));
466                         setattachment(this.weaponchild, this, t);
467                 }
468                 else
469                 {
470                         if (this.weaponchild) remove(this.weaponchild);
471                         this.weaponchild = NULL;
472                 }
473
474                 setorigin(this, '0 0 0');
475                 this.angles = '0 0 0';
476                 this.frame = 0;
477 #ifdef SVQC
478                 this.viewmodelforclient = NULL;
479 #else
480                 this.renderflags &= ~RF_VIEWMODEL;
481 #endif
482                 if (v_shot_idx)  // v_ model attached to invisible h_ model
483                 {
484                         this.movedir = gettaginfo(this.weaponchild, v_shot_idx);
485                 }
486                 else
487                 {
488                         int idx;
489                         if ((idx = gettagindex(this, "shot")) || (idx = gettagindex(this, "tag_shot")))
490                         {
491                                 this.movedir = gettaginfo(this, idx);
492                         }
493                         else
494                         {
495                                 LOG_WARNINGF("weapon model %s does not support the 'shot' tag, will display shots TOTALLY wrong\n",
496                                         this.model);
497                                 this.movedir = '0 0 0';
498                         }
499                 }
500                 {
501                         int idx = 0;
502                         // v_ model attached to invisible h_ model
503                         if (this.weaponchild
504                             && ((idx = gettagindex(this.weaponchild, "shell")) || (idx = gettagindex(this.weaponchild, "tag_shell"))))
505                         {
506                                 this.spawnorigin = gettaginfo(this.weaponchild, idx);
507                         }
508                         else if ((idx = gettagindex(this, "shell")) || (idx = gettagindex(this, "tag_shell")))
509                         {
510                                 this.spawnorigin = gettaginfo(this, idx);
511                         }
512                         else
513                         {
514                                 LOG_WARNINGF("weapon model %s does not support the 'shell' tag, will display casings wrong\n",
515                                         this.model);
516                                 this.spawnorigin = this.movedir;
517                         }
518                 }
519                 if (v_shot_idx)
520                 {
521                         this.oldorigin = '0 0 0';  // use regular attachment
522                 }
523                 else
524                 {
525                         int idx;
526                         if (this.weaponchild)
527                                 (idx = gettagindex(this, "weapon")) || (idx = gettagindex(this, "tag_weapon"));
528                         else
529                                 (idx = gettagindex(this, "handle")) || (idx = gettagindex(this, "tag_handle"));
530                         if (idx)
531                         {
532                                 this.oldorigin = this.movedir - gettaginfo(this, idx);
533                         }
534                         else
535                         {
536                                 LOG_WARNINGF(
537                                         "weapon model %s does not support the 'handle' tag "
538                                         "and neither does the v_ model support the 'shot' tag, "
539                                         "will display muzzle flashes TOTALLY wrong\n",
540                                         this.model);
541                                 this.oldorigin = '0 0 0';  // there is no way to recover from this
542                         }
543                 }
544
545 #ifdef SVQC
546                 this.viewmodelforclient = this.owner;
547 #else
548                 this.renderflags |= RF_VIEWMODEL;
549 #endif
550         }
551
552         this.view_ofs = '0 0 0';
553
554         if (this.movedir.x >= 0)
555         {
556 #ifdef SVQC
557                 int algn = this.owner.cvar_cl_gunalign;
558 #else
559                 int algn = autocvar_cl_gunalign;
560 #endif
561                 vector v = this.movedir;
562                 this.movedir = shotorg_adjust(v, false, false, algn);
563                 this.view_ofs = shotorg_adjust(v, false, true, algn) - v;
564         }
565         int compressed_shotorg = compressShotOrigin(this.movedir);
566         // make them match perfectly
567 #ifdef SVQC
568         this.movedir = decompressShotOrigin(this.owner.stat_shotorg = compressed_shotorg);
569 #else
570         this.movedir = decompressShotOrigin(compressed_shotorg);
571 #endif
572
573         this.spawnorigin += this.view_ofs;  // offset the casings origin by the same amount
574
575         // check if an instant weapon switch occurred
576         setorigin(this, this.view_ofs);
577         // reset animstate now
578         this.wframe = WFRAME_IDLE;
579         setanim(this, this.anim_idle, true, false, true);
580 }
581 #endif
582
583 #ifndef MENUQC
584
585 REGISTER_NET_TEMP(wframe, bool isNew)
586 #ifdef CSQC
587 {
588         vector a;
589         a.x = ReadCoord();
590     a.y = ReadCoord();
591     a.z = ReadCoord();
592         bool restartanim = ReadByte();
593         setanim(viewmodel, a, restartanim == false, restartanim, restartanim);
594 }
595 #endif
596
597 #ifdef SVQC
598 void wframe_send(entity actor, vector a, bool restartanim)
599 {
600         if (!IS_REAL_CLIENT(actor)) return;
601         int channel = MSG_ONE;
602         msg_entity = actor;
603         WriteHeader(channel, wframe);
604         WriteCoord(channel, a.x);
605         WriteCoord(channel, a.y);
606         WriteCoord(channel, a.z);
607         WriteByte(channel, restartanim);
608 }
609 #endif
610
611 #endif
612
613 #endif