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 if(this.classname != "spike_oktoremove")
49 W_Crylink_Dequeue(this);
53 void W_Crylink_Reset(entity this)
58 // force projectile to explode
59 void W_Crylink_LinkExplode(entity e, entity e2, entity directhitentity)
66 a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
68 .entity weaponentity = e.weaponentity_fld;
69 if(e == e.crylink_owner.(weaponentity).crylink_lastgroup)
70 e.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
72 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
74 RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius),
75 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, e.weaponentity_fld, directhitentity);
77 W_Crylink_LinkExplode(e.queuenext, e2, directhitentity);
79 e.classname = "spike_oktoremove";
83 // adjust towards center
84 // returns the origin where they will meet... and the time till the meeting is
85 // stored in w_crylink_linkjoin_time.
86 // could possibly network this origin and time, and display a special particle
87 // effect when projectiles meet there :P
88 // jspeed: joining speed (calculate this as join spread * initial speed)
89 float w_crylink_linkjoin_time;
90 vector W_Crylink_LinkJoin(entity e, float jspeed)
92 vector avg_origin, avg_velocity;
97 // FIXME remove this debug code
98 W_Crylink_CheckLinks(e);
100 w_crylink_linkjoin_time = 0;
102 avg_origin = e.origin;
103 avg_velocity = e.velocity;
105 for(p = e; (p = p.queuenext) != e; )
107 avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
108 avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
111 avg_origin *= (1.0 / n);
112 avg_velocity *= (1.0 / n);
115 return avg_origin; // nothing to do
117 // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
118 avg_dist = (vlen(e.origin - avg_origin) ** 2);
119 for(p = e; (p = p.queuenext) != e; )
120 avg_dist += (vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin) ** 2);
121 avg_dist *= (1.0 / n);
122 avg_dist = sqrt(avg_dist);
125 return avg_origin; // no change needed
129 e.velocity = avg_velocity;
130 UpdateCSQCProjectile(e);
131 for(p = e; (p = p.queuenext) != e; )
133 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
134 UpdateCSQCProjectile(p);
136 targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
140 w_crylink_linkjoin_time = avg_dist / jspeed;
141 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
143 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
144 UpdateCSQCProjectile(e);
145 for(p = e; (p = p.queuenext) != e; )
147 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
148 UpdateCSQCProjectile(p);
152 // jspeed -> +infinity:
153 // w_crylink_linkjoin_time -> +0
154 // targ_origin -> avg_origin
155 // p->velocity -> HUEG towards center
157 // w_crylink_linkjoin_time -> +/- infinity
158 // targ_origin -> avg_velocity * +/- infinity
159 // p->velocity -> avg_velocity
160 // jspeed -> -infinity:
161 // w_crylink_linkjoin_time -> -0
162 // targ_origin -> avg_origin
163 // p->velocity -> HUEG away from center
166 W_Crylink_CheckLinks(e);
171 void W_Crylink_LinkJoinEffect_Think(entity this)
173 // is there at least 2 projectiles very close?
176 .entity weaponentity = this.weaponentity_fld;
177 e = this.owner.(weaponentity).crylink_lastgroup;
181 if(vlen2(e.origin - this.origin) < vlen2(e.velocity) * frametime)
183 for(p = e; (p = p.queuenext) != e; )
185 if(vlen2(p.origin - this.origin) < vlen2(p.velocity) * frametime)
190 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
192 if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
194 n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
198 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
199 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
200 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n,
203 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n,
204 e.projectiledeathtype,
208 Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, this.origin, '0 0 0', n);
215 float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
217 entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, false);
218 float hit_friendly = 0;
223 if((head.takedamage != DAMAGE_NO) && (!IS_DEAD(head)))
225 if(SAME_TEAM(head, projectile.realowner))
234 return (hit_enemy ? false : hit_friendly);
237 // NO bounce protection, as bounces are limited!
238 void W_Crylink_Touch(entity this, entity toucher)
242 float isprimary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
243 PROJECTILE_TOUCH(this, toucher);
246 a = bound(0, 1 - (time - this.fade_time) * this.fade_rate, 1);
248 finalhit = ((this.cnt <= 0) || (toucher.takedamage != DAMAGE_NO));
252 f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
256 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),
257 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * f, this.projectiledeathtype, this.weaponentity_fld, toucher);
259 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)))))
261 .entity weaponentity = this.weaponentity_fld;
262 if(this == this.crylink_owner.(weaponentity).crylink_lastgroup)
263 this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
264 W_Crylink_LinkExplode(this.queuenext, this, toucher);
265 this.classname = "spike_oktoremove";
275 this.cnt = this.cnt - 1;
276 this.angles = vectoangles(this.velocity);
278 this.projectiledeathtype |= HITTYPE_BOUNCE;
279 // commented out as it causes a little hitch...
281 // CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
284 void W_Crylink_Fadethink(entity this)
289 void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
291 float counter, shots;
292 entity proj, prevproj, firstproj;
294 vector forward, right, up;
297 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo), weaponentity);
299 maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
300 maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
301 if(WEP_CVAR_PRI(crylink, joinexplode))
302 maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
304 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg, thiswep.m_id);
309 shots = WEP_CVAR_PRI(crylink, shots);
310 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
311 proj = prevproj = firstproj = NULL;
312 for(counter = 0; counter < shots; ++counter)
315 proj.dtor = W_Crylink_DeleteLink;
316 proj.reset = W_Crylink_Reset;
317 proj.realowner = proj.owner = actor;
318 proj.crylink_owner = actor;
319 proj.weaponentity_fld = weaponentity;
320 proj.bot_dodge = true;
321 proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
323 proj.queuenext = proj;
324 proj.queueprev = proj;
326 else if(counter == 0) { // first projectile, store in firstproj for now
329 else if(counter == shots - 1) { // last projectile, link up with first projectile
330 prevproj.queuenext = proj;
331 firstproj.queueprev = proj;
332 proj.queuenext = firstproj;
333 proj.queueprev = prevproj;
335 else { // else link up with previous projectile
336 prevproj.queuenext = proj;
337 proj.queueprev = prevproj;
342 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
343 PROJECTILE_MAKETRIGGER(proj);
344 proj.clipgroup = CLIPGROUP_UNHITTABLEPROJ;
345 proj.projectiledeathtype = thiswep.m_id;
346 //proj.gravity = 0.001;
348 setorigin(proj, w_shotorg);
349 setsize(proj, UNHITTABLEPROJ_MINS, UNHITTABLEPROJ_MAXS);
357 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
361 s = s * WEP_CVAR_PRI(crylink, spread) * autocvar_g_weaponspreadfactor;
362 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
363 settouch(proj, W_Crylink_Touch);
365 setthink(proj, W_Crylink_Fadethink);
368 proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
369 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
370 proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
374 proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
375 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
376 proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
378 proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
379 proj.cnt = WEP_CVAR_PRI(crylink, bounces);
380 //proj.scale = 1 + 1 * proj.cnt;
382 proj.angles = vectoangles(proj.velocity);
384 //proj.glow_size = 20;
386 proj.flags = FL_PROJECTILE;
387 IL_PUSH(g_projectiles, proj);
388 IL_PUSH(g_bot_dodge, proj);
389 proj.missile_flags = MIF_SPLASH;
391 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
393 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
395 if(WEP_CVAR_PRI(crylink, joinspread) != 0 && WEP_CVAR_PRI(crylink, shots) > 1)
397 actor.(weaponentity).crylink_lastgroup = proj;
398 W_Crylink_CheckLinks(proj);
399 actor.(weaponentity).crylink_waitrelease = 1;
403 void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
405 float counter, shots;
406 entity proj, prevproj, firstproj;
408 vector forward, right, up;
411 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
413 maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
414 maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
415 if(WEP_CVAR_SEC(crylink, joinexplode))
416 maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
418 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
423 shots = WEP_CVAR_SEC(crylink, shots);
424 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
425 proj = prevproj = firstproj = NULL;
426 for(counter = 0; counter < shots; ++counter)
429 proj.dtor = W_Crylink_DeleteLink;
430 proj.weaponentity_fld = weaponentity;
431 proj.reset = W_Crylink_Reset;
432 proj.realowner = proj.owner = actor;
433 proj.crylink_owner = actor;
434 proj.bot_dodge = true;
435 proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
437 proj.queuenext = proj;
438 proj.queueprev = proj;
440 else if(counter == 0) { // first projectile, store in firstproj for now
443 else if(counter == shots - 1) { // last projectile, link up with first projectile
444 prevproj.queuenext = proj;
445 firstproj.queueprev = proj;
446 proj.queuenext = firstproj;
447 proj.queueprev = prevproj;
449 else { // else link up with previous projectile
450 prevproj.queuenext = proj;
451 proj.queueprev = prevproj;
456 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
457 PROJECTILE_MAKETRIGGER(proj);
458 proj.clipgroup = CLIPGROUP_UNHITTABLEPROJ;
459 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
460 //proj.gravity = 0.001;
462 setorigin(proj, w_shotorg);
463 setsize(proj, UNHITTABLEPROJ_MINS, UNHITTABLEPROJ_MAXS);
465 if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
472 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
476 s = s * WEP_CVAR_SEC(crylink, spread) * autocvar_g_weaponspreadfactor;
477 s = w_shotdir + right * s.y + up * s.z;
481 s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * autocvar_g_weaponspreadfactor);
484 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
485 settouch(proj, W_Crylink_Touch);
486 setthink(proj, W_Crylink_Fadethink);
487 if(counter == (shots - 1) / 2)
489 proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
490 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
491 proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
495 proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
496 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
497 proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
499 proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
500 proj.cnt = WEP_CVAR_SEC(crylink, bounces);
501 //proj.scale = 1 + 1 * proj.cnt;
503 proj.angles = vectoangles(proj.velocity);
505 //proj.glow_size = 20;
507 proj.flags = FL_PROJECTILE;
508 IL_PUSH(g_projectiles, proj);
509 IL_PUSH(g_bot_dodge, proj);
510 proj.missile_flags = MIF_SPLASH;
512 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
514 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
516 if(WEP_CVAR_SEC(crylink, joinspread) != 0 && WEP_CVAR_SEC(crylink, shots) > 1)
518 actor.(weaponentity).crylink_lastgroup = proj;
519 W_Crylink_CheckLinks(proj);
520 actor.(weaponentity).crylink_waitrelease = 2;
524 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
527 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false, true);
529 PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false, true);
531 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
533 if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
534 thiswep.wr_reload(thiswep, actor, weaponentity);
539 if(actor.(weaponentity).crylink_waitrelease != 1)
540 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
542 W_Crylink_Attack(thiswep, actor, weaponentity);
543 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
547 if((fire & 2) && autocvar_g_balance_crylink_secondary)
549 if(actor.(weaponentity).crylink_waitrelease != 2)
550 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
552 W_Crylink_Attack2(thiswep, actor, weaponentity);
553 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
557 if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
559 if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
561 // fired and released now!
562 if(actor.(weaponentity).crylink_lastgroup)
565 entity linkjoineffect;
566 float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
568 pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
570 linkjoineffect = new(linkjoineffect);
571 linkjoineffect.weaponentity_fld = weaponentity;
572 setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
573 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
574 linkjoineffect.owner = actor;
575 setorigin(linkjoineffect, pos);
577 actor.(weaponentity).crylink_waitrelease = 0;
578 if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
579 if(!(actor.items & IT_UNLIMITED_AMMO))
582 actor.cnt = thiswep.m_id;
583 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
588 METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
590 // don't "run out of ammo" and switch weapons while waiting for release
591 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
594 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
595 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
598 METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
600 // don't "run out of ammo" and switch weapons while waiting for release
601 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
604 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
605 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
608 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
610 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), SND_RELOAD);
612 METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
614 return WEAPON_CRYLINK_SUICIDE;
616 METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
618 return WEAPON_CRYLINK_MURDER;
622 METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
624 vector org2 = w_org + w_backoff * 2;
625 if(w_deathtype & HITTYPE_SECONDARY)
627 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
629 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
633 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
635 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);