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_Reset(entity this)
48 W_Crylink_Dequeue(this);
52 // force projectile to explode
53 void W_Crylink_LinkExplode(entity e, entity e2, entity directhitentity)
60 a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
62 .entity weaponentity = e.weaponentity_fld;
63 if(e == e.crylink_owner.(weaponentity).crylink_lastgroup)
64 e.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
66 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
68 RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius),
69 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, e.weaponentity_fld, directhitentity);
71 W_Crylink_LinkExplode(e.queuenext, e2, directhitentity);
73 e.classname = "spike_oktoremove";
77 // adjust towards center
78 // returns the origin where they will meet... and the time till the meeting is
79 // stored in w_crylink_linkjoin_time.
80 // could possibly network this origin and time, and display a special particle
81 // effect when projectiles meet there :P
82 // jspeed: joining speed (calculate this as join spread * initial speed)
83 float w_crylink_linkjoin_time;
84 vector W_Crylink_LinkJoin(entity e, float jspeed)
86 vector avg_origin, avg_velocity;
91 // FIXME remove this debug code
92 W_Crylink_CheckLinks(e);
94 w_crylink_linkjoin_time = 0;
96 avg_origin = e.origin;
97 avg_velocity = e.velocity;
99 for(p = e; (p = p.queuenext) != e; )
101 avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
102 avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
105 avg_origin *= (1.0 / n);
106 avg_velocity *= (1.0 / n);
109 return avg_origin; // nothing to do
111 // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
112 avg_dist = (vlen(e.origin - avg_origin) ** 2);
113 for(p = e; (p = p.queuenext) != e; )
114 avg_dist += (vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin) ** 2);
115 avg_dist *= (1.0 / n);
116 avg_dist = sqrt(avg_dist);
119 return avg_origin; // no change needed
123 e.velocity = avg_velocity;
124 UpdateCSQCProjectile(e);
125 for(p = e; (p = p.queuenext) != e; )
127 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
128 UpdateCSQCProjectile(p);
130 targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
134 w_crylink_linkjoin_time = avg_dist / jspeed;
135 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
137 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
138 UpdateCSQCProjectile(e);
139 for(p = e; (p = p.queuenext) != e; )
141 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
142 UpdateCSQCProjectile(p);
146 // jspeed -> +infinity:
147 // w_crylink_linkjoin_time -> +0
148 // targ_origin -> avg_origin
149 // p->velocity -> HUEG towards center
151 // w_crylink_linkjoin_time -> +/- infinity
152 // targ_origin -> avg_velocity * +/- infinity
153 // p->velocity -> avg_velocity
154 // jspeed -> -infinity:
155 // w_crylink_linkjoin_time -> -0
156 // targ_origin -> avg_origin
157 // p->velocity -> HUEG away from center
160 W_Crylink_CheckLinks(e);
165 void W_Crylink_LinkJoinEffect_Think(entity this)
167 // is there at least 2 projectiles very close?
170 .entity weaponentity = this.weaponentity_fld;
171 e = this.owner.(weaponentity).crylink_lastgroup;
175 if(vlen2(e.origin - this.origin) < vlen2(e.velocity) * frametime)
177 for(p = e; (p = p.queuenext) != e; )
179 if(vlen2(p.origin - this.origin) < vlen2(p.velocity) * frametime)
184 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
186 if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
188 n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
192 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
193 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
194 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n,
197 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n,
198 e.projectiledeathtype,
202 Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, this.origin, '0 0 0', n);
209 float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
211 entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, false);
212 float hit_friendly = 0;
217 if((head.takedamage != DAMAGE_NO) && (!IS_DEAD(head)))
219 if(SAME_TEAM(head, projectile.realowner))
228 return (hit_enemy ? false : hit_friendly);
231 // NO bounce protection, as bounces are limited!
232 void W_Crylink_Touch(entity this, entity toucher)
236 float isprimary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
237 PROJECTILE_TOUCH(this, toucher);
240 a = bound(0, 1 - (time - this.fade_time) * this.fade_rate, 1);
242 finalhit = ((this.cnt <= 0) || (toucher.takedamage != DAMAGE_NO));
246 f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
250 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),
251 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * f, this.projectiledeathtype, this.weaponentity_fld, toucher);
253 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)))))
255 .entity weaponentity = this.weaponentity_fld;
256 if(this == this.crylink_owner.(weaponentity).crylink_lastgroup)
257 this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
258 W_Crylink_LinkExplode(this.queuenext, this, toucher);
259 this.classname = "spike_oktoremove";
266 W_Crylink_Dequeue(this);
270 this.cnt = this.cnt - 1;
271 this.angles = vectoangles(this.velocity);
273 this.projectiledeathtype |= HITTYPE_BOUNCE;
274 // commented out as it causes a little hitch...
276 // CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
279 void W_Crylink_Fadethink(entity this)
281 W_Crylink_Dequeue(this);
285 void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
287 float counter, shots;
288 entity proj, prevproj, firstproj;
290 vector forward, right, up;
293 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo), weaponentity);
295 maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
296 maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
297 if(WEP_CVAR_PRI(crylink, joinexplode))
298 maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
300 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, thiswep.m_id);
305 shots = WEP_CVAR_PRI(crylink, shots);
306 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
307 proj = prevproj = firstproj = NULL;
308 for(counter = 0; counter < shots; ++counter)
311 proj.reset = W_Crylink_Reset;
312 proj.realowner = proj.owner = actor;
313 proj.crylink_owner = actor;
314 proj.weaponentity_fld = weaponentity;
315 proj.bot_dodge = true;
316 proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
318 proj.queuenext = proj;
319 proj.queueprev = proj;
321 else if(counter == 0) { // first projectile, store in firstproj for now
324 else if(counter == shots - 1) { // last projectile, link up with first projectile
325 prevproj.queuenext = proj;
326 firstproj.queueprev = proj;
327 proj.queuenext = firstproj;
328 proj.queueprev = prevproj;
330 else { // else link up with previous projectile
331 prevproj.queuenext = proj;
332 proj.queueprev = prevproj;
337 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
338 PROJECTILE_MAKETRIGGER(proj);
339 proj.projectiledeathtype = thiswep.m_id;
340 //proj.gravity = 0.001;
342 setorigin(proj, w_shotorg);
343 setsize(proj, '0 0 0', '0 0 0');
351 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
355 s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
356 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
357 settouch(proj, W_Crylink_Touch);
359 setthink(proj, W_Crylink_Fadethink);
362 proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
363 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
364 proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
368 proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
369 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
370 proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
372 proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
373 proj.cnt = WEP_CVAR_PRI(crylink, bounces);
374 //proj.scale = 1 + 1 * proj.cnt;
376 proj.angles = vectoangles(proj.velocity);
378 //proj.glow_size = 20;
380 proj.flags = FL_PROJECTILE;
381 IL_PUSH(g_projectiles, proj);
382 IL_PUSH(g_bot_dodge, proj);
383 proj.missile_flags = MIF_SPLASH;
385 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
387 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
389 if(WEP_CVAR_PRI(crylink, joinspread) != 0)
391 actor.(weaponentity).crylink_lastgroup = proj;
392 W_Crylink_CheckLinks(proj);
393 actor.(weaponentity).crylink_waitrelease = 1;
397 void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
399 float counter, shots;
400 entity proj, prevproj, firstproj;
402 vector forward, right, up;
405 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
407 maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
408 maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
409 if(WEP_CVAR_SEC(crylink, joinexplode))
410 maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
412 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
417 shots = WEP_CVAR_SEC(crylink, shots);
418 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
419 proj = prevproj = firstproj = NULL;
420 for(counter = 0; counter < shots; ++counter)
423 proj.weaponentity_fld = weaponentity;
424 proj.reset = W_Crylink_Reset;
425 proj.realowner = proj.owner = actor;
426 proj.crylink_owner = actor;
427 proj.bot_dodge = true;
428 proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
430 proj.queuenext = proj;
431 proj.queueprev = proj;
433 else if(counter == 0) { // first projectile, store in firstproj for now
436 else if(counter == shots - 1) { // last projectile, link up with first projectile
437 prevproj.queuenext = proj;
438 firstproj.queueprev = proj;
439 proj.queuenext = firstproj;
440 proj.queueprev = prevproj;
442 else { // else link up with previous projectile
443 prevproj.queuenext = proj;
444 proj.queueprev = prevproj;
449 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
450 PROJECTILE_MAKETRIGGER(proj);
451 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
452 //proj.gravity = 0.001;
454 setorigin(proj, w_shotorg);
455 setsize(proj, '0 0 0', '0 0 0');
457 if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
464 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
468 s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
469 s = w_shotdir + right * s.y + up * s.z;
473 s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
476 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
477 settouch(proj, W_Crylink_Touch);
478 setthink(proj, W_Crylink_Fadethink);
479 if(counter == (shots - 1) / 2)
481 proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
482 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
483 proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
487 proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
488 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
489 proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
491 proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
492 proj.cnt = WEP_CVAR_SEC(crylink, bounces);
493 //proj.scale = 1 + 1 * proj.cnt;
495 proj.angles = vectoangles(proj.velocity);
497 //proj.glow_size = 20;
499 proj.flags = FL_PROJECTILE;
500 IL_PUSH(g_projectiles, proj);
501 IL_PUSH(g_bot_dodge, proj);
502 proj.missile_flags = MIF_SPLASH;
504 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
506 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
508 if(WEP_CVAR_SEC(crylink, joinspread) != 0)
510 actor.(weaponentity).crylink_lastgroup = proj;
511 W_Crylink_CheckLinks(proj);
512 actor.(weaponentity).crylink_waitrelease = 2;
516 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
519 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
521 PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
523 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
525 if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
526 thiswep.wr_reload(thiswep, actor, weaponentity);
531 if(actor.(weaponentity).crylink_waitrelease != 1)
532 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
534 W_Crylink_Attack(thiswep, actor, weaponentity);
535 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
539 if((fire & 2) && autocvar_g_balance_crylink_secondary)
541 if(actor.(weaponentity).crylink_waitrelease != 2)
542 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
544 W_Crylink_Attack2(thiswep, actor, weaponentity);
545 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
549 if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
551 if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
553 // fired and released now!
554 if(actor.(weaponentity).crylink_lastgroup)
557 entity linkjoineffect;
558 float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
560 pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
562 linkjoineffect = new(linkjoineffect);
563 linkjoineffect.weaponentity_fld = weaponentity;
564 setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
565 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
566 linkjoineffect.owner = actor;
567 setorigin(linkjoineffect, pos);
569 actor.(weaponentity).crylink_waitrelease = 0;
570 if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
571 if(!(actor.items & IT_UNLIMITED_AMMO))
574 actor.cnt = thiswep.m_id;
575 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
580 METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
582 // don't "run out of ammo" and switch weapons while waiting for release
583 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
586 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
587 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
590 METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
592 // don't "run out of ammo" and switch weapons while waiting for release
593 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
596 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
597 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
600 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
602 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), SND_RELOAD);
604 METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
606 return WEAPON_CRYLINK_SUICIDE;
608 METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
610 return WEAPON_CRYLINK_MURDER;
614 METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
617 org2 = w_org + w_backoff * 2;
618 if(w_deathtype & HITTYPE_SECONDARY)
620 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
622 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
626 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
628 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);