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.projectiledeathtype = thiswep.m_id;
345 //proj.gravity = 0.001;
347 setorigin(proj, w_shotorg);
348 setsize(proj, '0 0 0', '0 0 0');
356 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
360 s = s * WEP_CVAR_PRI(crylink, spread) * autocvar_g_weaponspreadfactor;
361 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
362 settouch(proj, W_Crylink_Touch);
364 setthink(proj, W_Crylink_Fadethink);
367 proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
368 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
369 proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
373 proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
374 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
375 proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
377 proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
378 proj.cnt = WEP_CVAR_PRI(crylink, bounces);
379 //proj.scale = 1 + 1 * proj.cnt;
381 proj.angles = vectoangles(proj.velocity);
383 //proj.glow_size = 20;
385 proj.flags = FL_PROJECTILE;
386 IL_PUSH(g_projectiles, proj);
387 IL_PUSH(g_bot_dodge, proj);
388 proj.missile_flags = MIF_SPLASH;
390 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
392 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
394 if(WEP_CVAR_PRI(crylink, joinspread) != 0)
396 actor.(weaponentity).crylink_lastgroup = proj;
397 W_Crylink_CheckLinks(proj);
398 actor.(weaponentity).crylink_waitrelease = 1;
402 void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
404 float counter, shots;
405 entity proj, prevproj, firstproj;
407 vector forward, right, up;
410 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
412 maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
413 maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
414 if(WEP_CVAR_SEC(crylink, joinexplode))
415 maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
417 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg, thiswep.m_id | HITTYPE_SECONDARY);
422 shots = WEP_CVAR_SEC(crylink, shots);
423 W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir);
424 proj = prevproj = firstproj = NULL;
425 for(counter = 0; counter < shots; ++counter)
428 proj.dtor = W_Crylink_DeleteLink;
429 proj.weaponentity_fld = weaponentity;
430 proj.reset = W_Crylink_Reset;
431 proj.realowner = proj.owner = actor;
432 proj.crylink_owner = actor;
433 proj.bot_dodge = true;
434 proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
436 proj.queuenext = proj;
437 proj.queueprev = proj;
439 else if(counter == 0) { // first projectile, store in firstproj for now
442 else if(counter == shots - 1) { // last projectile, link up with first projectile
443 prevproj.queuenext = proj;
444 firstproj.queueprev = proj;
445 proj.queuenext = firstproj;
446 proj.queueprev = prevproj;
448 else { // else link up with previous projectile
449 prevproj.queuenext = proj;
450 proj.queueprev = prevproj;
455 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
456 PROJECTILE_MAKETRIGGER(proj);
457 proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY;
458 //proj.gravity = 0.001;
460 setorigin(proj, w_shotorg);
461 setsize(proj, '0 0 0', '0 0 0');
463 if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
470 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
474 s = s * WEP_CVAR_SEC(crylink, spread) * autocvar_g_weaponspreadfactor;
475 s = w_shotdir + right * s.y + up * s.z;
479 s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * autocvar_g_weaponspreadfactor);
482 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
483 settouch(proj, W_Crylink_Touch);
484 setthink(proj, W_Crylink_Fadethink);
485 if(counter == (shots - 1) / 2)
487 proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
488 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
489 proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
493 proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
494 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
495 proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
497 proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
498 proj.cnt = WEP_CVAR_SEC(crylink, bounces);
499 //proj.scale = 1 + 1 * proj.cnt;
501 proj.angles = vectoangles(proj.velocity);
503 //proj.glow_size = 20;
505 proj.flags = FL_PROJECTILE;
506 IL_PUSH(g_projectiles, proj);
507 IL_PUSH(g_bot_dodge, proj);
508 proj.missile_flags = MIF_SPLASH;
510 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
512 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
514 if(WEP_CVAR_SEC(crylink, joinspread) != 0)
516 actor.(weaponentity).crylink_lastgroup = proj;
517 W_Crylink_CheckLinks(proj);
518 actor.(weaponentity).crylink_waitrelease = 2;
522 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
525 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
527 PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
529 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
531 if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
532 thiswep.wr_reload(thiswep, actor, weaponentity);
537 if(actor.(weaponentity).crylink_waitrelease != 1)
538 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
540 W_Crylink_Attack(thiswep, actor, weaponentity);
541 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
545 if((fire & 2) && autocvar_g_balance_crylink_secondary)
547 if(actor.(weaponentity).crylink_waitrelease != 2)
548 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
550 W_Crylink_Attack2(thiswep, actor, weaponentity);
551 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
555 if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
557 if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
559 // fired and released now!
560 if(actor.(weaponentity).crylink_lastgroup)
563 entity linkjoineffect;
564 float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
566 pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
568 linkjoineffect = new(linkjoineffect);
569 linkjoineffect.weaponentity_fld = weaponentity;
570 setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
571 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
572 linkjoineffect.owner = actor;
573 setorigin(linkjoineffect, pos);
575 actor.(weaponentity).crylink_waitrelease = 0;
576 if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
577 if(!(actor.items & IT_UNLIMITED_AMMO))
580 actor.cnt = thiswep.m_id;
581 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
586 METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
588 // don't "run out of ammo" and switch weapons while waiting for release
589 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
592 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
593 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
596 METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
598 // don't "run out of ammo" and switch weapons while waiting for release
599 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
602 float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
603 ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
606 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
608 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), SND_RELOAD);
610 METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
612 return WEAPON_CRYLINK_SUICIDE;
614 METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
616 return WEAPON_CRYLINK_MURDER;
620 METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
623 org2 = w_org + w_backoff * 2;
624 if(w_deathtype & HITTYPE_SECONDARY)
626 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
628 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
632 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
634 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);