4 spawnfunc(weapon_crylink) { weapon_defaultspawnfunc(this, WEP_CRYLINK); }
6 void W_Crylink_CheckLinks(entity e)
12 error("W_Crylink_CheckLinks: entity is NULL");
13 if(e.classname != "spike" || wasfreed(e))
14 error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
17 for(i = 0; i < 1000; ++i)
19 if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
20 error("W_Crylink_CheckLinks: queue is inconsistent");
26 error("W_Crylink_CheckLinks: infinite chain");
29 void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
31 W_Crylink_CheckLinks(next);
32 .entity weaponentity = me.weaponentity_fld;
33 if(me == own.(weaponentity).crylink_lastgroup)
34 own.(weaponentity).crylink_lastgroup = ((me == next) ? NULL : next);
35 prev.queuenext = next;
36 next.queueprev = prev;
37 me.classname = "spike_oktoremove";
39 W_Crylink_CheckLinks(next);
42 void W_Crylink_Dequeue(entity e)
44 W_Crylink_Dequeue_Raw(e.crylink_owner, e.queueprev, e, e.queuenext);
47 void W_Crylink_Reset(entity this)
49 W_Crylink_Dequeue(this);
53 // force projectile to explode
54 void W_Crylink_LinkExplode(entity e, entity e2, entity directhitentity)
61 a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
63 .entity weaponentity = e.weaponentity_fld;
64 if(e == e.crylink_owner.(weaponentity).crylink_lastgroup)
65 e.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
67 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
69 RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius),
70 NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, directhitentity);
72 W_Crylink_LinkExplode(e.queuenext, e2, directhitentity);
74 e.classname = "spike_oktoremove";
78 // adjust towards center
79 // returns the origin where they will meet... and the time till the meeting is
80 // stored in w_crylink_linkjoin_time.
81 // could possibly network this origin and time, and display a special particle
82 // effect when projectiles meet there :P
83 // jspeed: joining speed (calculate this as join spread * initial speed)
84 float w_crylink_linkjoin_time;
85 vector W_Crylink_LinkJoin(entity e, float jspeed)
87 vector avg_origin, avg_velocity;
92 // FIXME remove this debug code
93 W_Crylink_CheckLinks(e);
95 w_crylink_linkjoin_time = 0;
97 avg_origin = e.origin;
98 avg_velocity = e.velocity;
100 for(p = e; (p = p.queuenext) != e; )
102 avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
103 avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
106 avg_origin *= (1.0 / n);
107 avg_velocity *= (1.0 / n);
110 return avg_origin; // nothing to do
112 // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
113 avg_dist = (vlen(e.origin - avg_origin) ** 2);
114 for(p = e; (p = p.queuenext) != e; )
115 avg_dist += (vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin) ** 2);
116 avg_dist *= (1.0 / n);
117 avg_dist = sqrt(avg_dist);
120 return avg_origin; // no change needed
124 e.velocity = avg_velocity;
125 UpdateCSQCProjectile(e);
126 for(p = e; (p = p.queuenext) != e; )
128 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
129 UpdateCSQCProjectile(p);
131 targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
135 w_crylink_linkjoin_time = avg_dist / jspeed;
136 targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
138 e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
139 UpdateCSQCProjectile(e);
140 for(p = e; (p = p.queuenext) != e; )
142 p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
143 UpdateCSQCProjectile(p);
147 // jspeed -> +infinity:
148 // w_crylink_linkjoin_time -> +0
149 // targ_origin -> avg_origin
150 // p->velocity -> HUEG towards center
152 // w_crylink_linkjoin_time -> +/- infinity
153 // targ_origin -> avg_velocity * +/- infinity
154 // p->velocity -> avg_velocity
155 // jspeed -> -infinity:
156 // w_crylink_linkjoin_time -> -0
157 // targ_origin -> avg_origin
158 // p->velocity -> HUEG away from center
161 W_Crylink_CheckLinks(e);
166 void W_Crylink_LinkJoinEffect_Think(entity this)
168 // is there at least 2 projectiles very close?
171 .entity weaponentity = this.weaponentity_fld;
172 e = this.owner.(weaponentity).crylink_lastgroup;
176 if(vlen2(e.origin - this.origin) < vlen2(e.velocity) * frametime)
178 for(p = e; (p = p.queuenext) != e; )
180 if(vlen2(p.origin - this.origin) < vlen2(p.velocity) * frametime)
185 float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
187 if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
189 n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
193 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
194 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
195 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n,
198 WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n,
199 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), NULL, NULL, WEP_CVAR_BOTH(crylink, isprimary, force) * f, this.projectiledeathtype, toucher);
252 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)))))
254 .entity weaponentity = this.weaponentity_fld;
255 if(this == this.crylink_owner.(weaponentity).crylink_lastgroup)
256 this.crylink_owner.(weaponentity).crylink_lastgroup = NULL;
257 W_Crylink_LinkExplode(this.queuenext, this, toucher);
258 this.classname = "spike_oktoremove";
265 W_Crylink_Dequeue(this);
269 this.cnt = this.cnt - 1;
270 this.angles = vectoangles(this.velocity);
272 this.projectiledeathtype |= HITTYPE_BOUNCE;
273 // commented out as it causes a little hitch...
275 // CSQCProjectile(proj, true, PROJECTILE_CRYLINK, true);
278 void W_Crylink_Fadethink(entity this)
280 W_Crylink_Dequeue(this);
284 void W_Crylink_Attack(Weapon thiswep, entity actor, .entity weaponentity)
286 float counter, shots;
287 entity proj, prevproj, firstproj;
289 vector forward, right, up;
292 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo), weaponentity);
294 maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
295 maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
296 if(WEP_CVAR_PRI(crylink, joinexplode))
297 maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
299 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE, CH_WEAPON_A, maxdmg);
304 shots = WEP_CVAR_PRI(crylink, shots);
305 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
306 proj = prevproj = firstproj = NULL;
307 for(counter = 0; counter < shots; ++counter)
310 proj.reset = W_Crylink_Reset;
311 proj.realowner = proj.owner = actor;
312 proj.crylink_owner = actor;
313 proj.weaponentity_fld = weaponentity;
314 proj.bot_dodge = true;
315 proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
317 proj.queuenext = proj;
318 proj.queueprev = proj;
320 else if(counter == 0) { // first projectile, store in firstproj for now
323 else if(counter == shots - 1) { // last projectile, link up with first projectile
324 prevproj.queuenext = proj;
325 firstproj.queueprev = proj;
326 proj.queuenext = firstproj;
327 proj.queueprev = prevproj;
329 else { // else link up with previous projectile
330 prevproj.queuenext = proj;
331 proj.queueprev = prevproj;
336 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
337 PROJECTILE_MAKETRIGGER(proj);
338 proj.projectiledeathtype = WEP_CRYLINK.m_id;
339 //proj.gravity = 0.001;
341 setorigin(proj, w_shotorg);
342 setsize(proj, '0 0 0', '0 0 0');
350 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
354 s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
355 W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false);
356 settouch(proj, W_Crylink_Touch);
358 setthink(proj, W_Crylink_Fadethink);
361 proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
362 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
363 proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
367 proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
368 proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
369 proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
371 proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
372 proj.cnt = WEP_CVAR_PRI(crylink, bounces);
373 //proj.scale = 1 + 1 * proj.cnt;
375 proj.angles = vectoangles(proj.velocity);
377 //proj.glow_size = 20;
379 proj.flags = FL_PROJECTILE;
380 IL_PUSH(g_projectiles, proj);
381 IL_PUSH(g_bot_dodge, proj);
382 proj.missile_flags = MIF_SPLASH;
384 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
386 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
388 if(WEP_CVAR_PRI(crylink, joinspread) != 0)
390 actor.(weaponentity).crylink_lastgroup = proj;
391 W_Crylink_CheckLinks(proj);
392 actor.(weaponentity).crylink_waitrelease = 1;
396 void W_Crylink_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
398 float counter, shots;
399 entity proj, prevproj, firstproj;
401 vector forward, right, up;
404 W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
406 maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
407 maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
408 if(WEP_CVAR_SEC(crylink, joinexplode))
409 maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
411 W_SetupShot(actor, weaponentity, false, 2, SND_CRYLINK_FIRE2, CH_WEAPON_A, maxdmg);
416 shots = WEP_CVAR_SEC(crylink, shots);
417 Send_Effect(EFFECT_CRYLINK_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, shots);
418 proj = prevproj = firstproj = NULL;
419 for(counter = 0; counter < shots; ++counter)
422 proj.weaponentity_fld = weaponentity;
423 proj.reset = W_Crylink_Reset;
424 proj.realowner = proj.owner = actor;
425 proj.crylink_owner = actor;
426 proj.bot_dodge = true;
427 proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
429 proj.queuenext = proj;
430 proj.queueprev = proj;
432 else if(counter == 0) { // first projectile, store in firstproj for now
435 else if(counter == shots - 1) { // last projectile, link up with first projectile
436 prevproj.queuenext = proj;
437 firstproj.queueprev = proj;
438 proj.queuenext = firstproj;
439 proj.queueprev = prevproj;
441 else { // else link up with previous projectile
442 prevproj.queuenext = proj;
443 proj.queueprev = prevproj;
448 set_movetype(proj, MOVETYPE_BOUNCEMISSILE);
449 PROJECTILE_MAKETRIGGER(proj);
450 proj.projectiledeathtype = WEP_CRYLINK.m_id | HITTYPE_SECONDARY;
451 //proj.gravity = 0.001;
453 setorigin(proj, w_shotorg);
454 setsize(proj, '0 0 0', '0 0 0');
456 if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
463 makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
467 s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
468 s = w_shotdir + right * s.y + up * s.z;
472 s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
475 W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false);
476 settouch(proj, W_Crylink_Touch);
477 setthink(proj, W_Crylink_Fadethink);
478 if(counter == (shots - 1) / 2)
480 proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
481 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
482 proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
486 proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
487 proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
488 proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
490 proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
491 proj.cnt = WEP_CVAR_SEC(crylink, bounces);
492 //proj.scale = 1 + 1 * proj.cnt;
494 proj.angles = vectoangles(proj.velocity);
496 //proj.glow_size = 20;
498 proj.flags = FL_PROJECTILE;
499 IL_PUSH(g_projectiles, proj);
500 IL_PUSH(g_bot_dodge, proj);
501 proj.missile_flags = MIF_SPLASH;
503 CSQCProjectile(proj, true, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), true);
505 MUTATOR_CALLHOOK(EditProjectile, actor, proj);
507 if(WEP_CVAR_SEC(crylink, joinspread) != 0)
509 actor.(weaponentity).crylink_lastgroup = proj;
510 W_Crylink_CheckLinks(proj);
511 actor.(weaponentity).crylink_waitrelease = 2;
515 METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
518 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
520 PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
522 METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
524 if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
525 thiswep.wr_reload(thiswep, actor, weaponentity);
530 if(actor.(weaponentity).crylink_waitrelease != 1)
531 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
533 W_Crylink_Attack(thiswep, actor, weaponentity);
534 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
538 if((fire & 2) && autocvar_g_balance_crylink_secondary)
540 if(actor.(weaponentity).crylink_waitrelease != 2)
541 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
543 W_Crylink_Attack2(thiswep, actor, weaponentity);
544 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
548 if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
550 if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
552 // fired and released now!
553 if(actor.(weaponentity).crylink_lastgroup)
556 entity linkjoineffect;
557 float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
559 pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
561 linkjoineffect = new(linkjoineffect);
562 linkjoineffect.weaponentity_fld = weaponentity;
563 setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
564 linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
565 linkjoineffect.owner = actor;
566 setorigin(linkjoineffect, pos);
568 actor.(weaponentity).crylink_waitrelease = 0;
569 if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
570 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
573 actor.cnt = WEP_CRYLINK.m_id;
574 actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
579 METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
581 // don't "run out of ammo" and switch weapons while waiting for release
582 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
585 float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(crylink, ammo);
586 ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
589 METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
591 // don't "run out of ammo" and switch weapons while waiting for release
592 if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
595 float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(crylink, ammo);
596 ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
599 METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
601 W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), SND_RELOAD);
603 METHOD(Crylink, wr_suicidemessage, Notification(entity thiswep))
605 return WEAPON_CRYLINK_SUICIDE;
607 METHOD(Crylink, wr_killmessage, Notification(entity thiswep))
609 return WEAPON_CRYLINK_MURDER;
613 METHOD(Crylink, wr_impacteffect, void(entity thiswep, entity actor))
616 org2 = w_org + w_backoff * 2;
617 if(w_deathtype & HITTYPE_SECONDARY)
619 pointparticles(EFFECT_CRYLINK_IMPACT2, org2, '0 0 0', 1);
621 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT2, VOL_BASE, ATTN_NORM);
625 pointparticles(EFFECT_CRYLINK_IMPACT, org2, '0 0 0', 1);
627 sound(actor, CH_SHOTS, SND_CRYLINK_IMPACT, VOL_BASE, ATTN_NORM);