1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
6 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
7 float WaypointSprite_SendEntity(entity to, float sendflags)
9 WriteMutator(MSG_ENTITY, waypointsprites);
11 sendflags = sendflags & 0x7F;
15 else if (self.max_health || (self.pain_finished && (time < self.pain_finished + 0.25)))
18 WriteByte(MSG_ENTITY, sendflags);
19 WriteByte(MSG_ENTITY, self.wp_extra);
25 WriteByte(MSG_ENTITY, (self.health / self.max_health) * 191.0);
29 float dt = self.pain_finished - time;
30 dt = bound(0, dt * 32, 16383);
31 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
32 WriteByte(MSG_ENTITY, (dt & 0x00FF));
38 WriteCoord(MSG_ENTITY, self.origin.x);
39 WriteCoord(MSG_ENTITY, self.origin.y);
40 WriteCoord(MSG_ENTITY, self.origin.z);
45 WriteByte(MSG_ENTITY, self.team);
46 WriteByte(MSG_ENTITY, self.rule);
50 WriteString(MSG_ENTITY, self.model1);
53 WriteString(MSG_ENTITY, self.model2);
56 WriteString(MSG_ENTITY, self.model3);
60 WriteCoord(MSG_ENTITY, self.fade_time);
61 WriteCoord(MSG_ENTITY, self.teleport_time);
62 WriteShort(MSG_ENTITY, self.fade_rate); // maxdist
66 if (self.exteriormodeltoclient == to)
70 if (self.owner.classname == "onslaught_controlpoint")
72 entity wp_owner = self.owner;
73 entity e = WaypointSprite_getviewentity(to);
74 if (SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { f |= 2; }
75 if (!ons_ControlPoint_Attackable(wp_owner, e.team)) { f |= 2; }
77 if (self.owner.classname == "onslaught_generator")
79 entity wp_owner = self.owner;
80 if (wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { f |= 2; }
81 if (wp_owner.health <= 0) { f |= 2; }
84 WriteByte(MSG_ENTITY, f);
89 WriteByte(MSG_ENTITY, self.cnt); // icon on radar
90 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
91 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
92 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
94 if (WaypointSprite_isteammate(self.owner, WaypointSprite_getviewentity(to)))
96 float dt = (self.waypointsprite_helpmetime - time) / 0.1;
101 WriteByte(MSG_ENTITY, dt);
104 WriteByte(MSG_ENTITY, 0);
112 void Ent_WaypointSprite();
113 MUTATOR_HOOKFUNCTION(waypointsprites, CSQC_Ent_Update) {
114 if (MUTATOR_RETURNVALUE) return false;
115 if (!ReadMutatorEquals(mutator_argv_int_0, waypointsprites)) return false;
116 Ent_WaypointSprite();
120 void Ent_RemoveWaypointSprite()
122 if (self.netname) strunzone(self.netname);
123 if (self.netname2) strunzone(self.netname2);
124 if (self.netname3) strunzone(self.netname3);
127 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
128 void Ent_WaypointSprite()
130 int sendflags = ReadByte();
131 self.wp_extra = ReadByte();
134 self.spawntime = time;
136 self.draw2d = Draw_WaypointSprite;
138 InterpolateOrigin_Undo();
139 self.iflags |= IFLAG_ORIGIN;
141 if (sendflags & 0x80)
146 self.health = t / 191.0;
147 self.build_finished = 0;
151 t = (t - 192) * 256 + ReadByte();
152 self.build_started = servertime;
153 if (self.build_finished)
154 self.build_starthealth = bound(0, self.health, 1);
156 self.build_starthealth = 0;
157 self.build_finished = servertime + t / 32;
163 self.build_finished = 0;
168 // unfortunately, this needs to be exact (for the 3D display)
169 self.origin_x = ReadCoord();
170 self.origin_y = ReadCoord();
171 self.origin_z = ReadCoord();
172 setorigin(self, self.origin);
177 self.team = ReadByte();
178 self.rule = ReadByte();
184 strunzone(self.netname);
185 self.netname = strzone(ReadString());
191 strunzone(self.netname2);
192 self.netname2 = strzone(ReadString());
198 strunzone(self.netname3);
199 self.netname3 = strzone(ReadString());
204 self.lifetime = ReadCoord();
205 self.fadetime = ReadCoord();
206 self.maxdistance = ReadShort();
207 self.hideflags = ReadByte();
213 self.teamradar_icon = (f & 0x7F);
216 self.(teamradar_times[self.teamradar_time_index]) = time;
217 self.teamradar_time_index = (self.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
219 self.teamradar_color_x = ReadByte() / 255.0;
220 self.teamradar_color_y = ReadByte() / 255.0;
221 self.teamradar_color_z = ReadByte() / 255.0;
222 self.helpme = ReadByte() * 0.1;
224 self.helpme += servertime;
227 InterpolateOrigin_Note();
229 self.entremove = Ent_RemoveWaypointSprite;
234 float spritelookupblinkvalue(string s)
236 if (s == WP_Weapon.netname) {
237 if (get_weaponinfo(self.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
240 if (s == WP_Item.netname) return ITEMS[self.wp_extra].m_waypointblink;
244 case "item-invis": return 2;
245 case "item-extralife": return 2;
246 case "item-speed": return 2;
251 vector spritelookupcolor(string s, vector def)
253 if (s == WP_Weapon.netname) return get_weaponinfo(self.wp_extra).wpcolor;
254 if (s == WP_Item.netname) return ITEMS[self.wp_extra].m_color;
255 if (s == WP_Buff.netname) return BUFFS[self.wp_extra].m_color;
259 string spritelookuptext(string s)
261 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
262 if (s == WP_Weapon.netname) return get_weaponinfo(self.wp_extra).message;
263 if (s == WP_Item.netname) return ITEMS[self.wp_extra].m_waypoint;
264 if (s == WP_Buff.netname) return BUFFS[self.wp_extra].m_prettyName;
265 if (s == WP_Monster.netname) return get_monsterinfo(self.wp_extra).monster_name;
267 // need to loop, as our netname could be one of three
268 FOREACH(WAYPOINTS, it.netname == s, LAMBDA(
274 case "item-invis": return _("Invisibility");
275 case "item-extralife": return _("Extra life");
276 case "item-speed": return _("Speed");
283 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
285 vector v1, v2, v3, v4;
287 hotspot = -1 * hotspot;
289 // hotspot-relative coordinates of the corners
291 v2 = hotspot + '1 0 0' * sz.x;
292 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
293 v4 = hotspot + '0 1 0' * sz.y;
295 // rotate them, and make them absolute
296 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
297 v1 = rotate(v1, rot) + org;
298 v2 = rotate(v2, rot) + org;
299 v3 = rotate(v3, rot) + org;
300 v4 = rotate(v4, rot) + org;
303 R_BeginPolygon(pic, f);
304 R_PolygonVertex(v1, '0 0 0', rgb, a);
305 R_PolygonVertex(v2, '1 0 0', rgb, a);
306 R_PolygonVertex(v3, '1 1 0', rgb, a);
307 R_PolygonVertex(v4, '0 1 0', rgb, a);
311 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
313 R_BeginPolygon(pic, f);
314 R_PolygonVertex(o, '0 0 0', rgb, a);
315 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
316 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
317 R_PolygonVertex(o + up, '0 1 0', rgb, a);
321 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)
324 float owidth; // outer width
326 hotspot = -1 * hotspot;
328 // hotspot-relative coordinates of the healthbar corners
333 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
334 o = rotate(o, rot) + org;
335 ri = rotate(ri, rot);
336 up = rotate(up, rot);
338 owidth = width + 2 * border;
339 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
341 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
342 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
343 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
344 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
345 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
348 // returns location of sprite text
349 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
351 float size = 9.0 * t;
352 float border = 1.5 * t;
353 float margin = 4.0 * t;
355 float borderDiag = border * 1.414;
356 vector arrowX = eX * size;
357 vector arrowY = eY * (size+borderDiag);
358 vector borderX = eX * (size+borderDiag);
359 vector borderY = eY * (size+borderDiag+border);
361 R_BeginPolygon("", DRAWFLAG_NORMAL);
362 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
363 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
364 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
365 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
366 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
369 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
370 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
371 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
372 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
375 return o + rotate(eY * (borderDiag+size+margin), ang);
378 // returns location of sprite healthbar
379 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
383 float aspect, sa, ca;
385 sw = stringwidth(s, false, fontsize);
392 // how do corners work?
393 aspect = vid_conwidth / vid_conheight;
395 ca = cos(ang) * aspect;
396 if (fabs(sa) > fabs(ca))
399 algny = 0.5 - 0.5 * ca / fabs(sa);
403 algnx = 0.5 - 0.5 * sa / fabs(ca);
411 // we want to be onscreen
416 if (o.x > vid_conwidth - w)
417 o.x = vid_conwidth - w;
418 if (o.y > vid_conheight - h)
419 o.x = vid_conheight - h;
421 o.x += 0.5 * (w - sw);
423 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
431 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
433 vector yvec = '0.299 0.587 0.114';
434 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
437 vector fixrgbexcess(vector rgb)
440 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
442 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
443 if (rgb.z > 1) rgb.z = 1;
444 } else if (rgb.z > 1) {
445 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
446 if (rgb.y > 1) rgb.y = 1;
448 } else if (rgb.y > 1) {
449 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
451 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
452 if (rgb.z > 1) rgb.z = 1;
453 } else if (rgb.z > 1) {
454 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
455 if (rgb.x > 1) rgb.x = 1;
457 } else if (rgb.z > 1) {
458 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
460 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
461 if (rgb.y > 1) rgb.y = 1;
462 } else if (rgb.y > 1) {
463 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
464 if (rgb.x > 1) rgb.x = 1;
470 void Draw_WaypointSprite()
473 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
477 if (self.hideflags & 2)
478 return; // radar only
480 if (autocvar_cl_hidewaypoints >= 2)
483 if (self.hideflags & 1)
484 if (autocvar_cl_hidewaypoints)
485 return; // fixed waypoint
487 InterpolateOrigin_Do();
489 float t = GetPlayerColor(player_localnum) + 1;
491 string spriteimage = "";
496 case SPRITERULE_SPECTATOR:
498 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
499 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
502 spriteimage = self.netname;
504 case SPRITERULE_DEFAULT:
508 spriteimage = self.netname;
513 spriteimage = self.netname;
515 case SPRITERULE_TEAMPLAY:
516 if (t == NUM_SPECTATOR + 1)
517 spriteimage = self.netname3;
518 else if (self.team == t)
519 spriteimage = self.netname2;
521 spriteimage = self.netname;
524 error("Invalid waypointsprite rule!");
528 if (spriteimage == "")
531 ++waypointsprite_newcount;
534 dist = vlen(self.origin - view_origin);
537 a = self.alpha * autocvar_hud_panel_fg_alpha;
539 if (self.maxdistance > waypointsprite_normdistance)
540 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
541 else if (self.maxdistance > 0)
542 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
544 vector rgb = spritelookupcolor(spriteimage, self.teamradar_color);
547 self.teamradar_color = '1 0 1';
548 printf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
551 if (time - floor(time) > 0.5)
553 if (self.helpme && time < self.helpme)
554 a *= SPRITE_HELPME_BLINK;
555 else if (!self.lifetime) // fading out waypoints don't blink
556 a *= spritelookupblinkvalue(spriteimage);
568 rgb = fixrgbexcess(rgb);
573 o = project_3d_to_2d(self.origin);
575 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
576 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
577 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
578 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
580 // scale it to be just in view
584 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
585 ang = atan2(-d.x, -d.y);
589 f1 = d.x / vid_conwidth;
590 f2 = d.y / vid_conheight;
592 if (max(f1, -f1) > max(f2, -f2)) {
595 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
598 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
603 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
606 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
610 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
618 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
619 ang = atan2(-d.x, -d.y);
624 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
625 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
626 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
627 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
629 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
631 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
633 t = waypointsprite_scale * vidscale;
634 a *= waypointsprite_alpha;
637 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
638 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
640 if (edgedistance_min < waypointsprite_edgefadedistance) {
641 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
642 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
644 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
645 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
646 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
649 if (self.build_finished)
651 if (time < self.build_finished + 0.25)
653 if (time < self.build_started)
654 self.health = self.build_starthealth;
655 else if (time < self.build_finished)
656 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
664 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
667 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
670 txt = spritelookuptext(spriteimage);
671 if (self.helpme && time < self.helpme)
672 txt = sprintf(_("%s needing help!"), txt);
673 if (autocvar_g_waypointsprite_uppercase)
674 txt = strtoupper(txt);
676 draw_beginBoldFont();
677 if (self.health >= 0)
679 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
682 if (self.build_finished)
687 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
689 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
696 SPRITE_HEALTHBAR_WIDTH * t,
697 SPRITE_HEALTHBAR_HEIGHT * t,
699 SPRITE_HEALTHBAR_BORDER * t,
702 a * SPRITE_HEALTHBAR_BORDERALPHA,
704 a * SPRITE_HEALTHBAR_HEALTHALPHA,
710 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
715 void WaypointSprite_Load_Frames(string ext)
717 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
719 int ext_len = strlen(ext);
720 int n = search_getsize(dh);
721 for (int i = 0; i < n; ++i)
723 string s = search_getfilename(dh, i);
724 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
726 int o = strstrofs(s, "_frame", 0);
727 string sname = strcat("/spriteframes/", substring(s, 0, o));
728 string sframes = substring(s, o + 6, strlen(s) - o - 6);
729 int f = stof(sframes) + 1;
730 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
735 void WaypointSprite_Load();
736 STATIC_INIT(WaypointSprite_Load) {
737 WaypointSprite_Load();
738 WaypointSprite_Load_Frames(".tga");
739 WaypointSprite_Load_Frames(".jpg");
741 void WaypointSprite_Load()
743 waypointsprite_fadedistance = vlen(mi_scale);
744 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
745 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
746 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
747 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
748 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
749 waypointsprite_scale = autocvar_g_waypointsprite_scale;
750 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
751 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
752 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
753 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
754 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
755 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
756 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
757 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
758 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
759 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
760 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
761 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
762 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
763 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
764 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
766 waypointsprite_count = waypointsprite_newcount;
767 waypointsprite_newcount = 0;
772 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
774 string m1 = _m1.netname;
775 string m2 = _m2.netname;
776 string m3 = _m3.netname;
794 void WaypointSprite_UpdateHealth(entity e, float f)
796 f = bound(0, f, e.max_health);
797 if (f != e.health || e.pain_finished)
805 void WaypointSprite_UpdateMaxHealth(entity e, float f)
807 if (f != e.max_health || e.pain_finished)
815 void WaypointSprite_UpdateBuildFinished(entity e, float f)
817 if (f != e.pain_finished || e.max_health)
825 void WaypointSprite_UpdateOrigin(entity e, vector o)
834 void WaypointSprite_UpdateRule(entity e, float t, float r)
836 // no check, as this is never called without doing an actual change (usually only once)
842 void WaypointSprite_UpdateTeamRadar(entity e, float icon, vector col)
844 // no check, as this is never called without doing an actual change (usually only once)
845 e.cnt = (icon & 0x7F) | (e.cnt & 0x80);
850 void WaypointSprite_Ping(entity e)
853 if (time < e.waypointsprite_pingtime) return;
854 e.waypointsprite_pingtime = time + 0.3;
855 // ALWAYS sends (this causes a radar circle), thus no check
860 void WaypointSprite_HelpMePing(entity e)
862 WaypointSprite_Ping(e);
863 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
867 void WaypointSprite_FadeOutIn(entity e, float t)
872 e.teleport_time = time + t;
874 else if (t < (e.teleport_time - time))
876 // accelerate the waypoint's dying
878 // (e.teleport_time - time) / wp.fade_time stays
879 // e.teleport_time = time + fadetime
880 float current_fadetime;
881 current_fadetime = e.teleport_time - time;
882 e.teleport_time = time + t;
883 e.fade_time = e.fade_time * t / current_fadetime;
889 void WaypointSprite_Init()
891 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
892 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
893 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
896 void WaypointSprite_InitClient(entity e)
900 void WaypointSprite_Kill(entity wp)
903 if (wp.owner) wp.owner.(wp.owned_by_field) = world;
907 void WaypointSprite_Disown(entity wp, float fadetime)
910 if (wp.classname != "sprite_waypoint")
912 backtrace("Trying to disown a non-waypointsprite");
917 if (wp.exteriormodeltoclient == wp.owner)
918 wp.exteriormodeltoclient = world;
919 wp.owner.(wp.owned_by_field) = world;
922 WaypointSprite_FadeOutIn(wp, fadetime);
926 void WaypointSprite_Think()
928 bool doremove = false;
930 if (self.fade_time && time >= self.teleport_time)
935 if (self.exteriormodeltoclient)
936 WaypointSprite_UpdateOrigin(self, self.exteriormodeltoclient.origin + self.view_ofs);
939 WaypointSprite_Kill(self);
941 self.nextthink = time; // WHY?!?
944 float WaypointSprite_visible_for_player(entity e)
946 // personal waypoints
947 if (self.enemy && self.enemy != e)
951 if (self.rule == SPRITERULE_SPECTATOR)
953 if (!autocvar_sv_itemstime)
955 if (!warmup_stage && IS_PLAYER(e))
958 else if (self.team && self.rule == SPRITERULE_DEFAULT)
960 if (self.team != e.team)
969 entity WaypointSprite_getviewentity(entity e)
971 if (IS_SPEC(e)) e = e.enemy;
972 /* TODO idea (check this breaks nothing)
973 else if (e.classname == "observer")
979 float WaypointSprite_isteammate(entity e, entity e2)
982 return e2.team == e.team;
986 float WaypointSprite_Customize()
988 // this is not in SendEntity because it shall run every frame, not just every update
990 // make spectators see what the player would see
991 entity e = WaypointSprite_getviewentity(other);
993 if (MUTATOR_CALLHOOK(CustomizeWaypoint, self, other))
996 return self.waypointsprite_visible_for_player(e);
999 float WaypointSprite_SendEntity(entity to, float sendflags);
1001 void WaypointSprite_Reset()
1003 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1005 if (self.fade_time) // there was there before: || g_keyhunt, do we really need this?
1006 WaypointSprite_Kill(self);
1009 entity WaypointSprite_Spawn(
1010 entity spr, // sprite
1011 float _lifetime, float maxdistance, // lifetime, max distance
1012 entity ref, vector ofs, // position
1013 entity showto, float t, // show to whom? Use a flag to indicate a team
1014 entity own, .entity ownfield, // remove when own gets killed
1015 float hideable, // true when it should be controlled by cl_hidewaypoints
1016 float icon // initial icon
1019 entity wp = new(sprite_waypoint);
1020 wp.teleport_time = time + _lifetime;
1021 wp.fade_time = _lifetime;
1022 wp.exteriormodeltoclient = ref;
1026 setorigin(wp, ref.origin + ofs);
1033 wp.currentammo = hideable;
1037 remove(own.(ownfield));
1038 own.(ownfield) = wp;
1039 wp.owned_by_field = ownfield;
1041 wp.fade_rate = maxdistance;
1042 wp.think = WaypointSprite_Think;
1043 wp.nextthink = time;
1044 wp.model1 = spr.netname;
1045 wp.customizeentityforclient = WaypointSprite_Customize;
1046 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1047 wp.reset2 = WaypointSprite_Reset;
1049 wp.colormod = spr.m_color;
1050 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1054 entity WaypointSprite_SpawnFixed(
1059 float icon // initial icon
1062 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, own, ownfield, true, icon);
1065 entity WaypointSprite_DeployFixed(
1067 float limited_range,
1069 float icon // initial icon
1079 maxdistance = waypointsprite_limitedrange;
1082 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, world, ofs, world, t, self, waypointsprite_deployed_fixed, false, icon);
1085 entity WaypointSprite_DeployPersonal(
1088 float icon // initial icon
1091 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, self, waypointsprite_deployed_personal, false, icon);
1094 entity WaypointSprite_Attach(
1096 float limited_range,
1097 float icon // initial icon
1101 if (self.waypointsprite_attachedforcarrier)
1102 return world; // can't attach to FC
1109 maxdistance = waypointsprite_limitedrange;
1112 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, self, '0 0 64', world, t, self, waypointsprite_attached, false, icon);
1115 entity WaypointSprite_AttachCarrier(
1118 float icon // initial icon and color
1121 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1122 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', world, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1125 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
1126 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1131 void WaypointSprite_DetachCarrier(entity carrier)
1133 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1136 void WaypointSprite_ClearPersonal()
1138 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1141 void WaypointSprite_ClearOwned()
1143 WaypointSprite_Kill(self.waypointsprite_deployed_fixed);
1144 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1145 WaypointSprite_Kill(self.waypointsprite_attached);
1148 void WaypointSprite_PlayerDead()
1150 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1151 WaypointSprite_DetachCarrier(self);
1154 void WaypointSprite_PlayerGone()
1156 WaypointSprite_Disown(self.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1157 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1158 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1159 WaypointSprite_DetachCarrier(self);