1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
5 REGISTER_NET_LINKED(waypointsprites)
8 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
9 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
11 WriteHeader(MSG_ENTITY, waypointsprites);
13 sendflags = sendflags & 0x7F;
15 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
21 if(this.exteriormodeltoclient == to)
24 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
25 sendflags = M_ARGV(2, int);
28 WriteByte(MSG_ENTITY, sendflags);
29 WriteByte(MSG_ENTITY, this.wp_extra);
35 WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
39 float dt = this.pain_finished - time;
40 dt = bound(0, dt * 32, 16383);
41 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
42 WriteByte(MSG_ENTITY, (dt & 0x00FF));
48 WriteCoord(MSG_ENTITY, this.origin.x);
49 WriteCoord(MSG_ENTITY, this.origin.y);
50 WriteCoord(MSG_ENTITY, this.origin.z);
55 WriteByte(MSG_ENTITY, this.team);
56 WriteByte(MSG_ENTITY, this.rule);
60 WriteString(MSG_ENTITY, this.model1);
63 WriteString(MSG_ENTITY, this.model2);
66 WriteString(MSG_ENTITY, this.model3);
70 WriteCoord(MSG_ENTITY, this.fade_time);
71 WriteCoord(MSG_ENTITY, this.teleport_time);
72 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
73 WriteByte(MSG_ENTITY, f);
78 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
79 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
80 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
81 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
83 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
85 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
90 WriteByte(MSG_ENTITY, dt);
93 WriteByte(MSG_ENTITY, 0);
101 void Ent_WaypointSprite(entity this, bool isnew);
102 NET_HANDLE(waypointsprites, bool isnew) {
103 Ent_WaypointSprite(this, isnew);
107 void Ent_RemoveWaypointSprite(entity this)
109 if (this.netname) strunzone(this.netname);
110 if (this.netname2) strunzone(this.netname2);
111 if (this.netname3) strunzone(this.netname3);
114 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
115 void Ent_WaypointSprite(entity this, bool isnew)
117 int sendflags = ReadByte();
118 this.wp_extra = ReadByte();
121 this.spawntime = time;
123 this.draw2d = Draw_WaypointSprite;
125 IL_PUSH(g_drawables_2d, this);
126 IL_PUSH(g_radaricons, this);
129 InterpolateOrigin_Undo(this);
130 this.iflags |= IFLAG_ORIGIN;
132 if (sendflags & 0x80)
137 this.health = t / 191.0;
138 this.build_finished = 0;
142 t = (t - 192) * 256 + ReadByte();
143 this.build_started = servertime;
144 if (this.build_finished)
145 this.build_starthealth = bound(0, this.health, 1);
147 this.build_starthealth = 0;
148 this.build_finished = servertime + t / 32;
154 this.build_finished = 0;
159 // unfortunately, this needs to be exact (for the 3D display)
160 this.origin_x = ReadCoord();
161 this.origin_y = ReadCoord();
162 this.origin_z = ReadCoord();
163 setorigin(this, this.origin);
168 this.team = ReadByte();
169 this.rule = ReadByte();
175 strunzone(this.netname);
176 this.netname = strzone(ReadString());
182 strunzone(this.netname2);
183 this.netname2 = strzone(ReadString());
189 strunzone(this.netname3);
190 this.netname3 = strzone(ReadString());
195 this.lifetime = ReadCoord();
196 this.fadetime = ReadCoord();
197 this.maxdistance = ReadShort();
198 this.hideflags = ReadByte();
204 this.teamradar_icon = f & BITS(7);
207 this.(teamradar_times[this.teamradar_time_index]) = time;
208 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
210 this.teamradar_color_x = ReadByte() / 255.0;
211 this.teamradar_color_y = ReadByte() / 255.0;
212 this.teamradar_color_z = ReadByte() / 255.0;
213 this.helpme = ReadByte() * 0.1;
215 this.helpme += servertime;
218 InterpolateOrigin_Note(this);
220 this.entremove = Ent_RemoveWaypointSprite;
225 float spritelookupblinkvalue(entity this, string s)
227 if (s == WP_Weapon.netname) {
228 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
231 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
232 if(s == WP_FlagReturn.netname) return 2;
237 vector spritelookupcolor(entity this, string s, vector def)
239 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
240 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
241 if (MUTATOR_CALLHOOK(WP_Format, this, s))
243 return M_ARGV(2, vector);
248 string spritelookuptext(entity this, string s)
250 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
251 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
252 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
253 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
254 if (MUTATOR_CALLHOOK(WP_Format, this, s))
256 return M_ARGV(3, string);
259 // need to loop, as our netname could be one of three
260 FOREACH(Waypoints, it.netname == s, LAMBDA(
269 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
271 vector v1, v2, v3, v4;
273 hotspot = -1 * hotspot;
275 // hotspot-relative coordinates of the corners
277 v2 = hotspot + '1 0 0' * sz.x;
278 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
279 v4 = hotspot + '0 1 0' * sz.y;
281 // rotate them, and make them absolute
282 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
283 v1 = rotate(v1, rot) + org;
284 v2 = rotate(v2, rot) + org;
285 v3 = rotate(v3, rot) + org;
286 v4 = rotate(v4, rot) + org;
289 R_BeginPolygon(pic, f);
290 R_PolygonVertex(v1, '0 0 0', rgb, a);
291 R_PolygonVertex(v2, '1 0 0', rgb, a);
292 R_PolygonVertex(v3, '1 1 0', rgb, a);
293 R_PolygonVertex(v4, '0 1 0', rgb, a);
297 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
299 R_BeginPolygon(pic, f);
300 R_PolygonVertex(o, '0 0 0', rgb, a);
301 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
302 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
303 R_PolygonVertex(o + up, '0 1 0', rgb, a);
307 void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float theheight, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
310 float owidth; // outer width
312 hotspot = -1 * hotspot;
314 // hotspot-relative coordinates of the healthbar corners
319 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
320 o = rotate(o, rot) + org;
321 ri = rotate(ri, rot);
322 up = rotate(up, rot);
324 owidth = width + 2 * border;
325 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
327 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
328 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
329 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
330 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
331 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
334 // returns location of sprite text
335 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
337 float size = 9.0 * t;
338 float border = 1.5 * t;
339 float margin = 4.0 * t;
341 float borderDiag = border * 1.414;
342 vector arrowX = eX * size;
343 vector arrowY = eY * (size+borderDiag);
344 vector borderX = eX * (size+borderDiag);
345 vector borderY = eY * (size+borderDiag+border);
347 R_BeginPolygon("", DRAWFLAG_NORMAL);
348 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
349 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
350 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
351 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
352 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
355 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
356 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
357 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
358 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
361 return o + rotate(eY * (borderDiag+size+margin), ang);
364 // returns location of sprite healthbar
365 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
369 float aspect, sa, ca;
371 sw = stringwidth(s, false, fontsize);
378 // how do corners work?
379 aspect = vid_conwidth / vid_conheight;
381 ca = cos(ang) * aspect;
382 if (fabs(sa) > fabs(ca))
386 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
391 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
399 // we want to be onscreen
404 if (o.x > vid_conwidth - w)
405 o.x = vid_conwidth - w;
406 if (o.y > vid_conheight - h)
407 o.x = vid_conheight - h;
409 o.x += 0.5 * (w - sw);
411 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
419 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
421 vector yvec = '0.299 0.587 0.114';
422 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
425 vector fixrgbexcess(vector rgb)
428 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
430 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
431 if (rgb.z > 1) rgb.z = 1;
432 } else if (rgb.z > 1) {
433 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
434 if (rgb.y > 1) rgb.y = 1;
436 } else if (rgb.y > 1) {
437 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
439 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
440 if (rgb.z > 1) rgb.z = 1;
441 } else if (rgb.z > 1) {
442 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
443 if (rgb.x > 1) rgb.x = 1;
445 } else if (rgb.z > 1) {
446 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
448 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
449 if (rgb.y > 1) rgb.y = 1;
450 } else if (rgb.y > 1) {
451 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
452 if (rgb.x > 1) rgb.x = 1;
458 void Draw_WaypointSprite(entity this)
460 if (this.lifetime > 0)
461 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
465 if (this.hideflags & 2)
466 return; // radar only
468 if (autocvar_cl_hidewaypoints >= 2)
471 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
472 return; // fixed waypoint
474 InterpolateOrigin_Do(this);
476 float t = entcs_GetTeam(player_localnum) + 1;
477 string spriteimage = "";
482 case SPRITERULE_SPECTATOR:
484 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
485 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
488 spriteimage = this.netname;
490 case SPRITERULE_DEFAULT:
494 spriteimage = this.netname;
499 spriteimage = this.netname;
501 case SPRITERULE_TEAMPLAY:
502 if (t == NUM_SPECTATOR + 1)
503 spriteimage = this.netname3;
504 else if (this.team == t)
505 spriteimage = this.netname2;
507 spriteimage = this.netname;
510 error("Invalid waypointsprite rule!");
514 if (spriteimage == "")
517 ++waypointsprite_newcount;
519 float dist = vlen(this.origin - view_origin);
520 float a = this.alpha * autocvar_hud_panel_fg_alpha;
522 if (this.maxdistance > waypointsprite_normdistance)
523 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
524 else if (this.maxdistance > 0)
525 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
527 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
530 this.teamradar_color = '1 0 1';
531 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
534 if (time - floor(time) > 0.5)
536 if (this.helpme && time < this.helpme)
537 a *= SPRITE_HELPME_BLINK;
538 else if (this.lifetime > 0) // fading out waypoints don't blink
539 a *= spritelookupblinkvalue(this, spriteimage);
551 rgb = fixrgbexcess(rgb);
556 o = project_3d_to_2d(this.origin);
558 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
559 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
560 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
561 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
563 // scale it to be just in view
567 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
568 ang = atan2(-d.x, -d.y);
572 f1 = d.x / vid_conwidth;
573 f2 = d.y / vid_conheight;
575 if (max(f1, -f1) > max(f2, -f2)) {
578 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
581 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
586 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
589 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
593 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
601 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
602 ang = atan2(-d.x, -d.y);
607 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
608 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
609 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
610 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
612 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
614 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
616 t = waypointsprite_scale * vidscale;
617 a *= waypointsprite_alpha;
620 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
621 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
623 if (edgedistance_min < waypointsprite_edgefadedistance) {
624 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
625 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
627 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
628 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
629 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
632 if (this.build_finished)
634 if (time < this.build_finished + 0.25)
636 if (time < this.build_started)
637 this.health = this.build_starthealth;
638 else if (time < this.build_finished)
639 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
647 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
650 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
653 txt = spritelookuptext(this, spriteimage);
654 if (this.helpme && time < this.helpme)
655 txt = sprintf(_("%s needing help!"), txt);
656 if (autocvar_g_waypointsprite_uppercase)
657 txt = strtoupper(txt);
659 draw_beginBoldFont();
660 if (this.health >= 0)
662 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
665 if (this.build_finished)
670 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
672 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
679 SPRITE_HEALTHBAR_WIDTH * t,
680 SPRITE_HEALTHBAR_HEIGHT * t,
682 SPRITE_HEALTHBAR_BORDER * t,
685 a * SPRITE_HEALTHBAR_BORDERALPHA,
687 a * SPRITE_HEALTHBAR_HEALTHALPHA,
693 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
698 void WaypointSprite_Load_Frames(string ext)
700 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
702 int ext_len = strlen(ext);
703 int n = search_getsize(dh);
704 for (int i = 0; i < n; ++i)
706 string s = search_getfilename(dh, i);
707 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
709 int o = strstrofs(s, "_frame", 0);
710 string sname = strcat("/spriteframes/", substring(s, 0, o));
711 string sframes = substring(s, o + 6, strlen(s) - o - 6);
712 int f = stof(sframes) + 1;
713 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
718 void WaypointSprite_Load();
719 STATIC_INIT(WaypointSprite_Load) {
720 WaypointSprite_Load();
721 WaypointSprite_Load_Frames(".tga");
722 WaypointSprite_Load_Frames(".jpg");
724 void WaypointSprite_Load()
726 waypointsprite_fadedistance = vlen(mi_scale);
727 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
728 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
729 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
730 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
731 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
732 waypointsprite_scale = autocvar_g_waypointsprite_scale;
733 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
734 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
735 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
736 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
737 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
738 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
739 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
740 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
741 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
742 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
743 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
744 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
745 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
746 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
747 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
749 waypointsprite_count = waypointsprite_newcount;
750 waypointsprite_newcount = 0;
755 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
757 string m1 = _m1.netname;
758 string m2 = _m2.netname;
759 string m3 = _m3.netname;
777 void WaypointSprite_UpdateHealth(entity e, float f)
779 f = bound(0, f, e.max_health);
780 if (f != e.health || e.pain_finished)
788 void WaypointSprite_UpdateMaxHealth(entity e, float f)
790 if (f != e.max_health || e.pain_finished)
798 void WaypointSprite_UpdateBuildFinished(entity e, float f)
800 if (f != e.pain_finished || e.max_health)
808 void WaypointSprite_UpdateOrigin(entity e, vector o)
817 void WaypointSprite_UpdateRule(entity e, float t, float r)
819 // no check, as this is never called without doing an actual change (usually only once)
825 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
827 // no check, as this is never called without doing an actual change (usually only once)
829 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
834 void WaypointSprite_Ping(entity e)
837 if (time < e.waypointsprite_pingtime) return;
838 e.waypointsprite_pingtime = time + 0.3;
839 // ALWAYS sends (this causes a radar circle), thus no check
844 void WaypointSprite_HelpMePing(entity e)
846 WaypointSprite_Ping(e);
847 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
851 void WaypointSprite_FadeOutIn(entity e, float t)
856 e.teleport_time = time + t;
858 else if (t < (e.teleport_time - time))
860 // accelerate the waypoint's dying
862 // (e.teleport_time - time) / wp.fade_time stays
863 // e.teleport_time = time + fadetime
864 float current_fadetime = e.teleport_time - time;
865 e.teleport_time = time + t;
867 e.fade_time = -e.fade_time;
868 e.fade_time = e.fade_time * t / current_fadetime;
874 void WaypointSprite_Init()
876 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
877 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
878 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
881 void WaypointSprite_Kill(entity wp)
884 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
888 void WaypointSprite_Disown(entity wp, float fadetime)
891 if (wp.classname != "sprite_waypoint")
893 backtrace("Trying to disown a non-waypointsprite");
898 if (wp.exteriormodeltoclient == wp.owner)
899 wp.exteriormodeltoclient = NULL;
900 wp.owner.(wp.owned_by_field) = NULL;
903 WaypointSprite_FadeOutIn(wp, fadetime);
907 void WaypointSprite_Think(entity this)
909 bool doremove = false;
911 if (this.fade_time && time >= this.teleport_time)
916 if (this.exteriormodeltoclient)
917 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
920 WaypointSprite_Kill(this);
922 this.nextthink = time; // WHY?!?
925 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
927 // personal waypoints
928 if (this.enemy && this.enemy != view)
932 if (this.rule == SPRITERULE_SPECTATOR)
934 if (!autocvar_sv_itemstime)
936 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
939 else if (this.team && this.rule == SPRITERULE_DEFAULT)
941 if (this.team != view.team)
943 if (!IS_PLAYER(view))
950 entity WaypointSprite_getviewentity(entity e)
952 if (IS_SPEC(e)) e = e.enemy;
953 /* TODO idea (check this breaks nothing)
954 else if (e.classname == "observer")
960 float WaypointSprite_isteammate(entity e, entity e2)
963 return e2.team == e.team;
967 bool WaypointSprite_Customize(entity this, entity client)
969 // this is not in SendEntity because it shall run every frame, not just every update
971 // make spectators see what the player would see
972 entity e = WaypointSprite_getviewentity(client);
974 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
977 return this.waypointsprite_visible_for_player(this, client, e);
980 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
982 void WaypointSprite_Reset(entity this)
984 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
987 WaypointSprite_Kill(this);
990 entity WaypointSprite_Spawn(
991 entity spr, // sprite
992 float _lifetime, float maxdistance, // lifetime, max distance
993 entity ref, vector ofs, // position
994 entity showto, float t, // show to whom? Use a flag to indicate a team
995 entity own, .entity ownfield, // remove when own gets killed
996 float hideable, // true when it should be controlled by cl_hidewaypoints
997 entity icon // initial icon
1000 entity wp = new(sprite_waypoint);
1001 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1003 _lifetime = -_lifetime;
1004 wp.teleport_time = time + _lifetime;
1005 wp.exteriormodeltoclient = ref;
1009 setorigin(wp, ref.origin + ofs);
1016 wp.currentammo = hideable;
1020 delete(own.(ownfield));
1021 own.(ownfield) = wp;
1022 wp.owned_by_field = ownfield;
1024 wp.fade_rate = maxdistance;
1025 setthink(wp, WaypointSprite_Think);
1026 wp.nextthink = time;
1027 wp.model1 = spr.netname;
1028 setcefc(wp, WaypointSprite_Customize);
1029 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1030 wp.reset2 = WaypointSprite_Reset;
1032 wp.colormod = spr.m_color;
1033 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1037 entity WaypointSprite_SpawnFixed(
1042 entity icon // initial icon
1045 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1048 entity WaypointSprite_DeployFixed(
1050 float limited_range,
1053 entity icon // initial icon
1063 maxdistance = waypointsprite_limitedrange;
1066 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1069 entity WaypointSprite_DeployPersonal(
1073 entity icon // initial icon
1076 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1079 entity WaypointSprite_Attach(
1082 float limited_range,
1083 entity icon // initial icon
1087 if (player.waypointsprite_attachedforcarrier)
1088 return NULL; // can't attach to FC
1095 maxdistance = waypointsprite_limitedrange;
1098 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1101 entity WaypointSprite_AttachCarrier(
1104 entity icon // initial icon and color
1107 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1108 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1111 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1112 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1117 void WaypointSprite_DetachCarrier(entity carrier)
1119 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1122 void WaypointSprite_ClearPersonal(entity this)
1124 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1127 void WaypointSprite_ClearOwned(entity this)
1129 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1130 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1131 WaypointSprite_Kill(this.waypointsprite_attached);
1134 void WaypointSprite_PlayerDead(entity this)
1136 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1137 WaypointSprite_DetachCarrier(this);
1140 void WaypointSprite_PlayerGone(entity this)
1142 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1143 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1144 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1145 WaypointSprite_DetachCarrier(this);