#!/usr/bin/wish # show your vessels on a map # This is part of ypp-sc-tools, a set of third-party tools for assisting # players of Yohoho Puzzle Pirates. # # Copyright (C) 2009 Ian Jackson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Yohoho and Puzzle Pirates are probably trademarks of Three Rings and # are used without permission. This program is not endorsed or # sponsored by Three Rings. source yarrglib.tcl source panner.tcl package require http #---------- general utilities ---------- set debug 0 proc debug {m} { global debug if {$debug} { puts "DEBUG $m" } } proc badusage {m} { puts stderr "where-vessels: bad usage: $m" exit 1 } proc glset {n val} { upvar #0 $n var set var $val } #---------- expecting certain errors ---------- proc errexpect-setline {lno line} { glset errexpect_lno $lno glset errexpect_line $line } proc errexpect-error {m} { global errexpect_line errexpect_lno error $m "$errexpect_line\n" [list YARRG-ERREXPECT $errexpect_lno] } proc errexpect-arrayget {arrayvar key} { upvar 1 $arrayvar av upvar 1 ${arrayvar}($key) v if {[info exists v]} { return $v } errexpect-error "undefined $key" } proc errexpect-catch {code} { global errorInfo errorCode set rc [catch { uplevel 1 $code } rv] debug "ERREXPECT CATCH |$rc|$rv|$errorCode|$errorInfo|" if {$rc==1 && ![string compare YARRG-ERREXPECT [lindex $errorCode 0]]} { return [list 1 $rv [lindex $errorCode 1] $errorInfo] } elseif {$rc==0} { return [list 0 $rv] } else { return -code $rc -errorinfo $errorInfo -errorcode $errorCode $rv } } #---------- argument parsing ---------- proc nextarg {} { global ai argv if {$ai >= [llength $argv]} { badusage "option [lindex $argv [expr {$ai-1}]] needs a value" } set v [lindex $argv $ai] incr ai return $v } set notes_loc vessel-notes set scraper {./yppedia-ocean-scraper --chart} proc parseargs {} { global ai argv global debug scraper set ai 0 while {[regexp {^\-} [set arg [lindex $argv $ai]]]} { incr ai switch -exact -- $arg { -- { break } --pirate { glset pirate [string totitle [nextarg]] } --ocean { glset ocean [string totitle [nextarg]] } --clipboard-file { load-clipboard-file [nextarg] } --local-html-dir { lappend scraper --local-html-dir=[nextarg] } --notes { glset notes_loc [nextarg] } --debug { incr debug } default { badusage "unknown option $arg" } } } set argv [lrange $argv $ai end] if {[llength $argv]} { badusage "non-option args not allowed" } } proc argdefaults {} { global ocean notes_loc pirate scraper if {![info exists ocean] || ![info exists pirate]} { set cmd {./yarrg --find-window-only --quiet} if {[info exists ocean]} { lappend cmd --ocean $ocean } if {[info exists pirate]} { lappend cmd --pirate $pirate } manyset [split [eval exec $cmd] " "] ocean pirate } lappend scraper $ocean } #---------- loading and parsing the vessel notes ---------- proc load-notes {} { global notes_loc notes_data if {[regexp {^\w+\:} $notes_loc]} { vwait idletasks debug "FETCHING NOTES $notes_loc" ::http::geturl $notes_loc switch -glob [::http::status].[::http::ncode] { ok.200 { } ok.* { error "retrieving vessel-notes $url: [::http::code]" } * { error "retrieving vessel-notes $url: [::http::error]" } } set newdata [::http::data] ::http::cleanup } else { debug "READING NOTES $notes_loc" set vn [open $notes_loc] set newdata [read $vn] close $vn } set notes_data $newdata } proc parse-notes {} { global notes_data notes catch { unset notes } foreach l [split $notes_data "\n"] { regsub -all {\t+} $l "\t" l manyset [split $l "\t"] vname vid owner note set nk $vid.$vname debug "SET NOTE $nk" set notes($nk) [list $owner $note] } } #---------- loading and parsing the clipboard (vessel locations) ---------- proc vessel {vin} { global pirate notes_used upvar 1 $vin vi switch -exact [errexpect-arrayget vi vesselClass] { smsloop { set sz 00sl } lgsloop { set sz 01ct } dhow { set sz 02dh } longship { set sz 03ls } baghlah { set sz 04bg } merchbrig { set sz 05mb } warbrig { set sz 06wb } xebec { set sz 07xe } warfrig { set sz 08wf } merchgal { set sz 09mg } grandfrig { set sz 10gf } default { errexpect-error "unknown class" } } set abbrev $sz switch -exact [errexpect-arrayget vi vesselSubclass] { null { } icy { append abbrev F } default { errexpect-error "unknown subclass ?" } } switch -exact [errexpect-arrayget vi isLocked]/[ \ errexpect-arrayget vi isBattleReady] { true/false { append abbrev 2- } false/false { append abbrev 1+ } false/true { append abbrev 0* } default { errexpect-error "unexpected isLocked/isBattleReady" } } switch -exact [errexpect-arrayget vi inPort] { true { } false { append abbrev ? } default { errexpect-error "unexpected inPort" } } set nk [errexpect-arrayget vi vesselId].[errexpect-arrayget vi vesselName] upvar #0 notes($nk) note if {[info exists note]} { manyset $note owner xabbrev if {![string compare $owner $pirate]} { append abbrev = } else { append abbrev - } append abbrev $xabbrev set notes_used($nk) 1 } else { debug "UNKNOWN $nk" } set kk "[errexpect-arrayget vi islandName] $abbrev" upvar #0 count($kk) k if {![info exists k]} { set k 0 } incr k } set clipboard {} proc parse-clipboard {} { global clipboard count notes notes_used catch { unset count } catch { unset notes_used } set itemre { (\w+) = ([^=]*) } set manyitemre "^\\\[ $itemre ( (?: ,\\ $itemre)* ) \\]\$" debug $manyitemre set lno 0 foreach l [split $clipboard "\n"] { incr lno errexpect-setline $lno $l if {![string length $l]} continue catch { unset vi } while 1 { if {![regexp -expanded $manyitemre $l dummy \ thiskey thisval rhs]} { errexpect-error "badly formatted" } set vi($thiskey) $thisval if {![string length $rhs]} break regsub {^, } $rhs {} rhs set l "\[$rhs\]" } vessel vi } foreach nk [lsort [array names notes]] { if {![info exists notes_used($nk)]} { debug "IGNORED NOTE $nk" } } } proc load-clipboard-file {fn} { set f [open $fn] glset clipboard [read $f] close $f } #---------- loading and parsing the chart ---------- proc load-chart {} { global chart scraper debug "FETCHING CHART" set chart [eval exec $scraper [list | perl -we { use strict; use CommodsScrape; use IO::File; use IO::Handle; yppedia_chart_parse(\*STDIN, (new IO::File ">/dev/null"), sub { sprintf "%d %d", @_; }, sub { printf "archlabel %d %d %s\n", @_; }, sub { printf "island %s %s\n", @_; }, sub { printf "league %s %s %s.\n", @_; }, sub { printf STDERR "warning: %s: incomprehensible: %s", @_; } ); STDOUT->error and die $!; }]] } set scale 16 proc coord {c} { global scale return [expr {$c * $scale}] } proc chart-got/archlabel {args} { } proc chart-got/island {x y args} { # debug "ISLE $x $y $args" global canvas isleloc set isleloc($args) [list $x $y] set sz 5 # $canvas create oval \ # [expr {[coord $x] - $sz}] [expr {[coord $y] - $sz}] \ # [expr {[coord $x] + $sz}] [expr {[coord $y] + $sz}] \ # -fill blue $canvas create text [coord $x] [coord $y] \ -text $args -anchor s } proc chart-got/league {x1 y1 x2 y2 kind} { # debug "LEAGUE $x1 $y1 $x2 $y2 $kind" global canvas set l [$canvas create line \ [coord $x1] [coord $y1] \ [coord $x2] [coord $y2]] if {![string compare $kind .]} { $canvas itemconfigure $l -dash . } } proc draw {} { global chart count isleloc canvas $canvas delete all foreach l [split $chart "\n"] { # debug "CHART-GOT $l" set proc [lindex $l 0] eval chart-got/$proc [lrange $l 1 end] } set lastislandname {} foreach key [lsort [array names count]] { set c $count($key) # debug "SHOWING $key $c" regexp {^(.*) (\S+)$} $key dummy islandname abbrev if {[string compare $lastislandname $islandname]} { manyset $isleloc($islandname) x y set x [coord $x] set y [coord $y] set lastislandname $islandname # debug "START Y $y" } set text $abbrev regsub -all {[0-9]} $text {} text if {$c > 1} { set text [format "%2d%s" $c $text] } else { set text [format " %s" $text] } set id [$canvas create text $x $y \ -anchor nw -font fixed \ -text $text] set bbox [$canvas bbox $id] set bid [eval $canvas create rectangle $bbox -fill white] $canvas lower $bid $id manyset $bbox dummy dummy dummy y # debug "NEW Y $y" } panner::updatecanvas-bbox .ctrl.pan } #---------- user interface ---------- proc widgets-setup {} { global canvas debug acquire_deffont frame .f -border 1 -relief groove set canvas .f.c canvas $canvas pack $canvas -expand 1 -fill both pack .f -expand 1 -fill both -side left frame .ctrl pack .ctrl -side right debug "BBOX [$canvas bbox all]" panner::canvas-scroll-bbox .f.c panner::create .ctrl.pan .f.c 120 120 $debug pack .ctrl.pan -side top -pady 10 -padx 5 frame .ctrl.zoom pack .ctrl.zoom -side top button .ctrl.zoom.out -text - -font {Courier 16} -command {zoom /2} button .ctrl.zoom.in -text + -font {Courier 16} -command {zoom *2} pack .ctrl.zoom.out .ctrl.zoom.in -side left parser-control-create .ctrl.acquire \ acquire Acquire \ "Clipboard parsing error" \ pack .ctrl.acquire -side top -pady 2 parser-control-create .ctrl.notes \ notes "Reload notes" \ "Vessel notes loading error" \ pack .ctrl.notes -side top -pady 2 wm geometry . 1024x480 } proc parser-control-create {w base invokebuttontext etl_title} { frame $w button $w.do -text $invokebuttontext -command invoke_$base frame $w.resframe -width 120 -height 32 button $w.resframe.res -text {} -anchor nw \ -padx 1 -pady 1 -borderwidth 0 -justify left glset deffont_$base [$w.resframe.res cget -font] place $w.resframe.res -relx 0.5 -y 0 -anchor n pack $w.do -side top pack $w.resframe -side top -expand y -fill both set eb .err_$base toplevel $eb wm withdraw $eb wm title $eb "$etl_title - where-vessels" label $eb.title -text $etl_title pack $eb.title -side top button $eb.close -text Close -command [list wm withdraw $eb] pack $eb.close -side bottom frame $eb.emsg -bd 2 -relief groove label $eb.emsg.lab -text "Error:" text $eb.emsg.text -height 1 pack $eb.emsg.text -side bottom pack $eb.emsg.lab -side left pack $eb.emsg -side top -pady 2 frame $eb.text -bd 2 -relief groove pack $eb.text -side bottom -pady 2 label $eb.text.lab text $eb.text.text \ -xscrollcommand [list $eb.text.xscroll set] \ -yscrollcommand [list $eb.text.yscroll set] $eb.text.text tag configure error \ -background red -foreground white scrollbar $eb.text.xscroll -orient horizontal \ -command [list $eb.text.text xview] scrollbar $eb.text.yscroll -orient vertical \ -command [list $eb.text.text yview] grid configure $eb.text.lab -row 0 -column 0 -sticky w grid configure $eb.text.text -row 1 -column 0 grid configure $eb.text.yscroll -sticky ns -row 1 -column 1 grid configure $eb.text.xscroll -sticky ew -row 2 -column 0 } proc parser-control-ok-core {w base background show} { debug "parser-control-ok-core $w $base $background $show" upvar #0 deffont_$base deffont $w.resframe.res configure \ -background $background -disabledforeground black -font $deffont \ -state disabled -command {} \ -text $show } proc parser-control-ok {w base show} { parser-control-ok-core $w $base green $show } proc parser-control-none {w base show} { parser-control-ok-core $w $base blue $show } proc parser-control-failed-core {w base tiny summary fulldesc fulldata} { debug "parser-control-failed-core $w $base $summary $fulldesc" set eb .err_$base $eb.emsg.text delete 0.0 end $eb.emsg.text insert end $summary $eb.text.lab configure -text $fulldesc $eb.text.text delete 0.0 end $eb.text.text insert end $fulldata regsub -all {.{18}} $tiny "&\n" ewrap $w.resframe.res configure \ -background red -foreground white -font fixed \ -state normal -command [list wm deiconify $eb] \ -text $ewrap } proc parser-control-failed-expected {w base emsg lno ei fulldesc newdata} { set eb .err_$base set line [lindex [split $ei "\n"] 0] debug "parser-control-failed-expected: $w $base: $lno: $emsg\n $line" parser-control-failed-core $w $base \ "err: [string trim $emsg]: \"$line\"" \ "at line $lno: $emsg" \ $fulldesc $newdata $eb.text.text tag add error $lno.0 $lno.end $eb.text.text see $lno.0 } proc zoom {extail} { global scale canvas set nscale [expr "\$scale $extail"] debug "ZOOM $scale $nscale" if {$nscale < 1 || $nscale > 200} return set scale $nscale draw } proc reparse {base varname fulldesc okshow noneshow set parse} { upvar #0 $varname var set old $var uplevel 1 $set manyset [errexpect-catch { uplevel 1 $parse if {[string length [string trim $var]]} { parser-control-ok .ctrl.$base $base $okshow } else { parser-control-none .ctrl.$base $base $noneshow } }] failed emsg lno ei if {$failed} { parser-control-failed-expected .ctrl.$base $base \ $emsg $lno $ei $fulldesc $var set var $old uplevel 1 $parse } } proc invoke_acquire {} { global clipboard reparse acquire \ clipboard "Clipboard contents:" { acquired ok } { no vessels } \ { set clipboard [clipboard get] } \ { parse-clipboard } draw } proc invoke_notes {} { global notes_data errorInfo notes_loc reparse notes notes_data "Vessel notes:" "notes reloaded" { no notes } \ { if {[catch { load-notes } emsg]} { parser-control-failed-core .ctrl.notes notes \ $emsg $emsg "Details and stack trace:" \ "loading $notes_loc:\n\n$errorInfo" return } } \ { parse-notes } draw } #---------- main program ---------- parseargs argdefaults httpclientsetup where-vessels load-chart widgets-setup load-notes parse-notes parse-clipboard draw