flexible.py 99 KB

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