profanity_fe 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/profanity ADDED
@@ -0,0 +1,1692 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: US-ASCII
3
+ =begin
4
+
5
+ ProfanityFE v0.5.0
6
+ Copyright (C) 2013 Matthew Lowe
7
+
8
+ This program is free software; you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation; either version 2 of the License, or
11
+ (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License along
19
+ with this program; if not, write to the Free Software Foundation, Inc.,
20
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
+
22
+ matt@io4.us
23
+
24
+ =end
25
+
26
+ $version = '0.5.0'
27
+
28
+ require 'thread'
29
+ require 'socket'
30
+ require 'rexml/document'
31
+ require 'curses'
32
+ include Curses
33
+
34
+ require 'profanity_fe/text_window'
35
+ require 'profanity_fe/progress_window'
36
+ require 'profanity_fe/countdown_window'
37
+ require 'profanity_fe/indicator_window'
38
+
39
+ Curses.init_screen
40
+ Curses.start_color
41
+ Curses.cbreak
42
+ Curses.noecho
43
+
44
+ server = nil
45
+ command_buffer = String.new
46
+ command_buffer_pos = 0
47
+ command_buffer_offset = 0
48
+ command_history = Array.new
49
+ command_history_pos = 0
50
+ max_command_history = 20
51
+ min_cmd_length_for_history = 4
52
+ $server_time_offset = 0
53
+ skip_server_time_offset = false
54
+ key_binding = Hash.new
55
+ key_action = Hash.new
56
+ need_prompt = false
57
+ prompt_text = ">"
58
+ stream_handler = Hash.new
59
+ indicator_handler = Hash.new
60
+ progress_handler = Hash.new
61
+ countdown_handler = Hash.new
62
+ command_window = nil
63
+ command_window_layout = nil
64
+ # We need a mutex for the settings because highlights can be accessed during a
65
+ # reload. For now, it is just used to protect access to HIGHLIGHT, but if we
66
+ # ever support reloading other settings in the future it will have to protect
67
+ # those.
68
+ SETTINGS_LOCK = Mutex.new
69
+ HIGHLIGHT = Hash.new
70
+ PRESET = Hash.new
71
+ LAYOUT = Hash.new
72
+ WINDOWS = Hash.new
73
+ SCROLL_WINDOW = Array.new
74
+
75
+ def add_prompt(window, prompt_text, cmd="")
76
+ window.add_string("#{prompt_text}#{cmd}", [ h={ :start => 0, :end => (prompt_text.length + cmd.length), :fg => '555555' } ])
77
+ end
78
+
79
+ for arg in ARGV
80
+ if arg =~ /^\-\-help|^\-h|^\-\?/
81
+ puts ""
82
+ puts "Profanity FrontEnd v#{$version}"
83
+ puts ""
84
+ puts " --port=<port>"
85
+ puts " --default-color-id=<id>"
86
+ puts " --default-background-color-id=<id>"
87
+ puts " --custom-colors=<on|off>"
88
+ puts " --settings-file=<filename>"
89
+ puts ""
90
+ exit
91
+ elsif arg =~ /^\-\-port=([0-9]+)$/
92
+ PORT = $1.to_i
93
+ elsif arg =~ /^\-\-default\-color\-id=([0-9]+)$/
94
+ DEFAULT_COLOR_ID = $1.to_i
95
+ elsif arg =~ /^\-\-default\-background\-color\-id=([0-9]+)$/
96
+ DEFAULT_BACKGROUND_COLOR_ID = $1.to_i
97
+ elsif arg =~ /^\-\-custom\-colors=(on|off|yes|no)$/
98
+ fix_setting = { 'on' => true, 'yes' => true, 'off' => false, 'no' => false }
99
+ CUSTOM_COLORS = fix_setting[$1]
100
+ elsif arg =~ /^\-\-settings\-file=(.*?)$/
101
+ SETTINGS_FILENAME = $1
102
+ end
103
+ end
104
+
105
+ def log(value)
106
+ File.open('profanity.log', 'a') { |f| f.puts value }
107
+ end
108
+
109
+ unless defined?(PORT)
110
+ PORT = 8000
111
+ end
112
+ unless defined?(DEFAULT_COLOR_ID)
113
+ DEFAULT_COLOR_ID = 7
114
+ end
115
+ unless defined?(DEFAULT_BACKGROUND_COLOR_ID)
116
+ DEFAULT_BACKGROUND_COLOR_ID = 0
117
+ end
118
+ unless defined?(SETTINGS_FILENAME)
119
+ SETTINGS_FILENAME = File.expand_path('~/.profanity.xml')
120
+ end
121
+ unless defined?(CUSTOM_COLORS)
122
+ CUSTOM_COLORS = Curses.can_change_color?
123
+ end
124
+
125
+ DEFAULT_COLOR_CODE = Curses.color_content(DEFAULT_COLOR_ID).collect { |num| ((num/1000.0)*255).round.to_s(16) }.join('').rjust(6, '0')
126
+ DEFAULT_BACKGROUND_COLOR_CODE = Curses.color_content(DEFAULT_BACKGROUND_COLOR_ID).collect { |num| ((num/1000.0)*255).round.to_s(16) }.join('').rjust(6, '0')
127
+
128
+ unless File.exists?(SETTINGS_FILENAME)
129
+
130
+ File.open(SETTINGS_FILENAME, 'w') { |file| file.write "<settings>
131
+ <highlight fg='9090ff'>^(?:You gesture|You intone a phrase of elemental power|You recite a series of mystical phrases|You trace a series of glowing runes|Your hands glow with power as you invoke|You trace a simple rune while intoning|You trace a sign while petitioning the spirits|You trace an intricate sign that contorts in the air).*$</highlight>
132
+ <highlight fg='9090ff'>^(?:Cast Roundtime 3 Seconds\\.|Your spell is ready\\.)$</highlight>
133
+ <highlight fg='9090ff'>^.*remaining\\. \\]$</highlight>
134
+ <highlight fg='88aaff'>([A-Z][a-z]+ disk)</highlight>
135
+ <highlight fg='555555'>^\\[.*?\\](?:>|&gt;).*$</highlight>
136
+ <highlight fg='555555'>\\([0-9][0-9]\\:[0-9][0-9]\\:[0-9][0-9]\\)$</highlight>
137
+ <highlight fg='0000ff'>^\\[LNet\\]</highlight>
138
+ <highlight fg='008000'>^\\[code\\]</highlight>
139
+ <highlight fg='808000'>^\\[Shattered\\]</highlight>
140
+ <highlight fg='ffffff'>^\\[Private(?:To)?\\]</highlight>
141
+ <highlight fg='008000'>^--- Lich:.*</highlight>
142
+ <highlight fg='565656'>\\((?:calmed|dead|flying|hiding|kneeling|prone|sitting|sleeping|stunned)\\)</highlight>
143
+ <highlight fg='ff0000'>^.* throws (?:his|her) arms skyward!$|swirling black void|(?:Dozens of flaming meteors light the sky nearby!|Several flaming meteors light the nearby sky!|Several flaming rocks burst from the sky and smite the area!|A low roar of quickly parting air can be heard above!)</highlight>
144
+ <highlight fg='ffffff'>^.*(?:falls slack against the floor|falls slack against the ground|falls to the floor, motionless|falls to the ground dead|falls to the ground motionless|and dies|and lies still|goes still|going still)\\.$</highlight>
145
+ <highlight fg='ffffff'>^.* is stunned!$|^You come out of hiding\\.$</highlight>
146
+ <highlight fg='ffaaaa'>.*ruining your hiding place\\.$|^You are no longer hidden\\.$|^\\s*You are (?:stunned|knocked to the ground).*|^You are unable to remain hidden!$|^You are visible again\\.$|^You fade into sight\\.$|^You fade into view.*|^You feel drained!$|^You have overextended yourself!$|^You feel yourself going into shock!$</highlight>
147
+ <preset id='whisper' fg='66ff66'/>
148
+ <preset id='speech' fg='66ff66'/>
149
+ <preset id='roomName' fg='ffffff'/>
150
+ <preset id='monsterbold' fg='d2bc2a'/>
151
+ <preset id='familiar' bg='00001a'/>
152
+ <preset id='thoughts' bg='001a00'/>
153
+ <preset id='voln' bg='001a00'/>
154
+ <key id='alt'>
155
+ <key id='f' macro='something'/>
156
+ </key>
157
+ <key id='enter' action='send_command'/>
158
+ <key id='left' action='cursor_left'/>
159
+ <key id='right' action='cursor_right'/>
160
+ <key id='ctrl+left' action='cursor_word_left'/>
161
+ <key id='ctrl+right' action='cursor_word_right'/>
162
+ <key id='home' action='cursor_home'/>
163
+ <key id='end' action='cursor_end'/>
164
+ <key id='backspace' action='cursor_backspace'/>
165
+ <key id='win_backspace' action='cursor_backspace'/>
166
+ <key id='ctrl+?' action='cursor_backspace'/>
167
+ <key id='delete' action='cursor_delete'/>
168
+ <key id='tab' action='switch_current_window'/>
169
+ <key id='alt+page_up' action='scroll_current_window_up_one'/>
170
+ <key id='alt+page_down' action='scroll_current_window_down_one'/>
171
+ <key id='page_up' action='scroll_current_window_up_page'/>
172
+ <key id='page_down' action='scroll_current_window_down_page'/>
173
+ <key id='up' action='previous_command'/>
174
+ <key id='down' action='next_command'/>
175
+ <key id='ctrl+up' action='send_last_command'/>
176
+ <key id='alt+up' action='send_second_last_command'/>
177
+ <key id='resize' action='resize'/>
178
+ <key id='ctrl+d' macro='\\xstance defensive\\r'/>
179
+ <key id='ctrl+o' macro='\\xstance offensive\\r'/>
180
+ <key id='ctrl+g' macro='\\xremove my buckler\\r'/>
181
+ <key id='ctrl+p' macro='\\xwear my buckler\\r'/>
182
+ <key id='ctrl+f' macro='\\xtell familiar to '/>
183
+ <layout id='default'>
184
+ <window class='text' top='6' left='12' width='cols-12' height='lines-7' value='main' buffer-size='2000' />
185
+ <window class='text' top='0' left='0' height='6' width='cols' value='lnet,thoughts,voln' buffer-size='1000' />
186
+ <window class='text' top='7' left='0' width='11' height='lines-31' value='death,logons' buffer-size='500' />
187
+
188
+ <window class='indicator' top='lines-1' left='12' height='1' width='1' label='&gt;' value='prompt' fg='444444,44444'/>
189
+ <window class='command' top='lines-1' left='13' width='cols-13' height='1' />
190
+
191
+ <window class='progress' top='lines-11' left='0' width='11' height='1' label='stance:' value='stance' bg='290055'/>
192
+ <window class='progress' top='lines-10' left='0' width='11' height='1' label='mind:' value='mind' bg='663000,442000'/>
193
+ <window class='progress' top='lines-8' left='0' width='11' height='1' label='health:' value='health' bg='004800,003300'/>
194
+ <window class='progress' top='lines-7' left='0' width='11' height='1' label='spirit:' value='spirit' bg='333300,222200'/>
195
+ <window class='progress' top='lines-6' left='0' width='11' height='1' label='mana:' value='mana' bg='0000a0,000055'/>
196
+ <window class='progress' top='lines-5' left='0' width='11' height='1' label='stam:' value='stamina' bg='003333,002222'/>
197
+ <window class='progress' top='lines-3' left='0' width='11' height='1' label='load:' value='encumbrance' bg='990033,4c0019' fg='nil,nil,nil,444444'/>
198
+
199
+ <window class='countdown' top='lines-2' left='0' width='11' height='1' label='stun:' value='stunned' fg='444444,dddddd' bg='nil,aa0000'/>
200
+ <window class='countdown' top='lines-1' left='0' width='11' height='1' label='rndtime:' value='roundtime' fg='444444,dddddd,dddddd,dddddd' bg='nil,aa0000,0000aa'/>
201
+
202
+ <window class='indicator' top='lines-15' left='1' height='1' width='1' label='^' value='compass:up' fg='444444,ffff00'/>
203
+ <window class='indicator' top='lines-14' left='1' height='1' width='1' label='o' value='compass:out' fg='444444,ffff00'/>
204
+ <window class='indicator' top='lines-13' left='1' height='1' width='1' label='v' value='compass:down' fg='444444,ffff00'/>
205
+ <window class='indicator' top='lines-15' left='5' height='1' width='1' label='*' value='compass:nw' fg='444444,ffff00'/>
206
+ <window class='indicator' top='lines-14' left='5' height='1' width='1' label='&lt;' value='compass:w' fg='444444,ffff00'/>
207
+ <window class='indicator' top='lines-13' left='5' height='1' width='1' label='*' value='compass:sw' fg='444444,ffff00'/>
208
+ <window class='indicator' top='lines-15' left='7' height='1' width='1' label='^' value='compass:n' fg='444444,ffff00'/>
209
+ <window class='indicator' top='lines-13' left='7' height='1' width='1' label='v' value='compass:s' fg='444444,ffff00'/>
210
+ <window class='indicator' top='lines-15' left='9' height='1' width='1' label='*' value='compass:ne' fg='444444,ffff00'/>
211
+ <window class='indicator' top='lines-14' left='9' height='1' width='1' label='&gt;' value='compass:e' fg='444444,ffff00'/>
212
+ <window class='indicator' top='lines-13' left='9' height='1' width='1' label='*' value='compass:se' fg='444444,ffff00'/>
213
+
214
+ <window class='indicator' top='lines-23' left='1' height='1' width='1' label='e' value='leftEye' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
215
+ <window class='indicator' top='lines-23' left='5' height='1' width='1' label='e' value='rightEye' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
216
+ <window class='indicator' top='lines-22' left='3' height='1' width='1' label='O' value='head' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
217
+ <window class='indicator' top='lines-21' left='2' height='1' width='1' label='/' value='leftArm' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
218
+ <window class='indicator' top='lines-21' left='3' height='1' width='1' label='|' value='chest' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
219
+ <window class='indicator' top='lines-21' left='4' height='1' width='1' label='\\' value='rightArm' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
220
+ <window class='indicator' top='lines-20' left='1' height='1' width='1' label='o' value='leftHand' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
221
+ <window class='indicator' top='lines-20' left='3' height='1' width='1' label='|' value='abdomen' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
222
+ <window class='indicator' top='lines-20' left='5' height='1' width='1' label='o' value='rightHand' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
223
+ <window class='indicator' top='lines-19' left='1' height='2' width='2' label=' /o' value='leftLeg' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
224
+ <window class='indicator' top='lines-19' left='4' height='2' width='2' label='\\ o' value='rightLeg' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
225
+ <window class='indicator' top='lines-23' left='8' height='1' width='2' label='ns' value='nsys' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
226
+ <window class='indicator' top='lines-21' left='8' height='1' width='2' label='nk' value='neck' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
227
+ <window class='indicator' top='lines-19' left='8' height='1' width='2' label='bk' value='back' fg='444444,ffff00,ff6600,ff0000,00ffff,0066ff,0000ff'/>
228
+
229
+ <window class='indicator' top='lines-17' left='0' height='1' width='3' label='psn' value='poisoned' fg='444444,ff0000'/>
230
+ <window class='indicator' top='lines-17' left='4' height='1' width='3' label='dis' value='diseased' fg='444444,ff0000'/>
231
+ <window class='indicator' top='lines-17' left='8' height='1' width='3' label='bld' value='bleeding' fg='444444,ff0000'/>
232
+ </layout>
233
+ </settings>
234
+ " }
235
+
236
+ end
237
+
238
+ xml_escape_list = {
239
+ '&lt;' => '<',
240
+ '&gt;' => '>',
241
+ '&quot;' => '"',
242
+ '&apos;' => "'",
243
+ '&amp;' => '&',
244
+ # '&#xA' => "\n",
245
+ }
246
+
247
+ key_name = {
248
+ 'ctrl+a' => 1,
249
+ 'ctrl+b' => 2,
250
+ # 'ctrl+c' => 3,
251
+ 'ctrl+d' => 4,
252
+ 'ctrl+e' => 5,
253
+ 'ctrl+f' => 6,
254
+ 'ctrl+g' => 7,
255
+ 'ctrl+h' => 8,
256
+ 'win_backspace' => 8,
257
+ 'ctrl+i' => 9,
258
+ 'tab' => 9,
259
+ 'ctrl+j' => 10,
260
+ 'enter' => 10,
261
+ 'ctrl+k' => 11,
262
+ 'ctrl+l' => 12,
263
+ 'return' => 13,
264
+ 'ctrl+m' => 13,
265
+ 'ctrl+n' => 14,
266
+ 'ctrl+o' => 15,
267
+ 'ctrl+p' => 16,
268
+ # 'ctrl+q' => 17,
269
+ 'ctrl+r' => 18,
270
+ # 'ctrl+s' => 19,
271
+ 'ctrl+t' => 20,
272
+ 'ctrl+u' => 21,
273
+ 'ctrl+v' => 22,
274
+ 'ctrl+w' => 23,
275
+ 'ctrl+x' => 24,
276
+ 'ctrl+y' => 25,
277
+ # 'ctrl+z' => 26,
278
+ 'alt' => 27,
279
+ 'escape' => 27,
280
+ 'ctrl+?' => 127,
281
+ 'down' => 258,
282
+ 'up' => 259,
283
+ 'left' => 260,
284
+ 'right' => 261,
285
+ 'home' => 262,
286
+ 'backspace' => 263,
287
+ 'f1' => 265,
288
+ 'f2' => 266,
289
+ 'f3' => 267,
290
+ 'f4' => 268,
291
+ 'f5' => 269,
292
+ 'f6' => 270,
293
+ 'f7' => 271,
294
+ 'f8' => 272,
295
+ 'f9' => 273,
296
+ 'f10' => 274,
297
+ 'f11' => 275,
298
+ 'f12' => 276,
299
+ 'delete' => 330,
300
+ 'insert' => 331,
301
+ 'page_down' => 338,
302
+ 'page_up' => 339,
303
+ 'end' => 360,
304
+ 'resize' => 410,
305
+ 'ctrl+delete' => 513,
306
+ 'alt+down' => 517,
307
+ 'ctrl+down' => 519,
308
+ 'alt+left' => 537,
309
+ 'ctrl+left' => 539,
310
+ 'alt+page_down' => 542,
311
+ 'alt+page_up' => 547,
312
+ 'alt+right' => 552,
313
+ 'ctrl+right' => 554,
314
+ 'alt+up' => 558,
315
+ 'ctrl+up' => 560,
316
+ }
317
+
318
+ if CUSTOM_COLORS
319
+ COLOR_ID_LOOKUP = Hash.new
320
+ COLOR_ID_LOOKUP[DEFAULT_COLOR_CODE] = DEFAULT_COLOR_ID
321
+ COLOR_ID_LOOKUP[DEFAULT_BACKGROUND_COLOR_CODE] = DEFAULT_BACKGROUND_COLOR_ID
322
+ COLOR_ID_HISTORY = Array.new
323
+ for num in 0...Curses.colors
324
+ unless (num == DEFAULT_COLOR_ID) or (num == DEFAULT_BACKGROUND_COLOR_ID)
325
+ COLOR_ID_HISTORY.push(num)
326
+ end
327
+ end
328
+
329
+ def get_color_id(code)
330
+ if color_id = COLOR_ID_LOOKUP[code]
331
+ color_id
332
+ else
333
+ color_id = COLOR_ID_HISTORY.shift
334
+ COLOR_ID_LOOKUP.delete_if { |k,v| v == color_id }
335
+ sleep 0.01 # somehow this keeps Curses.init_color from failing sometimes
336
+ Curses.init_color(color_id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round)
337
+ COLOR_ID_LOOKUP[code] = color_id
338
+ COLOR_ID_HISTORY.push(color_id)
339
+ color_id
340
+ end
341
+ end
342
+ else
343
+ COLOR_CODE = [ '000000', '800000', '008000', '808000', '000080', '800080', '008080', 'c0c0c0', '808080', 'ff0000', '00ff00', 'ffff00', '0000ff', 'ff00ff', '00ffff', 'ffffff', '000000', '00005f', '000087', '0000af', '0000d7', '0000ff', '005f00', '005f5f', '005f87', '005faf', '005fd7', '005fff', '008700', '00875f', '008787', '0087af', '0087d7', '0087ff', '00af00', '00af5f', '00af87', '00afaf', '00afd7', '00afff', '00d700', '00d75f', '00d787', '00d7af', '00d7d7', '00d7ff', '00ff00', '00ff5f', '00ff87', '00ffaf', '00ffd7', '00ffff', '5f0000', '5f005f', '5f0087', '5f00af', '5f00d7', '5f00ff', '5f5f00', '5f5f5f', '5f5f87', '5f5faf', '5f5fd7', '5f5fff', '5f8700', '5f875f', '5f8787', '5f87af', '5f87d7', '5f87ff', '5faf00', '5faf5f', '5faf87', '5fafaf', '5fafd7', '5fafff', '5fd700', '5fd75f', '5fd787', '5fd7af', '5fd7d7', '5fd7ff', '5fff00', '5fff5f', '5fff87', '5fffaf', '5fffd7', '5fffff', '870000', '87005f', '870087', '8700af', '8700d7', '8700ff', '875f00', '875f5f', '875f87', '875faf', '875fd7', '875fff', '878700', '87875f', '878787', '8787af', '8787d7', '8787ff', '87af00', '87af5f', '87af87', '87afaf', '87afd7', '87afff', '87d700', '87d75f', '87d787', '87d7af', '87d7d7', '87d7ff', '87ff00', '87ff5f', '87ff87', '87ffaf', '87ffd7', '87ffff', 'af0000', 'af005f', 'af0087', 'af00af', 'af00d7', 'af00ff', 'af5f00', 'af5f5f', 'af5f87', 'af5faf', 'af5fd7', 'af5fff', 'af8700', 'af875f', 'af8787', 'af87af', 'af87d7', 'af87ff', 'afaf00', 'afaf5f', 'afaf87', 'afafaf', 'afafd7', 'afafff', 'afd700', 'afd75f', 'afd787', 'afd7af', 'afd7d7', 'afd7ff', 'afff00', 'afff5f', 'afff87', 'afffaf', 'afffd7', 'afffff', 'd70000', 'd7005f', 'd70087', 'd700af', 'd700d7', 'd700ff', 'd75f00', 'd75f5f', 'd75f87', 'd75faf', 'd75fd7', 'd75fff', 'd78700', 'd7875f', 'd78787', 'd787af', 'd787d7', 'd787ff', 'd7af00', 'd7af5f', 'd7af87', 'd7afaf', 'd7afd7', 'd7afff', 'd7d700', 'd7d75f', 'd7d787', 'd7d7af', 'd7d7d7', 'd7d7ff', 'd7ff00', 'd7ff5f', 'd7ff87', 'd7ffaf', 'd7ffd7', 'd7ffff', 'ff0000', 'ff005f', 'ff0087', 'ff00af', 'ff00d7', 'ff00ff', 'ff5f00', 'ff5f5f', 'ff5f87', 'ff5faf', 'ff5fd7', 'ff5fff', 'ff8700', 'ff875f', 'ff8787', 'ff87af', 'ff87d7', 'ff87ff', 'ffaf00', 'ffaf5f', 'ffaf87', 'ffafaf', 'ffafd7', 'ffafff', 'ffd700', 'ffd75f', 'ffd787', 'ffd7af', 'ffd7d7', 'ffd7ff', 'ffff00', 'ffff5f', 'ffff87', 'ffffaf', 'ffffd7', 'ffffff', '080808', '121212', '1c1c1c', '262626', '303030', '3a3a3a', '444444', '4e4e4e', '585858', '626262', '6c6c6c', '767676', '808080', '8a8a8a', '949494', '9e9e9e', 'a8a8a8', 'b2b2b2', 'bcbcbc', 'c6c6c6', 'd0d0d0', 'dadada', 'e4e4e4', 'eeeeee' ][0...Curses.colors]
344
+ COLOR_ID_LOOKUP = Hash.new
345
+
346
+ def get_color_id(code)
347
+ if color_id = COLOR_ID_LOOKUP[code]
348
+ color_id
349
+ else
350
+ least_error = nil
351
+ least_error_id = nil
352
+ COLOR_CODE.each_index { |color_id|
353
+ error = ((COLOR_CODE[color_id][0..1].hex - code[0..1].hex)**2) + ((COLOR_CODE[color_id][2..3].hex - code[2..3].hex)**2) + ((COLOR_CODE[color_id][4..6].hex - code[4..6].hex)**2)
354
+ if least_error.nil? or (error < least_error)
355
+ least_error = error
356
+ least_error_id = color_id
357
+ end
358
+ }
359
+ COLOR_ID_LOOKUP[code] = least_error_id
360
+ least_error_id
361
+ end
362
+ end
363
+ end
364
+
365
+ #COLOR_PAIR_LIST = Array.new
366
+ #for num in 1...Curses::color_pairs
367
+ # COLOR_PAIR_LIST.push h={ :color_id => nil, :background_id => nil, :id => num }
368
+ #end
369
+
370
+ #157+12+1 = 180
371
+ #38+1+6 = 45
372
+ #32767
373
+
374
+ COLOR_PAIR_ID_LOOKUP = Hash.new
375
+ COLOR_PAIR_HISTORY = Array.new
376
+
377
+ # fixme: high color pair id's change text?
378
+ # A_NORMAL = 0
379
+ # A_STANDOUT = 65536
380
+ # A_UNDERLINE = 131072
381
+ # 15000 = black background, dark blue-green text
382
+ # 10000 = dark yellow background, black text
383
+ # 5000 = black
384
+ # 2000 = black
385
+ # 1000 = highlights show up black
386
+ # 100 = normal
387
+ # 500 = black and some underline
388
+
389
+ for num in 1...Curses::color_pairs # fixme: things go to hell at about pair 256
390
+ #for num in 1...([Curses::color_pairs, 256].min)
391
+ COLOR_PAIR_HISTORY.push(num)
392
+ end
393
+
394
+ def get_color_pair_id(fg_code, bg_code)
395
+ if fg_code.nil?
396
+ fg_id = DEFAULT_COLOR_ID
397
+ else
398
+ fg_id = get_color_id(fg_code)
399
+ end
400
+ if bg_code.nil?
401
+ bg_id = DEFAULT_BACKGROUND_COLOR_ID
402
+ else
403
+ bg_id = get_color_id(bg_code)
404
+ end
405
+ if (COLOR_PAIR_ID_LOOKUP[fg_id]) and (color_pair_id = COLOR_PAIR_ID_LOOKUP[fg_id][bg_id])
406
+ color_pair_id
407
+ else
408
+ color_pair_id = COLOR_PAIR_HISTORY.shift
409
+ COLOR_PAIR_ID_LOOKUP.each { |w,x| x.delete_if { |y,z| z == color_pair_id } }
410
+ sleep 0.01
411
+ Curses.init_pair(color_pair_id, fg_id, bg_id)
412
+ COLOR_PAIR_ID_LOOKUP[fg_id] ||= Hash.new
413
+ COLOR_PAIR_ID_LOOKUP[fg_id][bg_id] = color_pair_id
414
+ COLOR_PAIR_HISTORY.push(color_pair_id)
415
+ color_pair_id
416
+ end
417
+ end
418
+
419
+ # Implement support for basic readline-style kill and yank (cut and paste)
420
+ # commands. Successive calls to delete_word, backspace_word, kill_forward, and
421
+ # kill_line will accumulate text into the kill_buffer as long as no other
422
+ # commands have changed the command buffer. These commands call kill_before to
423
+ # reset the kill_buffer if the command buffer has changed, add the newly
424
+ # deleted text to the kill_buffer, and finally call kill_after to remember the
425
+ # state of the command buffer for next time.
426
+ kill_buffer = ''
427
+ kill_original = ''
428
+ kill_last = ''
429
+ kill_last_pos = 0
430
+ kill_before = proc {
431
+ if kill_last != command_buffer || kill_last_pos != command_buffer_pos
432
+ kill_buffer = ''
433
+ kill_original = command_buffer
434
+ end
435
+ }
436
+ kill_after = proc {
437
+ kill_last = command_buffer.dup
438
+ kill_last_pos = command_buffer_pos
439
+ }
440
+
441
+ fix_layout_number = proc { |str|
442
+ str = str.gsub('lines', Curses.lines.to_s).gsub('cols', Curses.cols.to_s)
443
+ str.untaint
444
+ begin
445
+ proc { $SAFE = 1; eval(str) }.call.to_i
446
+ rescue
447
+ $stderr.puts $!
448
+ $stderr.puts $!.backtrace[0..1]
449
+ 0
450
+ end
451
+ }
452
+
453
+ load_layout = proc { |layout_id|
454
+ if xml = LAYOUT[layout_id]
455
+ old_windows = IndicatorWindow.list | TextWindow.list | CountdownWindow.list | ProgressWindow.list
456
+
457
+ previous_indicator_handler = indicator_handler
458
+ indicator_handler = Hash.new
459
+
460
+ previous_stream_handler = stream_handler
461
+ stream_handler = Hash.new
462
+
463
+ previous_progress_handler = progress_handler
464
+ progress_handler = Hash.new
465
+
466
+ previous_countdown_handler = countdown_handler
467
+ progress_handler = Hash.new
468
+
469
+ xml.elements.each { |e|
470
+ if e.name == 'window'
471
+ height, width, top, left = fix_layout_number.call(e.attributes['height']), fix_layout_number.call(e.attributes['width']), fix_layout_number.call(e.attributes['top']), fix_layout_number.call(e.attributes['left'])
472
+ if (height > 0) and (width > 0) and (top >= 0) and (left >= 0) and (top < Curses.lines) and (left < Curses.cols)
473
+ if e.attributes['class'] == 'indicator'
474
+ if e.attributes['value'] and (window = previous_indicator_handler[e.attributes['value']])
475
+ previous_indicator_handler[e.attributes['value']] = nil
476
+ old_windows.delete(window)
477
+ else
478
+ window = IndicatorWindow.new(height, width, top, left)
479
+ end
480
+ window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ]
481
+ window.scrollok(false)
482
+ window.label = e.attributes['label'] if e.attributes['label']
483
+ window.fg = e.attributes['fg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg']
484
+ window.bg = e.attributes['bg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg']
485
+ if e.attributes['value']
486
+ indicator_handler[e.attributes['value']] = window
487
+ end
488
+ window.redraw
489
+ elsif e.attributes['class'] == 'text'
490
+ if width > 1
491
+ if e.attributes['value'] and (window = previous_stream_handler[previous_stream_handler.keys.find { |key| e.attributes['value'].split(',').include?(key) }])
492
+ previous_stream_handler[e.attributes['value']] = nil
493
+ old_windows.delete(window)
494
+ else
495
+ window = TextWindow.new(height, width - 1, top, left)
496
+ window.scrollbar = Curses::Window.new(window.maxy, 1, window.begy, window.begx + window.maxx)
497
+ end
498
+ window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ]
499
+ window.scrollok(true)
500
+ window.max_buffer_size = e.attributes['buffer-size'] || 1000
501
+ e.attributes['value'].split(',').each { |str|
502
+ stream_handler[str] = window
503
+ }
504
+ end
505
+ elsif e.attributes['class'] == 'countdown'
506
+ if e.attributes['value'] and (window = previous_countdown_handler[e.attributes['value']])
507
+ previous_countdown_handler[e.attributes['value']] = nil
508
+ old_windows.delete(window)
509
+ else
510
+ window = CountdownWindow.new(height, width, top, left)
511
+ end
512
+ window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ]
513
+ window.scrollok(false)
514
+ window.label = e.attributes['label'] if e.attributes['label']
515
+ window.fg = e.attributes['fg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg']
516
+ window.bg = e.attributes['bg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg']
517
+ if e.attributes['value']
518
+ countdown_handler[e.attributes['value']] = window
519
+ end
520
+ window.update
521
+ elsif e.attributes['class'] == 'progress'
522
+ if e.attributes['value'] and (window = previous_progress_handler[e.attributes['value']])
523
+ previous_progress_handler[e.attributes['value']] = nil
524
+ old_windows.delete(window)
525
+ else
526
+ window = ProgressWindow.new(height, width, top, left)
527
+ end
528
+ window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ]
529
+ window.scrollok(false)
530
+ window.label = e.attributes['label'] if e.attributes['label']
531
+ window.fg = e.attributes['fg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg']
532
+ window.bg = e.attributes['bg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg']
533
+ if e.attributes['value']
534
+ progress_handler[e.attributes['value']] = window
535
+ end
536
+ window.redraw
537
+ elsif e.attributes['class'] == 'command'
538
+ unless command_window
539
+ command_window = Curses::Window.new(height, width, top, left)
540
+ end
541
+ command_window_layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ]
542
+ command_window.scrollok(false)
543
+ command_window.keypad(true)
544
+ end
545
+ end
546
+ end
547
+ }
548
+ if current_scroll_window = TextWindow.list[0]
549
+ current_scroll_window.update_scrollbar
550
+ end
551
+ for window in old_windows
552
+ IndicatorWindow.list.delete(window)
553
+ TextWindow.list.delete(window)
554
+ CountdownWindow.list.delete(window)
555
+ ProgressWindow.list.delete(window)
556
+ if window.class == TextWindow
557
+ window.scrollbar.close
558
+ end
559
+ window.close
560
+ end
561
+ Curses.doupdate
562
+ end
563
+ }
564
+
565
+ do_macro = nil
566
+
567
+ setup_key = proc { |xml,binding|
568
+ if key = xml.attributes['id']
569
+ if key =~ /^[0-9]+$/
570
+ key = key.to_i
571
+ elsif (key.class) == String and (key.length == 1)
572
+ nil
573
+ else
574
+ key = key_name[key]
575
+ end
576
+ if key
577
+ if macro = xml.attributes['macro']
578
+ binding[key] = proc { do_macro.call(macro) }
579
+ elsif xml.attributes['action'] and action = key_action[xml.attributes['action']]
580
+ binding[key] = action
581
+ else
582
+ binding[key] ||= Hash.new
583
+ xml.elements.each { |e|
584
+ setup_key.call(e, binding[key])
585
+ }
586
+ end
587
+ end
588
+ end
589
+ }
590
+
591
+ load_settings_file = proc { |reload|
592
+ SETTINGS_LOCK.synchronize {
593
+ begin
594
+ HIGHLIGHT.clear()
595
+ File.open(SETTINGS_FILENAME) { |file|
596
+ xml_doc = REXML::Document.new(file)
597
+ xml_root = xml_doc.root
598
+ xml_root.elements.each { |e|
599
+ if e.name == 'highlight'
600
+ begin
601
+ r = Regexp.new(e.text)
602
+ rescue
603
+ r = nil
604
+ $stderr.puts e.to_s
605
+ $stderr.puts $!
606
+ end
607
+ if r
608
+ HIGHLIGHT[r] = [ e.attributes['fg'], e.attributes['bg'], e.attributes['ul'] ]
609
+ end
610
+ end
611
+ # These are things that we ignore if we're doing a reload of the settings file
612
+ if !reload
613
+ if e.name == 'preset'
614
+ PRESET[e.attributes['id']] = [ e.attributes['fg'], e.attributes['bg'] ]
615
+ elsif (e.name == 'layout') and (layout_id = e.attributes['id'])
616
+ LAYOUT[layout_id] = e
617
+ elsif e.name == 'key'
618
+ setup_key.call(e, key_binding)
619
+ end
620
+ end
621
+ }
622
+ }
623
+ rescue
624
+ $stdout.puts $!
625
+ $stdout.puts $!.backtrace[0..1]
626
+ log $!
627
+ log $!.backtrace[0..1]
628
+
629
+ end
630
+ }
631
+ }
632
+
633
+ command_window_put_ch = proc { |ch|
634
+ if (command_buffer_pos - command_buffer_offset + 1) >= command_window.maxx
635
+ command_window.setpos(0,0)
636
+ command_window.delch
637
+ command_buffer_offset += 1
638
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
639
+ end
640
+ command_buffer.insert(command_buffer_pos, ch)
641
+ command_buffer_pos += 1
642
+ command_window.insch(ch)
643
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
644
+ }
645
+
646
+ do_macro = proc { |macro|
647
+ # fixme: gsub %whatever
648
+ backslash = false
649
+ at_pos = nil
650
+ backfill = nil
651
+ macro.split('').each_with_index { |ch, i|
652
+ if backslash
653
+ if ch == '\\'
654
+ command_window_put_ch.call('\\')
655
+ elsif ch == 'x'
656
+ command_buffer.clear
657
+ command_buffer_pos = 0
658
+ command_buffer_offset = 0
659
+ command_window.deleteln
660
+ command_window.setpos(0,0)
661
+ elsif ch == 'r'
662
+ at_pos = nil
663
+ key_action['send_command'].call
664
+ elsif ch == '@'
665
+ command_window_put_ch.call('@')
666
+ elsif ch == '?'
667
+ backfill = i - 3
668
+ else
669
+ nil
670
+ end
671
+ backslash = false
672
+ else
673
+ if ch == '\\'
674
+ backslash = true
675
+ elsif ch == '@'
676
+ at_pos = command_buffer_pos
677
+ else
678
+ command_window_put_ch.call(ch)
679
+ end
680
+ end
681
+ }
682
+ if at_pos
683
+ while at_pos < command_buffer_pos
684
+ key_action['cursor_left'].call
685
+ end
686
+ while at_pos > command_buffer_pos
687
+ key_action['cursor_right'].call
688
+ end
689
+ end
690
+ command_window.noutrefresh
691
+ if backfill then
692
+ command_window.setpos(0,backfill)
693
+ command_buffer_pos = backfill
694
+ backfill = nil
695
+ end
696
+ Curses.doupdate
697
+ }
698
+
699
+ key_action['resize'] = proc {
700
+ # fixme: re-word-wrap
701
+ window = Window.new(0,0,0,0)
702
+ window.refresh
703
+ window.close
704
+ first_text_window = true
705
+ for window in TextWindow.list.to_a
706
+ window.resize(fix_layout_number.call(window.layout[0]), fix_layout_number.call(window.layout[1]) - 1)
707
+ window.move(fix_layout_number.call(window.layout[2]), fix_layout_number.call(window.layout[3]))
708
+ window.scrollbar.resize(window.maxy, 1)
709
+ window.scrollbar.move(window.begy, window.begx + window.maxx)
710
+ window.scroll(-window.maxy)
711
+ window.scroll(window.maxy)
712
+ window.clear_scrollbar
713
+ if first_text_window
714
+ window.update_scrollbar
715
+ first_text_window = false
716
+ end
717
+ window.noutrefresh
718
+ end
719
+ for window in [ IndicatorWindow.list.to_a, ProgressWindow.list.to_a, CountdownWindow.list.to_a ].flatten
720
+ window.resize(fix_layout_number.call(window.layout[0]), fix_layout_number.call(window.layout[1]))
721
+ window.move(fix_layout_number.call(window.layout[2]), fix_layout_number.call(window.layout[3]))
722
+ window.noutrefresh
723
+ end
724
+ if command_window
725
+ command_window.resize(fix_layout_number.call(command_window_layout[0]), fix_layout_number.call(command_window_layout[1]))
726
+ command_window.move(fix_layout_number.call(command_window_layout[2]), fix_layout_number.call(command_window_layout[3]))
727
+ command_window.noutrefresh
728
+ end
729
+ Curses.doupdate
730
+ }
731
+
732
+ key_action['cursor_left'] = proc {
733
+ if (command_buffer_offset > 0) and (command_buffer_pos - command_buffer_offset == 0)
734
+ command_buffer_pos -= 1
735
+ command_buffer_offset -= 1
736
+ command_window.insch(command_buffer[command_buffer_pos])
737
+ else
738
+ command_buffer_pos = [command_buffer_pos - 1, 0].max
739
+ end
740
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
741
+ command_window.noutrefresh
742
+ Curses.doupdate
743
+ }
744
+
745
+ key_action['cursor_right'] = proc {
746
+ if ((command_buffer.length - command_buffer_offset) >= (command_window.maxx - 1)) and (command_buffer_pos - command_buffer_offset + 1) >= command_window.maxx
747
+ if command_buffer_pos < command_buffer.length
748
+ command_window.setpos(0,0)
749
+ command_window.delch
750
+ command_buffer_offset += 1
751
+ command_buffer_pos += 1
752
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
753
+ unless command_buffer_pos >= command_buffer.length
754
+ command_window.insch(command_buffer[command_buffer_pos])
755
+ end
756
+ end
757
+ else
758
+ command_buffer_pos = [command_buffer_pos + 1, command_buffer.length].min
759
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
760
+ end
761
+ command_window.noutrefresh
762
+ Curses.doupdate
763
+ }
764
+
765
+ key_action['cursor_word_left'] = proc {
766
+ if command_buffer_pos > 0
767
+ if m = command_buffer[0...(command_buffer_pos-1)].match(/.*(\w[^\w\s]|\W\w|\s\S)/)
768
+ new_pos = m.begin(1) + 1
769
+ else
770
+ new_pos = 0
771
+ end
772
+ if (command_buffer_offset > new_pos)
773
+ command_window.setpos(0, 0)
774
+ command_buffer[new_pos, (command_buffer_offset - new_pos)].split('').reverse.each { |ch| command_window.insch(ch) }
775
+ command_buffer_pos = new_pos
776
+ command_buffer_offset = new_pos
777
+ else
778
+ command_buffer_pos = new_pos
779
+ end
780
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
781
+ command_window.noutrefresh
782
+ Curses.doupdate
783
+ end
784
+ }
785
+
786
+ key_action['cursor_word_right'] = proc {
787
+ if command_buffer_pos < command_buffer.length
788
+ if m = command_buffer[command_buffer_pos..-1].match(/\w[^\w\s]|\W\w|\s\S/)
789
+ new_pos = command_buffer_pos + m.begin(0) + 1
790
+ else
791
+ new_pos = command_buffer.length
792
+ end
793
+ overflow = new_pos - command_window.maxx - command_buffer_offset + 1
794
+ if overflow > 0
795
+ command_window.setpos(0,0)
796
+ overflow.times {
797
+ command_window.delch
798
+ command_buffer_offset += 1
799
+ }
800
+ command_window.setpos(0, command_window.maxx - overflow)
801
+ command_window.addstr command_buffer[(command_window.maxx - overflow + command_buffer_offset),overflow]
802
+ end
803
+ command_buffer_pos = new_pos
804
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
805
+ command_window.noutrefresh
806
+ Curses.doupdate
807
+ end
808
+ }
809
+
810
+ key_action['cursor_home'] = proc {
811
+ command_buffer_pos = 0
812
+ command_window.setpos(0, 0)
813
+ for num in 1..command_buffer_offset
814
+ begin
815
+ command_window.insch(command_buffer[command_buffer_offset-num])
816
+ rescue
817
+ File.open('profanity.log', 'a') { |f| f.puts "command_buffer: #{command_buffer.inspect}"; f.puts "command_buffer_offset: #{command_buffer_offset.inspect}"; f.puts "num: #{num.inspect}"; f.puts $!; f.puts $!.backtrace[0...4] }
818
+ exit
819
+ end
820
+ end
821
+ command_buffer_offset = 0
822
+ command_window.noutrefresh
823
+ Curses.doupdate
824
+ }
825
+
826
+ key_action['cursor_end'] = proc {
827
+ if command_buffer.length < (command_window.maxx - 1)
828
+ command_buffer_pos = command_buffer.length
829
+ command_window.setpos(0, command_buffer_pos)
830
+ else
831
+ scroll_left_num = command_buffer.length - command_window.maxx + 1 - command_buffer_offset
832
+ command_window.setpos(0, 0)
833
+ scroll_left_num.times {
834
+ command_window.delch
835
+ command_buffer_offset += 1
836
+ }
837
+ command_buffer_pos = command_buffer_offset + command_window.maxx - 1 - scroll_left_num
838
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
839
+ scroll_left_num.times {
840
+ command_window.addch(command_buffer[command_buffer_pos])
841
+ command_buffer_pos += 1
842
+ }
843
+ end
844
+ command_window.noutrefresh
845
+ Curses.doupdate
846
+ }
847
+
848
+ key_action['cursor_backspace'] = proc {
849
+ if command_buffer_pos > 0
850
+ command_buffer_pos -= 1
851
+ if command_buffer_pos == 0
852
+ command_buffer = command_buffer[(command_buffer_pos+1)..-1]
853
+ else
854
+ command_buffer = command_buffer[0..(command_buffer_pos-1)] + command_buffer[(command_buffer_pos+1)..-1]
855
+ end
856
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
857
+ command_window.delch
858
+ if (command_buffer.length - command_buffer_offset + 1) > command_window.maxx
859
+ command_window.setpos(0, command_window.maxx - 1)
860
+ command_window.addch command_buffer[command_window.maxx - command_buffer_offset - 1]
861
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
862
+ end
863
+ command_window.noutrefresh
864
+ Curses.doupdate
865
+ end
866
+ }
867
+
868
+ class String
869
+ def alnum?
870
+ !!match(/^[[:alnum:]]+$/)
871
+ end
872
+ def digits?
873
+ !!match(/^[[:digit:]]+$/)
874
+ end
875
+ def punct?
876
+ !!match(/^[[:punct:]]+$/)
877
+ end
878
+ def space?
879
+ !!match(/^[[:space:]]+$/)
880
+ end
881
+ end
882
+
883
+ key_action['cursor_delete'] = proc {
884
+ if (command_buffer.length > 0) and (command_buffer_pos < command_buffer.length)
885
+ if command_buffer_pos == 0
886
+ command_buffer = command_buffer[(command_buffer_pos+1)..-1]
887
+ elsif command_buffer_pos < command_buffer.length
888
+ command_buffer = command_buffer[0..(command_buffer_pos-1)] + command_buffer[(command_buffer_pos+1)..-1]
889
+ end
890
+ command_window.delch
891
+ if (command_buffer.length - command_buffer_offset + 1) > command_window.maxx
892
+ command_window.setpos(0, command_window.maxx - 1)
893
+ command_window.addch command_buffer[command_window.maxx - command_buffer_offset - 1]
894
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
895
+ end
896
+ command_window.noutrefresh
897
+ Curses.doupdate
898
+ end
899
+ }
900
+
901
+
902
+ key_action['cursor_backspace_word'] = proc {
903
+ num_deleted = 0
904
+ deleted_alnum = false
905
+ deleted_nonspace = false
906
+ while command_buffer_pos > 0 do
907
+ next_char = command_buffer[command_buffer_pos - 1]
908
+ if num_deleted == 0 || (!deleted_alnum && next_char.punct?) || (!deleted_nonspace && next_char.space?) || next_char.alnum?
909
+ deleted_alnum = deleted_alnum || next_char.alnum?
910
+ deleted_nonspace = !next_char.space?
911
+ num_deleted += 1
912
+ kill_before.call
913
+ kill_buffer = next_char + kill_buffer
914
+ key_action['cursor_backspace'].call
915
+ kill_after.call
916
+ else
917
+ break
918
+ end
919
+ end
920
+ }
921
+
922
+ key_action['cursor_delete_word'] = proc {
923
+ num_deleted = 0
924
+ deleted_alnum = false
925
+ deleted_nonspace = false
926
+ while command_buffer_pos < command_buffer.length do
927
+ next_char = command_buffer[command_buffer_pos]
928
+ if num_deleted == 0 || (!deleted_alnum && next_char.punct?) || (!deleted_nonspace && next_char.space?) || next_char.alnum?
929
+ deleted_alnum = deleted_alnum || next_char.alnum?
930
+ deleted_nonspace = !next_char.space?
931
+ num_deleted += 1
932
+ kill_before.call
933
+ kill_buffer = kill_buffer + next_char
934
+ key_action['cursor_delete'].call
935
+ kill_after.call
936
+ else
937
+ break
938
+ end
939
+ end
940
+ }
941
+
942
+ key_action['cursor_kill_forward'] = proc {
943
+ if command_buffer_pos < command_buffer.length
944
+ kill_before.call
945
+ if command_buffer_pos == 0
946
+ kill_buffer = kill_buffer + command_buffer
947
+ command_buffer = ''
948
+ else
949
+ kill_buffer = kill_buffer + command_buffer[command_buffer_pos..-1]
950
+ command_buffer = command_buffer[0..(command_buffer_pos-1)]
951
+ end
952
+ kill_after.call
953
+ command_window.clrtoeol
954
+ command_window.noutrefresh
955
+ Curses.doupdate
956
+ end
957
+ }
958
+
959
+ key_action['cursor_kill_line'] = proc {
960
+ if command_buffer.length != 0
961
+ kill_before.call
962
+ kill_buffer = kill_original
963
+ command_buffer = ''
964
+ command_buffer_pos = 0
965
+ command_buffer_offset = 0
966
+ kill_after.call
967
+ command_window.setpos(0, 0)
968
+ command_window.clrtoeol
969
+ command_window.noutrefresh
970
+ Curses.doupdate
971
+ end
972
+ }
973
+
974
+ key_action['cursor_yank'] = proc {
975
+ kill_buffer.each_char { |c| command_window_put_ch.call(c) }
976
+ }
977
+
978
+ key_action['switch_current_window'] = proc {
979
+ if current_scroll_window = TextWindow.list[0]
980
+ current_scroll_window.clear_scrollbar
981
+ end
982
+ TextWindow.list.push(TextWindow.list.shift)
983
+ if current_scroll_window = TextWindow.list[0]
984
+ current_scroll_window.update_scrollbar
985
+ end
986
+ command_window.noutrefresh
987
+ Curses.doupdate
988
+ }
989
+
990
+ key_action['scroll_current_window_up_one'] = proc {
991
+ if current_scroll_window = TextWindow.list[0]
992
+ current_scroll_window.scroll(-1)
993
+ end
994
+ command_window.noutrefresh
995
+ Curses.doupdate
996
+ }
997
+
998
+ key_action['scroll_current_window_down_one'] = proc {
999
+ if current_scroll_window = TextWindow.list[0]
1000
+ current_scroll_window.scroll(1)
1001
+ end
1002
+ command_window.noutrefresh
1003
+ Curses.doupdate
1004
+ }
1005
+
1006
+ key_action['scroll_current_window_up_page'] = proc {
1007
+ if current_scroll_window = TextWindow.list[0]
1008
+ current_scroll_window.scroll(0 - current_scroll_window.maxy + 1)
1009
+ end
1010
+ command_window.noutrefresh
1011
+ Curses.doupdate
1012
+ }
1013
+
1014
+ key_action['scroll_current_window_down_page'] = proc {
1015
+ if current_scroll_window = TextWindow.list[0]
1016
+ current_scroll_window.scroll(current_scroll_window.maxy - 1)
1017
+ end
1018
+ command_window.noutrefresh
1019
+ Curses.doupdate
1020
+ }
1021
+
1022
+ key_action['scroll_current_window_bottom'] = proc {
1023
+ if current_scroll_window = TextWindow.list[0]
1024
+ current_scroll_window.scroll(current_scroll_window.max_buffer_size)
1025
+ end
1026
+ command_window.noutrefresh
1027
+ Curses.doupdate
1028
+ }
1029
+
1030
+ key_action['previous_command'] = proc {
1031
+ if command_history_pos < (command_history.length - 1)
1032
+ command_history[command_history_pos] = command_buffer.dup
1033
+ command_history_pos += 1
1034
+ command_buffer = command_history[command_history_pos].dup
1035
+ command_buffer_offset = [ (command_buffer.length - command_window.maxx + 1), 0 ].max
1036
+ command_buffer_pos = command_buffer.length
1037
+ command_window.setpos(0, 0)
1038
+ command_window.deleteln
1039
+ command_window.addstr command_buffer[command_buffer_offset,(command_buffer.length - command_buffer_offset)]
1040
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
1041
+ command_window.noutrefresh
1042
+ Curses.doupdate
1043
+ end
1044
+ }
1045
+
1046
+ key_action['next_command'] = proc {
1047
+ if command_history_pos == 0
1048
+ unless command_buffer.empty?
1049
+ command_history[command_history_pos] = command_buffer.dup
1050
+ command_history.unshift String.new
1051
+ command_buffer.clear
1052
+ command_window.deleteln
1053
+ command_buffer_pos = 0
1054
+ command_buffer_offset = 0
1055
+ command_window.setpos(0,0)
1056
+ command_window.noutrefresh
1057
+ Curses.doupdate
1058
+ end
1059
+ else
1060
+ command_history[command_history_pos] = command_buffer.dup
1061
+ command_history_pos -= 1
1062
+ command_buffer = command_history[command_history_pos].dup
1063
+ command_buffer_offset = [ (command_buffer.length - command_window.maxx + 1), 0 ].max
1064
+ command_buffer_pos = command_buffer.length
1065
+ command_window.setpos(0, 0)
1066
+ command_window.deleteln
1067
+ command_window.addstr command_buffer[command_buffer_offset,(command_buffer.length - command_buffer_offset)]
1068
+ command_window.setpos(0, command_buffer_pos - command_buffer_offset)
1069
+ command_window.noutrefresh
1070
+ Curses.doupdate
1071
+ end
1072
+ }
1073
+
1074
+ key_action['switch_arrow_mode'] = proc {
1075
+ if key_binding[Curses::KEY_UP] == key_action['previous_command']
1076
+ key_binding[Curses::KEY_UP] = key_action['scroll_current_window_up_page']
1077
+ key_binding[Curses::KEY_DOWN] = key_action['scroll_current_window_down_page']
1078
+ else
1079
+ key_binding[Curses::KEY_UP] = key_action['previous_command']
1080
+ key_binding[Curses::KEY_DOWN] = key_action['next_command']
1081
+ end
1082
+ }
1083
+
1084
+ key_action['send_command'] = proc {
1085
+ cmd = command_buffer.dup
1086
+ command_buffer.clear
1087
+ command_buffer_pos = 0
1088
+ command_buffer_offset = 0
1089
+ need_prompt = false
1090
+ if window = stream_handler['main']
1091
+ add_prompt(window, prompt_text, cmd)
1092
+ end
1093
+ command_window.deleteln
1094
+ command_window.setpos(0,0)
1095
+ command_window.noutrefresh
1096
+ Curses.doupdate
1097
+ command_history_pos = 0
1098
+ # Remember all digit commands because they are likely spells for voodoo.lic
1099
+ if (cmd.length >= min_cmd_length_for_history || cmd.digits?) and (cmd != command_history[1])
1100
+ if command_history[0].nil? or command_history[0].empty?
1101
+ command_history[0] = cmd
1102
+ else
1103
+ command_history.unshift cmd
1104
+ end
1105
+ command_history.unshift String.new
1106
+ end
1107
+ if cmd =~ /^\.quit/
1108
+ exit
1109
+ elsif cmd =~ /^\.key/i
1110
+ window = stream_handler['main']
1111
+ window.add_string("* ")
1112
+ window.add_string("* Waiting for key press...")
1113
+ command_window.noutrefresh
1114
+ Curses.doupdate
1115
+ window.add_string("* Detected keycode: #{command_window.getch.to_s}")
1116
+ window.add_string("* ")
1117
+ Curses.doupdate
1118
+ elsif cmd =~ /^\.copy/
1119
+ # fixme
1120
+ elsif cmd =~ /^\.fixcolor/i
1121
+ if CUSTOM_COLORS
1122
+ COLOR_ID_LOOKUP.each { |code,id|
1123
+ Curses.init_color(id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round)
1124
+ }
1125
+ end
1126
+ elsif cmd =~ /^\.resync/i
1127
+ skip_server_time_offset = false
1128
+ elsif cmd =~ /^\.reload/i
1129
+ load_settings_file.call(true)
1130
+ elsif cmd =~ /^\.layout\s+(.+)/
1131
+ load_layout.call($1)
1132
+ key_action['resize'].call
1133
+ elsif cmd =~ /^\.arrow/i
1134
+ key_action['switch_arrow_mode'].call
1135
+ elsif cmd =~ /^\.e (.*)/
1136
+ eval(cmd.sub(/^\.e /, ''))
1137
+ else
1138
+ server.puts cmd.sub(/^\./, ';')
1139
+ end
1140
+ }
1141
+
1142
+ key_action['send_last_command'] = proc {
1143
+ if cmd = command_history[1]
1144
+ if window = stream_handler['main']
1145
+ add_prompt(window, prompt_text, cmd)
1146
+ #window.add_string(">#{cmd}", [ h={ :start => 0, :end => (cmd.length + 1), :fg => '555555' } ])
1147
+ command_window.noutrefresh
1148
+ Curses.doupdate
1149
+ end
1150
+ if cmd =~ /^\.quit/i
1151
+ exit
1152
+ elsif cmd =~ /^\.fixcolor/i
1153
+ if CUSTOM_COLORS
1154
+ COLOR_ID_LOOKUP.each { |code,id|
1155
+ Curses.init_color(id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round)
1156
+ }
1157
+ end
1158
+ elsif cmd =~ /^\.resync/i
1159
+ skip_server_time_offset = false
1160
+ elsif cmd =~ /^\.arrow/i
1161
+ key_action['switch_arrow_mode'].call
1162
+ elsif cmd =~ /^\.e (.*)/
1163
+ eval(cmd.sub(/^\.e /, ''))
1164
+ else
1165
+ server.puts cmd.sub(/^\./, ';')
1166
+ end
1167
+ end
1168
+ }
1169
+
1170
+ key_action['send_second_last_command'] = proc {
1171
+ if cmd = command_history[2]
1172
+ if window = stream_handler['main']
1173
+ add_prompt(window, prompt_text, cmd)
1174
+ #window.add_string(">#{cmd}", [ h={ :start => 0, :end => (cmd.length + 1), :fg => '555555' } ])
1175
+ command_window.noutrefresh
1176
+ Curses.doupdate
1177
+ end
1178
+ if cmd =~ /^\.quit/i
1179
+ exit
1180
+ elsif cmd =~ /^\.fixcolor/i
1181
+ if CUSTOM_COLORS
1182
+ COLOR_ID_LOOKUP.each { |code,id|
1183
+ Curses.init_color(id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round)
1184
+ }
1185
+ end
1186
+ elsif cmd =~ /^\.resync/i
1187
+ skip_server_time_offset = false
1188
+ elsif cmd =~ /^\.arrow/i
1189
+ key_action['switch_arrow_mode'].call
1190
+ elsif cmd =~ /^\.e (.*)/
1191
+ eval(cmd.sub(/^\.e /, ''))
1192
+ else
1193
+ server.puts cmd.sub(/^\./, ';')
1194
+ end
1195
+ end
1196
+ }
1197
+
1198
+ new_stun = proc { |seconds|
1199
+ if window = countdown_handler['stunned']
1200
+ temp_stun_end = Time.now.to_f - $server_time_offset.to_f + seconds.to_f
1201
+ window.end_time = temp_stun_end
1202
+ window.update
1203
+ need_update = true
1204
+ Thread.new {
1205
+ while (countdown_handler['stunned'].end_time == temp_stun_end) and (countdown_handler['stunned'].value > 0)
1206
+ sleep 0.15
1207
+ if countdown_handler['stunned'].update
1208
+ command_window.noutrefresh
1209
+ Curses.doupdate
1210
+ end
1211
+ end
1212
+ }
1213
+ end
1214
+ }
1215
+
1216
+ load_settings_file.call(false)
1217
+ load_layout.call('default')
1218
+
1219
+ TextWindow.list.each { |w| w.maxy.times { w.add_string "\n" } }
1220
+
1221
+ server = TCPSocket.open('127.0.0.1', PORT)
1222
+
1223
+ Thread.new { sleep 15; skip_server_time_offset = false }
1224
+
1225
+ Thread.new {
1226
+ begin
1227
+ line = nil
1228
+ need_update = false
1229
+ line_colors = Array.new
1230
+ open_monsterbold = Array.new
1231
+ open_preset = Array.new
1232
+ open_style = nil
1233
+ open_color = Array.new
1234
+ current_stream = nil
1235
+
1236
+ handle_game_text = proc { |text|
1237
+
1238
+ for escapable in xml_escape_list.keys
1239
+ search_pos = 0
1240
+ while (pos = text.index(escapable, search_pos))
1241
+ text = text.sub(escapable, xml_escape_list[escapable])
1242
+ line_colors.each { |h|
1243
+ h[:start] -= (escapable.length - 1) if h[:start] > pos
1244
+ h[:end] -= (escapable.length - 1) if h[:end] > pos
1245
+ }
1246
+ if open_style and (open_style[:start] > pos)
1247
+ open_style[:start] -= (escapable.length - 1)
1248
+ end
1249
+ end
1250
+ end
1251
+
1252
+ if text =~ /^\[.*?\]>/
1253
+ need_prompt = false
1254
+ elsif text =~ /^\s*You are stunned for ([0-9]+) rounds?/
1255
+ new_stun.call($1.to_i * 5)
1256
+ elsif text =~ /^Deep and resonating, you feel the chant that falls from your lips instill within you with the strength of your faith\. You crouch beside [A-Z][a-z]+ and gently lift (?:he|she|him|her) into your arms, your muscles swelling with the power of your deity, and cradle (?:him|her) close to your chest\. Strength and life momentarily seep from your limbs, causing them to feel laden and heavy, and you are overcome with a sudden weakness\. With a sigh, you are able to lay [A-Z][a-z]+ back down\.$|^Moisture beads upon your skin and you feel your eyes cloud over with the darkness of a rising storm\. Power builds upon the air and when you utter the last syllable of your spell thunder rumbles from your lips\. The sound ripples upon the air, and colling with [A-Z][a-z&apos;]+ prone form and a brilliant flash transfers the spiritual energy between you\.$|^Lifting your finger, you begin to chant and draw a series of conjoined circles in the air\. Each circle turns to mist and takes on a different hue - white, blue, black, red, and green\. As the last ring is completed, you spread your fingers and gently allow your tips to touch each color before pushing the misty creation towards [A-Z][a-z]+\. A shock of energy courses through your body as the mist seeps into [A-Z][a-z&apos;]+ chest and life is slowly returned to (?:his|her) body\.$|^Crouching beside the prone form of [A-Z][a-z]+, you softly issue the last syllable of your chant\. Breathing deeply, you take in the scents around you and let the feel of your surroundings infuse you\. With only your gaze, you track the area and recreate the circumstances of [A-Z][a-z&apos;]+ within your mind\. Touching [A-Z][a-z]+, you follow the lines of the web that holds (?:his|her) soul in place and force it back into (?:his|her) body\. Raw energy courses through you and you feel your sense of justice and vengeance filling [A-Z][a-z]+ with life\.$|^Murmuring softly, you call upon your connection with the Destroyer,? and feel your words twist into an alien, spidery chant\. Dark shadows laced with crimson swirl before your eyes and at your forceful command sink into the chest of [A-Z][a-z]+\. The transference of energy is swift and immediate as you bind [A-Z][a-z]+ back into (?:his|her) body\.$|^Rich and lively, the scent of wild flowers suddenly fills the air as you finish your chant, and you feel alive with the energy of spring\. With renewal at your fingertips, you gently touch [A-Z][a-z]+ on the brow and revel in the sweet rush of energy that passes through you into (?:him|her|his)\.$|^Breathing slowly, you extend your senses towards the world around you and draw into you the very essence of nature\. You shift your gaze towards [A-z][a-z]+ and carefully release the energy you&apos;ve drawn into yourself towards (?:him|her)\. A rush of energy briefly flows between the two of you as you feel life slowly return to (?:him|her)\.$|^Your surroundings grow dim\.\.\.you lapse into a state of awareness only, unable to do anything\.\.\.$|^Murmuring softly, a mournful chant slips from your lips and you feel welts appear upon your wrists\. Dipping them briefly, you smear the crimson liquid the leaks from these sudden wounds in a thin line down [A-Z][a-z&apos;]+ face\. Tingling with each second that your skin touches (?:his|hers), you feel the transference of your raw energy pass into [A-Z][a-z]+ and momentarily reel with the pain of its release\. Slowly, the wounds on your wrists heal, though a lingering throb remains\.$|^Emptying all breathe from your body, you slowly still yourself and close your eyes\. You reach out with all of your senses and feel a film shift across your vision\. Opening your eyes, you gaze through a white haze and find images of [A-Z][a-z]+ floating above his prone form\. Acts of [A-Z][a-z]&apos;s? past, present, and future play out before your clouded vision\. With conviction and faith, you pluck a future image of [A-Z][a-z]+ from the air and coax (?:he|she|his|her) back into (?:he|she|his|her) body\. Slowly, the film slips from your eyes and images fade away\.$|^Thin at first, a fine layer of rime tickles your hands and fingertips\. The hoarfrost smoothly glides between you and [A-Z][a-z]+, turning to a light powder as it traverses the space\. The white substance clings to [A-Z][a-z]+&apos;s? eyelashes and cheeks for a moment before it becomes charged with spiritual power, then it slowly melts away\.$|^As you begin to chant,? you notice the scent of dry, dusty parchment and feel a cool mist cling to your skin somewhere near your feet\. You sense the ethereal tendrils of the mist as they coil about your body and notice that the world turns to a yellowish hue as the mist settles about your head\. Focusing on [A-Z][a-z]+, you feel the transfer of energy pass between you as you return (?:him|her) to life\.$|^Wrapped in an aura of chill, you close your eyes and softly begin to chant\. As the cold air that surrounds you condenses you feel it slowly ripple outward in waves that turn the breath of those nearby into a fine mist\. This mist swiftly moves to encompass you and you feel a pair of wings arc over your back\. With the last words of your chant, you open your eyes and watch as foggy wings rise above you and gently brush against [A-Z][a-z]+\. As they dissipate in a cold rush against [A-Z][a-z]+, you feel a surge of power spill forth from you and into (?:him|her)\.$|^As .*? begins to chant, your spirit is drawn closer to your body by the scent of dusty, dry parchment\. Topaz tendrils coil about .*?, and you feel an ancient presence demand that you return to your body\. All at once .*? focuses upon you and you feel a surge of energy bind you back into your now-living body\.$/
1257
+ # raise dead stun
1258
+ new_stun.call(30.6)
1259
+ elsif text =~ /^Just as you think the falling will never end, you crash through an ethereal barrier which bursts into a dazzling kaleidoscope of color! Your sensation of falling turns to dizziness and you feel unusually heavy for a moment\. Everything seems to stop for a prolonged second and then WHUMP!!!/
1260
+ # Shadow Valley exit stun
1261
+ new_stun.call(16.2)
1262
+ elsif text =~ /^You have.*?(?:case of uncontrollable convulsions|case of sporadic convulsions|strange case of muscle twitching)/
1263
+ # nsys wound will be correctly set by xml, dont set the scar using health verb output
1264
+ skip_nsys = true
1265
+ else
1266
+ if skip_nsys
1267
+ skip_nsys = false
1268
+ elsif window = indicator_handler['nsys']
1269
+ if text =~ /^You have.*? very difficult time with muscle control/
1270
+ if window.update(3)
1271
+ need_update = true
1272
+ end
1273
+ elsif text =~ /^You have.*? constant muscle spasms/
1274
+ if window.update(2)
1275
+ need_update = true
1276
+ end
1277
+ elsif text =~ /^You have.*? developed slurred speech/
1278
+ if window.update(1)
1279
+ need_update = true
1280
+ end
1281
+ end
1282
+ end
1283
+ end
1284
+
1285
+ if open_style
1286
+ h = open_style.dup
1287
+ h[:end] = text.length
1288
+ line_colors.push(h)
1289
+ open_style[:start] = 0
1290
+ end
1291
+ for oc in open_color
1292
+ ocd = oc.dup
1293
+ ocd[:end] = text.length
1294
+ line_colors.push(ocd)
1295
+ oc[:start] = 0
1296
+ end
1297
+
1298
+ if current_stream.nil? or stream_handler[current_stream] or (current_stream =~ /^(?:death|logons|thoughts|voln|familiar|assess|ooc|atomospherics)$/)
1299
+ SETTINGS_LOCK.synchronize {
1300
+ HIGHLIGHT.each_pair { |regex,colors|
1301
+ pos = 0
1302
+ while (match_data = text.match(regex, pos))
1303
+ h = {
1304
+ :start => match_data.begin(0),
1305
+ :end => match_data.end(0),
1306
+ :fg => colors[0],
1307
+ :bg => colors[1],
1308
+ :ul => colors[2]
1309
+ }
1310
+ line_colors.push(h)
1311
+ pos = match_data.end(0)
1312
+ end
1313
+ }
1314
+ }
1315
+ end
1316
+
1317
+ unless text.empty?
1318
+ if current_stream
1319
+ if current_stream == 'thoughts'
1320
+ if text =~ /^\[.+?\]\-[A-z]+\:[A-Z][a-z]+\: "|^\[server\]\: /
1321
+ current_stream = 'lnet'
1322
+ end
1323
+ end
1324
+ if window = stream_handler[current_stream]
1325
+ if current_stream == 'death'
1326
+ # fixme: has been vaporized!
1327
+ # fixme: ~ off to a rough start
1328
+ if text =~ /^\s\*\s(The death cry of )?([A-Z][a-z]+) (?:just bit the dust!|echoes in your mind!)/
1329
+ front_count = 3
1330
+ front_count += 17 if $1
1331
+ name = $2
1332
+ text = "#{name} #{Time.now.strftime('%l:%M%P').sub(/^0/, '')}"
1333
+ line_colors.each { |h|
1334
+ h[:start] -= front_count
1335
+ h[:end] = [ h[:end], name.length ].min
1336
+ }
1337
+ line_colors.delete_if { |h| h[:start] >= h[:end] }
1338
+ h = {
1339
+ :start => (name.length+1),
1340
+ :end => text.length,
1341
+ :fg => 'ff0000',
1342
+ }
1343
+ line_colors.push(h)
1344
+ end
1345
+ elsif current_stream == 'logons'
1346
+ foo = { 'joins the adventure.' => '007700', 'returns home from a hard day of adventuring.' => '777700', 'has disconnected.' => 'aa7733' }
1347
+ if text =~ /^\s\*\s([A-Z][a-z]+) (#{foo.keys.join('|')})/
1348
+ name = $1
1349
+ logon_type = $2
1350
+ text = "#{name} #{Time.now.strftime('%l:%M%P').sub(/^0/, '')}"
1351
+ line_colors.each { |h|
1352
+ h[:start] -= 3
1353
+ h[:end] = [ h[:end], name.length ].min
1354
+ }
1355
+ line_colors.delete_if { |h| h[:start] >= h[:end] }
1356
+ h = {
1357
+ :start => (name.length+1),
1358
+ :end => text.length,
1359
+ :fg => foo[logon_type],
1360
+ }
1361
+ line_colors.push(h)
1362
+ end
1363
+ end
1364
+ unless text =~ /^\[server\]: "(?:kill|connect)/
1365
+ window.add_string(text, line_colors)
1366
+ need_update = true
1367
+ end
1368
+ elsif current_stream =~ /^(?:death|logons|thoughts|voln|familiar|assess|ooc|atmospherics)$/
1369
+ if window = stream_handler['main']
1370
+ if PRESET[current_stream]
1371
+ line_colors.push(:start => 0, :fg => PRESET[current_stream][0], :bg => PRESET[current_stream][1], :end => text.length)
1372
+ end
1373
+ unless text.empty?
1374
+ if need_prompt
1375
+ need_prompt = false
1376
+ add_prompt(window, prompt_text)
1377
+ end
1378
+ window.add_string(text, line_colors)
1379
+ need_update = true
1380
+ end
1381
+ end
1382
+ else
1383
+ # stream_handler['main'].add_string "#{current_stream}: #{text.inspect}"
1384
+ end
1385
+ else
1386
+ if window = stream_handler['main']
1387
+ if need_prompt
1388
+ need_prompt = false
1389
+ add_prompt(window, prompt_text)
1390
+ end
1391
+ window.add_string(text, line_colors)
1392
+ need_update = true
1393
+ end
1394
+ end
1395
+ end
1396
+ line_colors = Array.new
1397
+ open_monsterbold.clear
1398
+ open_preset.clear
1399
+ # open_color.clear
1400
+ }
1401
+
1402
+ while (line = server.gets)
1403
+ line.chomp!
1404
+ if line.empty?
1405
+ if current_stream.nil?
1406
+ if need_prompt
1407
+ need_prompt = false
1408
+ add_prompt(stream_handler['main'], prompt_text)
1409
+ end
1410
+ stream_handler['main'].add_string String.new
1411
+ need_update = true
1412
+ end
1413
+ else
1414
+ while (start_pos = (line =~ /(<(prompt|spell|right|left|inv|compass).*?\2>|<.*?>)/))
1415
+ xml = $1
1416
+ line.slice!(start_pos, xml.length)
1417
+ if xml =~ /^<prompt time=('|")([0-9]+)\1.*?>(.*?)&gt;<\/prompt>$/
1418
+ unless skip_server_time_offset
1419
+ $server_time_offset = Time.now.to_f - $2.to_f
1420
+ skip_server_time_offset = true
1421
+ end
1422
+ new_prompt_text = "#{$3}>"
1423
+ if prompt_text != new_prompt_text
1424
+ need_prompt = false
1425
+ prompt_text = new_prompt_text
1426
+ add_prompt(stream_handler['main'], new_prompt_text)
1427
+ if prompt_window = indicator_handler["prompt"]
1428
+ init_prompt_height, init_prompt_width = fix_layout_number.call(prompt_window.layout[0]), fix_layout_number.call(prompt_window.layout[1])
1429
+ new_prompt_width = new_prompt_text.length
1430
+ prompt_window.resize(init_prompt_height, new_prompt_width)
1431
+ prompt_width_diff = new_prompt_width - init_prompt_width
1432
+ command_window.resize(fix_layout_number.call(command_window_layout[0]), fix_layout_number.call(command_window_layout[1]) - prompt_width_diff)
1433
+ ctop, cleft = fix_layout_number.call(command_window_layout[2]), fix_layout_number.call(command_window_layout[3]) + prompt_width_diff
1434
+ command_window.move(ctop, cleft)
1435
+ prompt_window.label = new_prompt_text
1436
+ end
1437
+ else
1438
+ need_prompt = true
1439
+ end
1440
+ elsif xml =~ /^<spell(?:>|\s.*?>)(.*?)<\/spell>$/
1441
+ if window = indicator_handler['spell']
1442
+ window.clear
1443
+ window.label = $1
1444
+ window.update($1 == 'None' ? 0 : 1)
1445
+ need_update = true
1446
+ end
1447
+ elsif xml =~ /^<(right|left)(?:>|\s.*?>).*?(\S*?)<\/\1>/
1448
+ if window = indicator_handler[$1]
1449
+ window.clear
1450
+ window.label = $2
1451
+ window.update($2 == 'Empty' ? 0 : 1)
1452
+ need_update = true
1453
+ end
1454
+ elsif xml =~ /^<roundTime value=('|")([0-9]+)\1/
1455
+ if window = countdown_handler['roundtime']
1456
+ temp_roundtime_end = $2.to_i
1457
+ window.end_time = temp_roundtime_end
1458
+ window.update
1459
+ need_update = true
1460
+ Thread.new {
1461
+ sleep 0.15
1462
+ while (countdown_handler['roundtime'].end_time == temp_roundtime_end) and (countdown_handler['roundtime'].value > 0)
1463
+ sleep 0.15
1464
+ if countdown_handler['roundtime'].update
1465
+ command_window.noutrefresh
1466
+ Curses.doupdate
1467
+ end
1468
+ end
1469
+ }
1470
+ end
1471
+ elsif xml =~ /^<castTime value=('|")([0-9]+)\1/
1472
+ if window = countdown_handler['roundtime']
1473
+ temp_casttime_end = $2.to_i
1474
+ window.secondary_end_time = temp_casttime_end
1475
+ window.update
1476
+ need_update = true
1477
+ Thread.new {
1478
+ while (countdown_handler['roundtime'].secondary_end_time == temp_casttime_end) and (countdown_handler['roundtime'].secondary_value > 0)
1479
+ sleep 0.15
1480
+ if countdown_handler['roundtime'].update
1481
+ command_window.noutrefresh
1482
+ Curses.doupdate
1483
+ end
1484
+ end
1485
+ }
1486
+ end
1487
+ elsif xml =~ /^<compass/
1488
+ current_dirs = xml.scan(/<dir value="(.*?)"/).flatten
1489
+ for dir in [ 'up', 'down', 'out', 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw' ]
1490
+ if window = indicator_handler["compass:#{dir}"]
1491
+ if window.update(current_dirs.include?(dir))
1492
+ need_update = true
1493
+ end
1494
+ end
1495
+ end
1496
+ elsif xml =~ /^<progressBar id='(.*?)' value='[0-9]+' text='\1 (\-?[0-9]+)\/([0-9]+)'/
1497
+ if window = progress_handler[$1]
1498
+ if window.update($2.to_i, $3.to_i)
1499
+ need_update = true
1500
+ end
1501
+ end
1502
+ elsif xml =~ /^<progressBar id='encumlevel' value='([0-9]+)' text='(.*?)'/
1503
+ if window = progress_handler['encumbrance']
1504
+ if $2 == 'Overloaded'
1505
+ value = 110
1506
+ else
1507
+ value = $1.to_i
1508
+ end
1509
+ if window.update(value, 110)
1510
+ need_update = true
1511
+ end
1512
+ end
1513
+ elsif xml =~ /^<progressBar id='pbarStance' value='([0-9]+)'/
1514
+ if window = progress_handler['stance']
1515
+ if window.update($1.to_i, 100)
1516
+ need_update = true
1517
+ end
1518
+ end
1519
+ elsif xml =~ /^<progressBar id='mindState' value='(.*?)' text='(.*?)'/
1520
+ if window = progress_handler['mind']
1521
+ if $2 == 'saturated'
1522
+ value = 110
1523
+ else
1524
+ value = $1.to_i
1525
+ end
1526
+ if window.update(value, 110)
1527
+ need_update = true
1528
+ end
1529
+ end
1530
+ elsif xml == '<pushBold/>' or xml == '<b>'
1531
+ h = { :start => start_pos }
1532
+ if PRESET['monsterbold']
1533
+ h[:fg] = PRESET['monsterbold'][0]
1534
+ h[:bg] = PRESET['monsterbold'][1]
1535
+ end
1536
+ open_monsterbold.push(h)
1537
+ elsif xml == '<popBold/>' or xml == '</b>'
1538
+ if h = open_monsterbold.pop
1539
+ h[:end] = start_pos
1540
+ line_colors.push(h) if h[:fg] or h[:bg]
1541
+ end
1542
+ elsif xml =~ /^<preset id=('|")(.*?)\1>$/
1543
+ h = { :start => start_pos }
1544
+ if PRESET[$2]
1545
+ h[:fg] = PRESET[$2][0]
1546
+ h[:bg] = PRESET[$2][1]
1547
+ end
1548
+ open_preset.push(h)
1549
+ elsif xml == '</preset>'
1550
+ if h = open_preset.pop
1551
+ h[:end] = start_pos
1552
+ line_colors.push(h) if h[:fg] or h[:bg]
1553
+ end
1554
+ elsif xml =~ /^<color/
1555
+ h = { :start => start_pos }
1556
+ if xml =~ /\sfg=('|")(.*?)\1[\s>]/
1557
+ h[:fg] = $2.downcase
1558
+ end
1559
+ if xml =~ /\sbg=('|")(.*?)\1[\s>]/
1560
+ h[:bg] = $2.downcase
1561
+ end
1562
+ if xml =~ /\sul=('|")(.*?)\1[\s>]/
1563
+ h[:ul] = $2.downcase
1564
+ end
1565
+ open_color.push(h)
1566
+ elsif xml == '</color>'
1567
+ if h = open_color.pop
1568
+ h[:end] = start_pos
1569
+ line_colors.push(h)
1570
+ end
1571
+ elsif xml =~ /^<style id=('|")(.*?)\1/
1572
+ if $2.empty?
1573
+ if open_style
1574
+ open_style[:end] = start_pos
1575
+ if (open_style[:start] < open_style[:end]) and (open_style[:fg] or open_style[:bg])
1576
+ line_colors.push(open_style)
1577
+ end
1578
+ open_style = nil
1579
+ end
1580
+ else
1581
+ open_style = { :start => start_pos }
1582
+ if PRESET[$2]
1583
+ open_style[:fg] = PRESET[$2][0]
1584
+ open_style[:bg] = PRESET[$2][1]
1585
+ end
1586
+ end
1587
+ elsif xml =~ /^<(?:pushStream|component) id=("|')(.*?)\1[^>]*\/?>$/
1588
+ new_stream = $2
1589
+ game_text = line.slice!(0, start_pos)
1590
+ handle_game_text.call(game_text)
1591
+ current_stream = new_stream
1592
+ elsif xml =~ /^<popStream/ or xml == '</component>'
1593
+ game_text = line.slice!(0, start_pos)
1594
+ handle_game_text.call(game_text)
1595
+ current_stream = nil
1596
+ elsif xml =~ /^<progressBar/
1597
+ nil
1598
+ elsif xml =~ /^<(?:dialogdata|a|\/a|d|\/d|\/?component|label|skin|output)/
1599
+ nil
1600
+ elsif xml =~ /^<indicator id=('|")Icon([A-Z]+)\1 visible=('|")([yn])\3/
1601
+ if window = countdown_handler[$2.downcase]
1602
+ window.active = ($4 == 'y')
1603
+ if window.update
1604
+ need_update = true
1605
+ end
1606
+ end
1607
+ if window = indicator_handler[$2.downcase]
1608
+ if window.update($4 == 'y')
1609
+ need_update = true
1610
+ end
1611
+ end
1612
+ elsif xml =~ /^<image id=('|")(back|leftHand|rightHand|head|rightArm|abdomen|leftEye|leftArm|chest|rightLeg|neck|leftLeg|nsys|rightEye)\1 name=('|")(.*?)\3/
1613
+ if $2 == 'nsys'
1614
+ if window = indicator_handler['nsys']
1615
+ if rank = $4.slice(/[0-9]/)
1616
+ if window.update(rank.to_i)
1617
+ need_update = true
1618
+ end
1619
+ else
1620
+ if window.update(0)
1621
+ need_update = true
1622
+ end
1623
+ end
1624
+ end
1625
+ else
1626
+ fix_value = { 'Injury1' => 1, 'Injury2' => 2, 'Injury3' => 3, 'Scar1' => 4, 'Scar2' => 5, 'Scar3' => 6 }
1627
+ if window = indicator_handler[$2]
1628
+ if window.update(fix_value[$4] || 0)
1629
+ need_update = true
1630
+ end
1631
+ end
1632
+ end
1633
+ elsif xml =~ /^<LaunchURL src="([^"]+)"/
1634
+ url = "\"https://www.play.net#{$1}\""
1635
+ # assume linux if not mac
1636
+ cmd = RUBY_PLATFORM =~ /darwin/ ? "open" : "firefox"
1637
+ system("#{cmd} #{url}")
1638
+ else
1639
+ nil
1640
+ end
1641
+ end
1642
+ handle_game_text.call(line)
1643
+ end
1644
+ #
1645
+ # delay screen update if there are more game lines waiting
1646
+ #
1647
+ if need_update and not IO.select([server], nil, nil, 0.01)
1648
+ need_update = false
1649
+ command_window.noutrefresh
1650
+ Curses.doupdate
1651
+ end
1652
+ end
1653
+ stream_handler['main'].add_string ' *'
1654
+ stream_handler['main'].add_string ' * Connection closed'
1655
+ stream_handler['main'].add_string ' *'
1656
+ command_window.noutrefresh
1657
+ Curses.doupdate
1658
+ rescue
1659
+ File.open('profanity.log', 'a') { |f| f.puts $!; f.puts $!.backtrace[0...4] }
1660
+ exit
1661
+ end
1662
+ }
1663
+
1664
+ begin
1665
+ key_combo = nil
1666
+ loop {
1667
+ ch = command_window.getch
1668
+ if key_combo
1669
+ if key_combo[ch].class == Proc
1670
+ key_combo[ch].call
1671
+ key_combo = nil
1672
+ elsif key_combo[ch].class == Hash
1673
+ key_combo = key_combo[ch]
1674
+ else
1675
+ key_combo = nil
1676
+ end
1677
+ elsif key_binding[ch].class == Proc
1678
+ key_binding[ch].call
1679
+ elsif key_binding[ch].class == Hash
1680
+ key_combo = key_binding[ch]
1681
+ elsif ch.class == String
1682
+ command_window_put_ch.call(ch)
1683
+ command_window.noutrefresh
1684
+ Curses.doupdate
1685
+ end
1686
+ }
1687
+ rescue
1688
+ File.open('profanity.log', 'a') { |f| f.puts $!; f.puts $!.backtrace[0...4] }
1689
+ ensure
1690
+ server.close rescue()
1691
+ Curses.close_screen
1692
+ end