Merge lp:~stefanor/ibid/mexican-shootout into lp:~ibid-core/ibid/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
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.

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

Do we want this? The #compsci guys use it when drunk.

Revision history for this message
Jonathan Hitchcock (vhata) wrote : Posted in a previous version of this proposal

<3

review: Approve
lp:~stefanor/ibid/mexican-shootout updated
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
Michael Gorven (mgorven) wrote :

 review approve

review: Approve
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:

Subscribers

People subscribed via source and target branches