]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/items/items.qc
Merge branch 'terencehill/minigame_spectator_list' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / items / items.qc
1 #include "items.qh"
2
3 #include <client/main.qh>
4 #include <common/items/_mod.qh>
5 #include <common/physics/movetypes/movetypes.qh>
6 #include <common/weapons/_all.qh>
7 #include <lib/csqcmodel/cl_model.qh>
8 #include <lib/csqcmodel/common.qh>
9 #include <lib/warpzone/common.qh>
10
11 .vector item_glowmod;
12 .int item_simple;
13 .float alpha;
14 .bool pushable;
15 .float anim_start_time; // reusing for bob waveform synchronisation
16 .vector angles_held; // reusing for (re)storing original angles
17 .float wait, delay, pointtime; // reusing for despawn effects
18 .vector m_mins, m_maxs; // reusing for storing standard bbox (same purpose as in SVQC itemdef)
19
20 HashMap ENT_CLIENT_ITEM_simple;
21 STATIC_INIT(ENT_CLIENT_ITEM_simple)
22 {
23         HM_NEW(ENT_CLIENT_ITEM_simple);
24 }
25 SHUTDOWN(ENT_CLIENT_ITEM_simple)
26 {
27         HM_DELETE(ENT_CLIENT_ITEM_simple);
28 }
29
30 void ItemSetModel(entity this, bool wantsimple)
31 {
32         if(wantsimple)
33         {
34                 string _fn2 = substring(this.mdl, 0 , strlen(this.mdl) -4);
35                 #define extensions(x) \
36                 x(iqm) \
37                 x(dpm) \
38                 x(md3) \
39                 x(mdl) \
40                 /**/
41                 #define tryext(ext) { \
42                         string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \
43                         string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \
44                         if (cached == "") { \
45                                 HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \
46                         } \
47                         if (cached != "0") { \
48                                 this.model = s; \
49                                 this.item_simple = 1; \
50                                 break; \
51                         } \
52                 }
53                 do {
54                         extensions(tryext);
55                         this.model = this.mdl; // fall back to 3d model
56                         this.item_simple = -1; // don't retry every frame
57                         LOG_TRACEF("Simple item requested for %s but no model exists for it", this.mdl);
58                 } while (0);
59                 #undef tryext
60                 #undef extensions
61         }
62         else
63         {
64                 this.model = this.mdl;
65                 this.item_simple = 0;
66         }
67
68         // this.model is an engine string so it doesn't need to be zoned and can't be unzoned
69         if(this.model == "")
70                 LOG_WARNF("this.model is unset for item %s", this.classname);
71         precache_model(this.model);
72         _setmodel(this, this.model);
73         setsize(this, this.m_mins, this.m_maxs);
74 }
75
76 void ItemDraw(entity this)
77 {
78         bool wantsimple = autocvar_cl_simple_items && (this.ItemStatus & ITS_ALLOWSI);
79         if(wantsimple != this.item_simple && this.item_simple != -1)
80                 ItemSetModel(this, wantsimple);
81
82         // no bobbing applied to simple items, for consistency's sake (no visual difference between ammo and weapons)
83         bool animate = (autocvar_cl_items_animate & 1) && this.item_simple <= 0 && ((this.ItemStatus & ITS_ANIMATE1) || (this.ItemStatus & ITS_ANIMATE2));
84
85         // rotation must be set before running physics
86         if(!animate)
87         {
88                 this.avelocity_y = 0;
89                 this.angles = this.angles_held; // restore angles sent from server
90         }
91         else if(!this.avelocity_y) // unset by MOVETYPE_TOSS or animation was disabled previously
92         {
93                 if(this.ItemStatus & ITS_ANIMATE1)
94                         this.avelocity_y = 180;
95                 else if(this.ItemStatus & ITS_ANIMATE2)
96                         this.avelocity_y = -90;
97         }
98
99         // CSQC physics OR bobbing (both would look weird)
100         float bobheight = 0; // reset bob offset if animations are disabled
101         if(this.move_movetype && (!IS_ONGROUND(this) || this.velocity != '0 0 0'))
102         {
103                 // this isn't equivalent to player prediction but allows smooth motion with very low ISF_LOCATION rate
104                 // which requires running this even if the item is just outside visible range (it could be moving into range)
105                 if(animate)
106                         bobheight = this.origin_z - this.oldorigin_z;
107                 Movetype_Physics_NoMatchTicrate(this, frametime, true);
108                 this.oldorigin = this.origin; // update real (SVQC equivalent) origin
109                 if(animate)
110                 {
111                         if(bobheight)
112                         {
113                                 this.anim_start_time += frametime; // bobbing is paused this frame
114                                 this.oldorigin_z -= bobheight; // restore bob offset (CSQC physics uses the offset bbox)
115                         }
116                         else
117                         {
118                                 this.anim_start_time = time; // starting our bob animation from NOW
119                                 if(this.ItemStatus & ITS_ANIMATE1)
120                                         bobheight = 10; // height of wave at 0 time
121                                 else if(this.ItemStatus & ITS_ANIMATE2)
122                                         bobheight = 8; // height of wave at 0 time
123                         }
124                 }
125         }
126         else if(animate)
127         {
128                 this.angles += this.avelocity * frametime; // MOVETYPE_TOSS does this while it's moving
129
130                 if(this.ItemStatus & ITS_ANIMATE1)
131                         bobheight = 10 + 8 * sin((time - this.anim_start_time) * 2);
132                 else if(this.ItemStatus & ITS_ANIMATE2)
133                         bobheight = 8 + 4 * sin((time - this.anim_start_time) * 3);
134         }
135
136         // apply new bob offset
137         if (bobheight != this.origin_z - this.oldorigin_z)
138         {
139                 this.origin_z = this.oldorigin_z + bobheight;
140                 this.mins_z = this.m_mins.z - bobheight; // don't want the absmin and absmax to bob
141                 this.maxs_z = this.m_maxs.z - bobheight;
142         }
143
144         // set alpha based on distance
145         this.alpha = 1;
146         this.drawmask = 0;
147         if(this.fade_end && !warpzone_warpzones_exist)
148         {
149                 vector org = getpropertyvec(VF_ORIGIN);
150                 if(vdist(org - this.origin, >, this.fade_end))
151                         this.alpha = 0; // save on some processing
152                 else if(autocvar_cl_items_fadedist > 0)
153                 {
154                         this.fade_start = max(500, this.fade_end - autocvar_cl_items_fadedist);
155                         if(vdist(org - this.origin, >, this.fade_start))
156                                 this.alpha = bound(0, (this.fade_end - vlen(org - this.origin - 0.5 * (this.mins + this.maxs))) / (this.fade_end - this.fade_start), 1);
157                 }
158         }
159
160         if(!this.alpha)
161                 return;
162
163         // modify alpha based on availability and vehicle hud
164         if(this.ItemStatus & ITS_AVAILABLE)
165         {
166                 if(hud) // apparently this means we're in a vehicle lol
167                 {
168                         this.alpha *= autocvar_cl_items_vehicle_alpha;
169                         this.colormod = this.glowmod = autocvar_cl_items_vehicle_color;
170                 }
171                 else if(this.ItemStatus & ITS_STAYWEP)
172                 {
173                         this.alpha *= autocvar_cl_weapon_stay_alpha;
174                         this.colormod = this.glowmod = autocvar_cl_weapon_stay_color;
175                 }
176                 else
177                 {
178                         this.colormod = '1 1 1';
179                         this.glowmod = this.item_glowmod;
180                 }
181         }
182         else
183         {
184                 this.alpha *= autocvar_cl_ghost_items;
185                 this.colormod = this.glowmod = autocvar_cl_ghost_items_color;
186         }
187
188         if(!this.alpha)
189                 return;
190
191         // loot item despawn effects
192         if(this.ItemStatus & ITS_EXPIRING)
193         {
194                 if(!this.wait) // when receiving the first message with ITS_EXPIRING set
195                 {
196                         this.wait = time + IT_DESPAWNFX_TIME; // it will despawn then
197                         this.delay = 0.25;
198                 }
199
200                 if(autocvar_cl_items_animate & 2)
201                         this.alpha *= (this.wait - time) / IT_DESPAWNFX_TIME;
202
203                 if((autocvar_cl_items_animate & 4) && time >= this.pointtime)
204                 {
205                         pointparticles(EFFECT_ITEM_DESPAWN, this.origin + '0 0 16', '0 0 0', 1);
206                         if (this.delay > 0.0625)
207                                 this.delay *= 0.5;
208                         this.pointtime = time + this.delay;
209                 }
210         }
211
212         this.drawmask = this.alpha <= 0 ? 0 : MASK_NORMAL;
213 }
214
215 void ItemRemove(entity this)
216 {
217         strfree(this.mdl);
218 }
219
220 NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
221 {
222         int sf = ReadByte();
223
224         if(sf & ISF_LOCATION)
225         {
226                 float bobheight = this.origin_z - this.oldorigin_z;
227                 this.origin = this.oldorigin = ReadVector();
228                 this.origin_z += bobheight; // restore animation offset (SVQC physics is unaware of CSQC bbox offset)
229                 setorigin(this, this.origin); // link
230         }
231
232         if(sf & ISF_ANGLES)
233         {
234                 this.angles = this.angles_held = ReadAngleVector();
235         }
236
237         if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc.
238         {
239                 int prevItemStatus = this.ItemStatus;
240                 this.ItemStatus = ReadByte();
241
242                 if(this.ItemStatus & ITS_ALLOWFB)
243                         this.effects |= EF_FULLBRIGHT;
244                 else
245                         this.effects &= ~EF_FULLBRIGHT;
246
247                 if(this.ItemStatus & ITS_AVAILABLE)
248                 {
249                         if(this.solid != SOLID_TRIGGER)
250                         {
251                                 this.solid = SOLID_TRIGGER;
252                                 setorigin(this, this.origin); // link it to the area grid
253                         }
254
255                         if(this.ItemStatus & ITS_GLOW)
256                                 this.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
257                         if(!(prevItemStatus & ITS_AVAILABLE) && this.alpha && !isnew)
258                                 pointparticles(EFFECT_ITEM_RESPAWN, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
259                 }
260                 else
261                 {
262                         if(this.solid != SOLID_NOT)
263                         {
264                                 this.solid = SOLID_NOT;
265                                 setorigin(this, this.origin); // optimisation: unlink it from the area grid
266                         }
267
268                         if(this.ItemStatus & ITS_GLOW)
269                                 this.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
270                         if((prevItemStatus & ITS_AVAILABLE) && this.alpha)
271                                 pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
272                 }
273         }
274
275         if(sf & ISF_SIZE || sf & ISF_SIZE2) // always true when it's spawned (in CSQC's perspective)
276         {
277                 if(isnew)
278                 {
279                         IL_PUSH(g_drawables, this);
280                         this.draw = ItemDraw;
281                         this.flags |= FL_ITEM;
282                         this.entremove = ItemRemove;
283                 }
284
285                 if(sf & ISF_SIZE && !(sf & ISF_SIZE2)) // Small
286                 {
287                         this.m_mins = ITEM_S_MINS;
288                         this.m_maxs = ITEM_S_MAXS;
289                 }
290                 else if(!(sf & ISF_SIZE) && sf & ISF_SIZE2) // Large
291                 {
292                         this.m_mins = ITEM_D_MINS;
293                         this.m_maxs = ITEM_L_MAXS;
294                 }
295                 else // Default
296                 {
297                         this.m_mins = ITEM_D_MINS;
298                         this.m_maxs = ITEM_D_MAXS;
299                 }
300
301                 this.fade_end = ReadShort();
302
303                 strcpy(this.mdl, ReadString());
304                 this.item_simple = -2;
305
306                 this.skin = ReadByte();
307         }
308
309         if(sf & ISF_COLORMAP)
310         {
311                 this.colormap = ReadShort();
312                 this.item_glowmod_x = ReadByte() / 255.0;
313                 this.item_glowmod_y = ReadByte() / 255.0;
314                 this.item_glowmod_z = ReadByte() / 255.0;
315         }
316
317         if(sf & ISF_DROP)
318         {
319                 this.gravity = 1;
320                 this.pushable = true;
321                 set_movetype(this, MOVETYPE_TOSS);
322                 this.velocity = ReadVector();
323         }
324         else if (this.gravity) // caution: kludge FIXME (with sv_legacy_bbox_expand)
325         {
326                 // workaround for prediction errors caused by bbox discrepancy between SVQC and CSQC
327                 this.gravity = 0; // don't do this kludge again
328                 this.pushable = false; // no fun allowed
329                 set_movetype(this, MOVETYPE_NONE); // disable physics
330                 this.velocity = '0 0 0'; // disable it more
331                 SET_ONGROUND(this); // extra overkill
332         }
333
334         if(sf & ISF_REMOVEFX && !(sf & ISF_SIZE) && !(sf & ISF_SIZE2)) // TODO !isnew isn't reliable for this... are we double sending initialisations?
335         {
336                 // no longer available to pick up, about to be removed
337                 if (this.drawmask) // this.alpha > 0
338                         pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
339                 // removing now causes CSQC_Ent_Remove() to spam
340                 this.drawmask = 0;
341                 IL_REMOVE(g_drawables, this);
342                 this.solid = SOLID_NOT;
343         }
344
345         return true;
346 }