1#include "projectile.qh"
2
3#include "../autocvars.qh"
4#include "../defs.qh"
5#include "../main.qh"
6#include "../mutators/events.qh"
7
8#include <common/constants.qh>
9#include <common/net_linked.qh>
10#include <common/physics/movetypes/movetypes.qh>
11
12#include <lib/csqcmodel/interpolate.qh>
13
14#include <lib/warpzone/anglestransform.qh>
15
16.float alpha;
17.float scale;
18.vector colormod;
19
20void SUB_Stop(entity this, entity toucher)
21{
22	this.velocity = this.avelocity = '0 0 0';
23	set_movetype(this, MOVETYPE_NONE);
24}
25
26void Projectile_ResetTrail(entity this, vector to)
27{
28	this.trail_oldorigin = to;
29	this.trail_oldtime = time;
30}
31
32void Projectile_DrawTrail(entity this, vector to)
33{
34	vector from = this.trail_oldorigin;
35	// float t0 = this.trail_oldtime;
36	this.trail_oldorigin = to;
37	this.trail_oldtime = time;
38
39	// force the effect even for stationary firemine
40	if (this.cnt == PROJECTILE_FIREMINE)
41		if (from == to)
42			from.z += 1;
43
44	if (this.traileffect)
45	{
46		particles_alphamin = particles_alphamax = particles_fade = sqrt(this.alpha);
47		boxparticles(particleeffectnum(Effects_from(this.traileffect)), this, from, to, this.velocity, this.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL);
48	}
49}
50
51bool Projectile_isnade(int proj); // TODO: remove
52
53void Projectile_Draw(entity this)
54{
55	vector rot;
56	vector trailorigin;
57	int f;
58	bool drawn;
59	float t;
60	float a;
61
62	f = this.flags;
63
64	if (this.count & 0x80)
65	{
66		// UNSET_ONGROUND(this);
67		if (this.move_movetype == MOVETYPE_NONE || this.move_movetype == MOVETYPE_FLY)
68			Movetype_Physics_NoMatchServer(this);
69		// the trivial movetypes do not have to match the
70		// server's ticrate as they are ticrate independent
71		// NOTE: this assumption is only true if MOVETYPE_FLY
72		// projectiles detonate on impact. If they continue
73		// moving, we might still be ticrate dependent.
74		else
75			Movetype_Physics_MatchServer(this, autocvar_cl_projectiles_sloppy);
76		if (!IS_ONGROUND(this))
77			if (this.velocity != '0 0 0')
78				this.angles = vectoangles(this.velocity);
79	}
80	else
81	{
82		InterpolateOrigin_Do(this);
83	}
84
85	if (this.count & 0x80)
86	{
87		drawn = (time >= this.spawntime - 0.02);
88		t = max(time, this.spawntime);
89	}
90	else
91	{
92		drawn = (this.iflags & IFLAG_VALID);
93		t = time;
94	}
95
96	if (!(f & FL_ONGROUND))
97	{
98		rot = '0 0 0';
99		switch (this.cnt)
100		{
101			/*
102			case PROJECTILE_GRENADE:
103			    rot = '-2000 0 0'; // forward
104			    break;
105			*/
106			case PROJECTILE_GRENADE_BOUNCING:
107				rot = '0 -1000 0'; // sideways
108				break;
109			case PROJECTILE_HOOKBOMB:
110				rot = '1000 0 0';  // forward
111				break;
112			default:
113				break;
114		}
115
116		if (Projectile_isnade(this.cnt))
117			rot = this.avelocity;
118
119		this.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(this.angles), rot * (t - this.spawntime)));
120	}
121
122	vector ang;
123	ang = this.angles;
124	ang.x = -ang.x;
125	makevectors(ang);
126
127	a = 1 - (time - this.fade_time) * this.fade_rate;
128	this.alpha = bound(0, this.alphamod * a, 1);
129	if (this.alpha <= 0)
130		drawn = 0;
131	this.renderflags = 0;
132
133	trailorigin = this.origin;
134	switch (this.cnt)
135	{
136		case PROJECTILE_GRENADE:
137		case PROJECTILE_GRENADE_BOUNCING:
138			trailorigin += v_right * 1 + v_forward * -10;
139			break;
140		default:
141			break;
142	}
143
144	if (Projectile_isnade(this.cnt))
145		trailorigin += v_up * 4;
146
147	if (drawn)
148		Projectile_DrawTrail(this, trailorigin);
149	else
150		Projectile_ResetTrail(this, trailorigin);
151
152	this.drawmask = 0;
153
154	if (!drawn)
155		return;
156
157	switch (this.cnt)
158	{
159		// Possibly add dlights here.
160		default:
161			break;
162	}
163
164	this.drawmask = MASK_NORMAL;
165}
166
167void loopsound(entity e, int ch, string samp, float vol, float attn)
168{
169    TC(int, ch);
170	if (e.silent)
171		return;
172
173	_sound(e, ch, samp, vol, attn);
174	e.snd_looping = ch;
175}
176
177void Ent_RemoveProjectile(entity this)
178{
179	if (this.count & 0x80)
180	{
181		tracebox(this.origin, this.mins, this.maxs, this.origin + this.velocity * 0.05, MOVE_NORMAL, this);
182		Projectile_DrawTrail(this, trace_endpos);
183	}
184}
185
186NET_HANDLE(ENT_CLIENT_PROJECTILE, bool isnew)
187{
188	// projectile properties:
189	//   kind (interpolated, or clientside)
190	//
191	//   modelindex
192	//   origin
193	//   scale
194	//   if clientside:
195	//     velocity
196	//     gravity
197	//   soundindex (hardcoded list)
198	//   effects
199	//
200	// projectiles don't send angles, because they always follow the velocity
201
202	int f = ReadByte();
203	this.count = (f & 0x80);
204	this.flags |= FL_PROJECTILE;
205	this.iflags = (this.iflags & IFLAG_INTERNALMASK) | IFLAG_AUTOANGLES | IFLAG_ANGLES | IFLAG_ORIGIN;
206	this.solid = SOLID_TRIGGER;
207	// this.effects = EF_NOMODELFLAGS;
208
209	// this should make collisions with bmodels more exact, but it leads to
210	// projectiles no longer being able to lie on a bmodel
211	this.move_nomonsters = MOVE_WORLDONLY;
212	if (f & 0x40)
213		SET_ONGROUND(this);
214	else
215		UNSET_ONGROUND(this);
216
217	if (!this.move_time)
218	{
219		// for some unknown reason, we don't need to care for
220		// sv_gameplayfix_delayprojectiles here.
221		this.move_time = time;
222		this.spawntime = time;
223	}
224	else
225	{
226		this.move_time = max(this.move_time, time);
227	}
228
229	if (!(this.count & 0x80))
230		InterpolateOrigin_Undo(this);
231
232	if (f & 1)
233	{
234		this.origin_x = ReadCoord();
235		this.origin_y = ReadCoord();
236		this.origin_z = ReadCoord();
237		setorigin(this, this.origin);
238		if (this.count & 0x80)
239		{
240			this.velocity_x = ReadCoord();
241			this.velocity_y = ReadCoord();
242			this.velocity_z = ReadCoord();
243			if (f & 0x10)
244				this.gravity = ReadCoord();
245			else
246				this.gravity = 0;  // none
247		}
248
249		if (time == this.spawntime || (this.count & 0x80) || (f & 0x08))
250		{
251			this.trail_oldorigin = this.origin;
252			if (!(this.count & 0x80))
253				InterpolateOrigin_Reset(this);
254		}
255
256		if (f & 0x20)
257		{
258			this.fade_time = time + ReadByte() * ticrate;
259			this.fade_rate = 1 / (ReadByte() * ticrate);
260		}
261		else
262		{
263			this.fade_time = 0;
264			this.fade_rate = 0;
265		}
266
267		int myteam = ReadByte();
268		this.team = myteam - 1;
269
270		if(teamplay)
271		{
272			if(myteam)
273				this.colormap = (this.team) * 0x11; // note: team - 1 on server (client uses different numbers)
274			else
275				this.colormap = 0x00;
276			this.colormap |= BIT(10); // RENDER_COLORMAPPED
277		}
278		else
279			this.colormap = myteam;
280		// TODO: projectiles use glowmaps for their color, not teams
281		#if 0
282		if(this.colormap > 0)
283			this.glowmod = colormapPaletteColor(this.colormap & 0x0F, true) * 2;
284		else
285			this.glowmod = '1 1 1';
286		#endif
287	}
288
289	if (f & 2)
290	{
291		this.cnt = ReadByte();
292
293		this.silent = (this.cnt & 0x80);
294		this.cnt = (this.cnt & 0x7F);
295
296		this.scale = 1;
297		this.traileffect = 0;
298		switch (this.cnt)
299		{
300#define HANDLE(id) case PROJECTILE_##id: setmodel(this, MDL_PROJECTILE_##id);
301			HANDLE(ELECTRO)            this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
302			HANDLE(ROCKET)             this.traileffect = EFFECT_TR_ROCKET.m_id; this.scale = 2; break;
303			HANDLE(CRYLINK)            this.traileffect = EFFECT_TR_CRYLINKPLASMA.m_id; break;
304			HANDLE(CRYLINK_BOUNCING)   this.traileffect = EFFECT_TR_CRYLINKPLASMA.m_id; break;
305			HANDLE(ELECTRO_BEAM)       this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
306			HANDLE(GRENADE)            this.traileffect = EFFECT_TR_GRENADE.m_id; break;
307			HANDLE(GRENADE_BOUNCING)   this.traileffect = EFFECT_TR_GRENADE.m_id; break;
308			HANDLE(MINE)               this.traileffect = EFFECT_TR_GRENADE.m_id; break;
309			HANDLE(BLASTER)            this.traileffect = EFFECT_Null.m_id; break;
310			HANDLE(ARC_BOLT)           this.traileffect = EFFECT_Null.m_id; break;
311			HANDLE(HLAC)               this.traileffect = EFFECT_Null.m_id; break;
312			HANDLE(PORTO_RED)          this.traileffect = EFFECT_TR_WIZSPIKE.m_id; this.scale = 4; break;
313			HANDLE(PORTO_BLUE)         this.traileffect = EFFECT_TR_WIZSPIKE.m_id; this.scale = 4; break;
314			HANDLE(HOOKBOMB)           this.traileffect = EFFECT_TR_KNIGHTSPIKE.m_id; break;
315			HANDLE(HAGAR)              this.traileffect = EFFECT_HAGAR_ROCKET.m_id; this.scale = 0.75; break;
316			HANDLE(HAGAR_BOUNCING)     this.traileffect = EFFECT_HAGAR_ROCKET.m_id; this.scale = 0.75; break;
317			HANDLE(FIREBALL)           this.modelindex = 0; this.traileffect = EFFECT_FIREBALL.m_id; break; // particle effect is good enough
318			HANDLE(FIREMINE)           this.modelindex = 0; this.traileffect = EFFECT_FIREMINE.m_id; break; // particle effect is good enough
319			HANDLE(TAG)                this.traileffect = EFFECT_TR_ROCKET.m_id; break;
320			HANDLE(FLAC)               this.scale = 0.4; this.traileffect = EFFECT_FLAC_TRAIL.m_id; break;
321			HANDLE(SEEKER)             this.traileffect = EFFECT_SEEKER_TRAIL.m_id; break;
322
323			HANDLE(MAGE_SPIKE)         this.traileffect = EFFECT_TR_VORESPIKE.m_id; break;
324			HANDLE(SHAMBLER_LIGHTNING) this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
325
326			HANDLE(RAPTORBOMB)         this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
327			HANDLE(RAPTORBOMBLET)      this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
328			HANDLE(RAPTORCANNON)       this.traileffect = EFFECT_TR_CRYLINKPLASMA.m_id; break;
329
330			HANDLE(SPIDERROCKET)       this.traileffect = EFFECT_SPIDERBOT_ROCKET_TRAIL.m_id; break;
331			HANDLE(WAKIROCKET)         this.traileffect = EFFECT_RACER_ROCKET_TRAIL.m_id; break;
332			HANDLE(WAKICANNON)         this.traileffect = EFFECT_Null.m_id; break;
333
334			HANDLE(BUMBLE_GUN)         this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
335			HANDLE(BUMBLE_BEAM)        this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
336
337			HANDLE(RPC)                this.traileffect = EFFECT_TR_ROCKET.m_id; break;
338
339			HANDLE(ROCKETMINSTA_LASER) this.traileffect = EFFECT_ROCKETMINSTA_LASER(this.team).m_id; break;
340#undef HANDLE
341			default:
342				if (MUTATOR_CALLHOOK(Ent_Projectile, this))
343					break;
344
345				error("Received invalid CSQC projectile, can't work with this!");
346				break;
347		}
348
349		this.mins = '0 0 0';
350		this.maxs = '0 0 0';
351		this.colormod = '0 0 0';
352		settouch(this, SUB_Stop);
353		set_movetype(this, MOVETYPE_TOSS);
354		this.alphamod = 1;
355
356		switch (this.cnt)
357		{
358			case PROJECTILE_ELECTRO:
359				// only new engines support sound moving with object
360				loopsound(this, CH_SHOTS_SINGLE, SND(ELECTRO_FLY), VOL_BASE, ATTEN_NORM);
361				this.mins = '-4 -4 -4';
362				this.maxs = '4 4 4';
363				set_movetype(this, MOVETYPE_BOUNCE);
364				settouch(this, func_null);
365				this.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
366				this.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
367				break;
368			case PROJECTILE_RPC:
369			case PROJECTILE_ROCKET:
370				loopsound(this, CH_SHOTS_SINGLE, SND(ROCKET_FLY), VOL_BASE, ATTEN_NORM);
371				this.mins = '-3 -3 -3';
372				this.maxs = '3 3 3';
373				break;
374			case PROJECTILE_GRENADE:
375				this.mins = '-3 -3 -3';
376				this.maxs = '3 3 3';
377				break;
378			case PROJECTILE_GRENADE_BOUNCING:
379				this.mins = '-3 -3 -3';
380				this.maxs = '3 3 3';
381				set_movetype(this, MOVETYPE_BOUNCE);
382				settouch(this, func_null);
383				this.bouncefactor = WEP_CVAR(mortar, bouncefactor);
384				this.bouncestop = WEP_CVAR(mortar, bouncestop);
385				break;
386			case PROJECTILE_SHAMBLER_LIGHTNING:
387				this.mins = '-8 -8 -8';
388				this.maxs = '8 8 8';
389				this.scale = 2.5;
390				this.avelocity = randomvec() * 720;
391				break;
392			case PROJECTILE_MINE:
393				this.mins = '-4 -4 -4';
394				this.maxs = '4 4 4';
395				break;
396			case PROJECTILE_PORTO_RED:
397				this.colormod = '2 1 1';
398				this.alphamod = 0.5;
399				set_movetype(this, MOVETYPE_BOUNCE);
400				settouch(this, func_null);
401				break;
402			case PROJECTILE_PORTO_BLUE:
403				this.colormod = '1 1 2';
404				this.alphamod = 0.5;
405				set_movetype(this, MOVETYPE_BOUNCE);
406				settouch(this, func_null);
407				break;
408			case PROJECTILE_HAGAR_BOUNCING:
409				set_movetype(this, MOVETYPE_BOUNCE);
410				settouch(this, func_null);
411				break;
412			case PROJECTILE_CRYLINK_BOUNCING:
413				set_movetype(this, MOVETYPE_BOUNCE);
414				settouch(this, func_null);
415				break;
416			case PROJECTILE_FIREBALL:
417				loopsound(this, CH_SHOTS_SINGLE, SND(FIREBALL_FLY2), VOL_BASE, ATTEN_NORM);
418				this.mins = '-16 -16 -16';
419				this.maxs = '16 16 16';
420				break;
421			case PROJECTILE_FIREMINE:
422				loopsound(this, CH_SHOTS_SINGLE, SND(FIREBALL_FLY), VOL_BASE, ATTEN_NORM);
423				set_movetype(this, MOVETYPE_BOUNCE);
424				settouch(this, func_null);
425				this.mins = '-4 -4 -4';
426				this.maxs = '4 4 4';
427				break;
428			case PROJECTILE_TAG:
429				this.mins = '-2 -2 -2';
430				this.maxs = '2 2 2';
431				break;
432			case PROJECTILE_FLAC:
433				this.mins = '-2 -2 -2';
434				this.maxs = '2 2 2';
435				break;
436			case PROJECTILE_SEEKER:
437				loopsound(this, CH_SHOTS_SINGLE, SND(TAG_ROCKET_FLY), VOL_BASE, ATTEN_NORM);
438				this.mins = '-4 -4 -4';
439				this.maxs = '4 4 4';
440				break;
441			case PROJECTILE_RAPTORBOMB:
442				this.mins = '-3 -3 -3';
443				this.maxs = '3 3 3';
444				break;
445			case PROJECTILE_RAPTORBOMBLET:
446				break;
447			case PROJECTILE_RAPTORCANNON:
448				break;
449			case PROJECTILE_SPIDERROCKET:
450				loopsound(this, CH_SHOTS_SINGLE, SND(TAG_ROCKET_FLY), VOL_BASE, ATTEN_NORM);
451				break;
452			case PROJECTILE_WAKIROCKET:
453				loopsound(this, CH_SHOTS_SINGLE, SND(TAG_ROCKET_FLY), VOL_BASE, ATTEN_NORM);
454				break;
455			/*
456			case PROJECTILE_WAKICANNON:
457			    break;
458			case PROJECTILE_BUMBLE_GUN:
459			    // only new engines support sound moving with object
460			    loopsound(this, CH_SHOTS_SINGLE, SND(ELECTRO_FLY), VOL_BASE, ATTEN_NORM);
461			    this.mins = '0 0 -4';
462			    this.maxs = '0 0 -4';
463			    this.move_movetype = MOVETYPE_BOUNCE;
464			    settouch(this, func_null);
465			    this.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
466			    this.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
467			    break;
468			*/
469			default:
470				break;
471		}
472
473		MUTATOR_CALLHOOK(EditProjectile, this);
474
475		setsize(this, this.mins, this.maxs);
476	}
477
478	return = true;
479
480	if (this.gravity)
481	{
482		if (this.move_movetype == MOVETYPE_FLY)
483			set_movetype(this, MOVETYPE_TOSS);
484		if (this.move_movetype == MOVETYPE_BOUNCEMISSILE)
485			set_movetype(this, MOVETYPE_BOUNCE);
486	}
487	else
488	{
489		if (this.move_movetype == MOVETYPE_TOSS)
490			set_movetype(this, MOVETYPE_FLY);
491		if (this.move_movetype == MOVETYPE_BOUNCE)
492			set_movetype(this, MOVETYPE_BOUNCEMISSILE);
493	}
494
495	if (!(this.count & 0x80))
496		InterpolateOrigin_Note(this);
497
498	this.classname = "csqcprojectile";
499	this.draw = Projectile_Draw;
500	if (isnew) IL_PUSH(g_drawables, this);
501	this.entremove = Ent_RemoveProjectile;
502}
503
504PRECACHE(Projectiles)
505{
506	MUTATOR_CALLHOOK(PrecacheProjectiles);
507}
508