]> jfr.im git - irc/rizon/acid.git/blob - pyva/src/main/python/erepublik/cmd_user.py
Import acidictive 4 and pyva plugin
[irc/rizon/acid.git] / pyva / src / main / python / erepublik / cmd_user.py
1 import itertools
2 import random
3 import re
4 from datetime import datetime
5 from decimal import Decimal, InvalidOperation
6 from math import ceil
7 from operator import itemgetter
8
9 from api import battle, citizen, company, country, exchange, extapi, feed, map as erepmap, market, region, utils
10 from cmd_manager import *
11 from utils import *
12 from erepublik_utils import *
13
14 def command_citizen_register(self, manager, opts, arg, channel, sender):
15 try:
16 c = citizen.from_heapy(int(arg), by_id=True) if 'id' in opts else citizen.from_heapy(arg)
17 except ValueError, e:
18 self.errormsg(channel, '%s is not a valid id.' % arg)
19 return
20 except feed.FeedError, e:
21 if e.msg == 'Citizen not found or not included in database':
22 if 'id' not in opts:
23 self.errormsg(channel, 'to link a citizen, use: @b.regnick -i @uyour_id@u@b')
24 return
25 try:
26 c = citizen.from_html(int(arg))
27 except feed.FeedError, e:
28 self.errormsg(channel, e.msg if e.msg != 'not found.' else 'citizen not found.')
29 return
30 else:
31 self.errormsg(channel, e.msg)
32 return
33
34 self.users.set(sender, 'citizen', c.id)
35 self.msg(channel, '%s: registered eRepublik citizen @b%s@b' % (sender, c.name))
36
37 def command_citizen_info(self, manager, opts, arg, channel, sender):
38 c = self.get_citizen(opts, arg, channel, sender)
39
40 if c == None:
41 return
42
43 if not c.is_organization:
44 position = 'President' if c.is_president else 'Congressman' if c.is_congressman else ''
45
46 message = u"""@sep @b{c.name}@b [{c.id}] @sep @bWellness@b {c.wellness} @sep @bStrength@b {c.strength:,.2f} @sep @bRank@b {c.rank[name]} \
47 [{c.rank[id]}] {c.rank_points:,}/{dmg_to_rank:,} points @sep @bLevel@b {c.level} ({c.experience:,} XP) @sep @bAge@b {age:,} days @sep \
48 @bLocation@b {c.region[name]}, {c.country[name]} @sep @bCitizenship@b {c.citizenship[name]}{position}{party}{employer} @sep""".format(
49 c = c,
50 dmg_to_rank = c.rank['points'] - 1,
51 age = c.age,
52 position = ' @sep @bPosition@b %s' % position if position else '',
53 party = ' @sep @bParty@b %s' % (c.party['name']) if c.is_party_member else '',
54 employer = u' @sep @bEmployed at@b {c.employer[name]} [{c.employer[id]}]'.format(c=c) if c.employer else '')
55
56 else:
57 message = '@sep [Org] @b%(name)s@b [%(id)d] @sep @bLocation@b %(region)s, %(country)s @sep @bAge@b %(age)d days @sep' % {
58 'name' : c.name,
59 'id' : c.id,
60 'age' : c.age,
61 'region' : c.region['name'],
62 'country' : c.country['name']}
63
64 self.msg(channel, format_citizen_message(c, message))
65
66 def command_citizen_avatar(self, manager, opts, arg, channel, sender):
67 c = self.get_citizen(opts, arg, channel, sender)
68
69 if c == None:
70 return
71
72 if 'small' in opts:
73 av = c.avatar
74 elif 'medium' in opts:
75 av = c.avatar.replace('_55x55', '_100x100')
76 elif 'large' in opts:
77 av = c.avatar.replace('_55x55', '_142x142')
78 else:
79 av = c.avatar.replace('_55x55', '')
80
81 self.msg(channel, format_citizen_message(c, "@sep @b%(name)s@b'%(s)s avatar link @sep %(link)s @sep" % {
82 'name': c.name,
83 's' : 's' if c.name[-1] != 's' else '',
84 'link': av}))
85
86 def command_citizen_fightcalc(self, manager, opts, arg, channel, sender):
87 fights = 1 if 'fights' not in opts or 'objective' in opts else opts['fights']
88 if fights >= 100000 or fights == 0:
89 self.errormsg(channel, 'fights: expected value (0 <= x <= 100000), got %d instead' % fights)
90 return
91
92 if 'rank' in opts and 'strength' in opts:
93 c = None
94 else:
95 c = self.get_citizen(opts, arg, channel, sender)
96
97 if c == None:
98 return
99
100 if c.is_organization:
101 self.errormsg(channel, '%s is an organization.' % c.name)
102 return
103
104 colors = [10, 9, 3, 8, 7, 4, 5, 6]
105 rank = c.rank if 'rank' not in opts else opts['rank']
106 strength = c.strength if 'strength' not in opts else opts['strength']
107 natural_enemy = True if 'naturalenemy' in opts else False
108
109 if 'nextrank' in opts:
110 if not c:
111 self.errormsg(channel, 'option -N needs a citizen')
112 return
113
114 natural_enemy = False # NE bonus removed for training
115 objective = (c.rank['points'] - c.rank_points) * 10
116 obj_str = 'fights required to rank up - {inf:,} '.format(inf=objective)
117 elif 'influence' in opts:
118 if not c:
119 self.errormsg(channel, 'option -I needs a citizen')
120 return
121
122 if opts['influence'] >= 10000000000:
123 self.errormsg(channel, 'option -I out of range')
124 return
125
126 natural_enemy = False
127 objective = 10 * (opts['influence'] - c.rank_points)
128 if objective < 0:
129 self.errormsg(channel, 'this citizen already has more than {rp:,} rank points'.format(rp=opts['influence']))
130 return
131
132 obj_str = 'fights required to reach {rp:,} rank points, {inf:,} '.format(rp=opts['influence'], inf=objective)
133 elif 'objective' in opts:
134 objective = opts['objective']
135 if objective >= 10000000000:
136 self.errormsg(channel, 'option -o out of range')
137 return
138
139 obj_str = 'fights required for {inf:,} '.format(inf=objective)
140 else:
141 objective = None
142 obj_str = ''
143
144 damage = [utils.fight_calc(q*20 if q != 7 else 200, rank, strength, natural_enemy) for q in range(8)]
145 if objective:
146 damage = [1 + (objective / d) if objective % d != 0 else objective / d for d in damage]
147 damagestr = ['@c%d[Q%d: @b%d@b]@c' % (z[0], q, fights * z[1]) for q, z in enumerate(zip(colors, damage))]
148
149 message = '@sep @b%(name)s@b(%(rank)s, %(strength)s strength%(ne)s) %(obj)sinfluence%(fights)s @sep %(damage)s @sep' % {
150 'name' : '%s ' % c.name if c else '',
151 'rank' : '%s [%d]' % (rank['name'], rank['id']),
152 'strength': strength,
153 'ne' : ', against Natural Enemy' if natural_enemy else '',
154 'obj' : obj_str,
155 'fights' : ' in %d fights' % fights if fights != 1 else '',
156 'damage' : ' '.join(damagestr)}
157
158 self.msg(channel, format_citizen_message(c, message))
159
160 def command_citizen_sscalc(self, manager, opts, arg, channel, sender):
161 natural_enemy = True if 'naturalenemy' in opts else False
162 friends = 0 if 'friends' not in opts else opts['friends']
163 if friends not in [0, 1, 2]:
164 self.errormsg(channel, 'Invalid number of friends. Acceptable values: 0, 1, 2.')
165 return
166
167 if 'strength' in opts:
168 c = None
169 else:
170 c = self.get_citizen(opts, arg, channel, sender)
171
172 if c == None:
173 return
174
175 if c.is_organization:
176 self.errormsg(channel, '%s is an organization.' % c.name)
177 return
178
179 strength = c.strength if 'strength' not in opts else opts['strength']
180 base = 6
181 bonus = ['daily reward (+1 str)']
182 bonus.append('%d friend%s%s' % (friends, 's' if friends != 1 else '', ' (+%s str)' % (friends * 0.5) if friends > 0 else ''))
183
184 next = (int(strength / 250) + 1) * 250
185 diff = next - strength
186 inc = base
187 cost = Decimal('0')
188
189 septbonus = 'septemberbonus' in opts
190 if septbonus:
191 inc += Decimal('1.5')
192 bonus.append('September training bonus (+30% str)')
193
194 if 'climbingcenter' in opts:
195 cost += Decimal('0.19')
196 inc += Decimal('2.5')
197 if septbonus:
198 inc += Decimal('0.8')
199 bonus.append('climbing center (+2.5 str, +0.19g)')
200 if 'shootingrange' in opts:
201 cost += Decimal('1.49')
202 inc += 5
203 if septbonus:
204 inc += Decimal('1.5')
205 bonus.append('shooting range (+5 str, +1.49g)')
206 if 'specialforces' in opts:
207 cost += Decimal('1.79')
208 inc += 10
209 if septbonus:
210 inc += 3
211 bonus.append('special forces (+10 str, +1.79g)')
212 if natural_enemy:
213 inc += Decimal('0.5')
214 bonus.append('natural enemy (+0.5 str)')
215
216 inc += friends * Decimal('0.5')
217
218 days = int(ceil(diff / inc))
219 total_cost = cost * days
220 message = """@sep {name}({strength:,} / {required:,} strength) days/gold required until next Super Soldier medal @sep @bResult@b {days} days and \
221 {gold:.2f} gold @sep @bBonus@b [+{bonus} / day] {bonus_list} @sep""".format(
222 name = '@b%s@b ' % c.name if c else '',
223 strength = strength,
224 required = next,
225 days = days,
226 gold = total_cost,
227 bonus = inc,
228 bonus_list = ', '.join(bonus))
229 self.msg(channel, format_citizen_message(c, message))
230
231 def command_citizen_link(self, manager, opts, arg, channel, sender):
232 c = self.get_citizen(opts, arg, channel, sender)
233
234 if c == None:
235 return
236
237 if not c.is_organization:
238 message = '@sep @b%(name)s@b profile @sep http://www.erepublik.com/en/citizen/profile/%(id)d @sep' % {
239 'name' : format_name(c.name),
240 'id' : c.id}
241 else:
242 message = '@sep @b%(name)s@b profile @sep http://economy.erepublik.com/en/accounts/%(id)d @sep' % {
243 'name' : format_name(c.name),
244 'id' : c.id}
245
246 self.msg(channel, format_citizen_message(c, message))
247
248 def command_citizen_donate(self, manager, opts, arg, channel, sender):
249 c = self.get_citizen(opts, arg, channel, sender)
250
251 if c == None:
252 return
253
254 self.msg(channel, format_citizen_message(c, """@sep @b%(name)s@b donations @sep @bItems@b http://www.erepublik.com/en/economy/donate-items/%(id)d @sep \
255 @bMoney@b http://www.erepublik.com/en/economy/donate-money/%(id)d @sep""") % {
256 'name' : format_name(c.name),
257 'id' : c.id})
258
259 def command_citizen_message(self, manager, opts, arg, channel, sender):
260 c = self.get_citizen(opts, arg, channel, sender)
261
262 if c == None:
263 return
264
265 self.msg(channel, format_citizen_message(c, '@sep @b%(name)s@b message link @sep http://www.erepublik.com/en/main/messages-compose/%(id)d @sep') % {
266 'name' : format_name(c.name),
267 'id' : c.id})
268
269 def command_citizen_medals(self, manager, opts, arg, channel, sender):
270 c = self.get_citizen(opts, arg, channel, sender, False)
271
272 if c == None:
273 return
274
275 medals = []
276 total = 0
277
278 for medal in sorted(c.medals, key = lambda medal: c.medals[medal], reverse = True):
279 medals.append('@b%d@bx %s' % (c.medals[medal], medal.title()))
280 total += c.medals[medal]
281
282 message = '@sep @b%(name)s@b @sep @bMedals@b %(count)d%(medals)s @sep' % {
283 'name' : c.name,
284 'count' : total,
285 'medals': ' @sep %s' % ' @sep '.join(medals) if total > 0 else ''}
286
287 self.msg(channel, format_citizen_message(c, message))
288
289 def command_citizen_rankings(self, manager, opts, arg, channel, sender):
290 c = self.get_citizen(opts, arg, channel, sender, no_api_request=True)
291
292 if c == None:
293 return
294
295 try:
296 if c['id']:
297 rankings = extapi.get_rankings(c['id'])
298 else:
299 rankings = extapi.get_rankings(c['name'])
300 except feed.FeedError, e:
301 self.errormsg(channel, e.msg)
302 return
303
304 if 'error' in rankings:
305 self.errormsg(channel, rankings['error'])
306 self.elog.warning('[warning] 1way API fail: %s' % rankings['error'])
307 return
308
309 country = rankings['country_rankings']
310 world = rankings['world_rankings']
311
312 def format_ranking(source):
313 return '@bStrength@b {str} ({cstr:,.2f}) @sep @bRank points@b {rp} ({r[rp]:,}) @sep @bQ5 Influence@b {inf} ({r[inf]:,}) @sep @bXP@b {xp} ({r[xp]:,})'.format(
314 r = rankings,
315 str = format_ordinal(source['strength'], add_thousands_sep=True),
316 cstr = Decimal(rankings['str']),
317 rp = format_ordinal(source['rank_points'], add_thousands_sep=True),
318 inf = format_ordinal(source['influence'], add_thousands_sep=True),
319 xp = format_ordinal(source['level'], add_thousands_sep=True))
320
321 self.msg(channel, format_citizen_message(rankings, u"""@sep @b{r[name]}@b [{r[id]}] rankings in @b{country_name}@b ({country[total]:,} citizens) @sep {ranking_str} @sep""".format(
322 r = rankings,
323 country = country,
324 ranking_str = format_ranking(country),
325 country_name = erepmap.get_country_name(rankings['cid'])), rankings['sex']))
326 self.msg(channel, format_citizen_message(rankings, u"""@sep @b{r[name]}@b [{r[id]}] @bglobal@b rankings ({world[total]:,} citizens) @sep {ranking_str} @sep""".format(
327 r = rankings,
328 world = world,
329 ranking_str = format_ranking(world)), rankings['sex']))
330
331 def command_market_info(self, manager, opts, arg, channel, sender):
332 regex = re.compile("^(?:(\D+?)\s(\D+?))(?:\s([1-6]))?$")
333 r = regex.search(arg)
334
335 if r == None or len(r.groups()) != 3:
336 self.usagemsg(channel, '.market INDUSTRY COUNTRY QUALITY', ['.market food usa 2', '.market grain italy'])
337 return
338
339 industry, country, quality = r.groups()
340
341 if not quality:
342 quality = 1
343 else:
344 quality = int(quality)
345
346 try:
347 industry = utils.get_industry(industry)
348 except utils.InvalidValueError, err:
349 self.errormsg(channel, str(err))
350 return
351
352 if quality > 5 and industry['id'] != 1 and industry['id'] != 2:
353 self.errormsg(channel, 'quality parameter out of range (1-5)')
354 return
355
356 try:
357 if country != 'world':
358 country_id = erepmap.get_country_id(country)
359 if country_id == None:
360 self.errormsg(channel, 'country \'%s\' not found.' % country)
361 return
362 country_name = erepmap.get_country_name(country_id)
363 else:
364 country_id = 0
365 country_name = 'World'
366 except feed.FeedError, e:
367 self.errormsg(channel, e.msg)
368 return
369
370 try:
371 m = market.get(industry, country_id, quality)
372 except feed.FeedError, e:
373 self.errormsg(channel, e.msg)
374 return
375
376 if len(m) > 0:
377 best = m[0]
378 currency = erepmap.get_country_currency(best['country'])
379
380 self.msg(channel, u"""@sep Best offer for {quality}@b{product}@b in @b{country}@b @sep @bPrice@b {off[price]} {currency} ({off[goldprice]} GOLD) \
381 {off[amount]} available @sep @bLink@b {link} @sep @bLast update@b {last_update} ago @sep""".format(
382 quality = 'Q%d ' % quality if industry['domain']['name'] != 'Land' else '',
383 product = industry['name'],
384 country = country_name,
385 off = best,
386 currency = currency,
387 link = 'http://economy.erepublik.com/en/market/%d/%d/%d' % (best['country'], industry['market_id'], quality),
388 last_update = get_timespan(datetime.fromtimestamp(best['last_updated_ts']))))
389 else:
390 self.msg(channel, 'No products available on the market with selected attributes.')
391
392 def command_region_info(self, manager, opts, arg, channel, sender):
393 r = self.get_region(opts, arg, channel, sender)
394
395 if r == None:
396 return
397
398 self.msg(channel, '@sep @b%(region)s, %(country)s@b [%(id)d] @sep @bPopulation@b %(pop)d%(raw)s%(cons)s @sep' % {
399 'region' : r.name,
400 'country' : r.country['name'],
401 'id' : r.id,
402 'pop' : r.population,
403 'raw' : ' @sep @bResource@b %s' % r.raw_materials if r.raw_materials else '',
404 'cons' : ' @sep @bConstructions@b%s%s' % (' %dx hospital%s' % (len(r.hospitals), 's' if len(r.hospitals) != 1 else '') if r.hospitals != 0 else '', ' %dx defense system%s' % (len(r.defense), 's' if len(r.defense) != 1 else '') if r.defense != 0 else '') if len(r.hospitals) != 0 and len(r.defense) != 0 else ''})
405
406 def command_region_borders(self, manager, opts, arg, channel, sender):
407 r = self.get_region(opts, arg, channel, sender)
408
409 if r == None:
410 return
411
412 neighbors = {}
413
414 for n in r.neighbours:
415 country = n['country']['name']
416 reg = n['region']['name']
417
418 if country in neighbors:
419 neighbors[country].append(reg)
420 else:
421 neighbors[country] = [reg]
422
423 self.msg(channel, '@sep Borders of @b%(region)s, %(country)s@b @sep %(borders)s @sep' % {
424 'region' : r.name,
425 'country' : r.country['name'],
426 'borders' : ' @sep '.join(['@b%s:@b %s' % (n , ', '.join(neighbors[n])) for n in neighbors])})
427
428 def command_region_distance(self, manager, opts, arg, channel, sender):
429 if ',' in arg:
430 start, end = arg.split(',', 1)
431 else:
432 start, end = None, arg
433
434 if not end:
435 self.errormsg(channel, 'region not found')
436 return
437
438 if start:
439 c = None
440 else:
441 c = self.get_citizen(opts, '', channel, sender)
442 if c == None:
443 return
444 start = c.region['name']
445
446 start_zone = erepmap.get_region_zone(start.strip())
447 if not start_zone:
448 self.errormsg(channel, 'region not found')
449 return
450
451 end_country = erepmap.get_country_id(end.strip())
452 if not end_country:
453 end_zone = erepmap.get_region_zone(end.strip()) # not a country, check to see if it's a region name
454 if not end_zone: # not even a region
455 self.errormsg(channel, 'region not found')
456 return
457 else: # it's a region, calculate path normally
458 distance = erepmap.get_region_distance(start_zone, end_zone)
459 self.msg(channel, '@sep {start[name]} [{szone}] -> {end[name]} [{ezone}] @sep @bDistance@b {dist} zones @sep @bCost@b {cost} currency units @sep'.format(
460 start = erepmap.get_region(start.strip()),
461 end = erepmap.get_region(end.strip()),
462 szone = start_zone,
463 ezone = end_zone,
464 dist = distance,
465 cost = 20 * distance
466 ))
467 else: # get all regions in this country, check shortest path for each region in a different zone
468 country_regions = country.get_regions(end_country)
469 country_regions = country_regions['regions']
470 paths = [] # [{'zone': zone, 'regions': regions in this zone, iterator group, 'cost': movement cost}, ...]
471 for zone, zone_regions in itertools.groupby(sorted(country_regions, key=itemgetter('zone')), itemgetter('zone')):
472 paths.append({'zone': zone, 'cost': erepmap.get_region_distance(start_zone, zone), 'regions': list(zone_regions)})
473
474 cheapest = sorted(paths, key=itemgetter('cost'))
475 if cheapest:
476 cheapest = cheapest[0]
477 self.msg(channel, '@sep {start[name]} [{szone}] -> @b{endcountry}@b ({end[name]}) [{ezone}] @sep @bDistance@b {dist} zones @sep @bCost@b {cost} currency units @sep'.format(
478 start = erepmap.get_region(start.strip()),
479 end = random.choice(cheapest['regions']),
480 endcountry = erepmap.get_country_name(end_country),
481 szone = start_zone,
482 ezone = cheapest['zone'],
483 dist = cheapest['cost'],
484 cost = 20 * cheapest['cost']
485 ))
486 else:
487 self.errormsg(channel, '@b%s@b has no regions' % erepmap.get_country_name(end_country))
488
489 def command_country_info(self, manager, opts, arg, channel, sender):
490 if 'id' in opts:
491 try:
492 country_id = int(arg)
493 except ValueError:
494 self.errormsg(channel, '%s is not a valid id.' % arg)
495 return
496 else:
497 country_id = erepmap.get_country_id(arg)
498
499 if not country_id:
500 self.errormsg(channel, 'country @b%s@b not found.' % arg)
501 return
502
503 try:
504 countryfeed = country.from_id(country_id)
505 except feed.FeedError as e:
506 self.errormsg(channel, e.msg)
507 return
508
509 if 'error' in countryfeed:
510 self.errormsg(channel, countryfeed['error'])
511 self.elog.warning('[warning] 1way API fail: %s' % countryfeed['error'])
512 return
513
514 if len(countryfeed['citizenships']) > 4:
515 cits = countryfeed['citizenships'][:3]
516 cits.append({'amount': sum(map(itemgetter('amount'), countryfeed['citizenships'][3:])), 'name': 'other countries'})
517 else:
518 cits = countryfeed['citizenships']
519
520 if len(countryfeed['residents']) > 4:
521 resids = countryfeed['residents'][:3]
522 resids.append({'amount': sum(map(itemgetter('amount'), countryfeed['residents'][3:])), 'name': 'other countries'})
523 else:
524 resids = countryfeed['residents']
525
526 self.msg(channel, u"""@sep @b{country[name]}@b [{country[id]}] @sep @bCapital@b {capital} \
527 @sep @bCitizenships@b {country[citizenships_total]:,} [{citizenships}] @sep @bResidents@b {country[residents_total]:,}{residents} \
528 @sep @bLink@b http://www.erepublik.com/en/country/society/{country[link]} @sep""".format(
529 country = countryfeed,
530 capital = countryfeed['capital']['name'] if countryfeed['capital'] else 'None',
531 citizenships = ', '.join(['{percent:.1%} in {country}'.format(percent = c['amount'] / float(countryfeed['citizenships_total']), country = c['name']) for c in cits]),
532 residents = (' [%s]' % ', '.join(['{percent:.1%} from {country}'.format(percent = c['amount'] / float(countryfeed['residents_total']), country = c['name']) for c in resids])) if resids else ''))
533
534
535 def command_country_resources(self, manager, opts, arg, channel, sender):
536 if 'id' in opts:
537 try:
538 country_id = int(arg)
539 except ValueError:
540 self.errormsg(channel, '%s is not a valid id.' % arg)
541 return
542 else:
543 country_id = erepmap.get_country_id(arg)
544
545 if not country_id:
546 self.errormsg(channel, 'country @b%s@b not found.' % arg)
547 return
548
549 try:
550 regions = country.get_regions(country_id)
551 except feed.FeedError as e:
552 self.errormsg(channel, e.msg)
553 return
554
555 if 'error' in regions:
556 self.errormsg(channel, regions['error'])
557 self.elog.warning('[warning] 1way API fail: %s' % regions['error'])
558 return
559
560 country_info = regions['country']
561 region_list = regions['regions']
562
563 frm = [7, 8, 9, 10, 11]
564 wrm = [12, 13, 14, 15, 16]
565
566 food_regions = []
567 weapon_regions = []
568
569 for region in itertools.ifilter(itemgetter('is_connected'), region_list):
570 bonus = region['resource']['id']
571 if bonus in frm:
572 food_regions.append(region)
573 elif bonus in wrm:
574 weapon_regions.append(region)
575
576 food_regions = dict((key['name'], list(group)) for (key, group) in itertools.groupby(sorted(food_regions, key=itemgetter('resource')), itemgetter('resource')))
577 weapon_regions = dict((key['name'], list(group)) for (key, group) in itertools.groupby(sorted(weapon_regions, key=itemgetter('resource')), itemgetter('resource')))
578
579 self.msg(channel, """@sep @b{country[name]}@b resources @sep @bFood@b +{food_bonus}%{food_list} @sep @bWeapons@b +{weapons_bonus}%{weapons_list} @sep \
580 @bLink@b http://www.erepublik.com/en/country/economy/{country[link]} @sep""".format(
581 country = country_info,
582 food_bonus = 20 * len(food_regions),
583 weapons_bonus = 20 * len(weapon_regions),
584 food_list = (' (%s)' % ', '.join(['%dx %s' % (len(regions), key) for key, regions in sorted(food_regions.iteritems())])) if food_regions else '',
585 weapons_list = (' (%s)' % ', '.join(['%dx %s' % (len(regions), key) for key, regions in sorted(weapon_regions.iteritems())])) if weapon_regions else ''
586 ))
587
588 def command_country_tax(self, manager, opts, arg, channel, sender):
589 sp = arg.split(' ', 1)
590 if len(sp) == 1:
591 self.usagemsg(channel, '.tax industry country', ['.tax food italy'])
592 return
593
594 industry, country_name = sp
595 try:
596 industry = utils.get_industry(industry)
597 except utils.InvalidValueError, err:
598 self.errormsg(channel, str(err))
599 return
600
601 country_id = erepmap.get_country_id(country_name)
602 if not country_id:
603 self.errormsg(channel, 'invalid country name')
604 return
605
606 res = country.get_taxes(country_id)
607 tax = itertools.ifilter(lambda x: x['industry']['id'] == industry['market_id'], res['taxes']).next()
608 self.msg(channel, """@sep @bTaxes@b in {country_name} @sep @bIndustry@b {tax[industry][name]} @sep @bIncome@b {tax[income]}% @sep @bImport@b {tax[import]}% @sep \
609 @bVAT@b {tax[vat]}% @sep @bLast updated@b {last_update} ago @sep""".format(
610 country_name = res['country']['name'],
611 tax = tax,
612 last_update = get_timespan(datetime.fromtimestamp(res['last_updated_ts']))))
613
614 def command_country_mpp(self, manager, opts, arg, channel, sender):
615 if arg == '' and not 'id' in opts:
616 citizen = self.get_citizen(opts, arg, channel, sender)
617
618 if citizen == None:
619 return
620
621 arg = citizen.country['id']
622 opts['id'] = True
623
624 try:
625 id = int(arg) if 'id' in opts else erepmap.get_country_id(arg)
626 except ValueError:
627 self.errormsg(channel, '%s is not a valid id.' % arg)
628 except feed.FeedError, e:
629 self.errormsg(channel, e.msg)
630 return
631
632 if id == None:
633 self.errormsg(channel, 'country not found.')
634 return
635
636 name = erepmap.get_country_name(id)
637 if not name:
638 self.errormsg(channel, 'country not found.')
639 return
640
641 try:
642 mpps = erepmap.get_mpp_list(id)
643 except feed.FeedError, e:
644 self.errormsg(channel, e.msg)
645 return
646
647 message = ['@b%s@b %s' % (mpp['country'], '%s/%s' % (mpp['expiration'][6:8], mpp['expiration'][4:6])) for mpp in mpps]
648 count = len(mpps)
649
650 if count > 0:
651 self.multimsg(channel, 15, '@sep @b%(name)s@b @sep %(count)s MPP%(s)s @sep ' % {
652 'name' : name,
653 'count' : count,
654 's' : 's' if count != 1 else ''},
655 ' @sep ', message, ' @sep ')
656 else:
657 self.msg(channel, '@sep @b%(name)s@b has no active MPPs @sep' % {'name': name})
658
659 def command_battle_info(self, manager, opts, arg, channel, sender):
660 try:
661 battle_id = int(arg)
662 except ValueError:
663 self.errormsg(channel, '%s is not a valid battle id.' % arg)
664 return
665
666 try:
667 b = battle.get_battle(battle_id)
668 except feed.FeedError as e:
669 self.errormsg(channel, e.msg)
670 return
671
672 if not b.active:
673 self.msg(channel, '@sep @bBattle [{b.id}]@b {b.region[name]} @sep {attacker}@b vs @b{b.defender[name]}@b @sep @bWinner@b {winner} @sep @bEnded@b {b.finished_at} @sep'.format(
674 b = b,
675 attacker = '@b' + b.attacker['name'] if not b.is_resistance else 'Resistance force of @b%s' % b.attacker['name'],
676 winner = erepmap.get_country_name(b.winner_id)))
677 else:
678 domination = b.status['domination']
679 if domination > 50:
680 separator = '<'
681 elif domination < 50:
682 separator = '>'
683 else:
684 separator = '-'
685 self.msg(channel, u"""@sep @bBattle [{b.id}]@b {b.region[name]} @sep {attacker}@b vs @b{b.defender[name]}@b @sep \
686 {status} @sep @bScore@b {b.status[attacker_points]} to \
687 {b.status[defender_points]} @sep @bTime@b {b.status[elapsed_time]} minutes @sep @bLink@b http://www.erepublik.com/en/military/battlefield/{b.id} @sep""".format(
688 b = b,
689 attacker = '@b' + b.attacker['name'] if not b.is_resistance else 'Resistance force of @b%s' % b.attacker['name'],
690 status = '@bDomination@b {wallatt:.2f}% {separator} {domination:.2f}%'.format(
691 wallatt = 100 - domination,
692 separator = separator,
693 domination = domination) if b.status['attacker_points'] < 1800 and b.status['defender_points'] < 1800 else
694 '@b{winner}@b won this round @sep @bStatus@b pending'.format(winner = b.attacker['name'] if b.status['attacker_points'] >= 1800 else b.defender['name'])
695 ))
696
697 def command_battle_list(self, manager, opts, arg, channel, sender):
698 try:
699 battles = erepmap.get_battles(arg)
700 except feed.FeedError, e:
701 self.errormsg(channel, e.msg)
702 return
703
704 message = [u'[{b[battle_id]}] {b[attacker]} vs {b[defender]} in {b[region]}'.format(b=b) for b in sorted(battles, key=itemgetter('battle_id'))]
705
706 if len(message) == 0:
707 self.notice(sender, 'There are no active battles matching your search.')
708 else:
709 self.multinotice(sender, 6, '@sep @bActive battles@b%s @sep ' % (' [%s]' % arg if arg != '' else ''), ' @sep ', message, ' @sep ')
710
711 def command_mass(self, manager, opts, arg, channel, sender):
712 if not self.channels[channel].mass:
713 self.notice(sender, 'The .mass command is disabled for this channel. To enable it, type @b/msg eRepublik chanmass %s@b (founder only).' % channel)
714 return
715
716 chan = self.inter.findChannel(channel)
717 senderinfo = self.inter.findUser(sender)
718 if not chan or not senderinfo:
719 return
720
721 sender_modes = chan.getModes(senderinfo.getNick())
722 if '@' not in sender_modes and '&' not in sender_modes and '~' not in sender_modes:
723 self.notice(sender, 'This command is only available to channel ops, admins and founders.')
724 return
725
726 members = chan.getUsers() # Set<String>
727 it = members.iterator()
728 userlist = []
729 while it.hasNext():
730 name = it.next()
731
732 u = self.inter.findUser(name)
733 if not u or u['server'].getName() in ['services.rizon.net', 'geo.rizon.net', 'py3.rizon.net']:
734 continue
735
736 user = u['nick']
737 if not self.users.is_valid(user) or self.users[user].mass:
738 userlist.append(user)
739
740 if userlist:
741 out = ''
742 while len(userlist) > 0:
743 if out:
744 out += ' ' + userlist.pop()
745 else:
746 out += userlist.pop()
747 if len(out) > 400:
748 self.msg(channel, '@sep @bMass@b @sep %s @sep' % out)
749 out = ''
750 self.msg(channel, '@sep @bMass@b @sep %s @sep' % out)
751 self.msg(channel, '@sep @bMass@b%s @sep %s @sep' % (' [@c4%s@c]' % arg if arg else '', 'To opt-out from being highlighted in .mass, type @b/msg eRepublik nousermass@b'))
752
753 def command_erepublik_info(self, manager, opts, arg, channel, sender):
754 self.notice(sender, '@sep @bRizon eRepublik Bot@b @sep @bDevelopers@b ElChE and martin @sep @bHelp/feedback@b %(channel)s @sep' % {
755 'channel' : '#fishbot'})
756
757 def command_erepublik_help(self, manager, opts, arg, channel, sender):
758 command = arg.lower()
759
760 if command == '':
761 message = ['erepublik: .help erepublik - for erepublik commands']
762 elif command == 'erepublik':
763 message = manager.get_help()
764 else:
765 message = manager.get_help(command)
766
767 if message == None:
768 message = ['%s is not a valid command.' % arg]
769
770 for line in message:
771 self.notice(sender, line)
772
773 id_opt = ('id', '-i', 'look up by id instead of name', {'action': 'store_true'}, ARG_YES)
774 nick_opt = ('nick', '-n', 'look up by irc nick', {'action': 'store_true'}, ARG_YES)
775 heapy_opt = ('heapy', '-h', 'use a custom API, not live data (might be a few days old) [use when erep API is offline]', {'action': 'store_true'}, ARG_OFFLINE)
776
777 class UserCommandManager(CommandManager):
778 def get_prefix(self):
779 return '.'
780
781 def get_commands(self):
782 return {
783 'regcit': 'register_citizen',
784 'regnick': 'register_citizen',
785 'register_citizen': (command_citizen_register, ARG_YES, 'links an eRepublik citizen to an IRC nickname', [
786 id_opt
787 ], 'citizen_name'),
788
789 'lp': 'lookup',
790 'lookup': (command_citizen_info, ARG_OPT, 'looks up a citizen', [
791 id_opt,
792 nick_opt,
793 heapy_opt
794 ]),
795
796 'fc': 'fightcalc',
797 'fightcalc': (command_citizen_fightcalc, ARG_OPT, 'calculates influence done with different weapon qualities', [
798 id_opt,
799 nick_opt,
800 heapy_opt,
801 ('strength', '-s', 'uses a custom strength', {'type': '+decimal'}, ARG_OFFLINE|ARG_OFFLINE_REQ),
802 ('rank', '-r', 'uses a custom military rank (use rank ID, not name)', {'type': 'rank'}, ARG_OFFLINE|ARG_OFFLINE_REQ),
803 ('fights', '-f', 'specifies number of fights', {'type': '+integer'}, ARG_OFFLINE),
804 ('naturalenemy', '-e', 'adds 10% natural enemy bonus to influence', {'action': 'store_true'}, ARG_OFFLINE),
805 ('nextrank', '-N', 'calculates how many fights are required to rank up', {'action': 'store_true'}),
806 ('objective', '-o', 'calculates how many fights are required to make a given amount of influence', {'type': '+integer'}, ARG_OFFLINE),
807 ('influence', '-I', 'calculates how many fights are required to reach a given amount of rank points', {'type': '+integer'}, ARG_OPT)
808 ]),
809
810 'ssc': 'sscalc',
811 'sscalc': (command_citizen_sscalc, ARG_OPT, 'calculates days/gold required for the next SuperSoldier medal', [
812 id_opt,
813 nick_opt,
814 heapy_opt,
815 ('strength', '-s', 'uses a custom strength', {'type': '+decimal'}, ARG_OFFLINE|ARG_OFFLINE_REQ),
816 ('friends', '-f', 'how many friends used in training (0, 1, 2)', {'type': '+integer'}, ARG_OFFLINE),
817 ('climbingcenter', '-1', 'adds climbing center bonus to training (+2.5 points)', {'action': 'store_true'}, ARG_OFFLINE),
818 ('shootingrange', '-2', 'adds shooting range bonus to training (+5 points)', {'action': 'store_true'}, ARG_OFFLINE),
819 ('specialforces', '-3', 'adds special forces bonus to training (+10 points)', {'action': 'store_true'}, ARG_OFFLINE),
820 ('naturalenemy', '-e', 'adds natural enemy bonus to training (+0.5 points)', {'action': 'store_true'}, ARG_OFFLINE),
821 ('septemberbonus', '-b', 'adds +30% bonus to daily training (September\'s bonus)', {'action': 'store_true'}, ARG_OFFLINE)
822 ]),
823
824 'link': (command_citizen_link, ARG_OPT, "link to a citizen's profile", [
825 id_opt,
826 nick_opt,
827 heapy_opt
828 ]),
829
830 'donate': (command_citizen_donate, ARG_OPT, 'link to the donation page of a citizen', [
831 id_opt,
832 nick_opt,
833 heapy_opt
834 ]),
835
836 'message': (command_citizen_message, ARG_OPT, 'link to send a message to a citizen', [
837 id_opt,
838 nick_opt,
839 heapy_opt
840 ]),
841
842 'avatar': (command_citizen_avatar, ARG_OPT, 'link to a citizen\'s avatar', [
843 id_opt,
844 nick_opt,
845 heapy_opt,
846 ('small', '-s', 'links to the small sized avatar (55x55px)', {'action': 'store_true'}, ARG_OPT),
847 ('medium', '-m', 'links to the medium sized avatar (100x100px)', {'action': 'store_true'}, ARG_OPT),
848 ('large', '-l', 'links to the large sized avatar (142x142px)', {'action': 'store_true'}, ARG_OPT)
849 ]),
850
851 'medals': (command_citizen_medals, ARG_OPT, 'medals owned by a citizen', [
852 id_opt,
853 nick_opt,
854 heapy_opt
855 ]),
856
857 # 'ranking': 'rankings',
858 # 'rankings': (command_citizen_rankings, ARG_OPT|ARG_OFFLINE, 'shows a citizen\'s ranking position', [
859 # id_opt,
860 # nick_opt
861 # ]),
862
863 'market': 'bestprice',
864 'bp' : 'bestprice',
865 'bestprice': (command_market_info, ARG_YES|ARG_OFFLINE, 'shows the best offer in the selected market', [], 'industry country quality'),
866
867 'region': (command_region_info, ARG_OPT, 'information about a region', [
868 id_opt
869 ]),
870
871 'borders': (command_region_borders, ARG_OPT, 'list of regions borders', [
872 id_opt
873 ]),
874
875 'dist': 'distance',
876 'distance': (command_region_distance, ARG_YES, 'calculates zones-distance between two regions', [], 'from_region, to_region'),
877
878 # 'country': (command_country_info, ARG_YES|ARG_OFFLINE, 'information about a country', [
879 # id_opt
880 # ], 'country_name'),
881
882 'res': 'resources',
883 'resource': 'resources',
884 'resources': (command_country_resources, ARG_YES|ARG_OFFLINE, "summary of a country's resources", [
885 id_opt
886 ], 'country_name'),
887
888 'tax': (command_country_tax, ARG_OPT|ARG_OFFLINE, 'information about a country', [], 'country industry'),
889
890 'mpp': 'mpps',
891 'mpps': (command_country_mpp, ARG_OPT, 'shows a country\'s active mpp list and their expiration date', [
892 id_opt
893 ]),
894
895 'battle': (command_battle_info, ARG_YES, 'information about a battle', [], 'battle_id'),
896
897 'battles': (command_battle_list, ARG_OPT, 'shows a list of active battles', []),
898
899 'mass': (command_mass, ARG_OPT, 'highlights all the nicks in the channel (limited to channel ops, admins and founders)', [], 'optional_message'),
900
901 'info': (command_erepublik_info, ARG_NO|ARG_OFFLINE, 'Displays version and author information', []),
902 'help': (command_erepublik_help, ARG_OPT|ARG_OFFLINE, 'Displays available commands and their usage', []),
903 }