12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863 |
- // Copyright (c) ZeniMax Media Inc.
- // Licensed under the GNU General Public License 2.0.
- #include "../g_local.h"
- #include "../m_player.h"
- #include <assert.h>
- enum match_t
- {
- MATCH_NONE,
- MATCH_SETUP,
- MATCH_PREGAME,
- MATCH_GAME,
- MATCH_POST
- };
- enum elect_t
- {
- ELECT_NONE,
- ELECT_MATCH,
- ELECT_ADMIN,
- ELECT_MAP
- };
- struct ctfgame_t
- {
- int team1, team2;
- int total1, total2; // these are only set when going into intermission except in teamplay
- gtime_t last_flag_capture;
- int last_capture_team;
- match_t match; // match state
- gtime_t matchtime; // time for match start/end (depends on state)
- int lasttime; // last time update, explicitly truncated to seconds
- bool countdown; // has audio countdown started?
- elect_t election; // election type
- edict_t *etarget; // for admin election, who's being elected
- char elevel[32]; // for map election, target level
- int evotes; // votes so far
- int needvotes; // votes needed
- gtime_t electtime; // remaining time until election times out
- char emsg[256]; // election name
- int warnactive; // true if stat string 30 is active
- ghost_t ghosts[MAX_CLIENTS]; // ghost codes
- };
- ctfgame_t ctfgame;
- cvar_t *ctf;
- cvar_t *teamplay;
- cvar_t *g_teamplay_force_join;
- // [Paril-KEX]
- bool G_TeamplayEnabled()
- {
- return ctf->integer || teamplay->integer;
- }
- // [Paril-KEX]
- void G_AdjustTeamScore(ctfteam_t team, int32_t offset)
- {
- if (team == CTF_TEAM1)
- ctfgame.total1 += offset;
- else if (team == CTF_TEAM2)
- ctfgame.total2 += offset;
- }
- cvar_t *competition;
- cvar_t *matchlock;
- cvar_t *electpercentage;
- cvar_t *matchtime;
- cvar_t *matchsetuptime;
- cvar_t *matchstarttime;
- cvar_t *admin_password;
- cvar_t *allow_admin;
- cvar_t *warp_list;
- cvar_t *warn_unbalanced;
- // Index for various CTF pics, this saves us from calling gi.imageindex
- // all the time and saves a few CPU cycles since we don't have to do
- // a bunch of string compares all the time.
- // These are set in CTFPrecache() called from worldspawn
- int imageindex_i_ctf1;
- int imageindex_i_ctf2;
- int imageindex_i_ctf1d;
- int imageindex_i_ctf2d;
- int imageindex_i_ctf1t;
- int imageindex_i_ctf2t;
- int imageindex_i_ctfj;
- int imageindex_sbfctf1;
- int imageindex_sbfctf2;
- int imageindex_ctfsb1;
- int imageindex_ctfsb2;
- int modelindex_flag1, modelindex_flag2; // [Paril-KEX]
- constexpr item_id_t tech_ids[] = { IT_TECH_RESISTANCE, IT_TECH_STRENGTH, IT_TECH_HASTE, IT_TECH_REGENERATION };
- /*--------------------------------------------------------------------------*/
- #ifndef KEX_Q2_GAME
- /*
- =================
- findradius
- Returns entities that have origins within a spherical area
- findradius (origin, radius)
- =================
- */
- static edict_t *loc_findradius(edict_t *from, const vec3_t &org, float rad)
- {
- vec3_t eorg;
- int j;
- if (!from)
- from = g_edicts;
- else
- from++;
- for (; from < &g_edicts[globals.num_edicts]; from++)
- {
- if (!from->inuse)
- continue;
- for (j = 0; j < 3; j++)
- eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5f);
- if (eorg.length() > rad)
- continue;
- return from;
- }
- return nullptr;
- }
- #endif
- static void loc_buildboxpoints(vec3_t (&p)[8], const vec3_t &org, const vec3_t &mins, const vec3_t &maxs)
- {
- p[0] = org + mins;
- p[1] = p[0];
- p[1][0] -= mins[0];
- p[2] = p[0];
- p[2][1] -= mins[1];
- p[3] = p[0];
- p[3][0] -= mins[0];
- p[3][1] -= mins[1];
- p[4] = org + maxs;
- p[5] = p[4];
- p[5][0] -= maxs[0];
- p[6] = p[0];
- p[6][1] -= maxs[1];
- p[7] = p[0];
- p[7][0] -= maxs[0];
- p[7][1] -= maxs[1];
- }
- static bool loc_CanSee(edict_t *targ, edict_t *inflictor)
- {
- trace_t trace;
- vec3_t targpoints[8];
- int i;
- vec3_t viewpoint;
- // bmodels need special checking because their origin is 0,0,0
- if (targ->movetype == MOVETYPE_PUSH)
- return false; // bmodels not supported
- loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
- viewpoint = inflictor->s.origin;
- viewpoint[2] += inflictor->viewheight;
- for (i = 0; i < 8; i++)
- {
- trace = gi.traceline(viewpoint, targpoints[i], inflictor, MASK_SOLID);
- if (trace.fraction == 1.0f)
- return true;
- }
- return false;
- }
- #
- /*--------------------------------------------------------------------------*/
- void CTFSpawn()
- {
- memset(&ctfgame, 0, sizeof(ctfgame));
- CTFSetupTechSpawn();
- if (competition->integer > 1)
- {
- ctfgame.match = MATCH_SETUP;
- ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value);
- }
- }
- void CTFInit()
- {
- ctf = gi.cvar("ctf", "0", CVAR_SERVERINFO | CVAR_LATCH);
- competition = gi.cvar("competition", "0", CVAR_SERVERINFO);
- matchlock = gi.cvar("matchlock", "1", CVAR_SERVERINFO);
- electpercentage = gi.cvar("electpercentage", "66", CVAR_NOFLAGS);
- matchtime = gi.cvar("matchtime", "20", CVAR_SERVERINFO);
- matchsetuptime = gi.cvar("matchsetuptime", "10", CVAR_NOFLAGS);
- matchstarttime = gi.cvar("matchstarttime", "20", CVAR_NOFLAGS);
- admin_password = gi.cvar("admin_password", "", CVAR_NOFLAGS);
- allow_admin = gi.cvar("allow_admin", "1", CVAR_NOFLAGS);
- warp_list = gi.cvar("warp_list", "q2ctf1 q2ctf2 q2ctf3 q2ctf4 q2ctf5", CVAR_NOFLAGS);
- warn_unbalanced = gi.cvar("warn_unbalanced", "0", CVAR_NOFLAGS);
- }
- /*
- * Precache CTF items
- */
- void CTFPrecache()
- {
- imageindex_i_ctf1 = gi.imageindex("i_ctf1");
- imageindex_i_ctf2 = gi.imageindex("i_ctf2");
- imageindex_i_ctf1d = gi.imageindex("i_ctf1d");
- imageindex_i_ctf2d = gi.imageindex("i_ctf2d");
- imageindex_i_ctf1t = gi.imageindex("i_ctf1t");
- imageindex_i_ctf2t = gi.imageindex("i_ctf2t");
- imageindex_i_ctfj = gi.imageindex("i_ctfj");
- imageindex_sbfctf1 = gi.imageindex("sbfctf1");
- imageindex_sbfctf2 = gi.imageindex("sbfctf2");
- imageindex_ctfsb1 = gi.imageindex("tag4");
- imageindex_ctfsb2 = gi.imageindex("tag5");
- modelindex_flag1 = gi.modelindex("players/male/flag1.md2");
- modelindex_flag2 = gi.modelindex("players/male/flag2.md2");
- PrecacheItem(GetItemByIndex(IT_WEAPON_GRAPPLE));
- }
- /*--------------------------------------------------------------------------*/
- const char *CTFTeamName(int team)
- {
- switch (team)
- {
- case CTF_TEAM1:
- return "RED";
- case CTF_TEAM2:
- return "BLUE";
- case CTF_NOTEAM:
- return "SPECTATOR";
- }
- return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN"
- }
- const char *CTFOtherTeamName(int team)
- {
- switch (team)
- {
- case CTF_TEAM1:
- return "BLUE";
- case CTF_TEAM2:
- return "RED";
- }
- return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN"
- }
- int CTFOtherTeam(int team)
- {
- switch (team)
- {
- case CTF_TEAM1:
- return CTF_TEAM2;
- case CTF_TEAM2:
- return CTF_TEAM1;
- }
- return -1; // invalid value
- }
- /*--------------------------------------------------------------------------*/
- float PlayersRangeFromSpot(edict_t *spot);
- bool SpawnPointClear(edict_t *spot);
- void CTFAssignSkin(edict_t *ent, const char *s)
- {
- int playernum = ent - g_edicts - 1;
- std::string_view t(s);
- if (size_t i = t.find_first_of('/'); i != std::string_view::npos)
- t = t.substr(0, i + 1);
- else
- t = "male/";
- switch (ent->client->resp.ctf_team)
- {
- case CTF_TEAM1:
- t = G_Fmt("{}\\{}{}\\default", ent->client->pers.netname, t, CTF_TEAM1_SKIN);
- break;
- case CTF_TEAM2:
- t = G_Fmt("{}\\{}{}\\default", ent->client->pers.netname, t, CTF_TEAM2_SKIN);
- break;
- default:
- t = G_Fmt("{}\\{}\\default", ent->client->pers.netname, s);
- break;
- }
- gi.configstring(CS_PLAYERSKINS + playernum, t.data());
- // gi.LocClient_Print(ent, PRINT_HIGH, "$g_assigned_team", ent->client->pers.netname);
- }
- void CTFAssignTeam(gclient_t *who)
- {
- edict_t *player;
- uint32_t team1count = 0, team2count = 0;
- who->resp.ctf_state = 0;
- if (!g_teamplay_force_join->integer && !(g_edicts[1 + (who - game.clients)].svflags & SVF_BOT))
- {
- who->resp.ctf_team = CTF_NOTEAM;
- return;
- }
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- player = &g_edicts[i];
- if (!player->inuse || player->client == who)
- continue;
- switch (player->client->resp.ctf_team)
- {
- case CTF_TEAM1:
- team1count++;
- break;
- case CTF_TEAM2:
- team2count++;
- break;
- default:
- break;
- }
- }
- if (team1count < team2count)
- who->resp.ctf_team = CTF_TEAM1;
- else if (team2count < team1count)
- who->resp.ctf_team = CTF_TEAM2;
- else if (brandom())
- who->resp.ctf_team = CTF_TEAM1;
- else
- who->resp.ctf_team = CTF_TEAM2;
- }
- /*
- ================
- SelectCTFSpawnPoint
- go to a ctf point, but NOT the two points closest
- to other players
- ================
- */
- edict_t *SelectCTFSpawnPoint(edict_t *ent, bool force_spawn)
- {
- if (ent->client->resp.ctf_state)
- {
- select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, false);
- if (result.any_valid)
- return result.spot;
- }
- const char *cname;
- switch (ent->client->resp.ctf_team)
- {
- case CTF_TEAM1:
- cname = "info_player_team1";
- break;
- case CTF_TEAM2:
- cname = "info_player_team2";
- break;
- default:
- {
- select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, true);
- if (result.any_valid)
- return result.spot;
- gi.Com_Error("can't find suitable spectator spawn point");
- return nullptr;
- }
- }
- static std::vector<edict_t *> spawn_points;
- edict_t *spot = nullptr;
- spawn_points.clear();
- while ((spot = G_FindByString<&edict_t::classname>(spot, cname)) != nullptr)
- spawn_points.push_back(spot);
- if (!spawn_points.size())
- {
- select_spawn_result_t result = SelectDeathmatchSpawnPoint(g_dm_spawn_farthest->integer, force_spawn, true);
- if (!result.any_valid)
- gi.Com_Error("can't find suitable CTF spawn point");
- return result.spot;
- }
- std::shuffle(spawn_points.begin(), spawn_points.end(), mt_rand);
- for (auto &point : spawn_points)
- if (SpawnPointClear(point))
- return point;
-
- if (force_spawn)
- return random_element(spawn_points);
- return nullptr;
- }
- /*------------------------------------------------------------------------*/
- /*
- CTFFragBonuses
- Calculate the bonuses for flag defense, flag carrier defense, etc.
- Note that bonuses are not cumaltive. You get one, they are in importance
- order.
- */
- void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker)
- {
- edict_t *ent;
- item_id_t flag_item, enemy_flag_item;
- int otherteam;
- edict_t *flag, *carrier = nullptr;
- const char *c;
- vec3_t v1, v2;
- if (targ->client && attacker->client)
- {
- if (attacker->client->resp.ghost)
- if (attacker != targ)
- attacker->client->resp.ghost->kills++;
- if (targ->client->resp.ghost)
- targ->client->resp.ghost->deaths++;
- }
- // no bonus for fragging yourself
- if (!targ->client || !attacker->client || targ == attacker)
- return;
- otherteam = CTFOtherTeam(targ->client->resp.ctf_team);
- if (otherteam < 0)
- return; // whoever died isn't on a team
- // same team, if the flag at base, check to he has the enemy flag
- if (targ->client->resp.ctf_team == CTF_TEAM1)
- {
- flag_item = IT_FLAG1;
- enemy_flag_item = IT_FLAG2;
- }
- else
- {
- flag_item = IT_FLAG2;
- enemy_flag_item = IT_FLAG1;
- }
- // did the attacker frag the flag carrier?
- if (targ->client->pers.inventory[enemy_flag_item])
- {
- attacker->client->resp.ctf_lastfraggedcarrier = level.time;
- attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS;
- gi.LocClient_Print(attacker, PRINT_MEDIUM, "$g_bonus_enemy_carrier",
- CTF_FRAG_CARRIER_BONUS);
- // the target had the flag, clear the hurt carrier
- // field on the other team
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- ent = g_edicts + i;
- if (ent->inuse && ent->client->resp.ctf_team == otherteam)
- ent->client->resp.ctf_lasthurtcarrier = 0_ms;
- }
- return;
- }
- if (targ->client->resp.ctf_lasthurtcarrier &&
- level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT &&
- !attacker->client->pers.inventory[flag_item])
- {
- // attacker is on the same team as the flag carrier and
- // fragged a guy who hurt our flag carrier
- attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS;
- gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_flag_defense",
- attacker->client->pers.netname,
- CTFTeamName(attacker->client->resp.ctf_team));
- if (attacker->client->resp.ghost)
- attacker->client->resp.ghost->carrierdef++;
- return;
- }
- // flag and flag carrier area defense bonuses
- // we have to find the flag and carrier entities
- // find the flag
- switch (attacker->client->resp.ctf_team)
- {
- case CTF_TEAM1:
- c = "item_flag_team1";
- break;
- case CTF_TEAM2:
- c = "item_flag_team2";
- break;
- default:
- return;
- }
- flag = nullptr;
- while ((flag = G_FindByString<&edict_t::classname>(flag, c)) != nullptr)
- {
- if (!(flag->spawnflags & SPAWNFLAG_ITEM_DROPPED))
- break;
- }
- if (!flag)
- return; // can't find attacker's flag
- // find attacker's team's flag carrier
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- carrier = g_edicts + i;
- if (carrier->inuse &&
- carrier->client->pers.inventory[flag_item])
- break;
- carrier = nullptr;
- }
- // ok we have the attackers flag and a pointer to the carrier
- // check to see if we are defending the base's flag
- v1 = targ->s.origin - flag->s.origin;
- v2 = attacker->s.origin - flag->s.origin;
- if ((v1.length() < CTF_TARGET_PROTECT_RADIUS ||
- v2.length() < CTF_TARGET_PROTECT_RADIUS ||
- loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) &&
- attacker->client->resp.ctf_team != targ->client->resp.ctf_team)
- {
- // we defended the base flag
- attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS;
- if (flag->solid == SOLID_NOT)
- gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_defend_base",
- attacker->client->pers.netname,
- CTFTeamName(attacker->client->resp.ctf_team));
- else
- gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_defend_flag",
- attacker->client->pers.netname,
- CTFTeamName(attacker->client->resp.ctf_team));
- if (attacker->client->resp.ghost)
- attacker->client->resp.ghost->basedef++;
- return;
- }
- if (carrier && carrier != attacker)
- {
- v1 = targ->s.origin - carrier->s.origin;
- v2 = attacker->s.origin - carrier->s.origin;
- if (v1.length() < CTF_ATTACKER_PROTECT_RADIUS ||
- v2.length() < CTF_ATTACKER_PROTECT_RADIUS ||
- loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker))
- {
- attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS;
- gi.LocBroadcast_Print(PRINT_MEDIUM, "$g_bonus_defend_carrier",
- attacker->client->pers.netname,
- CTFTeamName(attacker->client->resp.ctf_team));
- if (attacker->client->resp.ghost)
- attacker->client->resp.ghost->carrierdef++;
- return;
- }
- }
- }
- void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker)
- {
- item_id_t flag_item;
- if (!targ->client || !attacker->client)
- return;
- if (targ->client->resp.ctf_team == CTF_TEAM1)
- flag_item = IT_FLAG2;
- else
- flag_item = IT_FLAG1;
- if (targ->client->pers.inventory[flag_item] &&
- targ->client->resp.ctf_team != attacker->client->resp.ctf_team)
- attacker->client->resp.ctf_lasthurtcarrier = level.time;
- }
- /*------------------------------------------------------------------------*/
- void CTFResetFlag(int ctf_team)
- {
- const char *c;
- edict_t *ent;
- switch (ctf_team)
- {
- case CTF_TEAM1:
- c = "item_flag_team1";
- break;
- case CTF_TEAM2:
- c = "item_flag_team2";
- break;
- default:
- return;
- }
- ent = nullptr;
- while ((ent = G_FindByString<&edict_t::classname>(ent, c)) != nullptr)
- {
- if (ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED))
- G_FreeEdict(ent);
- else
- {
- ent->svflags &= ~SVF_NOCLIENT;
- ent->solid = SOLID_TRIGGER;
- gi.linkentity(ent);
- ent->s.event = EV_ITEM_RESPAWN;
- }
- }
- }
- void CTFResetFlags()
- {
- CTFResetFlag(CTF_TEAM1);
- CTFResetFlag(CTF_TEAM2);
- }
- bool CTFPickup_Flag(edict_t *ent, edict_t *other)
- {
- int ctf_team;
- edict_t *player;
- item_id_t flag_item, enemy_flag_item;
- // figure out what team this flag is
- if (ent->item->id == IT_FLAG1)
- ctf_team = CTF_TEAM1;
- else if (ent->item->id == IT_FLAG2)
- ctf_team = CTF_TEAM2;
- else
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Don't know what team the flag is on.\n");
- return false;
- }
- // same team, if the flag at base, check to he has the enemy flag
- if (ctf_team == CTF_TEAM1)
- {
- flag_item = IT_FLAG1;
- enemy_flag_item = IT_FLAG2;
- }
- else
- {
- flag_item = IT_FLAG2;
- enemy_flag_item = IT_FLAG1;
- }
- if (ctf_team == other->client->resp.ctf_team)
- {
- if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED))
- {
- // the flag is at home base. if the player has the enemy
- // flag, he's just won!
- if (other->client->pers.inventory[enemy_flag_item])
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_captured",
- other->client->pers.netname, CTFOtherTeamName(ctf_team));
- other->client->pers.inventory[enemy_flag_item] = 0;
- ctfgame.last_flag_capture = level.time;
- ctfgame.last_capture_team = ctf_team;
- if (ctf_team == CTF_TEAM1)
- ctfgame.team1++;
- else
- ctfgame.team2++;
- gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0);
- // other gets another 10 frag bonus
- other->client->resp.score += CTF_CAPTURE_BONUS;
- if (other->client->resp.ghost)
- other->client->resp.ghost->caps++;
- // Ok, let's do the player loop, hand out the bonuses
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- player = &g_edicts[i];
- if (!player->inuse)
- continue;
- if (player->client->resp.ctf_team != other->client->resp.ctf_team)
- player->client->resp.ctf_lasthurtcarrier = -5_sec;
- else if (player->client->resp.ctf_team == other->client->resp.ctf_team)
- {
- if (player != other)
- player->client->resp.score += CTF_TEAM_BONUS;
- // award extra points for capture assists
- if (player->client->resp.ctf_lastreturnedflag && player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_bonus_assist_return", player->client->pers.netname);
- player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS;
- }
- if (player->client->resp.ctf_lastfraggedcarrier && player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_bonus_assist_frag_carrier", player->client->pers.netname);
- player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS;
- }
- }
- }
- CTFResetFlags();
- return false;
- }
- return false; // its at home base already
- }
- // hey, its not home. return it by teleporting it back
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_returned_flag",
- other->client->pers.netname, CTFTeamName(ctf_team));
- other->client->resp.score += CTF_RECOVERY_BONUS;
- other->client->resp.ctf_lastreturnedflag = level.time;
- gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
- // CTFResetFlag will remove this entity! We must return false
- CTFResetFlag(ctf_team);
- return false;
- }
- // hey, its not our flag, pick it up
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_got_flag",
- other->client->pers.netname, CTFTeamName(ctf_team));
- other->client->resp.score += CTF_FLAG_BONUS;
- other->client->pers.inventory[flag_item] = 1;
- other->client->resp.ctf_flagsince = level.time;
- // pick up the flag
- // if it's not a dropped flag, we just make is disappear
- // if it's dropped, it will be removed by the pickup caller
- if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED))
- {
- ent->flags |= FL_RESPAWN;
- ent->svflags |= SVF_NOCLIENT;
- ent->solid = SOLID_NOT;
- }
- return true;
- }
- TOUCH(CTFDropFlagTouch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- // owner (who dropped us) can't touch for two secs
- if (other == ent->owner &&
- ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT - 2_sec)
- return;
- Touch_Item(ent, other, tr, other_touching_self);
- }
- THINK(CTFDropFlagThink) (edict_t *ent) -> void
- {
- // auto return the flag
- // reset flag will remove ourselves
- if (ent->item->id == IT_FLAG1)
- {
- CTFResetFlag(CTF_TEAM1);
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned",
- CTFTeamName(CTF_TEAM1));
- }
- else if (ent->item->id == IT_FLAG2)
- {
- CTFResetFlag(CTF_TEAM2);
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned",
- CTFTeamName(CTF_TEAM2));
- }
- gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
- }
- // Called from PlayerDie, to drop the flag from a dying player
- void CTFDeadDropFlag(edict_t *self)
- {
- edict_t *dropped = nullptr;
- if (self->client->pers.inventory[IT_FLAG1])
- {
- dropped = Drop_Item(self, GetItemByIndex(IT_FLAG1));
- self->client->pers.inventory[IT_FLAG1] = 0;
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_lost_flag",
- self->client->pers.netname, CTFTeamName(CTF_TEAM1));
- }
- else if (self->client->pers.inventory[IT_FLAG2])
- {
- dropped = Drop_Item(self, GetItemByIndex(IT_FLAG2));
- self->client->pers.inventory[IT_FLAG2] = 0;
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_lost_flag",
- self->client->pers.netname, CTFTeamName(CTF_TEAM2));
- }
- if (dropped)
- {
- dropped->think = CTFDropFlagThink;
- dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT;
- dropped->touch = CTFDropFlagTouch;
- }
- }
- void CTFDrop_Flag(edict_t *ent, gitem_t *item)
- {
- if (brandom())
- gi.LocClient_Print(ent, PRINT_HIGH, "$g_lusers_drop_flags");
- else
- gi.LocClient_Print(ent, PRINT_HIGH, "$g_winners_drop_flags");
- }
- THINK(CTFFlagThink) (edict_t *ent) -> void
- {
- if (ent->solid != SOLID_NOT)
- ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16);
- ent->nextthink = level.time + 10_hz;
- }
- THINK(CTFFlagSetup) (edict_t *ent) -> void
- {
- trace_t tr;
- vec3_t dest;
- ent->mins = { -15, -15, -15 };
- ent->maxs = { 15, 15, 15 };
- if (ent->model)
- gi.setmodel(ent, ent->model);
- else
- gi.setmodel(ent, ent->item->world_model);
- ent->solid = SOLID_TRIGGER;
- ent->movetype = MOVETYPE_TOSS;
- ent->touch = Touch_Item;
- ent->s.frame = 173;
- dest = ent->s.origin + vec3_t { 0, 0, -128 };
- tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
- if (tr.startsolid)
- {
- gi.Com_PrintFmt("CTFFlagSetup: {} startsolid at {}\n", ent->classname, ent->s.origin);
- G_FreeEdict(ent);
- return;
- }
- ent->s.origin = tr.endpos;
- gi.linkentity(ent);
- ent->nextthink = level.time + 10_hz;
- ent->think = CTFFlagThink;
- }
- void CTFEffects(edict_t *player)
- {
- player->s.effects &= ~(EF_FLAG1 | EF_FLAG2);
- if (player->health > 0)
- {
- if (player->client->pers.inventory[IT_FLAG1])
- {
- player->s.effects |= EF_FLAG1;
- }
- if (player->client->pers.inventory[IT_FLAG2])
- {
- player->s.effects |= EF_FLAG2;
- }
- }
- if (player->client->pers.inventory[IT_FLAG1])
- player->s.modelindex3 = modelindex_flag1;
- else if (player->client->pers.inventory[IT_FLAG2])
- player->s.modelindex3 = modelindex_flag2;
- else
- player->s.modelindex3 = 0;
- }
- // called when we enter the intermission
- void CTFCalcScores()
- {
- ctfgame.total1 = ctfgame.total2 = 0;
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- if (!g_edicts[i + 1].inuse)
- continue;
- if (game.clients[i].resp.ctf_team == CTF_TEAM1)
- ctfgame.total1 += game.clients[i].resp.score;
- else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
- ctfgame.total2 += game.clients[i].resp.score;
- }
- }
- // [Paril-KEX] end game rankings
- void CTFCalcRankings(std::array<uint32_t, MAX_CLIENTS> &player_ranks)
- {
- // we're all winners.. or losers. whatever
- if (ctfgame.total1 == ctfgame.total2)
- {
- player_ranks.fill(1);
- return;
- }
- ctfteam_t winning_team = (ctfgame.total1 > ctfgame.total2) ? CTF_TEAM1 : CTF_TEAM2;
- for (auto player : active_players())
- if (player->client->pers.spawned && player->client->resp.ctf_team != CTF_NOTEAM)
- player_ranks[player->s.number - 1] = player->client->resp.ctf_team == winning_team ? 1 : 2;
- }
- void CheckEndTDMLevel()
- {
- if (ctfgame.total1 >= fraglimit->integer || ctfgame.total2 >= fraglimit->integer)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_fraglimit_hit");
- EndDMLevel();
- }
- }
- void CTFID_f(edict_t *ent)
- {
- if (ent->client->resp.id_state)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Disabling player identication display.\n");
- ent->client->resp.id_state = false;
- }
- else
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Activating player identication display.\n");
- ent->client->resp.id_state = true;
- }
- }
- static void CTFSetIDView(edict_t *ent)
- {
- vec3_t forward, dir;
- trace_t tr;
- edict_t *who, *best;
- float bd = 0, d;
- // only check every few frames
- if (level.time - ent->client->resp.lastidtime < 250_ms)
- return;
- ent->client->resp.lastidtime = level.time;
- ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
- ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
- AngleVectors(ent->client->v_angle, forward, nullptr, nullptr);
- forward *= 1024;
- forward = ent->s.origin + forward;
- tr = gi.traceline(ent->s.origin, forward, ent, MASK_SOLID);
- if (tr.fraction < 1 && tr.ent && tr.ent->client)
- {
- ent->client->ps.stats[STAT_CTF_ID_VIEW] = (tr.ent - g_edicts);
- if (tr.ent->client->resp.ctf_team == CTF_TEAM1)
- ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
- else if (tr.ent->client->resp.ctf_team == CTF_TEAM2)
- ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
- return;
- }
- AngleVectors(ent->client->v_angle, forward, nullptr, nullptr);
- best = nullptr;
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- who = g_edicts + i;
- if (!who->inuse || who->solid == SOLID_NOT)
- continue;
- dir = who->s.origin - ent->s.origin;
- dir.normalize();
- d = forward.dot(dir);
- // we have teammate indicators that are better for this
- if (ent->client->resp.ctf_team == who->client->resp.ctf_team)
- continue;
- if (d > bd && loc_CanSee(ent, who))
- {
- bd = d;
- best = who;
- }
- }
- if (bd > 0.90f)
- {
- ent->client->ps.stats[STAT_CTF_ID_VIEW] = (best - g_edicts);
- if (best->client->resp.ctf_team == CTF_TEAM1)
- ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
- else if (best->client->resp.ctf_team == CTF_TEAM2)
- ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
- }
- }
- void SetCTFStats(edict_t *ent)
- {
- uint32_t i;
- int p1, p2;
- edict_t *e;
- if (ctfgame.match > MATCH_NONE)
- ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH;
- else
- ent->client->ps.stats[STAT_CTF_MATCH] = 0;
- if (ctfgame.warnactive)
- ent->client->ps.stats[STAT_CTF_TEAMINFO] = CONFIG_CTF_TEAMINFO;
- else
- ent->client->ps.stats[STAT_CTF_TEAMINFO] = 0;
- // ghosting
- if (ent->client->resp.ghost)
- {
- ent->client->resp.ghost->score = ent->client->resp.score;
- Q_strlcpy(ent->client->resp.ghost->netname, ent->client->pers.netname, sizeof(ent->client->resp.ghost->netname));
- ent->client->resp.ghost->number = ent->s.number;
- }
- // logo headers for the frag display
- ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = imageindex_ctfsb1;
- ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = imageindex_ctfsb2;
- bool blink = (level.time.milliseconds() % 1000) < 500;
- // if during intermission, we must blink the team header of the winning team
- if (level.intermissiontime && blink)
- {
- // blink half second
- // note that ctfgame.total[12] is set when we go to intermission
- if (ctfgame.team1 > ctfgame.team2)
- ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
- else if (ctfgame.team2 > ctfgame.team1)
- ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
- else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
- ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
- else if (ctfgame.total2 > ctfgame.total1)
- ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
- else
- { // tie game!
- ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
- ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
- }
- }
- // tech icon
- i = 0;
- ent->client->ps.stats[STAT_CTF_TECH] = 0;
- for (; i < q_countof(tech_ids); i++)
- {
- if (ent->client->pers.inventory[tech_ids[i]])
- {
- ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(GetItemByIndex(tech_ids[i])->icon);
- break;
- }
- }
- if (ctf->integer)
- {
- // figure out what icon to display for team logos
- // three states:
- // flag at base
- // flag taken
- // flag dropped
- p1 = imageindex_i_ctf1;
- e = G_FindByString<&edict_t::classname>(nullptr, "item_flag_team1");
- if (e != nullptr)
- {
- if (e->solid == SOLID_NOT)
- {
- // not at base
- // check if on player
- p1 = imageindex_i_ctf1d; // default to dropped
- for (i = 1; i <= game.maxclients; i++)
- if (g_edicts[i].inuse &&
- g_edicts[i].client->pers.inventory[IT_FLAG1])
- {
- // enemy has it
- p1 = imageindex_i_ctf1t;
- break;
- }
- // [Paril-KEX] make sure there is a dropped version on the map somewhere
- if (p1 == imageindex_i_ctf1d)
- {
- e = G_FindByString<&edict_t::classname>(e, "item_flag_team1");
- if (e == nullptr)
- {
- CTFResetFlag(CTF_TEAM1);
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned",
- CTFTeamName(CTF_TEAM1));
- gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
- }
- }
- }
- else if (e->spawnflags.has(SPAWNFLAG_ITEM_DROPPED))
- p1 = imageindex_i_ctf1d; // must be dropped
- }
- p2 = imageindex_i_ctf2;
- e = G_FindByString<&edict_t::classname>(nullptr, "item_flag_team2");
- if (e != nullptr)
- {
- if (e->solid == SOLID_NOT)
- {
- // not at base
- // check if on player
- p2 = imageindex_i_ctf2d; // default to dropped
- for (i = 1; i <= game.maxclients; i++)
- if (g_edicts[i].inuse &&
- g_edicts[i].client->pers.inventory[IT_FLAG2])
- {
- // enemy has it
- p2 = imageindex_i_ctf2t;
- break;
- }
- // [Paril-KEX] make sure there is a dropped version on the map somewhere
- if (p2 == imageindex_i_ctf2d)
- {
- e = G_FindByString<&edict_t::classname>(e, "item_flag_team2");
- if (e == nullptr)
- {
- CTFResetFlag(CTF_TEAM2);
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned",
- CTFTeamName(CTF_TEAM2));
- gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
- }
- }
- }
- else if (e->spawnflags.has(SPAWNFLAG_ITEM_DROPPED))
- p2 = imageindex_i_ctf2d; // must be dropped
- }
- ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
- ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
- if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5_sec)
- {
- if (ctfgame.last_capture_team == CTF_TEAM1)
- if (blink)
- ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
- else
- ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0;
- else if (blink)
- ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
- else
- ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0;
- }
- ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1;
- ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2;
- ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0;
- if (ent->client->resp.ctf_team == CTF_TEAM1 &&
- ent->client->pers.inventory[IT_FLAG2] &&
- (blink))
- ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf2;
- else if (ent->client->resp.ctf_team == CTF_TEAM2 &&
- ent->client->pers.inventory[IT_FLAG1] &&
- (blink))
- ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf1;
- }
- else
- {
- ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = imageindex_i_ctf1;
- ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = imageindex_i_ctf2;
- ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.total1;
- ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.total2;
- }
- ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0;
- ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0;
- if (ent->client->resp.ctf_team == CTF_TEAM1)
- ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = imageindex_i_ctfj;
- else if (ent->client->resp.ctf_team == CTF_TEAM2)
- ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = imageindex_i_ctfj;
- if (ent->client->resp.id_state)
- CTFSetIDView(ent);
- else
- {
- ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
- ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
- }
- }
- /*------------------------------------------------------------------------*/
- /*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32)
- potential team1 spawning position for ctf games
- */
- void SP_info_player_team1(edict_t *self)
- {
- }
- /*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32)
- potential team2 spawning position for ctf games
- */
- void SP_info_player_team2(edict_t *self)
- {
- }
- /*------------------------------------------------------------------------*/
- /* GRAPPLE */
- /*------------------------------------------------------------------------*/
- // ent is player
- void CTFPlayerResetGrapple(edict_t *ent)
- {
- if (ent->client && ent->client->ctf_grapple)
- CTFResetGrapple(ent->client->ctf_grapple);
- }
- // self is grapple, not player
- void CTFResetGrapple(edict_t *self)
- {
- if (!self->owner->client->ctf_grapple)
- return;
-
- gi.sound(self->owner, CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), self->owner->client->silencer_shots ? 0.2f : 1.0f, ATTN_NORM, 0);
- gclient_t *cl;
- cl = self->owner->client;
- cl->ctf_grapple = nullptr;
- cl->ctf_grapplereleasetime = level.time + 1_sec;
- cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
- self->owner->flags &= ~FL_NO_KNOCKBACK;
- G_FreeEdict(self);
- }
- TOUCH(CTFGrappleTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- float volume = 1.0;
- if (other == self->owner)
- return;
- if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY)
- return;
- if (tr.surface && (tr.surface->flags & SURF_SKY))
- {
- CTFResetGrapple(self);
- return;
- }
- self->velocity = {};
- PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
- if (other->takedamage)
- {
- if (self->dmg)
- T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, self->dmg, 1, DAMAGE_NONE, MOD_GRAPPLE);
- CTFResetGrapple(self);
- return;
- }
- self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook
- self->enemy = other;
- self->solid = SOLID_NOT;
- if (self->owner->client->silencer_shots)
- volume = 0.2f;
- gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0);
- self->s.sound = gi.soundindex("weapons/grapple/grpull.wav");
- gi.WriteByte(svc_temp_entity);
- gi.WriteByte(TE_SPARKS);
- gi.WritePosition(self->s.origin);
- gi.WriteDir(tr.plane.normal);
- gi.multicast(self->s.origin, MULTICAST_PVS, false);
- }
- // draw beam between grapple and self
- void CTFGrappleDrawCable(edict_t *self)
- {
- if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_HANG)
- return;
- vec3_t start, dir;
- P_ProjectSource(self->owner, self->owner->client->v_angle, { 7, 2, -9 }, start, dir);
- gi.WriteByte(svc_temp_entity);
- gi.WriteByte(TE_GRAPPLE_CABLE_2);
- gi.WriteEntity(self->owner);
- gi.WritePosition(start);
- gi.WritePosition(self->s.origin);
- gi.multicast(self->s.origin, MULTICAST_PVS, false);
- }
- void SV_AddGravity(edict_t *ent);
- // pull the player toward the grapple
- void CTFGrapplePull(edict_t *self)
- {
- vec3_t hookdir, v;
- float vlen;
- if (self->owner->client->pers.weapon && self->owner->client->pers.weapon->id == IT_WEAPON_GRAPPLE &&
- !(self->owner->client->newweapon || ((self->owner->client->latched_buttons | self->owner->client->buttons) & BUTTON_HOLSTER)) &&
- self->owner->client->weaponstate != WEAPON_FIRING &&
- self->owner->client->weaponstate != WEAPON_ACTIVATING)
- {
- if (!self->owner->client->newweapon)
- self->owner->client->newweapon = self->owner->client->pers.weapon;
- CTFResetGrapple(self);
- return;
- }
- if (self->enemy)
- {
- if (self->enemy->solid == SOLID_NOT)
- {
- CTFResetGrapple(self);
- return;
- }
- if (self->enemy->solid == SOLID_BBOX)
- {
- v = self->enemy->size * 0.5f;
- v += self->enemy->s.origin;
- self->s.origin = v + self->enemy->mins;
- gi.linkentity(self);
- }
- else
- self->velocity = self->enemy->velocity;
- if (self->enemy->deadflag)
- { // he died
- CTFResetGrapple(self);
- return;
- }
- }
- CTFGrappleDrawCable(self);
- if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
- {
- // pull player toward grapple
- vec3_t forward, up;
- AngleVectors(self->owner->client->v_angle, forward, nullptr, up);
- v = self->owner->s.origin;
- v[2] += self->owner->viewheight;
- hookdir = self->s.origin - v;
- vlen = hookdir.length();
- if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL &&
- vlen < 64)
- {
- self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG;
- self->s.sound = gi.soundindex("weapons/grapple/grhang.wav");
- }
- hookdir.normalize();
- hookdir = hookdir * g_grapple_pull_speed->value;
- self->owner->velocity = hookdir;
- self->owner->flags |= FL_NO_KNOCKBACK;
- SV_AddGravity(self->owner);
- }
- }
- DIE(grapple_die) (edict_t *self, edict_t *other, edict_t *inflictor, int damage, const vec3_t &point, const mod_t &mod) -> void
- {
- if (mod.id == MOD_CRUSH)
- CTFResetGrapple(self);
- }
- bool CTFFireGrapple(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, effects_t effect)
- {
- edict_t *grapple;
- trace_t tr;
- vec3_t normalized = dir.normalized();
- grapple = G_Spawn();
- grapple->s.origin = start;
- grapple->s.old_origin = start;
- grapple->s.angles = vectoangles(normalized);
- grapple->velocity = normalized * speed;
- grapple->movetype = MOVETYPE_FLYMISSILE;
- grapple->clipmask = MASK_PROJECTILE;
- // [Paril-KEX]
- if (self->client && !G_ShouldPlayersCollide(true))
- grapple->clipmask &= ~CONTENTS_PLAYER;
- grapple->solid = SOLID_BBOX;
- grapple->s.effects |= effect;
- grapple->s.modelindex = gi.modelindex("models/weapons/grapple/hook/tris.md2");
- grapple->owner = self;
- grapple->touch = CTFGrappleTouch;
- grapple->dmg = damage;
- grapple->flags |= FL_NO_KNOCKBACK | FL_NO_DAMAGE_EFFECTS;
- grapple->takedamage = true;
- grapple->die = grapple_die;
- self->client->ctf_grapple = grapple;
- self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
- gi.linkentity(grapple);
- tr = gi.traceline(self->s.origin, grapple->s.origin, grapple, grapple->clipmask);
- if (tr.fraction < 1.0f)
- {
- grapple->s.origin = tr.endpos + (tr.plane.normal * 1.f);
- grapple->touch(grapple, tr.ent, tr, false);
- return false;
- }
- grapple->s.sound = gi.soundindex("weapons/grapple/grfly.wav");
- return true;
- }
- void CTFGrappleFire(edict_t *ent, const vec3_t &g_offset, int damage, effects_t effect)
- {
- float volume = 1.0;
- if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
- return; // it's already out
- vec3_t start, dir;
- P_ProjectSource(ent, ent->client->v_angle, vec3_t{ 24, 8, -8 + 2 } + g_offset, start, dir);
- if (ent->client->silencer_shots)
- volume = 0.2f;
- if (CTFFireGrapple(ent, start, dir, damage, g_grapple_fly_speed->value, effect))
- gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0);
- PlayerNoise(ent, start, PNOISE_WEAPON);
- }
- void CTFWeapon_Grapple_Fire(edict_t *ent)
- {
- CTFGrappleFire(ent, vec3_origin, g_grapple_damage->integer, EF_NONE);
- }
- void CTFWeapon_Grapple(edict_t *ent)
- {
- constexpr int pause_frames[] = { 10, 18, 27, 0 };
- constexpr int fire_frames[] = { 6, 0 };
- int prevstate;
- // if the the attack button is still down, stay in the firing frame
- if ((ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER)) &&
- ent->client->weaponstate == WEAPON_FIRING &&
- ent->client->ctf_grapple)
- ent->client->ps.gunframe = 6;
- if (!(ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER)) &&
- ent->client->ctf_grapple)
- {
- CTFResetGrapple(ent->client->ctf_grapple);
- if (ent->client->weaponstate == WEAPON_FIRING)
- ent->client->weaponstate = WEAPON_READY;
- }
- if ((ent->client->newweapon || ((ent->client->latched_buttons | ent->client->buttons) & BUTTON_HOLSTER)) &&
- ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY &&
- ent->client->weaponstate == WEAPON_FIRING)
- {
- // he wants to change weapons while grappled
- if (!ent->client->newweapon)
- ent->client->newweapon = ent->client->pers.weapon;
- ent->client->weaponstate = WEAPON_DROPPING;
- ent->client->ps.gunframe = 32;
- }
- prevstate = ent->client->weaponstate;
- Weapon_Generic(ent, 5, 10, 31, 36, pause_frames, fire_frames,
- CTFWeapon_Grapple_Fire);
- // if the the attack button is still down, stay in the firing frame
- if ((ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER)) &&
- ent->client->weaponstate == WEAPON_FIRING &&
- ent->client->ctf_grapple)
- ent->client->ps.gunframe = 6;
- // if we just switched back to grapple, immediately go to fire frame
- if (prevstate == WEAPON_ACTIVATING &&
- ent->client->weaponstate == WEAPON_READY &&
- ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
- {
- if (!(ent->client->buttons & (BUTTON_ATTACK | BUTTON_HOLSTER)))
- ent->client->ps.gunframe = 6;
- else
- ent->client->ps.gunframe = 5;
- ent->client->weaponstate = WEAPON_FIRING;
- }
- }
- void CTFDirtyTeamMenu()
- {
- for (auto player : active_players())
- if (player->client->menu)
- {
- player->client->menudirty = true;
- player->client->menutime = level.time;
- }
- }
- void CTFTeam_f(edict_t *ent)
- {
- if (!G_TeamplayEnabled())
- return;
- const char *t;
- ctfteam_t desired_team;
- t = gi.args();
- if (!*t)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "$g_you_are_on_team",
- CTFTeamName(ent->client->resp.ctf_team));
- return;
- }
- if (ctfgame.match > MATCH_SETUP)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "$g_cant_change_teams");
- return;
- }
- // [Paril-KEX] with force-join, don't allow us to switch
- // using this command.
- if (g_teamplay_force_join->integer)
- {
- if (!(ent->svflags & SVF_BOT))
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "$g_cant_change_teams");
- return;
- }
- }
- if (Q_strcasecmp(t, "red") == 0)
- desired_team = CTF_TEAM1;
- else if (Q_strcasecmp(t, "blue") == 0)
- desired_team = CTF_TEAM2;
- else
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "$g_unknown_team", t);
- return;
- }
- if (ent->client->resp.ctf_team == desired_team)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "$g_already_on_team",
- CTFTeamName(ent->client->resp.ctf_team));
- return;
- }
- ////
- ent->svflags = SVF_NONE;
- ent->flags &= ~FL_GODMODE;
- ent->client->resp.ctf_team = desired_team;
- ent->client->resp.ctf_state = 0;
- char value[MAX_INFO_VALUE] = { 0 };
- gi.Info_ValueForKey(ent->client->pers.userinfo, "skin", value, sizeof(value));
- CTFAssignSkin(ent, value);
- // if anybody has a menu open, update it immediately
- CTFDirtyTeamMenu();
- if (ent->solid == SOLID_NOT)
- {
- // spectator
- PutClientInServer(ent);
- G_PostRespawn(ent);
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_team",
- ent->client->pers.netname, CTFTeamName(desired_team));
- return;
- }
- ent->health = 0;
- player_die(ent, ent, ent, 100000, vec3_origin, { MOD_SUICIDE, true });
- // don't even bother waiting for death frames
- ent->deadflag = true;
- respawn(ent);
- ent->client->resp.score = 0;
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_changed_team",
- ent->client->pers.netname, CTFTeamName(desired_team));
- }
- constexpr size_t MAX_CTF_STAT_LENGTH = 1024;
- /*
- ==================
- CTFScoreboardMessage
- ==================
- */
- void CTFScoreboardMessage(edict_t *ent, edict_t *killer)
- {
- uint32_t i, j, k, n;
- uint32_t sorted[2][MAX_CLIENTS];
- int32_t sortedscores[2][MAX_CLIENTS];
- int score;
- uint32_t total[2];
- int totalscore[2];
- uint32_t last[2];
- gclient_t *cl;
- edict_t *cl_ent;
- int team;
- // sort the clients by team and score
- total[0] = total[1] = 0;
- last[0] = last[1] = 0;
- totalscore[0] = totalscore[1] = 0;
- for (i = 0; i < game.maxclients; i++)
- {
- cl_ent = g_edicts + 1 + i;
- if (!cl_ent->inuse)
- continue;
- if (game.clients[i].resp.ctf_team == CTF_TEAM1)
- team = 0;
- else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
- team = 1;
- else
- continue; // unknown team?
- score = game.clients[i].resp.score;
- for (j = 0; j < total[team]; j++)
- {
- if (score > sortedscores[team][j])
- break;
- }
- for (k = total[team]; k > j; k--)
- {
- sorted[team][k] = sorted[team][k - 1];
- sortedscores[team][k] = sortedscores[team][k - 1];
- }
- sorted[team][j] = i;
- sortedscores[team][j] = score;
- totalscore[team] += score;
- total[team]++;
- }
- // print level name and exit rules
- // add the clients in sorted order
- static std::string string;
- string.clear();
- // [Paril-KEX] time & frags
- if (teamplay->integer)
- {
- if (fraglimit->integer)
- {
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv -20 yv -10 loc_string2 1 $g_score_frags \"{}\" "), fraglimit->integer);
- }
- }
- else
- {
- if (capturelimit->integer)
- {
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv -20 yv -10 loc_string2 1 $g_score_captures \"{}\" "), capturelimit->integer);
- }
- }
- if (timelimit->value)
- {
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 time_limit {} "), gi.ServerFrame() + ((gtime_t::from_min(timelimit->value) - level.time)).milliseconds() / gi.frame_time_ms);
- }
- // team one
- if (teamplay->integer)
- {
- fmt::format_to(std::back_inserter(string),
- FMT_STRING("if 25 xv -32 yv 8 pic 25 endif "
- "xv -123 yv 28 cstring \"{}\" "
- "xv 41 yv 12 num 3 19 "
- "if 26 xv 208 yv 8 pic 26 endif "
- "xv 117 yv 28 cstring \"{}\" "
- "xv 280 yv 12 num 3 21 "),
- total[0],
- total[1]);
- }
- else
- {
- fmt::format_to(std::back_inserter(string),
- FMT_STRING("if 25 xv -32 yv 8 pic 25 endif "
- "xv 0 yv 28 string \"{:4}/{:<3}\" "
- "xv 58 yv 12 num 2 19 "
- "if 26 xv 208 yv 8 pic 26 endif "
- "xv 240 yv 28 string \"{:4}/{:<3}\" "
- "xv 296 yv 12 num 2 21 "),
- totalscore[0], total[0],
- totalscore[1], total[1]);
- }
- for (i = 0; i < 16; i++)
- {
- if (i >= total[0] && i >= total[1])
- break; // we're done
- // left side
- if (i < total[0])
- {
- cl = &game.clients[sorted[0][i]];
- cl_ent = g_edicts + 1 + sorted[0][i];
- std::string_view entry = G_Fmt("ctf -40 {} {} {} {} {} ",
- 42 + i * 8,
- sorted[0][i],
- cl->resp.score,
- cl->ping > 999 ? 999 : cl->ping,
- cl_ent->client->pers.inventory[IT_FLAG2] ? "sbfctf2" : "\"\"");
- if (string.size() + entry.size() < MAX_CTF_STAT_LENGTH)
- {
- string += entry;
- last[0] = i;
- }
- }
- // right side
- if (i < total[1])
- {
- cl = &game.clients[sorted[1][i]];
- cl_ent = g_edicts + 1 + sorted[1][i];
- std::string_view entry = G_Fmt("ctf 200 {} {} {} {} {} ",
- 42 + i * 8,
- sorted[1][i],
- cl->resp.score,
- cl->ping > 999 ? 999 : cl->ping,
- cl_ent->client->pers.inventory[IT_FLAG1] ? "sbfctf1" : "\"\"");
-
- if (string.size() + entry.size() < MAX_CTF_STAT_LENGTH)
- {
- string += entry;
- last[1] = i;
- }
- }
- }
- // put in spectators if we have enough room
- if (last[0] > last[1])
- j = last[0];
- else
- j = last[1];
- j = (j + 2) * 8 + 42;
- k = n = 0;
- if (string.size() < MAX_CTF_STAT_LENGTH - 50)
- {
- for (i = 0; i < game.maxclients; i++)
- {
- cl_ent = g_edicts + 1 + i;
- cl = &game.clients[i];
- if (!cl_ent->inuse ||
- cl_ent->solid != SOLID_NOT ||
- cl_ent->client->resp.ctf_team != CTF_NOTEAM)
- continue;
- if (!k)
- {
- k = 1;
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv {} loc_string2 0 \"$g_pc_spectators\" "), j);
- j += 8;
- }
- std::string_view entry = G_Fmt("ctf {} {} {} {} {} \"\" ",
- (n & 1) ? 200 : -40, // x
- j, // y
- i, // playernum
- cl->resp.score,
- cl->ping > 999 ? 999 : cl->ping);
- if (string.size() + entry.size() < MAX_CTF_STAT_LENGTH)
- string += entry;
- if (n & 1)
- j += 8;
- n++;
- }
- }
- if (total[0] - last[0] > 1) // couldn't fit everyone
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv -32 yv {} loc_string 1 $g_ctf_and_more {} "),
- 42 + (last[0] + 1) * 8, total[0] - last[0] - 1);
- if (total[1] - last[1] > 1) // couldn't fit everyone
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv 208 yv {} loc_string 1 $g_ctf_and_more {} "),
- 42 + (last[1] + 1) * 8, total[1] - last[1] - 1);
- if (level.intermissiontime)
- fmt::format_to(std::back_inserter(string), FMT_STRING("ifgef {} yb -48 xv 0 loc_cstring2 0 \"$m_eou_press_button\" endif "), (level.intermission_server_frame + (5_sec).frames()));
- gi.WriteByte(svc_layout);
- gi.WriteString(string.c_str());
- }
- /*------------------------------------------------------------------------*/
- /* TECH */
- /*------------------------------------------------------------------------*/
- void CTFHasTech(edict_t *who)
- {
- if (level.time - who->client->ctf_lasttechmsg > 2_sec)
- {
- gi.LocCenter_Print(who, "$g_already_have_tech");
- who->client->ctf_lasttechmsg = level.time;
- }
- }
- gitem_t *CTFWhat_Tech(edict_t *ent)
- {
- int i;
- i = 0;
- for (; i < q_countof(tech_ids); i++)
- {
- if (ent->client->pers.inventory[tech_ids[i]])
- {
- return GetItemByIndex(tech_ids[i]);
- }
- }
- return nullptr;
- }
- bool CTFPickup_Tech(edict_t *ent, edict_t *other)
- {
- int i;
- i = 0;
- for (; i < q_countof(tech_ids); i++)
- {
- if (other->client->pers.inventory[tech_ids[i]])
- {
- CTFHasTech(other);
- return false; // has this one
- }
- }
- // client only gets one tech
- other->client->pers.inventory[ent->item->id]++;
- other->client->ctf_regentime = level.time;
- return true;
- }
- static void SpawnTech(gitem_t *item, edict_t *spot);
- static edict_t *FindTechSpawn()
- {
- return SelectDeathmatchSpawnPoint(false, true, true).spot;
- }
- THINK(TechThink) (edict_t *tech) -> void
- {
- edict_t *spot;
- if ((spot = FindTechSpawn()) != nullptr)
- {
- SpawnTech(tech->item, spot);
- G_FreeEdict(tech);
- }
- else
- {
- tech->nextthink = level.time + CTF_TECH_TIMEOUT;
- tech->think = TechThink;
- }
- }
- void CTFDrop_Tech(edict_t *ent, gitem_t *item)
- {
- edict_t *tech;
- tech = Drop_Item(ent, item);
- tech->nextthink = level.time + CTF_TECH_TIMEOUT;
- tech->think = TechThink;
- ent->client->pers.inventory[item->id] = 0;
- }
- void CTFDeadDropTech(edict_t *ent)
- {
- edict_t *dropped;
- int i;
- i = 0;
- for (; i < q_countof(tech_ids); i++)
- {
- if (ent->client->pers.inventory[tech_ids[i]])
- {
- dropped = Drop_Item(ent, GetItemByIndex(tech_ids[i]));
- // hack the velocity to make it bounce random
- dropped->velocity[0] = crandom_open() * 300;
- dropped->velocity[1] = crandom_open() * 300;
- dropped->nextthink = level.time + CTF_TECH_TIMEOUT;
- dropped->think = TechThink;
- dropped->owner = nullptr;
- ent->client->pers.inventory[tech_ids[i]] = 0;
- }
- }
- }
- static void SpawnTech(gitem_t *item, edict_t *spot)
- {
- edict_t *ent;
- vec3_t forward, right;
- vec3_t angles;
- ent = G_Spawn();
- ent->classname = item->classname;
- ent->item = item;
- ent->spawnflags = SPAWNFLAG_ITEM_DROPPED;
- ent->s.effects = item->world_model_flags;
- ent->s.renderfx = RF_GLOW | RF_NO_LOD;
- ent->mins = { -15, -15, -15 };
- ent->maxs = { 15, 15, 15 };
- gi.setmodel(ent, ent->item->world_model);
- ent->solid = SOLID_TRIGGER;
- ent->movetype = MOVETYPE_TOSS;
- ent->touch = Touch_Item;
- ent->owner = ent;
- angles[0] = 0;
- angles[1] = (float) irandom(360);
- angles[2] = 0;
- AngleVectors(angles, forward, right, nullptr);
- ent->s.origin = spot->s.origin;
- ent->s.origin[2] += 16;
- ent->velocity = forward * 100;
- ent->velocity[2] = 300;
- ent->nextthink = level.time + CTF_TECH_TIMEOUT;
- ent->think = TechThink;
- gi.linkentity(ent);
- }
- THINK(SpawnTechs) (edict_t *ent) -> void
- {
- edict_t *spot;
- int i;
- i = 0;
- for (; i < q_countof(tech_ids); i++)
- {
- if ((spot = FindTechSpawn()) != nullptr)
- SpawnTech(GetItemByIndex(tech_ids[i]), spot);
- }
- if (ent)
- G_FreeEdict(ent);
- }
- // frees the passed edict!
- void CTFRespawnTech(edict_t *ent)
- {
- edict_t *spot;
- if ((spot = FindTechSpawn()) != nullptr)
- SpawnTech(ent->item, spot);
- G_FreeEdict(ent);
- }
- void CTFSetupTechSpawn()
- {
- edict_t *ent;
- bool techs_allowed;
- // [Paril-KEX]
- if (!strcmp(g_allow_techs->string, "auto"))
- techs_allowed = !!ctf->integer;
- else
- techs_allowed = !!g_allow_techs->integer;
- if (!techs_allowed)
- return;
- ent = G_Spawn();
- ent->nextthink = level.time + 2_sec;
- ent->think = SpawnTechs;
- }
- void CTFResetTech()
- {
- edict_t *ent;
- uint32_t i;
- for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++)
- {
- if (ent->inuse)
- if (ent->item && (ent->item->flags & IF_TECH))
- G_FreeEdict(ent);
- }
- SpawnTechs(nullptr);
- }
- int CTFApplyResistance(edict_t *ent, int dmg)
- {
- float volume = 1.0;
- if (ent->client && ent->client->silencer_shots)
- volume = 0.2f;
- if (dmg && ent->client && ent->client->pers.inventory[IT_TECH_RESISTANCE])
- {
- // make noise
- gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0);
- return dmg / 2;
- }
- return dmg;
- }
- int CTFApplyStrength(edict_t *ent, int dmg)
- {
- if (dmg && ent->client && ent->client->pers.inventory[IT_TECH_STRENGTH])
- {
- return dmg * 2;
- }
- return dmg;
- }
- bool CTFApplyStrengthSound(edict_t *ent)
- {
- float volume = 1.0;
- if (ent->client && ent->client->silencer_shots)
- volume = 0.2f;
- if (ent->client &&
- ent->client->pers.inventory[IT_TECH_STRENGTH])
- {
- if (ent->client->ctf_techsndtime < level.time)
- {
- ent->client->ctf_techsndtime = level.time + 1_sec;
- if (ent->client->quad_time > level.time)
- gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0);
- else
- gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0);
- }
- return true;
- }
- return false;
- }
- bool CTFApplyHaste(edict_t *ent)
- {
- if (ent->client &&
- ent->client->pers.inventory[IT_TECH_HASTE])
- return true;
- return false;
- }
- void CTFApplyHasteSound(edict_t *ent)
- {
- float volume = 1.0;
- if (ent->client && ent->client->silencer_shots)
- volume = 0.2f;
- if (ent->client &&
- ent->client->pers.inventory[IT_TECH_HASTE] &&
- ent->client->ctf_techsndtime < level.time)
- {
- ent->client->ctf_techsndtime = level.time + 1_sec;
- gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0);
- }
- }
- void CTFApplyRegeneration(edict_t *ent)
- {
- bool noise = false;
- gclient_t *client;
- int index;
- float volume = 1.0;
- client = ent->client;
- if (!client)
- return;
- if (ent->client->silencer_shots)
- volume = 0.2f;
- if (client->pers.inventory[IT_TECH_REGENERATION])
- {
- if (client->ctf_regentime < level.time)
- {
- client->ctf_regentime = level.time;
- if (ent->health < 150)
- {
- ent->health += 5;
- if (ent->health > 150)
- ent->health = 150;
- client->ctf_regentime += 500_ms;
- noise = true;
- }
- index = ArmorIndex(ent);
- if (index && client->pers.inventory[index] < 150)
- {
- client->pers.inventory[index] += 5;
- if (client->pers.inventory[index] > 150)
- client->pers.inventory[index] = 150;
- client->ctf_regentime += 500_ms;
- noise = true;
- }
- }
- if (noise && ent->client->ctf_techsndtime < level.time)
- {
- ent->client->ctf_techsndtime = level.time + 1_sec;
- gi.sound(ent, CHAN_AUX, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0);
- }
- }
- }
- bool CTFHasRegeneration(edict_t *ent)
- {
- if (ent->client &&
- ent->client->pers.inventory[IT_TECH_REGENERATION])
- return true;
- return false;
- }
- void CTFSay_Team(edict_t *who, const char *msg_in)
- {
- edict_t *cl_ent;
- char outmsg[256];
- if (CheckFlood(who))
- return;
- Q_strlcpy(outmsg, msg_in, sizeof(outmsg));
- char *msg = outmsg;
- if (*msg == '\"')
- {
- msg[strlen(msg) - 1] = 0;
- msg++;
- }
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- cl_ent = g_edicts + 1 + i;
- if (!cl_ent->inuse)
- continue;
- if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team)
- gi.LocClient_Print(cl_ent, PRINT_CHAT, "({}): {}\n",
- who->client->pers.netname, msg);
- }
- }
- /*-----------------------------------------------------------------------*/
- /*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2
- The origin is the bottom of the banner.
- The banner is 248 tall.
- */
- THINK(misc_ctf_banner_think) (edict_t *ent) -> void
- {
- ent->s.frame = (ent->s.frame + 1) % 16;
- ent->nextthink = level.time + 10_hz;
- }
- constexpr spawnflags_t SPAWNFLAG_CTF_BANNER_BLUE = 1_spawnflag;
- void SP_misc_ctf_banner(edict_t *ent)
- {
- ent->movetype = MOVETYPE_NONE;
- ent->solid = SOLID_NOT;
- ent->s.modelindex = gi.modelindex("models/ctf/banner/tris.md2");
- if (ent->spawnflags.has(SPAWNFLAG_CTF_BANNER_BLUE)) // team2
- ent->s.skinnum = 1;
- ent->s.frame = irandom(16);
- gi.linkentity(ent);
- ent->think = misc_ctf_banner_think;
- ent->nextthink = level.time + 10_hz;
- }
- /*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2
- The origin is the bottom of the banner.
- The banner is 124 tall.
- */
- void SP_misc_ctf_small_banner(edict_t *ent)
- {
- ent->movetype = MOVETYPE_NONE;
- ent->solid = SOLID_NOT;
- ent->s.modelindex = gi.modelindex("models/ctf/banner/small.md2");
- if (ent->spawnflags.has(SPAWNFLAG_CTF_BANNER_BLUE)) // team2
- ent->s.skinnum = 1;
- ent->s.frame = irandom(16);
- gi.linkentity(ent);
- ent->think = misc_ctf_banner_think;
- ent->nextthink = level.time + 10_hz;
- }
- /*-----------------------------------------------------------------------*/
- static void SetGameName(pmenu_t *p)
- {
- if (ctf->integer)
- Q_strlcpy(p->text, "$g_pc_3wctf", sizeof(p->text));
- else
- Q_strlcpy(p->text, "$g_pc_teamplay", sizeof(p->text));
- }
- static void SetLevelName(pmenu_t *p)
- {
- static char levelname[33];
- levelname[0] = '*';
- if (g_edicts[0].message)
- Q_strlcpy(levelname + 1, g_edicts[0].message, sizeof(levelname) - 1);
- else
- Q_strlcpy(levelname + 1, level.mapname, sizeof(levelname) - 1);
- levelname[sizeof(levelname) - 1] = 0;
- Q_strlcpy(p->text, levelname, sizeof(p->text));
- }
- /*-----------------------------------------------------------------------*/
- /* ELECTIONS */
- bool CTFBeginElection(edict_t *ent, elect_t type, const char *msg)
- {
- int count;
- edict_t *e;
- if (electpercentage->value == 0)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Elections are disabled, only an admin can process this action.\n");
- return false;
- }
- if (ctfgame.election != ELECT_NONE)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Election already in progress.\n");
- return false;
- }
- // clear votes
- count = 0;
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- e = g_edicts + i;
- e->client->resp.voted = false;
- if (e->inuse)
- count++;
- }
- if (count < 2)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Not enough players for election.\n");
- return false;
- }
- ctfgame.etarget = ent;
- ctfgame.election = type;
- ctfgame.evotes = 0;
- ctfgame.needvotes = (int) ((count * electpercentage->value) / 100);
- ctfgame.electtime = level.time + 20_sec; // twenty seconds for election
- Q_strlcpy(ctfgame.emsg, msg, sizeof(ctfgame.emsg));
- // tell everyone
- gi.Broadcast_Print(PRINT_CHAT, ctfgame.emsg);
- gi.LocBroadcast_Print(PRINT_HIGH, "Type YES or NO to vote on this request.\n");
- gi.LocBroadcast_Print(PRINT_HIGH, "Votes: {} Needed: {} Time left: {}s\n", ctfgame.evotes, ctfgame.needvotes,
- (ctfgame.electtime - level.time).seconds<int>());
- return true;
- }
- void DoRespawn(edict_t *ent);
- void CTFResetAllPlayers()
- {
- uint32_t i;
- edict_t *ent;
- for (i = 1; i <= game.maxclients; i++)
- {
- ent = g_edicts + i;
- if (!ent->inuse)
- continue;
- if (ent->client->menu)
- PMenu_Close(ent);
- CTFPlayerResetGrapple(ent);
- CTFDeadDropFlag(ent);
- CTFDeadDropTech(ent);
- ent->client->resp.ctf_team = CTF_NOTEAM;
- ent->client->resp.ready = false;
- ent->svflags = SVF_NONE;
- ent->flags &= ~FL_GODMODE;
- PutClientInServer(ent);
- }
- // reset the level
- CTFResetTech();
- CTFResetFlags();
- for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++)
- {
- if (ent->inuse && !ent->client)
- {
- if (ent->solid == SOLID_NOT && ent->think == DoRespawn &&
- ent->nextthink >= level.time)
- {
- ent->nextthink = 0_ms;
- DoRespawn(ent);
- }
- }
- }
- if (ctfgame.match == MATCH_SETUP)
- ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value);
- }
- void CTFAssignGhost(edict_t *ent)
- {
- int ghost, i;
- for (ghost = 0; ghost < MAX_CLIENTS; ghost++)
- if (!ctfgame.ghosts[ghost].code)
- break;
- if (ghost == MAX_CLIENTS)
- return;
- ctfgame.ghosts[ghost].team = ent->client->resp.ctf_team;
- ctfgame.ghosts[ghost].score = 0;
- for (;;)
- {
- ctfgame.ghosts[ghost].code = irandom(10000, 100000);
- for (i = 0; i < MAX_CLIENTS; i++)
- if (i != ghost && ctfgame.ghosts[i].code == ctfgame.ghosts[ghost].code)
- break;
- if (i == MAX_CLIENTS)
- break;
- }
- ctfgame.ghosts[ghost].ent = ent;
- Q_strlcpy(ctfgame.ghosts[ghost].netname, ent->client->pers.netname, sizeof(ctfgame.ghosts[ghost].netname));
- ent->client->resp.ghost = ctfgame.ghosts + ghost;
- gi.LocClient_Print(ent, PRINT_CHAT, "Your ghost code is **** {} ****\n", ctfgame.ghosts[ghost].code);
- gi.LocClient_Print(ent, PRINT_HIGH, "If you lose connection, you can rejoin with your score intact by typing \"ghost {}\".\n",
- ctfgame.ghosts[ghost].code);
- }
- // start a match
- void CTFStartMatch()
- {
- edict_t *ent;
- ctfgame.match = MATCH_GAME;
- ctfgame.matchtime = level.time + gtime_t::from_min(matchtime->value);
- ctfgame.countdown = false;
- ctfgame.team1 = ctfgame.team2 = 0;
- memset(ctfgame.ghosts, 0, sizeof(ctfgame.ghosts));
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- ent = g_edicts + i;
- if (!ent->inuse)
- continue;
- ent->client->resp.score = 0;
- ent->client->resp.ctf_state = 0;
- ent->client->resp.ghost = nullptr;
- gi.LocCenter_Print(ent, "******************\n\nMATCH HAS STARTED!\n\n******************");
- if (ent->client->resp.ctf_team != CTF_NOTEAM)
- {
- // make up a ghost code
- CTFAssignGhost(ent);
- CTFPlayerResetGrapple(ent);
- ent->svflags = SVF_NOCLIENT;
- ent->flags &= ~FL_GODMODE;
- ent->client->respawn_time = level.time + random_time(1_sec, 4_sec);
- ent->client->ps.pmove.pm_type = PM_DEAD;
- ent->client->anim_priority = ANIM_DEATH;
- ent->s.frame = FRAME_death308 - 1;
- ent->client->anim_end = FRAME_death308;
- ent->deadflag = true;
- ent->movetype = MOVETYPE_NOCLIP;
- ent->client->ps.gunindex = 0;
- ent->client->ps.gunskin = 0;
- gi.linkentity(ent);
- }
- }
- }
- void CTFEndMatch()
- {
- ctfgame.match = MATCH_POST;
- gi.LocBroadcast_Print(PRINT_CHAT, "MATCH COMPLETED!\n");
- CTFCalcScores();
- gi.LocBroadcast_Print(PRINT_HIGH, "RED TEAM: {} captures, {} points\n",
- ctfgame.team1, ctfgame.total1);
- gi.LocBroadcast_Print(PRINT_HIGH, "BLUE TEAM: {} captures, {} points\n",
- ctfgame.team2, ctfgame.total2);
- if (ctfgame.team1 > ctfgame.team2)
- gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_red_wins_caps",
- ctfgame.team1 - ctfgame.team2);
- else if (ctfgame.team2 > ctfgame.team1)
- gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_blue_wins_caps",
- ctfgame.team2 - ctfgame.team1);
- else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
- gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_red_wins_points",
- ctfgame.total1 - ctfgame.total2);
- else if (ctfgame.total2 > ctfgame.total1)
- gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_blue_wins_points",
- ctfgame.total2 - ctfgame.total1);
- else
- gi.LocBroadcast_Print(PRINT_CHAT, "$g_ctf_tie_game");
- EndDMLevel();
- }
- bool CTFNextMap()
- {
- if (ctfgame.match == MATCH_POST)
- {
- ctfgame.match = MATCH_SETUP;
- CTFResetAllPlayers();
- return true;
- }
- return false;
- }
- void CTFWinElection()
- {
- switch (ctfgame.election)
- {
- case ELECT_MATCH:
- // reset into match mode
- if (competition->integer < 3)
- gi.cvar_set("competition", "2");
- ctfgame.match = MATCH_SETUP;
- CTFResetAllPlayers();
- break;
- case ELECT_ADMIN:
- ctfgame.etarget->client->resp.admin = true;
- gi.LocBroadcast_Print(PRINT_HIGH, "{} has become an admin.\n", ctfgame.etarget->client->pers.netname);
- gi.LocClient_Print(ctfgame.etarget, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
- break;
- case ELECT_MAP:
- gi.LocBroadcast_Print(PRINT_HIGH, "{} is warping to level {}.\n",
- ctfgame.etarget->client->pers.netname, ctfgame.elevel);
- Q_strlcpy(level.forcemap, ctfgame.elevel, sizeof(level.forcemap));
- EndDMLevel();
- break;
- default:
- break;
- }
- ctfgame.election = ELECT_NONE;
- }
- void CTFVoteYes(edict_t *ent)
- {
- if (ctfgame.election == ELECT_NONE)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "No election is in progress.\n");
- return;
- }
- if (ent->client->resp.voted)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You already voted.\n");
- return;
- }
- if (ctfgame.etarget == ent)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You can't vote for yourself.\n");
- return;
- }
- ent->client->resp.voted = true;
- ctfgame.evotes++;
- if (ctfgame.evotes == ctfgame.needvotes)
- {
- // the election has been won
- CTFWinElection();
- return;
- }
- gi.LocBroadcast_Print(PRINT_HIGH, "{}\n", ctfgame.emsg);
- gi.LocBroadcast_Print(PRINT_CHAT, "Votes: {} Needed: {} Time left: {}s\n", ctfgame.evotes, ctfgame.needvotes,
- (ctfgame.electtime - level.time).seconds<int>());
- }
- void CTFVoteNo(edict_t *ent)
- {
- if (ctfgame.election == ELECT_NONE)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "No election is in progress.\n");
- return;
- }
- if (ent->client->resp.voted)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You already voted.\n");
- return;
- }
- if (ctfgame.etarget == ent)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You can't vote for yourself.\n");
- return;
- }
- ent->client->resp.voted = true;
- gi.LocBroadcast_Print(PRINT_HIGH, "{}\n", ctfgame.emsg);
- gi.LocBroadcast_Print(PRINT_CHAT, "Votes: {} Needed: {} Time left: {}s\n", ctfgame.evotes, ctfgame.needvotes,
- (ctfgame.electtime - level.time).seconds<int>());
- }
- void CTFReady(edict_t *ent)
- {
- uint32_t i, j;
- edict_t *e;
- uint32_t t1, t2;
- if (ent->client->resp.ctf_team == CTF_NOTEAM)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
- return;
- }
- if (ctfgame.match != MATCH_SETUP)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "A match is not being setup.\n");
- return;
- }
- if (ent->client->resp.ready)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You have already commited.\n");
- return;
- }
- ent->client->resp.ready = true;
- gi.LocBroadcast_Print(PRINT_HIGH, "{} is ready.\n", ent->client->pers.netname);
- t1 = t2 = 0;
- for (j = 0, i = 1; i <= game.maxclients; i++)
- {
- e = g_edicts + i;
- if (!e->inuse)
- continue;
- if (e->client->resp.ctf_team != CTF_NOTEAM && !e->client->resp.ready)
- j++;
- if (e->client->resp.ctf_team == CTF_TEAM1)
- t1++;
- else if (e->client->resp.ctf_team == CTF_TEAM2)
- t2++;
- }
- if (!j && t1 && t2)
- {
- // everyone has commited
- gi.LocBroadcast_Print(PRINT_CHAT, "All players have committed. Match starting\n");
- ctfgame.match = MATCH_PREGAME;
- ctfgame.matchtime = level.time + gtime_t::from_sec(matchstarttime->value);
- ctfgame.countdown = false;
- gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
- }
- }
- void CTFNotReady(edict_t *ent)
- {
- if (ent->client->resp.ctf_team == CTF_NOTEAM)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
- return;
- }
- if (ctfgame.match != MATCH_SETUP && ctfgame.match != MATCH_PREGAME)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "A match is not being setup.\n");
- return;
- }
- if (!ent->client->resp.ready)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You haven't commited.\n");
- return;
- }
- ent->client->resp.ready = false;
- gi.LocBroadcast_Print(PRINT_HIGH, "{} is no longer ready.\n", ent->client->pers.netname);
- if (ctfgame.match == MATCH_PREGAME)
- {
- gi.LocBroadcast_Print(PRINT_CHAT, "Match halted.\n");
- ctfgame.match = MATCH_SETUP;
- ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value);
- }
- }
- void CTFGhost(edict_t *ent)
- {
- int i;
- int n;
- if (gi.argc() < 2)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Usage: ghost <code>\n");
- return;
- }
- if (ent->client->resp.ctf_team != CTF_NOTEAM)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You are already in the game.\n");
- return;
- }
- if (ctfgame.match != MATCH_GAME)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "No match is in progress.\n");
- return;
- }
- n = atoi(gi.argv(1));
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- if (ctfgame.ghosts[i].code && ctfgame.ghosts[i].code == n)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n");
- ctfgame.ghosts[i].ent->client->resp.ghost = nullptr;
- ent->client->resp.ctf_team = ctfgame.ghosts[i].team;
- ent->client->resp.ghost = ctfgame.ghosts + i;
- ent->client->resp.score = ctfgame.ghosts[i].score;
- ent->client->resp.ctf_state = 0;
- ctfgame.ghosts[i].ent = ent;
- ent->svflags = SVF_NONE;
- ent->flags &= ~FL_GODMODE;
- PutClientInServer(ent);
- gi.LocBroadcast_Print(PRINT_HIGH, "{} has been reinstated to {} team.\n",
- ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team));
- return;
- }
- }
- gi.LocClient_Print(ent, PRINT_HIGH, "Invalid ghost code.\n");
- }
- bool CTFMatchSetup()
- {
- if (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME)
- return true;
- return false;
- }
- bool CTFMatchOn()
- {
- if (ctfgame.match == MATCH_GAME)
- return true;
- return false;
- }
- /*-----------------------------------------------------------------------*/
- void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p);
- void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p);
- void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p);
- void CTFChaseCam(edict_t *ent, pmenuhnd_t *p);
- static const int jmenu_level = 1;
- static const int jmenu_match = 2;
- static const int jmenu_red = 4;
- static const int jmenu_blue = 7;
- static const int jmenu_chase = 10;
- static const int jmenu_reqmatch = 12;
- const pmenu_t joinmenu[] = {
- { "*$g_pc_3wctf", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr },
- { "$g_pc_join_red_team", PMENU_ALIGN_LEFT, CTFJoinTeam1 },
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "$g_pc_join_blue_team", PMENU_ALIGN_LEFT, CTFJoinTeam2 },
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "$g_pc_chase_camera", PMENU_ALIGN_LEFT, CTFChaseCam },
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "", PMENU_ALIGN_LEFT, nullptr },
- };
- const pmenu_t nochasemenu[] = {
- { "$g_pc_3wctf", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr },
- { "$g_pc_no_chase", PMENU_ALIGN_LEFT, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr },
- { "$g_pc_return", PMENU_ALIGN_LEFT, CTFReturnToMain }
- };
- void CTFJoinTeam(edict_t *ent, ctfteam_t desired_team)
- {
- PMenu_Close(ent);
- ent->svflags &= ~SVF_NOCLIENT;
- ent->client->resp.ctf_team = desired_team;
- ent->client->resp.ctf_state = 0;
- char value[MAX_INFO_VALUE] = { 0 };
- gi.Info_ValueForKey(ent->client->pers.userinfo, "skin", value, sizeof(value));
- CTFAssignSkin(ent, value);
- // assign a ghost if we are in match mode
- if (ctfgame.match == MATCH_GAME)
- {
- if (ent->client->resp.ghost)
- ent->client->resp.ghost->code = 0;
- ent->client->resp.ghost = nullptr;
- CTFAssignGhost(ent);
- }
- PutClientInServer(ent);
- G_PostRespawn(ent);
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_team",
- ent->client->pers.netname, CTFTeamName(desired_team));
- if (ctfgame.match == MATCH_SETUP)
- {
- gi.LocCenter_Print(ent, "Type \"ready\" in console to ready up.\n");
- }
- // if anybody has a menu open, update it immediately
- CTFDirtyTeamMenu();
- }
- void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p)
- {
- CTFJoinTeam(ent, CTF_TEAM1);
- }
- void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p)
- {
- CTFJoinTeam(ent, CTF_TEAM2);
- }
- static void CTFNoChaseCamUpdate(edict_t *ent)
- {
- pmenu_t *entries = ent->client->menu->entries;
- SetGameName(&entries[0]);
- SetLevelName(&entries[jmenu_level]);
- }
- void CTFChaseCam(edict_t *ent, pmenuhnd_t *p)
- {
- edict_t *e;
- CTFJoinTeam(ent, CTF_NOTEAM);
- if (ent->client->chase_target)
- {
- ent->client->chase_target = nullptr;
- ent->client->ps.pmove.pm_flags &= ~(PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION);
- PMenu_Close(ent);
- return;
- }
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- e = g_edicts + i;
- if (e->inuse && e->solid != SOLID_NOT)
- {
- ent->client->chase_target = e;
- PMenu_Close(ent);
- ent->client->update_chase = true;
- return;
- }
- }
- PMenu_Close(ent);
- PMenu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(pmenu_t), nullptr, CTFNoChaseCamUpdate);
- }
- void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p)
- {
- PMenu_Close(ent);
- CTFOpenJoinMenu(ent);
- }
- void CTFRequestMatch(edict_t *ent, pmenuhnd_t *p)
- {
- PMenu_Close(ent);
- CTFBeginElection(ent, ELECT_MATCH, G_Fmt("{} has requested to switch to competition mode.\n",
- ent->client->pers.netname).data());
- }
- void DeathmatchScoreboard(edict_t *ent);
- void CTFShowScores(edict_t *ent, pmenu_t *p)
- {
- PMenu_Close(ent);
- ent->client->showscores = true;
- ent->client->showinventory = false;
- DeathmatchScoreboard(ent);
- }
- void CTFUpdateJoinMenu(edict_t *ent)
- {
- pmenu_t *entries = ent->client->menu->entries;
- SetGameName(entries);
- if (ctfgame.match >= MATCH_PREGAME && matchlock->integer)
- {
- Q_strlcpy(entries[jmenu_red].text, "MATCH IS LOCKED", sizeof(entries[jmenu_red].text));
- entries[jmenu_red].SelectFunc = nullptr;
- Q_strlcpy(entries[jmenu_blue].text, " (entry is not permitted)", sizeof(entries[jmenu_blue].text));
- entries[jmenu_blue].SelectFunc = nullptr;
- }
- else
- {
- if (ctfgame.match >= MATCH_PREGAME)
- {
- Q_strlcpy(entries[jmenu_red].text, "Join Red MATCH Team", sizeof(entries[jmenu_red].text));
- Q_strlcpy(entries[jmenu_blue].text, "Join Blue MATCH Team", sizeof(entries[jmenu_blue].text));
- }
- else
- {
- Q_strlcpy(entries[jmenu_red].text, "$g_pc_join_red_team", sizeof(entries[jmenu_red].text));
- Q_strlcpy(entries[jmenu_blue].text, "$g_pc_join_blue_team", sizeof(entries[jmenu_blue].text));
- }
- entries[jmenu_red].SelectFunc = CTFJoinTeam1;
- entries[jmenu_blue].SelectFunc = CTFJoinTeam2;
- }
- // KEX_FIXME: what's this for?
- if (g_teamplay_force_join->string && *g_teamplay_force_join->string)
- {
- if (Q_strcasecmp(g_teamplay_force_join->string, "red") == 0)
- {
- entries[jmenu_blue].text[0] = '\0';
- entries[jmenu_blue].SelectFunc = nullptr;
- }
- else if (Q_strcasecmp(g_teamplay_force_join->string, "blue") == 0)
- {
- entries[jmenu_red].text[0] = '\0';
- entries[jmenu_red].SelectFunc = nullptr;
- }
- }
- if (ent->client->chase_target)
- Q_strlcpy(entries[jmenu_chase].text, "$g_pc_leave_chase_camera", sizeof(entries[jmenu_chase].text));
- else
- Q_strlcpy(entries[jmenu_chase].text, "$g_pc_chase_camera", sizeof(entries[jmenu_chase].text));
- SetLevelName(entries + jmenu_level);
- uint32_t num1 = 0, num2 = 0;
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- if (!g_edicts[i + 1].inuse)
- continue;
- if (game.clients[i].resp.ctf_team == CTF_TEAM1)
- num1++;
- else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
- num2++;
- }
- switch (ctfgame.match)
- {
- case MATCH_NONE:
- entries[jmenu_match].text[0] = '\0';
- break;
- case MATCH_SETUP:
- Q_strlcpy(entries[jmenu_match].text, "*MATCH SETUP IN PROGRESS", sizeof(entries[jmenu_match].text));
- break;
- case MATCH_PREGAME:
- Q_strlcpy(entries[jmenu_match].text, "*MATCH STARTING", sizeof(entries[jmenu_match].text));
- break;
- case MATCH_GAME:
- Q_strlcpy(entries[jmenu_match].text, "*MATCH IN PROGRESS", sizeof(entries[jmenu_match].text));
- break;
- default:
- break;
- }
- if (*entries[jmenu_red].text)
- {
- Q_strlcpy(entries[jmenu_red + 1].text, "$g_pc_playercount", sizeof(entries[jmenu_red + 1].text));
- G_FmtTo(entries[jmenu_red + 1].text_arg1, "{}", num1);
- }
- else
- {
- entries[jmenu_red + 1].text[0] = '\0';
- entries[jmenu_red + 1].text_arg1[0] = '\0';
- }
- if (*entries[jmenu_blue].text)
- {
- Q_strlcpy(entries[jmenu_blue + 1].text, "$g_pc_playercount", sizeof(entries[jmenu_blue + 1].text));
- G_FmtTo(entries[jmenu_blue + 1].text_arg1, "{}", num2);
- }
- else
- {
- entries[jmenu_blue + 1].text[0] = '\0';
- entries[jmenu_blue + 1].text_arg1[0] = '\0';
- }
- entries[jmenu_reqmatch].text[0] = '\0';
- entries[jmenu_reqmatch].SelectFunc = nullptr;
- if (competition->integer && ctfgame.match < MATCH_SETUP)
- {
- Q_strlcpy(entries[jmenu_reqmatch].text, "Request Match", sizeof(entries[jmenu_reqmatch].text));
- entries[jmenu_reqmatch].SelectFunc = CTFRequestMatch;
- }
- }
- void CTFOpenJoinMenu(edict_t *ent)
- {
- uint32_t num1 = 0, num2 = 0;
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- if (!g_edicts[i + 1].inuse)
- continue;
- if (game.clients[i].resp.ctf_team == CTF_TEAM1)
- num1++;
- else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
- num2++;
- }
- int team;
- if (num1 > num2)
- team = CTF_TEAM1;
- else if (num2 > num1)
- team = CTF_TEAM2;
- team = brandom() ? CTF_TEAM1 : CTF_TEAM2;
- PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), nullptr, CTFUpdateJoinMenu);
- }
- bool CTFStartClient(edict_t *ent)
- {
- if (!G_TeamplayEnabled())
- return false;
- if (ent->client->resp.ctf_team != CTF_NOTEAM)
- return false;
- if ((!(ent->svflags & SVF_BOT) && !g_teamplay_force_join->integer) || ctfgame.match >= MATCH_SETUP)
- {
- // start as 'observer'
- ent->movetype = MOVETYPE_NOCLIP;
- ent->solid = SOLID_NOT;
- ent->svflags |= SVF_NOCLIENT;
- ent->client->resp.ctf_team = CTF_NOTEAM;
- ent->client->resp.spectator = true;
- ent->client->ps.gunindex = 0;
- ent->client->ps.gunskin = 0;
- gi.linkentity(ent);
- CTFOpenJoinMenu(ent);
- return true;
- }
- return false;
- }
- void CTFObserver(edict_t *ent)
- {
- if (!G_TeamplayEnabled() || g_teamplay_force_join->integer)
- return;
- // start as 'observer'
- if (ent->movetype == MOVETYPE_NOCLIP)
- CTFPlayerResetGrapple(ent);
- CTFDeadDropFlag(ent);
- CTFDeadDropTech(ent);
- ent->deadflag = false;
- ent->movetype = MOVETYPE_NOCLIP;
- ent->solid = SOLID_NOT;
- ent->svflags |= SVF_NOCLIENT;
- ent->client->resp.ctf_team = CTF_NOTEAM;
- ent->client->ps.gunindex = 0;
- ent->client->ps.gunskin = 0;
- ent->client->resp.score = 0;
- PutClientInServer(ent);
- }
- bool CTFInMatch()
- {
- if (ctfgame.match > MATCH_NONE)
- return true;
- return false;
- }
- bool CTFCheckRules()
- {
- int t;
- uint32_t i, j;
- char text[64];
- edict_t *ent;
- if (ctfgame.election != ELECT_NONE && ctfgame.electtime <= level.time)
- {
- gi.LocBroadcast_Print(PRINT_CHAT, "Election timed out and has been cancelled.\n");
- ctfgame.election = ELECT_NONE;
- }
- if (ctfgame.match != MATCH_NONE)
- {
- t = (ctfgame.matchtime - level.time).seconds<int>();
- // no team warnings in match mode
- ctfgame.warnactive = 0;
- if (t <= 0)
- { // time ended on something
- switch (ctfgame.match)
- {
- case MATCH_SETUP:
- // go back to normal mode
- if (competition->integer < 3)
- {
- ctfgame.match = MATCH_NONE;
- gi.cvar_set("competition", "1");
- CTFResetAllPlayers();
- }
- else
- {
- // reset the time
- ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value);
- }
- return false;
- case MATCH_PREGAME:
- // match started!
- CTFStartMatch();
- gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/tele_up.wav"), 1, ATTN_NONE, 0);
- return false;
- case MATCH_GAME:
- // match ended!
- CTFEndMatch();
- gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/bigtele.wav"), 1, ATTN_NONE, 0);
- return false;
- default:
- break;
- }
- }
- if (t == ctfgame.lasttime)
- return false;
- ctfgame.lasttime = t;
- switch (ctfgame.match)
- {
- case MATCH_SETUP:
- for (j = 0, i = 1; i <= game.maxclients; i++)
- {
- ent = g_edicts + i;
- if (!ent->inuse)
- continue;
- if (ent->client->resp.ctf_team != CTF_NOTEAM &&
- !ent->client->resp.ready)
- j++;
- }
- if (competition->integer < 3)
- G_FmtTo(text, "{:02}:{:02} SETUP: {} not ready", t / 60, t % 60, j);
- else
- G_FmtTo(text, "SETUP: {} not ready", j);
- gi.configstring(CONFIG_CTF_MATCH, text);
- break;
- case MATCH_PREGAME:
- G_FmtTo(text, "{:02}:{:02} UNTIL START", t / 60, t % 60);
- gi.configstring(CONFIG_CTF_MATCH, text);
- if (t <= 10 && !ctfgame.countdown)
- {
- ctfgame.countdown = true;
- gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
- }
- break;
- case MATCH_GAME:
- G_FmtTo(text, "{:02}:{:02} MATCH", t / 60, t % 60);
- gi.configstring(CONFIG_CTF_MATCH, text);
- if (t <= 10 && !ctfgame.countdown)
- {
- ctfgame.countdown = true;
- gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
- }
- break;
- default:
- break;
- }
- return false;
- }
- else
- {
- int team1 = 0, team2 = 0;
- if (level.time == gtime_t::from_sec(ctfgame.lasttime))
- return false;
- ctfgame.lasttime = level.time.seconds<int>();
- // this is only done in non-match (public) mode
- if (warn_unbalanced->integer)
- {
- // count up the team totals
- for (i = 1; i <= game.maxclients; i++)
- {
- ent = g_edicts + i;
- if (!ent->inuse)
- continue;
- if (ent->client->resp.ctf_team == CTF_TEAM1)
- team1++;
- else if (ent->client->resp.ctf_team == CTF_TEAM2)
- team2++;
- }
- if (team1 - team2 >= 2 && team2 >= 2)
- {
- if (ctfgame.warnactive != CTF_TEAM1)
- {
- ctfgame.warnactive = CTF_TEAM1;
- gi.configstring(CONFIG_CTF_TEAMINFO, "WARNING: Red has too many players");
- }
- }
- else if (team2 - team1 >= 2 && team1 >= 2)
- {
- if (ctfgame.warnactive != CTF_TEAM2)
- {
- ctfgame.warnactive = CTF_TEAM2;
- gi.configstring(CONFIG_CTF_TEAMINFO, "WARNING: Blue has too many players");
- }
- }
- else
- ctfgame.warnactive = 0;
- }
- else
- ctfgame.warnactive = 0;
- }
- if (capturelimit->integer &&
- (ctfgame.team1 >= capturelimit->integer ||
- ctfgame.team2 >= capturelimit->integer))
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "$g_capturelimit_hit");
- return true;
- }
- return false;
- }
- /*--------------------------------------------------------------------------
- * just here to help old map conversions
- *--------------------------------------------------------------------------*/
- TOUCH(old_teleporter_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- edict_t *dest;
- vec3_t forward;
- if (!other->client)
- return;
- dest = G_PickTarget(self->target);
- if (!dest)
- {
- gi.Com_Print("Couldn't find destination\n");
- return;
- }
- // ZOID
- CTFPlayerResetGrapple(other);
- // ZOID
- // unlink to make sure it can't possibly interfere with KillBox
- gi.unlinkentity(other);
- other->s.origin = dest->s.origin;
- other->s.old_origin = dest->s.origin;
- // other->s.origin[2] += 10;
- // clear the velocity and hold them in place briefly
- other->velocity = {};
- other->client->ps.pmove.pm_time = 160; // hold time
- other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
- // draw the teleport splash at source and on the player
- self->enemy->s.event = EV_PLAYER_TELEPORT;
- other->s.event = EV_PLAYER_TELEPORT;
- // set angles
- other->client->ps.pmove.delta_angles = dest->s.angles - other->client->resp.cmd_angles;
- other->s.angles[PITCH] = 0;
- other->s.angles[YAW] = dest->s.angles[YAW];
- other->s.angles[ROLL] = 0;
- other->client->ps.viewangles = dest->s.angles;
- other->client->v_angle = dest->s.angles;
- // give a little forward velocity
- AngleVectors(other->client->v_angle, forward, nullptr, nullptr);
- other->velocity = forward * 200;
- gi.linkentity(other);
- // kill anything at the destination
- if (!KillBox(other, true))
- {
- }
- // [Paril-KEX] move sphere, if we own it
- if (other->client->owned_sphere)
- {
- edict_t *sphere = other->client->owned_sphere;
- sphere->s.origin = other->s.origin;
- sphere->s.origin[2] = other->absmax[2];
- sphere->s.angles[YAW] = other->s.angles[YAW];
- gi.linkentity(sphere);
- }
- }
- /*QUAKED trigger_ctf_teleport (0.5 0.5 0.5) ?
- Players touching this will be teleported
- */
- void SP_trigger_ctf_teleport(edict_t *ent)
- {
- edict_t *s;
- int i;
- if (!ent->target)
- {
- gi.Com_Print("teleporter without a target.\n");
- G_FreeEdict(ent);
- return;
- }
- ent->svflags |= SVF_NOCLIENT;
- ent->solid = SOLID_TRIGGER;
- ent->touch = old_teleporter_touch;
- gi.setmodel(ent, ent->model);
- gi.linkentity(ent);
- // noise maker and splash effect dude
- s = G_Spawn();
- ent->enemy = s;
- for (i = 0; i < 3; i++)
- s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i]) / 2;
- s->s.sound = gi.soundindex("world/hum1.wav");
- gi.linkentity(s);
- }
- /*QUAKED info_ctf_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32)
- Point trigger_teleports at these.
- */
- void SP_info_ctf_teleport_destination(edict_t *ent)
- {
- ent->s.origin[2] += 16;
- }
- /*----------------------------------------------------------------------------------*/
- /* ADMIN */
- struct admin_settings_t
- {
- int matchlen;
- int matchsetuplen;
- int matchstartlen;
- bool weaponsstay;
- bool instantitems;
- bool quaddrop;
- bool instantweap;
- bool matchlock;
- };
- void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu);
- void CTFOpenAdminMenu(edict_t *ent);
- void CTFAdmin_SettingsApply(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- if (settings->matchlen != matchtime->value)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} changed the match length to {} minutes.\n",
- ent->client->pers.netname, settings->matchlen);
- if (ctfgame.match == MATCH_GAME)
- {
- // in the middle of a match, change it on the fly
- ctfgame.matchtime = (ctfgame.matchtime - gtime_t::from_min(matchtime->value)) + gtime_t::from_min(settings->matchlen);
- }
- ;
- gi.cvar_set("matchtime", G_Fmt("{}", settings->matchlen).data());
- }
- if (settings->matchsetuplen != matchsetuptime->value)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} changed the match setup time to {} minutes.\n",
- ent->client->pers.netname, settings->matchsetuplen);
- if (ctfgame.match == MATCH_SETUP)
- {
- // in the middle of a match, change it on the fly
- ctfgame.matchtime = (ctfgame.matchtime - gtime_t::from_min(matchsetuptime->value)) + gtime_t::from_min(settings->matchsetuplen);
- }
- ;
- gi.cvar_set("matchsetuptime", G_Fmt("{}", settings->matchsetuplen).data());
- }
- if (settings->matchstartlen != matchstarttime->value)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} changed the match start time to {} seconds.\n",
- ent->client->pers.netname, settings->matchstartlen);
- if (ctfgame.match == MATCH_PREGAME)
- {
- // in the middle of a match, change it on the fly
- ctfgame.matchtime = (ctfgame.matchtime - gtime_t::from_sec(matchstarttime->value)) + gtime_t::from_sec(settings->matchstartlen);
- }
- gi.cvar_set("matchstarttime", G_Fmt("{}", settings->matchstartlen).data());
- }
- if (settings->weaponsstay != !!g_dm_weapons_stay->integer)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} weapons stay.\n",
- ent->client->pers.netname, settings->weaponsstay ? "on" : "off");
- gi.cvar_set("g_dm_weapons_stay", settings->weaponsstay ? "1" : "0");
- }
- if (settings->instantitems != !!g_dm_instant_items->integer)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} instant items.\n",
- ent->client->pers.netname, settings->instantitems ? "on" : "off");
- gi.cvar_set("g_dm_instant_items", settings->instantitems ? "1" : "0");
- }
- if (settings->quaddrop != (bool) !g_dm_no_quad_drop->integer)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} quad drop.\n",
- ent->client->pers.netname, settings->quaddrop ? "on" : "off");
- gi.cvar_set("g_dm_no_quad_drop", !settings->quaddrop ? "1" : "0");
- }
- if (settings->instantweap != !!g_instant_weapon_switch->integer)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} instant weapons.\n",
- ent->client->pers.netname, settings->instantweap ? "on" : "off");
- gi.cvar_set("g_instant_weapon_switch", settings->instantweap ? "1" : "0");
- }
- if (settings->matchlock != !!matchlock->integer)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} turned {} match lock.\n",
- ent->client->pers.netname, settings->matchlock ? "on" : "off");
- gi.cvar_set("matchlock", settings->matchlock ? "1" : "0");
- }
- PMenu_Close(ent);
- CTFOpenAdminMenu(ent);
- }
- void CTFAdmin_SettingsCancel(edict_t *ent, pmenuhnd_t *p)
- {
- PMenu_Close(ent);
- CTFOpenAdminMenu(ent);
- }
- void CTFAdmin_ChangeMatchLen(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->matchlen = (settings->matchlen % 60) + 5;
- if (settings->matchlen < 5)
- settings->matchlen = 5;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_ChangeMatchSetupLen(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->matchsetuplen = (settings->matchsetuplen % 60) + 5;
- if (settings->matchsetuplen < 5)
- settings->matchsetuplen = 5;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_ChangeMatchStartLen(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->matchstartlen = (settings->matchstartlen % 600) + 10;
- if (settings->matchstartlen < 20)
- settings->matchstartlen = 20;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_ChangeWeapStay(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->weaponsstay = !settings->weaponsstay;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_ChangeInstantItems(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->instantitems = !settings->instantitems;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_ChangeQuadDrop(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->quaddrop = !settings->quaddrop;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_ChangeInstantWeap(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->instantweap = !settings->instantweap;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_ChangeMatchLock(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings = (admin_settings_t *) p->arg;
- settings->matchlock = !settings->matchlock;
- CTFAdmin_UpdateSettings(ent, p);
- }
- void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu)
- {
- int i = 2;
- admin_settings_t *settings = (admin_settings_t *) setmenu->arg;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Len: {:2} mins", settings->matchlen).data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLen);
- i++;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Setup Len: {:2} mins", settings->matchsetuplen).data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchSetupLen);
- i++;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Start Len: {:2} secs", settings->matchstartlen).data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchStartLen);
- i++;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Weapons Stay: {}", settings->weaponsstay ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeWeapStay);
- i++;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Instant Items: {}", settings->instantitems ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantItems);
- i++;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Quad Drop: {}", settings->quaddrop ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeQuadDrop);
- i++;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Instant Weapons: {}", settings->instantweap ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantWeap);
- i++;
- PMenu_UpdateEntry(setmenu->entries + i, G_Fmt("Match Lock: {}", settings->matchlock ? "Yes" : "No").data(), PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLock);
- i++;
- PMenu_Update(ent);
- }
- const pmenu_t def_setmenu[] = {
- { "*Settings Menu", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_LEFT, nullptr }, // int matchlen;
- { "", PMENU_ALIGN_LEFT, nullptr }, // int matchsetuplen;
- { "", PMENU_ALIGN_LEFT, nullptr }, // int matchstartlen;
- { "", PMENU_ALIGN_LEFT, nullptr }, // bool weaponsstay;
- { "", PMENU_ALIGN_LEFT, nullptr }, // bool instantitems;
- { "", PMENU_ALIGN_LEFT, nullptr }, // bool quaddrop;
- { "", PMENU_ALIGN_LEFT, nullptr }, // bool instantweap;
- { "", PMENU_ALIGN_LEFT, nullptr }, // bool matchlock;
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "Apply", PMENU_ALIGN_LEFT, CTFAdmin_SettingsApply },
- { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_SettingsCancel }
- };
- void CTFAdmin_Settings(edict_t *ent, pmenuhnd_t *p)
- {
- admin_settings_t *settings;
- pmenuhnd_t *menu;
- PMenu_Close(ent);
- settings = (admin_settings_t *) gi.TagMalloc(sizeof(*settings), TAG_LEVEL);
- settings->matchlen = matchtime->integer;
- settings->matchsetuplen = matchsetuptime->integer;
- settings->matchstartlen = matchstarttime->integer;
- settings->weaponsstay = g_dm_weapons_stay->integer;
- settings->instantitems = g_dm_instant_items->integer;
- settings->quaddrop = !g_dm_no_quad_drop->integer;
- settings->instantweap = g_instant_weapon_switch->integer != 0;
- settings->matchlock = matchlock->integer != 0;
- menu = PMenu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(pmenu_t), settings, nullptr);
- CTFAdmin_UpdateSettings(ent, menu);
- }
- void CTFAdmin_MatchSet(edict_t *ent, pmenuhnd_t *p)
- {
- PMenu_Close(ent);
- if (ctfgame.match == MATCH_SETUP)
- {
- gi.LocBroadcast_Print(PRINT_CHAT, "Match has been forced to start.\n");
- ctfgame.match = MATCH_PREGAME;
- ctfgame.matchtime = level.time + gtime_t::from_sec(matchstarttime->value);
- gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
- ctfgame.countdown = false;
- }
- else if (ctfgame.match == MATCH_GAME)
- {
- gi.LocBroadcast_Print(PRINT_CHAT, "Match has been forced to terminate.\n");
- ctfgame.match = MATCH_SETUP;
- ctfgame.matchtime = level.time + gtime_t::from_min(matchsetuptime->value);
- CTFResetAllPlayers();
- }
- }
- void CTFAdmin_MatchMode(edict_t *ent, pmenuhnd_t *p)
- {
- PMenu_Close(ent);
- if (ctfgame.match != MATCH_SETUP)
- {
- if (competition->integer < 3)
- gi.cvar_set("competition", "2");
- ctfgame.match = MATCH_SETUP;
- CTFResetAllPlayers();
- }
- }
- void CTFAdmin_Reset(edict_t *ent, pmenuhnd_t *p)
- {
- PMenu_Close(ent);
- // go back to normal mode
- gi.LocBroadcast_Print(PRINT_CHAT, "Match mode has been terminated, reseting to normal game.\n");
- ctfgame.match = MATCH_NONE;
- gi.cvar_set("competition", "1");
- CTFResetAllPlayers();
- }
- void CTFAdmin_Cancel(edict_t *ent, pmenuhnd_t *p)
- {
- PMenu_Close(ent);
- }
- pmenu_t adminmenu[] = {
- { "*Administration Menu", PMENU_ALIGN_CENTER, nullptr },
- { "", PMENU_ALIGN_CENTER, nullptr }, // blank
- { "Settings", PMENU_ALIGN_LEFT, CTFAdmin_Settings },
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "", PMENU_ALIGN_LEFT, nullptr },
- { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_Cancel },
- { "", PMENU_ALIGN_CENTER, nullptr },
- };
- void CTFOpenAdminMenu(edict_t *ent)
- {
- adminmenu[3].text[0] = '\0';
- adminmenu[3].SelectFunc = nullptr;
- adminmenu[4].text[0] = '\0';
- adminmenu[4].SelectFunc = nullptr;
- if (ctfgame.match == MATCH_SETUP)
- {
- Q_strlcpy(adminmenu[3].text, "Force start match", sizeof(adminmenu[3].text));
- adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
- Q_strlcpy(adminmenu[4].text, "Reset to pickup mode", sizeof(adminmenu[4].text));
- adminmenu[4].SelectFunc = CTFAdmin_Reset;
- }
- else if (ctfgame.match == MATCH_GAME || ctfgame.match == MATCH_PREGAME)
- {
- Q_strlcpy(adminmenu[3].text, "Cancel match", sizeof(adminmenu[3].text));
- adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
- }
- else if (ctfgame.match == MATCH_NONE && competition->integer)
- {
- Q_strlcpy(adminmenu[3].text, "Switch to match mode", sizeof(adminmenu[3].text));
- adminmenu[3].SelectFunc = CTFAdmin_MatchMode;
- }
- // if (ent->client->menu)
- // PMenu_Close(ent->client->menu);
- PMenu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(pmenu_t), nullptr, nullptr);
- }
- void CTFAdmin(edict_t *ent)
- {
- if (!allow_admin->integer)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Administration is disabled\n");
- return;
- }
- if (gi.argc() > 1 && admin_password->string && *admin_password->string &&
- !ent->client->resp.admin && strcmp(admin_password->string, gi.argv(1)) == 0)
- {
- ent->client->resp.admin = true;
- gi.LocBroadcast_Print(PRINT_HIGH, "{} has become an admin.\n", ent->client->pers.netname);
- gi.LocClient_Print(ent, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
- }
- if (!ent->client->resp.admin)
- {
- CTFBeginElection(ent, ELECT_ADMIN, G_Fmt("{} has requested admin rights.\n",
- ent->client->pers.netname).data());
- return;
- }
- if (ent->client->menu)
- PMenu_Close(ent);
- CTFOpenAdminMenu(ent);
- }
- /*----------------------------------------------------------------*/
- void CTFStats(edict_t *ent)
- {
- if (!G_TeamplayEnabled())
- return;
- ghost_t *g;
- static std::string text;
- edict_t *e2;
- text.clear();
- if (ctfgame.match == MATCH_SETUP)
- {
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- e2 = g_edicts + i;
- if (!e2->inuse)
- continue;
- if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM)
- {
- std::string_view str = G_Fmt("{} is not ready.\n", e2->client->pers.netname);
- if (text.length() + str.length() < MAX_CTF_STAT_LENGTH - 50)
- text += str;
- }
- }
- }
- uint32_t i;
- for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++)
- if (g->ent)
- break;
- if (i == MAX_CLIENTS)
- {
- if (!text.length())
- text = "No statistics available.\n";
- gi.Client_Print(ent, PRINT_HIGH, text.c_str());
- return;
- }
- text += " #|Name |Score|Kills|Death|BasDf|CarDf|Effcy|\n";
- for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++)
- {
- if (!*g->netname)
- continue;
- int32_t e;
- if (g->deaths + g->kills == 0)
- e = 50;
- else
- e = g->kills * 100 / (g->kills + g->deaths);
- std::string_view str = G_Fmt("{:3}|{:<16.16}|{:5}|{:5}|{:5}|{:5}|{:5}|{:4}%|\n",
- g->number,
- g->netname,
- g->score,
- g->kills,
- g->deaths,
- g->basedef,
- g->carrierdef,
- e);
- if (text.length() + str.length() > MAX_CTF_STAT_LENGTH - 50)
- {
- text += "And more...\n";
- break;
- }
- text += str;
- }
- gi.Client_Print(ent, PRINT_HIGH, text.c_str());
- }
- void CTFPlayerList(edict_t *ent)
- {
- static std::string text;
- edict_t *e2;
- // number, name, connect time, ping, score, admin
- text.clear();
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- e2 = g_edicts + i;
- if (!e2->inuse)
- continue;
- std::string_view str = G_Fmt("{:3} {:<16.16} {:02}:{:02} {:4} {:3}{}{}\n",
- i,
- e2->client->pers.netname,
- (level.time - e2->client->resp.entertime).milliseconds() / 60000,
- ((level.time - e2->client->resp.entertime).milliseconds() % 60000) / 1000,
- e2->client->ping,
- e2->client->resp.score,
- (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) ? (e2->client->resp.ready ? " (ready)" : " (notready)") : "",
- e2->client->resp.admin ? " (admin)" : "");
- if (text.length() + str.length() > MAX_CTF_STAT_LENGTH - 50)
- {
- text += "And more...\n";
- break;
- }
- text += str;
- }
- gi.Client_Print(ent, PRINT_HIGH, text.data());
- }
- void CTFWarp(edict_t *ent)
- {
- char *token;
- if (gi.argc() < 2)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Where do you want to warp to?\n");
- gi.LocClient_Print(ent, PRINT_HIGH, "Available levels are: {}\n", warp_list->string);
- return;
- }
- const char *mlist = warp_list->string;
- while (*(token = COM_Parse(&mlist)))
- {
- if (Q_strcasecmp(token, gi.argv(1)) == 0)
- break;
- }
- if (!*token)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Unknown CTF level.\n");
- gi.LocClient_Print(ent, PRINT_HIGH, "Available levels are: {}\n", warp_list->string);
- return;
- }
- if (ent->client->resp.admin)
- {
- gi.LocBroadcast_Print(PRINT_HIGH, "{} is warping to level {}.\n",
- ent->client->pers.netname, gi.argv(1));
- Q_strlcpy(level.forcemap, gi.argv(1), sizeof(level.forcemap));
- EndDMLevel();
- return;
- }
- if (CTFBeginElection(ent, ELECT_MAP, G_Fmt("{} has requested warping to level {}.\n",
- ent->client->pers.netname, gi.argv(1)).data()))
- Q_strlcpy(ctfgame.elevel, gi.argv(1), sizeof(ctfgame.elevel));
- }
- void CTFBoot(edict_t *ent)
- {
- edict_t *targ;
- if (!ent->client->resp.admin)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "You are not an admin.\n");
- return;
- }
- if (gi.argc() < 2)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Who do you want to kick?\n");
- return;
- }
- if (*gi.argv(1) < '0' && *gi.argv(1) > '9')
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Specify the player number to kick.\n");
- return;
- }
- uint32_t i = strtoul(gi.argv(1), nullptr, 10);
- if (i < 1 || i > game.maxclients)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "Invalid player number.\n");
- return;
- }
- targ = g_edicts + i;
- if (!targ->inuse)
- {
- gi.LocClient_Print(ent, PRINT_HIGH, "That player number is not connected.\n");
- return;
- }
- gi.AddCommandString(G_Fmt("kick {}\n", i - 1).data());
- }
- void CTFSetPowerUpEffect(edict_t *ent, effects_t def)
- {
- if (ent->client->resp.ctf_team == CTF_TEAM1 && def == EF_QUAD)
- ent->s.effects |= EF_PENT; // red
- else if (ent->client->resp.ctf_team == CTF_TEAM2 && def == EF_PENT)
- ent->s.effects |= EF_QUAD; // blue
- else
- ent->s.effects |= def;
- }
|