flexible.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. from twisted.internet import reactor
  2. from twisted.internet import task
  3. from twisted.internet import defer
  4. from colorama import Fore, Back, Style
  5. from twisted.python import log
  6. import pendulum
  7. def merge(color_string):
  8. """ Given a string of colorama ANSI, merge them if you can. """
  9. return color_string.replace("m\x1b[", ";")
  10. class PlayerInput(object):
  11. def __init__(self, game):
  12. # I think game gives us access to everything we need
  13. self.game = game
  14. self.observer = self.game.observer
  15. self.save = None
  16. self.deferred = None
  17. self.queue_game = game.queue_game
  18. self.keep = {}
  19. # default colors
  20. self.c = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  21. self.cp = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  22. # useful consts
  23. self.r = Style.RESET_ALL
  24. self.nl = "\n\r"
  25. self.bsb = "\b \b"
  26. self.keepalive = None
  27. def color(self, c):
  28. self.c = c
  29. def colorp(self, cp):
  30. self.cp = cp
  31. def alive(self):
  32. log.msg("PlayerInput.alive()")
  33. self.game.queue_player.put(" ")
  34. def prompt(self, user_prompt, limit, **kw):
  35. """ Generate prompt for user input.
  36. prompt = text displayed.
  37. limit = # of characters allowed.
  38. default = (text to default to)
  39. keywords:
  40. abort_blank : Abort if they give us blank text.
  41. """
  42. log.msg("PlayerInput({0}, {1}, {2}".format(user_prompt, limit, kw))
  43. self.limit = limit
  44. self.input = ""
  45. self.kw = kw
  46. assert self.save is None
  47. assert self.keepalive is None
  48. # Note: This clears out the server "keep alive"
  49. self.save = self.observer.save()
  50. self.observer.connect("player", self.get_input)
  51. self.keepalive = task.LoopingCall(self.alive)
  52. self.keepalive.start(30)
  53. # We need to "hide" the game output.
  54. # Otherwise it WITH mess up the user input display.
  55. self.to_player = self.game.to_player
  56. self.game.to_player = False
  57. # Display prompt
  58. self.queue_game.put(self.r + self.nl + self.c + user_prompt + " " + self.cp)
  59. # Set "Background of prompt"
  60. self.queue_game.put(" " * limit + "\b" * limit)
  61. assert self.deferred is None
  62. d = defer.Deferred()
  63. self.deferred = d
  64. log.msg("Return deferred ...", self.deferred)
  65. return d
  66. def get_input(self, chunk):
  67. """ Data from player (in bytes) """
  68. chunk = chunk.decode("utf-8", "ignore")
  69. for ch in chunk:
  70. if ch == "\b":
  71. if len(self.input) > 0:
  72. self.queue_game.put(self.bsb)
  73. self.input = self.input[0:-1]
  74. else:
  75. self.queue_game.put("\a")
  76. elif ch == "\r":
  77. self.queue_game.put(self.r + self.nl)
  78. log.msg("Restore observer dispatch", self.save)
  79. assert not self.save is None
  80. self.observer.load(self.save)
  81. self.save = None
  82. log.msg("Disable keepalive")
  83. self.keepalive.stop()
  84. self.keepalive = None
  85. line = self.input
  86. self.input = ""
  87. assert not self.deferred is None
  88. # If they gave us the keyword name, save the value as that name
  89. if "name" in self.kw:
  90. self.keep[self.kw["name"]] = line
  91. if "abort_blank" in self.kw and self.kw["abort_blank"]:
  92. # Abort on blank input
  93. if line.strip() == "":
  94. # Yes, input is blank, abort.
  95. log.msg("errback, abort_blank")
  96. reactor.callLater(
  97. 0, self.deferred.errback, Exception("abort_blank")
  98. )
  99. self.deferred = None
  100. return
  101. # Ok, use deferred.callback, or reactor.callLater?
  102. # self.deferred.callback(line)
  103. reactor.callLater(0, self.deferred.callback, line)
  104. self.deferred = None
  105. return
  106. elif ch.isprintable():
  107. # Printable, but is it acceptable?
  108. if "digits" in self.kw:
  109. if not ch.isdigit():
  110. self.queue_game.put("\a")
  111. continue
  112. if len(self.input) + 1 <= self.limit:
  113. self.input += ch
  114. self.queue_game.put(ch)
  115. else:
  116. self.queue_game.put("\a")
  117. def output(self, line):
  118. """ A default display of what they just input. """
  119. log.msg("PlayerInput.output({0})".format(line))
  120. self.game.queue_game.put(self.r + self.nl + "[{0}]".format(line) + self.nl)
  121. return line
  122. class ProxyMenu(object):
  123. def __init__(self, game):
  124. self.nl = "\n\r"
  125. self.c = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  126. self.r = Style.RESET_ALL
  127. self.c1 = merge(Style.BRIGHT + Fore.BLUE)
  128. self.c2 = merge(Style.NORMAL + Fore.CYAN)
  129. self.game = game
  130. self.queue_game = game.queue_game
  131. self.observer = game.observer
  132. # Yes, at this point we would activate
  133. self.prompt = game.buffer
  134. self.save = self.observer.save()
  135. self.observer.connect("player", self.player)
  136. # If we want it, it's here.
  137. self.defer = None
  138. self.keepalive = task.LoopingCall(self.awake)
  139. self.keepalive.start(30)
  140. self.menu()
  141. def __del__(self):
  142. log.msg("ProxyMenu {0} RIP".format(self))
  143. def whenDone(self):
  144. self.defer = defer.Deferred()
  145. # Call this to chain something after we exit.
  146. return self.defer
  147. def menu(self):
  148. self.queue_game.put(
  149. self.nl + self.c + "TradeWars Proxy active." + self.r + self.nl
  150. )
  151. def menu_item(ch, desc):
  152. self.queue_game.put(
  153. " " + self.c1 + ch + self.c2 + " - " + self.c1 + desc + self.nl
  154. )
  155. menu_item("D", "Diagnostics")
  156. menu_item("Q", "Quest")
  157. menu_item("T", "Display current Time")
  158. menu_item("P", "Port CIM Report")
  159. menu_item("S", "Scripts")
  160. menu_item("X", "eXit")
  161. self.queue_game.put(" " + self.c + "-=>" + self.r + " ")
  162. def awake(self):
  163. log.msg("ProxyMenu.awake()")
  164. self.game.queue_player.put(" ")
  165. def player(self, chunk):
  166. """ Data from player (in bytes). """
  167. chunk = chunk.decode("utf-8", "ignore")
  168. key = chunk.upper()
  169. log.msg("ProxyMenu.player({0})".format(key))
  170. # Stop the keepalive if we are activating something else
  171. # or leaving...
  172. self.keepalive.stop()
  173. if key == "T":
  174. self.queue_game.put(self.c + key + self.r + self.nl)
  175. # perform T option
  176. now = pendulum.now()
  177. self.queue_game.put(
  178. self.nl + self.c1 + "Current time " + now.to_datetime_string() + self.nl
  179. )
  180. elif key == "Q":
  181. self.queue_game.put(self.c + key + self.r + self.nl)
  182. # This is an example of chaining PlayerInput prompt calls.
  183. ask = PlayerInput(self.game)
  184. d = ask.prompt("What is your quest?", 20, name="quest", abort_blank=True)
  185. # Display the user's input
  186. d.addCallback(ask.output)
  187. d.addCallback(
  188. lambda ignore: ask.prompt(
  189. "What is your favorite color?", 5, name="color"
  190. )
  191. )
  192. d.addCallback(ask.output)
  193. d.addCallback(
  194. lambda ignore: ask.prompt(
  195. "What is the meaning of the squirrel?",
  196. 40,
  197. name="squirrel",
  198. digits=True,
  199. )
  200. )
  201. d.addCallback(ask.output)
  202. def show_values(show):
  203. log.msg(show)
  204. d.addCallback(lambda ignore: show_values(ask.keep))
  205. d.addCallback(self.welcome_back)
  206. # On error, just return back
  207. # This doesn't seem to be getting called.
  208. # d.addErrback(lambda ignore: self.welcome_back)
  209. d.addErrback(self.welcome_back)
  210. return
  211. elif key == "X":
  212. self.queue_game.put(self.c + key + self.r + self.nl)
  213. self.observer.load(self.save)
  214. self.save = None
  215. # It isn't running (NOW), so don't try to stop it.
  216. # self.keepalive.stop()
  217. self.keepalive = None
  218. self.queue_game.put(self.prompt)
  219. self.prompt = None
  220. # Possibly: Send '\r' to re-display the prompt
  221. # instead of displaying the original one.
  222. # Were we asked to do something when we were done here?
  223. if self.defer:
  224. reactor.CallLater(0, self.defer.callback)
  225. # self.defer.callback()
  226. self.defer = None
  227. return
  228. self.keepalive.start(30, True)
  229. self.menu()
  230. def welcome_back(self, *_):
  231. log.msg("welcome_back")
  232. self.keepalive.start(30, True)
  233. self.menu()