flexible.py 142 KB

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