]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/items/items.qc
items: add loot despawn effects
[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 .bool item_simple; // probably not really needed, but better safe than sorry
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 void ItemDraw(entity this)
20 {
21         // no bobbing applied to simple items, for consistency's sake (no visual difference between ammo and weapons)
22         bool animate = autocvar_cl_items_animate & 1 && this.item_simple <= 0 && (this.ItemStatus & ITS_ANIMATE1 || this.ItemStatus & ITS_ANIMATE2);
23
24         // rotation must be set before running physics
25         if(!animate)
26         {
27                 this.avelocity_y = 0;
28                 this.angles = this.angles_held; // restore angles sent from server
29         }
30         else if(!this.avelocity_y) // unset by MOVETYPE_TOSS or animation was disabled previously
31         {
32                 if(this.ItemStatus & ITS_ANIMATE1)
33                         this.avelocity_y = 180;
34                 else if(this.ItemStatus & ITS_ANIMATE2)
35                         this.avelocity_y = -90;
36         }
37
38         // CSQC physics OR bobbing (both would look weird)
39         float bobheight = 0; // reset bob offset if animations are disabled
40         if(this.move_movetype && (!IS_ONGROUND(this) || this.velocity != '0 0 0'))
41         {
42                 // this isn't equivalent to player prediction but allows smooth motion with very low ISF_LOCATION rate
43                 // which requires running this even if the item is just outside visible range (it could be moving into range)
44                 if(animate)
45                         bobheight = this.origin_z - this.oldorigin_z;
46                 Movetype_Physics_NoMatchTicrate(this, frametime, true);
47                 this.oldorigin = this.origin; // update real (SVQC equivalent) origin
48                 if(animate)
49                 {
50                         if(bobheight)
51                         {
52                                 this.anim_start_time += frametime; // bobbing is paused this frame
53                                 this.oldorigin_z -= bobheight; // restore bob offset (CSQC physics uses the offset bbox)
54                         }
55                         else
56                         {
57                                 this.anim_start_time = time; // starting our bob animation from NOW
58                                 if(this.ItemStatus & ITS_ANIMATE1)
59                                         bobheight = 10; // height of wave at 0 time
60                                 else if(this.ItemStatus & ITS_ANIMATE2)
61                                         bobheight = 8; // height of wave at 0 time
62                         }
63                 }
64         }
65         else if(animate)
66         {
67                 this.angles += this.avelocity * frametime; // MOVETYPE_TOSS does this while it's moving
68
69                 if(this.ItemStatus & ITS_ANIMATE1)
70                         bobheight = 10 + 8 * sin((time - this.anim_start_time) * 2);
71                 else if(this.ItemStatus & ITS_ANIMATE2)
72                         bobheight = 8 + 4 * sin((time - this.anim_start_time) * 3);
73         }
74
75         // apply new bob offset
76         if (bobheight != this.origin_z - this.oldorigin_z)
77         {
78                 this.origin_z = this.oldorigin_z + bobheight;
79                 this.mins_z = 0 - bobheight; // don't want the absmin and absmax to bob
80                 this.maxs_z = 48 - bobheight;
81                 // bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
82         }
83
84         // set alpha based on distance
85         this.alpha = 1;
86         this.drawmask = 0;
87         if(this.fade_end && !warpzone_warpzones_exist)
88         {
89                 vector org = getpropertyvec(VF_ORIGIN);
90                 if(vdist(org - this.origin, >, this.fade_end))
91                         this.alpha = 0; // save on some processing
92                 else if(autocvar_cl_items_fadedist > 0)
93                 {
94                         this.fade_start = max(500, this.fade_end - autocvar_cl_items_fadedist);
95                         if(vdist(org - this.origin, >, this.fade_start))
96                                 this.alpha = bound(0, (this.fade_end - vlen(org - this.origin - 0.5 * (this.mins + this.maxs))) / (this.fade_end - this.fade_start), 1);
97                 }
98         }
99
100         if(!this.alpha)
101                 return;
102
103         // modify alpha based on availability and vehicle hud
104         if(this.ItemStatus & ITS_AVAILABLE)
105         {
106                 if(hud) // apparently this means we're in a vehicle lol
107                 {
108                         this.alpha *= autocvar_cl_items_vehicle_alpha;
109                         this.colormod = this.glowmod = autocvar_cl_items_vehicle_color;
110                 }
111                 else if(this.ItemStatus & ITS_STAYWEP)
112                 {
113                         this.alpha *= autocvar_cl_weapon_stay_alpha;
114                         this.colormod = this.glowmod = autocvar_cl_weapon_stay_color;
115                 }
116                 else
117                 {
118                         this.colormod = '1 1 1';
119                         this.glowmod = this.item_glowmod;
120                 }
121         }
122         else
123         {
124                 this.alpha *= autocvar_cl_ghost_items;
125                 this.colormod = this.glowmod = autocvar_cl_ghost_items_color;
126         }
127
128         if(!this.alpha)
129                 return;
130
131         // loot item despawn effects
132         if(this.ItemStatus & ITS_EXPIRING)
133         {
134                 if(!this.wait) // when receiving the first message with ITS_EXPIRING set
135                 {
136                         this.wait = time + IT_DESPAWNFX_TIME; // it will despawn then
137                         this.delay = 0.25;
138                 }
139
140                 if(autocvar_cl_items_animate & 2)
141                         this.alpha *= (this.wait - time) / IT_DESPAWNFX_TIME;
142
143                 if(autocvar_cl_items_animate & 4 && time >= this.pointtime)
144                 {
145                         pointparticles(EFFECT_ITEM_DESPAWN, this.origin + '0 0 16', '0 0 0', 1);
146                         if (this.delay > 0.0625)
147                                 this.delay *= 0.5;
148                         this.pointtime = time + this.delay;
149                 }
150         }
151
152         this.drawmask = this.alpha <= 0 ? 0 : MASK_NORMAL;
153 }
154
155 void ItemRemove(entity this)
156 {
157         strfree(this.mdl);
158 }
159
160 HashMap ENT_CLIENT_ITEM_simple;
161 STATIC_INIT(ENT_CLIENT_ITEM_simple)
162 {
163         HM_NEW(ENT_CLIENT_ITEM_simple);
164 }
165 SHUTDOWN(ENT_CLIENT_ITEM_simple)
166 {
167         HM_DELETE(ENT_CLIENT_ITEM_simple);
168 }
169
170 NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
171 {
172         int sf = ReadByte();
173
174         if(sf & ISF_LOCATION)
175         {
176                 float bobheight = this.origin_z - this.oldorigin_z;
177                 this.origin = this.oldorigin = ReadVector();
178                 this.origin_z += bobheight; // restore animation offset (SVQC physics is unaware of CSQC bbox offset)
179                 setorigin(this, this.origin); // link
180         }
181
182         if(sf & ISF_ANGLES)
183         {
184                 this.angles = this.angles_held = ReadAngleVector();
185         }
186
187         if(sf & ISF_SIZE)
188         {
189                 setsize(this, '-16 -16 0', '16 16 48');
190         }
191
192         if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc.
193         {
194                 this.ItemStatus = ReadByte();
195
196                 if(this.ItemStatus & ITS_ALLOWFB)
197                         this.effects |= EF_FULLBRIGHT;
198                 else
199                         this.effects &= ~EF_FULLBRIGHT;
200
201                 if(this.ItemStatus & ITS_GLOW)
202                 {
203                         if(this.ItemStatus & ITS_AVAILABLE)
204                                 this.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
205                         else
206                                 this.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
207                 }
208         }
209
210         if(sf & ISF_MODEL)
211         {
212                 if (isnew) IL_PUSH(g_drawables, this);
213                 this.draw = ItemDraw;
214                 this.solid = SOLID_TRIGGER;
215                 //this.flags |= FL_ITEM;
216
217                 this.fade_end = ReadShort();
218
219                 strfree(this.mdl);
220
221                 string _fn = ReadString();
222                 this.item_simple = false; // reset it!
223
224                 if(autocvar_cl_simple_items && (this.ItemStatus & ITS_ALLOWSI))
225                 {
226                         string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
227                         this.item_simple = true;
228
229                                 #define extensions(x) \
230                                         x(md3) \
231                                         x(dpm) \
232                                         x(iqm) \
233                                         x(mdl) \
234                                         /**/
235                                 #define tryext(ext) { \
236                                         string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \
237                                         string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \
238                                         if (cached == "") { \
239                                                 HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \
240                                         } \
241                                         if (cached != "0") { \
242                                                 strcpy(this.mdl, s); \
243                                                 break; \
244                                         } \
245                                 }
246                                 do {
247                                         extensions(tryext);
248                                         this.item_simple = false;
249                                         LOG_TRACEF("Simple item requested for %s but no model exists for it", _fn);
250                                 } while (0);
251                                 #undef tryext
252                                 #undef extensions
253                 }
254
255                 if(!this.item_simple)
256                         strcpy(this.mdl, _fn);
257
258                 if(this.mdl == "")
259                         LOG_WARNF("this.mdl is unset for item %s", this.classname);
260
261                 precache_model(this.mdl);
262                 _setmodel(this, this.mdl);
263
264                 this.skin = ReadByte();
265
266                 setsize(this, '-16 -16 0', '16 16 48');
267         }
268
269         if(sf & ISF_COLORMAP)
270         {
271                 this.colormap = ReadShort();
272                 this.item_glowmod_x = ReadByte() / 255.0;
273                 this.item_glowmod_y = ReadByte() / 255.0;
274                 this.item_glowmod_z = ReadByte() / 255.0;
275         }
276
277         if(sf & ISF_DROP)
278         {
279                 this.gravity = 1;
280                 this.pushable = true;
281                 //this.angles = '0 0 0';
282                 set_movetype(this, MOVETYPE_TOSS);
283                 this.velocity = ReadVector();
284         }
285
286         this.entremove = ItemRemove;
287
288         return true;
289 }