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