From: Mario Date: Sun, 11 Aug 2019 07:22:45 +0000 (+1000) Subject: Include the modicons files for the other gamemodes (CA and CTS excluded) X-Git-Tag: xonotic-v0.8.5~1411 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=c43f0c17e4167dabcc4818c3ad9227f9efc282ec;p=xonotic%2Fxonotic-data.pk3dir.git Include the modicons files for the other gamemodes (CA and CTS excluded) --- diff --git a/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc b/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc index 7bc5a9679..c6d756fbb 100644 --- a/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc @@ -1,4 +1,7 @@ // generated file; do not modify +#ifdef CSQC + #include +#endif #ifdef SVQC #include #endif diff --git a/qcsrc/common/gamemodes/gamemode/ctf/cl_ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/cl_ctf.qc new file mode 100644 index 000000000..c82e0bce9 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/ctf/cl_ctf.qc @@ -0,0 +1,198 @@ +#include "cl_ctf.qh" + +#include + +// 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; +} + +int autocvar__teams_available; +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 = 0, blueflag_statuschange_elapsedtime = 0, yellowflag_statuschange_elapsedtime = 0, pinkflag_statuschange_elapsedtime = 0, neutralflag_statuschange_elapsedtime = 0; // time since the status changed + bool ctf_oneflag; // one-flag CTF mode enabled/disabled + bool ctf_stalemate; // currently in stalemate + int stat_items = STAT(CTF_FLAGSTATUS); + float fs, fs2, fs3, size1, size2; + vector e1, e2; + + int nteams = autocvar__teams_available; + + 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); + + ctf_stalemate = (stat_items & CTF_STALEMATE); + + mod_active = (redflag || blueflag || yellowflag || pinkflag || neutralflag || (stat_items & CTF_SHIELDED)); + + if (autocvar__hud_configure) { + redflag = 1; + blueflag = 2; + if (nteams & BIT(2)) + yellowflag = 2; + if (nteams & BIT(3)) + 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) MACRO_BEGIN \ + 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; \ + MACRO_END + 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 = string_null, team##_icon_prevstatus = string_null; \ + int team##_alpha, team##_alpha_prevstatus; \ + team##_alpha = team##_alpha_prevstatus = 1; \ + MACRO_BEGIN \ + 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; \ + } \ + MACRO_END + X(red, myteam != NUM_TEAM_1 && (nteams & BIT(0))); + X(blue, myteam != NUM_TEAM_2 && (nteams & BIT(1))); + X(yellow, myteam != NUM_TEAM_3 && (nteams & BIT(2))); + X(pink, myteam != NUM_TEAM_4 && (nteams & BIT(3))); + X(neutral, ctf_oneflag); + #undef X + + int tcount = 2; + if(nteams & BIT(2)) + tcount = 3; + if(nteams & BIT(3)) + tcount = 4; + + 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 (tcount) { + 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) MACRO_BEGIN \ + f = bound(0, team##flag_statuschange_elapsedtime * 2, 1); \ + if (team##_icon && ctf_stalemate) \ + drawpic_aspect_skin(team##flag_pos, "flag_stalemate", flag_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); \ + 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); \ + MACRO_END + X(red); + X(blue); + X(yellow); + X(pink); + X(neutral); + #undef X +} diff --git a/qcsrc/common/gamemodes/gamemode/ctf/cl_ctf.qh b/qcsrc/common/gamemodes/gamemode/ctf/cl_ctf.qh new file mode 100644 index 000000000..1cf4bcfc2 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/ctf/cl_ctf.qh @@ -0,0 +1,6 @@ +#pragma once + +#include "ctf.qh" + +void HUD_Mod_CTF_Reset(); +void HUD_Mod_CTF(vector pos, vector mySize); diff --git a/qcsrc/common/gamemodes/gamemode/domination/_mod.inc b/qcsrc/common/gamemodes/gamemode/domination/_mod.inc index ff9bc11f8..ccaa150df 100644 --- a/qcsrc/common/gamemodes/gamemode/domination/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/domination/_mod.inc @@ -1,4 +1,7 @@ // generated file; do not modify +#ifdef CSQC + #include +#endif #ifdef SVQC #include #endif diff --git a/qcsrc/common/gamemodes/gamemode/domination/cl_domination.qc b/qcsrc/common/gamemodes/gamemode/domination/cl_domination.qc new file mode 100644 index 000000000..418a843d9 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/domination/cl_domination.qc @@ -0,0 +1,86 @@ +#include "cl_domination.qh" + +#include + +int autocvar_hud_panel_modicons_dom_layout; + +void DrawDomItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i) +{ + TC(int, layout); TC(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 = 0; + if(STAT(DOM_TOTAL_PPS)) + 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), vec2((2/3) * mySize.x, 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)), "%" ), vec2((2/3) * mySize.x, 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 = vec2(mySize.x / columns, mySize.y / rows); + for(i=0; i= rows) + { + row = 0; + ++column; + } + } +} diff --git a/qcsrc/common/gamemodes/gamemode/domination/cl_domination.qh b/qcsrc/common/gamemodes/gamemode/domination/cl_domination.qh new file mode 100644 index 000000000..11ab3daca --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/domination/cl_domination.qh @@ -0,0 +1,3 @@ +#pragma once + +void HUD_Mod_Dom(vector myPos, vector mySize); diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc index 420f7af78..2ce6f4493 100644 --- a/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc @@ -1,4 +1,7 @@ // generated file; do not modify +#ifdef CSQC + #include +#endif #ifdef SVQC #include #endif diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/cl_keepaway.qc b/qcsrc/common/gamemodes/gamemode/keepaway/cl_keepaway.qc new file mode 100644 index 000000000..b2d087428 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keepaway/cl_keepaway.qc @@ -0,0 +1,48 @@ +#include "cl_keepaway.qh" + +#include + +// 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 = STAT(ITEMS); + 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 = vec2(0.5 * mySize.x, mySize.y); + } else { + kaball_pos = pos + eY * 0.25 * mySize.y; + kaball_size = vec2(mySize.x, 0.5 * mySize.y); + } + + 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", vec2(mySize.x, mySize.y), '1 1 1', panel_fg_alpha * kaball_alpha * f, DRAWFLAG_NORMAL); +} diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/cl_keepaway.qh b/qcsrc/common/gamemodes/gamemode/keepaway/cl_keepaway.qh new file mode 100644 index 000000000..7bcc289cd --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keepaway/cl_keepaway.qh @@ -0,0 +1,3 @@ +#pragma once + +void HUD_Mod_Keepaway(vector pos, vector mySize); diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/cl_keyhunt.qc b/qcsrc/common/gamemodes/gamemode/keyhunt/cl_keyhunt.qc new file mode 100644 index 000000000..e95275eba --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/cl_keyhunt.qc @@ -0,0 +1,131 @@ +#include "cl_keyhunt.qh" + +#include + +// 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); + if(!state) return; + + 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 = vec2(mySize.x * 0.5, 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 = vec2(mySize.x / all_keys, mySize.y); + for(i = 0; i < all_keys; ++i) + KH_SLOTS[i] = pos + eX * slot_size.x * i; + } + else + { + // Vertical arrangement + slot_size = vec2(mySize.x, 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 alpha = 1; + if(carrying_keys) + { + float blink = 0.6 + sin(2 * M_PI * time) * 0.4; // Oscillate between 0.2 and 1 + 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); +} diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/cl_keyhunt.qh b/qcsrc/common/gamemodes/gamemode/keyhunt/cl_keyhunt.qh new file mode 100644 index 000000000..8d650f11d --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/cl_keyhunt.qh @@ -0,0 +1,3 @@ +#pragma once + +void HUD_Mod_KH(vector pos, vector mySize); diff --git a/qcsrc/common/gamemodes/gamemode/nexball/cl_nexball.qc b/qcsrc/common/gamemodes/gamemode/nexball/cl_nexball.qc new file mode 100644 index 000000000..5a6b42dc4 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/nexball/cl_nexball.qc @@ -0,0 +1,44 @@ +#include "cl_nexball.qh" + +#include + +// Nexball HUD mod icon +void HUD_Mod_NexBall(vector pos, vector mySize) +{ + float nb_pb_starttime, dt, p; + int stat_items; + + stat_items = STAT(ITEMS); + 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); +} + +int autocvar_cl_eventchase_nexball = 1; + +REGISTER_MUTATOR(cl_nb, true); + +MUTATOR_HOOKFUNCTION(cl_nb, WantEventchase) +{ + if(autocvar_cl_eventchase_nexball && ISGAMETYPE(NEXBALL) && !(WepSet_GetFromStat() & WEPSET(NEXBALL))) + return true; + return false; +} diff --git a/qcsrc/common/gamemodes/gamemode/nexball/cl_nexball.qh b/qcsrc/common/gamemodes/gamemode/nexball/cl_nexball.qh new file mode 100644 index 000000000..d0b3731bc --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/nexball/cl_nexball.qh @@ -0,0 +1,3 @@ +#pragma once + +void HUD_Mod_NexBall(vector pos, vector mySize); diff --git a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qc b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qc deleted file mode 100644 index ec46ac836..000000000 --- a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qc +++ /dev/null @@ -1,984 +0,0 @@ -#include "nexball.qh" - -#ifdef CSQC -int autocvar_cl_eventchase_nexball = 1; - -REGISTER_MUTATOR(cl_nb, true); - -MUTATOR_HOOKFUNCTION(cl_nb, WantEventchase) -{ - if(autocvar_cl_eventchase_nexball && ISGAMETYPE(NEXBALL) && !(WepSet_GetFromStat() & WEPSET(NEXBALL))) - return true; - return false; -} -#endif -#ifdef SVQC -.entity ballcarried; - -int autocvar_g_nexball_goalleadlimit; -#define autocvar_g_nexball_goallimit cvar("g_nexball_goallimit") - -bool autocvar_g_nexball_basketball_jumppad = true; -float autocvar_g_nexball_basketball_bouncefactor; -float autocvar_g_nexball_basketball_bouncestop; -float autocvar_g_nexball_basketball_carrier_highspeed; -bool autocvar_g_nexball_basketball_meter; -float autocvar_g_nexball_basketball_meter_maxpower; -float autocvar_g_nexball_basketball_meter_minpower; -float autocvar_g_nexball_delay_collect; -float autocvar_g_nexball_delay_goal; -float autocvar_g_nexball_delay_start; -bool autocvar_g_nexball_football_jumppad = true; -float autocvar_g_nexball_football_bouncefactor; -float autocvar_g_nexball_football_bouncestop; -bool autocvar_g_nexball_radar_showallplayers; -bool autocvar_g_nexball_sound_bounce; -int autocvar_g_nexball_trail_color; -bool autocvar_g_nexball_playerclip_collisions = true; - -float autocvar_g_nexball_safepass_turnrate; -float autocvar_g_nexball_safepass_maxdist; -float autocvar_g_nexball_safepass_holdtime; -float autocvar_g_nexball_viewmodel_scale; -float autocvar_g_nexball_tackling; -vector autocvar_g_nexball_viewmodel_offset; - -float autocvar_g_balance_nexball_primary_animtime; -float autocvar_g_balance_nexball_primary_refire; -float autocvar_g_balance_nexball_primary_speed; -float autocvar_g_balance_nexball_secondary_animtime; -float autocvar_g_balance_nexball_secondary_force; -float autocvar_g_balance_nexball_secondary_lifetime; -float autocvar_g_balance_nexball_secondary_refire; -float autocvar_g_balance_nexball_secondary_speed; - -void basketball_touch(entity this, entity toucher); -void football_touch(entity this, entity toucher); -void ResetBall(entity this); -const int NBM_NONE = 0; -const int NBM_FOOTBALL = 2; -const int NBM_BASKETBALL = 4; -float nexball_mode; - -float OtherTeam(float t) //works only if there are two teams on the map! -{ - entity e; - e = find(NULL, classname, "nexball_team"); - if(e.team == t) - e = find(e, classname, "nexball_team"); - return e.team; -} - -const int ST_NEXBALL_GOALS = 1; -void nb_ScoreRules(int teams) -{ - GameRules_scoring(teams, 0, 0, { - field_team(ST_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY); - field(SP_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY); - field(SP_NEXBALL_FAULTS, "faults", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER); - }); -} - -void LogNB(string mode, entity actor) -{ - string s; - if(!autocvar_sv_eventlog) - return; - s = strcat(":nexball:", mode); - if(actor != NULL) - s = strcat(s, ":", ftos(actor.playerid)); - GameLogEcho(s); -} - -void ball_restart(entity this) -{ - if(this.owner) - DropBall(this, this.owner.origin, '0 0 0'); - ResetBall(this); -} - -void nexball_setstatus(entity this) -{ - this.items &= ~IT_KEY1; - if(this.ballcarried) - { - if(this.ballcarried.teamtime && (this.ballcarried.teamtime < time)) - { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_NEXBALL_RETURN_HELD)); - entity e = this.ballcarried; - DropBall(this.ballcarried, this.ballcarried.owner.origin, '0 0 0'); - ResetBall(e); - } - else - this.items |= IT_KEY1; - } -} - -void relocate_nexball(entity this) -{ - tracebox(this.origin, BALL_MINS, BALL_MAXS, this.origin, true, this); - if(trace_startsolid) - { - vector o = this.origin; - if (!move_out_of_solid(this)) { - objerror(this, "could not get out of solid at all!"); - } - LOG_INFOF( - "^1NOTE: this map needs FIXING. %s at %s needs to be moved out of solid, e.g. by %s", - this.classname, - vtos(o - '0 0 1'), - vtos(this.origin - o) - ); - this.origin = o; - } -} - -void DropOwner(entity this) -{ - entity ownr; - ownr = this.owner; - DropBall(this, ownr.origin, ownr.velocity); - makevectors(ownr.v_angle.y * '0 1 0'); - ownr.velocity += ('0 0 0.75' - v_forward) * 1000; - UNSET_ONGROUND(ownr); -} - -void GiveBall(entity plyr, entity ball) -{ - .entity weaponentity = weaponentities[0]; // TODO: find ballstealer - entity ownr = ball.owner; - if(ownr) - { - ownr.effects &= ~autocvar_g_nexball_basketball_effects_default; - ownr.ballcarried = NULL; - GameRules_scoring_vip(ownr, false); - if(STAT(NB_METERSTART, ownr)) - { - STAT(NB_METERSTART, ownr) = 0; - ownr.(weaponentity).state = WS_READY; - } - WaypointSprite_Kill(ownr.waypointsprite_attachedforcarrier); - } - else - { - WaypointSprite_Kill(ball.waypointsprite_attachedforcarrier); - } - - //setattachment(ball, plyr, ""); - setorigin(ball, plyr.origin + plyr.view_ofs); - - if(ball.team != plyr.team) - ball.teamtime = time + autocvar_g_nexball_basketball_delay_hold_forteam; - - ball.owner = ball.pusher = plyr; //"owner" is set to the player carrying, "pusher" to the last player who touched it - ball.weaponentity_fld = weaponentity; - ball.team = plyr.team; - plyr.ballcarried = ball; - GameRules_scoring_vip(plyr, true); - ball.nb_dropper = plyr; - - plyr.effects |= autocvar_g_nexball_basketball_effects_default; - ball.effects &= ~autocvar_g_nexball_basketball_effects_default; - - ball.velocity = '0 0 0'; - set_movetype(ball, MOVETYPE_NONE); - settouch(ball, func_null); - ball.effects |= EF_NOSHADOW; - ball.scale = 1; // scale down. - - WaypointSprite_AttachCarrier(WP_NbBall, plyr, RADARICON_FLAGCARRIER); - WaypointSprite_UpdateRule(plyr.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); - - if(autocvar_g_nexball_basketball_delay_hold) - { - setthink(ball, DropOwner); - ball.nextthink = time + autocvar_g_nexball_basketball_delay_hold; - } - - STAT(WEAPONS, plyr.(weaponentity)) = STAT(WEAPONS, plyr); - plyr.m_switchweapon = plyr.(weaponentity).m_weapon; - STAT(WEAPONS, plyr) = WEPSET(NEXBALL); - Weapon w = WEP_NEXBALL; - w.wr_resetplayer(w, plyr); - plyr.(weaponentity).m_switchweapon = WEP_NEXBALL; - W_SwitchWeapon(plyr, WEP_NEXBALL, weaponentity); -} - -void DropBall(entity ball, vector org, vector vel) -{ - ball.effects |= autocvar_g_nexball_basketball_effects_default; - ball.effects &= ~EF_NOSHADOW; - ball.owner.effects &= ~autocvar_g_nexball_basketball_effects_default; - - setattachment(ball, NULL, ""); - setorigin(ball, org); - set_movetype(ball, MOVETYPE_BOUNCE); - UNSET_ONGROUND(ball); - ball.scale = ball_scale; - ball.velocity = vel; - ball.nb_droptime = time; - settouch(ball, basketball_touch); - setthink(ball, ResetBall); - ball.nextthink = min(time + autocvar_g_nexball_delay_idle, ball.teamtime); - - if(STAT(NB_METERSTART, ball.owner)) - { - STAT(NB_METERSTART, ball.owner) = 0; - .entity weaponentity = ball.weaponentity_fld; - ball.owner.(weaponentity).state = WS_READY; - } - - WaypointSprite_Kill(ball.owner.waypointsprite_attachedforcarrier); - WaypointSprite_Spawn(WP_NbBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); // no health bar please - WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); - - entity e = ball.owner; ball.owner = NULL; - e.ballcarried = NULL; - GameRules_scoring_vip(e, false); -} - -void InitBall(entity this) -{ - if(game_stopped) return; - UNSET_ONGROUND(this); - set_movetype(this, MOVETYPE_BOUNCE); - if(this.classname == "nexball_basketball") - settouch(this, basketball_touch); - else if(this.classname == "nexball_football") - settouch(this, football_touch); - this.cnt = 0; - setthink(this, ResetBall); - this.nextthink = time + autocvar_g_nexball_delay_idle + 3; - this.teamtime = 0; - this.pusher = NULL; - this.team = false; - _sound(this, CH_TRIGGER, this.noise1, VOL_BASE, ATTEN_NORM); - WaypointSprite_Ping(this.waypointsprite_attachedforcarrier); - LogNB("init", NULL); -} - -void ResetBall(entity this) -{ - if(this.cnt < 2) // step 1 - { - if(time == this.teamtime) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_NEXBALL_RETURN_HELD)); - - settouch(this, func_null); - set_movetype(this, MOVETYPE_NOCLIP); - this.velocity = '0 0 0'; // just in case? - if(!this.cnt) - LogNB("resetidle", NULL); - this.cnt = 2; - this.nextthink = time; - } - else if(this.cnt < 4) // step 2 and 3 - { -// dprint("Step ", ftos(this.cnt), ": Calculated velocity: ", vtos(this.spawnorigin - this.origin), ", time: ", ftos(time), "\n"); - this.velocity = (this.spawnorigin - this.origin) * (this.cnt - 1); // 1 or 0.5 second movement - this.nextthink = time + 0.5; - this.cnt += 1; - } - else // step 4 - { -// dprint("Step 4: time: ", ftos(time), "\n"); - if(vdist(this.origin - this.spawnorigin, >, 10)) // should not happen anymore - LOG_TRACE("The ball moved too far away from its spawn origin.\nOffset: ", - vtos(this.origin - this.spawnorigin), " Velocity: ", vtos(this.velocity), "\n"); - this.velocity = '0 0 0'; - setorigin(this, this.spawnorigin); // make sure it's positioned correctly anyway - set_movetype(this, MOVETYPE_NONE); - setthink(this, InitBall); - this.nextthink = max(time, game_starttime) + autocvar_g_nexball_delay_start; - } -} - -void football_touch(entity this, entity toucher) -{ - if(toucher.solid == SOLID_BSP) - { - if(time > this.lastground + 0.1) - { - _sound(this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - this.lastground = time; - } - if(this.velocity && !this.cnt) - this.nextthink = time + autocvar_g_nexball_delay_idle; - return; - } - if (!IS_PLAYER(toucher) && !IS_VEHICLE(toucher)) - return; - if(GetResource(toucher, RES_HEALTH) < 1) - return; - if(!this.cnt) - this.nextthink = time + autocvar_g_nexball_delay_idle; - - this.pusher = toucher; - this.team = toucher.team; - - if(autocvar_g_nexball_football_physics == -1) // MrBougo try 1, before decompiling Rev's original - { - if(toucher.velocity) - this.velocity = toucher.velocity * 1.5 + '0 0 1' * autocvar_g_nexball_football_boost_up; - } - else if(autocvar_g_nexball_football_physics == 1) // MrBougo's modded Rev style: partially independant of the height of the aiming point - { - makevectors(toucher.v_angle); - this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + '0 0 1' * autocvar_g_nexball_football_boost_up; - } - else if(autocvar_g_nexball_football_physics == 2) // 2nd mod try: totally independant. Really playable! - { - makevectors(toucher.v_angle.y * '0 1 0'); - this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up; - } - else // Revenant's original style (from the original mod's disassembly, acknowledged by Revenant) - { - makevectors(toucher.v_angle); - this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up; - } - this.avelocity = -250 * v_forward; // maybe there is a way to make it look better? -} - -void basketball_touch(entity this, entity toucher) -{ - if(toucher.ballcarried) - { - football_touch(this, toucher); - return; - } - if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect)) - { - if(GetResource(toucher, RES_HEALTH) < 1) - return; - LogNB("caught", toucher); - GiveBall(toucher, this); - } - else if(toucher.solid == SOLID_BSP) - { - _sound(this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - if(this.velocity && !this.cnt) - this.nextthink = min(time + autocvar_g_nexball_delay_idle, this.teamtime); - } -} - -void GoalTouch(entity this, entity toucher) -{ - entity ball; - float isclient, pscore, otherteam; - string pname; - - if(game_stopped) return; - if((this.spawnflags & GOAL_TOUCHPLAYER) && toucher.ballcarried) - ball = toucher.ballcarried; - else - ball = toucher; - if(ball.classname != "nexball_basketball") - if(ball.classname != "nexball_football") - return; - if((!ball.pusher && this.team != GOAL_OUT) || ball.cnt) - return; - EXACTTRIGGER_TOUCH(this, toucher); - - - if(NumTeams(nb_teams) == 2) - otherteam = OtherTeam(ball.team); - else - otherteam = 0; - - if((isclient = IS_CLIENT(ball.pusher))) - pname = ball.pusher.netname; - else - pname = "Someone (?)"; - - if(ball.team == this.team) //owngoal (regular goals) - { - LogNB("owngoal", ball.pusher); - bprint("Boo! ", pname, "^7 scored a goal against their own team!\n"); - pscore = -1; - } - else if(this.team == GOAL_FAULT) - { - LogNB("fault", ball.pusher); - if(NumTeams(nb_teams) == 2) - bprint(Team_ColoredFullName(otherteam), " gets a point due to ", pname, "^7's silliness.\n"); - else - bprint(Team_ColoredFullName(ball.team), " loses a point due to ", pname, "^7's silliness.\n"); - pscore = -1; - } - else if(this.team == GOAL_OUT) - { - LogNB("out", ball.pusher); - if((this.spawnflags & GOAL_TOUCHPLAYER) && ball.owner) - bprint(pname, "^7 went out of bounds.\n"); - else - bprint("The ball was returned.\n"); - pscore = 0; - } - else //score - { - LogNB(strcat("goal:", ftos(this.team)), ball.pusher); - bprint("Goaaaaal! ", pname, "^7 scored a point for the ", Team_ColoredFullName(ball.team), ".\n"); - pscore = 1; - } - - _sound(ball, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NONE); - - if(ball.team && pscore) - { - if(NumTeams(nb_teams) == 2 && pscore < 0) - TeamScore_AddToTeam(otherteam, ST_NEXBALL_GOALS, -pscore); - else - TeamScore_AddToTeam(ball.team, ST_NEXBALL_GOALS, pscore); - } - if(isclient) - { - if(pscore > 0) - GameRules_scoring_add(ball.pusher, NEXBALL_GOALS, pscore); - else if(pscore < 0) - GameRules_scoring_add(ball.pusher, NEXBALL_FAULTS, -pscore); - } - - if(ball.owner) // Happens on spawnflag GOAL_TOUCHPLAYER - DropBall(ball, ball.owner.origin, ball.owner.velocity); - - WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier); - - ball.cnt = 1; - setthink(ball, ResetBall); - if(ball.classname == "nexball_basketball") - settouch(ball, football_touch); // better than func_null: football control until the ball gets reset - ball.nextthink = time + autocvar_g_nexball_delay_goal * (this.team != GOAL_OUT); -} - -//=======================// -// team ents // -//=======================// -spawnfunc(nexball_team) -{ - if(!g_nexball) - { - delete(this); - return; - } - this.team = this.cnt + 1; -} - -void nb_spawnteam(string teamname, float teamcolor) -{ - LOG_TRACE("^2spawned team ", teamname); - entity e = new(nexball_team); - e.netname = teamname; - e.cnt = teamcolor; - e.team = e.cnt + 1; - //nb_teams += 1; -} - -void nb_spawnteams() -{ - bool t_red = false, t_blue = false, t_yellow = false, t_pink = false; - entity e; - for(e = NULL; (e = find(e, classname, "nexball_goal"));) - { - switch(e.team) - { - case NUM_TEAM_1: - if(!t_red) - { - nb_spawnteam("Red", e.team-1) ; - nb_teams |= BIT(0); - t_red = true; - } - break; - case NUM_TEAM_2: - if(!t_blue) - { - nb_spawnteam("Blue", e.team-1) ; - t_blue = true; - nb_teams |= BIT(1); - } - break; - case NUM_TEAM_3: - if(!t_yellow) - { - nb_spawnteam("Yellow", e.team-1); - t_yellow = true; - nb_teams |= BIT(2); - } - break; - case NUM_TEAM_4: - if(!t_pink) - { - nb_spawnteam("Pink", e.team-1) ; - t_pink = true; - nb_teams |= BIT(3); - } - break; - } - } -} - -void nb_delayedinit(entity this) -{ - if(find(NULL, classname, "nexball_team") == NULL) - nb_spawnteams(); - nb_ScoreRules(nb_teams); -} - - -//=======================// -// spawnfuncs // -//=======================// - -void SpawnBall(entity this) -{ - if(!g_nexball) { delete(this); return; } - -// balls += 4; // using the remaining bits to count balls will leave more than the max edict count, so it's fine - - if(this.model == "") - { - this.model = "models/nexball/ball.md3"; - this.scale = 1.3; - } - - precache_model(this.model); - _setmodel(this, this.model); - setsize(this, BALL_MINS, BALL_MAXS); - ball_scale = this.scale; - - relocate_nexball(this); - this.spawnorigin = this.origin; - - this.effects = this.effects | EF_LOWPRECISION; - - if(cvar(strcat("g_", this.classname, "_trail"))) //nexball_basketball :p - { - this.glow_color = autocvar_g_nexball_trail_color; - this.glow_trail = true; - } - - set_movetype(this, MOVETYPE_FLY); - - if(autocvar_g_nexball_playerclip_collisions) - this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP; - - if(!autocvar_g_nexball_sound_bounce) - this.noise = ""; - else if(this.noise == "") - this.noise = strzone(SND(NB_BOUNCE)); - //bounce sound placeholder (FIXME) - if(this.noise1 == "") - this.noise1 = strzone(SND(NB_DROP)); - //ball drop sound placeholder (FIXME) - if(this.noise2 == "") - this.noise2 = strzone(SND(NB_STEAL)); - //stealing sound placeholder (FIXME) - if(this.noise) precache_sound(this.noise); - precache_sound(this.noise1); - precache_sound(this.noise2); - - WaypointSprite_AttachCarrier(WP_NbBall, this, RADARICON_FLAGCARRIER); // the ball's team is not set yet, no rule update needed - - this.reset = ball_restart; - setthink(this, InitBall); - this.nextthink = game_starttime + autocvar_g_nexball_delay_start; -} - -spawnfunc(nexball_basketball) -{ - nexball_mode |= NBM_BASKETBALL; - this.classname = "nexball_basketball"; - if (!(balls & BALL_BASKET)) - { - /* - CVTOV(g_nexball_basketball_effects_default); - CVTOV(g_nexball_basketball_delay_hold); - CVTOV(g_nexball_basketball_delay_hold_forteam); - CVTOV(g_nexball_basketball_teamsteal); - */ - autocvar_g_nexball_basketball_effects_default = autocvar_g_nexball_basketball_effects_default & BALL_EFFECTMASK; - } - if(!this.effects) - this.effects = autocvar_g_nexball_basketball_effects_default; - this.solid = SOLID_TRIGGER; - this.pushable = autocvar_g_nexball_basketball_jumppad; - balls |= BALL_BASKET; - this.bouncefactor = autocvar_g_nexball_basketball_bouncefactor; - this.bouncestop = autocvar_g_nexball_basketball_bouncestop; - SpawnBall(this); -} - -spawnfunc(nexball_football) -{ - nexball_mode |= NBM_FOOTBALL; - this.classname = "nexball_football"; - this.solid = SOLID_TRIGGER; - balls |= BALL_FOOT; - this.pushable = autocvar_g_nexball_football_jumppad; - this.bouncefactor = autocvar_g_nexball_football_bouncefactor; - this.bouncestop = autocvar_g_nexball_football_bouncestop; - SpawnBall(this); -} - -bool nb_Goal_Customize(entity this, entity client) -{ - entity e = WaypointSprite_getviewentity(client); - entity wp_owner = this.owner; - if(SAME_TEAM(e, wp_owner)) { return false; } - - return true; -} - -void SpawnGoal(entity this) -{ - if(!g_nexball) { delete(this); return; } - - EXACTTRIGGER_INIT; - - if(this.team != GOAL_OUT && Team_IsValidTeam(this.team)) - { - entity wp = WaypointSprite_SpawnFixed(WP_NbGoal, (this.absmin + this.absmax) * 0.5, this, sprite, RADARICON_NONE); - wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0.5 0'); - setcefc(this.sprite, nb_Goal_Customize); - } - - this.classname = "nexball_goal"; - if(this.noise == "") - this.noise = "ctf/respawn.wav"; - precache_sound(this.noise); - settouch(this, GoalTouch); -} - -spawnfunc(nexball_redgoal) -{ - this.team = NUM_TEAM_1; - SpawnGoal(this); -} -spawnfunc(nexball_bluegoal) -{ - this.team = NUM_TEAM_2; - SpawnGoal(this); -} -spawnfunc(nexball_yellowgoal) -{ - this.team = NUM_TEAM_3; - SpawnGoal(this); -} -spawnfunc(nexball_pinkgoal) -{ - this.team = NUM_TEAM_4; - SpawnGoal(this); -} - -spawnfunc(nexball_fault) -{ - this.team = GOAL_FAULT; - if(this.noise == "") - this.noise = strzone(SND(TYPEHIT)); - SpawnGoal(this); -} - -spawnfunc(nexball_out) -{ - this.team = GOAL_OUT; - if(this.noise == "") - this.noise = strzone(SND(TYPEHIT)); - SpawnGoal(this); -} - -// -//Spawnfuncs preserved for compatibility -// - -spawnfunc(ball) -{ - spawnfunc_nexball_football(this); -} -spawnfunc(ball_football) -{ - spawnfunc_nexball_football(this); -} -spawnfunc(ball_basketball) -{ - spawnfunc_nexball_basketball(this); -} -// The "red goal" is defended by blue team. A ball in there counts as a point for red. -spawnfunc(ball_redgoal) -{ - spawnfunc_nexball_bluegoal(this); // I blame Revenant -} -spawnfunc(ball_bluegoal) -{ - spawnfunc_nexball_redgoal(this); // but he didn't mean to cause trouble :p -} -spawnfunc(ball_fault) -{ - spawnfunc_nexball_fault(this); -} -spawnfunc(ball_bound) -{ - spawnfunc_nexball_out(this); -} - -bool ball_customize(entity this, entity client) -{ - if(!this.owner) - { - this.effects &= ~EF_FLAME; - this.scale = 1; - setcefc(this, func_null); - return true; - } - - if(client == this.owner) - { - this.scale = autocvar_g_nexball_viewmodel_scale; - if(this.enemy) - this.effects |= EF_FLAME; - else - this.effects &= ~EF_FLAME; - } - else - { - this.effects &= ~EF_FLAME; - this.scale = 1; - } - - return true; -} - -void nb_DropBall(entity player) -{ - if(player.ballcarried && g_nexball) - DropBall(player.ballcarried, player.origin, player.velocity); -} - -MUTATOR_HOOKFUNCTION(nb, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - nb_DropBall(player); -} - -MUTATOR_HOOKFUNCTION(nb, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - nb_DropBall(frag_target); -} - -MUTATOR_HOOKFUNCTION(nb, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - nb_DropBall(player); - return false; -} - -MUTATOR_HOOKFUNCTION(nb, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - makevectors(player.v_angle); - if(nexball_mode & NBM_BASKETBALL) - { - if(player.ballcarried) - { - // 'view ball' - player.ballcarried.velocity = player.velocity; - setcefc(player.ballcarried, ball_customize); - - vector org = player.origin + player.view_ofs + - v_forward * autocvar_g_nexball_viewmodel_offset.x + - v_right * autocvar_g_nexball_viewmodel_offset.y + - v_up * autocvar_g_nexball_viewmodel_offset.z; - setorigin(player.ballcarried, org); - - // 'safe passing' - if(autocvar_g_nexball_safepass_maxdist) - { - if(player.ballcarried.wait < time && player.ballcarried.enemy) - { - //centerprint(player, sprintf("Lost lock on %s", player.ballcarried.enemy.netname)); - player.ballcarried.enemy = NULL; - } - - - //tracebox(player.origin + player.view_ofs, '-2 -2 -2', '2 2 2', player.origin + player.view_ofs + v_forward * autocvar_g_nexball_safepass_maxdist); - crosshair_trace(player); - if( trace_ent && - IS_CLIENT(trace_ent) && - !IS_DEAD(trace_ent) && - trace_ent.team == player.team && - vdist(trace_ent.origin - player.origin, <=, autocvar_g_nexball_safepass_maxdist) ) - { - - //if(player.ballcarried.enemy != trace_ent) - // centerprint(player, sprintf("Locked to %s", trace_ent.netname)); - player.ballcarried.enemy = trace_ent; - player.ballcarried.wait = time + autocvar_g_nexball_safepass_holdtime; - - - } - } - } - else - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - - if(STAT(WEAPONS, player.(weaponentity))) - { - STAT(WEAPONS, player) = STAT(WEAPONS, player.(weaponentity)); - Weapon w = WEP_NEXBALL; - w.wr_resetplayer(w, player); - player.(weaponentity).m_switchweapon = player.m_switchweapon; - W_SwitchWeapon(player, player.(weaponentity).m_switchweapon, weaponentity); - - STAT(WEAPONS, player.(weaponentity)) = '0 0 0'; - } - } - } - - } - - nexball_setstatus(player); -} - -MUTATOR_HOOKFUNCTION(nb, SpectateCopy) -{ - entity spectatee = M_ARGV(0, entity); - entity client = M_ARGV(1, entity); - - STAT(NB_METERSTART, client) = STAT(NB_METERSTART, spectatee); -} - -MUTATOR_HOOKFUNCTION(nb, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - STAT(NB_METERSTART, player) = 0; - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - STAT(WEAPONS, player.(weaponentity)) = '0 0 0'; - } - - if (nexball_mode & NBM_BASKETBALL) - STAT(WEAPONS, player) |= WEPSET(NEXBALL); - else - STAT(WEAPONS, player) = '0 0 0'; - - return false; -} - -MUTATOR_HOOKFUNCTION(nb, PlayerPhysics_UpdateStats) -{ - entity player = M_ARGV(0, entity); - // these automatically reset, no need to worry - - if(player.ballcarried) - STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_nexball_basketball_carrier_highspeed; -} - -MUTATOR_HOOKFUNCTION(nb, ForbidThrowCurrentWeapon) -{ - //entity player = M_ARGV(0, entity); - entity wepent = M_ARGV(1, entity); - - return wepent.m_weapon == WEP_NEXBALL; -} - -MUTATOR_HOOKFUNCTION(nb, ForbidDropCurrentWeapon) -{ - //entity player = M_ARGV(0, entity); - int wep = M_ARGV(1, int); - - return wep == WEP_MORTAR.m_id; // TODO: what is this for? -} - -MUTATOR_HOOKFUNCTION(nb, FilterItem) -{ - entity item = M_ARGV(0, entity); - - if(Item_IsLoot(item)) - if(item.weapon == WEP_NEXBALL.m_id) - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(nb, ItemTouch) -{ - entity item = M_ARGV(0, entity); - entity toucher = M_ARGV(1, entity); - - if(item.weapon && toucher.ballcarried) - return MUT_ITEMTOUCH_RETURN; // no new weapons for you, mister! - - return MUT_ITEMTOUCH_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(nb, TeamBalance_CheckAllowedTeams) -{ - M_ARGV(1, string) = "nexball_team"; - return true; -} - -MUTATOR_HOOKFUNCTION(nb, WantWeapon) -{ - M_ARGV(1, float) = 0; // weapon is set a few lines later, apparently - return true; -} - -MUTATOR_HOOKFUNCTION(nb, DropSpecialItems) -{ - entity frag_target = M_ARGV(0, entity); - - if(frag_target.ballcarried) - DropBall(frag_target.ballcarried, frag_target.origin, frag_target.velocity); - - return false; -} - -MUTATOR_HOOKFUNCTION(nb, SendWaypoint) -{ - M_ARGV(2, int) &= ~0x80; -} - -REGISTER_MUTATOR(nb, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - g_nexball_meter_period = autocvar_g_nexball_meter_period; - if(g_nexball_meter_period <= 0) - g_nexball_meter_period = 2; // avoid division by zero etc. due to silly users - g_nexball_meter_period = rint(g_nexball_meter_period * 32) / 32; //Round to 1/32ths to send as a byte multiplied by 32 - - // General settings - /* - CVTOV(g_nexball_football_boost_forward); //100 - CVTOV(g_nexball_football_boost_up); //200 - CVTOV(g_nexball_delay_idle); //10 - CVTOV(g_nexball_football_physics); //0 - */ - radar_showennemies = autocvar_g_nexball_radar_showallplayers; - - InitializeEntity(NULL, nb_delayedinit, INITPRIO_GAMETYPE); - WEP_NEXBALL.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - - GameRules_teams(true); - GameRules_limit_score(autocvar_g_nexball_goallimit); - GameRules_limit_lead(autocvar_g_nexball_goalleadlimit); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - WEP_NEXBALL.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - } - return 0; -} - -#endif diff --git a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh b/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh deleted file mode 100644 index 53797d2bc..000000000 --- a/qcsrc/common/gamemodes/gamemode/nexball/nexball.qh +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#ifdef SVQC -//EF_BRIGHTFIELD|EF_BRIGHTLIGHT|EF_DIMLIGHT|EF_BLUE|EF_RED|EF_FLAME -const float BALL_EFFECTMASK = 1229; -const vector BALL_MINS = '-16 -16 -16'; // The model is 24*24*24 -const vector BALL_MAXS = '16 16 16'; -const vector BALL_ATTACHORG = '3 0 16'; -const float BALL_FOOT = 1; -const float BALL_BASKET = 2; -//spawnflags -const float GOAL_TOUCHPLAYER = 1; -//goal types -const float GOAL_FAULT = -1; -const float GOAL_OUT = -2; - -void DropBall(entity ball, vector org, vector vel); -float autocvar_g_nexball_football_boost_forward; -float autocvar_g_nexball_football_boost_up; -float autocvar_g_nexball_football_physics; -float autocvar_g_nexball_delay_idle; -float autocvar_g_nexball_basketball_delay_hold; -float autocvar_g_nexball_basketball_delay_hold_forteam; -float autocvar_g_nexball_basketball_effects_default; -float autocvar_g_nexball_basketball_teamsteal; -float autocvar_g_nexball_meter_period; - -float balls; -float ball_scale; -float nb_teams; - -.entity nb_dropper; -.float nb_droptime; - -.float teamtime; -#endif diff --git a/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qc b/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qc new file mode 100644 index 000000000..38b7a7f53 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qc @@ -0,0 +1,969 @@ +#include "sv_nexball.qh" + +.entity ballcarried; + +int autocvar_g_nexball_goalleadlimit; +#define autocvar_g_nexball_goallimit cvar("g_nexball_goallimit") + +bool autocvar_g_nexball_basketball_jumppad = true; +float autocvar_g_nexball_basketball_bouncefactor; +float autocvar_g_nexball_basketball_bouncestop; +float autocvar_g_nexball_basketball_carrier_highspeed; +bool autocvar_g_nexball_basketball_meter; +float autocvar_g_nexball_basketball_meter_maxpower; +float autocvar_g_nexball_basketball_meter_minpower; +float autocvar_g_nexball_delay_collect; +float autocvar_g_nexball_delay_goal; +float autocvar_g_nexball_delay_start; +bool autocvar_g_nexball_football_jumppad = true; +float autocvar_g_nexball_football_bouncefactor; +float autocvar_g_nexball_football_bouncestop; +bool autocvar_g_nexball_radar_showallplayers; +bool autocvar_g_nexball_sound_bounce; +int autocvar_g_nexball_trail_color; +bool autocvar_g_nexball_playerclip_collisions = true; + +float autocvar_g_nexball_safepass_turnrate; +float autocvar_g_nexball_safepass_maxdist; +float autocvar_g_nexball_safepass_holdtime; +float autocvar_g_nexball_viewmodel_scale; +float autocvar_g_nexball_tackling; +vector autocvar_g_nexball_viewmodel_offset; + +float autocvar_g_balance_nexball_primary_animtime; +float autocvar_g_balance_nexball_primary_refire; +float autocvar_g_balance_nexball_primary_speed; +float autocvar_g_balance_nexball_secondary_animtime; +float autocvar_g_balance_nexball_secondary_force; +float autocvar_g_balance_nexball_secondary_lifetime; +float autocvar_g_balance_nexball_secondary_refire; +float autocvar_g_balance_nexball_secondary_speed; + +void basketball_touch(entity this, entity toucher); +void football_touch(entity this, entity toucher); +void ResetBall(entity this); +const int NBM_NONE = 0; +const int NBM_FOOTBALL = 2; +const int NBM_BASKETBALL = 4; +float nexball_mode; + +float OtherTeam(float t) //works only if there are two teams on the map! +{ + entity e; + e = find(NULL, classname, "nexball_team"); + if(e.team == t) + e = find(e, classname, "nexball_team"); + return e.team; +} + +const int ST_NEXBALL_GOALS = 1; +void nb_ScoreRules(int teams) +{ + GameRules_scoring(teams, 0, 0, { + field_team(ST_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY); + field(SP_NEXBALL_GOALS, "goals", SFL_SORT_PRIO_PRIMARY); + field(SP_NEXBALL_FAULTS, "faults", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER); + }); +} + +void LogNB(string mode, entity actor) +{ + string s; + if(!autocvar_sv_eventlog) + return; + s = strcat(":nexball:", mode); + if(actor != NULL) + s = strcat(s, ":", ftos(actor.playerid)); + GameLogEcho(s); +} + +void ball_restart(entity this) +{ + if(this.owner) + DropBall(this, this.owner.origin, '0 0 0'); + ResetBall(this); +} + +void nexball_setstatus(entity this) +{ + this.items &= ~IT_KEY1; + if(this.ballcarried) + { + if(this.ballcarried.teamtime && (this.ballcarried.teamtime < time)) + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_NEXBALL_RETURN_HELD)); + entity e = this.ballcarried; + DropBall(this.ballcarried, this.ballcarried.owner.origin, '0 0 0'); + ResetBall(e); + } + else + this.items |= IT_KEY1; + } +} + +void relocate_nexball(entity this) +{ + tracebox(this.origin, BALL_MINS, BALL_MAXS, this.origin, true, this); + if(trace_startsolid) + { + vector o = this.origin; + if (!move_out_of_solid(this)) { + objerror(this, "could not get out of solid at all!"); + } + LOG_INFOF( + "^1NOTE: this map needs FIXING. %s at %s needs to be moved out of solid, e.g. by %s", + this.classname, + vtos(o - '0 0 1'), + vtos(this.origin - o) + ); + this.origin = o; + } +} + +void DropOwner(entity this) +{ + entity ownr; + ownr = this.owner; + DropBall(this, ownr.origin, ownr.velocity); + makevectors(ownr.v_angle.y * '0 1 0'); + ownr.velocity += ('0 0 0.75' - v_forward) * 1000; + UNSET_ONGROUND(ownr); +} + +void GiveBall(entity plyr, entity ball) +{ + .entity weaponentity = weaponentities[0]; // TODO: find ballstealer + entity ownr = ball.owner; + if(ownr) + { + ownr.effects &= ~autocvar_g_nexball_basketball_effects_default; + ownr.ballcarried = NULL; + GameRules_scoring_vip(ownr, false); + if(STAT(NB_METERSTART, ownr)) + { + STAT(NB_METERSTART, ownr) = 0; + ownr.(weaponentity).state = WS_READY; + } + WaypointSprite_Kill(ownr.waypointsprite_attachedforcarrier); + } + else + { + WaypointSprite_Kill(ball.waypointsprite_attachedforcarrier); + } + + //setattachment(ball, plyr, ""); + setorigin(ball, plyr.origin + plyr.view_ofs); + + if(ball.team != plyr.team) + ball.teamtime = time + autocvar_g_nexball_basketball_delay_hold_forteam; + + ball.owner = ball.pusher = plyr; //"owner" is set to the player carrying, "pusher" to the last player who touched it + ball.weaponentity_fld = weaponentity; + ball.team = plyr.team; + plyr.ballcarried = ball; + GameRules_scoring_vip(plyr, true); + ball.nb_dropper = plyr; + + plyr.effects |= autocvar_g_nexball_basketball_effects_default; + ball.effects &= ~autocvar_g_nexball_basketball_effects_default; + + ball.velocity = '0 0 0'; + set_movetype(ball, MOVETYPE_NONE); + settouch(ball, func_null); + ball.effects |= EF_NOSHADOW; + ball.scale = 1; // scale down. + + WaypointSprite_AttachCarrier(WP_NbBall, plyr, RADARICON_FLAGCARRIER); + WaypointSprite_UpdateRule(plyr.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); + + if(autocvar_g_nexball_basketball_delay_hold) + { + setthink(ball, DropOwner); + ball.nextthink = time + autocvar_g_nexball_basketball_delay_hold; + } + + STAT(WEAPONS, plyr.(weaponentity)) = STAT(WEAPONS, plyr); + plyr.m_switchweapon = plyr.(weaponentity).m_weapon; + STAT(WEAPONS, plyr) = WEPSET(NEXBALL); + Weapon w = WEP_NEXBALL; + w.wr_resetplayer(w, plyr); + plyr.(weaponentity).m_switchweapon = WEP_NEXBALL; + W_SwitchWeapon(plyr, WEP_NEXBALL, weaponentity); +} + +void DropBall(entity ball, vector org, vector vel) +{ + ball.effects |= autocvar_g_nexball_basketball_effects_default; + ball.effects &= ~EF_NOSHADOW; + ball.owner.effects &= ~autocvar_g_nexball_basketball_effects_default; + + setattachment(ball, NULL, ""); + setorigin(ball, org); + set_movetype(ball, MOVETYPE_BOUNCE); + UNSET_ONGROUND(ball); + ball.scale = ball_scale; + ball.velocity = vel; + ball.nb_droptime = time; + settouch(ball, basketball_touch); + setthink(ball, ResetBall); + ball.nextthink = min(time + autocvar_g_nexball_delay_idle, ball.teamtime); + + if(STAT(NB_METERSTART, ball.owner)) + { + STAT(NB_METERSTART, ball.owner) = 0; + .entity weaponentity = ball.weaponentity_fld; + ball.owner.(weaponentity).state = WS_READY; + } + + WaypointSprite_Kill(ball.owner.waypointsprite_attachedforcarrier); + WaypointSprite_Spawn(WP_NbBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); // no health bar please + WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); + + entity e = ball.owner; ball.owner = NULL; + e.ballcarried = NULL; + GameRules_scoring_vip(e, false); +} + +void InitBall(entity this) +{ + if(game_stopped) return; + UNSET_ONGROUND(this); + set_movetype(this, MOVETYPE_BOUNCE); + if(this.classname == "nexball_basketball") + settouch(this, basketball_touch); + else if(this.classname == "nexball_football") + settouch(this, football_touch); + this.cnt = 0; + setthink(this, ResetBall); + this.nextthink = time + autocvar_g_nexball_delay_idle + 3; + this.teamtime = 0; + this.pusher = NULL; + this.team = false; + _sound(this, CH_TRIGGER, this.noise1, VOL_BASE, ATTEN_NORM); + WaypointSprite_Ping(this.waypointsprite_attachedforcarrier); + LogNB("init", NULL); +} + +void ResetBall(entity this) +{ + if(this.cnt < 2) // step 1 + { + if(time == this.teamtime) + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_NEXBALL_RETURN_HELD)); + + settouch(this, func_null); + set_movetype(this, MOVETYPE_NOCLIP); + this.velocity = '0 0 0'; // just in case? + if(!this.cnt) + LogNB("resetidle", NULL); + this.cnt = 2; + this.nextthink = time; + } + else if(this.cnt < 4) // step 2 and 3 + { +// dprint("Step ", ftos(this.cnt), ": Calculated velocity: ", vtos(this.spawnorigin - this.origin), ", time: ", ftos(time), "\n"); + this.velocity = (this.spawnorigin - this.origin) * (this.cnt - 1); // 1 or 0.5 second movement + this.nextthink = time + 0.5; + this.cnt += 1; + } + else // step 4 + { +// dprint("Step 4: time: ", ftos(time), "\n"); + if(vdist(this.origin - this.spawnorigin, >, 10)) // should not happen anymore + LOG_TRACE("The ball moved too far away from its spawn origin.\nOffset: ", + vtos(this.origin - this.spawnorigin), " Velocity: ", vtos(this.velocity), "\n"); + this.velocity = '0 0 0'; + setorigin(this, this.spawnorigin); // make sure it's positioned correctly anyway + set_movetype(this, MOVETYPE_NONE); + setthink(this, InitBall); + this.nextthink = max(time, game_starttime) + autocvar_g_nexball_delay_start; + } +} + +void football_touch(entity this, entity toucher) +{ + if(toucher.solid == SOLID_BSP) + { + if(time > this.lastground + 0.1) + { + _sound(this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + this.lastground = time; + } + if(this.velocity && !this.cnt) + this.nextthink = time + autocvar_g_nexball_delay_idle; + return; + } + if (!IS_PLAYER(toucher) && !IS_VEHICLE(toucher)) + return; + if(GetResource(toucher, RES_HEALTH) < 1) + return; + if(!this.cnt) + this.nextthink = time + autocvar_g_nexball_delay_idle; + + this.pusher = toucher; + this.team = toucher.team; + + if(autocvar_g_nexball_football_physics == -1) // MrBougo try 1, before decompiling Rev's original + { + if(toucher.velocity) + this.velocity = toucher.velocity * 1.5 + '0 0 1' * autocvar_g_nexball_football_boost_up; + } + else if(autocvar_g_nexball_football_physics == 1) // MrBougo's modded Rev style: partially independant of the height of the aiming point + { + makevectors(toucher.v_angle); + this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + '0 0 1' * autocvar_g_nexball_football_boost_up; + } + else if(autocvar_g_nexball_football_physics == 2) // 2nd mod try: totally independant. Really playable! + { + makevectors(toucher.v_angle.y * '0 1 0'); + this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up; + } + else // Revenant's original style (from the original mod's disassembly, acknowledged by Revenant) + { + makevectors(toucher.v_angle); + this.velocity = toucher.velocity + v_forward * autocvar_g_nexball_football_boost_forward + v_up * autocvar_g_nexball_football_boost_up; + } + this.avelocity = -250 * v_forward; // maybe there is a way to make it look better? +} + +void basketball_touch(entity this, entity toucher) +{ + if(toucher.ballcarried) + { + football_touch(this, toucher); + return; + } + if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect)) + { + if(GetResource(toucher, RES_HEALTH) < 1) + return; + LogNB("caught", toucher); + GiveBall(toucher, this); + } + else if(toucher.solid == SOLID_BSP) + { + _sound(this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + if(this.velocity && !this.cnt) + this.nextthink = min(time + autocvar_g_nexball_delay_idle, this.teamtime); + } +} + +void GoalTouch(entity this, entity toucher) +{ + entity ball; + float isclient, pscore, otherteam; + string pname; + + if(game_stopped) return; + if((this.spawnflags & GOAL_TOUCHPLAYER) && toucher.ballcarried) + ball = toucher.ballcarried; + else + ball = toucher; + if(ball.classname != "nexball_basketball") + if(ball.classname != "nexball_football") + return; + if((!ball.pusher && this.team != GOAL_OUT) || ball.cnt) + return; + EXACTTRIGGER_TOUCH(this, toucher); + + + if(NumTeams(nb_teams) == 2) + otherteam = OtherTeam(ball.team); + else + otherteam = 0; + + if((isclient = IS_CLIENT(ball.pusher))) + pname = ball.pusher.netname; + else + pname = "Someone (?)"; + + if(ball.team == this.team) //owngoal (regular goals) + { + LogNB("owngoal", ball.pusher); + bprint("Boo! ", pname, "^7 scored a goal against their own team!\n"); + pscore = -1; + } + else if(this.team == GOAL_FAULT) + { + LogNB("fault", ball.pusher); + if(NumTeams(nb_teams) == 2) + bprint(Team_ColoredFullName(otherteam), " gets a point due to ", pname, "^7's silliness.\n"); + else + bprint(Team_ColoredFullName(ball.team), " loses a point due to ", pname, "^7's silliness.\n"); + pscore = -1; + } + else if(this.team == GOAL_OUT) + { + LogNB("out", ball.pusher); + if((this.spawnflags & GOAL_TOUCHPLAYER) && ball.owner) + bprint(pname, "^7 went out of bounds.\n"); + else + bprint("The ball was returned.\n"); + pscore = 0; + } + else //score + { + LogNB(strcat("goal:", ftos(this.team)), ball.pusher); + bprint("Goaaaaal! ", pname, "^7 scored a point for the ", Team_ColoredFullName(ball.team), ".\n"); + pscore = 1; + } + + _sound(ball, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NONE); + + if(ball.team && pscore) + { + if(NumTeams(nb_teams) == 2 && pscore < 0) + TeamScore_AddToTeam(otherteam, ST_NEXBALL_GOALS, -pscore); + else + TeamScore_AddToTeam(ball.team, ST_NEXBALL_GOALS, pscore); + } + if(isclient) + { + if(pscore > 0) + GameRules_scoring_add(ball.pusher, NEXBALL_GOALS, pscore); + else if(pscore < 0) + GameRules_scoring_add(ball.pusher, NEXBALL_FAULTS, -pscore); + } + + if(ball.owner) // Happens on spawnflag GOAL_TOUCHPLAYER + DropBall(ball, ball.owner.origin, ball.owner.velocity); + + WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier); + + ball.cnt = 1; + setthink(ball, ResetBall); + if(ball.classname == "nexball_basketball") + settouch(ball, football_touch); // better than func_null: football control until the ball gets reset + ball.nextthink = time + autocvar_g_nexball_delay_goal * (this.team != GOAL_OUT); +} + +//=======================// +// team ents // +//=======================// +spawnfunc(nexball_team) +{ + if(!g_nexball) + { + delete(this); + return; + } + this.team = this.cnt + 1; +} + +void nb_spawnteam(string teamname, float teamcolor) +{ + LOG_TRACE("^2spawned team ", teamname); + entity e = new(nexball_team); + e.netname = teamname; + e.cnt = teamcolor; + e.team = e.cnt + 1; + //nb_teams += 1; +} + +void nb_spawnteams() +{ + bool t_red = false, t_blue = false, t_yellow = false, t_pink = false; + entity e; + for(e = NULL; (e = find(e, classname, "nexball_goal"));) + { + switch(e.team) + { + case NUM_TEAM_1: + if(!t_red) + { + nb_spawnteam("Red", e.team-1) ; + nb_teams |= BIT(0); + t_red = true; + } + break; + case NUM_TEAM_2: + if(!t_blue) + { + nb_spawnteam("Blue", e.team-1) ; + t_blue = true; + nb_teams |= BIT(1); + } + break; + case NUM_TEAM_3: + if(!t_yellow) + { + nb_spawnteam("Yellow", e.team-1); + t_yellow = true; + nb_teams |= BIT(2); + } + break; + case NUM_TEAM_4: + if(!t_pink) + { + nb_spawnteam("Pink", e.team-1) ; + t_pink = true; + nb_teams |= BIT(3); + } + break; + } + } +} + +void nb_delayedinit(entity this) +{ + if(find(NULL, classname, "nexball_team") == NULL) + nb_spawnteams(); + nb_ScoreRules(nb_teams); +} + + +//=======================// +// spawnfuncs // +//=======================// + +void SpawnBall(entity this) +{ + if(!g_nexball) { delete(this); return; } + +// balls += 4; // using the remaining bits to count balls will leave more than the max edict count, so it's fine + + if(this.model == "") + { + this.model = "models/nexball/ball.md3"; + this.scale = 1.3; + } + + precache_model(this.model); + _setmodel(this, this.model); + setsize(this, BALL_MINS, BALL_MAXS); + ball_scale = this.scale; + + relocate_nexball(this); + this.spawnorigin = this.origin; + + this.effects = this.effects | EF_LOWPRECISION; + + if(cvar(strcat("g_", this.classname, "_trail"))) //nexball_basketball :p + { + this.glow_color = autocvar_g_nexball_trail_color; + this.glow_trail = true; + } + + set_movetype(this, MOVETYPE_FLY); + + if(autocvar_g_nexball_playerclip_collisions) + this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP; + + if(!autocvar_g_nexball_sound_bounce) + this.noise = ""; + else if(this.noise == "") + this.noise = strzone(SND(NB_BOUNCE)); + //bounce sound placeholder (FIXME) + if(this.noise1 == "") + this.noise1 = strzone(SND(NB_DROP)); + //ball drop sound placeholder (FIXME) + if(this.noise2 == "") + this.noise2 = strzone(SND(NB_STEAL)); + //stealing sound placeholder (FIXME) + if(this.noise) precache_sound(this.noise); + precache_sound(this.noise1); + precache_sound(this.noise2); + + WaypointSprite_AttachCarrier(WP_NbBall, this, RADARICON_FLAGCARRIER); // the ball's team is not set yet, no rule update needed + + this.reset = ball_restart; + setthink(this, InitBall); + this.nextthink = game_starttime + autocvar_g_nexball_delay_start; +} + +spawnfunc(nexball_basketball) +{ + nexball_mode |= NBM_BASKETBALL; + this.classname = "nexball_basketball"; + if (!(balls & BALL_BASKET)) + { + /* + CVTOV(g_nexball_basketball_effects_default); + CVTOV(g_nexball_basketball_delay_hold); + CVTOV(g_nexball_basketball_delay_hold_forteam); + CVTOV(g_nexball_basketball_teamsteal); + */ + autocvar_g_nexball_basketball_effects_default = autocvar_g_nexball_basketball_effects_default & BALL_EFFECTMASK; + } + if(!this.effects) + this.effects = autocvar_g_nexball_basketball_effects_default; + this.solid = SOLID_TRIGGER; + this.pushable = autocvar_g_nexball_basketball_jumppad; + balls |= BALL_BASKET; + this.bouncefactor = autocvar_g_nexball_basketball_bouncefactor; + this.bouncestop = autocvar_g_nexball_basketball_bouncestop; + SpawnBall(this); +} + +spawnfunc(nexball_football) +{ + nexball_mode |= NBM_FOOTBALL; + this.classname = "nexball_football"; + this.solid = SOLID_TRIGGER; + balls |= BALL_FOOT; + this.pushable = autocvar_g_nexball_football_jumppad; + this.bouncefactor = autocvar_g_nexball_football_bouncefactor; + this.bouncestop = autocvar_g_nexball_football_bouncestop; + SpawnBall(this); +} + +bool nb_Goal_Customize(entity this, entity client) +{ + entity e = WaypointSprite_getviewentity(client); + entity wp_owner = this.owner; + if(SAME_TEAM(e, wp_owner)) { return false; } + + return true; +} + +void SpawnGoal(entity this) +{ + if(!g_nexball) { delete(this); return; } + + EXACTTRIGGER_INIT; + + if(this.team != GOAL_OUT && Team_IsValidTeam(this.team)) + { + entity wp = WaypointSprite_SpawnFixed(WP_NbGoal, (this.absmin + this.absmax) * 0.5, this, sprite, RADARICON_NONE); + wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0.5 0'); + setcefc(this.sprite, nb_Goal_Customize); + } + + this.classname = "nexball_goal"; + if(this.noise == "") + this.noise = "ctf/respawn.wav"; + precache_sound(this.noise); + settouch(this, GoalTouch); +} + +spawnfunc(nexball_redgoal) +{ + this.team = NUM_TEAM_1; + SpawnGoal(this); +} +spawnfunc(nexball_bluegoal) +{ + this.team = NUM_TEAM_2; + SpawnGoal(this); +} +spawnfunc(nexball_yellowgoal) +{ + this.team = NUM_TEAM_3; + SpawnGoal(this); +} +spawnfunc(nexball_pinkgoal) +{ + this.team = NUM_TEAM_4; + SpawnGoal(this); +} + +spawnfunc(nexball_fault) +{ + this.team = GOAL_FAULT; + if(this.noise == "") + this.noise = strzone(SND(TYPEHIT)); + SpawnGoal(this); +} + +spawnfunc(nexball_out) +{ + this.team = GOAL_OUT; + if(this.noise == "") + this.noise = strzone(SND(TYPEHIT)); + SpawnGoal(this); +} + +// +//Spawnfuncs preserved for compatibility +// + +spawnfunc(ball) +{ + spawnfunc_nexball_football(this); +} +spawnfunc(ball_football) +{ + spawnfunc_nexball_football(this); +} +spawnfunc(ball_basketball) +{ + spawnfunc_nexball_basketball(this); +} +// The "red goal" is defended by blue team. A ball in there counts as a point for red. +spawnfunc(ball_redgoal) +{ + spawnfunc_nexball_bluegoal(this); // I blame Revenant +} +spawnfunc(ball_bluegoal) +{ + spawnfunc_nexball_redgoal(this); // but he didn't mean to cause trouble :p +} +spawnfunc(ball_fault) +{ + spawnfunc_nexball_fault(this); +} +spawnfunc(ball_bound) +{ + spawnfunc_nexball_out(this); +} + +bool ball_customize(entity this, entity client) +{ + if(!this.owner) + { + this.effects &= ~EF_FLAME; + this.scale = 1; + setcefc(this, func_null); + return true; + } + + if(client == this.owner) + { + this.scale = autocvar_g_nexball_viewmodel_scale; + if(this.enemy) + this.effects |= EF_FLAME; + else + this.effects &= ~EF_FLAME; + } + else + { + this.effects &= ~EF_FLAME; + this.scale = 1; + } + + return true; +} + +void nb_DropBall(entity player) +{ + if(player.ballcarried && g_nexball) + DropBall(player.ballcarried, player.origin, player.velocity); +} + +MUTATOR_HOOKFUNCTION(nb, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + nb_DropBall(player); +} + +MUTATOR_HOOKFUNCTION(nb, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + nb_DropBall(frag_target); +} + +MUTATOR_HOOKFUNCTION(nb, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + nb_DropBall(player); + return false; +} + +MUTATOR_HOOKFUNCTION(nb, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + makevectors(player.v_angle); + if(nexball_mode & NBM_BASKETBALL) + { + if(player.ballcarried) + { + // 'view ball' + player.ballcarried.velocity = player.velocity; + setcefc(player.ballcarried, ball_customize); + + vector org = player.origin + player.view_ofs + + v_forward * autocvar_g_nexball_viewmodel_offset.x + + v_right * autocvar_g_nexball_viewmodel_offset.y + + v_up * autocvar_g_nexball_viewmodel_offset.z; + setorigin(player.ballcarried, org); + + // 'safe passing' + if(autocvar_g_nexball_safepass_maxdist) + { + if(player.ballcarried.wait < time && player.ballcarried.enemy) + { + //centerprint(player, sprintf("Lost lock on %s", player.ballcarried.enemy.netname)); + player.ballcarried.enemy = NULL; + } + + + //tracebox(player.origin + player.view_ofs, '-2 -2 -2', '2 2 2', player.origin + player.view_ofs + v_forward * autocvar_g_nexball_safepass_maxdist); + crosshair_trace(player); + if( trace_ent && + IS_CLIENT(trace_ent) && + !IS_DEAD(trace_ent) && + trace_ent.team == player.team && + vdist(trace_ent.origin - player.origin, <=, autocvar_g_nexball_safepass_maxdist) ) + { + + //if(player.ballcarried.enemy != trace_ent) + // centerprint(player, sprintf("Locked to %s", trace_ent.netname)); + player.ballcarried.enemy = trace_ent; + player.ballcarried.wait = time + autocvar_g_nexball_safepass_holdtime; + + + } + } + } + else + { + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + + if(STAT(WEAPONS, player.(weaponentity))) + { + STAT(WEAPONS, player) = STAT(WEAPONS, player.(weaponentity)); + Weapon w = WEP_NEXBALL; + w.wr_resetplayer(w, player); + player.(weaponentity).m_switchweapon = player.m_switchweapon; + W_SwitchWeapon(player, player.(weaponentity).m_switchweapon, weaponentity); + + STAT(WEAPONS, player.(weaponentity)) = '0 0 0'; + } + } + } + + } + + nexball_setstatus(player); +} + +MUTATOR_HOOKFUNCTION(nb, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + STAT(NB_METERSTART, client) = STAT(NB_METERSTART, spectatee); +} + +MUTATOR_HOOKFUNCTION(nb, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + STAT(NB_METERSTART, player) = 0; + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + STAT(WEAPONS, player.(weaponentity)) = '0 0 0'; + } + + if (nexball_mode & NBM_BASKETBALL) + STAT(WEAPONS, player) |= WEPSET(NEXBALL); + else + STAT(WEAPONS, player) = '0 0 0'; + + return false; +} + +MUTATOR_HOOKFUNCTION(nb, PlayerPhysics_UpdateStats) +{ + entity player = M_ARGV(0, entity); + // these automatically reset, no need to worry + + if(player.ballcarried) + STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_nexball_basketball_carrier_highspeed; +} + +MUTATOR_HOOKFUNCTION(nb, ForbidThrowCurrentWeapon) +{ + //entity player = M_ARGV(0, entity); + entity wepent = M_ARGV(1, entity); + + return wepent.m_weapon == WEP_NEXBALL; +} + +MUTATOR_HOOKFUNCTION(nb, ForbidDropCurrentWeapon) +{ + //entity player = M_ARGV(0, entity); + int wep = M_ARGV(1, int); + + return wep == WEP_MORTAR.m_id; // TODO: what is this for? +} + +MUTATOR_HOOKFUNCTION(nb, FilterItem) +{ + entity item = M_ARGV(0, entity); + + if(Item_IsLoot(item)) + if(item.weapon == WEP_NEXBALL.m_id) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(nb, ItemTouch) +{ + entity item = M_ARGV(0, entity); + entity toucher = M_ARGV(1, entity); + + if(item.weapon && toucher.ballcarried) + return MUT_ITEMTOUCH_RETURN; // no new weapons for you, mister! + + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(nb, TeamBalance_CheckAllowedTeams) +{ + M_ARGV(1, string) = "nexball_team"; + return true; +} + +MUTATOR_HOOKFUNCTION(nb, WantWeapon) +{ + M_ARGV(1, float) = 0; // weapon is set a few lines later, apparently + return true; +} + +MUTATOR_HOOKFUNCTION(nb, DropSpecialItems) +{ + entity frag_target = M_ARGV(0, entity); + + if(frag_target.ballcarried) + DropBall(frag_target.ballcarried, frag_target.origin, frag_target.velocity); + + return false; +} + +MUTATOR_HOOKFUNCTION(nb, SendWaypoint) +{ + M_ARGV(2, int) &= ~0x80; +} + +REGISTER_MUTATOR(nb, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + g_nexball_meter_period = autocvar_g_nexball_meter_period; + if(g_nexball_meter_period <= 0) + g_nexball_meter_period = 2; // avoid division by zero etc. due to silly users + g_nexball_meter_period = rint(g_nexball_meter_period * 32) / 32; //Round to 1/32ths to send as a byte multiplied by 32 + + // General settings + /* + CVTOV(g_nexball_football_boost_forward); //100 + CVTOV(g_nexball_football_boost_up); //200 + CVTOV(g_nexball_delay_idle); //10 + CVTOV(g_nexball_football_physics); //0 + */ + radar_showennemies = autocvar_g_nexball_radar_showallplayers; + + InitializeEntity(NULL, nb_delayedinit, INITPRIO_GAMETYPE); + WEP_NEXBALL.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + + GameRules_teams(true); + GameRules_limit_score(autocvar_g_nexball_goallimit); + GameRules_limit_lead(autocvar_g_nexball_goalleadlimit); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + WEP_NEXBALL.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + } + return 0; +} diff --git a/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qh b/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qh new file mode 100644 index 000000000..d7cde3a73 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/nexball/sv_nexball.qh @@ -0,0 +1,34 @@ +#pragma once + +//EF_BRIGHTFIELD|EF_BRIGHTLIGHT|EF_DIMLIGHT|EF_BLUE|EF_RED|EF_FLAME +const float BALL_EFFECTMASK = 1229; +const vector BALL_MINS = '-16 -16 -16'; // The model is 24*24*24 +const vector BALL_MAXS = '16 16 16'; +const vector BALL_ATTACHORG = '3 0 16'; +const float BALL_FOOT = 1; +const float BALL_BASKET = 2; +//spawnflags +const float GOAL_TOUCHPLAYER = 1; +//goal types +const float GOAL_FAULT = -1; +const float GOAL_OUT = -2; + +void DropBall(entity ball, vector org, vector vel); +float autocvar_g_nexball_football_boost_forward; +float autocvar_g_nexball_football_boost_up; +float autocvar_g_nexball_football_physics; +float autocvar_g_nexball_delay_idle; +float autocvar_g_nexball_basketball_delay_hold; +float autocvar_g_nexball_basketball_delay_hold_forteam; +float autocvar_g_nexball_basketball_effects_default; +float autocvar_g_nexball_basketball_teamsteal; +float autocvar_g_nexball_meter_period; + +float balls; +float ball_scale; +float nb_teams; + +.entity nb_dropper; +.float nb_droptime; + +.float teamtime;