flexible.py 79 KB

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