From: TimePath Date: Wed, 2 Dec 2015 04:29:15 +0000 (+1100) Subject: Merge branch 'master' into TimePath/gametypes/infection X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=3cf0ec118cf3094b0614dc125fc1d7a07bbeaeb1;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into TimePath/gametypes/infection # Conflicts: # qcsrc/client/hud.qc # qcsrc/server/cl_player.qc # qcsrc/server/mutators/mutators.qh # qcsrc/server/mutators/mutators_include.qc # qcsrc/server/teamplay.qc --- 3cf0ec118cf3094b0614dc125fc1d7a07bbeaeb1 diff --cc qcsrc/client/hud/panel/modicons.qc index 000000000,8fab80ed6..92969ce6f mode 000000,100644..100644 --- a/qcsrc/client/hud/panel/modicons.qc +++ b/qcsrc/client/hud/panel/modicons.qc @@@ -1,0 -1,776 +1,785 @@@ + // 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= 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= 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(); + } diff --cc qcsrc/common/mapinfo.qc index ebcd8bf3c,5ca1c3803..73bf7b7c6 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@@ -518,14 -538,6 +538,13 @@@ void _MapInfo_Map_ApplyGametype(string cvar_set("fraglimit", sa); s = cdr(s); } + - if(pWantedType == MAPINFO_TYPE_INFECTION) ++ if (pWantedType == MAPINFO_TYPE_INFECTION) + { + sa = car(s); - if(sa != "") - cvar_set("fraglimit", sa); ++ if (sa != "") cvar_set("fraglimit", sa); + s = cdr(s); + } /* keepaway wuz here if(pWantedType == MAPINFO_TYPE_KEEPAWAY) @@@ -899,7 -857,7 +864,7 @@@ float MapInfo_isRedundant(string fn, st } // load info about a map by name into the MapInfo_Map_* globals --float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, int pGametypeToSet) ++float MapInfo_Get_ByName_NoFallbacks(string pFilename, bool pAllowGenerate, int pGametypeToSet) { string fn; string s, t; @@@ -1174,10 -1143,10 +1150,10 @@@ MapInfo_Cache_Store(); if(MapInfo_Map_supportedGametypes != 0) return r; - dprint("Map ", pFilename, " supports no game types, ignored\n"); + LOG_TRACE("Map ", pFilename, " supports no game types, ignored\n"); return 0; } --float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, int pGametypeToSet) ++float MapInfo_Get_ByName(string pFilename, bool pAllowGenerate, int pGametypeToSet) { float r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet); @@@ -1188,6 -1157,6 +1164,7 @@@ if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH) _MapInfo_Map_ApplyGametypeEx ("", pGametypeToSet, MAPINFO_TYPE_TEAM_DEATHMATCH); } ++ _MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_INFECTION); // infection always works if(pGametypeToSet) { @@@ -1277,15 -1241,15 +1249,15 @@@ int MapInfo_CurrentGametype( return MAPINFO_TYPE_DEATHMATCH; } --float _MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise ++bool _MapInfo_CheckMap(string s) // returns false if the map can't be played with the current settings, true otherwise { -- if(!MapInfo_Get_ByName(s, 1, 0)) -- return 0; ++ if(!MapInfo_Get_ByName(s, true, 0)) ++ return false; if((MapInfo_Map_supportedGametypes & MapInfo_CurrentGametype()) == 0) -- return 0; ++ return false; if((MapInfo_Map_supportedFeatures & MapInfo_CurrentFeatures()) != MapInfo_CurrentFeatures()) -- return 0; -- return 1; ++ return false; ++ return true; } float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise @@@ -1362,9 -1326,9 +1334,7 @@@ void MapInfo_LoadMapSettings_SaveGameTy void MapInfo_LoadMapSettings(string s) // to be called from worldspawn { -- float t; -- -- t = MapInfo_CurrentGametype(); ++ int t = MapInfo_CurrentGametype(); MapInfo_LoadMapSettings_SaveGameType(t); if(!_MapInfo_CheckMap(s)) // with underscore, it keeps temps diff --cc qcsrc/common/mapinfo.qh index 87cc38278,702798bbf..6e2949914 --- a/qcsrc/common/mapinfo.qh +++ b/qcsrc/common/mapinfo.qh @@@ -3,93 -3,177 +3,179 @@@ #include "util.qh" + 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(4)) ++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; diff --cc qcsrc/menu/xonotic/util.qc index 10bf7468a,b879257b7..34bc2264b --- a/qcsrc/menu/xonotic/util.qc +++ b/qcsrc/menu/xonotic/util.qc @@@ -665,13 -667,11 +667,12 @@@ float updateCompression( // note: include only those that should be in the menu! #define GAMETYPES \ - GAMETYPE(MAPINFO_TYPE_ASSAULT) \ + GAMETYPE(MAPINFO_TYPE_DEATHMATCH) \ + GAMETYPE(MAPINFO_TYPE_TEAM_DEATHMATCH) \ GAMETYPE(MAPINFO_TYPE_CTF) \ GAMETYPE(MAPINFO_TYPE_CA) \ - GAMETYPE(MAPINFO_TYPE_DEATHMATCH) \ - GAMETYPE(MAPINFO_TYPE_DOMINATION) \ GAMETYPE(MAPINFO_TYPE_FREEZETAG) \ + GAMETYPE(MAPINFO_TYPE_INFECTION) \ GAMETYPE(MAPINFO_TYPE_KEEPAWAY) \ GAMETYPE(MAPINFO_TYPE_KEYHUNT) \ GAMETYPE(MAPINFO_TYPE_LMS) \ diff --cc qcsrc/server/mutators/all.inc index 000000000,1a80b4409..cb709f3ba mode 000000,100644..100644 --- a/qcsrc/server/mutators/all.inc +++ b/qcsrc/server/mutators/all.inc @@@ -1,0 -1,13 +1,14 @@@ + #include "mutator/gamemode_assault.qc" + #include "mutator/gamemode_ca.qc" + #include "mutator/gamemode_ctf.qc" + #include "mutator/gamemode_cts.qc" + #include "mutator/gamemode_deathmatch.qc" + #include "mutator/gamemode_domination.qc" + #include "mutator/gamemode_freezetag.qc" ++#include "mutator/gamemode_infection.qc" + #include "mutator/gamemode_invasion.qc" + #include "mutator/gamemode_keepaway.qc" + #include "mutator/gamemode_keyhunt.qc" + #include "mutator/gamemode_lms.qc" + #include "mutator/gamemode_race.qc" + #include "mutator/gamemode_tdm.qc" diff --cc qcsrc/server/mutators/mutator/gamemode_infection.qc index 000000000,000000000..4af6e488b new file mode 100644 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_infection.qc @@@ -1,0 -1,0 +1,261 @@@ ++#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