Step 5: complete
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / func / pointparticles.qc
1 REGISTER_NET_LINKED(ENT_CLIENT_POINTPARTICLES)
2
3 #ifdef SVQC
4 // NOTE: also contains func_sparks
5
6 bool pointparticles_SendEntity(entity this, entity to, float fl)
7 {
8         WriteHeader(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
9
10         // optional features to save space
11         fl = fl & 0x0F;
12         if(this.spawnflags & 2)
13                 fl |= 0x10; // absolute count on toggle-on
14         if(this.movedir != '0 0 0' || this.velocity != '0 0 0')
15                 fl |= 0x20; // 4 bytes - saves CPU
16         if(this.waterlevel || this.count != 1)
17                 fl |= 0x40; // 4 bytes - obscure features almost never used
18         if(this.mins != '0 0 0' || this.maxs != '0 0 0')
19                 fl |= 0x80; // 14 bytes - saves lots of space
20
21         WriteByte(MSG_ENTITY, fl);
22         if(fl & 2)
23         {
24                 if(this.state)
25                         WriteCoord(MSG_ENTITY, this.impulse);
26                 else
27                         WriteCoord(MSG_ENTITY, 0); // off
28         }
29         if(fl & 4)
30         {
31                 WriteCoord(MSG_ENTITY, this.origin_x);
32                 WriteCoord(MSG_ENTITY, this.origin_y);
33                 WriteCoord(MSG_ENTITY, this.origin_z);
34         }
35         if(fl & 1)
36         {
37                 if(this.model != "null")
38                 {
39                         WriteShort(MSG_ENTITY, this.modelindex);
40                         if(fl & 0x80)
41                         {
42                                 WriteCoord(MSG_ENTITY, this.mins_x);
43                                 WriteCoord(MSG_ENTITY, this.mins_y);
44                                 WriteCoord(MSG_ENTITY, this.mins_z);
45                                 WriteCoord(MSG_ENTITY, this.maxs_x);
46                                 WriteCoord(MSG_ENTITY, this.maxs_y);
47                                 WriteCoord(MSG_ENTITY, this.maxs_z);
48                         }
49                 }
50                 else
51                 {
52                         WriteShort(MSG_ENTITY, 0);
53                         if(fl & 0x80)
54                         {
55                                 WriteCoord(MSG_ENTITY, this.maxs_x);
56                                 WriteCoord(MSG_ENTITY, this.maxs_y);
57                                 WriteCoord(MSG_ENTITY, this.maxs_z);
58                         }
59                 }
60                 WriteShort(MSG_ENTITY, this.cnt);
61                 WriteString(MSG_ENTITY, this.mdl);
62                 if(fl & 0x20)
63                 {
64                         WriteShort(MSG_ENTITY, compressShortVector(this.velocity));
65                         WriteShort(MSG_ENTITY, compressShortVector(this.movedir));
66                 }
67                 if(fl & 0x40)
68                 {
69                         WriteShort(MSG_ENTITY, this.waterlevel * 16.0);
70                         WriteByte(MSG_ENTITY, this.count * 16.0);
71                 }
72                 WriteString(MSG_ENTITY, this.noise);
73                 if(this.noise != "")
74                 {
75                         WriteByte(MSG_ENTITY, floor(this.atten * 64));
76                         WriteByte(MSG_ENTITY, floor(this.volume * 255));
77                 }
78                 WriteString(MSG_ENTITY, this.bgmscript);
79                 if(this.bgmscript != "")
80                 {
81                         WriteByte(MSG_ENTITY, floor(this.bgmscriptattack * 64));
82                         WriteByte(MSG_ENTITY, floor(this.bgmscriptdecay * 64));
83                         WriteByte(MSG_ENTITY, floor(this.bgmscriptsustain * 255));
84                         WriteByte(MSG_ENTITY, floor(this.bgmscriptrelease * 64));
85                 }
86         }
87         return 1;
88 }
89
90 void pointparticles_use(entity this, entity actor, entity trigger)
91 {
92         this.state = !this.state;
93         this.SendFlags |= 2;
94 }
95
96 void pointparticles_think(entity this)
97 {
98         if(this.origin != this.oldorigin)
99         {
100                 this.SendFlags |= 4;
101                 this.oldorigin = this.origin;
102         }
103         this.nextthink = time;
104 }
105
106 void pointparticles_reset(entity this)
107 {
108         if(this.spawnflags & 1)
109                 this.state = 1;
110         else
111                 this.state = 0;
112 }
113
114 spawnfunc(func_pointparticles)
115 {
116         if(this.model != "") { precache_model(this.model); _setmodel(this, this.model); }
117         if(this.noise != "") precache_sound(this.noise);
118         if(this.mdl != "") this.cnt = 0; // use a good handler
119
120         if(!this.bgmscriptsustain) this.bgmscriptsustain = 1;
121         else if(this.bgmscriptsustain < 0) this.bgmscriptsustain = 0;
122
123         if(!this.atten) this.atten = ATTEN_NORM;
124         else if(this.atten < 0) this.atten = 0;
125         if(!this.volume) this.volume = 1;
126         if(!this.count) this.count = 1;
127         if(!this.impulse) this.impulse = 1;
128
129         if(!this.modelindex)
130         {
131                 setorigin(this, this.origin + this.mins);
132                 setsize(this, '0 0 0', this.maxs - this.mins);
133         }
134         //if(!this.cnt) this.cnt = _particleeffectnum(this.mdl);
135
136         Net_LinkEntity(this, (this.spawnflags & 4), 0, pointparticles_SendEntity);
137
138         IFTARGETED
139         {
140                 this.use = pointparticles_use;
141                 this.reset = pointparticles_reset;
142                 this.reset(this);
143         }
144         else
145                 this.state = 1;
146         setthink(this, pointparticles_think);
147         this.nextthink = time;
148 }
149
150 spawnfunc(func_sparks)
151 {
152         // this.cnt is the amount of sparks that one burst will spawn
153         if(this.cnt < 1) {
154                 this.cnt = 25.0; // nice default value
155         }
156
157         // this.wait is the probability that a sparkthink will spawn a spark shower
158         // range: 0 - 1, but 0 makes little sense, so...
159         if(this.wait < 0.05) {
160                 this.wait = 0.25; // nice default value
161         }
162
163         this.count = this.cnt;
164         this.mins = '0 0 0';
165         this.maxs = '0 0 0';
166         this.velocity = '0 0 -1';
167         this.mdl = "TE_SPARK";
168         this.impulse = 10 * this.wait; // by default 2.5/sec
169         this.wait = 0;
170         this.cnt = 0; // use mdl
171
172         spawnfunc_func_pointparticles(this);
173 }
174 #elif defined(CSQC)
175
176 .int dphitcontentsmask;
177
178 entityclass(PointParticles);
179 class(PointParticles) .int cnt; // effect number
180 class(PointParticles) .vector velocity; // particle velocity
181 class(PointParticles) .float waterlevel; // direction jitter
182 class(PointParticles) .int count; // count multiplier
183 class(PointParticles) .int impulse; // density
184 class(PointParticles) .string noise; // sound
185 class(PointParticles) .float atten;
186 class(PointParticles) .float volume;
187 class(PointParticles) .float absolute; // 1 = count per second is absolute, 2 = only spawn at toggle
188 class(PointParticles) .vector movedir; // trace direction
189 class(PointParticles) .float glow_color; // palette index
190
191 void Draw_PointParticles(entity this)
192 {
193         float n, i, fail;
194         vector p;
195         vector sz;
196         vector o;
197         o = this.origin;
198         sz = this.maxs - this.mins;
199         n = doBGMScript(this);
200         if(this.absolute == 2)
201         {
202                 if(n >= 0)
203                         n = this.just_toggled ? this.impulse : 0;
204                 else
205                         n = this.impulse * drawframetime;
206         }
207         else
208         {
209                 n *= this.impulse * drawframetime;
210                 if(this.just_toggled)
211                         if(n < 1)
212                                 n = 1;
213         }
214         if(n == 0)
215                 return;
216         fail = 0;
217         for(i = random(); i <= n && fail <= 64*n; ++i)
218         {
219                 p = o + this.mins;
220                 p.x += random() * sz.x;
221                 p.y += random() * sz.y;
222                 p.z += random() * sz.z;
223                 if(WarpZoneLib_BoxTouchesBrush(p, p, this, world))
224                 {
225                         if(this.movedir != '0 0 0')
226                         {
227                                 traceline(p, p + normalize(this.movedir) * 4096, 0, world);
228                                 p = trace_endpos;
229                                 int eff_num;
230                                 if(this.cnt)
231                                         eff_num = this.cnt;
232                                 else
233                                         eff_num = _particleeffectnum(this.mdl);
234                                 __pointparticles(eff_num, p, trace_plane_normal * vlen(this.movedir) + this.velocity + randomvec() * this.waterlevel, this.count);
235                         }
236                         else
237                         {
238                                 int eff_num;
239                                 if(this.cnt)
240                                         eff_num = this.cnt;
241                                 else
242                                         eff_num = _particleeffectnum(this.mdl);
243                                 __pointparticles(eff_num, p, this.velocity + randomvec() * this.waterlevel, this.count);
244                         }
245                         if(this.noise != "")
246                         {
247                                 setorigin(this, p);
248                                 _sound(this, CH_AMBIENT, this.noise, VOL_BASE * this.volume, this.atten);
249                         }
250                         this.just_toggled = 0;
251                 }
252                 else if(this.absolute)
253                 {
254                         ++fail;
255                         --i;
256                 }
257         }
258         setorigin(this, o);
259 }
260
261 void Ent_PointParticles_Remove(entity this)
262 {
263         if(this.noise)
264                 strunzone(this.noise);
265         this.noise = string_null;
266         if(this.bgmscript)
267                 strunzone(this.bgmscript);
268         this.bgmscript = string_null;
269         if(this.mdl)
270                 strunzone(this.mdl);
271         this.mdl = string_null;
272 }
273
274 NET_HANDLE(ENT_CLIENT_POINTPARTICLES, bool isnew)
275 {
276         float i;
277         vector v;
278         int f = ReadByte();
279         if(f & 2)
280         {
281                 i = ReadCoord(); // density (<0: point, >0: volume)
282                 if(i && !this.impulse && (this.cnt || this.mdl)) // this.cnt check is so it only happens if the ent already existed
283                         this.just_toggled = 1;
284                 this.impulse = i;
285         }
286         if(f & 4)
287         {
288                 this.origin_x = ReadCoord();
289                 this.origin_y = ReadCoord();
290                 this.origin_z = ReadCoord();
291         }
292         if(f & 1)
293         {
294                 this.modelindex = ReadShort();
295                 if(f & 0x80)
296                 {
297                         if(this.modelindex)
298                         {
299                                 this.mins_x = ReadCoord();
300                                 this.mins_y = ReadCoord();
301                                 this.mins_z = ReadCoord();
302                                 this.maxs_x = ReadCoord();
303                                 this.maxs_y = ReadCoord();
304                                 this.maxs_z = ReadCoord();
305                         }
306                         else
307                         {
308                                 this.mins    = '0 0 0';
309                                 this.maxs_x = ReadCoord();
310                                 this.maxs_y = ReadCoord();
311                                 this.maxs_z = ReadCoord();
312                         }
313                 }
314                 else
315                 {
316                         this.mins = this.maxs = '0 0 0';
317                 }
318
319                 this.cnt = ReadShort(); // effect number
320                 this.mdl = strzone(ReadString()); // effect string
321
322                 if(f & 0x20)
323                 {
324                         this.velocity = decompressShortVector(ReadShort());
325                         this.movedir = decompressShortVector(ReadShort());
326                 }
327                 else
328                 {
329                         this.velocity = this.movedir = '0 0 0';
330                 }
331                 if(f & 0x40)
332                 {
333                         this.waterlevel = ReadShort() / 16.0;
334                         this.count = ReadByte() / 16.0;
335                 }
336                 else
337                 {
338                         this.waterlevel = 0;
339                         this.count = 1;
340                 }
341                 if(this.noise)
342                         strunzone(this.noise);
343                 if(this.bgmscript)
344                         strunzone(this.bgmscript);
345                 this.noise = strzone(ReadString());
346                 if(this.noise != "")
347                 {
348                         this.atten = ReadByte() / 64.0;
349                         this.volume = ReadByte() / 255.0;
350                 }
351                 this.bgmscript = strzone(ReadString());
352                 if(this.bgmscript != "")
353                 {
354                         this.bgmscriptattack = ReadByte() / 64.0;
355                         this.bgmscriptdecay = ReadByte() / 64.0;
356                         this.bgmscriptsustain = ReadByte() / 255.0;
357                         this.bgmscriptrelease = ReadByte() / 64.0;
358                 }
359                 BGMScript_InitEntity(this);
360         }
361
362         return = true;
363
364         if(f & 2)
365         {
366                 this.absolute = (this.impulse >= 0);
367                 if(!this.absolute)
368                 {
369                         v = this.maxs - this.mins;
370                         this.impulse *= -v.x * v.y * v.z / 262144; // relative: particles per 64^3 cube
371                 }
372         }
373
374         if(f & 0x10)
375                 this.absolute = 2;
376
377         setorigin(this, this.origin);
378         setsize(this, this.mins, this.maxs);
379         this.solid = SOLID_NOT;
380         this.draw = Draw_PointParticles;
381         this.entremove = Ent_PointParticles_Remove;
382 }
383 #endif