flexible.py 49 KB

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