/* In order to incorporate this file into your mod, you have to do two things: 1. Include this file in your progs.src 2. Call Vote_Init(); inside worldspawn. You need fteqcc and fteqw-sv for this to work. */ /* builtins required */ float(string) tokenize = #441; string(float) argv = #442; void(entity e, string s) clientcommand = #440; void(entity player, string key, string value) forceinfokey = #213; string(string key) serverkey = #354; float(string key, optional float assumevalue) serverkeyfloat = #0:serverkeyfloat; void ChatMessage_Parse( entity, string ); string Util_TimeToString(float fTime); void Vote_Cmd_VoteYes(void); void Vote_Cmd_VoteNo(void); void Vote_Cmd_CallVote(string); void Vote_Help(entity); var string g_strVoteCmd; var float g_flVoteTime; var float g_iVoteState; var float autocvar_mp_allowvote = 1; .float voted; entity voteHandler; enum { VOTE_INACTIVE, VOTE_INPROGRESS, VOTE_PASSED }; /* callback from fteqw-sv */ void SV_ParseClientCommand(string cmd) { float argc; argc = tokenize(cmd); switch (argv(0)) { case "say": clientcommand(self, cmd); ChatMessage_Parse(self, argv(1)); break; default: clientcommand(self, cmd); } } void ChatMessage_Parse(entity sayingEnt, string commandString) { float isMap = 0; if (whichpack(strcat("maps/", commandString, ".bsp"))) { isMap = 1; } float args = tokenize(commandString); switch (argv(0)) { case "yes": self = sayingEnt; Vote_Cmd_VoteYes(); break; case "no": self = sayingEnt; Vote_Cmd_VoteNo(); break; case "rtv": bprint(PRINT_CHAT, sprintf("rock the vote requested by %s\n", sayingEnt.netname)); break; case "timeleft": string msg; string timestring; float timeleft; timeleft = cvar("timelimit") - (time / 60); timestring = Util_TimeToString(timeleft); msg = sprintf("we have %s minutes remaining\n", timestring); sprint(sayingEnt, PRINT_CHAT, msg); break; case "callvote": Vote_Cmd_CallVote(commandString); break; case "votehelp": Vote_Help(sayingEnt); break; default: if (isMap) { bprint(PRINT_CHAT, sprintf("%s was nominated by %s\n", commandString, sayingEnt.netname)); } break; } } string Util_TimeToString(float fTime) { fTime = rint(fTime); switch (fTime) { case 0: return "less than one"; case 1: return "one"; case 2: return "two"; case 3: return "three"; case 4: return "four"; case 5: return "five"; case 6: return "six"; case 7: return "seven"; case 8: return "eight"; case 9: return "nine"; case 10: return "ten"; default: return "over ten"; } } void Vote_End(void) { localcmd(sprintf("%s\n", g_strVoteCmd)); g_flVoteTime = 0.0f; g_iVoteState = VOTE_INACTIVE; remove(voteHandler); } void Vote_Reset(void) { forceinfokey(world, "votes_y", "0"); forceinfokey(world, "votes_n", "0"); forceinfokey(world, "vote_cmd", ""); for (entity e = world; (e = find(e, ::classname, "player"));) { e.voted = 0; } } void Vote_Passed(void) { g_flVoteTime = time + 5.0f; g_iVoteState = VOTE_PASSED; bprint(PRINT_CHAT, "Vote passed.\n"); g_strVoteCmd = serverkey("vote_cmd"); Vote_Reset(); } void Vote_Failed(void) { g_flVoteTime = 0.0; g_iVoteState = VOTE_INACTIVE; bprint(PRINT_CHAT, "Vote failed.\n"); Vote_Reset(); remove(voteHandler); } void Vote_Frame(void) { if (time >= g_flVoteTime) { if (g_iVoteState == VOTE_INPROGRESS) { if (serverkeyfloat("votes_y") > serverkeyfloat("votes_n")) { Vote_Passed(); } else { Vote_Failed(); } } else if (g_iVoteState == VOTE_PASSED) { Vote_End(); } } self.nextthink = time; } void Vote_Cmd_VoteYes(void) { /* No vote is in progress */ if (g_iVoteState != VOTE_INPROGRESS) { return; } if (self.classname != "player") { return; } if (self.voted) { return; } forceinfokey(world, "votes_y", ftos(serverkeyfloat("votes_y")+1)); self.voted = 1; /* HACK: Is there a better way to do this? */ float playernums = 0; for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) { playernums++; } /* We need at least half the players agreeing. */ if (serverkeyfloat("votes_y") > rint(playernums / 2)) { Vote_Passed(); return; } if (serverkeyfloat("votes_n") + serverkeyfloat("votes_y") == playernums) { g_flVoteTime = time + 0.0f; } } void Vote_Cmd_VoteNo(void) { /* No vote is in progress */ if (g_iVoteState != VOTE_INPROGRESS) { return; } if (self.classname != "player") { return; } if (self.voted) { return; } forceinfokey(world, "votes_n", ftos(serverkeyfloat("votes_n")+1)); self.voted = 1; /* HACK: Is there a better way to do this? */ float playernums = 0; for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) { playernums++; } /* We need at least half the players disagreeing. */ if (serverkeyfloat("votes_n") > rint(playernums / 2)) { Vote_Failed(); return; } if (serverkeyfloat("votes_n") + serverkeyfloat("votes_y") == playernums) { g_flVoteTime = time + 0.0f; } } void Vote_InitiateVote(string votemsg) { /* A vote is in progress */ if (g_iVoteState != VOTE_INACTIVE) { return; } if (self.classname != "player") { return; } Vote_Reset(); forceinfokey(world, "vote_cmd", votemsg); g_flVoteTime = time + 30.0f; g_iVoteState = VOTE_INPROGRESS; bprint(PRINT_CHAT, sprintf("vote: %S, type yes/no in chat!\n", votemsg)); } void Vote_Cmd_CallVote(string text) { if (autocvar_mp_allowvote == 0) { return; } /* No vote is in progress */ if (g_iVoteState != VOTE_INACTIVE) { sprint(self, PRINT_CHAT, "A vote is already in progress.\n"); return; } voteHandler = spawn(); voteHandler.think = Vote_Frame; voteHandler.nextthink = time; tokenize(text); switch (argv(1)) { case "map": if not (whichpack(sprintf("maps/%s.bsp", argv(2)))) { sprint(self, PRINT_CHAT, sprintf("Map '%s' not available on server.\n", argv(2))); break; } case "kick": case "slowmo": case "timelimit": case "fraglimit": case "map_restart": case "nextmap": Vote_InitiateVote(sprintf("%s %s", argv(1), argv(2))); Vote_Cmd_VoteYes(); break; default: sprint(self, PRINT_CHAT, sprintf("Cannot callvote for '%s'.\n", argv(1))); } } void Vote_Init(void) { Vote_Reset(); } void Vote_Help(entity targetPlayer) { centerprint(targetPlayer, "Voting is easy:\nsay: callvote map dm3\n\nplayers say 'yes' or 'no'\nto decide the outcome!\n\ncommands include:\nmap, kick, fraglimit, timelimit"); }