Merge lp:~stefanor/ibid/mexican-shootout into lp:~ibid-core/ibid/old-trunk-pack-0.92
- mexican-shootout
- Merge into old-trunk-pack-0.92
Proposed by
Stefano Rivera
Status: | Merged |
---|---|
Approved by: | Stefano Rivera |
Approved revision: | 677 |
Merged at revision: | 675 |
Proposed branch: | lp:~stefanor/ibid/mexican-shootout |
Merge into: | lp:~ibid-core/ibid/old-trunk-pack-0.92 |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~stefanor/ibid/mexican-shootout |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jonathan Hitchcock | Approve | ||
Michael Gorven | Approve | ||
Review via email: mp+8095@code.launchpad.net |
This proposal supersedes a proposal from 2009-06-27.
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal | # |
Revision history for this message
Jonathan Hitchcock (vhata) wrote : Posted in a previous version of this proposal | # |
<3
review:
Approve
- 673. By Stefano Rivera
-
More acceptance commands
- 674. By Stefano Rivera
-
The agressor can't confirm
- 675. By Stefano Rivera
-
Spelling
- 676. By Stefano Rivera
-
Capitalise names on start
- 677. By Stefano Rivera
-
3 spelling errors
Revision history for this message
Jonathan Hitchcock (vhata) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'ibid/plugins/core.py' |
2 | --- ibid/plugins/core.py 2009-05-08 16:21:03 +0000 |
3 | +++ ibid/plugins/core.py 2009-07-01 16:59:59 +0000 |
4 | @@ -85,6 +85,7 @@ |
5 | class Address(Processor): |
6 | |
7 | processed = True |
8 | + addressed = False |
9 | acknowledgements = Option('acknowledgements', 'Responses for positive acknowledgements', |
10 | (u'Okay', u'Sure', u'Done', u'Righto', u'Alrighty', u'Yessir')) |
11 | refusals = Option('refusals', 'Responses for negative acknowledgements', |
12 | |
13 | === added file 'ibid/plugins/games.py' |
14 | --- ibid/plugins/games.py 1970-01-01 00:00:00 +0000 |
15 | +++ ibid/plugins/games.py 2009-07-01 17:54:10 +0000 |
16 | @@ -0,0 +1,392 @@ |
17 | +import datetime |
18 | +import logging |
19 | +from random import choice, gauss, random |
20 | +import re |
21 | +import time |
22 | + |
23 | +import ibid |
24 | +from ibid.plugins import Processor, match, handler |
25 | +from ibid.config import Option, IntOption, BoolOption, FloatOption |
26 | +from ibid.utils import ibid_version |
27 | + |
28 | +help = {} |
29 | +log = logging.getLogger('plugins.games') |
30 | + |
31 | +help['duel'] = u"Duel at dawn, between channel members" |
32 | +class Duel(Processor): |
33 | + u"""I challenge <user> to a duel [over <something>] |
34 | + I demand satisfaction from <user> [over <something>] |
35 | + I throw the gauntlet down at <user>'s feet [over <something>] |
36 | + draw [my <weapon>] |
37 | + bam|pew|bang|kapow|pewpew|holyhandgrenadeofantioch""" |
38 | + feature = 'duel' |
39 | + |
40 | + addressed = BoolOption('addressed', 'Must the bot be addressed', True) |
41 | + |
42 | + extremities = Option('extremities', u'Extremities that can be hit', ( |
43 | + u'toe', u'foot', u'leg', u'thigh', u'finger', u'hand', u'arm', |
44 | + u'elbow', u'shoulder', u'ear', u'nose', u'stomach', |
45 | + )) |
46 | + |
47 | + vitals = Option('vitals', 'Vital parts of the body that can be hit', ( |
48 | + u'head', u'groin', u'chest', u'heart', u'neck', |
49 | + )) |
50 | + |
51 | + happy_endings = Option('happy_endings', 'Both survive', ( |
52 | + u'walk off into the sunset', u'go for a beer', u'call it quits', |
53 | + )) |
54 | + |
55 | + weapons = Option('weapons', 'Weapons that can be used: name: (chance, damage)', { |
56 | + u'bam': (0.75, 50), |
57 | + u'pew': (0.75, 50), |
58 | + u'fire': (0.75, 70), |
59 | + u'bang': (0.75, 70), |
60 | + u'kapow': (0.75, 90), |
61 | + u'pewpew': (0.75, 110), |
62 | + u'holyhandgrenadeofantioch': (1.0, 200), |
63 | + }) |
64 | + |
65 | + draw_required = BoolOption('draw_required', 'Must you draw your weapon before firing?', True) |
66 | + accept_timeout = FloatOption('accept_timeout', 'How long do we wait for acceptance?', 60.0) |
67 | + start_delay = IntOption('start_delay', 'Time between acceptance and start of duel (rounded down to the highest minute)', 30) |
68 | + timeout = FloatOption('timeout', 'How long is a duel on for', 10.0) |
69 | + extratime = FloatOption('extratime', 'How much more time to grant after every shot fired?', 1.0) |
70 | + |
71 | + duels = {} |
72 | + |
73 | + class Duel(object): |
74 | + pass |
75 | + |
76 | + @match(r'^(?:I\s+)throw\s+(?:down\s+(?:the|my)\s+gauntlet|(?:the|my)\s+gauntlet\s+down)\s+' |
77 | + r'at\s+(\S+?)(?:\'s\s+feet)?(?:\s+(?:over|because|for)\s+.+)?$') |
78 | + def initiate_gauntlet(self, event, recipient): |
79 | + self.initiate(event, recipient) |
80 | + |
81 | + @match(r'^(?:I\s+)?demand\s+satisfaction\s+from\s+(\S+)(?:\s+(?:over|because|for)\s+.+)?$') |
82 | + def initiate_satisfaction(self, event, recipient): |
83 | + self.initiate(event, recipient) |
84 | + |
85 | + @match(r'^(?:I\s+)?challenge\s+(\S+)(?:\s+to\s+a\s+duel)?(?:\s+(?:over|because|for)\s+.+)?$') |
86 | + def initiate(self, event, recipient): |
87 | + if not event.addressed: |
88 | + return |
89 | + |
90 | + if not event.public: |
91 | + event.addresponse(choice(( |
92 | + u"All duels must take place in public places, by decree of the bot", |
93 | + u"How do you expect to fight %(recipient)s, when he is not present?", |
94 | + u"Your challenge must be made in public, Sir Knight", |
95 | + )), { |
96 | + 'recipient': recipient |
97 | + }) |
98 | + return |
99 | + |
100 | + if (event.source, event.channel) in self.duels: |
101 | + event.addresponse(choice(( |
102 | + u"We already have a war in here. Take your fight outside", |
103 | + u"Isn't one fight enough? You may wait your turn", |
104 | + ))) |
105 | + return |
106 | + |
107 | + aggressor = event.sender['nick'] |
108 | + |
109 | + if recipient.lower() == aggressor.lower(): |
110 | + # Yes I know schizophrenia isn't the same as DID, but this sounds better :P |
111 | + event.addresponse(choice(( |
112 | + u"Are you schizophrenic?", |
113 | + u"Um, How exactly do you plan on fighting yourself?", |
114 | + ))) |
115 | + return |
116 | + |
117 | + if recipient.lower() in [name.lower() for name in ibid.config.plugins['core']['names']]: |
118 | + event.addresponse(choice(( |
119 | + u"I'm a peaceful bot", |
120 | + u"The ref can't take part in the battle", |
121 | + u"You just want me to die. No way", |
122 | + ))) |
123 | + return |
124 | + |
125 | + duel = self.Duel() |
126 | + self.duels[(event.source, event.channel)] = duel |
127 | + |
128 | + duel.hp = { |
129 | + aggressor.lower(): 100.0, |
130 | + recipient.lower(): 100.0, |
131 | + } |
132 | + duel.names = { |
133 | + aggressor.lower(): aggressor, |
134 | + recipient.lower(): recipient, |
135 | + } |
136 | + duel.drawn = { |
137 | + aggressor.lower(): False, |
138 | + recipient.lower(): False, |
139 | + } |
140 | + |
141 | + duel.started = False |
142 | + duel.confirmed = False |
143 | + duel.aggressor = event.sender['nick'].lower() |
144 | + duel.recipient = recipient.lower() |
145 | + |
146 | + duel.cancel_callback = ibid.dispatcher.call_later(self.accept_timeout, self.cancel, event) |
147 | + |
148 | + event.addresponse({'reply': (u'%(recipient)s: ' + choice(( |
149 | + u"The gauntlet has been thrown at your feet. Do you accept?", |
150 | + u"You have been challenged. Do you accept?", |
151 | + u"%(aggressor)s wishes to meet you at dawn on the field of honour. Do you accept?", |
152 | + ))) % { |
153 | + 'recipient': recipient, |
154 | + 'aggressor': event.sender['nick'], |
155 | + }}) |
156 | + |
157 | + def cancel(self, event): |
158 | + duel = self.duels[(event.source, event.channel)] |
159 | + del self.duels[(event.source, event.channel)] |
160 | + |
161 | + event.addresponse(choice(( |
162 | + u"%(recipient)s appears to has fled the country during the night", |
163 | + u"%(recipient)s refuses to meet your challenge and accepts dishonour", |
164 | + u"Your challenge was not met. I suggest anger management councelling", |
165 | + )), { |
166 | + 'recipient': duel.names[duel.recipient], |
167 | + }) |
168 | + |
169 | + @match(r'^.*\b(?:ok|yes|I\s+do|sure|accept|hit\s+me)\b.*$') |
170 | + def confirm(self, event): |
171 | + if not event.addressed: |
172 | + return |
173 | + |
174 | + if (event.source, event.channel) not in self.duels: |
175 | + return |
176 | + |
177 | + duel = self.duels[(event.source, event.channel)] |
178 | + |
179 | + if event.sender['nick'].lower() not in duel.names: |
180 | + return |
181 | + |
182 | + # Correct capitalisation |
183 | + duel.names[event.sender['nick'].lower()] = event.sender['nick'] |
184 | + |
185 | + duel.confirmed = True |
186 | + duel.cancel_callback.cancel() |
187 | + |
188 | + now = datetime.datetime.now() |
189 | + starttime = now + datetime.timedelta(seconds=self.start_delay + ((30 - now.second) % 30)) |
190 | + starttime = datetime.datetime(starttime.year, starttime.month, starttime.day, |
191 | + starttime.hour, starttime.minute, starttime.second) |
192 | + delay = starttime - now |
193 | + delay = delay.seconds + (delay.microseconds / 10.**6) |
194 | + |
195 | + duel.start_callback = ibid.dispatcher.call_later(delay, self.start, event) |
196 | + |
197 | + event.addresponse({'reply': ( |
198 | + u"%(aggressor)s, %(recipient)s: " |
199 | + u"The duel shall begin on the stroke of %(starttime)s %(timezone)s (in %(delay)s seconds). " |
200 | + + choice(( |
201 | + u"You may clean your pistols.", |
202 | + u"Prepare yourselves.", |
203 | + u"Get ready", |
204 | + )) |
205 | + ) % { |
206 | + 'aggressor': duel.names[duel.aggressor], |
207 | + 'recipient': duel.names[duel.recipient], |
208 | + 'starttime': starttime.time().isoformat(), |
209 | + 'timezone': time.tzname[0], |
210 | + 'delay': (starttime - now).seconds, |
211 | + }}) |
212 | + |
213 | + def start(self, event): |
214 | + duel = self.duels[(event.source, event.channel)] |
215 | + |
216 | + duel.started = True |
217 | + duel.timeout_callback = ibid.dispatcher.call_later(self.timeout, self.end, event) |
218 | + |
219 | + event.addresponse({'reply': |
220 | + u"%s, %s: %s" % (duel.aggressor, duel.recipient, choice(( |
221 | + u'aaaand ... go!', |
222 | + u'5 ... 4 ... 3 ... 2 ... 1 ... fire!', |
223 | + u'match on!', |
224 | + u'ready ... aim ... fire!' |
225 | + )))}) |
226 | + |
227 | + def setup(self): |
228 | + self.fire.im_func.pattern = re.compile( |
229 | + r'^(%s)(?:[\s,.!:;].*)?$' % '|'.join(self.weapons.keys()), |
230 | + re.I | re.DOTALL) |
231 | + |
232 | + @match(r'^draw(?:s\s+h(?:is|er)\s+.*|\s+my\s+.*)?$') |
233 | + def draw(self, event): |
234 | + if (event.source, event.channel) not in self.duels: |
235 | + if event.addressed: |
236 | + event.addresponse(choice(( |
237 | + u"We do not permit drawn weapons here", |
238 | + u"You may only draw a weapon on the field of honour", |
239 | + ))) |
240 | + return |
241 | + |
242 | + duel = self.duels[(event.source, event.channel)] |
243 | + |
244 | + shooter = event.sender['nick'] |
245 | + if shooter.lower() not in duel.names: |
246 | + event.addresponse(choice(( |
247 | + u"Spectators are not permitted to draw weapons", |
248 | + u"Do you think you are %(fighter)s?", |
249 | + )), {'fighter': choice(duel.names.values())}) |
250 | + return |
251 | + |
252 | + if not duel.started: |
253 | + event.addresponse(choice(( |
254 | + u"Now now, not so fast!", |
255 | + u"Did I say go yet?", |
256 | + u"Put that AWAY!", |
257 | + ))) |
258 | + return |
259 | + |
260 | + duel.drawn[shooter.lower()] = True |
261 | + event.addresponse(True) |
262 | + |
263 | + @handler |
264 | + def fire(self, event, weapon): |
265 | + shooter = event.sender['nick'].lower() |
266 | + if (event.source, event.channel) not in self.duels: |
267 | + return |
268 | + |
269 | + duel = self.duels[(event.source, event.channel)] |
270 | + |
271 | + if shooter not in duel.names: |
272 | + event.addresponse(choice(( |
273 | + u"You aren't in a war", |
274 | + u'You are a non-combatant', |
275 | + u'You are a spectator', |
276 | + ))) |
277 | + return |
278 | + |
279 | + enemy = set(duel.names.keys()) |
280 | + enemy.remove(shooter) |
281 | + enemy = enemy.pop() |
282 | + |
283 | + if self.draw_required and not duel.drawn[shooter]: |
284 | + recipient = shooter |
285 | + else: |
286 | + recipient = enemy |
287 | + |
288 | + if not duel.started or not duel.confirmed: |
289 | + if self.draw_required: |
290 | + message = choice(( |
291 | + u"%(shooter)s tried to escape his duel by shooting himself in the foot. The duel has been cancelled, but his honour is forfiet", |
292 | + u"%(shooter)s shot himself while preparing for his duel. The funeral will be held on the weekend", |
293 | + )) |
294 | + elif not duel.started: |
295 | + message = choice(( |
296 | + u"FOUL! %(shooter)s fired before my mark. Just as well you didn't hit anything. I refuse to referee under these conditions", |
297 | + u"FOUL! %(shooter)s injures %(enemy)s before the match started and is marched away in handcuffs", |
298 | + u"FOUL! %(shooter)s killed %(enemy)s before the match started and was shot by the referee before he could hurt anyone else", |
299 | + )) |
300 | + else: |
301 | + message = choice(( |
302 | + u"FOUL! The duel is not yet confirmed. %(shooter)s is marched away in handcuffs", |
303 | + u"FOUL! Arrest %(shooter)s! Firing a weapon within city limits is not permitted", |
304 | + )) |
305 | + event.addresponse({'reply': message % { |
306 | + 'shooter': duel.names[shooter], |
307 | + 'enemy': duel.names[enemy], |
308 | + }}) |
309 | + del self.duels[(event.source, event.channel)] |
310 | + if duel.cancel_callback.active(): |
311 | + duel.cancel_callback.cancel() |
312 | + if duel.start_callback.active(): |
313 | + duel.start_callback.cancel() |
314 | + return |
315 | + |
316 | + chance, power = self.weapons[weapon.lower()] |
317 | + |
318 | + if random() < chance: |
319 | + damage = max(gauss(power, power/2.0), 0) |
320 | + duel.hp[recipient] -= damage |
321 | + if duel.hp[recipient] <= 0.0: |
322 | + del self.duels[(event.source, event.channel)] |
323 | + duel.timeout_callback.cancel() |
324 | + else: |
325 | + duel.timeout_callback.delay(self.extratime) |
326 | + |
327 | + params = { |
328 | + 'shooter': duel.names[shooter], |
329 | + 'enemy': duel.names[enemy], |
330 | + 'part': u'foot', |
331 | + } |
332 | + if shooter == recipient: |
333 | + message = u"TRAGEDY: %(shooter)s shoots before drawing his weapon. " |
334 | + if damage > 100.0: |
335 | + message += choice(( |
336 | + u"The explosion killed him", |
337 | + u"There was little left of him", |
338 | + )) |
339 | + elif duel.hp[recipient] <= 0.0: |
340 | + message += choice(( |
341 | + u"Combined with his other injuries, he didn't stand a chance", |
342 | + u"He died during field surgary", |
343 | + )) |
344 | + else: |
345 | + message += choice(( |
346 | + u"Luckily, it was only a flesh wound", |
347 | + u"He narrowly missed his femoral artery", |
348 | + )) |
349 | + |
350 | + elif damage > 100.0: |
351 | + message = u'VICTORY: ' + choice(( |
352 | + u'%(shooter)s blows %(enemy)s away', |
353 | + u'%(shooter)s destroys %(enemy)s', |
354 | + )) |
355 | + elif duel.hp[enemy] <= 0.0: |
356 | + message = u'VICTORY: ' + choice(( |
357 | + u'%(shooter)s kills %(enemy)s with a shot to the %(part)s', |
358 | + u'%(shooter)s shoots %(enemy)s killing him with a fatal shot to the %(part)s', |
359 | + )) |
360 | + params['part'] = choice(self.vitals) |
361 | + else: |
362 | + message = choice(( |
363 | + u'%(shooter)s hits %(enemy)s in the %(part)s, wounding him', |
364 | + u'%(shooter)s shoots %(enemy)s in the %(part)s, but %(enemy)s can still fight', |
365 | + )) |
366 | + params['part'] = choice(self.extremities) |
367 | + |
368 | + event.addresponse({'reply': message % params}) |
369 | + |
370 | + elif shooter == recipient: |
371 | + event.addresponse({'reply': choice(( |
372 | + u"%s forget to draw his weapon. Luckily he missed his foot", |
373 | + u"%s fires a holstered weapon. Luckily it only put a hole in his jacket", |
374 | + u"%s won't win at this rate. He forgot to draw before firing. He missed himself too", |
375 | + )) % duel.names[shooter]}) |
376 | + else: |
377 | + event.addresponse({'reply': choice(( |
378 | + u'%s misses', |
379 | + u'%s aims wide', |
380 | + u'%s is useless with a weapon' |
381 | + )) % duel.names[shooter]}) |
382 | + |
383 | + def end(self, event): |
384 | + duel = self.duels[(event.source, event.channel)] |
385 | + del self.duels[(event.source, event.channel)] |
386 | + |
387 | + winner, loser = duel.names.keys() |
388 | + if duel.hp[winner] < duel.hp[loser]: |
389 | + winner, loser = loser, winner |
390 | + |
391 | + if duel.hp[loser] == 100.0: |
392 | + message = u"DRAW: %(winner)s and %(loser)s shake hands and %(ending)s" |
393 | + elif duel.hp[winner] < 50.0: |
394 | + message = u"DRAW: %(winner)s and %(loser)s bleed to death together" |
395 | + elif duel.hp[loser] < 50.0: |
396 | + message = u"VICTORY: %(loser)s bleeds to death" |
397 | + elif duel.hp[winner] < 100.0: |
398 | + message = u"DRAW: %(winner)s and %(loser)s hobble off together. Satisfaction is obtained" |
399 | + else: |
400 | + message = u"VICTORY: %(loser)s hobbles off while %(winner)s looks victorious" |
401 | + |
402 | + event.addresponse({'reply': message % { |
403 | + 'loser': duel.names[loser], |
404 | + 'winner': duel.names[winner], |
405 | + 'ending': choice(self.happy_endings), |
406 | + }}) |
407 | + |
408 | +# vi: set et sta sw=4 ts=4: |
Do we want this? The #compsci guys use it when drunk.