Olib 2.0.0.pre.rc.2 → 2.0.0.pre.rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59092281655cf2bb6035a9bf6117a4cd1b88539c2eb971041d13910f4d319f55
4
- data.tar.gz: 26d0a65a8edfeba5077b6e9efc0887d8bdbcad394fa4b29af015a9281ab00f12
3
+ metadata.gz: 6b6389e96091c8dc8e79ea4bb25b9c30805c91d47f09addab1a1e2075976a9dc
4
+ data.tar.gz: 6008e2a6431503d0c72b17c7ec5e27a940ae7a2a4d4af807f6c11670f346794f
5
5
  SHA512:
6
- metadata.gz: '038216104dd7f79a3d45d181a759b56256ed6bf611dfcece8f8c777886f6a5371c2476381af7ecc447567169265e9299275973f18fd492d32f37ba5573d86a7d'
7
- data.tar.gz: 1638f640f0540fc425937dd963a4914e0d44cbb391cc77f2417bb9a9e99b6c4107733818ad4448b1c3858149c803fa9933d164307070040593ecd3d6768de2a0
6
+ metadata.gz: b19fd1ecffec4ddd4a0e8a587b8db4ccb9089c28ffc1d74c4bab90626a2e43e52efcdec7c2abbb8ae943331d7b5c3deac78e1a2674cfdd47dd6f6410294a0fdf
7
+ data.tar.gz: bf7d1ddb8a5d621f5a9c98656127a051c93842942c0079d061d415fbefd937450e57866ece27d978652af98881ba620b599575b65735aab76f5daf732d0a0b00
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.authors = ["Ondreian Shamsiel"]
12
12
  s.email = "ondreian.shamsiel@gmail.com"
13
13
  s.homepage = "https://github.com/ondreian/Olib"
14
- s.files = %w[Olib.gemspec] + Dir["*.md", "lib/**/*.rb"]
14
+ s.files = %w[Olib.gemspec] + Dir["*.md", "lib/**/*.rb", "lib/**/*.json"]
15
15
  #s.require_paths = %w[lib]
16
16
  # s.add_runtime_dependency "shoes"
17
17
  s.license = "MIT"
@@ -1,15 +1,15 @@
1
1
  require "ostruct"
2
2
 
3
3
  class Bounty
4
- NPCS = /guard|sergeant|Felinium|clerk|purser|taskmaster|gemcutter|jeweler|akrash|kris|Ghaerdish|Furryback|healer|dealer|Ragnoz|Maraene|Kelph|Areacne|Jhiseth|Gaedrein/i
4
+ NPCS = /guard|sergeant|Luthrek|Felinium|clerk|purser|taskmaster|gemcutter|jeweler|akrash|kris|Ghaerdish|Furryback|healer|dealer|Ragnoz|Maraene|Kelph|Areacne|Jhiseth|Gaedrein/i
5
5
  HERBALIST_AREAS = /illistim|vaalor|legendary rest|solhaven/i
6
- @@listeners = {}
6
+
7
7
  # this should be refactored to use CONST
8
8
  REGEX = OpenStruct.new(
9
- creature_problem: /It appears they have a creature problem they\'d like you to solve/,
9
+ creature_problem: /"Hmm, I've got a task here from (?<town>.*?)\. It appears they have a creature problem they\'d like you to solve/,
10
10
  report_to_guard: /^You succeeded in your task and should report back to/,
11
11
  get_skin_bounty: /The local furrier/,
12
- heirloom_found: /^You have located the heirloom and should bring it back to/,
12
+ heirloom_found: /^You have located (?:a|an|some) (?<heirloom>.*?) and should bring it back to one of the (?<town>.*?) gate guards\./,
13
13
  cooldown: /^You are not currently assigned a task. You will be eligible for new task assignment in about (?<minutes>.*?) minute(s)./,
14
14
 
15
15
  dangerous: /You have been tasked to hunt down and kill a particularly dangerous (?<creature>.*) that has established a territory (?:in|on) (?:the )?(?<area>.*?)(?: near| between| under|\.)/,
@@ -17,25 +17,26 @@ class Bounty
17
17
  heirloom: /^You have been tasked to recover (a|an|some) (?<heirloom>.*?) that an unfortunate citizen lost after being attacked by (a|an|some) (?<creature>.*?) (?:in|on|around|near|by) (?<area>.*?)(| near (?<realm>.*?))\./,
18
18
 
19
19
 
20
- get_rescue: /It appears that a local resident urgently needs our help in some matter/,
21
- get_bandits: /It appears they have a bandit problem they'd like you to solve./,
22
- get_heirloom: /It appears they need your help in tracking down some kind of lost heirloom/,
20
+ get_rescue: /"Hmm, I've got a task here from (?<town>.*?)\. It appears that a local resident urgently needs our help in some matter/,
21
+ get_bandits: /"Hmm, I've got a task here from (?<town>.*?)\. It appears they have a bandit problem they'd like you to solve./,
22
+ get_heirloom: /"Hmm, I've got a task here from (?<town>.*?)\. It appears they need your help in tracking down some kind of lost heirloom/,
23
23
  get_herb_bounty: /local herbalist|local healer|local alchemist/,
24
- get_gem_bounty: /The local gem dealer, (?<npc>[a-zA-Z ]+), has an order to fill and wants our help/,
24
+ get_gem_bounty: /"Hmm, I've got a task here from (?<town>.*?)\. The local gem dealer, (?<npc>[a-zA-Z ]+), has an order to fill and wants our help/,
25
25
 
26
26
  herb: /requires (?:a |an |)(?<herb>.*?) found (?:in|on|around|near) (?<area>.*?)(| (near|between) (?<realm>.*?)). These samples must be in pristine condition. You have been tasked to retrieve (?<number>[\d]+)/,
27
27
  escort: /Go to the (.*?) and WAIT for (?:him|her|them) to meet you there. You must guarantee (?:his|her|their) safety to (?<destination>.*?) as soon as/,
28
- gem: /has received orders from multiple customers requesting (?:a|an|some) (?<gem>[a-zA-Z '-]+). You have been tasked to retrieve (?<number>[0-9]+)/,
29
-
28
+ gem: /The gem dealer in (?<town>.*?), (?<npc>.*?), has received orders from multiple customers requesting (?:a|an|some) (?<gem>[a-zA-Z '-]+). You have been tasked to retrieve (?<number>[0-9]+)/,
30
29
  cull: /^You have been tasked to suppress (?<creature>(?!bandit).*) activity (?:in|on|around) (?<area>.*?)(| (near|between) (?<realm>.*?)). You need to kill (?<number>[0-9]+)/,
31
- bandits: /^You have been tasked to suppress bandit activity (?:in|on|around) (?<area>.*?) (?:near|between|under) (?<realm>.*?). You need to kill (?<number>[0-9]+)/,
30
+ bandits: /^You have been tasked to suppress bandit activity (?:in|on|around|near) (?<area>.*?) (?:near|between|under) (?<realm>.*?). You need to kill (?<number>[0-9]+)/,
32
31
 
33
32
  rescue: /A local divinist has had visions of the child fleeing from (?:a|an) (?<creature>.*) (?:in|on) (?:the )?(?<area>.*?)(?: near| between| under|\.)/,
34
33
  failed: /You have failed in your task/,
35
34
  none: /You are not currently assigned a task/,
36
35
  skin: /^You have been tasked to retrieve (?<number>\d+) (?<skin>.*?) of at least (?<quality>.*?) quality for (?<buyer>.*?) in (?<realm>.*?)\.\s+You can SKIN them off the corpse of (a|an|some) (?<creature>.*?) or/,
37
36
 
38
- help_bandits: /You have been tasked to help (?<partner>.*?) suppress bandit activity (?:in|on|around) (?<area>.*?) (?:near|between|under) (?<realm>.*?). You need to kill (?<number>[0-9]+)/
37
+ help_bandits: /You have been tasked to help (?<partner>.*?) suppress bandit activity (?:in|on|around|near) (?<area>.*?) (?:near|between|under) (?<realm>.*?). You need to kill (?<number>[0-9]+)/,
38
+ help_creatures: /You have been tasked to help (?<partner>.*?) kill a dangerous creature by suppressing (?<creature>.*) activity (?:in|on|around|near) (?<area>.*?) (?:near|between|under) (?<realm>.*?) during the hunt. You need to kill (?<number>[0-9]+)/,
39
+ help_cull: /You have been tasked to help (?<partner>.*?) suppress (?<creature>.*) activity (?:in|on|around|near) (?<area>.*?) (?:near|between|under) (?<realm>.*?). You need to kill (?<number>[0-9]+)/,
39
40
  )
40
41
 
41
42
  # convenience list to get all types of bounties
@@ -115,23 +116,15 @@ class Bounty
115
116
  Bounty.parse(checkbounty)
116
117
  end
117
118
 
118
- def Bounty.ask_for_bounty
119
- if invisible?
120
- fput "unhide"
121
- end
122
-
123
- if hidden?
124
- fput "unhide"
125
- end
126
-
127
- if Bounty.npc
128
- fput "ask ##{Bounty.npc.id} for bounty"
129
- Bounty
119
+ def Bounty.ask_for_bounty(expedite: false)
120
+ fput "unhide" if invisible?
121
+ fput "unhide" if hidden?
122
+ raise Exception, "could not find Bounty.npc here" unless Bounty.npc
123
+ if expedite
124
+ fput "ask ##{Bounty.npc.id} for expedite"
130
125
  else
131
- raise Exception.new "could not find Bounty.npc here"
126
+ fput "ask ##{Bounty.npc.id} for bounty"
132
127
  end
133
- # give the XML parser time to update
134
- sleep 0.2
135
128
  end
136
129
 
137
130
  def Bounty.herbalist
@@ -149,15 +142,6 @@ class Bounty
149
142
  Bounty
150
143
  end
151
144
 
152
- def Bounty.on(namespace, &block)
153
- @@listeners[namespace] = block
154
- Bounty
155
- end
156
-
157
- def Bounty.listeners
158
- @@listeners
159
- end
160
-
161
145
  def Bounty.cooldown?
162
146
  not XMLData.active_spells["Next Bounty"].nil?
163
147
  end
@@ -170,38 +154,6 @@ class Bounty
170
154
  Bounty
171
155
  end
172
156
 
173
- def Bounty.throw_missing_listener
174
- msg = "\n"
175
- msg.concat "\nBounty.dispatch called for `:#{Bounty.type}` without a defined listener\n\n"
176
- msg.concat "define a listener with:\n"
177
- msg.concat " \n"
178
- msg.concat " Bounty.on(:#{Bounty.type}) {\n"
179
- msg.concat " # do something\n"
180
- msg.concat " }\n"
181
- msg.concat " \n"
182
- msg.concat "or rescue this error (Errors::Fatal) gracefully\n"
183
- msg.concat " \n"
184
- raise Errors::Fatal.new msg
185
- end
186
-
187
- def Bounty.dispatch(listener=nil)
188
- if listener
189
- if @@listeners[listener]
190
- @@listeners[listener].call
191
- return Bounty
192
- else
193
- Bounty.throw_missing_listener
194
- end
195
- end
196
-
197
- if @@listeners[Bounty.type]
198
- @@listeners[Bounty.type].call
199
- return Bounty
200
- else
201
- Bounty.throw_missing_listener
202
- end
203
- end
204
-
205
157
  def Bounty.find_guard
206
158
  Go2.advguard
207
159
  if Bounty.npc.nil? then Go2.advguard2 end
@@ -1,258 +1,99 @@
1
1
  require "ostruct"
2
+ require "benchmark"
2
3
  require "Olib/character/disk"
3
4
 
4
- module Group
5
- class Members
6
- include Enumerable
7
- attr_accessor :leader, :members, :birth
8
- def initialize
9
- @birth = Time.now
10
- @members = []
11
- end
12
-
13
- def clear!
14
- @members = []
15
- @birth = Time.now
16
- @leader = nil
17
- end
18
-
19
- def size
20
- @members.size
21
- end
22
-
23
- def empty?
24
- @members.empty?
25
- end
26
-
27
- def add(pc, leader = false)
28
- member = Member.new pc, leader
29
- if leader
30
- @leader = member
31
- end
32
- @members << member
33
- self
34
- end
35
-
36
- def each(&block)
37
- @members.each do |char| yield char end
38
- self
39
- end
5
+ class Group
6
+ @@members ||= []
7
+ @@leader ||= nil
8
+ @@checked ||= false
9
+ @@status ||= :closed
40
10
 
41
- def include?(pc)
42
- return true if pc.is_a?(String) and Char.name.eql?(pc)
43
- return true if pc.respond_to?(:noun) and Char.name.eql?(pc.noun)
44
- return true if pc.respond_to?(:name) and Char.name.eql?(pc.name)
45
- !find do |char|
46
- if pc.is_a?(String)
47
- char.noun.eql?(pc)
48
- else
49
- char.noun.eql?(pc.noun) || char.noun.eql?(pc.name)
50
- end
51
- end.nil?
52
- end
53
-
54
- def nonmembers
55
- ((GameObj.pcs || []) + Disk.all()).reject do |pc|
56
- include?(pc)
57
- end
58
- end
59
-
60
- def to_s
61
- "<Members: [#{@members.join(" ")}]>"
62
- end
63
- end
64
-
65
- class Member
66
- attr_reader :id, :leader, :name, :noun
67
- def initialize(pc, leader = false)
68
- @id = pc.id
69
- @leader = leader
70
- @name = pc.name
71
- @noun = pc.noun
72
- end
73
-
74
- def ref
75
- GameObj[@id]
76
- end
77
-
78
- def leader?
79
- @leader
80
- end
81
-
82
- def status
83
- (ref.status.split(" ") || []).map(&:to_sym)
84
- end
85
-
86
- def is(state)
87
- status =~ state
88
- end
89
-
90
- def ==(other)
91
- @id == other.id
92
- end
93
-
94
- def to_s
95
- "<#{name}: @leader=#{leader?} @status=#{status}>"
96
- end
97
- end
98
-
99
- MEMBERS = Members.new
100
- OPEN = :open
101
- CLOSED = :closed
102
- CHECK_HOOK = self.name.to_s
103
- NO_GROUP = /You are not currently in a group/
104
- MEMBER = /<a exist="(?<id>.*?)" noun="(?<name>.*?)">(.*?)<\/a> is (?<type>(the leader|also a member) of your group|following you)\./
105
- STATE = /^Your group status is currently (?<state>open|closed)\./
106
- END_GROUP = /list of other options\./
107
-
108
- PARSER = Proc.new do |line|
109
- if line.strip.empty? || line =~ NO_GROUP
110
- nil
111
- elsif line =~ STATE
112
- nil
113
- elsif line =~ END_GROUP
114
- Group.checked!
115
- DownstreamHook.remove(CHECK_HOOK)
116
- nil
117
- elsif line =~ MEMBER
118
- begin
119
- pc = line.match(MEMBER).to_struct
120
- Group::MEMBERS.add GameObj[pc.name], (line =~ /leader/ ? true : false)
121
- if line =~ /following/
122
- Group::MEMBERS.leader = OpenStruct.new(name: Char.name, leader: true)
123
- end
124
- nil
125
- rescue Exception => e
126
- respond e
127
- respond e.backtrace
128
- end
129
- else
130
- line
131
- end
11
+ def self.clear()
12
+ @@members = []
13
+ @@checked = false
132
14
  end
133
15
 
134
- @@checked = false
135
-
136
- def Group.checked?
16
+ def self.checked?
137
17
  @@checked
138
18
  end
139
19
 
140
- def Group.checked!
141
- @@checked = true
142
- self
20
+ def self.push(*members)
21
+ members.each do |member|
22
+ @@members.push(member) unless include?(member)
23
+ end
143
24
  end
144
25
 
145
- def Group.empty?
146
- MEMBERS.empty?
26
+ def self.delete(*members)
27
+ gone = members.map(&:id)
28
+ @@members.reject! do |m| gone.include?(m.id) end
147
29
  end
148
30
 
149
- def Group.exists?
150
- !empty?
31
+ def self.members
32
+ maybe_check
33
+ @@members.dup
151
34
  end
152
35
 
153
- def Group.members
154
- maybe_check
155
- MEMBERS
36
+ def self._members
37
+ @@members
156
38
  end
157
39
 
158
- def Group.disks
40
+ def self.disks
159
41
  return [Disk.find_by_name(Char.name)].compact unless Group.leader?
160
- members.map(&:name).map do |name| Disk.find_by_name(name) end.compact
42
+ members.map(&:noun).map do |noun| Disk.find_by_name(noun) end.compact
161
43
  end
162
44
 
163
- def Group.to_s
164
- MEMBERS.to_s
45
+ def self.to_s
46
+ @@members.to_s
165
47
  end
166
48
 
167
- # ran at the initialization of a script
168
- def Group.check
169
- Group.unobserve()
170
- @@checked = false
171
- MEMBERS.clear!
172
- DownstreamHook.add(CHECK_HOOK, PARSER)
173
- Game._puts "<c>group\r\n"
174
- wait_until { Group.checked? }
175
- Group.observe()
176
- MEMBERS
49
+ def self.checked=(flag)
50
+ @@checked = flag
177
51
  end
178
52
 
179
- def Group.maybe_check
180
- Group.check unless checked?
53
+ def self.status=(state)
54
+ @@status = state
181
55
  end
182
56
 
183
- def Group.nonmembers
184
- members.nonmembers
57
+ def self.status()
58
+ @@status
185
59
  end
186
60
 
187
- def Group.leader
188
- members.leader
61
+ def self.open?
62
+ maybe_check
63
+ @@status.eql?(:open)
189
64
  end
190
65
 
191
- def Group.leader?
192
- leader && leader.name == Char.name
66
+ def self.closed?
67
+ not open?
193
68
  end
194
69
 
70
+ # ran at the initialization of a script
71
+ def self.check
72
+ Group.clear()
73
+ ttl = Time.now + 3
74
+ Game._puts "<c>group\r\n"
75
+ wait_until { Group.checked? or Time.now > ttl }
76
+ @@members.dup
77
+ end
195
78
 
196
- module Term
197
- # <a exist="-10467645" noun="Oreh">Oreh</a> leaves your group
198
- # <a exist="-10467645" noun="Oreh">Oreh</a> joins your group.
199
- # You add <a exist="-10467645" noun="Oreh">Oreh</a> to your group.
200
- # You remove <a exist="-10467645" noun="Oreh">Oreh</a> from the group.
201
- # You disband your group.
202
- JOIN = %r{^<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> joins your group.$}
203
- LEAVE = %r{^<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> leaves your group.$}
204
- ADD = %r{^You add <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> to your group.$}
205
- REMOVE = %r{^You remove <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> from the group.$}
206
- NOOP = %r{^But <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> is already a member of your group!$}
207
- EXIST = %r{<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a>}
208
- DISBAND = %r{^You disband your group}
209
- ANY = Regexp.union(JOIN, LEAVE, ADD, REMOVE, NOOP)
79
+ def self.maybe_check
80
+ Group.check unless checked?
210
81
  end
211
82
 
212
- GROUP_OBSERVER = -> line {
213
- begin
214
- return line if DownstreamHook.list.include?(CHECK_HOOK)
215
- Group.consume(line.strip)
216
- rescue => exception
217
- respond exception
218
- respond exception.backtrace
219
- end
220
- line
221
- }
83
+ def self.nonmembers
84
+ GameObj.pcs.to_a.reject {|pc| ids.include?(pc.id) }
85
+ end
222
86
 
223
- def self.observe()
224
- wait_while do DownstreamHook.list.include?(CHECK_HOOK) end
225
- DownstreamHook.add("__group_observer", GROUP_OBSERVER)
87
+ def self.leader=(char)
88
+ @@leader = char
226
89
  end
227
90
 
228
- def self.unobserve()
229
- DownstreamHook.remove("__group_observer")
91
+ def self.leader
92
+ @@leader
230
93
  end
231
94
 
232
- Group.observe()
233
-
234
- def self.consume(line)
235
- return unless line.match(Group::Term::ANY)
236
- person = GameObj[Term::EXIST.match(line)[:id]]
237
- case line
238
- when Term::JOIN
239
- Group.members.add(person) unless Group.members.include?(person)
240
- when Term::ADD
241
- Group.members.add(person) unless Group.members.include?(person)
242
- when Term::NOOP
243
- Group.members.add(person) unless Group.members.include?(person)
244
- when Term::LEAVE
245
- Group.members.members.delete(Group.members.find do |member|
246
- member.id.eql?(person.id)
247
- end)
248
- when Term::REMOVE
249
- Group.members.members.delete(Group.members.find do |member|
250
- member.id.eql?(person.id)
251
- end)
252
- else
253
- # silence is golden
254
- end
255
- Group.persist()
95
+ def self.leader?
96
+ @@leader.eql?(:self)
256
97
  end
257
98
 
258
99
  def self.add(*members)
@@ -260,34 +101,201 @@ module Group
260
101
  if member.is_a?(Array)
261
102
  Group.add(*member)
262
103
  else
104
+ member = GameObj.pcs.find {|pc| pc.noun.eql?(member)} if member.is_a?(String)
105
+
106
+ return if member.nil?
107
+
263
108
  result = dothistimeout("group ##{member.id}", 3, Regexp.union(
264
109
  %r{You add #{member.noun} to your group},
265
110
  %r{#{member.noun}'s group status is closed},
266
111
  %r{But #{member.noun} is already a member of your group}))
267
112
 
268
113
  case result
269
- when %r{You add}
270
- Group.members.add(member)
271
- [:ok, member]
272
- when %r{already a member}
273
- [:noop, member]
114
+ when %r{You add}, %r{already a member}
115
+ Group.push(member)
116
+ {ok: member}
274
117
  when %r{closed}
275
- [:err, member]
118
+ Group.delete(member)
119
+ {err: member}
276
120
  else
277
121
  end
278
122
  end
279
123
  end
280
124
  end
281
125
 
282
- def self.persist()
283
- return
126
+ def self.ids
127
+ @@members.map(&:id)
128
+ end
129
+
130
+ def self.include?(*members)
131
+ members.all? { |m| ids.include?(m.id) }
284
132
  end
285
133
 
286
134
  def self.broken?
287
135
  if Group.leader?
288
- (GameObj.pcs.map(&:noun) & Group.members.map(&:noun)).size < Group.members.size
136
+ (GameObj.pcs.map(&:noun) & @@members.map(&:noun)).size < @@members.size
289
137
  else
290
138
  GameObj.pcs.find do |pc| pc.noun.eql?(Group.leader.noun) end.nil?
291
139
  end
292
140
  end
293
- end
141
+
142
+ def self.method_missing(method, *args, &block)
143
+ @@members.send(method, *args, &block)
144
+ end
145
+ end
146
+
147
+ class Group
148
+ module Observer
149
+ module Term
150
+ ##
151
+ ## passive messages
152
+ ##
153
+ # <a exist="-10467645" noun="Oreh">Oreh</a> joins your group.
154
+ JOIN = %r{^<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> joins your group.$}
155
+ # <a exist="-10467645" noun="Oreh">Oreh</a> leaves your group
156
+ LEAVE = %r{^<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> leaves your group.$}
157
+ # You add <a exist="-10467645" noun="Oreh">Oreh</a> to your group.
158
+ ADD = %r{^You add <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> to your group.$}
159
+ # You remove <a exist="-10467645" noun="Oreh">Oreh</a> from the group.
160
+ REMOVE = %r{^You remove <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> from the group.$}
161
+ NOOP = %r{^But <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> is already a member of your group!$}
162
+ # <a exist="-10488845" noun="Etanamir">Etanamir</a> designates you as the new leader of the group.
163
+ HAS_LEADER = %r{<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> designates you as the new leader of the group\.$}
164
+ SWAP_LEADER = %r{<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> designates <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> as the new leader of the group.}
165
+
166
+ # You designate <a exist="-10778599" noun="Ondreian">Ondreian</a> as the new leader of the group.
167
+ GAVE_LEADER_AWAY = %r{You designate <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> as the new leader of the group\.$}
168
+ # You disband your group.
169
+ DISBAND = %r{^You disband your group}
170
+ # <a exist="-10488845" noun="Etanamir">Etanamir</a> adds you to <a exist="-10488845" noun="Etanamir">his</a> group.
171
+ ADDED_TO_NEW_GROUP = %r{<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> adds you to <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> group.}
172
+ # You join <a exist="-10488845" noun="Etanamir">Etanamir</a>.
173
+ JOINED_NEW_GROUP = %r{You join <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a>\.$}
174
+ # <a exist="-10488845" noun="Etanamir">Etanamir</a> adds <a exist="-10974229" noun="Szan">Szan</a> to <a exist="-10488845" noun="Etanamir">his</a> group.
175
+ LEADER_ADDED_MEMBER = %r{<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> adds <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> to <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> group\.$}
176
+ # <a exist="-10488845" noun="Etanamir">Etanamir</a> removes <a exist="-10974229" noun="Szan">Szan</a> from the group.
177
+ LEADER_REMOVED_MEMBER = %r{<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> removes <a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a> from the group\.$}
178
+ ##
179
+ ## active messages
180
+ ##
181
+ NO_GROUP = /You are not currently in a group/
182
+ MEMBER = /<a exist="(?<id>.*?)" noun="(?<name>.*?)">(.*?)<\/a> is (?<type>(the leader|also a member) of your group|following you)\./
183
+ STATUS = /^Your group status is currently (?<status>open|closed)\./
184
+
185
+ GROUP_EMPTIED = %[<indicator id='IconJOINED' visible='n'/>]
186
+ GROUP_EXISTS = %[<indicator id='IconJOINED' visible='y'/>]
187
+ GIVEN_LEADERSHIP = %[designates you as the new leader of the group.]
188
+
189
+ ANY = Regexp.union(
190
+ JOIN,
191
+ LEAVE,
192
+ ADD,
193
+ REMOVE,
194
+ DISBAND,
195
+ NOOP,
196
+ STATUS,
197
+ NO_GROUP,
198
+ MEMBER,
199
+
200
+ HAS_LEADER,
201
+ SWAP_LEADER,
202
+
203
+ LEADER_ADDED_MEMBER,
204
+ LEADER_REMOVED_MEMBER,
205
+
206
+ ADDED_TO_NEW_GROUP,
207
+ JOINED_NEW_GROUP,
208
+ GAVE_LEADER_AWAY,
209
+ )
210
+
211
+ EXIST = %r{<a exist="(?<id>[\d-]+)" noun="(?<noun>[A-Za-z]+)">(?<name>\w+?)</a>}
212
+ end
213
+
214
+ CALLBACK = -> line {
215
+ begin
216
+ # fast first-pass
217
+ if line.include?("group") or line.include?("following you") or line.include?("IconJOINED")
218
+ # more detailed pass
219
+ if match_data = Observer.wants?(line)
220
+ Observer.consume(line.strip, match_data)
221
+ end
222
+ end
223
+ rescue => exception
224
+ respond(exception)
225
+ respond(exception.backtrace)
226
+ ensure
227
+ return line
228
+ end
229
+ }
230
+
231
+ def self.exist(xml)
232
+ xml.scan(Group::Observer::Term::EXIST).map { |id, noun, name| GameObj[id] }
233
+ end
234
+
235
+ def self.wants?(line)
236
+ line.strip.match(Term::ANY) or
237
+ line.include?(Term::GROUP_EMPTIED)
238
+ end
239
+
240
+ def self.consume(line, match_data)
241
+ if line.include?(Term::GIVEN_LEADERSHIP)
242
+ return Group.leader = :self
243
+ end
244
+
245
+ ## Group indicator changed!
246
+ if line.include?(Term::GROUP_EMPTIED)
247
+ Group.leader = :self
248
+ return Group._members.clear
249
+ end
250
+
251
+ people = exist(line)
252
+
253
+ if line.include?("is following you")
254
+ Group.leader = :self
255
+ elsif line.include?("is the leader of your group")
256
+ Group.leader = people.first
257
+ end
258
+
259
+ case line
260
+ when Term::NO_GROUP, Term::DISBAND
261
+ Group.leader = :self
262
+ return Group._members.clear
263
+ when Term::STATUS
264
+ Group.status = match_data[:status].to_sym
265
+ return Group.checked = true
266
+ when Term::GAVE_LEADER_AWAY
267
+ Group.push(people.first)
268
+ return Group.leader = people.first
269
+ when Term::ADDED_TO_NEW_GROUP, Term::JOINED_NEW_GROUP
270
+ Group.push(people.first)
271
+ return Group.leader = people.first
272
+ when Term::SWAP_LEADER
273
+ (old_leader, new_leader) = people
274
+ Group.push(*people) if Group.include?(old_leader) or Group.include?(new_leader)
275
+ return Group.leader = new_leader
276
+ when Term::LEADER_ADDED_MEMBER
277
+ (leader, added) = people
278
+ Group.push(added) if Group.include?(leader)
279
+ when Term::LEADER_REMOVED_MEMBER
280
+ (leader, removed) = people
281
+ return Group.delete(removed) if Group.include?(leader)
282
+ when Term::JOIN, Term::ADD, Term::NOOP, Term::MEMBER
283
+ return Group.push(*people)
284
+ when Term::LEAVE, Term::REMOVE
285
+ return Group.delete(*people)
286
+ end
287
+ end
288
+
289
+
290
+ def self.attach()
291
+ remove() if DownstreamHook.list.include?(self.name)
292
+ DownstreamHook.add(self.name, CALLBACK)
293
+ end
294
+
295
+ def self.remove()
296
+ DownstreamHook.remove(self.name)
297
+ end
298
+ end
299
+
300
+ Observer.attach()
301
+ end