flexible.py 106 KB

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