flexible.py 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361
  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. from twisted.internet.task import coiterate
  7. from twisted.internet.defer import gatherResults
  8. from itertools import cycle
  9. import pendulum
  10. from pprint import pformat
  11. def merge(color_string):
  12. """ Given a string of colorama ANSI, merge them if you can. """
  13. return color_string.replace("m\x1b[", ";")
  14. class PlayerInput(object):
  15. """ Player Input
  16. Example:
  17. from flexible import PlayerInput
  18. ask = PlayerInput(self.game)
  19. # abort_blank means, if the input field is blank, abort. Use error_back.
  20. d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  21. # Display the user's input / but not needed.
  22. d.addCallback(ask.output)
  23. d.addCallback(
  24. lambda ignore: ask.prompt(
  25. "What is your favorite color?", 10, name="color"
  26. )
  27. )
  28. d.addCallback(ask.output)
  29. d.addCallback(
  30. lambda ignore: ask.prompt(
  31. "What is your least favorite number?",
  32. 12,
  33. name="number",
  34. digits=True,
  35. )
  36. )
  37. d.addCallback(ask.output)
  38. def show_values(show):
  39. log.msg(show)
  40. self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  41. d.addCallback(lambda ignore: show_values(ask.keep))
  42. d.addCallback(self.welcome_back)
  43. # On error, just return back
  44. d.addErrback(self.welcome_back)
  45. """
  46. def __init__(self, game):
  47. # I think game gives us access to everything we need
  48. self.game = game
  49. self.observer = self.game.observer
  50. self.save = None
  51. self.deferred = None
  52. self.queue_game = game.queue_game
  53. self.keep = {}
  54. # default colors
  55. self.c = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  56. self.cp = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  57. # useful consts
  58. self.r = Style.RESET_ALL
  59. self.nl = "\n\r"
  60. self.bsb = "\b \b"
  61. self.keepalive = None
  62. def color(self, c):
  63. self.c = c
  64. def colorp(self, cp):
  65. self.cp = cp
  66. def alive(self):
  67. log.msg("PlayerInput.alive()")
  68. self.game.queue_player.put(" ")
  69. def prompt(self, user_prompt, limit, **kw):
  70. """ Generate prompt for user input.
  71. Note: This returns deferred.
  72. prompt = text displayed.
  73. limit = # of characters allowed.
  74. default = (text to default to)
  75. keywords:
  76. abort_blank : Abort if they give us blank text.
  77. name : Stores the input in self.keep dict.
  78. digits : Only allow 0-9 to be entered.
  79. """
  80. log.msg("PlayerInput({0}, {1}, {2}".format(user_prompt, limit, kw))
  81. self.limit = limit
  82. self.input = ""
  83. self.kw = kw
  84. assert self.save is None
  85. assert self.keepalive is None
  86. # Note: This clears out the server "keep alive"
  87. self.save = self.observer.save()
  88. self.observer.connect("player", self.get_input)
  89. self.keepalive = task.LoopingCall(self.alive)
  90. self.keepalive.start(30)
  91. # We need to "hide" the game output.
  92. # Otherwise it WITH mess up the user input display.
  93. self.to_player = self.game.to_player
  94. self.game.to_player = False
  95. # Display prompt
  96. # self.queue_game.put(self.r + self.nl + self.c + user_prompt + " " + self.cp)
  97. self.queue_game.put(self.r + self.c + user_prompt + self.r + " " + self.cp)
  98. # Set "Background of prompt"
  99. self.queue_game.put(" " * limit + "\b" * limit)
  100. assert self.deferred is None
  101. d = defer.Deferred()
  102. self.deferred = d
  103. log.msg("Return deferred ...", self.deferred)
  104. return d
  105. def get_input(self, chunk):
  106. """ Data from player (in bytes) """
  107. chunk = chunk.decode("utf-8", "ignore")
  108. for ch in chunk:
  109. if ch == "\b":
  110. if len(self.input) > 0:
  111. self.queue_game.put(self.bsb)
  112. self.input = self.input[0:-1]
  113. else:
  114. self.queue_game.put("\a")
  115. elif ch == "\r":
  116. self.queue_game.put(self.r + self.nl)
  117. log.msg("Restore observer dispatch", self.save)
  118. assert not self.save is None
  119. self.observer.load(self.save)
  120. self.save = None
  121. log.msg("Disable keepalive")
  122. self.keepalive.stop()
  123. self.keepalive = None
  124. line = self.input
  125. self.input = ""
  126. assert not self.deferred is None
  127. self.game.to_player = self.to_player
  128. # If they gave us the keyword name, save the value as that name
  129. if "name" in self.kw:
  130. self.keep[self.kw["name"]] = line
  131. if "abort_blank" in self.kw and self.kw["abort_blank"]:
  132. # Abort on blank input
  133. if line.strip() == "":
  134. # Yes, input is blank, abort.
  135. log.msg("errback, abort_blank")
  136. reactor.callLater(
  137. 0, self.deferred.errback, Exception("abort_blank")
  138. )
  139. self.deferred = None
  140. return
  141. # Ok, use deferred.callback, or reactor.callLater?
  142. # self.deferred.callback(line)
  143. reactor.callLater(0, self.deferred.callback, line)
  144. self.deferred = None
  145. return
  146. elif ch.isprintable():
  147. # Printable, but is it acceptable?
  148. if "digits" in self.kw:
  149. if not ch.isdigit():
  150. self.queue_game.put("\a")
  151. continue
  152. if len(self.input) + 1 <= self.limit:
  153. self.input += ch
  154. self.queue_game.put(ch)
  155. else:
  156. self.queue_game.put("\a")
  157. def output(self, line):
  158. """ A default display of what they just input. """
  159. log.msg("PlayerInput.output({0})".format(line))
  160. self.game.queue_game.put(self.r + "[{0}]".format(line) + self.nl)
  161. return line
  162. PORT_CLASSES = {
  163. 1: "BBS",
  164. 2: "BSB",
  165. 3: "SBB",
  166. 4: "SSB",
  167. 5: "SBS",
  168. 6: "BSS",
  169. 7: "SSS",
  170. 8: "BBB",
  171. }
  172. CLASSES_PORT = {v: k for k, v in PORT_CLASSES.items()}
  173. import re
  174. class CIMWarpReport(object):
  175. def __init__(self, game):
  176. self.game = game
  177. self.queue_game = game.queue_game
  178. self.queue_player = game.queue_player
  179. self.observer = game.observer
  180. # Yes, at this point we would activate
  181. self.prompt = game.buffer
  182. self.save = self.observer.save()
  183. # I actually don't want the player input, but I'll grab it anyway.
  184. self.observer.connect("player", self.player)
  185. self.observer.connect("prompt", self.game_prompt)
  186. self.observer.connect("game-line", self.game_line)
  187. # If we want it, it's here.
  188. self.defer = None
  189. self.to_player = self.game.to_player
  190. # Hide what's happening from the player
  191. self.game.to_player = False
  192. self.queue_player.put("^") # Activate CIM
  193. self.state = 1
  194. self.warpdata = {}
  195. self.warpcycle = cycle(["/", "-", "\\", "|"])
  196. def game_prompt(self, prompt):
  197. if prompt == ": ":
  198. if self.state == 1:
  199. # Ok, then we're ready to request the port report
  200. self.warpcycle = cycle(["/", "-", "\\", "|"])
  201. self.queue_player.put("I")
  202. self.state = 2
  203. elif self.state == 2:
  204. self.queue_player.put("Q")
  205. self.state = 3
  206. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  207. if self.state == 3:
  208. # Ok, time to exit
  209. # exit from this...
  210. self.game.to_player = self.to_player
  211. self.observer.load(self.save)
  212. self.save = None
  213. self.game.warpdata = self.warpdata
  214. self.queue_game.put("\b \b\r\n")
  215. if not self.defer is None:
  216. self.defer.callback(self.warpdata)
  217. self.defer = None
  218. def game_line(self, line):
  219. if line == "" or line == ": ":
  220. return
  221. if line == ": ENDINTERROG":
  222. return
  223. if line.startswith('Command [TL='):
  224. return
  225. # This should be the CIM Report Data -- parse it
  226. if self.warpcycle:
  227. if len(self.warpdata) % 10 == 0:
  228. self.queue_game.put("\b" + next(self.warpcycle))
  229. work = line.strip()
  230. parts = re.split(r"(?<=\d)\s", work)
  231. parts = [int(x) for x in parts]
  232. sector = parts.pop(0)
  233. # tuples are nicer on memory, and the warpdata map isn't going to be changing.
  234. self.warpdata[sector] = tuple(parts)
  235. self.game.gamedata.warp_to(sector, *parts)
  236. def __del__(self):
  237. log.msg("CIMWarpReport {0} RIP".format(self))
  238. def whenDone(self):
  239. self.defer = defer.Deferred()
  240. # Call this to chain something after we exit.
  241. return self.defer
  242. def player(self, chunk):
  243. """ Data from player (in bytes). """
  244. chunk = chunk.decode("utf-8", "ignore")
  245. key = chunk.upper()
  246. log.msg("CIMWarpReport.player({0}) : I AM stopping...".format(key))
  247. # Stop the keepalive if we are activating something else
  248. # or leaving...
  249. # self.keepalive.stop()
  250. self.queue_game.put("\b \b\r\n")
  251. if not self.defer is None:
  252. # We have something, so:
  253. self.game.to_player = self.to_player
  254. self.observer.load(self.save)
  255. self.save = None
  256. self.defer.errback(Exception("User Abort"))
  257. self.defer = None
  258. else:
  259. # Still "exit" out.
  260. self.game.to_player = self.to_player
  261. self.observer.load(self.save)
  262. class CIMPortReport(object):
  263. """ Parse data from CIM Port Report
  264. Example:
  265. from flexible import CIMPortReport
  266. report = CIMPortReport(self.game)
  267. d = report.whenDone()
  268. d.addCallback(self.port_report)
  269. d.addErrback(self.welcome_back)
  270. def port_report(self, portdata):
  271. self.portdata = portdata
  272. self.queue_game.put("Loaded {0} records.".format(len(portdata)) + self.nl)
  273. self.welcome_back()
  274. def welcome_back(self,*_):
  275. ... restore keep alive timers, etc.
  276. """
  277. def __init__(self, game):
  278. self.game = game
  279. self.queue_game = game.queue_game
  280. self.queue_player = game.queue_player
  281. self.observer = game.observer
  282. # Yes, at this point we would activate
  283. self.prompt = game.buffer
  284. self.save = self.observer.save()
  285. # I actually don't want the player input, but I'll grab it anyway.
  286. self.observer.connect("player", self.player)
  287. self.observer.connect("prompt", self.game_prompt)
  288. self.observer.connect("game-line", self.game_line)
  289. # If we want it, it's here.
  290. self.defer = None
  291. self.to_player = self.game.to_player
  292. log.msg("to_player (stored)", self.to_player)
  293. # Hide what's happening from the player
  294. self.game.to_player = False
  295. self.queue_player.put("^") # Activate CIM
  296. self.state = 1
  297. self.portdata = {}
  298. self.portcycle = cycle(["/", "-", "\\", "|"])
  299. def game_prompt(self, prompt):
  300. if prompt == ": ":
  301. if self.state == 1:
  302. # Ok, then we're ready to request the port report
  303. self.portcycle = cycle(["/", "-", "\\", "|"])
  304. self.queue_player.put("R")
  305. self.state = 2
  306. elif self.state == 2:
  307. self.queue_player.put("Q")
  308. self.state = 3
  309. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  310. if self.state == 3:
  311. # Ok, time to exit
  312. # exit from this...
  313. self.game.to_player = self.to_player
  314. self.observer.load(self.save)
  315. self.save = None
  316. self.game.portdata = self.portdata
  317. self.queue_game.put("\b \b\r\n")
  318. if not self.defer is None:
  319. self.defer.callback(self.portdata)
  320. self.defer = None
  321. def game_line(self, line):
  322. if line == "" or line == ": ":
  323. return
  324. if line == ": ENDINTERROG":
  325. return
  326. # This should be the CIM Report Data -- parse it
  327. if self.portcycle:
  328. if len(self.portdata) % 10 == 0:
  329. self.queue_game.put("\b" + next(self.portcycle))
  330. work = line.replace("%", "")
  331. parts = re.split(r"(?<=\d)\s", work)
  332. if len(parts) == 8:
  333. port = int(parts[0].strip())
  334. data = dict()
  335. def portBS(info):
  336. if info[0] == "-":
  337. bs = "B"
  338. else:
  339. bs = "S"
  340. return (bs, int(info[1:].strip()))
  341. data["fuel"] = dict()
  342. data["fuel"]["sale"], data["fuel"]["units"] = portBS(parts[1])
  343. data["fuel"]["pct"] = int(parts[2].strip())
  344. data["org"] = dict()
  345. data["org"]["sale"], data["org"]["units"] = portBS(parts[3])
  346. data["org"]["pct"] = int(parts[4].strip())
  347. data["equ"] = dict()
  348. data["equ"]["sale"], data["equ"]["units"] = portBS(parts[5])
  349. data["equ"]["pct"] = int(parts[6].strip())
  350. # Store what this port is buying/selling
  351. data["port"] = (
  352. data["fuel"]["sale"] + data["org"]["sale"] + data["equ"]["sale"]
  353. )
  354. # Convert BBS/SBB to Class number 1-8
  355. data["class"] = CLASSES_PORT[data["port"]]
  356. self.portdata[port] = data
  357. else:
  358. log.msg("CIMPortReport:", line, "???")
  359. def __del__(self):
  360. log.msg("CIMPortReport {0} RIP".format(self))
  361. def whenDone(self):
  362. self.defer = defer.Deferred()
  363. # Call this to chain something after we exit.
  364. return self.defer
  365. def player(self, chunk):
  366. """ Data from player (in bytes). """
  367. chunk = chunk.decode("utf-8", "ignore")
  368. key = chunk.upper()
  369. log.msg("CIMPortReport.player({0}) : I AM stopping...".format(key))
  370. # Stop the keepalive if we are activating something else
  371. # or leaving...
  372. # self.keepalive.stop()
  373. self.queue_game.put("\b \b\r\n")
  374. if not self.defer is None:
  375. # We have something, so:
  376. self.game.to_player = self.to_player
  377. self.observer.load(self.save)
  378. self.save = None
  379. self.defer.errback(Exception("User Abort"))
  380. self.defer = None
  381. else:
  382. # Still "exit" out.
  383. self.game.to_player = self.to_player
  384. self.observer.load(self.save)
  385. def port_burnt(port):
  386. """ Is this port burned out? """
  387. if all( x in port for x in ['fuel', 'org', 'equ']):
  388. if all( 'pct' in port[x] for x in ['fuel', 'org', 'equ']):
  389. if port['equ']['pct'] <= 20 or port['fuel']['pct'] <= 20 or port['org']['pct'] <= 20:
  390. return True
  391. return False
  392. # Since we don't have any port information, hope for the best, assume it isn't burnt.
  393. return False
  394. def flip(port):
  395. return port.replace('S', 'W').replace('B', 'S').replace('W', 'B')
  396. def port_trading(port1, port2):
  397. """ Are there possible trades at these ports? """
  398. if port1 == port2:
  399. return False
  400. p1 = [ c for c in port1]
  401. p2 = [ c for c in port2]
  402. # Any that are the same? Remove them.
  403. rem = False
  404. for i in range(3):
  405. if p1[i] == p2[i]:
  406. p1[i] = 'X'
  407. p2[i] = 'X'
  408. rem = True
  409. if rem:
  410. j1 = "".join(p1).replace('X', '')
  411. j2 = "".join(p2).replace('X', '')
  412. if j1 == 'BS' and j2 == 'SB':
  413. return True
  414. if j1 == 'SB' and j2 == 'BS':
  415. return True
  416. # Matching 2 of them.
  417. rport1 = flip(port1)
  418. c = 0
  419. match = []
  420. for i in range(3):
  421. if rport1[i] == port2[i]:
  422. match.append(port2[i])
  423. c += 1
  424. if c > 1:
  425. f = flip(match.pop(0))
  426. if f in match:
  427. return True
  428. return False
  429. return False
  430. class ScriptPort(object):
  431. """ Performs the Port script.
  432. This is close to the original.
  433. We don't ask for the port to trade with --
  434. because that information is available to us after "D" (display).
  435. We look at the adjacent sectors, and see if we know any ports.
  436. If the ports are burnt (< 20%), we remove them from the list.
  437. If there's just one, we use it. Otherwise we ask them to choose.
  438. """
  439. def __init__(self, game):
  440. self.game = game
  441. self.queue_game = game.queue_game
  442. self.queue_player = game.queue_player
  443. self.observer = game.observer
  444. self.r = Style.RESET_ALL
  445. self.nl = "\n\r"
  446. self.this_sector = None # Starting sector
  447. self.sector1 = None # Current Sector
  448. self.sector2 = None # Next Sector Stop
  449. self.percent = 5 # Stick with the good default.
  450. self.credits = 0
  451. self.last_credits = None
  452. self.times_left = 0
  453. # Activate
  454. self.prompt = game.buffer
  455. self.save = self.observer.save()
  456. self.observer.connect('player', self.player)
  457. self.observer.connect("prompt", self.game_prompt)
  458. self.observer.connect("game-line", self.game_line)
  459. self.defer = None
  460. self.queue_game.put(
  461. self.nl + "Script based on: Port Pair Trading v2.00" + self.r + self.nl
  462. )
  463. self.possible_sectors = None
  464. self.state = 1
  465. self.queue_player.put("D")
  466. # Original, send 'D' to display current sector.
  467. # We could get the sector number from the self.prompt string -- HOWEVER:
  468. # IF! We send 'D', we can also get the sectors around -- we might not even need to
  469. # prompt for sector to trade with (we could possibly figure it out ourselves).
  470. # [Command [TL=00:00:00]:[967] (?=Help)? : D]
  471. # [<Re-Display>]
  472. # []
  473. # [Sector : 967 in uncharted space.]
  474. # [Planets : (M) Into the Darkness]
  475. # [Warps to Sector(s) : 397 - (562) - (639)]
  476. # []
  477. def whenDone(self):
  478. self.defer = defer.Deferred()
  479. # Call this to chain something after we exit.
  480. return self.defer
  481. def deactivate(self):
  482. self.state = 0
  483. log.msg("ScriptPort.deactivate ({0})".format(self.times_left))
  484. assert(not self.save is None)
  485. self.observer.load(self.save)
  486. self.save = None
  487. if self.defer:
  488. self.defer.callback('done')
  489. self.defer = None
  490. def player(self, chunk: bytes):
  491. # If we receive anything -- ABORT!
  492. self.deactivate()
  493. def game_prompt(self, prompt: str):
  494. log.msg("{0} : {1}".format(self.state, prompt))
  495. if self.state == 3:
  496. log.msg("game_prompt: ", prompt)
  497. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  498. self.state = 4
  499. log.msg("Ok, state 4")
  500. if self.sector2 is None:
  501. # Ok, we need to prompt for this.
  502. self.queue_game.put(self.r + self.nl +
  503. "Which sector to trade with? {0}".format(port_show_part(self.this_sector, self.game.portdata[self.this_sector])) +
  504. self.nl + Fore.CYAN)
  505. for i, p in enumerate(self.possible):
  506. self.queue_game.put(" " + str(i + 1) + " : " + port_show_part(p, self.game.portdata[p]) + self.nl)
  507. pi = PlayerInput(self.game)
  508. def got_need1(*_):
  509. log.msg("Ok, I have:", pi.keep)
  510. if pi.keep['count'].strip() == '':
  511. self.deactivate()
  512. return
  513. self.times_left = int(pi.keep['count'])
  514. if pi.keep['choice'].strip() == '':
  515. self.deactivate()
  516. return
  517. c = int(pi.keep['choice']) -1
  518. if c < 0 or c >= len(self.possible):
  519. self.deactivate()
  520. return
  521. self.sector2 = self.possible[int(pi.keep['choice']) -1]
  522. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  523. self.state = 5
  524. self.trade()
  525. d = pi.prompt("Choose -=>", 5, name='choice', digits=True)
  526. d.addCallback(lambda ignore: pi.prompt("Times to execute script:", 5, name='count', digits=True))
  527. d.addCallback(got_need1)
  528. else:
  529. # We already have our target port, so...
  530. self.queue_game.put(self.r + self.nl + "Trading from {0} ({1}) to default {2} ({3}).".format(
  531. self.this_sector,
  532. self.game.portdata[self.this_sector]['port'],
  533. self.sector2, self.game.portdata[self.sector2]['port']) + self.nl
  534. )
  535. self.queue_game.put(self.r + self.nl + "Trading {0}".format(
  536. port_show(self.this_sector,
  537. self.game.portdata[self.this_sector],
  538. self.sector2,
  539. self.game.portdata[self.sector2])
  540. ))
  541. # The code is smart enough now, that this can't happen. :( Drat!
  542. if self.game.portdata[self.this_sector]['port'] == self.game.portdata[self.sector2]['port']:
  543. self.queue_game.put("Hey dummy! Look out the window! These ports are the same class!" + nl)
  544. self.deactivate()
  545. return
  546. pi = PlayerInput(self.game)
  547. def got_need2(*_):
  548. if pi.keep['count'].strip() == '':
  549. self.deactivate()
  550. return
  551. self.times_left = int(pi.keep['count'])
  552. log.msg("Ok, I have:", pi.keep)
  553. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  554. self.state = 5
  555. self.trade()
  556. self.queue_game.put(self.r + self.nl)
  557. d = pi.prompt("Times to execute script", 5, name='count')
  558. d.addCallback(got_need2)
  559. elif self.state == 6:
  560. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  561. if self.fixable:
  562. log.msg("Fixing...")
  563. if self.this_sector == self.sector1:
  564. self.this_sector = self.sector2
  565. self.queue_player.put("{0}\r".format(self.sector2))
  566. self.state = 5
  567. self.trade()
  568. else:
  569. self.this_sector = self.sector1
  570. self.queue_player.put("{0}\r".format(self.sector1))
  571. self.state = 5
  572. self.trade()
  573. elif self.state == 7:
  574. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  575. # Done
  576. if self.this_sector == self.sector1:
  577. self.this_sector = self.sector2
  578. self.queue_player.put("{0}\r".format(self.sector2))
  579. self.state = 10
  580. else:
  581. self.times_left -= 1
  582. if self.times_left <= 0:
  583. # Ok, exit out
  584. self.deactivate()
  585. return
  586. if self.last_credits is None:
  587. self.last_credits = self.credits
  588. else:
  589. if self.credits <= self.last_credits:
  590. log.msg("We don't seem to be making any money here...")
  591. self.queue_game.put(self.r + self.nl + "We don't seem to be making any money here. I'm stopping!" + self.nl)
  592. self.deactivate()
  593. return
  594. self.this_sector = self.sector1
  595. self.queue_player.put("{0}\r".format(self.sector1))
  596. self.state = 10
  597. elif re.match(r'Your offer \[\d+\] \?', prompt):
  598. if self.fix_offer:
  599. # Make real offer / WHAT?@?!
  600. work = prompt.replace(',', '')
  601. parts = re.split(r"\s+", work)
  602. amount = parts[2]
  603. # Ok, we have the amount, now to figure pct...
  604. if self.sell_pct > 100:
  605. self.sell_pct -= 1
  606. else:
  607. self.sell_pct += 1
  608. price = amount * self.sell_pct // 100
  609. log.msg("start: {0} % {1} price {2}".format(amount, self.sell_perc, price))
  610. if self.sell_pct > 100:
  611. self.sell_pct -= 1
  612. else:
  613. self.sell_pct += 1
  614. self.queue_player.put("{0}\r".format(price))
  615. elif self.state == 8:
  616. # What are we trading
  617. # How many holds of Equipment do you want to buy [75]?
  618. if re.match(r"How many holds of .+ do you want to buy \[\d+\]\?", prompt):
  619. parts = prompt.split()
  620. trade_type = parts[4]
  621. if trade_type == 'Fuel':
  622. if (self.tpc in (5,7)) and (self.opc in (2,3,4,8)):
  623. # Can buy equipment - fuel ore is worthless.
  624. self.queue_player.put("0\r")
  625. return
  626. if (self.tpc in(4,7)) and (self.opc in (1,3,5,8)):
  627. # Can buy organics - fuel ore is worthless.
  628. self.queue_player.put("0\r")
  629. return
  630. if (self.tpc in (4,7,3,5)) and (self.opc in (3,4,5,7)):
  631. # No point in buying fuel ore if it can't be sold.
  632. self.queue_player.put("0\r")
  633. return
  634. elif trade_type == 'Organics':
  635. if (self.tpc in (6,7)) and (self.opc in (2,3,4,8)):
  636. # Can buy equipment - organics is worthless.
  637. self.queue_player.put("0\r")
  638. return
  639. if (self.tpc in (2,4,6,7)) and (self.opc in (2,4,6,7)):
  640. # No point in buying organics if it can't be sold.
  641. self.queue_player.put("0\r")
  642. return
  643. elif trade_type == 'Equipment':
  644. if (self.opc in (1,5,6,7)):
  645. # No point in buying equipment if it can't be sold.
  646. self.queue_player.put("0\r")
  647. return
  648. self.queue_player.put("\r")
  649. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  650. # Done
  651. if self.this_sector == self.sector1:
  652. self.this_sector = self.sector2
  653. self.queue_player.put("{0}\r".format(self.sector2))
  654. self.state = 10
  655. else:
  656. self.times_left -= 1
  657. if self.times_left <= 0:
  658. # Ok, exit out
  659. self.deactivate()
  660. return
  661. if self.last_credits is None:
  662. self.last_credits = self.credits
  663. else:
  664. if self.credits <= self.last_credits:
  665. log.msg("We don't seem to be making any money here...")
  666. self.deactivate()
  667. return
  668. self.this_sector = self.sector1
  669. self.queue_player.put("{0}\r".format(self.sector1))
  670. self.state = 10
  671. def trade(self, *_):
  672. # state 5
  673. log.msg("trade!")
  674. self.queue_player.put("pt") # Port Trade
  675. self.this_port = self.game.portdata[self.this_sector]
  676. if self.this_sector == self.sector1:
  677. self.other_port = self.game.portdata[self.sector2]
  678. else:
  679. self.other_port = self.game.portdata[self.sector1]
  680. # Ok, perform some calculations
  681. self.tpc = self.this_port['class']
  682. self.opc = self.other_port['class']
  683. self.fixable = 0
  684. self.fix_offer = 0
  685. # [ Items Status Trading % of max OnBoard]
  686. # [ ----- ------ ------- -------- -------]
  687. # [Fuel Ore Selling 2573 93% 0]
  688. # [Organics Buying 2960 100% 0]
  689. # [Equipment Buying 1958 86% 0]
  690. # []
  691. # []
  692. # [You have 1,000 credits and 20 empty cargo holds.]
  693. # []
  694. # [We are selling up to 2573. You have 0 in your holds.]
  695. # [How many holds of Fuel Ore do you want to buy [20]? 0]
  696. def game_line(self, line: str):
  697. if line.startswith("You have ") and 'credits and' in line:
  698. parts = line.replace(',', '').split()
  699. credits = int(parts[2])
  700. log.msg("Credits: {0}".format(credits))
  701. self.credits = credits
  702. if self.state == 1:
  703. # First exploration
  704. if line.startswith("Sector :"):
  705. # We have starting sector information
  706. parts = re.split("\s+", line)
  707. self.this_sector = int(parts[2])
  708. # These will be the ones swapped around as we trade back and forth.
  709. self.sector1 = self.this_sector
  710. elif line.startswith("Warps to Sector(s) : "):
  711. # Warps to Sector(s) : 397 - (562) - (639)
  712. _, _, warps = line.partition(':')
  713. warps = warps.replace('-', '').replace('(', '').replace(')', '').strip()
  714. log.msg("Warps: [{0}]".format(warps))
  715. self.warps = [ int(x) for x in re.split("\s+", warps)]
  716. log.msg("Warps: [{0}]".format(self.warps))
  717. self.state = 2
  718. elif self.state == 2:
  719. if line == "":
  720. # Ok, we're done
  721. self.state = 3
  722. # Check to see if we have information on any possible ports
  723. if hasattr(self.game, 'portdata'):
  724. if not self.this_sector in self.game.portdata:
  725. self.state = 0
  726. log.msg("Current sector {0} not in portdata.".format(self.this_sector))
  727. self.queue_game.put(self.r + self.nl + "I can't find the current sector in the portdata." + self.nl)
  728. self.deactivate()
  729. return
  730. else:
  731. # Ok, we are in the portdata
  732. pd = self.game.portdata[self.this_sector]
  733. if port_burnt(pd):
  734. log.msg("Current sector {0} port is burnt (<= 20%).".format(self.this_sector))
  735. self.queue_game.put(self.r + self.nl + "Current sector port is burnt out. <= 20%." + self.nl)
  736. self.deactivate()
  737. return
  738. possible = [ x for x in self.warps if x in self.game.portdata ]
  739. log.msg("Possible:", possible)
  740. # BUG: Sometimes links to another sector, don't link back!
  741. # This causes the game to plot a course / autopilot.
  742. if hasattr(self.game, 'warpdata'):
  743. # Great! verify that those warps link back to us!
  744. possible = [ x for x in possible if self.this_sector in self.game.warpdata[x]]
  745. if len(possible) == 0:
  746. self.state = 0
  747. self.queue_game.put(self.r + self.nl + "I don't see any ports in [{0}].".format(self.warps) + self.nl)
  748. self.deactivate()
  749. return
  750. possible = [ x for x in possible if not port_burnt(self.game.portdata[x]) ]
  751. log.msg("Possible:", possible)
  752. if len(possible) == 0:
  753. self.state = 0
  754. self.queue_game.put(self.r + self.nl + "I don't see any unburnt ports in [{0}].".format(self.warps) + self.nl)
  755. self.deactivate()
  756. return
  757. possible = [ x for x in possible if port_trading(self.game.portdata[self.this_sector]['port'], self.game.portdata[x]['port'])]
  758. self.possible = possible
  759. if len(possible) == 0:
  760. self.state = 0
  761. self.queue_game.put(self.r + self.nl + "I don't see any possible port trades in [{0}].".format(self.warps) + self.nl)
  762. self.deactivate()
  763. return
  764. elif len(possible) == 1:
  765. # Ok! there's only one!
  766. self.sector2 = possible[0]
  767. # Display possible ports:
  768. # spos = [ str(x) for x in possible]
  769. # self.queue_game.put(self.r + self.nl + self.nl.join(spos) + self.nl)
  770. # At state 3, we only get a prompt.
  771. return
  772. else:
  773. self.state = 0
  774. log.msg("We don't have any portdata!")
  775. self.queue_game.put(self.r + self.nl + "I have no portdata. Please run CIM Port Report." + self.nl)
  776. self.deactivate()
  777. return
  778. elif self.state == 5:
  779. if "-----" in line:
  780. self.state = 6
  781. elif self.state == 6:
  782. if "We are buying up to" in line:
  783. # Sell
  784. self.state = 7
  785. self.queue_player.put("\r")
  786. self.sell_perc = 100 + self.percent
  787. if "We are selling up to" in line:
  788. # Buy
  789. self.state = 8
  790. self.sell_perc = 100 - self.percent
  791. if line.startswith('Fuel Ore') or line.startswith('Organics') or line.startswith('Equipment'):
  792. work = line.replace('Fuel Ore', 'Fuel')
  793. parts = re.split(r"\s+", work)
  794. # log.msg(parts)
  795. # Equipment, Selling xxx x% xxx
  796. if parts[-1] != '0' and parts[2] != '0' and parts[1] != 'Buying':
  797. log.msg("We have a problem -- they aren't buying what we have in stock!")
  798. stuff = line[0] # F O or E.
  799. if stuff == 'F':
  800. spos = 0
  801. elif stuff == 'O':
  802. spos = 1
  803. else:
  804. spos = 2
  805. other = self.other_port['port']
  806. if other[spos] == 'B':
  807. self.fixable = 1
  808. if "You don't have anything they want" in line:
  809. # Neither! DRAT!
  810. if not self.fixable:
  811. self.deactivate()
  812. return
  813. if "We're not interested." in line:
  814. log.msg("Try, try again. :(")
  815. self.state = 5
  816. self.trade()
  817. elif self.state == 7:
  818. # Haggle Sell
  819. if "We'll buy them for" in line or "Our final offer" in line:
  820. if "Our final offer" in line:
  821. self.sell_perc -= 1
  822. parts = line.replace(',', '').split()
  823. start_price = int(parts[4])
  824. price = start_price * self.sell_perc // 100
  825. log.msg("start: {0} % {1} price {2}".format(start_price, self.sell_perc, price))
  826. self.sell_perc -= 1
  827. self.queue_player.put("{0}\r".format(price))
  828. if "We are selling up to" in line:
  829. # Buy
  830. self.state = 8
  831. self.sell_perc = 100 - self.percent
  832. if "We're not interested." in line:
  833. log.msg("Try, try again. :(")
  834. self.state = 5
  835. self.trade()
  836. if "WHAT?!@!? you must be crazy!" in line:
  837. self.fix_offer = 1
  838. if "So, you think I'm as stupid as you look?" in line:
  839. self.fix_offer = 1
  840. elif self.state == 8:
  841. # Haggle Buy
  842. if "We'll sell them for" in line or "Our final offer" in line:
  843. if "Our final offer" in line:
  844. self.sell_perc += 1
  845. parts = line.replace(',', '').split()
  846. start_price = int(parts[4])
  847. price = start_price * self.sell_perc // 100
  848. log.msg("start: {0} % {1} price {2}".format(start_price, self.sell_perc, price))
  849. self.sell_perc += 1
  850. self.queue_player.put("{0}\r".format(price))
  851. if "We're not interested." in line:
  852. log.msg("Try, try again. :(")
  853. self.state = 5
  854. self.trade()
  855. elif self.state == 10:
  856. if "Sector : " in line:
  857. # Trade
  858. self.state = 5
  859. reactor.callLater(0, self.trade, 0)
  860. # self.trade()
  861. # elif self.state == 3:
  862. # log.msg("At state 3 [{0}]".format(line))
  863. # self.queue_game.put("At state 3.")
  864. # self.deactivate()
  865. # return
  866. def port_pct(port):
  867. # Make sure these exist in the port data given.
  868. if all( x in port for x in ['fuel', 'org', 'equ']):
  869. return "{0:3},{1:3},{2:3}%".format(
  870. port['fuel']['pct'],
  871. port['org']['pct'],
  872. port['equ']['pct'])
  873. else:
  874. return "---,---,---%"
  875. def port_show_part(sector, sector_port):
  876. return "{0:5} ({1}) {2}".format(sector, sector_port['port'], port_pct(sector_port))
  877. def port_show(sector, sector_port, warp, warp_port):
  878. sector_pct = port_pct(sector_port)
  879. warp_pct = port_pct(warp_port)
  880. return "{0} -=- {1}".format(
  881. port_show_part(sector, sector_port),
  882. port_show_part(warp, warp_port)
  883. )
  884. class ProxyMenu(object):
  885. """ Display ProxyMenu
  886. Example:
  887. from flexible import ProxyMenu
  888. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  889. menu = ProxyMenu(self.game)
  890. """
  891. def __init__(self, game):
  892. self.nl = "\n\r"
  893. self.c = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  894. self.r = Style.RESET_ALL
  895. self.c1 = merge(Style.BRIGHT + Fore.BLUE)
  896. self.c2 = merge(Style.NORMAL + Fore.CYAN)
  897. self.portdata = None
  898. self.game = game
  899. self.queue_game = game.queue_game
  900. self.observer = game.observer
  901. # Am I using self or game? (I think I want game, not self.)
  902. if hasattr(self.game, "portdata"):
  903. self.portdata = self.game.portdata
  904. else:
  905. self.portdata = {}
  906. if hasattr(self.game, 'warpdata'):
  907. self.warpdata = self.game.warpdata
  908. else:
  909. self.warpdata = {}
  910. if hasattr(self.game, 'trade_report'):
  911. self.trade_report = self.game.trade_report
  912. else:
  913. self.trade_report = []
  914. # Yes, at this point we would activate
  915. self.prompt = game.buffer
  916. self.save = self.observer.save()
  917. self.observer.connect("player", self.player)
  918. # If we want it, it's here.
  919. self.defer = None
  920. self.keepalive = task.LoopingCall(self.awake)
  921. self.keepalive.start(30)
  922. self.menu()
  923. def __del__(self):
  924. log.msg("ProxyMenu {0} RIP".format(self))
  925. def whenDone(self):
  926. self.defer = defer.Deferred()
  927. # Call this to chain something after we exit.
  928. return self.defer
  929. def menu(self):
  930. self.queue_game.put(
  931. self.nl + self.c + "TradeWars Proxy active." + self.r + self.nl
  932. )
  933. def menu_item(ch: str, desc: str):
  934. self.queue_game.put(
  935. " " + self.c1 + ch + self.c2 + " - " + self.c1 + desc + self.nl
  936. )
  937. menu_item("D", "Display Report again")
  938. # menu_item("Q", "Quest")
  939. if hasattr(self.game, 'portdata'):
  940. ports = len(self.game.portdata)
  941. else:
  942. ports = '?'
  943. menu_item("P", "Port CIM Report ({0})".format(ports))
  944. if hasattr(self.game, 'warpdata'):
  945. warps = len(self.game.warpdata)
  946. else:
  947. warps = '?'
  948. menu_item("W", "Warp CIM Report ({0})".format(warps))
  949. menu_item("T", "Trading Report")
  950. menu_item("S", "Scripts")
  951. menu_item("X", "eXit")
  952. self.queue_game.put(" " + self.c + "-=>" + self.r + " ")
  953. def awake(self):
  954. log.msg("ProxyMenu.awake()")
  955. self.game.queue_player.put(" ")
  956. def port_report(self, portdata: dict):
  957. self.portdata = portdata
  958. self.game.portdata = portdata
  959. self.queue_game.put("Loaded {0} ports.".format(len(portdata)) + self.nl)
  960. self.welcome_back()
  961. def warp_report(self, warpdata: dict):
  962. self.warpdata = warpdata
  963. self.game.warpdata = warpdata
  964. self.queue_game.put("Loaded {0} sectors.".format(len(warpdata)) + self.nl)
  965. self.welcome_back()
  966. def make_trade_report(self):
  967. log.msg("make_trade_report()")
  968. ok_trades = []
  969. best_trades = []
  970. for sector, pd in self.game.portdata.items():
  971. if not port_burnt(pd):
  972. pc = pd['class']
  973. # Ok, let's look into it.
  974. if not sector in self.game.warpdata:
  975. continue
  976. warps = self.game.warpdata[sector]
  977. for w in warps:
  978. # Verify that we have that warp's info, and that the sector is in it.
  979. # (We can get back from it)
  980. if w in self.game.warpdata and sector in self.game.warpdata[w]:
  981. # Ok, we can get there -- and get back!
  982. if w > sector and w in self.game.portdata and not port_burnt(self.game.portdata[w]):
  983. # it is > and has a port.
  984. wd = self.game.portdata[w]
  985. wc = wd['class']
  986. # 1: "BBS",
  987. # 2: "BSB",
  988. # 3: "SBB",
  989. # 4: "SSB",
  990. # 5: "SBS",
  991. # 6: "BSS",
  992. # 7: "SSS",
  993. # 8: "BBB",
  994. if pc in (1,5) and wc in (2,4):
  995. best_trades.append(port_show(sector, pd, w, wd))
  996. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  997. elif pc in (2,4) and wc in (1,5):
  998. best_trades.append(port_show(sector, pd, w, wd))
  999. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  1000. elif port_trading(pd['port'], self.game.portdata[w]['port']):
  1001. # ok_trades.append( "{0:5} -=- {1:5}".format(sector,w))
  1002. ok_trades.append(port_show(sector, pd, w, wd))
  1003. yield
  1004. self.trade_report.append("Best Trades: (org/equ)")
  1005. self.trade_report.extend(best_trades)
  1006. self.trade_report.append("Ok Trades:")
  1007. self.trade_report.extend(ok_trades)
  1008. # self.queue_game.put("BEST TRADES:" + self.nl + self.nl.join(best_trades) + self.nl)
  1009. # self.queue_game.put("OK TRADES:" + self.nl + self.nl.join(ok_trades) + self.nl)
  1010. def show_trade_report(self, *_):
  1011. self.game.trade_report = self.trade_report
  1012. for t in self.trade_report:
  1013. self.queue_game.put(t + self.nl)
  1014. self.welcome_back()
  1015. def player(self, chunk: bytes):
  1016. """ Data from player (in bytes). """
  1017. chunk = chunk.decode("utf-8", "ignore")
  1018. key = chunk.upper()
  1019. log.msg("ProxyMenu.player({0})".format(key))
  1020. # Stop the keepalive if we are activating something else
  1021. # or leaving...
  1022. self.keepalive.stop()
  1023. if key == "T":
  1024. self.queue_game.put(self.c + key + self.r + self.nl)
  1025. # Trade Report
  1026. # do we have enough information to do this?
  1027. if not hasattr(self.game, 'portdata') and not hasattr(self.game, 'warpdata'):
  1028. self.queue_game.put("Missing portdata and warpdata." + self.nl)
  1029. elif not hasattr(self.game, 'portdata'):
  1030. self.queue_game.put("Missing portdata." + self.nl)
  1031. elif not hasattr(self.game, 'warpdata'):
  1032. self.queue_game.put("Missing warpdata." + self.nl)
  1033. else:
  1034. # Yes, so let's start!
  1035. self.trade_report = []
  1036. d = coiterate(self.make_trade_report())
  1037. d.addCallback(self.show_trade_report)
  1038. return
  1039. elif key == "P":
  1040. self.queue_game.put(self.c + key + self.r + self.nl)
  1041. # Activate CIM Port Report
  1042. report = CIMPortReport(self.game)
  1043. d = report.whenDone()
  1044. d.addCallback(self.port_report)
  1045. d.addErrback(self.welcome_back)
  1046. return
  1047. elif key == "W":
  1048. self.queue_game.put(self.c + key + self.r + self.nl)
  1049. # Activate CIM Warp Report
  1050. report = CIMWarpReport(self.game)
  1051. d = report.whenDone()
  1052. d.addCallback(self.warp_report)
  1053. d.addErrback(self.welcome_back)
  1054. return
  1055. elif key == "S":
  1056. self.queue_game.put(self.c + key + self.r + self.nl)
  1057. # Scripts
  1058. self.activate_scripts_menu()
  1059. return
  1060. elif key == "D":
  1061. self.queue_game.put(self.c + key + self.r + self.nl)
  1062. # (Re) Display Trade Report
  1063. if self.trade_report:
  1064. for t in self.trade_report:
  1065. self.queue_game.put(t + self.nl)
  1066. else:
  1067. self.queue_game.put("Missing trade_report." + self.nl)
  1068. # self.queue_game.put(pformat(self.portdata).replace("\n", "\n\r") + self.nl)
  1069. # self.queue_game.put(pformat(self.warpdata).replace("\n", "\n\r") + self.nl)
  1070. elif key == "Q":
  1071. self.queue_game.put(self.c + key + self.r + self.nl)
  1072. # This is an example of chaining PlayerInput prompt calls.
  1073. ask = PlayerInput(self.game)
  1074. d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  1075. # Display the user's input
  1076. d.addCallback(ask.output)
  1077. d.addCallback(
  1078. lambda ignore: ask.prompt(
  1079. "What is your favorite color?", 10, name="color"
  1080. )
  1081. )
  1082. d.addCallback(ask.output)
  1083. d.addCallback(
  1084. lambda ignore: ask.prompt(
  1085. "What is the meaning of the squirrel?",
  1086. 12,
  1087. name="squirrel",
  1088. digits=True,
  1089. )
  1090. )
  1091. d.addCallback(ask.output)
  1092. def show_values(show):
  1093. log.msg(show)
  1094. self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  1095. d.addCallback(lambda ignore: show_values(ask.keep))
  1096. d.addCallback(self.welcome_back)
  1097. # On error, just return back
  1098. # This doesn't seem to be getting called.
  1099. # d.addErrback(lambda ignore: self.welcome_back)
  1100. d.addErrback(self.welcome_back)
  1101. return
  1102. elif key == "X":
  1103. self.queue_game.put(self.c + key + self.r + self.nl)
  1104. self.queue_game.put("Proxy done." + self.nl)
  1105. self.observer.load(self.save)
  1106. self.save = None
  1107. # It isn't running (NOW), so don't try to stop it.
  1108. # self.keepalive.stop()
  1109. self.keepalive = None
  1110. # Ok, this is a HORRIBLE idea, because the prompt might be
  1111. # outdated.
  1112. # self.queue_game.put(self.prompt)
  1113. self.prompt = None
  1114. # Possibly: Send '\r' to re-display the prompt
  1115. # instead of displaying the original one.
  1116. self.game.queue_player.put("d")
  1117. # Were we asked to do something when we were done here?
  1118. if self.defer:
  1119. reactor.CallLater(0, self.defer.callback)
  1120. # self.defer.callback()
  1121. self.defer = None
  1122. return
  1123. self.keepalive.start(30, True)
  1124. self.menu()
  1125. def activate_scripts_menu(self):
  1126. self.observer.disconnect("player", self.player)
  1127. self.observer.connect("player", self.scripts_player)
  1128. self.scripts_menu()
  1129. def deactivate_scripts_menu(self, *_):
  1130. self.observer.disconnect("player", self.scripts_player)
  1131. self.observer.connect("player", self.player)
  1132. self.welcome_back()
  1133. def scripts_menu(self, *_):
  1134. c1 = merge(Style.BRIGHT + Fore.CYAN)
  1135. c2 = merge(Style.NORMAL + Fore.CYAN)
  1136. def menu_item(ch, desc):
  1137. self.queue_game.put(
  1138. " " + c1 + ch + c2 + " - " + c1 + desc + self.nl
  1139. )
  1140. menu_item("1", "Ports (Trades between two sectors)")
  1141. menu_item("2", "TODO")
  1142. menu_item("3", "TODO")
  1143. menu_item("X", "eXit")
  1144. self.queue_game.put(" " + c1 + "-=>" + self.r + " ")
  1145. def scripts_player(self, chunk: bytes):
  1146. """ Data from player (in bytes). """
  1147. chunk = chunk.decode("utf-8", "ignore")
  1148. key = chunk.upper()
  1149. if key == '1':
  1150. self.queue_game.put(self.c + key + self.r + self.nl)
  1151. # Activate this magical event here
  1152. ports = ScriptPort(self.game)
  1153. d = ports.whenDone()
  1154. # d.addCallback(self.scripts_menu)
  1155. # d.addErrback(self.scripts_menu)
  1156. d.addCallback(self.deactivate_scripts_menu)
  1157. d.addErrback(self.deactivate_scripts_menu)
  1158. return
  1159. elif key == 'X':
  1160. self.queue_game.put(self.c + key + self.r + self.nl)
  1161. self.deactivate_scripts_menu()
  1162. return
  1163. else:
  1164. self.queue_game.put(self.c + "?" + self.r + self.nl)
  1165. self.scripts_menu()
  1166. def welcome_back(self, *_):
  1167. log.msg("welcome_back")
  1168. self.keepalive.start(30, True)
  1169. self.menu()