--- /dev/null
+import asyncio
+import math
+import re
+import shlex
+
+from plugins import PluginCommand
+import bot
+import cat
+
+class Heading(object):
+ def description(self):
+ return 'Helper for planetary navigation.'
+
+ def valid_commands(self):
+ return ['heading']
+
+ @asyncio.coroutine
+ def handle_command(self, message, command, raw):
+ if command not in self.valid_commands():
+ return PluginCommand.ignored
+ yield from self.handle_heading(message, shlex.split(raw)[1:])
+ return PluginCommand.exclusive
+
+ @asyncio.coroutine
+ def handle_help(self, message, command, *args):
+ yield from self.help_heading(message, *args)
+
+ @asyncio.coroutine
+ def handle_heading(self, message, args):
+ if not len(args):
+ return
+
+ command = args[0].lower()
+ if command == 'help':
+ yield from self.help_heading(message)
+ elif command in ['from', 'to']:
+ yield from self.navigate(message, args)
+
+ @asyncio.coroutine
+ def help_heading(self, message, *args):
+ lines = [
+ '{} can assist you with orbital cruise.'.format(client.user.mention),
+ '',
+ 'The two numbers at the right of the HUD are the **latitude**, measured from *-90* (south) to *90* (north) and below it the **longitude**, measured from *-180* (west) to *180* (east).',
+ '',
+ 'Top number **latitude**. Bottom number **longitude**.',
+ '',
+ 'To increase your *latitude* fly at heading **0**. To decrease your *latitude* fly at heading **180**.',
+ 'To increase your *longitude* fly at heading **90**. To decrease your *longitude* fly at heading **270**.',
+ 'To *increase both* your latitude and longitude fly at **45**. To *decrease both* your latitude and longitude fly at **225**.',
+ 'To *increase* your *latitude* and *decrease* your *longitude* fly at **315**. To *decrease* your *latitude* and *increase* your *longitude* fly at **135**.',
+ '',
+ 'Remember that co-ordinates can be negative and positive. To go from lat. -78 to lat. -27, for instance, requires an *increase* hence a heading of **0**.',
+ '',
+ 'I can calculate the exact heading to fly between your current position and target co-ordinates. Send me a command like this:',
+ '```heading from 44.08 -33.12 to -27.14 -130.95```'
+ ]
+ yield from bot.say_many(message.channel, lines)
+
+ @asyncio.coroutine
+ def navigate(self, message, args):
+ log.debug('Parsing {}'.format(args))
+
+ parsed = {}
+ ok = False
+ i = 0
+ while i < len(args):
+ arg = args[i].lower()
+ if i > len(args) - 1:
+ break
+ try:
+ param1 = args[i + 1]
+ param2 = args[i + 2]
+ except IndexError:
+ break
+ log.info('{}: {}={} {}'.format(i, arg, param1, param2))
+
+ if arg in ['from', 'to']:
+ k = arg
+ try:
+ params = [float(re.sub(r'[^\d.-]', '', param)) for param in [param1, param2]]
+ parsed[k] = params
+ ok = True
+ i += 1
+ except ValueError:
+ break
+ else:
+ yield from bot.say(message.channel, 'What is {}?'.format(arg))
+ return
+
+ if ok:
+ i += 2
+ else:
+ break
+
+ if not ok:
+ log.info('Failed to parse heading. Got: {}'.format(parsed))
+ yield from bot.say(message.channel, '{}?'.format(arg))
+ return None
+
+ for k in ['from', 'to']:
+ latitude, longitude = parsed[k]
+ if latitude < -90 or latitude > 90:
+ yield from bot.say(message.channel, 'Invalid latitude!')
+ return
+ if longitude < -180 or longitude > 180:
+ yield from bot.say(message.channel, 'Invalid longitude!')
+ return
+
+ yield from bot.say(message.channel, self.calculate_bearing(parsed['from'], parsed['to']))
+
+ def calculate_bearing(self, start, end):
+ log.info('Calculating bearing from {} to {}.'.format(start, end))
+
+ slat, slong = [math.radians(l) for l in start]
+ elat, elong = [math.radians(l) for l in end]
+
+ x = (math.cos(slat) * math.sin(elat)) - (math.sin(slat) * math.cos(elat) * math.cos(elong - slong));
+ y = math.sin(elong - slong) * math.cos(elat)
+ return int(math.degrees(math.atan2(x, y)) % 360)