]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/items/items.qc
Menu: remove useless registries and related code (TempEntities, LinkedEntities, C2S_P...
[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
19 HashMap ENT_CLIENT_ITEM_simple;
20 STATIC_INIT(ENT_CLIENT_ITEM_simple)
21 {
22         HM_NEW(ENT_CLIENT_ITEM_simple);
23 }
24 SHUTDOWN(ENT_CLIENT_ITEM_simple)
25 {
26         HM_DELETE(ENT_CLIENT_ITEM_simple);
27 }
28
29 void ItemSetModel(entity this, bool wantsimple)
30 {
31         if(wantsimple)
32         {
33                 string _fn2 = substring(this.mdl, 0 , strlen(this.mdl) -4);
34                 #define extensions(x) \
35                 x(iqm) \
36                 x(dpm) \
37                 x(md3) \
38                 x(mdl) \
39                 /**/
40                 #define tryext(ext) { \
41                         string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \
42                         string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \
43                         if (cached == "") { \
44                                 HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \
45                         } \
46                         if (cached != "0") { \
47                                 this.model = s; \
48                                 this.item_simple = 1; \
49                                 break; \
50                         } \
51                 }
52                 do {
53                         extensions(tryext);
54                         this.model = this.mdl; // fall back to 3d model
55                         this.item_simple = -1; // don't retry every frame
56                         LOG_TRACEF("Simple item requested for %s but no model exists for it", this.mdl);
57                 } while (0);
58                 #undef tryext
59                 #undef extensions
60         }
61         else
62         {
63                 this.model = this.mdl;
64                 this.item_simple = 0;
65         }
66
67         // this.model is an engine string so it doesn't need to be zoned and can't be unzoned
68         if(this.model == "")
69                 LOG_WARNF("this.model is unset for item %s", this.classname);
70         precache_model(this.model);
71         _setmodel(this, this.model);
72         setsize(this, '-16 -16 0', '16 16 48');
73         // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
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 = 0 - bobheight; // don't want the absmin and absmax to bob
141                 this.maxs_z = 48 - bobheight;
142                 // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
143         }
144
145         // set alpha based on distance
146         this.alpha = 1;
147         this.drawmask = 0;
148         if(this.fade_end && !warpzone_warpzones_exist)
149         {
150                 vector org = getpropertyvec(VF_ORIGIN);
151                 if(vdist(org - this.origin, >, this.fade_end))
152                         this.alpha = 0; // save on some processing
153                 else if(autocvar_cl_items_fadedist > 0)
154                 {
155                         this.fade_start = max(500, this.fade_end - autocvar_cl_items_fadedist);
156                         if(vdist(org - this.origin, >, this.fade_start))
157                                 this.alpha = bound(0, (this.fade_end - vlen(org - this.origin - 0.5 * (this.mins + this.maxs))) / (this.fade_end - this.fade_start), 1);
158                 }
159         }
160
161         if(!this.alpha)
162                 return;
163
164         // modify alpha based on availability and vehicle hud
165         if(this.ItemStatus & ITS_AVAILABLE)
166         {
167                 if(hud) // apparently this means we're in a vehicle lol
168                 {
169                         this.alpha *= autocvar_cl_items_vehicle_alpha;
170                         this.colormod = this.glowmod = autocvar_cl_items_vehicle_color;
171                 }
172                 else if(this.ItemStatus & ITS_STAYWEP)
173                 {
174                         this.alpha *= autocvar_cl_weapon_stay_alpha;
175                         this.colormod = this.glowmod = autocvar_cl_weapon_stay_color;
176                 }
177                 else
178                 {
179                         this.colormod = '1 1 1';
180                         this.glowmod = this.item_glowmod;
181                 }
182         }
183         else
184         {
185                 this.alpha *= autocvar_cl_ghost_items;
186                 this.colormod = this.glowmod = autocvar_cl_ghost_items_color;
187         }
188
189         if(!this.alpha)
190                 return;
191
192         // loot item despawn effects
193         if(this.ItemStatus & ITS_EXPIRING)
194         {
195                 if(!this.wait) // when receiving the first message with ITS_EXPIRING set
196                 {
197                         this.wait = time + IT_DESPAWNFX_TIME; // it will despawn then
198                         this.delay = 0.25;
199                 }
200
201                 if(autocvar_cl_items_animate & 2)
202                         this.alpha *= (this.wait - time) / IT_DESPAWNFX_TIME;
203
204                 if((autocvar_cl_items_animate & 4) && time >= this.pointtime)
205                 {
206                         pointparticles(EFFECT_ITEM_DESPAWN, this.origin + '0 0 16', '0 0 0', 1);
207                         if (this.delay > 0.0625)
208                                 this.delay *= 0.5;
209                         this.pointtime = time + this.delay;
210                 }
211         }
212
213         this.drawmask = this.alpha <= 0 ? 0 : MASK_NORMAL;
214 }
215
216 void ItemRemove(entity this)
217 {
218         strfree(this.mdl);
219 }
220
221 NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
222 {
223         int sf = ReadByte();
224
225         if(sf & ISF_LOCATION)
226         {
227                 float bobheight = this.origin_z - this.oldorigin_z;
228                 this.origin = this.oldorigin = ReadVector();
229                 this.origin_z += bobheight; // restore animation offset (SVQC physics is unaware of CSQC bbox offset)
230                 setorigin(this, this.origin); // link
231         }
232
233         if(sf & ISF_ANGLES)
234         {
235                 this.angles = this.angles_held = ReadAngleVector();
236         }
237
238         /* bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
239         if(sf & ISF_SIZE)
240         {
241                 setsize(this, '-16 -16 0', '16 16 48');
242         }
243         */
244
245         if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc.
246         {
247                 int prevItemStatus = this.ItemStatus;
248                 this.ItemStatus = ReadByte();
249
250                 if(this.ItemStatus & ITS_ALLOWFB)
251                         this.effects |= EF_FULLBRIGHT;
252                 else
253                         this.effects &= ~EF_FULLBRIGHT;
254
255                 if(this.ItemStatus & ITS_AVAILABLE)
256                 {
257                         if(this.solid != SOLID_TRIGGER)
258                         {
259                                 this.solid = SOLID_TRIGGER;
260                                 setorigin(this, this.origin); // link it to the area grid
261                         }
262
263                         if(this.ItemStatus & ITS_GLOW)
264                                 this.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
265                         if(!(prevItemStatus & ITS_AVAILABLE) && this.alpha && !isnew)
266                                 pointparticles(EFFECT_ITEM_RESPAWN, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
267                 }
268                 else
269                 {
270                         if(this.solid != SOLID_NOT)
271                         {
272                                 this.solid = SOLID_NOT;
273                                 setorigin(this, this.origin); // optimisation: unlink it from the area grid
274                         }
275
276                         if(this.ItemStatus & ITS_GLOW)
277                                 this.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
278                         if((prevItemStatus & ITS_AVAILABLE) && this.alpha)
279                                 pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
280                 }
281         }
282
283         if(sf & ISF_MODEL)
284         {
285                 if(isnew)
286                 {
287                         IL_PUSH(g_drawables, this);
288                         this.draw = ItemDraw;
289                         this.flags |= FL_ITEM;
290                         this.entremove = ItemRemove;
291                 }
292
293                 this.fade_end = ReadShort();
294
295                 strcpy(this.mdl, ReadString());
296                 this.item_simple = -2;
297
298                 this.skin = ReadByte();
299         }
300
301         if(sf & ISF_COLORMAP)
302         {
303                 this.colormap = ReadShort();
304                 this.item_glowmod_x = ReadByte() / 255.0;
305                 this.item_glowmod_y = ReadByte() / 255.0;
306                 this.item_glowmod_z = ReadByte() / 255.0;
307         }
308
309         if(sf & ISF_DROP)
310         {
311                 this.gravity = 1;
312                 this.pushable = true;
313                 set_movetype(this, MOVETYPE_TOSS);
314                 this.velocity = ReadVector();
315         }
316         else if (this.gravity) // caution: kludge FIXME (with sv_legacy_bbox_expand)
317         {
318                 // workaround for prediction errors caused by bbox discrepancy between SVQC and CSQC
319                 this.gravity = 0; // don't do this kludge again
320                 this.pushable = false; // no fun allowed
321                 set_movetype(this, MOVETYPE_NONE); // disable physics
322                 this.velocity = '0 0 0'; // disable it more
323                 SET_ONGROUND(this); // extra overkill
324         }
325
326         if(sf & ISF_REMOVEFX && !(sf & ISF_SIZE) && !(sf & ISF_MODEL)) // TODO !isnew isn't reliable for this... are we double sending initialisations?
327         {
328                 // no longer available to pick up, about to be removed
329                 if (this.drawmask) // this.alpha > 0
330                         pointparticles(EFFECT_ITEM_PICKUP, (this.absmin + this.absmax) * 0.5, '0 0 0', 1);
331                 // removing now causes CSQC_Ent_Remove() to spam
332                 this.drawmask = 0;
333                 IL_REMOVE(g_drawables, this);
334                 this.solid = SOLID_NOT;
335         }
336
337         return true;
338 }