--- /dev/null
+ // Mod icons panel (#10)
+
+ bool mod_active; // is there any active mod icon?
+
+ void DrawCAItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i)
+ {
+ int stat = -1;
+ string pic = "";
+ vector color = '0 0 0';
+ switch(i)
+ {
+ case 0:
+ stat = STAT(REDALIVE);
+ pic = "player_red.tga";
+ color = '1 0 0';
+ break;
+ case 1:
+ stat = STAT(BLUEALIVE);
+ pic = "player_blue.tga";
+ color = '0 0 1';
+ break;
+ case 2:
+ stat = STAT(YELLOWALIVE);
+ pic = "player_yellow.tga";
+ color = '1 1 0';
+ break;
+ default:
+ case 3:
+ stat = STAT(PINKALIVE);
+ pic = "player_pink.tga";
+ color = '1 0 1';
+ break;
+ }
+
+ if(mySize.x/mySize.y > aspect_ratio)
+ {
+ i = aspect_ratio * mySize.y;
+ myPos.x = myPos.x + (mySize.x - i) / 2;
+ mySize.x = i;
+ }
+ else
+ {
+ i = 1/aspect_ratio * mySize.x;
+ myPos.y = myPos.y + (mySize.y - i) / 2;
+ mySize.y = i;
+ }
+
+ if(layout)
+ {
+ drawpic_aspect_skin(myPos, pic, eX * 0.7 * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring_aspect(myPos + eX * 0.7 * mySize.x, ftos(stat), eX * 0.3 * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+ else
+ drawstring_aspect(myPos, ftos(stat), mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+
+ // Clan Arena and Freeze Tag HUD modicons
+ void HUD_Mod_CA(vector myPos, vector mySize)
+ {
+ mod_active = 1; // required in each mod function that always shows something
+
+ int layout;
+ if(gametype == MAPINFO_TYPE_CA)
+ layout = autocvar_hud_panel_modicons_ca_layout;
+ else //if(gametype == MAPINFO_TYPE_FREEZETAG)
+ layout = autocvar_hud_panel_modicons_freezetag_layout;
+ int rows, columns;
+ float aspect_ratio;
+ aspect_ratio = (layout) ? 2 : 1;
+ rows = HUD_GetRowCount(team_count, mySize, aspect_ratio);
+ columns = ceil(team_count/rows);
+
+ int i;
+ float row = 0, column = 0;
+ vector pos, itemSize;
+ itemSize = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
+ for(i=0; i<team_count; ++i)
+ {
+ pos = myPos + eX * column * itemSize.x + eY * row * itemSize.y;
+
+ DrawCAItem(pos, itemSize, aspect_ratio, layout, i);
+
+ ++row;
+ if(row >= rows)
+ {
+ row = 0;
+ ++column;
+ }
+ }
+ }
+
+ // CTF HUD modicon section
+ int redflag_prevframe, blueflag_prevframe, yellowflag_prevframe, pinkflag_prevframe, neutralflag_prevframe; // status during previous frame
+ int redflag_prevstatus, blueflag_prevstatus, yellowflag_prevstatus, pinkflag_prevstatus, neutralflag_prevstatus; // last remembered status
+ float redflag_statuschange_time, blueflag_statuschange_time, yellowflag_statuschange_time, pinkflag_statuschange_time, neutralflag_statuschange_time; // time when the status changed
+
+ void HUD_Mod_CTF_Reset()
+ {
+ redflag_prevstatus = blueflag_prevstatus = yellowflag_prevstatus = pinkflag_prevstatus = neutralflag_prevstatus = 0;
+ redflag_prevframe = blueflag_prevframe = yellowflag_prevframe = pinkflag_prevframe = neutralflag_prevframe = 0;
+ redflag_statuschange_time = blueflag_statuschange_time = yellowflag_statuschange_time = pinkflag_statuschange_time = neutralflag_statuschange_time = 0;
+ }
+
+ void HUD_Mod_CTF(vector pos, vector mySize)
+ {
+ vector redflag_pos, blueflag_pos, yellowflag_pos, pinkflag_pos, neutralflag_pos;
+ vector flag_size;
+ float f; // every function should have that
+
+ int redflag, blueflag, yellowflag, pinkflag, neutralflag; // current status
+ float redflag_statuschange_elapsedtime, blueflag_statuschange_elapsedtime, yellowflag_statuschange_elapsedtime, pinkflag_statuschange_elapsedtime, neutralflag_statuschange_elapsedtime; // time since the status changed
+ bool ctf_oneflag; // one-flag CTF mode enabled/disabled
+ int stat_items = STAT(CTF_FLAGSTATUS);
+ float fs, fs2, fs3, size1, size2;
+ vector e1, e2;
+
+ redflag = (stat_items/CTF_RED_FLAG_TAKEN) & 3;
+ blueflag = (stat_items/CTF_BLUE_FLAG_TAKEN) & 3;
+ yellowflag = (stat_items/CTF_YELLOW_FLAG_TAKEN) & 3;
+ pinkflag = (stat_items/CTF_PINK_FLAG_TAKEN) & 3;
+ neutralflag = (stat_items/CTF_NEUTRAL_FLAG_TAKEN) & 3;
+
+ ctf_oneflag = (stat_items & CTF_FLAG_NEUTRAL);
+
+ mod_active = (redflag || blueflag || yellowflag || pinkflag || neutralflag);
+
+ if (autocvar__hud_configure) {
+ redflag = 1;
+ blueflag = 2;
+ if (team_count >= 3)
+ yellowflag = 2;
+ if (team_count >= 4)
+ pinkflag = 3;
+ ctf_oneflag = neutralflag = 0; // disable neutral flag in hud editor?
+ }
+
+ // when status CHANGES, set old status into prevstatus and current status into status
+ #define X(team) do { \
+ if (team##flag != team##flag_prevframe) { \
+ team##flag_statuschange_time = time; \
+ team##flag_prevstatus = team##flag_prevframe; \
+ team##flag_prevframe = team##flag; \
+ } \
+ team##flag_statuschange_elapsedtime = time - team##flag_statuschange_time; \
+ } while (0)
+ X(red);
+ X(blue);
+ X(yellow);
+ X(pink);
+ X(neutral);
+ #undef X
+
+ const float BLINK_FACTOR = 0.15;
+ const float BLINK_BASE = 0.85;
+ // note:
+ // RMS = sqrt(BLINK_BASE^2 + 0.5 * BLINK_FACTOR^2)
+ // thus
+ // BLINK_BASE = sqrt(RMS^2 - 0.5 * BLINK_FACTOR^2)
+ // ensure RMS == 1
+ const float BLINK_FREQ = 5; // circle frequency, = 2*pi*frequency in hertz
+
+ #define X(team, cond) \
+ string team##_icon, team##_icon_prevstatus; \
+ int team##_alpha, team##_alpha_prevstatus; \
+ team##_alpha = team##_alpha_prevstatus = 1; \
+ do { \
+ switch (team##flag) { \
+ case 1: team##_icon = "flag_" #team "_taken"; break; \
+ case 2: team##_icon = "flag_" #team "_lost"; break; \
+ case 3: team##_icon = "flag_" #team "_carrying"; team##_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; \
+ default: \
+ if ((stat_items & CTF_SHIELDED) && (cond)) { \
+ team##_icon = "flag_" #team "_shielded"; \
+ } else { \
+ team##_icon = string_null; \
+ } \
+ break; \
+ } \
+ switch (team##flag_prevstatus) { \
+ case 1: team##_icon_prevstatus = "flag_" #team "_taken"; break; \
+ case 2: team##_icon_prevstatus = "flag_" #team "_lost"; break; \
+ case 3: team##_icon_prevstatus = "flag_" #team "_carrying"; team##_alpha_prevstatus = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; \
+ default: \
+ if (team##flag == 3) { \
+ team##_icon_prevstatus = "flag_" #team "_carrying"; /* make it more visible */\
+ } else if((stat_items & CTF_SHIELDED) && (cond)) { \
+ team##_icon_prevstatus = "flag_" #team "_shielded"; \
+ } else { \
+ team##_icon_prevstatus = string_null; \
+ } \
+ break; \
+ } \
+ } while (0)
+ X(red, myteam != NUM_TEAM_1);
+ X(blue, myteam != NUM_TEAM_2);
+ X(yellow, myteam != NUM_TEAM_3);
+ X(pink, myteam != NUM_TEAM_4);
+ X(neutral, true);
+ #undef X
+
+ if (ctf_oneflag) {
+ // hacky, but these aren't needed
+ red_icon = red_icon_prevstatus = blue_icon = blue_icon_prevstatus = yellow_icon = yellow_icon_prevstatus = pink_icon = pink_icon_prevstatus = string_null;
+ fs = fs2 = fs3 = 1;
+ } else switch (team_count) {
+ default:
+ case 2: fs = 0.5; fs2 = 0.5; fs3 = 0.5; break;
+ case 3: fs = 1; fs2 = 0.35; fs3 = 0.35; break;
+ case 4: fs = 0.75; fs2 = 0.25; fs3 = 0.5; break;
+ }
+
+ if (mySize_x > mySize_y) {
+ size1 = mySize_x;
+ size2 = mySize_y;
+ e1 = eX;
+ e2 = eY;
+ } else {
+ size1 = mySize_y;
+ size2 = mySize_x;
+ e1 = eY;
+ e2 = eX;
+ }
+
+ switch (myteam) {
+ default:
+ case NUM_TEAM_1: {
+ redflag_pos = pos;
+ blueflag_pos = pos + eX * fs2 * size1;
+ yellowflag_pos = pos - eX * fs2 * size1;
+ pinkflag_pos = pos + eX * fs3 * size1;
+ break;
+ }
+ case NUM_TEAM_2: {
+ redflag_pos = pos + eX * fs2 * size1;
+ blueflag_pos = pos;
+ yellowflag_pos = pos - eX * fs2 * size1;
+ pinkflag_pos = pos + eX * fs3 * size1;
+ break;
+ }
+ case NUM_TEAM_3: {
+ redflag_pos = pos + eX * fs3 * size1;
+ blueflag_pos = pos - eX * fs2 * size1;
+ yellowflag_pos = pos;
+ pinkflag_pos = pos + eX * fs2 * size1;
+ break;
+ }
+ case NUM_TEAM_4: {
+ redflag_pos = pos - eX * fs2 * size1;
+ blueflag_pos = pos + eX * fs3 * size1;
+ yellowflag_pos = pos + eX * fs2 * size1;
+ pinkflag_pos = pos;
+ break;
+ }
+ }
+ neutralflag_pos = pos;
+ flag_size = e1 * fs * size1 + e2 * size2;
+
+ #define X(team) do { \
+ f = bound(0, team##flag_statuschange_elapsedtime * 2, 1); \
+ if (team##_icon_prevstatus && f < 1) \
+ drawpic_aspect_skin_expanding(team##flag_pos, team##_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * team##_alpha_prevstatus, DRAWFLAG_NORMAL, f); \
+ if (team##_icon) \
+ drawpic_aspect_skin(team##flag_pos, team##_icon, flag_size, '1 1 1', panel_fg_alpha * team##_alpha * f, DRAWFLAG_NORMAL); \
+ } while (0)
+ X(red);
+ X(blue);
+ X(yellow);
+ X(pink);
+ X(neutral);
+ #undef X
+ }
+
+ // Keyhunt HUD modicon section
+ vector KH_SLOTS[4];
+
+ void HUD_Mod_KH(vector pos, vector mySize)
+ {
+ mod_active = 1; // keyhunt should never hide the mod icons panel
+
+ // Read current state
+
+ int state = STAT(KH_KEYS);
+ int i, key_state;
+ int all_keys, team1_keys, team2_keys, team3_keys, team4_keys, dropped_keys, carrying_keys;
+ all_keys = team1_keys = team2_keys = team3_keys = team4_keys = dropped_keys = carrying_keys = 0;
+
+ for(i = 0; i < 4; ++i)
+ {
+ key_state = (bitshift(state, i * -5) & 31) - 1;
+
+ if(key_state == -1)
+ continue;
+
+ if(key_state == 30)
+ {
+ ++carrying_keys;
+ key_state = myteam;
+ }
+
+ switch(key_state)
+ {
+ case NUM_TEAM_1: ++team1_keys; break;
+ case NUM_TEAM_2: ++team2_keys; break;
+ case NUM_TEAM_3: ++team3_keys; break;
+ case NUM_TEAM_4: ++team4_keys; break;
+ case 29: ++dropped_keys; break;
+ }
+
+ ++all_keys;
+ }
+
+ // Calculate slot measurements
+
+ vector slot_size;
+
+ if(all_keys == 4 && mySize.x * 0.5 < mySize.y && mySize.y * 0.5 < mySize.x)
+ {
+ // Quadratic arrangement
+ slot_size = eX * mySize.x * 0.5 + eY * mySize.y * 0.5;
+ KH_SLOTS[0] = pos;
+ KH_SLOTS[1] = pos + eX * slot_size.x;
+ KH_SLOTS[2] = pos + eY * slot_size.y;
+ KH_SLOTS[3] = pos + eX * slot_size.x + eY * slot_size.y;
+ }
+ else
+ {
+ if(mySize.x > mySize.y)
+ {
+ // Horizontal arrangement
+ slot_size = eX * mySize.x / all_keys + eY * mySize.y;
+ for(i = 0; i < all_keys; ++i)
+ KH_SLOTS[i] = pos + eX * slot_size.x * i;
+ }
+ else
+ {
+ // Vertical arrangement
+ slot_size = eX * mySize.x + eY * mySize.y / all_keys;
+ for(i = 0; i < all_keys; ++i)
+ KH_SLOTS[i] = pos + eY * slot_size.y * i;
+ }
+ }
+
+ // Make icons blink in case of RUN HERE
+
+ float blink = 0.6 + sin(2*M_PI*time) / 2.5; // Oscillate between 0.2 and 1
+ float alpha;
+ alpha = 1;
+
+ if(carrying_keys)
+ switch(myteam)
+ {
+ case NUM_TEAM_1: if(team1_keys == all_keys) alpha = blink; break;
+ case NUM_TEAM_2: if(team2_keys == all_keys) alpha = blink; break;
+ case NUM_TEAM_3: if(team3_keys == all_keys) alpha = blink; break;
+ case NUM_TEAM_4: if(team4_keys == all_keys) alpha = blink; break;
+ }
+
+ // Draw icons
+
+ i = 0;
+
+ while(team1_keys--)
+ if(myteam == NUM_TEAM_1 && carrying_keys)
+ {
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_red_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+ --carrying_keys;
+ }
+ else
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_red_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+
+ while(team2_keys--)
+ if(myteam == NUM_TEAM_2 && carrying_keys)
+ {
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_blue_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+ --carrying_keys;
+ }
+ else
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_blue_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+
+ while(team3_keys--)
+ if(myteam == NUM_TEAM_3 && carrying_keys)
+ {
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_yellow_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+ --carrying_keys;
+ }
+ else
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_yellow_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+
+ while(team4_keys--)
+ if(myteam == NUM_TEAM_4 && carrying_keys)
+ {
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_pink_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+ --carrying_keys;
+ }
+ else
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_pink_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+
+ while(dropped_keys--)
+ drawpic_aspect_skin(KH_SLOTS[i++], "kh_dropped", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
+ }
+
++void HUD_Mod_Infection(vector pos, vector mySize)
++{
++ mod_active = 1; // Infection should always show the mod HUD
++ int f = stof(getplayerkeyvalue(player_localentnum - 1, "colors"));
++ vector color = colormapPaletteColor(floor(f / 16), 0);
++ drawpic_aspect_skin(pos, "player_neutral", mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL);
++}
++
+ // Keepaway HUD mod icon
+ int kaball_prevstatus; // last remembered status
+ float kaball_statuschange_time; // time when the status changed
+
+ // we don't need to reset for keepaway since it immediately
+ // autocorrects prevstatus as to if the player has the ball or not
+
+ void HUD_Mod_Keepaway(vector pos, vector mySize)
+ {
+ mod_active = 1; // keepaway should always show the mod HUD
+
+ float BLINK_FACTOR = 0.15;
+ float BLINK_BASE = 0.85;
+ float BLINK_FREQ = 5;
+ float kaball_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
+
+ int stat_items = getstati(STAT_ITEMS, 0, 24);
+ int kaball = (stat_items/IT_KEY1) & 1;
+
+ if(kaball != kaball_prevstatus)
+ {
+ kaball_statuschange_time = time;
+ kaball_prevstatus = kaball;
+ }
+
+ vector kaball_pos, kaball_size;
+
+ if(mySize.x > mySize.y) {
+ kaball_pos = pos + eX * 0.25 * mySize.x;
+ kaball_size = eX * 0.5 * mySize.x + eY * mySize.y;
+ } else {
+ kaball_pos = pos + eY * 0.25 * mySize.y;
+ kaball_size = eY * 0.5 * mySize.y + eX * mySize.x;
+ }
+
+ float kaball_statuschange_elapsedtime = time - kaball_statuschange_time;
+ float f = bound(0, kaball_statuschange_elapsedtime*2, 1);
+
+ if(kaball_prevstatus && f < 1)
+ drawpic_aspect_skin_expanding(kaball_pos, "keepawayball_carrying", kaball_size, '1 1 1', panel_fg_alpha * kaball_alpha, DRAWFLAG_NORMAL, f);
+
+ if(kaball)
+ drawpic_aspect_skin(pos, "keepawayball_carrying", eX * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha * kaball_alpha * f, DRAWFLAG_NORMAL);
+ }
+
+
+ // Nexball HUD mod icon
+ void HUD_Mod_NexBall(vector pos, vector mySize)
+ {
+ float nb_pb_starttime, dt, p;
+ int stat_items;
+
+ stat_items = getstati(STAT_ITEMS, 0, 24);
+ nb_pb_starttime = STAT(NB_METERSTART);
+
+ if (stat_items & IT_KEY1)
+ mod_active = 1;
+ else
+ mod_active = 0;
+
+ //Manage the progress bar if any
+ if (nb_pb_starttime > 0)
+ {
+ dt = (time - nb_pb_starttime) % nb_pb_period;
+ // one period of positive triangle
+ p = 2 * dt / nb_pb_period;
+ if (p > 1)
+ p = 2 - p;
+
+ HUD_Panel_DrawProgressBar(pos, mySize, "progressbar", p, (mySize.x <= mySize.y), 0, autocvar_hud_progressbar_nexball_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+
+ if (stat_items & IT_KEY1)
+ drawpic_aspect_skin(pos, "nexball_carrying", eX * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+
+ // Race/CTS HUD mod icons
+ float crecordtime_prev; // last remembered crecordtime
+ float crecordtime_change_time; // time when crecordtime last changed
+ float srecordtime_prev; // last remembered srecordtime
+ float srecordtime_change_time; // time when srecordtime last changed
+
+ float race_status_time;
+ int race_status_prev;
+ string race_status_name_prev;
+ void HUD_Mod_Race(vector pos, vector mySize)
+ {
+ mod_active = 1; // race should never hide the mod icons panel
+ entity me;
+ me = playerslots[player_localnum];
+ float t, score;
+ float f; // yet another function has this
+ score = me.(scores[ps_primary]);
+
+ if(!(scores_flags[ps_primary] & SFL_TIME) || teamplay) // race/cts record display on HUD
+ return; // no records in the actual race
+
+ // clientside personal record
+ string rr;
+ if(gametype == MAPINFO_TYPE_CTS)
+ rr = CTS_RECORD;
+ else
+ rr = RACE_RECORD;
+ t = stof(db_get(ClientProgsDB, strcat(shortmapname, rr, "time")));
+
+ if(score && (score < t || !t)) {
+ db_put(ClientProgsDB, strcat(shortmapname, rr, "time"), ftos(score));
+ if(autocvar_cl_autodemo_delete_keeprecords)
+ {
+ f = autocvar_cl_autodemo_delete;
+ f &= ~1;
+ cvar_set("cl_autodemo_delete", ftos(f)); // don't delete demo with new record!
+ }
+ }
+
+ if(t != crecordtime_prev) {
+ crecordtime_prev = t;
+ crecordtime_change_time = time;
+ }
+
+ vector textPos, medalPos;
+ float squareSize;
+ if(mySize.x > mySize.y) {
+ // text on left side
+ squareSize = min(mySize.y, mySize.x/2);
+ textPos = pos + eX * 0.5 * max(0, mySize.x/2 - squareSize) + eY * 0.5 * (mySize.y - squareSize);
+ medalPos = pos + eX * 0.5 * max(0, mySize.x/2 - squareSize) + eX * 0.5 * mySize.x + eY * 0.5 * (mySize.y - squareSize);
+ } else {
+ // text on top
+ squareSize = min(mySize.x, mySize.y/2);
+ textPos = pos + eY * 0.5 * max(0, mySize.y/2 - squareSize) + eX * 0.5 * (mySize.x - squareSize);
+ medalPos = pos + eY * 0.5 * max(0, mySize.y/2 - squareSize) + eY * 0.5 * mySize.y + eX * 0.5 * (mySize.x - squareSize);
+ }
+
+ f = time - crecordtime_change_time;
+
+ if (f > 1) {
+ drawstring_aspect(textPos, _("Personal best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring_aspect(textPos + eY * 0.25 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ } else {
+ drawstring_aspect(textPos, _("Personal best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring_aspect(textPos + eY * 0.25 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring_aspect_expanding(pos, _("Personal best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
+ drawstring_aspect_expanding(pos + eY * 0.25 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
+ }
+
+ // server record
+ t = race_server_record;
+ if(t != srecordtime_prev) {
+ srecordtime_prev = t;
+ srecordtime_change_time = time;
+ }
+ f = time - srecordtime_change_time;
+
+ if (f > 1) {
+ drawstring_aspect(textPos + eY * 0.5 * squareSize, _("Server best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring_aspect(textPos + eY * 0.75 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ } else {
+ drawstring_aspect(textPos + eY * 0.5 * squareSize, _("Server best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring_aspect(textPos + eY * 0.75 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawstring_aspect_expanding(textPos + eY * 0.5 * squareSize, _("Server best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
+ drawstring_aspect_expanding(textPos + eY * 0.75 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
+ }
+
+ if (race_status != race_status_prev || race_status_name != race_status_name_prev) {
+ race_status_time = time + 5;
+ race_status_prev = race_status;
+ if (race_status_name_prev)
+ strunzone(race_status_name_prev);
+ race_status_name_prev = strzone(race_status_name);
+ }
+
+ // race "awards"
+ float a;
+ a = bound(0, race_status_time - time, 1);
+
+ string s;
+ s = textShortenToWidth(race_status_name, squareSize, '1 1 0' * 0.1 * squareSize, stringwidth_colors);
+
+ float rank;
+ if(race_status > 0)
+ rank = race_CheckName(race_status_name);
+ else
+ rank = 0;
+ string rankname;
+ rankname = count_ordinal(rank);
+
+ vector namepos;
+ namepos = medalPos + '0 0.8 0' * squareSize;
+ vector rankpos;
+ rankpos = medalPos + '0 0.15 0' * squareSize;
+
+ if(race_status == 0)
+ drawpic_aspect_skin(medalPos, "race_newfail", '1 1 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ else if(race_status == 1) {
+ drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newtime", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ } else if(race_status == 2) {
+ if(race_status_name == entcs_GetName(player_localnum) || !race_myrank || race_myrank < rank)
+ drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newrankgreen", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ else
+ drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newrankyellow", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ } else if(race_status == 3) {
+ drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newrecordserver", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
+ }
+
+ if (race_status_time - time <= 0) {
+ race_status_prev = -1;
+ race_status = -1;
+ if(race_status_name)
+ strunzone(race_status_name);
+ race_status_name = string_null;
+ if(race_status_name_prev)
+ strunzone(race_status_name_prev);
+ race_status_name_prev = string_null;
+ }
+ }
+
+ void DrawDomItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i)
+ {
+ float stat = -1;
+ string pic = "";
+ vector color = '0 0 0';
+ switch(i)
+ {
+ case 0:
+ stat = STAT(DOM_PPS_RED);
+ pic = "dom_icon_red";
+ color = '1 0 0';
+ break;
+ case 1:
+ stat = STAT(DOM_PPS_BLUE);
+ pic = "dom_icon_blue";
+ color = '0 0 1';
+ break;
+ case 2:
+ stat = STAT(DOM_PPS_YELLOW);
+ pic = "dom_icon_yellow";
+ color = '1 1 0';
+ break;
+ default:
+ case 3:
+ stat = STAT(DOM_PPS_PINK);
+ pic = "dom_icon_pink";
+ color = '1 0 1';
+ break;
+ }
+ float pps_ratio = stat / STAT(DOM_TOTAL_PPS);
+
+ if(mySize.x/mySize.y > aspect_ratio)
+ {
+ i = aspect_ratio * mySize.y;
+ myPos.x = myPos.x + (mySize.x - i) / 2;
+ mySize.x = i;
+ }
+ else
+ {
+ i = 1/aspect_ratio * mySize.x;
+ myPos.y = myPos.y + (mySize.y - i) / 2;
+ mySize.y = i;
+ }
+
+ if (layout) // show text too
+ {
+ //draw the text
+ color *= 0.5 + pps_ratio * (1 - 0.5); // half saturated color at min, full saturated at max
+ if (layout == 2) // average pps
+ drawstring_aspect(myPos + eX * mySize.y, ftos_decimals(stat, 2), eX * (2/3) * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
+ else // percentage of average pps
+ drawstring_aspect(myPos + eX * mySize.y, strcat( ftos(floor(pps_ratio*100 + 0.5)), "%" ), eX * (2/3) * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+
+ //draw the icon
+ drawpic_aspect_skin(myPos, pic, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ if (stat > 0)
+ {
+ drawsetcliparea(myPos.x, myPos.y + mySize.y * (1 - pps_ratio), mySize.y, mySize.y * pps_ratio);
+ drawpic_aspect_skin(myPos, strcat(pic, "-highlighted"), '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ drawresetcliparea();
+ }
+ }
+
+ void HUD_Mod_Dom(vector myPos, vector mySize)
+ {
+ mod_active = 1; // required in each mod function that always shows something
+
+ int layout = autocvar_hud_panel_modicons_dom_layout;
+ int rows, columns;
+ float aspect_ratio;
+ aspect_ratio = (layout) ? 3 : 1;
+ rows = HUD_GetRowCount(team_count, mySize, aspect_ratio);
+ columns = ceil(team_count/rows);
+
+ int i;
+ float row = 0, column = 0;
+ vector pos, itemSize;
+ itemSize = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
+ for(i=0; i<team_count; ++i)
+ {
+ pos = myPos + eX * column * itemSize.x + eY * row * itemSize.y;
+
+ DrawDomItem(pos, itemSize, aspect_ratio, layout, i);
+
+ ++row;
+ if(row >= rows)
+ {
+ row = 0;
+ ++column;
+ }
+ }
+ }
+
+ void HUD_ModIcons_SetFunc()
+ {
+ switch(gametype)
+ {
+ case MAPINFO_TYPE_KEYHUNT: HUD_ModIcons_GameType = HUD_Mod_KH; break;
+ case MAPINFO_TYPE_CTF: HUD_ModIcons_GameType = HUD_Mod_CTF; break;
+ case MAPINFO_TYPE_NEXBALL: HUD_ModIcons_GameType = HUD_Mod_NexBall; break;
+ case MAPINFO_TYPE_CTS:
+ case MAPINFO_TYPE_RACE: HUD_ModIcons_GameType = HUD_Mod_Race; break;
+ case MAPINFO_TYPE_CA:
+ case MAPINFO_TYPE_FREEZETAG: HUD_ModIcons_GameType = HUD_Mod_CA; break;
+ case MAPINFO_TYPE_DOMINATION: HUD_ModIcons_GameType = HUD_Mod_Dom; break;
+ case MAPINFO_TYPE_KEEPAWAY: HUD_ModIcons_GameType = HUD_Mod_Keepaway; break;
++ case MAPINFO_TYPE_INFECTION: HUD_ModIcons_GameType = HUD_Mod_Infection; break;
+ }
+ }
+
+ int mod_prev; // previous state of mod_active to check for a change
+ float mod_alpha;
+ float mod_change; // "time" when mod_active changed
+
+ void HUD_ModIcons()
+ {
+ if(!autocvar__hud_configure)
+ {
+ if(!autocvar_hud_panel_modicons) return;
+ if(!HUD_ModIcons_GameType) return;
+ }
+
+ HUD_Panel_UpdateCvars();
+
+ draw_beginBoldFont();
+
+ if(mod_active != mod_prev) {
+ mod_change = time;
+ mod_prev = mod_active;
+ }
+
+ if(mod_active || autocvar__hud_configure)
+ mod_alpha = bound(0, (time - mod_change) * 2, 1);
+ else
+ mod_alpha = bound(0, 1 - (time - mod_change) * 2, 1);
+
+ if(mod_alpha)
+ HUD_Panel_DrawBg(mod_alpha);
+
+ if(panel_bg_padding)
+ {
+ panel_pos += '1 1 0' * panel_bg_padding;
+ panel_size -= '2 2 0' * panel_bg_padding;
+ }
+
+ if(autocvar__hud_configure)
+ HUD_Mod_CTF(panel_pos, panel_size);
+ else
+ HUD_ModIcons_GameType(panel_pos, panel_size);
+
+ draw_endBoldFont();
+ }
#include "util.qh"
-REGISTRY(Gametypes, BITS(4))
+ CLASS(Gametype, Object)
+ ATTRIB(Gametype, m_id, int, 0)
+ /** game type ID */
+ ATTRIB(Gametype, items, int, 0)
+ /** game type name as in cvar (with g_ prefix) */
+ ATTRIB(Gametype, netname, string, string_null)
+ /** game type short name */
+ ATTRIB(Gametype, mdl, string, string_null)
+ /** human readable name */
+ ATTRIB(Gametype, message, string, string_null)
+ /** does this gametype support teamplay? */
+ ATTRIB(Gametype, team, bool, false)
+ /** game type defaults */
+ ATTRIB(Gametype, model2, string, string_null)
+ /** game type description */
+ ATTRIB(Gametype, gametype_description, string, string_null)
+
+ ATTRIB(Gametype, m_mutators, string, string_null)
+ ATTRIB(Gametype, m_parse_mapinfo, bool(string k, string v), func_null)
+
+ METHOD(Gametype, describe, string(entity this)) { return this.gametype_description; }
+
+ METHOD(Gametype, display, void(entity this, void(string name, string icon) returns)) {
+ returns(this.message, strcat("gametype_", this.mdl));
+ }
+
+ CONSTRUCTOR(Gametype, string hname, string sname, string g_name, bool gteamplay, string mutators, string defaults, string gdescription)
+ {
+ CONSTRUCT(Gametype);
+ this.netname = g_name;
+ this.mdl = sname;
+ this.message = hname;
+ this.team = gteamplay;
+ this.m_mutators = mutators;
+ this.model2 = defaults;
+ this.gametype_description = gdescription;
+ }
+ ENDCLASS(Gametype)
+
++REGISTRY(Gametypes, BITS(5))
+ #define Gametypes_from(i) _Gametypes_from(i, NULL)
+ REGISTER_REGISTRY(Gametypes)
+ REGISTRY_CHECK(Gametypes)
int MAPINFO_TYPE_ALL;
- entity MapInfo_Type_first;
- entity MapInfo_Type_last;
- .entity enemy; // internal next pointer
-
- .int items; // game type ID
- .string netname; // game type name as in cvar (with g_ prefix)
- .string mdl; // game type short name
- .string message; // human readable name
- .int team; // does this gametype support teamplay?
- .string model2; // game type defaults
- .string gametype_description; // game type description
-
- #define REGISTER_GAMETYPE(hname,sname,g_name,NAME,gteamplay,defaults,gdescription) \
- int MAPINFO_TYPE_##NAME; \
- entity MapInfo_Type##g_name; \
- void RegisterGametypes_##g_name() \
- { \
- MAPINFO_TYPE_##NAME = MAPINFO_TYPE_ALL + 1; \
- MAPINFO_TYPE_ALL |= MAPINFO_TYPE_##NAME; \
- MapInfo_Type##g_name = spawn(); \
- MapInfo_Type##g_name.items = MAPINFO_TYPE_##NAME; \
- MapInfo_Type##g_name.netname = #g_name; \
- MapInfo_Type##g_name.mdl = #sname; \
- MapInfo_Type##g_name.message = hname; \
- MapInfo_Type##g_name.team = gteamplay; \
- MapInfo_Type##g_name.model2 = defaults; \
- MapInfo_Type##g_name.gametype_description = gdescription; \
- if(!MapInfo_Type_first) \
- MapInfo_Type_first = MapInfo_Type##g_name; \
- if(MapInfo_Type_last) \
- MapInfo_Type_last.enemy = MapInfo_Type##g_name; \
- MapInfo_Type_last = MapInfo_Type##g_name; \
- } \
- ACCUMULATE_FUNCTION(RegisterGametypes, RegisterGametypes_##g_name)
+ #define REGISTER_GAMETYPE(hname, sname, g_name, NAME, gteamplay, mutators, defaults, gdescription) \
+ int MAPINFO_TYPE_##NAME; \
+ bool NAME##_mapinfo(string k, string v) { return = false; } \
+ REGISTER(Gametypes, MAPINFO_TYPE, g_name, m_id, \
+ NEW(Gametype, hname, #sname, #g_name, gteamplay, #sname " " mutators, defaults, gdescription) \
+ ) { \
+ /* same as `1 << m_id` */ \
+ MAPINFO_TYPE_##NAME = MAPINFO_TYPE_ALL + 1; MAPINFO_TYPE_ALL |= MAPINFO_TYPE_##NAME; \
+ this.items = MAPINFO_TYPE_##NAME; \
+ this.m_parse_mapinfo = NAME##_mapinfo; \
+ } \
+ [[accumulate]] bool NAME##_mapinfo(string k, string v)
#define IS_GAMETYPE(NAME) \
- (MapInfo_LoadedGametype == MAPINFO_TYPE_##NAME)
-
- REGISTER_GAMETYPE(_("Deathmatch"),dm,g_dm,DEATHMATCH,false,"timelimit=20 pointlimit=30 leadlimit=0",_("Kill all enemies"));
- #define g_dm IS_GAMETYPE(DEATHMATCH)
-
- REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,false,"timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
- #define g_lms IS_GAMETYPE(LMS)
-
- REGISTER_GAMETYPE(_("Race"),rc,g_race,RACE,false,"timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line"));
+ (MapInfo_LoadedGametype == MAPINFO_TYPE_##NAME)
+
+ REGISTER_GAMETYPE(_("Deathmatch"),dm,g_dm,DEATHMATCH,false,"","timelimit=20 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
+
+ REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,false,"","timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left"));
+
+ REGISTER_GAMETYPE(_("Race"),rc,g_race,RACE,false,"","timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line"))
+ {
+ if (!k) {
+ cvar_set("g_race_qualifying_timelimit", cvar_defstring("g_race_qualifying_timelimit"));
+ return true;
+ }
+ switch (k) {
+ case "qualifying_timelimit":
+ cvar_set("g_race_qualifying_timelimit", v);
+ return true;
+ }
+ }
#define g_race IS_GAMETYPE(RACE)
- REGISTER_GAMETYPE(_("Race CTS"),cts,g_cts,CTS,false,"timelimit=20 skill=-1",_("Race for fastest time"));
+ REGISTER_GAMETYPE(_("Race CTS"),cts,g_cts,CTS,false,"cloaked","timelimit=20",_("Race for fastest time."));
#define g_cts IS_GAMETYPE(CTS)
- REGISTER_GAMETYPE(_("Team Deathmatch"),tdm,g_tdm,TEAM_DEATHMATCH,true,"timelimit=20 pointlimit=50 teams=2 leadlimit=0",_("Kill all enemy teammates"));
+ REGISTER_GAMETYPE(_("Team Deathmatch"),tdm,g_tdm,TEAM_DEATHMATCH,true,"","timelimit=20 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"))
+ {
+ if (!k) {
+ cvar_set("g_tdm_teams", cvar_defstring("g_tdm_teams"));
+ return true;
+ }
+ switch (k) {
+ case "teams":
+ cvar_set("g_tdm_teams", v);
+ return true;
+ }
+ }
#define g_tdm IS_GAMETYPE(TEAM_DEATHMATCH)
- REGISTER_GAMETYPE(_("Capture the Flag"),ctf,g_ctf,CTF,true,"timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it"));
+ REGISTER_GAMETYPE(_("Capture the Flag"),ctf,g_ctf,CTF,true,"","timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it, defend your base from the other team"));
#define g_ctf IS_GAMETYPE(CTF)
- REGISTER_GAMETYPE(_("Clan Arena"),ca,g_ca,CA,true,"timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round"));
+ REGISTER_GAMETYPE(_("Clan Arena"),ca,g_ca,CA,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round"))
+ {
+ if (!k) {
+ cvar_set("g_ca_teams", cvar_defstring("g_ca_teams"));
+ return true;
+ }
+ switch (k) {
+ case "teams":
+ cvar_set("g_ca_teams", v);
+ return true;
+ }
+ }
#define g_ca IS_GAMETYPE(CA)
- REGISTER_GAMETYPE(_("Domination"),dom,g_domination,DOMINATION,true,"timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture all the control points to win"));
- #define g_domination IS_GAMETYPE(DOMINATION)
-
- REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,true,"timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round"));
- #define g_keyhunt IS_GAMETYPE(KEYHUNT)
-
- REGISTER_GAMETYPE(_("Assault"),as,g_assault,ASSAULT,true,"timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out"));
+ REGISTER_GAMETYPE(_("Domination"),dom,g_domination,DOMINATION,true,"","timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture and defend all the control points to win"))
+ {
+ if (!k) {
+ cvar_set("g_domination_default_teams", cvar_defstring("g_domination_default_teams"));
+ return true;
+ }
+ switch (k) {
+ case "teams":
+ cvar_set("g_domination_default_teams", v);
+ return true;
+ }
+ }
+
+ REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,true,"","timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round"))
+ {
+ if (!k) {
+ cvar_set("g_keyhunt_teams", cvar_defstring("g_keyhunt_teams"));
+ return true;
+ }
+ switch (k) {
+ case "teams":
+ cvar_set("g_keyhunt_teams", v);
+ return true;
+ }
+ }
+
+ REGISTER_GAMETYPE(_("Assault"),as,g_assault,ASSAULT,true,"","timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out"));
#define g_assault IS_GAMETYPE(ASSAULT)
- REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,true,"timelimit=20",_("Capture control points to reach and destroy the enemy generator"));
- #define g_onslaught IS_GAMETYPE(ONSLAUGHT)
+ REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,true,"","pointlimit=1 timelimit=20",_("Capture control points to reach and destroy the enemy generator"));
- REGISTER_GAMETYPE(_("Nexball"),nb,g_nexball,NEXBALL,true,"timelimit=20 pointlimit=5 leadlimit=0",_("XonSports"));
+ REGISTER_GAMETYPE(_("Nexball"),nb,g_nexball,NEXBALL,true,"","timelimit=20 pointlimit=5 leadlimit=0",_("Shoot and kick the ball into the enemies goal, keep your goal clean"));
#define g_nexball IS_GAMETYPE(NEXBALL)
- REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,true,"timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to teammates to revive them"));
+ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,true,"","timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to teammates to revive them, freeze the most enemies to win"))
+ {
+ if (!k) {
+ cvar_set("g_freezetag_teams", cvar_defstring("g_freezetag_teams"));
+ return true;
+ }
+ switch (k) {
+ case "teams":
+ cvar_set("g_freezetag_teams", v);
+ return true;
+ }
+ }
#define g_freezetag IS_GAMETYPE(FREEZETAG)
- REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,true,"timelimit=20 pointlimit=30",_("Hold the ball to get points for kills"));
- #define g_keepaway IS_GAMETYPE(KEEPAWAY)
+ REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,true,"","timelimit=20 pointlimit=30",_("Hold the ball to get points for kills"));
- REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,false,"pointlimit=50 teams=0",_("Survive against waves of monsters"));
- #define g_invasion IS_GAMETYPE(INVASION)
+ REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,false,"","pointlimit=50 teams=0",_("Survive against waves of monsters"))
+ {
+ switch (k) {
+ case "teams":
+ cvar_set("g_invasion_teams", v);
+ return true;
+ }
+ }
- REGISTER_GAMETYPE(_("Infection"),inf,g_infection,INFECTION,true,"timelimit=20",_("Survive against the infection"))
- #define g_infection IS_GAMETYPE(INFECTION)
++REGISTER_GAMETYPE(_("Infection"),inf,g_infection,INFECTION,true,"","timelimit=20",_("Survive against the infection"));
+
const int MAPINFO_FEATURE_WEAPONS = 1; // not defined for instagib-only maps
const int MAPINFO_FEATURE_VEHICLES = 2;
const int MAPINFO_FEATURE_TURRETS = 4;
--- /dev/null
--- /dev/null
++#ifdef IMPLEMENTATION
++
++float autocvar_g_infection_round_timelimit;
++float autocvar_g_infection_warmup;
++int autocvar_g_infection_teams;
++bool autocvar_g_infection_conversions;
++
++int infection_players_count;
++
++.int infectioncolor;
++.int infectioncolor_original;
++
++const int INFECTIONTEAM_NONE = -1;
++const int INFECTIONTEAM_UNDEFINED = -2;
++
++// safe team comparisons
++#define INF_SAMETEAM(a,b) (a.infectioncolor == b.infectioncolor)
++#define INF_DIFFTEAM(a,b) (a.infectioncolor != b.infectioncolor)
++
++void infection_SetColor(entity e, int _color)
++{
++ e.infectioncolor = _color;
++ setcolor(e, (_color << 4) | _color);
++}
++
++string color_owner_green, color_owner_red;
++// find whose color a player is carrying, true if it's his own, otherwise set color_owner_* to the other owner
++void infection_GetColorOwner(entity me)
++{
++ if (me.infectioncolor == me.infectioncolor_original) {
++ color_owner_green = "^2your own";
++ color_owner_red = "^1their own";
++ return;
++ }
++ entity e;
++ FOR_EACH_PLAYER(e) {
++ if (me.infectioncolor == e.infectioncolor_original) {
++ color_owner_green = sprintf("%s^2's", e.netname);
++ color_owner_red = sprintf("%s^1's", e.netname);
++ break;
++ }
++ }
++}
++
++void infection_Assign(bool late)
++{
++ if (!late) {
++ int infection_coloridx = 0;
++ entity e;
++ FOR_EACH_PLAYER(e) {
++ if (infection_coloridx < autocvar_g_infection_teams) { // Limit alphas
++ e.infectioncolor_original = infection_coloridx;
++ }
++ infection_SetColor(e, infection_coloridx++ % bound(0, autocvar_g_infection_teams, 15));
++ }
++ } else {
++ // Spawn too late, give player a random color
++ int color = 15;
++ int skip = rint(random() * (infection_players_count - 1)); // Ignore self
++ entity e;
++ FOR_EACH_PLAYER(e) {
++ if (e == self || IS_OBSERVER(e)) continue;
++ if (!skip --> 0) break;
++ }
++ dprintf("[INFECTION] copying %s's color\n", e.netname);
++ color = e.infectioncolor;
++ self.infectioncolor_original = INFECTIONTEAM_NONE; // Can't win if player didn't spawn during the round delay
++ infection_SetColor(self, color);
++ }
++}
++
++bool infection_CheckTeams()
++{
++ return infection_players_count > 1;
++}
++
++bool infection_CheckWinner()
++{
++ if (infection_players_count <= 1) return false; // There can be no winner
++
++ if (0 < round_handler_GetEndTime() && round_handler_GetEndTime() <= time) { // Round over
++ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
++ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
++ round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
++ return true;
++ }
++
++ // Check if only one color remains
++ int previnfectioncolor = -1;
++ bool we_have_a_winner = true; // until loop below proves us wrong
++ entity e;
++ FOR_EACH_PLAYER(e) {
++ // All infection colors are the same if we have a winner
++ if (previnfectioncolor != -1 && previnfectioncolor != e.infectioncolor) {
++ // In this case we still have more than one color alive
++ we_have_a_winner = false;
++ break;
++ }
++ previnfectioncolor = e.infectioncolor;
++ }
++
++ if (!we_have_a_winner) return false;
++
++ // Who is it?
++ FOR_EACH_PLAYER(e) {
++ if (e.infectioncolor == e.infectioncolor_original) {
++ UpdateFrags(e, 10); // Bonus points
++ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, e.netname);
++ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, e.netname);
++ }
++ }
++
++ round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
++ return true;
++}
++
++void infection_RoundStart()
++{
++ infection_Assign(false);
++ infection_CheckWinner();
++}
++
++MUTATOR_HOOKFUNCTION(inf, PlayerDies)
++{
++ infection_CheckWinner();
++ return true;
++}
++
++bool inf_RemovePlayer();
++MUTATOR_HOOKFUNCTION(inf, MakePlayerObserver) { return inf_RemovePlayer(); }
++MUTATOR_HOOKFUNCTION(inf, ClientDisconnect) { return inf_RemovePlayer(); }
++bool inf_RemovePlayer()
++{
++ if (!IS_PLAYER(self)) return true; // Wasn't playing
++
++ infection_players_count--;
++
++ self.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
++
++ // Grant alpha status to next of kin
++ entity e;
++ FOR_EACH_PLAYER(e) {
++ if (e.infectioncolor == self.infectioncolor_original) {
++ e.infectioncolor_original = self.infectioncolor;
++ centerprint(e, "^2You are now an alpha.\n");
++ break;
++ }
++ }
++
++ infection_CheckWinner();
++ return true;
++}
++
++MUTATOR_HOOKFUNCTION(inf, PlayerSpawn)
++{
++ if (self.infectioncolor_original != INFECTIONTEAM_UNDEFINED) return true; // Wasn't observing
++ infection_players_count++;
++
++ infection_Assign(true);
++
++ return true;
++}
++
++MUTATOR_HOOKFUNCTION(inf, GiveFragsForKill, CBC_ORDER_FIRST)
++{
++ frag_score = 0;
++ infection_GetColorOwner(frag_attacker);
++ // If this is the first time we die... (our infectioncolor remained unchanged)
++ if (autocvar_g_infection_conversions && frag_target.infectioncolor == frag_target.infectioncolor_original) {
++ entity e;
++ FOR_EACH_PLAYER(e) { // check other players...
++ if (INF_SAMETEAM(e, frag_target)) { // And see if they have our original infection color
++ // If so, remove it, our infection color has now "died out" from this round and we can not win anymore.
++ // The attacker will "summon" all of our previously fragged targets, and also us.
++ centerprint(e, sprintf("^1Your alpha ^7%s^1 was infected by ^7%s^1 with ^7%s^1 color.\n",
++ frag_target.netname, frag_attacker.netname, color_owner_red));
++ infection_SetColor(e, frag_attacker.infectioncolor);
++ frag_score++;
++ }
++ }
++ } else {
++ infection_SetColor(frag_target, frag_attacker.infectioncolor);
++ frag_score++;
++ }
++
++ string target = frag_target.netname, attacker = frag_attacker.netname;
++
++ centerprint(frag_attacker, sprintf("^2You infected ^7%s^2 with ^7%s^2 color.\n", target, color_owner_green));
++ centerprint(frag_target, sprintf("^1You were infected by ^7%s^1 with ^7%s^1 color.\n", attacker, color_owner_red));
++
++ bprint(sprintf("^7%s^1 was infected by ^7%s^1 with ^7%s^1 color.\n", frag_target.netname, attacker, color_owner_red));
++
++ return true;
++}
++
++MUTATOR_HOOKFUNCTION(inf, PlayerPreThink, CBC_ORDER_FIRST)
++{
++ if (IS_PLAYER(self)) {
++ // Prevent cheating by changing player colors
++ infection_SetColor(self, round_handler_IsRoundStarted()
++ ? self.infectioncolor : 15
++ );
++ }
++ return true;
++}
++
++MUTATOR_HOOKFUNCTION(inf, PlayerDamage_Calculate)
++{
++ if (IS_PLAYER(frag_attacker) // Allow environment damage
++ && frag_attacker != frag_target // Allow self damage
++ && INF_SAMETEAM(frag_attacker, frag_target) // Block friendly fire
++ ) {
++ frag_damage = 0;
++ frag_force = '0 0 0';
++ }
++ return false;
++}
++
++MUTATOR_HOOKFUNCTION(inf, BotShouldAttack)
++{
++ return INF_SAMETEAM(checkentity, self);
++}
++
++MUTATOR_HOOKFUNCTION(inf, ClientConnect)
++{
++ self.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
++ stuffcmd(self, "settemp cl_forceplayercolors 0\n");
++
++ return false;
++}
++
++REGISTER_MUTATOR(inf, false)
++{
++ MUTATOR_ONADD
++ {
++ if (time > 1) // game loads at time 1
++ {
++ error("This is a game type and it cannot be added at runtime.");
++ }
++ infection_players_count = 0;
++ round_handler_Spawn(infection_CheckTeams, infection_CheckWinner, infection_RoundStart);
++ round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
++ }
++
++ MUTATOR_ONROLLBACK_OR_REMOVE
++ {
++ // we actually cannot roll back inf_Initialize here
++ // BUT: we don't need to! If this gets called, adding always
++ // succeeds.
++ }
++
++ MUTATOR_ONREMOVE
++ {
++ print("This is a game type and it cannot be removed at runtime.");
++ return -1;
++ }
++
++ return 0;
++}
++
++#endif