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