#include <client/main.qc>
#include <client/mapvoting.qc>
#include <client/player_skeleton.qc>
+#include <client/polytrails.qc>
#include <client/shownames.qc>
#include <client/teamradar.qc>
#include <client/view.qc>
#include <client/main.qh>
#include <client/mapvoting.qh>
#include <client/player_skeleton.qh>
+#include <client/polytrails.qh>
#include <client/shownames.qh>
#include <client/teamradar.qh>
#include <client/view.qh>
--- /dev/null
+#include "polytrails.qh"
+
+#include <lib/warpzone/common.qh>
+
+#define POLYTRAIL_SEEK(_p, _rel) ((POLYTRAIL_BUFSIZE + (_p).polytrail_bufidx + (_rel)) % POLYTRAIL_BUFSIZE)
+void Trail_draw(entity this)
+{
+ if (wasfreed(this.polytrail_follow))
+ this.polytrail_follow = NULL;
+
+ // NOTE: need an internal reset distance for warpzones to work properly TODO?
+ float reset_distance = 200;
+
+ if (!this.polytrail_follow) {
+ float when = this.polytrail_buftime[this.polytrail_bufidx];
+ if (time - when > this.polytrail_lifetime) {
+ delete(this);
+ return;
+ }
+ } else if(reset_distance > 0 && this.polytrail_cnt > 0 && vdist(this.origin - this.polytrail_follow.origin, >, reset_distance)) {
+ Trail_recreate(this);
+ } else {
+ setorigin(this, this.polytrail_follow.origin);
+ if (this.polytrail_cnt < 0 || vdist(this.origin - this.polytrail_bufpos[this.polytrail_bufidx], >=, this.polytrail_segmentsize)) {
+ int i = POLYTRAIL_SEEK(this, 1);
+ this.polytrail_bufpos[i] = this.origin;
+ float f = this.polytrail_noise;
+ // TODO: alternate noise functions (none, chaotic, helix, zigzag, waves, ...)
+ switch(this.polytrail_noisefunc)
+ {
+ default:
+ case "none": this.polytrail_bufnoise[i] = '0 0 0'; break;
+ case "chaotic": this.polytrail_bufnoise[i] = randompos(f * '-1 -1 -1', f * '1 1 1'); break;
+ }
+ this.polytrail_buftime[i] = time;
+ this.polytrail_bufidx = i;
+ this.polytrail_cnt = bound(this.polytrail_cnt, i + 1, POLYTRAIL_BUFSIZE);
+ }
+ }
+
+ vector from = this.origin;
+ int count = this.polytrail_cnt;
+ for (int i = 0; i < count; ++i) {
+ int idx = POLYTRAIL_SEEK(this, -i);
+ float when = this.polytrail_buftime[idx];
+ // head: 0, tail: 1
+ float rtime = (time - when) / this.polytrail_lifetime;
+ if (rtime >= 1) {
+ break;
+ }
+ vector to = this.polytrail_bufpos[idx];
+ to += lerpvratio('0 0 0', this.polytrail_bufnoise[idx], rtime);
+ vector rgb = lerpv3ratio(this.polytrail_rgb[0], this.polytrail_rgb[1], this.polytrail_rgb[2], rtime);
+ float a = lerp3ratio(this.polytrail_alpha[0], this.polytrail_thickness[1], this.polytrail_alpha[2], rtime);
+ int thickness = lerp3ratio(this.polytrail_thickness[0], this.polytrail_thickness[1], this.polytrail_thickness[2], rtime);
+ vector thickdir = normalize(cross(normalize(to - from), view_origin - from)) * (thickness / 2);
+ vector A = from + thickdir;
+ vector B = from - thickdir;
+ vector C = to + thickdir;
+ vector D = to - thickdir;
+ R_BeginPolygon(this.polytrail_tex, DRAWFLAG_SCREEN, false);
+ R_PolygonVertex(B, '0 0 0', rgb, a);
+ R_PolygonVertex(A, '0 1 0', rgb, a);
+ R_PolygonVertex(C, '1 1 0', rgb, a);
+ R_PolygonVertex(D, '1 0 0', rgb, a);
+ R_EndPolygon();
+ from = to;
+ }
+}
+#undef POLYTRAIL_SEEK
+
+void Trail_recreate(entity this)
+{
+ PolyTrail t = NEW(PolyTrail, this.polytrail_follow);
+ t.polytrail_tex = this.polytrail_tex;
+ t.polytrail_rgb[0] = this.polytrail_rgb[0];
+ t.polytrail_rgb[1] = this.polytrail_rgb[1];
+ t.polytrail_rgb[2] = this.polytrail_rgb[2];
+ t.polytrail_alpha[0] = this.polytrail_alpha[0];
+ t.polytrail_alpha[1] = this.polytrail_alpha[1];
+ t.polytrail_alpha[2] = this.polytrail_alpha[2];
+ t.polytrail_thickness[0] = this.polytrail_thickness[0];
+ t.polytrail_thickness[1] = this.polytrail_thickness[1];
+ t.polytrail_thickness[2] = this.polytrail_thickness[2];
+ t.polytrail_lifetime = this.polytrail_lifetime;
+ t.polytrail_segmentsize = this.polytrail_segmentsize;
+ t.polytrail_noise = this.polytrail_noise;
+ t.polytrail_noisefunc = this.polytrail_noisefunc;
+
+ this.polytrail_follow = NULL;
+}
--- /dev/null
+#pragma once
+
+bool autocvar_cl_polytrails = true;
+
+CLASS(PolyTrail, Object)
+ ATTRIB(PolyTrail, polytrail_follow, entity, NULL);
+ ATTRIB(PolyTrail, polytrail_tex, string, string_null);
+ /** Lifetime per segment */
+ ATTRIB(PolyTrail, polytrail_lifetime, float, 0.2);
+ ATTRIB(PolyTrail, polytrail_noise, float, 0);
+ ATTRIB(PolyTrail, polytrail_segmentsize, float, 10);
+ ATTRIB(PolyTrail, polytrail_noisefunc, string, "chaotic");
+ ATTRIBARRAY(PolyTrail, polytrail_rgb, vector, 3);
+ ATTRIBARRAY(PolyTrail, polytrail_alpha, float, 3);
+ ATTRIBARRAY(PolyTrail, polytrail_thickness, float, 3);
+
+ /**
+ * Increase as necessary if the buffer is overflowing
+ * symptom: tail of trail is wrong
+ * cause: projectiles are too fast for the segment size
+ */
+ const int POLYTRAIL_BUFSIZE = 1 << 7;
+ /** One or more positional points */
+ ATTRIBARRAY(PolyTrail, polytrail_bufpos, vector, POLYTRAIL_BUFSIZE);
+ /** Noise for ending position */
+ ATTRIBARRAY(PolyTrail, polytrail_bufnoise, vector, POLYTRAIL_BUFSIZE);
+ /** Time of input */
+ ATTRIBARRAY(PolyTrail, polytrail_buftime, float, POLYTRAIL_BUFSIZE);
+ /** Current read index */
+ ATTRIB(PolyTrail, polytrail_bufidx, float, -1);
+ /** Counts positions stored */
+ ATTRIB(PolyTrail, polytrail_cnt, float, 0);
+
+ void Trail_draw(entity this);
+ void Trail_recreate(entity this);
+ ATTRIB(PolyTrail, draw, void(entity this), Trail_draw);
+ CONSTRUCTOR(PolyTrail, entity _follow) {
+ CONSTRUCT(PolyTrail);
+ this.polytrail_follow = _follow;
+ IL_PUSH(g_drawables, this);
+ }
+ENDCLASS(PolyTrail)
#include "projectile.qh"
#include <client/mutators/_mod.qh>
+#include <client/polytrails.qh>
#include <common/constants.qh>
#include <common/effects/all.qh>
#include <common/effects/effect.qh>
settouch(this, func_null);
this.bouncefactor = WEP_CVAR_SEC(WEP_ELECTRO, bouncefactor);
this.bouncestop = WEP_CVAR_SEC(WEP_ELECTRO, bouncestop);
+ if(autocvar_cl_polytrails)
+ {
+ this.traileffect = EFFECT_Null.m_id;
+ PolyTrail t = NEW(PolyTrail, this);
+ t.polytrail_tex = "particles/lgbeam.tga";
+ t.polytrail_rgb[0] = '0.8 0.8 1';
+ t.polytrail_rgb[1] = '0.5 0.5 1';
+ t.polytrail_rgb[2] = '0.1 0.1 0.7';
+ t.polytrail_alpha[0] = 1;
+ t.polytrail_alpha[1] = 0.5;
+ t.polytrail_alpha[2] = 0;
+ t.polytrail_thickness[0] = 15;
+ t.polytrail_thickness[1] = 7;
+ t.polytrail_thickness[2] = 0;
+ t.polytrail_lifetime = 0.18;
+ t.polytrail_segmentsize = 10;
+ t.polytrail_noise = 10;
+ t.polytrail_noisefunc = "chaotic";
+ }
break;
case PROJECTILE_RPC:
case PROJECTILE_ROCKET:
case PROJECTILE_CRYLINK_BOUNCING:
set_movetype(this, MOVETYPE_BOUNCE);
settouch(this, func_null);
+ if(autocvar_cl_polytrails)
+ {
+ this.traileffect = EFFECT_Null.m_id;
+ PolyTrail t = NEW(PolyTrail, this);
+ t.polytrail_tex = "gfx/crosshair_ring.tga"; // TODO: dedicated texture
+ t.polytrail_rgb[0] = '0.7 0 1';
+ t.polytrail_rgb[1] = '0.5 0 1';
+ t.polytrail_rgb[2] = '0.1 0 0.8';
+ t.polytrail_alpha[0] = 0.5;
+ t.polytrail_alpha[1] = 0.2;
+ t.polytrail_alpha[2] = 0;
+ t.polytrail_thickness[0] = 5;
+ t.polytrail_thickness[1] = 3;
+ t.polytrail_thickness[2] = 3;
+ t.polytrail_lifetime = 0.15;
+ t.polytrail_segmentsize = 80;
+ t.polytrail_noise = 0;
+ t.polytrail_noisefunc = "none";
+ }
break;
case PROJECTILE_FIREBALL:
loopsound(this, CH_SHOTS_SINGLE, SND_FIREBALL_FLY2, VOL_BASE, ATTEN_NORM);
case PROJECTILE_WAKIROCKET:
loopsound(this, CH_SHOTS_SINGLE, SND_TAG_ROCKET_FLY, VOL_BASE, ATTEN_NORM);
break;
+ case PROJECTILE_ELECTRO_BEAM:
+ if(autocvar_cl_polytrails)
+ {
+ this.traileffect = EFFECT_Null.m_id;
+ PolyTrail t = NEW(PolyTrail, this);
+ t.polytrail_tex = "particles/lgbeam.tga";
+ t.polytrail_rgb[0] = '0.75 0.75 1';
+ t.polytrail_rgb[1] = '0.45 0.45 1';
+ t.polytrail_rgb[2] = '0.1 0.1 0.75';
+ t.polytrail_alpha[0] = 1;
+ t.polytrail_alpha[1] = 0.5;
+ t.polytrail_alpha[2] = 0;
+ t.polytrail_thickness[0] = 5;
+ t.polytrail_thickness[1] = 5;
+ t.polytrail_thickness[2] = 0;
+ t.polytrail_lifetime = 0.27;
+ t.polytrail_segmentsize = 10;
+ t.polytrail_noise = 5;
+ t.polytrail_noisefunc = "chaotic";
+ }
+ break;
+ case PROJECTILE_CRYLINK:
+ if(autocvar_cl_polytrails)
+ {
+ this.traileffect = EFFECT_Null.m_id;
+ PolyTrail t = NEW(PolyTrail, this);
+ t.polytrail_tex = "gfx/crosshair_ring.tga"; // TODO: dedicated texture
+ t.polytrail_rgb[0] = '0.7 0 1';
+ t.polytrail_rgb[1] = '0.5 0 1';
+ t.polytrail_rgb[2] = '0.1 0 0.8';
+ t.polytrail_alpha[0] = 0.75;
+ t.polytrail_alpha[1] = 0.2;
+ t.polytrail_alpha[2] = 0;
+ t.polytrail_thickness[0] = 5;
+ t.polytrail_thickness[1] = 3;
+ t.polytrail_thickness[2] = 3;
+ t.polytrail_lifetime = 0.15;
+ t.polytrail_segmentsize = 80;
+ t.polytrail_noise = 0;
+ t.polytrail_noisefunc = "none";
+ }
+ break;
/*
case PROJECTILE_WAKICANNON:
break;
// Avoid perf problems near '0 0 0'; `set` without a description is because only current DP has this engine cvar
set cl_areagrid_link_SOLID_NOT 0
+
+seta cl_polytrails 1 "enable polygon-based trail effect drawing (looks better but may impact performance)"