User:BELATHUS/Front End: Difference between revisions
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]+)['"]>([^&]*)><\/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 |
|||
</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