flexible.py 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895
  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. from galaxy import GameData, PORT_CLASSES, CLASSES_PORT
  12. from boxes import Boxes
  13. class SpinningCursor(object):
  14. """ Spinner class, that handles every so many clicks
  15. s = SpinningCursor(5) # every 5
  16. for x in range(10):
  17. if s.click():
  18. print(s.cycle())
  19. """
  20. def __init__(self, every=10):
  21. self.itercycle = cycle(["/", "-", "\\", "|"])
  22. self.count = 0
  23. self.every = every
  24. def reset(self):
  25. self.itercycle = cycle(["/", "-", "\\", "|"])
  26. self.count = 0
  27. def click(self):
  28. self.count += 1
  29. return self.count % self.every == 0
  30. def cycle(self):
  31. return next(self.itercycle)
  32. def merge(color_string):
  33. """ Given a string of colorama ANSI, merge them if you can. """
  34. return color_string.replace("m\x1b[", ";")
  35. class PlayerInput(object):
  36. """ Player Input
  37. Example:
  38. from flexible import PlayerInput
  39. ask = PlayerInput(self.game)
  40. # abort_blank means, if the input field is blank, abort. Use error_back.
  41. d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  42. # Display the user's input / but not needed.
  43. d.addCallback(ask.output)
  44. d.addCallback(
  45. lambda ignore: ask.prompt(
  46. "What is your favorite color?", 10, name="color"
  47. )
  48. )
  49. d.addCallback(ask.output)
  50. d.addCallback(
  51. lambda ignore: ask.prompt(
  52. "What is your least favorite number?",
  53. 12,
  54. name="number",
  55. digits=True,
  56. )
  57. )
  58. d.addCallback(ask.output)
  59. def show_values(show):
  60. log.msg(show)
  61. self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  62. d.addCallback(lambda ignore: show_values(ask.keep))
  63. d.addCallback(self.welcome_back)
  64. # On error, just return back
  65. d.addErrback(self.welcome_back)
  66. """
  67. def __init__(self, game):
  68. # I think game gives us access to everything we need
  69. self.game = game
  70. self.observer = self.game.observer
  71. self.save = None
  72. self.deferred = None
  73. self.queue_game = game.queue_game
  74. self.keep = {}
  75. # default colors
  76. self.c = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  77. self.cp = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  78. # useful consts
  79. self.r = Style.RESET_ALL
  80. self.nl = "\n\r"
  81. self.bsb = "\b \b"
  82. self.keepalive = None
  83. def color(self, c):
  84. self.c = c
  85. def colorp(self, cp):
  86. self.cp = cp
  87. def alive(self):
  88. log.msg("PlayerInput.alive()")
  89. self.game.queue_player.put(" ")
  90. def prompt(self, user_prompt, limit, **kw):
  91. """ Generate prompt for user input.
  92. Note: This returns deferred.
  93. prompt = text displayed.
  94. limit = # of characters allowed.
  95. default = (text to default to)
  96. keywords:
  97. abort_blank : Abort if they give us blank text.
  98. name : Stores the input in self.keep dict.
  99. digits : Only allow 0-9 to be entered.
  100. """
  101. log.msg("PlayerInput({0}, {1}, {2}".format(user_prompt, limit, kw))
  102. self.limit = limit
  103. self.input = ""
  104. self.kw = kw
  105. assert self.save is None
  106. assert self.keepalive is None
  107. # Note: This clears out the server "keep alive"
  108. self.save = self.observer.save()
  109. self.observer.connect("player", self.get_input)
  110. self.keepalive = task.LoopingCall(self.alive)
  111. self.keepalive.start(30)
  112. # We need to "hide" the game output.
  113. # Otherwise it WITH mess up the user input display.
  114. self.to_player = self.game.to_player
  115. self.game.to_player = False
  116. # Display prompt
  117. # self.queue_game.put(self.r + self.nl + self.c + user_prompt + " " + self.cp)
  118. self.queue_game.put(self.r + self.c + user_prompt + self.r + " " + self.cp)
  119. # Set "Background of prompt"
  120. self.queue_game.put(" " * limit + "\b" * limit)
  121. assert self.deferred is None
  122. d = defer.Deferred()
  123. self.deferred = d
  124. log.msg("Return deferred ...", self.deferred)
  125. return d
  126. def get_input(self, chunk):
  127. """ Data from player (in bytes) """
  128. chunk = chunk.decode("latin-1", "ignore")
  129. for ch in chunk:
  130. if ch == "\b":
  131. if len(self.input) > 0:
  132. self.queue_game.put(self.bsb)
  133. self.input = self.input[0:-1]
  134. else:
  135. self.queue_game.put("\a")
  136. elif ch == "\r":
  137. self.queue_game.put(self.r + self.nl)
  138. log.msg("Restore observer dispatch", self.save)
  139. assert not self.save is None
  140. self.observer.load(self.save)
  141. self.save = None
  142. log.msg("Disable keepalive")
  143. self.keepalive.stop()
  144. self.keepalive = None
  145. line = self.input
  146. self.input = ""
  147. assert not self.deferred is None
  148. self.game.to_player = self.to_player
  149. # If they gave us the keyword name, save the value as that name
  150. if "name" in self.kw:
  151. self.keep[self.kw["name"]] = line
  152. if "abort_blank" in self.kw and self.kw["abort_blank"]:
  153. # Abort on blank input
  154. if line.strip() == "":
  155. # Yes, input is blank, abort.
  156. log.msg("errback, abort_blank")
  157. reactor.callLater(
  158. 0, self.deferred.errback, Exception("abort_blank")
  159. )
  160. self.deferred = None
  161. return
  162. # Ok, use deferred.callback, or reactor.callLater?
  163. # self.deferred.callback(line)
  164. reactor.callLater(0, self.deferred.callback, line)
  165. self.deferred = None
  166. return
  167. elif ch.isprintable():
  168. # Printable, but is it acceptable?
  169. if "digits" in self.kw:
  170. if not ch.isdigit():
  171. self.queue_game.put("\a")
  172. continue
  173. if len(self.input) + 1 <= self.limit:
  174. self.input += ch
  175. self.queue_game.put(ch)
  176. else:
  177. self.queue_game.put("\a")
  178. def output(self, line):
  179. """ A default display of what they just input. """
  180. log.msg("PlayerInput.output({0})".format(line))
  181. self.game.queue_game.put(self.r + "[{0}]".format(line) + self.nl)
  182. return line
  183. import re
  184. # The CIMWarpReport -- is only needed if the json file gets damaged in some way.
  185. # or needs to be reset. The warps should automatically update themselves now.
  186. class CIMWarpReport(object):
  187. def __init__(self, game):
  188. self.game = game
  189. self.queue_game = game.queue_game
  190. self.queue_player = game.queue_player
  191. self.observer = game.observer
  192. # Yes, at this point we would activate
  193. self.prompt = game.buffer
  194. self.save = self.observer.save()
  195. # I actually don't want the player input, but I'll grab it anyway.
  196. self.observer.connect("player", self.player)
  197. self.observer.connect("prompt", self.game_prompt)
  198. self.observer.connect("game-line", self.game_line)
  199. # If we want it, it's here.
  200. self.defer = None
  201. self.to_player = self.game.to_player
  202. # Hide what's happening from the player
  203. self.game.to_player = False
  204. self.queue_player.put("^") # Activate CIM
  205. self.state = 1
  206. # self.warpdata = {}
  207. self.warpcycle = SpinningCursor()
  208. def game_prompt(self, prompt):
  209. if prompt == ": ":
  210. if self.state == 1:
  211. # Ok, then we're ready to request the port report
  212. self.warpcycle.reset()
  213. self.queue_player.put("I")
  214. self.state = 2
  215. elif self.state == 2:
  216. self.queue_player.put("Q")
  217. self.state = 3
  218. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  219. if self.state == 3:
  220. # Ok, time to exit
  221. # exit from this...
  222. self.game.to_player = self.to_player
  223. self.observer.load(self.save)
  224. self.save = None
  225. # self.game.warpdata = self.warpdata
  226. self.queue_game.put("\b \b\r\n")
  227. if not self.defer is None:
  228. self.defer.callback(self.game.gamedata.warps)
  229. self.defer = None
  230. def game_line(self, line):
  231. if line == "" or line == ": ":
  232. return
  233. if line == ": ENDINTERROG":
  234. return
  235. if line.startswith('Command [TL='):
  236. return
  237. # This should be the CIM Report Data -- parse it
  238. if self.warpcycle:
  239. if self.warpcycle.click():
  240. self.queue_game.put("\b" + self.warpcycle.cycle())
  241. work = line.strip()
  242. parts = re.split(r"(?<=\d)\s", work)
  243. parts = [int(x) for x in parts]
  244. sector = parts.pop(0)
  245. # tuples are nicer on memory, and the warpdata map isn't going to be changing.
  246. # self.warpdata[sector] = tuple(parts)
  247. self.game.gamedata.warp_to(sector, *parts)
  248. def __del__(self):
  249. log.msg("CIMWarpReport {0} RIP".format(self))
  250. def whenDone(self):
  251. self.defer = defer.Deferred()
  252. # Call this to chain something after we exit.
  253. return self.defer
  254. def player(self, chunk):
  255. """ Data from player (in bytes). """
  256. chunk = chunk.decode("latin-1", "ignore")
  257. key = chunk.upper()
  258. log.msg("CIMWarpReport.player({0}) : I AM stopping...".format(key))
  259. # Stop the keepalive if we are activating something else
  260. # or leaving...
  261. # self.keepalive.stop()
  262. self.queue_game.put("\b \b\r\n")
  263. if not self.defer is None:
  264. # We have something, so:
  265. self.game.to_player = self.to_player
  266. self.observer.load(self.save)
  267. self.save = None
  268. self.defer.errback(Exception("User Abort"))
  269. self.defer = None
  270. else:
  271. # Still "exit" out.
  272. self.game.to_player = self.to_player
  273. self.observer.load(self.save)
  274. # the CIMPortReport will still be needed.
  275. # We can't get fresh report data (that changes) any other way.
  276. class CIMPortReport(object):
  277. """ Parse data from CIM Port Report
  278. Example:
  279. from flexible import CIMPortReport
  280. report = CIMPortReport(self.game)
  281. d = report.whenDone()
  282. d.addCallback(self.port_report)
  283. d.addErrback(self.welcome_back)
  284. def port_report(self, portdata):
  285. self.portdata = portdata
  286. self.queue_game.put("Loaded {0} records.".format(len(portdata)) + self.nl)
  287. self.welcome_back()
  288. def welcome_back(self,*_):
  289. ... restore keep alive timers, etc.
  290. """
  291. def __init__(self, game):
  292. self.game = game
  293. self.queue_game = game.queue_game
  294. self.queue_player = game.queue_player
  295. self.observer = game.observer
  296. # Yes, at this point we would activate
  297. self.prompt = game.buffer
  298. self.save = self.observer.save()
  299. # I actually don't want the player input, but I'll grab it anyway.
  300. self.observer.connect("player", self.player)
  301. self.observer.connect("prompt", self.game_prompt)
  302. self.observer.connect("game-line", self.game_line)
  303. # If we want it, it's here.
  304. self.defer = None
  305. self.to_player = self.game.to_player
  306. log.msg("to_player (stored)", self.to_player)
  307. # Hide what's happening from the player
  308. self.game.to_player = False
  309. self.queue_player.put("^") # Activate CIM
  310. self.state = 1
  311. # self.portdata = {}
  312. self.portcycle = SpinningCursor()
  313. def game_prompt(self, prompt):
  314. if prompt == ": ":
  315. if self.state == 1:
  316. # Ok, then we're ready to request the port report
  317. self.portcycle.reset()
  318. self.queue_player.put("R")
  319. self.state = 2
  320. elif self.state == 2:
  321. self.queue_player.put("Q")
  322. self.state = 3
  323. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  324. if self.state == 3:
  325. # Ok, time to exit
  326. # exit from this...
  327. self.game.to_player = self.to_player
  328. self.observer.load(self.save)
  329. self.save = None
  330. # self.game.portdata = self.portdata
  331. self.queue_game.put("\b \b\r\n")
  332. if not self.defer is None:
  333. self.defer.callback(self.game.gamedata.ports)
  334. self.defer = None
  335. def game_line(self, line: str):
  336. if line == "" or line == ": ":
  337. return
  338. if line == ": ENDINTERROG":
  339. return
  340. # This should be the CIM Report Data -- parse it
  341. if self.portcycle:
  342. if self.portcycle.click():
  343. self.queue_game.put("\b" + self.portcycle.cycle())
  344. work = line.replace("%", "")
  345. parts = re.split(r"(?<=\d)\s", work)
  346. if len(parts) == 8:
  347. port = int(parts[0].strip())
  348. data = dict()
  349. def portBS(info):
  350. if info[0] == "-":
  351. bs = "B"
  352. else:
  353. bs = "S"
  354. return (bs, int(info[1:].strip()))
  355. data["fuel"] = dict()
  356. data["fuel"]["sale"], data["fuel"]["units"] = portBS(parts[1])
  357. data["fuel"]["pct"] = int(parts[2].strip())
  358. data["org"] = dict()
  359. data["org"]["sale"], data["org"]["units"] = portBS(parts[3])
  360. data["org"]["pct"] = int(parts[4].strip())
  361. data["equ"] = dict()
  362. data["equ"]["sale"], data["equ"]["units"] = portBS(parts[5])
  363. data["equ"]["pct"] = int(parts[6].strip())
  364. # Store what this port is buying/selling
  365. data["port"] = (
  366. data["fuel"]["sale"] + data["org"]["sale"] + data["equ"]["sale"]
  367. )
  368. # Convert BBS/SBB to Class number 1-8
  369. data["class"] = CLASSES_PORT[data["port"]]
  370. self.game.gamedata.set_port(port, data)
  371. # self.portdata[port] = data
  372. else:
  373. log.msg("CIMPortReport:", line, "???")
  374. def __del__(self):
  375. log.msg("CIMPortReport {0} RIP".format(self))
  376. def whenDone(self):
  377. self.defer = defer.Deferred()
  378. # Call this to chain something after we exit.
  379. return self.defer
  380. def player(self, chunk):
  381. """ Data from player (in bytes). """
  382. chunk = chunk.decode("latin-1", "ignore")
  383. key = chunk.upper()
  384. log.msg("CIMPortReport.player({0}) : I AM stopping...".format(key))
  385. # Stop the keepalive if we are activating something else
  386. # or leaving...
  387. # self.keepalive.stop()
  388. self.queue_game.put("\b \b\r\n")
  389. if not self.defer is None:
  390. # We have something, so:
  391. self.game.to_player = self.to_player
  392. self.observer.load(self.save)
  393. self.save = None
  394. self.defer.errback(Exception("User Abort"))
  395. self.defer = None
  396. else:
  397. # Still "exit" out.
  398. self.game.to_player = self.to_player
  399. self.observer.load(self.save)
  400. class ScriptPort(object):
  401. """ Performs the Port script.
  402. This is close to the original.
  403. We don't ask for the port to trade with --
  404. because that information is available to us after "D" (display).
  405. We look at the adjacent sectors, and see if we know any ports.
  406. If the ports are burnt (< 20%), we remove them from the list.
  407. If there's just one, we use it. Otherwise we ask them to choose.
  408. """
  409. def __init__(self, game):
  410. self.game = game
  411. self.queue_game = game.queue_game
  412. self.queue_player = game.queue_player
  413. self.observer = game.observer
  414. self.r = Style.RESET_ALL
  415. self.nl = "\n\r"
  416. self.this_sector = None # Starting sector
  417. self.sector1 = None # Current Sector
  418. self.sector2 = None # Next Sector Stop
  419. self.percent = 5 # Stick with the good default.
  420. self.credits = 0
  421. self.last_credits = None
  422. self.times_left = 0
  423. # Activate
  424. self.prompt = game.buffer
  425. self.save = self.observer.save()
  426. self.observer.connect('player', self.player)
  427. self.observer.connect("prompt", self.game_prompt)
  428. self.observer.connect("game-line", self.game_line)
  429. self.defer = None
  430. self.queue_game.put(
  431. self.nl + "Script based on: Port Pair Trading v2.00" + self.r + self.nl
  432. )
  433. self.possible_sectors = None
  434. self.state = 1
  435. self.queue_player.put("D")
  436. # Original, send 'D' to display current sector.
  437. # We could get the sector number from the self.prompt string -- HOWEVER:
  438. # IF! We send 'D', we can also get the sectors around -- we might not even need to
  439. # prompt for sector to trade with (we could possibly figure it out ourselves).
  440. # [Command [TL=00:00:00]:[967] (?=Help)? : D]
  441. # [<Re-Display>]
  442. # []
  443. # [Sector : 967 in uncharted space.]
  444. # [Planets : (M) Into the Darkness]
  445. # [Warps to Sector(s) : 397 - (562) - (639)]
  446. # []
  447. def whenDone(self):
  448. self.defer = defer.Deferred()
  449. # Call this to chain something after we exit.
  450. return self.defer
  451. def deactivate(self):
  452. self.state = 0
  453. log.msg("ScriptPort.deactivate ({0})".format(self.times_left))
  454. assert(not self.save is None)
  455. self.observer.load(self.save)
  456. self.save = None
  457. if self.defer:
  458. self.defer.callback('done')
  459. self.defer = None
  460. def player(self, chunk: bytes):
  461. # If we receive anything -- ABORT!
  462. self.deactivate()
  463. def game_prompt(self, prompt: str):
  464. log.msg("{0} : {1}".format(self.state, prompt))
  465. if self.state == 3:
  466. log.msg("game_prompt: ", prompt)
  467. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  468. self.state = 4
  469. log.msg("Ok, state 4")
  470. if self.sector2 is None:
  471. # Ok, we need to prompt for this.
  472. self.queue_game.put(self.r + self.nl +
  473. "Which sector to trade with? {0}".format(GameData.port_show_part(self.this_sector, self.game.gamedata.ports[self.this_sector])) +
  474. self.nl + Fore.CYAN)
  475. for i, p in enumerate(self.possible):
  476. self.queue_game.put(" " + str(i + 1) + " : " + GameData.port_show_part(p, self.game.gamedata.ports[p]) + self.nl)
  477. pi = PlayerInput(self.game)
  478. def got_need1(*_):
  479. log.msg("Ok, I have:", pi.keep)
  480. if pi.keep['count'].strip() == '':
  481. self.deactivate()
  482. return
  483. self.times_left = int(pi.keep['count'])
  484. if pi.keep['choice'].strip() == '':
  485. self.deactivate()
  486. return
  487. c = int(pi.keep['choice']) -1
  488. if c < 0 or c >= len(self.possible):
  489. self.deactivate()
  490. return
  491. self.sector2 = self.possible[int(pi.keep['choice']) -1]
  492. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  493. self.state = 5
  494. self.trade()
  495. d = pi.prompt("Choose -=>", 5, name='choice', digits=True)
  496. d.addCallback(lambda ignore: pi.prompt("Times to execute script:", 5, name='count', digits=True))
  497. d.addCallback(got_need1)
  498. else:
  499. # We already have our target port, so...
  500. self.queue_game.put(self.r + self.nl + "Trading from {0} ({1}) to default {2} ({3}).".format(
  501. self.this_sector,
  502. self.game.gamedata.ports[self.this_sector]['port'],
  503. self.sector2, self.game.gamedata.ports[self.sector2]['port']) + self.nl
  504. )
  505. self.queue_game.put(self.r + self.nl + "Trading {0}".format(
  506. self.game.gamedata.port_trade_show(self.this_sector,
  507. self.sector2)
  508. ))
  509. # The code is smart enough now, that this can't happen. :( Drat!
  510. if self.game.gamedata.ports[self.this_sector]['port'] == self.game.gamedata.ports[self.sector2]['port']:
  511. self.queue_game.put("Hey dummy! Look out the window! These ports are the same class!" + nl)
  512. self.deactivate()
  513. return
  514. pi = PlayerInput(self.game)
  515. def got_need2(*_):
  516. if pi.keep['count'].strip() == '':
  517. self.deactivate()
  518. return
  519. self.times_left = int(pi.keep['count'])
  520. log.msg("Ok, I have:", pi.keep)
  521. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  522. self.state = 5
  523. self.trade()
  524. self.queue_game.put(self.r + self.nl)
  525. d = pi.prompt("Times to execute script", 5, name='count')
  526. d.addCallback(got_need2)
  527. elif self.state == 6:
  528. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  529. if self.fixable:
  530. log.msg("Fixing...")
  531. if self.this_sector == self.sector1:
  532. self.this_sector = self.sector2
  533. self.queue_player.put("{0}\r".format(self.sector2))
  534. self.state = 5
  535. self.trade()
  536. else:
  537. self.this_sector = self.sector1
  538. self.queue_player.put("{0}\r".format(self.sector1))
  539. self.state = 5
  540. self.trade()
  541. elif self.state == 7:
  542. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  543. # Done
  544. if self.this_sector == self.sector1:
  545. self.this_sector = self.sector2
  546. self.queue_player.put("{0}\r".format(self.sector2))
  547. self.state = 10
  548. else:
  549. self.times_left -= 1
  550. if self.times_left <= 0:
  551. # Ok, exit out
  552. self.deactivate()
  553. return
  554. if self.last_credits is None:
  555. self.last_credits = self.credits
  556. else:
  557. if self.credits <= self.last_credits:
  558. log.msg("We don't seem to be making any money here...")
  559. self.queue_game.put(self.r + self.nl + "We don't seem to be making any money here. I'm stopping!" + self.nl)
  560. self.deactivate()
  561. return
  562. self.this_sector = self.sector1
  563. self.queue_player.put("{0}\r".format(self.sector1))
  564. self.state = 10
  565. elif re.match(r'Your offer \[\d+\] \?', prompt):
  566. if self.fix_offer:
  567. # Make real offer / WHAT?@?!
  568. work = prompt.replace(',', '')
  569. parts = re.split(r"\s+", work)
  570. amount = parts[2]
  571. # Ok, we have the amount, now to figure pct...
  572. if self.sell_pct > 100:
  573. self.sell_pct -= 1
  574. else:
  575. self.sell_pct += 1
  576. price = amount * self.sell_pct // 100
  577. log.msg("start: {0} % {1} price {2}".format(amount, self.sell_perc, price))
  578. if self.sell_pct > 100:
  579. self.sell_pct -= 1
  580. else:
  581. self.sell_pct += 1
  582. self.queue_player.put("{0}\r".format(price))
  583. elif self.state == 8:
  584. # What are we trading
  585. # How many holds of Equipment do you want to buy [75]?
  586. if re.match(r"How many holds of .+ do you want to buy \[\d+\]\?", prompt):
  587. parts = prompt.split()
  588. trade_type = parts[4]
  589. if trade_type == 'Fuel':
  590. if (self.tpc in (5,7)) and (self.opc in (2,3,4,8)):
  591. # Can buy equipment - fuel ore is worthless.
  592. self.queue_player.put("0\r")
  593. return
  594. if (self.tpc in(4,7)) and (self.opc in (1,3,5,8)):
  595. # Can buy organics - fuel ore is worthless.
  596. self.queue_player.put("0\r")
  597. return
  598. if (self.tpc in (4,7,3,5)) and (self.opc in (3,4,5,7)):
  599. # No point in buying fuel ore if it can't be sold.
  600. self.queue_player.put("0\r")
  601. return
  602. elif trade_type == 'Organics':
  603. if (self.tpc in (6,7)) and (self.opc in (2,3,4,8)):
  604. # Can buy equipment - organics is worthless.
  605. self.queue_player.put("0\r")
  606. return
  607. if (self.tpc in (2,4,6,7)) and (self.opc in (2,4,6,7)):
  608. # No point in buying organics if it can't be sold.
  609. self.queue_player.put("0\r")
  610. return
  611. elif trade_type == 'Equipment':
  612. if (self.opc in (1,5,6,7)):
  613. # No point in buying equipment if it can't be sold.
  614. self.queue_player.put("0\r")
  615. return
  616. self.queue_player.put("\r")
  617. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  618. # Done
  619. if self.this_sector == self.sector1:
  620. self.this_sector = self.sector2
  621. self.queue_player.put("{0}\r".format(self.sector2))
  622. self.state = 10
  623. else:
  624. self.times_left -= 1
  625. if self.times_left <= 0:
  626. # Ok, exit out
  627. self.deactivate()
  628. return
  629. if self.last_credits is None:
  630. self.last_credits = self.credits
  631. else:
  632. if self.credits <= self.last_credits:
  633. log.msg("We don't seem to be making any money here...")
  634. self.deactivate()
  635. return
  636. self.this_sector = self.sector1
  637. self.queue_player.put("{0}\r".format(self.sector1))
  638. self.state = 10
  639. def trade(self, *_):
  640. # state 5
  641. log.msg("trade!")
  642. self.queue_player.put("pt") # Port Trade
  643. self.this_port = self.game.gamedata.ports[self.this_sector]
  644. if self.this_sector == self.sector1:
  645. self.other_port = self.game.gamedata.ports[self.sector2]
  646. else:
  647. self.other_port = self.game.gamedata.ports[self.sector1]
  648. # Ok, perform some calculations
  649. self.tpc = self.this_port['class']
  650. self.opc = self.other_port['class']
  651. self.fixable = 0
  652. self.fix_offer = 0
  653. # [ Items Status Trading % of max OnBoard]
  654. # [ ----- ------ ------- -------- -------]
  655. # [Fuel Ore Selling 2573 93% 0]
  656. # [Organics Buying 2960 100% 0]
  657. # [Equipment Buying 1958 86% 0]
  658. # []
  659. # []
  660. # [You have 1,000 credits and 20 empty cargo holds.]
  661. # []
  662. # [We are selling up to 2573. You have 0 in your holds.]
  663. # [How many holds of Fuel Ore do you want to buy [20]? 0]
  664. def game_line(self, line: str):
  665. if line.startswith("You have ") and 'credits and' in line:
  666. parts = line.replace(',', '').split()
  667. credits = int(parts[2])
  668. log.msg("Credits: {0}".format(credits))
  669. self.credits = credits
  670. if self.state == 1:
  671. # First exploration
  672. if line.startswith("Sector :"):
  673. # We have starting sector information
  674. parts = re.split("\s+", line)
  675. self.this_sector = int(parts[2])
  676. # These will be the ones swapped around as we trade back and forth.
  677. self.sector1 = self.this_sector
  678. elif line.startswith("Warps to Sector(s) : "):
  679. # Warps to Sector(s) : 397 - (562) - (639)
  680. _, _, warps = line.partition(':')
  681. warps = warps.replace('-', '').replace('(', '').replace(')', '').strip()
  682. log.msg("Warps: [{0}]".format(warps))
  683. self.warps = [ int(x) for x in re.split("\s+", warps)]
  684. log.msg("Warps: [{0}]".format(self.warps))
  685. self.state = 2
  686. elif self.state == 2:
  687. if line == "":
  688. # Ok, we're done
  689. self.state = 3
  690. # Check to see if we have information on any possible ports
  691. # if hasattr(self.game, 'portdata'):
  692. if True:
  693. if not self.this_sector in self.game.gamedata.ports:
  694. self.state = 0
  695. log.msg("Current sector {0} not in portdata.".format(self.this_sector))
  696. self.queue_game.put(self.r + self.nl + "I can't find the current sector in the portdata." + self.nl)
  697. self.deactivate()
  698. return
  699. else:
  700. # Ok, we are in the portdata
  701. pd = self.game.gamedata.ports[self.this_sector]
  702. if GameData.port_burnt(pd):
  703. log.msg("Current sector {0} port is burnt (<= 20%).".format(self.this_sector))
  704. self.queue_game.put(self.r + self.nl + "Current sector port is burnt out. <= 20%." + self.nl)
  705. self.deactivate()
  706. return
  707. possible = [ x for x in self.warps if x in self.game.gamedata.ports ]
  708. log.msg("Possible:", possible)
  709. # BUG: Sometimes links to another sector, don't link back!
  710. # This causes the game to plot a course / autopilot.
  711. # if hasattr(self.game, 'warpdata'):
  712. if True:
  713. # Great! verify that those warps link back to us!
  714. possible = [ x for x in possible if x in self.game.gamedata.warps and self.this_sector in self.game.gamedata.warps[x]]
  715. if len(possible) == 0:
  716. self.state = 0
  717. self.queue_game.put(self.r + self.nl + "I don't see any ports in [{0}].".format(self.warps) + self.nl)
  718. self.deactivate()
  719. return
  720. possible = [ x for x in possible if not GameData.port_burnt(self.game.gamedata.ports[x]) ]
  721. log.msg("Possible:", possible)
  722. if len(possible) == 0:
  723. self.state = 0
  724. self.queue_game.put(self.r + self.nl + "I don't see any unburnt ports in [{0}].".format(self.warps) + self.nl)
  725. self.deactivate()
  726. return
  727. possible = [ x for x in possible if GameData.port_trading(self.game.gamedata.ports[self.this_sector]['port'], self.game.gamedata.ports[x]['port'])]
  728. self.possible = possible
  729. if len(possible) == 0:
  730. self.state = 0
  731. self.queue_game.put(self.r + self.nl + "I don't see any possible port trades in [{0}].".format(self.warps) + self.nl)
  732. self.deactivate()
  733. return
  734. elif len(possible) == 1:
  735. # Ok! there's only one!
  736. self.sector2 = possible[0]
  737. # Display possible ports:
  738. # spos = [ str(x) for x in possible]
  739. # self.queue_game.put(self.r + self.nl + self.nl.join(spos) + self.nl)
  740. # At state 3, we only get a prompt.
  741. return
  742. else:
  743. self.state = 0
  744. log.msg("We don't have any portdata!")
  745. self.queue_game.put(self.r + self.nl + "I have no portdata. Please run CIM Port Report." + self.nl)
  746. self.deactivate()
  747. return
  748. elif self.state == 5:
  749. if "-----" in line:
  750. self.state = 6
  751. elif self.state == 6:
  752. if "We are buying up to" in line:
  753. # Sell
  754. self.state = 7
  755. self.queue_player.put("\r")
  756. self.sell_perc = 100 + self.percent
  757. if "We are selling up to" in line:
  758. # Buy
  759. self.state = 8
  760. self.sell_perc = 100 - self.percent
  761. if line.startswith('Fuel Ore') or line.startswith('Organics') or line.startswith('Equipment'):
  762. work = line.replace('Fuel Ore', 'Fuel')
  763. parts = re.split(r"\s+", work)
  764. # log.msg(parts)
  765. # Equipment, Selling xxx x% xxx
  766. if parts[-1] != '0' and parts[2] != '0' and parts[1] != 'Buying':
  767. log.msg("We have a problem -- they aren't buying what we have in stock!")
  768. stuff = line[0] # F O or E.
  769. if stuff == 'F':
  770. spos = 0
  771. elif stuff == 'O':
  772. spos = 1
  773. else:
  774. spos = 2
  775. other = self.other_port['port']
  776. if other[spos] == 'B':
  777. self.fixable = 1
  778. if "You don't have anything they want" in line:
  779. # Neither! DRAT!
  780. if not self.fixable:
  781. self.deactivate()
  782. return
  783. if "We're not interested." in line:
  784. log.msg("Try, try again. :(")
  785. self.state = 5
  786. self.trade()
  787. elif self.state == 7:
  788. # Haggle Sell
  789. if "We'll buy them for" in line or "Our final offer" in line:
  790. if "Our final offer" in line:
  791. self.sell_perc -= 1
  792. parts = line.replace(',', '').split()
  793. start_price = int(parts[4])
  794. price = start_price * self.sell_perc // 100
  795. log.msg("start: {0} % {1} price {2}".format(start_price, self.sell_perc, price))
  796. self.sell_perc -= 1
  797. self.queue_player.put("{0}\r".format(price))
  798. if "We are selling up to" in line:
  799. # Buy
  800. self.state = 8
  801. self.sell_perc = 100 - self.percent
  802. if "We're not interested." in line:
  803. log.msg("Try, try again. :(")
  804. self.state = 5
  805. self.trade()
  806. if "WHAT?!@!? you must be crazy!" in line:
  807. log.msg("fix offer")
  808. self.fix_offer = 1
  809. if "So, you think I'm as stupid as you look?" in line:
  810. log.msg("fix offer")
  811. self.fix_offer = 1
  812. if "Quit playing around, you're wasting my time!" in line:
  813. log.msg("fix offer")
  814. self.fix_offer = 1
  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. class ScriptExplore(object):
  842. """ Script Explore v1.00
  843. By: David Thielemann
  844. WARNINGS:
  845. We assume the player has a Holo-Scanner!
  846. We assume the player has lots o turns, or unlimited turns!
  847. We assume the player is aware we run infinitly until we can't find new sectors to move to!
  848. """
  849. def __init__(self, game):
  850. self.game = game
  851. self.queue_game = game.queue_game
  852. self.queue_player = game.queue_player
  853. self.observer = game.observer
  854. self.r = Style.RESET_ALL
  855. self.nl = "\n\r"
  856. # Our Stuff, Not our pants!
  857. self.dense = [] # We did a density, store that info.
  858. self.didScan = False # Track if we did a scan already... prevents us calling a scan multiple times.
  859. self.clear = [] # Warps that we know are clear.
  860. self.highsector = 0 # Selected Sector to move to next!
  861. self.highwarp = 0 # Selected Sector's Warp Count!
  862. self.stacksector = set() # Set of sectors that we have not picked but are unexplored... even though we did a holo!
  863. self.oneMoveSector = False
  864. self.times = 0
  865. self.maxtimes = 0
  866. # Activate
  867. self.prompt = game.buffer
  868. self.save = self.observer.save()
  869. self.observer.connect('player', self.player)
  870. self.observer.connect("prompt", self.game_prompt)
  871. self.observer.connect("game-line", self.game_line)
  872. self.defer = None
  873. self.send2player("Explorer v1.01")
  874. # How many times we going to go today?
  875. ask = PlayerInput(self.game)
  876. def settimes(*_):
  877. times = int(ask.keep['times'].strip())
  878. log.msg("settimes got '{0}'".format(times))
  879. if times == None:
  880. self.deactivate()
  881. else:
  882. self.times = times
  883. self.maxtimes = times
  884. self.send2game("D")
  885. self.state = 1
  886. d = ask.prompt("How many sectors would you lick to explode?", 5, name="times", digits=True)
  887. #d.addCallback(ask.output)
  888. #d.addCallback(lambda ignore: self.settimes(ask.keep))
  889. d.addCallback(settimes)
  890. def whenDone(self):
  891. self.defer = defer.Deferred()
  892. # Call this to chain something after we exit.
  893. return self.defer
  894. def deactivate(self):
  895. self.state = 0
  896. log.msg("ScriptExplore.deactivate()")
  897. assert(not self.save is None)
  898. self.observer.load(self.save)
  899. self.save = None
  900. if self.defer:
  901. self.defer.callback('done')
  902. self.defer = None
  903. def player(self, chunk: bytes):
  904. # If we receive anything -- ABORT!
  905. self.deactivate()
  906. def send2game(self, txt):
  907. self.queue_player.put(txt)
  908. def send2player(self, txt):
  909. self.queue_game.put(
  910. self.nl + txt + self.r + self.nl
  911. )
  912. def resetStuff(self):
  913. self.didScan = False
  914. self.dense = []
  915. self.clear = []
  916. self.highwarp = 0
  917. self.highsector = 0
  918. log.msg("ScriptExplore.resetStuff()")
  919. def dead_end(self):
  920. """ We've reached a dead end.
  921. Either pop a new location to travel to, or give it up.
  922. """
  923. self.send2player(Boxes.alert("** DEAD END **", base="blue"))
  924. if self.stacksector:
  925. # Ok, there's somewhere to go ...
  926. self.highsector = self.stacksector.pop()
  927. # travel state
  928. self.state = 10
  929. self.send2game("{0}\r".format(self.highsector))
  930. else:
  931. self.send2player(Boxes.alert("I've run out of places to look here."))
  932. self.deactivate()
  933. def game_prompt(self, prompt: str):
  934. log.msg("{0} : {1}".format(self.state, prompt))
  935. if self.state == 3:
  936. log.msg("dense is {0} sectors big".format(len(self.dense)))
  937. self.state += 1
  938. self.send2game("SH")
  939. if self.state == 12:
  940. # Looking for "Engage the Autopiolot?"
  941. if prompt.startswith('Engage the Autopilot? (Y/N/Single step/Express) [Y]'):
  942. self.send2game("S")
  943. self.travel_path.pop(0)
  944. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]'):
  945. self.send2game("SD")
  946. self.state += 1
  947. if self.state == 20:
  948. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]'):
  949. # Stop in this sector / Yes!
  950. self.send2game("Y")
  951. self.state = 1
  952. # this should re-trigger a scan
  953. def game_line(self, line: str):
  954. log.msg("{0} | {1}".format(self.state, line))
  955. if "Mine Control" in line: # If we don't have a Holo-Scanner and we attempted to do a Holo-scan, abort
  956. self.deactivate()
  957. if self.state == 1:
  958. # Density Scan, (Assume we have the Holo-Scanner)
  959. if not self.didScan:
  960. self.send2game("SD")
  961. self.didScan = True
  962. if "Relative Density Scan" in line:
  963. self.state += 1
  964. if self.state == 2:
  965. # Get the Density Data!
  966. if line.startswith("Sector"):
  967. new_sector = '(' in line
  968. work = line.replace(':', '').replace(')', '').replace('(', '').replace('%', '').replace('==>', '')
  969. work = re.split(r"\s+", work)
  970. log.msg(work)
  971. # 'Sector', '8192', '0', 'Warps', '4', 'NavHaz', '0', 'Anom', 'No'
  972. # 'Sector', '(', '8192)', '0', 'Warps', '4', 'NavHaz', '0', 'Anom', 'No'
  973. # New Sector?
  974. if new_sector:
  975. # Switch Anom into bool state
  976. if(work[8] == 'No'):
  977. temp = False
  978. else:
  979. temp = True
  980. self.dense.append( {'sector': int(work[1]), 'density': int(work[2]), 'warps': int(work[4]), 'navhaz': int(work[6]), 'anom': temp} )
  981. log.msg(self.dense)
  982. # {'sector': 8192, 'density': 0, 'warps': 4, 'navhaz': 0, 'anom': False}
  983. elif line == "":
  984. self.state += 1
  985. elif self.state == 4:
  986. # Begin Processing our data we got from density scan and find highest warp count in what sectors
  987. # Remove sectors with one warp
  988. # Do we have a new place to go? (That is also worth going to)
  989. if not self.dense: # Dense contains no new sectors, abort
  990. log.msg("No New Sectors Found!")
  991. self.dead_end()
  992. return
  993. # self.send2player(Boxes.alert("Find a new area for me to search in!"))
  994. # Attempt to resolve no new sectors!
  995. # if self.stacksector: # Do we have anything on the stack? (If so we set highsector with one of the randomly selected sectors)
  996. # self.highsector = self.stacksector.pop()
  997. # self.deactivate()
  998. elif self.dense: # Dense does contain at least 1 new sector, continue on
  999. # list comprehension
  1000. t = [d for d in self.dense if d['warps'] > 1]
  1001. # t = [] # Pre-Test to check if there are just a bunch of 1 warp sectors
  1002. # for d in self.dense:
  1003. # if d['warps'] > 1:
  1004. # t.append(d['sector'])
  1005. if not t: # If there are no sectors with more that 1 warp, abort
  1006. log.msg("No Sectors Found except one move sector!")
  1007. self.dead_end()
  1008. return
  1009. # self.send2player(Boxes.alert("Find a new area for me to look at!"))
  1010. # Attempt to resolve no new sectors with more than 1 warp!
  1011. # if self.stacksector: # Do we have anything on the stack? (If so we set highsector with one of the randomly selected sectors)
  1012. # self.highsector = self.stacksector.pop()
  1013. # self.deactivate()
  1014. # Is the sector safe to go into?
  1015. for d in self.dense:
  1016. if not d['anom']:
  1017. # Sector does not contain a Anomoly
  1018. if not d['navhaz']:
  1019. # Sector does not contain Hazards
  1020. if d['density'] in (0, 1, 100, 101):
  1021. # Sector does contain empty space / a beacon / a port / or a beacon and port
  1022. if d['warps'] > 1:
  1023. # If Sector is worth checking out?
  1024. self.clear.append(d['sector'])
  1025. if self.clear: # We have sector(s) we can move to!
  1026. log.msg("Clear Sectors: {0}".format(len(self.clear)))
  1027. # This was state 5 but why can't we reduce number of states? ( Yeah let's kick California and New York out of the US, oh wrong states :P )
  1028. # Sort to find greatest warp count
  1029. for c in self.clear:
  1030. for d in self.dense:
  1031. if d['sector'] == c:
  1032. if d['warps'] > self.highwarp:
  1033. self.highwarp = d['warps']
  1034. self.highsector = d['sector']
  1035. elif d['warps'] == self.highwarp:
  1036. if d['sector'] > self.highsector:
  1037. self.highsector = d['sector']
  1038. if self.highwarp and self.highsector:
  1039. log.msg("Sector: {0:5d} Warps: {1}".format(self.highsector, self.highwarp))
  1040. self.state += 1
  1041. elif self.state == 5:
  1042. # Add the dense scan of unknown sectors onto the stack of sectors, only save the ones we think are clear... for now.
  1043. for c in self.clear:
  1044. self.stacksector.add(c)
  1045. # Remove the sector we are just about to go to, we use discard so if the sector does not exist we don't throw a error!
  1046. self.stacksector.discard(self.highsector)
  1047. # Ok we know the sector we want to go to now let's move it!
  1048. self.send2game("m{0}\n\r".format(self.highsector))
  1049. # Reset Variables for fresh data
  1050. self.resetStuff()
  1051. self.state = 1
  1052. # Warning! Yes we can and will eat all the turns! :P
  1053. self.times -= 1
  1054. if self.times <= 0:
  1055. self.send2player("Completed {0}".format(self.maxtimes))
  1056. self.deactivate()
  1057. elif self.state == 10:
  1058. # Warping
  1059. self.go_on = True
  1060. if line.startswith('The shortest path ('):
  1061. # Ok, we've got a path.
  1062. self.state += 1
  1063. elif self.state == 11:
  1064. self.travel_path = line.split(' > ')
  1065. self.state += 1
  1066. elif self.state == 13:
  1067. if 'Relative Density Scan' in line:
  1068. self.state += 1
  1069. elif self.state == 14:
  1070. if line == "":
  1071. # end of the scan, decision time
  1072. if self.stophere:
  1073. # Ok, let's stop here!
  1074. # Re-save the sector we were trying to get to. (we didn't make it there)
  1075. self.stacksector.add(self.highsector)
  1076. self.state = 20
  1077. else:
  1078. if self.go_on:
  1079. # Ok, carry on!
  1080. self.state = 12
  1081. self.travel_path.pop(0)
  1082. else:
  1083. work = line.replace(' :', '').replace('%', '').replace(')', '').replace('==>', '')
  1084. # Does this contain something new? unseen?
  1085. stophere = '(' in work
  1086. work = work.replace('(','')
  1087. #Sector XXXX DENS Warps N NavHaz P Anom YN
  1088. parts = re.split(r'\s+', work)
  1089. # Don't bother stopping if there's only one warp
  1090. if stophere and parts[4] == '1':
  1091. stophere = False
  1092. if stophere:
  1093. self.stophere = True
  1094. next_stop = travel_path[0]
  1095. if parts[1] == next_stop:
  1096. # Ok, this is our next stop. Is it safe to travel to?
  1097. if parts[2] not in ('100', '0'):
  1098. # Ok, it's not safe to go on.
  1099. self.go_on = False
  1100. # Check the rest navhav and anom ...
  1101. class ScriptSpace(object):
  1102. """ Space Exploration script.
  1103. Send "SD", verify paths are clear.
  1104. Find nearest unknown. Sector + CR.
  1105. Save "shortest path from to" information.
  1106. At 'Engage the Autopilot', Send "S" (Single Step)
  1107. At '[Stop in this sector', Send "SD", verify path is clear.
  1108. Send "SH" (glean sector/port information along the way.)
  1109. Send "N" (Next)!
  1110. Send "SD" / Verify clear.
  1111. Send "SH"
  1112. Repeat for Next closest.
  1113. """
  1114. def __init__(self, game):
  1115. self.game = game
  1116. self.queue_game = game.queue_game
  1117. self.queue_player = game.queue_player
  1118. self.observer = game.observer
  1119. self.r = Style.RESET_ALL
  1120. self.nl = "\n\r"
  1121. self.this_sector = None # Starting sector
  1122. self.target_sector = None # Sector going to
  1123. self.path = []
  1124. self.times_left = 0 # How many times to look for target
  1125. self.density = dict() # Results of density scan. (Just the numbers)
  1126. # Activate
  1127. self.prompt = game.buffer
  1128. self.save = self.observer.save()
  1129. self.observer.connect('player', self.player)
  1130. self.observer.connect("prompt", self.game_prompt)
  1131. self.observer.connect("game-line", self.game_line)
  1132. self.defer = None
  1133. self.queue_game.put(
  1134. self.nl + "Bugz (like space), is big." + self.r + self.nl
  1135. )
  1136. self.state = 1
  1137. self.queue_player.put("SD")
  1138. # Get current density scan + also get the current sector.
  1139. # [Command [TL=00:00:00]:[XXXX] (?=Help)? : D]
  1140. def whenDone(self):
  1141. self.defer = defer.Deferred()
  1142. # Call this to chain something after we exit.
  1143. return self.defer
  1144. def deactivate(self):
  1145. self.state = 0
  1146. log.msg("ScriptPort.deactivate ({0})".format(self.times_left))
  1147. assert(not self.save is None)
  1148. self.observer.load(self.save)
  1149. self.save = None
  1150. if self.defer:
  1151. self.defer.callback('done')
  1152. self.defer = None
  1153. def player(self, chunk: bytes):
  1154. # If we receive anything -- ABORT!
  1155. self.deactivate()
  1156. def unknown_search(self, starting_sector):
  1157. seen = set()
  1158. possible = set()
  1159. possible.add(int(starting_sector))
  1160. done = False
  1161. while not done:
  1162. next_possible = set()
  1163. for s in possible:
  1164. p = self.game.gamedata.get_warps(s)
  1165. if p is not None:
  1166. for pos in p:
  1167. if pos not in seen:
  1168. next_possible.add(pos)
  1169. else:
  1170. log.msg("unknown found:", s)
  1171. self.unknown = s
  1172. done = True
  1173. break
  1174. seen.add(s)
  1175. if self.unknown is None:
  1176. log.msg("possible:", next_possible)
  1177. possible = next_possible
  1178. yield
  1179. def find_unknown(self, starting_sector):
  1180. log.msg("find_unknown( {0})".format(starting_sector))
  1181. d = defer.Deferred()
  1182. # Process things
  1183. self.unknown = None
  1184. c = coiterate(self.unknown_search(starting_sector))
  1185. c.addCallback(lambda unknown: d.callback(self.unknown))
  1186. return d
  1187. def show_unknown(self, sector):
  1188. if sector is None:
  1189. self.deactivate()
  1190. return
  1191. log.msg("Travel to {0}...".format(sector))
  1192. self.queue_player.put("{0}\r".format(sector))
  1193. def game_prompt(self, prompt: str):
  1194. log.msg("{0} : {1}".format(self.state, prompt))
  1195. if self.state == 3:
  1196. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1197. # this_sector code isn't working -- so! Get sector from prompt
  1198. self.state = 4
  1199. _, _, sector = prompt.partition(']:[')
  1200. sector, _, _ = sector.partition(']')
  1201. self.this_sector = int(sector)
  1202. # Ok, we're done with Density Scan, and we're back at the command prompt
  1203. log.msg("Go find the nearest unknown...")
  1204. d = self.find_unknown(sector)
  1205. d.addCallback(self.show_unknown)
  1206. elif self.state == 6:
  1207. # Engage the autopilot?
  1208. if prompt.startswith('Engage the Autopilot? (Y/N/Single step/Express) [Y]'):
  1209. self.state = 7
  1210. sector = self.path.pop(0)
  1211. if sector in self.density:
  1212. if self.density[sector] in (0, 100):
  1213. # Ok, looks safe!
  1214. self.queue_player.put("S")
  1215. self.this_sector = sector
  1216. else:
  1217. log.msg("FATAL: Density for {0} is {1}".format(sector, self.density[sector]))
  1218. self.deactivate()
  1219. return
  1220. else:
  1221. log.msg("{0} not in density scan? (how's that possible?)".format(sector))
  1222. self.deactivate()
  1223. return
  1224. elif self.state == 7:
  1225. # Ok, we're in a new sector (single stepping through space)
  1226. # update density scan
  1227. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1228. self.queue_player.put("SD")
  1229. self.state = 8
  1230. elif self.state == 10:
  1231. # Because we're here
  1232. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1233. self.queue_player.put("SH")
  1234. self.state = 11
  1235. elif self.state == 11:
  1236. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1237. # Ok, is the density scan clear?
  1238. sector = self.path.pop(0)
  1239. if sector in self.density:
  1240. if self.density[sector] in (0, 100):
  1241. # Ok, looks safe
  1242. self.queue_player.put("N")
  1243. self.state = 7
  1244. self.this_sector = sector
  1245. else:
  1246. log.msg("FATAL: Density for {0} is {1}".format(sector, self.density[sector]))
  1247. self.deactivate()
  1248. return
  1249. else:
  1250. log.msg("{0} not in density scane? (how's that possible...)".format(sector))
  1251. self.deactivate()
  1252. return
  1253. def next_unknown(self, sector):
  1254. log.msg("Unknown is :", sector)
  1255. self.deactivate()
  1256. def game_line(self, line: str):
  1257. log.msg("line {0} : {1}".format(self.state, line))
  1258. if line.startswith('Sector : '):
  1259. work = line.strip()
  1260. parts = re.split(r"\s+", work)
  1261. self.this_sector = int(parts[2])
  1262. log.msg("game_line sector", self.this_sector)
  1263. elif line.startswith("Command [TL=]"):
  1264. # Ok, get the current sector from this
  1265. _, _, sector = line.partition("]:[")
  1266. sector, _, _ = sector.partition("]")
  1267. self.this_sector = int(sector)
  1268. log.msg("current sector: {0}".format(self.this_sector))
  1269. elif line.startswith('Warps to Sector(s) :'):
  1270. # Warps to Sector(s) : 5468
  1271. _, _, work = line.partition(':')
  1272. work = work.strip().replace('(', '').replace(')', '').replace(' - ', ' ')
  1273. parts = [ int(x) for x in work.split(' ')]
  1274. self.path = list(parts)
  1275. if self.state in (1, 8):
  1276. if 'Relative Density Scan' in line:
  1277. # Start Density Scan
  1278. self.state += 1
  1279. self.density = {}
  1280. elif self.state in (2, 9):
  1281. if line == '':
  1282. # End of Density Scan
  1283. self.state += 1
  1284. log.msg("Density: {0}".format(self.density))
  1285. # self.deactivate()
  1286. elif line.startswith('Sector'):
  1287. # Parse Density Scan values
  1288. work = line.replace('(', '').replace(')', '').replace(':', '').replace('%', '').replace(',', '')
  1289. parts = re.split(r'\s+', work)
  1290. log.msg("Sector", parts)
  1291. sector = int(parts[1])
  1292. self.density[sector] = int(parts[3])
  1293. if parts[7] != '0':
  1294. log.msg("NavHaz {0} : {1}".format(parts[7], work))
  1295. self.density[sector] += 99
  1296. if parts[9] != 'No':
  1297. log.msg("Anom {0} : {1}".format(parts[9], work))
  1298. self.density[sector] += 990
  1299. elif self.state == 4:
  1300. # Looking for shortest path message / warp info
  1301. # Or possibly, "We're here!"
  1302. if line.startswith('Sector :') and str(self.unknown) in line:
  1303. # Ok, I'd guess that we're already there!
  1304. # Try it again!
  1305. self.queue_player.put("SD")
  1306. self.state = 1
  1307. if line.startswith('The shortest path'):
  1308. self.state = 5
  1309. elif self.state == 5:
  1310. # This is the warps line
  1311. # Can this be multiple lines?
  1312. if line == "":
  1313. self.state = 6
  1314. else:
  1315. work = line.replace("(", "").replace(")", "").replace(">", "").strip()
  1316. self.path = [int(x) for x in work.split()]
  1317. log.msg("Path:", self.path)
  1318. # Verify
  1319. current = self.path.pop(0)
  1320. if current != self.this_sector:
  1321. log.msg("Failed: {0} != {1}".format(current, self.this_sector))
  1322. self.deactivate()
  1323. return
  1324. elif self.state == 7:
  1325. if self.unknown == self.this_sector:
  1326. # We have arrived!
  1327. log.msg("We're here!")
  1328. self.deactivate()
  1329. class ProxyMenu(object):
  1330. """ Display ProxyMenu
  1331. Example:
  1332. from flexible import ProxyMenu
  1333. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1334. menu = ProxyMenu(self.game)
  1335. """
  1336. def __init__(self, game):
  1337. self.nl = "\n\r"
  1338. self.c = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  1339. self.r = Style.RESET_ALL
  1340. self.c1 = merge(Style.BRIGHT + Fore.BLUE)
  1341. self.c2 = merge(Style.NORMAL + Fore.CYAN)
  1342. # self.portdata = None
  1343. self.game = game
  1344. self.queue_game = game.queue_game
  1345. self.observer = game.observer
  1346. # Am I using self or game? (I think I want game, not self.)
  1347. # if hasattr(self.game, "portdata"):
  1348. # self.portdata = self.game.portdata
  1349. # else:
  1350. # self.portdata = {}
  1351. # if hasattr(self.game, 'warpdata'):
  1352. # self.warpdata = self.game.warpdata
  1353. # else:
  1354. # self.warpdata = {}
  1355. if hasattr(self.game, 'trade_report'):
  1356. self.trade_report = self.game.trade_report
  1357. else:
  1358. self.trade_report = []
  1359. # Yes, at this point we would activate
  1360. self.prompt = game.buffer
  1361. self.save = self.observer.save()
  1362. self.observer.connect("player", self.player)
  1363. # If we want it, it's here.
  1364. self.defer = None
  1365. self.keepalive = task.LoopingCall(self.awake)
  1366. self.keepalive.start(30)
  1367. self.menu()
  1368. def __del__(self):
  1369. log.msg("ProxyMenu {0} RIP".format(self))
  1370. def whenDone(self):
  1371. self.defer = defer.Deferred()
  1372. # Call this to chain something after we exit.
  1373. return self.defer
  1374. def menu(self):
  1375. self.queue_game.put(
  1376. self.nl + self.c + "TradeWars Proxy active." + self.r + self.nl
  1377. )
  1378. def menu_item(ch: str, desc: str):
  1379. self.queue_game.put(
  1380. " " + self.c1 + ch + self.c2 + " - " + self.c1 + desc + self.nl
  1381. )
  1382. menu_item("D", "Display Report again")
  1383. # menu_item("Q", "Quest")
  1384. # if hasattr(self.game, 'portdata'):
  1385. # ports = len(self.game.portdata)
  1386. # else:
  1387. # ports = '?'
  1388. menu_item("P", "Port CIM Report ({0})".format(len(self.game.gamedata.ports)))
  1389. # if hasattr(self.game, 'warpdata'):
  1390. # warps = len(self.game.warpdata)
  1391. # else:
  1392. # warps = '?'
  1393. menu_item("W", "Warp CIM Report ({0})".format(len(self.game.gamedata.warps)))
  1394. menu_item("T", "Trading Report")
  1395. menu_item("S", "Scripts")
  1396. menu_item("X", "eXit")
  1397. self.queue_game.put(" " + self.c + "-=>" + self.r + " ")
  1398. def awake(self):
  1399. log.msg("ProxyMenu.awake()")
  1400. self.game.queue_player.put(" ")
  1401. def port_report(self, portdata: dict):
  1402. # self.portdata = portdata
  1403. # self.game.portdata = portdata
  1404. self.queue_game.put("Loaded {0} ports.".format(len(portdata)) + self.nl)
  1405. self.welcome_back()
  1406. def warp_report(self, warpdata: dict):
  1407. # self.warpdata = warpdata
  1408. # self.game.warpdata = warpdata
  1409. self.queue_game.put("Loaded {0} sectors.".format(len(warpdata)) + self.nl)
  1410. self.welcome_back()
  1411. def make_trade_report(self):
  1412. log.msg("make_trade_report()")
  1413. ok_trades = []
  1414. best_trades = []
  1415. # for sector, pd in self.game.gamedata.ports.items():
  1416. for sector in sorted(self.game.gamedata.ports.keys()):
  1417. pd = self.game.gamedata.ports[sector]
  1418. if not GameData.port_burnt(pd):
  1419. pc = pd['class']
  1420. # Ok, let's look into it.
  1421. if not sector in self.game.gamedata.warps:
  1422. continue
  1423. warps = self.game.gamedata.warps[sector]
  1424. for w in warps:
  1425. # Verify that we have that warp's info, and that the sector is in it.
  1426. # (We can get back from it)
  1427. if w in self.game.gamedata.warps and sector in self.game.gamedata.warps[w]:
  1428. # Ok, we can get there -- and get back!
  1429. if w > sector and w in self.game.gamedata.ports and not GameData.port_burnt(self.game.gamedata.ports[w]):
  1430. # it is > and has a port.
  1431. wd = self.game.gamedata.ports[w]
  1432. wc = wd['class']
  1433. # 1: "BBS",
  1434. # 2: "BSB",
  1435. # 3: "SBB",
  1436. # 4: "SSB",
  1437. # 5: "SBS",
  1438. # 6: "BSS",
  1439. # 7: "SSS",
  1440. # 8: "BBB",
  1441. if pc in (1,5) and wc in (2,4):
  1442. best_trades.append(self.game.gamedata.port_trade_show(sector, w))
  1443. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  1444. elif pc in (2,4) and wc in (1,5):
  1445. best_trades.append(self.game.gamedata.port_trade_show(sector, w))
  1446. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  1447. elif GameData.port_trading(pd['port'], self.game.gamedata.ports[w]['port']):
  1448. # ok_trades.append( "{0:5} -=- {1:5}".format(sector,w))
  1449. ok_trades.append(self.game.gamedata.port_trade_show(sector, w))
  1450. yield
  1451. self.trade_report.append("Best Trades: (org/equ)")
  1452. self.trade_report.extend(best_trades)
  1453. self.trade_report.append("Ok Trades:")
  1454. self.trade_report.extend(ok_trades)
  1455. # self.queue_game.put("BEST TRADES:" + self.nl + self.nl.join(best_trades) + self.nl)
  1456. # self.queue_game.put("OK TRADES:" + self.nl + self.nl.join(ok_trades) + self.nl)
  1457. def show_trade_report(self, *_):
  1458. self.game.trade_report = self.trade_report
  1459. for t in self.trade_report:
  1460. self.queue_game.put(t + self.nl)
  1461. self.welcome_back()
  1462. def player(self, chunk: bytes):
  1463. """ Data from player (in bytes). """
  1464. chunk = chunk.decode("latin-1", "ignore")
  1465. key = chunk.upper()
  1466. log.msg("ProxyMenu.player({0})".format(key))
  1467. # Stop the keepalive if we are activating something else
  1468. # or leaving...
  1469. self.keepalive.stop()
  1470. if key == "T":
  1471. self.queue_game.put(self.c + key + self.r + self.nl)
  1472. # Trade Report
  1473. # do we have enough information to do this?
  1474. # if not hasattr(self.game, 'portdata') and not hasattr(self.game, 'warpdata'):
  1475. # self.queue_game.put("Missing portdata and warpdata." + self.nl)
  1476. # elif not hasattr(self.game, 'portdata'):
  1477. # self.queue_game.put("Missing portdata." + self.nl)
  1478. # elif not hasattr(self.game, 'warpdata'):
  1479. # self.queue_game.put("Missing warpdata." + self.nl)
  1480. # else:
  1481. if True:
  1482. # Yes, so let's start!
  1483. self.trade_report = []
  1484. d = coiterate(self.make_trade_report())
  1485. d.addCallback(self.show_trade_report)
  1486. return
  1487. elif key == "P":
  1488. self.queue_game.put(self.c + key + self.r + self.nl)
  1489. # Activate CIM Port Report
  1490. report = CIMPortReport(self.game)
  1491. d = report.whenDone()
  1492. d.addCallback(self.port_report)
  1493. d.addErrback(self.welcome_back)
  1494. return
  1495. elif key == "W":
  1496. self.queue_game.put(self.c + key + self.r + self.nl)
  1497. # Activate CIM Warp Report
  1498. report = CIMWarpReport(self.game)
  1499. d = report.whenDone()
  1500. d.addCallback(self.warp_report)
  1501. d.addErrback(self.welcome_back)
  1502. return
  1503. elif key == "S":
  1504. self.queue_game.put(self.c + key + self.r + self.nl)
  1505. # Scripts
  1506. self.activate_scripts_menu()
  1507. return
  1508. elif key == "D":
  1509. self.queue_game.put(self.c + key + self.r + self.nl)
  1510. # (Re) Display Trade Report
  1511. if self.trade_report:
  1512. for t in self.trade_report:
  1513. self.queue_game.put(t + self.nl)
  1514. else:
  1515. self.queue_game.put("Missing trade_report." + self.nl)
  1516. # self.queue_game.put(pformat(self.portdata).replace("\n", "\n\r") + self.nl)
  1517. # self.queue_game.put(pformat(self.warpdata).replace("\n", "\n\r") + self.nl)
  1518. elif key == "Q":
  1519. self.queue_game.put(self.c + key + self.r + self.nl)
  1520. # This is an example of chaining PlayerInput prompt calls.
  1521. ask = PlayerInput(self.game)
  1522. d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  1523. # Display the user's input
  1524. d.addCallback(ask.output)
  1525. d.addCallback(
  1526. lambda ignore: ask.prompt(
  1527. "What is your favorite color?", 10, name="color"
  1528. )
  1529. )
  1530. d.addCallback(ask.output)
  1531. d.addCallback(
  1532. lambda ignore: ask.prompt(
  1533. "What is the meaning of the squirrel?",
  1534. 12,
  1535. name="squirrel",
  1536. digits=True,
  1537. )
  1538. )
  1539. d.addCallback(ask.output)
  1540. def show_values(show):
  1541. log.msg(show)
  1542. self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  1543. d.addCallback(lambda ignore: show_values(ask.keep))
  1544. d.addCallback(self.welcome_back)
  1545. # On error, just return back
  1546. # This doesn't seem to be getting called.
  1547. # d.addErrback(lambda ignore: self.welcome_back)
  1548. d.addErrback(self.welcome_back)
  1549. return
  1550. elif key == "X":
  1551. self.queue_game.put(self.c + key + self.r + self.nl)
  1552. self.queue_game.put("Proxy done." + self.nl)
  1553. self.observer.load(self.save)
  1554. self.save = None
  1555. # It isn't running (NOW), so don't try to stop it.
  1556. # self.keepalive.stop()
  1557. self.keepalive = None
  1558. # Ok, this is a HORRIBLE idea, because the prompt might be
  1559. # outdated.
  1560. # self.queue_game.put(self.prompt)
  1561. self.prompt = None
  1562. # Possibly: Send '\r' to re-display the prompt
  1563. # instead of displaying the original one.
  1564. self.game.queue_player.put("d")
  1565. # Were we asked to do something when we were done here?
  1566. if self.defer:
  1567. reactor.CallLater(0, self.defer.callback)
  1568. # self.defer.callback()
  1569. self.defer = None
  1570. return
  1571. self.keepalive.start(30, True)
  1572. self.menu()
  1573. def activate_scripts_menu(self):
  1574. self.observer.disconnect("player", self.player)
  1575. self.observer.connect("player", self.scripts_player)
  1576. self.scripts_menu()
  1577. def deactivate_scripts_menu(self, *_):
  1578. self.observer.disconnect("player", self.scripts_player)
  1579. self.observer.connect("player", self.player)
  1580. self.welcome_back()
  1581. def scripts_menu(self, *_):
  1582. c1 = merge(Style.BRIGHT + Fore.CYAN)
  1583. c2 = merge(Style.NORMAL + Fore.CYAN)
  1584. def menu_item(ch, desc):
  1585. self.queue_game.put(
  1586. " " + c1 + ch + c2 + " - " + c1 + desc + self.nl
  1587. )
  1588. menu_item("1", "Ports (Trades between two sectors)")
  1589. menu_item("2", "Explore (Strange new sectors)")
  1590. menu_item("3", "Space... the final frontier...")
  1591. menu_item("X", "eXit")
  1592. self.queue_game.put(" " + c1 + "-=>" + self.r + " ")
  1593. def scripts_player(self, chunk: bytes):
  1594. """ Data from player (in bytes). """
  1595. chunk = chunk.decode("latin-1", "ignore")
  1596. key = chunk.upper()
  1597. if key == '1':
  1598. self.queue_game.put(self.c + key + self.r + self.nl)
  1599. # Activate this magical event here
  1600. ports = ScriptPort(self.game)
  1601. d = ports.whenDone()
  1602. # d.addCallback(self.scripts_menu)
  1603. # d.addErrback(self.scripts_menu)
  1604. d.addCallback(self.deactivate_scripts_menu)
  1605. d.addErrback(self.deactivate_scripts_menu)
  1606. return
  1607. elif key == '2':
  1608. self.queue_game.put(self.c + key + self.r + self.nl)
  1609. explore = ScriptExplore(self.game)
  1610. d = explore.whenDone()
  1611. d.addCallback(self.deactivate_scripts_menu)
  1612. d.addErrback(self.deactivate_scripts_menu)
  1613. return
  1614. elif key == '3':
  1615. self.queue_game.put(self.c + key + self.r + self.nl)
  1616. space = ScriptSpace(self.game)
  1617. d = space.whenDone()
  1618. d.addCallback(self.deactivate_scripts_menu)
  1619. d.addErrback(self.deactivate_scripts_menu)
  1620. return
  1621. elif key == 'X':
  1622. self.queue_game.put(self.c + key + self.r + self.nl)
  1623. self.deactivate_scripts_menu()
  1624. return
  1625. else:
  1626. self.queue_game.put(self.c + "?" + self.r + self.nl)
  1627. self.scripts_menu()
  1628. def welcome_back(self, *_):
  1629. log.msg("welcome_back")
  1630. self.keepalive.start(30, True)
  1631. self.menu()