-
Notifications
You must be signed in to change notification settings - Fork 178
/
dusk-labyrinth.lic
260 lines (231 loc) · 9.24 KB
/
dusk-labyrinth.lic
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
=begin
Documentation: https://elanthipedia.play.net/Lich_script_repository#dusk-labyrinth
=end
custom_require.call %w[common common-items events]
class DuskLab
def initialize
arg_definitions = [
[
{ name: 'loot', regex: /loot/i, optional: true, description: 'Run the maze for loot only. Do not search for a pet.' },
{ name: 'debug', regex: /debug/i, optional: true, description: 'Enable debug output' }
]
]
args = parse_args(arg_definitions)
$debug_mode_dl = UserVars.dusk_labyrinth_debug || args.debug || false
@settings = get_settings
@labyrinth_data = @settings.duskruin['labyrinth']
@loot_only = args.loot
DRC.message('This is a loot-only run.') if @loot_only
loot_container = @labyrinth_data['loot_container']
pet_noun = @labyrinth_data['pet_noun']
redeem_scrip = @labyrinth_data['redeem_scrip']
# Map of directions to help know where we've come from and how to go back.
@reverse_direction_map = {
'e' => 'w',
'w' => 'e',
's' => 'n',
'n' => 's',
'ne' => 'sw',
'sw' => 'ne',
'nw' => 'se',
'se' => 'nw',
'east' => 'west',
'west' => 'east',
'south' => 'north',
'north' => 'south',
'northeast' => 'southwest',
'southwest' => 'northeast',
'northwest' => 'southeast',
'southeast' => 'northwest'
}
# Be notified when certain events happen, such as seeing a pet move or the maze reshuffle.
Flags.add('five-minute-warning', 'You only have about 5 minutes left in the labyrinth!')
Flags.add('done', 'A surly Dwarf stomps in and glowers at you in the dim gloom')
Flags.add('no-pet', "A small #{pet_noun} scurries #{Regexp.new('(?<pet_dir>\w+)')}!")
Flags.add('pet', "You notice a small #{pet_noun} scurrying around the area")
Flags.add('caught', 'You pick them both up, claiming your new pet')
Flags.add('maze-shuffled', 'no longer certain of your directions')
# Let's do this!
init_maze
DRCI.stow_hands
main(loot_container) until done?
redeem if redeem_scrip
end
# Used at beginning of script and any time the maze shuffles.
def init_maze
echo("*** initializing map data") if $debug_mode_dl
# Maze data for the current room.
@current_room = nil
# Maze data for the previous room.
@previous_room = nil
# Direction we last moved to go from "previous_room" to "current_room".
@forward_move = nil
@reverse_move = nil
end
# Main loop to check our room, search, or wander the maze.
# Repeatedly invoked until the maze event ends.
def main(loot_container)
if @previous_room && @previous_room['exits'][@forward_move]['count'] > 1 && !Flags['caught'] && !Flags['maze-shuffled'] && !Flags['five-minute-warning']
echo("*** Been here before, moving to another room") if $debug_mode_dl
wander unless done?
return
end
echo("Checking room...") if $debug_mode_dl
# Wait until we determine that this room either
# doesn't have a pet, has a pet, we caught the pet, or the maze reshuffeld.
unless @loot_only
pause 1 until Flags['no-pet'] || Flags['pet'] || Flags['caught'] || Flags['maze-shuffled']
end
# Maze changed, forget everything we knew.
if Flags['maze-shuffled']
echo("maze reshuffled") if $debug_mode_dl
init_maze
wander_randomly
Flags.reset('maze-shuffled')
return
end
unless @loot_only
if Flags['no-pet'] && Flags['five-minute-warning']
echo("*** No pet in this room and we're running out of time. Searching to find loot.") if $debug_mode_dl
search
Flags.reset('no-pet')
elsif Flags['no-pet']
echo("*** No pet in this room. Will wander to another room.") if $debug_mode_dl
Flags.reset('no-pet')
elsif Flags['pet']
echo("*** Searching for a pet!") if $debug_mode_dl
search
Flags.reset('pet')
elsif Flags['five-minute-warning']
echo("*** Running out of time. Searching to find loot.") if $debug_mode_dl
search
elsif Flags['caught']
echo("*** We already caught a pet. Searching to find loot.") if $debug_mode_dl
search
end
else
echo("*** Script running to loot only.") if $debug_mode_dl
search
end
# Put the goods away.
stow_loot(loot_container)
# Move to another room.
wander unless done?
end
# When the maze reshuffles then the room exits change.
# The XMLData.room_exits data is not refreshed so we
# literally don't know where we we can move to.
# Randomly move in directions until one works.
def wander_randomly
DRC.message("Maze reshuffled, trying to find a direction to move to")
success = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'].each do |dir|
if move(dir)
break(true)
end
end
return if success
DRC.message('Unable to move to another room after maze shuffled')
DRC.message('Try to move yourself to another room then restart script')
exit
end
# Moves to a nearby room in the maze.
# Prioritizes rooms we have not been in yet.
# Skips over rooms we've been in before if looking for a pet.
def wander
waitrt?
@current_room = create_room if @current_room.nil?
echo("*** [wander] @current_room: #{@current_room}") if $debug_mode_dl
@forward_move = choose_next_move(@current_room['exits'])
@reverse_move = @reverse_direction_map[@forward_move]
echo("*** [wander] forward_move: #{@forward_move}") if $debug_mode_dl
echo("*** [wander] reverse_move: #{@reverse_move}") if $debug_mode_dl
move(@forward_move)
# Since rooms in the maze don't have ids, we build
# a bi-directional graph where each vertex (room)
# points to another vertex (room) and vice versa.
@previous_room = @current_room
@current_room = @current_room['exits'][@forward_move]['room'] || create_room
@previous_room['exits'][@forward_move]['room'] = @current_room
@current_room['exits'][@reverse_move]['room'] = @previous_room
# Increment the count of us having moved between these two rooms.
# In graph algorithms, this is known as a weighted graph so that
# when we pick the "next best move" to go down we choose paths
# with the least count (weight) thereby prioritizing paths we
# have not visited before spending time visiting rooms we've been to.
@previous_room['exits'][@forward_move]['count'] = @previous_room['exits'][@forward_move]['count'] + 1
@current_room['exits'][@reverse_move]['count'] = @current_room['exits'][@reverse_move]['count'] + 1
end
def create_room
new_room = {
'exits' => {}
}
# Initialize weights of each path out of this room.
checkpaths.each do |dir|
new_room['exits'][dir] = {
# How many times we've traversed this path.
'count' => 0,
# Which room this path moves you to.
# If not null then this room has an exit
# in the reverse direction that points back
# to this room we are creating.
# In this manner two rooms have bi-directional
# edges to the other room.
# This is critical to the maze mapping algorithm
# because each room has no roomid in the labyrinth
# so this is how we keep track in memory of which
# rooms we have visited.
'room' => nil
}
end
return new_room
end
# Returns a direction to move in.
# Prioritizes rooms we've not visited before.
def choose_next_move(exits)
# https://medium.com/@florenceliang/some-notes-about-using-hash-sort-by-in-ruby-f4b3a700fc33
sorted_exits = exits.sort_by { |_k, v| v['count'] }
echo("*** [choose_next_move] sorted_exits: #{sorted_exits}") if $debug_mode_dl
# The returned sorted exits is an array of arrays.
# The first entry is the top sorted hash entry, which is now an array of [key, value].
# Return the first entry of the inner array which is the direction to move in.
next_move = sorted_exits[0][0]
echo("*** [choose_next_move] next_move: #{next_move}") if $debug_mode_dl
return next_move
end
# Stow whatever bloodscrip or items are in our hands into the given container.
def stow_loot(loot_container)
if loot_container
fput("put my #{DRC.left_hand} in my #{loot_container}") if DRC.left_hand
fput("put my #{DRC.right_hand} in my #{loot_container}") if DRC.right_hand
else
DRCI.stow_hands
end
end
# Perform a search, hopefully catching a pet, but otherwise
# grabbing up some bloodscrip or other loot.
def search
DRC.bput('search', 'You search around', "You've recently searched this area", 'As you begin to search')
waitrt?
DRC.fix_standing
end
# Check if we've ran out of time and are now outside the maze.
def done?
XMLData.room_title == '[[Duskruin, Darkened Antichamber]]' || Flags['done']
end
# Redeem bloodscrip for tickets.
def redeem
while DRC.bput('get my bloodscrip', 'You get', 'What were') == 'You get'
DRC.bput('redeem my bloodscrip', 'You quickly pocket')
end
end
end
# Remove flag variables when script ends.
before_dying do
Flags.delete('five-minute-warning')
Flags.delete('done')
Flags.delete('no-pet')
Flags.delete('pet')
Flags.delete('caught')
Flags.delete('maze-shuffled')
end
DuskLab.new