--- deliantra/Deliantra-Client/bin/cfplus 2006/08/13 02:43:22 1.106 +++ deliantra/Deliantra-Client/bin/cfplus 2006/12/09 22:06:51 1.139 @@ -1,8 +1,31 @@ #!/opt/bin/perl +my $startup_done = sub { }; + +# do splash-screen thingy on win32 +BEGIN { + if (%PAR::LibCache && $^O eq "MSWin32") { + while (my ($filename, $zip) = each %PAR::LibCache) { + $zip->extractMember ("SPLASH.bmp", "$ENV{PAR_TEMP}/SPLASH.bmp"); + } + + require Win32::GUI::SplashScreen; + + Win32::GUI::SplashScreen::Show ( + -file => "$ENV{PAR_TEMP}/SPLASH.bmp", + ); + + $startup_done = sub { + Win32::GUI::SplashScreen::Done (1); + }; + } +} + use strict; use utf8; +use Carp 'verbose'; + # do things only needed for single-binary version (par) BEGIN { if (%PAR::LibCache) { @@ -10,7 +33,7 @@ while (my ($filename, $zip) = each %PAR::LibCache) { for ($zip->memberNames) { - next unless /^\/root\/(.*)/; + next unless /^root\/(.*)/; $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1") unless -e "$ENV{PAR_TEMP}/$1"; } @@ -38,41 +61,31 @@ use CFPlus::OpenGL (); use CFPlus::Protocol; use CFPlus::UI; +use CFPlus::UI::Inventory; +use CFPlus::UI::SpellList; use CFPlus::Pod; -use CFPlus::BindingEditor; use CFPlus::MapWidget; +use CFPlus::Macro; $SIG{QUIT} = sub { Carp::cluck "QUIT" }; $SIG{PIPE} = 'IGNORE'; -$Event::Eval = 0; +$Event::Eval = 1; $Event::DIED = sub { - # TODO: display dialog box or so - Carp::cluck $_[1];#d#TODO: remove when stable - CFPlus::error $_[1]; + CFPlus::fatal Carp::longmess $_[1] }; -$SIG{__DIE__} = sub { - return if CFPlus::in_destruct; - Carp::cluck $_[0]; - CFPlus::error $_[0]; - return;#d# - #return unless defined $^S && !$^S; - $Event::DIED->(undef, $_[0]); -}; - -our $VERSION = '0.1'; - my $MAX_FPS = 60; my $MIN_FPS = 5; # unused as of yet -our $META_SERVER = "crossfire.real-time.com:13326"; +our $META_SERVER = "http://metaserver.schmorp.de/current.json"; our $LAST_REFRESH; our $NOW; our $CFG; our $CONN; +our $PROFILE; # current profile our $FAST; # fast, low-quality mode, possibly useful for software-rendering our $WANT_REFRESH; @@ -113,6 +126,7 @@ our $STATS_PAGE; our $SKILL_PAGE; our $SPELL_PAGE; +our $SPELL_LIST; our $HELP_WINDOW; our $MESSAGE_WINDOW; @@ -135,11 +149,11 @@ our $INVR; our $INV_RIGHT_HB; -our $BIND_EDITOR; -our $BIND_UPD_CB; - our $PICKUP_CFG; +our $IN_BUILD_MODE; +our $BUILD_BUTTON; + sub status { $STATUSBOX->add (CFPlus::asxml $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]); } @@ -148,6 +162,17 @@ $DEBUG_STATUS->set_text ($_[0]); } +sub message { + my ($para) = @_; + + my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0]; + + $para->{markup} = "$time $para->{markup}"; + + $LOGVIEW->add_paragraph ($para); + $LOGVIEW->scroll_to_bottom; +} + sub destroy_query_dialog { (delete $_[0]{query_dialog})->destroy if $_[0]{query_dialog}; @@ -157,7 +182,7 @@ sub server_query { my ($conn, $flags, $prompt) = @_; - $conn->{query_dialog} = my $dialog = new CFPlus::UI::FancyFrame + $conn->{query_dialog} = my $dialog = new CFPlus::UI::Toplevel x => "center", y => "center", title => "Server Query", @@ -192,7 +217,7 @@ $dialog->grab_focus; } elsif ($flags & CS_QUERY_SINGLECHAR) { - $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)"; + $dialog->{tooltip} = "#charcreation_focus"; if ($prompt =~ /Now choose a character|Press any key for the next race/i) { $MESSAGE_WINDOW->show; @@ -224,14 +249,7 @@ unshift @dialog, new CFPlus::UI::Label max_w => $::WIDTH * 0.4, ellipsise => 0, - markup => - "Character Creation: Race\n\n" - . "Look at the Messages window to see a description of this race " - . "and the center of the screen to see how this race looks like " - . "(below this dialog window: you may need to move the dialog away and " - . "click into the display area to make it visible).\n\n" - . "You can look at another race, or accept this race (you will cycle back to " - . "this race eventually, so you can take your time making this important choice." + markup => (CFPlus::Pod::section_label ui => "chargen_race"), ; } elsif ($prompt =~ /roll new stats/) { @@ -315,19 +333,14 @@ expand => 1, can_events => 1, can_hover => 1, - tooltip => $CFPlus::STAT_TOOLTIP{$name}, + tooltip => "#stat_$name", ); } unshift @dialog, new CFPlus::UI::Label max_w => $::WIDTH * 0.4, ellipsise => 0, - markup => - "Character Creation: Stats\n\n" - . "Stats are a very important aspect of your character. You can use the tooltips to learn what each Stat governs.\n\n" - . "The stats generated by the server are always sorted from Str (highest) to Cha (lowest). " - . "They will be modified later by both the race and the class you choose.\n\n" - . "You can create another set of stats, swap two stat values with each other or accept the stats as shown below and continue.\n" + markup => (CFPlus::Pod::section_label ui => "chargen_stats"), ; } @@ -368,19 +381,21 @@ my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32; - my ($host, $port) = split /:/, $CFG->{profile}{default}{host}; + my ($host, $port) = split /:/, $PROFILE->{host}; - $MAP = new CFPlus::Map $mapsize, $mapsize; + $MAP = new CFPlus::Map; $CONN = eval { new CFPlus::Protocol host => $host, port => $port || 13327, - user => $CFG->{profile}{default}{user}, - pass => $CFG->{profile}{default}{password}, + user => $PROFILE->{user}, + pass => $PROFILE->{password}, mapw => $mapsize, maph => $mapsize, + client => "cfplus $CFPlus::VERSION $] $^O", + map_widget => $MAPWIDGET, logview => $LOGVIEW, statusbox => $STATUSBOX, @@ -416,7 +431,7 @@ $SETUP_NOTEBOOK->set_current_page ($SETUP_SERVER); $SETUP_DIALOG->show; $PL_WINDOW->hide; - $SPELL_PAGE->clear_spells; + $SPELL_LIST->clear_spells; return unless $CONN; @@ -624,19 +639,13 @@ ], ); - $hb->add (my $hg = new CFPlus::UI::Gauge type => 'hp', - tooltip => "Health points. Measures of how much damage you can take before dying. Hit points are determined from your level and are influenced by the value of your Con. Hp value may range between 1 to beyond 500 and higher values indicate a greater ability to withstand punishment."); - $hb->add (my $mg = new CFPlus::UI::Gauge type => 'mana', - tooltip => "Spell points. Measures of how much \"fuel\" you have for casting spells and incantations. Mana is calculated from your level and your Pow. Mana values can range between 1 to beyond 500 (glowing crystals can increase the current spell points beyond your normal maximum). Higher values indicate greater amounts of mana."); - $hb->add (my $gg = new CFPlus::UI::Gauge type => 'grace', - tooltip => "Grace points - how favored you are by your god. In game terms, how much divine magic you can cast. Your level, Wis and Pow effect what the value of grace is. Prayong on an altar of your god can increase this value beyond your normal maximum. Grace can take on large positive and negative values. Positive values indicate favor by the gods."); - $hb->add (my $fg = new CFPlus::UI::Gauge type => 'food', - tooltip => "Food. Ranges between 0 (starving) and 999 (satiated). At a value of 0 the character begins to die. Some magic can speed up or slow down the character digestion. Healing wounds will speed up digestion too."); - - $vbox->add (my $exp = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, - tooltip => "Experience points and overall level - experience is increased as a reward for appropriate action (such as killing monsters) and may decrease as a result of a magical attack or dying. Level is directly derived from the experience value. As the level of the character increases, the character becomes able to succeed at more difficult tasks. A character's level starts at a value of 0 and may range up beyond 100."); - $vbox->add (my $rng = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, - tooltip => "Ranged attack - how you attack when you press shift-cursor (spell, skill, weapon etc.)"); + $hb->add (my $hg = new CFPlus::UI::Gauge type => 'hp', tooltip => "#stat_health"); + $hb->add (my $mg = new CFPlus::UI::Gauge type => 'mana', tooltip => "#stat_mana"); + $hb->add (my $gg = new CFPlus::UI::Gauge type => 'grace', tooltip => "#stat_grace"); + $hb->add (my $fg = new CFPlus::UI::Gauge type => 'food', tooltip => "#stat_food"); + + $vbox->add (my $exp = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp"); + $vbox->add (my $rng = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged"); $GAUGES = { exp => $exp, win => $win, range => $rng, @@ -659,6 +668,7 @@ $table->add (1, 2, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 }); $table->add (0, 3, new CFPlus::UI::Label text => "Suppress Tooltips"); $table->add (1, 3, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 }); + $table->add (0, 4, new CFPlus::UI::Button text => "die on click(tm)", on_activate => sub { die "violator" } ); my @default_smooth = (0.05, 0.13, 0.05, 0.13, 0.30, 0.13, 0.05, 0.13, 0.05); @@ -672,6 +682,7 @@ } } + $table->add (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d# $table } @@ -683,14 +694,19 @@ ); $r->add (my $vb = new CFPlus::UI::VBox); - $vb->add ($STATWIDS->{title} = new CFPlus::UI::Label valign => 0, align => -1, text => "Title:", expand => 1, + $vb->add (new CFPlus::UI::FancyFrame + label => "Player", + child => (my $pi = new CFPlus::UI::VBox), + ); + + $pi->add ($STATWIDS->{title} = new CFPlus::UI::Label valign => 0, align => -1, text => "Title:", expand => 1, can_hover => 1, can_events => 1, tooltip => "Your name and title. You can change your title by using the title command, if supported by the server."); - $vb->add ($STATWIDS->{map} = new CFPlus::UI::Label valign => 0, align => -1, text => "Map:", expand => 1, + $pi->add ($STATWIDS->{map} = new CFPlus::UI::Label valign => 0, align => -1, text => "Map:", expand => 1, can_hover => 1, can_events => 1, tooltip => "The map you are currently on (if supported by the server)."); - $vb->add (my $hb0 = new CFPlus::UI::HBox); + $pi->add (my $hb0 = new CFPlus::UI::HBox); $hb0->add ($STATWIDS->{weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1, can_hover => 1, can_events => 1, tooltip => "The weight of the player including all inventory items."); @@ -698,7 +714,10 @@ can_hover => 1, can_events => 1, tooltip => "The weight limit: you cannot carry more than this."); - $vb->add (my $hb = new CFPlus::UI::HBox expand => 1); + $vb->add (new CFPlus::UI::FancyFrame + label => "Primary/Secondary Statistics", + child => (my $hb = new CFPlus::UI::HBox expand => 1), + ); $hb->add (my $tbl = new CFPlus::UI::Table expand => 1); my $color2 = [1, 1, 0]; @@ -723,13 +742,16 @@ $tbl->add ($col , $row, $STATWIDS->{$id} = new CFPlus::UI::Label font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, - align => +1, template => $template, tooltip => $CFPlus::STAT_TOOLTIP{$label}); + align => +1, template => $template, tooltip => "#stat_$label"); $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFPlus::UI::Label font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, - align => -1, text => $label, tooltip => $CFPlus::STAT_TOOLTIP{$label}); + align => -1, text => $label, tooltip => "#stat_$label"); } - $vb->add (my $tbl2 = new CFPlus::UI::Table expand => 1); + $vb->add (new CFPlus::UI::FancyFrame + label => "Resistancies", + child => (my $tbl2 = new CFPlus::UI::Table expand => 1), + ); my $row = 0; my $col = 0; @@ -839,27 +861,15 @@ $table->clear; $table->add (0, 0, my $label = new CFPlus::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list..."); - my $buf; + my $ok = 0; - my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0; - - unless ($fh) { - $label->set_text ("unable to contact metaserver: $!"); - return; - } - - Event->io (fd => $fh, poll => 'r', cb => sub { - my $res = sysread $fh, $buf, 8192, length $buf; - - if (!defined $res) { - $_[0]->w->cancel; - $label->set_text ("error while retrieving server list: $!"); - } elsif ($res == 0) { - $_[0]->w->cancel; - status "server list retrieved"; - - utf8::decode $buf if utf8::valid $buf; + CFPlus::background { + my $ua = CFPlus::lwp_useragent; + CFPlus::background_msg CFPlus::from_json +(CFPlus::lwp_check $ua->get ($META_SERVER))->decoded_content; + } sub { + my ($msg) = @_; + if ($msg) { $table->clear; my @tip = ( @@ -879,23 +889,30 @@ my @align = qw(1 0 1 1 -1); my $y = 0; - for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) { - my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m; + for my $m ( + sort { + $b->{version} <=> $a->{version} + or $b->{users} <=> $a->{users} + } + @{ $msg->{servers} } + ) { + my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = + @$m{qw(ip age hostname users version description ibytes obytes uptime)}; for ($desc) { s/
/\n/gi; s/
  • /\n· /gi; s/<.*?>//sgi; - s/&/&/g; - s//>/g; + s/&/&/g; + s/<//g; } $uptime = sprintf "%dd %02d:%02d:%02d", - (int $m->[8] / 86400), - (int $m->[8] / 3600) % 24, - (int $m->[8] / 60) % 60, - $m->[8] % 60; + (int $uptime / 86400), + (int $uptime / 3600) % 24, + (int $uptime / 60) % 60, + $uptime % 60; $m = [$users, $host, $uptime, $version, $desc]; @@ -915,17 +932,22 @@ ]); $table->add ($_, $y, new CFPlus::UI::Label + max_w => $::WIDTH * 0.4, ellipsise => 0, align => $align[$_], text => $m->[$_], tooltip => $tip[$_], + fg => ($m->[3] =~ /\+$/ ? [1, 1, 1] : [.7, .7, .7]), can_hover => 1, can_events => 1, fontsize => 0.8) for 0 .. $#$m; } + } else { + $ok or $label->set_text ("error while contacting metaserver"); } - }); + }; + } sub metaserver_dialog { @@ -933,13 +955,14 @@ my $table = new CFPlus::UI::Table; $vbox->add (new CFPlus::UI::ScrolledWindow expand => 1, child => $table); - my $dialog = new CFPlus::UI::FancyFrame + my $dialog = new CFPlus::UI::Toplevel title => "Server List", name => 'metaserver_dialog', x => 'center', y => 'center', z => 3, - force_h => $::HEIGHT * 0.4, + force_w => $::WIDTH * 0.9, + force_h => $::HEIGHT * 0.7, child => $vbox, has_close_button => 1, table => $table, @@ -955,7 +978,10 @@ sub server_setup { my $vbox = new CFPlus::UI::VBox; - $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]); + $vbox->add (new CFPlus::UI::FancyFrame + label => "Connection Settings", + child => (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]), + ); $table->add (0, 2, new CFPlus::UI::Label valign => 0, align => 1, text => "Host:Port"); { @@ -1049,7 +1075,7 @@ $table->add (0, 12, new CFPlus::UI::Label valign => 0, align => 1, text => "Chat Command"); $table->add (1, 12, my $saycmd = new CFPlus::UI::Entry - text => $CFG->{say_command}, + text => $CFG->{say_command}, tooltip => "This is the command that will be used if you write a line in the message window entry or press \" in the map window. " . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. " . "But you could also set it to tell playername to only chat with that user.", @@ -1060,20 +1086,27 @@ } ); - $vbox->add (new CFPlus::UI::Label - text => "Server Info", - fontsize => 1.2, - padding_y => 8, - fg => [1, 1, 0, 1], + $table->add (0, 13, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day"); + $table->add (1, 13, my $saycmd = new CFPlus::UI::CheckBox + state => $CFG->{show_tips}, + tooltip => "Show the Tip of the day window at startup?", + on_changed => sub { + my ($self, $value) = @_; + $CFG->{show_tips} = $value; + 0 + } ); - $vbox->add ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0); + $vbox->add (new CFPlus::UI::FancyFrame + label => "Server Info", + child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0), + ); $vbox } sub message_window { - my $window = new CFPlus::UI::FancyFrame + my $window = new CFPlus::UI::Toplevel name => "message_window", title => "Messages", border_bg => [1, 1, 1, 1], @@ -1135,62 +1168,6 @@ $window } -sub open_string_query { - my $cb = $_[1]; - my $dialog = new CFPlus::UI::FancyFrame - x => "center", - y => "center", - z => 50, - force_w => $WIDTH * 4/5, - title => $_[0]; - - $dialog->add ( - my $e = new CFPlus::UI::Entry - on_activate => sub { $cb->(@_); $dialog->hide; 0 }, - on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 }, - ); - - $e->grab_focus; - $dialog->show; -} - -sub open_quit_dialog { - unless ($QUIT_DIALOG) { - $QUIT_DIALOG = new CFPlus::UI::FancyFrame - x => "center", - y => "center", - z => 50, - title => "Really Quit?", - on_key_down => sub { - my ($dialog, $ev) = @_; - $ev->{sym} == 27 and $dialog->hide; - } - ; - - $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1); - - $vb->add (new CFPlus::UI::Label - text => "You should find a savebed and apply it first!", - max_w => $WIDTH * 0.25, - ellipsize => 0, - ); - $vb->add (my $hb = new CFPlus::UI::HBox expand => 1); - $hb->add (new CFPlus::UI::Button - text => "Ok", - expand => 1, - on_activate => sub { $QUIT_DIALOG->hide; 0 }, - ); - $hb->add (new CFPlus::UI::Button - text => "Quit anyway", - expand => 1, - on_activate => sub { exit }, - ); - } - - $QUIT_DIALOG->show; - $QUIT_DIALOG->grab_focus; -} - sub autopickup_setup { my $table = new CFPlus::UI::Table; @@ -1216,12 +1193,12 @@ ["Cloaks" => PICKUP_CLOAK], ], - ["Readables", 2, 2, + ["Readables", 2, 0, ["Spellbooks" => PICKUP_SPELLBOOK], ["Skillscrolls" => PICKUP_SKILLSCROLL], ["Normal Books/Scrolls" => PICKUP_READABLES], ], - ["Misc", 2, 7, + ["Misc", 2, 5, ["Food" => PICKUP_FOOD], ["Drinks" => PICKUP_DRINK], ["Valuables (Money, Gems)" => PICKUP_VALUABLES], @@ -1231,6 +1208,7 @@ ["Magic Devices" => PICKUP_MAGIC_DEVICE], ["Ignore cursed" => PICKUP_NOT_CURSED], ["Jewelery" => PICKUP_JEWELS], + ["Flesh" => PICKUP_FLESH], ], ["Weight/Value ratio", 2, 17] ) @@ -1290,11 +1268,17 @@ my %SORT_ORDER = ( type => undef, - mtime => sub { sort { - ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED) - or $b->{mtime} <=> $a->{mtime} - or $a->{type} <=> $b->{type} - } @_ }, + mtime => sub { + my $NOW = time; + sort { + my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6; + my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6; + + ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED) + or $btime <=> $atime + or $a->{type} <=> $b->{type} + } @_ + }, weight => sub { sort { $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1) or $a->{type} <=> $b->{type} @@ -1329,6 +1313,7 @@ $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1); $sw1->add ($INV = new CFPlus::UI::Inventory); + $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}}); $hb->add (my $vb2 = new CFPlus::UI::VBox); @@ -1355,7 +1340,7 @@ } sub player_window { - my $plwin = $PL_WINDOW = new CFPlus::UI::FancyFrame + my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel x => "center", y => "center", force_w => $WIDTH * 9/10, @@ -1378,8 +1363,8 @@ "Shows all your Skills." ); - my $spellsw = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1); - $spellsw->add ($SPELL_PAGE = new CFPlus::UI::SpellList); + my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1); + $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList); $ntb->add ( "Spellbook (F4)" => $spellsw, "Displays all spells you have and lets you edit keyboard shortcuts for them." @@ -1396,165 +1381,220 @@ $plwin } -sub update_bindings { - $BIND_UPD_CB->() if $BIND_UPD_CB; +sub keyboard_setup { + CFPlus::Macro::keyboard_setup } -sub keyboard_setup { - my $binding_list = new CFPlus::UI::VBox; +sub help_window { + my $win = new CFPlus::UI::Toplevel + x => 'center', + y => 'center', + z => 4, + name => 'doc_browser', + force_w => int $WIDTH * 7/8, + force_h => int $HEIGHT * 7/8, + title => "Help Browser", + has_close_button => 1; - my $refresh; - $refresh = $BIND_UPD_CB = sub { - $binding_list->clear (); - - for my $mod (keys %{$::CFG->{profile}{default}{bindings}}) { - for my $sym (keys %{$::CFG->{profile}{default}{bindings}{$mod}}) { - my $cmds = $::CFG->{profile}{default}{bindings}{$mod}{$sym}; - next unless ref $cmds eq 'ARRAY' and @$cmds > 0; - - my $lbl = join "; ", @$cmds; - my $nam = CFPlus::BindingEditor::keycombo_to_name ($mod, $sym); - $binding_list->add (my $hb = new CFPlus::UI::HBox); - $hb->add (new CFPlus::UI::Button - text => "delete", - tooltip => "Deletes the binding", - on_activate => sub { - $binding_list->remove ($hb); - delete $::CFG->{profile}{default}{bindings}{$mod}{$sym}; - 0 - }); + $win->add (my $vbox = new CFPlus::UI::VBox); - $hb->add (new CFPlus::UI::Button - text => "edit", - tooltip => "Edits the binding", - on_activate => sub { - $::BIND_EDITOR->set_binding ( - $mod, $sym, $::CFG->{profile}{default}{bindings}{$mod}{$sym}, - sub { - my ($nmod, $nsym, $ncmds) = @_; - $::BIND_EDITOR->cfg_unbind ($mod, $sym); - $::BIND_EDITOR->cfg_bind ($nmod, $nsym, $ncmds); - $refresh->(); - $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD); - $SETUP_DIALOG->show; - }, - sub { - $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD); - $SETUP_DIALOG->show; - }); - $::BIND_EDITOR->show; - $SETUP_DIALOG->hide; - 0 - }); + $vbox->add (new CFPlus::UI::FancyFrame + label => "Navigation", + child => (my $buttons = new CFPlus::UI::HBox), + ); + $vbox->add (my $viewer = new CFPlus::UI::TextScroller + expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4); - $hb->add (new CFPlus::UI::Label text => "(Key: $nam)"); - $hb->add (new CFPlus::UI::Label text => $lbl, expand => 1); - } - } - }; + my @history; + my @future; + my $curnode; - my $vb = new CFPlus::UI::VBox; - $vb->add (my $hb = new CFPlus::UI::HBox); - $hb->add (new CFPlus::UI::Label text => "only shift-up stops fire"); - $hb->add (new CFPlus::UI::CheckBox - expand => 1, - state => $CFG->{shift_fire_stop}, - tooltip => "If this checkbox is enabled you will stop fire only if you stop pressing shift", - on_changed => sub { - my ($cbox, $value) = @_; - $CFG->{shift_fire_stop} = $value; - 0 - }); + my $load_node; $load_node = sub { + my ($node, $para) = @_; - $vb->add ($binding_list); - $vb->add (my $hb = new CFPlus::UI::HBox); + $buttons->clear; - $hb->add (new CFPlus::UI::Button - text => "record new", - expand => 1, - tooltip => "This button opens the binding editor with an empty binding.", - on_activate => sub { - $::BIND_EDITOR->set_binding (undef, undef, [], - sub { - my ($mod, $sym, $cmds) = @_; - $::BIND_EDITOR->cfg_bind ($mod, $sym, $cmds); - $refresh->(); - $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD); - $SETUP_DIALOG->show; + $buttons->add (new CFPlus::UI::Button + text => "⇤", + tooltip => "back to the starting page", + on_activate => sub { + unshift @future, [$curnode, $viewer->current_paragraph] if $curnode; + unshift @future, @history; + @history = (); + $load_node->(@{shift @future}); + }, + ); + + if (@history) { + $buttons->add (new CFPlus::UI::Button + text => "⋘", + tooltip => "back to " . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "", + on_activate => sub { + unshift @future, [$curnode, $viewer->current_paragraph] if $curnode; + $load_node->(@{pop @history}); }, - sub { - $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD); - $SETUP_DIALOG->show; + ); + } + + if (@future) { + $buttons->add (new CFPlus::UI::Button + text => "⋙", + tooltip => "forward to " . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "", + on_activate => sub { + push @history, [$curnode, $viewer->current_paragraph]; + $load_node->(@{shift @future}); }, ); - $SETUP_DIALOG->hide; - $::BIND_EDITOR->show; - 0 - }, - ); + } - $hb->add (new CFPlus::UI::Button - text => "close", - tooltip => "Closes the binding window", - expand => 1, - on_activate => sub { - $SETUP_DIALOG->hide; - 0 + $buttons->add (new CFPlus::UI::Label text => " "); + + my @path = CFPlus::Pod::full_path_of $node; + pop @path; # drop current node + + for my $node (@path) { + $buttons->add (new CFPlus::UI::Button + text => $node->{kw}[0], + tooltip => "go to " . (CFPlus::asxml CFPlus::Pod::full_path $node) . "", + on_activate => sub { + push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = (); + $load_node->($node); + }, + ); + $buttons->add (new CFPlus::UI::Label text => "/"); } + + $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4); + + $curnode = $node; + + $viewer->clear; + $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode); + $viewer->scroll_to ($para); + }; + + $load_node->(CFPlus::Pod::find pod => "mainpage"); + + $CFPlus::Pod::goto_document = sub { + my (@path) = @_; + + push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = (); + + $load_node->((CFPlus::Pod::find @path)[0]); + $win->show; + }; + + $win +} + +sub open_string_query { + my ($title, $cb, $txt, $tooltip) = @_; + my $dialog = new CFPlus::UI::Toplevel + x => "center", + y => "center", + z => 50, + force_w => $WIDTH * 4/5, + title => $title; + + $dialog->add ( + my $e = new CFPlus::UI::Entry + on_activate => sub { $cb->(@_); $dialog->hide; 0 }, + on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 }, + tooltip => $tooltip ); - $refresh->(); + $e->grab_focus; + $e->set_text ($txt) if $txt; + $dialog->show; +} + +sub open_quit_dialog { + unless ($QUIT_DIALOG) { + $QUIT_DIALOG = new CFPlus::UI::Toplevel + x => "center", + y => "center", + z => 50, + title => "Really Quit?", + on_key_down => sub { + my ($dialog, $ev) = @_; + $ev->{sym} == 27 and $dialog->hide; + } + ; + + $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1); - $vb + $vb->add (new CFPlus::UI::Label + text => "You should find a savebed and apply it first!", + max_w => $WIDTH * 0.25, + ellipsize => 0, + ); + $vb->add (my $hb = new CFPlus::UI::HBox expand => 1); + $hb->add (new CFPlus::UI::Button + text => "Ok", + expand => 1, + on_activate => sub { $QUIT_DIALOG->hide; 0 }, + ); + $hb->add (new CFPlus::UI::Button + text => "Quit anyway", + expand => 1, + on_activate => sub { exit }, + ); + } + + $QUIT_DIALOG->show; + $QUIT_DIALOG->grab_focus; } -sub help_window { - my $win = new CFPlus::UI::FancyFrame - x => 'center', - y => 'center', - z => 2, - name => 'doc_browser', - force_w => int $WIDTH * 7/8, - force_h => int $HEIGHT * 7/8, - title => "Help Browser", - has_close_button => 1; +sub show_tip_of_the_day { + # find all tips + my @tod = CFPlus::Pod::find tip_of_the_day => "*"; + + my $todindex = $CFPlus::DB_STATE->get ("tip_of_the_day"); + $todindex = 0 if $todindex >= @tod; + $CFPlus::DB_STATE->put (tip_of_the_day => $todindex + 1); - $win->add (my $vbox = new CFPlus::UI::VBox); + # create dialog + my $dialog; + + my $close = sub { + $dialog->destroy; + }; + + $dialog = new CFPlus::UI::Toplevel + x => "center", + y => "center", + z => 3, + name => 'tip_of_the_day', + force_w => int $WIDTH * 4/9, + force_h => int $WIDTH * 2/9, + title => "Tip of the day #" . (1 + $todindex), + child => my $vbox = new CFPlus::UI::VBox, + has_close_button => 1, + on_delete => $close, + ; - $vbox->add (my $buttons = new CFPlus::UI::HBox); $vbox->add (my $viewer = new CFPlus::UI::TextScroller - expand => 1, fontsize => 0.8, padding_x => 4); + expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4); + $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]); - $buttons->add (new CFPlus::UI::Label text => "Choose a document to display: "); - $buttons->add (my $combo = new CFPlus::UI::Selector - value => undef, - options => [ - [intro => "Introduction"], - [manual => "Main Manual"], - [skill_help => "Skill Reference"], - [command_help => "Command Reference"], - [dmcommand_help => "DM Commands"], - [COPYING => "License Terms"], - ], - on_changed => sub { - my ($self, $pod) = @_; + $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]); - $viewer->clear; - $viewer->add_paragraph (CFPlus::Pod::section pod => $pod); - $viewer->set_offset (0); + $table->add (0, 0, new CFPlus::UI::Button + text => "Close", + tooltip => "Close the tip of the day window. To never see it again, disable the tip of the day in the Server Setup.", + on_activate => $close, + ); - 0 - }, - on_visibility_change => sub { - my ($self, $visible) = @_; - return unless $visible; - return if $self->{value}; - $self->set_value ("intro"); - 0 + $table->add (2, 0, new CFPlus::UI::Button + text => "Next", + tooltip => "Show the next Tip of the day.", + on_activate => sub { + $close->(); + &show_tip_of_the_day; }, ); - $win + $dialog->show; } sub sdl_init { @@ -1599,8 +1639,6 @@ force_y => 0; $DEBUG_STATUS->show; - $BIND_EDITOR = new CFPlus::BindingEditor (x => "max", y => 0); - $STATUSBOX = new CFPlus::UI::Statusbox; $STATUSBOX->add ("Use Alt-Enter to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]); @@ -1611,7 +1649,7 @@ child => $STATUSBOX, )->show; - CFPlus::UI::FancyFrame->new ( + CFPlus::UI::Toplevel->new ( title => "Map", name => "mapmap", x => 0, @@ -1649,7 +1687,7 @@ tooltip => "Server Log. This text viewer contains all the messages sent by the server.", ; - $SETUP_DIALOG = new CFPlus::UI::FancyFrame + $SETUP_DIALOG = new CFPlus::UI::Toplevel title => "Setup", name => "setup_dialog", x => 'center', @@ -1710,6 +1748,7 @@ $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window, tooltip => "View Documentation"); + $BUTTONBAR->add (new CFPlus::UI::Button text => "Quit", tooltip => "Terminates the program", @@ -1730,6 +1769,124 @@ $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]); } +sub setup_build_button { + my ($enabled) = @_; + if ($enabled) { + $BUILD_BUTTON ||= new CFPlus::UI::Button + text => "Build", + tooltip => "Opens the ingame builder", + on_activate => sub { + if ($CONN) { + $CONN->send_ext_req (builder_player_items => sub { + open_ingame_editor ($_[0]) if exists $_[0]->{items}; + }); + } + 0 + }; + $BUTTONBAR->add ($BUILD_BUTTON); + } else { + $BUILD_BUTTON->hide if $BUILD_BUTTON; + } +} + +sub open_ingame_editor { + my ($msg) = @_; + + my $win = new CFPlus::UI::Toplevel + x => 0, + y => 'center', + z => 4, + name => 'builder_window', + force_w => int $WIDTH * 1/4, + force_h => int $HEIGHT * 3/4, + title => "In game builder", + has_close_button => 1; + + my $r = new CFPlus::UI::ScrolledWindow ( + expand => 1, + scroll_y => 1 + ); + $r->add (my $vb = new CFPlus::UI::VBox); + $win->add ($r); + + + $vb->add ( + new CFPlus::UI::Button + text => "Disable build mode", + on_activate => sub { $::IN_BUILD_MODE = undef } + ); + $vb->add ( + new CFPlus::UI::Button + text => "ERASE", + on_activate => sub { $::IN_BUILD_MODE = { do_erase => 1 } } + ); + + for my $itemarchname ( + sort { + $msg->{items}->{$a}->{build_arch_name} + cmp $msg->{items}->{$b}->{build_arch_name} + } keys %{$msg->{items}} + ) { + my $info = $msg->{items}->{$itemarchname}; + $vb->add ( + new CFPlus::UI::Button text => $info->{build_arch_name}, + on_activate => sub { + $::IN_BUILD_MODE = { item => $itemarchname, info => $info }; + + if (grep { $msg->{items}->{$itemarchname}->{$_} } qw/has_connection has_name has_text/) { + build_mode_query_arch_info (); + } + } + ); + } + + $win->show; +} + +sub build_mode_query_arch_info { + my ($iteminfo) = $::IN_BUILD_MODE; + my $itemarchname = $iteminfo->{item}; + my $info = $iteminfo->{info}; + + my $dialog = new CFPlus::UI::Toplevel + x => "center", + y => "center", + z => 50, + force_w => int $WIDTH * 1/2, + title => "Enter information for placement of '$itemarchname'", + has_close_button => 1; + + $dialog->add (my $vb = new CFPlus::UI::VBox expand => 1); + + $vb->add (my $table = new CFPlus::UI::Table expand => 1); + my $row = 0; + if ($info->{has_name}) { + $table->add (0, $row, new CFPlus::UI::Label text => "Name:"); + $table->add (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{name} = $_[1]; 0 }); + } + if ($info->{has_text}) { + $table->add (0, $row, new CFPlus::UI::Label text => "Text:"); + $table->add (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{text} = $_[1]; 0 }); + } + if ($info->{has_connection}) { + $table->add (0, $row, new CFPlus::UI::Label text => "Connection ID:"); + $table->add (1, $row++, + new CFPlus::UI::Entry + expand => 1, + on_changed => sub { $::IN_BUILD_MODE->{connection} = $_[1]; 0 }, + tooltip => "Enter the connection ID here. The connection ID connects actors like a lever to a gate or a magic ear to a gate" + ); + } + + $vb->add (my $hb = new CFPlus::UI::HBox expand => 1); + $hb->add (new CFPlus::UI::Button + text => "Close", + expand => 1, + on_activate => sub { $dialog->hide; 0 }, + ); + $dialog->show; +} + sub video_shutdown { CFPlus::OpenGL::shutdown; @@ -1770,6 +1927,7 @@ audio_music_finished; + local $_; while (<$fh>) { next if /^\s*#/; next if /^\s*$/; @@ -1864,7 +2022,11 @@ CFPlus::UI::full_refresh; }, CFPlus::SDL_ACTIVEEVENT => sub { -# printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d# +# not useful, as APPACTIVE include sonly iconified state, not unmapped +# printf "active %x %x\n", $_[0]{gain}, $_[0]{state};#d# +# printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE; +# printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS; +# printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS; }, CFPlus::SDL_KEYDOWN => sub { if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) { @@ -1913,7 +2075,7 @@ gauge_size => 0.35, stat_fontsize => 0.7, mapsize => 100, - say_command => 'say', + say_command => 'chat', audio_enable => 1, bgm_enable => 1, bgm_volume => 0.25, @@ -1923,6 +2085,7 @@ pickup => 0, inv_sort => "mtime", default => "profile", # default profile + show_tips => 1, ); while (my ($k, $v) = each %DEF_CFG) { @@ -1930,6 +2093,19 @@ } $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de"; + $PROFILE = $CFG->{profile}{default}; + + # convert old bindings (only default profile matters) + if (my $bindings = delete $PROFILE->{bindings}) { + while (my ($mod, $syms) = each %$bindings) { + while (my ($sym, $cmds) = each %$syms) { + push @{ $PROFILE->{macro} }, { + accelkey => [$mod*1, $sym*1], + action => $cmds, + }; + } + } + } sdl_init; @@ -1976,10 +2152,14 @@ # warn $t2-$t1; # } + $startup_done->(); + video_init; audio_init; } +show_tip_of_the_day if $CFG->{show_tips}; + Event::loop; #CFPlus::SDL_Quit; #CFPlus::_exit 0;