1 #include "waypointsprites.qh"
5 REGISTER_MUTATOR(waypointsprites, true);
7 REGISTER_NET_LINKED(waypointsprites)
10 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
11 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
13 WriteHeader(MSG_ENTITY, waypointsprites);
15 sendflags = sendflags & 0x7F;
17 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
23 if(this.exteriormodeltoclient == to)
26 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
27 sendflags = M_ARGV(2, int);
30 WriteByte(MSG_ENTITY, sendflags);
31 WriteByte(MSG_ENTITY, this.wp_extra);
37 WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
41 float dt = this.pain_finished - time;
42 dt = bound(0, dt * 32, 16383);
43 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
44 WriteByte(MSG_ENTITY, (dt & 0x00FF));
50 WriteCoord(MSG_ENTITY, this.origin.x);
51 WriteCoord(MSG_ENTITY, this.origin.y);
52 WriteCoord(MSG_ENTITY, this.origin.z);
57 WriteByte(MSG_ENTITY, this.team);
58 WriteByte(MSG_ENTITY, this.rule);
62 WriteString(MSG_ENTITY, this.model1);
65 WriteString(MSG_ENTITY, this.model2);
68 WriteString(MSG_ENTITY, this.model3);
72 WriteCoord(MSG_ENTITY, this.fade_time);
73 WriteCoord(MSG_ENTITY, this.teleport_time);
74 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
75 WriteByte(MSG_ENTITY, f);
80 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
81 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
82 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
83 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
85 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
87 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
92 WriteByte(MSG_ENTITY, dt);
95 WriteByte(MSG_ENTITY, 0);
103 void Ent_WaypointSprite(entity this, bool isnew);
104 NET_HANDLE(waypointsprites, bool isnew) {
105 Ent_WaypointSprite(this, isnew);
109 void Ent_RemoveWaypointSprite(entity this)
111 if (this.netname) strunzone(this.netname);
112 if (this.netname2) strunzone(this.netname2);
113 if (this.netname3) strunzone(this.netname3);
116 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
117 void Ent_WaypointSprite(entity this, bool isnew)
119 int sendflags = ReadByte();
120 this.wp_extra = ReadByte();
123 this.spawntime = time;
125 this.draw2d = Draw_WaypointSprite;
127 IL_PUSH(g_drawables_2d, this);
128 IL_PUSH(g_radaricons, this);
131 InterpolateOrigin_Undo(this);
132 this.iflags |= IFLAG_ORIGIN;
134 if (sendflags & 0x80)
139 this.health = t / 191.0;
140 this.build_finished = 0;
144 t = (t - 192) * 256 + ReadByte();
145 this.build_started = servertime;
146 if (this.build_finished)
147 this.build_starthealth = bound(0, this.health, 1);
149 this.build_starthealth = 0;
150 this.build_finished = servertime + t / 32;
156 this.build_finished = 0;
161 // unfortunately, this needs to be exact (for the 3D display)
162 this.origin_x = ReadCoord();
163 this.origin_y = ReadCoord();
164 this.origin_z = ReadCoord();
165 setorigin(this, this.origin);
170 this.team = ReadByte();
171 this.rule = ReadByte();
177 strunzone(this.netname);
178 this.netname = strzone(ReadString());
184 strunzone(this.netname2);
185 this.netname2 = strzone(ReadString());
191 strunzone(this.netname3);
192 this.netname3 = strzone(ReadString());
197 this.lifetime = ReadCoord();
198 this.fadetime = ReadCoord();
199 this.maxdistance = ReadShort();
200 this.hideflags = ReadByte();
206 this.teamradar_icon = f & BITS(7);
209 this.(teamradar_times[this.teamradar_time_index]) = time;
210 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
212 this.teamradar_color_x = ReadByte() / 255.0;
213 this.teamradar_color_y = ReadByte() / 255.0;
214 this.teamradar_color_z = ReadByte() / 255.0;
215 this.helpme = ReadByte() * 0.1;
217 this.helpme += servertime;
220 InterpolateOrigin_Note(this);
222 this.entremove = Ent_RemoveWaypointSprite;
227 float spritelookupblinkvalue(entity this, string s)
229 if (s == WP_Weapon.netname) {
230 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
233 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
234 if(s == WP_FlagReturn.netname) return 2;
239 vector spritelookupcolor(entity this, string s, vector def)
241 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
242 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
243 if (MUTATOR_CALLHOOK(WP_Format, this, s))
245 return M_ARGV(2, vector);
250 string spritelookuptext(entity this, string s)
252 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
253 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
254 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
255 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
256 if (MUTATOR_CALLHOOK(WP_Format, this, s))
258 return M_ARGV(3, string);
261 // need to loop, as our netname could be one of three
262 FOREACH(Waypoints, it.netname == s, LAMBDA(
271 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
273 vector v1, v2, v3, v4;
275 hotspot = -1 * hotspot;
277 // hotspot-relative coordinates of the corners
279 v2 = hotspot + '1 0 0' * sz.x;
280 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
281 v4 = hotspot + '0 1 0' * sz.y;
283 // rotate them, and make them absolute
284 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
285 v1 = rotate(v1, rot) + org;
286 v2 = rotate(v2, rot) + org;
287 v3 = rotate(v3, rot) + org;
288 v4 = rotate(v4, rot) + org;
291 R_BeginPolygon(pic, f);
292 R_PolygonVertex(v1, '0 0 0', rgb, a);
293 R_PolygonVertex(v2, '1 0 0', rgb, a);
294 R_PolygonVertex(v3, '1 1 0', rgb, a);
295 R_PolygonVertex(v4, '0 1 0', rgb, a);
299 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
301 R_BeginPolygon(pic, f);
302 R_PolygonVertex(o, '0 0 0', rgb, a);
303 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
304 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
305 R_PolygonVertex(o + up, '0 1 0', rgb, a);
309 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)
312 float owidth; // outer width
314 hotspot = -1 * hotspot;
316 // hotspot-relative coordinates of the healthbar corners
321 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
322 o = rotate(o, rot) + org;
323 ri = rotate(ri, rot);
324 up = rotate(up, rot);
326 owidth = width + 2 * border;
327 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
329 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
330 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
331 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
332 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
333 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
336 // returns location of sprite text
337 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
339 float size = 9.0 * t;
340 float border = 1.5 * t;
341 float margin = 4.0 * t;
343 float borderDiag = border * 1.414;
344 vector arrowX = eX * size;
345 vector arrowY = eY * (size+borderDiag);
346 vector borderX = eX * (size+borderDiag);
347 vector borderY = eY * (size+borderDiag+border);
349 R_BeginPolygon("", DRAWFLAG_NORMAL);
350 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
351 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
352 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
353 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
354 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
357 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
358 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
359 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
360 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
363 return o + rotate(eY * (borderDiag+size+margin), ang);
366 // returns location of sprite healthbar
367 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
371 float aspect, sa, ca;
373 sw = stringwidth(s, false, fontsize);
380 // how do corners work?
381 aspect = vid_conwidth / vid_conheight;
383 ca = cos(ang) * aspect;
384 if (fabs(sa) > fabs(ca))
388 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
393 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
401 // we want to be onscreen
406 if (o.x > vid_conwidth - w)
407 o.x = vid_conwidth - w;
408 if (o.y > vid_conheight - h)
409 o.x = vid_conheight - h;
411 o.x += 0.5 * (w - sw);
413 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
421 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
423 vector yvec = '0.299 0.587 0.114';
424 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
427 vector fixrgbexcess(vector rgb)
430 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
432 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
433 if (rgb.z > 1) rgb.z = 1;
434 } else if (rgb.z > 1) {
435 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
436 if (rgb.y > 1) rgb.y = 1;
438 } else if (rgb.y > 1) {
439 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
441 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
442 if (rgb.z > 1) rgb.z = 1;
443 } else if (rgb.z > 1) {
444 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
445 if (rgb.x > 1) rgb.x = 1;
447 } else if (rgb.z > 1) {
448 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
450 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
451 if (rgb.y > 1) rgb.y = 1;
452 } else if (rgb.y > 1) {
453 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
454 if (rgb.x > 1) rgb.x = 1;
460 void Draw_WaypointSprite(entity this)
462 if (this.lifetime > 0)
463 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
464 else if (this.lifetime < 0)
465 this.alpha = (time < this.fadetime) ? 1 : 0; // no fading out effect
469 if (this.hideflags & 2)
470 return; // radar only
472 if (autocvar_cl_hidewaypoints >= 2)
475 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
476 return; // fixed waypoint
478 InterpolateOrigin_Do(this);
480 float t = entcs_GetTeam(player_localnum) + 1;
481 string spriteimage = "";
486 case SPRITERULE_SPECTATOR:
488 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
489 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
492 spriteimage = this.netname;
494 case SPRITERULE_DEFAULT:
498 spriteimage = this.netname;
503 spriteimage = this.netname;
505 case SPRITERULE_TEAMPLAY:
506 if (t == NUM_SPECTATOR + 1)
507 spriteimage = this.netname3;
508 else if (this.team == t)
509 spriteimage = this.netname2;
511 spriteimage = this.netname;
514 error("Invalid waypointsprite rule!");
518 if (spriteimage == "")
521 ++waypointsprite_newcount;
523 float dist = vlen(this.origin - view_origin);
524 float a = this.alpha * autocvar_hud_panel_fg_alpha;
526 if (this.maxdistance > waypointsprite_normdistance)
527 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
528 else if (this.maxdistance > 0)
529 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
531 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
534 this.teamradar_color = '1 0 1';
535 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
538 if (time - floor(time) > 0.5)
540 if (this.helpme && time < this.helpme)
541 a *= SPRITE_HELPME_BLINK;
542 else if (this.lifetime > 0) // fading out waypoints don't blink
543 a *= spritelookupblinkvalue(this, spriteimage);
555 rgb = fixrgbexcess(rgb);
560 o = project_3d_to_2d(this.origin);
562 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
563 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
564 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
565 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
567 // scale it to be just in view
571 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
572 ang = atan2(-d.x, -d.y);
576 f1 = d.x / vid_conwidth;
577 f2 = d.y / vid_conheight;
579 if (max(f1, -f1) > max(f2, -f2)) {
582 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
585 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
590 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
593 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
597 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
605 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
606 ang = atan2(-d.x, -d.y);
611 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
612 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
613 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
614 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
616 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
618 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
620 t = waypointsprite_scale * vidscale;
621 a *= waypointsprite_alpha;
624 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
625 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
627 if (edgedistance_min < waypointsprite_edgefadedistance) {
628 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
629 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
631 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
632 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
633 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
636 if (this.build_finished)
638 if (time < this.build_finished + 0.25)
640 if (time < this.build_started)
641 this.health = this.build_starthealth;
642 else if (time < this.build_finished)
643 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
651 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
654 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
657 txt = spritelookuptext(this, spriteimage);
658 if (this.helpme && time < this.helpme)
659 txt = sprintf(_("%s needing help!"), txt);
660 if (autocvar_g_waypointsprite_uppercase)
661 txt = strtoupper(txt);
663 draw_beginBoldFont();
664 if (this.health >= 0)
666 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
669 if (this.build_finished)
674 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
676 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
683 SPRITE_HEALTHBAR_WIDTH * t,
684 SPRITE_HEALTHBAR_HEIGHT * t,
686 SPRITE_HEALTHBAR_BORDER * t,
689 a * SPRITE_HEALTHBAR_BORDERALPHA,
691 a * SPRITE_HEALTHBAR_HEALTHALPHA,
697 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
702 void WaypointSprite_Load_Frames(string ext)
704 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
706 int ext_len = strlen(ext);
707 int n = search_getsize(dh);
708 for (int i = 0; i < n; ++i)
710 string s = search_getfilename(dh, i);
711 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
713 int o = strstrofs(s, "_frame", 0);
714 string sname = strcat("/spriteframes/", substring(s, 0, o));
715 string sframes = substring(s, o + 6, strlen(s) - o - 6);
716 int f = stof(sframes) + 1;
717 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
722 void WaypointSprite_Load();
723 STATIC_INIT(WaypointSprite_Load) {
724 WaypointSprite_Load();
725 WaypointSprite_Load_Frames(".tga");
726 WaypointSprite_Load_Frames(".jpg");
728 void WaypointSprite_Load()
730 waypointsprite_fadedistance = vlen(mi_scale);
731 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
732 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
733 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
734 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
735 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
736 waypointsprite_scale = autocvar_g_waypointsprite_scale;
737 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
738 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
739 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
740 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
741 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
742 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
743 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
744 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
745 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
746 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
747 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
748 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
749 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
750 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
751 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
753 waypointsprite_count = waypointsprite_newcount;
754 waypointsprite_newcount = 0;
759 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
761 string m1 = _m1.netname;
762 string m2 = _m2.netname;
763 string m3 = _m3.netname;
781 void WaypointSprite_UpdateHealth(entity e, float f)
783 f = bound(0, f, e.max_health);
784 if (f != e.health || e.pain_finished)
792 void WaypointSprite_UpdateMaxHealth(entity e, float f)
794 if (f != e.max_health || e.pain_finished)
802 void WaypointSprite_UpdateBuildFinished(entity e, float f)
804 if (f != e.pain_finished || e.max_health)
812 void WaypointSprite_UpdateOrigin(entity e, vector o)
821 void WaypointSprite_UpdateRule(entity e, float t, float r)
823 // no check, as this is never called without doing an actual change (usually only once)
829 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
831 // no check, as this is never called without doing an actual change (usually only once)
833 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
838 void WaypointSprite_Ping(entity e)
841 if (time < e.waypointsprite_pingtime) return;
842 e.waypointsprite_pingtime = time + 0.3;
843 // ALWAYS sends (this causes a radar circle), thus no check
848 void WaypointSprite_HelpMePing(entity e)
850 WaypointSprite_Ping(e);
851 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
855 void WaypointSprite_FadeOutIn(entity e, float t)
860 e.teleport_time = time + t;
862 else if (t < (e.teleport_time - time))
864 // accelerate the waypoint's dying
866 // (e.teleport_time - time) / wp.fade_time stays
867 // e.teleport_time = time + fadetime
868 float current_fadetime = e.teleport_time - time;
869 e.teleport_time = time + t;
871 e.fade_time = -e.fade_time;
872 e.fade_time = e.fade_time * t / current_fadetime;
878 void WaypointSprite_Init()
880 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
881 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
882 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
885 void WaypointSprite_Kill(entity wp)
888 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
892 void WaypointSprite_Disown(entity wp, float fadetime)
895 if (wp.classname != "sprite_waypoint")
897 backtrace("Trying to disown a non-waypointsprite");
902 if (wp.exteriormodeltoclient == wp.owner)
903 wp.exteriormodeltoclient = NULL;
904 wp.owner.(wp.owned_by_field) = NULL;
907 WaypointSprite_FadeOutIn(wp, fadetime);
911 void WaypointSprite_Think(entity this)
913 bool doremove = false;
915 if (this.fade_time && time >= this.teleport_time)
920 if (this.exteriormodeltoclient)
921 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
924 WaypointSprite_Kill(this);
926 this.nextthink = time; // WHY?!?
929 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
931 // personal waypoints
932 if (this.enemy && this.enemy != view)
936 if (this.rule == SPRITERULE_SPECTATOR)
938 if (!autocvar_sv_itemstime)
940 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
943 else if (this.team && this.rule == SPRITERULE_DEFAULT)
945 if (this.team != view.team)
947 if (!IS_PLAYER(view))
954 entity WaypointSprite_getviewentity(entity e)
956 if (IS_SPEC(e)) e = e.enemy;
957 /* TODO idea (check this breaks nothing)
958 else if (e.classname == "observer")
964 float WaypointSprite_isteammate(entity e, entity e2)
967 return e2.team == e.team;
971 bool WaypointSprite_Customize(entity this, entity client)
973 // this is not in SendEntity because it shall run every frame, not just every update
975 // make spectators see what the player would see
976 entity e = WaypointSprite_getviewentity(client);
978 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
981 return this.waypointsprite_visible_for_player(this, client, e);
984 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
986 void WaypointSprite_Reset(entity this)
988 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
991 WaypointSprite_Kill(this);
994 entity WaypointSprite_Spawn(
995 entity spr, // sprite
996 float _lifetime, float maxdistance, // lifetime, max distance
997 entity ref, vector ofs, // position
998 entity showto, float t, // show to whom? Use a flag to indicate a team
999 entity own, .entity ownfield, // remove when own gets killed
1000 float hideable, // true when it should be controlled by cl_hidewaypoints
1001 entity icon // initial icon
1004 entity wp = new(sprite_waypoint);
1005 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1007 _lifetime = -_lifetime;
1008 wp.teleport_time = time + _lifetime;
1009 wp.exteriormodeltoclient = ref;
1013 setorigin(wp, ref.origin + ofs);
1020 wp.currentammo = hideable;
1024 delete(own.(ownfield));
1025 own.(ownfield) = wp;
1026 wp.owned_by_field = ownfield;
1028 wp.fade_rate = maxdistance;
1029 setthink(wp, WaypointSprite_Think);
1030 wp.nextthink = time;
1031 wp.model1 = spr.netname;
1032 setcefc(wp, WaypointSprite_Customize);
1033 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1034 wp.reset2 = WaypointSprite_Reset;
1036 wp.colormod = spr.m_color;
1037 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1041 entity WaypointSprite_SpawnFixed(
1046 entity icon // initial icon
1049 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1052 entity WaypointSprite_DeployFixed(
1054 float limited_range,
1057 entity icon // initial icon
1067 maxdistance = waypointsprite_limitedrange;
1070 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1073 entity WaypointSprite_DeployPersonal(
1077 entity icon // initial icon
1080 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1083 entity WaypointSprite_Attach(
1086 float limited_range,
1087 entity icon // initial icon
1091 if (player.waypointsprite_attachedforcarrier)
1092 return NULL; // can't attach to FC
1099 maxdistance = waypointsprite_limitedrange;
1102 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1105 entity WaypointSprite_AttachCarrier(
1108 entity icon // initial icon and color
1111 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1112 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1115 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1116 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1121 void WaypointSprite_DetachCarrier(entity carrier)
1123 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1126 void WaypointSprite_ClearPersonal(entity this)
1128 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1131 void WaypointSprite_ClearOwned(entity this)
1133 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1134 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1135 WaypointSprite_Kill(this.waypointsprite_attached);
1138 void WaypointSprite_PlayerDead(entity this)
1140 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1141 WaypointSprite_DetachCarrier(this);
1144 void WaypointSprite_PlayerGone(entity this)
1146 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1147 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1148 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1149 WaypointSprite_DetachCarrier(this);