flexible.py 113 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831
  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. BLINK = '\x1b[5m'
  15. log = logging.getLogger(__name__)
  16. class SpinningCursor(object):
  17. """ Spinner class, that handles every so many clicks
  18. s = SpinningCursor(5) # every 5
  19. for x in range(10):
  20. if s.click():
  21. print(s.cycle())
  22. """
  23. def __init__(self, every=10):
  24. self.itercycle = cycle(["/", "-", "\\", "|"])
  25. self.count = 0
  26. self.every = every
  27. def reset(self):
  28. self.itercycle = cycle(["/", "-", "\\", "|"])
  29. self.count = 0
  30. def click(self):
  31. self.count += 1
  32. return self.count % self.every == 0
  33. def cycle(self):
  34. return next(self.itercycle)
  35. def again(self, count: int):
  36. return self.count % count == 0
  37. def merge(color_string):
  38. """ Given a string of colorama ANSI, merge them if you can. """
  39. return color_string.replace("m\x1b[", ";")
  40. class PlayerInput(object):
  41. """ Player Input
  42. Example:
  43. from flexible import PlayerInput
  44. ask = PlayerInput(self.game)
  45. # abort_blank means, if the input field is blank, abort. Use error_back.
  46. d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  47. # Display the user's input / but not needed.
  48. d.addCallback(ask.output)
  49. d.addCallback(
  50. lambda ignore: ask.prompt(
  51. "What is your favorite color?", 10, name="color"
  52. )
  53. )
  54. d.addCallback(ask.output)
  55. d.addCallback(
  56. lambda ignore: ask.prompt(
  57. "What is your least favorite number?",
  58. 12,
  59. name="number",
  60. digits=True,
  61. )
  62. )
  63. d.addCallback(ask.output)
  64. def show_values(show):
  65. log.debug(show)
  66. self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  67. d.addCallback(lambda ignore: show_values(ask.keep))
  68. d.addCallback(self.welcome_back)
  69. # On error, just return back
  70. d.addErrback(self.welcome_back)
  71. """
  72. def __init__(self, game):
  73. # I think game gives us access to everything we need
  74. self.game = game
  75. self.observer = self.game.observer
  76. self.save = None
  77. self.deferred = None
  78. self.queue_game = game.queue_game
  79. self.keep = {}
  80. # default colors
  81. # prompt
  82. self.c = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  83. # input
  84. self.cp = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  85. # useful constants
  86. self.r = Style.RESET_ALL
  87. self.nl = "\n\r"
  88. self.bsb = "\b \b"
  89. self.keepalive = None
  90. def color(self, c):
  91. self.c = c
  92. def colorp(self, cp):
  93. self.cp = cp
  94. def alive(self):
  95. log.debug("PlayerInput.alive()")
  96. self.game.queue_player.put(" ")
  97. def prompt(self, user_prompt, limit, **kw):
  98. """ Generate prompt for user input.
  99. Note: This returns deferred.
  100. prompt = text displayed.
  101. limit = # of characters allowed.
  102. default = (text to default to)
  103. keywords:
  104. abort_blank : Abort if they give us blank text.
  105. name : Stores the input in self.keep dict.
  106. digits : Only allow 0-9 to be entered.
  107. """
  108. log.debug("PlayerInput({0}, {1}, {2}".format(user_prompt, limit, kw))
  109. self.limit = limit
  110. self.input = ""
  111. self.kw = kw
  112. assert self.save is None
  113. assert self.keepalive is None
  114. # Note: This clears out the server "keep alive"
  115. self.save = self.observer.save()
  116. self.observer.connect("player", self.get_input)
  117. self.keepalive = task.LoopingCall(self.alive)
  118. self.keepalive.start(30)
  119. # We need to "hide" the game output.
  120. # Otherwise it WITH mess up the user input display.
  121. self.to_player = self.game.to_player
  122. self.game.to_player = False
  123. # Display prompt
  124. # self.queue_game.put(self.r + self.nl + self.c + user_prompt + " " + self.cp)
  125. self.queue_game.put(self.r + self.c + user_prompt + self.r + " " + self.cp)
  126. # Set "Background of prompt"
  127. self.queue_game.put(" " * limit + "\b" * limit)
  128. assert self.deferred is None
  129. d = defer.Deferred()
  130. self.deferred = d
  131. log.debug("Return deferred ...")
  132. return d
  133. def get_input(self, chunk):
  134. """ Data from player (in bytes) """
  135. chunk = chunk.decode("latin-1", "ignore")
  136. for ch in chunk:
  137. if ch == "\b":
  138. if len(self.input) > 0:
  139. self.queue_game.put(self.bsb)
  140. self.input = self.input[0:-1]
  141. else:
  142. self.queue_game.put("\a")
  143. elif ch == "\r":
  144. self.queue_game.put(self.r + self.nl)
  145. log.debug("Restore observer dispatch {0}".format(self.save))
  146. assert not self.save is None
  147. self.observer.load(self.save)
  148. self.save = None
  149. log.debug("Disable keepalive")
  150. self.keepalive.stop()
  151. self.keepalive = None
  152. line = self.input
  153. self.input = ""
  154. assert not self.deferred is None
  155. self.game.to_player = self.to_player
  156. # If they gave us the keyword name, save the value as that name
  157. if "name" in self.kw:
  158. self.keep[self.kw["name"]] = line
  159. if "abort_blank" in self.kw and self.kw["abort_blank"]:
  160. # Abort on blank input
  161. if line.strip() == "":
  162. # Yes, input is blank, abort.
  163. log.info("errback, abort_blank")
  164. reactor.callLater(
  165. 0, self.deferred.errback, Exception("abort_blank")
  166. )
  167. self.deferred = None
  168. return
  169. # Ok, use deferred.callback, or reactor.callLater?
  170. # self.deferred.callback(line)
  171. reactor.callLater(0, self.deferred.callback, line)
  172. self.deferred = None
  173. return
  174. elif ch.isprintable():
  175. # Printable, but is it acceptable?
  176. if "digits" in self.kw:
  177. if not ch.isdigit():
  178. self.queue_game.put("\a")
  179. continue
  180. if len(self.input) + 1 <= self.limit:
  181. self.input += ch
  182. self.queue_game.put(ch)
  183. else:
  184. self.queue_game.put("\a")
  185. def output(self, line):
  186. """ A default display of what they just input. """
  187. log.debug("PlayerInput.output({0})".format(line))
  188. self.game.queue_game.put(self.r + "[{0}]".format(line) + self.nl)
  189. return line
  190. import re
  191. # The CIMWarpReport -- is only needed if the json file gets damaged in some way.
  192. # Like when the universe gets bigbanged. :P
  193. # or needs to be reset. The warps should automatically update themselves now.
  194. class CIMWarpReport(object):
  195. def __init__(self, game):
  196. self.game = game
  197. self.queue_game = game.queue_game
  198. self.queue_player = game.queue_player
  199. self.observer = game.observer
  200. # Yes, at this point we would activate
  201. self.prompt = game.buffer
  202. self.save = self.observer.save()
  203. # I actually don't want the player input, but I'll grab it anyway.
  204. self.observer.connect("player", self.player)
  205. self.observer.connect("prompt", self.game_prompt)
  206. self.observer.connect("game-line", self.game_line)
  207. # If we want it, it's here.
  208. self.defer = None
  209. self.to_player = self.game.to_player
  210. # Hide what's happening from the player
  211. self.game.to_player = False
  212. self.queue_player.put("^") # Activate CIM
  213. self.state = 1
  214. # self.warpdata = {}
  215. self.queue_game.put("Loading ... ") # cycle eats last char.
  216. self.warpcycle = SpinningCursor()
  217. def game_prompt(self, prompt):
  218. if prompt == ": ":
  219. if self.state == 1:
  220. # Ok, then we're ready to request the port report
  221. self.warpcycle.reset()
  222. self.queue_player.put("I")
  223. self.state = 2
  224. elif self.state == 2:
  225. self.queue_player.put("Q")
  226. self.state = 3
  227. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  228. if self.state == 3:
  229. # Ok, time to exit
  230. # exit from this...
  231. self.game.to_player = self.to_player
  232. self.observer.load(self.save)
  233. self.save = None
  234. # self.game.warpdata = self.warpdata
  235. self.queue_game.put("\b \b\r\n")
  236. if not self.defer is None:
  237. self.defer.callback(self.game.gamedata.warps)
  238. self.defer = None
  239. def game_line(self, line):
  240. if line == "" or line == ": ":
  241. return
  242. if line == ": ENDINTERROG":
  243. return
  244. if line.startswith('Command [TL='):
  245. return
  246. # This should be the CIM Report Data -- parse it
  247. if self.warpcycle:
  248. if self.warpcycle.click():
  249. self.queue_game.put("\b")
  250. if self.warpcycle.again(1000):
  251. self.queue_game.put(".")
  252. self.queue_game.put(self.warpcycle.cycle())
  253. work = line.strip()
  254. parts = re.split(r"(?<=\d)\s", work)
  255. parts = [int(x) for x in parts]
  256. sector = parts.pop(0)
  257. # tuples are nicer on memory, and the warpdata map isn't going to be changing.
  258. # self.warpdata[sector] = tuple(parts)
  259. self.game.gamedata.warp_to(sector, *parts)
  260. def __del__(self):
  261. log.debug("CIMWarpReport {0} RIP".format(self))
  262. def whenDone(self):
  263. self.defer = defer.Deferred()
  264. # Call this to chain something after we exit.
  265. return self.defer
  266. def player(self, chunk):
  267. """ Data from player (in bytes). """
  268. chunk = chunk.decode("latin-1", "ignore")
  269. key = chunk.upper()
  270. log.warn("CIMWarpReport.player({0}) : I AM stopping...".format(key))
  271. # Stop the keepalive if we are activating something else
  272. # or leaving...
  273. # self.keepalive.stop()
  274. self.queue_game.put("\b \b\r\n")
  275. if not self.defer is None:
  276. # We have something, so:
  277. self.game.to_player = self.to_player
  278. self.observer.load(self.save)
  279. self.save = None
  280. self.defer.errback(Exception("User Abort"))
  281. self.defer = None
  282. else:
  283. # Still "exit" out.
  284. self.game.to_player = self.to_player
  285. self.observer.load(self.save)
  286. # the CIMPortReport will still be needed.
  287. # We can't get fresh report data (that changes) any other way.
  288. class CIMPortReport(object):
  289. """ Parse data from CIM Port Report
  290. Example:
  291. from flexible import CIMPortReport
  292. report = CIMPortReport(self.game)
  293. d = report.whenDone()
  294. d.addCallback(self.port_report)
  295. d.addErrback(self.welcome_back)
  296. def port_report(self, portdata):
  297. self.portdata = portdata
  298. self.queue_game.put("Loaded {0} records.".format(len(portdata)) + self.nl)
  299. self.welcome_back()
  300. def welcome_back(self,*_):
  301. ... restore keep alive timers, etc.
  302. """
  303. def __init__(self, game):
  304. self.game = game
  305. self.queue_game = game.queue_game
  306. self.queue_player = game.queue_player
  307. self.observer = game.observer
  308. # Yes, at this point we would activate
  309. self.prompt = game.buffer
  310. self.save = self.observer.save()
  311. # I actually don't want the player input, but I'll grab it anyway.
  312. self.observer.connect("player", self.player)
  313. self.observer.connect("prompt", self.game_prompt)
  314. self.observer.connect("game-line", self.game_line)
  315. # If we want it, it's here.
  316. self.defer = None
  317. self.to_player = self.game.to_player
  318. log.debug("to_player (stored) {0}".format(self.to_player))
  319. # Hide what's happening from the player
  320. self.game.to_player = False
  321. self.queue_player.put("^") # Activate CIM
  322. self.state = 1
  323. # self.portdata = {}
  324. self.queue_game.put("Loading ... ") # cycle eats last char.
  325. self.portcycle = SpinningCursor()
  326. def game_prompt(self, prompt):
  327. if prompt == ": ":
  328. if self.state == 1:
  329. # Ok, then we're ready to request the port report
  330. self.portcycle.reset()
  331. self.queue_player.put("R")
  332. self.state = 2
  333. elif self.state == 2:
  334. self.queue_player.put("Q")
  335. self.state = 3
  336. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  337. if self.state == 3:
  338. # Ok, time to exit
  339. # exit from this...
  340. self.game.to_player = self.to_player
  341. self.observer.load(self.save)
  342. self.save = None
  343. # self.game.portdata = self.portdata
  344. self.queue_game.put("\b \b\r\n")
  345. if not self.defer is None:
  346. self.defer.callback(self.game.gamedata.ports)
  347. self.defer = None
  348. def game_line(self, line: str):
  349. if line == "" or line == ": ":
  350. return
  351. if line == ": ENDINTERROG":
  352. return
  353. # This should be the CIM Report Data -- parse it
  354. if self.portcycle:
  355. if self.portcycle.click():
  356. self.queue_game.put("\b")
  357. if self.portcycle.again(1000):
  358. self.queue_game.put(".")
  359. self.queue_game.put(self.portcycle.cycle())
  360. work = line.replace("%", "")
  361. parts = re.split(r"(?<=\d)\s", work)
  362. if len(parts) == 8:
  363. port = int(parts[0].strip())
  364. data = dict()
  365. def portBS(info):
  366. if info[0] == "-":
  367. bs = "B"
  368. else:
  369. bs = "S"
  370. return (bs, int(info[1:].strip()))
  371. data["fuel"] = dict()
  372. data["fuel"]["sale"], data["fuel"]["units"] = portBS(parts[1])
  373. data["fuel"]["pct"] = int(parts[2].strip())
  374. data["org"] = dict()
  375. data["org"]["sale"], data["org"]["units"] = portBS(parts[3])
  376. data["org"]["pct"] = int(parts[4].strip())
  377. data["equ"] = dict()
  378. data["equ"]["sale"], data["equ"]["units"] = portBS(parts[5])
  379. data["equ"]["pct"] = int(parts[6].strip())
  380. # Store what this port is buying/selling
  381. data["port"] = (
  382. data["fuel"]["sale"] + data["org"]["sale"] + data["equ"]["sale"]
  383. )
  384. # Convert BBS/SBB to Class number 1-8
  385. data["class"] = CLASSES_PORT[data["port"]]
  386. self.game.gamedata.set_port(port, data)
  387. # self.portdata[port] = data
  388. else:
  389. log.error("CIMPortReport: {0} ???".format(line))
  390. def __del__(self):
  391. log.debug("CIMPortReport {0} RIP".format(self))
  392. def whenDone(self):
  393. self.defer = defer.Deferred()
  394. # Call this to chain something after we exit.
  395. return self.defer
  396. def player(self, chunk):
  397. """ Data from player (in bytes). """
  398. chunk = chunk.decode("latin-1", "ignore")
  399. key = chunk.upper()
  400. log.warn("CIMPortReport.player({0}) : I AM stopping...".format(key))
  401. # Stop the keepalive if we are activating something else
  402. # or leaving...
  403. # self.keepalive.stop()
  404. self.queue_game.put("\b \b\r\n")
  405. if not self.defer is None:
  406. # We have something, so:
  407. self.game.to_player = self.to_player
  408. self.observer.load(self.save)
  409. self.save = None
  410. self.defer.errback(Exception("User Abort"))
  411. self.defer = None
  412. else:
  413. # Still "exit" out.
  414. self.game.to_player = self.to_player
  415. self.observer.load(self.save)
  416. class ScriptPort(object):
  417. """ Performs the Port script.
  418. This is close to the original.
  419. We don't ask for the port to trade with --
  420. because that information is available to us after "D" (display).
  421. We look at the adjacent sectors, and see if we know any ports.
  422. If the ports are burnt (< 20%), we remove them from the list.
  423. We sort the best trades first.
  424. If there's just one, we use it. Otherwise we ask them to choose.
  425. We have options Trade_UseFirst, which uses the first one, if
  426. there is more then one.
  427. Option Trade_Turns, will use this as default turns, without
  428. asking.
  429. """
  430. def __init__(self, game):
  431. self.game = game
  432. self.queue_game = game.queue_game
  433. self.queue_player = game.queue_player
  434. self.observer = game.observer
  435. self.r = Style.RESET_ALL
  436. self.nl = "\n\r"
  437. self.this_sector = None # Starting sector
  438. self.sector1 = None # Current Sector
  439. self.sector2 = None # Next Sector Stop
  440. self.percent = self.game.gamedata.get_config('Trade_Percent', '5')
  441. self.stop = self.game.gamedata.get_config('Trade_Stop', '10')
  442. try:
  443. self.stop = int(self.stop)
  444. except ValueError:
  445. self.stop = 10
  446. # Validate what we got from the config.
  447. # Not an int: make it 5, and update.
  448. # > 50, make it 5 and update.
  449. # < 0, make it 0 and update.
  450. update_config = False
  451. try:
  452. self.percent = int(self.percent)
  453. except ValueError:
  454. self.percent = 5
  455. update_config = True
  456. if self.percent > 50:
  457. self.percent = 5
  458. update_config = True
  459. if self.percent < 0:
  460. self.percent = 0
  461. update_config = True
  462. if update_config:
  463. self.game.gamedata.set_config('Trade_Percent', self.percent)
  464. self.credits = 0
  465. self.last_credits = None
  466. self.times_left = 0
  467. # Activate
  468. self.prompt = game.buffer
  469. self.save = self.observer.save()
  470. self.observer.connect('player', self.player)
  471. self.observer.connect("prompt", self.game_prompt)
  472. self.observer.connect("game-line", self.game_line)
  473. self.defer = None
  474. self.send2player(self.r + "Script based on: Port Pair Trading v2.00" + self.nl)
  475. self.possible_sectors = None
  476. self.state = 1
  477. self.queue_player.put("D")
  478. # Original, send 'D' to display current sector.
  479. # We could get the sector number from the self.prompt string -- HOWEVER:
  480. # IF! We send 'D', we can also get the sectors around -- we might not even need to
  481. # prompt for sector to trade with (we could possibly figure it out ourselves).
  482. # [Command [TL=00:00:00]:[967] (?=Help)? : D]
  483. # [<Re-Display>]
  484. # []
  485. # [Sector : 967 in uncharted space.]
  486. # [Planets : (M) Into the Darkness]
  487. # [Warps to Sector(s) : 397 - (562) - (639)]
  488. # []
  489. def whenDone(self):
  490. self.defer = defer.Deferred()
  491. # Call this to chain something after we exit.
  492. return self.defer
  493. def deactivate(self, andExit=True):
  494. self.state = 0
  495. log.debug("ScriptPort.deactivate ({0})".format(self.times_left))
  496. self.queue_game.put(self.nl + Boxes.alert("Trading Script deactivating..."))
  497. assert(not self.save is None)
  498. self.observer.load(self.save)
  499. self.save = None
  500. if self.defer:
  501. if andExit:
  502. self.defer.callback({'exit':True})
  503. else:
  504. self.defer.callback('done')
  505. self.defer = None
  506. def player(self, chunk: bytes):
  507. # If we receive anything -- ABORT!
  508. self.deactivate()
  509. def send2game(self, txt):
  510. log.debug("ScriptPort.send2game({0})".format(txt))
  511. self.queue_player.put(txt)
  512. def send2player(self, txt):
  513. log.debug("ScriptPort.send2player({0})".format(txt))
  514. self.queue_game.put(txt)
  515. def game_prompt(self, prompt: str):
  516. log.debug("{0} : {1}".format(self.state, prompt))
  517. if self.state == 3:
  518. # log.("game_prompt: ", prompt)
  519. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  520. self.state = 4
  521. log.debug("Ok, state 4")
  522. use_first = self.game.gamedata.get_config('Trade_UseFirst', 'N').upper()[0] == 'Y'
  523. if self.sector2 is None and use_first:
  524. # Use the first one by default
  525. self.sector2 = self.possible[0]
  526. log.info("default to {0}".format(self.sector2))
  527. if self.sector2 is None:
  528. # Ok, we need to prompt for this.
  529. self.queue_game.put(self.r + self.nl +
  530. "Which sector to trade with? {0}".format(GameData.port_show_part(self.this_sector, self.game.gamedata.ports[self.this_sector])) +
  531. self.nl)
  532. for i, p in enumerate(self.possible):
  533. self.queue_game.put(" " + Fore.CYAN + str(i + 1) + " : " + GameData.port_show_part(p, self.game.gamedata.ports[p]) + self.nl)
  534. pi = PlayerInput(self.game)
  535. def got_need1(*_):
  536. log.debug("Ok, I have: {0}".format(pi.keep))
  537. if pi.keep['count'].strip() == '':
  538. self.deactivate()
  539. return
  540. self.times_left = int(pi.keep['count'])
  541. if pi.keep['choice'].strip() == '':
  542. self.deactivate()
  543. return
  544. c = int(pi.keep['choice']) -1
  545. if c < 0 or c >= len(self.possible):
  546. self.deactivate()
  547. return
  548. self.sector2 = self.possible[int(pi.keep['choice']) -1]
  549. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  550. self.state = 5
  551. self.trade()
  552. d = pi.prompt("Choose -=>", 5, name='choice', digits=True)
  553. d.addCallback(lambda ignore: pi.prompt("Times to execute script:", 5, name='count', digits=True))
  554. d.addCallback(got_need1)
  555. else:
  556. # We already have our target port, so...
  557. self.queue_game.put(self.r + self.nl + "Trading from {0} ({1}) to default {2} ({3}).".format(
  558. self.this_sector,
  559. self.game.gamedata.ports[self.this_sector]['port'],
  560. self.sector2, self.game.gamedata.ports[self.sector2]['port']) + self.nl
  561. )
  562. self.queue_game.put(self.r + self.nl + "Trading {0}".format(
  563. self.game.gamedata.port_trade_show(self.this_sector,
  564. self.sector2, 0)
  565. ))
  566. pi = PlayerInput(self.game)
  567. def got_need2(*_):
  568. if pi.keep['count'].strip() == '':
  569. self.deactivate()
  570. return
  571. self.times_left = int(pi.keep['count'])
  572. log.debug("Ok, I have: {0}".format(pi.keep))
  573. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  574. self.state = 5
  575. self.trade()
  576. self.queue_game.put(self.r + self.nl)
  577. default_turns = self.game.gamedata.get_config('Trade_Turns', '0')
  578. if default_turns == '0':
  579. # No default given, ask.
  580. d = pi.prompt("Times to execute script", 5, name='count')
  581. d.addCallback(got_need2)
  582. else:
  583. try:
  584. self.times_left = int(default_turns)
  585. except ValueError:
  586. self.times_left = 30
  587. self.state = 5
  588. self.trade()
  589. elif self.state == 6:
  590. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  591. if self.end_trans:
  592. self.deactivate()
  593. return
  594. if self.fixable:
  595. # self.queue_game.put("Ok! Let's fix this by going to the other sector..." + self.nl)
  596. self.queue_game.put(self.nl + Boxes.alert("Ok, FINE. We'll trade with the other port.", base="green", style=0))
  597. log.debug("Fixing...")
  598. # Swap this and other sector
  599. self.this_sector, self.other_sector = (self.other_sector, self.this_sector)
  600. self.queue_player.put("{0}\r".format(self.sector2))
  601. self.state = 5
  602. self.trade()
  603. elif self.state == 7:
  604. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  605. # Done
  606. if self.end_trans:
  607. self.deactivate()
  608. return
  609. # Swap this and other sector
  610. self.this_sector, self.other_sector = (self.other_sector, self.this_sector)
  611. if self.this_sector == self.sector2:
  612. self.times_left -= 1
  613. if self.times_left <= 0:
  614. # Ok, exit out
  615. self.deactivate()
  616. return
  617. if self.last_credits is None:
  618. self.last_credits = self.credits
  619. else:
  620. if self.credits <= self.last_credits:
  621. log.warn("We don't seem to be making any money here...")
  622. self.queue_game.put(self.r + self.nl + "We don't seem to be making any money here. I'm stopping!" + self.nl)
  623. self.deactivate()
  624. return
  625. self.queue_player.put("{0}\r".format(self.this_sector))
  626. self.state = 10
  627. elif re.match(r'Your offer \[\d+\] \?', prompt):
  628. log.info("Your offer? [{0}]".format(self.fix_offer))
  629. if self.fix_offer:
  630. # Make real offer / WHAT?@?!
  631. work = prompt.replace(',', '')
  632. parts = re.split(r"\s+", work)
  633. amount = parts[2]
  634. # Ok, we have the amount, now to figure pct...
  635. if self.sell_percent > 100:
  636. self.sell_percent -= 1
  637. else:
  638. self.sell_percent += 1
  639. price = amount * self.sell_percent // 100
  640. log.debug("start: {0} % {1} price {2}".format(amount, self.sell_percent, price))
  641. if self.sell_percent > 100:
  642. self.sell_percent -= 1
  643. else:
  644. self.sell_percent += 1
  645. self.queue_player.put("{0}\r".format(price))
  646. # elif re.match(r"How many holds of .+ do you want to sell \[\d+\]\?", prompt):
  647. # log.info("Sell everything we can...")
  648. # this seems to screw up the sync of everything.
  649. # self.queue_player.put("\r")
  650. elif self.state == 8:
  651. # What are we trading
  652. # How many holds of Equipment do you want to buy [75]?
  653. if re.match(r"How many holds of .+ do you want to buy \[\d+\]\?", prompt):
  654. parts = prompt.split()
  655. trade_type = parts[4]
  656. log.info("Buy {0} [{1} ~ {2}]".format(trade_type, self.tpc, self.opc))
  657. if trade_type == 'Fuel':
  658. if (self.tpc in (5,7)) and (self.opc in (2,3,4,8)):
  659. # Can buy equipment - fuel ore is worthless.
  660. self.queue_player.put("0\r")
  661. return
  662. if (self.tpc in(4,7)) and (self.opc in (1,3,5,8)):
  663. # Can buy organics - fuel ore is worthless.
  664. self.queue_player.put("0\r")
  665. return
  666. if (self.tpc in (4,7,3,5)) and (self.opc in (3,4,5,7)):
  667. # No point in buying fuel ore if it can't be sold.
  668. self.queue_player.put("0\r")
  669. return
  670. elif trade_type == 'Organics':
  671. if (self.tpc in (6,7)) and (self.opc in (2,3,4,8)):
  672. # Can buy equipment - organics is worthless.
  673. self.queue_player.put("0\r")
  674. return
  675. if (self.tpc in (2,4,6,7)) and (self.opc in (2,4,6,7)):
  676. # No point in buying organics if it can't be sold.
  677. self.queue_player.put("0\r")
  678. return
  679. elif trade_type == 'Equipment':
  680. if (self.opc in (1,5,6,7)):
  681. # No point in buying equipment if it can't be sold.
  682. self.queue_player.put("0\r")
  683. return
  684. self.queue_player.put("\r")
  685. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  686. # Done
  687. if self.end_trans:
  688. self.deactivate()
  689. return
  690. # Swap this and other sector
  691. self.this_sector, self.other_sector = (self.other_sector, self.this_sector)
  692. if self.this_sector == self.sector2:
  693. self.times_left -= 1
  694. if self.times_left <= 0:
  695. # Ok, exit out
  696. self.deactivate()
  697. return
  698. if self.last_credits is None:
  699. self.last_credits = self.credits
  700. else:
  701. if self.credits <= self.last_credits:
  702. log.warn("We don't seem to be making any money here...")
  703. self.queue_game.put(self.r + self.nl + "We don't seem to be making any money here. I'm stopping!" + self.nl)
  704. self.deactivate()
  705. return
  706. self.queue_player.put("{0}\r".format(self.this_sector))
  707. self.state = 10
  708. elif self.state == 99:
  709. # This is a good place to deactivate at.
  710. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  711. if hasattr(self, 'message'):
  712. if self.message is not None:
  713. self.queue_game.put(self.message)
  714. self.message = None
  715. self.deactivate()
  716. def trade(self, *_):
  717. # state 5
  718. log.debug("trade!")
  719. self.queue_player.put("pt") # Port Trade
  720. self.end_trans = False
  721. self.fixable = False
  722. self.this_port = self.game.gamedata.ports[self.this_sector]
  723. # I think other_sector will alway be correct, but leaving this
  724. # for now. FUTURE: TODO: REMOVE
  725. if self.this_sector == self.sector1:
  726. self.other_sector = self.sector2
  727. else:
  728. self.other_sector = self.sector1
  729. self.other_port = self.game.gamedata.ports[self.other_sector]
  730. # Ok, perform some calculations
  731. self.tpc = self.this_port['class']
  732. self.opc = self.other_port['class']
  733. self.fixable = 0
  734. self.fix_offer = 0
  735. # [ Items Status Trading % of max OnBoard]
  736. # [ ----- ------ ------- -------- -------]
  737. # [Fuel Ore Selling 2573 93% 0]
  738. # [Organics Buying 2960 100% 0]
  739. # [Equipment Buying 1958 86% 0]
  740. # []
  741. # []
  742. # [You have 1,000 credits and 20 empty cargo holds.]
  743. # []
  744. # [We are selling up to 2573. You have 0 in your holds.]
  745. # [How many holds of Fuel Ore do you want to buy [20]? 0]
  746. def game_line(self, line: str):
  747. if line.startswith("You have ") and 'credits and' in line:
  748. parts = line.replace(',', '').split()
  749. credits = int(parts[2])
  750. log.debug("Credits: {0}".format(credits))
  751. self.credits = credits
  752. if self.state == 1:
  753. # First exploration
  754. if line.startswith("Sector :"):
  755. # We have starting sector information
  756. parts = re.split(r"\s+", line)
  757. self.this_sector = int(parts[2])
  758. # These will be the ones swapped around as we trade back and forth.
  759. self.sector1 = self.this_sector
  760. elif line.startswith("Warps to Sector(s) : "):
  761. # Warps to Sector(s) : 397 - (562) - (639)
  762. _, _, warps = line.partition(':')
  763. warps = warps.replace('-', '').replace('(', '').replace(')', '').strip()
  764. log.debug("Warps: [{0}]".format(warps))
  765. self.warps = [ int(x) for x in re.split(r"\s+", warps)]
  766. log.debug("Warps: [{0}]".format(self.warps))
  767. self.state = 2
  768. elif self.state == 2:
  769. if line == "":
  770. # Ok, we're done
  771. self.state = 3
  772. # Check to see if we have information on any possible ports
  773. # if hasattr(self.game, 'portdata'):
  774. if True:
  775. if not self.this_sector in self.game.gamedata.ports:
  776. self.state = 0
  777. log.debug("Current sector {0} not in portdata.".format(self.this_sector))
  778. self.queue_game.put(self.r + self.nl + "I can't find the current sector in the portdata." + self.nl)
  779. self.deactivate()
  780. return
  781. else:
  782. # Ok, we are in the portdata
  783. pd = self.game.gamedata.ports[self.this_sector]
  784. if GameData.port_burnt(pd):
  785. log.debug("Current sector {0} port is burnt (<= 20%).".format(self.this_sector))
  786. self.queue_game.put(self.r + self.nl + "Current sector port is burnt out. <= 20%." + self.nl)
  787. self.deactivate()
  788. return
  789. possible = [ x for x in self.warps if x in self.game.gamedata.ports ]
  790. log.debug("Possible: {0}".format(possible))
  791. # BUG: Sometimes links to another sector, don't link back!
  792. # This causes the game to plot a course / autopilot.
  793. # if hasattr(self.game, 'warpdata'):
  794. if True:
  795. # Great! verify that those warps link back to us!
  796. possible = [ x for x in possible if x in self.game.gamedata.warps and self.this_sector in self.game.gamedata.warps[x]]
  797. if len(possible) == 0:
  798. self.state = 0
  799. self.queue_game.put(self.r + self.nl + "I don't see any ports in [{0}].".format(self.warps) + self.nl)
  800. self.deactivate()
  801. return
  802. possible = [ x for x in possible if not GameData.port_burnt(self.game.gamedata.ports[x]) ]
  803. log.debug("Possible: {0}".format(possible))
  804. if len(possible) == 0:
  805. self.state = 0
  806. self.queue_game.put(self.r + self.nl + "I don't see any unburnt ports in [{0}].".format(self.warps) + self.nl)
  807. self.deactivate()
  808. return
  809. possible = [ x for x in possible if GameData.port_trading(self.game.gamedata.ports[self.this_sector]['port'], self.game.gamedata.ports[x]['port'])]
  810. # sort by best, then by %
  811. start_port = self.game.gamedata.ports[self.this_sector]
  812. if 'port' in start_port:
  813. start_port_port = start_port['port']
  814. start_with = start_port_port[1:]
  815. if start_with in ('BS', 'SB'):
  816. # Ok, good trades may be possible
  817. best = GameData.flip(start_with)
  818. log.info("Sorting the best ({0}) to the top.".format(best))
  819. log.info("{0}".format(possible))
  820. dec = [ [self.game.gamedata.ports[p].get('port', '---')[1:] == best, p] for p in possible]
  821. dec = sorted(dec, reverse=True)
  822. possible = [x[1] for x in dec]
  823. log.info("{0}".format(possible))
  824. self.possible = possible
  825. if len(possible) == 0:
  826. self.state = 0
  827. self.queue_game.put(self.r + self.nl + "I don't see any possible port trades in [{0}].".format(self.warps) + self.nl)
  828. self.deactivate()
  829. return
  830. elif len(possible) == 1:
  831. # Ok! there's only one!
  832. self.sector2 = possible[0]
  833. # Display possible ports:
  834. # spos = [ str(x) for x in possible]
  835. # self.queue_game.put(self.r + self.nl + self.nl.join(spos) + self.nl)
  836. # At state 3, we only get a prompt.
  837. return
  838. else:
  839. self.state = 0
  840. log.warn("We don't have any portdata!")
  841. self.queue_game.put(self.r + self.nl + "I have no portdata. Please run CIM Port Report." + self.nl)
  842. self.deactivate()
  843. return
  844. elif self.state == 5:
  845. if "-----" in line:
  846. self.state = 6
  847. elif self.state == 6:
  848. if "We are buying up to" in line:
  849. log.info("buying up to -- so sell all")
  850. # Sell
  851. self.state = 7
  852. self.queue_player.put("\r")
  853. self.sell_percent = 100 + self.percent
  854. if "We are selling up to" in line:
  855. log.info("selling up to -- state 8 / set percent")
  856. # Buy
  857. self.state = 8
  858. self.sell_percent = 100 - self.percent
  859. if line.startswith('Fuel Ore') or line.startswith('Organics') or line.startswith('Equipment'):
  860. work = line.replace('Fuel Ore', 'Fuel').replace('%', '')
  861. parts = re.split(r"\s+", work)
  862. # log.debug(parts)
  863. # Equipment, Selling xxx x% xxx
  864. if parts[-1] != '0' and parts[2] != '0' and parts[1] != 'Buying':
  865. log.warn("We have a problem -- they aren't buying what we have in stock!")
  866. stuff = line[0] # F O or E.
  867. if self.game.gamedata.port_buying(self.other_sector, stuff):
  868. log.info("fixable")
  869. self.fixable = True
  870. if int(parts[3]) < self.stop:
  871. log.info("Port is burnt! % < {0} (end_trans)".format(self.stop))
  872. self.end_trans = True
  873. if "You don't have anything they want" in line:
  874. log.warn("Don't have anything they want.")
  875. # Neither! DRAT!
  876. if not self.fixable:
  877. self.state = 99
  878. return
  879. if "We're not interested." in line:
  880. log.warn("Try, try again. :(")
  881. self.state = 5
  882. self.trade()
  883. elif self.state == 7:
  884. # Haggle Sell
  885. if "We'll buy them for" in line or "Our final offer" in line:
  886. if "Our final offer" in line:
  887. self.sell_percent -= 1
  888. parts = line.replace(',', '').split()
  889. start_price = int(parts[4])
  890. price = start_price * self.sell_percent // 100
  891. log.debug("start: {0} % {1} price {2}".format(start_price, self.sell_percent, price))
  892. self.sell_percent -= 1
  893. self.queue_player.put("{0}\r".format(price))
  894. if "We are selling up to" in line:
  895. log.info("selling up to / state 8 / set percent")
  896. # Buy
  897. self.state = 8
  898. self.sell_percent = 100 - self.percent
  899. if "We are buying up to" in line:
  900. log.info("buying up to -- so sell all")
  901. # Sell
  902. self.state = 7
  903. self.queue_player.put("\r")
  904. self.sell_percent = 100 + self.percent
  905. if "We're not interested." in line:
  906. log.info("Not interested. Try, try again. :(")
  907. self.state = 5
  908. self.trade()
  909. if "WHAT?!@!? you must be crazy!" in line:
  910. log.warn("fix offer")
  911. self.fix_offer = 1
  912. if "So, you think I'm as stupid as you look?" in line:
  913. log.warn("fix offer")
  914. self.fix_offer = 1
  915. if "Quit playing around, you're wasting my time!" in line:
  916. log.warn("fix offer")
  917. self.fix_offer = 1
  918. elif self.state == 8:
  919. # Haggle Buy
  920. if "We'll sell them for" in line or "Our final offer" in line:
  921. if "Our final offer" in line:
  922. self.sell_percent += 1
  923. parts = line.replace(',', '').split()
  924. start_price = int(parts[4])
  925. price = start_price * self.sell_percent // 100
  926. log.debug("start: {0} % {1} price {2}".format(start_price, self.sell_percent, price))
  927. self.sell_percent += 1
  928. self.queue_player.put("{0}\r".format(price))
  929. if "We're not interested." in line:
  930. log.info("Not interested. Try, try again. :(")
  931. self.state = 5
  932. self.trade()
  933. elif self.state == 10:
  934. if "Sector : " in line:
  935. # Trade
  936. self.state = 5
  937. reactor.callLater(0, self.trade, 0)
  938. # self.trade()
  939. # elif self.state == 3:
  940. # log.debug("At state 3 [{0}]".format(line))
  941. # self.queue_game.put("At state 3.")
  942. # self.deactivate()
  943. # return
  944. class ScriptExplore(object):
  945. """ Exploration Script
  946. WARNINGS:
  947. We assume the player has lots o turns, or unlimited turns!
  948. """
  949. def __init__(self, game):
  950. self.game = game
  951. self.queue_game = game.queue_game
  952. self.queue_player = game.queue_player
  953. self.observer = game.observer
  954. self.r = Style.RESET_ALL
  955. self.c = merge(Style.BRIGHT + Fore.YELLOW)
  956. self.nl = "\n\r"
  957. # Our Stuff, Not our pants!
  958. self.dense = [] # We did a density, store that info.
  959. self.clear = [] # Warps that we know are clear.
  960. self.highsector = 0 # Selected Sector to move to next!
  961. self.highwarp = 0 # Selected Sector's Warp Count!
  962. self.stacksector = [] # Set of sectors that we have not picked but are unexplored... even though we did a holo!
  963. self.oneMoveSector = False
  964. self.times = 0
  965. self.maxtimes = 0
  966. # Activate
  967. self.prompt = game.buffer
  968. self.save = self.observer.save()
  969. self.observer.connect('player', self.player)
  970. self.observer.connect("prompt", self.game_prompt)
  971. self.observer.connect("game-line", self.game_line)
  972. self.prefer_ports = self.game.gamedata.get_config('Explorer_PrefPorts', 'N').upper()[0] == 'Y'
  973. self.defer = None
  974. self.send2player(Boxes.alert("Explorer", base="green"))
  975. # How many times we going to go today?
  976. ask = PlayerInput(self.game)
  977. def settimes(*_):
  978. times = ask.keep['times'].strip()
  979. log.debug("settimes got '{0}'".format(times))
  980. if times == '':
  981. self.deactivate()
  982. else:
  983. times = int(times)
  984. self.times = times
  985. self.maxtimes = times
  986. self.send2game("D")
  987. log.debug("times: {0} maxtimes: {0}".format(self.times))
  988. self.state = 1
  989. d = ask.prompt("How many sectors would you like to explorer?", 5, name="times", digits=True)
  990. #d.addCallback(ask.output)
  991. #d.addCallback(lambda ignore: self.settimes(ask.keep))
  992. d.addCallback(settimes)
  993. def whenDone(self):
  994. self.defer = defer.Deferred()
  995. # Call this to chain something after we exit.
  996. return self.defer
  997. def deactivate(self, andExit=False):
  998. self.state = 0
  999. log.debug("ScriptExplore.deactivate()")
  1000. assert(not self.save is None)
  1001. self.observer.load(self.save)
  1002. self.save = None
  1003. if self.defer:
  1004. if andExit:
  1005. self.defer.callback({'exit':True})
  1006. else:
  1007. self.defer.callback('done')
  1008. self.defer = None
  1009. def player(self, chunk: bytes):
  1010. # If we receive anything -- ABORT!
  1011. self.deactivate(True)
  1012. def send2game(self, txt):
  1013. log.debug("ScriptExplore.send2game({0})".format(txt))
  1014. self.queue_player.put(txt)
  1015. def send2player(self, txt):
  1016. log.debug("ScriptExplore.send2player({0})".format(txt))
  1017. self.queue_game.put(txt)
  1018. def resetStuff(self):
  1019. self.dense = []
  1020. self.clear = []
  1021. self.highwarp = 0
  1022. self.highsector = 0
  1023. log.debug("ScriptExplore.resetStuff()")
  1024. def dead_end(self):
  1025. """ We've reached a dead end.
  1026. Either pop a new location to travel to, or give it up.
  1027. """
  1028. self.send2player(self.nl + Boxes.alert("** DEAD END **", base="blue"))
  1029. if self.stacksector:
  1030. # Ok, there's somewhere to go ...
  1031. self.highsector = self.stacksector.pop()
  1032. # travel state
  1033. self.state = 10
  1034. self.send2game("{0}\r".format(self.highsector))
  1035. else:
  1036. self.send2player(self.nl + Boxes.alert("I've run out of places to look ({0}/{1}).".format(self.maxtimes - self.times, self.maxtimes)))
  1037. self.deactivate(True)
  1038. def game_over(self, msg):
  1039. self.send2player(self.nl + Boxes.alert("STOP: {0} ({1}/{2}).".format(msg, self.maxtimes - self.times, self.maxtimes)))
  1040. self.deactivate()
  1041. def game_prompt(self, prompt: str):
  1042. log.debug("{0} : {1}".format(self.state, prompt))
  1043. if self.state == 2:
  1044. if "Select (H)olo Scan or (D)ensity Scan or (Q)uit" in prompt:
  1045. self.send2game("D")
  1046. # self.state += 1
  1047. elif self.state == 5:
  1048. log.debug("dense is {0} sectors big".format(len(self.dense)))
  1049. self.state += 1
  1050. self.send2game("SH")
  1051. elif self.state == 10:
  1052. if prompt.startswith('Do you want to engage the TransWarp drive? '):
  1053. self.send2game("N")
  1054. elif self.state == 12:
  1055. # Looking for "Engage the Autopilot?"
  1056. if prompt.startswith('Engage the Autopilot? (Y/N/Single step/Express) [Y]'):
  1057. self.send2game("S")
  1058. self.travel_path.pop(0)
  1059. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]'):
  1060. self.send2game("SD")
  1061. self.state += 1
  1062. # Arriving sector :1691 Autopilot disengaging.
  1063. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1064. log.info("We made it to where we wanted to go!")
  1065. # can't init state 1, because we're at a prompt, so...
  1066. self.send2game("S")
  1067. self.state = 2
  1068. return
  1069. elif self.state == 15:
  1070. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]'):
  1071. self.send2game("N")
  1072. self.travel_path.pop(0)
  1073. self.state = 12
  1074. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1075. log.info("We made it to where we wanted to go!")
  1076. # can't init state 1, because we're at a prompt, so...
  1077. self.send2game("S")
  1078. self.state = 2
  1079. return
  1080. elif self.state == 20:
  1081. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]'):
  1082. # Stop in this sector / Yes!
  1083. self.send2game("Y")
  1084. self.state = 1
  1085. # this should re-trigger a scan
  1086. def game_line(self, line: str):
  1087. log.debug("{0} | {1}".format(self.state, line))
  1088. #if "Mine Control" in line: # If we don't have a Holo-Scanner and we attempted to do a Holo-scan, abort
  1089. # self.deactivate()
  1090. if self.state == 1:
  1091. self.send2game("S")
  1092. self.state += 1
  1093. elif self.state == 2:
  1094. if "Relative Density Scan" in line:
  1095. self.state = 3
  1096. elif "You don't have a long range scanner." in line:
  1097. log.warn("FATAL: No Long Range Scanner Installed!")
  1098. self.send2player(Boxes.alert("You need a Long Range Scanner!"))
  1099. self.deactivate(True)
  1100. return
  1101. # elif "Long Range Scan" in line:
  1102. # self.state += 1
  1103. elif self.state == 3:
  1104. # Get the Density Data!
  1105. if line.startswith("Sector"):
  1106. new_sector = '(' in line
  1107. work = line.replace(':', '').replace(')', '').replace('(', '').replace('%', '').replace('==>', '')
  1108. work = re.split(r"\s+", work)
  1109. log.debug(work)
  1110. # 'Sector', '8192', '0', 'Warps', '4', 'NavHaz', '0', 'Anom', 'No'
  1111. # 'Sector', '(', '8192)', '0', 'Warps', '4', 'NavHaz', '0', 'Anom', 'No'
  1112. # New Sector?
  1113. if new_sector:
  1114. # Switch Anom into bool state
  1115. # if(work[8] == 'No'):
  1116. # temp = False
  1117. # else:
  1118. # temp = True
  1119. #self.dense.append( {'sector': int(work[1]), 'density': int(work[2]), 'warps': int(work[4]), 'navhaz': int(work[6]), 'anom': temp} )
  1120. self.dense.append( {'sector': int(work[1]), 'density': int(work[2]), 'warps': int(work[4]), 'navhaz': int(work[6]), 'anom': work[8] == 'Yes'} )
  1121. log.debug(self.dense)
  1122. # {'sector': 8192, 'density': 0, 'warps': 4, 'navhaz': 0, 'anom': False}
  1123. elif line == "":
  1124. self.state += 1
  1125. # yeah, this would be better in the above line...
  1126. # leaving it for now.
  1127. # Which is why I broke the elif chain. ...
  1128. if self.state == 4:
  1129. # Begin Processing our data we got from density scan and find highest warp count in what sectors
  1130. # Remove sectors with one warp
  1131. log.debug("state 4: {0}".format(self.dense))
  1132. # Do we have a new place to go? (That is also worth going to)
  1133. if not self.dense: # Dense contains no new sectors, abort
  1134. log.info("No New Sectors Found!")
  1135. self.dead_end()
  1136. return
  1137. # Is the sector safe to go into?
  1138. # 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 ]
  1139. self.clear = [ x for x in self.dense if not x['anom'] and not x['navhaz'] and x['density'] in (0,1,100,101) ]
  1140. if self.clear: # We have sector(s) we can move to!
  1141. log.debug("Clear Sectors: {0}".format(len(self.clear)))
  1142. # 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 )
  1143. # Sort to find greatest warp count
  1144. if self.prefer_ports:
  1145. _, self.highwarp, self.highsector = max( (x['density'], x['warps'], x['sector']) for x in self.clear)
  1146. else:
  1147. self.highwarp, self.highsector = max( (x['warps'], x['sector']) for x in self.clear)
  1148. log.info("Sector: {0:5d} Warps: {1}".format(self.highsector, self.highwarp))
  1149. self.state += 1
  1150. else:
  1151. log.warn("No (safe) sectors to move to!")
  1152. # Let's try this?!
  1153. # self.dead_end() # NO!
  1154. self.game_over("No SAFE moves.")
  1155. # Another NOP state. This also could be merged into above.
  1156. # break the elif chain.
  1157. if self.state == 5:
  1158. # Add the dense scan of unknown sectors onto the stack of sectors, only save the ones we think are clear... for now.
  1159. for c in self.clear:
  1160. sector = c['sector']
  1161. if sector != self.highsector:
  1162. if sector not in self.stacksector:
  1163. self.stacksector.append(sector)
  1164. # Or simply not add it in the first place ...
  1165. # 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!
  1166. # self.stacksector.discard(self.highsector)
  1167. # Ok, we need to decide to stop exploring -- before we
  1168. # issue the sector move! :P
  1169. #
  1170. # Warning! Yes we can and will eat all the turns! :P
  1171. if self.times == 0:
  1172. self.send2player(Boxes.alert("Completed {0}".format(self.maxtimes), base="green"))
  1173. log.info("Completed {0}".format(self.maxtimes))
  1174. self.deactivate()
  1175. return
  1176. self.times -= 1
  1177. # Ok we know the sector we want to go to now let's move it!
  1178. self.send2game("m{0}\r".format(self.highsector))
  1179. if self.highsector in self.stacksector:
  1180. log.info("Removing {0} from stacksector list.".format(self.highsector))
  1181. self.stacksector.remove(self.highsector)
  1182. # Reset Variables for fresh data
  1183. self.resetStuff()
  1184. self.state = 1
  1185. elif self.state == 10:
  1186. if line.startswith("You are already in that sector!"):
  1187. log.info("Already here. (Whoops!)")
  1188. self.state = 1
  1189. return
  1190. if line.startswith("Sector : {0}".format(self.highsector)):
  1191. log.info("We're here!")
  1192. # Ok, we're already there! no autopilot needed!
  1193. self.state = 1
  1194. return
  1195. # Warping
  1196. self.go_on = True
  1197. if line.startswith('The shortest path ('):
  1198. # Ok, we've got a path.
  1199. self.state += 1
  1200. self.travel_path = []
  1201. elif self.state == 11:
  1202. if line == '':
  1203. # The end of the (possibly) multiline warp.
  1204. self.state += 1
  1205. self.travel_path.pop(0) # First sector is one we're in.
  1206. self.stophere = False
  1207. self.go_on = True
  1208. else:
  1209. self.travel_path.extend(line.replace('(', '').replace(')', '').split(' > ') )
  1210. log.debug("Travel path: {0}".format(self.travel_path))
  1211. elif self.state == 12:
  1212. # Arriving sector :1691 Autopilot disengaging.
  1213. if 'Autopilot disengaging.' in line:
  1214. log.info("We made it to where we wanted to go!")
  1215. self.state = 1
  1216. return
  1217. elif self.state == 13:
  1218. if 'Relative Density Scan' in line:
  1219. self.state += 1
  1220. elif self.state == 14:
  1221. if line == "":
  1222. log.debug("PATH: {0}".format(self.travel_path))
  1223. # end of the scan, decision time
  1224. if self.stophere:
  1225. log.info("STOPHERE")
  1226. # Ok, let's stop here!
  1227. # Re-save the sector we were trying to get to. (we didn't make it there)
  1228. if self.highsector not in self.stacksector:
  1229. self.stacksector.append(self.highsector)
  1230. self.state = 20
  1231. else:
  1232. if self.go_on:
  1233. log.info("GO ON")
  1234. # Ok, carry on!
  1235. self.state = 15
  1236. else:
  1237. log.warn("Our way is blocked...")
  1238. if self.highsector not in self.stacksector:
  1239. self.stacksector.append(self.highsector)
  1240. self.state = 20
  1241. else:
  1242. if line.strip('-') != '':
  1243. work = line.replace(' :', '').replace('%', '').replace(')', '').replace('==>', '')
  1244. # Does this contain something new? unseen?
  1245. stophere = '(' in work
  1246. work = work.replace('(','')
  1247. #Sector XXXX DENS Warps N NavHaz P Anom YN
  1248. parts = re.split(r'\s+', work)
  1249. # Don't bother stopping if there's only one warp
  1250. # YES! Stop, even if there is just one warp!
  1251. # if stophere and parts[4] == '1':
  1252. # stophere = False
  1253. if stophere:
  1254. self.stophere = True
  1255. next_stop = self.travel_path[0]
  1256. log.debug("next_stop {0} from {1}".format(next_stop, self.travel_path))
  1257. log.debug("parts: {0}".format(parts))
  1258. if parts[1] == next_stop:
  1259. log.info("next_stop {0} found...".format(next_stop))
  1260. # Ok, this is our next stop. Is it safe to travel to?
  1261. if parts[2] not in ('100', '0', '1', '101'):
  1262. # Ok, it's not safe to go on.
  1263. self.go_on = False
  1264. # Check the rest navhav and anom ...
  1265. class ScriptSpace(object):
  1266. """ Space Exploration script.
  1267. Send "SD", verify paths are clear.
  1268. Find nearest unknown. Sector + CR.
  1269. Save "shortest path from to" information.
  1270. At 'Engage the Autopilot', Send "S" (Single Step)
  1271. At '[Stop in this sector', Send "SD", verify path is clear.
  1272. Send "SH" (glean sector/port information along the way.)
  1273. Send "N" (Next)!
  1274. Send "SD" / Verify clear.
  1275. Send "SH"
  1276. Repeat for Next closest.
  1277. """
  1278. def __init__(self, game):
  1279. self.game = game
  1280. self.queue_game = game.queue_game
  1281. self.queue_player = game.queue_player
  1282. self.observer = game.observer
  1283. self.r = Style.RESET_ALL
  1284. self.nl = "\n\r"
  1285. self.this_sector = None # Starting sector
  1286. self.target_sector = None # Sector going to
  1287. self.path = []
  1288. self.times_left = 0 # How many times to look for target
  1289. self.density = dict() # Results of density scan. (Just the numbers)
  1290. # Activate
  1291. self.prompt = game.buffer
  1292. self.save = self.observer.save()
  1293. self.observer.connect('player', self.player)
  1294. self.observer.connect("prompt", self.game_prompt)
  1295. self.observer.connect("game-line", self.game_line)
  1296. self.defer = None
  1297. self.queue_game.put(
  1298. self.nl + "Bugz (like space), is big." + self.r + self.nl
  1299. )
  1300. self.state = 1
  1301. self.queue_player.put("SD")
  1302. # Get current density scan + also get the current sector.
  1303. # [Command [TL=00:00:00]:[XXXX] (?=Help)? : D]
  1304. def whenDone(self):
  1305. self.defer = defer.Deferred()
  1306. # Call this to chain something after we exit.
  1307. return self.defer
  1308. def deactivate(self, andExit=False):
  1309. self.state = 0
  1310. log.debug("ScriptPort.deactivate ({0})".format(self.times_left))
  1311. assert(not self.save is None)
  1312. self.observer.load(self.save)
  1313. self.save = None
  1314. if self.defer:
  1315. if andExit:
  1316. self.defer.callback({'exit':True})
  1317. else:
  1318. self.defer.callback('done')
  1319. self.defer = None
  1320. def player(self, chunk: bytes):
  1321. # If we receive anything -- ABORT!
  1322. self.deactivate(True)
  1323. def unknown_search(self, starting_sector):
  1324. seen = set()
  1325. possible = set()
  1326. possible.add(int(starting_sector))
  1327. done = False
  1328. while not done:
  1329. next_possible = set()
  1330. for s in possible:
  1331. p = self.game.gamedata.get_warps(s)
  1332. if p is not None:
  1333. for pos in p:
  1334. if pos not in seen:
  1335. next_possible.add(pos)
  1336. else:
  1337. log.debug("unknown found: {0}".format(s))
  1338. self.unknown = s
  1339. done = True
  1340. break
  1341. seen.add(s)
  1342. if self.unknown is None:
  1343. log.debug("possible: {0}".format(next_possible))
  1344. possible = next_possible
  1345. yield
  1346. def find_unknown(self, starting_sector):
  1347. log.debug("find_unknown( {0})".format(starting_sector))
  1348. d = defer.Deferred()
  1349. # Process things
  1350. self.unknown = None
  1351. c = coiterate(self.unknown_search(starting_sector))
  1352. c.addCallback(lambda unknown: d.callback(self.unknown))
  1353. return d
  1354. def show_unknown(self, sector):
  1355. if sector is None:
  1356. self.deactivate()
  1357. return
  1358. log.debug("Travel to {0}...".format(sector))
  1359. self.queue_player.put("{0}\r".format(sector))
  1360. def game_prompt(self, prompt: str):
  1361. log.debug("{0} : {1}".format(self.state, prompt))
  1362. if self.state == 3:
  1363. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1364. # this_sector code isn't working -- so! Get sector from prompt
  1365. self.state = 4
  1366. _, _, sector = prompt.partition(']:[')
  1367. sector, _, _ = sector.partition(']')
  1368. self.this_sector = int(sector)
  1369. # Ok, we're done with Density Scan, and we're back at the command prompt
  1370. log.debug("Go find the nearest unknown...")
  1371. d = self.find_unknown(sector)
  1372. d.addCallback(self.show_unknown)
  1373. elif self.state == 6:
  1374. # Engage the autopilot?
  1375. if prompt.startswith('Engage the Autopilot? (Y/N/Single step/Express) [Y]'):
  1376. self.state = 7
  1377. sector = self.path.pop(0)
  1378. if sector in self.density:
  1379. if self.density[sector] in (0, 100):
  1380. # Ok, looks safe!
  1381. self.queue_player.put("S")
  1382. self.this_sector = sector
  1383. else:
  1384. log.warn("FATAL: Density for {0} is {1}".format(sector, self.density[sector]))
  1385. self.deactivate(True)
  1386. return
  1387. else:
  1388. log.error("{0} not in density scan? (how's that possible?)".format(sector))
  1389. self.deactivate(True)
  1390. return
  1391. elif self.state == 7:
  1392. # Ok, we're in a new sector (single stepping through space)
  1393. # update density scan
  1394. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1395. self.queue_player.put("SD")
  1396. self.state = 8
  1397. elif self.state == 10:
  1398. # Because we're here
  1399. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1400. self.queue_player.put("SH")
  1401. self.state = 11
  1402. elif self.state == 11:
  1403. if prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1404. # Ok, is the density scan clear?
  1405. sector = self.path.pop(0)
  1406. if sector in self.density:
  1407. if self.density[sector] in (0, 100):
  1408. # Ok, looks safe
  1409. self.queue_player.put("N")
  1410. self.state = 7
  1411. self.this_sector = sector
  1412. else:
  1413. log.warn("FATAL: Density for {0} is {1}".format(sector, self.density[sector]))
  1414. self.deactivate()
  1415. return
  1416. else:
  1417. log.error("{0} not in density scane? (how's that possible...)".format(sector))
  1418. self.deactivate()
  1419. return
  1420. def next_unknown(self, sector):
  1421. log.info("Unknown is : {0}".format(sector))
  1422. self.deactivate()
  1423. def game_line(self, line: str):
  1424. log.debug("line {0} : {1}".format(self.state, line))
  1425. if line.startswith('Sector : '):
  1426. work = line.strip()
  1427. parts = re.split(r"\s+", work)
  1428. self.this_sector = int(parts[2])
  1429. log.debug("game_line sector {0}".format(self.this_sector))
  1430. elif line.startswith("Command [TL=]"):
  1431. # Ok, get the current sector from this
  1432. _, _, sector = line.partition("]:[")
  1433. sector, _, _ = sector.partition("]")
  1434. self.this_sector = int(sector)
  1435. log.debug("current sector: {0}".format(self.this_sector))
  1436. elif line.startswith('Warps to Sector(s) :'):
  1437. # Warps to Sector(s) : 5468
  1438. _, _, work = line.partition(':')
  1439. work = work.strip().replace('(', '').replace(')', '').replace(' - ', ' ')
  1440. parts = [ int(x) for x in work.split(' ')]
  1441. self.path = list(parts)
  1442. if self.state in (1, 8):
  1443. if 'Relative Density Scan' in line:
  1444. # Start Density Scan
  1445. self.state += 1
  1446. self.density = {}
  1447. elif self.state in (2, 9):
  1448. if line == '':
  1449. # End of Density Scan
  1450. self.state += 1
  1451. log.debug("Density: {0}".format(self.density))
  1452. # self.deactivate()
  1453. elif line.startswith('Sector'):
  1454. # Parse Density Scan values
  1455. work = line.replace('(', '').replace(')', '').replace(':', '').replace('%', '').replace(',', '')
  1456. parts = re.split(r'\s+', work)
  1457. log.debug("Sector {0}".format(parts))
  1458. sector = int(parts[1])
  1459. self.density[sector] = int(parts[3])
  1460. if parts[7] != '0':
  1461. log.warn("NavHaz {0} : {1}".format(parts[7], work))
  1462. self.density[sector] += 99
  1463. if parts[9] != 'No':
  1464. log.warn("Anom {0} : {1}".format(parts[9], work))
  1465. self.density[sector] += 990
  1466. elif self.state == 4:
  1467. # Looking for shortest path message / warp info
  1468. # Or possibly, "We're here!"
  1469. if line.startswith('Sector :') and str(self.unknown) in line:
  1470. # Ok, I'd guess that we're already there!
  1471. # Try it again!
  1472. self.queue_player.put("SD")
  1473. self.state = 1
  1474. if line.startswith('The shortest path'):
  1475. self.state = 5
  1476. elif self.state == 5:
  1477. # This is the warps line
  1478. # Can this be multiple lines?
  1479. if line == "":
  1480. self.state = 6
  1481. else:
  1482. work = line.replace("(", "").replace(")", "").replace(">", "").strip()
  1483. self.path = [int(x) for x in work.split()]
  1484. log.debug("Path: {0}".format(self.path))
  1485. # Verify
  1486. current = self.path.pop(0)
  1487. if current != self.this_sector:
  1488. log.warn("Failed: {0} != {1}".format(current, self.this_sector))
  1489. self.deactivate()
  1490. return
  1491. elif self.state == 7:
  1492. if self.unknown == self.this_sector:
  1493. # We have arrived!
  1494. log.info("We're here!")
  1495. self.deactivate()
  1496. class ScriptTerror(object):
  1497. """ Terror script.
  1498. This uses the Port Trading script.
  1499. Basically, we look for the next best port trading pair.
  1500. Move to it, fire off the Port Trading script.
  1501. Repeat until our loop is done, or there's no more
  1502. pairs.
  1503. """
  1504. def __init__(self, game, proxy, count):
  1505. self.game = game
  1506. self.queue_game = game.queue_game
  1507. self.queue_player = game.queue_player
  1508. self.proxy = proxy
  1509. self.count = count
  1510. self.observer = game.observer
  1511. self.r = Style.RESET_ALL
  1512. self.nl = "\n\r"
  1513. self.target_sector = None
  1514. # Activate
  1515. self.prompt = game.buffer
  1516. self.save = self.observer.save()
  1517. self.observer.connect('player', self.player)
  1518. self.observer.connect("prompt", self.game_prompt)
  1519. self.observer.connect("game-line", self.game_line)
  1520. self.state = 0
  1521. self.defer = None
  1522. # Verify that they have configured auto-port trading.
  1523. # Otherwise, this doesn't work!
  1524. usefirst = self.game.gamedata.get_config('Trade_UseFirst')
  1525. if usefirst is None:
  1526. usefirst = '?'
  1527. if usefirst.upper()[0] != 'Y':
  1528. self.queue_game.put(Boxes.alert("Sorry! You need to config Trade_UseFirst=Y", base="red"))
  1529. self.deactivate()
  1530. return
  1531. turns = self.game.gamedata.get_config('Trade_Turns')
  1532. if turns is None:
  1533. turns = '0'
  1534. try:
  1535. t = int(turns)
  1536. except ValueError:
  1537. self.queue_game.put(Boxes.alert("Sorry! You need to config Trade_Turns", base="red"))
  1538. self.deactivate()
  1539. return
  1540. if t < 5:
  1541. self.queue_game.put(Boxes.alert("Sorry! You need to config Trade_Turns >", base="red"))
  1542. self.deactivate()
  1543. return
  1544. c = coiterate(self.find_next_good_trade_pair())
  1545. c.addCallback(lambda unknown: self.scary())
  1546. def scary(self):
  1547. if self.target_sector is None:
  1548. self.queue_game.put(Boxes.alert("Sorry! I don't see any ports to trade with.", base="red"))
  1549. self.deactivate()
  1550. else:
  1551. self.state = 1
  1552. self.queue_player.put("{0}\r".format(self.target_sector))
  1553. def find_next_good_trade_pair(self):
  1554. """ Find the next GOOD trade pair sector. """
  1555. show_limit = 90
  1556. # Look for "GOOD" trades
  1557. for sector in sorted(self.game.gamedata.ports.keys()):
  1558. pd = self.game.gamedata.ports[sector]
  1559. if not GameData.port_burnt(pd):
  1560. pc = pd['class']
  1561. # Ok, let's look into it.
  1562. if not sector in self.game.gamedata.warps:
  1563. continue
  1564. warps = self.game.gamedata.warps[sector]
  1565. for w in warps:
  1566. # We can get there, and get back.
  1567. if w in self.game.gamedata.warps and sector in self.game.gamedata.warps[w]:
  1568. # Ok, we can get there -- and get back!
  1569. if w > sector and w in self.game.gamedata.ports and not GameData.port_burnt(self.game.gamedata.ports[w]):
  1570. wd = self.game.gamedata.ports[w]
  1571. wc = wd['class']
  1572. if pc in (1,5) and wc in (2,4):
  1573. data = self.game.gamedata.port_trade_show(sector, w, show_limit)
  1574. if data:
  1575. self.target_sector = sector
  1576. return sector
  1577. elif pc in (2,4) and wc in (1,5):
  1578. data = self.game.gamedata.port_trade_show(sector, w, show_limit)
  1579. if data:
  1580. self.target_sector = sector
  1581. return sector
  1582. yield
  1583. # Look for OK trades
  1584. for sector in sorted(self.game.gamedata.ports.keys()):
  1585. pd = self.game.gamedata.ports[sector]
  1586. if not GameData.port_burnt(pd):
  1587. pc = pd['class']
  1588. # Ok, let's look into it.
  1589. if not sector in self.game.gamedata.warps:
  1590. continue
  1591. warps = self.game.gamedata.warps[sector]
  1592. for w in warps:
  1593. # We can get there, and get back.
  1594. if w in self.game.gamedata.warps and sector in self.game.gamedata.warps[w]:
  1595. # Ok, we can get there -- and get back!
  1596. if w > sector and w in self.game.gamedata.ports and not GameData.port_burnt(self.game.gamedata.ports[w]):
  1597. wd = self.game.gamedata.ports[w]
  1598. wc = wd['class']
  1599. if GameData.port_trading(pd['port'], self.game.gamedata.ports[w]['port']):
  1600. data = self.game.gamedata.port_trade_show(sector, w, show_limit)
  1601. if data:
  1602. self.target_sector = sector
  1603. return sector
  1604. yield
  1605. self.target_sector = None
  1606. def whenDone(self):
  1607. self.defer = defer.Deferred()
  1608. # Call this to chain something after we exit.
  1609. return self.defer
  1610. def deactivate(self, andExit=False):
  1611. self.state = 0
  1612. log.debug("ScriptTerror.deactivate")
  1613. assert(not self.save is None)
  1614. self.observer.load(self.save)
  1615. self.save = None
  1616. if self.defer:
  1617. if andExit:
  1618. self.defer.callback({'exit':True})
  1619. else:
  1620. self.defer.callback('done')
  1621. self.defer = None
  1622. def player(self, chunk: bytes):
  1623. # If we receive anything -- ABORT!
  1624. self.deactivate(True)
  1625. def journey_on(self, *_):
  1626. log.info("journey_on( {0})".format(self.count))
  1627. if self.count > 0:
  1628. self.count -= 1
  1629. c = coiterate(self.find_next_good_trade_pair())
  1630. c.addCallback(lambda unknown: self.scary())
  1631. # self.target_sector = self.proxy.find_next_good_trade_pair() # Sector going to
  1632. # self.state = 1
  1633. # self.queue_player.put("{0}\r".format(self.target_sector))
  1634. else:
  1635. self.deactivate()
  1636. def game_prompt(self, prompt: str):
  1637. log.debug("{0} : {1}".format(self.state, prompt))
  1638. if self.state == 1:
  1639. if prompt.startswith('Do you want to engage the TransWarp drive? '):
  1640. self.queue_player.put("N")
  1641. elif self.state == 2:
  1642. if prompt.startswith('Engage the Autopilot? (Y/N/Single step/Express) [Y]'):
  1643. self.queue_player.put("E")
  1644. elif prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1645. self.queue_player.put("N")
  1646. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1647. # We should be where we wanted to.
  1648. ports = ScriptPort(self.game)
  1649. d = ports.whenDone()
  1650. d.addCallback(self.journey_on)
  1651. d.addErrback(self.journey_on)
  1652. def game_line(self, line: str):
  1653. log.debug("line {0} : {1}".format(self.state, line))
  1654. if self.state == 1:
  1655. if line.startswith('The shortest path ('):
  1656. self.state = 2
  1657. elif line.startswith("You are already in that sector!"):
  1658. # Whoops.
  1659. ports = ScriptPort(self.game)
  1660. d = ports.whenDone()
  1661. d.addCallback(self.journey_on)
  1662. d.addErrback(self.journey_on)
  1663. class PlanetUpScript(object):
  1664. def __init__(self, game):
  1665. self.game = game
  1666. self.queue_game = game.queue_game
  1667. self.queue_player = game.queue_player
  1668. self.observer = game.observer
  1669. # Yes, at this point we would activate
  1670. self.prompt = game.buffer
  1671. self.save = self.observer.save()
  1672. self.nl = "\n\r"
  1673. # I actually don't want the player input, but I'll grab it anyway.
  1674. self.observer.connect("player", self.player)
  1675. self.observer.connect("prompt", self.game_prompt)
  1676. self.observer.connect("game-line", self.game_line)
  1677. # If we want it, it's here.
  1678. self.defer = None
  1679. self.to_player = self.game.to_player
  1680. self.planets = {}
  1681. # Hide what's happening from the player
  1682. self.game.to_player = False
  1683. self.queue_player.put("CYQ") # Computer -> Your Planets -> Quit
  1684. self.state = 1
  1685. # self.warpdata = {}
  1686. self.queue_game.put(Boxes.alert("Let me see what I can see here..."))
  1687. def game_prompt(self, prompt):
  1688. if self.state == 2 and re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1689. self.game.to_player = True
  1690. # For now we output the information, and exit
  1691. # self.queue_game.put("{0}\r\n".format(self.planets))
  1692. # self.queue_game.put(pformat(self.planets).replace("\n", self.nl) + self.nl)
  1693. if len(self.planets) == 0:
  1694. # Ok, this is easy.
  1695. self.queue_game.put(self.nl + Boxes.alert("You don't have any planets? You poor, poor dear.", base="red"))
  1696. self.deactivate()
  1697. return
  1698. # Get current sector from the prompt
  1699. # Command [TL=00:00:00]:[10202] (?=Help)? :
  1700. _, _, part = prompt.partition(']:[')
  1701. sector, _, _ = part.partition(']')
  1702. self.current_sector = int(sector)
  1703. # self.queue_game.put("Current: {0}\r\n".format(sector))
  1704. # Only ONE planet right in front of us? Default to upgrading it!
  1705. # Otherwise, we'll have to ask.
  1706. obvious = [ s for s in self.planets.keys() if self.planets[s]['sector'] == self.current_sector ]
  1707. if len(obvious) == 1:
  1708. # There is one.
  1709. self.planet_number = obvious[0]
  1710. self.planet_sector = self.planets[self.planet_number]['sector']
  1711. self.planet_name = self.planets[self.planet_number]['name']
  1712. self.state = 4
  1713. # begin landing procedure ...
  1714. self.queue_player.put("L")
  1715. return
  1716. # There are either no obvious planets to upgrade,
  1717. # or there are multiple planet choices.
  1718. self.queue_game.put("Choose:" + self.nl)
  1719. for s in self.planets.keys():
  1720. self.queue_game.put("{0} [{1}] {2}\r\n".format( s, self.planets[s]['sector'], self.planets[s]['name']))
  1721. ask = PlayerInput(self.game)
  1722. d = ask.prompt("Choose a planet", 3, name="planet", digits=True)
  1723. d.addCallback(self.planet_chosen)
  1724. elif self.state == 3:
  1725. if prompt.startswith('Do you want to engage the TransWarp drive? '):
  1726. self.queue_player.put("N")
  1727. if prompt.startswith('Engage the Autopilot? (Y/N/Single step/Express) [Y]'):
  1728. self.queue_player.put("E")
  1729. elif prompt.startswith('Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?'):
  1730. self.queue_player.put("N")
  1731. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1732. # We're here!
  1733. self.state = 4
  1734. self.queue_player.put("L")
  1735. elif self.state == 4:
  1736. # If you have a planet scanner, or if there's more then one planet.
  1737. if prompt.startswith('Land on which planet <Q to abort> ?'):
  1738. self.queue_player.put("{0}\r".format(self.planet_number))
  1739. if prompt.startswith('Planet command (?=help) [D] '):
  1740. self.queue_game.put(self.nl + "{0} : {1}".format( self.colonists, self.cargo) + self.nl)
  1741. self.state = 5
  1742. self.queue_player.put("C")
  1743. elif self.state == 5:
  1744. if prompt.startswith('Do you wish to construct one? '):
  1745. # Have we met all the needs? If so, do it. ;)
  1746. # If not, xfer the cargo on the ship to the planet and get moving!
  1747. ready = True
  1748. for i in ['F', 'O', 'E']:
  1749. if self.need[i] > self.cargo[i]:
  1750. ready = False
  1751. self.queue_game.put( "Need: {0}".format(i) + self.nl)
  1752. # break
  1753. if self.need['C'] > self.colonists:
  1754. self.queue_game.put("Need: people" + self.nl)
  1755. ready = False
  1756. self.queue_game.put(self.nl + "{0}".format(self.need))
  1757. if ready:
  1758. self.queue_player.put('YQ')
  1759. self.deactivate()
  1760. return
  1761. self.queue_player.put("N")
  1762. # Xfer cargo, and get ready to travel...
  1763. if prompt.startswith('Planet command (?=help) [D] '):
  1764. self.queue_game.put(pformat(self.ship_cargo).replace("\n", self.nl) + self.nl)
  1765. self.queue_game.put(pformat(self.cargo).replace("\n", self.nl) + self.nl)
  1766. for idx, c in enumerate(('F', 'O', 'E')):
  1767. if self.ship_cargo[c] > 0:
  1768. # Transfer Cargo, (No display), Leave [1,2, or 3], \r = All of it.
  1769. self.queue_player.put("TNL{0}\r".format(idx + 1))
  1770. self.cargo[c] += self.ship_cargo[c]
  1771. self.ship_cargo[c] = 0
  1772. break
  1773. self.queue_player.put("Q")
  1774. self.queue_game.put(pformat(self.ship_cargo).replace("\n", self.nl) + self.nl)
  1775. self.queue_game.put(pformat(self.cargo).replace("\n", self.nl) + self.nl)
  1776. self.deactivate()
  1777. def planet_chosen(self, choice: str):
  1778. if choice.strip() == '':
  1779. self.deactivate()
  1780. else:
  1781. self.planet_number = int(choice)
  1782. if self.planet_number in self.planets:
  1783. # Ok, this'll work
  1784. self.planet_sector = self.planets[self.planet_number]['sector']
  1785. self.planet_name = self.planets[self.planet_number]['name']
  1786. if self.current_sector == self.planet_sector:
  1787. # We're here. Land
  1788. self.state = 4
  1789. self.queue_player.put("L")
  1790. else:
  1791. # Get moving!
  1792. self.state = 3
  1793. self.queue_player.put("{0}\r".format(self.planet_sector))
  1794. else:
  1795. self.deactivate()
  1796. def game_line(self, line):
  1797. if self.state == 1:
  1798. if 'Personal Planet Scan' in line:
  1799. self.state = 2
  1800. elif self.state == 2:
  1801. # Ok, we're in the planet scan part of this
  1802. # 10202 #3 Home of Bugz Class M, Earth Type No Citadel]
  1803. if '#' in line:
  1804. # Ok, we have a planet detail line.
  1805. detail, _, _ = line.partition('Class')
  1806. detail = detail.strip() # Sector #X Name of planet
  1807. sector, number, name = re.split(r'\s+', detail, 2)
  1808. sector = int(sector)
  1809. number = int(number[1:])
  1810. self.last_seen = number
  1811. self.planets[number] = {"sector": sector, "name": name}
  1812. log.info("Planet # {0} in {1} called {2}".format( number, sector, name))
  1813. if '---' in line:
  1814. number = self.last_seen
  1815. # Ok, take the last_seen number, and use it for this line
  1816. # [ Sector Planet Name Ore Org Equ Ore Org Equ Fighters Citadel]
  1817. # [ Shields Population -=Productions=- -=-=-=-=-On Hands-=-=-=-=- Credits]
  1818. # [ 10202 #3 Home of Bugz Class M, Earth Type No Citadel]
  1819. # [ --- (1M) 144 49 26 145 75 12 10 0]
  1820. details = re.split(r"\s+", line.strip())
  1821. # OK: Likely, I'm not going to use these numbers AT ALL.
  1822. self.planets[number]['population'] = details[1].replace('(', '').replace(')', '')
  1823. # Ok, there's going to have to be some sort of modifier (K, M, etc.)
  1824. # to these numbers. Beware!
  1825. self.planets[number]['ore'] = details[5]
  1826. self.planets[number]['org'] = details[6]
  1827. self.planets[number]['equ'] = details[7]
  1828. elif self.state == 4:
  1829. # TODO:
  1830. # I need the ship amounts -- so I can xfer to planet...
  1831. # [ Item Colonists Colonists Daily Planet Ship Planet ]
  1832. # [Fuel Ore 0 1 0 0 0 1,000,000]
  1833. # [Organics 0 N/A 0 0 0 10,000]
  1834. # [Equipment 0 500 0 125 125 100,000]
  1835. # [Fighters N/A N/A 0 0 400 1,000,000]
  1836. items = ['Fuel Ore', 'Organics', 'Equipment']
  1837. for i in items:
  1838. if line.startswith(i):
  1839. cargo = line[0].upper()
  1840. work = line.replace(',', '')
  1841. if i == 'Fuel Ore':
  1842. work = work.replace(i, 'Fuel')
  1843. self.colonists = 0
  1844. self.cargo = {}
  1845. self.need = {}
  1846. self.ship_cargo = {}
  1847. self.place_people = {}
  1848. parts = re.split(r'\s+', work)
  1849. c = int(parts[1])
  1850. planet_has = int(parts[4])
  1851. self.colonists += c
  1852. self.cargo[cargo] = planet_has
  1853. self.ship_cargo[cargo] = int(parts[5])
  1854. # Boolean, can we place people here? If N/A, then don't!
  1855. self.place_people[cargo] = parts[2] != 'N/A'
  1856. elif self.state == 5:
  1857. # [Planet command (?=help) [D] C]
  1858. # [Be patient, your Citadel is not yet finished.]
  1859. if line.startswith('Be patient, your Citadel is not yet finished.'):
  1860. # Ah HA!
  1861. self.queue_game.put(self.nl + Boxes.alert("NO, NOT YET!") + self.nl)
  1862. self.queue_player.put("Q") # quit Planet menu.
  1863. self.deactivate()
  1864. else:
  1865. items = ['Colonists', 'Fuel Ore', 'Organics', 'Equipment']
  1866. work = line.replace(',', '').replace(' units of', '').strip()
  1867. # 800,000 Colonists to support the construction,
  1868. # 500 units of Fuel Ore,
  1869. # 300 units of Organics,
  1870. # 600 units of Equipment and
  1871. for i in items:
  1872. if i in line:
  1873. count = int(work.split()[0])
  1874. k = i[0].upper()
  1875. # keep colonists in same units.
  1876. if k == 'C':
  1877. count //= 1000
  1878. self.need[k] = count
  1879. def __del__(self):
  1880. log.debug("PlanetUpScript {0} RIP".format(self))
  1881. def whenDone(self):
  1882. self.defer = defer.Deferred()
  1883. # Call this to chain something after we exit.
  1884. return self.defer
  1885. def deactivate(self):
  1886. if not self.defer is None:
  1887. # We have something, so:
  1888. self.game.to_player = self.to_player
  1889. self.observer.load(self.save)
  1890. self.save = None
  1891. self.defer.callback(1)
  1892. self.defer = None
  1893. else:
  1894. # Still "exit" out.
  1895. self.game.to_player = self.to_player
  1896. self.observer.load(self.save)
  1897. def player(self, chunk):
  1898. """ Data from player (in bytes). """
  1899. chunk = chunk.decode("latin-1", "ignore")
  1900. key = chunk.upper()
  1901. log.warn("PlanetUpScript.player({0}) : I AM stopping...".format(key))
  1902. if not self.defer is None:
  1903. # We have something, so:
  1904. self.game.to_player = self.to_player
  1905. self.observer.load(self.save)
  1906. self.save = None
  1907. self.defer.errback(Exception("User Abort"))
  1908. self.defer = None
  1909. else:
  1910. # Still "exit" out.
  1911. self.game.to_player = self.to_player
  1912. self.observer.load(self.save)
  1913. class ProxyMenu(object):
  1914. """ Display ProxyMenu
  1915. Example:
  1916. from flexible import ProxyMenu
  1917. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1918. menu = ProxyMenu(self.game)
  1919. """
  1920. def __init__(self, game):
  1921. self.nl = "\n\r"
  1922. self.c = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  1923. self.r = Style.RESET_ALL
  1924. self.c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  1925. self.c2 = merge(Style.NORMAL + Fore.CYAN + Back.BLUE)
  1926. # self.portdata = None
  1927. self.game = game
  1928. self.queue_game = game.queue_game
  1929. self.observer = game.observer
  1930. # Am I using self or game? (I think I want game, not self.)
  1931. # if hasattr(self.game, "portdata"):
  1932. # self.portdata = self.game.portdata
  1933. # else:
  1934. # self.portdata = {}
  1935. # if hasattr(self.game, 'warpdata'):
  1936. # self.warpdata = self.game.warpdata
  1937. # else:
  1938. # self.warpdata = {}
  1939. if hasattr(self.game, 'trade_report'):
  1940. self.trade_report = self.game.trade_report
  1941. else:
  1942. self.trade_report = []
  1943. # Yes, at this point we would activate
  1944. self.prompt = game.buffer
  1945. self.save = self.observer.save()
  1946. self.observer.connect("player", self.player)
  1947. # If we want it, it's here.
  1948. self.defer = None
  1949. self.keepalive = task.LoopingCall(self.awake)
  1950. self.keepalive.start(30)
  1951. self.menu()
  1952. def __del__(self):
  1953. log.debug("ProxyMenu {0} RIP".format(self))
  1954. def whenDone(self):
  1955. self.defer = defer.Deferred()
  1956. # Call this to chain something after we exit.
  1957. return self.defer
  1958. def menu(self):
  1959. box = Boxes(30, color=self.c)
  1960. self.queue_game.put(box.top())
  1961. text = self.c + "{0:^30}".format("TradeWars Proxy Active")
  1962. text = text.replace('Active', BLINK + 'Active' + Style.RESET_ALL + self.c)
  1963. self.queue_game.put(box.row(text))
  1964. self.queue_game.put(box.middle())
  1965. def menu_item(ch: str, desc: str):
  1966. row = self.c1 + " {0} {1}- {2}{3:25}".format(ch, self.c2, self.c1, desc)
  1967. self.queue_game.put(box.row(row))
  1968. # self.queue_game.put(
  1969. # " " + self.c1 + ch + self.c2 + " - " + self.c1 + desc + self.nl
  1970. # )
  1971. menu_item("C", "Configuration ({0})".format(len(self.game.gamedata.config)))
  1972. menu_item("D", "Display Report again")
  1973. menu_item("E", "Export Data (Save)")
  1974. # menu_item("Q", "Quest")
  1975. menu_item("P", "Port CIM Report ({0})".format(len(self.game.gamedata.ports)))
  1976. menu_item("W", "Warp CIM Report ({0})".format(len(self.game.gamedata.warps)))
  1977. menu_item("T", "Trading Report")
  1978. menu_item("S", "Scripts")
  1979. menu_item("X", "eXit")
  1980. self.queue_game.put(box.bottom())
  1981. self.queue_game.put(" " + self.c + "-=>" + self.r + " ")
  1982. def awake(self):
  1983. log.info("ProxyMenu.awake()")
  1984. self.game.queue_player.put(" ")
  1985. def port_report(self, portdata: dict):
  1986. # self.portdata = portdata
  1987. # self.game.portdata = portdata
  1988. self.queue_game.put("Loaded {0} ports.".format(len(portdata)) + self.nl)
  1989. self.welcome_back()
  1990. def warp_report(self, warpdata: dict):
  1991. # self.warpdata = warpdata
  1992. # self.game.warpdata = warpdata
  1993. self.queue_game.put("Loaded {0} sectors.".format(len(warpdata)) + self.nl)
  1994. self.welcome_back()
  1995. def make_trade_report(self):
  1996. log.debug("make_trade_report()")
  1997. ok_trades = []
  1998. best_trades = []
  1999. show_best = self.game.gamedata.get_config('Display_Best', 'Y').upper()[0] == 'Y'
  2000. show_ok = self.game.gamedata.get_config('Display_Ok', 'N').upper()[0] == 'Y'
  2001. show_limit = self.game.gamedata.get_config('Display_Percent', '90')
  2002. update_config = False
  2003. try:
  2004. show_limit = int(show_limit)
  2005. except ValueError:
  2006. show_limit = 90
  2007. update_config = True
  2008. if show_limit < 0:
  2009. show_limit = 0
  2010. update_config = True
  2011. elif show_limit > 100:
  2012. show_limit = 100
  2013. update_config = True
  2014. if update_config:
  2015. self.game.gamedata.set_config('Display_Percent', show_limit)
  2016. # for sector, pd in self.game.gamedata.ports.items():
  2017. for sector in sorted(self.game.gamedata.ports.keys()):
  2018. pd = self.game.gamedata.ports[sector]
  2019. if not GameData.port_burnt(pd):
  2020. pc = pd['class']
  2021. # Ok, let's look into it.
  2022. if not sector in self.game.gamedata.warps:
  2023. continue
  2024. warps = self.game.gamedata.warps[sector]
  2025. for w in warps:
  2026. # Verify that we have that warp's info, and that the sector is in it.
  2027. # (We can get back from it)
  2028. if w in self.game.gamedata.warps and sector in self.game.gamedata.warps[w]:
  2029. # Ok, we can get there -- and get back!
  2030. if w > sector and w in self.game.gamedata.ports and not GameData.port_burnt(self.game.gamedata.ports[w]):
  2031. # it is > and has a port.
  2032. wd = self.game.gamedata.ports[w]
  2033. wc = wd['class']
  2034. # 1: "BBS",
  2035. # 2: "BSB",
  2036. # 3: "SBB",
  2037. # 4: "SSB",
  2038. # 5: "SBS",
  2039. # 6: "BSS",
  2040. # 7: "SSS",
  2041. # 8: "BBB",
  2042. if pc in (1,5) and wc in (2,4):
  2043. data = self.game.gamedata.port_trade_show(sector, w, show_limit)
  2044. if data:
  2045. best_trades.append(data)
  2046. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  2047. elif pc in (2,4) and wc in (1,5):
  2048. data = self.game.gamedata.port_trade_show(sector, w, show_limit)
  2049. if data:
  2050. best_trades.append(data)
  2051. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  2052. elif GameData.port_trading(pd['port'], self.game.gamedata.ports[w]['port']):
  2053. # ok_trades.append( "{0:5} -=- {1:5}".format(sector,w))
  2054. data = self.game.gamedata.port_trade_show(sector, w, show_limit)
  2055. if data:
  2056. ok_trades.append(data)
  2057. yield
  2058. if show_best:
  2059. self.trade_report.append("Best Trades: (org/equ)")
  2060. self.trade_report.extend(best_trades)
  2061. if show_ok:
  2062. self.trade_report.append("Ok Trades:")
  2063. self.trade_report.extend(ok_trades)
  2064. if not show_best and not show_ok:
  2065. self.queue_game.put(Boxes.alert("You probably want to choose something to display in configuration!", base="red"))
  2066. # self.queue_game.put("BEST TRADES:" + self.nl + self.nl.join(best_trades) + self.nl)
  2067. # self.queue_game.put("OK TRADES:" + self.nl + self.nl.join(ok_trades) + self.nl)
  2068. def get_display_maxlines(self):
  2069. show_maxlines = self.game.gamedata.get_config('Display_Maxlines', '0')
  2070. try:
  2071. show_maxlines = int(show_maxlines)
  2072. except ValueError:
  2073. show_maxlines = 0
  2074. if show_maxlines <= 0:
  2075. show_maxlines = None
  2076. return show_maxlines
  2077. def show_trade_report(self, *_):
  2078. show_maxlines = self.get_display_maxlines()
  2079. self.game.trade_report = self.trade_report
  2080. for t in self.trade_report[:show_maxlines]:
  2081. self.queue_game.put(t + self.nl)
  2082. self.queue_game.put(self.nl + Boxes.alert("Proxy done.", base="green"))
  2083. self.observer.load(self.save)
  2084. self.save = None
  2085. self.keepalive = None
  2086. self.prompt = None
  2087. # self.welcome_back()
  2088. def player(self, chunk: bytes):
  2089. """ Data from player (in bytes). """
  2090. chunk = chunk.decode("latin-1", "ignore")
  2091. key = chunk.upper()
  2092. log.debug("ProxyMenu.player({0})".format(key))
  2093. # Stop the keepalive if we are activating something else
  2094. # or leaving...
  2095. self.keepalive.stop()
  2096. if key == "T":
  2097. self.queue_game.put(self.c + key + self.r + self.nl)
  2098. # Trade Report
  2099. # do we have enough information to do this?
  2100. # if not hasattr(self.game, 'portdata') and not hasattr(self.game, 'warpdata'):
  2101. # self.queue_game.put("Missing portdata and warpdata." + self.nl)
  2102. # elif not hasattr(self.game, 'portdata'):
  2103. # self.queue_game.put("Missing portdata." + self.nl)
  2104. # elif not hasattr(self.game, 'warpdata'):
  2105. # self.queue_game.put("Missing warpdata." + self.nl)
  2106. # else:
  2107. if True:
  2108. # Yes, so let's start!
  2109. self.trade_report = []
  2110. d = coiterate(self.make_trade_report())
  2111. d.addCallback(self.show_trade_report)
  2112. return
  2113. elif key == "P":
  2114. self.queue_game.put(self.c + key + self.r + self.nl)
  2115. self.game.gamedata.reset_ports()
  2116. # Activate CIM Port Report
  2117. report = CIMPortReport(self.game)
  2118. d = report.whenDone()
  2119. d.addCallback(self.port_report)
  2120. d.addErrback(self.welcome_back)
  2121. return
  2122. elif key == "W":
  2123. self.queue_game.put(self.c + key + self.r + self.nl)
  2124. self.game.gamedata.reset_warps()
  2125. # Activate CIM Warp Report
  2126. report = CIMWarpReport(self.game)
  2127. d = report.whenDone()
  2128. d.addCallback(self.warp_report)
  2129. d.addErrback(self.welcome_back)
  2130. return
  2131. elif key == "S":
  2132. self.queue_game.put(self.c + key + self.r + self.nl)
  2133. # Scripts
  2134. self.activate_scripts_menu()
  2135. return
  2136. elif key == "D":
  2137. self.queue_game.put(self.c + key + self.r + self.nl)
  2138. # (Re) Display Trade Report
  2139. show_maxlines = self.get_display_maxlines()
  2140. if self.trade_report:
  2141. for t in self.trade_report[:show_maxlines]:
  2142. self.queue_game.put(t + self.nl)
  2143. self.queue_game.put(self.nl + Boxes.alert("Proxy done.", base="green"))
  2144. self.observer.load(self.save)
  2145. self.save = None
  2146. self.keepalive = None
  2147. self.prompt = None
  2148. return
  2149. else:
  2150. self.queue_game.put("Missing trade_report." + self.nl)
  2151. elif key == 'E':
  2152. self.queue_game.put(self.c + key + self.r + self.nl)
  2153. self.queue_game.put(Boxes.alert("Saving..."))
  2154. then_do = coiterate(self.game.gamedata.save())
  2155. then_do.addCallback(self.welcome_back)
  2156. return
  2157. elif key == "C":
  2158. self.queue_game.put(self.c + key + self.r + self.nl)
  2159. self.activate_config_menu()
  2160. return
  2161. # self.queue_game.put(pformat(self.portdata).replace("\n", "\n\r") + self.nl)
  2162. # self.queue_game.put(pformat(self.warpdata).replace("\n", "\n\r") + self.nl)
  2163. elif key == "Q":
  2164. self.queue_game.put(self.c + key + self.r + self.nl)
  2165. # This is an example of chaining PlayerInput prompt calls.
  2166. ask = PlayerInput(self.game)
  2167. d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  2168. # Display the user's input
  2169. d.addCallback(ask.output)
  2170. d.addCallback(
  2171. lambda ignore: ask.prompt(
  2172. "What is your favorite color?", 10, name="color"
  2173. )
  2174. )
  2175. d.addCallback(ask.output)
  2176. d.addCallback(
  2177. lambda ignore: ask.prompt(
  2178. "What is the meaning of the squirrel?",
  2179. 12,
  2180. name="squirrel",
  2181. digits=True,
  2182. )
  2183. )
  2184. d.addCallback(ask.output)
  2185. def show_values(show):
  2186. log.debug(show)
  2187. self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  2188. d.addCallback(lambda ignore: show_values(ask.keep))
  2189. d.addCallback(self.welcome_back)
  2190. # On error, just return back
  2191. # This doesn't seem to be getting called.
  2192. # d.addErrback(lambda ignore: self.welcome_back)
  2193. d.addErrback(self.welcome_back)
  2194. return
  2195. elif key == "X":
  2196. self.queue_game.put(self.c + key + self.r + self.nl)
  2197. self.queue_game.put(Boxes.alert("Proxy done.", base="green"))
  2198. self.observer.load(self.save)
  2199. self.save = None
  2200. # It isn't running (NOW), so don't try to stop it.
  2201. # self.keepalive.stop()
  2202. self.keepalive = None
  2203. # Ok, this is a HORRIBLE idea, because the prompt might be
  2204. # outdated.
  2205. # self.queue_game.put(self.prompt)
  2206. self.prompt = None
  2207. # Send '\r' to re-display the prompt
  2208. # instead of displaying the original one.
  2209. self.game.queue_player.put("d")
  2210. # Were we asked to do something when we were done here?
  2211. if self.defer:
  2212. reactor.CallLater(0, self.defer.callback)
  2213. # self.defer.callback()
  2214. self.defer = None
  2215. return
  2216. self.keepalive.start(30, True)
  2217. self.menu()
  2218. def activate_config_menu(self):
  2219. self.observer.disconnect("player", self.player)
  2220. self.observer.connect("player", self.config_player)
  2221. self.config_menu()
  2222. def deactivate_config_menu(self, *data):
  2223. log.warn("deactivate_config_menu ({0})".format(data))
  2224. self.observer.disconnect("player", self.config_player)
  2225. self.observer.connect("player", self.player)
  2226. self.welcome_back()
  2227. def activate_scripts_menu(self):
  2228. self.observer.disconnect("player", self.player)
  2229. self.observer.connect("player", self.scripts_player)
  2230. self.scripts_menu()
  2231. def option_entry(self, entry):
  2232. if len(entry) > 0:
  2233. # Ok, they gave us something
  2234. self.game.gamedata.set_config(self.option_select, entry.strip())
  2235. else:
  2236. self.queue_game.put("Edit aborted." + self.nl)
  2237. self.config_menu()
  2238. def option_input(self, option):
  2239. if len(option) > 0:
  2240. option = int(option)
  2241. if option in self.config_opt:
  2242. # Ok, it's a valid option!
  2243. self.option_select = self.config_opt[option]
  2244. ask = PlayerInput(self.game)
  2245. d = ask.prompt("Change {0} to?".format(self.option_select), 18)
  2246. d.addCallback(self.option_entry)
  2247. # d.addErrback(self.config_menu)
  2248. else:
  2249. self.queue_game.put("Unknown option, sorry." + self.nl)
  2250. self.config_menu()
  2251. else:
  2252. # Aborted
  2253. self.config_menu()
  2254. def config_player(self, chunk: bytes):
  2255. """ Data from player (in bytes). """
  2256. chunk = chunk.decode("latin-1", "ignore")
  2257. key = chunk.upper()
  2258. if key == 'C':
  2259. self.queue_game.put(self.c + key + self.r + self.nl)
  2260. self.game.gamedata.config = {}
  2261. elif key == 'E':
  2262. self.queue_game.put(self.c + key + self.r + self.nl)
  2263. ask = PlayerInput(self.game)
  2264. d = ask.prompt("Which to edit?", 4, name='option', abort_blank=True, digits=True)
  2265. d.addCallback(self.option_input)
  2266. d.addErrback(self.config_menu)
  2267. return
  2268. elif key in ('1','2','3','4','5','6','7','8','9'):
  2269. self.queue_game.put(self.c + key + self.r + self.nl)
  2270. option = int(key)
  2271. if option in self.config_opt:
  2272. # Ok, it's a valid option!
  2273. self.option_select = self.config_opt[option]
  2274. ask = PlayerInput(self.game)
  2275. d = ask.prompt("Change {0} to?".format(self.option_select), 18)
  2276. d.addCallback(self.option_entry)
  2277. # d.addErrback(self.config_menu)
  2278. return
  2279. else:
  2280. self.queue_game.put("Unknown option, sorry." + self.nl)
  2281. elif key == 'X':
  2282. self.queue_game.put(self.c + key + self.r + self.nl)
  2283. self.deactivate_config_menu()
  2284. return
  2285. else:
  2286. self.queue_game.put(self.c + "?" + self.r + self.nl)
  2287. self.config_menu()
  2288. def config_menu(self, *_):
  2289. titlecolor = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  2290. tc = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  2291. c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  2292. c2 = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  2293. #box = Boxes(44, color=titlecolor)
  2294. box = Boxes(44, color=tc)
  2295. self.queue_game.put(box.top())
  2296. #self.queue_game.put(box.row(titlecolor + "{0:^44}".format("Configuration")))
  2297. self.queue_game.put(box.row(tc + "{0:^44}".format("Configuration")))
  2298. self.queue_game.put(box.middle())
  2299. def config_option(index, key, value):
  2300. row = "{0}{1:2} {2:19}{3}{4:<20}".format(c1, index, key, c2, value)
  2301. self.queue_game.put(box.row(row))
  2302. def menu_item(ch, desc):
  2303. row = "{0} {1} {2}-{3} {4:39}".format(c1, ch, c2, c1, desc)
  2304. # self.queue_game.put(
  2305. # " " + c1 + ch + c2 + " - " + c1 + desc + self.nl
  2306. # )
  2307. self.queue_game.put(box.row(row))
  2308. index = 1
  2309. self.config_opt = {}
  2310. for k in sorted(self.game.gamedata.config.keys()):
  2311. # for k, v in self.game.gamedata.config.items():
  2312. v = self.game.gamedata.config[k]
  2313. self.config_opt[index] = k
  2314. config_option(index, k, v)
  2315. index += 1
  2316. self.queue_game.put(box.middle())
  2317. menu_item("C", "Clear Config")
  2318. menu_item("E", "Edit Item")
  2319. menu_item("X", "eXit")
  2320. self.queue_game.put(box.bottom())
  2321. self.queue_game.put(" " + tc + "-=>" + self.r + " ")
  2322. def deactivate_scripts_menu(self, *data):
  2323. log.warn("deactivate_scripts_menu ({0})".format(data))
  2324. self.observer.disconnect("player", self.scripts_player)
  2325. self.observer.connect("player", self.player)
  2326. # Did they request exit?
  2327. if len(data) > 0 and type(data[0]) == dict:
  2328. info = data[0]
  2329. if 'exit' in info and info['exit']:
  2330. log.warn("exit proxy...")
  2331. # Exit Proxy Code
  2332. self.queue_game.put(self.nl + Boxes.alert("Proxy done.", base="green",style=3))
  2333. self.observer.load(self.save)
  2334. self.save = None
  2335. # It isn't running (NOW), so don't try to stop it.
  2336. # self.keepalive.stop()
  2337. self.keepalive = None
  2338. # Ok, this is a HORRIBLE idea, because the prompt might be
  2339. # outdated.
  2340. # self.queue_game.put(self.prompt)
  2341. self.prompt = None
  2342. # I'm not sure where we are, we might not be at a prompt.
  2343. # let's check!
  2344. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", self.game.getPrompt()):
  2345. # Send '\r' to re-display the prompt
  2346. # instead of displaying the original one.
  2347. self.game.queue_player.put("d")
  2348. # Were we asked to do something when we were done here?
  2349. if self.defer:
  2350. reactor.CallLater(0, self.defer.callback)
  2351. # self.defer.callback()
  2352. self.defer = None
  2353. return
  2354. log.warn("calling welcome_back")
  2355. self.welcome_back()
  2356. def scripts_menu(self, *_):
  2357. c1 = merge(Style.BRIGHT + Fore.CYAN)
  2358. c2 = merge(Style.NORMAL + Fore.CYAN)
  2359. box = Boxes(40, color=c1)
  2360. self.queue_game.put(box.top())
  2361. self.queue_game.put(box.row(c1 + "{0:^40}".format("Scripts")))
  2362. self.queue_game.put(box.middle())
  2363. def menu_item(ch, desc):
  2364. row = " {0}{1} {2}-{3} {4:35}".format(c1, ch, c2, c1, desc)
  2365. # self.queue_game.put(
  2366. # " " + c1 + ch + c2 + " - " + c1 + desc + self.nl
  2367. # )
  2368. self.queue_game.put(box.row(row))
  2369. menu_item("1", "Ports (Trades between two sectors)")
  2370. menu_item("!", "Terrorize Ports/Trades")
  2371. menu_item("2", "Explore (Strange new sectors)")
  2372. menu_item("3", "Space... the broken script...")
  2373. menu_item("4", "Upgrade Planet")
  2374. menu_item("X", "eXit")
  2375. self.queue_game.put(box.bottom())
  2376. self.queue_game.put(" " + c1 + "-=>" + self.r + " ")
  2377. def terror(self, *_):
  2378. log.debug("terror {0}".format(_))
  2379. loops = _[0]
  2380. if loops.strip() == '':
  2381. self.deactivate_scripts_menu()
  2382. else:
  2383. # Ok, we have something here, I think...
  2384. terror = ScriptTerror(self.game, self, int(loops))
  2385. d = terror.whenDone()
  2386. d.addCallback(self.deactivate_scripts_menu)
  2387. d.addErrback(self.deactivate_scripts_menu)
  2388. def scripts_player(self, chunk: bytes):
  2389. """ Data from player (in bytes). """
  2390. chunk = chunk.decode("latin-1", "ignore")
  2391. key = chunk.upper()
  2392. if key == '1':
  2393. self.queue_game.put(self.c + key + self.r + self.nl)
  2394. # Activate this magical event here
  2395. ports = ScriptPort(self.game)
  2396. d = ports.whenDone()
  2397. # d.addCallback(self.scripts_menu)
  2398. # d.addErrback(self.scripts_menu)
  2399. d.addCallback(self.deactivate_scripts_menu)
  2400. d.addErrback(self.deactivate_scripts_menu)
  2401. return
  2402. elif key == '!':
  2403. self.queue_game.put(self.c + key + self.r + self.nl)
  2404. ask = PlayerInput(self.game)
  2405. # This is TERROR, so do something!
  2406. ask.color(merge(Style.BRIGHT + Fore.WHITE + Back.RED))
  2407. ask.colorp(merge(Style.BRIGHT + Fore.YELLOW + Back.RED))
  2408. d = ask.prompt("How many loops of terror?", 4, name="loops", digits=True, abort_blank=True)
  2409. d.addCallback(self.terror, ask)
  2410. d.addErrback(self.deactivate_scripts_menu)
  2411. return
  2412. elif key == '2':
  2413. self.queue_game.put(self.c + key + self.r + self.nl)
  2414. explore = ScriptExplore(self.game)
  2415. d = explore.whenDone()
  2416. d.addCallback(self.deactivate_scripts_menu)
  2417. d.addErrback(self.deactivate_scripts_menu)
  2418. return
  2419. elif key == '3':
  2420. self.queue_game.put(self.c + key + self.r + self.nl)
  2421. space = ScriptSpace(self.game)
  2422. d = space.whenDone()
  2423. d.addCallback(self.deactivate_scripts_menu)
  2424. d.addErrback(self.deactivate_scripts_menu)
  2425. return
  2426. elif key == '4':
  2427. self.queue_game.put(self.c + key + self.r + self.nl)
  2428. upgrade = PlanetUpScript(self.game)
  2429. d = upgrade.whenDone()
  2430. d.addCallback(self.deactivate_scripts_menu)
  2431. d.addErrback(self.deactivate_scripts_menu)
  2432. return
  2433. elif key == 'X':
  2434. self.queue_game.put(self.c + key + self.r + self.nl)
  2435. self.deactivate_scripts_menu()
  2436. return
  2437. else:
  2438. self.queue_game.put(self.c + "?" + self.r + self.nl)
  2439. self.scripts_menu()
  2440. def welcome_back(self, *_):
  2441. log.debug("welcome_back")
  2442. self.keepalive.start(30, True)
  2443. self.menu()