Olib 2.0.0.pre.rc.1 → 2.0.0.pre.rc.6

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: a3847ae46163103e49356d4e6fcc43badaa3b4ddd29a9ea3ad48c509203b5eb7
4
- data.tar.gz: 6ff8a42a9ca26c9cf394bd311ff7095052b7a507005b34aa42d73d63f9701a79
3
+ metadata.gz: f7d8e8dfe837282ceb421c8919c9b14385299f9f718493dc74efef8c9863cb4a
4
+ data.tar.gz: dffc7436f4fc2da600ed00104ca8ca53e9880226d846c12c67987fe36ca9845d
5
5
  SHA512:
6
- metadata.gz: eb34b92ed44adeab5c1b15e7ae65f73124d7557af2c613bef5113c7efe76607e362807553076d52f51f05b95787b93adbf65cb3893343962af82485f008098d0
7
- data.tar.gz: c89d3853c1d5569f7ed375269a047cf7eee943dde1f5ad8b11eb9c2ffb0e1abd29bdf9df8893491afb1cd4f0b29fb1c3df440d3559fc133f8db4119d390a8856
6
+ metadata.gz: bde4171be06ed24d979ffbc9a49b68a7159af161006607194485502d4e9f8a43d6c9b10b8417b2e915f8e30475670a4c87965ac0d63609302f804ea4c6e15990
7
+ data.tar.gz: 97ebab201d826e5e483777535ee91d93a9d80110eea2f329c142d3893a0985409957e95f2d4f74e28bedd1832cdf17636c160e14dd580003013a940fa2c4de83
@@ -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"
@@ -6,7 +6,7 @@ class Bounty
6
6
  @@listeners = {}
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
12
  heirloom_found: /^You have located the heirloom and should bring it back to/,
@@ -17,16 +17,15 @@ 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
30
  bandits: /^You have been tasked to suppress bandit activity (?:in|on|around) (?<area>.*?) (?:near|between|under) (?<realm>.*?). You need to kill (?<number>[0-9]+)/,
32
31
 
@@ -35,7 +34,9 @@ class Bounty
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) (?<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
@@ -1,257 +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
40
-
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
5
+ class Group
6
+ @@members ||= []
7
+ @@leader ||= nil
8
+ @@checked ||= false
9
+ @@status ||= :closed
93
10
 
94
- def to_s
95
- "<#{name}: @leader=#{leader?} @status=#{status}>"
96
- end
97
- end
98
- MEMBERS = Members.new
99
- OPEN = :open
100
- CLOSED = :closed
101
- CHECK_HOOK = self.name.to_s
102
- NO_GROUP = /You are not currently in a group/
103
- MEMBER = /<a exist="(?<id>.*?)" noun="(?<name>.*?)">(.*?)<\/a> is (?<type>(the leader|also a member) of your group|following you)\./
104
- STATE = /^Your group status is currently (?<state>open|closed)\./
105
- END_GROUP = /list of other options\./
106
-
107
- PARSER = Proc.new do |line|
108
- if line.strip.empty? || line =~ NO_GROUP
109
- nil
110
- elsif line =~ STATE
111
- nil
112
- elsif line =~ END_GROUP
113
- Group.checked!
114
- DownstreamHook.remove(CHECK_HOOK)
115
- nil
116
- elsif line =~ MEMBER
117
- begin
118
- pc = line.match(MEMBER).to_struct
119
- Group::MEMBERS.add GameObj[pc.name], (line =~ /leader/ ? true : false)
120
- if line =~ /following/
121
- Group::MEMBERS.leader = OpenStruct.new(name: Char.name, leader: true)
122
- end
123
- nil
124
- rescue Exception => e
125
- respond e
126
- respond e.backtrace
127
- end
128
- else
129
- line
130
- end
11
+ def self.clear()
12
+ @@members = []
13
+ @@checked = false
131
14
  end
132
15
 
133
- @@checked = false
134
-
135
- def Group.checked?
16
+ def self.checked?
136
17
  @@checked
137
18
  end
138
19
 
139
- def Group.checked!
140
- @@checked = true
141
- self
20
+ def self.push(*members)
21
+ members.each do |member|
22
+ @@members.push(member) unless include?(member)
23
+ end
142
24
  end
143
25
 
144
- def Group.empty?
145
- MEMBERS.empty?
26
+ def self.delete(*members)
27
+ gone = members.map(&:id)
28
+ @@members.reject! do |m| gone.include?(m.id) end
146
29
  end
147
30
 
148
- def Group.exists?
149
- !empty?
31
+ def self.members
32
+ maybe_check
33
+ @@members.dup
150
34
  end
151
35
 
152
- def Group.members
153
- maybe_check
154
- MEMBERS
36
+ def self._members
37
+ @@members
155
38
  end
156
39
 
157
- def Group.disks
40
+ def self.disks
158
41
  return [Disk.find_by_name(Char.name)].compact unless Group.leader?
159
- members.map(&:name).map do |name|
160
- Disk.find_by_name(name)
161
- end.compact
42
+ members.map(&:noun).map do |noun| Disk.find_by_name(noun) end.compact
162
43
  end
163
44
 
164
- def Group.to_s
165
- MEMBERS.to_s
45
+ def self.to_s
46
+ @@members.to_s
166
47
  end
167
48
 
168
- # ran at the initialization of a script
169
- def Group.check
170
- Group.unobserve()
171
- @@checked = false
172
- MEMBERS.clear!
173
- DownstreamHook.add(CHECK_HOOK, PARSER)
174
- Game._puts "<c>group\r\n"
175
- wait_until { Group.checked? }
176
- Group.observe()
177
- MEMBERS
49
+ def self.checked=(flag)
50
+ @@checked = flag
178
51
  end
179
52
 
180
- def Group.maybe_check
181
- Group.check unless checked?
53
+ def self.status=(state)
54
+ @@status = state
182
55
  end
183
56
 
184
- def Group.nonmembers
185
- members.nonmembers
57
+ def self.status()
58
+ @@status
186
59
  end
187
60
 
188
- def Group.leader
189
- members.leader
61
+ def self.open?
62
+ maybe_check
63
+ @@status.eql?(:open)
190
64
  end
191
65
 
192
- def Group.leader?
193
- leader && leader.name == Char.name
66
+ def self.closed?
67
+ not open?
194
68
  end
195
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
196
78
 
197
- module Term
198
- # <a exist="-10467645" noun="Oreh">Oreh</a> leaves your group
199
- # <a exist="-10467645" noun="Oreh">Oreh</a> joins your group.
200
- # You add <a exist="-10467645" noun="Oreh">Oreh</a> to your group.
201
- # You remove <a exist="-10467645" noun="Oreh">Oreh</a> from the 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
- ANY = Regexp.union(JOIN, LEAVE, ADD, REMOVE, NOOP)
79
+ def self.maybe_check
80
+ Group.check unless checked?
209
81
  end
210
82
 
211
- GROUP_OBSERVER = -> line {
212
- begin
213
- return line if DownstreamHook.list.include?(CHECK_HOOK)
214
- Group.consume(line.strip)
215
- rescue => exception
216
- respond exception
217
- respond exception.backtrace
218
- end
219
- line
220
- }
83
+ def self.nonmembers
84
+ GameObj.pcs.to_a.reject {|pc| ids.include?(pc.id) }
85
+ end
221
86
 
222
- def self.observe()
223
- wait_while do DownstreamHook.list.include?(CHECK_HOOK) end
224
- DownstreamHook.add("__group_observer", GROUP_OBSERVER)
87
+ def self.leader=(char)
88
+ @@leader = char
225
89
  end
226
90
 
227
- def self.unobserve()
228
- DownstreamHook.remove("__group_observer")
91
+ def self.leader
92
+ @@leader
229
93
  end
230
94
 
231
- Group.observe()
232
-
233
- def self.consume(line)
234
- return unless line.match(Group::Term::ANY)
235
- person = GameObj[Term::EXIST.match(line)[:id]]
236
- case line
237
- when Term::JOIN
238
- Group.members.add(person) unless Group.members.include?(person)
239
- when Term::ADD
240
- Group.members.add(person) unless Group.members.include?(person)
241
- when Term::NOOP
242
- Group.members.add(person) unless Group.members.include?(person)
243
- when Term::LEAVE
244
- Group.members.members.delete(Group.members.find do |member|
245
- member.id.eql?(person.id)
246
- end)
247
- when Term::REMOVE
248
- Group.members.members.delete(Group.members.find do |member|
249
- member.id.eql?(person.id)
250
- end)
251
- else
252
- # silence is golden
253
- end
254
- Group.persist()
95
+ def self.leader?
96
+ @@leader.eql?(:self)
255
97
  end
256
98
 
257
99
  def self.add(*members)
@@ -259,30 +101,201 @@ module Group
259
101
  if member.is_a?(Array)
260
102
  Group.add(*member)
261
103
  else
104
+ member = GameObj.pcs.find {|pc| pc.noun.eql?(member)} if member.is_a?(String)
105
+
106
+ return if member.nil?
107
+
262
108
  result = dothistimeout("group ##{member.id}", 3, Regexp.union(
263
109
  %r{You add #{member.noun} to your group},
264
110
  %r{#{member.noun}'s group status is closed},
265
111
  %r{But #{member.noun} is already a member of your group}))
266
112
 
267
113
  case result
268
- when %r{You add}
269
- Group.members.add(member)
270
- [:ok, member]
271
- when %r{already a member}
272
- [:noop, member]
114
+ when %r{You add}, %r{already a member}
115
+ Group.push(member)
116
+ {ok: member}
273
117
  when %r{closed}
274
- [:err, member]
118
+ Group.delete(member)
119
+ {err: member}
275
120
  else
276
121
  end
277
122
  end
278
123
  end
279
124
  end
280
125
 
281
- def self.persist()
282
- 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) }
283
132
  end
284
133
 
285
134
  def self.broken?
286
- (GameObj.pcs.to_a.map(&:noun) & Group.members.map(&:noun)).size.eql?(Group.members.size)
287
- end
288
- end
135
+ if Group.leader?
136
+ (GameObj.pcs.map(&:noun) & @@members.map(&:noun)).size < @@members.size
137
+ else
138
+ GameObj.pcs.find do |pc| pc.noun.eql?(Group.leader.noun) end.nil?
139
+ end
140
+ 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