5 void W_Crylink_CheckLinks(entity e)
11 error("W_Crylink_CheckLinks: entity is NULL");
12 if(e.classname != "spike" || wasfreed(e))
13 error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
16 for(i = 0; i < 1000; ++i)
18 if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
19 error("W_Crylink_CheckLinks: queue is inconsistent");
25 error("W_Crylink_CheckLinks: infinite chain");
28 void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
30 W_Crylink_CheckLinks(next);
31 .entity weaponentity = me.weaponentity_fld;
32 if(me == own.(weaponentity).crylink_lastgroup)
33 own.(weaponentity).crylink_lastgroup = ((me == next) ? NULL : next);
34 prev.queuenext = next;
35 next.queueprev = prev;
36 me.classname = "spike_oktoremove";
38 W_Crylink_CheckLinks(next);
41 void W_Crylink_Dequeue(entity e)
43 W_Crylink_Dequeue_Raw(e.crylink_owner, e.queueprev, e, e.queuenext);
46 void W_Crylink_DeleteLink(entity this)
48 W_Crylink_Dequeue(this);
52 void W_Crylink_Reset(entity this)
57 // force projectile to explode
58 void W_Crylink_LinkExplode(entity e, entity e2, entity directhitentity)
65 a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
67 .entity weaponentity = e.weaponentity_fld;
68 if(e == e.crylink_owner.(weaponentity).crylink_lastgroup)
69 e.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
71 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
73 RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius),
74 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, e.weaponentity_fld, directhitentity);
76 W_Crylink_LinkExplode(e.queuenext, e2, directhitentity);
78 e.classname = "spike_oktoremove";
82 // adjust towards center
83 // returns the origin where they will meet... and the time till the meeting is
84 // stored in w_crylink_linkjoin_time.
85 // could possibly network this origin and time, and display a special particle
86 // effect when projectiles meet there :P
87 // jspeed: joining speed (calculate this as join spread * initial speed)
88 float w_crylink_linkjoin_time;
89 vector W_Crylink_LinkJoin(entity e, float jspeed)
91 vector avg_origin, avg_velocity;
96 // FIXME remove this debug code
97 W_Crylink_CheckLinks(e);
99 w_crylink_linkjoin_time = 0;
101 avg_origin = e.origin;
102 avg_velocity = e.velocity;
104 for(p = e; (p = p.queuenext) != e; )
106 avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
107 avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
110 avg_origin *= (1.0 / n);
111 avg_velocity *= (1.0 / n);
114 return avg_origin; // nothing to do
116 // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
117 avg_dist = (vlen(e.origin - avg_origin) ** 2);
118 for(p = e; (p = p.queuenext) != e; )
119 avg_dist += (vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin) ** 2);
120 avg_dist *= (1.0 / n);
121 avg_dist = sqrt(avg_dist);
124 return avg_origin; // no change needed
128 e.velocity = avg_velocity;
129 UpdateCSQCProjectile(e);
130 for(p = e; (p = p.queuenext) != e; )
132 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
133 UpdateCSQCProjectile(p);
135 targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
139 w_crylink_linkjoin_time = avg_dist / jspeed;
140 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
142 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
143 UpdateCSQCProjectile(e);
144 for(p = e; (p = p.queuenext) != e; )
146 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
147 UpdateCSQCProjectile(p);
151 // jspeed -> +infinity:
152 // w_crylink_linkjoin_time -> +0
153 // targ_origin -> avg_origin
154 // p->velocity -> HUEG towards center
156 // w_crylink_linkjoin_time -> +/- infinity
157 // targ_origin -> avg_velocity * +/- infinity
158 // p->velocity -> avg_velocity
159 // jspeed -> -infinity:
160 // w_crylink_linkjoin_time -> -0
161 // targ_origin -> avg_origin
162 // p->velocity -> HUEG away from center
165 W_Crylink_CheckLinks(e);
170 void W_Crylink_LinkJoinEffect_Think(entity this)
172 // is there at least 2 projectiles very close?
175 .entity weaponentity = this.weaponentity_fld;
176 e = this.owner.(weaponentity).crylink_lastgroup;
180 if(vlen2(e.origin - this.origin) < vlen2(e.velocity) * frametime)
182 for(p = e; (p = p.queuenext) != e; )
184 if(vlen2(p.origin - this.origin) < vlen2(p.velocity) * frametime)
189 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
191 if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
193 n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
197 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
198 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
199 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n,
202 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n,
203 e.projectiledeathtype,
207 Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, this.origin, '0 0 0', n);
214 float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
216 entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, false);
217 float hit_friendly = 0;
222 if((head.takedamage != DAMAGE_NO) && (!IS_DEAD(head)))
224 if(SAME_TEAM(head, projectile.realowner))
233 return (hit_enemy ? false : hit_friendly);
236 // NO bounce protection, as bounces are limited!
237 void W_Crylink_Touch(entity this, entity toucher)
241 float isprimary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
242 PROJECTILE_TOUCH(this, toucher);
245 a = bound(0, 1 - (time - this.fade_time) * this.fade_rate, 1);
247 finalhit = ((this.cnt <= 0) || (toucher.takedamage != DAMAGE_NO));
251 f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
255 float totaldamage = RadiusDamage(this, this.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * f, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * f, WEP_CVAR_BOTH(crylink, isprimary, radius),
256 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * f, this.projectiledeathtype, this.weaponentity_fld, toucher);
258 if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(this, WEP_CVAR_BOTH(crylink, isprimary, radius)))))
260 .entity weaponentity = this.weaponentity_fld;
261 if(this == this.crylink_owner.(weaponentity).crylink_lastgroup)
262 this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
263 W_Crylink_LinkExplode(this.queuenext, this, toucher);
264 this.classname = "spike_oktoremove";
274 this.cnt = this.cnt - 1;
275 this.angles = vectoangles(this.velocity);
277 this.projectiledeathtype |= HITTYPE_BOUNCE;
278 // commented out as it causes a little hitch...
280 // CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
283 void W_Crylink_Fadethink(entity this)
288 void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
290 float counter, shots;
291 entity proj, prevproj, firstproj;
293 vector forward, right, up;
296 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo), weaponentity);
298 maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
299 maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
300 if(WEP_CVAR_PRI(crylink, joinexplode))
301 maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
303 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, thiswep.m_id);
308 shots = WEP_CVAR_PRI(crylink, shots);
309 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
310 proj = prevproj = firstproj = NULL;
311 for(counter = 0; counter < shots; ++counter)
314 proj.dtor = W_Crylink_DeleteLink;
315 proj.reset = W_Crylink_Reset;
316 proj.realowner = proj.owner = actor;
317 proj.crylink_owner = actor;
318 proj.weaponentity_fld = weaponentity;
319 proj.bot_dodge = true;
320 proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
322 proj.queuenext = proj;
323 proj.queueprev = proj;
325 else if(counter == 0) { // first projectile, store in firstproj for now
328 else if(counter == shots - 1) { // last projectile, link up with first projectile
329 prevproj.queuenext = proj;
330 firstproj.queueprev = proj;
331 proj.queuenext = firstproj;
332 proj.queueprev = prevproj;
334 else { // else link up with previous projectile
335 prevproj.queuenext = proj;
336 proj.queueprev = prevproj;
341 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
342 PROJECTILE_MAKETRIGGER(proj);
343 proj.projectiledeathtype = thiswep.m_id;
344 //proj.gravity = 0.001;
346 setorigin(proj, w_shotorg);
347 setsize(proj, '0 0 0', '0 0 0');
355 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
359 s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
360 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
361 settouch(proj, W_Crylink_Touch);
363 setthink(proj, W_Crylink_Fadethink);
366 proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
367 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
368 proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
372 proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
373 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
374 proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
376 proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
377 proj.cnt = WEP_CVAR_PRI(crylink, bounces);
378 //proj.scale = 1 + 1 * proj.cnt;
380 proj.angles = vectoangles(proj.velocity);
382 //proj.glow_size = 20;
384 proj.flags = FL_PROJECTILE;
385 IL_PUSH(g_projectiles, proj);
386 IL_PUSH(g_bot_dodge, proj);
387 proj.missile_flags = MIF_SPLASH;
389 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
391 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
393 if(WEP_CVAR_PRI(crylink, joinspread) != 0)
395 actor.(weaponentity).crylink_lastgroup = proj;
396 W_Crylink_CheckLinks(proj);
397 actor.(weaponentity).crylink_waitrelease = 1;
401 void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
403 float counter, shots;
404 entity proj, prevproj, firstproj;
406 vector forward, right, up;
409 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
411 maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
412 maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
413 if(WEP_CVAR_SEC(crylink, joinexplode))
414 maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
416 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
421 shots = WEP_CVAR_SEC(crylink, shots);
422 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
423 proj = prevproj = firstproj = NULL;
424 for(counter = 0; counter < shots; ++counter)
427 proj.dtor = W_Crylink_DeleteLink;
428 proj.weaponentity_fld = weaponentity;
429 proj.reset = W_Crylink_Reset;
430 proj.realowner = proj.owner = actor;
431 proj.crylink_owner = actor;
432 proj.bot_dodge = true;
433 proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
435 proj.queuenext = proj;
436 proj.queueprev = proj;
438 else if(counter == 0) { // first projectile, store in firstproj for now
441 else if(counter == shots - 1) { // last projectile, link up with first projectile
442 prevproj.queuenext = proj;
443 firstproj.queueprev = proj;
444 proj.queuenext = firstproj;
445 proj.queueprev = prevproj;
447 else { // else link up with previous projectile
448 prevproj.queuenext = proj;
449 proj.queueprev = prevproj;
454 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
455 PROJECTILE_MAKETRIGGER(proj);
456 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
457 //proj.gravity = 0.001;
459 setorigin(proj, w_shotorg);
460 setsize(proj, '0 0 0', '0 0 0');
462 if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
469 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
473 s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
474 s = w_shotdir + right * s.y + up * s.z;
478 s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
481 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
482 settouch(proj, W_Crylink_Touch);
483 setthink(proj, W_Crylink_Fadethink);
484 if(counter == (shots - 1) / 2)
486 proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
487 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
488 proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
492 proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
493 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
494 proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
496 proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
497 proj.cnt = WEP_CVAR_SEC(crylink, bounces);
498 //proj.scale = 1 + 1 * proj.cnt;
500 proj.angles = vectoangles(proj.velocity);
502 //proj.glow_size = 20;
504 proj.flags = FL_PROJECTILE;
505 IL_PUSH(g_projectiles, proj);
506 IL_PUSH(g_bot_dodge, proj);
507 proj.missile_flags = MIF_SPLASH;
509 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
511 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
513 if(WEP_CVAR_SEC(crylink, joinspread) != 0)
515 actor.(weaponentity).crylink_lastgroup = proj;
516 W_Crylink_CheckLinks(proj);
517 actor.(weaponentity).crylink_waitrelease = 2;
521 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
524 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
526 PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
528 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
530 if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
531 thiswep.wr_reload(thiswep, actor, weaponentity);
536 if(actor.(weaponentity).crylink_waitrelease != 1)
537 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
539 W_Crylink_Attack(thiswep, actor, weaponentity);
540 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
544 if((fire & 2) && autocvar_g_balance_crylink_secondary)
546 if(actor.(weaponentity).crylink_waitrelease != 2)
547 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
549 W_Crylink_Attack2(thiswep, actor, weaponentity);
550 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
554 if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
556 if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
558 // fired and released now!
559 if(actor.(weaponentity).crylink_lastgroup)
562 entity linkjoineffect;
563 float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
565 pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
567 linkjoineffect = new(linkjoineffect);
568 linkjoineffect.weaponentity_fld = weaponentity;
569 setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
570 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
571 linkjoineffect.owner = actor;
572 setorigin(linkjoineffect, pos);
574 actor.(weaponentity).crylink_waitrelease = 0;
575 if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
576 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
579 actor.cnt = thiswep.m_id;
580 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
585 METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
587 // don't "run out of ammo" and switch weapons while waiting for release
588 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
591 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
592 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
595 METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
597 // don't "run out of ammo" and switch weapons while waiting for release
598 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
601 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
602 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
605 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
607 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), SND_RELOAD);
609 METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
611 return WEAPON_CRYLINK_SUICIDE;
613 METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
615 return WEAPON_CRYLINK_MURDER;
619 METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
622 org2 = w_org + w_backoff * 2;
623 if(w_deathtype & HITTYPE_SECONDARY)
625 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
627 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
631 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
633 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);