flexible.py 59 KB

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