User:BELATHUS/Front End: Difference between revisions

The official GemStone IV encyclopedia.
< User:BELATHUS
Jump to navigation Jump to search
m (This... really is nothing.)
 
(Current source code for the project.)
Line 1: Line 1:
This article contains the current source code for my project. Keep in mind that while this program runs, it is almost utterly devoid of features and isn't finished in any way. There is no scripting support, no highlighting, nothing.
I get to brag about my project here. Though, it is nothing special, yet. Stay tuned!


<pre>
#!/usr/bin/env ruby
# This script is written by Andrew Sage.

require 'gtk2'
require 'socket'

# Settings!
$localecho = true

# Text tags, for the TagTables.
$tag = Hash.new
$tag['monbold'] = Gtk::TextTag.new # Monsterbold.

$_TAGHASH_ = Hash.new

# This class is the login screen window.
class LOGIN_WINDOW # Creates the login window.
def initialize()
# Starting the connection here. Then, displaying the login window.
@window = Gtk::Window.new # Creates the login window.
@window.title = "GemStone IV Login"
@window.signal_connect("delete_event") { Gtk.main_quit; false }

# This window will only use a vertical box. Four elements.
@mainbox = Gtk::VBox.new(false, 0)
# I suppose creating a menu bar for this window is pointless... so I won't.
# The following line is all the major objects in the login window.
accountframe(); passwordframe(); buttons(); characterlist(); statusbar()
#Packing the frames into the main box.
@mainbox.pack_start(@account_frame, true, true, 0)
@mainbox.pack_start(@password_frame, true, true, 0)
@mainbox.pack_start(@character_list, true, true, 0)
@mainbox.pack_start(@character_button, false, false, 0)
@mainbox.pack_start(@login_button, false, false, 0)
@mainbox.pack_start(@status_bar, false, false, 0)
@window.add(@mainbox)
@window.show_all
@character_button.visible = false # I have no idea why this doesn't work before the windows are shown.
end

def accountframe()
@account_frame = Gtk::Frame.new(nil)
@account_label = Gtk::Label.new
@account_label.set_markup("<span foreground='#003300' weight='bold'>Account Name</span>")
@account_frame.label_widget = @account_label
@account_frame.shadow_type = Gtk::SHADOW_NONE # Hides a border.
@account_entry = Gtk::ComboBoxEntry.new(true)
@account_frame.add(@account_entry)
end

def passwordframe()
@password_frame = Gtk::Frame.new(nil)
@password_frame.shadow_type = Gtk::SHADOW_NONE # Hides a border.
@password_label = Gtk::Label.new
@password_label.set_markup("<span foreground='#003300' weight='bold'>Password</span>")
@password_frame.label_widget = @password_label
@password_entry = Gtk::Entry.new()
@password_entry.visibility = false #This makes the frame display the "Password" character.
@password_frame.add(@password_entry)
end

#Defines the buttons and sets up their signals when pressed.
def buttons()
@login_button = Gtk::Button.new("_Login", true)
@character_button = Gtk::Button.new("_Choose", true)
@login_button.signal_connect("clicked") do
if @characters = login(@account_entry.child.text, @password_entry.text)
# This removes the account and password frames and displays the character list.
@password_frame.visible = false
@account_frame.visible = false
@character_list.visible = true
@login_button.visible = false
@character_button.visible = true
@character_hash = Hash.new
@characters.each_index do |n| @characters[n].strip! end
unless @characters[6] == nil
@character_hash["#{@characters[6]}"] = Array.new
@character_hash["#{@characters[6]}"][0] = @characters[6]
@character_hash["#{@characters[6]}"][1] = @characters[5]
object = @character_list_model.append(); object[0] = @characters[6]
n = 8; until @characters[n] == nil
@character_hash["#{@characters[n]}"] = Array.new
@character_hash["#{@characters[n]}"][0] = @characters[n]
@character_hash["#{@characters[n]}"][1] = @characters[n - 1]
object = @character_list_model.append(); object[0] = @characters[n]
n += 2
end
@character_list.append_column(Gtk::TreeViewColumn.new("Name", Gtk::CellRendererText.new, :text => 0))
#@character_list.append_column(Gtk::TreeViewColumn.new("Info", Gtk::CellRendererText.new, :text => 1))
else
@status_bar.push(0, "No characters!")
end
end
end
@character_button.signal_connect("clicked") do
#@window.hide; $main.show
selection = @character_list.selection
if selected = selection.selected then choose(selected[0]) else puts "Error!" end
end
end

def characterlist()
@character_list_model = Gtk::ListStore.new(String)
@character_list = Gtk::TreeView.new(@character_list_model)
@character_list.visible = false
end

def statusbar()
@status_bar = Gtk::Statusbar.new
end

def login(account, password)
$_SERVER_.puts("K")
hashkey = $_SERVER_.gets
password.length.times { |n| password[n] = ((password[n] - 32) ^ hashkey[n] + 32) }
$_SERVER_.puts("A\t#{account}\t#{password}")
check = $_SERVER_.gets
# If login is correct, break loop, else, redo the loop.
if check =~ /\tKEY\t/
$_SERVER_.puts "M"
servers = $_SERVER_.gets
#puts servers
$_SERVER_.puts "N\tGS3"
stormfront = $_SERVER_.gets
# This line tells which services are supported by the given connection.
if stormfront =~ /STORM/
$_SERVER_.puts "F\tGS3"
accountstatus = $_SERVER_.gets
#puts accountstatus
if accountstatus =~ /EXPIRED|NEW_TO_GAME|\?/
@status_bar.push(0, "Login Failed: Expired account")
return false
else
$_SERVER_.puts "G\tGS3" # Gets website address and the like. Toss it.
$_SERVER_.gets
$_SERVER_.puts "P\tGS3" # Gets pricing information. Toss it.
$_SERVER_.gets
$_SERVER_.puts "C" # Sends information about the characters. I want to save this.
return $_SERVER_.gets.split("\t")
end
else
@status_bar.push(0, "Login Failed: Uh... GemStone IV doesn't support the StormFront protocol?")
return false
end
else
@status_bar.push(0, "Login Failed: Account or password incorrect")
return false
end
end

def choose(character)
puts @character_hash[character][1]
$_SERVER_.puts "L\t#{@character_hash[character][1]}\tSTORM"
if (logindata = $_SERVER_.gets) =~ /GAMEHOST/
#puts "Login data received."
@window.destroy
logindata = logindata.strip.split("\t")
gamehost = logindata[7].gsub(/GAMEHOST=/, '')
gameport = logindata[8].gsub(/GAMEPORT=/, '').to_i
gamekey = logindata[9].gsub(/KEY=/, '').strip
#puts "#{gamehost}, #{gameport}, #{gamekey}"
$_SERVER_.close
$_SERVER_ = TCPsocket.open(gamehost, gameport)
$main.new_thread # Starts the "get" loop as a new thread. A part of the main window.
$_SERVER_.puts(gamekey)
$_SERVER_.puts "<c>/FE:WIZARD /VERSION:1.0.1.22 /P:WIN_XP /XML"
sleep 1.5; $_SERVER_.puts "<c>"; sleep 1.5; $_SERVER_.puts "<c>_STATE CHATMODE OFF"
else
puts "Apparantly, this is something else..."
puts logindata
end
end
end

# This particular class is the main game window.
class MAIN_WINDOW # Creates a window with an entry bar.
def initialize()
@window = Gtk::Window.new # Creates the main window.
@window.set_size_request(300, 150)
@window.title = "Story"
@window.signal_connect("delete_event") { send("quit"); false }

#mainbox is the primary vertical box in the main window.
@mainbox = Gtk::VBox.new(false, 0)

#story is the text window in the main window.
@story_scroll = Gtk::ScrolledWindow.new
@story_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS)
#Creating the "TextView" object to put inside the "ScrolledWindow" object.
@story = Gtk::TextView.new
@story.editable = false
@story.wrap_mode = Gtk::TextTag::WRAP_WORD
@story_scroll.add(@story)
@story_buffer = @story.buffer

#entry is the text entry bar under the story window in the main window.
@entry = Gtk::Entry.new
# This allows the program to pick up when the ENTER key is pressed in the "entry" bar.
@entry.signal_connect("activate") do
send(@entry.text)
@entry.text = ""
end
@statusbox = Gtk::HBox.new(false, 0) # This will contain the four bars at the bottom (health, stamina, etc.)
@healthbar = Gtk::ProgressBar.new
@healthbar.fraction = 1.0; @healthbar.text = "Health"
redbar = Gtk::Style.new
redbar.set_bg(Gtk::STATE_PRELIGHT, 0xFFFF, 0x0000, 0x0000)
@healthbar.set_style(redbar)
@manabar = Gtk::ProgressBar.new
@manabar.fraction = 1.0; @manabar.text = "Mana"
bluebar = Gtk::Style.new
bluebar.set_bg(Gtk::STATE_PRELIGHT, 0x000, 0x0000, 0xFFFF)
@manabar.set_style(bluebar)
@staminabar = Gtk::ProgressBar.new
@staminabar.fraction = 1.0; @staminabar.text = "Stamina"
yellowbar = Gtk::Style.new
yellowbar.set_bg(Gtk::STATE_PRELIGHT, 0xFFFF, 0xFFFF, 0x0000)
@staminabar.set_style(yellowbar)
@spiritbar = Gtk::ProgressBar.new
@spiritbar.fraction = 1.0; @spiritbar.text = "Spirit"
greybar = Gtk::Style.new
greybar.set_bg(Gtk::STATE_PRELIGHT, 0x9999, 0x9999, 0x9999)
@spiritbar.set_style(greybar)
@statusbox.pack_start(@healthbar, true, true, 0)
@statusbox.pack_start(@manabar, true, true, 0)
@statusbox.pack_start(@staminabar, true, true, 0)
@statusbox.pack_start(@spiritbar, true, true, 0)

@mainbox.pack_start(@story_scroll, true, true, 0)
@mainbox.pack_start(@entry, false, false, 0)
@mainbox.pack_start(@statusbox, false, false, 0)

@window.add(@mainbox)
end
def show
@window.show_all
end

def send(command) # Sends a command to the server and, if $localecho is true, echo it to story window.
if command =~ /^quit/i
echo "Closed connection."
Gtk.main_quit
$_SERVER_.puts("<c>" + command)
$_SERVER_.close
elsif command =~ /^\.\w+/
puts "Scripting is not yet supported."
else
$_SERVER_.puts("<c>" + command)
end
if $localecho == true
scrollbar = @story_scroll.vadjustment
#puts "-------------------------"
#puts "Value: #{scrollbar.value}"
#puts "Page: #{scrollbar.page_size}"
#puts "Upper: #{scrollbar.upper}"
#puts "Combined: #{scrollbar.value + scrollbar.page_size}"
@story_buffer.insert(@story_buffer.end_iter, "#{command}\n") # insert text at end of buffer
scrollbar.value = scrollbar.upper #This puts the scrollbar at the bottom of the window.
end
end

def receive(line) # Echo text to story window and put it in the "most recent line" buffer.
# Should see about saving the number of lines and deleting them as the buffer gets too large.
line.strip!
# puts $suspend_stream #Used for debugging.
n = 0
streams = $stream.keys.join('|')
while true
puts "[[#{line}]]"
if n >= 10 then puts "Broke on over-looping"; puts line; break else n += 1 end
if line =~ /<streamWindow id=['"]main['"] title=["']Story['"] subtitle="([^"]+)"[^>]+>/
subtitle = $1; #puts "Changing main window name...#{subtitle}"
line.gsub!(/<streamWindow id=["']main['"] title=["']Story['"] subtitle=\"([^"]+)"[^>]+>/, '')
@window.title = "Story#{subtitle}"
redo
elsif line =~ /<streamWindow id='room' title='Room' subtitle="([^\"]+)"[^>]+>/
#puts "Changing room title."
$stream['room'].title($1)
line.gsub!(/<streamWindow id='room' title='Room' subtitle="[^\"]+"[^>]+>/, '')
redo
elsif line =~ /<compDef id='([^\']+)'>(.*?)<\/compDef>/
#puts "Caught component definition."
type = $1; descrip = $2; $stream['room'].component(type, descrip)
line.gsub!(/<compDef id='[^\']+'>.*?<\/compDef>/, '')
redo
elsif line =~ /<clear(?:Stream|Container) id=['"](\w+)['"]\/>/
stream = $1
$stream[stream].clearbuffer
line.gsub!(/<clear(?:Stream|Container) id=["']#{stream}['"]\/>/, '')
redo
## STREAMS ##
elsif line =~ /<pushStream id=["'](#{streams})['"]\/>/ or $suspend_stream =~ /#{streams}/i
if line =~ /<pushStream id=["'](#{streams})['"]\/>/
stream = $1
#puts "Handling stream: #{stream}"
linesplit = line.split(/<pushStream id=['"][^'"]+['"]\/>/)
handlestream(stream, linesplit[1])
line = linesplit[0]
redo
else
handlestream($suspend_stream, line)
end
## Handle containers ##
elsif line =~ /<exposeContainer id=['"](stow)["']\/>/
container = $1
puts "Exposed container: #{container}"
$stream[container].show
line.gsub!(/<exposeContainer id=['"][a-z]+["']\/>/, '')
redo
# Container streams
elsif line =~ /<container id='([^\']+)' title="([^\"]+)"/
puts "Container title update"
if line =~ /<container id=["'](#{streams})['"] title="([^\"]+)"[^\/]*\/>/
stream = $1; title = $2; puts "Title: #{title}; Stream: #{stream}"
$stream[stream].title(title)
line.gsub!(/<container id=['"][^'"]+['"] title="[^\"]+"[^\/]*\/>/, '')
redo
end
elsif line =~ /<inv id='(\w+)'>(.+?)<\/inv>/
stream = $1; text = $2
$stream[stream].receive(text)
line.gsub!(/<inv id='\w+'>.+?<\/inv>/, '')
redo
# Unknown streams
elsif line =~ /<pushStream id=['"]([^'"]+)['"]\/>/ or $suspend_stream !~ /main|blank/
if line =~ /<pushStream id=['"]([^'"]+)['"]\/>/
stream = $1
puts "#{stream} is undefined."
linesplit = line.split(/<pushStream id=['"][^'"]+['"]\/>/)
$suspend_stream = stream
puts linesplit[1] # This is to allow capturing of an undefined stream.
line = linesplit[0]
if linesplit[1] =~ /<popStream[^\/]+\/>/
line = linesplit[1].gsub!(/.*<popStream[^\/]+\/>/, '')
$suspend_stream = 'main'
redo
end
redo
else
stream = $suspend_stream
puts line
if line =~ /<popStream[^\/]+\/>/
line.gsub!(/.*<popStream[^\/]+\/>/, '')
$suspend_stream = 'main'
redo
end
end
## Handle the prompt ## Handling the prompt is somewhat unique.
elsif line =~ /<prompt time=['"]([0-9]+)['"]>([^&]*)&gt;<\/prompt>/
prompttime = $1; status = $2
$time_offset = Time.now.to_f - prompttime.to_f
#puts "Local: #{Time.now.to_i}, Remote: #{prompttime.to_i}, Time offset: #{$time_offset}"
line.gsub!(/<prompt time=['"][0-9]+['"]>[^&]*&gt;<\/prompt>/, "#{status}>")
redo
#Handle roundtime. This is what we save the $time_offset for.
elsif line =~ /<roundTime value=['"]([0-9]+)['"]\/>/
roundtime = $1.to_f - Time.now.to_f + $time_offset
puts "Roundtime: #{roundtime} seconds"
Thread.new do
$_TAGHASH["roundtime"] = true
sleep roundtime
$_TAGHASH["roundtime"] = false
end
#get health
elsif line =~ /<progressBar id='health' value='([^']+)' text='([^']+)'[^>]+\/>/
value = $1.to_f / 100.0; text = $2
changehealth(value, text)
line.gsub!(/<progressBar id='health'[^>]+>/, '')
#puts line
redo
#get mana
elsif line =~ /<progressBar id='mana' value='([^']+)' text='([^']+)'[^>]+\/>/
value = $1.to_f / 100.0; text = $2
changemana(value, text)
line.gsub!(/<progressBar id='mana'[^>]+>/, '')
#puts line
redo
#get stamina
elsif line =~ /<progressBar id='stamina' value='([^']+)' text='([^']+)'[^>]+\/>/
value = $1.to_f / 100.0; text = $2
changestamina(value, text)
line.gsub!(/<progressBar id='stamina'[^>]+>/, '')
#puts line
redo
elsif line =~ /<progressBar id='spirit' value='([^']+)' text='([^']+)'[^>]+\/>/
value = $1.to_f / 100.0; text = $2
changespirit(value, text)
line.gsub!(/<progressBar id='spirit'[^>]+>/, '')
#puts line
redo
elsif line =~ /<dialogData id='minivitals'>.+<\/dialogData>/
line.gsub!(/<dialogData id='minivitals'>.+<\/dialogData>/, '')
redo
#<progressBar id='stamina' value='67' text='stamina 61/91' left='50%' customText='t' top='0%' width='25%' height='100%'/>
elsif line =~ /<mode id="GAME"\/>/
$suspend_stream = 'main'
elsif line =~ /<mode id="CMGR"\/>/
$suspend_stream = 'blank'
elsif line =~ /<popStream[^>]*\/>/i
$suspend_stream = 'main'
line.gsub!(/<popStream[^>]*\/>/i, '')
unless line =~ /^$/ then fecho(line) end
puts "Erroneous popStream."
# More special prompt handling. Eventually change the fecho to its own sort of echo.
elsif line =~ /^\w+>$/ then fecho(line)
elsif line =~ /^$/ then nil # If the line ends up blank, do nothing else.
else
fecho(line)
end
break
end
end

def handlestream(stream, line)
# puts "Stream handled: #{stream}"
$suspend_stream = stream
if line =~ /<popStream[^>]*\/>(.*)/
saveline = $1
line.gsub!(/<popStream[^>]*\/>.*/, '')
unless line =~ /^$/ then $stream[stream].receive(line) end
$suspend_stream = 'main'
receive(saveline)
else
$stream[stream].receive(line)
end
end

def fecho(line) # Force echo. Ignores the status of the $localecho variable.
scrollbar = @story_scroll.vadjustment
if scrollbar.upper <= scrollbar.value + scrollbar.page_size + 500
readjust = true
else
readjust = false
end
@story_buffer.insert(@story_buffer.end_iter, "#{line}\n") # inserts text at end of buffer
if readjust then scrollbar.value = scrollbar.upper end
end

def echo(line) # Simply echo text back to the story window, if $localecho is on.
if $localecho == true
fecho(line)
end
end

def changehealth(value, text)
@healthbar.fraction = value; @healthbar.text = text.capitalize
end

def changemana(value, text)
@manabar.fraction = value; @manabar.text = text.capitalize
end

def changestamina(value, text)
@staminabar.fraction = value; @staminabar.text = text.capitalize
end

def changespirit(value, text)
@spiritbar.fraction = value; @spiritbar.text = text.capitalize
end

def new_thread()
@window.show_all
$stream.each_key { |key| $stream[key].show }
$stream['room'].show
$suspend_stream = "blank"
receive("This is working.")
Thread.new do
until $_SERVER_.closed?
line = $_SERVER_.gets
receive(line)
#puts "Received a line."
end
puts "Connection closed."
end
Thread.new do
while true; line = gets; receive(line); end # This allows emulating a server message from the terminal.
end
end
end

class TEXT_WINDOW # Creates a window with only a TextView.
def initialize(title)
@window = Gtk::Window.new # Creates the main window.
@window.set_size_request(300, 150)
@window.title = title
@window.signal_connect("delete_event") { send("quit"); true }
#story is the text window in the main window.
@text_scroll = Gtk::ScrolledWindow.new
@text_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS)
#Creating the "TextView" object to put inside the "ScrolledWindow" object.
@text = Gtk::TextView.new
@text.editable = false
@text.wrap_mode = Gtk::TextTag::WRAP_WORD
@text_scroll.add(@text)
@text_buffer = @text.buffer
@window.add(@text_scroll)
end

def send(command) # Sends a command to the server and, if $localecho is true, echo it to story window.
if $localecho == true
scrollbar = @text_scroll.vadjustment
#puts "-------------------------"
#puts "Value: #{scrollbar.value}"
#puts "Page: #{scrollbar.page_size}"
#puts "Upper: #{scrollbar.upper}"
#puts "Combined: #{scrollbar.value + scrollbar.page_size}"
@text_buffer.insert(@text_buffer.end_iter, "Cannot close this, yet.\n")
scrollbar.value = scrollbar.upper #This puts the scrollbar at the bottom of the window.
end
end

def receive(line) # receive game data.
scrollbar = @text_scroll.vadjustment
unless line =~ /^$/ then @text_buffer.insert(@text_buffer.end_iter, "#{line}\n") end
scrollbar.value = scrollbar.upper #This puts the scrollbar at the bottom of the window.
end

def clearbuffer()
@text_buffer.text = ''
end

def show()
@window.show_all
end

def title(title = nil)
if title == nil then @window.title else @window.title = "Room#{title}" end
end
end

class ROOM_WINDOW # Creates a window with only a TextView.
def initialize()
@window = Gtk::Window.new # Creates the main window.
@window.set_size_request(300, 75)
@window.title = "Room"
@window.signal_connect("delete_event") { true }
@descrip = Gtk::TextView.new
@descrip.editable = false
@descrip.wrap_mode = Gtk::TextTag::WRAP_WORD
@objects = Gtk::TextView.new
@objects.editable = false
@objects.wrap_mode = Gtk::TextTag::WRAP_WORD
@pcs = Gtk::TextView.new
@pcs.editable = false
@pcs.wrap_mode = Gtk::TextTag::WRAP_WORD
@compass = Gtk::TextView.new
@compass.editable = false
@compass.wrap_mode = Gtk::TextTag::WRAP_WORD
@roombox = Gtk::VBox.new
@roombox.pack_start(@descrip, false, false, 0)
@roombox.pack_start(@objects, false, false, 0)
@roombox.pack_start(@pcs, false, false, 0)
@roombox.pack_start(@compass, true, true, 0)
@descrip_buffer = @descrip.buffer
@objects_buffer = @objects.buffer
@pcs_buffer = @pcs.buffer
@compass_buffer = @compass.buffer
@window.add(@roombox)
end

def show()
@window.show_all
end

def title(title = nil)
if title == nil then @window.title else @window.title = "Room#{title}" end
end
def receive(line)
puts "The room window received a line. #{line}"
if line =~ /<popStream[^>]*\/>/ then $suspend_stream = 'main' end
end
def clearbuffer()
puts "The room window should be cleared."
end
def send(line)
#puts "The room window was sent a line. #{line}"
end
def component(type, descrip)
#puts "#{type}: #{descrip}"
case type
when /room desc/ then descrip(descrip)
when /room objs/ then objects(descrip)
when /room players/ then pcs(descrip)
when /room exits/ then compass(descrip)
when /sprite/ then puts "Sprite?"
else puts "Unknown component definition."
end
end
def descrip(text)
@descrip_buffer.text = text
end
def objects(text)
@descrip_buffer.text = text
end
def pcs(text)
@pcs_buffer.text = text
end
def compass(text)
@compass_buffer.text = text
end
def clearbuffer()
nil
end
end

# login windows
$login = LOGIN_WINDOW.new

# after login windows
$main = MAIN_WINDOW.new # Main story window.
$stream = Hash.new # Stream windows.
$stream['death'] = TEXT_WINDOW.new("Deaths")
$stream['spellfront'] = TEXT_WINDOW.new("Active Spells")
$stream['bounty'] = TEXT_WINDOW.new("Bounty")
$stream['inv'] = TEXT_WINDOW.new("Inventory")
$stream['logons'] = TEXT_WINDOW.new("Logons")
$stream['room'] = ROOM_WINDOW.new
$stream['stow'] = TEXT_WINDOW.new("Untitled")
# With the way Gtk works with multiple threads, I need to predefine windows in the main thread.
# If a window is defined in a secondary thread, the script hangs. However, windows can be exposed
# in a secondary thread. Thus, windows that will be 'created' in the secondary thread need to be
# predefined.
#$stream['cont1'] = TEXT_WINDOW.new("Untitled") # Not yet used.

$_SERVER_ = TCPsocket.open('eaccess.play.net', 7900)
Gtk.main
</pre>

Revision as of 22:14, 1 June 2007

This article contains the current source code for my project. Keep in mind that while this program runs, it is almost utterly devoid of features and isn't finished in any way. There is no scripting support, no highlighting, nothing.


#!/usr/bin/env ruby
# This script is written by Andrew Sage.

require 'gtk2'
require 'socket'

# Settings!
$localecho = true

# Text tags, for the TagTables.
$tag = Hash.new
$tag['monbold'] = Gtk::TextTag.new # Monsterbold.

$_TAGHASH_ = Hash.new

# This class is the login screen window.
class LOGIN_WINDOW # Creates the login window.
	def initialize()
		# Starting the connection here.  Then, displaying the login window.
		@window = Gtk::Window.new # Creates the login window.
		@window.title = "GemStone IV Login"
		@window.signal_connect("delete_event") { Gtk.main_quit; false }

		# This window will only use a vertical box.  Four elements.
		@mainbox = Gtk::VBox.new(false, 0)
		# I suppose creating a menu bar for this window is pointless... so I won't.
		# The following line is all the major objects in the login window.
		accountframe(); passwordframe(); buttons(); characterlist(); statusbar()
		#Packing the frames into the main box.
		@mainbox.pack_start(@account_frame, true, true, 0)
		@mainbox.pack_start(@password_frame, true, true, 0)
		@mainbox.pack_start(@character_list, true, true, 0)
		@mainbox.pack_start(@character_button, false, false, 0)
		@mainbox.pack_start(@login_button, false, false, 0)
		@mainbox.pack_start(@status_bar, false, false, 0)
		@window.add(@mainbox)
		@window.show_all
		@character_button.visible = false # I have no idea why this doesn't work before the windows are shown.
	end

	def accountframe()
		@account_frame = Gtk::Frame.new(nil)
		@account_label = Gtk::Label.new
		@account_label.set_markup("<span foreground='#003300' weight='bold'>Account Name</span>")
		@account_frame.label_widget = @account_label
		@account_frame.shadow_type = Gtk::SHADOW_NONE # Hides a border.
		@account_entry = Gtk::ComboBoxEntry.new(true)
		@account_frame.add(@account_entry)
	end

	def passwordframe()
		@password_frame = Gtk::Frame.new(nil)
		@password_frame.shadow_type = Gtk::SHADOW_NONE # Hides a border.
		@password_label = Gtk::Label.new
		@password_label.set_markup("<span foreground='#003300' weight='bold'>Password</span>")
		@password_frame.label_widget = @password_label
		@password_entry = Gtk::Entry.new()
		@password_entry.visibility = false #This makes the frame display the "Password" character.
		@password_frame.add(@password_entry)
	end

	#Defines the buttons and sets up their signals when pressed.
	def buttons()
		@login_button = Gtk::Button.new("_Login", true)
		@character_button = Gtk::Button.new("_Choose", true)
		@login_button.signal_connect("clicked") do
			if @characters = login(@account_entry.child.text, @password_entry.text)
				# This removes the account and password frames and displays the character list.
				@password_frame.visible = false
				@account_frame.visible = false
				@character_list.visible = true
				@login_button.visible = false
				@character_button.visible = true
				@character_hash = Hash.new
				@characters.each_index do |n| @characters[n].strip! end
				unless @characters[6] == nil
					@character_hash["#{@characters[6]}"] = Array.new
					@character_hash["#{@characters[6]}"][0] = @characters[6]
					@character_hash["#{@characters[6]}"][1] = @characters[5]
					object = @character_list_model.append(); object[0] = @characters[6]
					n = 8; until @characters[n] == nil
						@character_hash["#{@characters[n]}"] = Array.new
						@character_hash["#{@characters[n]}"][0] = @characters[n]
						@character_hash["#{@characters[n]}"][1] = @characters[n - 1]
						object = @character_list_model.append(); object[0] = @characters[n]
						n += 2
					end
					@character_list.append_column(Gtk::TreeViewColumn.new("Name", Gtk::CellRendererText.new, :text => 0))
					#@character_list.append_column(Gtk::TreeViewColumn.new("Info", Gtk::CellRendererText.new, :text => 1))
				else
					@status_bar.push(0, "No characters!")
				end
			end
		end
		@character_button.signal_connect("clicked") do
		    #@window.hide; $main.show
		    selection = @character_list.selection
		    if selected = selection.selected then choose(selected[0]) else puts "Error!" end
		end
	end

	def characterlist()
		@character_list_model = Gtk::ListStore.new(String)
		@character_list = Gtk::TreeView.new(@character_list_model)
		@character_list.visible = false
	end

	def statusbar()
		@status_bar = Gtk::Statusbar.new
	end

	def login(account, password)
		$_SERVER_.puts("K")
		hashkey = $_SERVER_.gets
		password.length.times { |n| password[n] = ((password[n] - 32) ^ hashkey[n] + 32) }
		$_SERVER_.puts("A\t#{account}\t#{password}")
		check = $_SERVER_.gets
		# If login is correct, break loop, else, redo the loop.
		if check =~ /\tKEY\t/
			$_SERVER_.puts "M"
			servers = $_SERVER_.gets
			#puts servers
			$_SERVER_.puts "N\tGS3"
			stormfront = $_SERVER_.gets
			# This line tells which services are supported by the given connection.
			if stormfront =~ /STORM/
				$_SERVER_.puts "F\tGS3"
				accountstatus = $_SERVER_.gets
				#puts accountstatus
				if accountstatus =~ /EXPIRED|NEW_TO_GAME|\?/
					@status_bar.push(0, "Login Failed: Expired account")
					return false
				else
					$_SERVER_.puts "G\tGS3" # Gets website address and the like.  Toss it.
					$_SERVER_.gets
					$_SERVER_.puts "P\tGS3" # Gets pricing information.  Toss it.
					$_SERVER_.gets
					$_SERVER_.puts "C" # Sends information about the characters.  I want to save this.
					return $_SERVER_.gets.split("\t")
				end
			else
				@status_bar.push(0, "Login Failed: Uh... GemStone IV doesn't support the StormFront protocol?")
				return false
			end
		else
			@status_bar.push(0, "Login Failed: Account or password incorrect")
			return false
		end
	end

	def choose(character)
		puts @character_hash[character][1]
		$_SERVER_.puts "L\t#{@character_hash[character][1]}\tSTORM"
		if (logindata = $_SERVER_.gets) =~ /GAMEHOST/
			#puts "Login data received."
			@window.destroy
			logindata = logindata.strip.split("\t")
			gamehost = logindata[7].gsub(/GAMEHOST=/, '')
			gameport = logindata[8].gsub(/GAMEPORT=/, '').to_i
			gamekey = logindata[9].gsub(/KEY=/, '').strip
			#puts "#{gamehost}, #{gameport}, #{gamekey}"
			$_SERVER_.close
			$_SERVER_ = TCPsocket.open(gamehost, gameport)
			$main.new_thread # Starts the "get" loop as a new thread.  A part of the main window.
			$_SERVER_.puts(gamekey)
			$_SERVER_.puts "<c>/FE:WIZARD /VERSION:1.0.1.22 /P:WIN_XP /XML"
			sleep 1.5; $_SERVER_.puts "<c>"; sleep 1.5; $_SERVER_.puts "<c>_STATE CHATMODE OFF"
		else
			puts "Apparantly, this is something else..."
			puts logindata
		end
	end
end

# This particular class is the main game window.
class MAIN_WINDOW # Creates a window with an entry bar.
	def initialize()
		@window = Gtk::Window.new # Creates the main window.
		@window.set_size_request(300, 150)
		@window.title = "Story"
		@window.signal_connect("delete_event") { send("quit"); false }

		#mainbox is the primary vertical box in the main window.
		@mainbox = Gtk::VBox.new(false, 0)

		#story is the text window in the main window.
		@story_scroll = Gtk::ScrolledWindow.new
		@story_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS)
		#Creating the "TextView" object to put inside the "ScrolledWindow" object.
		@story = Gtk::TextView.new
		@story.editable = false
		@story.wrap_mode = Gtk::TextTag::WRAP_WORD
		@story_scroll.add(@story)
		@story_buffer = @story.buffer

		#entry is the text entry bar under the story window in the main window.
		@entry = Gtk::Entry.new
		# This allows the program to pick up when the ENTER key is pressed in the "entry" bar.
		@entry.signal_connect("activate") do
			send(@entry.text)
			@entry.text = ""
		end
		@statusbox = Gtk::HBox.new(false, 0) # This will contain the four bars at the bottom (health, stamina, etc.)
		@healthbar = Gtk::ProgressBar.new
		@healthbar.fraction = 1.0; @healthbar.text = "Health"
		redbar = Gtk::Style.new
		redbar.set_bg(Gtk::STATE_PRELIGHT, 0xFFFF, 0x0000, 0x0000)
		@healthbar.set_style(redbar)
		@manabar = Gtk::ProgressBar.new
		@manabar.fraction = 1.0; @manabar.text = "Mana"
		bluebar = Gtk::Style.new
		bluebar.set_bg(Gtk::STATE_PRELIGHT, 0x000, 0x0000, 0xFFFF)
		@manabar.set_style(bluebar)
		@staminabar = Gtk::ProgressBar.new
		@staminabar.fraction = 1.0; @staminabar.text = "Stamina"
		yellowbar = Gtk::Style.new
		yellowbar.set_bg(Gtk::STATE_PRELIGHT, 0xFFFF, 0xFFFF, 0x0000)
		@staminabar.set_style(yellowbar)
		@spiritbar = Gtk::ProgressBar.new
		@spiritbar.fraction = 1.0; @spiritbar.text = "Spirit"
		greybar = Gtk::Style.new
		greybar.set_bg(Gtk::STATE_PRELIGHT, 0x9999, 0x9999, 0x9999)
		@spiritbar.set_style(greybar)
		@statusbox.pack_start(@healthbar, true, true, 0)
		@statusbox.pack_start(@manabar, true, true, 0)
		@statusbox.pack_start(@staminabar, true, true, 0)
		@statusbox.pack_start(@spiritbar, true, true, 0)    

		@mainbox.pack_start(@story_scroll, true, true, 0)
		@mainbox.pack_start(@entry, false, false, 0)
		@mainbox.pack_start(@statusbox, false, false, 0)

		@window.add(@mainbox)
	end
	def show
		@window.show_all
	end

	def send(command) # Sends a command to the server and, if $localecho is true, echo it to story window.
		if command =~ /^quit/i
			echo "Closed connection."
			Gtk.main_quit
			$_SERVER_.puts("<c>" + command)
			$_SERVER_.close
		elsif command =~ /^\.\w+/
			puts "Scripting is not yet supported."
		else
			$_SERVER_.puts("<c>" + command)
		end
		if $localecho == true
			scrollbar = @story_scroll.vadjustment
			#puts "-------------------------"
			#puts "Value: #{scrollbar.value}"
			#puts "Page: #{scrollbar.page_size}"
			#puts "Upper: #{scrollbar.upper}"
			#puts "Combined: #{scrollbar.value + scrollbar.page_size}"
			@story_buffer.insert(@story_buffer.end_iter, "#{command}\n") # insert text at end of buffer
			scrollbar.value = scrollbar.upper #This puts the scrollbar at the bottom of the window.
		end
	end

  def receive(line) # Echo text to story window and put it in the "most recent line" buffer.
    # Should see about saving the number of lines and deleting them as the buffer gets too large.
    line.strip!
    # puts $suspend_stream #Used for debugging.
    n = 0
    streams = $stream.keys.join('|')
    while true
       puts "[[#{line}]]"
      if n >= 10 then puts "Broke on over-looping"; puts line; break else n += 1 end
      if line =~ /<streamWindow id=['"]main['"] title=["']Story['"] subtitle="([^"]+)"[^>]+>/
        subtitle = $1; #puts "Changing main window name...#{subtitle}"
        line.gsub!(/<streamWindow id=["']main['"] title=["']Story['"] subtitle=\"([^"]+)"[^>]+>/, '')
        @window.title = "Story#{subtitle}"
        redo
      elsif line =~ /<streamWindow id='room' title='Room' subtitle="([^\"]+)"[^>]+>/
        #puts "Changing room title."
        $stream['room'].title($1)
        line.gsub!(/<streamWindow id='room' title='Room' subtitle="[^\"]+"[^>]+>/, '')
        redo
      elsif line =~ /<compDef id='([^\']+)'>(.*?)<\/compDef>/
        #puts "Caught component definition."
        type = $1; descrip = $2; $stream['room'].component(type, descrip)
        line.gsub!(/<compDef id='[^\']+'>.*?<\/compDef>/, '')
        redo
      elsif line =~ /<clear(?:Stream|Container) id=['"](\w+)['"]\/>/
        stream = $1
        $stream[stream].clearbuffer
        line.gsub!(/<clear(?:Stream|Container) id=["']#{stream}['"]\/>/, '')
        redo
      ## STREAMS ##
      elsif line =~ /<pushStream id=["'](#{streams})['"]\/>/ or $suspend_stream =~ /#{streams}/i
        if line =~ /<pushStream id=["'](#{streams})['"]\/>/
          stream = $1
          #puts "Handling stream: #{stream}"
          linesplit = line.split(/<pushStream id=['"][^'"]+['"]\/>/)
          handlestream(stream, linesplit[1])
          line = linesplit[0]
          redo
        else
          handlestream($suspend_stream, line)
        end
      ## Handle containers ##
      elsif line =~ /<exposeContainer id=['"](stow)["']\/>/
        container = $1
        puts "Exposed container: #{container}"
        $stream[container].show
        line.gsub!(/<exposeContainer id=['"][a-z]+["']\/>/, '')
        redo
      # Container streams
      elsif line =~ /<container id='([^\']+)' title="([^\"]+)"/
        puts "Container title update"
        if line =~ /<container id=["'](#{streams})['"] title="([^\"]+)"[^\/]*\/>/
          stream = $1; title = $2; puts "Title: #{title}; Stream: #{stream}"
          $stream[stream].title(title)
          line.gsub!(/<container id=['"][^'"]+['"] title="[^\"]+"[^\/]*\/>/, '')
          redo
        end
      elsif line =~ /<inv id='(\w+)'>(.+?)<\/inv>/
        stream = $1; text = $2
        $stream[stream].receive(text)
        line.gsub!(/<inv id='\w+'>.+?<\/inv>/, '')
        redo
      # Unknown streams
      elsif line =~ /<pushStream id=['"]([^'"]+)['"]\/>/ or $suspend_stream !~ /main|blank/
        if line =~ /<pushStream id=['"]([^'"]+)['"]\/>/
          stream = $1
          puts "#{stream} is undefined."
          linesplit = line.split(/<pushStream id=['"][^'"]+['"]\/>/)
          $suspend_stream = stream
          puts linesplit[1] # This is to allow capturing of an undefined stream.
          line = linesplit[0]
          if linesplit[1] =~ /<popStream[^\/]+\/>/
            line = linesplit[1].gsub!(/.*<popStream[^\/]+\/>/, '')
            $suspend_stream = 'main'
            redo
          end
          redo
        else
          stream = $suspend_stream
          puts line
          if line =~ /<popStream[^\/]+\/>/
            line.gsub!(/.*<popStream[^\/]+\/>/, '')
            $suspend_stream = 'main'
            redo
          end
        end
      ## Handle the prompt ##  Handling the prompt is somewhat unique.
      elsif line =~ /<prompt time=['"]([0-9]+)['"]>([^&]*)><\/prompt>/
        prompttime = $1; status = $2
        $time_offset = Time.now.to_f - prompttime.to_f
        #puts "Local: #{Time.now.to_i}, Remote: #{prompttime.to_i}, Time offset: #{$time_offset}"
        line.gsub!(/<prompt time=['"][0-9]+['"]>[^&]*><\/prompt>/, "#{status}>")
        redo
      #Handle roundtime.  This is what we save the $time_offset for.
      elsif line =~ /<roundTime value=['"]([0-9]+)['"]\/>/
        roundtime = $1.to_f - Time.now.to_f + $time_offset
        puts "Roundtime: #{roundtime} seconds"
        Thread.new do
          $_TAGHASH["roundtime"] = true
          sleep roundtime
          $_TAGHASH["roundtime"] = false
        end
      #get health
      elsif line =~ /<progressBar id='health' value='([^']+)' text='([^']+)'[^>]+\/>/
        value = $1.to_f / 100.0; text = $2
        changehealth(value, text)
        line.gsub!(/<progressBar id='health'[^>]+>/, '')
        #puts line
        redo
      #get mana
      elsif line =~ /<progressBar id='mana' value='([^']+)' text='([^']+)'[^>]+\/>/
        value = $1.to_f / 100.0; text = $2
        changemana(value, text)
        line.gsub!(/<progressBar id='mana'[^>]+>/, '')
        #puts line
        redo
      #get stamina
      elsif line =~ /<progressBar id='stamina' value='([^']+)' text='([^']+)'[^>]+\/>/
        value = $1.to_f / 100.0; text = $2
        changestamina(value, text)
        line.gsub!(/<progressBar id='stamina'[^>]+>/, '')
        #puts line
        redo
      elsif line =~ /<progressBar id='spirit' value='([^']+)' text='([^']+)'[^>]+\/>/
        value = $1.to_f / 100.0; text = $2
        changespirit(value, text)
        line.gsub!(/<progressBar id='spirit'[^>]+>/, '')
        #puts line
        redo
      elsif line =~ /<dialogData id='minivitals'>.+<\/dialogData>/
        line.gsub!(/<dialogData id='minivitals'>.+<\/dialogData>/, '')
        redo
#<progressBar id='stamina' value='67' text='stamina 61/91' left='50%' customText='t' top='0%' width='25%' height='100%'/>
      elsif line =~ /<mode id="GAME"\/>/
        $suspend_stream = 'main'
      elsif line =~ /<mode id="CMGR"\/>/
        $suspend_stream = 'blank'
      elsif line =~ /<popStream[^>]*\/>/i
        $suspend_stream = 'main'
        line.gsub!(/<popStream[^>]*\/>/i, '')
        unless line =~ /^$/ then fecho(line) end
        puts "Erroneous popStream."
      # More special prompt handling.  Eventually change the fecho to its own sort of echo.
      elsif line =~ /^\w+>$/ then fecho(line)
      elsif line =~ /^$/ then nil # If the line ends up blank, do nothing else.
      else
        fecho(line)
      end
      break
    end
  end

  def handlestream(stream, line)
    # puts "Stream handled: #{stream}"
    $suspend_stream = stream
    if line =~ /<popStream[^>]*\/>(.*)/
      saveline = $1
      line.gsub!(/<popStream[^>]*\/>.*/, '')
      unless line =~ /^$/ then $stream[stream].receive(line) end
      $suspend_stream = 'main'
      receive(saveline)
    else
      $stream[stream].receive(line)
    end
  end

  def fecho(line) # Force echo.  Ignores the status of the $localecho variable.
    scrollbar = @story_scroll.vadjustment
    if scrollbar.upper <= scrollbar.value + scrollbar.page_size + 500
      readjust = true 
    else
      readjust = false
    end
    @story_buffer.insert(@story_buffer.end_iter, "#{line}\n") # inserts text at end of buffer
    if readjust then scrollbar.value = scrollbar.upper end
  end

  def echo(line) # Simply echo text back to the story window, if $localecho is on.
    if $localecho == true
      fecho(line)
    end
  end

  def changehealth(value, text)
    @healthbar.fraction = value; @healthbar.text = text.capitalize
  end

  def changemana(value, text)
    @manabar.fraction = value; @manabar.text = text.capitalize
  end

  def changestamina(value, text)
    @staminabar.fraction = value; @staminabar.text = text.capitalize
  end

  def changespirit(value, text)
    @spiritbar.fraction = value; @spiritbar.text = text.capitalize
  end

  def new_thread()
    @window.show_all
    $stream.each_key { |key| $stream[key].show }
    $stream['room'].show
    $suspend_stream = "blank"
    receive("This is working.")
    Thread.new do
      until $_SERVER_.closed?
        line = $_SERVER_.gets
        receive(line)
        #puts "Received a line."
      end
      puts "Connection closed."
    end
    Thread.new do
      while true; line = gets; receive(line); end # This allows emulating a server message from the terminal.
    end
  end
end

class TEXT_WINDOW # Creates a window with only a TextView.
  def initialize(title)
    @window = Gtk::Window.new # Creates the main window.
    @window.set_size_request(300, 150)
    @window.title = title
    @window.signal_connect("delete_event") { send("quit"); true }
    #story is the text window in the main window.
    @text_scroll = Gtk::ScrolledWindow.new
    @text_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS)
    #Creating the "TextView" object to put inside the "ScrolledWindow" object.
    @text = Gtk::TextView.new
    @text.editable = false
    @text.wrap_mode = Gtk::TextTag::WRAP_WORD
    @text_scroll.add(@text)
    @text_buffer = @text.buffer
    @window.add(@text_scroll)
  end

  def send(command) # Sends a command to the server and, if $localecho is true, echo it to story window.
    if $localecho == true
      scrollbar = @text_scroll.vadjustment
      #puts "-------------------------"
      #puts "Value: #{scrollbar.value}"
      #puts "Page: #{scrollbar.page_size}"
      #puts "Upper: #{scrollbar.upper}"
      #puts "Combined: #{scrollbar.value + scrollbar.page_size}"
      @text_buffer.insert(@text_buffer.end_iter, "Cannot close this, yet.\n")
      scrollbar.value = scrollbar.upper #This puts the scrollbar at the bottom of the window.
    end
  end

  def receive(line) # receive game data.
    scrollbar = @text_scroll.vadjustment
    unless line =~ /^$/ then @text_buffer.insert(@text_buffer.end_iter, "#{line}\n") end
    scrollbar.value = scrollbar.upper #This puts the scrollbar at the bottom of the window.
  end

  def clearbuffer()
    @text_buffer.text = ''
  end

  def show()
    @window.show_all
  end

  def title(title = nil)
    if title == nil then @window.title else @window.title = "Room#{title}" end
  end
end

class ROOM_WINDOW # Creates a window with only a TextView.
  def initialize()
    @window = Gtk::Window.new # Creates the main window.
    @window.set_size_request(300, 75)
    @window.title = "Room"
    @window.signal_connect("delete_event") { true }
    @descrip = Gtk::TextView.new
    @descrip.editable = false
    @descrip.wrap_mode = Gtk::TextTag::WRAP_WORD
    @objects = Gtk::TextView.new
    @objects.editable = false
    @objects.wrap_mode = Gtk::TextTag::WRAP_WORD
    @pcs = Gtk::TextView.new
    @pcs.editable = false
    @pcs.wrap_mode = Gtk::TextTag::WRAP_WORD
    @compass = Gtk::TextView.new
    @compass.editable = false
    @compass.wrap_mode = Gtk::TextTag::WRAP_WORD
    @roombox = Gtk::VBox.new
    @roombox.pack_start(@descrip, false, false, 0)
    @roombox.pack_start(@objects, false, false, 0)
    @roombox.pack_start(@pcs, false, false, 0)
    @roombox.pack_start(@compass, true, true, 0)
    @descrip_buffer = @descrip.buffer
    @objects_buffer = @objects.buffer
    @pcs_buffer = @pcs.buffer
    @compass_buffer = @compass.buffer
    @window.add(@roombox)
  end

  def show()
    @window.show_all
  end

  def title(title = nil)
    if title == nil then @window.title else @window.title = "Room#{title}" end
  end
  def receive(line)
    puts "The room window received a line. #{line}"
    if line =~ /<popStream[^>]*\/>/ then $suspend_stream = 'main' end
  end
  def clearbuffer()
    puts "The room window should be cleared."
  end
  def send(line)
    #puts "The room window was sent a line. #{line}"
  end
  def component(type, descrip)
	#puts "#{type}: #{descrip}"
    case type
    when /room desc/ then descrip(descrip)
    when /room objs/ then objects(descrip)
    when /room players/ then pcs(descrip)
    when /room exits/ then compass(descrip)
    when /sprite/ then puts "Sprite?"
    else puts "Unknown component definition."
    end
  end
  def descrip(text)
    @descrip_buffer.text = text
  end
  def objects(text)
    @descrip_buffer.text = text
  end
  def pcs(text)
    @pcs_buffer.text = text
  end
  def compass(text)
    @compass_buffer.text = text
  end
  def clearbuffer()
    nil
  end
end

# login windows
$login = LOGIN_WINDOW.new

# after login windows
$main = MAIN_WINDOW.new # Main story window.
$stream = Hash.new # Stream windows.
$stream['death'] = TEXT_WINDOW.new("Deaths")
$stream['spellfront'] = TEXT_WINDOW.new("Active Spells")
$stream['bounty'] = TEXT_WINDOW.new("Bounty")
$stream['inv'] = TEXT_WINDOW.new("Inventory")
$stream['logons'] = TEXT_WINDOW.new("Logons")
$stream['room'] = ROOM_WINDOW.new
$stream['stow'] = TEXT_WINDOW.new("Untitled")
# With the way Gtk works with multiple threads, I need to predefine windows in the main thread.
# If a window is defined in a secondary thread, the script hangs.  However, windows can be exposed
# in a secondary thread.  Thus, windows that will be 'created' in the secondary thread need to be
# predefined.
#$stream['cont1'] = TEXT_WINDOW.new("Untitled") # Not yet used.

$_SERVER_ = TCPsocket.open('eaccess.play.net', 7900)
Gtk.main