Repository navigation
Expand file tree
/
Copy pathrepair.py
More file actions
592 lines (546 loc) · 24.7 KB
/
Copy pathrepair.py
File metadata and controls
592 lines (546 loc) · 24.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
"""Repair your gear at the nearest repair shop and put it back on: ;repair
;repair appraise what you wear and hold; repair every piece at or below the floor
;repair check appraise only: each piece's condition, nothing moves
;repair check <piece> the same for those pieces, against the floor
;repair <piece> ... those pieces, whatever their condition (worn, held or in a container)
;repair floor=<N> the floor in % (the profile's `repair_floor`, else 80)
;repair tools ANALYZE the profile's `repair_tools`; the worn ones go to the tool repairman
;repair tools <tool> that tool, whatever its condition
;repair pickup walk to the shop your ticket names and collect what is ready
;repair ... back walk back to where you started when done
;repair return (typed while it runs) hand in nothing more, collect the tickets given, end
;stop repair quit at once; the tickets keep for ;repair pickup
What it does
- Reads each condition (APPRAISE QUICK; ANALYZE for a tool) and takes in the pieces
whose band tops out at or below the floor.
- The pieces: the profile's `repair_items`, else the hands and everything worn.
- A piece in a sheath or container is taken out to appraise and put back where it
was; a weapon of the profile's `weapons` goes back in with SHEATHE.
- GIVEs each to the nearest known repairman for the estimate, again to pay; stows the ticket.
- A price the purse cannot cover: coins from the nearest teller, then a second round.
- Waits out the longest estimate (a roisaen is a minute), hands the tickets back and
wears each piece again; a held one stays in hand, a tool is stowed.
When it stops
- death, or ;repair return once the tickets are collected
- no known shop on the map, or none reachable
- a ticket refused, or not ready after a dozen waits (the ticket is kept)
Shops and condition bands: client/game/repair.py (Elanthipedia: Repair; dr-scripts'
repair.lic and base-town.yaml).
"""
from client.game import bank, hands, money, travel
from client.game.act import ask, missing
from client.game.loop import wants_stop
from client.game.mapdb import MapDB
from client.game.money import phrase
from client.game.profile import load_profile
from client.game.repair import (
DEFAULT_FLOOR,
SHOPS,
TOOL_SHOPS,
candidates,
classify_give,
classify_pickup,
condition,
home_of,
inside,
needs_repair,
read_ticket,
shop_room,
weapon_homes,
)
from client.game.walker import locate, walk
# The design notes the manual above leaves out: what each rule came
# from, with its issue — read by people, never served as ;help.
_NOTES = """Repair your gear at the nearest repair shop: ;repair
;repair appraise what you wear and hold, take every piece at or below the floor to the nearest shop, wait, wear it back
;repair check appraise only: each piece's condition, nothing moves
;repair plate shield those pieces, whatever their condition (worn, held, or GOT from a container)
;repair floor=70 repair pieces whose condition tops out at or below 70 % (the profile's `repair_floor`, else 80)
;repair tools ANALYZE the profile's `repair_tools` (mortar, pestle...) and take every one at or below the floor to Rangu;
a bare mortar or pestle is the profile's `mortar` or `pestle`, named whole (iron pestle)
;repair tools pestle that tool, whatever its condition
;repair pickup walk to the shop your tickets name and collect what is ready
;repair ... back walk back to where you started when done
;repair return (typed while it runs) hand in nothing more, collect the tickets already given, end
Weapons, shields and armor wear down in use, and a repair shop's NPC
mends any of them for coin (Elanthipedia: Repair; dr-scripts'
repair.lic is the same errand). Each piece is APPRAISEd QUICK (5 s of
roundtime) for its condition, the wiki's ten phrases from "in pristine
condition" to "battered and practically destroyed", each a band of
the item's health; a piece whose band tops out at or below the floor
is taken in — a worn one REMOVEd, a held one given from the hand. The
rest stay on. An answer without a condition (a pouch, a sack) is
skipped. The pieces are the profile's `repair_items` when set, else
what the hands hold and everything worn as the parser's last INV LIST
saw it (;sheet takes one at login).
The script walks to the nearest room the map tags `repair` whose
repairman it knows (client/game/repair.py's SHOPS, after dr-scripts'
base-town.yaml: Catrox in the Crossing, Randal at Wolf Clan, ...),
GIVEs the piece for the estimate — "That will cost 108 Kronars to
repair ... ready in 5 roisaen", in copper — and GIVEs it again at once
to pay: the offer lapses within twenty seconds. The ticket he hands
back is STOWed. A piece he calls unscratched goes back on. A price
the purse cannot cover puts the piece back; after the round the
shortfall is WITHDRAWn at the nearest teller (client/game/bank.py)
and those pieces go in on a second round. Then it waits the longest
estimate out at the shop — a roisaen is a minute — GETs each ticket,
GIVEs it back ("handed back some light full plate"), a "not done for
another N roisaen" waited out, and WEARs each worn piece back; a held
one stays in the hand. Captured 2026-09-24 at Catrox's Forge (#307).
Stops on death. `pickup` collects tickets from an earlier run: LOOK
AT MY TICKET names the shop, the script walks there and hands each in.
`tools` is the same errand for crafting tools, which no weapon or
armor shop takes: each is GOT and ANALYZEd — APPRAISE names no
condition for a tool, ANALYZE does, in the same phrases, 10 s of
roundtime — and STOWed back, and the worn ones go to the Crossing
Engineering Society's Repairman Rangu ("Only crafting tools are
repaired here"; Elanthipedia: Engineering Society (Crossing), Crafting
tools), who quotes and tickets the way Catrox does and hands each tool
back to be STOWed. A pestle "battered and practically destroyed" cost
20 Kronars and 10 roisaen there (2026-09-26); a new one is 125, and a
tool that far gone is past field repair with a wire brush and oil
(Blacksmithing techniques: Advanced Tool Repair).
Stop with: ;stop repair, or ;repair return (keeps the tickets' pickup).
"""
ROISAEN_SECONDS = 60 # a roisaen is a real minute (client/game/eltime.py)
ALMOST_SECONDS = 30 # "just give me a few more moments here"
MAX_PICKUP_TRIES = 12 # per ticket: the fuse under the waits
MAX_TICKETS = 20
# A piece that is not on you, beyond act.NOT_FOUND's two wordings: what
# APPRAISE, REMOVE and WEAR answer for one you neither carry nor wear.
NOT_ON_YOU = ("don't have", "not wearing")
def parse_words(words):
"""(mode, items, floor, back) from the words typed after ;repair."""
mode, items, floor, back = "repair", [], None, False
for word in words:
lowered = word.lower()
if lowered in ("check", "pickup", "tools"):
mode = lowered
elif lowered == "back":
back = True
elif lowered.startswith("floor=") and lowered[6:].isdigit():
floor = int(lowered[6:])
else:
items.append(lowered)
return mode, items, floor, back
def appraise(s, pieces, floor, echo_all=True, named=False, homes=None):
"""APPRAISE QUICK each (noun, place); the ones at or below the floor,
as [(noun, place, reading)]. Every reading is echoed. A piece the
game will not appraise inside something (a sheathed weapon, #429) is
taken out, appraised in hand and put back where it was — its place
then home_of's, a weapon of the profile's (`homes`) sheathed again.
A `named` piece (the profile's list, or typed) whose answer names no
condition is said; a worn sack's silence is not."""
due = []
for noun, place in pieces:
if s.dead:
return due
answer = ask(s, f"appraise my {noun} quick")
s.waitrt()
if missing(answer, NOT_ON_YOU):
s.echo(f"repair: no {noun} on you — skipped")
continue
if inside(answer):
place = home_of(noun, getattr(s.state, "possessions", None), homes)
if not take(s, noun, place):
continue
answer = ask(s, f"appraise my {noun} quick")
s.waitrt()
put_back(s, noun, place)
reading = condition(answer)
if reading is None:
if named:
s.echo(f"repair: APPRAISE named no condition for the {noun} — skipped")
continue
verdict = needs_repair(reading, floor)
if echo_all or verdict:
mark = " — to repair" if verdict else ""
s.echo(
f"repair: {noun} is {reading[0]} ({reading[1]}-{reading[2]} %){mark}"
)
if verdict:
due.append((noun, place, reading))
return due
def analyze(s, tools, floor):
"""GET and ANALYZE each tool noun, STOW it back unless it was in a
hand; the ones at or below the floor as [(noun, place, reading)],
place "held" or "stowed". Every reading is echoed."""
due = []
for noun in tools:
if s.dead:
return due
place = "held" if hands.holding(s, noun) else "stowed"
if place == "stowed":
if not free_hand(s):
return due
answer = ask(s, f"get my {noun}")
s.waitrt()
if missing(answer, NOT_ON_YOU):
s.echo(f"repair: no {noun} on you — skipped")
continue
reading = condition(ask(s, f"analyze my {noun}"))
s.waitrt()
if place == "stowed":
ask(s, f"stow my {noun}")
s.waitrt()
if reading is None:
s.echo(f"repair: ANALYZE named no condition for the {noun} — skipped")
continue
verdict = needs_repair(reading, floor)
mark = " — to repair" if verdict else ""
s.echo(f"repair: {noun} is {reading[0]} ({reading[1]}-{reading[2]} %){mark}")
if verdict:
due.append((noun, place, reading))
return due
def free_hand(s):
"""A hand empty for a REMOVE or a ticket: the left hand's item STOWed
when both are full (hands.free_one, the answer the judge). False,
said, when it would not go."""
if not hands.full(s):
return True
noun = hands.held(s)["left"]
freed = hands.free_one(s, ask=ask)
s.waitrt()
if not freed:
s.echo(f"repair: both hands full and the {noun} would not stow — stopping")
return False
s.echo(f"repair: stowed the {noun} to free a hand")
return True
def take(s, noun, place):
"""The piece into a hand: a worn one REMOVEd, a held one already
there, a sheathed weapon WIELDed, anything else GOT. False, said,
when it did not come."""
if place == "held" and hands.holding(s, noun):
return True
if not free_hand(s):
return False
if place in ("worn", "held"):
answer = ask(s, f"remove my {noun}")
s.waitrt()
if not missing(answer, NOT_ON_YOU):
return True
if place.startswith("sheathed:"):
# WIELD is how a weapon leaves its sheath (DRAW is an attack),
# by its INV LIST id when listed (#456).
answer = hands.wield(s, noun, ask=ask)
s.waitrt()
if not missing(answer, NOT_ON_YOU) and hands.holding(s, noun):
return True
answer = ask(s, f"get my {noun}")
s.waitrt()
if missing(answer, NOT_ON_YOU):
s.echo(f"repair: could not take the {noun} off or out — skipped")
return False
return True
def put_back(s, noun, place):
"""The piece where it was: worn ones WORN back, a stowed tool
STOWed, a weapon SHEATHEd where WIELD drew it from, anything else PUT in
the container it came from (STOWed when that is refused), a held
one left in the hand."""
if place == "stowed":
ask(s, f"stow my {noun}")
s.waitrt()
elif place.startswith("sheathed:"):
container = place.split(":", 1)[1]
if not hands.sheathe(s, noun, container, ask=ask):
hands.stow(s, noun, ask=ask)
s.echo(f"repair: the {noun} would not go in the {container} — stowed")
s.waitrt()
elif place.startswith("in:"):
container = place.split(":", 1)[1]
answer = ask(s, f"put my {noun} in my {container}")
s.waitrt()
if hands.refused(answer):
hands.stow(s, noun, ask=ask)
s.echo(f"repair: the {noun} would not go in the {container} — stowed")
elif place == "worn":
answer = ask(s, f"wear my {noun}")
s.waitrt()
if missing(answer, NOT_ON_YOU) or "can't" in answer.lower():
ask(s, f"stow my {noun}")
s.waitrt()
s.echo(f"repair: the {noun} would not go on — stowed")
def hand_in(s, name, due, purse):
"""GIVE each due piece to `name` twice — the estimate, then the
payment; a first GIVE answered with a ticket paid an estimate still
standing (Rangu, 2026-09-26: back from the teller within seconds)
and counts as handed in. Returns (given, short, stopped): given [(noun, place,
roisaen)], short [(noun, place, copper, currency)] the purse could
not cover, stopped True once "return" was typed. `purse`
({currency: copper}) is spent down as tickets come back."""
given, short = [], []
for noun, place, _ in due:
if s.dead or wants_stop(s):
s.echo("repair: handing in nothing more")
return given, short, True
if not take(s, noun, place):
continue
answer = classify_give(ask(s, f"give my {noun} to {name}"))
if answer["kind"] == "busy":
s.sleep(5)
answer = classify_give(ask(s, f"give my {noun} to {name}"))
if answer["kind"] == "ticket":
copper, currency = answer["copper"], answer["currency"]
if purse is not None and copper and currency:
purse[currency] = purse.get(currency, 0) - copper
cost = phrase(copper, currency) if copper and currency else "the estimate"
s.echo(
f"repair: {noun} handed in for {cost}, "
f"ready in {answer['roisaen']} roisaen"
)
ask(s, "stow my ticket")
s.waitrt()
given.append((noun, place, answer["roisaen"]))
continue
if answer["kind"] != "quote":
reason = {
"undamaged": "not a scratch on it",
"refused": f"{name} will not repair it",
"short": "not enough coin",
}.get(answer["kind"], "no estimate")
s.echo(f"repair: {noun}: {reason} — put back")
put_back(s, noun, place)
continue
copper, currency = answer["copper"], answer["currency"]
if purse is not None and purse.get(currency, 0) < copper:
s.echo(
f"repair: {noun} costs {phrase(copper, currency)}, you carry "
f"{phrase(purse.get(currency, 0), currency)} — put back for now"
)
short.append((noun, place, copper, currency))
put_back(s, noun, place)
continue
paid = classify_give(ask(s, f"give my {noun} to {name}"))
if paid["kind"] != "ticket":
s.echo(f"repair: {noun}: {name} did not take it — put back")
put_back(s, noun, place)
if paid["kind"] == "short":
short.append((noun, place, copper, currency))
continue
if purse is not None:
purse[currency] = purse.get(currency, 0) - copper
roisaen = paid["roisaen"] or answer["roisaen"]
s.echo(
f"repair: {noun} handed in for {phrase(copper, currency)}, "
f"ready in {roisaen} roisaen"
)
ask(s, "stow my ticket")
s.waitrt()
given.append((noun, place, roisaen))
return given, short, False
def wait_out(s, seconds):
"""Sleep `seconds` in one-second slices; a typed return is noted
(the tickets are collected anyway), death ends it: False."""
left = seconds
noted = False
while left > 0:
s.sleep(min(1, left))
left -= 1
if s.dead:
return False
if not noted and wants_stop(s):
s.echo("repair: return noted — collecting the tickets first")
noted = True
return True
def collect(s, name, places, default="worn"):
"""Hand each of `name`'s tickets back until none is left, waiting out
"not done for another N roisaen"; each piece back goes where
`places` ({noun: place}) says, else `default`. The count."""
returned = 0
for _ in range(MAX_TICKETS):
if s.dead:
return returned
if not free_hand(s):
return returned
if missing(ask(s, f"get my {name} ticket"), NOT_ON_YOU):
return returned
s.waitrt()
for _try in range(MAX_PICKUP_TRIES):
answer = classify_pickup(ask(s, f"give my ticket to {name}"))
if answer["kind"] == "returned":
noun = answer["noun"]
s.echo(f"repair: {answer['item']} back")
put_back(s, noun, places.get(noun, default))
returned += 1
break
if answer["kind"] == "wait":
seconds = answer["roisaen"] * ROISAEN_SECONDS or ALMOST_SECONDS
s.echo(f"repair: not ready — waiting {seconds} s")
if not wait_out(s, seconds):
return returned
continue
s.echo(f"repair: {name} did not take the ticket — kept, stopping")
ask(s, "stow my ticket")
return returned
else:
s.echo("repair: waited too long on a ticket — kept, stopping")
ask(s, "stow my ticket")
return returned
return returned
def purse_of(s):
"""WEALTH's carried coin per currency (money.purse), or None when
unreadable."""
return money.purse(s, ask) or None
def walk_to_shop(s, mapdb, walk_fn, rooms=None, shops=SHOPS):
"""Walk to the nearest known repair shop (`shops`: the gear shops by
default, TOOL_SHOPS for tools); its repairman's name, or None, said.
A gear shop must carry the map's `repair` tag; the tool shop has
none."""
tagged = set(mapdb.rooms_tagged("repair"))
goals = {
room
for room in (rooms or shops)
if room in mapdb.rooms and (room in TOOL_SHOPS or room in tagged)
}
if not goals:
s.echo("repair: the map has no repair shop whose repairman is known")
return None
if not travel.go(s, goals, "the repair shop", db=mapdb, walk=walk_fn):
s.echo("repair: could not reach a repair shop — stopping")
return None
here = locate(mapdb, s.state)
name = {**SHOPS, **TOOL_SHOPS}.get(here)
if name is None:
s.echo("repair: arrived, but not in a shop whose repairman is known")
return name
def repair(s, mapdb, walk_fn, due, shops=SHOPS):
"""Hand every due piece in at the nearest shop, fetch coins for what
the purse could not cover, wait and collect. The pieces back."""
name = walk_to_shop(s, mapdb, walk_fn, shops=shops)
if name is None:
return 0
shop = locate(mapdb, s.state)
purse = purse_of(s)
given, short, stopped = hand_in(s, name, due, purse)
if short and not s.dead and not stopped:
currency = short[0][3]
need = sum(copper for *_, copper, cur in short if cur == currency)
need -= (purse or {}).get(currency, 0)
if need > 0 and bank.withdraw(
s, mapdb, walk_fn, ask, "repair", need, currency, retry="run ;repair again"
):
if travel.go(s, {shop}, f"{name}'s shop", db=mapdb, walk=walk_fn):
more, _, _ = hand_in(
s, name, [(n, p, None) for n, p, *_ in short], purse_of(s)
)
given += more
if not given:
s.echo("repair: nothing handed in")
return 0
longest = max(roisaen or 0 for *_, roisaen in given)
if longest:
s.echo(f"repair: waiting {longest} roisaen for {name}")
if not wait_out(s, longest * ROISAEN_SECONDS):
return 0
return collect(s, name, {noun: place for noun, place, _ in given})
def pickup(s, mapdb, walk_fn, homes=None):
"""Collect tickets from an earlier run: the ticket names the shop. A
weapon of the profile's (`homes`, weapon_homes) is sheathed back."""
if not free_hand(s):
return 0
if missing(ask(s, "get my ticket"), NOT_ON_YOU):
s.echo("repair: no repair ticket on you")
return 0
ticket = read_ticket(ask(s, "look at my ticket"))
ask(s, "stow my ticket")
if ticket is None:
s.echo("repair: that ticket names no shop — stopping")
return 0
room = shop_room(ticket["shop"])
if room is None:
s.echo(f"repair: {ticket['shop']}'s shop is not one the map knows — stopping")
return 0
name = walk_to_shop(s, mapdb, walk_fn, rooms={room})
if name is None:
return 0
# A tool shop's ticket is a tool, never worn: STOWed (2026-09-26,
# the pestle's pickup tried WEAR first).
if name in TOOL_SHOPS.values():
return collect(s, name, {}, "stowed")
places = {
noun: f"sheathed:{container}" for noun, container in (homes or {}).items()
}
return collect(s, name, places, "worn")
def run(s, words, mapdb=None, walk_fn=walk, profile=None):
mode, items, floor, back = parse_words(words)
profile = profile if profile is not None else load_profile(s.state.name)
if floor is None:
floor = int(profile.get("repair_floor") or DEFAULT_FLOOR)
if mode == "pickup":
if mapdb is None:
s.echo("repair: pickup needs the map — none loaded")
return
start = locate(mapdb, s.state)
count = pickup(s, mapdb, walk_fn, homes=weapon_homes(profile))
s.echo(f"repair: {count} piece{'s' if count != 1 else ''} collected")
walk_home(s, mapdb, walk_fn, start, back)
return
if mode == "tools":
tools = items or profile.get("repair_tools") or []
if not tools:
s.echo(
"repair: no tools named — ;repair tools pestle mortar, or the "
"profile's repair_tools"
)
return
tools = [tool_name(profile, tool) for tool in tools]
due = analyze(s, tools, 100 if items else floor)
if not due:
s.echo(f"repair: no tool at or below {floor} % — all good")
return
if mapdb is None:
s.echo("repair: walking to a shop needs the map — none loaded")
return
start = locate(mapdb, s.state)
count = repair(s, mapdb, walk_fn, due, shops=TOOL_SHOPS)
s.echo(f"repair: {count} of {len(due)} repaired")
walk_home(s, mapdb, walk_fn, start, back)
return
listed = items or profile.get("repair_items") or []
tags = hands.tags(s)
pieces = candidates(
getattr(s.state, "possessions", None) or [],
[tags["right"], tags["left"]],
listed,
)
if not pieces:
s.echo("repair: nothing to look at — ;sheet inv lists what you wear")
return
# Named pieces go in whatever their condition: the floor is 100 —
# but a check reads them against the floor it reports (#363).
due = appraise(
s,
pieces,
100 if items and mode != "check" else floor,
named=bool(listed),
homes=weapon_homes(profile),
)
if mode == "check":
s.echo(f"repair: {len(due)} to repair at a floor of {floor} %")
return
if not due:
s.echo(f"repair: nothing at or below {floor} % — all good")
return
if mapdb is None:
s.echo("repair: walking to a shop needs the map — none loaded")
return
start = locate(mapdb, s.state)
count = repair(s, mapdb, walk_fn, due)
s.echo(f"repair: {count} of {len(due)} repaired")
walk_home(s, mapdb, walk_fn, start, back)
def tool_name(profile, tool):
"""A bare "mortar" or "pestle" as the profile spells the tool whole
("iron pestle"), so a looted one is not the one analyzed and paid
for (#478); any other tool as given."""
tool = str(tool).strip().lower()
if tool in ("mortar", "pestle"):
return str(profile.get(tool) or tool).strip().lower() or tool
return tool
def walk_home(s, mapdb, walk_fn, start, back):
if back and start is not None and not s.dead and locate(mapdb, s.state) != start:
if not travel.go(s, {start}, "where you started", db=mapdb, walk=walk_fn):
s.echo("repair: could not walk back — you are at the shop")
def main(s):
words = list(s.args)
mode = parse_words(words)[0]
run(s, words, mapdb=None if mode == "check" else MapDB.load())