/
votemap.py
247 lines (210 loc) · 8.82 KB
/
votemap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# Copyright (c) James Hofmann 2012.
# This file is part of pyspades.
# pyspades is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# pyspades is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with pyspades. If not, see <http://www.gnu.org/licenses/>.
import random
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from pyspades.common import prettify_timespan
from piqueserver.map import check_rotation
from piqueserver.scheduler import Scheduler
from piqueserver.commands import command
from piqueserver.config import config
votemap_config = config.section('votemap')
VOTEMAP_AUTOSCHEDULE_OPTION = votemap_config.option('autoschedule', 180)
VOTEMAP_PUBLIC_VOTES_OPTION = votemap_config.option( 'public_votes', True)
VOTEMAP_TIME_OPTION = votemap_config.option('time', 120)
VOTEMAP_EXTENSION_TIME_OPTION = votemap_config.option('extension_time', 15)
VOTEMAP_PLAYER_DRIVEN_OPTION = votemap_config.option('player_driven', False)
VOTEMAP_PERCENTAGE_OPTION = votemap_config.option('percentage', 80)
def cancel_verify(connection, instigator):
return (connection.admin or
connection is instigator or
connection.rights.cancel)
class VoteMap(object):
extension_time = 0.0
public_votes = True
instigator = None
protocol = None
def __init__(self, connection, protocol, rotation):
self.instigator = connection
self.rotation = rotation
self.protocol = protocol
self.vote_percentage = self.protocol.votemap_percentage
self.vote_time = self.protocol.votemap_time
self.vote_interval = self.protocol.votemap_interval
self.public_votes = self.protocol.votemap_public_votes
self.extension_time = self.protocol.votemap_extension_time
rotation = [n.name.lower() for n in rotation]
final_rotation = random.sample(rotation, min(len(rotation), 5))
if self.extension_time > 0:
final_rotation.append("extend")
self.picks = final_rotation
self.votes = {}
if connection is None:
name = 'server'
else:
name = connection.name
def votes_left(self):
thresh = int((len(self.protocol.players)) *
self.vote_percentage / 100.0)
counts = {}
for v in list(self.votes.values()):
if v in counts:
counts[v]['count'] += 1
else:
counts[v] = {'name': v, 'count': 1}
cvlist = list(counts.values())
if len(cvlist) <= 0:
return {'name': self.picks[0], 'count': 0}
mv = cvlist[0]
for n in list(counts.keys()):
if counts[n]['count'] > mv['count']:
mv = n
mv['count'] = thresh - mv['count']
return mv
def verify(self):
instigator = self.instigator
if instigator is None:
return True
last = instigator.last_votemap
if (last is not None and
reactor.seconds() - last < self.vote_interval):
return "You can't start a vote now."
return True
def start(self):
instigator = self.instigator
protocol = self.protocol
if instigator is None:
protocol.send_chat('Time to vote!', irc=True)
else:
protocol.send_chat(
'* %s initiated a map vote.' % instigator.name, irc=True)
self.schedule = schedule = Scheduler(protocol)
schedule.call_later(self.vote_time, self.timeout)
schedule.loop_call(30.0, self.update)
self.protocol.votemap = self
self.update()
def vote(self, connection, mapname):
mapname = mapname.lower()
if mapname not in self.picks:
connection.send_chat("Map %s is not available." % mapname)
return
self.votes[connection] = mapname
if self.public_votes:
self.protocol.send_chat('%s voted for %s.' % (connection.name,
mapname))
if self.votes_left()['count'] <= 0:
self.on_majority()
def cancel(self, connection=None):
if connection is None:
message = 'Cancelled'
elif not cancel_verify(connection, self.instigator):
return 'You did not start the vote.'
else:
message = 'Cancelled by %s' % connection.name
self.protocol.send_chat(message)
self.set_cooldown()
self.finish()
def update(self):
self.protocol.send_chat(
'Choose next map. Say /vote <name> to cast vote.')
names = ' '.join(self.picks)
self.protocol.send_chat('Maps: %s' % names)
self.protocol.send_chat('To extend current map: /vote extend')
def timeout(self):
self.show_result()
self.finish()
def on_majority(self):
self.show_result()
self.finish()
def show_result(self):
result = self.votes_left()['name']
if result == "extend":
tl = self.protocol.set_time_limit(self.extension_time, True)
span = prettify_timespan(tl * 60.0)
self.protocol.send_chat('Mapvote ended. Current map will '
'continue for %s.' % span, irc=True)
self.protocol.autoschedule_votemap()
else:
self.protocol.send_chat('Mapvote ended. Next map will be: %s.' %
result, irc=True)
self.protocol.planned_map = check_rotation([result])[0]
self.set_cooldown()
def set_cooldown(self):
if self.instigator is not None and not self.instigator.admin:
self.instigator.last_votemap = reactor.seconds()
def finish(self):
self.schedule.reset()
self.protocol.votemap = None
@command()
def votemap(connection, *arg):
if connection not in connection.protocol.players:
raise KeyError()
if not connection.protocol.votemap_player_driven and not connection.admin:
return "Player-initiated mapvotes are disabled on this server."
return connection.protocol.start_votemap(
VoteMap(connection, connection.protocol, connection.protocol.maps))
@command('vote')
def votemap_vote(connection, value):
if connection not in connection.protocol.players:
raise KeyError()
if connection.protocol.votemap is not None:
return connection.protocol.votemap.vote(connection, value)
else:
return 'No map vote in progress.'
def apply_script(protocol, connection, config):
class VoteProtocol(protocol):
# voting
votemap_time = 120
votemap_interval = 3 * 60
votemap_percentage = 80.0
votemap = None
planned_map = None
autoschedule_call = None
# voting
def __init__(self, interface, config):
protocol.__init__(self, interface, config)
self.votemap_autoschedule = VOTEMAP_AUTOSCHEDULE_OPTION.get()
self.votemap_public_votes = VOTEMAP_PUBLIC_VOTES_OPTION.get()
self.votemap_time = VOTEMAP_TIME_OPTION.get()
self.votemap_extension_time = VOTEMAP_EXTENSION_TIME_OPTION.get()
self.votemap_player_driven = VOTEMAP_PLAYER_DRIVEN_OPTION.get()
self.votemap_percentage = VOTEMAP_PERCENTAGE_OPTION.get()
self.autoschedule_votemap()
def autoschedule_votemap(self):
if self.votemap_autoschedule > 0 and self.autoschedule_call is None:
self.autoschedule_call = self.call_end(
self.votemap_autoschedule, self.start_votemap)
def cancel_vote(self, connection=None):
if self.votemap is not None:
return self.votemap.cancel(connection)
else:
return protocol.cancel_vote(self, connection)
def start_votemap(self, votemap=None):
if self.votemap is not None:
return self.votemap.update()
if votemap is None:
votemap = VoteMap(None, self, self.maps)
verify = votemap.verify()
if verify is True:
votemap.start()
else:
return verify
def set_map_name(self, *arg, **kw):
protocol.set_map_name(self, *arg, **kw)
self.end_votes()
def end_votes(self):
if self.votemap is not None:
self.votemap.finish()
class VoteConnection(connection):
last_votemap = None
return VoteProtocol, VoteConnection