/* Alternate voting system, Quake III: Arena. Copyright 2002 by PhaethonH Permission granted to copy, modify, distribute, or otherwise use this code, provided this copyright notice remains intact. */ #include "q_shared.h" #include "g_local.h" /* Drop-in replacement for vote system. To use: in g_main.c, change CheckVote() to CheckVote2() in g_cmds.c, change Cmd_CallVote_f() to Cmd_callvote_f() change Cmd_Vote_f() to Cmd_vote_f() in g_svcmds.c, in ConsoleCommand() add towards the end: if (Vote2_hook_svcmd(cmd)) return qtrue; (in g_svcmds.c): if (Q_stricmp (cmd, "listip") == 0) { trap_SendConsoleCommand( EXEC_NOW, "g_banIPs\n" ); return qtrue; } if (Vote2_hook_svcmd(cmd)) return qtrue; */ /**************/ /* Interface. */ /**************/ /* Having distinct votebox instances allows separation of global vote from team vote, etc. */ typedef struct votebox_s votebox_t; struct votebox_s { char title[MAX_CVAR_VALUE_STRING]; /* displayed to clients. */ char motion[MAX_CVAR_VALUE_STRING]; /* pending command. */ int weight[MAX_CLIENTS]; /* Weight of voter (0 = can't vote) */ char ballot[MAX_CLIENTS]; /* the vote cast of each voter. */ int numvoters; /* how many voters registered. */ float majority; /* the minimal fraction of yes votes to pass. Minimal number of no votes to fail implied (1.0 - majority). */ int opentime; /* When voting started. */ int closetime; /* Time when voting ends. */ int enacttime; /* Time when command executes. */ qboolean recastable; /* if voters can change their minds or not. */ int aye; /* votes for yes. */ int nay; /* votes for no. */ }; /* Methods: * init (title, cmd) - set up voting * destroy () - tear down voting * open (majority, time) - Start election (collect votes) * close () - End election (do not accept further votes) * register (clientnum) - Add client for votability * unregister (clientnum) - Remove client for votability * cast (clientnum, vote) - Take in vote * update () - tally votes, run pending enactments * isbusy () - vote in progress (taking votes) * enact (time) - schedule command for execution (votes collected, has passed) * force () - force execution of command immediately */ int Vote2_hook_svcmd (const char *); void Cmd_callvote_f (gentity_t *ent); void Cmd_vote_f (gentity_t *ent); int CheckVote2 (); /*******************/ /* Implementation. */ /*******************/ #define VOTE_YES 1 #define VOTE_NO 2 /* XXX: this should go into an external file. Later, though. */ static char * strdup(const char *orig) { char *x; int l; l = strlen(orig) + 1; x = (char*)malloc(l); memcpy(x, orig, l); return x; } /* Submit a vote (callvote), entitled `name', which executes command `cmd'. Returns: 0 - petitioning failed (e.g. vote still in progress). non-zero - petitioning succeeded (vote in progress). */ int votebox_init (votebox_t *self, char *name, char *cmd) { /* Can't set up if previous vote still in progress. */ if (votebox_isbusy(self)) return 0; /* flush pending enactments. */ if (self->enacttime) votebox_force(self); if (cmd) Q_strncpyz(self->motion, cmd, sizeof(self->motion)); else *self->motion = 0; if (name) Q_strncpyz(self->title, name, sizeof(self->title)); else strcpy(self->title, self->motion); return 1; } /* Tear down election. Returns: nothing of significance. */ int votebox_destroy (votebox_t *self) { int i; memset(self, 0, sizeof(*self)); return 42; } /* Check if vote in progress. Returns: 0 - not in progress non-zero - in progress */ int votebox_isbusy (votebox_t *self) { // if (self->title[0]) if (self->opentime) { return 1; } return 0; } /* Begin collecting votes for `votetime' seconds. Vote passes after `passfrac' fraction of votes are Yes. Vote fails when No votes exceed (1.0 - `passfrac'). Returns: 0 - failure/problem non-zero - success */ int votebox_open (votebox_t *self, float passfrac, int votetime) { int i; if (!self->motion) { return 0; } self->majority = passfrac; self->numvoters = 0; for (i = 0; i < MAX_CLIENTS; i++) { self->ballot[i] = 0; if (self->weight[i] > 0) self->numvoters++; } self->opentime = level.time; self->closetime = self->opentime + (votetime * 1000); //printf("Election started at %d with %d registered voters\n", self->opentime, self->numvoters); return 1; } /* Close the election. Stop accepting votes, preserve enactment data. Returns: 0 - failure non-zero - success */ int votebox_close (votebox_t *self) { int i; //printf("Voting ends.\n"); self->title[0] = 0; self->numvoters = 0; self->aye = 0; self->nay = 0; self->majority = 0; self->opentime = 0; self->closetime = 0; for (i = 0; i < MAX_CLIENTS; i++) { self->weight[i] = 0; self->ballot[i] = 0; } } /* Register client `voter' for voting, with ballot weight `weight' (typically 1). Returns: 0 - failure in registrations process other - success */ int votebox_register (votebox_t *self, int voter, int weight) { // if (!self->weight[voter]) // self->numvoters++; self->weight[voter] = weight; self->ballot[voter] = 0; return 1; } /* Remove client from vote registration, revoking the accompanying ballot. Returns: 0 - failure in process other - success */ int votebox_unregister (votebox_t *self, int voter) { // if (self->weight[voter]) // self->numvoters--; self->weight[voter] = 0; self->ballot[voter] = 0; return 1; } /* Player casts a vote. Returns: 0 - vote denied (already cast, not allowed to vote) other - registered choice. */ int votebox_cast (votebox_t *self, int voter, int choice) { if (!votebox_isbusy(self)) return 0; /* No vote in progress. */ if (!self->weight[voter]) return 0; /* Not allowed to vote. */ if (!self->recastable) { if (self->ballot[voter]) return 0; /* Not allowed to re-vote. */ } /* record. */ self->ballot[voter] = choice; // /* tally. */ // votebox_update(self); /* report. */ return self->ballot[voter]; } /* Schedule the command to run in `graceperiod' seconds. Returns: 0 - failure non-zero - success */ int votebox_enact (votebox_t *self, int graceperiod) { /* XXX: proclaim passage? */ self->enacttime = level.time + (graceperiod * 1000); // trap_SendConsoleCommand(EXEC_APPEND, self->motion); // self->motion[0] = 0; return 1; } /* Force immediate execution regardless of grace period. Returns: 0 - failure else - success */ int votebox_force (votebox_t *self) { if (self->enacttime) { trap_SendConsoleCommand(EXEC_APPEND, va("%s\n", self->motion)); self->enacttime = 0; self->motion[0] = 0; } return 1; } /* Find out the state of voting. Returns: VOTE_NO : no wins VOTE_YES : yes wins 0 : still voting/no change in election result */ int votebox_update (votebox_t *self) { int i; // if (self->closetime > level.time) // { // /* Vote fails. If it succeeded, we wouldn't be triggering this check. */ // return -1; // } if (!votebox_isbusy(self)) { /* not taking votes. Do we need to enact? */ if (!self->enacttime) return 0; if (self->enacttime < level.time) votebox_force(self); return 0; } /* if no one can vote, then vote cannot ever pass. */ if (self->numvoters == 0) return VOTE_NO; self->aye = self->nay = 0; for (i = 0; i < MAX_CLIENTS; i++) { switch (self->ballot[i]) { case VOTE_YES: self->aye += self->weight[i]; break; case VOTE_NO: self->nay += self->weight[i]; break; default: break; } } if (level.time - self->opentime >= VOTE_TIME) return VOTE_NO; if ((self->aye/(float)self->numvoters) > self->majority) return VOTE_YES; if ((self->nay/(float)self->numvoters) >= (1.0 - self->majority)) return VOTE_NO; return 0; } /* Set configstrings. Returns: 0 - failure else - success */ int votebox_configstrings (votebox_t *self, int cs_time, int cs_string, int cs_aye, int cs_nay) { if (cs_time > 1) { if (self->opentime) trap_SetConfigstring(cs_time, va("%d", self->opentime)); else trap_SetConfigstring(cs_time, ""); } if (cs_string > 1) trap_SetConfigstring(cs_string, self->title); if (cs_aye > 1) trap_SetConfigstring(cs_aye, va("%d", self->aye)); if (cs_nay > 1) trap_SetConfigstring(cs_nay, va("%d", self->nay)); return 1; } /* Callvoting. */ /* Ruleset: | |-- "kick" | \-- :playername | |-- "fraglimit" | \-- :integer | |- 20 | \- 200 | |-- "gametype" | |-- ! -- :integer | | \-- 2 | \-- :integer | |- 0 | \- 5 | \-- "g_friendlyfire" \-- :real |- -1.0 \- 1.0 */ #define MAX_RULESET 32 enum { RULETYPE_ANY, RULETYPE_INT, RULETYPE_REAL, RULETYPE_ENUM, RULETYPE_PLAYERNAME, RULETYPE_MAPNAME, RULETYPE_VOID, }; union callvote_ruleitem_range_u { struct { int lower; int upper; } N; struct { float lower; float upper; } R; char *s; }; struct callvote_ruleitem_s { qboolean allow; float pass; /* percentage "yes" for passage. */ int ruletype; union callvote_ruleitem_range_u range; struct callvote_ruleitem_s *next; }; /* tail points to last valid member. NULL means no list. */ struct callvote_rule_s { char *cmd; /* base command name. */ int num_constraints; /* number of parameter constraints. */ struct callvote_ruleitem_s *head, *tail; struct callvote_rule_s *next; }; struct callvote_ruleset_s { char list[MAX_TOKEN_CHARS]; int num_rules; struct callvote_rule_s *head, *tail; }; void callvote_ruleitem_init (struct callvote_ruleitem_s *self) { memset(self, 0, sizeof(*self)); self->allow = 1; self->next = NULL; } void callvote_ruleitem_destroy (struct callvote_ruleitem_s *self) { self->allow = 0; self->ruletype = 0; self->range.R.upper = 0; self->range.R.lower = 0; self->next = 0; } void callvote_ruleitem_setany (struct callvote_ruleitem_s *self) { self->ruletype = RULETYPE_ANY; self->range.s = 0; } void callvote_ruleitem_setint (struct callvote_ruleitem_s *self, int lower, int upper) { self->ruletype = RULETYPE_REAL; self->range.N.lower = lower; self->range.N.upper = upper; } void callvote_ruleitem_setreal (struct callvote_ruleitem_s *self, float lower, float upper) { self->ruletype = RULETYPE_REAL; self->range.R.lower = lower; self->range.R.upper = upper; } void callvote_ruleitem_setenum (struct callvote_ruleitem_s *self, char *string) { self->ruletype = RULETYPE_ENUM; self->range.s = string; } void callvote_ruleitem_allow (struct callvote_ruleitem_s *self) { self->allow = 1; } void callvote_ruleitem_deny (struct callvote_ruleitem_s *self) { self->allow = 0; } void callvote_rule_init (struct callvote_rule_s *self, char *cmd) { if (!self) return; self->head = self->tail = NULL; self->cmd = cmd; self->num_constraints = 0; } void callvote_rule_destroy (struct callvote_rule_s *self) { struct callvote_ruleitem_s *curr, *next; curr = self->head; while (curr) { next = curr->next; callvote_ruleitem_destroy(curr); free(curr); curr = next; } self->num_constraints = 0; free(self->cmd); self->cmd = 0; self->head = self->tail = NULL; } /* Get pointer such that ->next points to nth element. Returns: NULL - is head. otherwise - elements such that ->next points to the nth element. if ->next points to NULL, is tail. */ struct callvote_ruleitem_s * callvote_rule_find (struct callvote_rule_s *self, int n) { struct callvote_ruleitem_s *curr; int i; if (!self->head) return NULL; if (n == 0) return NULL; curr = self->head; /* So that '1' stops at head (head->next is index 1) */ for (i = 1; i < n; i++) { if (curr->next == NULL) break; curr = curr->next; } return curr; } /* Returns pointer such that ->next == tail */ struct callvote_ruleitem_s * callvote_rule_harm_tail (struct callvote_rule_s *self) { struct callvote_ruleitem_s *curr; if (!self->head) return NULL; if (!self->tail) return NULL; curr = self->head; while (curr && (curr->next != self->tail)) { curr = curr->next; } return curr; } int callvote_rule_add (struct callvote_rule_s *self, struct callvote_ruleitem_s *ruleitem) { struct callvote_ruleitem_s *curr; ruleitem->next = NULL; if (!self->head) { self->head = ruleitem; self->head->next = NULL; self->tail = self->head; } self->tail->next = ruleitem; self->tail = ruleitem; self->tail->next = NULL; self->num_constraints++; return self->num_constraints; } int callvote_rule_insert (struct callvote_rule_s *self, int n, struct callvote_ruleitem_s *ruleitem) { struct callvote_ruleitem_s *curr; if (!self->head) { return callvote_rule_add(self, ruleitem); } curr = callvote_rule_find(self, n); if (curr == NULL) { /* start of list. */ ruleitem->next = self->head; self->head = ruleitem; } else { if (curr->next) { /* middle of list. */ ruleitem->next = curr->next; curr->next = ruleitem->next; } else { /* end of list. */ self->tail->next = ruleitem; ruleitem->next = NULL; } } self->num_constraints++; return self->num_constraints; } int callvote_rule_replace (struct callvote_rule_s *self, int n, struct callvote_ruleitem_s *ruleitem) { struct callvote_ruleitem_s *curr; if (!self->head) { return callvote_rule_add(self, ruleitem); } curr = callvote_rule_find(self, n); if (curr == NULL) { /* start of list. */ ruleitem->next = self->head->next; free(self->head); self->head = ruleitem; } else { if (curr->next) { /* middle of list. */ ruleitem->next = curr->next->next; free(curr->next); curr->next = ruleitem; } else { /* end of list. */ /* nothing to replace. append instead. */ curr = callvote_rule_harm_tail(self); if (curr) { free(curr->next); curr->next = ruleitem; self->tail = curr->next; self->tail->next = NULL; } } } return self->num_constraints; } int callvote_rule_remove (struct callvote_rule_s *self, int n) { struct callvote_ruleitem_s *curr, *loss; curr = callvote_rule_find(self, n); if (curr == NULL) { /* start of list. */ curr = self->head; self->head = self->head->next; free(curr); } else { if (curr->next) { /* middle of list. */ loss = curr->next; curr->next = curr->next->next; free(loss); } else { /* end of list. */ curr = callvote_rule_harm_tail(self); if (!curr) return self->num_constraints; free(curr->next); self->tail = curr; self->tail->next = NULL; } } self->num_constraints--; return self->num_constraints; } int callvote_ruleset_init (struct callvote_ruleset_s *self) { if (!self) return 0; self->num_rules = 0; self->head = self->tail = NULL; return 0; } void callvote_ruleset_destroy (struct callvote_ruleset_s *self) { struct callvote_rule_s *curr, *next; curr = self->head; while (curr) { next = curr->next; callvote_rule_destroy(curr); free(curr); curr = next; } self->num_rules = 0; self->head = self->tail = NULL; } /* Add `cmdname' to list of ruleset. Returns: NULL - a problem. pointer to rule struct - memory space of rule item (malloc'd if needed) */ struct callvote_rule_s * callvote_ruleset_add (struct callvote_ruleset_s *self, char *cmdname) { struct callvote_rule_s *curr; if (!self->head) { self->head = malloc(sizeof(*curr)); callvote_rule_init(self->head, strdup(cmdname)); self->tail = self->head; self->num_rules = 1; snprintf(self->list, sizeof(self->list), "%s", cmdname); return self->head; } /* see if this command already in rulesets list. */ curr = self->head; while (curr) { if (Q_stricmp(curr->cmd, cmdname) == 0) break; curr = curr->next; } if (curr) { /* exists. */ return curr; } /* does not exist. append. */ self->tail->next = malloc(sizeof(*curr)); self->tail = self->tail->next; callvote_rule_init(self->tail, strdup(cmdname)); snprintf(self->list, sizeof(self->list), va("%s, %s", self->list, cmdname)); self->num_rules++; return self->tail; } /* check if particular vote parameter is allowed. Returns fraction votes for passage. */ struct callvote_ruleitem_s * callvote_rule_match (struct callvote_rule_s *rule, char *parm) { struct callvote_ruleitem_s *ruleitem; int N; float R; int match; for (ruleitem = rule->head; ruleitem; ruleitem = ruleitem->next) { switch (ruleitem->ruletype) { case RULETYPE_INT: if (!parm || !*parm) break; R = atof(parm); N = (int)R; #define EPSILON 0.0001 if (fabs(R - (float)N) > EPSILON) break; N = atoi(parm); if ((ruleitem->range.N.lower <= N) && (N <= ruleitem->range.N.upper)) return ruleitem; break; case RULETYPE_REAL: if (!parm || !*parm) break; R = atoi(parm); if ((ruleitem->range.R.lower <= N) && (N <= ruleitem->range.R.upper)) return ruleitem; break; case RULETYPE_ENUM: if (parm == ruleitem->range.s) return ruleitem; if (!parm || !*parm) break; if (Q_stricmp(ruleitem->range.s, parm) == 0) return ruleitem; break; case RULETYPE_VOID: if (parm && *parm) break; return ruleitem; break; case RULETYPE_ANY: if (!parm || !*parm) break; return ruleitem; break; default: return ruleitem; break; } } /* deny by default. */ //printf("Rule for [%s] in [%s] not found\n", parm, rule->cmd); return NULL; } #if 0 /* check if particular vote is allowed. Returns fraction votes for passage. */ float callvote_ruleset_check (struct callvote_ruleset_s *ruleset, char *cmdname, char *parm) { struct callvote_rule_s *curr; int i; for (curr = ruleset->head; curr; curr = curr->next) { if (Q_stricmp(curr->cmd, cmdname) == 0) { return callvote_rule_check(curr, parm); } } /* deny by default. */ //printf("Ruleset for [%s] not found\n"); return -1; } #endif /* 0 */ struct callvote_rule_s * callvote_ruleset_match (struct callvote_ruleset_s *ruleset, char *cmdname, char *parm) { struct callvote_rule_s *curr; for (curr = ruleset->head; curr; curr = curr->next) { if (Q_stricmp(curr->cmd, cmdname) == 0) { // return callvote_rule_match(curr, parm); return curr; } } return NULL; } /*************************** * Q3 commands interfacing. * ****************************/ votebox_t globalvote; struct callvote_ruleset_s CallvoteRuleset = { 0, }; int Svcmd_votefilter_add () { struct callvote_rule_s *rule; struct callvote_ruleitem_s *ruleitem; char cmdname[256]; char str[256]; char buf[256]; int argn, slen; char *p; ruleitem = malloc(sizeof(struct callvote_ruleitem_s)); if (!ruleitem) return 0; callvote_ruleitem_init(ruleitem); argn = 1; trap_Argv(argn++, buf, sizeof(buf)); ruleitem->pass = atof(buf); if (strchr(buf, '!')) ruleitem->allow = 0; if (strchr(buf, '%')) ruleitem->pass /= 100; trap_Argv(argn++, cmdname, sizeof(cmdname)); trap_Argv(argn++, buf, sizeof(buf)); /* determine parameter type. */ slen = strspn(buf, "-+0123456789.:"); if (0); else if (*buf == 0) { /* empty parameter. */ ruleitem->ruletype = RULETYPE_ANY; } else if (Q_stricmp(buf, ".") == 0) { /* Disallow parameter. */ ruleitem->ruletype = RULETYPE_VOID; } else if (slen == strlen(buf)) { /* range (numbers). */ if (strchr(buf, '.')) ruleitem->ruletype = RULETYPE_REAL; else ruleitem->ruletype = RULETYPE_INT; p = strchr(buf, ':'); /* indicates explicit range (upper-bound). */ switch (ruleitem->ruletype) { case RULETYPE_REAL: ruleitem->range.R.lower = atof(buf); if (p) ruleitem->range.R.upper = atof(p+1); else ruleitem->range.R.upper = ruleitem->range.R.lower; break; case RULETYPE_INT: ruleitem->range.N.lower = atoi(buf); if (p) ruleitem->range.N.upper = atoi(p+1); else ruleitem->range.N.upper = ruleitem->range.N.lower; break; } } else { /* enumeration (exact string). */ ruleitem->ruletype = RULETYPE_ENUM; ruleitem->range.s = strdup(buf); } rule = callvote_ruleset_add(&CallvoteRuleset, cmdname); if (rule) { callvote_rule_add(rule, ruleitem); } else { free(ruleitem); } return 0; } int Svcmd_votefilter_clear () { callvote_ruleset_destroy(&CallvoteRuleset); callvote_ruleset_init(&CallvoteRuleset); return 0; } int Svcmd_votefilter_show () { struct callvote_rule_s *rule; struct callvote_ruleitem_s *ruleitem; Com_Printf("begin callvote ruleset\n"); Com_Printf("pass command parmtype lower upper\n"); for (rule = CallvoteRuleset.head; rule; rule = rule->next) { for (ruleitem = rule->head; ruleitem; ruleitem = ruleitem->next) { if (ruleitem->allow) Com_Printf("%.3f ", ruleitem->pass); else Com_Printf("%5s ", "!"); Com_Printf("%20s ", rule->cmd); switch (ruleitem->ruletype) { case RULETYPE_INT: Com_Printf("N %6d %6d\n", ruleitem->range.N.lower, ruleitem->range.N.upper); break; case RULETYPE_REAL: Com_Printf("R %#3.4f %#3.4f\n", ruleitem->range.R.lower, ruleitem->range.R.upper); break; case RULETYPE_ENUM: Com_Printf("S %s\n", ruleitem->range.s); break; case RULETYPE_VOID: Com_Printf(".\n"); break; default: Com_Printf("(any)\n"); break; } } } Com_Printf("end callvote ruleset\n"); return 0; } extern char *ConcatArgs(int); int Svcmd_votefilter_test () { char cmdname[256]; char parm[256]; char reason[MAX_TOKEN_CHARS]; // float x; struct callvote_ruleitem_s *x; int argn; argn = 1; trap_Argv(argn++, cmdname, sizeof(cmdname)); trap_Argv(argn++, parm, sizeof(parm)); Q_strncpyz (reason, ConcatArgs(argn++), sizeof(reason)); /* send through filters. */ x = callvote_rule_match(callvote_ruleset_match(&CallvoteRuleset, cmdname, parm), parm); if (x && (x->allow) && (x->pass >= 0)) { Com_Printf("callvote [%s] [%s] => ALLOW (%2.2f%%)\n", cmdname, parm, x->pass*100); } else { Com_Printf("callvote [%s] [%s] => DENY\n", cmdname, parm); } return 0; } #define TellClient(foo) trap_SendServerCommand(clientnum, va("print \"%s\"", foo)) void Cmd_callvote_f (gentity_t *ent) { struct callvote_rule_s *rule; struct callvote_ruleitem_s *ruleitem; int i; int clientnum; // float passfrac; char cmdname[256]; char parm[256]; char reason[MAX_TOKEN_CHARS]; char votecmd[MAX_TOKEN_CHARS]; char votetitle[MAX_TOKEN_CHARS]; int argn; clientnum = ent - g_entities; if ((!g_allowVote.integer) || (!CallvoteRuleset.num_rules)) { TellClient("Voting not allowed here.\n"); return; } #ifdef USE_SANE_NEXTMAP /* Save original nextmap. */ if (!*sv_nextmap.string) { trap_Cvar_VariableStringBuffer("nextmap", sv_nextmap.string, sizeof(sv_nextmap.string)); } #endif /* USE_SANE_NEXTMAP */ if (votebox_isbusy(&globalvote)) { TellClient("Vote already in progress.\n"); return; } if (ent->client->pers.voteCount >= MAX_VOTE_COUNT) { TellClient("You have called the maximum number of votes.\n"); return; } /* Allow spectator vote. Why shouldn't they? */ #if 0 if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) { TellClient("Not allowed to call a vote as spectator.\n"); return; } #endif /* 0 */ argn = 1; trap_Argv(argn++, cmdname, sizeof(cmdname)); trap_Argv(argn++, parm, sizeof(parm)); Q_strncpyz (reason, ConcatArgs(argn++), sizeof(reason)); rule = callvote_ruleset_match(&CallvoteRuleset, cmdname, parm); ruleitem = callvote_rule_match(rule, parm); if (!rule) { /* Bad command. Tell what's available. */ TellClient(va("Allowed vote commands: %s\n", CallvoteRuleset.list)); return; } if ((!ruleitem) || (!ruleitem->allow)) { /* Disallowed parameter. Tell what's allowed. */ TellClient(va("Parameter '%s' to command '%s' not allowed\n", parm, cmdname)); return; } /* Execute any pending enactment before starting election. */ votebox_force(&globalvote); /* Special-case commands. */ if (0); else if (Q_stricmp(cmdname, "map") == 0) { // special case for map changes, we want to reset the nextmap setting // this allows a player to change maps, but not upset the map rotation char s[MAX_STRING_CHARS]; trap_Cvar_VariableStringBuffer("nextmap", s, sizeof(s)); if (*s) { /* XXX: something about this thing may be causing map-rotation breakage... */ snprintf(votecmd, sizeof(votecmd), "%s %s; set nextmap \"%s\"", cmdname, parm, s); } else { snprintf(votecmd, sizeof(votecmd), "%s %s", cmdname, parm); } snprintf(votetitle, sizeof(votetitle), "%s", votecmd); } else if (Q_stricmp(cmdname, "nextmap") == 0) { char s[MAX_STRING_CHARS]; trap_Cvar_VariableStringBuffer("nextmap", s, sizeof(s)); if (!*s) { TellClient("nextmap not set.\n"); return; } snprintf(votecmd, sizeof(votecmd), "vstr nextmap"); snprintf(votetitle, sizeof(votetitle), "%s", votecmd); } else { snprintf(votecmd, sizeof(votecmd), "%s \"%s\"", cmdname, parm); snprintf(votetitle, sizeof(votetitle), "%s", votecmd); } /* obliterate vote box and build it up. */ votebox_destroy(&globalvote); if (!votebox_init(&globalvote, votetitle, votecmd)) { /* Why would it fail now? Oh well. */ return; } trap_SendServerCommand(-1, va("print \"%s^7 called a vote.\n\"", ent->client->pers.netname)); // start the voting, the caller automatically votes yes /* Register valid voters. */ for (i = 0; i < MAX_CLIENTS; i++) { /* Filter out based on who can't vote. */ if (level.clients[i].pers.connected != CON_CONNECTED) continue; if (level.clients[i].sess.sessionTeam == TEAM_SPECTATOR) continue; votebox_register(&globalvote, i, 1); } votebox_open(&globalvote, ruleitem->pass, 30); votebox_cast(&globalvote, clientnum, VOTE_YES); votebox_configstrings(&globalvote, CS_VOTE_TIME, CS_VOTE_STRING, CS_VOTE_YES, CS_VOTE_NO); #if 0 for (i = 0; i < level.maxclients; i++) { level.clients[i].ps.eFlags &= ~EF_VOTED; } ent->client->ps.eFlags |= EF_VOTED; trap_SetConfigstring(CS_VOTE_TIME, va("%i", level.voteTime)); trap_SetConfigstring(CS_VOTE_STRING, level.voteDisplayString); trap_SetConfigstring(CS_VOTE_YES, va("%i", level.voteYes)); trap_SetConfigstring(CS_VOTE_NO, va("%i", level.voteNo)); #endif /* 0 */ return; } int CheckVote2 () { int v; float margin = 0; v = votebox_update(&globalvote); switch (v) { case VOTE_YES: if (globalvote.numvoters) margin = globalvote.aye / (float)globalvote.numvoters; // trap_SendServerCommand(-1, va("print \"Vote passed (Y=%d N=%d, V=%d, with %.2f, needed %.2f).\n\"", globalvote.aye, globalvote.nay, globalvote.numvoters, margin, globalvote.majority)); trap_SendServerCommand(-1, va("print \"Vote passed (with %2.1f, needed %2.1f)\n\"", margin * 100, globalvote.majority * 100)); // trap_SendServerCommand(-1, "print \"Vote passed.\n""); votebox_close(&globalvote); votebox_enact(&globalvote, 3); break; case VOTE_NO: if (globalvote.numvoters) margin = globalvote.aye / (float)globalvote.numvoters; // trap_SendServerCommand(-1, va("print \"Vote failed (Y=%d N=%d, V=%d, with %.2f, needed %.2f).\n\"", globalvote.aye, globalvote.nay, globalvote.numvoters, margin, globalvote.majority)); trap_SendServerCommand(-1, va("print \"Vote failed (at %2.1f, needs more than %2.1f)\n\"", margin * 100, globalvote.majority * 100)); // trap_SendServerCommand(-1, "print \"Vote failed.\n\"); votebox_close(&globalvote); break; case 0: default: /* Still pending. */ break; } votebox_configstrings(&globalvote, CS_VOTE_TIME, CS_VOTE_STRING, CS_VOTE_YES, CS_VOTE_NO); return 1; } void Cmd_vote_f (gentity_t *ent) { char parm[MAX_CVAR_VALUE_STRING]; int ballot; int clientnum; int argn; clientnum = ent - g_entities; argn = 1; trap_Argv(argn++, parm, sizeof(parm)); switch (*parm) { /* XXX: Biased towards English. */ case 'Y': case 'y': case '1': ballot = VOTE_YES; break; default: ballot = VOTE_NO; break; } if (votebox_cast(&globalvote, clientnum, ballot)) { TellClient("Vote recorded\n"); } else { TellClient("Vote already cast.\n"); } } int Vote2_hook_svcmd (const char *cmd) { #define SVCMD(str, fn) if (!Q_stricmp(cmd,str)) { fn(); return qtrue; } SVCMD("votefilter_clear", Svcmd_votefilter_clear) SVCMD("votefilter_add", Svcmd_votefilter_add) SVCMD("votefilter_show", Svcmd_votefilter_show) SVCMD("votefilter_test", Svcmd_votefilter_test) return qfalse; }