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>
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
19 HashMap ENT_CLIENT_ITEM_simple;
20 STATIC_INIT(ENT_CLIENT_ITEM_simple)
22 HM_NEW(ENT_CLIENT_ITEM_simple);
24 SHUTDOWN(ENT_CLIENT_ITEM_simple)
26 HM_DELETE(ENT_CLIENT_ITEM_simple);
29 void ItemSetModel(entity this, bool wantsimple)
33 string _fn2 = substring(this.mdl, 0 , strlen(this.mdl) -4);
34 #define extensions(x) \
40 #define tryext(ext) { \
41 string s = strcat(_fn2, autocvar_cl_simpleitems_postfix, "." #ext); \
42 string cached = HM_gets(ENT_CLIENT_ITEM_simple, s); \
44 HM_sets(ENT_CLIENT_ITEM_simple, s, cached = fexists(s) ? "1" : "0"); \
46 if (cached != "0") { \
48 this.item_simple = 1; \
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);
63 this.model = this.mdl;
67 // this.model is an engine string so it doesn't need to be zoned and can't be unzoned
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
76 void ItemDraw(entity this)
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);
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);
85 // rotation must be set before running physics
89 this.angles = this.angles_held; // restore angles sent from server
91 else if(!this.avelocity_y) // unset by MOVETYPE_TOSS or animation was disabled previously
93 if(this.ItemStatus & ITS_ANIMATE1)
94 this.avelocity_y = 180;
95 else if(this.ItemStatus & ITS_ANIMATE2)
96 this.avelocity_y = -90;
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'))
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)
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
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)
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
128 this.angles += this.avelocity * frametime; // MOVETYPE_TOSS does this while it's moving
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);
136 // apply new bob offset
137 if (bobheight != this.origin_z - this.oldorigin_z)
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
145 // set alpha based on distance
148 if(this.fade_end && !warpzone_warpzones_exist)
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)
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);
164 // modify alpha based on availability and vehicle hud
165 if(this.ItemStatus & ITS_AVAILABLE)
167 if(hud) // apparently this means we're in a vehicle lol
169 this.alpha *= autocvar_cl_items_vehicle_alpha;
170 this.colormod = this.glowmod = autocvar_cl_items_vehicle_color;
172 else if(this.ItemStatus & ITS_STAYWEP)
174 this.alpha *= autocvar_cl_weapon_stay_alpha;
175 this.colormod = this.glowmod = autocvar_cl_weapon_stay_color;
179 this.colormod = '1 1 1';
180 this.glowmod = this.item_glowmod;
185 this.alpha *= autocvar_cl_ghost_items;
186 this.colormod = this.glowmod = autocvar_cl_ghost_items_color;
192 // loot item despawn effects
193 if(this.ItemStatus & ITS_EXPIRING)
195 if(!this.wait) // when receiving the first message with ITS_EXPIRING set
197 this.wait = time + IT_DESPAWNFX_TIME; // it will despawn then
201 if(autocvar_cl_items_animate & 2)
202 this.alpha *= (this.wait - time) / IT_DESPAWNFX_TIME;
204 if(autocvar_cl_items_animate & 4 && time >= this.pointtime)
206 pointparticles(EFFECT_ITEM_DESPAWN, this.origin + '0 0 16', '0 0 0', 1);
207 if (this.delay > 0.0625)
209 this.pointtime = time + this.delay;
213 this.drawmask = this.alpha <= 0 ? 0 : MASK_NORMAL;
216 void ItemRemove(entity this)
221 NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
225 if(sf & ISF_LOCATION)
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
235 this.angles = this.angles_held = ReadAngleVector();
238 /* bones_was_here TODO: network proper box size for sv_legacy_bbox_expand 0
241 setsize(this, '-16 -16 0', '16 16 48');
245 if(sf & ISF_STATUS) // need to read/write status first so model can handle simple, fb etc.
247 int prevItemStatus = this.ItemStatus;
248 this.ItemStatus = ReadByte();
250 if(this.ItemStatus & ITS_ALLOWFB)
251 this.effects |= EF_FULLBRIGHT;
253 this.effects &= ~EF_FULLBRIGHT;
255 if(this.ItemStatus & ITS_AVAILABLE)
257 if(this.solid != SOLID_TRIGGER)
259 this.solid = SOLID_TRIGGER;
260 setorigin(this, this.origin); // link it to the area grid
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);
270 if(this.solid != SOLID_NOT)
272 this.solid = SOLID_NOT;
273 setorigin(this, this.origin); // optimisation: unlink it from the area grid
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);
287 IL_PUSH(g_drawables, this);
288 this.draw = ItemDraw;
289 this.flags |= FL_ITEM;
290 this.entremove = ItemRemove;
293 this.fade_end = ReadShort();
295 strcpy(this.mdl, ReadString());
296 this.item_simple = -2;
298 this.skin = ReadByte();
301 if(sf & ISF_COLORMAP)
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;
312 this.pushable = true;
313 set_movetype(this, MOVETYPE_TOSS);
314 this.velocity = ReadVector();
316 else if (this.gravity) // caution: kludge FIXME (with sv_legacy_bbox_expand)
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
326 if(sf & ISF_REMOVEFX && !(sf & ISF_SIZE) && !(sf & ISF_MODEL)) // TODO !isnew isn't reliable for this... are we double sending initialisations?
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
333 IL_REMOVE(g_drawables, this);
334 this.solid = SOLID_NOT;