flexible.py 214 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401
  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. from time import localtime
  15. BLINK = "\x1b[5m"
  16. log = logging.getLogger(__name__)
  17. class SpinningCursor(object):
  18. """ Spinner class, that handles every so many clicks
  19. s = SpinningCursor(5) # every 5
  20. for x in range(10):
  21. if s.click():
  22. print(s.cycle())
  23. """
  24. def __init__(self, every=10):
  25. self.itercycle = cycle(["/", "-", "\\", "|"])
  26. self.count = 0
  27. self.every = every
  28. def reset(self):
  29. self.itercycle = cycle(["/", "-", "\\", "|"])
  30. self.count = 0
  31. def click(self):
  32. self.count += 1
  33. return self.count % self.every == 0
  34. def cycle(self):
  35. return next(self.itercycle)
  36. def again(self, count: int):
  37. return self.count % count == 0
  38. def merge(color_string):
  39. """ Given a string of colorama ANSI, merge them if you can. """
  40. return color_string.replace("m\x1b[", ";")
  41. class PlayerInput(object):
  42. """ Player Input
  43. Example:
  44. from flexible import PlayerInput
  45. ask = PlayerInput(self.game)
  46. # abort_blank means, if the input field is blank, abort. Use error_back.
  47. d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  48. # Display the user's input / but not needed.
  49. d.addCallback(ask.output)
  50. d.addCallback(
  51. lambda ignore: ask.prompt(
  52. "What is your favorite color?", 10, name="color"
  53. )
  54. )
  55. d.addCallback(ask.output)
  56. d.addCallback(
  57. lambda ignore: ask.prompt(
  58. "What is your least favorite number?",
  59. 12,
  60. name="number",
  61. digits=True,
  62. )
  63. )
  64. d.addCallback(ask.output)
  65. def show_values(show):
  66. log.debug(show)
  67. self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  68. d.addCallback(lambda ignore: show_values(ask.keep))
  69. d.addCallback(self.welcome_back)
  70. # On error, just return back
  71. d.addErrback(self.welcome_back)
  72. """
  73. def __init__(self, game):
  74. # I think game gives us access to everything we need
  75. self.game = game
  76. self.observer = self.game.observer
  77. self.save = None
  78. self.deferred = None
  79. self.queue_game = game.queue_game
  80. self.keep = {}
  81. # default colors
  82. # prompt
  83. self.c = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  84. # input
  85. self.cp = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  86. # useful constants
  87. self.r = Style.RESET_ALL
  88. self.nl = "\n\r"
  89. self.bsb = "\b \b"
  90. self.keepalive = None
  91. def color(self, c):
  92. self.c = c
  93. def colorp(self, cp):
  94. self.cp = cp
  95. def alive(self):
  96. log.debug("PlayerInput.alive()")
  97. self.game.queue_player.put(" ")
  98. def prompt(self, user_prompt, limit, **kw):
  99. """ Generate prompt for user input.
  100. Note: This returns deferred.
  101. prompt = text displayed.
  102. limit = # of characters allowed.
  103. default = (text to default to)
  104. keywords:
  105. abort_blank : Abort if they give us blank text.
  106. name : Stores the input in self.keep dict.
  107. digits : Only allow 0-9 to be entered.
  108. """
  109. log.debug("PlayerInput({0}, {1}, {2}".format(user_prompt, limit, kw))
  110. self.limit = limit
  111. self.input = ""
  112. self.kw = kw
  113. assert self.save is None
  114. assert self.keepalive is None
  115. # Note: This clears out the server "keep alive"
  116. self.save = self.observer.save()
  117. self.observer.connect("player", self.get_input)
  118. self.keepalive = task.LoopingCall(self.alive)
  119. self.keepalive.start(30)
  120. # We need to "hide" the game output.
  121. # Otherwise it WITH mess up the user input display.
  122. self.to_player = self.game.to_player
  123. self.game.to_player = False
  124. # Display prompt
  125. # self.queue_game.put(self.r + self.nl + self.c + user_prompt + " " + self.cp)
  126. self.queue_game.put(self.r + self.c + user_prompt + self.r + " " + self.cp)
  127. # Set "Background of prompt"
  128. self.queue_game.put(" " * limit + "\b" * limit)
  129. assert self.deferred is None
  130. d = defer.Deferred()
  131. self.deferred = d
  132. log.debug("Return deferred ...")
  133. return d
  134. def get_input(self, chunk):
  135. """ Data from player (in bytes) """
  136. chunk = chunk.decode("latin-1", "ignore")
  137. for ch in chunk:
  138. if ch == "\b":
  139. if len(self.input) > 0:
  140. self.queue_game.put(self.bsb)
  141. self.input = self.input[0:-1]
  142. else:
  143. self.queue_game.put("\a")
  144. elif ch == "\r":
  145. self.queue_game.put(self.r + self.nl)
  146. log.debug("Restore observer dispatch {0}".format(self.save))
  147. assert not self.save is None
  148. self.observer.load(self.save)
  149. self.save = None
  150. log.debug("Disable keepalive")
  151. self.keepalive.stop()
  152. self.keepalive = None
  153. line = self.input
  154. self.input = ""
  155. assert not self.deferred is None
  156. self.game.to_player = self.to_player
  157. # If they gave us the keyword name, save the value as that name
  158. if "name" in self.kw:
  159. self.keep[self.kw["name"]] = line
  160. if "abort_blank" in self.kw and self.kw["abort_blank"]:
  161. # Abort on blank input
  162. if line.strip() == "":
  163. # Yes, input is blank, abort.
  164. log.info("errback, abort_blank")
  165. reactor.callLater(
  166. 0, self.deferred.errback, Exception("abort_blank")
  167. )
  168. self.deferred = None
  169. return
  170. # Ok, use deferred.callback, or reactor.callLater?
  171. # self.deferred.callback(line)
  172. reactor.callLater(0, self.deferred.callback, line)
  173. self.deferred = None
  174. return
  175. elif ch.isprintable():
  176. # Printable, but is it acceptable?
  177. if "digits" in self.kw:
  178. if not ch.isdigit():
  179. self.queue_game.put("\a")
  180. continue
  181. if len(self.input) + 1 <= self.limit:
  182. self.input += ch
  183. self.queue_game.put(ch)
  184. else:
  185. self.queue_game.put("\a")
  186. def output(self, line):
  187. """ A default display of what they just input. """
  188. log.debug("PlayerInput.output({0})".format(line))
  189. self.game.queue_game.put(self.r + "[{0}]".format(line) + self.nl)
  190. return line
  191. import re
  192. # The CIMWarpReport -- is only needed if the json file gets damaged in some way.
  193. # Like when the universe gets bigbanged. :P
  194. # or needs to be reset. The warps should automatically update themselves now.
  195. class CIMWarpReport(object):
  196. def __init__(self, game):
  197. self.game = game
  198. self.queue_game = game.queue_game
  199. self.queue_player = game.queue_player
  200. self.observer = game.observer
  201. # Yes, at this point we would activate
  202. self.prompt = game.buffer
  203. self.save = self.observer.save()
  204. self.days = ("Mon", "Tues", "Wed", "Thurs", "Fri", "Sat", "Sun")
  205. # I actually don't want the player input, but I'll grab it anyway.
  206. self.observer.connect("player", self.player)
  207. self.observer.connect("prompt", self.game_prompt)
  208. self.observer.connect("game-line", self.game_line)
  209. # If we want it, it's here.
  210. self.defer = None
  211. self.to_player = self.game.to_player
  212. # Hide what's happening from the player
  213. self.game.to_player = False
  214. self.queue_player.put("^") # Activate CIM
  215. self.state = 1
  216. # self.warpdata = {}
  217. tm = localtime()
  218. log.debug("Today is {0}".format(self.days[tm[6]]))
  219. self.queue_game.put("Loading ") # cycle eats last char.
  220. self.warpcycle = SpinningCursor()
  221. def game_prompt(self, prompt):
  222. if prompt == ": ":
  223. if self.state == 1:
  224. # Ok, then we're ready to request the port report
  225. self.warpcycle.reset()
  226. self.queue_player.put("I")
  227. self.state = 2
  228. elif self.state == 2:
  229. self.queue_player.put("Q")
  230. self.state = 3
  231. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  232. if self.state == 3:
  233. # Ok, time to exit
  234. # exit from this...
  235. self.game.to_player = self.to_player
  236. self.observer.load(self.save)
  237. self.save = None
  238. # self.game.warpdata = self.warpdata
  239. self.queue_game.put("\b \b\r\n")
  240. if not self.defer is None:
  241. self.defer.callback(self.game.gamedata.warps)
  242. self.defer = None
  243. def game_line(self, line):
  244. if line == "" or line == ": ":
  245. return
  246. if line == ": ENDINTERROG":
  247. return
  248. if line.startswith("Command [TL="):
  249. return
  250. # This should be the CIM Report Data -- parse it
  251. if self.warpcycle:
  252. if self.warpcycle.click():
  253. self.queue_game.put("\b")
  254. if self.warpcycle.again(1000):
  255. self.queue_game.put(".")
  256. self.queue_game.put(self.warpcycle.cycle())
  257. work = line.strip()
  258. parts = re.split(r"(?<=\d)\s", work)
  259. parts = [int(x) for x in parts]
  260. sector = parts.pop(0)
  261. # tuples are nicer on memory, and the warpdata map isn't going to be changing.
  262. # self.warpdata[sector] = tuple(parts)
  263. self.game.gamedata.warp_to(sector, *parts)
  264. def __del__(self):
  265. log.debug("CIMWarpReport {0} RIP".format(self))
  266. def whenDone(self):
  267. self.defer = defer.Deferred()
  268. # Call this to chain something after we exit.
  269. return self.defer
  270. def player(self, chunk):
  271. """ Data from player (in bytes). """
  272. chunk = chunk.decode("latin-1", "ignore")
  273. key = chunk.upper()
  274. log.warn("CIMWarpReport.player({0}) : I AM stopping...".format(key))
  275. # Stop the keepalive if we are activating something else
  276. # or leaving...
  277. # self.keepalive.stop()
  278. self.queue_game.put("\b \b\r\n")
  279. if not self.defer is None:
  280. # We have something, so:
  281. self.game.to_player = self.to_player
  282. self.observer.load(self.save)
  283. self.save = None
  284. self.defer.errback(Exception("User Abort"))
  285. self.defer = None
  286. else:
  287. # Still "exit" out.
  288. self.game.to_player = self.to_player
  289. self.observer.load(self.save)
  290. # the CIMPortReport will still be needed.
  291. # We can't get fresh report data (that changes) any other way.
  292. class CIMPortReport(object):
  293. """ Parse data from CIM Port Report
  294. Example:
  295. from flexible import CIMPortReport
  296. report = CIMPortReport(self.game)
  297. d = report.whenDone()
  298. d.addCallback(self.port_report)
  299. d.addErrback(self.welcome_back)
  300. def port_report(self, portdata):
  301. self.portdata = portdata
  302. self.queue_game.put("Loaded {0} records.".format(len(portdata)) + self.nl)
  303. self.welcome_back()
  304. def welcome_back(self,*_):
  305. ... restore keep alive timers, etc.
  306. """
  307. def __init__(self, game):
  308. self.game = game
  309. self.queue_game = game.queue_game
  310. self.queue_player = game.queue_player
  311. self.observer = game.observer
  312. # Yes, at this point we would activate
  313. self.prompt = game.buffer
  314. self.save = self.observer.save()
  315. self.days = ("Mon", "Tues", "Wed", "Thurs", "Fri", "Sat", "Sun")
  316. # I actually don't want the player input, but I'll grab it anyway.
  317. self.observer.connect("player", self.player)
  318. self.observer.connect("prompt", self.game_prompt)
  319. self.observer.connect("game-line", self.game_line)
  320. # If we want it, it's here.
  321. self.defer = None
  322. self.to_player = self.game.to_player
  323. log.debug("to_player (stored) {0}".format(self.to_player))
  324. # Hide what's happening from the player
  325. self.game.to_player = False
  326. self.queue_player.put("^") # Activate CIM
  327. self.state = 1
  328. # self.portdata = {}
  329. tm = localtime()
  330. log.debug("Today is {0}".format(self.days[tm[6]]))
  331. self.queue_game.put("Loading ") # cycle eats last char.
  332. self.portcycle = SpinningCursor()
  333. def game_prompt(self, prompt):
  334. if prompt == ": ":
  335. if self.state == 1:
  336. # Ok, then we're ready to request the port report
  337. self.portcycle.reset()
  338. self.queue_player.put("R")
  339. self.state = 2
  340. elif self.state == 2:
  341. self.queue_player.put("Q")
  342. self.state = 3
  343. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  344. if self.state == 3:
  345. # Ok, time to exit
  346. # exit from this...
  347. self.game.to_player = self.to_player
  348. self.observer.load(self.save)
  349. self.save = None
  350. # self.game.portdata = self.portdata
  351. self.queue_game.put("\b \b\r\n")
  352. if not self.defer is None:
  353. self.defer.callback(self.game.gamedata.ports)
  354. self.defer = None
  355. def game_line(self, line: str):
  356. if line == "" or line == ": ":
  357. return
  358. if line == ": ENDINTERROG":
  359. return
  360. # This should be the CIM Report Data -- parse it
  361. if self.portcycle:
  362. if self.portcycle.click():
  363. self.queue_game.put("\b")
  364. if self.portcycle.again(1000):
  365. self.queue_game.put(".")
  366. self.queue_game.put(self.portcycle.cycle())
  367. work = line.replace("%", "")
  368. parts = re.split(r"(?<=\d)\s", work)
  369. if len(parts) == 8:
  370. port = int(parts[0].strip())
  371. data = dict()
  372. def portBS(info):
  373. if info[0] == "-":
  374. bs = "B"
  375. else:
  376. bs = "S"
  377. return (bs, int(info[1:].strip()))
  378. data["fuel"] = dict()
  379. data["fuel"]["sale"], data["fuel"]["units"] = portBS(parts[1])
  380. data["fuel"]["pct"] = int(parts[2].strip())
  381. data["org"] = dict()
  382. data["org"]["sale"], data["org"]["units"] = portBS(parts[3])
  383. data["org"]["pct"] = int(parts[4].strip())
  384. data["equ"] = dict()
  385. data["equ"]["sale"], data["equ"]["units"] = portBS(parts[5])
  386. data["equ"]["pct"] = int(parts[6].strip())
  387. # Store what this port is buying/selling
  388. data["port"] = (
  389. data["fuel"]["sale"] + data["org"]["sale"] + data["equ"]["sale"]
  390. )
  391. # Convert BBS/SBB to Class number 1-8
  392. data["class"] = CLASSES_PORT[data["port"]]
  393. self.game.gamedata.set_port(port, data)
  394. # self.portdata[port] = data
  395. else:
  396. log.error("CIMPortReport: {0} ???".format(line))
  397. def __del__(self):
  398. log.debug("CIMPortReport {0} RIP".format(self))
  399. def whenDone(self):
  400. self.defer = defer.Deferred()
  401. # Call this to chain something after we exit.
  402. return self.defer
  403. def player(self, chunk):
  404. """ Data from player (in bytes). """
  405. chunk = chunk.decode("latin-1", "ignore")
  406. key = chunk.upper()
  407. log.warn("CIMPortReport.player({0}) : I AM stopping...".format(key))
  408. # Stop the keepalive if we are activating something else
  409. # or leaving...
  410. # self.keepalive.stop()
  411. self.queue_game.put("\b \b\r\n")
  412. if not self.defer is None:
  413. # We have something, so:
  414. self.game.to_player = self.to_player
  415. self.observer.load(self.save)
  416. self.save = None
  417. self.defer.errback(Exception("User Abort"))
  418. self.defer = None
  419. else:
  420. # Still "exit" out.
  421. self.game.to_player = self.to_player
  422. self.observer.load(self.save)
  423. class ScriptPort(object):
  424. """ Performs the Port script.
  425. This is close to the original.
  426. We don't ask for the port to trade with --
  427. because that information is available to us after "D" (display).
  428. We look at the adjacent sectors, and see if we know any ports.
  429. If the ports are burnt (< 20%), we remove them from the list.
  430. We sort the best trades first.
  431. If there's just one, we use it. Otherwise we ask them to choose.
  432. We have options Trade_UseFirst, which uses the first one, if
  433. there is more then one.
  434. Option Trade_Turns, will use this as default turns, without
  435. asking.
  436. state = 1 "D"
  437. Get current sector, store possible warps.
  438. state = 2 Done with display sector.
  439. Look for possible trades.
  440. If possible, or only one, state to 3.
  441. state = 3 Command prompt, state = 4
  442. Prompt for port to trade with (If not Trade_UserFirst).
  443. Port found/given to trade with, state = 5, trade()
  444. trade() "PT"
  445. state = 5 "-----" in line, state = 6
  446. state = 6 "We are buying", "\r" (sell all!), state = 7
  447. "We are selling", state = 8
  448. state = 7 "Haggle sell"
  449. "We are buying", "\r" (sell all!), state = 7
  450. "We are selling", state = 8
  451. state = 8 "Haggle buy"
  452. Done, if times_left > 0 then
  453. move and state = 10
  454. """
  455. def __init__(self, game):
  456. self.game = game
  457. self.queue_game = game.queue_game
  458. self.queue_player = game.queue_player
  459. self.observer = game.observer
  460. self.r = Style.RESET_ALL
  461. self.nl = "\n\r"
  462. self.this_sector = None # Starting sector
  463. self.sector1 = None # Current Sector
  464. self.sector2 = None # Next Sector Stop
  465. self.percent = self.game.gamedata.get_config("Trade_Percent", "5")
  466. self.stop = self.game.gamedata.get_config("Trade_Stop", "10")
  467. try:
  468. self.stop = int(self.stop)
  469. except ValueError:
  470. self.stop = 10
  471. # Validate what we got from the config.
  472. # Not an int: make it 5, and update.
  473. # > 50, make it 5 and update.
  474. # < 0, make it 0 and update.
  475. update_config = False
  476. try:
  477. self.percent = int(self.percent)
  478. except ValueError:
  479. self.percent = 5
  480. update_config = True
  481. if self.percent > 50:
  482. self.percent = 5
  483. update_config = True
  484. if self.percent < 0:
  485. self.percent = 0
  486. update_config = True
  487. if update_config:
  488. self.game.gamedata.set_config("Trade_Percent", self.percent)
  489. self.credits = 0
  490. self.last_credits = None
  491. self.times_left = 0
  492. # Activate
  493. self.prompt = game.buffer
  494. self.save = self.observer.save()
  495. self.observer.connect("player", self.player)
  496. self.observer.connect("prompt", self.game_prompt)
  497. self.observer.connect("game-line", self.game_line)
  498. self.defer = None
  499. self.send2player(self.r + "Script based on: Port Pair Trading v2.00" + self.nl)
  500. self.possible_sectors = None
  501. self.state = 1
  502. self.queue_player.put("D")
  503. # Original, send 'D' to display current sector.
  504. # We could get the sector number from the self.prompt string -- HOWEVER:
  505. # IF! We send 'D', we can also get the sectors around -- we might not even need to
  506. # prompt for sector to trade with (we could possibly figure it out ourselves).
  507. # [Command [TL=00:00:00]:[967] (?=Help)? : D]
  508. # [<Re-Display>]
  509. # []
  510. # [Sector : 967 in uncharted space.]
  511. # [Planets : (M) Into the Darkness]
  512. # [Warps to Sector(s) : 397 - (562) - (639)]
  513. # []
  514. def whenDone(self):
  515. self.defer = defer.Deferred()
  516. # Call this to chain something after we exit.
  517. return self.defer
  518. def deactivate(self, andExit=True):
  519. self.state = 0
  520. log.debug("ScriptPort.deactivate ({0})".format(self.times_left))
  521. self.queue_game.put(self.nl + Boxes.alert("Trading Script deactivating..."))
  522. assert not self.save is None
  523. self.observer.load(self.save)
  524. self.save = None
  525. if self.defer:
  526. if andExit:
  527. self.defer.callback({"exit": True})
  528. else:
  529. self.defer.callback("done")
  530. self.defer = None
  531. def player(self, chunk: bytes):
  532. # If we receive anything -- ABORT!
  533. self.deactivate()
  534. def send2game(self, txt):
  535. log.debug("ScriptPort.send2game({0})".format(txt))
  536. self.queue_player.put(txt)
  537. def send2player(self, txt):
  538. log.debug("ScriptPort.send2player({0})".format(txt))
  539. self.queue_game.put(txt)
  540. def game_prompt(self, prompt: str):
  541. log.debug("{0} : {1}".format(self.state, prompt))
  542. if self.state == 3:
  543. # log.("game_prompt: ", prompt)
  544. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  545. self.state = 4
  546. log.debug("Ok, state 4")
  547. use_first = (
  548. self.game.gamedata.get_config("Trade_UseFirst", "N").upper()[0]
  549. == "Y"
  550. )
  551. if self.sector2 is None and use_first:
  552. # Use the first one by default
  553. self.sector2 = self.possible[0]
  554. log.info("default to {0}".format(self.sector2))
  555. if self.sector2 is None:
  556. # Ok, we need to prompt for this.
  557. self.queue_game.put(
  558. self.r
  559. + self.nl
  560. + "Which sector to trade with? {0}".format(
  561. GameData.port_show_part(
  562. self.this_sector,
  563. self.game.gamedata.ports[self.this_sector],
  564. )
  565. )
  566. + self.nl
  567. )
  568. for i, p in enumerate(self.possible):
  569. self.queue_game.put(
  570. " "
  571. + Fore.CYAN
  572. + str(i + 1)
  573. + " : "
  574. + GameData.port_show_part(p, self.game.gamedata.ports[p])
  575. + self.nl
  576. )
  577. pi = PlayerInput(self.game)
  578. def got_need1(*_):
  579. log.debug("Ok, I have: {0}".format(pi.keep))
  580. if pi.keep["count"].strip() == "":
  581. self.deactivate()
  582. return
  583. self.times_left = int(pi.keep["count"])
  584. if pi.keep["choice"].strip() == "":
  585. self.deactivate()
  586. return
  587. c = int(pi.keep["choice"]) - 1
  588. if c < 0 or c >= len(self.possible):
  589. self.deactivate()
  590. return
  591. self.sector2 = self.possible[int(pi.keep["choice"]) - 1]
  592. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  593. self.state = 5
  594. self.trade()
  595. d = pi.prompt("Choose -=>", 5, name="choice", digits=True)
  596. d.addCallback(
  597. lambda ignore: pi.prompt(
  598. "Times to execute script:", 5, name="count", digits=True
  599. )
  600. )
  601. d.addCallback(got_need1)
  602. else:
  603. # We already have our target port, so...
  604. self.queue_game.put(
  605. self.r
  606. + self.nl
  607. + "Trading from {0} ({1}) to default {2} ({3}).".format(
  608. self.this_sector,
  609. self.game.gamedata.ports[self.this_sector]["port"],
  610. self.sector2,
  611. self.game.gamedata.ports[self.sector2]["port"],
  612. )
  613. + self.nl
  614. )
  615. self.queue_game.put(
  616. self.r
  617. + self.nl
  618. + "Trading {0}".format(
  619. self.game.gamedata.port_trade_show(
  620. self.this_sector, self.sector2, 0
  621. )
  622. )
  623. )
  624. pi = PlayerInput(self.game)
  625. def got_need2(*_):
  626. if pi.keep["count"].strip() == "":
  627. self.deactivate()
  628. return
  629. self.times_left = int(pi.keep["count"])
  630. log.debug("Ok, I have: {0}".format(pi.keep))
  631. # self.queue_game.put(pformat(pi.keep).replace("\n", "\n\r"))
  632. self.state = 5
  633. self.trade()
  634. self.queue_game.put(self.r + self.nl)
  635. default_turns = self.game.gamedata.get_config("Trade_Turns", "0")
  636. if default_turns == "0":
  637. # No default given, ask.
  638. d = pi.prompt("Times to execute script", 5, name="count")
  639. d.addCallback(got_need2)
  640. else:
  641. try:
  642. self.times_left = int(default_turns)
  643. except ValueError:
  644. self.times_left = 30
  645. self.state = 5
  646. self.trade()
  647. elif self.state == 6:
  648. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  649. if self.end_trans:
  650. self.deactivate()
  651. return
  652. if self.fixable:
  653. # self.queue_game.put("Ok! Let's fix this by going to the other sector..." + self.nl)
  654. self.queue_game.put(
  655. self.nl
  656. + Boxes.alert(
  657. "Ok, FINE. We'll trade with the other port.",
  658. base="green",
  659. style=0,
  660. )
  661. )
  662. log.debug("Fixing...")
  663. # Swap this and other sector
  664. self.this_sector, self.other_sector = (
  665. self.other_sector,
  666. self.this_sector,
  667. )
  668. self.queue_player.put("{0}\r".format(self.sector2))
  669. self.state = 5
  670. self.trade()
  671. elif self.state == 7:
  672. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  673. # Done
  674. if self.end_trans:
  675. self.deactivate()
  676. return
  677. # Swap this and other sector
  678. self.this_sector, self.other_sector = (
  679. self.other_sector,
  680. self.this_sector,
  681. )
  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(
  694. self.r
  695. + self.nl
  696. + "We don't seem to be making any money here. I'm stopping!"
  697. + self.nl
  698. )
  699. self.deactivate()
  700. return
  701. self.queue_player.put("{0}\r".format(self.this_sector))
  702. self.state = 10
  703. elif re.match(r"Your offer \[\d+\] \?", prompt):
  704. log.info("Your offer? [{0}]".format(self.fix_offer))
  705. if self.fix_offer:
  706. # Make real offer / WHAT?@?!
  707. work = prompt.replace(",", "")
  708. parts = re.split(r"\s+", work)
  709. amount = parts[2]
  710. # Ok, we have the amount, now to figure pct...
  711. if self.sell_percent > 100:
  712. self.sell_percent -= 1
  713. else:
  714. self.sell_percent += 1
  715. price = amount * self.sell_percent // 100
  716. log.debug(
  717. "start: {0} % {1} price {2}".format(
  718. amount, self.sell_percent, price
  719. )
  720. )
  721. if self.sell_percent > 100:
  722. self.sell_percent -= 1
  723. else:
  724. self.sell_percent += 1
  725. self.queue_player.put("{0}\r".format(price))
  726. # elif re.match(r"How many holds of .+ do you want to sell \[\d+\]\?", prompt):
  727. # log.info("Sell everything we can...")
  728. # this seems to screw up the sync of everything.
  729. # self.queue_player.put("\r")
  730. elif self.state == 8:
  731. # What are we trading
  732. # How many holds of Equipment do you want to buy [75]?
  733. if re.match(r"How many holds of .+ do you want to buy \[\d+\]\?", prompt):
  734. parts = prompt.split()
  735. trade_type = parts[4]
  736. log.info("Buy {0} [{1} ~ {2}]".format(trade_type, self.tpc, self.opc))
  737. if trade_type == "Fuel":
  738. if (self.tpc in (5, 7)) and (self.opc in (2, 3, 4, 8)):
  739. # Can buy equipment - fuel ore is worthless.
  740. self.queue_player.put("0\r")
  741. return
  742. if (self.tpc in (4, 7)) and (self.opc in (1, 3, 5, 8)):
  743. # Can buy organics - fuel ore is worthless.
  744. self.queue_player.put("0\r")
  745. return
  746. if (self.tpc in (4, 7, 3, 5)) and (self.opc in (3, 4, 5, 7)):
  747. # No point in buying fuel ore if it can't be sold.
  748. self.queue_player.put("0\r")
  749. return
  750. elif trade_type == "Organics":
  751. if (self.tpc in (6, 7)) and (self.opc in (2, 3, 4, 8)):
  752. # Can buy equipment - organics is worthless.
  753. self.queue_player.put("0\r")
  754. return
  755. if (self.tpc in (2, 4, 6, 7)) and (self.opc in (2, 4, 6, 7)):
  756. # No point in buying organics if it can't be sold.
  757. self.queue_player.put("0\r")
  758. return
  759. elif trade_type == "Equipment":
  760. if self.opc in (1, 5, 6, 7):
  761. # No point in buying equipment if it can't be sold.
  762. self.queue_player.put("0\r")
  763. return
  764. self.queue_player.put("\r")
  765. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  766. # Done
  767. if self.end_trans:
  768. self.deactivate()
  769. return
  770. # Swap this and other sector
  771. self.this_sector, self.other_sector = (
  772. self.other_sector,
  773. self.this_sector,
  774. )
  775. if self.this_sector == self.sector2:
  776. self.times_left -= 1
  777. if self.times_left <= 0:
  778. # Ok, exit out
  779. self.deactivate()
  780. return
  781. if self.last_credits is None:
  782. self.last_credits = self.credits
  783. else:
  784. if self.credits <= self.last_credits:
  785. log.warn("We don't seem to be making any money here...")
  786. self.queue_game.put(
  787. self.r
  788. + self.nl
  789. + "We don't seem to be making any money here. I'm stopping!"
  790. + self.nl
  791. )
  792. self.deactivate()
  793. return
  794. self.queue_player.put("{0}\r".format(self.this_sector))
  795. self.state = 10
  796. elif self.state == 10:
  797. # DANGER! You have marked sector X to be avoided!
  798. if prompt.startswith("Do you really want to warp there? (Y/N) "):
  799. self.queue_player.put("Y")
  800. elif self.state == 99:
  801. # This is a good place to deactivate at.
  802. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  803. if hasattr(self, "message"):
  804. if self.message is not None:
  805. self.queue_game.put(self.message)
  806. self.message = None
  807. self.deactivate()
  808. def trade(self, *_):
  809. # state 5
  810. log.debug("trade!")
  811. self.queue_player.put("pt") # Port Trade
  812. self.end_trans = False
  813. self.fixable = False
  814. self.this_port = self.game.gamedata.ports[self.this_sector]
  815. # I think other_sector will alway be correct, but leaving this
  816. # for now. FUTURE: TODO: REMOVE
  817. if self.this_sector == self.sector1:
  818. self.other_sector = self.sector2
  819. else:
  820. self.other_sector = self.sector1
  821. self.other_port = self.game.gamedata.ports[self.other_sector]
  822. # Ok, perform some calculations
  823. self.tpc = self.this_port["class"]
  824. self.opc = self.other_port["class"]
  825. self.fixable = 0
  826. self.fix_offer = 0
  827. # [ Items Status Trading % of max OnBoard]
  828. # [ ----- ------ ------- -------- -------]
  829. # [Fuel Ore Selling 2573 93% 0]
  830. # [Organics Buying 2960 100% 0]
  831. # [Equipment Buying 1958 86% 0]
  832. # []
  833. # []
  834. # [You have 1,000 credits and 20 empty cargo holds.]
  835. # []
  836. # [We are selling up to 2573. You have 0 in your holds.]
  837. # [How many holds of Fuel Ore do you want to buy [20]? 0]
  838. def game_line(self, line: str):
  839. if line.startswith("You have ") and "credits and" in line:
  840. parts = line.replace(",", "").split()
  841. credits = int(parts[2])
  842. log.debug("Credits: {0}".format(credits))
  843. self.credits = credits
  844. if self.state == 1:
  845. # First exploration
  846. if line.startswith("Sector :"):
  847. # We have starting sector information
  848. parts = re.split(r"\s+", line)
  849. self.this_sector = int(parts[2])
  850. # These will be the ones swapped around as we trade back and forth.
  851. self.sector1 = self.this_sector
  852. elif line.startswith("Warps to Sector(s) : "):
  853. # Warps to Sector(s) : 397 - (562) - (639)
  854. _, _, warps = line.partition(":")
  855. warps = warps.replace("-", "").replace("(", "").replace(")", "").strip()
  856. log.debug("Warps: [{0}]".format(warps))
  857. self.warps = [int(x) for x in re.split(r"\s+", warps)]
  858. log.debug("Warps: [{0}]".format(self.warps))
  859. self.state = 2
  860. elif self.state == 2:
  861. if line == "":
  862. # Ok, we're done
  863. self.state = 3
  864. # Check to see if we have information on any possible ports
  865. # if hasattr(self.game, 'portdata'):
  866. if True:
  867. if not self.this_sector in self.game.gamedata.ports:
  868. self.state = 0
  869. log.debug(
  870. "Current sector {0} not in portdata.".format(
  871. self.this_sector
  872. )
  873. )
  874. self.queue_game.put(
  875. self.r
  876. + self.nl
  877. + "I can't find the current sector in the portdata."
  878. + self.nl
  879. )
  880. self.deactivate()
  881. return
  882. else:
  883. # Ok, we are in the portdata
  884. pd = self.game.gamedata.ports[self.this_sector]
  885. if GameData.port_burnt(pd):
  886. log.debug(
  887. "Current sector {0} port is burnt (<= 20%).".format(
  888. self.this_sector
  889. )
  890. )
  891. self.queue_game.put(
  892. self.r
  893. + self.nl
  894. + "Current sector port is burnt out. <= 20%."
  895. + self.nl
  896. )
  897. self.deactivate()
  898. return
  899. possible = [x for x in self.warps if x in self.game.gamedata.ports]
  900. log.debug("Possible: {0}".format(possible))
  901. # BUG: Sometimes links to another sector, don't link back!
  902. # This causes the game to plot a course / autopilot.
  903. # if hasattr(self.game, 'warpdata'):
  904. if True:
  905. # Great! verify that those warps link back to us!
  906. possible = [
  907. x
  908. for x in possible
  909. if x in self.game.gamedata.warps
  910. and self.this_sector in self.game.gamedata.warps[x]
  911. ]
  912. if len(possible) == 0:
  913. self.state = 0
  914. self.queue_game.put(
  915. self.r
  916. + self.nl
  917. + "I don't see any ports in [{0}].".format(self.warps)
  918. + self.nl
  919. )
  920. self.deactivate()
  921. return
  922. possible = [
  923. x
  924. for x in possible
  925. if not GameData.port_burnt(self.game.gamedata.ports[x])
  926. ]
  927. log.debug("Possible: {0}".format(possible))
  928. if len(possible) == 0:
  929. self.state = 0
  930. self.queue_game.put(
  931. self.r
  932. + self.nl
  933. + "I don't see any unburnt ports in [{0}].".format(
  934. self.warps
  935. )
  936. + self.nl
  937. )
  938. self.deactivate()
  939. return
  940. possible = [
  941. x
  942. for x in possible
  943. if GameData.port_trading(
  944. self.game.gamedata.ports[self.this_sector]["port"],
  945. self.game.gamedata.ports[x]["port"],
  946. )
  947. ]
  948. # sort by best, then by %
  949. start_port = self.game.gamedata.ports[self.this_sector]
  950. if "port" in start_port:
  951. start_port_port = start_port["port"]
  952. start_with = start_port_port[1:]
  953. if start_with in ("BS", "SB"):
  954. # Ok, good trades may be possible
  955. best = GameData.flip(start_with)
  956. log.info("Sorting the best ({0}) to the top.".format(best))
  957. log.info("{0}".format(possible))
  958. dec = [
  959. [
  960. self.game.gamedata.ports[p].get("port", "---")[1:]
  961. == best,
  962. p,
  963. ]
  964. for p in possible
  965. ]
  966. dec = sorted(dec, reverse=True)
  967. possible = [x[1] for x in dec]
  968. log.info("{0}".format(possible))
  969. self.possible = possible
  970. if len(possible) == 0:
  971. self.state = 0
  972. self.queue_game.put(
  973. self.r
  974. + self.nl
  975. + "I don't see any possible port trades in [{0}].".format(
  976. self.warps
  977. )
  978. + self.nl
  979. )
  980. self.deactivate()
  981. return
  982. elif len(possible) == 1:
  983. # Ok! there's only one!
  984. self.sector2 = possible[0]
  985. # Display possible ports:
  986. # spos = [ str(x) for x in possible]
  987. # self.queue_game.put(self.r + self.nl + self.nl.join(spos) + self.nl)
  988. # At state 3, we only get a prompt.
  989. return
  990. else:
  991. self.state = 0
  992. log.warn("We don't have any portdata!")
  993. self.queue_game.put(
  994. self.r
  995. + self.nl
  996. + "I have no portdata. Please run CIM Port Report."
  997. + self.nl
  998. )
  999. self.deactivate()
  1000. return
  1001. elif self.state == 5:
  1002. if "-----" in line:
  1003. self.state = 6
  1004. elif self.state == 6:
  1005. if "We are buying up to" in line:
  1006. log.info("buying up to -- so sell all")
  1007. # Sell
  1008. self.state = 7
  1009. self.queue_player.put("\r")
  1010. self.sell_percent = 100 + self.percent
  1011. if "We are selling up to" in line:
  1012. log.info("selling up to -- state 8 / set percent")
  1013. # Buy
  1014. self.state = 8
  1015. self.sell_percent = 100 - self.percent
  1016. if (
  1017. line.startswith("Fuel Ore")
  1018. or line.startswith("Organics")
  1019. or line.startswith("Equipment")
  1020. ):
  1021. work = line.replace("Fuel Ore", "Fuel").replace("%", "")
  1022. parts = re.split(r"\s+", work)
  1023. # log.debug(parts)
  1024. # Equipment, Selling xxx x% xxx
  1025. if parts[-1] != "0" and parts[2] != "0" and parts[1] != "Buying":
  1026. log.warn(
  1027. "We have a problem -- they aren't buying what we have in stock!"
  1028. )
  1029. stuff = line[0] # F O or E.
  1030. if self.game.gamedata.port_buying(self.other_sector, stuff):
  1031. log.info("fixable")
  1032. self.fixable = True
  1033. if int(parts[3]) < self.stop:
  1034. log.info("Port is burnt! % < {0} (end_trans)".format(self.stop))
  1035. self.end_trans = True
  1036. if "You don't have anything they want" in line:
  1037. log.warn("Don't have anything they want.")
  1038. # Neither! DRAT!
  1039. if not self.fixable:
  1040. self.state = 99
  1041. return
  1042. if "We're not interested." in line:
  1043. log.warn("Try, try again. :(")
  1044. self.state = 5
  1045. self.trade()
  1046. elif self.state == 7:
  1047. # Haggle Sell
  1048. if "We'll buy them for" in line or "Our final offer" in line:
  1049. if "Our final offer" in line:
  1050. self.sell_percent -= 1
  1051. parts = line.replace(",", "").split()
  1052. start_price = int(parts[4])
  1053. price = start_price * self.sell_percent // 100
  1054. log.debug(
  1055. "start: {0} % {1} price {2}".format(
  1056. start_price, self.sell_percent, price
  1057. )
  1058. )
  1059. self.sell_percent -= 1
  1060. self.queue_player.put("{0}\r".format(price))
  1061. if "We are selling up to" in line:
  1062. log.info("selling up to / state 8 / set percent")
  1063. # Buy
  1064. self.state = 8
  1065. self.sell_percent = 100 - self.percent
  1066. if "We are buying up to" in line:
  1067. log.info("buying up to -- so sell all")
  1068. # Sell
  1069. self.state = 7
  1070. self.queue_player.put("\r")
  1071. self.sell_percent = 100 + self.percent
  1072. if "We're not interested." in line:
  1073. log.info("Not interested. Try, try again. :(")
  1074. self.state = 5
  1075. self.trade()
  1076. if "WHAT?!@!? you must be crazy!" in line:
  1077. log.warn("fix offer")
  1078. self.fix_offer = 1
  1079. if "So, you think I'm as stupid as you look?" in line:
  1080. log.warn("fix offer")
  1081. self.fix_offer = 1
  1082. if "Quit playing around, you're wasting my time!" in line:
  1083. log.warn("fix offer")
  1084. self.fix_offer = 1
  1085. elif self.state == 8:
  1086. # Haggle Buy
  1087. if "We'll sell them for" in line or "Our final offer" in line:
  1088. if "Our final offer" in line:
  1089. self.sell_percent += 1
  1090. parts = line.replace(",", "").split()
  1091. start_price = int(parts[4])
  1092. price = start_price * self.sell_percent // 100
  1093. log.debug(
  1094. "start: {0} % {1} price {2}".format(
  1095. start_price, self.sell_percent, price
  1096. )
  1097. )
  1098. self.sell_percent += 1
  1099. self.queue_player.put("{0}\r".format(price))
  1100. if "We're not interested." in line:
  1101. log.info("Not interested. Try, try again. :(")
  1102. self.state = 5
  1103. self.trade()
  1104. elif self.state == 10:
  1105. if "Sector : " in line:
  1106. # Trade
  1107. self.state = 5
  1108. reactor.callLater(0, self.trade, 0)
  1109. # self.trade()
  1110. # elif self.state == 3:
  1111. # log.debug("At state 3 [{0}]".format(line))
  1112. # self.queue_game.put("At state 3.")
  1113. # self.deactivate()
  1114. # return
  1115. class ScriptExplore(object):
  1116. """ Exploration Script
  1117. WARNINGS:
  1118. We assume the player has lots o turns, or unlimited turns!
  1119. """
  1120. def __init__(self, game):
  1121. self.game = game
  1122. self.queue_game = game.queue_game
  1123. self.queue_player = game.queue_player
  1124. self.observer = game.observer
  1125. self.r = Style.RESET_ALL
  1126. self.c = merge(Style.BRIGHT + Fore.YELLOW)
  1127. self.nl = "\n\r"
  1128. # Our Stuff, Not our pants!
  1129. self.dense = [] # We did a density, store that info.
  1130. self.clear = [] # Warps that we know are clear.
  1131. self.highsector = 0 # Selected Sector to move to next!
  1132. self.highwarp = 0 # Selected Sector's Warp Count!
  1133. self.stacksector = (
  1134. []
  1135. ) # Set of sectors that we have not picked but are unexplored... even though we did a holo!
  1136. self.oneMoveSector = False
  1137. self.times = 0
  1138. self.maxtimes = 0
  1139. # Activate
  1140. self.prompt = game.buffer
  1141. self.save = self.observer.save()
  1142. self.observer.connect("player", self.player)
  1143. self.observer.connect("prompt", self.game_prompt)
  1144. self.observer.connect("game-line", self.game_line)
  1145. self.prefer_ports = (
  1146. self.game.gamedata.get_config("Explorer_PrefPorts", "N").upper()[0] == "Y"
  1147. )
  1148. self.defer = None
  1149. self.send2player(Boxes.alert("Explorer", base="green"))
  1150. # How many times we going to go today?
  1151. ask = PlayerInput(self.game)
  1152. def settimes(*_):
  1153. times = ask.keep["times"].strip()
  1154. log.debug("settimes got '{0}'".format(times))
  1155. if times == "":
  1156. self.deactivate()
  1157. else:
  1158. times = int(times)
  1159. self.times = times
  1160. self.maxtimes = times
  1161. self.send2game("D")
  1162. log.debug("times: {0} maxtimes: {0}".format(self.times))
  1163. self.state = 1
  1164. d = ask.prompt(
  1165. "How many sectors would you like to explorer?", 5, name="times", digits=True
  1166. )
  1167. # d.addCallback(ask.output)
  1168. # d.addCallback(lambda ignore: self.settimes(ask.keep))
  1169. d.addCallback(settimes)
  1170. def whenDone(self):
  1171. self.defer = defer.Deferred()
  1172. # Call this to chain something after we exit.
  1173. return self.defer
  1174. def deactivate(self, andExit=False):
  1175. self.state = 0
  1176. log.debug("ScriptExplore.deactivate()")
  1177. assert not self.save is None
  1178. self.observer.load(self.save)
  1179. self.save = None
  1180. if self.defer:
  1181. if andExit:
  1182. self.defer.callback({"exit": True})
  1183. else:
  1184. self.defer.callback("done")
  1185. self.defer = None
  1186. def player(self, chunk: bytes):
  1187. # If we receive anything -- ABORT!
  1188. self.deactivate(True)
  1189. def send2game(self, txt):
  1190. log.debug("ScriptExplore.send2game({0})".format(txt))
  1191. self.queue_player.put(txt)
  1192. def send2player(self, txt):
  1193. log.debug("ScriptExplore.send2player({0})".format(txt))
  1194. self.queue_game.put(txt)
  1195. def resetStuff(self):
  1196. self.dense = []
  1197. self.clear = []
  1198. self.highwarp = 0
  1199. self.highsector = 0
  1200. log.debug("ScriptExplore.resetStuff()")
  1201. def dead_end(self):
  1202. """ We've reached a dead end.
  1203. Either pop a new location to travel to, or give it up.
  1204. """
  1205. self.send2player(self.nl + Boxes.alert("** DEAD END **", base="blue"))
  1206. if self.stacksector:
  1207. # Ok, there's somewhere to go ...
  1208. self.highsector = self.stacksector.pop()
  1209. # travel state
  1210. self.state = 10
  1211. self.send2game("{0}\r".format(self.highsector))
  1212. else:
  1213. self.send2player(
  1214. self.nl
  1215. + Boxes.alert(
  1216. "I've run out of places to look ({0}/{1}).".format(
  1217. self.maxtimes - self.times, self.maxtimes
  1218. )
  1219. )
  1220. )
  1221. self.deactivate(True)
  1222. def game_over(self, msg):
  1223. self.send2player(
  1224. self.nl
  1225. + Boxes.alert(
  1226. "STOP: {0} ({1}/{2}).".format(
  1227. msg, self.maxtimes - self.times, self.maxtimes
  1228. )
  1229. )
  1230. )
  1231. self.deactivate()
  1232. def game_prompt(self, prompt: str):
  1233. log.debug("{0} : {1}".format(self.state, prompt))
  1234. if self.state == 2:
  1235. if "Select (H)olo Scan or (D)ensity Scan or (Q)uit" in prompt:
  1236. self.send2game("D")
  1237. # self.state += 1
  1238. elif self.state == 5:
  1239. log.debug("dense is {0} sectors big".format(len(self.dense)))
  1240. self.state += 1
  1241. self.send2game("SH")
  1242. elif self.state == 10:
  1243. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  1244. self.send2game("N")
  1245. elif self.state == 12:
  1246. # Looking for "Engage the Autopilot?"
  1247. if prompt.startswith("Engage the Autopilot? (Y/N/Single step/Express) [Y]"):
  1248. self.send2game("S")
  1249. self.travel_path.pop(0)
  1250. if prompt.startswith(
  1251. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]"
  1252. ):
  1253. self.send2game("SD")
  1254. self.state += 1
  1255. # Arriving sector :1691 Autopilot disengaging.
  1256. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1257. log.info("We made it to where we wanted to go!")
  1258. # can't init state 1, because we're at a prompt, so...
  1259. self.send2game("S")
  1260. self.state = 2
  1261. return
  1262. elif self.state == 15:
  1263. if prompt.startswith(
  1264. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]"
  1265. ):
  1266. self.send2game("N")
  1267. self.travel_path.pop(0)
  1268. self.state = 12
  1269. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1270. log.info("We made it to where we wanted to go!")
  1271. # can't init state 1, because we're at a prompt, so...
  1272. self.send2game("S")
  1273. self.state = 2
  1274. return
  1275. elif self.state == 20:
  1276. if prompt.startswith(
  1277. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N]"
  1278. ):
  1279. # Stop in this sector / Yes!
  1280. self.send2game("Y")
  1281. self.state = 1
  1282. # this should re-trigger a scan
  1283. def game_line(self, line: str):
  1284. log.debug("{0} | {1}".format(self.state, line))
  1285. # if "Mine Control" in line: # If we don't have a Holo-Scanner and we attempted to do a Holo-scan, abort
  1286. # self.deactivate()
  1287. if line.startswith("You don't have enough turns left."):
  1288. self.send2player(self.nl + Boxes.alert("You're out of turns!"))
  1289. self.deactivate(True)
  1290. return
  1291. if self.state == 1:
  1292. if line.startswith("You have ") and "turns left." in line:
  1293. # Ok, you're in trouble!
  1294. self.send2player(self.nl + Boxes.alert("You're running low on turns!"))
  1295. self.deactivate(True)
  1296. return
  1297. self.send2game("S")
  1298. self.state += 1
  1299. elif self.state == 2:
  1300. if "Relative Density Scan" in line:
  1301. self.state = 3
  1302. elif "You don't have a long range scanner." in line:
  1303. log.warn("FATAL: No Long Range Scanner Installed!")
  1304. self.send2player(Boxes.alert("You need a Long Range Scanner!"))
  1305. self.deactivate(True)
  1306. return
  1307. # elif "Long Range Scan" in line:
  1308. # self.state += 1
  1309. elif self.state == 3:
  1310. # Get the Density Data!
  1311. if line.startswith("Sector"):
  1312. new_sector = "(" in line
  1313. work = (
  1314. line.replace(":", "")
  1315. .replace(")", "")
  1316. .replace("(", "")
  1317. .replace("%", "")
  1318. .replace("==>", "")
  1319. )
  1320. work = re.split(r"\s+", work)
  1321. log.debug(work)
  1322. # 'Sector', '8192', '0', 'Warps', '4', 'NavHaz', '0', 'Anom', 'No'
  1323. # 'Sector', '(', '8192)', '0', 'Warps', '4', 'NavHaz', '0', 'Anom', 'No'
  1324. # New Sector?
  1325. if new_sector:
  1326. # Switch Anom into bool state
  1327. # if(work[8] == 'No'):
  1328. # temp = False
  1329. # else:
  1330. # temp = True
  1331. # self.dense.append( {'sector': int(work[1]), 'density': int(work[2]), 'warps': int(work[4]), 'navhaz': int(work[6]), 'anom': temp} )
  1332. self.dense.append(
  1333. {
  1334. "sector": int(work[1]),
  1335. "density": int(work[2]),
  1336. "warps": int(work[4]),
  1337. "navhaz": int(work[6]),
  1338. "anom": work[8] == "Yes",
  1339. }
  1340. )
  1341. log.debug(self.dense)
  1342. # {'sector': 8192, 'density': 0, 'warps': 4, 'navhaz': 0, 'anom': False}
  1343. elif line == "":
  1344. self.state += 1
  1345. # yeah, this would be better in the above line...
  1346. # leaving it for now.
  1347. # Which is why I broke the elif chain. ...
  1348. if self.state == 4:
  1349. # Begin Processing our data we got from density scan and find highest warp count in what sectors
  1350. # Remove sectors with one warp
  1351. log.debug("state 4: {0}".format(self.dense))
  1352. # Do we have a new place to go? (That is also worth going to)
  1353. if not self.dense: # Dense contains no new sectors, abort
  1354. log.info("No New Sectors Found!")
  1355. self.dead_end()
  1356. return
  1357. # Is the sector safe to go into?
  1358. # 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 ]
  1359. self.clear = [
  1360. x
  1361. for x in self.dense
  1362. if not x["anom"]
  1363. and not x["navhaz"]
  1364. and x["density"] in (0, 1, 100, 101)
  1365. ]
  1366. if self.clear: # We have sector(s) we can move to!
  1367. log.debug("Clear Sectors: {0}".format(len(self.clear)))
  1368. # 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 )
  1369. # Sort to find greatest warp count
  1370. if self.prefer_ports:
  1371. _, self.highwarp, self.highsector = max(
  1372. (x["density"], x["warps"], x["sector"]) for x in self.clear
  1373. )
  1374. else:
  1375. self.highwarp, self.highsector = max(
  1376. (x["warps"], x["sector"]) for x in self.clear
  1377. )
  1378. log.info(
  1379. "Sector: {0:5d} Warps: {1}".format(self.highsector, self.highwarp)
  1380. )
  1381. self.state += 1
  1382. else:
  1383. log.warn("No (safe) sectors to move to!")
  1384. # Let's try this?!
  1385. # self.dead_end() # NO!
  1386. self.game_over("No SAFE moves.")
  1387. # Another NOP state. This also could be merged into above.
  1388. # break the elif chain.
  1389. if self.state == 5:
  1390. # Add the dense scan of unknown sectors onto the stack of sectors, only save the ones we think are clear... for now.
  1391. for c in self.clear:
  1392. sector = c["sector"]
  1393. if sector != self.highsector:
  1394. if sector not in self.stacksector:
  1395. self.stacksector.append(sector)
  1396. # Or simply not add it in the first place ...
  1397. # 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!
  1398. # self.stacksector.discard(self.highsector)
  1399. # Ok, we need to decide to stop exploring -- before we
  1400. # issue the sector move! :P
  1401. #
  1402. # Warning! Yes we can and will eat all the turns! :P
  1403. if self.times == 0:
  1404. self.send2player(
  1405. Boxes.alert("Completed {0}".format(self.maxtimes), base="green")
  1406. )
  1407. log.info("Completed {0}".format(self.maxtimes))
  1408. self.deactivate()
  1409. return
  1410. self.times -= 1
  1411. # Ok we know the sector we want to go to now let's move it!
  1412. self.send2game("m{0}\r".format(self.highsector))
  1413. if self.highsector in self.stacksector:
  1414. log.info("Removing {0} from stacksector list.".format(self.highsector))
  1415. self.stacksector.remove(self.highsector)
  1416. # Reset Variables for fresh data
  1417. self.resetStuff()
  1418. self.state = 1
  1419. elif self.state == 10:
  1420. if line.startswith("You are already in that sector!"):
  1421. log.info("Already here. (Whoops!)")
  1422. self.state = 1
  1423. return
  1424. if line.startswith("Sector : {0}".format(self.highsector)):
  1425. log.info("We're here!")
  1426. # Ok, we're already there! no autopilot needed!
  1427. self.state = 1
  1428. return
  1429. # Warping
  1430. self.go_on = True
  1431. if line.startswith("The shortest path ("):
  1432. # Ok, we've got a path.
  1433. self.state += 1
  1434. self.travel_path = []
  1435. elif self.state == 11:
  1436. if line == "":
  1437. # The end of the (possibly) multiline warp.
  1438. self.state += 1
  1439. self.travel_path.pop(0) # First sector is one we're in.
  1440. self.stophere = False
  1441. self.go_on = True
  1442. else:
  1443. self.travel_path.extend(
  1444. line.replace("(", "").replace(")", "").split(" > ")
  1445. )
  1446. log.debug("Travel path: {0}".format(self.travel_path))
  1447. elif self.state == 12:
  1448. # Arriving sector :1691 Autopilot disengaging.
  1449. if "Autopilot disengaging." in line:
  1450. log.info("We made it to where we wanted to go!")
  1451. self.state = 1
  1452. return
  1453. elif self.state == 13:
  1454. if "Relative Density Scan" in line:
  1455. self.state += 1
  1456. elif self.state == 14:
  1457. if line == "":
  1458. log.debug("PATH: {0}".format(self.travel_path))
  1459. # end of the scan, decision time
  1460. if self.stophere:
  1461. log.info("STOPHERE")
  1462. # Ok, let's stop here!
  1463. # Re-save the sector we were trying to get to. (we didn't make it there)
  1464. if self.highsector not in self.stacksector:
  1465. self.stacksector.append(self.highsector)
  1466. self.state = 20
  1467. else:
  1468. if self.go_on:
  1469. log.info("GO ON")
  1470. # Ok, carry on!
  1471. self.state = 15
  1472. else:
  1473. log.warn("Our way is blocked...")
  1474. if self.highsector not in self.stacksector:
  1475. self.stacksector.append(self.highsector)
  1476. self.state = 20
  1477. else:
  1478. if line.strip("-") != "":
  1479. work = (
  1480. line.replace(" :", "")
  1481. .replace("%", "")
  1482. .replace(")", "")
  1483. .replace("==>", "")
  1484. )
  1485. # Does this contain something new? unseen?
  1486. stophere = "(" in work
  1487. work = work.replace("(", "")
  1488. # Sector XXXX DENS Warps N NavHaz P Anom YN
  1489. parts = re.split(r"\s+", work)
  1490. # Don't bother stopping if there's only one warp
  1491. # YES! Stop, even if there is just one warp!
  1492. # if stophere and parts[4] == '1':
  1493. # stophere = False
  1494. if stophere:
  1495. self.stophere = True
  1496. next_stop = self.travel_path[0]
  1497. log.debug(
  1498. "next_stop {0} from {1}".format(next_stop, self.travel_path)
  1499. )
  1500. log.debug("parts: {0}".format(parts))
  1501. if parts[1] == next_stop:
  1502. log.info("next_stop {0} found...".format(next_stop))
  1503. # Ok, this is our next stop. Is it safe to travel to?
  1504. if parts[2] not in ("100", "0", "1", "101"):
  1505. # Ok, it's not safe to go on.
  1506. self.go_on = False
  1507. # Check the rest navhav and anom ...
  1508. class ScriptSpace(object):
  1509. """ Space Exploration script.
  1510. Send "SD", verify paths are clear.
  1511. Find nearest unknown. Sector + CR.
  1512. Save "shortest path from to" information.
  1513. At 'Engage the Autopilot', Send "S" (Single Step)
  1514. At '[Stop in this sector', Send "SD", verify path is clear.
  1515. Send "SH" (glean sector/port information along the way.)
  1516. Send "N" (Next)!
  1517. Send "SD" / Verify clear.
  1518. Send "SH"
  1519. Repeat for Next closest.
  1520. """
  1521. def __init__(self, game):
  1522. self.game = game
  1523. self.queue_game = game.queue_game
  1524. self.queue_player = game.queue_player
  1525. self.observer = game.observer
  1526. self.r = Style.RESET_ALL
  1527. self.nl = "\n\r"
  1528. self.this_sector = None # Starting sector
  1529. self.target_sector = None # Sector going to
  1530. self.path = []
  1531. self.times_left = 0 # How many times to look for target
  1532. self.density = dict() # Results of density scan. (Just the numbers)
  1533. # Activate
  1534. self.prompt = game.buffer
  1535. self.save = self.observer.save()
  1536. self.observer.connect("player", self.player)
  1537. self.observer.connect("prompt", self.game_prompt)
  1538. self.observer.connect("game-line", self.game_line)
  1539. self.defer = None
  1540. self.queue_game.put(self.nl + "Bugz (like space), is big." + self.r + self.nl)
  1541. self.state = 1
  1542. self.queue_player.put("SD")
  1543. # Get current density scan + also get the current sector.
  1544. # [Command [TL=00:00:00]:[XXXX] (?=Help)? : D]
  1545. def whenDone(self):
  1546. self.defer = defer.Deferred()
  1547. # Call this to chain something after we exit.
  1548. return self.defer
  1549. def deactivate(self, andExit=False):
  1550. self.state = 0
  1551. log.debug("ScriptPort.deactivate ({0})".format(self.times_left))
  1552. assert not self.save is None
  1553. self.observer.load(self.save)
  1554. self.save = None
  1555. if self.defer:
  1556. if andExit:
  1557. self.defer.callback({"exit": True})
  1558. else:
  1559. self.defer.callback("done")
  1560. self.defer = None
  1561. def player(self, chunk: bytes):
  1562. # If we receive anything -- ABORT!
  1563. self.deactivate(True)
  1564. def unknown_search(self, starting_sector):
  1565. seen = set()
  1566. possible = set()
  1567. possible.add(int(starting_sector))
  1568. done = False
  1569. while not done:
  1570. next_possible = set()
  1571. for s in possible:
  1572. p = self.game.gamedata.get_warps(s)
  1573. if p is not None:
  1574. for pos in p:
  1575. if pos not in seen:
  1576. next_possible.add(pos)
  1577. else:
  1578. log.debug("unknown found: {0}".format(s))
  1579. self.unknown = s
  1580. done = True
  1581. break
  1582. seen.add(s)
  1583. if self.unknown is None:
  1584. log.debug("possible: {0}".format(next_possible))
  1585. possible = next_possible
  1586. yield
  1587. def find_unknown(self, starting_sector):
  1588. log.debug("find_unknown( {0})".format(starting_sector))
  1589. d = defer.Deferred()
  1590. # Process things
  1591. self.unknown = None
  1592. c = coiterate(self.unknown_search(starting_sector))
  1593. c.addCallback(lambda unknown: d.callback(self.unknown))
  1594. return d
  1595. def show_unknown(self, sector):
  1596. if sector is None:
  1597. self.deactivate()
  1598. return
  1599. log.debug("Travel to {0}...".format(sector))
  1600. self.queue_player.put("{0}\r".format(sector))
  1601. def game_prompt(self, prompt: str):
  1602. log.debug("{0} : {1}".format(self.state, prompt))
  1603. if self.state == 3:
  1604. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  1605. # this_sector code isn't working -- so! Get sector from prompt
  1606. self.state = 4
  1607. _, _, sector = prompt.partition("]:[")
  1608. sector, _, _ = sector.partition("]")
  1609. self.this_sector = int(sector)
  1610. # Ok, we're done with Density Scan, and we're back at the command prompt
  1611. log.debug("Go find the nearest unknown...")
  1612. d = self.find_unknown(sector)
  1613. d.addCallback(self.show_unknown)
  1614. elif self.state == 6:
  1615. # Engage the autopilot?
  1616. if prompt.startswith("Engage the Autopilot? (Y/N/Single step/Express) [Y]"):
  1617. self.state = 7
  1618. sector = self.path.pop(0)
  1619. if sector in self.density:
  1620. if self.density[sector] in (0, 100):
  1621. # Ok, looks safe!
  1622. self.queue_player.put("S")
  1623. self.this_sector = sector
  1624. else:
  1625. log.warn(
  1626. "FATAL: Density for {0} is {1}".format(
  1627. sector, self.density[sector]
  1628. )
  1629. )
  1630. self.deactivate(True)
  1631. return
  1632. else:
  1633. log.error(
  1634. "{0} not in density scan? (how's that possible?)".format(sector)
  1635. )
  1636. self.deactivate(True)
  1637. return
  1638. elif self.state == 7:
  1639. # Ok, we're in a new sector (single stepping through space)
  1640. # update density scan
  1641. if prompt.startswith(
  1642. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  1643. ):
  1644. self.queue_player.put("SD")
  1645. self.state = 8
  1646. elif self.state == 10:
  1647. # Because we're here
  1648. if prompt.startswith(
  1649. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  1650. ):
  1651. self.queue_player.put("SH")
  1652. self.state = 11
  1653. elif self.state == 11:
  1654. if prompt.startswith(
  1655. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  1656. ):
  1657. # Ok, is the density scan clear?
  1658. sector = self.path.pop(0)
  1659. if sector in self.density:
  1660. if self.density[sector] in (0, 100):
  1661. # Ok, looks safe
  1662. self.queue_player.put("N")
  1663. self.state = 7
  1664. self.this_sector = sector
  1665. else:
  1666. log.warn(
  1667. "FATAL: Density for {0} is {1}".format(
  1668. sector, self.density[sector]
  1669. )
  1670. )
  1671. self.deactivate()
  1672. return
  1673. else:
  1674. log.error(
  1675. "{0} not in density scane? (how's that possible...)".format(
  1676. sector
  1677. )
  1678. )
  1679. self.deactivate()
  1680. return
  1681. def next_unknown(self, sector):
  1682. log.info("Unknown is : {0}".format(sector))
  1683. self.deactivate()
  1684. def game_line(self, line: str):
  1685. log.debug("line {0} : {1}".format(self.state, line))
  1686. if line.startswith("Sector : "):
  1687. work = line.strip()
  1688. parts = re.split(r"\s+", work)
  1689. self.this_sector = int(parts[2])
  1690. log.debug("game_line sector {0}".format(self.this_sector))
  1691. elif line.startswith("Command [TL=]"):
  1692. # Ok, get the current sector from this
  1693. _, _, sector = line.partition("]:[")
  1694. sector, _, _ = sector.partition("]")
  1695. self.this_sector = int(sector)
  1696. log.debug("current sector: {0}".format(self.this_sector))
  1697. elif line.startswith("Warps to Sector(s) :"):
  1698. # Warps to Sector(s) : 5468
  1699. _, _, work = line.partition(":")
  1700. work = work.strip().replace("(", "").replace(")", "").replace(" - ", " ")
  1701. parts = [int(x) for x in work.split(" ")]
  1702. self.path = list(parts)
  1703. if self.state in (1, 8):
  1704. if "Relative Density Scan" in line:
  1705. # Start Density Scan
  1706. self.state += 1
  1707. self.density = {}
  1708. elif self.state in (2, 9):
  1709. if line == "":
  1710. # End of Density Scan
  1711. self.state += 1
  1712. log.debug("Density: {0}".format(self.density))
  1713. # self.deactivate()
  1714. elif line.startswith("Sector"):
  1715. # Parse Density Scan values
  1716. work = (
  1717. line.replace("(", "")
  1718. .replace(")", "")
  1719. .replace(":", "")
  1720. .replace("%", "")
  1721. .replace(",", "")
  1722. )
  1723. parts = re.split(r"\s+", work)
  1724. log.debug("Sector {0}".format(parts))
  1725. sector = int(parts[1])
  1726. self.density[sector] = int(parts[3])
  1727. if parts[7] != "0":
  1728. log.warn("NavHaz {0} : {1}".format(parts[7], work))
  1729. # navhaz already alters density
  1730. # self.density[sector] += 99
  1731. if parts[9] != "No":
  1732. log.warn("Anom {0} : {1}".format(parts[9], work))
  1733. # anom already alters density
  1734. self.density[sector] += 990
  1735. elif self.state == 4:
  1736. # Looking for shortest path message / warp info
  1737. # Or possibly, "We're here!"
  1738. if line.startswith("Sector :") and str(self.unknown) in line:
  1739. # Ok, I'd guess that we're already there!
  1740. # Try it again!
  1741. self.queue_player.put("SD")
  1742. self.state = 1
  1743. if line.startswith("The shortest path"):
  1744. self.state = 5
  1745. elif self.state == 5:
  1746. # This is the warps line
  1747. # Can this be multiple lines?
  1748. if line == "":
  1749. self.state = 6
  1750. else:
  1751. work = line.replace("(", "").replace(")", "").replace(">", "").strip()
  1752. self.path = [int(x) for x in work.split()]
  1753. log.debug("Path: {0}".format(self.path))
  1754. # Verify
  1755. current = self.path.pop(0)
  1756. if current != self.this_sector:
  1757. log.warn("Failed: {0} != {1}".format(current, self.this_sector))
  1758. self.deactivate()
  1759. return
  1760. elif self.state == 7:
  1761. if self.unknown == self.this_sector:
  1762. # We have arrived!
  1763. log.info("We're here!")
  1764. self.deactivate()
  1765. class ScriptTerror(object):
  1766. """ Terror script.
  1767. This uses the Port Trading script.
  1768. Basically, we look for the next best port trading pair.
  1769. Move to it, fire off the Port Trading script.
  1770. Repeat until our loop is done, or there's no more
  1771. pairs.
  1772. Terror_Use:
  1773. 'F' First available/Trade report ordering.
  1774. 'C' Closest trade pair
  1775. state=1 entered sector number to move to.
  1776. state=2 (route "The shortest path")
  1777. Fire ScriptPort. Callback journey_on.
  1778. """
  1779. def __init__(self, game, proxy, count):
  1780. self.game = game
  1781. self.queue_game = game.queue_game
  1782. self.queue_player = game.queue_player
  1783. self.proxy = proxy
  1784. self.count = count
  1785. self.observer = game.observer
  1786. self.r = Style.RESET_ALL
  1787. self.nl = "\n\r"
  1788. self.target_sector = None
  1789. # Activate
  1790. self.prompt = game.buffer
  1791. self.save = self.observer.save()
  1792. self.observer.connect("player", self.player)
  1793. self.observer.connect("prompt", self.game_prompt)
  1794. self.observer.connect("game-line", self.game_line)
  1795. self.state = 0
  1796. self.defer = None
  1797. # Verify that they have configured auto-port trading.
  1798. # Otherwise, this doesn't work!
  1799. usefirst = self.game.gamedata.get_config("Trade_UseFirst")
  1800. if usefirst is None:
  1801. usefirst = "?"
  1802. if usefirst.upper()[0] != "Y":
  1803. self.queue_game.put(
  1804. Boxes.alert("Sorry! You need to config Trade_UseFirst=Y", base="red")
  1805. )
  1806. self.deactivate()
  1807. return
  1808. turns = self.game.gamedata.get_config("Trade_Turns")
  1809. if turns is None:
  1810. turns = "0"
  1811. try:
  1812. t = int(turns)
  1813. except ValueError:
  1814. self.queue_game.put(
  1815. Boxes.alert("Sorry! You need to config Trade_Turns", base="red")
  1816. )
  1817. self.deactivate()
  1818. return
  1819. if t < 5:
  1820. self.queue_game.put(
  1821. Boxes.alert("Sorry! You need to config Trade_Turns >", base="red")
  1822. )
  1823. self.deactivate()
  1824. return
  1825. self.usewhich = self.game.gamedata.get_config("Terror_Use", "F").upper()[0]
  1826. if self.usewhich == "F":
  1827. c = coiterate(self.find_next_good_trade_pair())
  1828. c.addCallback(lambda unknown: self.scary())
  1829. return
  1830. if self.usewhich == "C":
  1831. # Where are we?
  1832. # Command [TL=00:00:00]:[10202] (?=Help)? :
  1833. prompt = self.game.getPrompt()
  1834. _, _, sector = prompt.partition("]:[")
  1835. sector, _, _ = sector.partition("]")
  1836. self.sector = int(sector)
  1837. log.info("find_nearest_tradepairs({0})".format(self.sector))
  1838. c = coiterate(self.game.gamedata.find_nearest_tradepairs(self.sector, self))
  1839. c.addCallback(lambda unknown: self.scary())
  1840. return
  1841. self.queue_game.put(Boxes.alert("What? What?", base="red"))
  1842. self.deactivate()
  1843. def scary(self):
  1844. if self.target_sector is None:
  1845. self.queue_game.put(
  1846. Boxes.alert("Sorry! I don't see any ports to trade with.", base="red")
  1847. )
  1848. self.deactivate()
  1849. else:
  1850. self.state = 1
  1851. self.queue_player.put("{0}\r".format(self.target_sector))
  1852. # This will have to be our best guess. :P
  1853. self.sector = self.target_sector
  1854. def find_next_good_trade_pair(self):
  1855. """ Find the next GOOD trade pair sector.
  1856. This is sequential (just like Trade report)
  1857. """
  1858. # This should be using the setting in the config, not a hard coded value!
  1859. # show_limit (galaxy.port_trade_show) is pct >= show_limit.
  1860. show_limit = self.game.gamedata.get_config("Display_Percent", "90")
  1861. try:
  1862. show_limit = int(show_limit)
  1863. except ValueError:
  1864. show_limit = 90
  1865. if show_limit < 15:
  1866. show_limit = 15
  1867. if show_limit > 90:
  1868. show_limit = 90
  1869. # Look for "GOOD" trades
  1870. for sector in sorted(self.game.gamedata.ports.keys()):
  1871. pd = self.game.gamedata.ports[sector]
  1872. if not GameData.port_burnt(pd):
  1873. # This happens when you trade with a StarDock
  1874. if "class" not in pd:
  1875. continue
  1876. pc = pd["class"]
  1877. # Ok, let's look into it.
  1878. if not sector in self.game.gamedata.warps:
  1879. continue
  1880. # Busted port
  1881. if not sector in self.game.gamedata.busts:
  1882. continue
  1883. warps = self.game.gamedata.warps[sector]
  1884. for w in warps:
  1885. # We can get there, and get back.
  1886. if (
  1887. w in self.game.gamedata.warps
  1888. and sector in self.game.gamedata.warps[w]
  1889. ):
  1890. # Ok, we can get there -- and get back!
  1891. if (
  1892. w > sector
  1893. and w in self.game.gamedata.ports
  1894. and not GameData.port_burnt(self.game.gamedata.ports[w])
  1895. ):
  1896. wd = self.game.gamedata.ports[w]
  1897. if "class" not in wd:
  1898. continue
  1899. wc = wd["class"]
  1900. if pc in (1, 5) and wc in (2, 4):
  1901. data = self.game.gamedata.port_trade_show(
  1902. sector, w, show_limit
  1903. )
  1904. if data:
  1905. self.target_sector = sector
  1906. return sector
  1907. elif pc in (2, 4) and wc in (1, 5):
  1908. data = self.game.gamedata.port_trade_show(
  1909. sector, w, show_limit
  1910. )
  1911. if data:
  1912. self.target_sector = sector
  1913. return sector
  1914. yield
  1915. # Look for OK trades
  1916. for sector in sorted(self.game.gamedata.ports.keys()):
  1917. pd = self.game.gamedata.ports[sector]
  1918. if not GameData.port_burnt(pd):
  1919. if "class" not in pd:
  1920. continue
  1921. pc = pd["class"]
  1922. # Ok, let's look into it.
  1923. if not sector in self.game.gamedata.warps:
  1924. continue
  1925. # Busted port
  1926. if not sector in self.game.gamedata.busts:
  1927. continue
  1928. warps = self.game.gamedata.warps[sector]
  1929. for w in warps:
  1930. # We can get there, and get back.
  1931. if (
  1932. w in self.game.gamedata.warps
  1933. and sector in self.game.gamedata.warps[w]
  1934. ):
  1935. # Ok, we can get there -- and get back!
  1936. if (
  1937. w > sector
  1938. and w in self.game.gamedata.ports
  1939. and not GameData.port_burnt(self.game.gamedata.ports[w])
  1940. ):
  1941. wd = self.game.gamedata.ports[w]
  1942. if "class" not in wd:
  1943. continue
  1944. wc = wd["class"]
  1945. if GameData.port_trading(
  1946. pd["port"], self.game.gamedata.ports[w]["port"]
  1947. ):
  1948. data = self.game.gamedata.port_trade_show(
  1949. sector, w, show_limit
  1950. )
  1951. if data:
  1952. self.target_sector = sector
  1953. return sector
  1954. yield
  1955. self.target_sector = None
  1956. def whenDone(self):
  1957. self.defer = defer.Deferred()
  1958. # Call this to chain something after we exit.
  1959. return self.defer
  1960. def deactivate(self, andExit=False):
  1961. self.state = 0
  1962. log.debug("ScriptTerror.deactivate")
  1963. assert not self.save is None
  1964. self.observer.load(self.save)
  1965. self.save = None
  1966. if self.defer:
  1967. if andExit:
  1968. self.defer.callback({"exit": True})
  1969. else:
  1970. self.defer.callback("done")
  1971. self.defer = None
  1972. def player(self, chunk: bytes):
  1973. # If we receive anything -- ABORT!
  1974. self.deactivate(True)
  1975. def journey_on(self, *_):
  1976. log.info("journey_on( {0})".format(self.count))
  1977. if self.count > 0:
  1978. self.count -= 1
  1979. if self.usewhich == "F":
  1980. c = coiterate(self.find_next_good_trade_pair())
  1981. c.addCallback(lambda unknown: self.scary())
  1982. return
  1983. if self.usewhich == "C":
  1984. log.info("find_nearest_tradepairs({0})".format(self.sector))
  1985. c = coiterate(
  1986. self.game.gamedata.find_nearest_tradepairs(self.sector, self)
  1987. )
  1988. c.addCallback(lambda unknown: self.scary())
  1989. return
  1990. self.deactivate()
  1991. # c = coiterate(self.find_next_good_trade_pair())
  1992. # c.addCallback(lambda unknown: self.scary())
  1993. # self.target_sector = self.proxy.find_next_good_trade_pair() # Sector going to
  1994. # self.state = 1
  1995. # self.queue_player.put("{0}\r".format(self.target_sector))
  1996. else:
  1997. self.deactivate()
  1998. def game_prompt(self, prompt: str):
  1999. log.debug("{0} : {1}".format(self.state, prompt))
  2000. if self.state == 1:
  2001. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  2002. self.queue_player.put("N")
  2003. elif self.state == 2:
  2004. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  2005. self.queue_player.put("N")
  2006. elif prompt.startswith(
  2007. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2008. ):
  2009. self.queue_player.put("E")
  2010. elif prompt.startswith(
  2011. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  2012. ):
  2013. self.queue_player.put("N")
  2014. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2015. # We should be where we wanted to.
  2016. ports = ScriptPort(self.game)
  2017. d = ports.whenDone()
  2018. d.addCallback(self.journey_on)
  2019. d.addErrback(self.journey_on)
  2020. def game_line(self, line: str):
  2021. log.debug("line {0} : {1}".format(self.state, line))
  2022. if self.state == 1:
  2023. if line.startswith("The shortest path ("):
  2024. self.state = 2
  2025. elif line.startswith("Warping to Sector "):
  2026. self.state = 2
  2027. elif line.startswith("You are already in that sector!"):
  2028. # Whoops.
  2029. ports = ScriptPort(self.game)
  2030. d = ports.whenDone()
  2031. d.addCallback(self.journey_on)
  2032. d.addErrback(self.journey_on)
  2033. class PlanetUpScript(object):
  2034. """
  2035. Planet Upgrade Script
  2036. state=1 Pulling TLQ (Team/Corp List Planets)
  2037. Pulling CYQ (Computer, Your planets)
  2038. state=2 'Personal Planet Scan' or 'Corporate Planet Scan' parse.
  2039. Display list of planets, and prompt user for planet number to upgrade.
  2040. state=3 Moving to planet
  2041. state=4 Landing on planet, parse planet contents. select 'C'
  2042. state=5 Parse requirements for next upgrade
  2043. state=6 move to next needed item. (Colonists, F, O, E)
  2044. If completed, 'L' and state=4
  2045. Otherwise move, fetch=ITEM, and state=7
  2046. state=7 travel to where we need something.
  2047. Once there, L (land) for Colonist, otherwise PT (Port trade)
  2048. state=8 Return to planet.
  2049. state=9 At planet, or in route.
  2050. Land. Transfer Colonists/Cargo. state=6
  2051. """
  2052. def __init__(self, game):
  2053. self.game = game
  2054. self.queue_game = game.queue_game
  2055. self.queue_player = game.queue_player
  2056. self.observer = game.observer
  2057. # Yes, at this point we would activate
  2058. self.prompt = game.buffer
  2059. self.save = self.observer.save()
  2060. self.nl = "\n\r"
  2061. self.cargo_index = {"F": 0, "O": 1, "E": 2}
  2062. self.index_cargo = ("F", "O", "E")
  2063. # I actually don't want the player input, but I'll grab it anyway.
  2064. self.observer.connect("player", self.player)
  2065. self.observer.connect("prompt", self.game_prompt)
  2066. self.observer.connect("game-line", self.game_line)
  2067. # If we want it, it's here.
  2068. self.defer = None
  2069. self.to_player = self.game.to_player
  2070. self.planets = {}
  2071. self.citadel = False
  2072. # Hide what's happening from the player
  2073. self.game.to_player = False
  2074. # self.queue_player.put("CYQ") # Computer -> Your Planets -> Quit
  2075. self.queue_player.put("TLQ") # Team/Corp -> List Corp Planets -> Quit
  2076. self.corp = True
  2077. self.state = 1
  2078. # self.warpdata = {}
  2079. self.queue_game.put(Boxes.alert("Let me see what I can see here..."))
  2080. def display_planets(self, justLocal=True):
  2081. tc = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  2082. c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  2083. c2 = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  2084. box = Boxes(44, color=tc)
  2085. self.queue_game.put(box.top())
  2086. # Are we just displaying local only? Then say so.
  2087. if justLocal:
  2088. planet_label = "LOCAL Planet Name"
  2089. else:
  2090. planet_label = "Planet Name"
  2091. self.queue_game.put(
  2092. box.row(
  2093. tc
  2094. + "{0:3} {1:6} {2:20} {3} ".format(
  2095. " # ", "Sector", planet_label, "Citadel LVL"
  2096. )
  2097. )
  2098. )
  2099. self.queue_game.put(box.middle())
  2100. def planet_output(number, sector, name, citadel):
  2101. row = "{0}{1:^3} {2:6} {3}{4:29} {0}{5:2} ".format(
  2102. c1, number, sector, c2, name, citadel
  2103. )
  2104. self.queue_game.put(box.row(row))
  2105. for s in sorted(self.planets.keys()):
  2106. if (justLocal == False) or (
  2107. self.current_sector == self.planets[s]["sector"]
  2108. ):
  2109. planet_output(
  2110. s,
  2111. self.planets[s]["sector"],
  2112. self.planets[s]["name"],
  2113. self.planets[s]["citadel"],
  2114. )
  2115. self.queue_game.put(box.bottom())
  2116. def game_prompt(self, prompt):
  2117. log.info("prompt {0} : {1}".format(self.state, prompt))
  2118. if self.state == 1 and re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2119. # Ok, you're not on a team. :P
  2120. self.queue_player.put("CYQ")
  2121. if self.state == 2 and re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2122. if self.corp:
  2123. self.corp = False
  2124. self.state = 1
  2125. self.queue_player.put("CYQ")
  2126. return
  2127. self.game.to_player = True
  2128. # For now we output the information, and exit
  2129. # self.queue_game.put("{0}\r\n".format(self.planets))
  2130. # self.queue_game.put(pformat(self.planets).replace("\n", self.nl) + self.nl)
  2131. if len(self.planets) == 0:
  2132. # Ok, this is easy.
  2133. self.queue_game.put(
  2134. self.nl
  2135. + Boxes.alert(
  2136. "You don't have any planets? You poor, poor dear.", base="red"
  2137. )
  2138. )
  2139. self.deactivate(True)
  2140. return
  2141. # I need this to know if I can just land, or need to move to the planet.
  2142. # Get current sector from the prompt
  2143. # Command [TL=00:00:00]:[10202] (?=Help)? :
  2144. _, _, part = prompt.partition("]:[")
  2145. sector, _, _ = part.partition("]")
  2146. self.current_sector = int(sector)
  2147. # A better default is to ask which planet to upgrade.
  2148. self.display_planets(True)
  2149. ask = PlayerInput(self.game)
  2150. d = ask.prompt("Choose a planet (L to List)", 3, name="planet")
  2151. d.addCallback(self.planet_chosen)
  2152. elif self.state == 3:
  2153. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  2154. self.queue_player.put("N")
  2155. elif prompt.startswith(
  2156. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2157. ):
  2158. self.queue_player.put("E")
  2159. elif prompt.startswith(
  2160. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  2161. ):
  2162. self.queue_player.put("N")
  2163. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2164. # We're here!
  2165. self.state = 4
  2166. self.queue_player.put("L")
  2167. elif self.state == 4:
  2168. # If you have a planet scanner, or if there's more then one planet.
  2169. if prompt.startswith("Land on which planet <Q to abort> ?"):
  2170. self.queue_player.put("{0}\r".format(self.planet_number))
  2171. if prompt.startswith("Planet command (?=help) [D] "):
  2172. # self.queue_game.put(self.nl + "{0} : {1}".format( self.colonists, self.cargo) + self.nl)
  2173. self.state = 5
  2174. self.queue_player.put("C")
  2175. elif self.state == 5:
  2176. if re.match(r"Do you wish to construct .+\?", prompt):
  2177. # 'Do you wish to construct a Combat Control Computer?'
  2178. # Have we met all the needs? If so, do it. ;)
  2179. # If not, xfer the cargo on the ship to the planet and get moving!
  2180. ready = True
  2181. for i in self.index_cargo: # ['F', 'O', 'E']:
  2182. if self.need[i] > self.cargo[i]:
  2183. ready = False
  2184. log.info("Need: {0}".format(i))
  2185. # break
  2186. if self.need["C"] > self.colonists:
  2187. log.info("Need: people")
  2188. ready = False
  2189. # self.queue_game.put(self.nl + "{0}".format(self.need))
  2190. if ready:
  2191. self.queue_game.put(self.nl + Boxes.alert("Party Planet Pants On!"))
  2192. self.queue_player.put("YQ")
  2193. if self.citadel:
  2194. # Need extra Quit to get out of citadel, then out of planet.
  2195. self.queue_player.put("Q")
  2196. self.deactivate(True)
  2197. return
  2198. if "construct one" in prompt:
  2199. # No, but start moving things around to build one.
  2200. self.queue_player.put("N")
  2201. else:
  2202. # No, and quit the Citadel menu.
  2203. self.queue_player.put("NQ")
  2204. # Xfer cargo, and get ready to travel...
  2205. elif prompt.startswith("Citadel command (?=help)"):
  2206. self.queue_player.put("U")
  2207. self.citadel = True
  2208. if prompt.startswith("Planet command (?=help) [D] "):
  2209. # self.queue_game.put(pformat(self.ship_cargo).replace("\n", self.nl) + self.nl)
  2210. # self.queue_game.put(pformat(self.cargo).replace("\n", self.nl) + self.nl)
  2211. for idx, c in enumerate(self.index_cargo): # ('F', 'O', 'E')):
  2212. if self.ship_cargo[c] > 0:
  2213. # Transfer Cargo, (No display), Leave [1,2, or 3], \r = All of it.
  2214. self.queue_player.put("TNL{0}\r".format(idx + 1))
  2215. self.cargo[c] += self.ship_cargo[c]
  2216. self.ship_cargo[c] = 0
  2217. return
  2218. break
  2219. self.queue_player.put("Q")
  2220. self.state = 6
  2221. # self.queue_game.put(pformat(self.ship_cargo).replace("\n", self.nl) + self.nl)
  2222. # self.queue_game.put(pformat(self.cargo).replace("\n", self.nl) + self.nl)
  2223. # self.deactivate()
  2224. elif self.state == 6:
  2225. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2226. # Ok, what do we need and where do we get it?
  2227. if self.need["C"] > self.colonists:
  2228. # NavPoint, T, Express
  2229. self.fetch = "C"
  2230. self.queue_player.put("NTE")
  2231. self.state = 7
  2232. self.send_land = False
  2233. else:
  2234. for i in ("F", "O", "E"):
  2235. if self.need[i] > self.cargo[i]:
  2236. self.fetch = i
  2237. # TODO: Make this a config setting.
  2238. place = self.game.gamedata.find_nearest_selling(
  2239. self.planet_sector, i, 400
  2240. )
  2241. if place == 0:
  2242. self.queue_game.put(
  2243. self.nl + Boxes.alert("Find Nearest Failed!")
  2244. )
  2245. self.deactivate(True)
  2246. return
  2247. self.queue_player.put("{0}\r".format(place))
  2248. # self.queue_player.put("{0}\rE".format(place))
  2249. self.state = 7
  2250. return
  2251. # Ok, upgrade time!
  2252. self.state = 4
  2253. self.queue_player.put("L")
  2254. # self.queue_game.put("No, not yet!" + self.nl)
  2255. # self.deactivate()
  2256. # for i in ['F', 'O', 'E']:
  2257. # if self.need[i] > self.cargo[i]:
  2258. # ready = False
  2259. # self.queue_game.put( "Need: {0}".format(i) + self.nl)
  2260. elif self.state == 7:
  2261. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2262. if self.fetch == "C":
  2263. # Colonist
  2264. # [How many groups of Colonists do you want to take ([125] empty holds) ? ]
  2265. if self.send_land == False:
  2266. self.send_land = True
  2267. self.queue_player.put("L") # "LT\r")
  2268. elif self.fetch in self.index_cargo: # ('F', 'O', 'E'):
  2269. # Port, Trade
  2270. self.queue_player.put("pt")
  2271. elif re.match(r"How many holds of .+ do you want to sell", prompt):
  2272. # This shouldn't occur...
  2273. self.queue_game.put("OH NOSE!" + self.nl)
  2274. self.deactivate(True)
  2275. return
  2276. elif prompt.startswith("Do you want to engage the TransWarp drive? "):
  2277. self.queue_player.put("N")
  2278. elif prompt.startswith(
  2279. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2280. ):
  2281. self.queue_player.put("E")
  2282. elif prompt.startswith(
  2283. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  2284. ):
  2285. self.queue_player.put("N")
  2286. elif re.match(
  2287. r"How many holds of .+ do you want to buy \[\d+\]\? ", prompt
  2288. ):
  2289. if prompt.startswith("How many holds of " + self.fetch):
  2290. _, _, holds = prompt.partition("[")
  2291. holds, _, _ = holds.partition("]")
  2292. self.fetch_amount = int(holds)
  2293. log.info("Buying {0} of {1}".format(holds, self.fetch))
  2294. # BTTZ.
  2295. if int(holds) == 0:
  2296. # FAIL-WHALE
  2297. self.queue_player.put("\r")
  2298. self.queue_game.put(
  2299. self.nl
  2300. + Boxes.alert(
  2301. "FAILURE: Just bought 0 holds! Out of money!?!",
  2302. base="red",
  2303. )
  2304. )
  2305. self.deactivate(True)
  2306. return
  2307. self.queue_player.put("\r\r")
  2308. self.state = 8
  2309. else:
  2310. # We don't want to buy this one. Skip it!
  2311. self.queue_player.put("0\r")
  2312. elif prompt.startswith("Land on which planet <Q to abort> ?"):
  2313. if self.fetch == "C":
  2314. self.queue_player.put("1\r")
  2315. elif prompt.startswith(
  2316. "Do you wish to (L)eave or (T)ake Colonists? [T] (Q to leave)"
  2317. ):
  2318. if self.fetch == "C":
  2319. self.queue_player.put("T\r")
  2320. self.state = 8
  2321. elif self.state == 8:
  2322. if re.match(r"How many holds of .+ do you want to buy \[\d+\]\? ", prompt):
  2323. # No, no, we're done buying!
  2324. self.queue_player.put("0\r")
  2325. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2326. # Ok, return to the planet...
  2327. _, _, part = prompt.partition("]:[")
  2328. sector, _, _ = part.partition("]")
  2329. if sector != self.planet_sector:
  2330. log.info("{0} is {1}".format(sector, self.planet_sector))
  2331. self.queue_player.put("{0}\rE".format(self.planet_sector))
  2332. else:
  2333. log.info("{0} is not {1}".format(sector, self.planet_sector))
  2334. self.queue_player.put("\r")
  2335. self.state = 9
  2336. elif self.state == 9:
  2337. log.info("prompt9 {0} : {1}".format(self.state, prompt))
  2338. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2339. # land
  2340. self.queue_player.put("L")
  2341. elif prompt.startswith("Do you want to engage the TransWarp drive? "):
  2342. self.queue_player.put("N")
  2343. elif prompt.startswith(
  2344. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2345. ):
  2346. self.queue_player.put("E")
  2347. elif prompt.startswith("Engage Express mode? (Y/N) [N] "):
  2348. self.queue_player.put("Y")
  2349. elif prompt.startswith(
  2350. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  2351. ):
  2352. self.queue_player.put("N")
  2353. elif prompt.startswith("Land on which planet <Q to abort> ?"):
  2354. self.queue_player.put("{0}\r".format(self.planet_number))
  2355. elif prompt.startswith("Planet command (?=help) [D]"):
  2356. if self.fetch == "C":
  2357. # Colonist / No display Planet / Leave
  2358. self.queue_player.put("SNL")
  2359. elif self.fetch in self.index_cargo: # ('F', 'O', 'E'):
  2360. # Cargo, No display / Leave
  2361. self.queue_player.put("TNL")
  2362. elif prompt.startswith("(1)Ore, (2)Org or (3)Equipment ?"):
  2363. self.queue_player.put(
  2364. "{0}\r\rQ".format(self.cargo_index[self.fetch] + 1)
  2365. )
  2366. self.cargo[self.fetch] += self.fetch_amount
  2367. self.state = 6
  2368. elif prompt.startswith("(1)Ore, (2)Org or (3)Equipment Production?"):
  2369. log.info("place_people: {0}".format(self.place_people))
  2370. safe_places = [
  2371. k for k in self.place_people.keys() if self.place_people[k]
  2372. ]
  2373. if len(safe_places) == 0:
  2374. # Ok, (GREAT) Class "U" Vaporous/Gaseus >:(
  2375. safe_places = ["F", "O", "E"]
  2376. # TO FIX: Use self.place_people to decide.
  2377. # Ok, I'd choose, but for right now.
  2378. log.info("Safe {0} index {1}".format(safe_places, self.place_start))
  2379. put_people = safe_places[self.place_start]
  2380. log.info("Use: {0}".format(put_people))
  2381. self.place_start += 1
  2382. if self.place_start >= len(safe_places):
  2383. self.place_start = 0
  2384. self.queue_player.put(
  2385. "{0}\r\rQ".format(self.cargo_index[put_people] + 1)
  2386. )
  2387. self.colonists += self.fetch_amount
  2388. self.state = 6
  2389. def planet_chosen(self, choice: str):
  2390. if choice.strip() == "":
  2391. self.deactivate()
  2392. elif choice.strip().upper()[0] == "L":
  2393. self.display_planets(False)
  2394. ask = PlayerInput(self.game)
  2395. d = ask.prompt("Choose a planet (L to List)", 3, name="planet")
  2396. d.addCallback(self.planet_chosen)
  2397. return
  2398. else:
  2399. try:
  2400. self.planet_number = int(choice)
  2401. except ValueError:
  2402. self.deactivate()
  2403. return
  2404. if self.planet_number in self.planets:
  2405. # Ok, this'll work
  2406. self.planet_sector = self.planets[self.planet_number]["sector"]
  2407. self.planet_name = self.planets[self.planet_number]["name"]
  2408. if self.current_sector == self.planet_sector:
  2409. # We're here. Land
  2410. self.state = 4
  2411. self.queue_player.put("L")
  2412. else:
  2413. # Get moving!
  2414. self.state = 3
  2415. self.queue_player.put("{0}\r".format(self.planet_sector))
  2416. else:
  2417. self.deactivate()
  2418. def game_line(self, line):
  2419. log.info("line {0} : {1}".format(self.state, line))
  2420. if self.state == 1:
  2421. if "Personal Planet Scan" in line or "Corporate Planet Scan" in line:
  2422. self.state = 2
  2423. elif self.state == 2:
  2424. # Ok, we're in the planet scan part of this
  2425. # 10202 #3 Home of Bugz Class M, Earth Type No Citadel]
  2426. if "#" in line:
  2427. # Ok, we have a planet detail line.
  2428. # There's an extra T when the planet is maxed out.
  2429. if line[7] == "T":
  2430. line = line[:7] + " " + line[8:]
  2431. details, _, citadel = line.partition("Class")
  2432. # log.info(details) # Is that what we are after?
  2433. # log.info(citadel)
  2434. sector, planet_number, name = re.split(r"\s+", details.strip(), 2)
  2435. sector = int(sector)
  2436. number = int(planet_number.replace("#", ""))
  2437. if "Level" in citadel:
  2438. cit = int(citadel[-1]) # Grab last item
  2439. else: # Oops, there looks like there is no citadel here.
  2440. cit = 0
  2441. self.last_seen = number
  2442. self.planets[number] = {"sector": sector, "name": name, "citadel": cit}
  2443. log.info(
  2444. "Planet # {0} in {1} called {2} with a lvl {3} citadel".format(
  2445. number, sector, name, cit
  2446. )
  2447. )
  2448. # detail, _, _ = line.partition('Class')
  2449. # detail = detail.strip() # Sector #X Name of planet
  2450. # sector, number, name = re.split(r'\s+', detail, 2)
  2451. # sector = int(sector)
  2452. # number = int(number[1:])
  2453. # self.last_seen = number
  2454. # self.planets[number] = {"sector": sector, "name": name}
  2455. # log.info("Planet # {0} in {1} called {2}".format( number, sector, name))
  2456. if "---" in line:
  2457. number = self.last_seen
  2458. # Ok, take the last_seen number, and use it for this line
  2459. # [ Sector Planet Name Ore Org Equ Ore Org Equ Fighters Citadel]
  2460. # [ Shields Population -=Productions=- -=-=-=-=-On Hands-=-=-=-=- Credits]
  2461. # [ 10202 #3 Home of Bugz Class M, Earth Type No Citadel]
  2462. # [ --- (1M) 144 49 26 145 75 12 10 0]
  2463. details = re.split(r"\s+", line.strip())
  2464. # OK: Likely, I'm not going to use these numbers AT ALL.
  2465. self.planets[number]["population"] = (
  2466. details[1].replace("(", "").replace(")", "")
  2467. )
  2468. # Ok, there's going to have to be some sort of modifier (K, M, etc.)
  2469. # to these numbers. Beware!
  2470. self.planets[number]["ore"] = details[5]
  2471. self.planets[number]["org"] = details[6]
  2472. self.planets[number]["equ"] = details[7]
  2473. elif self.state == 4:
  2474. # Combat Control Computer under construction, 4 day(s) till complete.
  2475. if "under construction, " in line and "day(s) till complete" in line:
  2476. # Ok, already building.
  2477. self.queue_game.put(self.nl + Boxes.alert("NO, NOT YET!") + self.nl)
  2478. self.queue_player.put("Q") # quit Planet menu.
  2479. self.deactivate(True)
  2480. return
  2481. # [ Item Colonists Colonists Daily Planet Ship Planet ]
  2482. # [Fuel Ore 0 1 0 0 0 1,000,000]
  2483. # [Organics 0 N/A 0 0 0 10,000]
  2484. # [Equipment 0 500 0 125 125 100,000]
  2485. # [Fighters N/A N/A 0 0 400 1,000,000]
  2486. items = ["Fuel Ore", "Organics", "Equipment"]
  2487. for i in items:
  2488. if line.startswith(i):
  2489. cargo = line[0].upper()
  2490. work = line.replace(",", "")
  2491. if i == "Fuel Ore":
  2492. work = work.replace(i, "Fuel")
  2493. self.colonists = 0
  2494. self.cargo = {}
  2495. self.need = {}
  2496. self.ship_cargo = {}
  2497. self.place_people = {}
  2498. parts = re.split(r"\s+", work)
  2499. log.info("parts: {0}".format(parts))
  2500. c = int(parts[1])
  2501. planet_has = int(parts[4])
  2502. self.colonists += c
  2503. self.cargo[cargo] = planet_has
  2504. self.ship_cargo[cargo] = int(parts[5])
  2505. # Boolean, can we place people here? If N/A, then don't!
  2506. self.place_people[cargo] = parts[2] != "N/A"
  2507. self.place_start = 0
  2508. elif self.state == 5:
  2509. # [Planet command (?=help) [D] C]
  2510. # [Be patient, your Citadel is not yet finished.]
  2511. if line.startswith("Be patient, your Citadel is not yet finished."):
  2512. # Ah HA!
  2513. self.queue_game.put(self.nl + Boxes.alert("NO, NOT YET!") + self.nl)
  2514. self.queue_player.put("Q") # quit Planet menu.
  2515. self.deactivate(True)
  2516. elif line.startswith("This Citadel cannot be upgraded further"):
  2517. self.queue_game.put(self.nl + Boxes.alert("NO MORE!") + self.nl)
  2518. self.queue_player.put("QQ") # quit Citadel, quit Planet menu.
  2519. self.deactivate(True)
  2520. else:
  2521. items = ["Colonists", "Fuel Ore", "Organics", "Equipment"]
  2522. work = line.replace(",", "").replace(" units of", "").strip()
  2523. # 800,000 Colonists to support the construction,
  2524. # 500 units of Fuel Ore,
  2525. # 300 units of Organics,
  2526. # 600 units of Equipment and
  2527. for i in items:
  2528. if i in line:
  2529. count = int(work.split()[0])
  2530. k = i[0].upper()
  2531. # keep colonists in same units.
  2532. if k == "C":
  2533. count //= 1000
  2534. self.need[k] = count
  2535. elif self.state == 8:
  2536. if re.match(
  2537. r"How many groups of Colonists do you want to take \(\[\d+\] empty holds\) \?",
  2538. line,
  2539. ):
  2540. # Ok, how many holds?
  2541. _, _, holds = line.partition("[")
  2542. holds, _, _ = holds.partition("]")
  2543. self.fetch_amount = int(holds)
  2544. if line.startswith("One turn deducted, "):
  2545. parts = line.split()
  2546. turns = int(parts[3])
  2547. if turns < 200:
  2548. self.queue_game.put(self.nl + Boxes.alert("LOW TURNS.") + self.nl)
  2549. self.deactivate(True)
  2550. elif self.state == 9:
  2551. if re.match(r"You have \d turns left.", line):
  2552. parts = line.split()
  2553. turns = int(parts[2])
  2554. log.debug("Turns: {0}".format(turns))
  2555. def __del__(self):
  2556. log.debug("PlanetUpScript {0} RIP".format(self))
  2557. def whenDone(self):
  2558. self.defer = defer.Deferred()
  2559. # Call this to chain something after we exit.
  2560. return self.defer
  2561. def deactivate(self, andExit=False):
  2562. self.state = 0
  2563. if not self.defer is None:
  2564. # We have something, so:
  2565. self.game.to_player = self.to_player
  2566. self.observer.load(self.save)
  2567. self.save = None
  2568. if andExit:
  2569. self.defer.callback({"exit": True})
  2570. else:
  2571. self.defer.callback("done")
  2572. self.defer = None
  2573. else:
  2574. # Still "exit" out.
  2575. self.game.to_player = self.to_player
  2576. self.observer.load(self.save)
  2577. def player(self, chunk):
  2578. """ Data from player (in bytes). """
  2579. chunk = chunk.decode("latin-1", "ignore")
  2580. key = chunk.upper()
  2581. log.warn(
  2582. "PlanetUpScript.player({0}) : I AM stopping...(user input)".format(key)
  2583. )
  2584. if not self.defer is None:
  2585. # We have something, so:
  2586. self.game.to_player = self.to_player
  2587. self.observer.load(self.save)
  2588. self.save = None
  2589. self.defer.errback(Exception("User Abort"))
  2590. self.defer = None
  2591. else:
  2592. # Still "exit" out.
  2593. self.game.to_player = self.to_player
  2594. self.observer.load(self.save)
  2595. class ColoScript2(object):
  2596. """ ColoScript 2.0
  2597. Goal:
  2598. * Allow a player to move people from anywhere to anywhere. (Acheived!)
  2599. States:
  2600. 1 = Computer talks with us giving corp and personal planet info
  2601. 2 = Grab's data and asks series of questions, TO, FROM, LOOPS
  2602. 3 = Move to FROM
  2603. 4 = Identify Population Categories / Is it terra?
  2604. 5 = Load People, then move to planet TO
  2605. 6 = Init Land on TO
  2606. 7 = Decide to loop (Jump back to 3), Unload People
  2607. """
  2608. def __init__(self, game):
  2609. self.game = game
  2610. self.queue_game = game.queue_game
  2611. self.queue_player = game.queue_player
  2612. self.observer = game.observer
  2613. self.r = Style.RESET_ALL
  2614. self.c = merge(Style.BRIGHT + Fore.YELLOW)
  2615. self.nl = "\n\r"
  2616. # My "stuff" in my pants!
  2617. self.state = 1
  2618. self.corp = True
  2619. self.planets = {}
  2620. self.loops = 0
  2621. self.maxloops = 0
  2622. self.last_seen = 0
  2623. self.valid2From = True
  2624. # Sector Numbers of Planets TO and FROM
  2625. self.TO = 0
  2626. self.FROM = 0
  2627. # Planet Numbers of Planets TO and FROM
  2628. self.to_number = 0
  2629. self.from_number = 0
  2630. # Activate
  2631. self.prompt = game.buffer
  2632. self.save = self.observer.save()
  2633. self.observer.connect("player", self.player)
  2634. self.observer.connect("prompt", self.game_prompt)
  2635. self.observer.connect("game-line", self.game_line)
  2636. self.defer = None
  2637. self.to_player = self.game.to_player
  2638. self.game.to_player = False
  2639. self.send2game("TLQ")
  2640. def whenDone(self):
  2641. self.defer = defer.Deferred()
  2642. return self.defer
  2643. def deactivate(self, andExit=False):
  2644. self.state = 0
  2645. log.debug("ColoScript2.deactivate()")
  2646. self.game.to_player = True
  2647. assert not self.save is None
  2648. self.observer.load(self.save)
  2649. self.save = None
  2650. if self.defer:
  2651. if andExit:
  2652. self.defer.callback({"exit": True})
  2653. else:
  2654. self.defer.callback("done")
  2655. self.defer = None
  2656. def player(self, chunk: bytes):
  2657. self.deactivate(True)
  2658. def send2game(self, txt):
  2659. # Removed debug info since I use this to also display Boxes.alert
  2660. self.queue_player.put(txt)
  2661. def send2player(self, txt):
  2662. # Removed debug since it really doesn't help with anything
  2663. self.queue_game.put(txt)
  2664. def to_chosen(self, choice: str):
  2665. if choice.strip() == "":
  2666. self.deactivate(True)
  2667. else:
  2668. self.to_number = int(choice)
  2669. if self.to_number == 1:
  2670. # Oooh Terra!
  2671. self.TO = 1
  2672. self.planet_name = "Terra"
  2673. ask1 = PlayerInput(self.game)
  2674. d1 = ask1.prompt("From: ", 3, name="from", digits=True)
  2675. d1.addCallback(self.from_chosen)
  2676. elif self.to_number in self.planets:
  2677. # Ok, this'll work
  2678. self.TO = self.planets[self.to_number]["sector"]
  2679. self.planet_name = self.planets[self.to_number]["name"]
  2680. # Are we really getting this? Yup
  2681. # log.debug("TO Planet Number: {0} Sector: {1}".format(self.to_number, self.TO))
  2682. ask1 = PlayerInput(self.game)
  2683. d1 = ask1.prompt("From: ", 3, name="from", digits=True)
  2684. d1.addCallback(self.from_chosen)
  2685. else:
  2686. self.deactivate(True)
  2687. def from_chosen(self, choice: str):
  2688. if choice.strip() == "":
  2689. self.deactivate(True)
  2690. else:
  2691. self.from_number = int(choice)
  2692. if self.from_number == self.to_number:
  2693. # Prevent the user from asking to move folks to the same planet they would be getting
  2694. # them from. See Recursion!
  2695. self.valid2From = False
  2696. if self.from_number in self.planets:
  2697. # Ok, this'll work
  2698. self.FROM = self.planets[self.from_number]["sector"]
  2699. self.planet_name = self.planets[self.from_number]["name"]
  2700. # Are we really getting this? Yup Yup
  2701. # log.debug("FROM Planet Number: {0} Sector: {1}".format(self.from_number, self.FROM))
  2702. ask1 = PlayerInput(self.game)
  2703. d1 = ask1.prompt("How many times ", 3, name="rolls", digits=True)
  2704. d1.addCallback(self.loop_chosen)
  2705. elif self.from_number == 1:
  2706. # Yup handle if the user picks to pull from Terra
  2707. self.FROM = 1
  2708. ask1 = PlayerInput(self.game)
  2709. d1 = ask1.prompt("How many times ", 3, name="rolls", digits=True)
  2710. d1.addCallback(self.loop_chosen)
  2711. else:
  2712. self.deactivate(True)
  2713. def loop_chosen(self, choice: str):
  2714. if choice.strip() == "":
  2715. self.deactivate(True)
  2716. else:
  2717. self.loops = abs(int(choice))
  2718. if self.loops == 0:
  2719. self.loops = 1
  2720. self.maxloops = self.loops
  2721. # ask2 = PlayerInput(self.game)
  2722. # d2 = ask2.prompt("From? ", 5, name="from", digits=True)
  2723. # d2.addCallback(self.from_chosen)
  2724. if self.valid2From:
  2725. self.state = 3
  2726. self.game.to_player = False
  2727. self.send2game("I")
  2728. else:
  2729. self.game.to_player = True
  2730. self.send2player(
  2731. self.nl
  2732. + Boxes.alert(
  2733. "Completed ({0}) Wow, it's like nothing happened.".format(
  2734. self.maxloops
  2735. )
  2736. )
  2737. )
  2738. self.deactivate(True)
  2739. def game_prompt(self, prompt: str):
  2740. log.debug("P {0} | {1}".format(self.state, prompt))
  2741. if self.state == 1 and re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2742. # Ok, you're not on a team. :P
  2743. self.queue_player.put("CYQ")
  2744. if self.state == 2 and re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2745. if self.corp:
  2746. self.corp = False
  2747. self.state = 1
  2748. self.queue_player.put("CYQ")
  2749. return
  2750. self.game.to_player = True
  2751. # For now we output the information, and exit
  2752. # self.queue_game.put("{0}\r\n".format(self.planets))
  2753. # self.queue_game.put(pformat(self.planets).replace("\n", self.nl) + self.nl)
  2754. if len(self.planets) == 0:
  2755. # Ok, this is easy.
  2756. self.queue_game.put(
  2757. self.nl
  2758. + Boxes.alert(
  2759. "You don't have any planets? You poor, poor dear.", base="red"
  2760. )
  2761. )
  2762. self.deactivate(True)
  2763. return
  2764. # I need this to know if I can just land, or need to move to the planet.
  2765. # Get current sector from the prompt
  2766. # Command [TL=00:00:00]:[10202] (?=Help)? :
  2767. _, _, part = prompt.partition("]:[")
  2768. sector, _, _ = part.partition("]")
  2769. self.current_sector = int(sector)
  2770. # A better default is to ask which planet to upgrade.
  2771. tc = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  2772. c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  2773. c2 = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  2774. box = Boxes(44, color=tc)
  2775. self.queue_game.put(box.top())
  2776. self.queue_game.put(
  2777. box.row(
  2778. tc
  2779. + "{0:3} {1:6} {2:20} {3} ".format(
  2780. " # ", "Sector", "Planet Name", "Citadel LVL"
  2781. )
  2782. )
  2783. )
  2784. self.queue_game.put(box.middle())
  2785. def planet_output(number, sector, name, citadel):
  2786. row = "{0}{1:^3} {2:6} {3}{4:29} {0}{5:2} ".format(
  2787. c1, number, sector, c2, name, citadel
  2788. )
  2789. self.queue_game.put(box.row(row))
  2790. # Manually add in Terra so a player knows about it,
  2791. # Since we now support Terra being TO or FROM.
  2792. terra = row = "{0}{1:^3} {2:6} {3}{4:29} {0}{5:2} ".format(
  2793. c1, 1, 1, c2, "Terra", " "
  2794. )
  2795. self.send2player(box.row(terra))
  2796. for s in sorted(self.planets.keys()):
  2797. planet_output(
  2798. s,
  2799. self.planets[s]["sector"],
  2800. self.planets[s]["name"],
  2801. self.planets[s]["citadel"],
  2802. )
  2803. self.queue_game.put(box.bottom())
  2804. ask = PlayerInput(self.game)
  2805. d = ask.prompt("To: ", 3, name="planet", digits=True)
  2806. d.addCallback(self.to_chosen)
  2807. elif self.state == 3:
  2808. # Initalize moving to sector 1, Terra
  2809. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  2810. self.queue_player.put("N")
  2811. elif prompt.startswith(
  2812. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2813. ):
  2814. self.queue_player.put("E")
  2815. elif prompt.startswith(
  2816. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  2817. ):
  2818. self.queue_player.put("N")
  2819. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2820. self.state = 4
  2821. self.game.to_player = True
  2822. self.send2game("{0}\r".format(self.FROM))
  2823. # Move to planet FROM
  2824. elif self.state == 4:
  2825. # Are we there yet? (NNY)
  2826. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  2827. self.queue_player.put("N")
  2828. elif prompt.startswith(
  2829. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2830. ):
  2831. self.queue_player.put("E")
  2832. elif prompt.startswith(
  2833. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  2834. ):
  2835. self.queue_player.put("N")
  2836. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2837. self.state = 5
  2838. self.send2game("L")
  2839. elif self.state == 5:
  2840. if self.FROM == 1: # Is our FROM planet Terra?
  2841. if prompt.startswith("Land on which planet <Q to abort> ?"):
  2842. self.send2game("1\r\r\r{0}\r".format(self.TO))
  2843. self.state = 6
  2844. # Planetary Scanner Detected, Move to sector with planet
  2845. elif prompt.startswith(
  2846. "Do you wish to (L)eave or (T)ake Colonists? [T] (Q to leave)"
  2847. ):
  2848. self.send2game("\r\r{0}\r".format(self.TO))
  2849. self.state = 6
  2850. # No Planetary Scanner Detected, Move to sector with planet
  2851. elif prompt.startswith(
  2852. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2853. ):
  2854. self.send2game("E")
  2855. self.state = 4
  2856. # We are not at terra, go back
  2857. else:
  2858. if prompt.startswith("Land on which planet <Q to abort> ?"):
  2859. self.send2game(
  2860. "{0}\rs\rt1\rq{1}\r".format(self.from_number, self.TO)
  2861. )
  2862. self.state = 6
  2863. # Planetary Scanner Detected, Move to sector with planet
  2864. elif prompt.startswith("Planet command (?=help) [D] "):
  2865. self.send2game("s\rt1\rq{0}\r".format(self.TO))
  2866. self.state = 6
  2867. elif prompt.startswith(
  2868. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2869. ):
  2870. self.send2game("E")
  2871. self.state = 4
  2872. # We are not at terra, go back
  2873. elif self.state == 6:
  2874. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  2875. self.queue_player.put("N")
  2876. elif prompt.startswith(
  2877. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  2878. ):
  2879. self.queue_player.put("E")
  2880. elif prompt.startswith(
  2881. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  2882. ):
  2883. self.queue_player.put("N")
  2884. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  2885. self.state = 6
  2886. self.send2game("L")
  2887. if self.TO != 1:
  2888. if prompt.startswith("Land on which planet <Q to abort> ?"):
  2889. self.state = 7
  2890. self.send2game("{0}\r".format(self.to_number))
  2891. # Planetary Scanner Detected selecting planet number
  2892. elif prompt.startswith("Planet command (?=help) [D] "):
  2893. self.state = 7
  2894. self.send2game("D")
  2895. # No Planetary Scaner skip on
  2896. else:
  2897. if prompt.startswith("Land on which planet <Q to abort> ?"):
  2898. self.state = 7
  2899. self.send2game("1\r")
  2900. # Planetary Scanner Detected selecting planet number
  2901. elif prompt.startswith("Do you wish to (L)eave or (T)ake Colonists?"):
  2902. self.state = 7
  2903. self.send2game("L")
  2904. # No Planetary Scaner skip on
  2905. elif self.state == 7:
  2906. if prompt.startswith("Engage the Autopilot? (Y/N/Single step/Express) [Y]"):
  2907. self.state = 6
  2908. self.send2game("E")
  2909. # Missed moving jump back to state 6
  2910. if self.TO != 1:
  2911. if prompt.startswith("Land on which planet <Q to abort> ?"):
  2912. self.queue_player.put("{0}\r".format(self.to_number))
  2913. # Planetary Scanner Detected selecting planet number
  2914. if prompt.startswith("Planet command (?=help) [D] "):
  2915. # Unload people and process the loop
  2916. self.loops -= 1
  2917. if self.loops:
  2918. self.state = 3
  2919. self.send2game("S\r\r1\rQ")
  2920. # Jump to state 3 we are not done
  2921. else:
  2922. self.send2game("S\r\r1\rQ")
  2923. self.send2player(
  2924. "\r" + Boxes.alert("Completed ({0})".format(self.maxloops))
  2925. )
  2926. self.deactivate(True)
  2927. # Ok we are done
  2928. else:
  2929. if prompt.startswith("Land on which planet <Q to abort> ?"):
  2930. self.queue_player.put("1\r")
  2931. # Planetary Scanner Detected selecting planet number
  2932. if prompt.startswith("Do you wish to (L)eave or (T)ake Colonists?"):
  2933. # Unload people and process the loop
  2934. self.loops -= 1
  2935. if self.loops:
  2936. self.state = 3
  2937. self.send2game("L\r")
  2938. # Jump to state 3 we are not done
  2939. else:
  2940. self.send2game("L\r")
  2941. self.send2player(
  2942. "\r" + Boxes.alert("Completed ({0})".format(self.maxloops))
  2943. )
  2944. self.deactivate(True)
  2945. # Ok we are done
  2946. if prompt.startswith(
  2947. "How many groups of Colonists do you want to leave"
  2948. ):
  2949. self.loops -= 1
  2950. if self.loops:
  2951. self.state = 3
  2952. self.send2game("\r")
  2953. # Jump to state 3 we are not done
  2954. else:
  2955. self.send2game("\r")
  2956. self.send2player(
  2957. "\r" + Boxes.alert("Completed ({0})".format(self.maxloops))
  2958. )
  2959. self.deactivate(True)
  2960. # Ok we are done
  2961. def game_line(self, line: str):
  2962. log.debug("L {0} | {1}".format(self.state, line))
  2963. # IF at any state we see turns left lets grab it
  2964. if "turns left" in line:
  2965. work = line[19:].replace(" turns left.", "").strip()
  2966. self.turns = work
  2967. log.debug("TURNS LEFT: {0}".format(self.turns))
  2968. if int(self.turns) < 200:
  2969. self.send2player(
  2970. "\r" + Boxes.alert("Low Turns! ({0})".format(self.turns))
  2971. )
  2972. self.deactivate(True)
  2973. # IF at any state we see how many holds avalible let's get that
  2974. if "Total Holds" in line:
  2975. work = line[16:].replace("-", "").replace("=", " ").split()
  2976. self.total_holds = int(work[0])
  2977. count = 0
  2978. seen_empty = False
  2979. for w in work:
  2980. if w != "Empty":
  2981. count += 1
  2982. elif w == "Empty":
  2983. count += 1
  2984. self.holds = int(work[count])
  2985. log.debug("EMPTY HOLDS = {0}".format(self.holds))
  2986. self.holds_percent = int((self.holds / self.total_holds) * 100.0)
  2987. log.debug("HOLDS PERCENT = {0}%".format(self.holds_percent))
  2988. seen_empty = True
  2989. if self.holds < self.total_holds:
  2990. self.send2player(
  2991. "\r"
  2992. + Boxes.alert(
  2993. "You need {0} holds empty! ({1} Empty)".format(
  2994. self.total_holds, self.holds
  2995. )
  2996. )
  2997. )
  2998. self.deactivate(True)
  2999. if (
  3000. seen_empty != True
  3001. ): # Just incase we didn't see Empty... meaning we have 0 avalible holds
  3002. self.send2player(
  3003. "\r"
  3004. + Boxes.alert(
  3005. "You need {0} holds empty! (0 Empty)".format(self.total_holds)
  3006. )
  3007. )
  3008. self.deactivate(True)
  3009. if "There aren't that many on the planet!" in line:
  3010. self.send2player("\r" + Boxes.alert("We're missing people!"))
  3011. self.deactivate(True)
  3012. if "There isn't room on the planet for that many!" in line:
  3013. self.send2player("\r" + Boxes.alert("We have to many people!"))
  3014. self.deactivate(True)
  3015. if "There aren't that many on Terra!" in line:
  3016. self.send2player("\r" + Boxes.alert("No people on Terra to get!"))
  3017. self.deactivate(True)
  3018. # Now back to our scheduled program
  3019. if self.state == 1:
  3020. if "Personal Planet Scan" in line or "Corporate Planet Scan" in line:
  3021. self.state = 2
  3022. elif self.state == 2:
  3023. # Ok, we're in the planet scan part of this
  3024. # 10202 #3 Home of Bugz Class M, Earth Type No Citadel]
  3025. if "#" in line:
  3026. # Ok, we have a planet detail line.
  3027. # There's an extra T when the planet is maxed out.
  3028. # [ 986 #114 Trantor Class M, Earth Type No Citadel]
  3029. # [ 23 #17 gift Class O, Oceanic Level 1]
  3030. if line[7] == "T":
  3031. line = line[:7] + " " + line[8:]
  3032. details, _, citadel = line.partition("Class")
  3033. # log.info(details) # Is that what we are after?
  3034. # log.info(citadel)
  3035. sector, planet_number, name = re.split(r"\s+", details.strip(), 2)
  3036. sector = int(sector)
  3037. number = int(planet_number.replace("#", ""))
  3038. if "Level" in citadel:
  3039. cit = int(citadel[-1]) # Grab last item
  3040. else: # Oops, there looks like there is no citadel here.
  3041. cit = 0
  3042. self.last_seen = number
  3043. self.planets[number] = {"sector": sector, "name": name, "citadel": cit}
  3044. log.info(
  3045. "Planet # {0} in {1} called {2} with a lvl {3} citadel".format(
  3046. number, sector, name, cit
  3047. )
  3048. )
  3049. if "---" in line:
  3050. number = self.last_seen
  3051. # Ok, take the last_seen number, and use it for this line
  3052. # [ Sector Planet Name Ore Org Equ Ore Org Equ Fighters Citadel]
  3053. # [ Shields Population -=Productions=- -=-=-=-=-On Hands-=-=-=-=- Credits]
  3054. # [ 10202 #3 Home of Bugz Class M, Earth Type No Citadel]
  3055. # [ --- (1M) 144 49 26 145 75 12 10 0]
  3056. details = re.split(r"\s+", line.strip())
  3057. log.debug(details) # What are we getting?
  3058. # OK: Likely, I'm not going to use these numbers AT ALL.
  3059. self.planets[number]["population"] = (
  3060. details[1].replace("(", "").replace(")", "")
  3061. )
  3062. # Ok, there's going to have to be some sort of modifier (K, M, etc.)
  3063. # to these numbers. Beware!
  3064. self.planets[number]["ore"] = details[5]
  3065. self.planets[number]["org"] = details[6]
  3066. self.planets[number]["equ"] = details[7]
  3067. # self.planets[number]['cit'] = details[9] # This is if we wanted cit money... but that's not it
  3068. class MaxFighterMake(object):
  3069. """ Max Fighter Make
  3070. State of the Union:
  3071. 1 = Grab planets in current sector
  3072. 2 = Ask what planet to utilize, land
  3073. 3 = Grab Snapshot of planet (We will need to check population lvls too)
  3074. 4 = try cat 1 to cat 2 (Or perhaps this defaults to false if cat 2 is N/A)
  3075. 5 = if bad then try cat 1 to cat 3 (Or perhaps this defaults to false if cat 3 is N/A)
  3076. 6 = if bad then try cat 2 to cat 3 (Or perhaps this defaults to false if cat 3 is N/A)
  3077. 7 = loop back to state 3 until all result in bad (Yay best acheived)
  3078. """
  3079. def __init__(self, game):
  3080. self.game = game
  3081. self.queue_game = game.queue_game
  3082. self.queue_player = game.queue_player
  3083. self.observer = game.observer
  3084. self.r = Style.RESET_ALL
  3085. self.c = merge(Style.BRIGHT + Fore.YELLOW)
  3086. self.nl = "\n\r"
  3087. # My "stuff" in my pants!
  3088. self.state = 1
  3089. self.corp = True
  3090. self.target_sector = 0 # Sector Number of target
  3091. self.target_number = 0 # Planet Number of target
  3092. self.last_seen = {"Ore": False, "Org": False, "Equ": False, "Fig": False}
  3093. self.max_product = 0
  3094. self.trying = "Ore2Org"
  3095. self.trying_NEXT = False
  3096. self.movement = 1000
  3097. self.planets_list = [] # Will be a list of dicts, containing planet information
  3098. self.cap = False # Capture line for planet
  3099. # Activate
  3100. self.prompt = game.buffer
  3101. self.save = self.observer.save()
  3102. self.observer.connect("player", self.player)
  3103. self.observer.connect("prompt", self.game_prompt)
  3104. self.observer.connect("game-line", self.game_line)
  3105. self.ore = {}
  3106. self.org = {}
  3107. self.equ = {}
  3108. self.fig = {}
  3109. self.defer = None
  3110. self.to_player = self.game.to_player
  3111. self.game.to_player = False
  3112. self.send2game("D")
  3113. def whenDone(self):
  3114. self.defer = defer.Deferred()
  3115. return self.defer
  3116. def deactivate(self, andExit=False):
  3117. self.state = 0
  3118. log.debug("MaxFighterMake.deactivate()")
  3119. self.game.to_player = True
  3120. assert not self.save is None
  3121. self.observer.load(self.save)
  3122. self.save = None
  3123. if self.defer:
  3124. if andExit:
  3125. self.defer.callback({"exit": True})
  3126. else:
  3127. self.defer.callback("done")
  3128. self.defer = None
  3129. def player(self, chunk: bytes):
  3130. self.deactivate(True)
  3131. def send2game(self, txt):
  3132. # Removed debug info since I use this to also display Boxes.alert
  3133. self.queue_player.put(txt)
  3134. def send2player(self, txt):
  3135. # Removed debug since it really doesn't help with anything
  3136. self.queue_game.put(txt)
  3137. def target_chosen(self, choice: str):
  3138. if choice.strip() == "":
  3139. self.deactivate(True)
  3140. else:
  3141. self.target_number = int(choice)
  3142. if self.target_number > self.total:
  3143. self.deactivate(True)
  3144. else:
  3145. self.state = 3
  3146. self.send2game("L")
  3147. def check_resource(self, resource):
  3148. # Given a valid string that can be converted to int
  3149. if resource == "N/A":
  3150. resource = -1
  3151. else:
  3152. resource = int(resource.replace(",", ""))
  3153. return resource
  3154. def game_prompt(self, prompt: str):
  3155. log.debug("P {0} | {1}".format(self.state, prompt))
  3156. if self.state == 2:
  3157. self.game.to_player = True
  3158. if len(self.planets_list) == 0:
  3159. self.send2player(self.nl + Boxes.alert("No planets in this sector!"))
  3160. self.deactivate(True)
  3161. tc = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  3162. c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  3163. c2 = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  3164. box = Boxes(44, color=tc)
  3165. self.queue_game.put(box.top())
  3166. self.queue_game.put(
  3167. box.row(
  3168. tc
  3169. + "{0:3} {1:20} {2:18} ".format(" # ", "Planet Name", "Planet Type")
  3170. )
  3171. )
  3172. self.queue_game.put(box.middle())
  3173. def planet_output(num, planet_name, planet_type):
  3174. row = "{0}{1:3} {2}{3:29} {4:9} ".format(
  3175. c1, num, c2, planet_name, planet_type
  3176. )
  3177. self.send2player(box.row(row))
  3178. self.total = 0
  3179. for p in self.planets_list:
  3180. planet_output(self.total, p["name"], p["type"])
  3181. self.total += 1
  3182. ask = PlayerInput(self.game)
  3183. d = ask.prompt("Choose a planet", 3, name="planet", digits=True)
  3184. d = d.addCallback(self.target_chosen)
  3185. elif self.state == 4:
  3186. if prompt.startswith("Planet command (?=help) [D]"):
  3187. self.send2game("?")
  3188. self.state = 6
  3189. elif self.state == 6:
  3190. if prompt.startswith("Planet command (?=help) [D]"):
  3191. self.send2game("P")
  3192. self.state = 7
  3193. elif self.state == 7:
  3194. if prompt.startswith("Display planet?"):
  3195. self.send2game("N")
  3196. self.state = 8
  3197. elif self.state == 8:
  3198. if prompt.startswith("(1)Ore, (2)Org or (3)Equipment?"):
  3199. if self.trying == "Ore2Org" or self.trying == "Ore2Equ":
  3200. self.send2game("1")
  3201. elif self.trying == "Org2Equ" or self.trying == "Org2Ore":
  3202. self.send2game("2")
  3203. elif self.trying == "Equ2Org" or self.trying == "Equ2Ore":
  3204. self.send2game("3")
  3205. self.state = 9
  3206. elif self.state == 9:
  3207. if prompt.startswith("How many groups of Colonists do you want to move?"):
  3208. self.send2game("{0}\r".format(self.movement))
  3209. self.state = 10
  3210. elif self.state == 10:
  3211. if prompt.startswith("(1)Ore, (2)Org or (3)Equipment?"):
  3212. if self.trying == "Org2Ore" or self.trying == "Equ2Ore":
  3213. self.send2game("1")
  3214. elif self.trying == "Ore2Org" or self.trying == "Equ2Org":
  3215. self.send2game("2")
  3216. elif self.trying == "Ore2Equ" or self.trying == "Org2Equ":
  3217. self.send2game("3")
  3218. self.state = 11
  3219. elif self.state == 11:
  3220. if prompt.startswith("Planet command (?=help) [D]"):
  3221. self.send2game("D")
  3222. self.trying_NEXT = False
  3223. self.last_seen["Ore"] = False
  3224. self.last_seen["Org"] = False
  3225. self.last_seen["Equ"] = False
  3226. self.last_seen["Fig"] = False
  3227. self.state = 12
  3228. elif self.state == 12:
  3229. if prompt.startswith("Planet command (?=help) [D]"):
  3230. self.send2game("?")
  3231. self.state = 6
  3232. # if(self.trying == 'Ore2Org'):
  3233. # self.send2game('P\r1{0}2D'.format(self.movement))
  3234. # self.state = 4
  3235. # # Back to looking at the planet
  3236. # elif(self.trying == 'Ore2Equ'):
  3237. # self.send2game('P\r1{0}3D'.format(self.movement))
  3238. # self.state = 4
  3239. # # Back to looking at the planet
  3240. # elif(self.trying == 'Org2Equ'):
  3241. # self.send2game('P\r2{0}3D'.format(self.movement))
  3242. # self.state = 4
  3243. # elif(self.trying == 'reverse'):
  3244. # if(self.trying_OLD == 'Ore2Org'):
  3245. # self.send2game('P\r2{0}1D'.format(self.movement))
  3246. # elif(self.trying_OLD == 'Ore2Equ'):
  3247. # self.send2game('P\r3{0}1D'.format(self.movement))
  3248. # elif(self.trying_OLD == 'Org2Equ'):
  3249. # self.send2game('P\r3{0}2D'.format(self.movement))
  3250. # self.state = 4
  3251. # self.trying = self.trying_NEXT
  3252. def game_line(self, line: str):
  3253. log.debug("L {0} | {1}".format(self.state, line))
  3254. # IF at any state we see turns left lets grab it
  3255. if "turns left" in line:
  3256. work = line[19:].replace(" turns left.", "").strip()
  3257. self.turns = work
  3258. log.debug("TURNS LEFT: {0}".format(self.turns))
  3259. if int(self.turns) < 200:
  3260. self.send2player(
  3261. "\r" + Boxes.alert("Low Turns! ({0})".format(self.turns))
  3262. )
  3263. self.deactivate(True)
  3264. # IF at any state we see how many holds avalible let's get that
  3265. if "Total Holds" in line:
  3266. work = line[16:].replace("-", "").replace("=", " ").split()
  3267. self.total_holds = int(work[0])
  3268. count = 0
  3269. for w in work:
  3270. if w != "Empty":
  3271. count += 1
  3272. elif w == "Empty":
  3273. count += 1
  3274. self.holds = int(work[count])
  3275. log.debug("EMPTY HOLDS = {0}".format(self.holds))
  3276. if self.holds < self.total_holds:
  3277. self.send2player(
  3278. "\r"
  3279. + Boxes.alert(
  3280. "You need {0} holds empty! ({1} Empty)".format(
  3281. self.total_holds, self.holds
  3282. )
  3283. )
  3284. )
  3285. self.deactivate(True)
  3286. if "There aren't that many on the planet!" in line:
  3287. self.send2player("\r" + Boxes.alert("We're missing people!"))
  3288. self.deactivate(True)
  3289. if "There isn't room on the planet for that many!" in line:
  3290. self.send2player("\r" + Boxes.alert("We have to many people!"))
  3291. self.deactivate(True)
  3292. # Now back to our scheduled program
  3293. if self.state == 1:
  3294. if line.startswith("Planets"):
  3295. self.cap = True
  3296. work = re.split(r"\s+", line)
  3297. # log.debug("'{0}'".format(work))
  3298. final = {"name": work[-1], "type": work[-2]}
  3299. self.planets_list.append(final)
  3300. log.debug("Planet {0} type {1}".format(final["name"], final["type"]))
  3301. elif line.startswith("Ships"): # Stop 1, so we don't capture everything
  3302. self.cap = False
  3303. elif line.startswith("Fighters"): # Stop 2, so we don't capture everything
  3304. self.cap = False
  3305. elif self.cap == True:
  3306. work = re.split(r"\s+", line)
  3307. # log.debug("'{0}'".format(work))
  3308. final = {"name": work[-1], "type": work[-2]}
  3309. self.planets_list.append(final)
  3310. log.debug("Planet {0} type {1}".format(final["name"], final["type"]))
  3311. elif self.cap == False and len(self.planets_list) != 0:
  3312. self.state = 2
  3313. elif self.state == 3:
  3314. if self.planets_list[self.target_number]["name"] in line:
  3315. work = re.split(r"\s+", line)
  3316. log.debug("'{0}'".format(work))
  3317. pnumber = work[2].replace(">", "")
  3318. self.planets_list[self.target_number][
  3319. "pnumber"
  3320. ] = pnumber # Add this to our dict of the planet
  3321. log.debug(
  3322. "Pnumber {0} is {1}".format(
  3323. pnumber, self.planets_list[self.target_number]["name"]
  3324. )
  3325. )
  3326. self.send2game("{0}\r".format(pnumber))
  3327. self.state = 4
  3328. elif self.state == 4:
  3329. # Item Colonists Colonists Daily Planet Ship Planet
  3330. # (1000s) 2 Build 1 Product Amount Amount Maximum
  3331. # ------- --------- --------- --------- --------- --------- ---------
  3332. # Fuel Ore 707 2 353 10,376 0 200,000
  3333. # Organics 651 5 130 4,304 0 200,000
  3334. # Equipment 1,641 20 82 2,618 0 100,000
  3335. # Fighters N/A 63 47 1,463 400 1,000,000
  3336. if line.startswith("Fuel Ore"):
  3337. work = re.split(r"\s+", line)
  3338. # log.debug("Ore - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[2], work[3], work[4], work[5], work[6], work[7]))
  3339. self.ore = {
  3340. "pop": self.check_resource(work[2]),
  3341. "make": self.check_resource(work[3]),
  3342. "daily": self.check_resource(work[4]),
  3343. "amount": self.check_resource(work[5]),
  3344. "ship": self.check_resource(work[6]),
  3345. "pmax": self.check_resource(work[7]),
  3346. }
  3347. self.last_seen["Ore"] = True
  3348. elif line.startswith("Organics"):
  3349. work = re.split(r"\s+", line)
  3350. # log.debug("Org - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[1], work[2], work[3], work[4], work[5], work[6]))
  3351. self.org = {
  3352. "pop": self.check_resource(work[1]),
  3353. "make": self.check_resource(work[2]),
  3354. "daily": self.check_resource(work[3]),
  3355. "amount": self.check_resource(work[4]),
  3356. "ship": self.check_resource(work[5]),
  3357. "pmax": self.check_resource(work[6]),
  3358. }
  3359. self.last_seen["Org"] = True
  3360. elif line.startswith("Equipment"):
  3361. work = re.split(r"\s+", line)
  3362. # log.debug("Equ - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[1], work[2], work[3], work[4], work[5], work[6]))
  3363. self.equ = {
  3364. "pop": self.check_resource(work[1]),
  3365. "make": self.check_resource(work[2]),
  3366. "daily": self.check_resource(work[3]),
  3367. "amount": self.check_resource(work[4]),
  3368. "ship": self.check_resource(work[5]),
  3369. "pmax": self.check_resource(work[6]),
  3370. }
  3371. self.last_seen["Equ"] = True
  3372. elif line.startswith("Fighters"):
  3373. work = re.split(r"\s+", line)
  3374. # log.debug("Fig - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[1], work[2], work[3], work[4], work[5], work[6]))
  3375. self.fig = {
  3376. "pop": self.check_resource(work[1]),
  3377. "make": self.check_resource(work[2]),
  3378. "daily": self.check_resource(work[3]),
  3379. "amount": self.check_resource(work[4]),
  3380. "ship": self.check_resource(work[5]),
  3381. "pmax": self.check_resource(work[6]),
  3382. }
  3383. self.last_seen["Fig"] = True
  3384. else:
  3385. if (
  3386. self.last_seen["Ore"] == True
  3387. and self.last_seen["Org"] == True
  3388. and self.last_seen["Equ"] == True
  3389. and self.last_seen["Fig"] == True
  3390. ):
  3391. if self.fig["daily"] > self.max_product:
  3392. self.max_product = self.fig["daily"]
  3393. self.planets_list[self.target_number]["Ore"] = self.ore
  3394. self.planets_list[self.target_number]["Org"] = self.org
  3395. self.planets_list[self.target_number]["Equ"] = self.equ
  3396. self.planets_list[self.target_number]["Fig"] = self.fig
  3397. log.debug("Max Production is {0}!".format(self.max_product))
  3398. self.state == 6
  3399. elif self.state == 12:
  3400. if line.startswith("Fuel Ore"):
  3401. work = re.split(r"\s+", line)
  3402. # log.debug("Ore - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[2], work[3], work[4], work[5], work[6], work[7]))
  3403. self.ore = {
  3404. "pop": self.check_resource(work[2]),
  3405. "make": self.check_resource(work[3]),
  3406. "daily": self.check_resource(work[4]),
  3407. "amount": self.check_resource(work[5]),
  3408. "ship": self.check_resource(work[6]),
  3409. "pmax": self.check_resource(work[7]),
  3410. }
  3411. self.last_seen["Ore"] = True
  3412. elif line.startswith("Organics"):
  3413. work = re.split(r"\s+", line)
  3414. # log.debug("Org - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[1], work[2], work[3], work[4], work[5], work[6]))
  3415. self.org = {
  3416. "pop": self.check_resource(work[1]),
  3417. "make": self.check_resource(work[2]),
  3418. "daily": self.check_resource(work[3]),
  3419. "amount": self.check_resource(work[4]),
  3420. "ship": self.check_resource(work[5]),
  3421. "pmax": self.check_resource(work[6]),
  3422. }
  3423. self.last_seen["Org"] = True
  3424. elif line.startswith("Equipment"):
  3425. work = re.split(r"\s+", line)
  3426. # log.debug("Equ - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[1], work[2], work[3], work[4], work[5], work[6]))
  3427. self.equ = {
  3428. "pop": self.check_resource(work[1]),
  3429. "make": self.check_resource(work[2]),
  3430. "daily": self.check_resource(work[3]),
  3431. "amount": self.check_resource(work[4]),
  3432. "ship": self.check_resource(work[5]),
  3433. "pmax": self.check_resource(work[6]),
  3434. }
  3435. self.last_seen["Equ"] = True
  3436. elif line.startswith("Fighters"):
  3437. work = re.split(r"\s+", line)
  3438. # log.debug("Fig - Pop: {0}, 2make1: {1} dailyproduct: {2} pamount: {3} saamount: {4} pmax: {5}".format(work[1], work[2], work[3], work[4], work[5], work[6]))
  3439. self.fig = {
  3440. "pop": self.check_resource(work[1]),
  3441. "make": self.check_resource(work[2]),
  3442. "daily": self.check_resource(work[3]),
  3443. "amount": self.check_resource(work[4]),
  3444. "ship": self.check_resource(work[5]),
  3445. "pmax": self.check_resource(work[6]),
  3446. }
  3447. self.last_seen["Fig"] = True
  3448. else:
  3449. if (
  3450. self.last_seen["Ore"] == True
  3451. and self.last_seen["Org"] == True
  3452. and self.last_seen["Equ"] == True
  3453. and self.last_seen["Fig"] == True
  3454. ):
  3455. if self.fig["daily"] > self.max_product:
  3456. self.max_product = self.fig["daily"]
  3457. self.planets_list[self.target_number]["Ore"] = self.ore
  3458. self.planets_list[self.target_number]["Org"] = self.org
  3459. self.planets_list[self.target_number]["Equ"] = self.equ
  3460. self.planets_list[self.target_number]["Fig"] = self.fig
  3461. log.debug("Max Production is now {0}!".format(self.max_product))
  3462. if self.fig["daily"] < self.max_product:
  3463. # Oh nose, our change actually made things worse... let's fix that and try a different way
  3464. if self.trying == "Ore2Org":
  3465. self.trying = "Org2Ore"
  3466. elif self.trying == "Ore2Equ":
  3467. self.trying = "Equ2Ore"
  3468. elif self.trying == "Org2Equ":
  3469. self.trying = "Equ2Org"
  3470. self.state = 6
  3471. if (
  3472. self.ore["pop"] < 1
  3473. or self.org["pop"] < 1
  3474. or self.equ["pop"] < 1
  3475. ):
  3476. self.send2game("Q")
  3477. self.send2player(
  3478. self.nl
  3479. + Boxes.alert(
  3480. "Peak Fighter Production: {0}".format(self.fig["daily"])
  3481. )
  3482. )
  3483. self.deactivate(True)
  3484. class evilTrade(object):
  3485. """
  3486. A little bit on playing evil,
  3487. Doing SSM, a bust will cause some holds and any cargo to be removed!
  3488. You must move to different ports to reduce the chance of a bust,
  3489. Bust's are reset after so many days... so might want to handle that.
  3490. SSM rewards you -10 Alignment and +5 Experiece if you are sucessful
  3491. in robbing back your cargo you just sold.
  3492. States:
  3493. 0 = Pre-State, Asks the user how many times to run
  3494. 1 = Gather user info
  3495. 2 = Verify Cargo is full of Equ, else jump to state 3.5
  3496. 3 = Find nearest "Evil Pair" of ports, move to first, jump to 4
  3497. 3.5 = User does not have Equ, lets buy it, then move on
  3498. (SSM: Sell-Steal-Move)
  3499. 4 = Port and Sell Equ
  3500. 5 = Port and Steal Equ
  3501. 6 = Move to 2nd "Evil Pair"
  3502. 7 = Port and Sell Equ
  3503. 8 = Port and Steal Equ
  3504. 9 = Move to 1st "Evil Pair"
  3505. 10 = Decide to loop, if so we jump back to state 4, else exit
  3506. """
  3507. def __init__(self, game):
  3508. self.game = game
  3509. self.queue_game = game.queue_game
  3510. self.queue_player = game.queue_player
  3511. self.observer = game.observer
  3512. self.r = Style.RESET_ALL
  3513. self.c = merge(Style.BRIGHT + Fore.YELLOW)
  3514. self.nl = "\n\r"
  3515. self.state = 0 # Pre-state
  3516. self.holds = 0 # Total holds empty, self.holds == self.cargo["Empty"]
  3517. self.cargo = {
  3518. "Ore": 0,
  3519. "Org": 0,
  3520. "Equ": 0,
  3521. "Colo": 0,
  3522. "Empty": 0,
  3523. } # What is in our holds?
  3524. self.maxHolds = 0 # Total holds
  3525. self.maxLoops = 0 # So when we display done show the total loops asked to do
  3526. self.loops = 0 # Current number of loops left to do
  3527. self.completeINFO = False # Collected all INFO needed
  3528. self.exp = 0 # Experience Points the player has
  3529. self.alignment = 0 # Alignment the player has
  3530. self.credits = 0 # Credits player has
  3531. self.turns = 0 # Turns the player has
  3532. self.sector = 0 # Current sector
  3533. self.sectors = False # Do we need to update sector1 and 2?
  3534. self.sector1 = None
  3535. self.sector2 = None
  3536. self.target_sector = 0 # Next port to trade with
  3537. self.name = "" # Player name to include into stats for logs
  3538. self.prompt = game.buffer
  3539. self.save = self.observer.save()
  3540. self.observer.connect("player", self.player)
  3541. self.observer.connect("prompt", self.game_prompt)
  3542. self.observer.connect("game-line", self.game_line)
  3543. self.defer = None
  3544. self.to_player = self.game.to_player
  3545. self.game.to_player = False
  3546. self.send2game("D")
  3547. def whenDone(self):
  3548. self.defer = defer.Deferred()
  3549. return self.defer
  3550. def deactivate(self, andExit=False):
  3551. self.state = 0
  3552. log.debug("EvilTrade.deactivate()")
  3553. self.game.to_player = True
  3554. assert not self.save is None
  3555. self.observer.load(self.save)
  3556. self.save = None
  3557. if self.defer:
  3558. if andExit:
  3559. self.defer.callback({"exit": True})
  3560. else:
  3561. self.defer.callback("done")
  3562. self.defer = None
  3563. def player(self, chunk: bytes):
  3564. self.deactivate(True)
  3565. def send2game(self, txt):
  3566. self.queue_player.put(txt)
  3567. def send2player(self, txt):
  3568. self.queue_game.put(txt)
  3569. def loops_chosen(self, choice: str):
  3570. if choice.strip() == "":
  3571. self.deactivate(True)
  3572. else:
  3573. self.loops = int(choice)
  3574. self.maxLoops = self.loops
  3575. self.state = 1
  3576. self.send2game("I")
  3577. def game_prompt(self, prompt: str):
  3578. log.debug("P {0} | {1}".format(self.state, prompt))
  3579. if self.state == 0:
  3580. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3581. ask = PlayerInput(self.game)
  3582. d = ask.prompt("How many times: ", 3, name="times", digits=True)
  3583. d.addCallback(self.loops_chosen)
  3584. elif self.state == 1:
  3585. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3586. if self.completeINFO: # We have all our info, lets display it into logs
  3587. log.debug("{0}'s Stats...".format(self.name))
  3588. log.debug(
  3589. "Experience: {0} Alignment: {1} Turns: {2} Credits: {3} Total Holds: {4} Avaible Holds: {5} Current Sector: {6}".format(
  3590. self.exp,
  3591. self.alignment,
  3592. self.turns,
  3593. self.credits,
  3594. self.maxHolds,
  3595. self.holds,
  3596. self.sector,
  3597. )
  3598. )
  3599. log.debug(
  3600. "Ore: {0} Org: {1} Equ: {2} Colo: {3} Empty: {4}".format(
  3601. self.cargo["Ore"],
  3602. self.cargo["Org"],
  3603. self.cargo["Equ"],
  3604. self.cargo["Colo"],
  3605. self.cargo["Empty"],
  3606. )
  3607. )
  3608. self.state = 2
  3609. self.send2game("D")
  3610. self.game.to_player = True
  3611. elif self.state == 2:
  3612. if self.cargo["Ore"] != 0: # Nada, we want Equ
  3613. self.send2player(
  3614. self.nl + Boxes.alert("Holds must contain Equ not Ore!")
  3615. )
  3616. self.deactivate(True)
  3617. elif self.cargo["Org"] != 0: # Oops, wrong, Give use Equ
  3618. self.send2player(
  3619. self.nl + Boxes.alert("Holds must contain Equ not Org!")
  3620. )
  3621. self.deactivate(True)
  3622. elif (
  3623. self.cargo["Colo"] != 0
  3624. ): # We don't want people, let the user handle this
  3625. self.send2player(
  3626. self.nl + Boxes.alert("Holds must contain Equ not Colonist!")
  3627. )
  3628. self.deactivate(True)
  3629. elif self.cargo["Equ"] != 0: # We do have Equ, but do we need more?
  3630. if self.cargo["Equ"] != self.maxHolds:
  3631. self.send2player(
  3632. self.nl
  3633. + Boxes.alert(
  3634. "You need {0} more Equ!".format(self.cargo["Empty"])
  3635. )
  3636. )
  3637. # Ok nope, we need more, let's find the nearest and "Mush!"
  3638. place = self.game.gamedata.find_nearest_selling(
  3639. self.sector, "E", 400
  3640. )
  3641. if place == 0:
  3642. self.queue_game.put(
  3643. self.nl + Boxes.alert("Find Nearest Failed!")
  3644. )
  3645. self.deactivate(True)
  3646. else:
  3647. self.send2game("{0}\rEPT".format(place))
  3648. # Code here to handle getting Equ
  3649. self.state = 3.5
  3650. else: # All OK, now to find_nearest_evilpairs
  3651. self.state = 3
  3652. self.game.to_player = False
  3653. self.send2game("D")
  3654. self.game.to_player = True
  3655. else: # Ok so we have Empty holds, lets fill it with Equ
  3656. place = self.game.gamedata.find_nearest_selling(self.sector, "E", 400)
  3657. if place == 0:
  3658. self.queue_game.put(self.nl + Boxes.alert("Find Nearest Failed!"))
  3659. self.deactivate(True)
  3660. else:
  3661. self.send2game("{0}\rEPT".format(place))
  3662. # Code here to handle getting Equ
  3663. self.state = 3.5
  3664. elif self.state == 3.5:
  3665. # Ok, we are getting Equ into holds then onto 3
  3666. if prompt.startswith("How many holds of Fuel Ore do you want to buy "):
  3667. self.send2game("0\r")
  3668. elif prompt.startswith("How many holds of Organics do you want to buy "):
  3669. self.send2game("0\r")
  3670. elif prompt.startswith("How many holds of Equipment do you want to buy "):
  3671. self.send2game("\r\r")
  3672. # Oops, we are expecting the user to have enough money to buy it!
  3673. self.state = 3
  3674. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3675. self.send2game("D")
  3676. self.state = 3
  3677. elif self.state == 3:
  3678. if prompt.startswith("Your offer"):
  3679. self.send2game("0\r")
  3680. self.deactivate(True)
  3681. else:
  3682. # Now to go to evil trade pair \o/
  3683. # Ok so if we go to line 2117 we can see how to use find_nearest_tradepairs()
  3684. # Which will give us a starting point for what needs to be done for us trading
  3685. log.info("find_nearest_evilpairs({0})".format(self.sector))
  3686. c = coiterate(
  3687. self.game.gamedata.find_nearest_evilpairs(self.sector, self)
  3688. )
  3689. c.addCallback(lambda unknown: self.evil())
  3690. self.state = 4
  3691. elif self.state == 4:
  3692. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  3693. self.queue_player.put("N")
  3694. elif prompt.startswith(
  3695. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  3696. ):
  3697. self.queue_player.put("E")
  3698. elif prompt.startswith(
  3699. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  3700. ):
  3701. self.queue_player.put("N")
  3702. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3703. # We should be where we wanted to.
  3704. self.send2game("PT\r\r")
  3705. self.state = 5
  3706. elif self.state == 5:
  3707. if prompt.startswith("How many holds of Fuel Ore do you want to buy"):
  3708. self.send2game("0\r")
  3709. elif prompt.startswith("How many holds of Organics do you want to buy"):
  3710. self.send2game("0\r")
  3711. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3712. self.send2game("PR\rS3\r")
  3713. self.state = 6
  3714. elif self.state == 7:
  3715. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3716. c = coiterate(self.find_next_evilpair())
  3717. c.addCallback(lambda unknown: self.evil())
  3718. self.state = 8
  3719. elif self.state == 8:
  3720. if prompt.startswith("Do you want to engage the TransWarp drive? "):
  3721. self.queue_player.put("N")
  3722. elif prompt.startswith(
  3723. "Engage the Autopilot? (Y/N/Single step/Express) [Y]"
  3724. ):
  3725. self.queue_player.put("E")
  3726. elif prompt.startswith(
  3727. "Stop in this sector (Y,N,E,I,R,S,D,P,?) (?=Help) [N] ?"
  3728. ):
  3729. self.queue_player.put("N")
  3730. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3731. # We should be where we wanted to.
  3732. self.send2game("PT\r\r")
  3733. self.state = 9
  3734. elif self.state == 9:
  3735. if prompt.startswith("How many holds of Fuel Ore do you want to buy"):
  3736. self.send2game("0\r")
  3737. elif prompt.startswith("How many holds of Organics do you want to buy"):
  3738. self.send2game("0\r")
  3739. elif re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  3740. self.send2game("PR\rS3\r")
  3741. self.state = 10
  3742. def evil(self):
  3743. if self.target_sector is None:
  3744. self.send2player(
  3745. self.nl + Boxes.alert("I don't see any ports to trade with.")
  3746. )
  3747. self.deactivate(True)
  3748. else:
  3749. self.send2game("{0}\r".format(self.target_sector))
  3750. if self.sectors == False:
  3751. self.sector1 = self.sector
  3752. self.sector2 = self.target_sector
  3753. self.sectors = True
  3754. self.sector = self.target_sector
  3755. def find_next_evilpair(self):
  3756. """ Find the next GOOD trade pair sector.
  3757. This is sequential (just like Trade report)
  3758. """
  3759. # This should be using the setting in the config, not a hard coded value!
  3760. # show_limit (galaxy.port_trade_show) is pct >= show_limit.
  3761. show_limit = self.game.gamedata.get_config("Display_Percent", "90")
  3762. try:
  3763. show_limit = int(show_limit)
  3764. except ValueError:
  3765. show_limit = 90
  3766. if show_limit < 15:
  3767. show_limit = 15
  3768. if show_limit > 90:
  3769. show_limit = 90
  3770. # Look for "GOOD" trades
  3771. for sector in sorted(self.game.gamedata.ports.keys()):
  3772. pd = self.game.gamedata.ports[sector]
  3773. if not GameData.port_burnt(pd):
  3774. # This happens when you trade with a StarDock
  3775. if "class" not in pd:
  3776. continue
  3777. pc = pd["class"]
  3778. # Ok, let's look into it.
  3779. if not sector in self.game.gamedata.warps:
  3780. continue
  3781. # Busted port
  3782. if not sector in self.game.gamedata.busts:
  3783. continue
  3784. warps = self.game.gamedata.warps[sector]
  3785. for w in warps:
  3786. # We can get there, and get back.
  3787. if (
  3788. w in self.game.gamedata.warps
  3789. and sector in self.game.gamedata.warps[w]
  3790. ):
  3791. # Ok, we can get there -- and get back!
  3792. if (
  3793. w > sector
  3794. and w in self.game.gamedata.ports
  3795. and not GameData.port_burnt(self.game.gamedata.ports[w])
  3796. ):
  3797. wd = self.game.gamedata.ports[w]
  3798. if "class" not in wd:
  3799. continue
  3800. wc = wd["class"]
  3801. if pc in (2, 3, 4, 8) and wc in (2, 3, 4, 8):
  3802. data = self.game.gamedata.port_trade_show(
  3803. sector, w, show_limit
  3804. )
  3805. if data:
  3806. self.target_sector = sector
  3807. return sector
  3808. yield
  3809. # Look for OK trades
  3810. for sector in sorted(self.game.gamedata.ports.keys()):
  3811. pd = self.game.gamedata.ports[sector]
  3812. if not GameData.port_burnt(pd):
  3813. if "class" not in pd:
  3814. continue
  3815. pc = pd["class"]
  3816. # Ok, let's look into it.
  3817. if not sector in self.game.gamedata.warps:
  3818. continue
  3819. # Busted port
  3820. if not sector in self.game.gamedata.busts:
  3821. continue
  3822. warps = self.game.gamedata.warps[sector]
  3823. for w in warps:
  3824. # We can get there, and get back.
  3825. if (
  3826. w in self.game.gamedata.warps
  3827. and sector in self.game.gamedata.warps[w]
  3828. ):
  3829. # Ok, we can get there -- and get back!
  3830. if (
  3831. w > sector
  3832. and w in self.game.gamedata.ports
  3833. and not GameData.port_burnt(self.game.gamedata.ports[w])
  3834. ):
  3835. wd = self.game.gamedata.ports[w]
  3836. if "class" not in wd:
  3837. continue
  3838. wc = wd["class"]
  3839. if GameData.port_trading(
  3840. pd["port"], self.game.gamedata.ports[w]["port"]
  3841. ):
  3842. data = self.game.gamedata.port_trade_show(
  3843. sector, w, show_limit
  3844. )
  3845. if data:
  3846. self.target_sector = sector
  3847. return sector
  3848. yield
  3849. self.target_sector = None
  3850. def game_line(self, line: str):
  3851. log.debug("L {0} | {1}".format(self.state, line))
  3852. if self.state == 1:
  3853. if line.startswith("Trader Name"):
  3854. work = line.replace("Trader Name :", "").split()
  3855. self.name = work[-1]
  3856. elif line.startswith("Current Sector"):
  3857. work = line.replace("Current Sector : ", "")
  3858. self.sector = int(work)
  3859. elif line.startswith("Rank and Exp"):
  3860. work = line.replace("Rank and Exp :", "").split()
  3861. # Rank and Exp : 110 points, Alignment=-116 Crass
  3862. self.exp = work[0]
  3863. self.alignment = work[2].replace("Alignment=", "")
  3864. elif line.startswith("Turns left"):
  3865. work = line.replace("Turns left : ", "")
  3866. self.turns = int(work)
  3867. elif line.startswith("Total Holds"):
  3868. work = (
  3869. line[16:]
  3870. .replace("-", "")
  3871. .replace("=", " ")
  3872. .replace("Fuel Ore", "Fuel")
  3873. .split()
  3874. )
  3875. self.maxHolds = int(work[0])
  3876. # Total Holds : 53 - Empty=53
  3877. # Total Holds : 53 - Fuel Ore=1 Organics=2 Equipment=3 Colonists=4 Empty=43
  3878. # log.debug(work)
  3879. # '53', 'Fuel', '1', 'Organics', '2', 'Equipment', '3', 'Colonists', '4', 'Empty', '43'
  3880. count = 0
  3881. for w in work:
  3882. if w == "Fuel":
  3883. try:
  3884. self.cargo["Ore"] = int(work[count + 1])
  3885. except ValueError: # The next value is not a number!
  3886. self.cargo["Ore"] = 0
  3887. elif w == "Organics":
  3888. try:
  3889. self.cargo["Org"] = int(work[count + 1])
  3890. except ValueError: # The next value is not a number!
  3891. self.cargo["Org"] = 0
  3892. elif w == "Equipment":
  3893. try:
  3894. self.cargo["Equ"] = int(work[count + 1])
  3895. except ValueError: # The next value is not a number!
  3896. self.cargo["Equ"] = 0
  3897. elif w == "Colonists":
  3898. try:
  3899. self.cargo["Colo"] = int(work[count + 1])
  3900. except ValueError: # The next value is not a number!
  3901. self.cargo["Colo"] = 0
  3902. elif w == "Empty":
  3903. try:
  3904. self.cargo["Empty"] = int(work[count + 1])
  3905. self.holds = self.cargo["Empty"]
  3906. except ValueError: # The next value is not a number!
  3907. self.cargo["Empty"] = 0
  3908. count += 1
  3909. elif line.startswith("Credits"):
  3910. work = line.replace("Credits : ", "").replace(",", "")
  3911. self.credits = int(work)
  3912. self.completeINFO = True
  3913. if self.state == 6:
  3914. if "Suddenly you're Busted!" in line:
  3915. # Oops! We got busted, let's stop.
  3916. self.deactivate(True)
  3917. elif "Success!" in line:
  3918. # Ok we passed that... now to move on.
  3919. self.state = 7
  3920. if self.state == 10:
  3921. if "Suddenly you're Busted!" in line:
  3922. # Oops! We got busted, let's stop.
  3923. self.deactivate(True)
  3924. elif "Success!" in line:
  3925. # Ok we passed that... now to move on.
  3926. self.loops -= 1
  3927. if self.loops >= 0:
  3928. self.send2player(
  3929. self.nl + Boxes.alert("Completed {0}".format(self.maxLoops))
  3930. )
  3931. self.deactivate(True)
  3932. else:
  3933. c = coiterate(self.find_next_evilpair())
  3934. c.addCallback(lambda unknown: self.evil())
  3935. self.state = 4
  3936. class bustViewer(object):
  3937. """ Display all busts
  3938. Perhaps also add it so we can manually reset/clear busts?
  3939. Or maybe...
  3940. We make galaxy's maint_busts() configurable so the check in days is in the config
  3941. States:
  3942. 0 = Perform convertion of self.game.gamedata.busts into a more understandable format.
  3943. 1 = Display/List all busts perferably just their sector numbers and when (what date) did that occur.
  3944. Optionals:
  3945. 1. Give numbering to state 0 so you can select a specific bust to be perhaps removed and/or add a remove all option.
  3946. (Dangerous, might not want to allow someone to manually reset all that)
  3947. 2. Add math that calculates the age in days for that particular bust. (Then add this to state 1 Display/List)
  3948. (Ok well this one should be really easy make a string/tuple storing month/day/year for both now and the bust then,)
  3949. (compare them one by one against eachother if 0 we know it's not that category) <- Should always be days since we are clearing them out automatically
  3950. """
  3951. def __init__(self, game):
  3952. self.game = game
  3953. self.queue_game = game.queue_game
  3954. self.queue_player = game.queue_player
  3955. self.observer = game.observer
  3956. self.r = Style.RESET_ALL
  3957. self.c = merge(Style.BRIGHT + Fore.YELLOW)
  3958. self.nl = "\n\r"
  3959. # Stuff
  3960. self.busted = {} # Make custom bust dict based on self.game.gamedata.busts dict
  3961. self.age = {}
  3962. self.state = 0 # Display/List
  3963. self.prompt = game.buffer
  3964. self.save = self.observer.save()
  3965. self.observer.connect("player", self.player)
  3966. self.observer.connect("prompt", self.game_prompt)
  3967. self.observer.connect("game-line", self.game_line)
  3968. self.defer = None
  3969. self.to_player = self.game.to_player
  3970. self.game.to_player = False
  3971. self.send2game("D") # Since we need to send at least 1 character
  3972. def whenDone(self):
  3973. self.defer = defer.Deferred()
  3974. return self.defer
  3975. def deactivate(self, andExit=False):
  3976. self.state = 0
  3977. log.debug("bustViewer.deactivate()")
  3978. self.game.to_player = True
  3979. assert not self.save is None
  3980. self.observer.load(self.save)
  3981. self.save = None
  3982. if self.defer:
  3983. if andExit:
  3984. self.defer.callback({"exit": True})
  3985. else:
  3986. self.defer.callback("done")
  3987. self.defer = None
  3988. def player(self, chunk: bytes):
  3989. self.deactivate(True)
  3990. def send2game(self, txt):
  3991. self.queue_player.put(txt)
  3992. def send2player(self, txt):
  3993. self.queue_game.put(txt)
  3994. def calcAge(self, dt):
  3995. rightNow = pendulum.now()
  3996. result = rightNow.diff(dt).in_days()
  3997. log.debug("calcAge('{0}') got {1} days".format(dt.to_datetime_string(), result))
  3998. return result
  3999. def game_prompt(self, prompt: str):
  4000. log.debug("P {0} | {1}".format(self.state, prompt))
  4001. if self.state == 0:
  4002. # Convert from YYYY-MM-DD to MM-DD-YYYY
  4003. for s in self.game.gamedata.busts:
  4004. d = self.game.gamedata.busts[s] # Keep as string
  4005. d1 = pendulum.parse(d) # Convert to dateTime obj
  4006. log.debug("{0} on {1}".format(s, d))
  4007. self.busted[s] = "{0:02d}-{1:02d}-{2:04d}".format(
  4008. d1.month, d1.day, d1.year
  4009. )
  4010. self.age[s] = self.calcAge(d1)
  4011. self.state += 1
  4012. self.send2game("D")
  4013. elif self.state == 1:
  4014. # Display it nicely to the user, then exit
  4015. tc = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  4016. c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  4017. c2 = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  4018. if self.busted:
  4019. box = Boxes(42, color=tc)
  4020. self.queue_game.put(box.top())
  4021. self.queue_game.put(
  4022. box.row(
  4023. tc
  4024. + " {0:10} {1:<20} {2:8} ".format(
  4025. "Sector", "Date Busted On", "Days old"
  4026. )
  4027. )
  4028. )
  4029. self.queue_game.put(box.middle())
  4030. for s in self.busted:
  4031. if self.age[s]:
  4032. self.queue_game.put(
  4033. box.row(
  4034. c1
  4035. + " {0:<10} {1:<20} {2:8} ".format(
  4036. s, self.busted[s], self.age[s]
  4037. )
  4038. )
  4039. )
  4040. else:
  4041. self.queue_game.put(
  4042. box.row(
  4043. c1
  4044. + " {0:<10} {1:<20} ".format(s, self.busted[s])
  4045. )
  4046. )
  4047. self.queue_game.put(box.bottom())
  4048. else:
  4049. self.send2player(
  4050. self.nl
  4051. + Boxes.alert(
  4052. "You have no busts to view, perhaps you just aren't into trouble."
  4053. )
  4054. )
  4055. self.deactivate(True)
  4056. def game_line(self, line: str):
  4057. log.debug("L {0} | {1}".format(self.state, line))
  4058. class firstActivate(object):
  4059. """ Upon first time activation of the proxy what happens
  4060. States:
  4061. 0 = Send V and gather information on when busts were cleared
  4062. 1 = Calculate date from pendulum.now().subtract(days=#)
  4063. 2 = If pendulum.now() is greater than our date to clear then self.game.gamedata.reset_busts()
  4064. Well drat... while in a game you can't access when busts were cleared. :(
  4065. """
  4066. def __init__(self, game):
  4067. self.game = game
  4068. self.queue_game = game.queue_game
  4069. self.queue_player = game.queue_player
  4070. self.observer = game.observer
  4071. self.r = Style.RESET_ALL
  4072. self.c = merge(Style.BRIGHT + Fore.YELLOW)
  4073. self.nl = "\n\r"
  4074. # Stuffz
  4075. self.state = 0 # V and gather info
  4076. self.prompt = game.buffer
  4077. self.save = self.observer.save()
  4078. self.observer.connect("player", self.player)
  4079. self.observer.connect("prompt", self.game_prompt)
  4080. self.observer.connect("game-line", self.game_line)
  4081. self.defer = None
  4082. self.to_player = self.game.to_player
  4083. self.game.to_player = False
  4084. self.send2game("V") # Since we need to send at least 1 character
  4085. def whenDone(self):
  4086. self.defer = defer.Deferred()
  4087. return self.defer
  4088. def deactivate(self, andExit=False):
  4089. self.state = 0
  4090. log.debug("firstActivate.deactivate()")
  4091. self.game.to_player = True
  4092. assert not self.save is None
  4093. self.observer.load(self.save)
  4094. self.save = None
  4095. if self.defer:
  4096. if andExit:
  4097. self.defer.callback({"exit": True})
  4098. else:
  4099. self.defer.callback("done")
  4100. self.defer = None
  4101. def player(self, chunk: bytes):
  4102. self.deactivate(True)
  4103. def send2game(self, txt):
  4104. self.queue_player.put(txt)
  4105. def send2player(self, txt):
  4106. self.queue_game.put(txt)
  4107. def game_prompt(self, prompt: str):
  4108. log.debug("P {0} | {1}".format(self.state, prompt))
  4109. if self.state == 0:
  4110. # Enable sloppy-shit mode. This shows random parts
  4111. # of the command line because you don't wait for
  4112. # the entire commandline to be displayed.
  4113. # There's a darn good reason I worked on the regex to
  4114. # match the prompt...
  4115. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  4116. # if prompt.startswith("Command"):
  4117. self.deactivate()
  4118. def game_line(self, line: str):
  4119. log.debug("L {0} | {1}".format(self.state, line))
  4120. class ProxyMenu(object):
  4121. """ Display ProxyMenu
  4122. Example:
  4123. from flexible import ProxyMenu
  4124. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
  4125. menu = ProxyMenu(self.game)
  4126. """
  4127. def __init__(self, game):
  4128. self.nl = "\n\r"
  4129. self.c = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  4130. self.r = Style.RESET_ALL
  4131. self.c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  4132. self.c2 = merge(Style.NORMAL + Fore.CYAN + Back.BLUE)
  4133. # self.portdata = None
  4134. self.game = game
  4135. self.queue_game = game.queue_game
  4136. self.observer = game.observer
  4137. self.game.gamedata.get_config("Macro", "D^1N^D")
  4138. # Am I using self or game? (I think I want game, not self.)
  4139. # if hasattr(self.game, "portdata"):
  4140. # self.portdata = self.game.portdata
  4141. # else:
  4142. # self.portdata = {}
  4143. # if hasattr(self.game, 'warpdata'):
  4144. # self.warpdata = self.game.warpdata
  4145. # else:
  4146. # self.warpdata = {}
  4147. if hasattr(self.game, "trade_report"):
  4148. self.trade_report = self.game.trade_report
  4149. else:
  4150. self.trade_report = []
  4151. # Yes, at this point we would activate
  4152. self.prompt = game.buffer
  4153. self.save = self.observer.save()
  4154. self.observer.connect("player", self.player)
  4155. # If we want it, it's here.
  4156. self.defer = None
  4157. self.game.to_player = True
  4158. # Is it our first time activated? if so do a little extra stuff.
  4159. if self.game.gamedata.activated == False:
  4160. self.game.gamedata.activated = True
  4161. # Run V to get date of bust clear from game
  4162. # Decide to clear or not
  4163. # Clear or continue on
  4164. log.debug("First time activation dectected!")
  4165. first = firstActivate(self.game)
  4166. d = first.whenDone()
  4167. d.addCallback(self.pre_back)
  4168. d.addErrback(self.pre_back)
  4169. else:
  4170. # well that was all skipped because we already did that.
  4171. self.pre_back()
  4172. def __del__(self):
  4173. log.debug("ProxyMenu {0} RIP".format(self))
  4174. # When we exit, always make sure player echo is on.
  4175. self.game.to_player = True
  4176. def pre_back(self, stuffz=None):
  4177. self.keepalive = task.LoopingCall(self.awake)
  4178. self.keepalive.start(30)
  4179. self.menu()
  4180. def whenDone(self):
  4181. self.defer = defer.Deferred()
  4182. # Call this to chain something after we exit.
  4183. return self.defer
  4184. def menu(self):
  4185. box = Boxes(30, color=self.c)
  4186. self.queue_game.put(box.top())
  4187. text = self.c + "{0:^30}".format("TradeWars Proxy Active")
  4188. text = text.replace("Active", BLINK + "Active" + Style.RESET_ALL + self.c)
  4189. self.queue_game.put(box.row(text))
  4190. self.queue_game.put(box.bottom())
  4191. self.queue_game.put(" " + self.c + "-=>" + self.r + " ")
  4192. def fullmenu(self):
  4193. box = Boxes(30, color=self.c)
  4194. self.queue_game.put(box.top())
  4195. def menu_item(ch: str, desc: str):
  4196. row = self.c1 + " {0} {1}- {2}{3:25}".format(ch, self.c2, self.c1, desc)
  4197. self.queue_game.put(box.row(row))
  4198. # self.queue_game.put(
  4199. # " " + self.c1 + ch + self.c2 + " - " + self.c1 + desc + self.nl
  4200. # )
  4201. menu_item("C", "Configuration ({0})".format(len(self.game.gamedata.config)))
  4202. menu_item("D", "Display Report again")
  4203. menu_item("E", "Export Data (Save)")
  4204. # menu_item("Q", "Quest")
  4205. menu_item("M", "Macro")
  4206. menu_item("P", "Port CIM Report ({0})".format(len(self.game.gamedata.ports)))
  4207. menu_item("W", "Warp CIM Report ({0})".format(len(self.game.gamedata.warps)))
  4208. menu_item("R", "Restock Report")
  4209. menu_item("T", "Trading Report")
  4210. menu_item("B", "Display Busts ({0})".format(len(self.game.gamedata.busts)))
  4211. menu_item("S", "Scripts")
  4212. menu_item("X", "eXit")
  4213. bottom = box.bottom()
  4214. self.queue_game.put(bottom[:-2])
  4215. def awake(self):
  4216. log.info("ProxyMenu.awake()")
  4217. self.game.queue_player.put(" ")
  4218. def port_report(self, portdata: dict):
  4219. # Check to see if the old data is close to the new data.
  4220. # If so, don't toss out the special!
  4221. matches = 0
  4222. for k, v in self.old_ports.items():
  4223. if k in self.game.gamedata.ports:
  4224. # Ok, key exists. Is the class the same
  4225. if self.game.gamedata.ports[k]["class"] == v["class"]:
  4226. matches += 1
  4227. log.info("Got {0} matches old ports to new.".format(matches))
  4228. if matches > 12:
  4229. self.queue_game.put(
  4230. "Restoring (SPECIAL) class ports ({0}).".format(len(self.specials))
  4231. + self.nl
  4232. )
  4233. for p in self.specials.keys():
  4234. self.game.gamedata.ports[int(p)] = self.specials[p]
  4235. # self.portdata = portdata
  4236. # self.game.portdata = portdata
  4237. self.queue_game.put("Loaded {0} ports.".format(len(self.game.ports)) + self.nl)
  4238. self.welcome_back()
  4239. def warp_report(self, warpdata: dict):
  4240. # self.warpdata = warpdata
  4241. # self.game.warpdata = warpdata
  4242. self.queue_game.put("Loaded {0} sectors.".format(len(warpdata)) + self.nl)
  4243. self.welcome_back()
  4244. def make_trade_report(self):
  4245. log.debug("make_trade_report()")
  4246. ok_trades = []
  4247. best_trades = []
  4248. show_best = self.game.gamedata.get_config("Display_Best", "Y").upper()[0] == "Y"
  4249. show_ok = self.game.gamedata.get_config("Display_Ok", "N").upper()[0] == "Y"
  4250. show_limit = self.game.gamedata.get_config("Display_Percent", "90")
  4251. update_config = False
  4252. try:
  4253. show_limit = int(show_limit)
  4254. except ValueError:
  4255. show_limit = 90
  4256. update_config = True
  4257. if show_limit < 0:
  4258. show_limit = 0
  4259. update_config = True
  4260. elif show_limit > 100:
  4261. show_limit = 100
  4262. update_config = True
  4263. if update_config:
  4264. self.game.gamedata.set_config("Display_Percent", show_limit)
  4265. # for sector, pd in self.game.gamedata.ports.items():
  4266. for sector in sorted(self.game.gamedata.ports.keys()):
  4267. pd = self.game.gamedata.ports[sector]
  4268. if not GameData.port_burnt(pd):
  4269. # This happens if you trade with a StarDock. (It doesn't get the class set.)
  4270. if "class" not in pd:
  4271. continue
  4272. pc = pd["class"]
  4273. # Ok, let's look into it.
  4274. if not sector in self.game.gamedata.warps:
  4275. continue
  4276. warps = self.game.gamedata.warps[sector]
  4277. for w in warps:
  4278. # Verify that we have that warp's info, and that the sector is in it.
  4279. # (We can get back from it)
  4280. if (
  4281. w in self.game.gamedata.warps
  4282. and sector in self.game.gamedata.warps[w]
  4283. ):
  4284. # Ok, we can get there -- and get back!
  4285. if (
  4286. w > sector
  4287. and w in self.game.gamedata.ports
  4288. and not GameData.port_burnt(self.game.gamedata.ports[w])
  4289. ):
  4290. # it is > and has a port.
  4291. wd = self.game.gamedata.ports[w]
  4292. if "class" not in wd:
  4293. continue
  4294. wc = wd["class"]
  4295. # 1: "BBS",
  4296. # 2: "BSB",
  4297. # 3: "SBB",
  4298. # 4: "SSB",
  4299. # 5: "SBS",
  4300. # 6: "BSS",
  4301. # 7: "SSS",
  4302. # 8: "BBB",
  4303. if pc in (1, 5) and wc in (2, 4):
  4304. data = self.game.gamedata.port_trade_show(
  4305. sector, w, show_limit
  4306. )
  4307. if data:
  4308. best_trades.append(data)
  4309. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  4310. elif pc in (2, 4) and wc in (1, 5):
  4311. data = self.game.gamedata.port_trade_show(
  4312. sector, w, show_limit
  4313. )
  4314. if data:
  4315. best_trades.append(data)
  4316. # best_trades.append( "{0:5} -=- {1:5}".format(sector, w))
  4317. elif GameData.port_trading(
  4318. pd["port"], self.game.gamedata.ports[w]["port"]
  4319. ):
  4320. # ok_trades.append( "{0:5} -=- {1:5}".format(sector,w))
  4321. data = self.game.gamedata.port_trade_show(
  4322. sector, w, show_limit
  4323. )
  4324. if data:
  4325. ok_trades.append(data)
  4326. yield
  4327. if show_best:
  4328. self.trade_report.append("Best Trades: (org/equ)")
  4329. self.trade_report.extend(best_trades)
  4330. if show_ok:
  4331. self.trade_report.append("Ok Trades:")
  4332. self.trade_report.extend(ok_trades)
  4333. if not show_best and not show_ok:
  4334. self.queue_game.put(
  4335. Boxes.alert(
  4336. "You probably want to choose something to display in configuration!",
  4337. base="red",
  4338. )
  4339. )
  4340. # self.queue_game.put("BEST TRADES:" + self.nl + self.nl.join(best_trades) + self.nl)
  4341. # self.queue_game.put("OK TRADES:" + self.nl + self.nl.join(ok_trades) + self.nl)
  4342. def get_display_maxlines(self):
  4343. show_maxlines = self.game.gamedata.get_config("Display_Maxlines", "0")
  4344. try:
  4345. show_maxlines = int(show_maxlines)
  4346. except ValueError:
  4347. show_maxlines = 0
  4348. if show_maxlines <= 0:
  4349. show_maxlines = None
  4350. return show_maxlines
  4351. def show_trade_report(self, *_):
  4352. show_maxlines = self.get_display_maxlines()
  4353. self.game.trade_report = self.trade_report
  4354. for t in self.trade_report[:show_maxlines]:
  4355. self.queue_game.put(t + self.nl)
  4356. self.queue_game.put(self.nl + Boxes.alert("Proxy done.", base="green"))
  4357. self.observer.load(self.save)
  4358. self.save = None
  4359. self.keepalive = None
  4360. self.prompt = None
  4361. # self.welcome_back()
  4362. def player(self, chunk: bytes):
  4363. """ Data from player (in bytes). """
  4364. chunk = chunk.decode("latin-1", "ignore")
  4365. key = chunk.upper()
  4366. log.debug("ProxyMenu.player({0})".format(key))
  4367. # Stop the keepalive if we are activating something else
  4368. # or leaving...
  4369. self.keepalive.stop()
  4370. if key == "T":
  4371. self.queue_game.put(self.c + key + self.r + self.nl)
  4372. # Trade Report
  4373. # do we have enough information to do this?
  4374. # if not hasattr(self.game, 'portdata') and not hasattr(self.game, 'warpdata'):
  4375. # self.queue_game.put("Missing portdata and warpdata." + self.nl)
  4376. # elif not hasattr(self.game, 'portdata'):
  4377. # self.queue_game.put("Missing portdata." + self.nl)
  4378. # elif not hasattr(self.game, 'warpdata'):
  4379. # self.queue_game.put("Missing warpdata." + self.nl)
  4380. # else:
  4381. if True:
  4382. # Yes, so let's start!
  4383. self.trade_report = []
  4384. d = coiterate(self.make_trade_report())
  4385. d.addCallback(self.show_trade_report)
  4386. return
  4387. elif key == "P":
  4388. self.queue_game.put(self.c + key + self.r + self.nl)
  4389. # Save specials, save 10 ports
  4390. self.specials = self.game.gamedata.special_ports()
  4391. # Save 20 ports. https://stackoverflow.com/questions/7971618/python-return-first-n-keyvalue-pairs-from-dict#7971655
  4392. self.old_ports = {
  4393. k: self.game.gamedata.ports[k]
  4394. for k in list(self.game.gamedata.ports)[:20]
  4395. }
  4396. self.game.gamedata.reset_ports()
  4397. # Activate CIM Port Report
  4398. report = CIMPortReport(self.game)
  4399. d = report.whenDone()
  4400. d.addCallback(self.port_report)
  4401. d.addErrback(self.welcome_back)
  4402. return
  4403. elif key == "W":
  4404. self.queue_game.put(self.c + key + self.r + self.nl)
  4405. self.game.gamedata.reset_warps()
  4406. # Activate CIM Warp Report
  4407. report = CIMWarpReport(self.game)
  4408. d = report.whenDone()
  4409. d.addCallback(self.warp_report)
  4410. d.addErrback(self.welcome_back)
  4411. return
  4412. elif key == "M":
  4413. self.queue_game.put(self.c + key + self.r + self.nl)
  4414. self.activate_macro(1)
  4415. if False:
  4416. ask = PlayerInput(self.game)
  4417. d = ask.prompt(
  4418. "How many times?", 10, name="times", abort_blank=True, digits=True
  4419. )
  4420. d.addCallback(self.activate_macro)
  4421. d.addErrback(self.welcome_back)
  4422. return
  4423. elif key == "B":
  4424. self.queue_game.put(self.c + key + self.r + self.nl)
  4425. # Display Busts
  4426. busting = bustViewer(self.game)
  4427. d = busting.whenDone()
  4428. d.addCallback(self.welcome_back)
  4429. d.addErrback(self.welcome_back)
  4430. return
  4431. elif key == "S":
  4432. self.queue_game.put(self.c + key + self.r + self.nl)
  4433. # Scripts
  4434. self.activate_scripts_menu()
  4435. return
  4436. elif key == "R":
  4437. self.queue_game.put(self.c + key + self.r + self.nl)
  4438. s = self.game.gamedata.special_ports()
  4439. box = Boxes(14, color=self.c)
  4440. self.queue_game.put(box.top())
  4441. self.queue_game.put(box.row(self.c1 + " Sector Class "))
  4442. for sector, data in s.items():
  4443. self.queue_game.put(
  4444. box.row(
  4445. "{0} {1:5}{2} {3:^5} ".format(
  4446. self.c1, sector, self.c2, data["class"]
  4447. )
  4448. )
  4449. )
  4450. self.queue_game.put(box.bottom())
  4451. elif key == "D":
  4452. self.queue_game.put(self.c + key + self.r + self.nl)
  4453. # (Re) Display Trade Report
  4454. show_maxlines = self.get_display_maxlines()
  4455. if self.trade_report:
  4456. for t in self.trade_report[:show_maxlines]:
  4457. self.queue_game.put(t + self.nl)
  4458. self.queue_game.put(self.nl + Boxes.alert("Proxy done.", base="green"))
  4459. self.observer.load(self.save)
  4460. self.save = None
  4461. self.keepalive = None
  4462. self.prompt = None
  4463. return
  4464. else:
  4465. self.queue_game.put("Missing trade_report." + self.nl)
  4466. elif key == "E":
  4467. self.queue_game.put(self.c + key + self.r + self.nl)
  4468. self.queue_game.put(Boxes.alert("Saving..."))
  4469. then_do = coiterate(self.game.gamedata.save())
  4470. then_do.addCallback(self.welcome_back)
  4471. return
  4472. elif key == "C":
  4473. self.queue_game.put(self.c + key + self.r + self.nl)
  4474. self.activate_config_menu()
  4475. return
  4476. # self.queue_game.put(pformat(self.portdata).replace("\n", "\n\r") + self.nl)
  4477. # self.queue_game.put(pformat(self.warpdata).replace("\n", "\n\r") + self.nl)
  4478. # elif key == "Q":
  4479. # self.queue_game.put(self.c + key + self.r + self.nl)
  4480. #
  4481. # # This is an example of chaining PlayerInput prompt calls.
  4482. #
  4483. # ask = PlayerInput(self.game)
  4484. # d = ask.prompt("What is your quest?", 40, name="quest", abort_blank=True)
  4485. #
  4486. # # Display the user's input
  4487. # d.addCallback(ask.output)
  4488. #
  4489. # d.addCallback(
  4490. # lambda ignore: ask.prompt(
  4491. # "What is your favorite color?", 10, name="color"
  4492. # )
  4493. # )
  4494. # d.addCallback(ask.output)
  4495. #
  4496. # d.addCallback(
  4497. # lambda ignore: ask.prompt(
  4498. # "What is the meaning of the squirrel?",
  4499. # 12,
  4500. # name="squirrel",
  4501. # digits=True,
  4502. # )
  4503. # )
  4504. # d.addCallback(ask.output)
  4505. #
  4506. # def show_values(show):
  4507. # log.debug(show)
  4508. # self.queue_game.put(pformat(show).replace("\n", "\n\r") + self.nl)
  4509. #
  4510. # d.addCallback(lambda ignore: show_values(ask.keep))
  4511. # d.addCallback(self.welcome_back)
  4512. #
  4513. # # On error, just return back
  4514. # # This doesn't seem to be getting called.
  4515. # # d.addErrback(lambda ignore: self.welcome_back)
  4516. # d.addErrback(self.welcome_back)
  4517. # return
  4518. elif key == "X":
  4519. self.queue_game.put(self.c + key + self.r + self.nl)
  4520. self.queue_game.put(Boxes.alert("Proxy done.", base="green"))
  4521. self.observer.load(self.save)
  4522. self.save = None
  4523. # It isn't running (NOW), so don't try to stop it.
  4524. # self.keepalive.stop()
  4525. self.keepalive = None
  4526. # Ok, this is a HORRIBLE idea, because the prompt might be
  4527. # outdated.
  4528. # self.queue_game.put(self.prompt)
  4529. self.prompt = None
  4530. # Send '\r' to re-display the prompt
  4531. # instead of displaying the original one.
  4532. self.game.queue_player.put("d")
  4533. # Were we asked to do something when we were done here?
  4534. if self.defer:
  4535. reactor.CallLater(0, self.defer.callback)
  4536. # self.defer.callback()
  4537. self.defer = None
  4538. return
  4539. else:
  4540. self.fullmenu()
  4541. self.keepalive.start(30, True)
  4542. self.menu()
  4543. def activate_config_menu(self):
  4544. self.observer.disconnect("player", self.player)
  4545. self.observer.connect("player", self.config_player)
  4546. self.config_menu()
  4547. def deactivate_config_menu(self, *data):
  4548. log.warn("deactivate_config_menu ({0})".format(data))
  4549. self.observer.disconnect("player", self.config_player)
  4550. self.observer.connect("player", self.player)
  4551. self.welcome_back()
  4552. def activate_macro(self, *data):
  4553. log.warn("macro: ({0})".format(data))
  4554. macro = self.game.gamedata.get_config("Macro", "D")
  4555. # Macro processing would go in here ...
  4556. macro = macro.replace("^", "\r")
  4557. log.warn("macro: [{0}]".format(repr(macro)))
  4558. self.game.queue_player.put(macro)
  4559. log.warn("Restore ...")
  4560. self.observer.load(self.save)
  4561. self.save = None
  4562. self.keepalive = None
  4563. if self.defer:
  4564. reactor.CallLater(0, self.defer.callback)
  4565. self.defer = None
  4566. # self.welcome_back()
  4567. def activate_scripts_menu(self):
  4568. self.observer.disconnect("player", self.player)
  4569. self.observer.connect("player", self.scripts_player)
  4570. self.scripts_menu()
  4571. def option_entry(self, entry):
  4572. if len(entry) > 0:
  4573. # Ok, they gave us something
  4574. self.game.gamedata.set_config(self.option_select, entry.strip())
  4575. else:
  4576. self.queue_game.put("Edit aborted." + self.nl)
  4577. self.config_menu()
  4578. def option_input(self, option):
  4579. if len(option) > 0:
  4580. option = int(option)
  4581. if option in self.config_opt:
  4582. # Ok, it's a valid option!
  4583. self.option_select = self.config_opt[option]
  4584. ask = PlayerInput(self.game)
  4585. if self.option_select == "Macro":
  4586. d = ask.prompt("Change {0} to?".format(self.option_select), 48)
  4587. else:
  4588. d = ask.prompt("Change {0} to?".format(self.option_select), 18)
  4589. d.addCallback(self.option_entry)
  4590. # d.addErrback(self.config_menu)
  4591. else:
  4592. self.queue_game.put("Unknown option, sorry." + self.nl)
  4593. self.config_menu()
  4594. else:
  4595. # Aborted
  4596. self.config_menu()
  4597. def config_player(self, chunk: bytes):
  4598. """ Data from player (in bytes). """
  4599. chunk = chunk.decode("latin-1", "ignore")
  4600. key = chunk.upper()
  4601. if key == "C":
  4602. self.queue_game.put(self.c + key + self.r + self.nl)
  4603. self.game.gamedata.config = {}
  4604. elif key == "E":
  4605. self.queue_game.put(self.c + key + self.r + self.nl)
  4606. ask = PlayerInput(self.game)
  4607. d = ask.prompt(
  4608. "Which to edit?", 4, name="option", abort_blank=True, digits=True
  4609. )
  4610. d.addCallback(self.option_input)
  4611. d.addErrback(self.config_menu)
  4612. return
  4613. elif key in ("1", "2", "3", "4", "5", "6", "7", "8", "9"):
  4614. self.queue_game.put(self.c + key + self.r + self.nl)
  4615. option = int(key)
  4616. if option in self.config_opt:
  4617. # Ok, it's a valid option!
  4618. self.option_select = self.config_opt[option]
  4619. ask = PlayerInput(self.game)
  4620. if self.option_select == "Macro":
  4621. d = ask.prompt("Change {0} to?".format(self.option_select), 48)
  4622. else:
  4623. d = ask.prompt("Change {0} to?".format(self.option_select), 18)
  4624. d.addCallback(self.option_entry)
  4625. # d.addErrback(self.config_menu)
  4626. return
  4627. else:
  4628. self.queue_game.put("Unknown option, sorry." + self.nl)
  4629. elif key == "X":
  4630. self.queue_game.put(self.c + key + self.r + self.nl)
  4631. self.deactivate_config_menu()
  4632. return
  4633. else:
  4634. self.queue_game.put(self.c + "?" + self.r + self.nl)
  4635. self.config_menu()
  4636. def config_menu(self, *_):
  4637. titlecolor = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  4638. tc = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
  4639. c1 = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
  4640. c2 = merge(Style.BRIGHT + Fore.CYAN + Back.BLUE)
  4641. # box = Boxes(44, color=titlecolor)
  4642. box = Boxes(44, color=tc)
  4643. self.queue_game.put(box.top())
  4644. # self.queue_game.put(box.row(titlecolor + "{0:^44}".format("Configuration")))
  4645. self.queue_game.put(box.row(tc + "{0:^44}".format("Configuration")))
  4646. self.queue_game.put(box.middle())
  4647. def config_option(index, key, value):
  4648. # Really, python? Because
  4649. # builtins.ValueError: Precision not allowed in integer format specifier
  4650. if type(value) == int:
  4651. value = str(value)
  4652. row = "{0}{1:2} {2:19}{3}{4:<20.20}".format(c1, index, key, c2, value)
  4653. self.queue_game.put(box.row(row))
  4654. def menu_item(ch, desc):
  4655. row = "{0} {1} {2}-{3} {4:39}".format(c1, ch, c2, c1, desc)
  4656. # self.queue_game.put(
  4657. # " " + c1 + ch + c2 + " - " + c1 + desc + self.nl
  4658. # )
  4659. self.queue_game.put(box.row(row))
  4660. index = 1
  4661. self.config_opt = {}
  4662. for k in sorted(self.game.gamedata.config.keys()):
  4663. # for k, v in self.game.gamedata.config.items():
  4664. v = self.game.gamedata.config[k]
  4665. self.config_opt[index] = k
  4666. config_option(index, k, v)
  4667. index += 1
  4668. self.queue_game.put(box.middle())
  4669. menu_item("C", "Clear Config")
  4670. menu_item("E", "Edit Item")
  4671. menu_item("X", "eXit")
  4672. self.queue_game.put(box.bottom())
  4673. self.queue_game.put(" " + tc + "-=>" + self.r + " ")
  4674. def deactivate_scripts_menu(self, *data):
  4675. log.warn("deactivate_scripts_menu ({0})".format(data))
  4676. self.observer.disconnect("player", self.scripts_player)
  4677. self.observer.connect("player", self.player)
  4678. # Did they request exit?
  4679. if len(data) > 0 and type(data[0]) == dict:
  4680. info = data[0]
  4681. if "exit" in info and info["exit"]:
  4682. log.warn("exit proxy...")
  4683. # Exit Proxy Code
  4684. self.queue_game.put(
  4685. self.nl + Boxes.alert("Proxy done.", base="green", style=3)
  4686. )
  4687. self.observer.load(self.save)
  4688. self.save = None
  4689. # It isn't running (NOW), so don't try to stop it.
  4690. # self.keepalive.stop()
  4691. self.keepalive = None
  4692. # Ok, this is a HORRIBLE idea, because the prompt might be
  4693. # outdated.
  4694. # self.queue_game.put(self.prompt)
  4695. self.prompt = None
  4696. # I'm not sure where we are, we might not be at a prompt.
  4697. # let's check!
  4698. if re.match(r"Command \[TL=.* \(\?=Help\)\? :", self.game.getPrompt()):
  4699. # Send '\r' to re-display the prompt
  4700. # instead of displaying the original one.
  4701. self.game.queue_player.put("d")
  4702. # Were we asked to do something when we were done here?
  4703. if self.defer:
  4704. reactor.CallLater(0, self.defer.callback)
  4705. # self.defer.callback()
  4706. self.defer = None
  4707. return
  4708. log.warn("calling welcome_back")
  4709. self.welcome_back()
  4710. def scripts_menu(self, *_):
  4711. c1 = merge(Style.BRIGHT + Fore.CYAN)
  4712. c2 = merge(Style.NORMAL + Fore.CYAN)
  4713. box = Boxes(40, color=c1)
  4714. self.queue_game.put(box.top())
  4715. self.queue_game.put(box.row(c1 + "{0:^40}".format("Scripts")))
  4716. self.queue_game.put(box.middle())
  4717. def menu_item(ch, desc):
  4718. row = " {0}{1} {2}-{3} {4:35}".format(c1, ch, c2, c1, desc)
  4719. # self.queue_game.put(
  4720. # " " + c1 + ch + c2 + " - " + c1 + desc + self.nl
  4721. # )
  4722. self.queue_game.put(box.row(row))
  4723. menu_item("1", "Ports (Trades between two sectors)")
  4724. menu_item("!", "Terrorize Ports/Trades")
  4725. menu_item("2", "Explore (Strange new sectors)")
  4726. menu_item("3", "Space... the broken script...")
  4727. menu_item("4", "Upgrade Planet")
  4728. # menu_item("5", "Colonize Planet")
  4729. menu_item("5", "Colonize Planet v2.0")
  4730. menu_item("%", "Maxumize Fighter Production")
  4731. menu_item("6", "Trade Evil (Does SSM)")
  4732. menu_item("X", "eXit")
  4733. self.queue_game.put(box.bottom())
  4734. self.queue_game.put(" " + c1 + "-=>" + self.r + " ")
  4735. def terror(self, *_):
  4736. log.debug("terror {0}".format(_))
  4737. loops = _[0]
  4738. if loops.strip() == "":
  4739. self.deactivate_scripts_menu()
  4740. else:
  4741. # Ok, we have something here, I think...
  4742. terror = ScriptTerror(self.game, self, int(loops))
  4743. d = terror.whenDone()
  4744. d.addCallback(self.deactivate_scripts_menu)
  4745. d.addErrback(self.deactivate_scripts_menu)
  4746. def scripts_player(self, chunk: bytes):
  4747. """ Data from player (in bytes). """
  4748. chunk = chunk.decode("latin-1", "ignore")
  4749. key = chunk.upper()
  4750. if key == "1":
  4751. self.queue_game.put(self.c + key + self.r + self.nl)
  4752. # Activate this magical event here
  4753. ports = ScriptPort(self.game)
  4754. d = ports.whenDone()
  4755. # d.addCallback(self.scripts_menu)
  4756. # d.addErrback(self.scripts_menu)
  4757. d.addCallback(self.deactivate_scripts_menu)
  4758. d.addErrback(self.deactivate_scripts_menu)
  4759. return
  4760. elif key == "!":
  4761. self.queue_game.put(self.c + key + self.r + self.nl)
  4762. ask = PlayerInput(self.game)
  4763. # This is TERROR, so do something!
  4764. ask.color(merge(Style.BRIGHT + Fore.WHITE + Back.RED))
  4765. ask.colorp(merge(Style.BRIGHT + Fore.YELLOW + Back.RED))
  4766. d = ask.prompt(
  4767. "How many loops of terror?",
  4768. 4,
  4769. name="loops",
  4770. digits=True,
  4771. abort_blank=True,
  4772. )
  4773. d.addCallback(self.terror, ask)
  4774. d.addErrback(self.deactivate_scripts_menu)
  4775. return
  4776. elif key == "2":
  4777. self.queue_game.put(self.c + key + self.r + self.nl)
  4778. explore = ScriptExplore(self.game)
  4779. d = explore.whenDone()
  4780. d.addCallback(self.deactivate_scripts_menu)
  4781. d.addErrback(self.deactivate_scripts_menu)
  4782. return
  4783. elif key == "3":
  4784. self.queue_game.put(self.c + key + self.r + self.nl)
  4785. space = ScriptSpace(self.game)
  4786. d = space.whenDone()
  4787. d.addCallback(self.deactivate_scripts_menu)
  4788. d.addErrback(self.deactivate_scripts_menu)
  4789. return
  4790. elif key == "4":
  4791. self.queue_game.put(self.c + key + self.r + self.nl)
  4792. upgrade = PlanetUpScript(self.game)
  4793. d = upgrade.whenDone()
  4794. d.addCallback(self.deactivate_scripts_menu)
  4795. d.addErrback(self.deactivate_scripts_menu)
  4796. return
  4797. elif key == "5":
  4798. self.queue_game.put(self.c + key + self.r + self.nl)
  4799. # colo = ColoScript(self.game)
  4800. colo = ColoScript2(self.game)
  4801. d = colo.whenDone()
  4802. d.addCallback(self.deactivate_scripts_menu)
  4803. d.addErrback(self.deactivate_scripts_menu)
  4804. return
  4805. elif key == "%":
  4806. self.queue_game.put(self.c + key + self.r + self.nl)
  4807. maxfigs = MaxFighterMake(self.game)
  4808. d = maxfigs.whenDone()
  4809. d.addCallback(self.deactivate_scripts_menu)
  4810. d.addErrback(self.deactivate_scripts_menu)
  4811. return
  4812. elif key == "6":
  4813. self.queue_game.put(self.c + key + self.r + self.nl)
  4814. evil = evilTrade(self.game)
  4815. d = evil.whenDone()
  4816. d.addCallback(self.deactivate_scripts_menu)
  4817. d.addErrback(self.deactivate_scripts_menu)
  4818. return
  4819. elif key == "X":
  4820. self.queue_game.put(self.c + key + self.r + self.nl)
  4821. self.deactivate_scripts_menu()
  4822. return
  4823. else:
  4824. self.queue_game.put(self.c + "?" + self.r + self.nl)
  4825. self.scripts_menu()
  4826. def welcome_back(self, *_):
  4827. log.debug("welcome_back")
  4828. self.keepalive.start(30, True)
  4829. self.menu()