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, 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,
201 Send_Effect(EFFECT_CRYLINK_JOINEXPLODE, this.origin, '0 0 0', n);
208 float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
210 entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, false);
211 float hit_friendly = 0;
216 if((head.takedamage != DAMAGE_NO) && (!IS_DEAD(head)))
218 if(SAME_TEAM(head, projectile.realowner))
227 return (hit_enemy ? false : hit_friendly);
230 // NO bounce protection, as bounces are limited!
231 void W_Crylink_Touch(entity this, entity toucher)
235 float isprimary = !(this.projectiledeathtype & HITTYPE_SECONDARY);
236 PROJECTILE_TOUCH(this, toucher);
239 a = bound(0, 1 - (time - this.fade_time) * this.fade_rate, 1);
241 finalhit = ((this.cnt <= 0) || (toucher.takedamage != DAMAGE_NO));
245 f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
249 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), NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * f, this.projectiledeathtype, toucher);
251 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)))))
253 .entity weaponentity = this.weaponentity_fld;
254 if(this == this.crylink_owner.(weaponentity).crylink_lastgroup)
255 this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
256 W_Crylink_LinkExplode(this.queuenext, this, toucher);
257 this.classname = "spike_oktoremove";
264 W_Crylink_Dequeue(this);
268 this.cnt = this.cnt - 1;
269 this.angles = vectoangles(this.velocity);
271 this.projectiledeathtype |= HITTYPE_BOUNCE;
272 // commented out as it causes a little hitch...
274 // CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
277 void W_Crylink_Fadethink(entity this)
279 W_Crylink_Dequeue(this);
283 void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
285 float counter, shots;
286 entity proj, prevproj, firstproj;
288 vector forward, right, up;
291 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo), weaponentity);
293 maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
294 maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
295 if(WEP_CVAR_PRI(crylink, joinexplode))
296 maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
298 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg);
303 shots = WEP_CVAR_PRI(crylink, shots);
304 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
305 proj = prevproj = firstproj = NULL;
306 for(counter = 0; counter < shots; ++counter)
309 proj.reset = W_Crylink_Reset;
310 proj.realowner = proj.owner = actor;
311 proj.crylink_owner = actor;
312 proj.weaponentity_fld = weaponentity;
313 proj.bot_dodge = true;
314 proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
316 proj.queuenext = proj;
317 proj.queueprev = proj;
319 else if(counter == 0) { // first projectile, store in firstproj for now
322 else if(counter == shots - 1) { // last projectile, link up with first projectile
323 prevproj.queuenext = proj;
324 firstproj.queueprev = proj;
325 proj.queuenext = firstproj;
326 proj.queueprev = prevproj;
328 else { // else link up with previous projectile
329 prevproj.queuenext = proj;
330 proj.queueprev = prevproj;
335 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
336 PROJECTILE_MAKETRIGGER(proj);
337 proj.projectiledeathtype = WEP_CRYLINK.m_id;
338 //proj.gravity = 0.001;
340 setorigin(proj, w_shotorg);
341 setsize(proj, '0 0 0', '0 0 0');
349 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
353 s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
354 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
355 settouch(proj, W_Crylink_Touch);
357 setthink(proj, W_Crylink_Fadethink);
360 proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
361 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
362 proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
366 proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
367 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
368 proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
370 proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
371 proj.cnt = WEP_CVAR_PRI(crylink, bounces);
372 //proj.scale = 1 + 1 * proj.cnt;
374 proj.angles = vectoangles(proj.velocity);
376 //proj.glow_size = 20;
378 proj.flags = FL_PROJECTILE;
379 IL_PUSH(g_projectiles, proj);
380 IL_PUSH(g_bot_dodge, proj);
381 proj.missile_flags = MIF_SPLASH;
383 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
385 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
387 if(WEP_CVAR_PRI(crylink, joinspread) != 0)
389 actor.(weaponentity).crylink_lastgroup = proj;
390 W_Crylink_CheckLinks(proj);
391 actor.(weaponentity).crylink_waitrelease = 1;
395 void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
397 float counter, shots;
398 entity proj, prevproj, firstproj;
400 vector forward, right, up;
403 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
405 maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
406 maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
407 if(WEP_CVAR_SEC(crylink, joinexplode))
408 maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
410 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg);
415 shots = WEP_CVAR_SEC(crylink, shots);
416 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
417 proj = prevproj = firstproj = NULL;
418 for(counter = 0; counter < shots; ++counter)
421 proj.weaponentity_fld = weaponentity;
422 proj.reset = W_Crylink_Reset;
423 proj.realowner = proj.owner = actor;
424 proj.crylink_owner = actor;
425 proj.bot_dodge = true;
426 proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
428 proj.queuenext = proj;
429 proj.queueprev = proj;
431 else if(counter == 0) { // first projectile, store in firstproj for now
434 else if(counter == shots - 1) { // last projectile, link up with first projectile
435 prevproj.queuenext = proj;
436 firstproj.queueprev = proj;
437 proj.queuenext = firstproj;
438 proj.queueprev = prevproj;
440 else { // else link up with previous projectile
441 prevproj.queuenext = proj;
442 proj.queueprev = prevproj;
447 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
448 PROJECTILE_MAKETRIGGER(proj);
449 proj.projectiledeathtype = WEP_CRYLINK.m_id | HITTYPE_SECONDARY;
450 //proj.gravity = 0.001;
452 setorigin(proj, w_shotorg);
453 setsize(proj, '0 0 0', '0 0 0');
455 if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
462 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
466 s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
467 s = w_shotdir + right * s.y + up * s.z;
471 s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
474 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
475 settouch(proj, W_Crylink_Touch);
476 setthink(proj, W_Crylink_Fadethink);
477 if(counter == (shots - 1) / 2)
479 proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
480 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
481 proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
485 proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
486 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
487 proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
489 proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
490 proj.cnt = WEP_CVAR_SEC(crylink, bounces);
491 //proj.scale = 1 + 1 * proj.cnt;
493 proj.angles = vectoangles(proj.velocity);
495 //proj.glow_size = 20;
497 proj.flags = FL_PROJECTILE;
498 IL_PUSH(g_projectiles, proj);
499 IL_PUSH(g_bot_dodge, proj);
500 proj.missile_flags = MIF_SPLASH;
502 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
504 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
506 if(WEP_CVAR_SEC(crylink, joinspread) != 0)
508 actor.(weaponentity).crylink_lastgroup = proj;
509 W_Crylink_CheckLinks(proj);
510 actor.(weaponentity).crylink_waitrelease = 2;
514 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
517 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
519 PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
521 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
523 if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
524 thiswep.wr_reload(thiswep, actor, weaponentity);
529 if(actor.(weaponentity).crylink_waitrelease != 1)
530 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
532 W_Crylink_Attack(thiswep, actor, weaponentity);
533 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
537 if((fire & 2) && autocvar_g_balance_crylink_secondary)
539 if(actor.(weaponentity).crylink_waitrelease != 2)
540 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
542 W_Crylink_Attack2(thiswep, actor, weaponentity);
543 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
547 if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
549 if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
551 // fired and released now!
552 if(actor.(weaponentity).crylink_lastgroup)
555 entity linkjoineffect;
556 float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
558 pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
560 linkjoineffect = new(linkjoineffect);
561 linkjoineffect.weaponentity_fld = weaponentity;
562 setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
563 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
564 linkjoineffect.owner = actor;
565 setorigin(linkjoineffect, pos);
567 actor.(weaponentity).crylink_waitrelease = 0;
568 if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
569 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
572 actor.cnt = WEP_CRYLINK.m_id;
573 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
578 METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
580 // don't "run out of ammo" and switch weapons while waiting for release
581 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
584 float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(crylink, ammo);
585 ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
588 METHOD(Crylink, wr_checkammo2, 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 = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(crylink, ammo);
595 ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
598 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
600 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), SND_RELOAD);
602 METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
604 return WEAPON_CRYLINK_SUICIDE;
606 METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
608 return WEAPON_CRYLINK_MURDER;
612 METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
615 org2 = w_org + w_backoff * 2;
616 if(w_deathtype & HITTYPE_SECONDARY)
618 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
620 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
624 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
626 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);